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
Copy
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
Copy
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
Copy
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
Copy
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
Copy
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
- Cache Achievement Data: Cache achievement metadata locally
- Batch Queries: Query multiple achievements in parallel
- Event Listening: Use events for real-time updates
- Progress Caching: Cache progress data with TTL
- Gas Estimation: Estimate gas before claiming
Security Considerations
- Verify Requirements: Always check canClaim() before claiming
- Validate Proofs: Ensure proof generation is secure
- Monitor Events: Track all claim events
- Gas Limits: Set appropriate gas limits
- Error Handling: Implement comprehensive error handling
User Experience
- Progress Feedback: Show real-time progress updates
- Clear Requirements: Display achievement requirements clearly
- Claim Notifications: Notify users when achievements are claimable
- Celebration: Celebrate achievement claims
- Badge Display: Show earned badges prominently
Related Documentation
- Contract Overview - Contract architecture and features
- Functions Reference - Complete function documentation
- Events Reference - Event documentation
- Custom Achievements - Creating custom achievements