Skip to content

Start typing to search the documentation.

Basic Integration

Overview

The @selfxyz/contracts SDK provides you with a SelfVerificationRoot abstract contract that wires your contract to the Identity Verification Hub V2. Your contract receives a callback with disclosed, verified attributes only after the proof succeeds.

Key flow

  1. Your contract exposes verifySelfProof(bytes proofPayload, bytes userContextData) from the abstract contract.
  2. It takes a verification config from your contract and forwards a packed input to Hub V2.
  3. If the proof is valid, the Hub calls back your contract’s onVerificationSuccess(bytes output, bytes userData) .
  4. You implement custom logic in customVerificationHook(...).

SelfVerificationRoot

This is an abstract contract that you must override by providing custom logic for returning a config id along with a hook that is called with the disclosed attributes. Here’s what you need to override:

1. getConfigId

function getConfigId(
    bytes32 destinationChainId,
    bytes32 userIdentifier,
    bytes memory userDefinedData
) public view virtual override returns (bytes32)

Return the verification config ID that the hub should enforce for this request. In simple cases, you may store a single config ID in storage and return it. In advanced cases, compute a dynamic config id based on the inputs.

Example (static config):

bytes32 public verificationConfigId;

function getConfigId(
    bytes32, bytes32, bytes memory
) public view override returns (bytes32) {
    return verificationConfigId;
}

2. customVerificationHook

function customVerificationHook(
    ISelfVerificationRoot.GenericDiscloseOutputV2 memory output,
    bytes memory userData
) internal virtual override

This is called after hub verification succeeds. Use it to:

  • Mark the user as verified
  • Mint/allowlist/gate features
  • Emit events or write your own structs

Constructor & Scope

constructor(
    address hubV2,
    string memory scopeSeed
) SelfVerificationRoot(hubV2, scopeSeed) {}

SelfVerificationRoot computes a scope at deploy time:

  • It Poseidon‑hashes the contract address (chunked) with your scopeSeed to produce a unique uint256 scope.
  • The hub enforces that submitted proofs match this scope.

Why scope matters:

  • Prevents cross‑contract proof replay.
  • Allow anonymity between different applications as the nullifier is calculated as a function of the scope.

Guidelines

  • Keep scopeSeed short (≤31 ASCII bytes). Example: "proof-of-human".
  • Changing contract address changes the scope (by design). Re‑deploys will need a fresh frontend config.
  • You can read the current scope on‑chain via function scope() public view returns (uint256).

Setting Verification Configs

A verification config is simply what you want to verify your user against. Your contract must reference a verification config that the hub recognizes. Typical steps:

  1. Format and register the config off‑chain or in a setup contract:
SelfStructs.VerificationConfigV2 public verificationConfig;
bytes32 public verificationConfigId;

constructor(
    address hubV2,
    string memory scopeSeed,
    SelfUtils.UnformattedVerificationConfigV2 memory rawCfg
) SelfVerificationRoot(hubV2, scopeSeed) {
    // 1) Format the human‑readable struct into the on‑chain wire format
    verificationConfig = SelfUtils.formatVerificationConfigV2(rawCfg);

    // 2) Register the config in the Hub. **This call RETURNS the configId.**
    verificationConfigId = IIdentityVerificationHubV2(hubV2).setVerificationConfigV2(verificationConfig);
}
  1. Return the config id from getConfigId(...) (static or dynamic):
function getConfigId(
    bytes32,
    bytes32,
    bytes memory
) public view override returns (bytes32) {
    return verificationConfigId;
}

Here’s how you would create a raw config:

import { SelfUtils } from "@selfxyz/contracts/contracts/libraries/SelfUtils.sol";

// Inside your contract constructor or setup function:
string[] memory forbiddenCountries = new string[](1);
forbiddenCountries[0] = CountryCodes.UNITED_STATES; // ISO 3-letter codes, max 40 countries

SelfUtils.UnformattedVerificationConfigV2 memory verificationConfig = SelfUtils
    .UnformattedVerificationConfigV2({
        olderThan: 18,              // Minimum age (0 = no age check)
        forbiddenCountries: forbiddenCountries, // Countries to block (empty array = no restriction)
        ofacEnabled: false          // Enable OFAC sanctions screening
    });

Frontend ↔ Contract config must match

Common pitfalls:

  • Frontend uses minimumAge: 18 but contract config expects 21 .
  • Frontend uses different scope (e.g., points to a different contract address or uses a different scopeSeed).

Extracting data from a users proof

The customVerificationHook receives a GenericDiscloseOutputV2 struct with all verified attributes. Here are all available fields:

