@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 +4 -3
- package/dist/{chunk-FICPW2P2.js → chunk-IFREGXMV.js} +81 -33
- package/dist/{chunk-FICPW2P2.js.map → chunk-IFREGXMV.js.map} +1 -1
- package/dist/{chunk-IIPLIHNY.js → chunk-JCWXGKPJ.js} +2 -2
- package/dist/index.cjs +79 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -2
- package/dist/protocol.cjs +79 -31
- package/dist/protocol.cjs.map +1 -1
- package/dist/protocol.js +2 -2
- package/dist/provider.cjs +79 -31
- package/dist/provider.cjs.map +1 -1
- package/dist/provider.js +1 -1
- package/package.json +5 -5
- /package/dist/{chunk-IIPLIHNY.js.map → chunk-JCWXGKPJ.js.map} +0 -0
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,
|
|
118
|
-
|
|
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 (
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
597
|
-
|
|
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
|
-
|
|
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
|
|
654
|
+
for (const pool of list) {
|
|
608
655
|
const code = res[at++];
|
|
609
|
-
if (typeof code !== "string"
|
|
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
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
return
|
|
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-
|
|
912
|
+
//# sourceMappingURL=chunk-IFREGXMV.js.map
|