Skip to main content

Overview

This guide provides comprehensive examples for integrating with the ZKScore Identity SBT contract. It covers Web3 integration, error handling, best practices, and real-world use cases.
Always test your integration on testnet before deploying to mainnet. Use the testnet contract addresses provided in the Contract Overview section.

Web3 Integration

Basic Setup

import { ethers } from 'ethers';

// Contract configuration
const CONTRACT_ADDRESS = '0x1234567890123456789012345678901234567890';
const CONTRACT_ABI = [
  // ... ABI definitions
];

// Initialize provider and contract
const provider = new ethers.providers.JsonRpcProvider('https://mainnet.infura.io/v3/YOUR_KEY');
const contract = new ethers.Contract(CONTRACT_ADDRESS, CONTRACT_ABI, provider);

// Initialize with signer for transactions
const signer = provider.getSigner();
const contractWithSigner = contract.connect(signer);

Contract Interaction Class

class IdentitySBTIntegration {
  constructor(provider, contractAddress, abi) {
    this.provider = provider;
    this.contract = new ethers.Contract(contractAddress, abi, provider);
    this.contractWithSigner = null;
  }
  
  // Set signer for transactions
  setSigner(signer) {
    this.contractWithSigner = this.contract.connect(signer);
  }
  
  // Mint new identity
  async mintIdentity(to, name, metadataURI) {
    if (!this.contractWithSigner) {
      throw new Error('Signer not set');
    }
    
    try {
      const tx = await this.contractWithSigner.mint(to, name, metadataURI);
      const receipt = await tx.wait();
      
      // Get token ID from event
      const event = receipt.events.find(e => e.event === 'IdentityMinted');
      const tokenId = event.args.tokenId;
      
      return {
        success: true,
        tokenId: tokenId.toString(),
        transactionHash: receipt.transactionHash,
        blockNumber: receipt.blockNumber
      };
    } catch (error) {
      return {
        success: false,
        error: error.message
      };
    }
  }
  
  // Activate identity
  async activateIdentity(tokenId) {
    if (!this.contractWithSigner) {
      throw new Error('Signer not set');
    }
    
    try {
      const tx = await this.contractWithSigner.activate(tokenId);
      const receipt = await tx.wait();
      
      return {
        success: true,
        transactionHash: receipt.transactionHash,
        blockNumber: receipt.blockNumber
      };
    } catch (error) {
      return {
        success: false,
        error: error.message
      };
    }
  }
  
  // Get identity information
  async getIdentityInfo(tokenId) {
    try {
      const [owner, isActivated, tokenURI] = await Promise.all([
        this.contract.ownerOf(tokenId),
        this.contract.isActivated(tokenId),
        this.contract.tokenURI(tokenId)
      ]);
      
      return {
        tokenId: tokenId.toString(),
        owner,
        isActivated,
        tokenURI
      };
    } catch (error) {
      return {
        error: error.message
      };
    }
  }
  
  // Get user's identities
  async getUserIdentities(address) {
    try {
      const balance = await this.contract.balanceOf(address);
      const identities = [];
      
      for (let i = 0; i < balance.toNumber(); i++) {
        const tokenId = await this.contract.tokenOfOwnerByIndex(address, i);
        const info = await this.getIdentityInfo(tokenId);
        identities.push(info);
      }
      
      return identities;
    } catch (error) {
      return {
        error: error.message
      };
    }
  }
}

// Usage
const integration = new IdentitySBTIntegration(
  provider,
  '0x1234567890123456789012345678901234567890',
  IDENTITY_SBT_ABI
);

integration.setSigner(signer);

Error Handling

Comprehensive Error Handling

