4 chapters · 14 questions

Bonding curves: how launchpads price a token, and where the bugs are

graduates · 25× start price a buy pays this area 0 800M tokens sold → price
Price from the chapter 2 formula, price = 30,000 / (1,000 − tokens sold)², in quote per million tokens. It starts at 0.03 and ends at 0.75.

A bonding curve is a contract that sells a token and buys it back at a price set by a formula. The formula only looks at how many tokens have been sold so far, so every buy makes the next token a bit more expensive and every sell makes it cheaper.

Most memecoin launchpads use the curve that pump.fun made popular: Uniswap's x · y = k formula with virtual starting reserves. This lesson follows one example launch from start to end. Chapter 1 explains why launchpads use a curve instead of a normal pool. Chapter 2 covers the formula and the real money behind it, chapter 3 buying and selling, and chapter 4 how the token moves to a real pool at the end. Every chapter uses the same numbers, and chapters 2 to 4 end with a list of things to check in the code. Some quiz questions show a short Solidity function with one bug in it.

One example, not the only design

This lesson follows one simple launch so the numbers stay easy to check. Real launchpads differ in almost every detail. Some use other curve shapes, such as linear, fixed-price or curves built from several price ranges. They graduate into different pools: Uniswap V2, V3 or V4, PancakeSwap, Raydium, PumpSwap or Meteora, and some lock the LP tokens instead of burning them. Fees vary too: some pay the creator a share of every trade, most charge a protocol fee, and some take a fee at graduation.

How to read the formulas

x
Purple marks money. x is the amount of backing currency, such as SOL or ETH, in the formula. It starts above zero; chapter 2 explains why.
y
Blue marks tokens. y is the number of tokens in the formula. It goes down as people buy.
Δy
Orange marks the number being worked out, for example the tokens a buyer gets.
Δ
Means "change in". Δx is the money a buyer pays in.
Quote tokenThe currency you pay with: SOL, ETH or USDC. This lesson calls it the quote token, or just quote.
Tokens soldHow many of the new tokens the curve has handed out. Pump.fun-style curves mint the whole supply at launch and hand it out; other designs mint on each buy. The price depends only on this number.
ReserveThe quote buyers have actually paid in. This is the reserve, and every sell is paid out of it.
01Why curves exist

Chapter 1 of 4

A new token has nobody to trade with, so the contract trades with you itself

To buy a token, someone has to sell it to you. That someone is the counterparty. Nobody has bought a token created a minute ago, and it has no pool, so there is nobody to trade with and no price.

Before launchpads: the creator seeds a pool

Before launchpads, the creator put the new token and some of their own ETH into an AMM pool, and everyone traded against that pool's liquidity. This had three problems. The creator needed ETH up front. The creator picked the opening price by choosing how much of each to deposit. And the creator got the pool's LP tokens, so they could withdraw all the ETH at any time and leave buyers holding a token nobody could sell. That is one way to rug pull.

With a curve, the contract is the counterparty

A bonding curve contract sells tokens to anyone who pays and buys them back from anyone who sells. The price comes from the formula, so you can work it out before you trade. Buys push it up and sells push it down.

Nobody has to deposit anything to start. The opening price comes from constants set at launch, and after that only trading moves it. This is price discovery. There are no LP tokens, so nobody has a claim on the money buyers paid in. The only way to get money out is to sell tokens back to the curve.

In shortThe contract sells its token and buys it back at a price anyone can calculate. Each sale raises the price and each buyback lowers it. In a correctly written contract, nobody, the creator included, can take buyers' money out except by selling tokens back.
  1. LaunchSomeone creates the token through the launchpad. The formula's starting values set the first price. Nobody deposits money.
  2. Trading on the curveAnyone can buy from or sell to the contract at the formula's price.
  3. GraduationWhen enough money has come in, the curve stops trading and moves the money and a set amount of tokens into an AMM pool.
  4. AMM poolThe token now trades like any other. The launchpad burns the pool's LP tokens, so nobody can withdraw the liquidity.

What the curve guarantees

  • Until graduation, you can always buy from or sell to the contract.
  • You can calculate the price before you trade.
  • Money paid in stays in the contract until someone sells.

What it does not guarantee

  • That you get in first. Bots often buy in the launch block.
  • That the price is fair. It only shows what the last buyers paid.
  • That the contract has no bugs.

