Why a batch auction
Most launches sell tokens one transaction at a time. On a bonding curve every buyer moves the price for the next one, so arriving first is worth money, and bots compete to be first. The crowd that actually decides the price ends up paying several times what the first block paid.
A uniform-price batch auction removes the queue. Bids are collected over a window, then cleared together. Arrival order has no effect on price or allocation, so there is nothing to snipe.
Finding the clearing price
Each bid is a pair: a budget in USDG and a max price. At price p a bid demands budget / p tokens if its max is at least p. Total demand falls as price rises; supply S is fixed.
// levels sorted by max price, highest first; B = budget above the current level for level k: if B ≤ S·p_k ≤ B + budget_k: // this level is marginal price = p_k; fill_k = (S·p_k − B) / budget_k; break B += budget_k if next_p ≤ B / S < p_k: // price falls between two levels price = B / S; break if no price: price = reserve // undersubscribed, rest is burned
Bids above the price fill completely and spend their whole budget; bids at exactly the price share what's left pro-rata; bids below it are refunded. Everyone filled pays the same price.
Try it
| Bid | Budget USDG | Max price | Tokens | Refund | Status |
|---|
Random close — the candle
Candela is Latin for candle, and the name comes from the English "candle auction": bidding ran until an inch of candle burned out, so nobody knew the exact end. An open book invites a last-second game: wait, watch the price, bid at the end. Candela ends the round at a moment nobody knows in advance — somewhere in the 5 minutes after the scheduled close. In production the offset comes from a block hash that doesn't exist yet when the window opens:
actualClose = scheduledClose + uint256(blockhash(closeBlock)) % 300
Bids that land after the hidden close are refunded in full. Cancels are disabled for the last 10 minutes before the scheduled close, so large bids can't be pulled at the end to move the price.
Settlement and the pool
- Filled bidders receive
spent / pricetokens and claim refunds of unspent USDG. - A share of the raise (set by the creator, 10–80%) and the matching amount of tokens seed a pool at the clearing price, so the first trade starts where the auction ended instead of jumping.
- Unsold tokens are burned. The creator receives the rest of the raise minus a 1.5% protocol fee.
Compared with other launch formats
| Format | Who sets the price | First-block edge | Overpay risk |
|---|---|---|---|
| Bonding curve | Order of arrival | Large | Late buyers pay many times more |
| Fixed-price sale | The team | Speed decides who gets in | Mispriced; lottery or gas war |
| Dutch auction | Falling clock | Timing game | Waiting too long means missing out |
| Commit-then-launch | Target, then market | Moves to launch moment | Price set after commits |
| Candela | The whole book at once | None | Never above your own max price |
Contract sketch
Not deployed. This is the interface the production contract would expose; clearing itself would run off-chain and be verified on-chain against the committed bid set.
interface ICandela { function createAuction(address token, uint128 supply, uint128 reserve, uint64 opens, uint64 closes, uint16 lpBps) returns (uint256 id); function bid(uint256 id, uint128 budget, uint128 maxPrice) returns (uint256 bidId); function cancel(uint256 id, uint256 bidId); // reverts inside the lock window function settle(uint256 id, uint128 price, uint128 marginalFillE18, bytes32 bookRoot); // anyone; checked against stored bids function claim(uint256 id, uint256[] bidIds); // tokens + refund } // chain: Robinhood Chain (4663) · payment: USDG · tokens escrowed at creation
Limits
- Bidders, wallets and amounts are generated from a seed; rounds repeat on a schedule.
- Your bids and balance live in your browser's storage only.
- The random close is derived from a seed, not a block hash.
- Nothing is audited, deployed or connected to a wallet.