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

GroupFields
Wiring (set in initialize)factory, usdg, token, creator, treasury, router, locker, floor
Snapshotted parametersvirtualUsdg, virtualTokens, curveSupply (after team allocation), lpSupply, graduationUsdg, tradeFeeBps, creatorShareBps, graduationFeeBps, antiSniperSeconds, cooldownSeconds, maxWallet, maxTx, maxBatchCommit, floorBps, lpLockSeconds
Curve stateusdgRaised, tokensSold, launchedAt, batchEndsAt, tradingOpenedAt, graduatedAt, settled
BatchbatchUsdg, batchTokensOut, batchRefund, batchTokensUnclaimed, batchRefundUnclaimed, batchCommitOf[buyer]
GuardslastBuyAt[to]
Post-graduationpair, 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

FunctionWhoPreconditions / revertsEffects
initialize(InitParams)factory, onceAlreadyInitializedSnapshots parameters, derives virtualUsdg, creates the team vesting schedule (VestingVault.create), sets batchEndsAt = now + batchSeconds, emits Launched
commitBatch(usdgIn)anyoneZeroAmount; BatchClosed if now ≥ batchEndsAt; CommitTooLarge if the wallet's total > maxBatchCommitPulls USDG, records the commitment, emits BatchCommitted(buyer, usdgIn, total)
commitBatchFor(payer, buyer, usdgIn)factory onlysameUsed for the creator's disclosed dev buy at creation
settleBatch()anyoneBatchOpen while now < batchEndsAt; no-op if already settledFills the batch (see Curve maths), pays fees, emits BatchSettled; graduates if the curve sold out
claimBatch()committersettles first if needed; NothingToClaimTransfers pro-rata tokens and refund, emits BatchClaimed
buy(usdgIn, minTokensOut, to)anyoneZeroAmount, 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)anyoneZeroAmount, CurveGraduated, BatchOpen, Slippage (also if tokensIn > tokensSold)Pulls tokens, pays USDG net of fee, emits Trade(msg.sender, false, …). Never rate-limited
Viewsprice, 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.