Joseon docs
AppExplorerLaunch app

Protocol

Bonding curve

Virtual reserves, buy and sell quotes, progress, graduation.

Constant product on virtual reserves#

Each launch gets a BondingCurvePool that prices trades with the same invariant a Uniswap V2 pair uses, except both reserves start out virtual — the pool quotes against numbers that exist only in storage, not against deposits.

virtualEthReserve × virtualTokenReserve = k

Buying pushes ETH into the virtual reserve and pulls tokens out, so the price rises. Selling does the reverse. Because k is fixed at deployment, the entire price path of a launch is known before anybody trades.

Virtual reserves are what make a zero-liquidity launch possible. A real constant-product pool needs both sides deposited before it can quote anything; a virtual one starts with a defined price and lets real ETH accumulate as people buy.

The two sets of reserves#

StorageWhat it does
virtualEthReserve, virtualTokenReserveprice the next trade
realEthReserveETH actually held, and payable out on sells
realTokenReservecurve tokens left to sell
initialRealTokenReserve800M, the denominator for progress
dexTokenReserve200M held back for the pool at graduation
creatorFeeAccrued, protocolFeeAccruedfees inside realEthReserve that are not spendable

Sells pay out of realEthReserve minus both accrued fee balances. That subtraction is the reason a sell can never drain fees somebody else has already earned.

Starting values#

virtualEthReserve1.2 ETH
virtualTokenReserve1,066,666,666.666666666666666666
Opening price~0.000000001125 ETH per token

Quoting a buy#

The fee comes off the top, then the remainder moves the curve:

fee     = grossEthIn × 200 / 10000        // 2%
netEthIn = grossEthIn - fee

nextVirtualEthReserve   = virtualEthReserve + netEthIn
nextVirtualTokenReserve = k / nextVirtualEthReserve
tokenOut                = virtualTokenReserve - nextVirtualTokenReserve

quoteBuy returns all of it — ethUsed, ethRefunded, fee, netEthIn, tokenOut, both next reserves, and willGraduate — so a UI can show the exact outcome before signing rather than estimating.

When your buy is the last one#

If tokenOut would exceed what the curve has left, the pool does not sell you a partial fill at a broken price and it does not revert. It binary-searches for the smallest ethUsed that still buys every remaining token, refunds grossEthIn - ethUsed in the same transaction, and sets willGraduate = true.

availableTokens = realTokenReserve - minimumCurveReserve

tokenOut < availableTokens   ──▶ normal fill, no refund
tokenOut >= availableTokens  ──▶ solve for ethUsed, refund the rest, graduate

You cannot overpay into a graduation. Sending 10 ETH at a curve that needs 0.4 ETH to finish returns ~9.6 ETH in the same transaction.

Quoting a sell#

nextVirtualTokenReserve = virtualTokenReserve + tokenIn
nextVirtualEthReserve   = k / nextVirtualTokenReserve
grossEthOut             = virtualEthReserve - nextVirtualEthReserve

fee       = grossEthOut × 200 / 10000     // 2%
netEthOut = grossEthOut - fee

Two guards apply. You cannot sell more than the curve has circulated (initialRealTokenReserve - realTokenReserve), and the payout is capped at spendable ETH — reserves minus accrued creator and protocol fees — with a 1 wei rounding tolerance.

Both fee sides are charged in ETH, never in tokens, so a sell burns no supply.

Progress#

progress = (initialRealTokenReserve - realTokenReserve) / (initialRealTokenReserve - minimumCurveReserve)

minimumCurveReserve is 0 on this stack, so progress is simply the share of the 800M allocation that has been sold, returned as a WAD (1e18 = 100%).

Graduation, derived#

Graduation fires when the curve allocation is gone:

realTokenReserve <= minimumCurveReserve   // 0 on this stack

Selling all 800M tokens moves the virtual reserves to a known end state, which fixes every headline number:

virtual token reserve at the end = 1,066,666,666.666… - 800,000,000
                                 = 266,666,666.666…
virtual eth reserve at the end   = k / 266,666,666.666… = 4.8 ETH

net ETH collected  = 4.8 - 1.2 = 3.6 ETH
final price        = 4.8 / 266,666,666.666… = 0.000000018 ETH per token
graduation FDV     = 0.000000018 × 1,000,000,000 = 18 ETH

Because the 2% buy fee is charged on the gross amount, the ETH users actually spend to get there is 3.6 / 0.98 ≈ 3.67346938775510204 ETH, of which 0.07346938775510204 ETH is fee. That splits 70/30: 0.051428571428571428 ETH to the creator and 0.022040816326530612 ETH to the protocol. Neither goes into the pool — only the clean 3.6 ETH is migrated.

Amount
Gross ETH in, no sells~3.67346938775510204 ETH
Total curve fee~0.07346938775510204 ETH
Creator share (70%)~0.051428571428571428 ETH
Protocol share (30%)~0.022040816326530612 ETH
Migrated to the pool3.6 ETH

Those figures assume nobody sells. Sells charge fees too, so a curve with churn ends up having collected more fees in total, while still migrating exactly 3.6 ETH.

Why token count, not an ETH target#

Graduation could trigger on an ETH threshold instead. Using the token count makes the end state exact: the final virtual reserves, the final price, and the pool's opening price are all known in advance, which is what lets the migration assert that the curve price and the DEX price match inside one transaction. An ETH target would leave the token side floating and reintroduce a price gap.

Next: graduation covers how those 3.6 ETH and 200M tokens become a locked pool.