Protocol
Curve maths
The exact arithmetic of the bonding curve: reserves, price, quotes, fees and rounding, as implemented in BondingCurve.sol.
Every number the app shows for a curve is computed the same way the contract computes it. The app's app/lib/curve.ts is a bigint mirror of these formulas; the simulator and the trade panel use it, and test_quotesMatchExecution in the fork tests checks that quotes equal executed amounts.
Units
| Quantity | Unit |
|---|---|
USDG amounts (usdgIn, usdgRaised, virtualUsdg, fees) | 6-decimal integers (1 USDG = 1e6) |
Token amounts (tokensSold, virtualTokens, curveSupply) | 18-decimal integers |
price() | USDG per whole token, scaled by 1e18 (1e18 = 1 USDG). Computed as u * 1e30 / t |
| bps | parts per 10,000 (BPS = 10_000) |
Reserves
The curve is a constant-product AMM whose USDG side starts with a virtual balance so the first token has a non-zero price:
supplyOnCurve = curveSupply − teamAllocation // set once in initialize()
virtualUsdg = graduationUsdg × (virtualTokens − supplyOnCurve) / supplyOnCurve
u = virtualUsdg + usdgRaised // USDG reserve
t = virtualTokens − tokensSold // token reserve
k = u × t
price = u / t // reported as u × 1e30 / t
Deriving virtualUsdg from the graduation raise means that selling exactly supplyOnCurve tokens always raises exactly graduationUsdg, whatever team allocation the creator chose.
With the defaults (virtualTokens = 1,073,000,000e18, curveSupply = 800,000,000e18, no team, graduationUsdg = 12,400e6): virtualUsdg = 12,400 × 273 / 800 = 4,231.5 USDG.
Buy
quoteBuy(usdgIn) and buy(usdgIn, minTokensOut, to):
fee = usdgIn × tradeFeeBps / BPS
net = usdgIn − fee
tokensOut = t − ceil(k / (u + net)) // constant product, rounded against the buyer
remaining = supplyOnCurve − tokensSold
if tokensOut ≥ remaining: // the buy would overshoot the curve
tokensOut = remaining
usdgUsed = ceil(k / (t − remaining)) − u // exact USDG to buy out the curve
usdgUsed = min(usdgUsed, net) // rounding guard
fee = usdgUsed × tradeFeeBps / (BPS − tradeFeeBps) // fee re-based on what was used
else:
usdgUsed = net
The buyer transfers usdgUsed + fee; anything above that stays in the buyer's wallet (nothing is pulled and refunded). buy reverts with Slippage if tokensOut < minTokensOut. When tokensSold == supplyOnCurve after the buy, the same transaction graduates the curve.
Sell
quoteSell(tokensIn) and sell(tokensIn, minUsdgOut, to):
gross = u − ceil(k / (t + tokensIn)) // rounded against the seller
fee = gross × tradeFeeBps / BPS
usdgOut = gross − fee
usdgRaised decreases by gross (so the curve's own accounting stays exact) and the fee is paid from the gross. tokensIn > tokensSold reverts with Slippage.
Fees
toCreator = fee × creatorShareBps / BPS → transferred to creator immediately, added to creatorFeesEarned
toTreasury = fee − toCreator → transferred to treasury
No fee is ever held by the curve.
Batch settlement
The batch is settled as one buy of batchUsdg:
fee = batchUsdg × tradeFeeBps / BPS
(tokensOut, usdgUsed) = _quoteBuy(batchUsdg − fee)
if usdgUsed < batchUsdg − fee: fee = usdgUsed × tradeFeeBps / (BPS − tradeFeeBps) // batch alone sold out the curve
batchRefund = batchUsdg − usdgUsed − fee
clearingPrice = usdgUsed × 1e30 / tokensOut // emitted in BatchSettled
claim(buyer) = tokensOut × commit / batchUsdg, refund × commit / batchUsdg
Worked example (defaults, empty curve)
| Buy | Fee | Net | Tokens out | Avg price | Price after |
|---|---|---|---|---|---|
| 100 USDG | 1.00 | 99.00 | ≈ 24.5M | ≈ 0.0000040 | ≈ 0.0000041 |
| 1,000 USDG | 10.00 | 990.00 | ≈ 203.4M | ≈ 0.0000049 | ≈ 0.0000060 |
| 5,000 USDG | 50.00 | 4,950.00 | ≈ 578.5M | ≈ 0.0000086 | ≈ 0.0000186 |
| 12,525.25 USDG | 125.25 | 12,400.00 | 800M (sells out) | 0.0000155 | graduates |
Figures rounded; the simulator prints the exact bigint results on the live parameters.
Progress
progressBps() = tokensSold × BPS / supplyOnCurve. The app shows it as a percentage and switches tabs at 60% ("Graduating").