Why the curve ends

The curve only sells part of the supply, and you can only trade on it through the launchpad. Wallets, DEX aggregators and other protocols work with normal pools. So when enough money has come in, the contract puts that money and a matching amount of tokens into an AMM pool and stops trading. This is graduation, covered in chapter 4.

The whole launch. Move the slider to change how many tokens have been sold. The purple area under the price line is the money buyers have paid in. Leaving fees aside, if everyone sold back, the curve would pay out exactly that amount.
Phase
Price vs launch
Money in the contract
Market cap
Quiz

Why doesn't the creator have to put in any money when launching on a bonding curve?

Quiz

A creator who seeded a pool could pull the liquidity out. Why can't they do the same with a curve?

02Constant product

Chapter 2 of 4

The x · y = k formula, with virtual starting reserves

→Full derivation on the Uniswap lessonThis chapter is the short version. The Uniswap lesson derives x · y = k step by step and covers fees and price impact.

A Uniswap V2 pool holds two tokens and only allows trades that keep the two amounts multiplied together the same (ignoring fees, which make it grow a little). That is the constant product rule, x · y = k, and k is called the invariant. When you pay quote in, x goes up, so y has to go down to keep the product at k. The tokens taken off y are what you get.

A launch curve uses the same rule, but it starts with no money, and with x = 0 both the product and the price are 0. So the contract does its math as if it already held 30 quote and 1,000,000,000 tokens. These are virtual reserves. They only exist in the formula, they set the opening price, and they can never be paid out.

The price of the next token, the spot price, is x divided by y. In this example the curve sells 800M tokens and stops when y reaches 200M. Those last 200M are never sold on the curve; chapter 4 shows where they go.

x · y = k

In wordsx times y is the same before and after every trade.

Δy = y − kx + Δx    p = xy

In wordsTo find the tokens you get, add your quote to x, divide k by the new x to get the new y, and subtract that from the old y. The spot price is x over y.

Symbols in play

x
Quote in the formula. Starts at 30, all of it virtual. Real money raised is x − 30.
y
Tokens in the formula, in millions. Starts at 1,000.
k
Invariant. 30 × 1,000 = 30,000 (quote × million tokens).
Δx
Quote you pay in.
Δy
Tokens you receive, in millions.
p
Spot price, in quote per million tokens.
In shortThink of x and y as the width and height of a rectangle whose area must stay 30,000. Paying in makes it wider, so it has to get shorter, and the height it loses is the tokens you get. The wider it already is, the less height each extra unit of width removes. That is why the price goes up.

Worked example: buy with 10 quote at launch

  1. Opening spot price.
    p = 30 / 1,000 = 0.030 per million tokens
  2. Quote side after the buy.
    x = 30 + 10 = 40
  3. Token side that keeps the product at 30,000.
    y = 30,000 / 40 = 750
  4. Tokens out.
    Δy = 1,000 − 750 = 250 million tokens
  5. You paid 0.040 per million on average, more than the opening 0.030. The next buyer starts at 0.0533.
    10 / 250 = 0.040 · 40 / 750 = 0.0533
  6. The curve ends when 800M are sold and y = 200.
    x = 30,000 / 200 = 150 → real money raised = 150 − 30 = 120
The x · y = 30,000 curve. Every possible state of the launch is a point on this line. Paying in quote moves the point right and down.
Tokens out Δy
Average price / 1M
Spot price after / 1M
Price impact

Auditor's note · how big k gets

On-chain, amounts are in wei. One billion 18-decimal tokens is 10²⁷ wei, and 30 of an 18-decimal quote token is 3 × 10¹⁹ wei, so k is about 3 × 10⁴⁶. Each reserve fits in a uint128, which contracts often use to pack two reserves into one storage slot, but k does not. Check that k is a uint256 and that x * y is computed in 256 bits, not as two uint128 values multiplied together.

Real money and virtual money

x is the 30 virtual quote plus the real money buyers have paid in. Only the real part is in the contract, so the contract has to track it separately and pay every sell out of it.

On a constant product curve the numbers work out exactly. If every holder sells back, x returns to 30 and the payouts add up to the real money taken in, so the contract can always pay every seller. This is called solvency. It holds as long as fees are kept out of the reserve and every rounding goes the contract's way.

Rreal = x − x₀

