Skip to main content

Overview

Complete guide for integrating ZKScore smart contracts using the ethers.js library.

Installation

npm install ethers
# or
yarn add ethers

Basic Setup

Provider Configuration

import { ethers } from 'ethers';

// Provider setup
const provider = new ethers.providers.JsonRpcProvider(
  'https://eth-mainnet.alchemyapi.io/v2/YOUR_API_KEY'
);

// Wallet setup
const wallet = new ethers.Wallet(PRIVATE_KEY, provider);

// Contract addresses
const CONTRACTS = {
  identitySBT: '0x1234567890123456789012345678901234567890',
  scoreCalculator: '0x2345678901234567890123456789012345678901',
  achievementRegistry: '0x3456789012345678901234567890123456789012',
  trustRegistry: '0x4567890123456789012345678901234567890123'
};

Contract Initialization

// Import ABIs
import IdentitySBTABI from '@zkscore/contracts/abis/IdentitySBT.json';
import ScoreCalculatorABI from '@zkscore/contracts/abis/ScoreCalculator.json';

// Initialize contracts
const identityContract = new ethers.Contract(
  CONTRACTS.identitySBT,
  IdentitySBTABI,
  provider
);

const scoreContract = new ethers.Contract(
  CONTRACTS.scoreCalculator,
  ScoreCalculatorABI,
  provider
);

Identity Operations

Check Identity Status

async function getIdentityInfo(userAddress) {
  try {
    const identity = await identityContract.getIdentity(userAddress);
    const isActivated = await identityContract.isActivated(userAddress);
    
    return {
      exists: identity.exists,
      zksId: identity.zksId,
      displayName: identity.displayName,
      avatarUrl: identity.avatarUrl,
      isActivated: isActivated,
      tokenId: identity.tokenId.toString()
    };
  } catch (error) {
    console.error('Error getting identity:', error);
    return null;
  }
}

Mint Identity

async function mintIdentity(zksId, displayName, avatarUrl) {
  try {
    // Get minting fee
    const mintingFee = await identityContract.mintingFee();
    console.log('Minting fee:', ethers.utils.formatEther(mintingFee), 'ETH');
    
    // Estimate gas
    const gasEstimate = await identityContract.estimateGas.mintIdentity(
      zksId,
      displayName,
      avatarUrl,
      { value: mintingFee }
    );
    
    // Execute transaction
    const tx = await identityContract.mintIdentity(
      zksId,
      displayName,
      avatarUrl,
      { 
        value: mintingFee,
        gasLimit: gasEstimate.mul(120).div(100) // 20% buffer
      }
    );
    
    console.log('Transaction sent:', tx.hash);
    const receipt = await tx.wait();
    console.log('Transaction confirmed:', receipt.transactionHash);
    
    return receipt;
  } catch (error) {
    console.error('Minting failed:', error);
    throw error;
  }
}

Score Operations

Get User Score

async function getUserScore(userAddress) {
  try {
    const score = await scoreContract.getScore(userAddress);
    const breakdown = await scoreContract.getScoreBreakdown(userAddress);
    
    return {
      total: score.total.toString(),
      breakdown: {
        defi: breakdown.defi.toString(),
        nft: breakdown.nft.toString(),
        social: breakdown.social.toString(),
        trading: breakdown.trading.toString(),
        governance: breakdown.governance.toString(),
        gaming: breakdown.gaming.toString(),
        identity: breakdown.identity.toString(),
        trust: breakdown.trust.toString()
      },
      lastUpdated: new Date(score.lastUpdated.toNumber() * 1000)
    };
  } catch (error) {
    console.error('Error getting score:', error);
    return null;
  }
}

Listen to Score Updates

// Set up event listeners
function setupScoreListeners() {
  scoreContract.on('ScoreUpdated', (user, oldScore, newScore, category, event) => {
    console.log(`Score updated for ${user}:`);
    console.log(`Old: ${oldScore.toString()}, New: ${newScore.toString()}`);
    console.log(`Category: ${category}`);
    console.log(`Block: ${event.blockNumber}`);
  });
  
  scoreContract.on('ScoreCalculated', (user, totalScore, breakdown, event) => {
    console.log(`Score calculated for ${user}: ${totalScore.toString()}`);
  });
}

// Remove listeners when done
function removeScoreListeners() {
  scoreContract.removeAllListeners('ScoreUpdated');
  scoreContract.removeAllListeners('ScoreCalculated');
}

Achievement Operations

Get User Achievements

