Skip to main content

Overview

Complete guide for integrating ZKScore smart contracts using the viem library.

Installation

npm install viem
# or
yarn add viem

Basic Setup

Client Configuration

import { createPublicClient, createWalletClient, http } from 'viem';
import { mainnet, polygon, arbitrum, base } from 'viem/chains';

// Public client for read operations
const publicClient = createPublicClient({
  chain: mainnet,
  transport: http('https://eth-mainnet.alchemyapi.io/v2/YOUR_API_KEY')
});

// Wallet client for write operations
const walletClient = createWalletClient({
  chain: mainnet,
  transport: http('https://eth-mainnet.alchemyapi.io/v2/YOUR_API_KEY'),
  account: privateKeyToAccount(PRIVATE_KEY)
});

Contract Configuration

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

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

Identity Operations

Check Identity Status

async function getIdentityInfo(userAddress) {
  try {
    const identity = await publicClient.readContract({
      address: CONTRACTS.identitySBT,
      abi: IdentitySBTABI,
      functionName: 'getIdentity',
      args: [userAddress]
    });
    
    const isActivated = await publicClient.readContract({
      address: CONTRACTS.identitySBT,
      abi: IdentitySBTABI,
      functionName: 'isActivated',
      args: [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 publicClient.readContract({
      address: CONTRACTS.identitySBT,
      abi: IdentitySBTABI,
      functionName: 'mintingFee'
    });
    
    console.log('Minting fee:', formatEther(mintingFee), 'ETH');
    
    // Execute transaction
    const hash = await walletClient.writeContract({
      address: CONTRACTS.identitySBT,
      abi: IdentitySBTABI,
      functionName: 'mintIdentity',
      args: [zksId, displayName, avatarUrl],
      value: mintingFee
    });
    
    console.log('Transaction sent:', hash);
    
    // Wait for confirmation
    const receipt = await publicClient.waitForTransactionReceipt({ hash });
    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 publicClient.readContract({
      address: CONTRACTS.scoreCalculator,
      abi: ScoreCalculatorABI,
      functionName: 'getScore',
      args: [userAddress]
    });
    
    const breakdown = await publicClient.readContract({
      address: CONTRACTS.scoreCalculator,
      abi: ScoreCalculatorABI,
      functionName: 'getScoreBreakdown',
      args: [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(Number(score.lastUpdated) * 1000)
    };
  } catch (error) {
    console.error('Error getting score:', error);
    return null;
  }
}

Listen to Score Updates

// Set up event listeners
function setupScoreListeners() {
  publicClient.watchContractEvent({
    address: CONTRACTS.scoreCalculator,
    abi: ScoreCalculatorABI,
    eventName: 'ScoreUpdated',
    onLogs: (logs) => {
      logs.forEach((log) => {
        console.log(`Score updated for ${log.args.user}:`);
        console.log(`Old: ${log.args.oldScore.toString()}, New: ${log.args.newScore.toString()}`);
        console.log(`Category: ${log.args.category}`);
        console.log(`Block: ${log.blockNumber}`);
      });
    }
  });
}

Achievement Operations

Get User Achievements

async function getUserAchievements(userAddress) {
  try {
    const achievementCount = await publicClient.readContract({
      address: CONTRACTS.achievementRegistry,
      abi: AchievementRegistryABI,
      functionName: 'getUserAchievementCount',
      args: [userAddress]
    });
    
    const achievements = [];
    for (let i = 0; i < Number(achievementCount); i++) {
      const achievement = await publicClient.readContract({
        address: CONTRACTS.achievementRegistry,
        abi: AchievementRegistryABI,
        functionName: 'getUserAchievement',
        args: [userAddress, BigInt(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(Number(achievement.claimedAt) * 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 publicClient.readContract({
      address: CONTRACTS.achievementRegistry,
      abi: AchievementRegistryABI,
      functionName: 'canClaimAchievement',
      args: [walletClient.account.address, achievementId]
    });
    
    if (!canClaim) {
      throw new Error('Cannot claim this achievement');
    }
    
    // Execute transaction
    const hash = await walletClient.writeContract({
      address: CONTRACTS.achievementRegistry,
      abi: AchievementRegistryABI,
      functionName: 'claimAchievement',
      args: [achievementId]
    });
    
    console.log('Claim transaction sent:', hash);
    const receipt = await publicClient.waitForTransactionReceipt({ hash });
    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 = encodeAbiParameters(
      parseAbiParameters('string, uint256, address'),
      [data.skill, BigInt(data.level), data.verifiedBy]
    );
    
    // Execute transaction
    const hash = await walletClient.writeContract({
      address: CONTRACTS.trustRegistry,
      abi: TrustRegistryABI,
      functionName: 'createAttestation',
      args: [recipient, schemaUID, encodedData, BigInt(expiration), revocable]
    });
    
    console.log('Attestation transaction sent:', hash);
    const receipt = await publicClient.waitForTransactionReceipt({ hash });
    console.log('Attestation created!', receipt.transactionHash);
    
    return receipt;
  } catch (error) {
    console.error('Attestation creation failed:', error);
    throw error;
  }
}

Multi-chain Support

Chain Configuration

// Multi-chain setup
const chains = {
  ethereum: mainnet,
  polygon: polygon,
  arbitrum: arbitrum,
  base: base
};

function createClient(chainName, rpcUrl) {
  return createPublicClient({
    chain: chains[chainName],
    transport: http(rpcUrl)
  });
}

// Usage
const ethereumClient = createClient('ethereum', ETHEREUM_RPC_URL);
const polygonClient = createClient('polygon', POLYGON_RPC_URL);

Best Practices

  1. Use Type Safety: Leverage viem’s TypeScript support
  2. Handle Errors Gracefully: Implement comprehensive error handling
  3. Use Event Watchers: Watch 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