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 rest refunded, and
minTokensOut then bounds the price rather than the quantity. Read CurveBuy and
CurveBuyRefunded from the receipt. The same buy usually triggers graduation.