COOK Launchpad — Integration Guide for Routers, Aggregators & Trading Terminals
For partners routing order flow into COOK and charging their own fee on top. Everything below is taken from the deployed contracts, not from a spec.
01Quick facts
02Two phases — route differently
BONDING GRADUATED
──────────────────────────────── ────────────────────────────────
Venue : COOKFactory bonding curve Venue : PancakeSwap V2 pair
Buy : factory.buyFor() Buy : router, fee-on-transfer variant
Sell : factory.sellFrom() Sell : router, fee-on-transfer variant
Token : TRANSFER-LOCKED Token : freely transferable, 1% swap taxDetect with factory.getToken(token).graduated. Graduation happens automatically inside the buy that fills the curve — same transaction, no separate listing step, no window to snipe the listing itself.
Calling buy() / sell() on the factory after graduation reverts AlreadyGraduated (0xe6a0d45f). Calling the PancakeSwap pair before graduation is impossible — the pair does not exist yet.
03The constraint that breaks naive integrations
COOKToken._transfer rejects any transfer where neither side is the factory, reverting TransfersLocked (0xdb89e3f4). This is deliberate — it is what stops anyone seeding an AMM pool or dumping OTC before the curve completes.
The API is built so you never need custody. Every path has the factory on one side:
✅ BUY : you send BNB → factory mints/transfers straight to the end user
✅ SELL: user approves the FACTORY → factory pulls straight from the userAfter graduation the token is a normal (taxed) ERC-20 and you can route it however you like.
04Buying — buyFor
function buyFor(
address token,
address recipient, // end user; tokens go here directly
uint256 minTokensOut,
uint256 deadline // unix seconds, 0 = no deadline
) external payable returns (uint256 tokensOut);Permissionless — no allowlist needed.
Taking your fee: skim the BNB before forwarding. The factory only ever sees the net amount, so your fee never touches the curve accounting.
function buyWithFee(address token, address user) external payable {
uint256 fee = msg.value * 25 / 10_000; // your 0.25%
payable(feeCollector).transfer(fee);
factory.buyFor{value: msg.value - fee}(token, user, minOut, deadline);
}- Tokens never pass through you.
recipientreceives them directly from the factory. - Overfill refunds go to recipient, not to the caller. If the order is larger than the remaining curve inventory, the factory spends only what it needs and refunds the rest to
recipient. Your contract will not receive stray BNB, so it does not need areceive()for this path.
05Selling — sellFrom (allowlist required)
function sellFrom(
address token,
address owner, // the end user whose tokens are sold
uint256 tokenAmount,
uint256 minBnbOut,
address recipient, // where the BNB lands — you, or the user
uint256 deadline
) external returns (uint256 bnbOut);The user approves the factory, not you. The factory then pulls owner → factory, which the transfer lock permits.
// 1) user (off-chain, their own wallet): token.approve(FACTORY, exactAmount)
// 2) you:
function sellWithFee(address token, address user, uint256 amount) external {
uint256 before = address(this).balance;
factory.sellFrom(token, user, amount, minOut, address(this), deadline);
uint256 gross = address(this).balance - before;
uint256 fee = gross * 25 / 10_000; // your 0.25%
payable(feeCollector).transfer(fee);
payable(user).transfer(gross - fee);
}Your contract needs a receive() external payable for this path.
Getting allowlisted
sellFrom is gated by isAllowedRouter[msg.sender], set by the COOK operator/owner via setAllowedRouter. Unlisted callers revert NotOperator (0x7c214f04).
Contact the COOK team with your router address to be added. Expect scrutiny: a cleared router picks recipient, minBnbOut and the timing, so it can in principle redirect the full proceeds of anyone holding an open approval. We ask integrators to:
- Use exact-amount approvals in your UI — never
type(uint256).maxon the factory. - Set
recipientto the end user where your flow allows it. - Keep the router contract immutable or behind a timelock.
If you would rather not be allowlisted, you can still route sells by having the user call sell() directly and charging your fee off-chain or on the buy side only.
06Quoting
function getBuyQuote(address token, uint256 bnbIn)
external view returns (uint256 tokensOut, uint256 fee, uint256 refund);
function getSellQuote(address token, uint256 tokenAmount)
external view returns (uint256 bnbOut, uint256 fee);These run the exact same math as buy / sell, so quoted == executed absent other traffic in between. fee is the 1% protocol fee in BNB; getSellQuote's bnbOut is already net of it.
Both revert AlreadyGraduated for graduated tokens — check graduated first, or catch the selector and fall through to your PancakeSwap quoting path.
Remember to subtract your own fee from what you display, since the factory has no knowledge of it.
Curve mechanics, if you want to quote locally
Constant product with virtual reserves:
k = x0 · y0
x = x0 + bnbRaised
y = y0 − tokensSold
y0 = 1,066,666,666.666… (VIRTUAL_TOKEN_RESERVE, larger than real supply on purpose)
x0 = raiseTarget / 3Sold on the curve: 800,000,000 (80%). Paired into the burned LP at graduation: 200,000,000 (20%) plus the entire raise. y0 = S²/(S−L) is chosen so the curve's closing price equals the pool's opening price — there is no price step at graduation for you to arbitrage or to explain to users.
07After graduation — PancakeSwap V2
Standard pair, with one hard requirement:
You must use the …SupportingFeeOnTransferTokens router variants. The token takes 1% on swaps against the pair, soswapExactETHForTokens/swapExactTokensForETHwill revert. Set slippage tolerance to at least 1.5%.
Wallet-to-wallet transfers are not taxed. Only transfers where the pair is the counterparty.
08Displaying the tax correctly
The nominal tax is exactly 1%, both directions. A sell measured with no swap-back pending costs 94 bps — the 1% tax, with rounding.
The variance comes from the swap-back. The token withholds tax in tokens. When the buffer reaches swapThreshold (0.05% of supply = 500,000), the next sell first swaps that buffer to BNB, and only then executes the user's own sell. The buffer's price impact therefore lands inside that one transaction.
09Error selectors
Factory
0x025dbdd4 InsufficientFee msg.value < creationFee()
0x37c6d202 InitialBuyTooLarge launch buy exceeds 5% of supply
0x8199f5f3 SlippageExceeded minTokensOut / minBnbOut not met
0x70f65caa DeadlinePassed past deadline
0xe6a0d45f AlreadyGraduated token has moved to PancakeSwap
0x8698bf37 UnknownToken not created by this factory
0x7c214f04 NotOperator caller is not an allowlisted router
0x1f2a2005 ZeroAmount
0xd92e233d ZeroAddress
Token
0xdb89e3f4 TransfersLocked transfer attempted during bonding
0x13be252b InsufficientAllowance user has not approved the factory
0xf4d678b8 InsufficientBalance10Events for indexing
TokenCreated 0x91de26bc430b3a4f1d6cfb11d72f2e5ca75d7622d37b2a88a8998ec28e747a11
Trade 0xf4682780a0e17045675b1fdf80687447d8cc9589bc87f718bcba8cbd53a2e81e
Graduated 0x487dc7f66c623fb0ff13f9024a3ff9675453d069e075eceb12d9f8d7870e2374event Trade(
address indexed token,
address indexed trader, // your router, when you route
address indexed recipient, // the end user
bool isBuy,
uint256 bnbAmount,
uint256 tokenAmount,
uint256 fee,
uint256 bnbReserve, // curve state AFTER this trade
uint256 tokensSold
);