FieldTypeDescriptionRequires Disclosure
attestationIdbytes32Document type: 1 = Passport, 2 = EU ID Card, 3 = Aadhaar, 4 = KYCNo
userIdentifieruint256User’s identifier. Derive address: address(uint160(output.userIdentifier))No
nullifieruint256Unique per-user per-scope value for Sybil resistanceNo
forbiddenCountriesListPackeduint256[4]Packed bitfield of excluded countriesNo
olderThanuint256Minimum age verified (e.g. 18). 0 if not checkedNo
ofacbool[3]OFAC check results: [0] = passport number, [1] = name+DOB, [2] = name+YOBNo
issuingStatestringISO 3-letter code of issuing country (e.g. "GBR")Yes
namestring[]User’s name fields from documentYes
idNumberstringDocument number (passport number, ID number, etc.)Yes
nationalitystringISO 3-letter nationality codeYes
dateOfBirthstringDate of birth in document formatYes
genderstringGender ("M" or "F")Yes
expiryDatestringDocument expiry dateYes

“Requires Disclosure” means the field is only populated if your frontend disclosure config explicitly requests it. Fields not requested will be empty/zero.

Example — extracting nationality and age in your hook:

function customVerificationHook(
    ISelfVerificationRoot.GenericDiscloseOutputV2 memory output,
    bytes memory userData
) internal override {
    // Always available
    require(output.olderThan >= 18, "Must be 18+");

    // Only available if disclosure was requested
    string memory nationality = output.nationality; // e.g. "GBR"

    // Derive user's address
    address user = address(uint160(output.userIdentifier));

    // Check OFAC results (if enabled in config)
    // ofac[0] = passport number match, ofac[1] = name+DOB, ofac[2] = name+YOB
    // All must be false (not sanctioned) for verification to pass
}

The Happy Birthday Example contains a full working example of extracting data from the output object.

Minimal Example: Proof Of Human

// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;

import {SelfVerificationRoot} from "@selfxyz/contracts/contracts/abstract/SelfVerificationRoot.sol";
import {ISelfVerificationRoot} from "@selfxyz/contracts/contracts/interfaces/ISelfVerificationRoot.sol";
import {SelfStructs} from "@selfxyz/contracts/contracts/libraries/SelfStructs.sol";
import {SelfUtils} from "@selfxyz/contracts/contracts/libraries/SelfUtils.sol";
import {IIdentityVerificationHubV2} from "@selfxyz/contracts/contracts/interfaces/IIdentityVerificationHubV2.sol";

/**
 * @title ProofOfHuman
 * @notice Test implementation of SelfVerificationRoot for the docs
 * @dev This contract provides a concrete implementation of the abstract SelfVerificationRoot
 */
contract ProofOfHuman is SelfVerificationRoot {
    // Storage for testing purposes
    SelfStructs.VerificationConfigV2 public verificationConfig;
    bytes32 public verificationConfigId;

    // Events for testing
    event VerificationCompleted(
        ISelfVerificationRoot.GenericDiscloseOutputV2 output,
        bytes userData
    );

    /**
     * @notice Constructor for the test contract
     * @param identityVerificationHubV2Address The address of the Identity Verification Hub V2
     */
    constructor(
        address identityVerificationHubV2Address, // Hub V2 address — see Deployed Contracts page
        string memory scopeSeed,                  // Unique app identifier (≤31 ASCII bytes), hashed into scope
        SelfUtils.UnformattedVerificationConfigV2 memory _verificationConfig // What to verify (age, countries, OFAC)
    ) SelfVerificationRoot(identityVerificationHubV2Address, scopeSeed) {
        verificationConfig =
            SelfUtils.formatVerificationConfigV2(_verificationConfig);
        verificationConfigId =
            IIdentityVerificationHubV2(identityVerificationHubV2Address)
            .setVerificationConfigV2(verificationConfig);
    }

    /**
     * @notice Implementation of customVerificationHook for testing
     * @dev This function is called by onVerificationSuccess after hub address validation
     * @param output The verification output from the hub
     * @param userData The user data passed through verification
     */
    function customVerificationHook(
        ISelfVerificationRoot.GenericDiscloseOutputV2 memory output,
        bytes memory userData
    ) internal override {
        emit VerificationCompleted(output, userData);
    }

    function getConfigId(
        bytes32 /* destinationChainId */,
        bytes32 /* userIdentifier */,
        bytes memory /* userDefinedData */
    ) public view override returns (bytes32) {
        return verificationConfigId;
    }
}