In wordsThe real money held is x minus the virtual starting quote x₀, which is 30 here.

Worked example: everyone sells back after the 10 quote buy

  1. State after the buy.
    x = 40, y = 750 → R_real = 40 − 30 = 10
  2. Every holder sells all 250M back.
    y = 1,000 → x = 30 → payouts 40 − 30 = 10 = R_real
  3. The payouts equal the real reserve exactly. With a 1% fee, sellers get 9.90 and the 0.10 fee sits in a separate fee balance.
    10 − 10 = 0 left, no shortfall

Auditor's note · never pay out the virtual amount

If a sell pays out based on x without subtracting the virtual 30, it pays money that was never there, and the last sellers find the contract empty. Check that no payout can be more than x − 30, especially right after launch, and that fees are kept in their own variable, not in the balance the curve prices from.

Quiz

Why do pump.fun-style curves start from virtual reserves instead of empty ones?

Your turn · calculate it

At launch (x = 30, y = 1,000M), how many million tokens does a buy with 20 quote receive?

million tokens

Quiz

After 250M tokens are sold, every holder sells them all back. With no fees, how much does the curve pay out in total?

Find the bug

Almost every buy on this curve reverts. Which line is the root cause?

function buy(uint256 quoteIn, uint256 minOut) external {
    uint256 newX = vQuote + quoteIn;
    uint256 newY = k / newX;
    uint256 out  = vToken - newY;
    require(out >= minOut && out > 0, "out");
    require(newX * newY >= k, "k");
    vQuote = newX;
    vToken = newY;
    quote.safeTransferFrom(msg.sender, address(this), quoteIn);
    token.safeTransfer(msg.sender, out);
}

Security check · curve math

  • Tokens out round down, required quote in rounds up, sell payouts round down, fees round up.

    Details
    What
    Every division that rounds toward the trader gives away up to 1 wei and lets x · y fall below k. One wei is rarely worth the gas, but with low-decimal tokens, or when the rounded value is multiplied by a price or share afterwards, it can add up to real money. If the code also checks x * y >= k after rounding the wrong way, the check fails and the trade reverts instead.
    Check
    Go through every division and ask who gains if it rounds down. Do this separately for buys with exact input, buys with exact output, sells, fees and graduation.
    Fix
    Use explicit mulDivUp / mulDivDown and ceiling division where the protocol needs the larger number.
  • If the last few wei of quote buy zero tokens, or are below a minimum buy, every buy reverts and the curve never graduates.

    Details
    What
    An attacker buys until the curve needs only a few wei of quote to finish. Any later buy gets cut down to that amount, and two things can make it revert. If one wei of the token is worth more than one wei of quote (a token with few decimals, or a very expensive one), the output rounds to 0 and a zero-output check fails. If the contract has a minimum buy size, the cut-down amount is below it. With this lesson's numbers neither happens: near the end, 1 wei of quote still buys about 1.3 million wei of tokens.
    Check
    How does the code cut down the last buy? Does it compute the output with the formula, which can round to 0, or hand over all remaining curve tokens? Does a minimum buy size apply to the cut-down amount? Can sells unfreeze the curve, and are sells allowed at that point?
    Fix
    On the last buy, give the buyer all remaining curve tokens instead of using the formula, or graduate once the quote still needed is below a small dust amount.
  • After many trades the real balances are a few wei off from what the launch math predicted, and an == check reverts.

    Details
    What
    Amounts worked out in advance, such as the tokens left for the pool or the quote raised at the end, assume exact math. Every rounded trade moves the real amounts by a wei.
    Check
    Search for == on balances and amounts in graduation and migration code.
    Fix
    Derive pool amounts from actual balances at migration time, or compare with a tolerance and sweep the dust.
  • Curve sales plus pool tokens plus any creator share must never exceed max supply.

    Details
    What
    If a buy can go past the 800M meant for the curve, the extra comes out of the tokens reserved for the pool, or gets minted on top of the supply.
    Check
    Every buy is cut off at the cap. The pool's tokens are set aside once, as a constant. If there is a mint function, it checks the cap itself.
    Fix
    Cut buys off at the remaining curve tokens and check totalSupply() <= MAX_SUPPLY wherever tokens are minted.
  • All possible sell payouts added up must never be more than the real reserve.

    Details
    What
    Fees, rounding errors or virtual amounts mixed into the reserve can leave the last sellers unpaid.
    Check
    Tracked reserve equals x − x₀ after every trade. Fees are stored separately. Sell payouts round down.
    Fix
    Write a fuzz test: random buys, then sell everything back in random chunks. Every sell must succeed and the reserve must never go below zero.
