factory.launchOf(token). There is no router: approvals go to the launch itself — the quote token for buys, the launched token for sells — and no function takes a token argument.
The four entry points
Two payment shapes, four distinct selectors — not overloads. All take adeadline and a mandatory recipient.
Quote-token (ERC-20) entries:
acceptsNative()):
buyWithNative has no amountIn — the amount is msg.value.
Units — read this twice
Everything is denominated in the quote token’s own decimals:amountIn, minAmountOut on sell, quotes, reserves, event amounts. minTokensOut is always in launch-token units (18 decimals).
Recipient and refund semantics
- Buys: tokens go to
recipient; any refund goes to the payer (msg.sender). Only the curve-completing buy refunds — it takes exactly what is left on the curve and returns the overshoot (native refunds come back unwrapped, plus any sub-unit dust). That buy also graduates the launch. - Sells: tokens are pulled from
msg.senderwithtransferFrom, so the seller approves the launched token to the launch first.recipientredirects only the proceeds.
The anti-snipe window
A launch may open with a surcharge on buys that starts near the whole trade and decays to nothing over a window of seconds, measured fromopenedAt():
- Buys only. Sells are never surcharged.
- Keyed on the recipient, not the sender. A buy through an aggregator has the aggregator as
msg.senderand the buyer asrecipient— a router is never treated as the sniper. - Exempt: the creator, and any address the creator listed in
launchParams(at most 32). The atomic opening buy at creation goes to the creator, so it is never surcharged. - Capped: curve fee plus surcharge never exceeds 9,999 bps.
- Kept by the protocol. None of the surcharge goes to the creator, so a creator cannot profit from their own launch being sniped.
- No separate event:
Trade.feeincludes it.
config() (snipeTaxBps, snipeWindow, snipeDecay); the contract bounds them to a window of at most 600 seconds and a decay exponent from 1 to 24, and a window of zero turns the surcharge off. For a given buyer:
Quoting
quoteBuydoes not know who the tokens are for, so it answers for a buyer who is not exempt — the highest fee anyone would pay. UsequoteBuyForfor the exact figure; past the window the two agree.quoteBuyWithNativealso assumes a non-exempt buyer, and has no…Forvariant.quoteBuy/quoteSellreturn zeros instead of reverting outside theTradingphase, andquoteSellalso fortokensIn > sold— treat zeros as “not tradable”, not as a price.quoteBuyWithNativeis notquoteBuy(nativeIn / divisor): native→quote truncation is returned as dust in native units. Use the native quoter for native trades.
1 is constant-product over virtual reserves:
reserve(), sold() and openedAt() from storage, the parameters from config(), and exemptions from isExempt — these are the constant-product implementation’s own vocabulary, deliberately outside the cross-type interface, so bind the concrete type-1 ABI.
Where the fee goes
To a trader,fee is one number. Behind it, curveFeeProtocolBps of the base fee goes to the protocol and the rest to the creator, while any anti-snipe surcharge goes wholly to the protocol. Both sides accrue in the launch (protocolFees(), creatorFees()) and leave through collectFees(), which either recipient may call, in any phase, and which pays both sides.
The arithmetic, the caps, and every other fee the launchpad charges are on Fees.
Errors you will hit
Revert reasons may gain new cases over time; none will silently change meaning.
The buy that fills the curve graduates it, in the same transaction: it costs more gas than an ordinary buy, and the graduation reward goes to its
msg.sender — a router that fills a curve receives the reward. If graduation cannot complete, that buy reverts and the curve keeps trading just short of full. The factory’s creationPaused never affects trading on existing curves.