Skip to main content

Overview

This guide explains how to create custom achievements tailored to your application’s needs. Custom achievements enable you to reward users for specific behaviors, milestones, or accomplishments unique to your platform.
Custom achievements should align with your platform’s goals and provide meaningful recognition for user accomplishments.

Achievement Design

Planning Your Achievement

Before creating an achievement, consider:
  1. Purpose: What behavior are you rewarding?
  2. Requirements: What must users do to earn it?
  3. Rarity: How difficult should it be?
  4. Rewards: What points/boost should it grant?
  5. Verification: How will you verify completion?

Achievement Categories

Choose the most appropriate category:
  • DeFi: Financial protocol interactions
  • NFT: NFT-related activities
  • Social: Community engagement
  • Trading: Trading performance
  • Governance: DAO participation
  • Gaming: Gaming achievements
  • Identity: Identity verification
  • Trust: Trust-building activities

Rarity Guidelines

RarityDifficultyUser %PointsBoost
CommonEasy60%10-50+1%
UncommonModerate30%50-100+2%
RareChallenging8%100-250+5%
EpicVery Hard1.5%250-500+10%
LegendaryExtremely Rare0.5%500-1000+20%

Requirement Types

1. Score Threshold

Require users to reach a minimum score.
// Encode requirement data
const requirementData = ethers.utils.defaultAbiCoder.encode(
  ['uint256'], // minimum score
  [500]
);

// Create achievement
await contract.createAchievement(
  'Rising Star',
  'Reach a ZKScore of 500',
  'ipfs://QmXyz.../rising-star.png',
  6, // Identity category
  1, // Uncommon rarity
  75, // 75 points
  150, // 1.5% boost
  1, // SCORE_THRESHOLD type
  requirementData
);

2. Category Score

Require minimum score in a specific category.
// Encode requirement: category index and minimum score
const requirementData = ethers.utils.defaultAbiCoder.encode(
  ['uint8', 'uint256'],
  [0, 300] // DeFi category, 300 minimum
);

await contract.createAchievement(
  'DeFi Expert',
  'Reach 300 DeFi score',
  'ipfs://QmXyz.../defi-expert.png',
  0, // DeFi category
  2, // Rare
  150,
  300,
  2, // CATEGORY_SCORE type
  requirementData
);

3. Activity Count

Require a certain number of activities.
// Encode requirement: activity type and count
const requirementData = ethers.utils.defaultAbiCoder.encode(
  ['bytes32', 'uint256'],
  [
    ethers.utils.id('NFT_TRADE'), // activity type hash
    50 // required count
  ]
);

await contract.createAchievement(
  'Active Trader',
  'Complete 50 NFT trades',
  'ipfs://QmXyz.../active-trader.png',
  1, // NFT category
  1, // Uncommon
  100,
  200,
  3, // ACTIVITY_COUNT type
  requirementData
);

4. Time-Based

Require users to be active for a certain duration.
// Encode requirement: duration in seconds
const requirementData = ethers.utils.defaultAbiCoder.encode(
  ['uint256'],
  [180 * 24 * 60 * 60] // 180 days
);

await contract.createAchievement(
  'Veteran Member',
  'Be active for 6 months',
  'ipfs://QmXyz.../veteran.png',
  6, // Identity category
  2, // Rare
  200,
  400,
  4, // TIME_BASED type
  requirementData
);

5. Composite

Combine multiple requirements.
// Encode multiple requirements
const requirementData = ethers.utils.defaultAbiCoder.encode(
  ['uint8[]', 'uint256[]'],
  [
    [0, 1, 2], // Categories: DeFi, NFT, Social
    [200, 200, 200] // Minimum scores for each
  ]
);

await contract.createAchievement(
  'Well-Rounded',
  'Score 200+ in DeFi, NFT, and Social',
  'ipfs://QmXyz.../well-rounded.png',
  6, // Identity category
  3, // Epic
  400,
  800,
  5, // COMPOSITE type
  requirementData
);

6. Proof-Based

Require zero-knowledge proof verification.
// Encode proof verification parameters
const requirementData = ethers.utils.defaultAbiCoder.encode(
  ['address', 'bytes32'],
  [
    '0xVerifierContractAddress',
    ethers.utils.id('PROTOCOL_PARTICIPATION')
  ]
);

