@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 +1 -1
- package/dist/index.cjs +3 -3
- package/dist/index.d.ts +44 -12
- package/dist/index.mjs +622 -518
- package/package.json +3 -2
- package/dist/index.cjs.map +0 -1
- package/dist/index.mjs.map +0 -1
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,
|
|
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 ${
|
|
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" | "
|
|
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
|
-
*
|
|
523
|
-
*
|
|
524
|
-
*
|
|
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 -
|
|
531
|
-
* @
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
539
|
-
private
|
|
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
|
-
|
|
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
|
/**
|