03Trading, sandwiches

Chapter 3 of 4

Selling, fees and sandwich attacks

Selling is a buy run backwards. You give tokens back, y goes up, so x has to go down to keep x · y at k, and you are paid the difference. Without fees, if you buy with 10 quote and sell the tokens straight back, you get exactly 10 back.

Launchpads charge a fee f on each trade, usually around 1%. In this lesson a buyer pays the fee on top, and a seller has it taken off the payout. Many contracts instead take the buyer's fee out of the amount sent, like the buy function in the quiz below; the cost is about the same. So buying and selling straight back costs you about 2%. That gap between the price you buy at and the price you can sell at is the spread.

Every trade moves the price, so the order of trades matters. A bot that sees your buy waiting in the mempool can buy just before you, let your buy push the price up, and sell right after. This is a sandwich attack, a kind of front-running. You protect yourself with minOut, the fewest tokens you will accept. If the price has moved too far by the time your trade runs, it reverts. How far you let it move is your slippage tolerance.

→Price impact and slippage on the Uniswap lessonThe V1 chapter works through a swap step by step and explains how price impact differs from slippage.
out = x − ky + Δy

In wordsAdd the tokens sold to y, divide k by that to get the new x, and pay out the difference.

Profit = out · (1 − f) − a · (1 + f)

In wordsThe attacker pays a plus the fee to buy first, then gets the sell payout minus the fee. The attack is worth it when your buy moves the price by more than the two fees.

Symbols in play

Δy
Tokens sold back.
out
Quote paid out for those tokens, before the fee.
f
Fee as a fraction. 1% is 0.01.
a
Quote the attacker spends on the front-run.
In shortA bot buys right before you, your buy raises the price, and the bot sells right after at that higher price. Its profit comes out of the tokens you would have got. A tight minOut makes your trade revert instead.

Worked example: 1% fee, launch state x = 30, y = 1,000M

  1. You buy with 10 quote and get 250M tokens, as in chapter 2.
    you pay 10 × 1.01 = 10.10
  2. You sell the 250M straight back. The curve returns to x = 30.
    out = 40 − 30,000 / 1,000 = 10 → you receive 10 × 0.99 = 9.90
  3. You lost the fee twice, once on each trade.
    10.10 − 9.90 = 0.20
  4. Start over. This time a bot buys first with 10 quote and gets those 250M.
    x = 40, y = 750 · attacker pays 10.10
  5. Your 10 quote now buys at a higher price.
    y = 30,000 / 50 = 600 → you get 750 − 600 = 150M instead of 250M
  6. The bot sells its 250M back.
    y = 850 → x = 30,000 / 850 = 35.29 → out = 50 − 35.29 = 14.71
  7. The bot's profit after both fees. With minOut set close to 250M, your buy would have reverted and the bot would have lost its fees instead.
    14.71 × 0.99 − 10.10 = 4.46
A sandwich on the curve. Price against tokens sold. Your buy is always 10 quote, so both orange areas are the same size. The sandwiched one sits higher on the curve, so it is narrower, which means fewer tokens.
attacker's front-runyour buy, sandwichedyour buy, no attack
You get, no attack
You get, sandwiched
Tokens you lose
Attacker profit
Quiz

With no other traders, you buy tokens and sell the same amount right back. Why do you end with less quote than you started with?

Your turn · calculate it

From launch, an attacker front-runs with 20 quote. How many million tokens does your 10 quote buy now?

million tokens

Find the bug

Which of these is a bug in this buy function?

function buy(uint256 minOut, uint256 deadline) external payable {
    require(block.timestamp <= deadline, "expired");
    uint256 fee = (msg.value * feeBps + 9_999) / 10_000; // round up
    uint256 out = quoteBuy(msg.value - fee);
    require(out > 0, "zero out");
    vQuote += msg.value - fee;
    vToken -= out;
    token.safeTransfer(msg.sender, out);
}

