DRAW — Whitepaper
Every buy is a ticket. The vault only grows — until one buy takes all of it.
Abstract
DRAW is a Uniswap V4 pool that runs a lottery inside a single swap hook. Every buy of $DRAW pays one flat fee — 1% of the ETH spent, taken in WETH off the trade — into a vault, and registers the buy as a numbered ticket. The draw runs one step behind: a buy settles only the tickets ahead of it whose blocks have closed, and a ticket's number is the keccak hash of its own buy block — a value that does not exist when the buy is signed. Clear the difficulty mark and the vault, less a 1/16 seed, transfers to that wallet in the same transaction; miss, and the vault rolls forward untouched. After 500 cold draws the mark eases and the odds double; thirteen rungs down a hit is certain, so the vault must pay out to one trader. No operator, no owner key, no off switch; the fee is fixed in the bytecode.
1 · Motivation
An automated market maker prices swaps; it does not run prizes. Uniswap V2 fixed a flat 0.30% fee, V3 added fee tiers, and V4 moved swap-time logic into a hook called through the pool lifecycle — used so far mostly to reprice the swap. DRAW uses that lifecycle differently: a flat charge and a draw, the trading curve untouched.
Bolting a lottery onto a plain token fails two ways. A transfer-tax ERC-20 can only tax itself; it cannot reach the WETH leg of a swap, so its jackpot is the volatile token itself and its draw needs an off-chain trigger or operator — and an owner key is the first thing a careful buyer screens for. On-chain randomness is the second trap: a fixed, public, address-bound difficulty target invites unbounded offline search, while Chainlink VRF removes the grind only by adding an operator to fund each draw. DRAW closes both. The hook skims the fee in WETH — the only component inside the swap that can reach the counter-currency — so the prize is real ETH; and the entropy is the buyer's own commit-block hash, sealed only after the buy is mined, so there is nothing to grind or precompute.
2 · Mechanic Specification
State. The hook stores six fields. vault (uint256) is the WETH in the pot. ticket (uint64) counts tickets ever sold; head (uint64) is the last ticket drawn, so the unsettled queue is (head, ticket]. open (uint32) counts cold draws since the last win or ease, and mark (uint8) is the difficulty, opening at 13. A slip mapping holds each unsettled ticket's buyer and commit block, deleted on settlement. The four counters pack into one storage slot.
Update rule. A drawn ticket produces a 256-bit number, measured against the mark:
h = keccak256( blockhash(b) ‖ a ‖ n ) interpreted as uint256
hit(h, m) ⇔ h ≤ (2^256 − 1) >> m
P(hit | m) = 2^(256 − m) / 2^256 = 2^(−m)
where b is the ticket's own buy block, a the buyer's address, n the ticket id, and m the current mark. The same logic in Solidity:
bytes32 seed = blockhash(s.blk); // 0 once aged past 256 blocks
uint256 h = uint256(keccak256(abi.encodePacked(seed, s.buyer, head)));
if (seed != 0 && h <= type(uint256).max >> mark) {
_win(s.buyer); // hit
} else {
if (++open >= WINDOW) { open = 0; if (mark != 0) --mark; } // miss
}
At mark = 13 the winning slice is 2^(−13) = 1/8,192; each ease decrements the mark and doubles the slice. A win pays out and resets directly from the vault:
uint256 keep = vault >> 4; // 1/16 stays back to seed the next draw
uint256 prize = vault - keep;
vault = keep;
mark = START; // 13 — odds snap back to strict
Lifecycle. One hook method carries logic: beforeSwap. It rejects non-PoolManager callers and ignores sells and exact-output swaps. On a qualifying buy it draws up to 16 queued tickets whose commit blocks have closed, skims 1% of the WETH input (fee = amountIn / 100) into vault via pm.take, registers the new slip, and returns a matching BeforeSwapDelta. No afterSwap or liquidity hooks are used — the pool's LP behaviour and 0.30% fee stay standard.
Invariants. (i) mark ∈ [0, 13], so P(hit) ∈ [1/8,192, 1] — the odds never fall below the opening rung nor exceed certainty. (ii) head only increments and each step deletes one slip, so every ticket is settled exactly once. (iii) The vault falls only on a win, which always leaves vault >> 4 behind. (iv) A ticket is drawn only after its commit block closes, so blockhash(b) reads a sealed past block — or zero, once aged out.
Off-chain dependencies. None — no oracle, no keeper, no operator. Randomness is the EVM's native blockhash, and the draw is triggered entirely by swap flow. A frontend only displays the vault and odds ladder.
3 · Worked Scenarios
Scenario 1 — Two buys, one rate. Setup: vault = 8.000 ETH, mark = 11 (1 in 2,048). A small buyer buys with 0.05 WETH; the hook skims 1% — 0.0005 WETH — and issues one ticket. Minutes later a whale buys with 50 WETH; the hook skims 0.500 WETH and issues one ticket. Outcome: the vault stands at 8.5005 ETH. The whale fed the pot a thousand times more, yet holds exactly one ticket — no cheap-ticket edge for size, no flat toll on the small buy.
Scenario 2 — A miss, and an ease. Setup: vault = 30.000 ETH, mark = 9 (1 in 512), open = 499. A later buy settles a queued ticket whose commit block sits inside the 256-block window, so seed = blockhash(b) is real entropy. The hook computes h = keccak256(seed ‖ buyer ‖ id) and tests it against (2^256 − 1) >> 9. The number exceeds the threshold — a miss, as expected at 1 in 512. open ticks to 500, hits WINDOW, resets, and the mark eases from 9 to 8. Outcome: the ticket missed, but the 500th cold draw doubled the odds — the next window pays 1 in 256. The vault held at 30.000 ETH; it only climbs.
Scenario 3 — A hit, and the reset. Setup: vault = 41.920 ETH, mark = 3 (1 in 8). A buy settles a queued ticket whose number lands inside the winning slice, h ≤ (2^256 − 1) >> 3. The hook sets keep = vault >> 4 = 2.620 ETH and prize = 39.300 ETH, writes vault = 2.620 ETH, snaps mark to 13, zeroes open, and transfers 39.300 WETH to the winner. Outcome: 39.300 ETH lands in the winner's wallet in the same transaction that settled the ticket — no claim page, no announcement. The vault resets to its 2.620 ETH seed and the next draw opens cold at 1 in 8,192. Had mark instead reached 0, h ≤ (2^256 − 1) >> 0 holds for every hash — the next ticket wins with certainty.
4 · Security Model
Commit-blind randomness. A ticket's number depends on blockhash(b), the hash of the buyer's own commit block — a value that does not exist when the buy is signed. For any ticket settled while b is inside the 256-block window — every ticket in an active pool — there is nothing to precompute, simulate, or grind. The buyer's address and ticket id are folded into the hash, so a winning result cannot be ported to another wallet.
Proposer influence. The one party with leverage over blockhash(b) is the validator proposing the buyer's commit block. A proposer who is also the buyer can vary that block's contents to shift its hash — but only as a single, costly grind over a block they must actually produce — not the cheap offline search a fixed public target permits.
Quiet-pool information edge. In an active pool a stranger's later buy settles a ticket at a moment its holder does not choose. In a quiet pool the holder may be the only one who can settle their own ticket — and that holder can wait for the commit block to seal, read the hash, and submit only on a known result. The edge is in timing, not probability.
Expiry. DRAW does not fall back to a recent, knowable blockhash when a ticket ages out — that would hand a self-settling buyer a hash to grind. If a pool goes silent past 256 blocks (~51 minutes), the commit hash is gone, blockhash(b) returns zero, and the ticket settles as a miss — that buyer paid the fee and the draw never came.
Re-entrancy and standard hazards. beforeSwap reverts for any caller but the PoolManager. The 16-ticket settle cap bounds the gas of any buy and stops a same-block burst forcing one buyer to clear the backlog. Payouts are a plain WETH transfer to the recorded buyer — no claim function, no withdrawal queue, no privileged path to the vault.
5 · Parameters & Deployment
- Draw fee: 1% (100 bps) of the WETH input, skimmed per buy
- LP fee: 0.30% (3,000 bps), static
- Easing window: 500 cold draws per rung
- Opening mark: 13 → odds 1 in 8,192
- Rungs: thirteen eases, from 1 in 8,192 to 1 in 1
- Cold draws to certainty: ~6,500
- Seed retained on a win: 1/16 of the vault (
vault >> 4) - Settle burst cap: 16 tickets per buy
- Live randomness window: 256 blocks (~51 minutes)
- Randomness source: EVM
blockhash— no oracle - Token supply: 1,000,000,000 $DRAW, fixed — no mint, no burn, no owner
- AMM: Uniswap V4 PoolManager —
0x000000000004444c5dc75cb358380d2e3de08a90(mainnet) - WETH:
0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2 - Hook contract: pending
- Token contract: pending
6 · Limitations & Future Work
DRAW is a draw, not a priced bet. At the opening mark a ticket wins once in 8,192 and pays 15/16 of the vault, so a buy is fairly priced only once the vault holds roughly 8,700 times the 1% fee that buy paid in. Vault growth and each ease pull that line down, but every win resets the vault to a 1/16 seed and the mark to 13 — so expected value drops far below zero again right after each payout. The design claims a vault of real ETH, odds that only shorten within a round, and a draw that cannot fail to resolve; it does not claim a standing edge for the buyer.
DRAW cannot protect a buyer against a quiet pool: a ticket is drawn only by a later buy, so settlement tracks flow the contract cannot control. Future work includes a settlement path backed by EIP-2935 historical block hashes, extending verifiable entropy beyond the 256-block window and closing the expiry case without an operator.
7 · References
- Uniswap V4 Core — the PoolManager and hook interfaces this design is built on.
- Uniswap V4 Hooks — the beforeSwap lifecycle method and BeforeSwapDelta accounting used here.
- Uniswap V4 Overview — the singleton-and-hooks architecture that lets swap-time logic skim the counter-currency.
- Uniswap V2 Whitepaper — the constant-product pool and flat 0.30% fee DRAW leaves untouched.
- Uniswap V3 Whitepaper — the selectable fee-tier prior art DRAW does not modify.
- Solidity — Units and Globally Available Variables — defines blockhash and the 256-block window the randomness depends on.
- EIP-4399: Supplant DIFFICULTY opcode with PREVRANDAO — post-Merge block randomness and the limits of proposer influence.
- EIP-2935: Serve historical block hashes from state — extends historical block-hash access beyond 256 blocks; the basis for the future-work fix.
- Chainlink VRF — operator-funded verifiable randomness, the alternative DRAW deliberately avoids to stay operator-free.
The draw cannot fail to pay. The only question left open is who is holding the ticket when it lands.