Skip to main content

Overview

This guide provides comprehensive examples for integrating with the ZKScore Achievement Registry contract. It covers Web3 integration, claiming workflows, progress tracking, and best practices for production deployments.
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 = '0x3456789012345678901234567890123456789012';
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);

Integration Class

class AchievementRegistryIntegration {
  constructor(provider, contractAddress, abi) {
    this.provider = provider;
    this.contract = new ethers.Contract(contractAddress, abi, provider);
    this.contractWithSigner = null;
    this.categories = ['DeFi', 'NFT', 'Social', 'Trading', 'Governance', 'Gaming', 'Identity', 'Trust'];
    this.rarities = ['Common', 'Uncommon', 'Rare', 'Epic', 'Legendary'];
  }
  
  setSigner(signer) {
    this.contractWithSigner = this.contract.connect(signer);
  }
  
  // Get achievement details
  async getAchievement(achievementId) {
    try {
      const achievement = await this.contract.getAchievement(achievementId);
      
      return {
        success: true,
        achievement: {
          id: achievement.id.toString(),
          name: achievement.name,
          description: achievement.description,
          imageURI: achievement.imageURI,
          category: this.categories[achievement.category],
          rarity: this.rarities[achievement.rarity],
          points: achievement.points.toString(),
          scoreBoost: achievement.scoreBoost.toString(),
          totalClaimed: achievement.totalClaimed.toString(),
          maxClaims: achievement.maxClaims.toString(),
          isActive: achievement.isActive
        }
      };
    } catch (error) {
      return {
        success: false,
        error: error.message
      };
    }
  }
  
  // Get user's achievements
  async getUserAchievements(userAddress) {
    try {
      const achievementIds = await this.contract.getUserAchievements(userAddress);
      
      // Get details for each achievement
      const achievements = await Promise.all(
        achievementIds.map(id => this.getAchievement(id))
      );
      
      return {
        success: true,
        count: achievementIds.length,
        achievements: achievements.filter(a => a.success).map(a => a.achievement)
      };
    } catch (error) {
      return {
        success: false,
        error: error.message
      };
    }
  }
  
  // Get progress for achievement
  async getProgress(userAddress, achievementId) {
    try {
      const progress = await this.contract.getProgress(userAddress, achievementId);
      
      return {
        success: true,
        progress: {
          current: progress.current.toString(),
          required: progress.required.toString(),
          percentage: progress.percentage.toString(),
          lastUpdated: new Date(progress.lastUpdated.toNumber() * 1000).toISOString(),
          canClaim: progress.canClaim
        }
      };
    } catch (error) {
      return {
        success: false,
        error: error.message
      };
    }
  }
  
  // Claim achievement
  async claimAchievement(achievementId, proof = '0x') {
    if (!this.contractWithSigner) {
      throw new Error('Signer not set');
    }
    
    try {
      const tx = await this.contractWithSigner.claimAchievement(achievementId, proof);
      const receipt = await tx.wait();
      
      // Get events
      const claimEvent = receipt.events.find(e => e.event === 'AchievementClaimed');
      const badgeEvent = receipt.events.find(e => e.event === 'BadgeMinted');
      
      return {
        success: true,
        achievementId: claimEvent.args.achievementId.toString(),
        badgeTokenId: badgeEvent.args.tokenId.toString(),
        timestamp: claimEvent.args.timestamp.toNumber(),
        transactionHash: receipt.transactionHash
      };
    } catch (error) {
      return {
        success: false,
        error: error.message
      };
    }
  }
  
  // Check if can claim
  async canClaim(userAddress, achievementId) {
    try {
      const canClaim = await this.contract.canClaim(userAddress, achievementId);
      
      return {
        success: true,
        canClaim
      };
    } catch (error) {
      return {
        success: false,
        error: error.message
      };
    }
  }
}

// Usage
const integration = new AchievementRegistryIntegration(
  provider,
  '0x3456789012345678901234567890123456789012',
  ACHIEVEMENT_REGISTRY_ABI
);

integration.setSigner(signer);

// Get user's achievements
const result = await integration.getUserAchievements('0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb');
console.log('User achievements:', result);

Achievement Dashboard

Complete Achievement Tracker

class AchievementDashboard {
  constructor(integration) {
    this.integration = integration;
    this.achievementCache = new Map();
    this.userProgress = new Map();
  }
  
