Launchpad Math

Bonding-curve and fee formulas behind the GIWA token launchpad

This page derives every formula behind the Launchpad architecture - the constant-product bonding curve, the two distinct fee mechanisms, and the max-buy cap. See the Launchpad overview for the plain-language version of these same guarantees.

Bonding Curve

Each pool prices trades against a constant-product curve seeded with virtual reserves - non-withdrawable reserves that exist only for pricing math, not real liquidity. This keeps the starting price low and the curve smooth without needing a withdrawable seed, and without the gas cost or audit surface of a Bancor-style power curve.

Let xvx_v be the pool's virtual ETH reserve (VIRTUAL_ETH_RESERVE, a fixed constant) and xrx_r its real ETH reserve (realEthReserve, starts at zero, grows with every buy). Let yvy_v be the virtual token reserve (virtualTokenReserve) and yry_r the real token reserve (realTokenReserve) - the tokens actually available to be bought. The curve prices against the effective reserves x=xv+xrx = x_v + x_r and y=yv+yry = y_v + y_r:

xy=kx \cdot y = k

virtualTokenReserve is snapshotted equal to curveSupply at pool creation, and realTokenReserve also starts at curveSupply - so at creation, xr=0x_r = 0, yr=yv=curveSupplyy_r = y_v = \text{curveSupply}, giving:

k=VIRTUAL_ETH_RESERVE×2×curveSupplyk = \text{VIRTUAL\_ETH\_RESERVE} \times 2 \times \text{curveSupply}

A buy of Δx\Delta x gross ETH, after the applicable fee (see below) is deducted, nets (Δx)net=Δx(1feeBps10000)(\Delta x)_{\text{net}} = \Delta x \cdot \left(1 - \dfrac{\text{feeBps}}{10000}\right) into the effective ETH reserve, and returns:

Δy=(Δx)netyx+(Δx)net\Delta y = \frac{(\Delta x)_{\text{net}} \cdot y}{x + (\Delta x)_{\text{net}}}

tokens out - the standard constant-product swap formula, with xx and yy the pre-trade effective reserves. Sells run the same formula in reverse (tokens in, ETH out), and are only valid pre-graduation - once a pool graduates, trading moves to LaunchpadAmmPair directly.

realTokenReserve can never exceed curveSupply - it only decreases on a buy and increases (back up, never past its starting value) on a sell. This is enforced as an invariant, fuzz-tested across thousands of randomized buy/sell sequences.

Virtual Reserves, the Finite Depletion Boundary, and totalNetEthRaised

A subtlety falls directly out of the curve math above, and it's worth spelling out because it shapes both the dynamic fee and graduation trigger below - an earlier version of this page explained it via an "asymptote" that made VIRTUAL_ETH_RESERVE unreachable by construction. That reasoning was wrong. Here's the corrected derivation.

virtualTokenReserve is snapshotted to a fixed value (yv=Cy_v = C, curveSupply) at pool creation and never tracks realTokenReserve as the latter depletes. Because yvy_v alone already equals CC, the effective token reserve y=yv+yry = y_v + y_r is bounded below by CC even as yry_r (realTokenReserve) depletes all the way to 00 - yy only ever ranges over the finite interval

y[C, 2C]as yr ranges over [0, C]y \in [C,\ 2C] \quad \text{as } y_r \text{ ranges over } [0,\ C]

