Protocol
Curve lifecycle & state
State machine, storage, every external function with its checks and reverts, and every event.
One BondingCurve clone per token (Clones.clone(curveImplementation), initialised once by the factory). Storage is written by initialize and then only by the transitions below.
State machine
createToken ─▶ BATCH (now < batchEndsAt)
│ commitBatch / commitBatchFor(dev buy)
▼ batchEndsAt reached
SETTLING ── settleBatch() | first buy/sell/claimBatch ──▶ LIVE (settled = true, tradingOpenedAt)
│ anti-sniper window until batchEndsAt + antiSniperSeconds
│ buy / sell
▼ tokensSold == supplyOnCurve
GRADUATED (graduatedAt ≠ 0): only claimBatch remains
A batch that alone sells out the curve graduates inside settleBatch (test_batchAloneCanGraduate).
Storage
| Group | Fields |
|---|---|
Wiring (set in initialize) | factory, usdg, token, creator, treasury, router, locker, floor |
| Snapshotted parameters | virtualUsdg, virtualTokens, curveSupply (after team allocation), lpSupply, graduationUsdg, tradeFeeBps, creatorShareBps, graduationFeeBps, antiSniperSeconds, cooldownSeconds, maxWallet, maxTx, maxBatchCommit, floorBps, lpLockSeconds |
| Curve state | usdgRaised, tokensSold, launchedAt, batchEndsAt, tradingOpenedAt, graduatedAt, settled |
| Batch | batchUsdg, batchTokensOut, batchRefund, batchTokensUnclaimed, batchRefundUnclaimed, batchCommitOf[buyer] |
| Guards | lastBuyAt[to] |
| Post-graduation | pair, lpLockId, teamVestingId, creatorFeesEarned |
maxWallet and maxTx are absolute token amounts (bps of total supply, computed by the factory); maxBatchCommit is an absolute USDG amount (bps of the graduation raise).
Functions
| Function | Who | Preconditions / reverts | Effects |
|---|---|---|---|
initialize(InitParams) | factory, once | AlreadyInitialized | Snapshots parameters, derives virtualUsdg, creates the team vesting schedule (VestingVault.create), sets batchEndsAt = now + batchSeconds, emits Launched |
commitBatch(usdgIn) | anyone | ZeroAmount; BatchClosed if now ≥ batchEndsAt; CommitTooLarge if the wallet's total > maxBatchCommit | Pulls USDG, records the commitment, emits BatchCommitted(buyer, usdgIn, total) |
commitBatchFor(payer, buyer, usdgIn) | factory only | same | Used for the creator's disclosed dev buy at creation |
settleBatch() | anyone | BatchOpen while now < batchEndsAt; no-op if already settled | Fills the batch (see Curve maths), pays fees, emits BatchSettled; graduates if the curve sold out |
claimBatch() | committer | settles first if needed; NothingToClaim | Transfers pro-rata tokens and refund, emits BatchClaimed |
buy(usdgIn, minTokensOut, to) | anyone | ZeroAmount, CurveGraduated, BatchOpen (via implicit settle), Slippage; during the anti-sniper window MaxTx, MaxWallet, Cooldown (checked against to) | Pulls usdgUsed + fee, updates reserves, transfers tokens, pays fees, emits Trade(to, true, …); graduates on sell-out |
sell(tokensIn, minUsdgOut, to) | anyone | ZeroAmount, CurveGraduated, BatchOpen, Slippage (also if tokensIn > tokensSold) | Pulls tokens, pays USDG net of fee, emits Trade(msg.sender, false, …). Never rate-limited |
| Views | — | — | price, progressBps, quoteBuy, quoteSell, isGraduated, inBatch, inAntiSniperWindow |
All state-changing functions are nonReentrant.
Anti-sniper window, precisely
The window is [batchEndsAt, batchEndsAt + antiSniperSeconds) — anchored to the end of the batch, not to when settlement happened, so the schedule is known before launch. Inside it a buy must satisfy tokensOut ≤ maxTx, balanceOf(to) + tokensOut ≤ maxWallet and now ≥ lastBuyAt[to] + cooldownSeconds. Batch fills are not subject to these limits (they are capped by maxBatchCommit instead).
Events
event Launched(address indexed token, address indexed creator, uint64 batchEndsAt);
event BatchCommitted(address indexed buyer, uint256 usdgIn, uint256 total);
event BatchSettled(uint256 usdgUsed, uint256 tokensOut, uint256 clearingPrice, uint256 refund);
event BatchClaimed(address indexed buyer, uint256 tokensOut, uint256 refund);
event Trade(address indexed trader, bool indexed isBuy, uint256 usdgAmount, uint256 tokenAmount, uint256 price, uint256 fee);
event Graduated(address indexed token, address indexed pair, uint256 usdgLiquidity, uint256 tokenLiquidity, uint256 lpLockId, uint256 floorUsdg);
Trade.usdgAmount is usdgUsed on a buy and usdgOut (net) on a sell; price is the spot price after the trade. Graduated.lpLockId is type(uint256).max when the LP was burned.
Errors
AlreadyInitialized, OnlyFactory, ZeroAmount, BatchOpen, BatchClosed, BatchNotSettled, NothingToClaim, CurveGraduated, Slippage, MaxTx, MaxWallet, Cooldown, CommitTooLarge.