await contract.createAchievement(
  'Privacy Champion',
  'Verify protocol participation with ZK proof',
  'ipfs://QmXyz.../privacy-champion.png',
  7, // Trust category
  4, // Legendary
  1000,
  2000,
  6, // PROOF_BASED type
  requirementData
);

Complete Creation Example

DeFi Protocol Achievement

const { ethers } = require('ethers');

async function createDeFiAchievement() {
  const provider = new ethers.providers.JsonRpcProvider('https://mainnet.infura.io/v3/YOUR_KEY');
  const signer = provider.getSigner();
  
  const contract = new ethers.Contract(
    '0x3456789012345678901234567890123456789012',
    ACHIEVEMENT_REGISTRY_ABI,
    signer
  );
  
  // Achievement parameters
  const name = 'Liquidity Provider Pro';
  const description = 'Provide liquidity across 5 different protocols';
  const imageURI = 'ipfs://QmXyz.../lp-pro.png';
  const category = 0; // DeFi
  const rarity = 2; // Rare
  const points = 200;
  const scoreBoost = 400; // 4%
  
  // Encode requirement: 5 different protocols
  const requirementData = ethers.utils.defaultAbiCoder.encode(
    ['bytes32', 'uint256'],
    [
      ethers.utils.id('LIQUIDITY_PROVIDED'),
      5 // 5 protocols
    ]
  );
  
  const requirementType = 3; // ACTIVITY_COUNT
  
  // Create achievement
  const tx = await contract.createAchievement(
    name,
    description,
    imageURI,
    category,
    rarity,
    points,
    scoreBoost,
    requirementType,
    requirementData
  );
  
  const receipt = await tx.wait();
  
  // Get achievement ID from event
  const event = receipt.events.find(e => e.event === 'AchievementCreated');
  const achievementId = event.args.achievementId;
  
  console.log(`Achievement created!`);
  console.log(`ID: ${achievementId.toString()}`);
  console.log(`Name: ${name}`);
  console.log(`Category: DeFi`);
  console.log(`Rarity: Rare`);
  console.log(`Points: ${points}`);
  console.log(`Boost: ${scoreBoost / 100}%`);
  
  return achievementId;
}

Metadata Best Practices

Image Guidelines

  1. Format: PNG with transparency
  2. Size: 512x512 pixels minimum
  3. Style: Consistent with your brand
  4. Quality: High resolution for badges
  5. Storage: IPFS or decentralized storage

Creating Badge Images

// Upload to IPFS
const FormData = require('form-data');
const fs = require('fs');
const axios = require('axios');

async function uploadBadgeToIPFS(imagePath) {
  const formData = new FormData();
  formData.append('file', fs.createReadStream(imagePath));
  
  const response = await axios.post(
    'https://api.pinata.cloud/pinning/pinFileToIPFS',
    formData,
    {
      headers: {
        'Authorization': `Bearer ${process.env.PINATA_JWT}`,
        ...formData.getHeaders()
      }
    }
  );
  
  const ipfsHash = response.data.IpfsHash;
  const imageURI = `ipfs://${ipfsHash}`;
  
  console.log(`Badge uploaded: ${imageURI}`);
  return imageURI;
}

Description Guidelines

  1. Length: 50-150 characters
  2. Clarity: Clear requirements
  3. Tone: Encouraging and positive
  4. Action: Describe what to do
  5. Benefit: Mention rewards

Verification Setup

Manual Verification

For achievements requiring manual review:
class ManualVerifier {
  constructor(contract, account) {
    this.contract = contract;
    this.account = account;
  }
  
  async verifyUser(userAddress, achievementId, proofData) {
    // Verify proof data
    const isValid = await this.validateProof(proofData);
    
    if (!isValid) {
      throw new Error('Invalid proof data');
    }
    
    // Update progress to 100%
    const achievement = await this.contract.getAchievement(achievementId);
    const requiredProgress = achievement.requirementData; // depends on type
    
    await this.contract.updateProgress(
      userAddress,
      achievementId,
      requiredProgress
    );
    
    console.log(`User ${userAddress} verified for achievement ${achievementId}`);
  }
  
  async validateProof(proofData) {
    // Implement your validation logic
    return true;
  }
}

Automatic Verification

For automated achievements:
class AutomaticVerifier {
  constructor(contract) {
    this.contract = contract;
  }
  
