Skip to content
On this page

L2-18: Token Curated Registry

1. Problem

Build a decentralized, high-quality list using a token staking mechanism -- economic incentives drive the quality of list content. Applicants stake tokens to add entries to the list; any token holder can initiate a challenge (also requiring a stake); the community votes to decide whether an entry stays or goes; the winner receives the loser's staked tokens.

This is the Token Curated Registry (TCR) pattern -- a curation system that achieves decentralized moderation through cryptoeconomic incentives.

2. Why

The decentralized nature of Web3 requires decentralized content moderation mechanisms. The traditional internet relies on centralized platforms (Yelp, App Store, Trustpilot) to maintain list quality, but these platforms suffer from moderation bias, censorship, and data monopolies.

TCR solves these problems through economic incentives:

  • Staking as commitment: applicants commit real value (tokens) to vouch for their entry's quality
  • Challenge as checks-and-balances: anyone who discovers a low-quality entry can challenge it and win a reward
  • Voting as governance: token holders participate in curation decisions through voting
  • Economic security: dishonest submitters lose their stake; honest curators earn rewards

Use cases: curated DApp lists, token whitelists, content moderation, a decentralized Yelp alternative.

3. Solution

Contract Architecture

solidity
contract TokenCuratedRegistry {
    enum ListingState { None, Applied, Challenged, Accepted, Rejected }

    struct Listing {
        bytes32 listingHash;
        address applicant;
        address challenger;
        uint256 depositAmount;
        uint256 challengeAmount;
        uint256 applyTime;
        uint256 challengeTime;
        ListingState state;
        uint256 votesFor;
        uint256 votesAgainst;
        bool resolved;
    }

    IERC20 public immutable token;       // staking token
    uint256 public immutable minDeposit; // minimum stake amount
    uint256 public immutable challengePeriod; // challenge window
    uint256 public immutable votePeriod; // voting window

    mapping(bytes32 => Listing) public listings;
    mapping(bytes32 => bool) public isListed;
}

Lifecycle

1. applyListing(hash) → stake MIN_DEPOSIT → state = Applied
2. challenge(hash)    → [within challenge window] → challenger stakes → state = Challenged
3. vote(hash, accept) → token-weighted vote → forVotes / againstVotes accumulate
4. resolve(hash)      → [voting period ended] → winner gets both stakes
5. autoAccept(hash)   → [no challenge + timeout] → stake returned, entry accepted

Vote Weight

solidity
function vote(bytes32 listingHash, bool acceptVote) external {
    Listing storage listing = listings[listingHash];
    require(listing.state == ListingState.Challenged, "No active challenge");
    require(block.timestamp <= listing.challengeTime + votePeriod, "Vote ended");
    require(!hasVoted[listingHash][msg.sender], "Already voted");

    uint256 balance = token.balanceOf(msg.sender);
    require(balance > 0, "No tokens");

    if (acceptVote) {
        listing.votesFor += balance;
    } else {
        listing.votesAgainst += balance;
    }
}

4. Pitfalls Encountered

4.1 Sybil Attack Risk

Token voting weight is based on balanceOf(msg.sender). An attacker can distribute tokens across multiple addresses to gain more voting power. This is the same Sybil attack problem as in DAO governance -- TCR's solution is requiring a minimum stake (raising the cost of attack).

4.2 Voter Apathy

Most token holders do not participate in voting. If only a small minority votes, the TCR can be easily manipulated by a few active users. This requires mechanism design to incentivize voting participation -- for example, sharing a portion of the losing stake with voters.

4.3 Challenge Window Duration

challengePeriod too short → the community has insufficient time to discover and challenge low-quality entries. Too long → entries remain in an uncertain state on the list for too long. Typical values are 3-7 days.

4.4 Liquidity Lock-up of Staked Assets

The applicant's and challenger's tokens are locked (non-transferable and unusable) during the dispute period. If the challenge period + voting period is long (e.g., 14 days), large amounts of locked tokens could affect the token's liquidity.

5. Why the Pitfalls Happen

5.1

Token-balance-based voting is inherently not Sybil-resistant. Unlike identity-based systems (e.g., 1 person 1 vote), TCR assumes that economic incentives constrain behavior -- an attacker needs a large number of tokens to manipulate the vote, and holders of those tokens also want the TCR to function properly (otherwise the token loses value).

5.3

The challenge window is a trade-off between efficiency and quality. Financial lists (e.g., token whitelists) need short windows (fast decisions), while cultural lists (e.g., art curation) can tolerate longer windows (slow curation).

6. How to Resolve the Pitfalls

  • minDeposit must be high enough to make Sybil attack costs significant
  • Build off-chain notification systems (Twitter bot, Discord webhook) to alert the community when new entries are submitted
  • Consider a "voting reward" mechanism -- distribute a portion of the losing stake to voters (requires implementing proportional distribution in resolve)
  • Set challengePeriod and votePeriod appropriately for the use case

7. Technical Highlights

PointDescription
TCR lifecycleApplied → Challenged → Accepted/Rejected
Two-sided stakingBoth applicant and challenger must stake MIN_DEPOSIT
Token-weighted votingbalanceOf(voter) determines voting power
Economic adjudicationWinner receives both stakes; loser forfeits theirs
Timeout auto-acceptNo challenge + timeout → autoAccept returns stake
Challenge windowchallengePeriod determines the fair challenge timeframe
Double-vote preventionhasVoted mapping ensures each address votes only once

Built with AiAda