@peachprojects/aggregator-sdk 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -67,7 +67,7 @@ const quote = await client.getQuote({
67
67
  byAmountIn?: boolean, // true: input→output (default: true)
68
68
  depth?: number, // Route search depth (default: 3)
69
69
  splitCount?: number, // Trade split count (default: 20)
70
- providers?: string[], // DEX providers (default: all 7 — PANCAKEV2, PANCAKEV3, PANCAKE_INFINITY_CL, UNISWAPV3, UNISWAPV4, DODO, THENA)
70
+ providers?: string[], // DEX providers (default: all 11 — PANCAKEV2, PANCAKEV3, PANCAKE_INFINITY_CL, UNISWAPV3, UNISWAPV4, DODO, THENAV3, NOMISWAP_STABLE, BISWAP, APESWAP, BABYDOGESWAP)
71
71
  deadlineSeconds?: number, // Tx deadline in seconds (default: 1200 = 20min)
72
72
  }
73
73
  });
package/dist/index.cjs CHANGED
@@ -1,4 +1,4 @@
1
- "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const p=require("ethers"),L="https://api.peach.ag",B=50,P=10000n,C=1200,x=6e4,S=[50,100,200,400,800,1200],F="0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";function _(w){return w.toLowerCase()===F.toLowerCase()}const K=4001;var m=(w=>(w.PancakeV2="PancakeV2",w.PancakeV3="PancakeV3",w.PancakeInfinityCl="Pancake_Infinity_Cl",w.UniswapV3="UniswapV3",w.UniswapV4="UniswapV4",w.Dodo="Dodo",w.Thena="Thena",w))(m||{});const W={chainId:56,rpcUrl:"https://bsc-dataseed.binance.org",weth:"0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c",adapters:[]},G={chainId:97,rpcUrl:"https://bsc-testnet-rpc.publicnode.com",weth:"0xae13d989daC2f0dEbFf460aC112a837C89BAa7cd",adapters:[]};class R extends Error{constructor(t,e,n){super(t),this.name="ExecuteTimeoutError",this.stage=e,this.txHash=n}}const I={depth:3,splitCount:20,providers:["PANCAKEV2","PANCAKEV3","PANCAKE_INFINITY_CL","UNISWAPV3","UNISWAPV4","DODO","THENA"],clientVersion:1001500},H=1e4;class U{constructor(t={}){this.baseUrl=t.baseUrl||L,this.timeout=t.timeout||H}async findRoutes(t){const{from:e,target:n,amount:o,byAmountIn:s=!0,depth:r=I.depth,splitCount:a=I.splitCount,providers:i=I.providers}=t,c=new URLSearchParams({from:e,target:n,amount:o.toString(),by_amount_in:s.toString(),depth:r.toString(),split_count:a.toString(),providers:i.join(","),v:I.clientVersion.toString()}),u=`${this.baseUrl}/router/find_routes?${c}`,d=new AbortController,l=setTimeout(()=>d.abort(),this.timeout);try{const f=await fetch(u,{method:"GET",headers:{Accept:"application/json"},signal:d.signal});if(clearTimeout(l),!f.ok)throw new A(`API request failed: ${f.status} ${f.statusText}`,f.status);const v=await f.json();if(v.code!==200)throw new A(v.msg||"Route not found",v.code);if(!v.data||!v.data.paths||v.data.paths.length===0)throw new A("No routes found",404);return v.data}catch(f){throw clearTimeout(l),f instanceof A?f:f instanceof Error?f.name==="AbortError"?new A("API request timeout",408):new A(`API request failed: ${f.message}`,0):new A("Unknown API error",0)}}async getStatus(){const t=`${this.baseUrl}/router/status`,e=new AbortController,n=setTimeout(()=>e.abort(),this.timeout);try{const o=await fetch(t,{method:"GET",headers:{Accept:"application/json"},signal:e.signal});if(clearTimeout(n),!o.ok)throw new A(`API request failed: ${o.status} ${o.statusText}`,o.status);const s=await o.json();if(s.code!==200)throw new A(s.msg||"Failed to get status",s.code);return s.data}catch(o){throw clearTimeout(n),o instanceof A?o:o instanceof Error?o.name==="AbortError"?new A("API request timeout",408):new A(`API request failed: ${o.message}`,0):new A("Unknown API error",0)}}async getAvailableProviders(){return(await this.getStatus()).providers}setBaseUrl(t){this.baseUrl=t}getBaseUrl(){return this.baseUrl}}class A extends Error{constructor(t,e){super(t),this.name="ApiError",this.code=e}}async function y(w,t=x){if(t<=0)return w;let e;try{return await Promise.race([w,new Promise((n,o)=>{e=setTimeout(()=>{o(new R(`Wallet did not settle sendTransaction within ${t}ms.`,"wallet_send"))},t)})])}finally{e&&clearTimeout(e)}}const j=["function swap((address srcToken, address dstToken, uint256 amountIn, uint256 amountOutMin, (address adapter, address pool, address tokenIn, address tokenOut, uint256 amountIn, bytes extraData)[] steps, address[] intermediateTokens, uint256 deadline, bytes32 quoteId, uint256 expectAmountOut) params) external returns (uint256 amountOut)","function swapETH((address srcToken, address dstToken, uint256 amountIn, uint256 amountOutMin, (address adapter, address pool, address tokenIn, address tokenOut, uint256 amountIn, bytes extraData)[] steps, address[] intermediateTokens, uint256 deadline, bytes32 quoteId, uint256 expectAmountOut) params) external payable returns (uint256 amountOut)","function isAdapterRegistered(address adapter) external view returns (bool)","function WETH() external view returns (address)"],k=["function approve(address spender, uint256 amount) external returns (bool)","function allowance(address owner, address spender) external view returns (uint256)","function balanceOf(address account) external view returns (uint256)","function decimals() external view returns (uint8)","function symbol() external view returns (string)"],q="0xa0FfB9c1CE1Fe56963B0321B32E7A0302114058b",D={PANCAKEV3:m.PancakeV3,PANCAKEV2:m.PancakeV2,PANCAKE_INFINITY_CL:m.PancakeInfinityCl,UNISWAPV3:m.UniswapV3,UNISWAPV4:m.UniswapV4,DODO:m.Dodo,THENA:m.Thena};class Z{constructor(t,e,n){this.config=t,this.provider=e||new p.ethers.JsonRpcProvider(t.rpcUrl),this.routerContract=new p.ethers.Contract(t.routerAddress||p.ethers.ZeroAddress,j,this.provider),this.apiClient=new U(n?.api)}getRouterAddress(t){const e=t.routerAddress||this.config.routerAddress;if(!e||e===p.ethers.ZeroAddress)throw new Error("No router address available. Provide routerAddress in config or use API-based getQuote.");return e}applySlippage(t,e){if(e<0||e>1e4)throw new Error("slippageBps must be between 0 and 10000");const n=t.amountOutMin*(P-BigInt(e))/P;return{...t,amountOutMin:n}}async swap(t,e,n){const o=this.getRouterAddress(t),{tx:s,method:r}=this.buildSwapTransactionRequest(t,n);let a;return t.srcNative||(a=await this.buildApprovalRequest(t.srcToken,e,t.amountIn,o,n)),{routerAddress:o,method:r,tx:s,approval:a}}async execute(t,e,n){const o=await e.getAddress(),s=await this.swap(t,o,n);try{return s.approval&&await(await this.sendTransactionWithTimeout(e,s.approval.tx,n)).wait(),await this.sendTransactionWithTimeout(e,s.tx,n)}catch(r){const a=r instanceof Error?r.message:String(r),i=/estimateGas/i.test(a),c=/missing revert data/i.test(a)||a.includes("reason=null")&&a.includes("data=null");if(i&&(c||/reason=null|data=null/.test(a))){const u="Transaction reverted during gas estimation and the RPC did not return a revert reason. Try: 1) Get a fresh quote and confirm immediately 2) Switch network or RPC 3) Increase slippage.",d=new Error(`${u} (estimateGas/missing revert data)`);throw d.cause=r,d}throw r}}encodeParams(t){return{srcToken:t.srcToken,dstToken:t.dstToken,amountIn:t.amountIn,amountOutMin:t.amountOutMin,steps:t.steps.map(e=>({adapter:e.adapter,pool:e.pool,tokenIn:e.tokenIn,tokenOut:e.tokenOut,amountIn:e.amountIn,extraData:e.extraData})),intermediateTokens:t.intermediateTokens,deadline:t.deadline,quoteId:t.quoteId,expectAmountOut:t.expectAmountOut}}getProtocolForProvider(t){if(t in D)return D[t];throw new Error(`Unsupported provider: ${t}`)}encodeSwapCalldata(t,e){const n=t.routerAddress??this.config.routerAddress??this.routerContract.target,o=this.applySlippage(t.params,e),s=t.srcNative===!0||t.dstNative===!0,r=this.encodeParams(o);if(s){const a=this.routerContract.interface.encodeFunctionData("swapETH",[r]);return{to:n,data:a,value:t.srcNative?t.amountIn:0n,method:"swapETH"}}else{const a=this.routerContract.interface.encodeFunctionData("swap",[r]);return{to:n,data:a,value:0n,method:"swap"}}}buildSwapTransactionRequest(t,e){const{to:n,data:o,value:s,method:r}=this.encodeSwapCalldata(t,e.slippageBps);return{method:r,tx:this.applyTxOverrides({to:n,data:o,value:s},e,!0)}}async buildApprovalRequest(t,e,n,o,s){const r=await this.getAllowance(t,e,o);if(!(r>=n))return{token:t,owner:e,spender:o,currentAllowance:r,requiredAmount:n,approveAmount:p.ethers.MaxUint256,tx:this.buildApprovalTransactionRequest(t,o,s)}}buildApprovalTransactionRequest(t,e,n){const s=new p.ethers.Interface(k).encodeFunctionData("approve",[e,p.ethers.MaxUint256]);return this.applyTxOverrides({to:t,data:s,value:0n},n,!1)}async getAllowance(t,e,n){return new p.ethers.Contract(t,k,this.provider).allowance(e,n)}applyTxOverrides(t,e,n){const o={...t};return e.gasPrice&&(o.gasPrice=e.gasPrice),n&&e.gasLimit&&(o.gasLimit=e.gasLimit),o}async sendTransactionWithTimeout(t,e,n){const o=n.timeoutMs??x;if(o<=0)return t.sendTransaction(e);const s=t;if(typeof s.sendUncheckedTransaction=="function"&&s.provider){const r=await y(s.sendUncheckedTransaction(e),o),a=await this.waitForTransactionResponse(s.provider,r,o,n.transactionResponsePollingIntervalsMs);if(a.response)return a.response;const i=a.rpcErrors>0?`${a.rpcErrors} transient provider error(s) and ${a.nullResponses} null response(s)`:`${a.nullResponses} null response(s)`;throw new R(`Transaction was broadcast but provider did not return TransactionResponse within ${o}ms (${i}).`,"provider_index",r)}return y(t.sendTransaction(e),o)}async waitForTransactionResponse(t,e,n,o=S){const s=Date.now()+n;let r=0,a=0,i=0;for(;Date.now()<s;){try{const u=await t.getTransaction(e);if(u)return{response:u,nullResponses:a,rpcErrors:i};a++}catch{i++}const c=this.getNextPollingDelay(o,r);r++,await this.delay(Math.min(c,Math.max(25,s-Date.now())))}return{response:null,nullResponses:a,rpcErrors:i}}async delay(t){t<=0||await new Promise(e=>{setTimeout(e,t)})}getNextPollingDelay(t,e){return t.length===0?S.at(-1)??1200:t[Math.min(e,t.length-1)]??1200}async getTokenInfo(t,e){const n=new p.ethers.Contract(t,k,this.provider),[o,s,r]=await Promise.all([n.symbol(),n.decimals(),e?n.balanceOf(e):Promise.resolve(void 0)]);return r===void 0?{symbol:o,decimals:s}:{symbol:o,decimals:s,balance:r}}async getBalance(t,e){return new p.ethers.Contract(t,k,this.provider).balanceOf(e)}async getQuote(t){const{srcToken:e,dstToken:n,amountIn:o,options:s={}}=t,{byAmountIn:r=!0,depth:a=I.depth,splitCount:i=I.splitCount,providers:c=I.providers,deadlineSeconds:u=C}=s,d=_(e),l=_(n),f=d?this.config.weth:e,v=l?this.config.weth:n,E=await this.apiClient.findRoutes({from:f,target:v,amount:o,byAmountIn:r,depth:a,splitCount:i,providers:c});return this.buildQuoteFromApi(E,f,v,u,c,d,l)}filterPathsByProviders(t,e,n,o){const s=new Set(e.map(l=>l.toUpperCase()));let r=t.filter(l=>s.has(l.provider.toUpperCase()));if(r.length===t.length)return r;const a=n.toLowerCase(),i=o.toLowerCase(),c=new Set;c.add(a);let u=!0;for(;u;){u=!1;for(const l of r)c.has(l.token_in.toLowerCase())&&!c.has(l.token_out.toLowerCase())&&(c.add(l.token_out.toLowerCase()),u=!0)}const d=new Set;for(d.add(i),u=!0;u;){u=!1;for(const l of r)d.has(l.token_out.toLowerCase())&&!d.has(l.token_in.toLowerCase())&&(d.add(l.token_in.toLowerCase()),u=!0)}return r.length,r=r.filter(l=>c.has(l.token_in.toLowerCase())&&d.has(l.token_out.toLowerCase())),r}buildQuoteFromRouteData(t,e,n,o,s){const r=this.buildQuoteFromRouteDataInternal(t,e,n,o??C,void 0);return s?.srcNative&&(r.srcNative=!0),s?.dstNative&&(r.dstNative=!0),r}buildQuoteFromApi(t,e,n,o,s,r,a){const i=this.buildQuoteFromRouteDataInternal(t,e,n,o,s);return r&&(i.srcNative=!0),a&&(i.dstNative=!0),i}buildQuoteFromRouteDataInternal(t,e,n,o,s){const r=e.toLowerCase();t.paths.length;let a=t.paths.filter(h=>!(!(h.token_in.toLowerCase()===r)&&BigInt(h.amount_in)===0n&&BigInt(h.amount_out)===0n));if(a.length===0)throw new A("All route paths have zero amounts",4001);if(s&&s.length>0&&(a=this.filterPathsByProviders(a,s,e,n),a.length===0))throw new A("No valid route paths remaining after provider filtering",4001);const i=n.toLowerCase();let c=0n,u=0n;for(const h of a)h.token_in.toLowerCase()===r&&(c+=BigInt(h.amount_in)),h.token_out.toLowerCase()===i&&(u+=BigInt(h.amount_out));const d=BigInt(Math.floor(Date.now()/1e3)+o),l=new Map;for(const h of a){const g=h.token_in.toLowerCase();l.set(g,(l.get(g)??0)+1)}const f=new Map,v=a.map(h=>{const g=h.token_in.toLowerCase(),b=(f.get(g)??0)+1;f.set(g,b);const N=l.get(g),M=N>1&&b<N,$=g===r;return{adapter:h.adapter,pool:h.provider.toUpperCase()==="PANCAKE_INFINITY_CL"?q:h.provider.toUpperCase()==="UNISWAPV4"?p.ethers.ZeroAddress:h.pool,tokenIn:h.token_in,tokenOut:h.token_out,amountIn:$||M?BigInt(h.amount_in):0n,extraData:h.extra_data||"0x"}}),E=new Set;for(const h of a)h.token_out.toLowerCase()!==i&&E.add(h.token_out);const T=Array.from(E),O={srcToken:e,dstToken:n,amountIn:c,amountOutMin:u,steps:v,intermediateTokens:T,deadline:d,quoteId:t.request_id?p.ethers.id(t.request_id).slice(0,66):p.ethers.ZeroHash,expectAmountOut:u},V={routes:[{steps:a.map(h=>({pool:{address:h.pool,token0:h.token_in,token1:h.token_out,protocol:this.getProtocolForProvider(h.provider),fee:h.fee_rate?Math.round(parseFloat(h.fee_rate)*1e6):void 0},tokenIn:h.token_in,tokenOut:h.token_out,amountIn:BigInt(h.amount_in),amountOut:BigInt(h.amount_out)})),amountIn:c,amountOut:u,gasEstimate:BigInt(t.gas)}],percentages:[1e4],totalAmountIn:c,totalAmountOut:u,totalGasEstimate:BigInt(t.gas)};if(!t.contracts?.router)throw new A("API response missing contracts.router address",4002);return{srcToken:e,dstToken:n,amountIn:c,amountOut:u,priceImpact:parseFloat(t.deviation_ratio||"0"),route:V,params:O,gasEstimate:BigInt(t.gas),routerAddress:t.contracts?.router}}async getAvailableProviders(){return this.apiClient.getAvailableProviders()}async simulate(t,e,n,o){const s=n||p.ethers.ZeroAddress,{to:r,data:a,value:i,method:c}=this.encodeSwapCalldata(t,e);if(console.log("[PeachClient] simulate using router:",r),o){const u=this.getJsonRpcProviderForStateOverrides(),d=i>0n?"0x"+i.toString(16):void 0,l=await u.send("eth_call",[{from:s,to:r,data:a,value:d},"latest",o]),[f]=this.routerContract.interface.decodeFunctionResult(c,l);return{amountOut:f,method:c}}else{const u=await this.provider.call({from:s,to:r,data:a,value:i>0n?i:void 0}),[d]=this.routerContract.interface.decodeFunctionResult(c,u);return{amountOut:d,method:c}}}getJsonRpcProviderForStateOverrides(){if(typeof this.provider.send!="function")throw new Error("stateOverrides require a JsonRpcProvider-compatible provider with send(method, params).");return this.provider}formatSimulateError(t,e,n,o){const s=t instanceof Error?t:new Error(String(t)),r=t;let a="unknown";if(r.reason&&typeof r.reason=="string")a=r.reason;else if(r.revert&&typeof r.revert=="object"){const d=r.revert;d.args&&Array.isArray(d.args)&&(a=d.args.join(", "))}else s.message&&(a=s.message);const i=e.params.steps.map((d,l)=>` Step ${l}: ${d.tokenIn.slice(0,10)}→${d.tokenOut.slice(0,10)} via adapter ${d.adapter.slice(0,10)} pool ${d.pool.slice(0,10)}`).join(`
2
- `),c=[`Simulate ${n} failed: ${a}`,` Route: ${e.srcToken} → ${e.dstToken}`,` AmountIn: ${e.amountIn}`,` AmountOutMin: ${e.params.amountOutMin}`,` Router: ${e.routerAddress}`,` Caller: ${o}`,` Steps (${e.params.steps.length}):`,i].join(`
3
- `),u=new Error(c);return u.cause=s,u.reason=a,u}async findFailingStep(t,e,n,o,s){const r=t.params.steps;if(!r.length)return null;const a=s!=null?this.normalizeRevertReason(s):void 0;for(let i=1;i<=r.length;i++){const c=this.quoteWithFirstNSteps(t,i);try{await this.simulate(c,e,n,o)}catch(u){const d=this.normalizeRevertReason(u),l=u?.message??u?.reason??u?.shortMessage;if(a!=null?d===a:!0)return{stepIndex:i-1,step:r[i-1],error:u,revertMessage:typeof l=="string"?l:void 0,fullRouteRevertMessage:a??void 0}}}return null}normalizeRevertReason(t){if(t==null)return;const e=t;if(typeof e.reason=="string"&&e.reason.length>0)return e.reason;const n=e.shortMessage??e.message;if(typeof n!="string")return;const o=n.match(/reason="([^"]+)"/);if(o)return o[1];const s=n.match(/reverted:\s*"([^"]+)"/);if(s)return s[1];const r=n.match(/execution reverted:\s*"([^"]+)"/);if(r)return r[1]}quoteWithFirstNSteps(t,e){const n=t.params.steps.slice(0,e),o=t.dstToken.toLowerCase(),s=new Set,r=[];for(const a of n){const i=a.tokenOut.toLowerCase();i!==o&&!s.has(i)&&(s.add(i),r.push(a.tokenOut))}return{...t,params:{...t.params,steps:n,intermediateTokens:r}}}buildStateOverrides(t,e,n,o,s,r){if(r?.isNative||_(t))return{};if(!n||n===p.ethers.ZeroAddress)throw new Error("buildStateOverrides requires a non-zero routerAddress.");const a=p.ethers.AbiCoder.defaultAbiCoder(),i=o||p.ethers.parseUnits("1000000",18),c=s??n,u=p.ethers.zeroPadValue(p.ethers.toBeHex(i),32),d=p.ethers.zeroPadValue(p.ethers.toBeHex(p.ethers.MaxUint256),32),l={},f=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,50,51,52,100,101,102];for(const v of f){const E=p.ethers.keccak256(a.encode(["address","uint256"],[e,v]));l[E]=u;const T=p.ethers.keccak256(a.encode(["address","uint256"],[e,v])),O=p.ethers.keccak256(a.encode(["address","bytes32"],[c,T]));l[O]=d}return{[t.toLowerCase()]:{stateDiff:l}}}}const Q=["function token0() external view returns (address)","function token1() external view returns (address)","function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast)"],Y=["function token0() external view returns (address)","function token1() external view returns (address)","function fee() external view returns (uint24)","function liquidity() external view returns (uint128)","function slot0() external view returns (uint160 sqrtPriceX96, int24 tick, uint16 observationIndex, uint16 observationCardinality, uint16 observationCardinalityNext, uint32 feeProtocol, bool unlocked)"],z="0xcA143Ce32Fe78f1f7019d7d551a6402fC5350c73",J=["function getPair(address tokenA, address tokenB) external view returns (address pair)"],X="0x0BFbCF9fa4f9C56B0F40a671Ad40E0805A091865",tt=["function getPool(address tokenA, address tokenB, uint24 fee) external view returns (address pool)"],et=[100,500,2500,1e4],nt=60000n,ot=120000n,rt=100000n;class st{constructor(t,e){this.poolCache=new Map,this.provider=t,this.config=e,this.v2Factory=new p.ethers.Contract(z,J,t),this.v3Factory=new p.ethers.Contract(X,tt,t)}async findBestRoute(t,e,n){const o=await this.discoverPools(t,e);if(o.length===0)throw new Error(`No pools found for ${t} -> ${e}`);const r=(await this.calculateRoutes(o,t,e,n)).reduce((a,i)=>i.amountOut>a.amountOut?i:a);return{routes:[r],percentages:[1e4],totalAmountIn:n,totalAmountOut:r.amountOut,totalGasEstimate:r.gasEstimate}}async discoverPools(t,e){const n=[];try{const o=await this.findV2Pool(t,e);o&&n.push(o)}catch{}for(const o of et)try{const s=await this.findV3Pool(t,e,o);s&&n.push(s)}catch{}return n}async findV2Pool(t,e){const n=`v2-${t}-${e}`;if(this.poolCache.has(n))return this.poolCache.get(n);const o=await this.v2Factory.getPair(t,e);if(o===p.ethers.ZeroAddress)return null;const s=new p.ethers.Contract(o,Q,this.provider),[r,a,i]=await Promise.all([s.token0(),s.token1(),s.getReserves()]),c={address:o,token0:r,token1:a,protocol:m.PancakeV2,reserve0:i.reserve0,reserve1:i.reserve1};return this.poolCache.set(n,c),c}async findV3Pool(t,e,n){const o=`v3-${t}-${e}-${n}`;if(this.poolCache.has(o))return this.poolCache.get(o);const s=await this.v3Factory.getPool(t,e,n);if(s===p.ethers.ZeroAddress)return null;const r=new p.ethers.Contract(s,Y,this.provider),[a,i,c]=await Promise.all([r.token0(),r.token1(),r.liquidity()]);if(c===0n)return null;const u={address:s,token0:a,token1:i,protocol:m.PancakeV3,fee:n,liquidity:c};return this.poolCache.set(o,u),u}async calculateRoutes(t,e,n,o){const s=[];for(const r of t)try{const a=await this.getAmountOut(r,e,n,o);a>0n&&s.push({steps:[{pool:r,tokenIn:e,tokenOut:n,amountIn:o,amountOut:a}],amountIn:o,amountOut:a,gasEstimate:r.protocol===m.PancakeV2?nt:r.protocol===m.Dodo?rt:ot})}catch{}return s}async getAmountOut(t,e,n,o){if(t.protocol===m.PancakeV2)return this.getV2AmountOut(t,e,o);if(t.protocol===m.Dodo)throw new Error("DODO amount out not supported in local route discovery");return this.estimateV3AmountOut(t,e,o)}getV2AmountOut(t,e,n){const o=e.toLowerCase()===t.token0.toLowerCase(),[s,r]=o?[t.reserve0,t.reserve1]:[t.reserve1,t.reserve0],a=n*9975n,i=a*r,c=s*10000n+a;return i/c}estimateV3AmountOut(t,e,n){const s=1000000n-BigInt(t.fee||2500);return n*s/1000000n}clearCache(){this.poolCache.clear()}}class at{constructor(t){this.adapters=t}build(t,e,n,o,s=C){const r=this.flattenRoutes(t),a=this.mergeIdenticalPools(r),i=this.topologicalSort(a,e),c=this.convertToSwapSteps(i),u=this.extractIntermediates(i,e,n),d=BigInt(Math.floor(Date.now()/1e3)+s);return{srcToken:e,dstToken:n,amountIn:t.totalAmountIn,amountOutMin:o,steps:c,intermediateTokens:u,deadline:d,quoteId:p.ethers.ZeroHash,expectAmountOut:t.totalAmountOut}}flattenRoutes(t){const e=[];for(let n=0;n<t.routes.length;n++){const o=t.routes[n],s=t.percentages[n],r=t.totalAmountIn*BigInt(s)/P;for(let a=0;a<o.steps.length;a++){const i=o.steps[a];e.push({pool:i.pool,tokenIn:i.tokenIn,tokenOut:i.tokenOut,amountIn:a===0?r:0n,routeIndex:n,stepIndex:a})}}return e}mergeIdenticalPools(t){const e=new Map,n=[];for(const o of t){const s=`${o.pool.address}-${o.tokenIn}-${o.tokenOut}`;if(e.has(s)){const r=e.get(s);o.amountIn>0n&&r.amountIn>0n&&(r.amountIn=0n)}else{const r={...o};e.set(s,r),n.push(r)}}for(const o of n){const s=`${o.pool.address}-${o.tokenIn}-${o.tokenOut}`;t.filter(a=>`${a.pool.address}-${a.tokenIn}-${a.tokenOut}`===s).length>1&&(o.amountIn=0n)}return n}topologicalSort(t,e){const n=new Map,o=new Map,s=new Map;for(const i of t){const c=this.stepKey(i);o.set(c,i),n.set(c,0),s.set(c,[])}for(const i of t){const c=this.stepKey(i);if(i.tokenIn!==e){for(const u of t)if(u.tokenOut===i.tokenIn){const d=this.stepKey(u);d!==c&&(s.get(d).push(c),n.set(c,(n.get(c)||0)+1))}}}const r=[];for(const[i,c]of n)c===0&&r.push(i);const a=[];for(;r.length>0;){const i=r.shift(),c=o.get(i);a.push(c);for(const u of s.get(i)||[]){const d=(n.get(u)||0)-1;n.set(u,d),d===0&&r.push(u)}}if(a.length!==t.length)throw new Error("Circular dependency detected in route");return a}convertToSwapSteps(t){return t.map(e=>{const n=this.adapters.get(e.pool.protocol);if(!n)throw new Error(`No adapter for protocol: ${e.pool.protocol}`);return{adapter:n,pool:e.pool.address,tokenIn:e.tokenIn,tokenOut:e.tokenOut,amountIn:e.amountIn,extraData:this.encodeExtraData(e.pool)}})}encodeExtraData(t){switch(t.protocol){case m.PancakeV2:return"0x";case m.PancakeV3:case m.UniswapV3:case m.UniswapV4:return"0x";case m.Dodo:return"0x";default:return"0x"}}extractIntermediates(t,e,n){const o=new Set;for(const s of t)s.tokenOut!==n&&o.add(s.tokenOut);return o.delete(e),o.delete(n),Array.from(o)}stepKey(t){return`${t.pool.address}-${t.tokenIn}-${t.tokenOut}`}}exports.API_DEFAULTS=I;exports.ApiClient=U;exports.ApiError=A;exports.BPS_DENOMINATOR=P;exports.BSC_MAINNET_CONFIG=W;exports.BSC_TESTNET_CONFIG=G;exports.DEFAULT_API_URL=L;exports.DEFAULT_DEADLINE_SECONDS=C;exports.DEFAULT_EXECUTE_TIMEOUT_MS=x;exports.DEFAULT_SLIPPAGE_BPS=B;exports.DEFAULT_TRANSACTION_RESPONSE_POLL_INTERVALS_MS=S;exports.ERR_ZERO_AMOUNT_PATHS=K;exports.ExecuteTimeoutError=R;exports.NATIVE_TOKEN_ADDRESS=F;exports.PeachClient=Z;exports.ProtocolType=m;exports.RouteDiscovery=st;exports.SwapBuilder=at;exports.isNativeTokenAddress=_;exports.withWalletSendTimeout=y;
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const h=require("ethers"),Y="https://api.peach.ag",J=50,P=10000n,N=500,ee=5e3,R=1200,V=6e4,F=[50,100,200,400,800,1200],Q="0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";function T(f){return f.toLowerCase()===Q.toLowerCase()}const te=4001;var d=(f=>(f.PancakeV2="PancakeV2",f.PancakeV3="PancakeV3",f.PancakeInfinityCl="Pancake_Infinity_Cl",f.PancakeInfinityLb="Pancake_Infinity_Lb",f.UniswapV3="UniswapV3",f.UniswapV4="UniswapV4",f.Dodo="Dodo",f.ThenaV3="ThenaV3",f.ThenaFusion="Thena_Fusion",f.NomiswapStable="Nomiswap_Stable",f.Biswap="Biswap",f.Apeswap="Apeswap",f.BabyDogeSwap="BabyDogeSwap",f.BabySwap="BabySwap",f.PancakeStable="Pancake_Stable",f.ListaStable="Lista_Stable",f.SquadSwapV3="SquadSwap_V3",f.SquadSwapV2="SquadSwap_V2",f.Wombat="Wombat",f.SushiSwapV2="SushiSwap_V2",f.SushiSwapV3="SushiSwap_V3",f))(d||{});const ne={chainId:56,rpcUrl:"https://bsc-dataseed.binance.org",weth:"0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c",adapters:[]},re={chainId:97,rpcUrl:"https://bsc-testnet-rpc.publicnode.com",weth:"0xae13d989daC2f0dEbFf460aC112a837C89BAa7cd",adapters:[]};class x extends Error{constructor(e){super(e),this.name="CustomFeeError"}}class U extends Error{constructor(e,t,n){super(e),this.name="ExecuteTimeoutError",this.stage=t,this.txHash=n}}const _={depth:3,splitCount:20,providers:["PANCAKEV2","PANCAKEV3","PANCAKE_INFINITY_CL","PANCAKE_INFINITY_LB","UNISWAPV3","UNISWAPV4","DODO","THENAV3","NOMISWAP_STABLE","BISWAP","APESWAP","BABYDOGESWAP","BABYSWAP","PANCAKE_STABLE","LISTA_STABLE","SQUADSWAP_V3","SQUADSWAP_V2","WOMBAT","THENA_FUSION","SUSHISWAP_V2","SUSHISWAP_V3"],clientVersion:1001500},oe=1e4;function se(f){const e={};return f.forEach((t,n)=>{e[n]=t}),e}class Z{constructor(e={}){this.baseUrl=e.baseUrl||Y,this.timeout=e.timeout||oe,this.extraHeaders=e.headers?{...e.headers}:{}}async findRoutes(e){const{from:t,target:n,amount:r,byAmountIn:o=!0,depth:s=_.depth,splitCount:a=_.splitCount,providers:i=_.providers,includeResponseHeaders:c=!1}=e,u=new URLSearchParams({from:t,target:n,amount:r.toString(),by_amount_in:o.toString(),depth:s.toString(),split_count:a.toString(),providers:i.join(","),v:_.clientVersion.toString()}),l=`${this.baseUrl}/router/find_routes?${u}`,p=new AbortController,A=setTimeout(()=>p.abort(),this.timeout);try{const m=await fetch(l,{method:"GET",headers:{Accept:"application/json",...this.extraHeaders},signal:p.signal});if(clearTimeout(A),!m.ok)throw new g(`API request failed: ${m.status} ${m.statusText}`,m.status);const S=await m.json();if(S.code!==200)throw new g(S.msg||"Route not found",S.code);if(!S.data||!S.data.paths||S.data.paths.length===0)throw new g("No routes found",404);return c?{data:S.data,responseHeaders:se(m.headers)}:S.data}catch(m){throw clearTimeout(A),m instanceof g?m:m instanceof Error?m.name==="AbortError"?new g("API request timeout",408):new g(`API request failed: ${m.message}`,0):new g("Unknown API error",0)}}async getStatus(){const e=`${this.baseUrl}/router/status`,t=new AbortController,n=setTimeout(()=>t.abort(),this.timeout);try{const r=await fetch(e,{method:"GET",headers:{Accept:"application/json",...this.extraHeaders},signal:t.signal});if(clearTimeout(n),!r.ok)throw new g(`API request failed: ${r.status} ${r.statusText}`,r.status);const o=await r.json();if(o.code!==200)throw new g(o.msg||"Failed to get status",o.code);return o.data}catch(r){throw clearTimeout(n),r instanceof g?r:r instanceof Error?r.name==="AbortError"?new g("API request timeout",408):new g(`API request failed: ${r.message}`,0):new g("Unknown API error",0)}}async getAvailableProviders(){return(await this.getStatus()).providers}setBaseUrl(e){this.baseUrl=e}getBaseUrl(){return this.baseUrl}}class g extends Error{constructor(e,t){super(e),this.name="ApiError",this.code=t}}async function L(f,e=V){if(e<=0)return f;let t;try{return await Promise.race([f,new Promise((n,r)=>{t=setTimeout(()=>{r(new U(`Wallet did not settle sendTransaction within ${e}ms.`,"wallet_send"))},e)})])}finally{t&&clearTimeout(t)}}const y=["function swap((address srcToken, address dstToken, uint256 amountIn, uint256 amountOutMin, (address adapter, address pool, address tokenIn, address tokenOut, uint256 amountIn, bytes extraData)[] steps, address[] intermediateTokens, uint256 deadline, bytes32 quoteId, uint256 expectAmountOut, address feeReceiver, uint16 feeBps) params) external returns (uint256 amountOut)","function swapETH((address srcToken, address dstToken, uint256 amountIn, uint256 amountOutMin, (address adapter, address pool, address tokenIn, address tokenOut, uint256 amountIn, bytes extraData)[] steps, address[] intermediateTokens, uint256 deadline, bytes32 quoteId, uint256 expectAmountOut, address feeReceiver, uint16 feeBps) params) external payable returns (uint256 amountOut)","function isAdapterRegistered(address adapter) external view returns (bool)","function getAdapterProtocolId(address adapter) external view returns (bytes32)","function WETH() external view returns (address)","function owner() external view returns (address)","function pendingOwner() external view returns (address)","function paused() external view returns (bool)","function maxCustomFeeBps() external view returns (uint16)","function maxProtocolCutBps() external view returns (uint16)","function protocolFeeReceiver() external view returns (address)","function protocolCutBps() external view returns (uint16)"],b=["function approve(address spender, uint256 amount) external returns (bool)","function allowance(address owner, address spender) external view returns (uint256)","function balanceOf(address account) external view returns (uint256)","function decimals() external view returns (uint8)","function symbol() external view returns (string)"],ae="0xa0FfB9c1CE1Fe56963B0321B32E7A0302114058b",ie="0xC697d2898e0D09264376196696c51D7aBbbAA4a9",ce="0x28e2ea090877bf75740558f6bfb36a5ffee9e9df",K={PANCAKEV3:d.PancakeV3,PANCAKEV2:d.PancakeV2,PANCAKE_INFINITY_CL:d.PancakeInfinityCl,PANCAKE_INFINITY_LB:d.PancakeInfinityLb,UNISWAPV3:d.UniswapV3,UNISWAPV4:d.UniswapV4,DODO:d.Dodo,THENAV3:d.ThenaV3,NOMISWAP_STABLE:d.NomiswapStable,BISWAP:d.Biswap,APESWAP:d.Apeswap,BABYDOGESWAP:d.BabyDogeSwap,BABYSWAP:d.BabySwap,PANCAKE_STABLE:d.PancakeStable,LISTA_STABLE:d.ListaStable,SQUADSWAP_V3:d.SquadSwapV3,SQUADSWAP_V2:d.SquadSwapV2,WOMBAT:d.Wombat,THENA_FUSION:d.ThenaFusion,SUSHISWAP_V2:d.SushiSwapV2,SUSHISWAP_V3:d.SushiSwapV3},O=class O{constructor(e,t,n){this.config=e,this.provider=t||new h.ethers.JsonRpcProvider(e.rpcUrl),this.routerContract=new h.ethers.Contract(e.routerAddress||h.ethers.ZeroAddress,y,this.provider),this.apiClient=new Z(n?.api)}getRouterAddress(e){const t=e.routerAddress||this.config.routerAddress;if(!t||t===h.ethers.ZeroAddress)throw new Error("No router address available. Provide routerAddress in config or use API-based getQuote.");return t}applySlippage(e,t){if(t<0||t>1e4)throw new Error("slippageBps must be between 0 and 10000");const n=e.amountOutMin*(P-BigInt(t))/P;return{...e,amountOutMin:n}}async swap(e,t,n){const r=this.getRouterAddress(e),{tx:o,method:s}=this.buildSwapTransactionRequest(e,n);let a;return e.srcNative||(a=await this.buildApprovalRequest(e.srcToken,t,e.amountIn,r,n)),{routerAddress:r,method:s,tx:o,approval:a}}async execute(e,t,n){const r=await t.getAddress(),o=await this.swap(e,r,n);try{return o.approval&&await(await this.sendTransactionWithTimeout(t,o.approval.tx,n)).wait(),await this.sendTransactionWithTimeout(t,o.tx,n)}catch(s){const a=s instanceof Error?s.message:String(s),i=/estimateGas/i.test(a),c=/missing revert data/i.test(a)||a.includes("reason=null")&&a.includes("data=null");if(i&&(c||/reason=null|data=null/.test(a))){const u="Transaction reverted during gas estimation and the RPC did not return a revert reason. Try: 1) Get a fresh quote and confirm immediately 2) Switch network or RPC 3) Increase slippage.",l=new Error(`${u} (estimateGas/missing revert data)`);throw l.cause=s,l}throw s}}encodeParams(e){return{srcToken:e.srcToken,dstToken:e.dstToken,amountIn:e.amountIn,amountOutMin:e.amountOutMin,steps:e.steps.map(t=>({adapter:t.adapter,pool:t.pool,tokenIn:t.tokenIn,tokenOut:t.tokenOut,amountIn:t.amountIn,extraData:t.extraData})),intermediateTokens:e.intermediateTokens,deadline:e.deadline,quoteId:e.quoteId,expectAmountOut:e.expectAmountOut,feeReceiver:e.feeReceiver,feeBps:e.feeBps}}getProtocolForProvider(e){if(e in K)return K[e];throw new Error(`Unsupported provider: ${e}`)}encodeSwapCalldata(e,t){const n=e.routerAddress??this.config.routerAddress??this.routerContract.target,r=this.applySlippage(e.params,t),o=e.srcNative===!0||e.dstNative===!0,s=this.encodeParams(r);if(o){const a=this.routerContract.interface.encodeFunctionData("swapETH",[s]);return{to:n,data:a,value:e.srcNative?e.amountIn:0n,method:"swapETH"}}else{const a=this.routerContract.interface.encodeFunctionData("swap",[s]);return{to:n,data:a,value:0n,method:"swap"}}}buildSwapTransactionRequest(e,t){const{to:n,data:r,value:o,method:s}=this.encodeSwapCalldata(e,t.slippageBps);return{method:s,tx:this.applyTxOverrides({to:n,data:r,value:o},t,!0)}}async buildApprovalRequest(e,t,n,r,o){const s=await this.getAllowance(e,t,r);if(!(s>=n))return{token:e,owner:t,spender:r,currentAllowance:s,requiredAmount:n,approveAmount:h.ethers.MaxUint256,tx:this.buildApprovalTransactionRequest(e,r,o)}}buildApprovalTransactionRequest(e,t,n){const o=new h.ethers.Interface(b).encodeFunctionData("approve",[t,h.ethers.MaxUint256]);return this.applyTxOverrides({to:e,data:o,value:0n},n,!1)}async getAllowance(e,t,n){return new h.ethers.Contract(e,b,this.provider).allowance(t,n)}applyTxOverrides(e,t,n){const r={...e};return t.gasPrice&&(r.gasPrice=t.gasPrice),n&&t.gasLimit&&(r.gasLimit=t.gasLimit),r}async sendTransactionWithTimeout(e,t,n){const r=n.timeoutMs??V;if(r<=0)return e.sendTransaction(t);const o=e;if(typeof o.sendUncheckedTransaction=="function"&&o.provider){const s=await L(o.sendUncheckedTransaction(t),r),a=await this.waitForTransactionResponse(o.provider,s,r,n.transactionResponsePollingIntervalsMs);if(a.response)return a.response;const i=a.rpcErrors>0?`${a.rpcErrors} transient provider error(s) and ${a.nullResponses} null response(s)`:`${a.nullResponses} null response(s)`;throw new U(`Transaction was broadcast but provider did not return TransactionResponse within ${r}ms (${i}).`,"provider_index",s)}return L(e.sendTransaction(t),r)}async waitForTransactionResponse(e,t,n,r=F){const o=Date.now()+n;let s=0,a=0,i=0;for(;Date.now()<o;){try{const u=await e.getTransaction(t);if(u)return{response:u,nullResponses:a,rpcErrors:i};a++}catch{i++}const c=this.getNextPollingDelay(r,s);s++,await this.delay(Math.min(c,Math.max(25,o-Date.now())))}return{response:null,nullResponses:a,rpcErrors:i}}async delay(e){e<=0||await new Promise(t=>{setTimeout(t,e)})}getNextPollingDelay(e,t){return e.length===0?F.at(-1)??1200:e[Math.min(t,e.length-1)]??1200}async getTokenInfo(e,t){const n=new h.ethers.Contract(e,b,this.provider),[r,o,s]=await Promise.all([n.symbol(),n.decimals(),t?n.balanceOf(t):Promise.resolve(void 0)]);return s===void 0?{symbol:r,decimals:o}:{symbol:r,decimals:o,balance:s}}async getBalance(e,t){return new h.ethers.Contract(e,b,this.provider).balanceOf(t)}async getQuote(e){const{srcToken:t,dstToken:n,amountIn:r,options:o={}}=e,{byAmountIn:s=!0,depth:a=_.depth,splitCount:i=_.splitCount,providers:c=_.providers,deadlineSeconds:u=R,includeResponseHeaders:l=!1,customFee:p}=o;O.validateCustomFee(p);const A=T(t),m=T(n),S=A?this.config.weth:t,v=m?this.config.weth:n;if(l){const{data:C,responseHeaders:k}=await this.apiClient.findRoutes({from:S,target:v,amount:r,byAmountIn:s,depth:a,splitCount:i,providers:c,includeResponseHeaders:!0});return this.buildQuoteFromApi(C,S,v,u,c,A,m,k,p)}const I=await this.apiClient.findRoutes({from:S,target:v,amount:r,byAmountIn:s,depth:a,splitCount:i,providers:c});return this.buildQuoteFromApi(I,S,v,u,c,A,m,void 0,p)}static validateCustomFee(e){if(!e)return;const{feeAccount:t,feeBps:n}=e;if(!Number.isInteger(n)||n<1||n>N)throw new x(`customFee.feeBps must be an integer in [1, ${N}] (5%), got ${n}`);if(!t||!h.ethers.isAddress(t))throw new x(`customFee.feeAccount must be a valid address, got ${t}`);if(t===h.ethers.ZeroAddress)throw new x("customFee.feeAccount must not be the zero address")}resolveRouterAddress(e){const t=e??this.config.routerAddress;if(!t||t===h.ethers.ZeroAddress)throw new Error("No router address available. Pass one explicitly or set config.routerAddress.");return t}async getProtocolFeeConfig(e){const t=this.resolveRouterAddress(e),n=new h.ethers.Contract(t,y,this.provider),[r,o]=await Promise.all([n.protocolFeeReceiver(),n.protocolCutBps()]);return{protocolFeeReceiver:r,protocolCutBps:Number(o)}}async getPlatformConfig(e){return this.getProtocolFeeConfig(e)}async getRouterConfig(e){const t=this.resolveRouterAddress(e),n=new h.ethers.Contract(t,y,this.provider),[r,o,s,a,i,c,u,l]=await Promise.all([n.WETH(),n.owner(),n.pendingOwner(),n.paused(),n.maxCustomFeeBps(),n.maxProtocolCutBps(),n.protocolFeeReceiver(),n.protocolCutBps()]);return{address:t,weth:r,owner:o,pendingOwner:s,paused:!!a,maxCustomFeeBps:Number(i),maxProtocolCutBps:Number(c),protocolFee:{protocolFeeReceiver:u,protocolCutBps:Number(l)}}}filterPathsByProviders(e,t,n,r){const o=new Set(t.map(p=>p.toUpperCase()));let s=e.filter(p=>o.has(p.provider.toUpperCase()));if(s.length===e.length)return s;const a=n.toLowerCase(),i=r.toLowerCase(),c=new Set;c.add(a);let u=!0;for(;u;){u=!1;for(const p of s)c.has(p.token_in.toLowerCase())&&!c.has(p.token_out.toLowerCase())&&(c.add(p.token_out.toLowerCase()),u=!0)}const l=new Set;for(l.add(i),u=!0;u;){u=!1;for(const p of s)l.has(p.token_out.toLowerCase())&&!l.has(p.token_in.toLowerCase())&&(l.add(p.token_in.toLowerCase()),u=!0)}return s.length,s=s.filter(p=>c.has(p.token_in.toLowerCase())&&l.has(p.token_out.toLowerCase())),s}buildQuoteFromRouteData(e,t,n,r,o){const s=this.buildQuoteFromRouteDataInternal(e,t,n,r??R,void 0);return o?.srcNative&&(s.srcNative=!0),o?.dstNative&&(s.dstNative=!0),s}buildQuoteFromApi(e,t,n,r,o,s,a,i,c){const u=this.buildQuoteFromRouteDataInternal(e,t,n,r,o,c);return s&&(u.srcNative=!0),a&&(u.dstNative=!0),i&&(u.responseHeaders=i),u}buildQuoteFromRouteDataInternal(e,t,n,r,o,s){const a=t.toLowerCase();e.paths.length;let i=e.paths.filter(w=>!(!(w.token_in.toLowerCase()===a)&&BigInt(w.amount_in)===0n&&BigInt(w.amount_out)===0n));if(i.length===0)throw new g("All route paths have zero amounts",4001);if(o&&o.length>0&&(i=this.filterPathsByProviders(i,o,t,n),i.length===0))throw new g("No valid route paths remaining after provider filtering",4001);const c=n.toLowerCase();let u=0n,l=0n;for(const w of i)w.token_in.toLowerCase()===a&&(u+=BigInt(w.amount_in)),w.token_out.toLowerCase()===c&&(l+=BigInt(w.amount_out));const p=BigInt(Math.floor(Date.now()/1e3)+r),A=new Map;for(const w of i){const E=w.token_in.toLowerCase();A.set(E,(A.get(E)??0)+1)}const m=new Map,S=i.map(w=>{const E=w.token_in.toLowerCase(),W=(m.get(E)??0)+1;m.set(E,W);const H=A.get(E),z=H>1&&W<H,X=E===a;return{adapter:w.adapter,pool:w.provider.toUpperCase()==="PANCAKE_INFINITY_CL"?ae:w.provider.toUpperCase()==="PANCAKE_INFINITY_LB"?ie:w.provider.toUpperCase()==="UNISWAPV4"?ce:w.pool,tokenIn:w.token_in,tokenOut:w.token_out,amountIn:X||z?BigInt(w.amount_in):0n,extraData:w.extra_data||"0x"}}),v=new Set;for(const w of i)w.token_out.toLowerCase()!==c&&v.add(w.token_out);const I=Array.from(v),C=s?.feeBps??0,k=s?.feeAccount??h.ethers.ZeroAddress,M=l,$=C>0?M*BigInt(C)/P:0n,B=M-$,j={srcToken:t,dstToken:n,amountIn:u,amountOutMin:B,steps:S,intermediateTokens:I,deadline:p,quoteId:e.request_id?h.ethers.id(e.request_id).slice(0,66):h.ethers.ZeroHash,expectAmountOut:B,feeReceiver:k,feeBps:C},q={routes:[{steps:i.map(w=>({pool:{address:w.pool,token0:w.token_in,token1:w.token_out,protocol:this.getProtocolForProvider(w.provider),fee:w.fee_rate?Math.round(parseFloat(w.fee_rate)*1e6):void 0},tokenIn:w.token_in,tokenOut:w.token_out,amountIn:BigInt(w.amount_in),amountOut:BigInt(w.amount_out)})),amountIn:u,amountOut:l,gasEstimate:BigInt(e.gas)}],percentages:[1e4],totalAmountIn:u,totalAmountOut:l,totalGasEstimate:BigInt(e.gas)};if(!e.contracts?.router)throw new g("API response missing contracts.router address",4002);return{srcToken:t,dstToken:n,amountIn:u,amountOut:B,priceImpact:parseFloat(e.deviation_ratio||"0"),route:q,params:j,gasEstimate:BigInt(e.gas),routerAddress:e.contracts?.router,customFee:C>0?{feeAccount:k,feeBps:C,feeAmount:$}:void 0}}async getAvailableProviders(){return this.apiClient.getAvailableProviders()}async simulate(e,t,n,r){const o=n||h.ethers.ZeroAddress,{to:s,data:a,value:i,method:c}=this.encodeSwapCalldata(e,t);if(r){const u=this.getJsonRpcProviderForStateOverrides(),l=i>0n?"0x"+i.toString(16):void 0,p=await u.send("eth_call",[{from:o,to:s,data:a,value:l},"latest",r]),[A]=this.routerContract.interface.decodeFunctionResult(c,p);return{amountOut:A,method:c}}else{const u=await this.provider.call({from:o,to:s,data:a,value:i>0n?i:void 0}),[l]=this.routerContract.interface.decodeFunctionResult(c,u);return{amountOut:l,method:c}}}getJsonRpcProviderForStateOverrides(){if(typeof this.provider.send!="function")throw new Error("stateOverrides require a JsonRpcProvider-compatible provider with send(method, params).");return this.provider}formatSimulateError(e,t,n,r){const o=e instanceof Error?e:new Error(String(e)),s=e;let a="unknown";if(s.reason&&typeof s.reason=="string")a=s.reason;else if(s.revert&&typeof s.revert=="object"){const l=s.revert;l.args&&Array.isArray(l.args)&&(a=l.args.join(", "))}else o.message&&(a=o.message);const i=t.params.steps.map((l,p)=>` Step ${p}: ${l.tokenIn.slice(0,10)}→${l.tokenOut.slice(0,10)} via adapter ${l.adapter.slice(0,10)} pool ${l.pool.slice(0,10)}`).join(`
2
+ `),c=[`Simulate ${n} failed: ${a}`,` Route: ${t.srcToken} → ${t.dstToken}`,` AmountIn: ${t.amountIn}`,` AmountOutMin: ${t.params.amountOutMin}`,` Router: ${t.routerAddress}`,` Caller: ${r}`,` Steps (${t.params.steps.length}):`,i].join(`
3
+ `),u=new Error(c);return u.cause=o,u.reason=a,u}async findFailingStep(e,t,n,r,o,s=50){const a=e.params.steps;if(!a.length)return null;const i=o!=null?this.normalizeRevertReason(o):void 0;for(let c=1;c<=a.length;c++){const u=this.buildTruncatedQuote(e,c);let l,p;try{l=(await this.simulate(u,t,n,r)).amountOut}catch(v){p=v}if(p!==void 0){const v=this.normalizeRevertReason(p),I=p?.shortMessage??p?.reason??p?.message;if(i==null||v===i)return{stepIndex:c-1,step:a[c-1],error:p,revertMessage:typeof I=="string"?I:v,fullRouteRevertMessage:i};continue}if(l===void 0)continue;const A=this.getQuotedStepAmountOut(e,c-1);if(A===void 0||A===0n)continue;const m=A>l?A-l:0n;if(m===0n)continue;const S=m*P/A;if(S>BigInt(s)){const v=`Step ${c-1} deviates: quoted=${A} simulated=${l} shortfall=${m} (${S}bps, threshold=${s}bps). Likely cause: transfer-tax on ${a[c-1].tokenOut}, stale pool data, or dynamic-fee drift.`;return{stepIndex:c-1,step:a[c-1],error:new Error(v),revertMessage:v,fullRouteRevertMessage:i}}}return null}normalizeRevertReason(e){if(e==null)return;const t=e;if(typeof t.reason=="string"&&t.reason.length>0)return t.reason;const n=this.extractRevertData(e);if(n&&n.length>=10){const c=n.slice(0,10).toLowerCase();return O.KNOWN_ERROR_SELECTORS[c]??c}const r=t.shortMessage??t.message;if(typeof r!="string")return;const o=r.match(/reason="([^"]+)"/);if(o)return o[1];const s=r.match(/reverted:\s*"([^"]+)"/);if(s)return s[1];const a=r.match(/execution reverted:\s*"([^"]+)"/);if(a)return a[1];const i=r.match(/\b0x[0-9a-fA-F]{8}\b/);if(i){const c=i[0].toLowerCase();return O.KNOWN_ERROR_SELECTORS[c]??c}}extractRevertData(e){const t=new Set,n=[e];for(;n.length;){const r=n.shift();if(r==null||typeof r!="object"||t.has(r))continue;t.add(r);const o=r,s=o.data;if(typeof s=="string"&&/^0x[0-9a-fA-F]{8,}$/.test(s))return s;for(const a of["info","error","cause","revert","originalError"])o[a]&&n.push(o[a])}}getQuotedStepAmountOut(e,t){return e.route?.routes?.[0]?.steps?.[t]?.amountOut}buildTruncatedQuote(e,t){const n=e.params.steps.slice(0,t),o=n[n.length-1].tokenOut,s=o.toLowerCase(),a=e.srcToken.toLowerCase(),i=t===e.params.steps.length,c=new Set,u=[];for(const l of n){const p=l.tokenOut.toLowerCase();p===s||p===a||c.has(p)||(c.add(p),u.push(l.tokenOut))}return{...e,dstToken:o,dstNative:i?e.dstNative:!1,amountOut:i?e.amountOut:0n,customFee:i?e.customFee:void 0,params:{...e.params,dstToken:o,amountOutMin:i?e.params.amountOutMin:0n,expectAmountOut:i?e.params.expectAmountOut:0n,feeReceiver:i?e.params.feeReceiver:h.ethers.ZeroAddress,feeBps:i?e.params.feeBps:0,steps:n,intermediateTokens:u}}}buildStateOverrides(e,t,n,r,o,s){if(s?.isNative||T(e))return{};if(!n||n===h.ethers.ZeroAddress)throw new Error("buildStateOverrides requires a non-zero routerAddress.");const a=h.ethers.AbiCoder.defaultAbiCoder(),i=r||h.ethers.parseUnits("1000000",18),c=o??n,u=h.ethers.zeroPadValue(h.ethers.toBeHex(i),32),l=h.ethers.zeroPadValue(h.ethers.toBeHex(h.ethers.MaxUint256),32),p={},A=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,50,51,52,100,101,102];for(const m of A){const S=h.ethers.keccak256(a.encode(["address","uint256"],[t,m]));p[S]=u;const v=h.ethers.keccak256(a.encode(["address","uint256"],[t,m])),I=h.ethers.keccak256(a.encode(["address","bytes32"],[c,v]));p[I]=l}return{[e.toLowerCase()]:{stateDiff:p}}}};O.KNOWN_ERROR_SELECTORS={"0x2c19b8b8":"InsufficientOutput"};let D=O;const G=["function token0() external view returns (address)","function token1() external view returns (address)","function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast)"],ue=["function token0() external view returns (address)","function token1() external view returns (address)","function fee() external view returns (uint24)","function liquidity() external view returns (uint128)","function slot0() external view returns (uint160 sqrtPriceX96, int24 tick, uint16 observationIndex, uint16 observationCardinality, uint16 observationCardinalityNext, uint32 feeProtocol, bool unlocked)"],de="0xcA143Ce32Fe78f1f7019d7d551a6402fC5350c73",le=["function getPair(address tokenA, address tokenB) external view returns (address pair)"],pe="0x86407bEa2078ea5f5EB5A52B2caA963bC1F889Da",he=["function getPair(address tokenA, address tokenB) external view returns (address pair)"],fe="0x0BFbCF9fa4f9C56B0F40a671Ad40E0805A091865",we=["function getPool(address tokenA, address tokenB, uint24 fee) external view returns (address pool)"],me=[100,500,2500,1e4],Ae=60000n,Se=120000n,ve=100000n;class ge{constructor(e,t){this.poolCache=new Map,this.provider=e,this.v2Factory=new h.ethers.Contract(de,le,e),this.v3Factory=new h.ethers.Contract(fe,we,e),this.babySwapFactory=new h.ethers.Contract(pe,he,e)}async findBestRoute(e,t,n){const r=await this.discoverPools(e,t);if(r.length===0)throw new Error(`No pools found for ${e} -> ${t}`);const s=(await this.calculateRoutes(r,e,t,n)).reduce((a,i)=>i.amountOut>a.amountOut?i:a);return{routes:[s],percentages:[1e4],totalAmountIn:n,totalAmountOut:s.amountOut,totalGasEstimate:s.gasEstimate}}async discoverPools(e,t){const n=[];try{const r=await this.findV2Pool(e,t);r&&n.push(r)}catch{}for(const r of me)try{const o=await this.findV3Pool(e,t,r);o&&n.push(o)}catch{}try{const r=await this.findBabySwapPool(e,t);r&&n.push(r)}catch{}return n}async findV2Pool(e,t){const n=`v2-${e}-${t}`;if(this.poolCache.has(n))return this.poolCache.get(n);const r=await this.v2Factory.getPair(e,t);if(r===h.ethers.ZeroAddress)return null;const o=new h.ethers.Contract(r,G,this.provider),[s,a,i]=await Promise.all([o.token0(),o.token1(),o.getReserves()]),c={address:r,token0:s,token1:a,protocol:d.PancakeV2,reserve0:i.reserve0,reserve1:i.reserve1};return this.poolCache.set(n,c),c}async findV3Pool(e,t,n){const r=`v3-${e}-${t}-${n}`;if(this.poolCache.has(r))return this.poolCache.get(r);const o=await this.v3Factory.getPool(e,t,n);if(o===h.ethers.ZeroAddress)return null;const s=new h.ethers.Contract(o,ue,this.provider),[a,i,c]=await Promise.all([s.token0(),s.token1(),s.liquidity()]);if(c===0n)return null;const u={address:o,token0:a,token1:i,protocol:d.PancakeV3,fee:n,liquidity:c};return this.poolCache.set(r,u),u}async findBabySwapPool(e,t){const n=`babyswap-${e}-${t}`;if(this.poolCache.has(n))return this.poolCache.get(n);const r=await this.babySwapFactory.getPair(e,t);if(r===h.ethers.ZeroAddress)return null;const o=new h.ethers.Contract(r,G,this.provider),[s,a,i]=await Promise.all([o.token0(),o.token1(),o.getReserves()]),c={address:r,token0:s,token1:a,protocol:d.BabySwap,reserve0:i.reserve0,reserve1:i.reserve1};return this.poolCache.set(n,c),c}async calculateRoutes(e,t,n,r){const o=[];for(const s of e)try{const a=await this.getAmountOut(s,t,n,r);a>0n&&o.push({steps:[{pool:s,tokenIn:t,tokenOut:n,amountIn:r,amountOut:a}],amountIn:r,amountOut:a,gasEstimate:s.protocol===d.PancakeV2||s.protocol===d.BabySwap?Ae:s.protocol===d.Dodo?ve:Se})}catch{}return o}async getAmountOut(e,t,n,r){if(e.protocol===d.PancakeV2)return this.getV2AmountOut(e,t,r);if(e.protocol===d.BabySwap)return this.getBabySwapAmountOut(e,t,r);if(e.protocol===d.Dodo)throw new Error("DODO amount out not supported in local route discovery");return this.estimateV3AmountOut(e,t,r)}getV2AmountOut(e,t,n){const r=t.toLowerCase()===e.token0.toLowerCase(),[o,s]=r?[e.reserve0,e.reserve1]:[e.reserve1,e.reserve0],a=n*9975n,i=a*s,c=o*10000n+a;return i/c}getBabySwapAmountOut(e,t,n){const r=t.toLowerCase()===e.token0.toLowerCase(),[o,s]=r?[e.reserve0,e.reserve1]:[e.reserve1,e.reserve0],a=n*998n,i=a*s,c=o*1000n+a;return i/c}estimateV3AmountOut(e,t,n){const o=1000000n-BigInt(e.fee||2500);return n*o/1000000n}clearCache(){this.poolCache.clear()}}class Ee{constructor(e){this.adapters=e}build(e,t,n,r,o=R){const s=this.flattenRoutes(e),a=this.mergeIdenticalPools(s),i=this.topologicalSort(a,t),c=this.convertToSwapSteps(i),u=this.extractIntermediates(i,t,n),l=BigInt(Math.floor(Date.now()/1e3)+o);return{srcToken:t,dstToken:n,amountIn:e.totalAmountIn,amountOutMin:r,steps:c,intermediateTokens:u,deadline:l,quoteId:h.ethers.ZeroHash,expectAmountOut:e.totalAmountOut,feeReceiver:h.ethers.ZeroAddress,feeBps:0}}flattenRoutes(e){const t=[];for(let n=0;n<e.routes.length;n++){const r=e.routes[n],o=e.percentages[n],s=e.totalAmountIn*BigInt(o)/P;for(let a=0;a<r.steps.length;a++){const i=r.steps[a];t.push({pool:i.pool,tokenIn:i.tokenIn,tokenOut:i.tokenOut,amountIn:a===0?s:0n,routeIndex:n,stepIndex:a})}}return t}mergeIdenticalPools(e){const t=new Map,n=[];for(const r of e){const o=`${r.pool.address}-${r.tokenIn}-${r.tokenOut}`;if(t.has(o)){const s=t.get(o);r.amountIn>0n&&s.amountIn>0n&&(s.amountIn=0n)}else{const s={...r};t.set(o,s),n.push(s)}}for(const r of n){const o=`${r.pool.address}-${r.tokenIn}-${r.tokenOut}`;e.filter(a=>`${a.pool.address}-${a.tokenIn}-${a.tokenOut}`===o).length>1&&(r.amountIn=0n)}return n}topologicalSort(e,t){const n=new Map,r=new Map,o=new Map;for(const i of e){const c=this.stepKey(i);r.set(c,i),n.set(c,0),o.set(c,[])}for(const i of e){const c=this.stepKey(i);if(i.tokenIn!==t){for(const u of e)if(u.tokenOut===i.tokenIn){const l=this.stepKey(u);l!==c&&(o.get(l).push(c),n.set(c,(n.get(c)||0)+1))}}}const s=[];for(const[i,c]of n)c===0&&s.push(i);const a=[];for(;s.length>0;){const i=s.shift(),c=r.get(i);a.push(c);for(const u of o.get(i)||[]){const l=(n.get(u)||0)-1;n.set(u,l),l===0&&s.push(u)}}if(a.length!==e.length)throw new Error("Circular dependency detected in route");return a}convertToSwapSteps(e){return e.map(t=>{const n=this.adapters.get(t.pool.protocol);if(!n)throw new Error(`No adapter for protocol: ${t.pool.protocol}`);return{adapter:n,pool:t.pool.address,tokenIn:t.tokenIn,tokenOut:t.tokenOut,amountIn:t.amountIn,extraData:this.encodeExtraData(t.pool)}})}encodeExtraData(e){switch(e.protocol){case d.PancakeV2:return"0x";case d.PancakeV3:case d.UniswapV3:case d.UniswapV4:case d.SquadSwapV3:case d.ThenaFusion:return"0x";case d.PancakeInfinityCl:case d.PancakeInfinityLb:return"0x";case d.Dodo:return"0x";case d.NomiswapStable:return"0x";case d.Biswap:return"0x";case d.Apeswap:return"0x";case d.BabyDogeSwap:return"0x";case d.BabySwap:return"0x";case d.SquadSwapV2:return"0x";case d.SushiSwapV2:return"0x";case d.SushiSwapV3:return"0x";case d.PancakeStable:return"0x";case d.ListaStable:return"0x";case d.Wombat:return"0x";default:return"0x"}}extractIntermediates(e,t,n){const r=new Set;for(const o of e)o.tokenOut!==n&&r.add(o.tokenOut);return r.delete(t),r.delete(n),Array.from(r)}stepKey(e){return`${e.pool.address}-${e.tokenIn}-${e.tokenOut}`}}exports.API_DEFAULTS=_;exports.ApiClient=Z;exports.ApiError=g;exports.BPS_DENOMINATOR=P;exports.BSC_MAINNET_CONFIG=ne;exports.BSC_TESTNET_CONFIG=re;exports.CustomFeeError=x;exports.DEFAULT_API_URL=Y;exports.DEFAULT_DEADLINE_SECONDS=R;exports.DEFAULT_EXECUTE_TIMEOUT_MS=V;exports.DEFAULT_SLIPPAGE_BPS=J;exports.DEFAULT_TRANSACTION_RESPONSE_POLL_INTERVALS_MS=F;exports.ERR_ZERO_AMOUNT_PATHS=te;exports.ExecuteTimeoutError=U;exports.MAX_FEE_BPS=N;exports.MAX_PLATFORM_CUT_BPS=ee;exports.NATIVE_TOKEN_ADDRESS=Q;exports.PeachClient=D;exports.ProtocolType=d;exports.RouteDiscovery=ge;exports.SwapBuilder=Ee;exports.isNativeTokenAddress=T;exports.withWalletSendTimeout=L;
4
4
  //# sourceMappingURL=index.cjs.map
package/dist/index.d.ts CHANGED
@@ -22,18 +22,19 @@ export declare const API_DEFAULTS: {
22
22
  export declare class ApiClient {
23
23
  private baseUrl;
24
24
  private timeout;
25
+ private extraHeaders;
25
26
  constructor(config?: ApiClientConfig);
26
27
  /**
27
28
  * Find optimal routes via API
28
29
  */
29
- findRoutes(params: {
30
- from: string;
31
- target: string;
32
- amount: bigint;
33
- byAmountIn?: boolean;
34
- depth?: number;
35
- splitCount?: number;
36
- providers?: Provider[];
30
+ findRoutes(params: FindRoutesParams & {
31
+ includeResponseHeaders: true;
32
+ }): Promise<{
33
+ data: ApiFindRouteData;
34
+ responseHeaders: Record<string, string>;
35
+ }>;
36
+ findRoutes(params: FindRoutesParams & {
37
+ includeResponseHeaders?: false;
37
38
  }): Promise<ApiFindRouteData>;
38
39
  /**
39
40
  * Get service status including available providers
@@ -58,6 +59,8 @@ export declare interface ApiClientConfig {
58
59
  baseUrl?: string;
59
60
  /** Request timeout in ms (default: 10000) */
60
61
  timeout?: number;
62
+ /** Extra HTTP headers to send with every request (e.g. `Cookie` for gated staging environments). */
63
+ headers?: Record<string, string>;
61
64
  }
62
65
 
63
66
  /**
@@ -128,6 +131,20 @@ export declare interface ApiFindRouteRequest {
128
131
  */
129
132
  export declare type ApiFindRouteResponse = ApiResponse<ApiFindRouteData>;
130
133
 
134
+ /**
135
+ * PeachRouter fee configuration snapshot served by the aggregator `/router/status`
136
+ * endpoint. Lets clients discover the router address and current fee caps without
137
+ * a separate on-chain read.
138
+ */
139
+ export declare interface ApiPeachRouterConfig {
140
+ /** Deployed PeachRouter contract address the aggregator routes through. */
141
+ router_address: string;
142
+ /** Current upper bound on `CustomFee.feeBps` the router will accept (bps). */
143
+ max_custom_fee_bps: number;
144
+ /** Current upper bound on the protocol's share of the custom fee (bps of the custom fee). */
145
+ max_protocol_cut_bps: number;
146
+ }
147
+
131
148
  /**
132
149
  * Aggregator API response wrapper
133
150
  */
@@ -171,6 +188,8 @@ export declare interface ApiStatusData {
171
188
  providers: string[];
172
189
  /** Chain sync status for each provider */
173
190
  chainflows: ChainflowStatus[];
191
+ /** PeachRouter address + fee-cap configuration. */
192
+ peach_router: ApiPeachRouterConfig;
174
193
  }
175
194
 
176
195
  /**
@@ -203,6 +222,32 @@ export declare interface ChainflowStatus {
203
222
  update_at: number;
204
223
  }
205
224
 
225
+ /**
226
+ * Custom-fee configuration. Pass via `QuoteOptions.customFee` to charge an
227
+ * additional fee on top of the swap output, sent atomically by the on-chain router.
228
+ *
229
+ * Note: the Peach protocol takes a configurable share of this fee (owner-settable,
230
+ * default 20%, capped by the owner-settable `maxProtocolCutBps`). The `feeBps` you
231
+ * set here is the TOTAL fee charged to the user — integrator and protocol split
232
+ * that total according to the router's `protocolCutBps()` at execution time. Use
233
+ * `PeachClient.getRouterConfig()` (or `getProtocolFeeConfig()` for just the split)
234
+ * to read the current values so the UI can display "X bps integrator / Y bps protocol".
235
+ */
236
+ export declare interface CustomFee {
237
+ /** Address that receives the integrator's share of the custom fee (paid in dstToken, or native BNB when dstToken is native). */
238
+ feeAccount: string;
239
+ /** Total custom fee in basis points (1 bps = 0.01%). Must be in `[1, maxCustomFeeBps]` as read from the router. */
240
+ feeBps: number;
241
+ }
242
+
243
+ /**
244
+ * Thrown when a custom-fee configuration is invalid (bad bps or feeAccount).
245
+ * Surfaced before any RPC round-trip so integrators get an actionable error.
246
+ */
247
+ export declare class CustomFeeError extends Error {
248
+ constructor(message: string);
249
+ }
250
+
206
251
  /**
207
252
  * Peach Aggregator SDK Types
208
253
  *
@@ -254,6 +299,17 @@ export declare interface FindFailingStepResult {
254
299
  fullRouteRevertMessage?: string;
255
300
  }
256
301
 
302
+ export declare type FindRoutesParams = {
303
+ from: string;
304
+ target: string;
305
+ amount: bigint;
306
+ byAmountIn?: boolean;
307
+ depth?: number;
308
+ splitCount?: number;
309
+ providers?: Provider[];
310
+ includeResponseHeaders?: boolean;
311
+ };
312
+
257
313
  /**
258
314
  * Check if an address is the native token sentinel address.
259
315
  */
@@ -262,7 +318,28 @@ export declare function isNativeTokenAddress(address: string): boolean;
262
318
  /**
263
319
  * Known DEX providers supported by the SDK.
264
320
  */
265
- export declare type KnownProvider = "PANCAKEV2" | "PANCAKEV3" | "PANCAKE_INFINITY_CL" | "UNISWAPV3" | "UNISWAPV4" | "DODO" | "THENA";
321
+ export declare type KnownProvider = "PANCAKEV2" | "PANCAKEV3" | "PANCAKE_INFINITY_CL" | "PANCAKE_INFINITY_LB" | "UNISWAPV3" | "UNISWAPV4" | "DODO" | "THENAV3" | "THENA_FUSION" | "NOMISWAP_STABLE" | "BISWAP" | "APESWAP" | "BABYDOGESWAP" | "BABYSWAP" | "PANCAKE_STABLE" | "LISTA_STABLE" | "SQUADSWAP_V3" | "SQUADSWAP_V2" | "WOMBAT" | "SUSHISWAP_V2" | "SUSHISWAP_V3";
322
+
323
+ /**
324
+ * Client-side default cap on the integrator-set custom fee. Mirrors the PeachRouter v2
325
+ * deploy-time initial value of `maxCustomFeeBps` (500 bps = 5%).
326
+ *
327
+ * IMPORTANT: In PeachRouter v2 the cap is an OWNER-SETTABLE state variable in [0, 10_000]
328
+ * bps, not a bytecode constant. This SDK constant is only a convenience default for
329
+ * pre-RPC validation; callers that need the live value must read it via
330
+ * `PeachClient.getRouterConfig()` and validate `customFee.feeBps <= maxCustomFeeBps`.
331
+ */
332
+ export declare const MAX_FEE_BPS = 500;
333
+
334
+ /**
335
+ * Client-side default cap on the protocol's share of the custom fee. Mirrors the
336
+ * PeachRouter v2 deploy-time initial value of `maxProtocolCutBps` (5_000 bps = 50%).
337
+ *
338
+ * IMPORTANT: In PeachRouter v2 this is an OWNER-SETTABLE state variable in [0, 10_000]
339
+ * bps — the owner (Safe multisig) can raise it up to 100%. UIs that want the live
340
+ * value must read it from the chain via `PeachClient.getRouterConfig()`.
341
+ */
342
+ export declare const MAX_PLATFORM_CUT_BPS = 5000;
266
343
 
267
344
  /**
268
345
  * Sentinel address indicating native token (e.g. BNB on BSC).
@@ -337,6 +414,7 @@ export declare class PeachClient {
337
414
  /**
338
415
  * Get quote via API
339
416
  * @throws Error if API client is not configured
417
+ * @throws CustomFeeError if `options.customFee` is malformed
340
418
  */
341
419
  getQuote(params: {
342
420
  srcToken: string;
@@ -344,6 +422,53 @@ export declare class PeachClient {
344
422
  amountIn: bigint;
345
423
  options?: QuoteOptions;
346
424
  }): Promise<Quote>;
425
+ /**
426
+ * Validate custom-fee configuration. Pure / static so callers (tests, custom flows)
427
+ * can reuse it. Throws CustomFeeError on any violation; returns silently for valid input.
428
+ */
429
+ static validateCustomFee(customFee?: CustomFee): void;
430
+ /**
431
+ * Resolve the router address for read methods: argument → quote.routerAddress →
432
+ * config.routerAddress. Throws when none is available so callers never silently
433
+ * read from the zero address.
434
+ */
435
+ private resolveRouterAddress;
436
+ /**
437
+ * Read the protocol-cut configuration from a deployed PeachRouter. Useful for
438
+ * displaying "X bps integrator / Y bps protocol" before the user signs.
439
+ *
440
+ * Values are mutable on-chain — the router owner (Safe) can change them at any
441
+ * time. Re-read before each swap if your UI needs exact numbers on the signing
442
+ * screen.
443
+ *
444
+ * @param routerAddress Optional router address. Defaults to `config.routerAddress`.
445
+ * Pass an explicit address when reading from a different deployment.
446
+ */
447
+ getProtocolFeeConfig(routerAddress?: string): Promise<ProtocolFeeConfig>;
448
+ /**
449
+ * @deprecated Use {@link getProtocolFeeConfig}. Kept as an alias for the pre-v2 name.
450
+ * The returned object now uses the `ProtocolFeeConfig` field names
451
+ * (`protocolFeeReceiver` / `protocolCutBps`).
452
+ */
453
+ getPlatformConfig(routerAddress?: string): Promise<ProtocolFeeConfig>;
454
+ /**
455
+ * Read the full PeachRouter configuration in a single batched call: identity
456
+ * (WETH / owner / pendingOwner), emergency state (`paused`), fee caps
457
+ * (`maxCustomFeeBps`, `maxProtocolCutBps`), and protocol-cut split
458
+ * (`protocolFeeReceiver`, `protocolCutBps`).
459
+ *
460
+ * Intended for:
461
+ * - Verifying a deployment before the UI surfaces swap flows.
462
+ * - Pre-swap checks: reject quotes when `paused == true` or when the requested
463
+ * `customFee.feeBps` exceeds the live `maxCustomFeeBps`.
464
+ * - Showing the integrator / protocol fee split on the signing screen.
465
+ *
466
+ * All fields reflect on-chain state AT READ TIME — the owner (Safe) can mutate
467
+ * caps, the pause flag, and protocol-fee configuration at any time.
468
+ *
469
+ * @param routerAddress Optional router address. Defaults to `config.routerAddress`.
470
+ */
471
+ getRouterConfig(routerAddress?: string): Promise<RouterConfig>;
347
472
  /**
348
473
  * Filter paths by allowed providers, removing paths with disallowed providers
349
474
  * and cascade-removing orphaned paths that depend on removed paths.
@@ -394,24 +519,47 @@ export declare class PeachClient {
394
519
  */
395
520
  private formatSimulateError;
396
521
  /**
397
- * Find which step in the route causes the same revert as the full route (e.g. MUL_ERROR).
398
- * Simulates with steps [0..1], [0..2], ... and returns the first step whose revert
399
- * matches fullRouteError. Ignores steps that revert with a different reason (e.g. unknown custom error).
522
+ * Pinpoint the first problematic step in a route.
523
+ *
524
+ * For each prefix [0..n), builds a rebased truncated quote (dstToken = last
525
+ * step's tokenOut, amountOutMin/expectAmountOut/customFee zeroed) and simulates it:
526
+ *
527
+ * - If the prefix REVERTS, that prefix's final step is the culprit. When a
528
+ * `fullRouteError` is supplied we only return when the prefix's revert
529
+ * matches the full-route reason; otherwise any revert qualifies.
530
+ * - If the prefix succeeds but its output is materially smaller than the
531
+ * aggregator's per-hop quoted amountOut, that step is flagged as the
532
+ * first deviator. This catches silent failures like transfer-tax tokens
533
+ * where simulation doesn't revert mid-route but cumulative shortfall
534
+ * eventually trips the final InsufficientOutput check.
400
535
  *
401
536
  * @param quote - Full quote from getQuote (the one that fails when simulated)
402
537
  * @param slippageBps - Same as for simulate
403
538
  * @param fromAddress - Same as for simulate
404
539
  * @param stateOverrides - Same as for simulate (use when simulating ERC20 sell with arbitrary address)
405
- * @param fullRouteError - The error from simulating the full route. Required so we match by revert reason (e.g. "MUL_ERROR"); only the step that produces the same reason is returned.
406
- * @returns The step index and step details whose revert matches fullRouteError, or null if none match or full route succeeds
540
+ * @param fullRouteError - Optional: the error from simulating the full route. If provided, prefix reverts whose reason differs are skipped (useful for MUL_ERROR-style targeted matching).
541
+ * @param deviationThresholdBps - Maximum tolerated per-step shortfall vs quoted amountOut before flagging the step (default 50 = 0.5%).
542
+ * @returns The first step matching the criteria above, or null if every prefix simulates within tolerance.
407
543
  */
408
544
  findFailingStep(quote: Quote, slippageBps: number, fromAddress?: string, stateOverrides?: Record<string, {
409
545
  stateDiff: Record<string, string>;
410
- }>, fullRouteError?: unknown): Promise<FindFailingStepResult | null>;
411
- /** Extract a comparable revert reason (e.g. "MUL_ERROR") from an error for findFailingStep matching. */
546
+ }>, fullRouteError?: unknown, deviationThresholdBps?: number): Promise<FindFailingStepResult | null>;
547
+ /** Known custom-error selectors on the Peach router, for human-readable reason matching. */
548
+ private static readonly KNOWN_ERROR_SELECTORS;
549
+ /** Extract a comparable revert reason (e.g. "MUL_ERROR", "InsufficientOutput", "0xc956d868") from an error. */
412
550
  private normalizeRevertReason;
413
- /** Build a quote that only includes the first stepCount steps (for findFailingStep). */
414
- private quoteWithFirstNSteps;
551
+ /** Walk an error object (including nested `info`/`error`/`cause`/`revert`) for the first hex revert-data blob. */
552
+ private extractRevertData;
553
+ /** Read the aggregator's quoted per-step amountOut from the quote's flattened route. */
554
+ private getQuotedStepAmountOut;
555
+ /**
556
+ * Build a quote that simulates only the first `stepCount` steps by rebasing
557
+ * dstToken to the last included step's tokenOut and zeroing all output
558
+ * constraints (amountOutMin/expectAmountOut/customFee). Without this rebase
559
+ * the truncated call would always revert — the router would expect the
560
+ * original dstToken to arrive, which never happens for an intermediate prefix.
561
+ */
562
+ private buildTruncatedQuote;
415
563
  /**
416
564
  * Build state overrides for ERC20 token balance and allowance.
417
565
  * Useful for simulating swaps without actual on-chain token balance/approval.
@@ -450,6 +598,9 @@ export declare interface PeachConfig {
450
598
  adapters: AdapterConfig[];
451
599
  }
452
600
 
601
+ /** @deprecated Use {@link ProtocolFeeConfig}. Kept as an alias for the pre-v2 name. */
602
+ export declare type PlatformConfig = ProtocolFeeConfig;
603
+
453
604
  export declare interface PoolInfo {
454
605
  address: string;
455
606
  token0: string;
@@ -463,14 +614,40 @@ export declare interface PoolInfo {
463
614
  tick?: number;
464
615
  }
465
616
 
617
+ /**
618
+ * On-chain protocol-cut configuration as read from the deployed PeachRouter.
619
+ * Returned by `PeachClient.getProtocolFeeConfig()` so UIs can display the integrator /
620
+ * protocol split of the custom fee before signing.
621
+ */
622
+ export declare interface ProtocolFeeConfig {
623
+ /** Recipient of the protocol's portion. `ethers.ZeroAddress` means the cut is disabled and the entire custom fee flows to the integrator. */
624
+ protocolFeeReceiver: string;
625
+ /** Protocol's share of the custom fee, in basis points OF THE CUSTOM FEE (not of the swap output). */
626
+ protocolCutBps: number;
627
+ }
628
+
466
629
  export declare enum ProtocolType {
467
630
  PancakeV2 = "PancakeV2",
468
631
  PancakeV3 = "PancakeV3",
469
632
  PancakeInfinityCl = "Pancake_Infinity_Cl",
633
+ PancakeInfinityLb = "Pancake_Infinity_Lb",
470
634
  UniswapV3 = "UniswapV3",
471
635
  UniswapV4 = "UniswapV4",
472
636
  Dodo = "Dodo",
473
- Thena = "Thena"
637
+ ThenaV3 = "ThenaV3",
638
+ ThenaFusion = "Thena_Fusion",
639
+ NomiswapStable = "Nomiswap_Stable",
640
+ Biswap = "Biswap",
641
+ Apeswap = "Apeswap",
642
+ BabyDogeSwap = "BabyDogeSwap",
643
+ BabySwap = "BabySwap",
644
+ PancakeStable = "Pancake_Stable",
645
+ ListaStable = "Lista_Stable",
646
+ SquadSwapV3 = "SquadSwap_V3",
647
+ SquadSwapV2 = "SquadSwap_V2",
648
+ Wombat = "Wombat",
649
+ SushiSwapV2 = "SushiSwap_V2",
650
+ SushiSwapV3 = "SushiSwap_V3"
474
651
  }
475
652
 
476
653
  /**
@@ -483,6 +660,10 @@ export declare interface Quote {
483
660
  srcToken: string;
484
661
  dstToken: string;
485
662
  amountIn: bigint;
663
+ /**
664
+ * User-perceived dstToken output AFTER custom fee deduction.
665
+ * The gross figure (before fee) is available at `route.totalAmountOut`.
666
+ */
486
667
  amountOut: bigint;
487
668
  priceImpact: number;
488
669
  route: SplitRoute;
@@ -494,6 +675,21 @@ export declare interface Quote {
494
675
  srcNative?: boolean;
495
676
  /** True when the original dstToken was the native token sentinel address */
496
677
  dstNative?: boolean;
678
+ /**
679
+ * HTTP response headers from the find_routes API call.
680
+ * Only set when `getQuote` was called with `options.includeResponseHeaders: true`.
681
+ */
682
+ responseHeaders?: Record<string, string>;
683
+ /**
684
+ * Custom-fee summary, populated when `getQuote` was called with `options.customFee`.
685
+ * Surfaces what was actually requested (account/bps) and the projected feeAmount in
686
+ * dstToken so the UI can display "X% custom fee → Y dstToken" before signing.
687
+ */
688
+ customFee?: {
689
+ feeAccount: string;
690
+ feeBps: number;
691
+ feeAmount: bigint;
692
+ };
497
693
  }
498
694
 
499
695
  /**
@@ -510,6 +706,18 @@ export declare interface QuoteOptions {
510
706
  providers?: Provider[];
511
707
  /** Transaction deadline in seconds from now (default: 1200 = 20 min) */
512
708
  deadlineSeconds?: number;
709
+ /**
710
+ * When true, the returned `Quote` includes `responseHeaders` with all HTTP response headers from find_routes.
711
+ * Default false — headers are omitted to keep payloads small.
712
+ */
713
+ includeResponseHeaders?: boolean;
714
+ /**
715
+ * Optional custom-fee configuration. When set, an additional fee in basis points
716
+ * is taken from the dstToken output and sent atomically to `customFee.feeAccount`
717
+ * by the on-chain router. The user receives `amountOut * (1 - feeBps/10000)`.
718
+ * Capped at `MAX_FEE_BPS` (500 bps = 5%).
719
+ */
720
+ customFee?: CustomFee;
513
721
  }
514
722
 
515
723
  export declare interface Route {
@@ -521,11 +729,11 @@ export declare interface Route {
521
729
 
522
730
  export declare class RouteDiscovery {
523
731
  private provider;
524
- private config;
525
732
  private v2Factory;
526
733
  private v3Factory;
734
+ private babySwapFactory;
527
735
  private poolCache;
528
- constructor(provider: ethers.Provider, config: PeachConfig);
736
+ constructor(provider: ethers.Provider, _config: PeachConfig);
529
737
  /**
530
738
  * Discover optimal route
531
739
  */
@@ -542,6 +750,10 @@ export declare class RouteDiscovery {
542
750
  * Find V3 pool
543
751
  */
544
752
  private findV3Pool;
753
+ /**
754
+ * Find BabySwap pool (V2 fork, 0.2% fee)
755
+ */
756
+ private findBabySwapPool;
545
757
  /**
546
758
  * Calculate route quotes
547
759
  */
@@ -554,6 +766,10 @@ export declare class RouteDiscovery {
554
766
  * V2 output calculation
555
767
  */
556
768
  private getV2AmountOut;
769
+ /**
770
+ * BabySwap output calculation (V2 fork, 0.2% fee — coefficient 2 in 1000-scale)
771
+ */
772
+ private getBabySwapAmountOut;
557
773
  /**
558
774
  * V3 output estimation (simplified)
559
775
  */
@@ -564,6 +780,33 @@ export declare class RouteDiscovery {
564
780
  clearCache(): void;
565
781
  }
566
782
 
783
+ /**
784
+ * Full PeachRouter configuration snapshot. Returned by `PeachClient.getRouterConfig()`
785
+ * so UIs and integrators can verify router identity, fee caps, pause status, and
786
+ * current protocol-fee split in a single batched read.
787
+ *
788
+ * All fields reflect on-chain state AT READ TIME — the owner (Safe multisig) can
789
+ * mutate caps, the pause flag, and the protocol-fee configuration at any time.
790
+ */
791
+ export declare interface RouterConfig {
792
+ /** Router contract address this snapshot was read from. */
793
+ address: string;
794
+ /** Wrapped native-token address bound at deployment (WBNB on BSC). Immutable. */
795
+ weth: string;
796
+ /** Current owner (Ownable2Step). Typically the Peach Safe multisig. */
797
+ owner: string;
798
+ /** Pending owner from an in-flight `transferOwnership`; `ethers.ZeroAddress` when none. */
799
+ pendingOwner: string;
800
+ /** Emergency pause flag; when true `swap` / `swapETH` revert with `RouterPaused`. */
801
+ paused: boolean;
802
+ /** Active cap on `CustomFee.feeBps`. Owner-settable in [0, 10_000] bps. */
803
+ maxCustomFeeBps: number;
804
+ /** Active cap on `protocolCutBps`. Owner-settable in [0, 10_000] bps. */
805
+ maxProtocolCutBps: number;
806
+ /** Protocol-cut configuration (receiver + bps split of the custom fee). */
807
+ protocolFee: ProtocolFeeConfig;
808
+ }
809
+
567
810
  export declare interface RouteStep {
568
811
  pool: PoolInfo;
569
812
  tokenIn: string;
@@ -641,12 +884,30 @@ export declare interface SwapParams {
641
884
  srcToken: string;
642
885
  dstToken: string;
643
886
  amountIn: bigint;
887
+ /**
888
+ * Minimum dstToken the user is willing to receive AFTER custom fee deduction.
889
+ * Slippage protection applies to the user-perceived amount, not the gross output.
890
+ */
644
891
  amountOutMin: bigint;
645
892
  steps: SwapStep[];
646
893
  intermediateTokens: string[];
647
894
  deadline: bigint;
648
895
  quoteId: string;
896
+ /**
897
+ * Off-chain pre-calculated expected output AFTER custom fee (0 if unused).
898
+ */
649
899
  expectAmountOut: bigint;
900
+ /**
901
+ * Custom-fee recipient address. Ignored when `feeBps == 0`.
902
+ * Use `ethers.ZeroAddress` when no custom fee is configured.
903
+ */
904
+ feeReceiver: string;
905
+ /**
906
+ * Custom fee in basis points, capped at `MAX_FEE_BPS` (500 = 5%).
907
+ * The fee is taken from dstToken output before reaching the user; the on-chain
908
+ * router further splits it between the integrator and the platform.
909
+ */
910
+ feeBps: number;
650
911
  }
651
912
 
652
913
  export declare interface SwapRequest {