CTO Voting
Community Takeover lets token holders replace an absent or hostile deployer. The deployer role carries a fee share and a metadata ownership — nothing else — so a takeover transfers exactly that.
Passing conditions
All four must hold:
| Condition | Value |
|---|---|
| YES quorum | 40% YES of circulating |
| Distinct voters | at least 10 |
| Majority margin | YES ≥ 1.1 × NO |
| Voting closed | after 259,200 s (3 days), or early once the first three already hold |
Quorum is measured on YES votes alone, not on turnout. A vote where everyone participates but only 30% support the takeover fails.
Circulating supply
The quorum denominator excludes tokens that cannot vote:
circulating = TOTAL_SUPPLY
- balanceOf(LP_CUSTODIAN)
- balanceOf(DEAD_ADDRESS)
LP_CUSTODIAN is whichever contract actually holds the pooled tokens on that chain, which is not always the contract that runs the pool. On Uniswap V4 the PoolManager does both. On PancakeSwap Infinity custody is split out into the Vault, and the CLPoolManager holds a zero balance — pointing the constant at it subtracts nothing and inflates the denominator. On Gnosis there is no singleton at all: each pair has its own V3 pool contract, resolved through the factory at read time, and before graduation the lookup returns the zero address so LP balance is treated as zero.
Initiating
initiate_cto_vote(token) requires:
- The exact chain fee — Base 0.01 ETH, BSC 0.03 BNB, Polygon 240 POL, Robinhood 0.01 ETH, Gnosis 24 xDAI. Exact, with no overage refund, to avoid stranded funds and failed refunds to contract wallets.
- 60,000,000 × 10⁹ (60M, 5% of supply) approved to the hook. The stake transfers into the vote contract and counts as the initiator's YES vote.
- No vote already active for that token.
- The token is not $LP24.
The hook clones CTOVoteTemplate, records it in active_cto_votes[token], and initializes it. The fee goes to protocolBalance. All state is written before any external call.
Voting
Voting is by token deposit. vote(amount, support) transfers tokens into the vote contract and records a choice; add_votes(amount) adds to an existing position in the same direction. One position per address — you cannot vote both ways or switch sides.
Incumbent weighting
The current deployer's stake counts at 2× weight in the tally, as an incumbency defence. The stored balance is always the real amount, so withdrawal is unaffected — only the tally is weighted.
Live during voting, frozen after
While voting is open the quorum denominator is read live, so buying tokens to win a vote works — that is legitimate and, for the token, bullish. Once end_vote() runs, the outcome is computed once and stored in vote_passed. Finalization reads that stored result rather than re-checking, so post-close balance changes cannot flip a decided vote.
Flash-loan defence
withdraw_tokens() requires block.number > voting_ended_block. A flash-funded buy → vote → end → withdraw → repay sequence cannot complete inside one transaction, because the withdrawal is unavailable until at least the next block.
Finalization
finalize() calls back into the hook, which performs the transition state-first and then makes every external call best-effort:
_changeDeployer(token, new_deployer)
├─→ tokenInfo[token].deployer = new_deployer
├─→ deployerBalance[token] = 0
├─→ active_cto_votes[token] = 0
├─→ Factory.moveDeployerToken(token, old, new) // best effort
├─→ pay out old balance to old deployer // best effort
│ └─→ on failure, roll back into deployerBalance (now owned by the new team)
└─→ TokenSocials.transfer_owner(token, new_deployer)
Nothing a third party controls can block finalization. A malicious outgoing deployer that reverts on receive simply forfeits the payout into the balance the new team inherits. A guard rejects a zero new_deployer, which would otherwise lock the fee balance permanently.
Failed votes
A vote that ends without passing leaves active_cto_votes[token] set, which blocks new attempts. Anyone can call cancel_failed_cto(token) to clear it, after asserting the vote ended, was not finalized, and did not pass. Voters withdraw their deposits regardless of outcome.
The number of attempts per token is recorded in cto_vote_count[token], which is informational — there is no cap.
What a takeover does not do
The new deployer receives the fee share and metadata ownership. They cannot mint, cannot move liquidity, cannot change any protocol constant, and cannot alter the token's contract. Those capabilities do not exist for anyone.