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.