Security check · trading

  • Without both, any pending trade can be sandwiched or executed hours later at a worse price.

    Details
    What
    minOut limits how far the price can move against the trader. The deadline limits how long a signed transaction can sit in the mempool before it runs.
    Check
    Every entry point (buy, sell, buy with exact output, router paths) takes both and actually compares them. The frontend doesn't fill in 0 and type(uint256).max by default.
    Fix
    require(out >= minOut) and require(block.timestamp <= deadline) on every path.
  • A sandwich pays when the victim's price move exceeds roughly twice the fee.

    Details
    What
    The attacker earns the price move the victim causes, minus a fee on each leg. Early on the curve, small trades move the price a lot.
    Check
    Compute the price impact of typical buys near launch. Confirm minOut is mandatory and cannot be bypassed through a second entry point.
    Fix
    Make slippage limits mandatory. A higher fee in the first blocks after launch also helps.
  • The first tokens on a curve are the cheapest, and bots try to buy them in the launch block. This is sniping.

    Details
    What
    If creating the token and the creator's first buy are separate transactions, a bot watching the mempool can buy in between.
    Check
    Can the creator buy in the same transaction that creates the token? Is there a per-wallet or per-transaction cap in the first blocks? Can the creator bundle a large buy that nobody sees until it lands?
    Fix
    Let the creator buy in the create transaction, up to a public cap. Limit buy size in the first blocks, or start with a high fee that drops over the first blocks.
04Graduation

Chapter 4 of 4

Graduation: moving from the curve to a real pool

Once the real reserve reaches a target, the contract stops trading on the curve. This is graduation, also called migration. The contract creates a pool on an AMM, deposits the quote it collected together with a matching amount of tokens, and burns the LP tokens so nobody can withdraw the liquidity.

The pool has to open at the price where the curve stopped. If it opens lower, every holder loses value the moment the pool exists, and the first pool buyers get tokens cheaper than the last curve buyers paid. If it opens higher, the last curve buyers can sell into the pool right away at a profit, taken out of the liquidity the curve just deposited.

The last buy on the curve usually asks for more tokens than are left. The contract fills what it can and sends the rest of the money back as a refund. Bugs here usually come from a minOut check that is not scaled to the partial fill, or from a refund sent before the contract's state is updated.

→How a V2 pair mints its first liquidityThe Uniswap V2 chapter covers the first LP mint into a new pair and the 1,000 units it locks forever as MINIMUM_LIQUIDITY.
tokenspool = Rrealpend

In wordsDeposit as many tokens as it takes for the pool's price (quote divided by tokens) to equal the curve's last price.

minOut′ = minOut · usedsent

In wordsIf only part of a buy is filled, scale minOut down by the same fraction before checking it.

Symbols in play

Rreal
Real quote raised. 120 at graduation in our example.
pend
Spot price at the end: 150 / 200M = 0.75 per million tokens.
sent, used
Quote the buyer sent, and the part the curve could take.
minOut
Minimum tokens the buyer set for the full amount sent.
minOut′
That minimum scaled to the filled part.
In shortThe pool must open at the curve's last price. The last buyer gets the tokens that are left, a minOut scaled to that part, and the unused quote back.

Worked example: graduation and the last buy

  1. Market cap at the end, with 1,000M total tokens, before the 40M left over are burned.
    0.75 × 1,000 = 750 quote
  2. Tokens needed to pair with the 120 quote raised, at that price.
    120 / 0.75 = 160 million tokens
  3. Where all 1,000M tokens end up: 800M sold, 160M in the pool, and the 40M left over are burned.
    800 + 160 + 40 = 1,000 million
  4. Now the last buy. 90 quote has been raised so far (x = 120, y = 250). Alice sends 50. Her wallet worked out her tokens as if the curve kept going and set minOut 1% below that.
    (250 − 30,000/170) × 0.99 = 73.53 × 0.99 → minOut = 72.79M
  5. Only 30 more quote fits. She gets every token down to y = 200, and 20 quote back.
    out = 250 − 200 = 50M · refund = 20
  6. Checking 50M against her minOut fails, as it always will when minOut is more than the tokens left. The scaled check passes.
    50 ≥ 72.79 ✗ · minOut′ = 72.79 × 30/50 = 43.68 → 50 ≥ 43.68 ✓
