Skip to content
Skip the search — install the Self skill Let your AI agent integrate Self for you.

Start typing to search the documentation.

V1 to V2 Migration Guide

This guide helps you migrate from Self Protocol V1 to V2. V2 introduces multi-document support, dynamic configuration, and improved data structures.

Overview of Changes

What’s New in V2

  • Multi-document support: E-Passports, EU ID Cards, Aadhaar, and KYC
  • Dynamic configuration: Switch configurations without redeployment
  • Enhanced data formats: Pre-extracted, human-readable outputs
  • User context data: Pass custom data through verification flow
  • Improved error handling: Detailed configuration mismatch reporting

Breaking Changes

  • Backend SDK constructor requires new parameters
  • Configuration methods replaced with interface
  • Verification method signature changed
  • Frontend requires disclosures object
  • Smart contract interfaces updated

Backend Migration

1. Update Dependencies

npm install @selfxyz/core@latest

2. Update Constructor

V1 (Old):

const verifier = new SelfBackendVerifier(
  "my-app-scope",
  "https://api.example.com/verify",
  false  // mock mode
);

V2 (New):

import { SelfBackendVerifier, AttestationId, UserIdType, IConfigStorage, AllIds } from '@selfxyz/core';

// Option 1: Use AllIds for all document types (recommended for most cases)
const allowedIds = AllIds;

// Option 2: Define specific allowed document types
// const allowedIds = new Map();
// allowedIds.set(AttestationId.E_PASSPORT, true);  // Accept passports
// allowedIds.set(AttestationId.EU_ID_CARD, true);  // Accept EU ID cards
// allowedIds.set(AttestationId.AADHAAR, true);  // Accept Aadhaar
// allowedIds.set(AttestationId.KYC, true);  // Accept KYC

// Implement configuration storage
class ConfigStorage implements IConfigStorage {
  async getConfig(configId: string) {
    // Return your verification requirements
    return {
      minimumAge: 18,
      excludedCountries: ['IRN', 'PRK'],
      ofac: true
    };
  }

  async getActionId(userIdentifier: string, userDefinedData?: string) {
    // Return config ID based on your logic
    return 'default_config';
  }
}

const verifier = new SelfBackendVerifier(
  "my-app-scope",
  "https://api.example.com/verify",
  false,                        // mock mode
  allowedIds,                   // NEW: allowed document types
  new ConfigStorage(),          // NEW: config storage
  UserIdType.UUID               // NEW: user ID type
);

3. Update Configuration

V1 (Old):

// Direct method calls
verifier.setMinimumAge(18);
verifier.excludeCountries('Iran', 'North Korea');
verifier.enablePassportNoOfacCheck();
verifier.enableNameOfacCheck();
verifier.enableDobOfacCheck();

V2 (New):

// Configuration via IConfigStorage implementation
class ConfigStorage implements IConfigStorage {
  async getConfig(configId: string) {
    // All configuration in one place
    return {
      minimumAge: 18,
      excludedCountries: ['IRN', 'PRK'],  // Use ISO 3-letter codes
      ofac: true  // Single boolean for all OFAC checks
    };
  }
}

4. Update Verification Method

V1 (Old):

