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