The last buy. Tokens sold against quote raised. The curve ends at 120. Move the buy across that line and compare the two slippage checks.
filled partrefunded part, where the wallet assumed the curve kept goinggraduation
Used / refunded
Tokens out
Naive: out ≥ minOut
Scaled: out ≥ minOut′

Auditor's note · units at the handover

The curve may hold native ETH or SOL, but the pool needs the wrapped token. Check that the contract wraps before it deposits, that the pool price accounts for both tokens' decimals (a 6-decimal quote against an 18-decimal token is off by 10¹² if it doesn't), and that the graduation check uses >= so a rounded total can't skip past the target.

Donations: money that did not come from a buy

Anyone can transfer tokens straight to the curve contract, which is called a donation. If the code reads balanceOf to get its reserve, it counts the donation as if buyers had paid it. The price goes up without a trade, and a graduation check that reads the balance triggers before buyers have paid the full target. The same works on the pool itself before graduation; the checklist below covers both.

In shortCount the money that came in through buys and went out through sells, not whatever happens to be in the contract. Anyone can add to the balance.

Worked example: a donation of 10 after the 10 quote buy

  1. The curve is at x = 40 and holds 10 real quote. Someone transfers 10 more straight to it.
    tracked R_real = 10 · balance = 20
  2. Code that prices from its balance now uses x = 50 instead of 40.
    p = 50 / 750 = 0.0667 vs 40 / 750 = 0.0533 → +25%
  3. A graduation check that reads the balance wants 120, and 10 of what it counts was never paid by a buyer.
    graduates after 110 of real buys instead of 120
Quiz

The curve needs 12 more quote to graduate. Alice sends 30 with a minOut sized for the full 30. What should the contract do?

Your turn · calculate it

Bob sends 50 quote with minOut = 60M tokens. Only 30 quote fits before graduation. What is his scaled minOut′, in millions?

million tokens

Find the bug

Anyone can change what this sell pays out. Which line lets them?

function sell(uint256 amount, uint256 minOut) external {
    uint256 x = VIRTUAL_QUOTE + quote.balanceOf(address(this));
    uint256 newY = vToken + amount;
    uint256 out = x - Math.ceilDiv(x * vToken, newY);
    require(out >= minOut, "slippage");
    vToken = newY;
    token.safeTransferFrom(msg.sender, address(this), amount);
    quote.safeTransfer(msg.sender, out);
}
Find the bug

This runs at the end of the last buy. The order of two steps lets the buyer reenter. Which?

function _finishCurve(uint256 used, uint256 sent) internal {
    uint256 refund = sent - used;
    if (refund > 0) {
        (bool ok, ) = msg.sender.call{value: refund}("");
        require(ok, "refund failed");
    }
    graduated = true;
    _migrate();
}
Find the bug

This migration can be blocked by anyone, forever. Why?

function _migrate() internal {
    address pair = factory.createPair(address(token), address(weth));
    weth.deposit{value: realQuote}();
    weth.transfer(pair, realQuote);
    token.transfer(pair, TOKENS_FOR_POOL);
    IUniswapV2Pair(pair).mint(DEAD);
}