async function getUserAchievements(userAddress) {
  try {
    const achievementCount = await achievementContract.getUserAchievementCount(userAddress);
    const achievements = [];
    
    for (let i = 0; i < achievementCount.toNumber(); i++) {
      const achievement = await achievementContract.getUserAchievement(userAddress, i);
      achievements.push({
        id: achievement.id.toString(),
        name: achievement.name,
        description: achievement.description,
        category: achievement.category,
        rarity: achievement.rarity,
        points: achievement.points.toString(),
        claimedAt: new Date(achievement.claimedAt.toNumber() * 1000)
      });
    }
    
    return achievements;
  } catch (error) {
    console.error('Error getting achievements:', error);
    return [];
  }
}

Claim Achievement

async function claimAchievement(achievementId) {
  try {
    // Check if user can claim
    const canClaim = await achievementContract.canClaimAchievement(
      wallet.address,
      achievementId
    );
    
    if (!canClaim) {
      throw new Error('Cannot claim this achievement');
    }
    
    // Estimate gas
    const gasEstimate = await achievementContract.estimateGas.claimAchievement(
      achievementId
    );
    
    // Execute transaction
    const tx = await achievementContract.claimAchievement(achievementId, {
      gasLimit: gasEstimate.mul(120).div(100)
    });
    
    console.log('Claim transaction sent:', tx.hash);
    const receipt = await tx.wait();
    console.log('Achievement claimed!', receipt.transactionHash);
    
    return receipt;
  } catch (error) {
    console.error('Claim failed:', error);
    throw error;
  }
}

Trust Registry Operations

Create Attestation

async function createAttestation(recipient, schemaUID, data, expiration, revocable) {
  try {
    // Encode data
    const encodedData = ethers.utils.defaultAbiCoder.encode(
      ['string', 'uint256', 'address'],
      [data.skill, data.level, data.verifiedBy]
    );
    
    // Estimate gas
    const gasEstimate = await trustContract.estimateGas.createAttestation(
      recipient,
      schemaUID,
      encodedData,
      expiration,
      revocable
    );
    
    // Execute transaction
    const tx = await trustContract.createAttestation(
      recipient,
      schemaUID,
      encodedData,
      expiration,
      revocable,
      { gasLimit: gasEstimate.mul(120).div(100) }
    );
    
    console.log('Attestation transaction sent:', tx.hash);
    const receipt = await tx.wait();
    console.log('Attestation created!', receipt.transactionHash);
    
    return receipt;
  } catch (error) {
    console.error('Attestation creation failed:', error);
    throw error;
  }
}

Get Attestations

async function getUserAttestations(userAddress) {
  try {
    const attestationCount = await trustContract.getUserAttestationCount(userAddress);
    const attestations = [];
    
    for (let i = 0; i < attestationCount.toNumber(); i++) {
      const attestation = await trustContract.getUserAttestation(userAddress, i);
      attestations.push({
        uid: attestation.uid,
        attester: attestation.attester,
        recipient: attestation.recipient,
        schema: attestation.schema,
        data: attestation.data,
        expiration: attestation.expiration.toNumber(),
        revocable: attestation.revocable,
        revoked: attestation.revoked
      });
    }
    
    return attestations;
  } catch (error) {
    console.error('Error getting attestations:', error);
    return [];
  }
}

Error Handling

Comprehensive Error Handling

class ZKScoreError extends Error {
  constructor(message, code, details) {
    super(message);
    this.name = 'ZKScoreError';
    this.code = code;
    this.details = details;
  }
}

async function safeContractCall(contractMethod, ...args) {
  try {
    const result = await contractMethod(...args);
    return { success: true, data: result };
  } catch (error) {
    let errorMessage = 'Unknown error';
    let errorCode = 'UNKNOWN_ERROR';
    
    if (error.code === 'INSUFFICIENT_FUNDS') {
      errorMessage = 'Insufficient funds for transaction';
      errorCode = 'INSUFFICIENT_FUNDS';
    } else if (error.code === 'USER_REJECTED') {
      errorMessage = 'User rejected transaction';
      errorCode = 'USER_REJECTED';
    } else if (error.message.includes('Identity already exists')) {
      errorMessage = 'User already has an identity';
      errorCode = 'IDENTITY_EXISTS';
    } else if (error.message.includes('ZKS ID already taken')) {
      errorMessage = 'ZKS ID is already taken';
      errorCode = 'ZKS_ID_TAKEN';
    }
    
    return { 
      success: false, 
      error: new ZKScoreError(errorMessage, errorCode, error) 
    };
  }
}

Best Practices

  1. Always Estimate Gas: Use gas estimation before transactions
  2. Handle Errors Gracefully: Implement comprehensive error handling
  3. Use Event Listeners: Listen to contract events for real-time updates
  4. Batch Operations: Combine multiple calls when possible
  5. Monitor Gas Prices: Use appropriate gas prices for network conditions