yy itself never approaches zero. That means yry_r reaching exactly 00 (the curve's entire real token supply bought out) is a finite, exactly reachable boundary - not an asymptote requiring unbounded capital - unlike a standard two-real-sided constant-product AMM (neither side virtually floored), where draining one real side to zero really would require infinite input.

At that boundary (yr=0y_r = 0, so y=Cy = C):

x=ky=VIRTUAL_ETH_RESERVE×2CC=2×VIRTUAL_ETH_RESERVEx = \frac{k}{y} = \frac{\text{VIRTUAL\_ETH\_RESERVE} \times 2C}{C} = 2 \times \text{VIRTUAL\_ETH\_RESERVE} xr=xVIRTUAL_ETH_RESERVE=VIRTUAL_ETH_RESERVEx_r = x - \text{VIRTUAL\_ETH\_RESERVE} = \text{VIRTUAL\_ETH\_RESERVE}

so the real ETH reserve reaches VIRTUAL_ETH_RESERVE exactly - at a finite point, not asymptotically.

Why VIRTUAL_ETH_RESERVE Is 2× MAX_RAISE, Not 1×

Generalize with VIRTUAL_ETH_RESERVE=R×MAX_RAISE\text{VIRTUAL\_ETH\_RESERVE} = R \times \text{MAX\_RAISE} for some ratio RR. At the moment totalNetEthRaised (and, absent any sells, xrx_r too) first reaches MAX_RAISE:

x=VIRTUAL_ETH_RESERVE+MAX_RAISE=(R+1)MAX_RAISEx = \text{VIRTUAL\_ETH\_RESERVE} + \text{MAX\_RAISE} = (R + 1)\,\text{MAX\_RAISE} y=kx=2RCR+1y = \frac{k}{x} = \frac{2RC}{R + 1}

so the real tokens still sitting in the curve at that moment are:

yr=yC=CR1R+1y_r = y - C = C \cdot \frac{R - 1}{R + 1}

At R=1R = 1 - a previous fix that set VIRTUAL_ETH_RESERVE equal to MAX_RAISE - this is exactly 00: an ordinary, sell-free graduation would drain the curve of real tokens at the exact moment it triggers, leaving ~nothing to seed the post-graduation AMM (see Graduation) with - not as a rare edge case, but as the deterministic outcome of every normal, no-sells launch.

At R=2R = 2 - the current, corrected value - it's

yr=C212+1=C3y_r = C \cdot \frac{2 - 1}{2 + 1} = \frac{C}{3}

a full third of curveSupply remains as real, sellable liquidity at ordinary graduation. That's why VIRTUAL_ETH_RESERVE is 2 ether, twice MAX_RAISE (1 ether), rather than equal to it. Buyers can still keep buying past this point - nothing stops them short of the buy-headroom cap at MAX_RAISE - this derivation only guarantees the default, no-sells outcome leaves real liquidity behind rather than draining the curve exactly at graduation.

Doubling VIRTUAL_ETH_RESERVE also doubles the curve's starting marginal price, xv/yv=VIRTUAL_ETH_RESERVE/curveSupply=2 ether/Cx_v / y_v = \text{VIRTUAL\_ETH\_RESERVE} / \text{curveSupply} = 2\ \text{ether} / C - immaterial at a billion-token curveSupply, where either value keeps the curve smooth and the starting price low.

Why totalNetEthRaised Still Exists

None of the derivation above implies realEthReserve has some unreachable ceiling that graduation needs to work around - it doesn't; xrx_r reaches VIRTUAL_ETH_RESERVE at a perfectly finite, reachable point, as shown above. The actual reason graduation and the dynamic fee are measured against a separate totalNetEthRaised field, rather than realEthReserve directly, is sell-resistance, not reachability.

PoolState tracks totalNetEthRaised as a monotonically non-decreasing counter, incremented by the net (post-fee) ETH of every buy and never decremented by a sell - whereas realEthReserve moves in both directions: up on a buy, down on a sell. A pool that raises close to MAX_RAISE, sees heavy selling drive realEthReserve back down, and then has to re-raise from a lower point, still gets full credit for its cumulative trading activity under totalNetEthRaised. Measuring graduation off realEthReserve directly would let a well-timed sell indefinitely delay - or even reset - graduation. realEthReserve continues to serve only the constant-product pricing math, exactly as the formula above requires.

Anti-Snipe Decaying Fee

Buys landing within the pool's first ANTI_SNIPE_DECAY_BLOCKS blocks pay a fee that starts high and decays linearly, block by block, to the pool's current baseline fee:

feesnipe(b)={feestart(feestartfeebase)bNb<NfeebasebN\text{fee}_{\text{snipe}}(b) = \begin{cases} \text{fee}_{\text{start}} - (\text{fee}_{\text{start}} - \text{fee}_{\text{base}}) \cdot \dfrac{b}{N} & b < N \\[4pt] \text{fee}_{\text{base}} & b \ge N \end{cases}

where bb is the number of blocks since the pool's creation block, NN = ANTI_SNIPE_DECAY_BLOCKS (15), feestart\text{fee}_{\text{start}} = ANTI_SNIPE_FEE_START_BPS (8,000 bps, 80%), and feebase\text{fee}_{\text{base}} is the pool's current dynamic baseline fee (below) - the anti-snipe premium always decays toward whatever the ongoing fee happens to be, not to a fixed floor.

This is a deliberate response to GIWA being a flat-gas OP-Stack chain: there's no priority-fee auction to cap (unlike Ethereum L1), so the fee taxes block-inclusion order itself rather than gas price. Whoever wins the race to land in the earliest blocks pays for it, regardless of how much gas they spent to get there.

While inside this window, every buy is additionally capped (see below), and any fee collected routes entirely to the treasury - the anti-snipe premium is a deterrent, not a creator revenue line.

Dynamic Market-Cap-Scaled Fee

Outside the anti-snipe window, every buy and sell pays a baseline fee that scales with how much of the raise target has been reached so far:

feebase(r)=feemax(feemaxfeemin)min ⁣(1,rMAX_RAISE)\text{fee}_{\text{base}}(r) = \text{fee}_{\max} - (\text{fee}_{\max} - \text{fee}_{\min}) \cdot \min\!\left(1, \frac{r}{\text{MAX\_RAISE}}\right)

where rr = totalNetEthRaised (the monotonic counter above, not the instantaneous curve reserve), feemax\text{fee}_{\max} = DYNAMIC_FEE_MAX_BPS (500 bps, 5%), and feemin\text{fee}_{\min} = DYNAMIC_FEE_MIN_BPS (100 bps, 1%). A brand-new pool with nothing raised yet pays the full 5%; a pool that has fully reached MAX_RAISE (and is about to graduate) pays the floor of 1%.

This is the PRD's "Project Ascend" pattern: reward sustained growth with a cheaper fee, and keep new/thin pools - the ones most exposed to opportunistic extraction - paying a higher one. Once a pool graduates, this mechanism stops applying entirely; the AMM pair instead runs a flat POST_GRADUATION_AMM_FEE_BPS (30 bps, 0.3%) swap fee, the same for every graduated token.

Whichever of the two fees applies (anti-snipe or dynamic baseline), it's split between the treasury and the pool's creator - see the creator fee-share formula below.

Max-Buy Cap

While the anti-snipe window is active, a single buy cannot claim more than a fixed share of what's left in the curve:

ΔyMAX_BUY_CAP_BPS×yr10000\Delta y \le \text{MAX\_BUY\_CAP\_BPS} \times \frac{y_r}{10000}

with MAX_BUY_CAP_BPS = 300 (3%) and yry_r = realTokenReserve at the moment of the buy. A buy that would demand more than this reverts outright rather than silently clamping - the caller has to split it into multiple transactions, each subject to the same 3% ceiling, spreading out concentration risk even for a bot willing to spend more gas. Outside the anti-snipe window, this cap no longer applies; buys are limited only by however much curve supply actually remains.

Worked Example: Sniping vs. Waiting

Take a freshly created pool where the creator sets no initial allocation, so the full default supply becomes the curve's supply: C=curveSupply=1,000,000,000C = \text{curveSupply} = 1{,}000{,}000{,}000 tokens (ignoring the standard 18-decimal scaling for readability). At creation, xv=VIRTUAL_ETH_RESERVE=2x_v = \text{VIRTUAL\_ETH\_RESERVE} = 2 ether and yv=yr=Cy_v = y_r = C, so the effective pre-trade reserves are x=2x = 2 ether and y=2,000,000,000y = 2{,}000{,}000{,}000 tokens.

The Anti-Snipe Fee, Block by Block

Nobody has bought yet, so totalNetEthRaised is 0 and the dynamic baseline fee sits at its ceiling, feebase=DYNAMIC_FEE_MAX_BPS=5%\text{fee}_{\text{base}} = \text{DYNAMIC\_FEE\_MAX\_BPS} = 5\%. Plugging that into the anti-snipe formula above for a few block offsets:

Blocks since creation (bb)Fee paid
0 (same block as creation)80%
365%
745%
1030%
1410%
15+ (window closed)5%

With these constants the decay happens to land on exact 5-percentage-point steps each block - not a coincidence worth relying on for other constant values, but a convenient way to see the linearity directly: 80, 75, 70, ... down to the 5% baseline at block 15.

Same Gross Spend, Two Very Different Outcomes

Two buyers each send 0.05 ETH gross against this same freshly created pool - one lands in the creation block (b=0b=0), the other waits until block 20, after the anti-snipe window has closed. Assume no other trades happen in between, to isolate the fee's effect.

Buyer A, block 0: fee = 80% (from the table above). Net ETH into the curve: 0.05×(10.80)=0.010.05 \times (1 - 0.80) = 0.01 ETH.

Δy=0.01×2,000,000,0002+0.019,950,249 tokens\Delta y = \frac{0.01 \times 2{,}000{,}000{,}000}{2 + 0.01} \approx 9{,}950{,}249 \text{ tokens}

(Well under the max-buy cap - 3% of the 1,000,000,000-token curve supply, or 30,000,000 tokens - so it's the fee, not the cap, doing the work here.)

Buyer B, block 20: the anti-snipe window has closed, so this buy pays only the 5% dynamic baseline. Net ETH into the curve: 0.05×(10.05)=0.04750.05 \times (1 - 0.05) = 0.0475 ETH.

Δy=0.0475×2,000,000,0002+0.047546,398,048 tokens\Delta y = \frac{0.0475 \times 2{,}000{,}000{,}000}{2 + 0.0475} \approx 46{,}398{,}048 \text{ tokens}

For the identical 0.05 ETH spend, Buyer B receives ≈4.66× more tokens than Buyer A. The curve didn't move between the two buys in this example - the entire difference is 75 of Buyer A's 80 percentage points of fee going straight to the treasury as an anti-snipe premium, with nothing to show for it on the buyer's side. This is the mechanism working as intended: racing to land in the creation block is the worst time to buy, not the best.

This example assumes 0% creator allocation and no trades between the two buys, to keep the arithmetic isolated to the fee's effect alone. In a live pool, real trading between blocks pushes totalNetEthRaised up and the dynamic baseline fee down, so Buyer B's actual fee by block 20 would typically be even lower than the 5% ceiling used here.

Creator Fee-Share

A creator can configure a cut of the baseline trading fee (never the anti-snipe fee) to accrue to themselves per pool:

creatorCut=feeAmount×creatorFeeShareBps10000\text{creatorCut} = \text{feeAmount} \times \frac{\text{creatorFeeShareBps}}{10000}

with creatorFeeShareBps bounded by CREATOR_FEE_SHARE_MAX_BPS (5,000 bps, 50%) at pool creation. The remainder of the fee (feeAmount - creatorCut) always accrues to the treasury. This accrual is pulled via claimRevenue(poolId).

Fee-share is accrue-and-claim, not auto-paid per trade: crediting a creator on every single buy/sell would compound gas costs with the anti-snipe and dynamic fee math already running on the hot trading path, for a benefit (immediate payout) creators don't actually need.

On this page