Basic Integration

This page explains how to integrate your smart contracts with Self’s on‑chain verification flow using the abstract base SelfVerificationRoot.

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):

2. customVerificationHook

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

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).

You can get the hub addresses from Deployed Contracts

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:

  1. Return the config id from getConfigId(...) (static or dynamic):

Here's how you would create a raw config:

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

Various data fields can be extracted from the user's disclosure proof.

  • attestationId, userIdentifier, nullifier, forbiddenCountriesListPacked, olderThan, ofac can be extracted normally.

  • issuingState, name, idNumber, nationality, dateOfBirth, gender, expiryDate can be extracted only if the app requests the user to disclose this information.

  • User's address can be derived from userIdentifier with address(uint160(output.userIdentifier))

The Happy Birthday Example contains a working example of how to extract data from the output object.

Minimal Example: Proof Of Human

Last updated