@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/README.md CHANGED
@@ -34,7 +34,7 @@ const curve = curveProvider({
34
34
 
35
35
  | Option | Default | Meaning |
36
36
  |---|---|---|
37
- | `rpcUrls` | — | `{ chainId: [url…] }`, tried in order. Quotes, preflight and receipts. A chain without one is not offered. |
37
+ | `rpcUrls` | — | `{ chainId: [url…] }`, tried in order. Quotes, preflight and receipts. A chain without one is not offered. A batch an endpoint answers only in part (a rate-limit element such as QuickNode's -32007, a node fault) goes to the next one, once more after a 1 s pause when the last refusal was for the request rate, and then refuses as `no RPC answered on chain N`; only a revert counts as an answer. |
38
38
  | `defaultSlippageBps` | auto | Used when the caller passes none. Default: swap2-core's `suggestSlippage` for the pair and the route's price impact (stable ↔ stable 10 bps, majors 50, others 100, plus half the impact). |
39
39
  | `quoteTtlMs` | `180000` | The only expiry: Router NG has no deadline. Long enough for an approval to be mined on Ethereum. |
40
40
  | `onWarning` | `console.warn` | Told when a chain's AddressProvider lists another router than the pinned one (never acted on). |
@@ -114,6 +114,7 @@ WETH9 deposit.
114
114
  ## Live check
115
115
 
116
116
  `node scripts/verify-live.mjs [chainId,…]` (after `pnpm build`): per chain the router's code hash, version, wrapped
117
- native and ABI family against the pins, then up to three quotes from the allowlist, each simulated with `eth_call` as a
118
- dummy wallet funded by a state override (balance and router allowance); the router's return must reach the minimum.
117
+ native and ABI family against the pins, every allowlisted pool through the preflight's own code and coins check
118
+ (`curveOnChain(…).checkPools`), then up to three quotes from the allowlist, each simulated with `eth_call` as a dummy
119
+ wallet funded by a state override (balance and router allowance); the router's return must reach the minimum.
119
120
  Read-only, nothing signed.
@@ -398,27 +398,58 @@ function checkRoute(chainId, deployment, table, args, tokenIn, tokenOut) {
398
398
  }
399
399
 
400
400
  // src/rpc.ts
401
- import { timeoutSignal } from "@anywalletkit/protocol-swap2-core/util";
401
+ import { redactUrls, timeoutSignal } from "@anywalletkit/protocol-swap2-core/util";
402
402
  var RPC_BATCH_MAX = 10;
403
+ var RPC_RATE_LIMIT_PAUSE_MS = 1e3;
404
+ function isExecutionError(error) {
405
+ if (!error || typeof error !== "object") return false;
406
+ const { code, message } = error;
407
+ return code === 3 || typeof message === "string" && /revert|invalid opcode|out of gas/i.test(message);
408
+ }
409
+ function isRateLimit(error) {
410
+ if (!error || typeof error !== "object") return false;
411
+ const { code, message } = error;
412
+ return code === -32005 || code === -32007 || code === 429 || typeof message === "string" && /rate|limit|too many/i.test(message);
413
+ }
414
+ var errorText = (error) => {
415
+ const m = error && typeof error === "object" ? error.message : void 0;
416
+ return redactUrls(typeof m === "string" ? m : JSON.stringify(error) ?? String(error)).slice(0, 160);
417
+ };
403
418
  function rpcClient(rpcUrls) {
419
+ async function once(url, calls, signal) {
420
+ const res = await fetch(url, {
421
+ method: "POST",
422
+ headers: { "content-type": "application/json" },
423
+ body: JSON.stringify(calls.map((c, id) => ({ jsonrpc: "2.0", id, ...c }))),
424
+ signal: signal ?? timeoutSignal(12e3)
425
+ });
426
+ if (!res.ok) throw Object.assign(new Error(`HTTP ${res.status}`), { limited: res.status === 429 });
427
+ const body = await res.json();
428
+ if (!Array.isArray(body)) throw Object.assign(new Error(body.error?.message ?? "batch refused"), { limited: isRateLimit(body.error) });
429
+ const out = calls.map(() => null);
430
+ const answered = /* @__PURE__ */ new Set();
431
+ for (const r of body) {
432
+ if (typeof r.id !== "number" || r.id < 0 || r.id >= out.length) continue;
433
+ if (r.error !== void 0 && r.error !== null && !isExecutionError(r.error)) throw Object.assign(new Error(errorText(r.error)), { limited: isRateLimit(r.error) });
434
+ answered.add(r.id);
435
+ if (r.error === void 0 || r.error === null) out[r.id] = r.result ?? null;
436
+ }
437
+ if (answered.size !== calls.length) throw new Error(`answered ${answered.size} of ${calls.length} calls`);
438
+ return out;
439
+ }
404
440
  async function batch(chainId, calls, signal) {
405
441
  let lastErr;
406
- for (const url of rpcUrls[chainId] ?? []) {
407
- try {
408
- const res = await fetch(url, {
409
- method: "POST",
410
- headers: { "content-type": "application/json" },
411
- body: JSON.stringify(calls.map((c, id) => ({ jsonrpc: "2.0", id, ...c }))),
412
- signal: signal ?? timeoutSignal(12e3)
413
- });
414
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
415
- const body = await res.json();
416
- if (!Array.isArray(body)) throw new Error(body.error?.message ?? "batch refused");
417
- const out = calls.map(() => null);
418
- 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;
419
- return out;
420
- } catch (e) {
421
- lastErr = e;
442
+ for (let pass = 0; pass < 2; pass++) {
443
+ if (pass > 0) {
444
+ if (!lastErr?.limited || signal?.aborted) break;
445
+ await new Promise((r) => setTimeout(r, RPC_RATE_LIMIT_PAUSE_MS));
446
+ }
447
+ for (const url of rpcUrls[chainId] ?? []) {
448
+ try {
449
+ return await once(url, calls, signal);
450
+ } catch (e) {
451
+ lastErr = e;
452
+ }
422
453
  }
423
454
  }
424
455
  throw new Error(`curve: no RPC answered on chain ${chainId} (${lastErr instanceof Error ? lastErr.message : String(lastErr)})`);
@@ -431,11 +462,16 @@ function rpcClient(rpcUrls) {
431
462
  };
432
463
  }
433
464
  var ethCall = (to, data) => ({ method: "eth_call", params: [{ to, data }, "latest"] });
465
+ function firstWord(raw) {
466
+ return typeof raw === "string" && raw.length >= 66 && /^0x(?:[0-9a-fA-F]{64})+$/.test(raw) ? raw.slice(2, 66).toLowerCase() : null;
467
+ }
434
468
  function wordAddress(raw) {
435
- return typeof raw === "string" && /^0x[0-9a-fA-F]{64}$/.test(raw) ? `0x${raw.slice(-40)}`.toLowerCase() : null;
469
+ const w = firstWord(raw);
470
+ return w !== null && /^0{24}/.test(w) ? `0x${w.slice(24)}` : null;
436
471
  }
437
472
  function wordUint(raw) {
438
- return typeof raw === "string" && /^0x[0-9a-fA-F]{64}$/.test(raw) ? BigInt(raw) : null;
473
+ const w = firstWord(raw);
474
+ return w === null ? null : BigInt(`0x${w}`);
439
475
  }
440
476
 
441
477
  // src/slippage.ts
@@ -593,20 +629,32 @@ function curveOnChain(opts) {
593
629
  await verifyDeployment(chainId);
594
630
  const index = poolIndex(chainId, d, own(pools, chainId));
595
631
  const hops = routePools(call).map((a) => index.get(lc2(a)));
596
- const calls = [ethCall(d.router, encodeGetDy(d.family, call, call.amount))];
597
- for (const pool of hops) {
598
- calls.push({ method: "eth_getCode", params: [pool.address, "latest"] });
599
- pool.coins.forEach((_, k) => calls.push(ethCall(pool.address, `${COINS_UINT}${k.toString(16).padStart(64, "0")}`)));
600
- }
632
+ const reads = poolCalls(hops);
633
+ const calls = [ethCall(d.router, encodeGetDy(d.family, call, call.amount)), ...reads];
601
634
  const probeIn = call.amount / 1000n;
602
635
  const probeAt = calls.length;
603
636
  if (probe && probeIn > 0n) calls.push(ethCall(d.router, encodeGetDy(d.family, call, probeIn)));
604
637
  const res = await rpc(chainId, calls);
605
- let at = 1;
638
+ const poolReason = await poolsReason(chainId, hops, res.slice(1, 1 + reads.length));
639
+ if (poolReason) return poolReason;
640
+ const out = wordUint(res[0]);
641
+ if (out === null) return "the router no longer quotes this route";
642
+ if (out < call.minDy) return `price moved \u2014 the pools now pay ${out}, under your minimum ${call.minDy}`;
643
+ return { out, probeOut: probe && probeIn > 0n ? wordUint(res[probeAt]) : null, amount: call.amount, minDy: call.minDy };
644
+ }
645
+ function poolCalls(list) {
646
+ return list.flatMap((pool) => [
647
+ { method: "eth_getCode", params: [pool.address, "latest"] },
648
+ ...pool.coins.map((_, k) => ethCall(pool.address, `${COINS_UINT}${k.toString(16).padStart(64, "0")}`))
649
+ ]);
650
+ }
651
+ async function poolsReason(chainId, list, res) {
652
+ let at = 0;
606
653
  const retry = [];
607
- for (const pool of hops) {
654
+ for (const pool of list) {
608
655
  const code = res[at++];
609
- if (typeof code !== "string" || code.length <= 2 || lc2(keccak256(code)) !== lc2(pool.codeHash)) return `pool ${pool.name} (${pool.address}) is not the pinned code`;
656
+ if (typeof code !== "string") return `could not read pool ${pool.name} (${pool.address}) over RPC`;
657
+ if (code.length <= 2 || lc2(keccak256(code)) !== lc2(pool.codeHash)) return `pool ${pool.name} (${pool.address}) is not the pinned code`;
610
658
  for (let k = 0; k < pool.coins.length; k++) {
611
659
  const coin = wordAddress(res[at++]);
612
660
  if (coin === null) retry.push({ pool, k });
@@ -620,12 +668,12 @@ function curveOnChain(opts) {
620
668
  if (coin !== lc2(pool.coins[k])) return `pool ${pool.name} does not report its pinned coin ${k}`;
621
669
  }
622
670
  }
623
- const out = wordUint(res[0]);
624
- if (out === null) return "the router no longer quotes this route";
625
- if (out < call.minDy) return `price moved \u2014 the pools now pay ${out}, under your minimum ${call.minDy}`;
626
- return { out, probeOut: probe && probeIn > 0n ? wordUint(res[probeAt]) : null, amount: call.amount, minDy: call.minDy };
671
+ return null;
672
+ }
673
+ async function checkPools(chainId, list) {
674
+ return list.length ? poolsReason(chainId, list, await rpc(chainId, poolCalls(list))) : null;
627
675
  }
628
- return { rpc, verifyDeployment, checkSealed, inspectSealed };
676
+ return { rpc, verifyDeployment, checkSealed, inspectSealed, checkPools };
629
677
  }
630
678
  function lastToken(call) {
631
679
  let k = 0;
@@ -861,4 +909,4 @@ export {
861
909
  curveRemotePins,
862
910
  curveProvider
863
911
  };
864
- //# sourceMappingURL=chunk-FICPW2P2.js.map
912
+ //# sourceMappingURL=chunk-IFREGXMV.js.map