class IdentitySBTErrorHandler {
  static handleError(error) {
    const errorMessage = error.message || error.toString();
    
    // Common error patterns
    if (errorMessage.includes('AccessControl')) {
      return {
        type: 'PERMISSION_ERROR',
        message: 'Insufficient permissions to perform this action',
        code: 'ACCESS_DENIED'
      };
    }
    
    if (errorMessage.includes('Token is soulbound')) {
      return {
        type: 'SOULBOUND_ERROR',
        message: 'Token is soulbound and cannot be transferred',
        code: 'TOKEN_SOULBOUND'
      };
    }
    
    if (errorMessage.includes('Token does not exist')) {
      return {
        type: 'NOT_FOUND_ERROR',
        message: 'Token does not exist',
        code: 'TOKEN_NOT_FOUND'
      };
    }
    
    if (errorMessage.includes('Already activated')) {
      return {
        type: 'ALREADY_ACTIVATED_ERROR',
        message: 'Token is already activated',
        code: 'ALREADY_ACTIVATED'
      };
    }
    
    if (errorMessage.includes('Not token owner')) {
      return {
        type: 'OWNERSHIP_ERROR',
        message: 'Caller is not the owner of the token',
        code: 'NOT_OWNER'
      };
    }
    
    if (errorMessage.includes('name already exists')) {
      return {
        type: 'DUPLICATE_ERROR',
        message: 'Name is already taken',
        code: 'NAME_EXISTS'
      };
    }
    
    if (errorMessage.includes('insufficient funds')) {
      return {
        type: 'INSUFFICIENT_FUNDS_ERROR',
        message: 'Insufficient funds for transaction',
        code: 'INSUFFICIENT_FUNDS'
      };
    }
    
    if (errorMessage.includes('gas limit exceeded')) {
      return {
        type: 'GAS_ERROR',
        message: 'Transaction gas limit exceeded',
        code: 'GAS_LIMIT_EXCEEDED'
      };
    }
    
    // Default error
    return {
      type: 'UNKNOWN_ERROR',
      message: errorMessage,
      code: 'UNKNOWN'
    };
  }
  
  static async safeExecute(operation, retries = 3) {
    for (let attempt = 1; attempt <= retries; attempt++) {
      try {
        return await operation();
      } catch (error) {
        const errorInfo = this.handleError(error);
        
        if (attempt === retries) {
          throw errorInfo;
        }
        
        // Wait before retry
        await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
      }
    }
  }
}

// Usage
try {
  const result = await IdentitySBTErrorHandler.safeExecute(async () => {
    return await contract.mint(to, name, metadataURI);
  });
  console.log('Success:', result);
} catch (error) {
  console.error('Error:', error.message);
  console.error('Code:', error.code);
}

Event Integration

Event Listener Setup

class IdentityEventManager {
  constructor(contract) {
    this.contract = contract;
    this.listeners = new Map();
    this.isListening = false;
  }
  
  startListening() {
    if (this.isListening) return;
    
    this.contract.on('IdentityMinted', (to, tokenId, name, event) => {
      this.handleEvent('IdentityMinted', {
        to,
        tokenId: tokenId.toString(),
        name,
        blockNumber: event.blockNumber,
        transactionHash: event.transactionHash
      });
    });
    
    this.contract.on('IdentityActivated', (tokenId, owner, event) => {
      this.handleEvent('IdentityActivated', {
        tokenId: tokenId.toString(),
        owner,
        blockNumber: event.blockNumber,
        transactionHash: event.transactionHash
      });
    });
    
    this.contract.on('Transfer', (from, to, tokenId, event) => {
      this.handleEvent('Transfer', {
        from,
        to,
        tokenId: tokenId.toString(),
        blockNumber: event.blockNumber,
        transactionHash: event.transactionHash
      });
    });
    
    this.isListening = true;
  }
  
  stopListening() {
    this.contract.removeAllListeners();
    this.isListening = false;
  }
  
  handleEvent(eventType, data) {
    console.log(`Event: ${eventType}`, data);
    
    if (this.listeners.has(eventType)) {
      this.listeners.get(eventType).forEach(callback => {
        try {
          callback(data);
        } catch (error) {
          console.error(`Error in event listener for ${eventType}:`, error);
        }
      });
    }
  }
  
  on(eventType, callback) {
    if (!this.listeners.has(eventType)) {
      this.listeners.set(eventType, []);
    }
    this.listeners.get(eventType).push(callback);
  }
  
  off(eventType, callback) {
    if (this.listeners.has(eventType)) {
      const callbacks = this.listeners.get(eventType);
      const index = callbacks.indexOf(callback);
      if (index > -1) {
        callbacks.splice(index, 1);
      }
    }
  }
}

// Usage
const eventManager = new IdentityEventManager(contract);
eventManager.startListening();

