Appearance
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
minDepositmust 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
challengePeriodandvotePeriodappropriately for the use case
7. Technical Highlights
| Point | Description |
|---|---|
| TCR lifecycle | Applied → Challenged → Accepted/Rejected |
| Two-sided staking | Both applicant and challenger must stake MIN_DEPOSIT |
| Token-weighted voting | balanceOf(voter) determines voting power |
| Economic adjudication | Winner receives both stakes; loser forfeits theirs |
| Timeout auto-accept | No challenge + timeout → autoAccept returns stake |
| Challenge window | challengePeriod determines the fair challenge timeframe |
| Double-vote prevention | hasVoted mapping ensures each address votes only once |