DEVELOPERS / TRADING

Trading on the curve

Only Model V2 has a curve. Get its address from the factory's launch record, then read reserves and quote locally with the same constant-product formula the contract uses.

ethers v6 · AgiV2BondingCurve
const launch = await factory.getLaunchedToken(tokenAddress);
// launch.curve, launch.pairToken, launch.phase:
//   0 NotGraduated · 1 Swept · 2 PoolCreated · 3 Rescued
if (launch.phase !== 0n) throw new Error('this launch no longer trades on its curve');

const curve = new ethers.Contract(launch.curve, CURVE_ABI, signer);
const recipient = await signer.getAddress();    // who receives the tokens
const [quoteReserve, tokenReserve] = await curve.getReserves();
const feeBps   = await curve.feeBps();          // base fee
const taxBps   = await curve.creatorTaxBps();   // creator tax
const snipeBps = await curve.currentSnipeTaxBps(recipient); // measured on the recipient, not the signer

// Same math as AgiV2BondingCurveMath.getAmountOut, fees taken off the input.
const quoteIn = ethers.parseEther('0.1');
const net     = quoteIn - (quoteIn * (feeBps + taxBps + snipeBps)) / 10000n;
const out     = (net * tokenReserve) / (quoteReserve + net);
const minOut  = (out * 99n) / 100n;             // your own slippage bound

// Native quote: quoteIn must equal msg.value.
await curve.buy(quoteIn, minOut, recipient, { value: quoteIn });

// ERC-20 quote: approve the curve first and send no value.
// await erc20.approve(launch.curve, quoteIn);
// await curve.buy(quoteIn, minOut, recipient);

// Selling: approve the curve for the tokens, then
// await curve.sell(tokensIn, minQuoteOut, recipient);
Expect a partial fill on the last buy

A buy that would cross the reserved allocation is clamped and the difference refunded, and minTokensOut is then enforced as a price bound rather than a quantity bound. Read CurveBuy and CurveBuyRefunded from the receipt instead of assuming you received what you quoted. That same buy usually triggers graduation on its way out.