// Little John Team — RC22 integration reference, ethers v6. // No private keys, signing, automatic approvals or transaction broadcasts. import {Contract, Interface, getAddress, ZeroAddress} from 'ethers'; export const CHAIN_ID=4663n; export const FACTORY='0x327a1bbbb2472f8f9bfaa5b064bf2c08eb492b76'; export const FACTORY_ABI=[ 'function curveCount() view returns(uint256)', 'function curves(uint256) view returns(address)', 'event TokenCreated(address indexed creator,address indexed token,address indexed curve,address vault,string name,string symbol,bytes32 metadataHash)' ]; export const TOKEN_ABI=[ 'function curve() view returns(address)','function decimals() view returns(uint8)', 'function balanceOf(address) view returns(uint256)','function allowance(address,address) view returns(uint256)', 'function approve(address,uint256) returns(bool)' ]; export const CURVE_ABI=[ 'function token() view returns(address)','function factory() view returns(address)', 'function graduated() view returns(bool)','function publicBuyOpensAt() view returns(uint256)', 'function creatorTaxBps() view returns(uint256)','function remainingBondGross() view returns(uint256)', 'function quoteBuy(uint256) returns(uint256 out,uint256 fee)', 'function quoteSell(uint256) returns(uint256 out,uint256 fee)', 'function quoteV4Buy() payable returns(uint256 out,uint256 fee)', 'function buy(uint256 minOut,uint256 deadline,address recipient) payable', 'function sell(uint256 amount,uint256 minOut,uint256 deadline,address recipient)', 'event Buy(address indexed buyer,address indexed recipient,uint256 grossEth,uint256 tokensOut,uint256 fee)', 'event Sell(address indexed seller,address indexed recipient,uint256 tokensIn,uint256 netEth,uint256 fee)', 'event Graduated(bytes32 indexed poolId,uint256 ethAmount,uint256 tokenAmount,uint128 liquidity,uint160 sqrtPriceX96)' ]; const ci=new Interface(CURVE_ABI),ti=new Interface(TOKEN_ABI); const same=(a,b)=>getAddress(a)===getAddress(b); const check=(ok,message)=>{if(!ok)throw Error(message);}; export function minimumOut(out,slippageBps=50){ check(typeof out==='bigint'&&out>0n,'Quote output must be positive bigint'); check(Number.isInteger(slippageBps)&&slippageBps>=0&&slippageBps<=1000,'Example permits 0–1000 slippage bps'); const result=out*BigInt(10000-slippageBps)/10000n; check(result>0n,'Minimum output rounds to zero');return result; } async function snapshot(provider){ check(BigInt(await provider.send('eth_chainId',[]))===CHAIN_ID,'Wrong chain'); const block=await provider.getBlock('latest');check(block?.hash,'Head unavailable');return block; } async function bind(provider,index,block){ index=BigInt(index);check(index>=0n,'Factory index must be nonnegative'); const opts={blockTag:block.number},f=new Contract(FACTORY,FACTORY_ABI,provider); check(index0n,'ETH value must be a positive bigint in wei'); owner=getAddress(owner);recipient=getAddress(recipient);check(owner!==ZeroAddress&&recipient!==ZeroAddress,'Zero address'); const m=await inspectCurve(provider,factoryIndex);check(!same(recipient,m.curveAddress),'Curve cannot be recipient'); const opts={from:owner,blockTag:m.block.number}; if(!m.graduated){ check(BigInt(m.block.timestamp)>=await m.curve.publicBuyOpensAt(opts),'Launch protection active'); check(value<=await m.curve.remainingBondGross(opts),'Buy exceeds remaining bond; reduce amount and requote'); } const [out,fee]=m.graduated?await m.curve.quoteV4Buy.staticCall({...opts,value}):await m.curve.quoteBuy.staticCall(value,opts); const minOut=minimumOut(out,slippageBps),deadline=BigInt(m.block.timestamp)+120n; const request={chainId:CHAIN_ID,to:m.curveAddress,from:owner,value,data:ci.encodeFunctionData('buy',[minOut,deadline,recipient])}; return {request,out,fee,minOut,deadline,quotedBlock:m.block.number,quotedBlockHash:m.block.hash,graduated:m.graduated,tokenAddress:m.tokenAddress}; } export async function prepareSell(provider,{factoryIndex,owner,recipient=owner,amount,slippageBps=50}){ check(typeof amount==='bigint'&&amount>0n,'Token amount must be a positive bigint in base units'); owner=getAddress(owner);recipient=getAddress(recipient);check(owner!==ZeroAddress&&recipient!==ZeroAddress,'Zero address'); const m=await inspectCurve(provider,factoryIndex);check(!same(recipient,m.curveAddress),'Curve cannot be recipient'); const opts={from:owner,blockTag:m.block.number}; check(await m.token.balanceOf(owner,opts)>=amount,'Insufficient token balance'); if(await m.token.allowance(owner,m.curveAddress,opts)