@peachprojects/aggregator-sdk 0.0.0-experimental-20260324105259

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/dist/index.mjs ADDED
@@ -0,0 +1,1098 @@
1
+ import { ethers as h } from "ethers";
2
+ const X = 50, y = 10000n, T = 1200, D = 6e4, x = [50, 100, 200, 400, 800, 1200], V = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
3
+ function O(m) {
4
+ return m.toLowerCase() === V.toLowerCase();
5
+ }
6
+ const tt = 4001;
7
+ var g = /* @__PURE__ */ ((m) => (m.PancakeV2 = "PancakeV2", m.PancakeV3 = "PancakeV3", m.UniswapV3 = "UniswapV3", m.Dodo = "Dodo", m.Thena = "Thena", m))(g || {});
8
+ const et = {
9
+ chainId: 56,
10
+ rpcUrl: "https://bsc-dataseed.binance.org",
11
+ weth: "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c",
12
+ // WBNB
13
+ adapters: []
14
+ }, nt = {
15
+ chainId: 97,
16
+ rpcUrl: "https://bsc-testnet-rpc.publicnode.com",
17
+ weth: "0xae13d989daC2f0dEbFf460aC112a837C89BAa7cd",
18
+ // WBNB Testnet
19
+ adapters: []
20
+ };
21
+ class $ extends Error {
22
+ constructor(t, e, n) {
23
+ super(t), this.name = "ExecuteTimeoutError", this.stage = e, this.txHash = n;
24
+ }
25
+ }
26
+ const k = {
27
+ /** Default route search depth */
28
+ depth: 3,
29
+ /** Default trade split count */
30
+ splitCount: 20,
31
+ /** Default DEX providers */
32
+ providers: ["PANCAKEV2", "PANCAKEV3", "UNISWAPV3", "DODO", "THENA"],
33
+ /** Default client version for V3 API */
34
+ clientVersion: 1001500
35
+ }, M = 1e4;
36
+ class U {
37
+ constructor(t) {
38
+ if (!t.baseUrl)
39
+ throw new Error("ApiClient requires a baseUrl");
40
+ this.baseUrl = t.baseUrl, this.timeout = t.timeout || M;
41
+ }
42
+ /**
43
+ * Find optimal routes via API
44
+ */
45
+ async findRoutes(t) {
46
+ const {
47
+ from: e,
48
+ target: n,
49
+ amount: o,
50
+ byAmountIn: s = !0,
51
+ depth: r = k.depth,
52
+ splitCount: a = k.splitCount,
53
+ providers: i = k.providers
54
+ } = t, c = new URLSearchParams({
55
+ from: e,
56
+ target: n,
57
+ amount: o.toString(),
58
+ by_amount_in: s.toString(),
59
+ depth: r.toString(),
60
+ split_count: a.toString(),
61
+ providers: i.join(","),
62
+ v: k.clientVersion.toString()
63
+ }), u = `${this.baseUrl}/router/find_routes?${c}`, d = new AbortController(), l = setTimeout(() => d.abort(), this.timeout);
64
+ try {
65
+ const f = await fetch(u, {
66
+ method: "GET",
67
+ headers: {
68
+ Accept: "application/json"
69
+ },
70
+ signal: d.signal
71
+ });
72
+ if (clearTimeout(l), !f.ok)
73
+ throw new w(
74
+ `API request failed: ${f.status} ${f.statusText}`,
75
+ f.status
76
+ );
77
+ const v = await f.json();
78
+ if (v.code !== 200)
79
+ throw new w(v.msg || "Route not found", v.code);
80
+ if (!v.data || !v.data.paths || v.data.paths.length === 0)
81
+ throw new w("No routes found", 404);
82
+ return v.data;
83
+ } catch (f) {
84
+ throw clearTimeout(l), f instanceof w ? f : f instanceof Error ? f.name === "AbortError" ? new w("API request timeout", 408) : new w(`API request failed: ${f.message}`, 0) : new w("Unknown API error", 0);
85
+ }
86
+ }
87
+ /**
88
+ * Get service status including available providers
89
+ */
90
+ async getStatus() {
91
+ const t = `${this.baseUrl}/router/status`, e = new AbortController(), n = setTimeout(() => e.abort(), this.timeout);
92
+ try {
93
+ const o = await fetch(t, {
94
+ method: "GET",
95
+ headers: {
96
+ Accept: "application/json"
97
+ },
98
+ signal: e.signal
99
+ });
100
+ if (clearTimeout(n), !o.ok)
101
+ throw new w(
102
+ `API request failed: ${o.status} ${o.statusText}`,
103
+ o.status
104
+ );
105
+ const s = await o.json();
106
+ if (s.code !== 200)
107
+ throw new w(s.msg || "Failed to get status", s.code);
108
+ return s.data;
109
+ } catch (o) {
110
+ throw clearTimeout(n), o instanceof w ? o : o instanceof Error ? o.name === "AbortError" ? new w("API request timeout", 408) : new w(`API request failed: ${o.message}`, 0) : new w("Unknown API error", 0);
111
+ }
112
+ }
113
+ /**
114
+ * Get list of available providers
115
+ */
116
+ async getAvailableProviders() {
117
+ return (await this.getStatus()).providers;
118
+ }
119
+ /**
120
+ * Update API base URL
121
+ */
122
+ setBaseUrl(t) {
123
+ this.baseUrl = t;
124
+ }
125
+ /**
126
+ * Get current API base URL
127
+ */
128
+ getBaseUrl() {
129
+ return this.baseUrl;
130
+ }
131
+ }
132
+ class w extends Error {
133
+ constructor(t, e) {
134
+ super(t), this.name = "ApiError", this.code = e;
135
+ }
136
+ }
137
+ async function b(m, t = D) {
138
+ if (t <= 0)
139
+ return m;
140
+ let e;
141
+ try {
142
+ return await Promise.race([
143
+ m,
144
+ new Promise((n, o) => {
145
+ e = setTimeout(() => {
146
+ o(
147
+ new $(
148
+ `Wallet did not settle sendTransaction within ${t}ms.`,
149
+ "wallet_send"
150
+ )
151
+ );
152
+ }, t);
153
+ })
154
+ ]);
155
+ } finally {
156
+ e && clearTimeout(e);
157
+ }
158
+ }
159
+ const B = [
160
+ "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)",
161
+ "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)",
162
+ "function isAdapterRegistered(address adapter) external view returns (bool)",
163
+ "function WETH() external view returns (address)"
164
+ ], E = [
165
+ "function approve(address spender, uint256 amount) external returns (bool)",
166
+ "function allowance(address owner, address spender) external view returns (uint256)",
167
+ "function balanceOf(address account) external view returns (uint256)",
168
+ "function decimals() external view returns (uint8)",
169
+ "function symbol() external view returns (string)"
170
+ ], R = {
171
+ PANCAKEV3: g.PancakeV3,
172
+ PANCAKEV2: g.PancakeV2,
173
+ UNISWAPV3: g.UniswapV3,
174
+ DODO: g.Dodo,
175
+ THENA: g.Thena
176
+ };
177
+ class ot {
178
+ constructor(t, e, n) {
179
+ this.config = t, this.provider = e || new h.JsonRpcProvider(t.rpcUrl), this.routerContract = new h.Contract(
180
+ t.routerAddress || h.ZeroAddress,
181
+ B,
182
+ this.provider
183
+ ), this.apiClient = n?.api ? new U(n.api) : null;
184
+ }
185
+ /**
186
+ * Get the effective router address for a quote.
187
+ */
188
+ getRouterAddress(t) {
189
+ const e = t.routerAddress || this.config.routerAddress;
190
+ if (!e || e === h.ZeroAddress)
191
+ throw new Error("No router address available. Provide routerAddress in config or use API-based getQuote.");
192
+ return e;
193
+ }
194
+ /**
195
+ * Apply slippage to swap params, returning a new SwapParams with adjusted amountOutMin
196
+ */
197
+ applySlippage(t, e) {
198
+ if (e < 0 || e > 1e4)
199
+ throw new Error("slippageBps must be between 0 and 10000");
200
+ const n = t.amountOutMin * (y - BigInt(e)) / y;
201
+ return { ...t, amountOutMin: n };
202
+ }
203
+ /**
204
+ * Build transaction requests for an approval (if needed) and the swap itself.
205
+ */
206
+ async swap(t, e, n) {
207
+ const o = this.getRouterAddress(t), { tx: s, method: r } = this.buildSwapTransactionRequest(t, n);
208
+ let a;
209
+ return t.srcNative || (a = await this.buildApprovalRequest(
210
+ t.srcToken,
211
+ e,
212
+ t.amountIn,
213
+ o,
214
+ n
215
+ )), {
216
+ routerAddress: o,
217
+ method: r,
218
+ tx: s,
219
+ approval: a
220
+ };
221
+ }
222
+ /**
223
+ * Execute swap using the legacy signer-managed flow.
224
+ *
225
+ * @deprecated Prefer swap(), then send the returned tx request with your wallet/client.
226
+ */
227
+ async execute(t, e, n) {
228
+ const o = await e.getAddress(), s = await this.swap(t, o, n);
229
+ try {
230
+ return s.approval && await (await this.sendTransactionWithTimeout(
231
+ e,
232
+ s.approval.tx,
233
+ n
234
+ )).wait(), await this.sendTransactionWithTimeout(e, s.tx, n);
235
+ } catch (r) {
236
+ 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");
237
+ if (i && (c || /reason=null|data=null/.test(a))) {
238
+ 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)`);
239
+ throw d.cause = r, d;
240
+ }
241
+ throw r;
242
+ }
243
+ }
244
+ /**
245
+ * Encode parameters to contract format
246
+ */
247
+ encodeParams(t) {
248
+ return {
249
+ srcToken: t.srcToken,
250
+ dstToken: t.dstToken,
251
+ amountIn: t.amountIn,
252
+ amountOutMin: t.amountOutMin,
253
+ steps: t.steps.map((e) => ({
254
+ adapter: e.adapter,
255
+ pool: e.pool,
256
+ tokenIn: e.tokenIn,
257
+ tokenOut: e.tokenOut,
258
+ amountIn: e.amountIn,
259
+ extraData: e.extraData
260
+ })),
261
+ intermediateTokens: t.intermediateTokens,
262
+ deadline: t.deadline,
263
+ quoteId: t.quoteId,
264
+ expectAmountOut: t.expectAmountOut
265
+ };
266
+ }
267
+ getProtocolForProvider(t) {
268
+ if (t in R)
269
+ return R[t];
270
+ throw new Error(`Unsupported provider: ${t}`);
271
+ }
272
+ /**
273
+ * Encode swap calldata for the Peach Aggregator contract
274
+ * Useful for simulation or building custom transactions
275
+ *
276
+ * @param quote - Quote object from getQuote
277
+ * @param slippageBps - Slippage tolerance in basis points (e.g. 50 = 0.5%). Required.
278
+ * @returns Encoded calldata and transaction info (to address, value)
279
+ */
280
+ encodeSwapCalldata(t, e) {
281
+ const n = t.routerAddress ?? this.config.routerAddress ?? this.routerContract.target, o = this.applySlippage(t.params, e), s = t.srcNative === !0, r = this.encodeParams(o);
282
+ if (s) {
283
+ const a = this.routerContract.interface.encodeFunctionData("swapETH", [r]);
284
+ return {
285
+ to: n,
286
+ data: a,
287
+ value: t.amountIn,
288
+ method: "swapETH"
289
+ };
290
+ } else {
291
+ const a = this.routerContract.interface.encodeFunctionData("swap", [r]);
292
+ return {
293
+ to: n,
294
+ data: a,
295
+ value: 0n,
296
+ method: "swap"
297
+ };
298
+ }
299
+ }
300
+ buildSwapTransactionRequest(t, e) {
301
+ const { to: n, data: o, value: s, method: r } = this.encodeSwapCalldata(t, e.slippageBps);
302
+ return {
303
+ method: r,
304
+ tx: this.applyTxOverrides({ to: n, data: o, value: s }, e, !0)
305
+ };
306
+ }
307
+ async buildApprovalRequest(t, e, n, o, s) {
308
+ const r = await this.getAllowance(t, e, o);
309
+ if (!(r >= n))
310
+ return {
311
+ token: t,
312
+ owner: e,
313
+ spender: o,
314
+ currentAllowance: r,
315
+ requiredAmount: n,
316
+ approveAmount: h.MaxUint256,
317
+ tx: this.buildApprovalTransactionRequest(t, o, s)
318
+ };
319
+ }
320
+ buildApprovalTransactionRequest(t, e, n) {
321
+ const s = new h.Interface(E).encodeFunctionData("approve", [e, h.MaxUint256]);
322
+ return this.applyTxOverrides({ to: t, data: s, value: 0n }, n, !1);
323
+ }
324
+ async getAllowance(t, e, n) {
325
+ return new h.Contract(t, E, this.provider).allowance(e, n);
326
+ }
327
+ applyTxOverrides(t, e, n) {
328
+ const o = { ...t };
329
+ return e.gasPrice && (o.gasPrice = e.gasPrice), n && e.gasLimit && (o.gasLimit = e.gasLimit), o;
330
+ }
331
+ async sendTransactionWithTimeout(t, e, n) {
332
+ const o = n.timeoutMs ?? D;
333
+ if (o <= 0)
334
+ return t.sendTransaction(e);
335
+ const s = t;
336
+ if (typeof s.sendUncheckedTransaction == "function" && s.provider) {
337
+ const r = await b(
338
+ s.sendUncheckedTransaction(e),
339
+ o
340
+ ), a = await this.waitForTransactionResponse(
341
+ s.provider,
342
+ r,
343
+ o,
344
+ n.transactionResponsePollingIntervalsMs
345
+ );
346
+ if (a.response)
347
+ return a.response;
348
+ const i = a.rpcErrors > 0 ? `${a.rpcErrors} transient provider error(s) and ${a.nullResponses} null response(s)` : `${a.nullResponses} null response(s)`;
349
+ throw new $(
350
+ `Transaction was broadcast but provider did not return TransactionResponse within ${o}ms (${i}).`,
351
+ "provider_index",
352
+ r
353
+ );
354
+ }
355
+ return b(t.sendTransaction(e), o);
356
+ }
357
+ async waitForTransactionResponse(t, e, n, o = x) {
358
+ const s = Date.now() + n;
359
+ let r = 0, a = 0, i = 0;
360
+ for (; Date.now() < s; ) {
361
+ try {
362
+ const u = await t.getTransaction(e);
363
+ if (u)
364
+ return { response: u, nullResponses: a, rpcErrors: i };
365
+ a++;
366
+ } catch {
367
+ i++;
368
+ }
369
+ const c = this.getNextPollingDelay(o, r);
370
+ r++, await this.delay(Math.min(c, Math.max(25, s - Date.now())));
371
+ }
372
+ return { response: null, nullResponses: a, rpcErrors: i };
373
+ }
374
+ async delay(t) {
375
+ t <= 0 || await new Promise((e) => {
376
+ setTimeout(e, t);
377
+ });
378
+ }
379
+ getNextPollingDelay(t, e) {
380
+ return t.length === 0 ? x.at(-1) ?? 1200 : t[Math.min(e, t.length - 1)] ?? 1200;
381
+ }
382
+ /**
383
+ * Get token metadata and, optionally, the balance for a specific owner.
384
+ */
385
+ async getTokenInfo(t, e) {
386
+ const n = new h.Contract(t, E, this.provider), [o, s, r] = await Promise.all([
387
+ n.symbol(),
388
+ n.decimals(),
389
+ e ? n.balanceOf(e) : Promise.resolve(void 0)
390
+ ]);
391
+ return r === void 0 ? { symbol: o, decimals: s } : { symbol: o, decimals: s, balance: r };
392
+ }
393
+ /**
394
+ * Get user token balance
395
+ */
396
+ async getBalance(t, e) {
397
+ return new h.Contract(t, E, this.provider).balanceOf(e);
398
+ }
399
+ /**
400
+ * Get quote via API
401
+ * @throws Error if API client is not configured
402
+ */
403
+ async getQuote(t) {
404
+ const { srcToken: e, dstToken: n, amountIn: o, options: s = {} } = t, {
405
+ byAmountIn: r = !0,
406
+ depth: a = k.depth,
407
+ splitCount: i = k.splitCount,
408
+ providers: c = k.providers,
409
+ deadlineSeconds: u = T
410
+ } = s;
411
+ if (!this.apiClient)
412
+ throw new Error("API client is not configured. getQuote() requires PeachClientOptions.api.");
413
+ const d = O(e), l = O(n), f = d ? this.config.weth : e, v = l ? this.config.weth : n, I = await this.apiClient.findRoutes({
414
+ from: f,
415
+ target: v,
416
+ amount: o,
417
+ byAmountIn: r,
418
+ depth: a,
419
+ splitCount: i,
420
+ providers: c
421
+ });
422
+ return this.buildQuoteFromApi(I, f, v, u, c, d, l);
423
+ }
424
+ /**
425
+ * Filter paths by allowed providers, removing paths with disallowed providers
426
+ * and cascade-removing orphaned paths that depend on removed paths.
427
+ */
428
+ filterPathsByProviders(t, e, n, o) {
429
+ const s = new Set(e.map((l) => l.toUpperCase()));
430
+ let r = t.filter((l) => s.has(l.provider.toUpperCase()));
431
+ if (r.length === t.length)
432
+ return r;
433
+ const a = n.toLowerCase(), i = o.toLowerCase(), c = /* @__PURE__ */ new Set();
434
+ c.add(a);
435
+ let u = !0;
436
+ for (; u; ) {
437
+ u = !1;
438
+ for (const l of r)
439
+ c.has(l.token_in.toLowerCase()) && !c.has(l.token_out.toLowerCase()) && (c.add(l.token_out.toLowerCase()), u = !0);
440
+ }
441
+ const d = /* @__PURE__ */ new Set();
442
+ for (d.add(i), u = !0; u; ) {
443
+ u = !1;
444
+ for (const l of r)
445
+ d.has(l.token_out.toLowerCase()) && !d.has(l.token_in.toLowerCase()) && (d.add(l.token_in.toLowerCase()), u = !0);
446
+ }
447
+ return r.length, r = r.filter(
448
+ (l) => c.has(l.token_in.toLowerCase()) && d.has(l.token_out.toLowerCase())
449
+ ), r;
450
+ }
451
+ /**
452
+ * Build Quote from route data (e.g. from JSON file or API response).
453
+ * Use this to simulate a swap from a pre-computed route without calling the API.
454
+ *
455
+ * @param data - Route data with paths, amount_in, amount_out, contracts, gas
456
+ * @param srcToken - Source token address (first path token_in)
457
+ * @param dstToken - Destination token address (last path token_out)
458
+ * @param deadlineSeconds - Optional deadline in seconds from now (default: 20 min)
459
+ */
460
+ buildQuoteFromRouteData(t, e, n, o, s) {
461
+ const r = this.buildQuoteFromRouteDataInternal(
462
+ t,
463
+ e,
464
+ n,
465
+ o ?? T,
466
+ void 0
467
+ );
468
+ return s?.srcNative && (r.srcNative = !0), s?.dstNative && (r.dstNative = !0), r;
469
+ }
470
+ /**
471
+ * Build Quote from API response
472
+ */
473
+ buildQuoteFromApi(t, e, n, o, s, r, a) {
474
+ const i = this.buildQuoteFromRouteDataInternal(
475
+ t,
476
+ e,
477
+ n,
478
+ o,
479
+ s
480
+ );
481
+ return r && (i.srcNative = !0), a && (i.dstNative = !0), i;
482
+ }
483
+ buildQuoteFromRouteDataInternal(t, e, n, o, s) {
484
+ const r = e.toLowerCase();
485
+ t.paths.length;
486
+ let a = t.paths.filter((p) => !(!(p.token_in.toLowerCase() === r) && BigInt(p.amount_in) === 0n && BigInt(p.amount_out) === 0n));
487
+ if (a.length === 0)
488
+ throw new w("All route paths have zero amounts", 4001);
489
+ if (s && s.length > 0 && (a = this.filterPathsByProviders(a, s, e, n), a.length === 0))
490
+ throw new w("No valid route paths remaining after provider filtering", 4001);
491
+ const i = n.toLowerCase();
492
+ let c = 0n, u = 0n;
493
+ for (const p of a)
494
+ p.token_in.toLowerCase() === r && (c += BigInt(p.amount_in)), p.token_out.toLowerCase() === i && (u += BigInt(p.amount_out));
495
+ const d = BigInt(
496
+ Math.floor(Date.now() / 1e3) + o
497
+ ), l = /* @__PURE__ */ new Map();
498
+ for (const p of a) {
499
+ const A = p.token_in.toLowerCase();
500
+ l.set(A, (l.get(A) ?? 0) + 1);
501
+ }
502
+ const f = /* @__PURE__ */ new Map(), v = a.map((p) => {
503
+ const A = p.token_in.toLowerCase(), _ = (f.get(A) ?? 0) + 1;
504
+ f.set(A, _);
505
+ const S = l.get(A), L = S > 1 && _ < S, N = A === r;
506
+ return {
507
+ adapter: p.adapter,
508
+ pool: p.pool,
509
+ tokenIn: p.token_in,
510
+ tokenOut: p.token_out,
511
+ amountIn: N || L ? BigInt(p.amount_in) : 0n,
512
+ extraData: p.extra_data || "0x"
513
+ };
514
+ }), I = /* @__PURE__ */ new Set();
515
+ for (const p of a)
516
+ p.token_out.toLowerCase() !== i && I.add(p.token_out);
517
+ const P = Array.from(I), C = {
518
+ srcToken: e,
519
+ dstToken: n,
520
+ amountIn: c,
521
+ amountOutMin: u,
522
+ steps: v,
523
+ intermediateTokens: P,
524
+ deadline: d,
525
+ quoteId: t.request_id ? h.id(t.request_id).slice(0, 66) : h.ZeroHash,
526
+ expectAmountOut: u
527
+ }, F = {
528
+ routes: [
529
+ {
530
+ steps: a.map((p) => ({
531
+ pool: {
532
+ address: p.pool,
533
+ token0: p.token_in,
534
+ token1: p.token_out,
535
+ protocol: this.getProtocolForProvider(p.provider),
536
+ fee: p.fee_rate ? Math.round(parseFloat(p.fee_rate) * 1e6) : void 0
537
+ },
538
+ tokenIn: p.token_in,
539
+ tokenOut: p.token_out,
540
+ amountIn: BigInt(p.amount_in),
541
+ amountOut: BigInt(p.amount_out)
542
+ })),
543
+ amountIn: c,
544
+ amountOut: u,
545
+ gasEstimate: BigInt(t.gas)
546
+ }
547
+ ],
548
+ percentages: [1e4],
549
+ totalAmountIn: c,
550
+ totalAmountOut: u,
551
+ totalGasEstimate: BigInt(t.gas)
552
+ };
553
+ if (!t.contracts?.router)
554
+ throw new w("API response missing contracts.router address", 4002);
555
+ return {
556
+ srcToken: e,
557
+ dstToken: n,
558
+ amountIn: c,
559
+ amountOut: u,
560
+ priceImpact: parseFloat(t.deviation_ratio || "0"),
561
+ route: F,
562
+ params: C,
563
+ gasEstimate: BigInt(t.gas),
564
+ routerAddress: t.contracts?.router
565
+ };
566
+ }
567
+ /**
568
+ * Get available providers from the API
569
+ * @throws Error if API client is not configured
570
+ */
571
+ async getAvailableProviders() {
572
+ if (!this.apiClient)
573
+ throw new Error("API client is not configured. getAvailableProviders() requires PeachClientOptions.api.");
574
+ return this.apiClient.getAvailableProviders();
575
+ }
576
+ /**
577
+ * Simulate swap via eth_call (no gas, no state change)
578
+ * Useful for testing and verifying quote accuracy
579
+ *
580
+ * @param quote - Quote object from getQuote
581
+ * @param slippageBps - Slippage tolerance in basis points (e.g. 50 = 0.5%). Required.
582
+ * @param fromAddress - Optional caller address for simulation (default: zero address)
583
+ * @param stateOverrides - Optional state overrides for ERC20 balance/allowance
584
+ * @returns Simulated amountOut and method used
585
+ */
586
+ async simulate(t, e, n, o) {
587
+ const s = n || h.ZeroAddress, { to: r, data: a, value: i, method: c } = this.encodeSwapCalldata(t, e);
588
+ if (console.log("[PeachClient] simulate using router:", r), o) {
589
+ const u = this.getJsonRpcProviderForStateOverrides(), d = i > 0n ? "0x" + i.toString(16) : void 0, l = await u.send("eth_call", [
590
+ { from: s, to: r, data: a, value: d },
591
+ "latest",
592
+ o
593
+ ]), [f] = this.routerContract.interface.decodeFunctionResult(c, l);
594
+ return { amountOut: f, method: c };
595
+ } else {
596
+ const u = await this.provider.call({
597
+ from: s,
598
+ to: r,
599
+ data: a,
600
+ value: i > 0n ? i : void 0
601
+ }), [d] = this.routerContract.interface.decodeFunctionResult(c, u);
602
+ return { amountOut: d, method: c };
603
+ }
604
+ }
605
+ getJsonRpcProviderForStateOverrides() {
606
+ if (typeof this.provider.send != "function")
607
+ throw new Error(
608
+ "stateOverrides require a JsonRpcProvider-compatible provider with send(method, params)."
609
+ );
610
+ return this.provider;
611
+ }
612
+ /**
613
+ * Format simulate error with human-readable details
614
+ */
615
+ formatSimulateError(t, e, n, o) {
616
+ const s = t instanceof Error ? t : new Error(String(t)), r = t;
617
+ let a = "unknown";
618
+ if (r.reason && typeof r.reason == "string")
619
+ a = r.reason;
620
+ else if (r.revert && typeof r.revert == "object") {
621
+ const d = r.revert;
622
+ d.args && Array.isArray(d.args) && (a = d.args.join(", "));
623
+ } else s.message && (a = s.message);
624
+ const i = e.params.steps.map(
625
+ (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)}`
626
+ ).join(`
627
+ `), c = [
628
+ `Simulate ${n} failed: ${a}`,
629
+ ` Route: ${e.srcToken} → ${e.dstToken}`,
630
+ ` AmountIn: ${e.amountIn}`,
631
+ ` AmountOutMin: ${e.params.amountOutMin}`,
632
+ ` Router: ${e.routerAddress}`,
633
+ ` Caller: ${o}`,
634
+ ` Steps (${e.params.steps.length}):`,
635
+ i
636
+ ].join(`
637
+ `), u = new Error(c);
638
+ return u.cause = s, u.reason = a, u;
639
+ }
640
+ /**
641
+ * Find which step in the route causes the same revert as the full route (e.g. MUL_ERROR).
642
+ * Simulates with steps [0..1], [0..2], ... and returns the first step whose revert
643
+ * matches fullRouteError. Ignores steps that revert with a different reason (e.g. unknown custom error).
644
+ *
645
+ * @param quote - Full quote from getQuote (the one that fails when simulated)
646
+ * @param slippageBps - Same as for simulate
647
+ * @param fromAddress - Same as for simulate
648
+ * @param stateOverrides - Same as for simulate (use when simulating ERC20 sell with arbitrary address)
649
+ * @param fullRouteError - The error from simulating the full route. Required so we match by revert reason (e.g. "MUL_ERROR"); only the step that produces the same reason is returned.
650
+ * @returns The step index and step details whose revert matches fullRouteError, or null if none match or full route succeeds
651
+ */
652
+ async findFailingStep(t, e, n, o, s) {
653
+ const r = t.params.steps;
654
+ if (!r.length) return null;
655
+ const a = s != null ? this.normalizeRevertReason(s) : void 0;
656
+ for (let i = 1; i <= r.length; i++) {
657
+ const c = this.quoteWithFirstNSteps(t, i);
658
+ try {
659
+ await this.simulate(c, e, n, o);
660
+ } catch (u) {
661
+ const d = this.normalizeRevertReason(u), l = u?.message ?? u?.reason ?? u?.shortMessage;
662
+ if (a != null ? d === a : !0)
663
+ return {
664
+ stepIndex: i - 1,
665
+ step: r[i - 1],
666
+ error: u,
667
+ revertMessage: typeof l == "string" ? l : void 0,
668
+ fullRouteRevertMessage: a ?? void 0
669
+ };
670
+ }
671
+ }
672
+ return null;
673
+ }
674
+ /** Extract a comparable revert reason (e.g. "MUL_ERROR") from an error for findFailingStep matching. */
675
+ normalizeRevertReason(t) {
676
+ if (t == null) return;
677
+ const e = t;
678
+ if (typeof e.reason == "string" && e.reason.length > 0) return e.reason;
679
+ const n = e.shortMessage ?? e.message;
680
+ if (typeof n != "string") return;
681
+ const o = n.match(/reason="([^"]+)"/);
682
+ if (o) return o[1];
683
+ const s = n.match(/reverted:\s*"([^"]+)"/);
684
+ if (s) return s[1];
685
+ const r = n.match(/execution reverted:\s*"([^"]+)"/);
686
+ if (r) return r[1];
687
+ }
688
+ /** Build a quote that only includes the first stepCount steps (for findFailingStep). */
689
+ quoteWithFirstNSteps(t, e) {
690
+ const n = t.params.steps.slice(0, e), o = t.dstToken.toLowerCase(), s = /* @__PURE__ */ new Set(), r = [];
691
+ for (const a of n) {
692
+ const i = a.tokenOut.toLowerCase();
693
+ i !== o && !s.has(i) && (s.add(i), r.push(a.tokenOut));
694
+ }
695
+ return {
696
+ ...t,
697
+ params: {
698
+ ...t.params,
699
+ steps: n,
700
+ intermediateTokens: r
701
+ }
702
+ };
703
+ }
704
+ /**
705
+ * Build state overrides for ERC20 token balance and allowance.
706
+ * Useful for simulating swaps without actual on-chain token balance/approval.
707
+ *
708
+ * Automatically covers multiple storage slot layouts (slots 0-2 for balance,
709
+ * slots 0-7 for allowance) to handle OZ ERC20 (slot 0/1), Ownable+ERC20 (slot 1/2),
710
+ * and other common BSC token implementations.
711
+ *
712
+ * WBNB (native wrap) is skipped automatically — swapETH wraps msg.value
713
+ * internally so no ERC20 approval from the sender is needed.
714
+ *
715
+ * @param tokenAddress - ERC20 token address (WBNB returns empty overrides)
716
+ * @param owner - Address that needs the balance and allowance
717
+ * @param routerAddress - Router address (used as fallback spender)
718
+ * @param balance - Balance to inject (default: 1M tokens with 18 decimals)
719
+ * @param spenderAddress - Spender to approve (default: routerAddress). Pass quote.routerAddress when simulating API quotes.
720
+ */
721
+ buildStateOverrides(t, e, n, o, s, r) {
722
+ if (r?.isNative || O(t))
723
+ return {};
724
+ if (!n || n === h.ZeroAddress)
725
+ throw new Error("buildStateOverrides requires a non-zero routerAddress.");
726
+ const a = h.AbiCoder.defaultAbiCoder(), i = o || h.parseUnits("1000000", 18), c = s ?? n, u = h.zeroPadValue(h.toBeHex(i), 32), d = h.zeroPadValue(h.toBeHex(h.MaxUint256), 32), l = {}, f = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 50, 51, 52, 100, 101, 102];
727
+ for (const v of f) {
728
+ const I = h.keccak256(a.encode(["address", "uint256"], [e, v]));
729
+ l[I] = u;
730
+ const P = h.keccak256(a.encode(["address", "uint256"], [e, v])), C = h.keccak256(a.encode(["address", "bytes32"], [c, P]));
731
+ l[C] = d;
732
+ }
733
+ return {
734
+ [t.toLowerCase()]: { stateDiff: l }
735
+ };
736
+ }
737
+ }
738
+ const q = [
739
+ "function token0() external view returns (address)",
740
+ "function token1() external view returns (address)",
741
+ "function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast)"
742
+ ], H = [
743
+ "function token0() external view returns (address)",
744
+ "function token1() external view returns (address)",
745
+ "function fee() external view returns (uint24)",
746
+ "function liquidity() external view returns (uint128)",
747
+ "function slot0() external view returns (uint160 sqrtPriceX96, int24 tick, uint16 observationIndex, uint16 observationCardinality, uint16 observationCardinalityNext, uint32 feeProtocol, bool unlocked)"
748
+ ], W = "0xcA143Ce32Fe78f1f7019d7d551a6402fC5350c73", G = [
749
+ "function getPair(address tokenA, address tokenB) external view returns (address pair)"
750
+ ], K = "0x0BFbCF9fa4f9C56B0F40a671Ad40E0805A091865", j = [
751
+ "function getPool(address tokenA, address tokenB, uint24 fee) external view returns (address pool)"
752
+ ], Q = [100, 500, 2500, 1e4], Z = 60000n, z = 120000n, J = 100000n;
753
+ class rt {
754
+ constructor(t, e) {
755
+ this.poolCache = /* @__PURE__ */ new Map(), this.provider = t, this.config = e, this.v2Factory = new h.Contract(
756
+ W,
757
+ G,
758
+ t
759
+ ), this.v3Factory = new h.Contract(
760
+ K,
761
+ j,
762
+ t
763
+ );
764
+ }
765
+ /**
766
+ * Discover optimal route
767
+ */
768
+ async findBestRoute(t, e, n) {
769
+ const o = await this.discoverPools(t, e);
770
+ if (o.length === 0)
771
+ throw new Error(`No pools found for ${t} -> ${e}`);
772
+ const r = (await this.calculateRoutes(
773
+ o,
774
+ t,
775
+ e,
776
+ n
777
+ )).reduce(
778
+ (a, i) => i.amountOut > a.amountOut ? i : a
779
+ );
780
+ return {
781
+ routes: [r],
782
+ percentages: [1e4],
783
+ // 100%
784
+ totalAmountIn: n,
785
+ totalAmountOut: r.amountOut,
786
+ totalGasEstimate: r.gasEstimate
787
+ };
788
+ }
789
+ /**
790
+ * Discover available pools (direct path)
791
+ */
792
+ async discoverPools(t, e) {
793
+ const n = [];
794
+ try {
795
+ const o = await this.findV2Pool(t, e);
796
+ o && n.push(o);
797
+ } catch {
798
+ }
799
+ for (const o of Q)
800
+ try {
801
+ const s = await this.findV3Pool(t, e, o);
802
+ s && n.push(s);
803
+ } catch {
804
+ }
805
+ return n;
806
+ }
807
+ /**
808
+ * Find V2 pool
809
+ */
810
+ async findV2Pool(t, e) {
811
+ const n = `v2-${t}-${e}`;
812
+ if (this.poolCache.has(n))
813
+ return this.poolCache.get(n);
814
+ const o = await this.v2Factory.getPair(t, e);
815
+ if (o === h.ZeroAddress)
816
+ return null;
817
+ const s = new h.Contract(o, q, this.provider), [r, a, i] = await Promise.all([
818
+ s.token0(),
819
+ s.token1(),
820
+ s.getReserves()
821
+ ]), c = {
822
+ address: o,
823
+ token0: r,
824
+ token1: a,
825
+ protocol: g.PancakeV2,
826
+ reserve0: i.reserve0,
827
+ reserve1: i.reserve1
828
+ };
829
+ return this.poolCache.set(n, c), c;
830
+ }
831
+ /**
832
+ * Find V3 pool
833
+ */
834
+ async findV3Pool(t, e, n) {
835
+ const o = `v3-${t}-${e}-${n}`;
836
+ if (this.poolCache.has(o))
837
+ return this.poolCache.get(o);
838
+ const s = await this.v3Factory.getPool(t, e, n);
839
+ if (s === h.ZeroAddress)
840
+ return null;
841
+ const r = new h.Contract(s, H, this.provider), [a, i, c] = await Promise.all([
842
+ r.token0(),
843
+ r.token1(),
844
+ r.liquidity()
845
+ ]);
846
+ if (c === 0n)
847
+ return null;
848
+ const u = {
849
+ address: s,
850
+ token0: a,
851
+ token1: i,
852
+ protocol: g.PancakeV3,
853
+ fee: n,
854
+ liquidity: c
855
+ };
856
+ return this.poolCache.set(o, u), u;
857
+ }
858
+ /**
859
+ * Calculate route quotes
860
+ */
861
+ async calculateRoutes(t, e, n, o) {
862
+ const s = [];
863
+ for (const r of t)
864
+ try {
865
+ const a = await this.getAmountOut(
866
+ r,
867
+ e,
868
+ n,
869
+ o
870
+ );
871
+ a > 0n && s.push({
872
+ steps: [
873
+ {
874
+ pool: r,
875
+ tokenIn: e,
876
+ tokenOut: n,
877
+ amountIn: o,
878
+ amountOut: a
879
+ }
880
+ ],
881
+ amountIn: o,
882
+ amountOut: a,
883
+ gasEstimate: r.protocol === g.PancakeV2 ? Z : r.protocol === g.Dodo ? J : z
884
+ });
885
+ } catch {
886
+ }
887
+ return s;
888
+ }
889
+ /**
890
+ * Get output for a single pool
891
+ */
892
+ async getAmountOut(t, e, n, o) {
893
+ if (t.protocol === g.PancakeV2)
894
+ return this.getV2AmountOut(t, e, o);
895
+ if (t.protocol === g.Dodo)
896
+ throw new Error("DODO amount out not supported in local route discovery");
897
+ return this.estimateV3AmountOut(t, e, o);
898
+ }
899
+ /**
900
+ * V2 output calculation
901
+ */
902
+ getV2AmountOut(t, e, n) {
903
+ 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;
904
+ return i / c;
905
+ }
906
+ /**
907
+ * V3 output estimation (simplified)
908
+ */
909
+ estimateV3AmountOut(t, e, n) {
910
+ const s = 1000000n - BigInt(t.fee || 2500);
911
+ return n * s / 1000000n;
912
+ }
913
+ /**
914
+ * Clear cache
915
+ */
916
+ clearCache() {
917
+ this.poolCache.clear();
918
+ }
919
+ }
920
+ class st {
921
+ constructor(t) {
922
+ this.adapters = t;
923
+ }
924
+ /**
925
+ * Build SwapParams from split route
926
+ */
927
+ build(t, e, n, o, s = T) {
928
+ const r = this.flattenRoutes(t), a = this.mergeIdenticalPools(r), i = this.topologicalSort(a, e), c = this.convertToSwapSteps(i), u = this.extractIntermediates(
929
+ i,
930
+ e,
931
+ n
932
+ ), d = BigInt(Math.floor(Date.now() / 1e3) + s);
933
+ return {
934
+ srcToken: e,
935
+ dstToken: n,
936
+ amountIn: t.totalAmountIn,
937
+ amountOutMin: o,
938
+ steps: c,
939
+ intermediateTokens: u,
940
+ deadline: d,
941
+ quoteId: h.ZeroHash,
942
+ expectAmountOut: t.totalAmountOut
943
+ };
944
+ }
945
+ /**
946
+ * Flatten split route
947
+ */
948
+ flattenRoutes(t) {
949
+ const e = [];
950
+ for (let n = 0; n < t.routes.length; n++) {
951
+ const o = t.routes[n], s = t.percentages[n], r = t.totalAmountIn * BigInt(s) / y;
952
+ for (let a = 0; a < o.steps.length; a++) {
953
+ const i = o.steps[a];
954
+ e.push({
955
+ pool: i.pool,
956
+ tokenIn: i.tokenIn,
957
+ tokenOut: i.tokenOut,
958
+ // Only first step has fixed amount, subsequent steps depend on previous output
959
+ amountIn: a === 0 ? r : 0n,
960
+ routeIndex: n,
961
+ stepIndex: a
962
+ });
963
+ }
964
+ }
965
+ return e;
966
+ }
967
+ /**
968
+ * Merge identical pools
969
+ * Key: pool + tokenIn + tokenOut
970
+ */
971
+ mergeIdenticalPools(t) {
972
+ const e = /* @__PURE__ */ new Map(), n = [];
973
+ for (const o of t) {
974
+ const s = `${o.pool.address}-${o.tokenIn}-${o.tokenOut}`;
975
+ if (e.has(s)) {
976
+ const r = e.get(s);
977
+ o.amountIn > 0n && r.amountIn > 0n && (r.amountIn = 0n);
978
+ } else {
979
+ const r = { ...o };
980
+ e.set(s, r), n.push(r);
981
+ }
982
+ }
983
+ for (const o of n) {
984
+ const s = `${o.pool.address}-${o.tokenIn}-${o.tokenOut}`;
985
+ t.filter(
986
+ (a) => `${a.pool.address}-${a.tokenIn}-${a.tokenOut}` === s
987
+ ).length > 1 && (o.amountIn = 0n);
988
+ }
989
+ return n;
990
+ }
991
+ /**
992
+ * Topological sort (Kahn's Algorithm)
993
+ */
994
+ topologicalSort(t, e) {
995
+ const n = /* @__PURE__ */ new Map(), o = /* @__PURE__ */ new Map(), s = /* @__PURE__ */ new Map();
996
+ for (const i of t) {
997
+ const c = this.stepKey(i);
998
+ o.set(c, i), n.set(c, 0), s.set(c, []);
999
+ }
1000
+ for (const i of t) {
1001
+ const c = this.stepKey(i);
1002
+ if (i.tokenIn !== e) {
1003
+ for (const u of t)
1004
+ if (u.tokenOut === i.tokenIn) {
1005
+ const d = this.stepKey(u);
1006
+ d !== c && (s.get(d).push(c), n.set(c, (n.get(c) || 0) + 1));
1007
+ }
1008
+ }
1009
+ }
1010
+ const r = [];
1011
+ for (const [i, c] of n)
1012
+ c === 0 && r.push(i);
1013
+ const a = [];
1014
+ for (; r.length > 0; ) {
1015
+ const i = r.shift(), c = o.get(i);
1016
+ a.push(c);
1017
+ for (const u of s.get(i) || []) {
1018
+ const d = (n.get(u) || 0) - 1;
1019
+ n.set(u, d), d === 0 && r.push(u);
1020
+ }
1021
+ }
1022
+ if (a.length !== t.length)
1023
+ throw new Error("Circular dependency detected in route");
1024
+ return a;
1025
+ }
1026
+ /**
1027
+ * Convert to contract SwapStep format
1028
+ */
1029
+ convertToSwapSteps(t) {
1030
+ return t.map((e) => {
1031
+ const n = this.adapters.get(e.pool.protocol);
1032
+ if (!n)
1033
+ throw new Error(`No adapter for protocol: ${e.pool.protocol}`);
1034
+ return {
1035
+ adapter: n,
1036
+ pool: e.pool.address,
1037
+ tokenIn: e.tokenIn,
1038
+ tokenOut: e.tokenOut,
1039
+ amountIn: e.amountIn,
1040
+ extraData: this.encodeExtraData(e.pool)
1041
+ };
1042
+ });
1043
+ }
1044
+ /**
1045
+ * Encode protocol-specific parameters
1046
+ */
1047
+ encodeExtraData(t) {
1048
+ switch (t.protocol) {
1049
+ case g.PancakeV2:
1050
+ return "0x";
1051
+ // V2 doesn't need extra parameters
1052
+ case g.PancakeV3:
1053
+ case g.UniswapV3:
1054
+ return "0x";
1055
+ case g.Dodo:
1056
+ return "0x";
1057
+ default:
1058
+ return "0x";
1059
+ }
1060
+ }
1061
+ /**
1062
+ * Extract intermediate tokens
1063
+ */
1064
+ extractIntermediates(t, e, n) {
1065
+ const o = /* @__PURE__ */ new Set();
1066
+ for (const s of t)
1067
+ s.tokenOut !== n && o.add(s.tokenOut);
1068
+ return o.delete(e), o.delete(n), Array.from(o);
1069
+ }
1070
+ /**
1071
+ * Generate unique step key
1072
+ */
1073
+ stepKey(t) {
1074
+ return `${t.pool.address}-${t.tokenIn}-${t.tokenOut}`;
1075
+ }
1076
+ }
1077
+ export {
1078
+ k as API_DEFAULTS,
1079
+ U as ApiClient,
1080
+ w as ApiError,
1081
+ y as BPS_DENOMINATOR,
1082
+ et as BSC_MAINNET_CONFIG,
1083
+ nt as BSC_TESTNET_CONFIG,
1084
+ T as DEFAULT_DEADLINE_SECONDS,
1085
+ D as DEFAULT_EXECUTE_TIMEOUT_MS,
1086
+ X as DEFAULT_SLIPPAGE_BPS,
1087
+ x as DEFAULT_TRANSACTION_RESPONSE_POLL_INTERVALS_MS,
1088
+ tt as ERR_ZERO_AMOUNT_PATHS,
1089
+ $ as ExecuteTimeoutError,
1090
+ V as NATIVE_TOKEN_ADDRESS,
1091
+ ot as PeachClient,
1092
+ g as ProtocolType,
1093
+ rt as RouteDiscovery,
1094
+ st as SwapBuilder,
1095
+ O as isNativeTokenAddress,
1096
+ b as withWalletSendTimeout
1097
+ };
1098
+ //# sourceMappingURL=index.mjs.map