@peachprojects/aggregator-sdk 0.1.2 → 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 11 — PANCAKEV2, PANCAKEV3, PANCAKE_INFINITY_CL, UNISWAPV3, UNISWAPV4, DODO, THENA, NOMISWAP_STABLE, BISWAP, APESWAP, BABYDOGESWAP)
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 l=require("ethers"),K="https://api.peach.ag",X=50,_=10000n,R=500,J=5e3,b=1200,D=6e4,F=[50,100,200,400,800,1200],Y="0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";function T(w){return w.toLowerCase()===Y.toLowerCase()}const ee=4001;var f=(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.NomiswapStable="Nomiswap_Stable",w.Biswap="Biswap",w.Apeswap="Apeswap",w.BabyDogeSwap="BabyDogeSwap",w.BabySwap="BabySwap",w))(f||{});const te={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 y extends Error{constructor(e){super(e),this.name="CustomFeeError"}}class L extends Error{constructor(e,t,r){super(e),this.name="ExecuteTimeoutError",this.stage=t,this.txHash=r}}const I={depth:3,splitCount:20,providers:["PANCAKEV2","PANCAKEV3","PANCAKE_INFINITY_CL","UNISWAPV3","UNISWAPV4","DODO","THENA","NOMISWAP_STABLE","BISWAP","APESWAP","BABYDOGESWAP","BABYSWAP"],clientVersion:1001500},oe=1e4;function ne(w){const e={};return w.forEach((t,r)=>{e[r]=t}),e}class Z{constructor(e={}){this.baseUrl=e.baseUrl||K,this.timeout=e.timeout||oe,this.extraHeaders=e.headers?{...e.headers}:{}}async findRoutes(e){const{from:t,target:r,amount:o,byAmountIn:s=!0,depth:n=I.depth,splitCount:a=I.splitCount,providers:i=I.providers,includeResponseHeaders:c=!1}=e,u=new URLSearchParams({from:t,target:r,amount:o.toString(),by_amount_in:s.toString(),depth:n.toString(),split_count:a.toString(),providers:i.join(","),v:I.clientVersion.toString()}),d=`${this.baseUrl}/router/find_routes?${u}`,p=new AbortController,g=setTimeout(()=>p.abort(),this.timeout);try{const m=await fetch(d,{method:"GET",headers:{Accept:"application/json",...this.extraHeaders},signal:p.signal});if(clearTimeout(g),!m.ok)throw new v(`API request failed: ${m.status} ${m.statusText}`,m.status);const A=await m.json();if(A.code!==200)throw new v(A.msg||"Route not found",A.code);if(!A.data||!A.data.paths||A.data.paths.length===0)throw new v("No routes found",404);return c?{data:A.data,responseHeaders:ne(m.headers)}:A.data}catch(m){throw clearTimeout(g),m instanceof v?m:m instanceof Error?m.name==="AbortError"?new v("API request timeout",408):new v(`API request failed: ${m.message}`,0):new v("Unknown API error",0)}}async getStatus(){const e=`${this.baseUrl}/router/status`,t=new AbortController,r=setTimeout(()=>t.abort(),this.timeout);try{const o=await fetch(e,{method:"GET",headers:{Accept:"application/json",...this.extraHeaders},signal:t.signal});if(clearTimeout(r),!o.ok)throw new v(`API request failed: ${o.status} ${o.statusText}`,o.status);const s=await o.json();if(s.code!==200)throw new v(s.msg||"Failed to get status",s.code);return s.data}catch(o){throw clearTimeout(r),o instanceof v?o:o instanceof Error?o.name==="AbortError"?new v("API request timeout",408):new v(`API request failed: ${o.message}`,0):new v("Unknown API error",0)}}async getAvailableProviders(){return(await this.getStatus()).providers}setBaseUrl(e){this.baseUrl=e}getBaseUrl(){return this.baseUrl}}class v extends Error{constructor(e,t){super(e),this.name="ApiError",this.code=t}}async function N(w,e=D){if(e<=0)return w;let t;try{return await Promise.race([w,new Promise((r,o)=>{t=setTimeout(()=>{o(new L(`Wallet did not settle sendTransaction within ${e}ms.`,"wallet_send"))},e)})])}finally{t&&clearTimeout(t)}}const B=["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)"],O=["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)"],se="0xa0FfB9c1CE1Fe56963B0321B32E7A0302114058b",H={PANCAKEV3:f.PancakeV3,PANCAKEV2:f.PancakeV2,PANCAKE_INFINITY_CL:f.PancakeInfinityCl,UNISWAPV3:f.UniswapV3,UNISWAPV4:f.UniswapV4,DODO:f.Dodo,THENA:f.Thena,NOMISWAP_STABLE:f.NomiswapStable,BISWAP:f.Biswap,APESWAP:f.Apeswap,BABYDOGESWAP:f.BabyDogeSwap,BABYSWAP:f.BabySwap};class U{constructor(e,t,r){this.config=e,this.provider=t||new l.ethers.JsonRpcProvider(e.rpcUrl),this.routerContract=new l.ethers.Contract(e.routerAddress||l.ethers.ZeroAddress,B,this.provider),this.apiClient=new Z(r?.api)}getRouterAddress(e){const t=e.routerAddress||this.config.routerAddress;if(!t||t===l.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 r=e.amountOutMin*(_-BigInt(t))/_;return{...e,amountOutMin:r}}async swap(e,t,r){const o=this.getRouterAddress(e),{tx:s,method:n}=this.buildSwapTransactionRequest(e,r);let a;return e.srcNative||(a=await this.buildApprovalRequest(e.srcToken,t,e.amountIn,o,r)),{routerAddress:o,method:n,tx:s,approval:a}}async execute(e,t,r){const o=await t.getAddress(),s=await this.swap(e,o,r);try{return s.approval&&await(await this.sendTransactionWithTimeout(t,s.approval.tx,r)).wait(),await this.sendTransactionWithTimeout(t,s.tx,r)}catch(n){const a=n instanceof Error?n.message:String(n),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=n,d}throw n}}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 H)return H[e];throw new Error(`Unsupported provider: ${e}`)}encodeSwapCalldata(e,t){const r=e.routerAddress??this.config.routerAddress??this.routerContract.target,o=this.applySlippage(e.params,t),s=e.srcNative===!0||e.dstNative===!0,n=this.encodeParams(o);if(s){const a=this.routerContract.interface.encodeFunctionData("swapETH",[n]);return{to:r,data:a,value:e.srcNative?e.amountIn:0n,method:"swapETH"}}else{const a=this.routerContract.interface.encodeFunctionData("swap",[n]);return{to:r,data:a,value:0n,method:"swap"}}}buildSwapTransactionRequest(e,t){const{to:r,data:o,value:s,method:n}=this.encodeSwapCalldata(e,t.slippageBps);return{method:n,tx:this.applyTxOverrides({to:r,data:o,value:s},t,!0)}}async buildApprovalRequest(e,t,r,o,s){const n=await this.getAllowance(e,t,o);if(!(n>=r))return{token:e,owner:t,spender:o,currentAllowance:n,requiredAmount:r,approveAmount:l.ethers.MaxUint256,tx:this.buildApprovalTransactionRequest(e,o,s)}}buildApprovalTransactionRequest(e,t,r){const s=new l.ethers.Interface(O).encodeFunctionData("approve",[t,l.ethers.MaxUint256]);return this.applyTxOverrides({to:e,data:s,value:0n},r,!1)}async getAllowance(e,t,r){return new l.ethers.Contract(e,O,this.provider).allowance(t,r)}applyTxOverrides(e,t,r){const o={...e};return t.gasPrice&&(o.gasPrice=t.gasPrice),r&&t.gasLimit&&(o.gasLimit=t.gasLimit),o}async sendTransactionWithTimeout(e,t,r){const o=r.timeoutMs??D;if(o<=0)return e.sendTransaction(t);const s=e;if(typeof s.sendUncheckedTransaction=="function"&&s.provider){const n=await N(s.sendUncheckedTransaction(t),o),a=await this.waitForTransactionResponse(s.provider,n,o,r.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 L(`Transaction was broadcast but provider did not return TransactionResponse within ${o}ms (${i}).`,"provider_index",n)}return N(e.sendTransaction(t),o)}async waitForTransactionResponse(e,t,r,o=F){const s=Date.now()+r;let n=0,a=0,i=0;for(;Date.now()<s;){try{const u=await e.getTransaction(t);if(u)return{response:u,nullResponses:a,rpcErrors:i};a++}catch{i++}const c=this.getNextPollingDelay(o,n);n++,await this.delay(Math.min(c,Math.max(25,s-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 r=new l.ethers.Contract(e,O,this.provider),[o,s,n]=await Promise.all([r.symbol(),r.decimals(),t?r.balanceOf(t):Promise.resolve(void 0)]);return n===void 0?{symbol:o,decimals:s}:{symbol:o,decimals:s,balance:n}}async getBalance(e,t){return new l.ethers.Contract(e,O,this.provider).balanceOf(t)}async getQuote(e){const{srcToken:t,dstToken:r,amountIn:o,options:s={}}=e,{byAmountIn:n=!0,depth:a=I.depth,splitCount:i=I.splitCount,providers:c=I.providers,deadlineSeconds:u=b,includeResponseHeaders:d=!1,customFee:p}=s;U.validateCustomFee(p);const g=T(t),m=T(r),A=g?this.config.weth:t,E=m?this.config.weth:r;if(d){const{data:P,responseHeaders:k}=await this.apiClient.findRoutes({from:A,target:E,amount:o,byAmountIn:n,depth:a,splitCount:i,providers:c,includeResponseHeaders:!0});return this.buildQuoteFromApi(P,A,E,u,c,g,m,k,p)}const S=await this.apiClient.findRoutes({from:A,target:E,amount:o,byAmountIn:n,depth:a,splitCount:i,providers:c});return this.buildQuoteFromApi(S,A,E,u,c,g,m,void 0,p)}static validateCustomFee(e){if(!e)return;const{feeAccount:t,feeBps:r}=e;if(!Number.isInteger(r)||r<1||r>R)throw new y(`customFee.feeBps must be an integer in [1, ${R}] (5%), got ${r}`);if(!t||!l.ethers.isAddress(t))throw new y(`customFee.feeAccount must be a valid address, got ${t}`);if(t===l.ethers.ZeroAddress)throw new y("customFee.feeAccount must not be the zero address")}resolveRouterAddress(e){const t=e??this.config.routerAddress;if(!t||t===l.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),r=new l.ethers.Contract(t,B,this.provider),[o,s]=await Promise.all([r.protocolFeeReceiver(),r.protocolCutBps()]);return{protocolFeeReceiver:o,protocolCutBps:Number(s)}}async getPlatformConfig(e){return this.getProtocolFeeConfig(e)}async getRouterConfig(e){const t=this.resolveRouterAddress(e),r=new l.ethers.Contract(t,B,this.provider),[o,s,n,a,i,c,u,d]=await Promise.all([r.WETH(),r.owner(),r.pendingOwner(),r.paused(),r.maxCustomFeeBps(),r.maxProtocolCutBps(),r.protocolFeeReceiver(),r.protocolCutBps()]);return{address:t,weth:o,owner:s,pendingOwner:n,paused:!!a,maxCustomFeeBps:Number(i),maxProtocolCutBps:Number(c),protocolFee:{protocolFeeReceiver:u,protocolCutBps:Number(d)}}}filterPathsByProviders(e,t,r,o){const s=new Set(t.map(p=>p.toUpperCase()));let n=e.filter(p=>s.has(p.provider.toUpperCase()));if(n.length===e.length)return n;const a=r.toLowerCase(),i=o.toLowerCase(),c=new Set;c.add(a);let u=!0;for(;u;){u=!1;for(const p of n)c.has(p.token_in.toLowerCase())&&!c.has(p.token_out.toLowerCase())&&(c.add(p.token_out.toLowerCase()),u=!0)}const d=new Set;for(d.add(i),u=!0;u;){u=!1;for(const p of n)d.has(p.token_out.toLowerCase())&&!d.has(p.token_in.toLowerCase())&&(d.add(p.token_in.toLowerCase()),u=!0)}return n.length,n=n.filter(p=>c.has(p.token_in.toLowerCase())&&d.has(p.token_out.toLowerCase())),n}buildQuoteFromRouteData(e,t,r,o,s){const n=this.buildQuoteFromRouteDataInternal(e,t,r,o??b,void 0);return s?.srcNative&&(n.srcNative=!0),s?.dstNative&&(n.dstNative=!0),n}buildQuoteFromApi(e,t,r,o,s,n,a,i,c){const u=this.buildQuoteFromRouteDataInternal(e,t,r,o,s,c);return n&&(u.srcNative=!0),a&&(u.dstNative=!0),i&&(u.responseHeaders=i),u}buildQuoteFromRouteDataInternal(e,t,r,o,s,n){const a=t.toLowerCase();e.paths.length;let i=e.paths.filter(h=>!(!(h.token_in.toLowerCase()===a)&&BigInt(h.amount_in)===0n&&BigInt(h.amount_out)===0n));if(i.length===0)throw new v("All route paths have zero amounts",4001);if(s&&s.length>0&&(i=this.filterPathsByProviders(i,s,t,r),i.length===0))throw new v("No valid route paths remaining after provider filtering",4001);const c=r.toLowerCase();let u=0n,d=0n;for(const h of i)h.token_in.toLowerCase()===a&&(u+=BigInt(h.amount_in)),h.token_out.toLowerCase()===c&&(d+=BigInt(h.amount_out));const p=BigInt(Math.floor(Date.now()/1e3)+o),g=new Map;for(const h of i){const C=h.token_in.toLowerCase();g.set(C,(g.get(C)??0)+1)}const m=new Map,A=i.map(h=>{const C=h.token_in.toLowerCase(),V=(m.get(C)??0)+1;m.set(C,V);const W=g.get(C),Q=W>1&&V<W,z=C===a;return{adapter:h.adapter,pool:h.provider.toUpperCase()==="PANCAKE_INFINITY_CL"?se:h.provider.toUpperCase()==="UNISWAPV4"?l.ethers.ZeroAddress:h.pool,tokenIn:h.token_in,tokenOut:h.token_out,amountIn:z||Q?BigInt(h.amount_in):0n,extraData:h.extra_data||"0x"}}),E=new Set;for(const h of i)h.token_out.toLowerCase()!==c&&E.add(h.token_out);const S=Array.from(E),P=n?.feeBps??0,k=n?.feeAccount??l.ethers.ZeroAddress,M=d,$=P>0?M*BigInt(P)/_:0n,x=M-$,j={srcToken:t,dstToken:r,amountIn:u,amountOutMin:x,steps:A,intermediateTokens:S,deadline:p,quoteId:e.request_id?l.ethers.id(e.request_id).slice(0,66):l.ethers.ZeroHash,expectAmountOut:x,feeReceiver:k,feeBps:P},q={routes:[{steps:i.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:u,amountOut:d,gasEstimate:BigInt(e.gas)}],percentages:[1e4],totalAmountIn:u,totalAmountOut:d,totalGasEstimate:BigInt(e.gas)};if(!e.contracts?.router)throw new v("API response missing contracts.router address",4002);return{srcToken:t,dstToken:r,amountIn:u,amountOut:x,priceImpact:parseFloat(e.deviation_ratio||"0"),route:q,params:j,gasEstimate:BigInt(e.gas),routerAddress:e.contracts?.router,customFee:P>0?{feeAccount:k,feeBps:P,feeAmount:$}:void 0}}async getAvailableProviders(){return this.apiClient.getAvailableProviders()}async simulate(e,t,r,o){const s=r||l.ethers.ZeroAddress,{to:n,data:a,value:i,method:c}=this.encodeSwapCalldata(e,t);if(o){const u=this.getJsonRpcProviderForStateOverrides(),d=i>0n?"0x"+i.toString(16):void 0,p=await u.send("eth_call",[{from:s,to:n,data:a,value:d},"latest",o]),[g]=this.routerContract.interface.decodeFunctionResult(c,p);return{amountOut:g,method:c}}else{const u=await this.provider.call({from:s,to:n,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(e,t,r,o){const s=e instanceof Error?e:new Error(String(e)),n=e;let a="unknown";if(n.reason&&typeof n.reason=="string")a=n.reason;else if(n.revert&&typeof n.revert=="object"){const d=n.revert;d.args&&Array.isArray(d.args)&&(a=d.args.join(", "))}else s.message&&(a=s.message);const i=t.params.steps.map((d,p)=>` Step ${p}: ${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 ${r} failed: ${a}`,` Route: ${t.srcToken} → ${t.dstToken}`,` AmountIn: ${t.amountIn}`,` AmountOutMin: ${t.params.amountOutMin}`,` Router: ${t.routerAddress}`,` Caller: ${o}`,` Steps (${t.params.steps.length}):`,i].join(`
3
- `),u=new Error(c);return u.cause=s,u.reason=a,u}async findFailingStep(e,t,r,o,s){const n=e.params.steps;if(!n.length)return null;const a=s!=null?this.normalizeRevertReason(s):void 0;for(let i=1;i<=n.length;i++){const c=this.quoteWithFirstNSteps(e,i);try{await this.simulate(c,t,r,o)}catch(u){const d=this.normalizeRevertReason(u),p=u?.message??u?.reason??u?.shortMessage;if(a!=null?d===a:!0)return{stepIndex:i-1,step:n[i-1],error:u,revertMessage:typeof p=="string"?p:void 0,fullRouteRevertMessage:a??void 0}}}return null}normalizeRevertReason(e){if(e==null)return;const t=e;if(typeof t.reason=="string"&&t.reason.length>0)return t.reason;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 n=r.match(/execution reverted:\s*"([^"]+)"/);if(n)return n[1]}quoteWithFirstNSteps(e,t){const r=e.params.steps.slice(0,t),o=e.dstToken.toLowerCase(),s=new Set,n=[];for(const a of r){const i=a.tokenOut.toLowerCase();i!==o&&!s.has(i)&&(s.add(i),n.push(a.tokenOut))}return{...e,params:{...e.params,steps:r,intermediateTokens:n}}}buildStateOverrides(e,t,r,o,s,n){if(n?.isNative||T(e))return{};if(!r||r===l.ethers.ZeroAddress)throw new Error("buildStateOverrides requires a non-zero routerAddress.");const a=l.ethers.AbiCoder.defaultAbiCoder(),i=o||l.ethers.parseUnits("1000000",18),c=s??r,u=l.ethers.zeroPadValue(l.ethers.toBeHex(i),32),d=l.ethers.zeroPadValue(l.ethers.toBeHex(l.ethers.MaxUint256),32),p={},g=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,50,51,52,100,101,102];for(const m of g){const A=l.ethers.keccak256(a.encode(["address","uint256"],[t,m]));p[A]=u;const E=l.ethers.keccak256(a.encode(["address","uint256"],[t,m])),S=l.ethers.keccak256(a.encode(["address","bytes32"],[c,E]));p[S]=d}return{[e.toLowerCase()]:{stateDiff:p}}}}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)"],ae=["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)"],ie="0xcA143Ce32Fe78f1f7019d7d551a6402fC5350c73",ce=["function getPair(address tokenA, address tokenB) external view returns (address pair)"],ue="0x86407bEa2078ea5f5EB5A52B2caA963bC1F889Da",de=["function getPair(address tokenA, address tokenB) external view returns (address pair)"],le="0x0BFbCF9fa4f9C56B0F40a671Ad40E0805A091865",pe=["function getPool(address tokenA, address tokenB, uint24 fee) external view returns (address pool)"],he=[100,500,2500,1e4],fe=60000n,we=120000n,me=100000n;class Ae{constructor(e,t){this.poolCache=new Map,this.provider=e,this.v2Factory=new l.ethers.Contract(ie,ce,e),this.v3Factory=new l.ethers.Contract(le,pe,e),this.babySwapFactory=new l.ethers.Contract(ue,de,e)}async findBestRoute(e,t,r){const o=await this.discoverPools(e,t);if(o.length===0)throw new Error(`No pools found for ${e} -> ${t}`);const n=(await this.calculateRoutes(o,e,t,r)).reduce((a,i)=>i.amountOut>a.amountOut?i:a);return{routes:[n],percentages:[1e4],totalAmountIn:r,totalAmountOut:n.amountOut,totalGasEstimate:n.gasEstimate}}async discoverPools(e,t){const r=[];try{const o=await this.findV2Pool(e,t);o&&r.push(o)}catch{}for(const o of he)try{const s=await this.findV3Pool(e,t,o);s&&r.push(s)}catch{}try{const o=await this.findBabySwapPool(e,t);o&&r.push(o)}catch{}return r}async findV2Pool(e,t){const r=`v2-${e}-${t}`;if(this.poolCache.has(r))return this.poolCache.get(r);const o=await this.v2Factory.getPair(e,t);if(o===l.ethers.ZeroAddress)return null;const s=new l.ethers.Contract(o,G,this.provider),[n,a,i]=await Promise.all([s.token0(),s.token1(),s.getReserves()]),c={address:o,token0:n,token1:a,protocol:f.PancakeV2,reserve0:i.reserve0,reserve1:i.reserve1};return this.poolCache.set(r,c),c}async findV3Pool(e,t,r){const o=`v3-${e}-${t}-${r}`;if(this.poolCache.has(o))return this.poolCache.get(o);const s=await this.v3Factory.getPool(e,t,r);if(s===l.ethers.ZeroAddress)return null;const n=new l.ethers.Contract(s,ae,this.provider),[a,i,c]=await Promise.all([n.token0(),n.token1(),n.liquidity()]);if(c===0n)return null;const u={address:s,token0:a,token1:i,protocol:f.PancakeV3,fee:r,liquidity:c};return this.poolCache.set(o,u),u}async findBabySwapPool(e,t){const r=`babyswap-${e}-${t}`;if(this.poolCache.has(r))return this.poolCache.get(r);const o=await this.babySwapFactory.getPair(e,t);if(o===l.ethers.ZeroAddress)return null;const s=new l.ethers.Contract(o,G,this.provider),[n,a,i]=await Promise.all([s.token0(),s.token1(),s.getReserves()]),c={address:o,token0:n,token1:a,protocol:f.BabySwap,reserve0:i.reserve0,reserve1:i.reserve1};return this.poolCache.set(r,c),c}async calculateRoutes(e,t,r,o){const s=[];for(const n of e)try{const a=await this.getAmountOut(n,t,r,o);a>0n&&s.push({steps:[{pool:n,tokenIn:t,tokenOut:r,amountIn:o,amountOut:a}],amountIn:o,amountOut:a,gasEstimate:n.protocol===f.PancakeV2||n.protocol===f.BabySwap?fe:n.protocol===f.Dodo?me:we})}catch{}return s}async getAmountOut(e,t,r,o){if(e.protocol===f.PancakeV2)return this.getV2AmountOut(e,t,o);if(e.protocol===f.BabySwap)return this.getBabySwapAmountOut(e,t,o);if(e.protocol===f.Dodo)throw new Error("DODO amount out not supported in local route discovery");return this.estimateV3AmountOut(e,t,o)}getV2AmountOut(e,t,r){const o=t.toLowerCase()===e.token0.toLowerCase(),[s,n]=o?[e.reserve0,e.reserve1]:[e.reserve1,e.reserve0],a=r*9975n,i=a*n,c=s*10000n+a;return i/c}getBabySwapAmountOut(e,t,r){const o=t.toLowerCase()===e.token0.toLowerCase(),[s,n]=o?[e.reserve0,e.reserve1]:[e.reserve1,e.reserve0],a=r*998n,i=a*n,c=s*1000n+a;return i/c}estimateV3AmountOut(e,t,r){const s=1000000n-BigInt(e.fee||2500);return r*s/1000000n}clearCache(){this.poolCache.clear()}}class ve{constructor(e){this.adapters=e}build(e,t,r,o,s=b){const n=this.flattenRoutes(e),a=this.mergeIdenticalPools(n),i=this.topologicalSort(a,t),c=this.convertToSwapSteps(i),u=this.extractIntermediates(i,t,r),d=BigInt(Math.floor(Date.now()/1e3)+s);return{srcToken:t,dstToken:r,amountIn:e.totalAmountIn,amountOutMin:o,steps:c,intermediateTokens:u,deadline:d,quoteId:l.ethers.ZeroHash,expectAmountOut:e.totalAmountOut,feeReceiver:l.ethers.ZeroAddress,feeBps:0}}flattenRoutes(e){const t=[];for(let r=0;r<e.routes.length;r++){const o=e.routes[r],s=e.percentages[r],n=e.totalAmountIn*BigInt(s)/_;for(let a=0;a<o.steps.length;a++){const i=o.steps[a];t.push({pool:i.pool,tokenIn:i.tokenIn,tokenOut:i.tokenOut,amountIn:a===0?n:0n,routeIndex:r,stepIndex:a})}}return t}mergeIdenticalPools(e){const t=new Map,r=[];for(const o of e){const s=`${o.pool.address}-${o.tokenIn}-${o.tokenOut}`;if(t.has(s)){const n=t.get(s);o.amountIn>0n&&n.amountIn>0n&&(n.amountIn=0n)}else{const n={...o};t.set(s,n),r.push(n)}}for(const o of r){const s=`${o.pool.address}-${o.tokenIn}-${o.tokenOut}`;e.filter(a=>`${a.pool.address}-${a.tokenIn}-${a.tokenOut}`===s).length>1&&(o.amountIn=0n)}return r}topologicalSort(e,t){const r=new Map,o=new Map,s=new Map;for(const i of e){const c=this.stepKey(i);o.set(c,i),r.set(c,0),s.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 d=this.stepKey(u);d!==c&&(s.get(d).push(c),r.set(c,(r.get(c)||0)+1))}}}const n=[];for(const[i,c]of r)c===0&&n.push(i);const a=[];for(;n.length>0;){const i=n.shift(),c=o.get(i);a.push(c);for(const u of s.get(i)||[]){const d=(r.get(u)||0)-1;r.set(u,d),d===0&&n.push(u)}}if(a.length!==e.length)throw new Error("Circular dependency detected in route");return a}convertToSwapSteps(e){return e.map(t=>{const r=this.adapters.get(t.pool.protocol);if(!r)throw new Error(`No adapter for protocol: ${t.pool.protocol}`);return{adapter:r,pool:t.pool.address,tokenIn:t.tokenIn,tokenOut:t.tokenOut,amountIn:t.amountIn,extraData:this.encodeExtraData(t.pool)}})}encodeExtraData(e){switch(e.protocol){case f.PancakeV2:return"0x";case f.PancakeV3:case f.UniswapV3:case f.UniswapV4:return"0x";case f.Dodo:return"0x";case f.NomiswapStable:return"0x";case f.Biswap:return"0x";case f.Apeswap:return"0x";case f.BabyDogeSwap:return"0x";case f.BabySwap:return"0x";default:return"0x"}}extractIntermediates(e,t,r){const o=new Set;for(const s of e)s.tokenOut!==r&&o.add(s.tokenOut);return o.delete(t),o.delete(r),Array.from(o)}stepKey(e){return`${e.pool.address}-${e.tokenIn}-${e.tokenOut}`}}exports.API_DEFAULTS=I;exports.ApiClient=Z;exports.ApiError=v;exports.BPS_DENOMINATOR=_;exports.BSC_MAINNET_CONFIG=te;exports.BSC_TESTNET_CONFIG=re;exports.CustomFeeError=y;exports.DEFAULT_API_URL=K;exports.DEFAULT_DEADLINE_SECONDS=b;exports.DEFAULT_EXECUTE_TIMEOUT_MS=D;exports.DEFAULT_SLIPPAGE_BPS=X;exports.DEFAULT_TRANSACTION_RESPONSE_POLL_INTERVALS_MS=F;exports.ERR_ZERO_AMOUNT_PATHS=ee;exports.ExecuteTimeoutError=L;exports.MAX_FEE_BPS=R;exports.MAX_PLATFORM_CUT_BPS=J;exports.NATIVE_TOKEN_ADDRESS=Y;exports.PeachClient=U;exports.ProtocolType=f;exports.RouteDiscovery=Ae;exports.SwapBuilder=ve;exports.isNativeTokenAddress=T;exports.withWalletSendTimeout=N;
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
@@ -318,7 +318,7 @@ export declare function isNativeTokenAddress(address: string): boolean;
318
318
  /**
319
319
  * Known DEX providers supported by the SDK.
320
320
  */
321
- export declare type KnownProvider = "PANCAKEV2" | "PANCAKEV3" | "PANCAKE_INFINITY_CL" | "UNISWAPV3" | "UNISWAPV4" | "DODO" | "THENA" | "NOMISWAP_STABLE" | "BISWAP" | "APESWAP" | "BABYDOGESWAP" | "BABYSWAP";
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
322
 
323
323
  /**
324
324
  * Client-side default cap on the integrator-set custom fee. Mirrors the PeachRouter v2
@@ -519,24 +519,47 @@ export declare class PeachClient {
519
519
  */
520
520
  private formatSimulateError;
521
521
  /**
522
- * Find which step in the route causes the same revert as the full route (e.g. MUL_ERROR).
523
- * Simulates with steps [0..1], [0..2], ... and returns the first step whose revert
524
- * 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.
525
535
  *
526
536
  * @param quote - Full quote from getQuote (the one that fails when simulated)
527
537
  * @param slippageBps - Same as for simulate
528
538
  * @param fromAddress - Same as for simulate
529
539
  * @param stateOverrides - Same as for simulate (use when simulating ERC20 sell with arbitrary address)
530
- * @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.
531
- * @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.
532
543
  */
533
544
  findFailingStep(quote: Quote, slippageBps: number, fromAddress?: string, stateOverrides?: Record<string, {
534
545
  stateDiff: Record<string, string>;
535
- }>, fullRouteError?: unknown): Promise<FindFailingStepResult | null>;
536
- /** 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. */
537
550
  private normalizeRevertReason;
538
- /** Build a quote that only includes the first stepCount steps (for findFailingStep). */
539
- 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;
540
563
  /**
541
564
  * Build state overrides for ERC20 token balance and allowance.
542
565
  * Useful for simulating swaps without actual on-chain token balance/approval.
@@ -607,15 +630,24 @@ export declare enum ProtocolType {
607
630
  PancakeV2 = "PancakeV2",
608
631
  PancakeV3 = "PancakeV3",
609
632
  PancakeInfinityCl = "Pancake_Infinity_Cl",
633
+ PancakeInfinityLb = "Pancake_Infinity_Lb",
610
634
  UniswapV3 = "UniswapV3",
611
635
  UniswapV4 = "UniswapV4",
612
636
  Dodo = "Dodo",
613
- Thena = "Thena",
637
+ ThenaV3 = "ThenaV3",
638
+ ThenaFusion = "Thena_Fusion",
614
639
  NomiswapStable = "Nomiswap_Stable",
615
640
  Biswap = "Biswap",
616
641
  Apeswap = "Apeswap",
617
642
  BabyDogeSwap = "BabyDogeSwap",
618
- BabySwap = "BabySwap"
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"
619
651
  }
620
652
 
621
653
  /**