Basic Integration

This document provides an overview and integration guide for our smart contract, available as an npm package. You can install it with:

npm install @selfxyz/contracts

V2 Integration

Package Structure

The V2 package supports multiple document types with enhanced verification architecture:

.
├── abstract
 └── SelfVerificationRoot.sol # Base impl in self verification V2
├── constants
 ├── AttestationId.sol # Unique identifiers for identity documents (E_PASSPORT, EU_ID_CARD)
 └── CircuitConstantsV2.sol # V2 indices for public signals in our circuits
├── interfaces # Interfaces for V2 contracts
 ├── IDscCircuitVerifier.sol
 ├── IIdentityRegistryV1.sol
 ├── IIdentityRegistryIdCardV1.sol # New: EU ID Card registry interface
 ├── IIdentityVerificationHubV2.sol # V2 hub interface
 ├── IRegisterCircuitVerifier.sol
 ├── ISelfVerificationRoot.sol
 └── IVcAndDiscloseCircuitVerifier.sol
├── libraries
 ├── SelfStructs.sol # V2 data structures for verification
 ├── CustomVerifier.sol # Custom verification logic for different document types
 ├── CircuitAttributeHandlerV2.sol # V2 attribute extraction
 ├── GenericFormatter.sol # V2 output formatting
 └── Formatter.sol # Utility functions (maintained for compatibility)
└── example
  ├── HappyBirthday.sol # Updated V2 example supporting both passports and EU ID cards
  ├── Airdrop.sol # V2 airdrop example
  └── SelfIdentityERC721.sol # NFT example with identity verification

Step-by-Step Integration Guide

Step 1: Install Dependencies

npm install @selfxyz/contracts @openzeppelin/contracts

Step 2: Create Your Contract

Extend SelfVerificationRoot and implement the required methods:

import {SelfVerificationRoot} from "@selfxyz/contracts/contracts/abstract/SelfVerificationRoot.sol";
import {ISelfVerificationRoot} from "@selfxyz/contracts/contracts/interfaces/ISelfVerificationRoot.sol";
import {IIdentityVerificationHubV2} from "@selfxyz/contracts/contracts/interfaces/IIdentityVerificationHubV2.sol";
import {SelfStructs} from "@selfxyz/contracts/contracts/libraries/SelfStructs.sol";
import {AttestationId} from "@selfxyz/contracts/contracts/constants/AttestationId.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";

contract ExampleV2 is SelfVerificationRoot, Ownable {
    // Your app-specific configuration ID
    bytes32 public configId;
    
    constructor(
        address _identityVerificationHubV2, // V2 Hub address
        uint256 _scope // Application-specific scope identifier
    ) 
        SelfVerificationRoot(_identityVerificationHubV2, _scope)
        Ownable(msg.sender)
    {
        // Initialize with empty configId - set it up in Step 2
    }

    // Required: Override to provide configId for verification
    function getConfigId(
        bytes32 destinationChainId,
        bytes32 userIdentifier, 
        bytes memory userDefinedData // Custom data from the qr code configuration
    ) public view override returns (bytes32) {
        // Return your app's configuration ID
        return configId;
    }

    // Override to handle successful verification
    function customVerificationHook(
        ISelfVerificationRoot.GenericDiscloseOutputV2 memory output,
        bytes memory userData
    ) internal virtual override {
        // Your custom business logic here
        // Example: Store verified user data, mint NFT, transfer tokens, etc.
        
        // Access verified data:
        // output.userIdentifier - user's unique identifier
        // output.name - verified name
        // output.nationality - verified nationality
        // output.dateOfBirth - verified birth date
        // etc.
        
        // Example: Simple verification check
        require(bytes(output.nationality).length > 0, "Nationality required");
    }
}

For more details on the server-side architecture of Self, please refer to the detailed architecture documentation.

Step 3: Generate Configuration ID

Use the Self Configuration Tools to easily create your verification configuration and generate a config ID. This tool allows you to configure age requirements, country restrictions, and OFAC checks with a user-friendly interface.

Once you have your config ID from the tool, you can use it in your contract in several ways:

Option 1: Hard-coded (simple approach)

function getConfigId(
    bytes32 destinationChainId,
    bytes32 userIdentifier, 
    bytes memory userDefinedData
) public view override returns (bytes32) {
    // Replace with your actual config ID from the tool
    return 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef;
}

Option 2: Store and update via setter

bytes32 public configId;

function setConfigId(bytes32 _configId) external onlyOwner {
    configId = _configId;
}

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

Option 3: Dynamic based on user context

mapping(bytes32 => bytes32) public userConfigIds;

function getConfigId(
    bytes32 destinationChainId,
    bytes32 userIdentifier, 
    bytes memory userDefinedData
) public view override returns (bytes32) {
    // Use different config IDs based on user or context
    bytes32 userSpecificConfig = userConfigIds[userIdentifier];
    return userSpecificConfig != bytes32(0) ? userSpecificConfig : configId;
}

Step 4: Deploy Your Contract

Deploy with the V2 Hub address:

  • Celo Mainnet: 0xe57F4773bd9c9d8b6Cd70431117d353298B9f5BF

  • Celo Testnet: 0x68c931C9a534D37aa78094877F46fE46a49F1A51

Step 5: Set Up Scope

Your contract needs a proper scope for verification. You have two approaches:

Option 1: Predict address with CREATE2 (advanced)

// Calculate scope before deployment using predicted address
// Use tools.self.xyz to calculate scope with your predicted contract address

Option 2: Update scope after deployment (easier)

uint256 public scope;

function setScope(uint256 _scope) external onlyOwner {
    scope = _scope;
    // Update the scope in the parent contract
    _setScope(_scope);
}

After deployment, use the Self Configuration Tools to calculate the actual scope with your deployed contract address and update it using the setter function.

Step 6: Configure Frontend SDK

Set up your frontend with the deployed contract address (see Frontend Configuration).

Next Steps

Last updated