  // Initialize dashboard for user
  async initializeUser(userAddress) {
    try {
      // Get user's achievements
      const earnedResult = await this.integration.getUserAchievements(userAddress);
      
      if (!earnedResult.success) {
        throw new Error(earnedResult.error);
      }
      
      // Get all available achievements (example: first 100)
      const allAchievements = await this.getAllAchievements(100);
      
      // Calculate progress for achievements not yet earned
      const notEarnedIds = allAchievements
        .filter(a => !earnedResult.achievements.find(ea => ea.id === a.id))
        .map(a => a.id);
      
      const progressResults = await Promise.all(
        notEarnedIds.map(id => 
          this.integration.getProgress(userAddress, id)
        )
      );
      
      // Store progress
      progressResults.forEach((result, index) => {
        if (result.success) {
          this.userProgress.set(notEarnedIds[index], result.progress);
        }
      });
      
      return {
        success: true,
        earned: earnedResult.achievements,
        inProgress: this.getInProgress(),
        claimable: this.getClaimable()
      };
    } catch (error) {
      return {
        success: false,
        error: error.message
      };
    }
  }
  
  // Get all available achievements
  async getAllAchievements(limit = 100) {
    const achievements = [];
    
    for (let i = 1; i <= limit; i++) {
      const result = await this.integration.getAchievement(i);
      
      if (result.success && result.achievement.isActive) {
        achievements.push(result.achievement);
        this.achievementCache.set(result.achievement.id, result.achievement);
      } else {
        break; // Stop if achievement doesn't exist
      }
    }
    
    return achievements;
  }
  
  // Get achievements in progress
  getInProgress() {
    const inProgress = [];
    
    for (const [id, progress] of this.userProgress.entries()) {
      if (parseInt(progress.percentage) > 0 && !progress.canClaim) {
        const achievement = this.achievementCache.get(id);
        if (achievement) {
          inProgress.push({
            ...achievement,
            progress
          });
        }
      }
    }
    
    return inProgress.sort((a, b) => 
      parseInt(b.progress.percentage) - parseInt(a.progress.percentage)
    );
  }
  
  // Get claimable achievements
  getClaimable() {
    const claimable = [];
    
    for (const [id, progress] of this.userProgress.entries()) {
      if (progress.canClaim) {
        const achievement = this.achievementCache.get(id);
        if (achievement) {
          claimable.push({
            ...achievement,
            progress
          });
        }
      }
    }
    
    return claimable;
  }
  
  // Claim achievement with UI feedback
  async claimWithFeedback(achievementId) {
    try {
      console.log(`Claiming achievement ${achievementId}...`);
      
      const result = await this.integration.claimAchievement(achievementId);
      
      if (result.success) {
        console.log('🎉 Achievement claimed!');
        console.log(`Badge Token ID: ${result.badgeTokenId}`);
        console.log(`Transaction: ${result.transactionHash}`);
        
        // Update local state
        this.userProgress.delete(achievementId);
        
        return result;
      }
      
      throw new Error(result.error);
    } catch (error) {
      console.error('❌ Claim failed:', error.message);
      throw error;
    }
  }
  
  // Get achievement by category
  getByCategory(category) {
    const achievements = [];
    
    for (const [id, achievement] of this.achievementCache.entries()) {
      if (achievement.category === category) {
        achievements.push(achievement);
      }
    }
    
    return achievements;
  }
  
  // Get achievement by rarity
  getByRarity(rarity) {
    const achievements = [];
    
    for (const [id, achievement] of this.achievementCache.entries()) {
      if (achievement.rarity === rarity) {
        achievements.push(achievement);
      }
    }
    
    return achievements;
  }
}

// Usage
const dashboard = new AchievementDashboard(integration);

// Initialize for user
const userAddress = '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb';
const dashboardData = await dashboard.initializeUser(userAddress);

console.log('Earned achievements:', dashboardData.earned);
console.log('In progress:', dashboardData.inProgress);
console.log('Claimable:', dashboardData.claimable);

// Claim achievement
if (dashboardData.claimable.length > 0) {
  const firstClaimable = dashboardData.claimable[0];
  await dashboard.claimWithFeedback(firstClaimable.id);
}

Real-time Progress Tracking

Progress Monitor

class ProgressMonitor {
  constructor(contract, userAddress) {
    this.contract = contract;
    this.userAddress = userAddress;
    this.progressListeners = new Map();
    this.startMonitoring();
  }
  
  startMonitoring() {
    // Listen for progress updates
    const filter = this.contract.filters.ProgressUpdated(this.userAddress);
    
    this.contract.on(filter, (user, achievementId, progress, event) => {
      this.handleProgressUpdate(achievementId, progress, event);
    });
    
    // Listen for achievements claimed
    const claimFilter = this.contract.filters.AchievementClaimed(this.userAddress);
    
    this.contract.on(claimFilter, (user, achievementId, timestamp, event) => {
      this.handleAchievementClaimed(achievementId, timestamp, event);
    });
  }
  