eventManager.on('IdentityMinted', (data) => {
  console.log('New identity minted:', data);
  // Update UI, send notifications, etc.
});

eventManager.on('IdentityActivated', (data) => {
  console.log('Identity activated:', data);
  // Update UI, send notifications, etc.
});

Real-world Integration Examples

Complete Identity Management System

class IdentityManagementSystem {
  constructor(provider, contractAddress, abi) {
    this.provider = provider;
    this.contract = new ethers.Contract(contractAddress, abi, provider);
    this.contractWithSigner = null;
    this.eventManager = new IdentityEventManager(this.contract);
  }
  
  setSigner(signer) {
    this.contractWithSigner = this.contract.connect(signer);
  }
  
  // Complete identity creation flow
  async createIdentity(name, metadataURI, autoActivate = false) {
    try {
      // Step 1: Mint identity
      const mintResult = await this.mintIdentity(name, metadataURI);
      if (!mintResult.success) {
        return mintResult;
      }
      
      const tokenId = mintResult.tokenId;
      
      // Step 2: Auto-activate if requested
      if (autoActivate) {
        const activateResult = await this.activateIdentity(tokenId);
        if (!activateResult.success) {
          return {
            success: false,
            error: `Identity minted but activation failed: ${activateResult.error}`,
            tokenId
          };
        }
      }
      
      return {
        success: true,
        tokenId,
        isActivated: autoActivate,
        transactionHash: mintResult.transactionHash
      };
    } catch (error) {
      return {
        success: false,
        error: error.message
      };
    }
  }
  
  // Get comprehensive identity data
  async getIdentityData(tokenId) {
    try {
      const [owner, isActivated, tokenURI, balance] = await Promise.all([
        this.contract.ownerOf(tokenId),
        this.contract.isActivated(tokenId),
        this.contract.tokenURI(tokenId),
        this.contract.balanceOf(await this.contract.ownerOf(tokenId))
      ]);
      
      return {
        tokenId: tokenId.toString(),
        owner,
        isActivated,
        tokenURI,
        ownerBalance: balance.toString(),
        isSoulbound: isActivated
      };
    } catch (error) {
      return {
        error: error.message
      };
    }
  }
  
  // Batch operations
  async batchMintIdentities(identities) {
    const results = [];
    
    for (const identity of identities) {
      try {
        const result = await this.mintIdentity(identity.name, identity.metadataURI);
        results.push({
          name: identity.name,
          success: result.success,
          tokenId: result.tokenId,
          error: result.error
        });
      } catch (error) {
        results.push({
          name: identity.name,
          success: false,
          error: error.message
        });
      }
    }
    
    return results;
  }
  
  // Start event monitoring
  startEventMonitoring() {
    this.eventManager.startListening();
  }
  
  // Stop event monitoring
  stopEventMonitoring() {
    this.eventManager.stopListening();
  }
}

// Usage
const identitySystem = new IdentityManagementSystem(
  provider,
  '0x1234567890123456789012345678901234567890',
  IDENTITY_SBT_ABI
);

identitySystem.setSigner(signer);
identitySystem.startEventMonitoring();

// Create identity
const result = await identitySystem.createIdentity(
  'alice.zks',
  'https://api.onzks.com/metadata/alice.zks',
  true // Auto-activate
);

if (result.success) {
  console.log('Identity created:', result.tokenId);
} else {
  console.error('Failed to create identity:', result.error);
}

Best Practices

Security Considerations

  1. Private Key Management: Never hardcode private keys in your application
  2. Input Validation: Always validate inputs before sending transactions
  3. Gas Estimation: Estimate gas before sending transactions
  4. Error Handling: Implement comprehensive error handling
  5. Event Monitoring: Monitor events for state changes

Performance Optimization

  1. Batch Operations: Group multiple operations when possible
  2. Caching: Cache frequently accessed data
  3. Event Filtering: Use event filters to reduce data processing
  4. Gas Optimization: Use appropriate gas limits
  5. Connection Pooling: Reuse connections when possible

Testing

  1. Unit Tests: Test individual functions
  2. Integration Tests: Test complete workflows
  3. Error Testing: Test error conditions
  4. Performance Testing: Test under load
  5. Security Testing: Test for vulnerabilities