Merkle challenges provide cryptographic proof-based approval mechanisms using SHA256 Merkle trees. They enable secure, gas-efficient whitelisting and claim code systems without storing large address lists on-chain.
Key Benefits:
Gas Efficiency: Distribute gas costs among users instead of collection creators
Flexibility: Support both whitelist trees and claim code systems
Scalability: Handle large user bases without on-chain storage
Interface Definition
exportinterfaceMerkleChallenge<TextendsNumberType>{root:string;// SHA256 Merkle tree root hashexpectedProofLength:T;// Required proof length (security)useCreatorAddressAsLeaf:boolean;// Use initiator address as leaf?maxUsesPerLeaf:T;// Maximum uses per leafuri:string;// Metadata URIcustomData:string;// Custom data fieldchallengeTrackerId:string;// Unique tracker identifierleafSigner:string;// Optional leaf signature authority}
Basic Example
Challenge Types
1. Claim Code Challenges
Create a Merkle tree of secret claim codes that users must provide to claim tokens.
Use Case: Private claim codes, invitation systems, promotional campaigns
Process:
Generate secret claim codes
Build Merkle tree from hashed codes
Distribute codes privately to users with leaf signatures
Users provide code + Merkle proof in transfer
2. Whitelist Challenges
Create a Merkle tree of user addresses for gas-efficient whitelisting.
Use Case: Large whitelists, community access, gas cost distribution
Process:
Collect user addresses
Build Merkle tree from hashed addresses
Users provide their address + Merkle proof
System verifies address is in whitelist / valid proof
Gas Cost Distribution: Instead of the collection creator paying gas to store N addresses on-chain, each user pays their own gas for proof verification.
Understanding useCreatorAddressAsLeaf
The useCreatorAddressAsLeaf field determines how the system handles the leaf value in Merkle proofs:
Whitelist Trees (useCreatorAddressAsLeaf: true)
Purpose: Verify that the transaction initiator is in the whitelist.
How It Works:
Automatic Override: The system expects the provided leaf to be the initiator's BitBadges address ("bb1...")
Address Verification: Checks if the initiator's address exists in the Merkle tree
No Manual Leaf: Users don't need to provide their address as the leaf - the system handles it
Recommended Configuration:
Set initiatedByList to "All" (whitelist tree handles the restriction)
Set useCreatorAddressAsLeaf: true
Build Merkle tree from BitBadges addresses as leaves ["bb1...", "bb2...", "bb3..."]
Claim Code Trees (useCreatorAddressAsLeaf: false)
Purpose: Verify that the user possesses a valid claim code.
How It Works:
Manual Leaf: User must provide the actual claim code as the leaf
Code Verification: System verifies the provided code exists in the Merkle tree
User Responsibility: Users must know and provide their claim code
Recommended Configuration:
Set useCreatorAddressAsLeaf: false
Build Merkle tree from claim codes as leaves ["secret1", "secret2", "secret3"]
Post root hash on-chain as challenge
Distribute codes privately to users with leaf signatures
Security Features
Expected Proof Length
Critical Security Feature: All proofs must have the same length to prevent preimage and second preimage attacks.
Design Requirement: Your Merkle tree must be constructed so all leaves are at the same depth.
Max Uses Per Leaf
Control how many times each leaf can be used:
Setting
Behavior
Use Case
"0" or null
Unlimited uses
Public claim codes
"1"
One-time use
Single-use codes
"5"
Five uses maximum
Limited distribution
Critical Security Requirement: For claim code challenges (useCreatorAddressAsLeaf: false), maxUsesPerLeaf must be "1" to prevent replay attacks.
Replay Attack Protection
β οΈ CRITICAL SECURITY RISK: Non-address trees (claim codes) are vulnerable to front-running attacks.
The Problem:
User submits transaction with valid Merkle proof
Proof becomes visible in mempool (public blockchain)
Malicious actor sees the proof and front-runs the transaction
Original user's transaction fails, attacker gets the token
Why This Happens:
Merkle proofs for claim codes are reusable until consumed
Once in mempool, proofs are publicly visible
No built-in protection against proof reuse
The Solution: Leaf signatures provide cryptographic protection against this attack.
Challenge Tracking
Tracker System
Uses increment-only, immutable trackers to prevent double-spending:
Note the fact we use leaf indices to track usage and not leaf values.
Tracker Examples
Important: Trackers are scoped to specific approvals and cannot be shared between different approval configurations.
Tracker Management
Increment-Only: Once used, the number of uses cannot be decremented
Immutable: Tracker state cannot be modified
Best Practice: Use unique challengeTrackerId for fresh tracking of new approvals
Leaf Signatures
Protection Against Front-Running
Leaf signatures provide cryptographic protection against front-running attacks on claim code challenges.
How It Works:
Security Mechanism:
Address Binding: Each proof is cryptographically tied to a specific BitBadges address
Replay Prevention: Even if proof is intercepted, it cannot be used by other addresses
Mempool Safety: Intercepted proofs in mempool are useless to attackers
Implementation
Critical Benefits:
Front-Running Protection: Prevents attackers from stealing tokens via mempool interception
Address-Specific: Each proof is cryptographically bound to the intended recipient
Mempool Safety: Makes intercepted proofs useless to malicious actors
Required for Claim Codes: Strongly recommended for all non-address tree challenges
β οΈ IMPORTANT: For claim code challenges, leaf signatures are not just recommendedβthey are essential for security against front-running attacks.
Merkle Tree Construction
Standard Configuration
Critical Requirements
Same Layer: All leaves must be at the same depth
Consistent Proof Length: All proofs must have identical length
Test Thoroughly: Verify all paths work before deployment
Use Tested Options: Stick to the fillDefaultHash configuration
Merkle challenges and ETH signature challenges are very similar. The main difference is that Merkle challenges must also check that the signed message was pre-committed to in the tree, whereas ETH signature challenges only need to check that the signature is valid and not used before.
1-collection- -approvalId-uniqueID-0 β USED 1 TIME
1-collection- -approvalId-uniqueID-1 β UNUSED
1-collection- -approvalId-uniqueID-2 β USED 3 TIMES