For the complete documentation index, see llms.txt. This page is also available as Markdown.

Smart Contracts

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

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

Field
Type
Description
Requires Disclosure

attestationId

bytes32

Document type: 1 = Passport, 2 = EU ID Card, 3 = Aadhaar, 4 = KYC

No

userIdentifier

uint256

User's identifier. Derive address: address(uint160(output.userIdentifier))

No

nullifier

uint256

Unique per-user per-scope value for Sybil resistance

No

forbiddenCountriesListPacked

uint256[4]

Packed bitfield of excluded countries

No

olderThan

uint256

Minimum age verified (e.g. 18). 0 if not checked

No

ofac

bool[3]

OFAC check results: [0] = passport number, [1] = name+DOB, [2] = name+YOB

No

issuingState

string

ISO 3-letter code of issuing country (e.g. "GBR")

Yes

name

string[]

User's name fields from document

Yes

idNumber

string

Document number (passport number, ID number, etc.)

Yes

nationality

string

ISO 3-letter nationality code

Yes

dateOfBirth

string

Date of birth in document format

Yes

gender

string

Gender ("M" or "F")

Yes

expiryDate

string

Document expiry date

Yes

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

The format of disclosed fields can vary by document type. Passports use ICAO MRZ format, Aadhaar uses different date and name formats, and KYC fields depend on the provider. See the Document Specifications section for per-document-type details.

Example — extracting nationality and age in your hook:

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

Minimal Example: Proof Of Human

Last updated