@anywalletkit/protocol-swap2-curve 0.1.0 → 0.1.2

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.d.cts CHANGED
@@ -350,6 +350,7 @@ declare function curveOnChain(opts: CurveOnChainOptions): {
350
350
  amount: bigint;
351
351
  minDy: bigint;
352
352
  }>;
353
+ checkPools: (chainId: number, list: readonly CurvePool[]) => Promise<string | null>;
353
354
  };
354
355
  /**
355
356
  * How far the device's own auto slippage may sit under the backend's before a remote build's minimum counts as too low:
package/dist/index.d.ts CHANGED
@@ -350,6 +350,7 @@ declare function curveOnChain(opts: CurveOnChainOptions): {
350
350
  amount: bigint;
351
351
  minDy: bigint;
352
352
  }>;
353
+ checkPools: (chainId: number, list: readonly CurvePool[]) => Promise<string | null>;
353
354
  };
354
355
  /**
355
356
  * How far the device's own auto slippage may sit under the backend's before a remote build's minimum counts as too low:
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  CurveSwap2Protocol
3
- } from "./chunk-IIPLIHNY.js";
3
+ } from "./chunk-JCWXGKPJ.js";
4
4
  import {
5
5
  CURVE_AUTO_SLIPPAGE_DRIFT_BPS,
6
6
  CURVE_POOLS,
@@ -19,7 +19,7 @@ import {
19
19
  routeArgs,
20
20
  routePools,
21
21
  underlyingAllowed
22
- } from "./chunk-FICPW2P2.js";
22
+ } from "./chunk-IFREGXMV.js";
23
23
  import {
24
24
  ADDRESS_PROVIDER_ABI,
25
25
  COINS_INT,
package/dist/protocol.cjs CHANGED
@@ -601,25 +601,56 @@ function checkRoute(chainId, deployment, table, args, tokenIn, tokenOut) {
601
601
  // src/rpc.ts
602
602
  var import_util2 = require("@anywalletkit/protocol-swap2-core/util");
603
603
  var RPC_BATCH_MAX = 10;
604
+ var RPC_RATE_LIMIT_PAUSE_MS = 1e3;
605
+ function isExecutionError(error) {
606
+ if (!error || typeof error !== "object") return false;
607
+ const { code, message } = error;
608
+ return code === 3 || typeof message === "string" && /revert|invalid opcode|out of gas/i.test(message);
609
+ }
610
+ function isRateLimit(error) {
611
+ if (!error || typeof error !== "object") return false;
612
+ const { code, message } = error;
613
+ return code === -32005 || code === -32007 || code === 429 || typeof message === "string" && /rate|limit|too many/i.test(message);
614
+ }
615
+ var errorText = (error) => {
616
+ const m = error && typeof error === "object" ? error.message : void 0;
617
+ return (0, import_util2.redactUrls)(typeof m === "string" ? m : JSON.stringify(error) ?? String(error)).slice(0, 160);
618
+ };
604
619
  function rpcClient(rpcUrls) {
620
+ async function once(url, calls, signal) {
621
+ const res = await fetch(url, {
622
+ method: "POST",
623
+ headers: { "content-type": "application/json" },
624
+ body: JSON.stringify(calls.map((c, id) => ({ jsonrpc: "2.0", id, ...c }))),
625
+ signal: signal ?? (0, import_util2.timeoutSignal)(12e3)
626
+ });
627
+ if (!res.ok) throw Object.assign(new Error(`HTTP ${res.status}`), { limited: res.status === 429 });
628
+ const body = await res.json();
629
+ if (!Array.isArray(body)) throw Object.assign(new Error(body.error?.message ?? "batch refused"), { limited: isRateLimit(body.error) });
630
+ const out = calls.map(() => null);
631
+ const answered = /* @__PURE__ */ new Set();
632
+ for (const r of body) {
633
+ if (typeof r.id !== "number" || r.id < 0 || r.id >= out.length) continue;
634
+ if (r.error !== void 0 && r.error !== null && !isExecutionError(r.error)) throw Object.assign(new Error(errorText(r.error)), { limited: isRateLimit(r.error) });
635
+ answered.add(r.id);
636
+ if (r.error === void 0 || r.error === null) out[r.id] = r.result ?? null;
637
+ }
638
+ if (answered.size !== calls.length) throw new Error(`answered ${answered.size} of ${calls.length} calls`);
639
+ return out;
640
+ }
605
641
  async function batch(chainId, calls, signal) {
606
642
  let lastErr;
607
- for (const url of rpcUrls[chainId] ?? []) {
608
- try {
609
- const res = await fetch(url, {
610
- method: "POST",
611
- headers: { "content-type": "application/json" },
612
- body: JSON.stringify(calls.map((c, id) => ({ jsonrpc: "2.0", id, ...c }))),
613
- signal: signal ?? (0, import_util2.timeoutSignal)(12e3)
614
- });
615
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
616
- const body = await res.json();
617
- if (!Array.isArray(body)) throw new Error(body.error?.message ?? "batch refused");
618
- const out = calls.map(() => null);
619
- for (const r of body) if (typeof r.id === "number" && r.id >= 0 && r.id < out.length && r.error === void 0) out[r.id] = r.result ?? null;
620
- return out;
621
- } catch (e) {
622
- lastErr = e;
643
+ for (let pass = 0; pass < 2; pass++) {
644
+ if (pass > 0) {
645
+ if (!lastErr?.limited || signal?.aborted) break;
646
+ await new Promise((r) => setTimeout(r, RPC_RATE_LIMIT_PAUSE_MS));
647
+ }
648
+ for (const url of rpcUrls[chainId] ?? []) {
649
+ try {
650
+ return await once(url, calls, signal);
651
+ } catch (e) {
652
+ lastErr = e;
653
+ }
623
654
  }
624
655
  }
625
656
  throw new Error(`curve: no RPC answered on chain ${chainId} (${lastErr instanceof Error ? lastErr.message : String(lastErr)})`);
@@ -632,11 +663,16 @@ function rpcClient(rpcUrls) {
632
663
  };
633
664
  }
634
665
  var ethCall = (to, data) => ({ method: "eth_call", params: [{ to, data }, "latest"] });
666
+ function firstWord(raw) {
667
+ return typeof raw === "string" && raw.length >= 66 && /^0x(?:[0-9a-fA-F]{64})+$/.test(raw) ? raw.slice(2, 66).toLowerCase() : null;
668
+ }
635
669
  function wordAddress(raw) {
636
- return typeof raw === "string" && /^0x[0-9a-fA-F]{64}$/.test(raw) ? `0x${raw.slice(-40)}`.toLowerCase() : null;
670
+ const w = firstWord(raw);
671
+ return w !== null && /^0{24}/.test(w) ? `0x${w.slice(24)}` : null;
637
672
  }
638
673
  function wordUint(raw) {
639
- return typeof raw === "string" && /^0x[0-9a-fA-F]{64}$/.test(raw) ? BigInt(raw) : null;
674
+ const w = firstWord(raw);
675
+ return w === null ? null : BigInt(`0x${w}`);
640
676
  }
641
677
 
642
678
  // src/slippage.ts
@@ -794,20 +830,32 @@ function curveOnChain(opts) {
794
830
  await verifyDeployment(chainId);
795
831
  const index = poolIndex(chainId, d2, own(pools, chainId));
796
832
  const hops = routePools(call).map((a) => index.get(lc2(a)));
797
- const calls = [ethCall(d2.router, encodeGetDy(d2.family, call, call.amount))];
798
- for (const pool of hops) {
799
- calls.push({ method: "eth_getCode", params: [pool.address, "latest"] });
800
- pool.coins.forEach((_, k) => calls.push(ethCall(pool.address, `${COINS_UINT}${k.toString(16).padStart(64, "0")}`)));
801
- }
833
+ const reads = poolCalls(hops);
834
+ const calls = [ethCall(d2.router, encodeGetDy(d2.family, call, call.amount)), ...reads];
802
835
  const probeIn = call.amount / 1000n;
803
836
  const probeAt = calls.length;
804
837
  if (probe && probeIn > 0n) calls.push(ethCall(d2.router, encodeGetDy(d2.family, call, probeIn)));
805
838
  const res = await rpc(chainId, calls);
806
- let at = 1;
839
+ const poolReason = await poolsReason(chainId, hops, res.slice(1, 1 + reads.length));
840
+ if (poolReason) return poolReason;
841
+ const out = wordUint(res[0]);
842
+ if (out === null) return "the router no longer quotes this route";
843
+ if (out < call.minDy) return `price moved \u2014 the pools now pay ${out}, under your minimum ${call.minDy}`;
844
+ return { out, probeOut: probe && probeIn > 0n ? wordUint(res[probeAt]) : null, amount: call.amount, minDy: call.minDy };
845
+ }
846
+ function poolCalls(list) {
847
+ return list.flatMap((pool) => [
848
+ { method: "eth_getCode", params: [pool.address, "latest"] },
849
+ ...pool.coins.map((_, k) => ethCall(pool.address, `${COINS_UINT}${k.toString(16).padStart(64, "0")}`))
850
+ ]);
851
+ }
852
+ async function poolsReason(chainId, list, res) {
853
+ let at = 0;
807
854
  const retry = [];
808
- for (const pool of hops) {
855
+ for (const pool of list) {
809
856
  const code = res[at++];
810
- if (typeof code !== "string" || code.length <= 2 || lc2((0, import_viem3.keccak256)(code)) !== lc2(pool.codeHash)) return `pool ${pool.name} (${pool.address}) is not the pinned code`;
857
+ if (typeof code !== "string") return `could not read pool ${pool.name} (${pool.address}) over RPC`;
858
+ if (code.length <= 2 || lc2((0, import_viem3.keccak256)(code)) !== lc2(pool.codeHash)) return `pool ${pool.name} (${pool.address}) is not the pinned code`;
811
859
  for (let k = 0; k < pool.coins.length; k++) {
812
860
  const coin = wordAddress(res[at++]);
813
861
  if (coin === null) retry.push({ pool, k });
@@ -821,12 +869,12 @@ function curveOnChain(opts) {
821
869
  if (coin !== lc2(pool.coins[k])) return `pool ${pool.name} does not report its pinned coin ${k}`;
822
870
  }
823
871
  }
824
- const out = wordUint(res[0]);
825
- if (out === null) return "the router no longer quotes this route";
826
- if (out < call.minDy) return `price moved \u2014 the pools now pay ${out}, under your minimum ${call.minDy}`;
827
- return { out, probeOut: probe && probeIn > 0n ? wordUint(res[probeAt]) : null, amount: call.amount, minDy: call.minDy };
872
+ return null;
873
+ }
874
+ async function checkPools(chainId, list) {
875
+ return list.length ? poolsReason(chainId, list, await rpc(chainId, poolCalls(list))) : null;
828
876
  }
829
- return { rpc, verifyDeployment, checkSealed, inspectSealed };
877
+ return { rpc, verifyDeployment, checkSealed, inspectSealed, checkPools };
830
878
  }
831
879
  function lastToken(call) {
832
880
  let k = 0;