  // Listen for relevant events
  async monitorActivity(userAddress, activityType) {
    // Monitor blockchain events
    const filter = {
      address: PROTOCOL_ADDRESS,
      topics: [
        ethers.utils.id(activityType),
        ethers.utils.hexZeroPad(userAddress, 32)
      ]
    };
    
    provider.on(filter, async (log) => {
      await this.incrementProgress(userAddress, log);
    });
  }
  
  async incrementProgress(userAddress, log) {
    // Parse event data
    const eventData = parseEventLog(log);
    
    // Update achievement progress
    const achievementIds = await this.getRelatedAchievements(eventData.type);
    
    for (const id of achievementIds) {
      const currentProgress = await this.contract.getProgress(userAddress, id);
      const newProgress = currentProgress.current + 1;
      
      await this.contract.updateProgress(userAddress, id, newProgress);
    }
  }
  
  async getRelatedAchievements(activityType) {
    // Return achievement IDs that track this activity
    return [123, 456, 789];
  }
}

Testing Custom Achievements

Testing on Testnet

async function testAchievement(achievementId) {
  // 1. Create test user
  const testUser = ethers.Wallet.createRandom().connect(provider);
  
  // 2. Fund test user
  await signer.sendTransaction({
    to: testUser.address,
    value: ethers.utils.parseEther('0.1')
  });
  
  // 3. Check initial progress
  const initialProgress = await contract.getProgress(testUser.address, achievementId);
  console.log('Initial progress:', initialProgress);
  
  // 4. Simulate activity (update progress)
  await contract.updateProgress(testUser.address, achievementId, 100);
  
  // 5. Verify can claim
  const canClaim = await contract.canClaim(testUser.address, achievementId);
  console.log('Can claim:', canClaim);
  
  // 6. Claim achievement
  const contractWithTestUser = contract.connect(testUser);
  const tx = await contractWithTestUser.claimAchievement(achievementId, '0x');
  await tx.wait();
  
  console.log('✅ Achievement successfully claimed in test!');
}

Batch Achievement Creation

Creating Multiple Achievements

async function createAchievementSeries() {
  const achievements = [
    {
      name: 'First Trade',
      description: 'Complete your first trade',
      category: 3,
      rarity: 0,
      points: 10,
      requirement: { type: 'ACTIVITY_COUNT', count: 1 }
    },
    {
      name: 'Active Trader',
      description: 'Complete 10 trades',
      category: 3,
      rarity: 1,
      points: 50,
      requirement: { type: 'ACTIVITY_COUNT', count: 10 }
    },
    {
      name: 'Trading Master',
      description: 'Complete 100 trades',
      category: 3,
      rarity: 2,
      points: 200,
      requirement: { type: 'ACTIVITY_COUNT', count: 100 }
    }
  ];
  
  const createdIds = [];
  
  for (const ach of achievements) {
    const requirementData = ethers.utils.defaultAbiCoder.encode(
      ['bytes32', 'uint256'],
      [ethers.utils.id('TRADE_COMPLETED'), ach.requirement.count]
    );
    
    const tx = await contract.createAchievement(
      ach.name,
      ach.description,
      `ipfs://QmXyz.../${ach.name.toLowerCase().replace(/\s/g, '-')}.png`,
      ach.category,
      ach.rarity,
      ach.points,
      ach.points * 2, // 2x points as boost
      3, // ACTIVITY_COUNT
      requirementData
    );
    
    const receipt = await tx.wait();
    const event = receipt.events.find(e => e.event === 'AchievementCreated');
    
    createdIds.push(event.args.achievementId.toString());
    console.log(`Created: ${ach.name} (ID: ${event.args.achievementId.toString()})`);
  }
  
  return createdIds;
}

Best Practices

Achievement Design

  1. Progressive Difficulty: Create series with increasing difficulty
  2. Clear Requirements: Make requirements obvious
  3. Balanced Rewards: Match rewards to difficulty
  4. Category Alignment: Ensure category matches activity
  5. Unique Names: Use descriptive, unique names

Implementation

  1. Test Thoroughly: Test on testnet first
  2. Monitor Events: Track creation and claim events
  3. Update Documentation: Document custom achievements
  4. User Communication: Announce new achievements
  5. Gather Feedback: Iterate based on user feedback

Verification

  1. Secure Verification: Protect verification endpoints
  2. Prevent Gaming: Implement anti-cheat measures
  3. Audit Logs: Maintain verification logs
  4. Regular Reviews: Review achievement completion
  5. Fair Distribution: Ensure fair access