Basic Integration
This page explains how to integrate your smart contracts with Self’s on‑chain verification flow using the abstract base SelfVerificationRoot.
Troubleshooting Celo Sepolia: If you encounter a Chain 11142220 not supported error when deploying to Celo Sepolia, try to update Foundry to version 0.3.0:
foundryup --install 0.3.0Overview
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
Your contract exposes
verifySelfProof(bytes proofPayload, bytes userContextData)from the abstract contract.It takes a verification config from your contract and forwards a packed input to Hub V2.
If the proof is valid, the Hub calls back your contract’s
onVerificationSuccess(bytes output, bytes userData).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
getConfigIdfunction 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
customVerificationHookThis 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
scopeSeedto produce a uniqueuint256scope.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
scopeSeedshort (≤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:
Format and register the config off‑chain or in a setup contract:
Return the config id from
getConfigId(...)(static or dynamic):
Here's how you would create a raw config:
Only a maximum of 40 countries are allowed!
Frontend ↔ Contract config must match
The frontend disclosure/verification config used to produce the proof must exactly match the contract’s verification config (the configId you return). Otherwise the hub will detect a mismatch and verification fails.
Common pitfalls:
Frontend uses
minimumAge: 18but contract config expects21.Frontend uses different scope (e.g., points to a different contract address or uses a different
scopeSeed).
Best practice: Generate the config once, register it with the hub to get configId, and reference that same id in your dApp’s builder payload.
Extracting data from a users proof
Various data fields can be extracted from the user's disclosure proof.
attestationId,userIdentifier,nullifier,forbiddenCountriesListPacked,olderThan,ofaccan be extracted normally.issuingState,name,idNumber,nationality,dateOfBirth,gender,expiryDatecan 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