app.post('/api/verify', async (req, res) => {
  const { proof, publicSignals } = req.body;

  try {
    const isValid = await verifier.verify(proof, publicSignals);
    res.json({ valid: isValid });
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
});

V2 (New):

app.post('/api/verify', async (req, res) => {
  const { attestationId, proof, pubSignals, userContextData } = req.body;

  try {
    const result = await verifier.verify(
      attestationId,      // NEW: 1 passport, 2 EU ID, 3 Aadhaar, 4 KYC
      proof,
      pubSignals,
      userContextData     // NEW: hex-encoded context data
    );

    if (result.isValidDetails.isValid) {
      res.json({
        status: 'success',
        result: true,
        credentialSubject: result.discloseOutput,
        documentType:
          attestationId === 1 ? 'passport' :
          attestationId === 2 ? 'eu_id_card' :
          attestationId === 3 ? 'aadhaar' :
          'kyc'
      });
    } else {
      res.status(400).json({
        status: 'error',
        result: false,
        details: result.isValidDetails
      });
    }
  } catch (error) {
    if (error.name === 'ConfigMismatchError') {
      res.status(400).json({
        status: 'error',
        message: 'Configuration mismatch',
        issues: error.issues
      });
    } else {
      res.status(500).json({ error: error.message });
    }
  }
});

5. Handle New Response Format

V1 Response:

// Simple boolean
true/false

V2 Response:

{
  attestationId: 1,              // Document type
  isValidDetails: {
    isValid: boolean,            // Overall result
    isOlderThanValid: boolean,   // Age check result
    isOfacValid: boolean         // OFAC check result
  },
  forbiddenCountriesList: [],    // Excluded countries
  discloseOutput: {              // Pre-extracted data
    nationality: "USA",
    minimumAge: "21",
    name: ["JOHN", "DOE"],
    dateOfBirth: "01-01-1990",
    issuingState: "USA",
    idNumber: "123456789",
    gender: "M",
    expiryDate: "01-01-2030",
    ofac: [true, true, true]
  },
  userData: {
    userIdentifier: "uuid-here",
    userDefinedData: "custom-data"
  }
}

Frontend Migration

1. Update Dependencies

npm install @selfxyz/qrcode@latest

2. Update QR Code Configuration

V1 (Old):

import { SelfAppBuilder } from '@selfxyz/qrcode';

const selfApp = new SelfAppBuilder({
  appName: "My App",
  scope: "my-app-scope",
  endpoint: "https://api.example.com/verify",
  userId: userId,
  logoBase64: logo
}).build();

V2 (New):

import { SelfAppBuilder } from '@selfxyz/qrcode';

const selfApp = new SelfAppBuilder({
  appName: "My App",
  scope: "my-app-scope",
  endpoint: "https://api.example.com/verify",
  userId: userId,
  logoBase64: logo,
  version: 2,                          // NEW: Specify V2
  userDefinedData: "custom-data",      // NEW: Optional custom data
  disclosures: {                       // NEW: Must match backend config
    // Verification rules
    minimumAge: 18,
    excludedCountries: ['IRN', 'PRK'],
    ofac: true,
    // Data fields to reveal
    name: true,
    nationality: true,
    dateOfBirth: true
  }
}).build();

3. Important: Disclosures Object

The disclosures object in V2 contains both verification rules and data fields:

disclosures: {
  // Verification rules (must match backend exactly)
  minimumAge: 18,                      // Age requirement
  excludedCountries: ['IRN', 'PRK'],   // ISO 3-letter codes
  ofac: true,                          // OFAC checking

  // Data fields to reveal
  name: true,                          // Full name
  nationality: true,                   // Nationality
  dateOfBirth: true,                   // Date of birth
  issuingState: true,                  // Issuing country
  idNumber: true,                      // Document number
  gender: true,                        // Gender
  expiryDate: true                     // Expiration date
}

Smart Contract Migration

1. Update Contract Inheritance

V1 (Old):

import { IIdentityVerificationHub } from "@selfxyz/contracts/interfaces/IIdentityVerificationHub.sol";

contract MyContract {
    IIdentityVerificationHub public hub;

    function verify(/* params */) external {
        // Direct hub integration
    }
}

V2 (New):

import { SelfVerificationRoot } from "@selfxyz/contracts/abstract/SelfVerificationRoot.sol";

contract MyContract is SelfVerificationRoot {
    constructor(
        address _hub,
        bytes32 _scope
    ) SelfVerificationRoot(_hub, _scope) {}

    // Override to handle verification results
    function customVerificationHook(
        GenericDiscloseOutputV2 memory output,
        bytes memory userData
    ) internal override {
        // Your business logic here
    }

    // Override to provide configuration ID
    function getConfigId(
        bytes32 destinationChainId,
        bytes32 userIdentifier,
        bytes memory userDefinedData
    ) public view override returns (bytes32) {
        // Generate your config ID at https://tools.self.xyz/
        // Default config ID: 0x7b6436b0c98f62380866d9432c2af0ee08ce16a171bda6951aecd95ee1307d61
        return 0x7b6436b0c98f62380866d9432c2af0ee08ce16a171bda6951aecd95ee1307d61;
    }
}

2. Update Hub Addresses

V2 Hub Addresses:

// Celo Mainnet
address constant HUB_V2 = 0xe57F4773bd9c9d8b6Cd70431117d353298B9f5BF;

// Celo Sepolia Testnet
address constant HUB_V2_STAGING = 0x16ECBA51e18a4a7e61fdC417f0d47AFEeDfbed74;

3. Handle New Data Structure

V1 Structure:

struct VcAndDiscloseVerificationResult {
    uint256 attestationId;
    uint256 scope;
    uint256 userIdentifier;
    uint256 nullifier;
    uint256[3] revealedDataPacked;   // Packed data
}

V2 Structure:

struct GenericDiscloseOutputV2 {
    bytes32 attestationId;           // Now bytes32
    uint256 userIdentifier;
    uint256 nullifier;
    string issuingState;             // Pre-extracted
    string[] name;                   // Pre-extracted array
    string idNumber;                 // Renamed from passportNumber
    string nationality;              // Pre-extracted
    string dateOfBirth;              // Pre-extracted format
    string gender;                   // Pre-extracted
    string expiryDate;               // Pre-extracted
    uint256 minimumAge;
    bool[3] ofac;                    // Bool array
    uint256[4] forbiddenCountriesListPacked;
}

Common Migration Issues

1. Configuration Mismatch

Problem: Frontend disclosures don’t match backend configuration

ConfigMismatchError: Configuration mismatch

Solution: Ensure frontend and backend have identical settings:

// Frontend
disclosures: {
  minimumAge: 18,
  excludedCountries: ['IRN', 'PRK'],
  ofac: true
}

// Backend (in getConfig)
return {
  minimumAge: 18,
  excludedCountries: ['IRN', 'PRK'],
  ofac: true
};

2. Missing Attestation ID

Problem: Verification fails with missing attestation ID

Solution: Frontend must send attestation ID:

const requestBody = {
  attestationId: 1,  // 1 passport, 2 EU ID, 3 Aadhaar, 4 KYC
  proof: proof,
  pubSignals: pubSignals,
  userContextData: userContextData
};

3. Invalid User Context Data

Problem: User context data validation fails

Solution: Ensure proper hex encoding:

// Create user context data (256 bytes total)
const userContextData = '0x' + '0'.repeat(512);  // 512 hex chars = 256 bytes

4. Document Type Not Allowed

Problem: “Attestation ID is not allowed” error

Solution: Add document type to allowedIds:

// Option 1: Use AllIds for all document types
const allowedIds = AllIds;

// Option 2: Define specific allowed document types
// const allowedIds = new Map();
// allowedIds.set(AttestationId.E_PASSPORT, true);  // Add passport
// allowedIds.set(AttestationId.EU_ID_CARD, true);  // Add EU ID card
// allowedIds.set(AttestationId.AADHAAR, true);  // Add Aadhaar
// allowedIds.set(AttestationId.KYC, true);  // Add KYC

Testing Your Migration

1. Test with Mock Passports

// Use staging/testnet for development
const verifier = new SelfBackendVerifier(
  "test-scope",
  "https://test.ngrok.app/verify",
  true,  // Enable mock mode
  allowedIds,
  configStorage,
  UserIdType.UUID
);

// Disable OFAC for mock passports
class ConfigStorage {
  async getConfig() {
    return {
      minimumAge: 18,
      ofac: false  // Must be false for mock passports
    };
  }
}

2. Test Multiple Document Types

// Test passport verification
const passportResult = await verifier.verify(
  AttestationId.E_PASSPORT,  // 1
  passportProof,
  passportSignals,
  userContextData
);

// Test EU ID card verification
const idCardResult = await verifier.verify(
  AttestationId.EU_ID_CARD,  // 2
  idCardProof,
  idCardSignals,
  userContextData
);

// Test KYC verification
const kycResult = await verifier.verify(
  AttestationId.KYC,  // 4
  kycProof,
  kycSignals,
  userContextData
);

3. Verify Configuration Switching

class DynamicConfigStorage {
  async getConfig(configId: string) {
    switch(configId) {
      case 'strict':
        return { minimumAge: 21, ofac: true };
      case 'relaxed':
        return { minimumAge: 18, ofac: false };
      default:
        return { minimumAge: 18, ofac: true };
    }
  }

  async getActionId(userIdentifier: string, userDefinedData?: string) {
    // Select config based on user data
    return userDefinedData === 'premium' ? 'strict' : 'relaxed';
  }
}

Best Practices

1. Configuration Management

  • Store configurations in a database for easy updates
  • Version your configurations for rollback capability
  • Use meaningful config IDs (not just hashes)
  • Document configuration requirements

2. Error Handling

try {
  const result = await verifier.verify(/* params */);
} catch (error) {
  if (error.name === 'ConfigMismatchError') {
    // Log detailed issues for debugging
    console.error('Config issues:', error.issues);
    // Return user-friendly message
    return { error: 'Verification configuration error' };
  }
  // Handle other errors
}

3. Security Considerations

  • Always validate attestation IDs
  • Store and check nullifiers to prevent replay
  • Use appropriate scopes for different use cases
  • Never expose configuration details to frontend

4. Performance Optimization

  • Cache configuration objects
  • Reuse verifier instances
  • Batch verification requests when possible
  • Use connection pooling for RPC calls

Resources

Need Help?

If you encounter issues during migration:

  1. Review example implementations
  2. Report issues at GitHub Issues
Was this page helpful?