Security check · graduation

  • Without scaling, a large last buy usually reverts. If the code skips the check instead, the buyer has no protection.

    Details
    What
    The buyer sets minOut for the full amount, but the curve only uses part of it. Comparing the full minOut to a partial fill reverts even when the price was fine. Code that skips the check on this path leaves the buyer open to a sandwich.
    Check
    Find the code that cuts down the last buy. Is minOut checked there, and against what?
    Fix
    minOut * used / sent (rounded up), then require(out >= scaledMin).
  • A V2 createPair call fails if the pair already exists, and a concentrated-liquidity pool that someone else initialized starts at the price they picked.

    Details
    What
    Anyone can work out the pool's address or ID in advance. For V2-style pools, creating the pair first makes graduation's createPair call revert. For V3 or V4 pools, initializing the pool at an extreme price means the curve's money is deposited at that price, or the deposit fails. The attacker can also add a tiny bit of liquidity so the pool already has a price and reserves that graduation has to deal with.
    Check
    Does graduation always call create, or does it first look for an existing pool? Does it read the price and reserves already there? What happens to the deposit if the starting price is far from the curve's final price?
    Fix
    Handle a pool that already exists: read its state and trade the price back if needed. For V4, use a beforeInitialize hook that only lets the launchpad initialize the pool. Or create the pool in the same transaction as the launch.
  • Any math that reads balanceOf or address(this).balance counts money anyone can send.

    Details
    What
    A donation raises x: the price goes up, sells pay more, and a graduation check that reads the balance triggers early, so the pool opens at the wrong price.
    Check
    Is the reserve a stored variable? Does graduation read balances or stored values? Does a receive() function accept plain ETH?
    Fix
    Track reserves in storage; ignore or sweep surplus balances.
  • Tokens already sitting in the pair are counted when liquidity is minted.

    Details
    What
    A V2 pair mints LP tokens based on its balance minus its stored reserves, so tokens sent in earlier count as part of the first deposit. Donated WETH raises the opening price, and the attacker sells curve tokens they bought cheap into the pool at that price.
    Check
    Does migration account for existing balances in the pair? Can curve tokens be transferred to the pair before graduation?
    Fix
    Call sync or skim first and size deposits from reserves; block transfers of the launch token to the pair until graduation.
  • An external call before state is final lets the receiver trade again or run migration twice.

    Details
    What
    Native ETH refunds, token hooks (ERC-777, ERC-1363) and launch callbacks all run code at the receiving address.
    Check
    Where is the first external call when the last buy runs? Is graduated set before it? Is there a reentrancy guard on buy, sell and migrate?
    Fix
    Checks-effects-interactions plus nonReentrant; send the refund last.
  • transfer only forwards 2,300 gas. A smart wallet whose receive function needs more runs out of gas, and the last buy reverts.

    Details
    What
    Smart contract wallets, multisigs and proxies often need more than 2,300 gas in receive or fallback. If they do, every last buy they send fails.
    Check
    Search for .transfer( and .send( on native refunds.
    Fix
    (bool ok, ) = to.call{value: amount}(""); require(ok); behind a reentrancy guard, or credit the refund for a separate withdraw.
  • If it does, every retry fails the same way and the raised money is stuck.

    Details
    What
    The launchpad's own checks pass, but modifyLiquidity reverts on limits that only V4 core checks. The total liquidity of all positions that use a tick as a boundary can be at most maxLiquidityPerTick, and uint128 amounts have to fit into int128 deltas. If graduation goes over either limit, it reverts every time.
    Check
    Compare the liquidity graduation adds with maxLiquidityPerTick for the pool's tick spacing. Follow every cast from uint128 to int128 for amounts and liquidity.
    Fix
    Calculate the liquidity before minting and keep it under the per-tick limit, spreading it over several ranges if needed. Use checked casts, and give graduation a way to recover if the deposit still fails.
⇄

Before and after graduation

The curve and the pool use the same formula with different reserves

The curve and the pool both use x · y = k. The difference is where the reserves come from and who can trade against them.

Launch curveAMM pool after graduation
Price rulex · y = k, with virtual x₀, y₀x · y = k, real reserves only
Liquidity fromBuyers' quote and the contract's own tokens. Virtual reserves only set the price. Nobody deposits.The curve's reserve plus the tokens set aside for the pool.
Can it be pulledOnly through sells, at curve prices.Not if LP tokens are burned.
Opening pricex₀ / y₀ (0.03 per 1M here)The curve's final price (0.75 per 1M)
Tokens tradedA fixed allotment (800M)Whatever was deposited (160M) plus holders' tokens
EndsAt the graduation targetNever
Who can tradeOnly through the launchpad contractAny router or aggregator
Main audit risksRounding, sniping, a reserve read from balanceOf, the last buyPre-created pools, donations, opening price
↗

Quick reference · checked October 2026

Who uses constant product curves

Launchpads change often. Check the current docs before relying on a row.

ProtocolRoleHow it is used
Pump.fun (Solana)Launch curveConstant product with virtual reserves, as in chapter 2.
Raydium LaunchLab (Solana)Launch curveSupports constant product, linear and fixed-price curves. Constant product with virtual reserves is the default and the most used.
PumpSwap (Solana)Graduation poolPump.fun's own AMM. Graduated pump.fun tokens migrate here.
Raydium AMM (Solana)Graduation poolRaydium's AMM. Graduated pump.fun tokens migrated here before PumpSwap launched.
Uniswap V2 (EVM)Graduation poolThe classic EVM x · y = k pool, and the migration target in the last question.
A–Z

Reference · every term in this lesson

Dictionary