Skip to main content

Overview

Complete guide for integrating ZKScore smart contracts using wagmi hooks in React applications.

Installation

npm install wagmi viem @tanstack/react-query
# or
yarn add wagmi viem @tanstack/react-query

Basic Setup

Provider Configuration

import { WagmiProvider, createConfig, http } from 'wagmi';
import { mainnet, polygon, arbitrum, base } from 'wagmi/chains';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { injected, metaMask, walletConnect } from 'wagmi/connectors';

// Create wagmi config
const config = createConfig({
  chains: [mainnet, polygon, arbitrum, base],
  connectors: [
    injected(),
    metaMask(),
    walletConnect({ projectId: 'YOUR_PROJECT_ID' })
  ],
  transports: {
    [mainnet.id]: http(),
    [polygon.id]: http(),
    [arbitrum.id]: http(),
    [base.id]: http()
  }
});

// Query client
const queryClient = new QueryClient();

// App component
function App() {
  return (
    <WagmiProvider config={config}>
      <QueryClientProvider client={queryClient}>
        <YourApp />
      </QueryClientProvider>
    </WagmiProvider>
  );
}

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

Get Identity Hook

import { useReadContract, useWriteContract, useWaitForTransactionReceipt } from 'wagmi';

function useIdentity(userAddress) {
  const { data: identity, isLoading, error } = useReadContract({
    address: CONTRACTS.identitySBT,
    abi: IdentitySBTABI,
    functionName: 'getIdentity',
    args: [userAddress]
  });
  
  const { data: isActivated } = useReadContract({
    address: CONTRACTS.identitySBT,
    abi: IdentitySBTABI,
    functionName: 'isActivated',
    args: [userAddress]
  });
  
  return {
    identity,
    isActivated,
    isLoading,
    error
  };
}

Mint Identity Hook

function useMintIdentity() {
  const { writeContract, data: hash, isPending } = useWriteContract();
  const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({
    hash
  });
  
  const mintIdentity = async (zksId, displayName, avatarUrl) => {
    try {
      // Get minting fee
      const mintingFee = await publicClient.readContract({
        address: CONTRACTS.identitySBT,
        abi: IdentitySBTABI,
        functionName: 'mintingFee'
      });
      
      await writeContract({
        address: CONTRACTS.identitySBT,
        abi: IdentitySBTABI,
        functionName: 'mintIdentity',
        args: [zksId, displayName, avatarUrl],
        value: mintingFee
      });
    } catch (error) {
      console.error('Minting failed:', error);
    }
  };
  
  return {
    mintIdentity,
    hash,
    isPending,
    isConfirming,
    isSuccess
  };
}

Score Operations

Get Score Hook

function useScore(userAddress) {
  const { data: score, isLoading, error } = useReadContract({
    address: CONTRACTS.scoreCalculator,
    abi: ScoreCalculatorABI,
    functionName: 'getScore',
    args: [userAddress]
  });
  
  const { data: breakdown } = useReadContract({
    address: CONTRACTS.scoreCalculator,
    abi: ScoreCalculatorABI,
    functionName: 'getScoreBreakdown',
    args: [userAddress]
  });
  
  return {
    score,
    breakdown,
    isLoading,
    error
  };
}

Score Events Hook

import { useWatchContractEvent } from 'wagmi';

function useScoreEvents(userAddress) {
  const [scoreUpdates, setScoreUpdates] = useState([]);
  
  useWatchContractEvent({
    address: CONTRACTS.scoreCalculator,
    abi: ScoreCalculatorABI,
    eventName: 'ScoreUpdated',
    args: {
      user: userAddress
    },
    onLogs: (logs) => {
      setScoreUpdates(prev => [...prev, ...logs]);
    }
  });
  
  return scoreUpdates;
}

Achievement Operations

Get Achievements Hook

function useAchievements(userAddress) {
  const { data: achievementCount } = useReadContract({
    address: CONTRACTS.achievementRegistry,
    abi: AchievementRegistryABI,
    functionName: 'getUserAchievementCount',
    args: [userAddress]
  });
  
  const achievements = useMemo(() => {
    if (!achievementCount) return [];
    
    const achievementPromises = [];
    for (let i = 0; i < Number(achievementCount); i++) {
      achievementPromises.push(
        publicClient.readContract({
          address: CONTRACTS.achievementRegistry,
          abi: AchievementRegistryABI,
          functionName: 'getUserAchievement',
          args: [userAddress, BigInt(i)]
        })
      );
    }
    
    return Promise.all(achievementPromises);
  }, [achievementCount, userAddress]);
  
  return {
    achievements,
    count: achievementCount
  };
}

Claim Achievement Hook

function useClaimAchievement() {
  const { writeContract, data: hash, isPending } = useWriteContract();
  const { isLoading: isConfirming, isSuccess } = useWaitForTransactionReceipt({
    hash
  });
  
  const claimAchievement = async (achievementId) => {
    try {
      await writeContract({
        address: CONTRACTS.achievementRegistry,
        abi: AchievementRegistryABI,
        functionName: 'claimAchievement',
        args: [achievementId]
      });
    } catch (error) {
      console.error('Claim failed:', error);
    }
  };
  
  return {
    claimAchievement,
    hash,
    isPending,
    isConfirming,
    isSuccess
  };
}

React Components

Identity Component

function IdentityCard({ userAddress }) {
  const { identity, isActivated, isLoading } = useIdentity(userAddress);
  const { mintIdentity, isPending } = useMintIdentity();
  
  if (isLoading) return <div>Loading...</div>;
  
  if (!identity?.exists) {
    return (
      <div>
        <h3>No Identity Found</h3>
        <button 
          onClick={() => mintIdentity('alice.zks', 'Alice Smith', 'https://example.com/avatar.jpg')}
          disabled={isPending}
        >
          {isPending ? 'Minting...' : 'Mint Identity'}
        </button>
      </div>
    );
  }
  
  return (
    <div>
      <h3>{identity.displayName}</h3>
      <p>ZKS ID: {identity.zksId}</p>
      <p>Status: {isActivated ? 'Activated' : 'Not Activated'}</p>
    </div>
  );
}

Score Component

function ScoreDisplay({ userAddress }) {
  const { score, breakdown, isLoading } = useScore(userAddress);
  const scoreUpdates = useScoreEvents(userAddress);
  
  if (isLoading) return <div>Loading score...</div>;
  
  return (
    <div>
      <h3>ZKScore: {score?.total.toString() || '0'}</h3>
      <div>
        <h4>Breakdown:</h4>
        <ul>
          <li>DeFi: {breakdown?.defi.toString() || '0'}</li>
          <li>NFT: {breakdown?.nft.toString() || '0'}</li>
          <li>Social: {breakdown?.social.toString() || '0'}</li>
          <li>Trading: {breakdown?.trading.toString() || '0'}</li>
        </ul>
      </div>
      {scoreUpdates.length > 0 && (
        <div>
          <h4>Recent Updates:</h4>
          {scoreUpdates.map((update, index) => (
            <div key={index}>
              Score: {update.args.oldScore.toString()}{update.args.newScore.toString()}
            </div>
          ))}
        </div>
      )}
    </div>
  );
}

Best Practices

  1. Use Hooks: Leverage wagmi’s built-in hooks for state management
  2. Handle Loading States: Always handle loading and error states
  3. Optimize Re-renders: Use useMemo and useCallback appropriately
  4. Error Boundaries: Implement error boundaries for better UX
  5. Type Safety: Use TypeScript for better development experience