Every swap pays the holders who stayed.
DualCurve takes {{ feePct }}% of the ETH leg on every trade through its Uniswap v4 hook and routes it into two distribution curves. One pays the top {{ smallTopN }} holders every {{ smallThreshold }} ETH; the other opens a Merkle round for the top 20 every {{ largeThreshold }} ETH. No treasury, no emissions, no governance token.
Two curves, one fee stream.
Both curves fill from the same fees and empty the same way. The only difference is the height of the ceiling, which is what makes one pay constantly and the other pay rarely and large.
Five steps, all on-chain.
Your standing in the ranking.
Rewards are computed from your balance at snapshot time, not at claim time.
Held {{ me.held }}, above the 300-block minimum. Your balance must stay at or above 0.1% of supply through the snapshot to receive the push.
Trade ETH against DCRV.
The {{ feePct }}% hook fee applies to the ETH leg only: taken from your input on a buy, from your output on a sell. It is never held: the hook forwards it to the router inside the same transaction.
The ranking is the contract.
Every transfer updates a bounded 100-slot heap. Holding under 0.1% of supply evicts you; holding under 300 blocks makes you visible but not yet eligible.
Every payout, since block one.
SmallCurve pushes settle in a single transaction. LargeCurve rounds publish a Merkle root and stay open for 30 days.
Pull your Merkle share.
Your leaf is keccak256(address ++ amount). The contract verifies the proof against the round's root, marks you claimed, then transfers, in that order. Anything unclaimed after the deadline is swept into SmallCurve and paid to the top 100 instead.
Seven contracts. One direction of flow.
Nothing loops back. ETH moves from the pool to the hook, from the hook to the router, from the router into the two curves, and from the curves to holders. Ranking and snapshots are read-only inputs to that flow.
{{ n.title }}
{{ n.body }}
DualCurve protocol specification
A complete description of the fee-capture hook, the ranking heap, the snapshot security model and both distribution curves as deployed on Robinhood Chain. Every constant quoted here is compiled into the bytecode and cannot be changed after renouncement.
A fee that pays the register, not a treasury
DualCurve is a fixed-supply ERC-20 whose trading fee is captured by a Uniswap v4 hook and redistributed to its own largest, longest-held holders. There is no treasury address, no emission schedule and no governance token. The protocol is seven contracts: a token, a hook, a router, a ranking heap, two snapshot managers and two distributors.
Value moves in exactly one direction. The hook takes {{ feePct }}% of the ETH leg of every swap and forwards it to the router in the same transaction. The router splits it {{ smallSplitPct }}/{{ largeSplitPct }} between two accumulators. Each accumulator has a ceiling; crossing it locks the balance, requests a ranking snapshot, then pays out once a mandatory delay has passed. SmallCurve pushes ETH to the top {{ smallTopN }} holders. LargeCurve publishes a Merkle root for the top 20 and lets them pull.
Seven contracts, no cycles
The deployment script broadcasts the seven contracts and wires them with one-shot setters that revert once set, so the topology below is final. Ranking and snapshots are read-only inputs: no contract downstream of them can write back.
The hook, both legs
The hook is attached to a single ETH/DCRV v4 pool and holds four permissions, which the PoolManager enforces through the low bits of the hook's own address: the deployment mines a CREATE2 salt until uint160(addr) & ALL_HOOK_MASK == 0xCC. Only exactIn swaps are taxed; exact-output swaps and any non-ETH leg pass through untouched with a zero delta.
{{ l.body }}
The fee rate is capped in the bytecode at 1,000 bps (10%) and was deployed at {{ feeBps }} bps. The setter that could raise it within that cap is unreachable: the hook has renounced ownership. A fee of zero, or a computed fee that truncates to zero, short-circuits to a zero delta rather than reverting.
Splitting in basis points
The router is deliberately dumb: it holds no balance between transactions and has no withdrawal path. It receives ETH, computes two shares, and calls deposit() on each accumulator, which in turn verify that the caller is the router. Both a direct transfer and an explicit routeFees() call route identically.
A bounded heap, updated on every transfer
The token's _update override notifies the ranking with both parties' new balances on every transfer, mint and burn. The ranking keeps a 100-slot min-heap whose root is the smallest balance in the set, so admission is an O(1) comparison and insertion is O(log n).
Packing the balance above the address makes the encoded integer sort by balance, which is what lets a plain min-heap rank holders with no comparator. 96 bits holds about 7.9 × 10²⁸, comfortably above the 10²⁷ wei-scale total supply, and an overflow reverts rather than truncating.
A snapshot can never be used in the transaction that made it
This is the protocol's central security property. Crossing a threshold does not pay anyone; it records a request with an executeAfter block. Until that block, executeSnapshot reverts with TooEarlyToExecute. Any attempt to reshuffle the ranking inside the window is public, and execution is permissionless, so an honest party can settle the snapshot before an attacker can finish positioning.
Execution also intersects the top-N list with getEligibleHolders(), so hold time is enforced a second time at settlement. The stored snapshot is immutable: holders, balances and the block it was taken at.
Push distribution, every {{ smallThreshold }} ETH
On the deposit that crosses the ceiling, the accumulated balance is moved to lockedForDistribution, the accumulator resets to zero, and a snapshot is requested. Once executable, anyone can call executeDistribution(), which iterates the snapshot and transfers each holder's pro-rata share.
Merkle rounds, every {{ largeThreshold }} ETH
LargeCurve never pushes. At the ceiling it computes each holder's share, drops zero-share entries, builds a Merkle tree in memory and stores only the root, the total and a deadline. Holders then pull with a proof. This keeps a 20-holder settlement inside one transaction regardless of how hostile the receiving addresses are.
Deployed constants
What can and cannot be changed
{{ r.title }}
{{ r.body }}
This document summarises protocol design. The contract source and the deployment playbook are the authoritative reference. Licensed MIT. Nothing here is an offer, a solicitation, or investment advice.