  handleProgressUpdate(achievementId, progress, event) {
    console.log(`Progress update for achievement ${achievementId.toString()}: ${progress.toString()}`);
    
    // Notify listeners
    if (this.progressListeners.has(achievementId.toString())) {
      const callbacks = this.progressListeners.get(achievementId.toString());
      callbacks.forEach(callback => callback(progress.toString(), event));
    }
    
    // Check if now claimable
    this.checkClaimable(achievementId);
  }
  
  handleAchievementClaimed(achievementId, timestamp, event) {
    console.log(`🎉 Achievement ${achievementId.toString()} claimed!`);
    
    // Trigger celebration
    this.celebrateClaim(achievementId, timestamp);
  }
  
  async checkClaimable(achievementId) {
    const progress = await this.contract.getProgress(this.userAddress, achievementId);
    
    if (progress.canClaim) {
      console.log(`✨ Achievement ${achievementId.toString()} is now claimable!`);
      this.notifyClaimable(achievementId);
    }
  }
  
  onProgress(achievementId, callback) {
    const id = achievementId.toString();
    
    if (!this.progressListeners.has(id)) {
      this.progressListeners.set(id, []);
    }
    
    this.progressListeners.get(id).push(callback);
  }
  
  celebrateClaim(achievementId, timestamp) {
    // Implement celebration logic
    console.log(`🏆 Congratulations! Achievement ${achievementId.toString()} unlocked!`);
  }
  
  notifyClaimable(achievementId) {
    // Implement notification logic
    console.log(`📢 Achievement ${achievementId.toString()} is ready to claim!`);
  }
  
  stopMonitoring() {
    this.contract.removeAllListeners();
  }
}

// Usage
const monitor = new ProgressMonitor(contract, userAddress);

// Subscribe to progress updates
monitor.onProgress(123, (progress, event) => {
  console.log(`Achievement 123 progress: ${progress}`);
  
  // Update UI
  updateProgressBar(progress);
});

Error Handling

Comprehensive Error Handler

class AchievementErrorHandler {
  static handleError(error) {
    const errorMessage = error.message || error.toString();
    
    if (errorMessage.includes('Achievement not active')) {
      return {
        type: 'ACHIEVEMENT_INACTIVE',
        message: 'This achievement is no longer available',
        code: 'ACH001'
      };
    }
    
    if (errorMessage.includes('Already claimed')) {
      return {
        type: 'ALREADY_CLAIMED',
        message: 'You have already claimed this achievement',
        code: 'ACH002'
      };
    }
    
    if (errorMessage.includes('Requirements not met')) {
      return {
        type: 'REQUIREMENTS_NOT_MET',
        message: 'You do not meet the requirements for this achievement',
        code: 'ACH003'
      };
    }
    
    if (errorMessage.includes('Invalid proof')) {
      return {
        type: 'INVALID_PROOF',
        message: 'Verification proof is invalid',
        code: 'ACH004'
      };
    }
    
    if (errorMessage.includes('Max claims reached')) {
      return {
        type: 'MAX_CLAIMS_REACHED',
        message: 'Maximum number of claims reached for this achievement',
        code: 'ACH005'
      };
    }
    
    return {
      type: 'UNKNOWN_ERROR',
      message: errorMessage,
      code: 'ACH999'
    };
  }
  
  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 (exponential backoff)
        await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, attempt - 1)));
      }
    }
  }
}

// Usage
try {
  const result = await AchievementErrorHandler.safeExecute(async () => {
    return await integration.claimAchievement(123);
  });
  console.log('Success:', result);
} catch (error) {
  console.error(`Error [${error.code}]:`, error.message);
  console.error('Type:', error.type);
}

Best Practices

Performance Optimization

  1. Cache Achievement Data: Cache achievement metadata locally
  2. Batch Queries: Query multiple achievements in parallel
  3. Event Listening: Use events for real-time updates
  4. Progress Caching: Cache progress data with TTL
  5. Gas Estimation: Estimate gas before claiming

Security Considerations

  1. Verify Requirements: Always check canClaim() before claiming
  2. Validate Proofs: Ensure proof generation is secure
  3. Monitor Events: Track all claim events
  4. Gas Limits: Set appropriate gas limits
  5. Error Handling: Implement comprehensive error handling

User Experience

  1. Progress Feedback: Show real-time progress updates
  2. Clear Requirements: Display achievement requirements clearly
  3. Claim Notifications: Notify users when achievements are claimable
  4. Celebration: Celebrate achievement claims
  5. Badge Display: Show earned badges prominently