@anywalletkit/protocol-swap2-curve 0.1.3 → 0.1.4
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/{chunk-JCWXGKPJ.js → chunk-7AXQMAHJ.js} +2 -2
- package/dist/{chunk-IFREGXMV.js → chunk-NW2OLRIK.js} +127 -105
- package/dist/chunk-NW2OLRIK.js.map +1 -0
- package/dist/index.cjs +126 -104
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/protocol.cjs +119 -99
- package/dist/protocol.cjs.map +1 -1
- package/dist/protocol.js +2 -2
- package/dist/provider.cjs +119 -99
- package/dist/provider.cjs.map +1 -1
- package/dist/provider.js +1 -1
- package/package.json +4 -4
- package/dist/chunk-IFREGXMV.js.map +0 -1
- /package/dist/{chunk-JCWXGKPJ.js.map → chunk-7AXQMAHJ.js.map} +0 -0
package/dist/protocol.js
CHANGED
package/dist/provider.cjs
CHANGED
|
@@ -25,6 +25,7 @@ __export(provider_exports, {
|
|
|
25
25
|
module.exports = __toCommonJS(provider_exports);
|
|
26
26
|
var import_viem4 = require("viem");
|
|
27
27
|
var import_util5 = require("@anywalletkit/protocol-swap2-core/util");
|
|
28
|
+
var import_errors4 = require("@anywalletkit/protocol-swap2-core/errors");
|
|
28
29
|
|
|
29
30
|
// src/abi.ts
|
|
30
31
|
var import_viem = require("viem");
|
|
@@ -378,6 +379,18 @@ var CURVE_POOLS = Object.freeze({
|
|
|
378
379
|
// src/routes.ts
|
|
379
380
|
var import_viem2 = require("viem");
|
|
380
381
|
var import_swap2_assets = require("@anywalletkit/swap2-assets");
|
|
382
|
+
|
|
383
|
+
// src/reasons.ts
|
|
384
|
+
var import_errors = require("@anywalletkit/protocol-swap2-core/errors");
|
|
385
|
+
var notes = (0, import_errors.refusalNotes)(256);
|
|
386
|
+
function noted(text, info) {
|
|
387
|
+
return info === void 0 ? text : notes.note(text, info);
|
|
388
|
+
}
|
|
389
|
+
function tagged(error, info) {
|
|
390
|
+
return info === void 0 ? error : (0, import_errors.withReason)(error, info);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// src/routes.ts
|
|
381
394
|
var MAX_QUOTE_CANDIDATES = 30;
|
|
382
395
|
var MAX_EXPLORED = 2e4;
|
|
383
396
|
var lc = (a) => a.toLowerCase();
|
|
@@ -528,7 +541,7 @@ function routePools(args) {
|
|
|
528
541
|
}
|
|
529
542
|
function checkRoute(chainId, deployment, table, args, tokenIn, tokenOut) {
|
|
530
543
|
const cols = deployment.family === "ng5" ? 5 : 4;
|
|
531
|
-
if (args.route.length !== 11 || args.params.length !== 5 || args.params.some((r) => r.length !== cols)) return `the route is not address[11] with uint256[${cols}][5]
|
|
544
|
+
if (args.route.length !== 11 || args.params.length !== 5 || args.params.some((r) => r.length !== cols)) return noted(`the route is not address[11] with uint256[${cols}][5]`, "policy_refused");
|
|
532
545
|
const index = poolIndex(chainId, deployment, table);
|
|
533
546
|
const zero = lc(ZERO_ADDRESS);
|
|
534
547
|
const eth = lc(deployment.eth);
|
|
@@ -536,14 +549,14 @@ function checkRoute(chainId, deployment, table, args, tokenIn, tokenOut) {
|
|
|
536
549
|
const v = erc4626Of(deployment);
|
|
537
550
|
let steps = 0;
|
|
538
551
|
while (steps < 5 && lc(args.route[2 * steps + 1]) !== zero) steps++;
|
|
539
|
-
if (steps === 0) return "the route has no step";
|
|
540
|
-
if (steps > MAX_ROUTE_STEPS) return `the route has ${steps} steps, more than ${MAX_ROUTE_STEPS}
|
|
541
|
-
for (let k = 2 * steps + 1; k < 11; k++) if (lc(args.route[k]) !== zero) return "the route names addresses after its end";
|
|
542
|
-
for (let k = steps; k < 5; k++) if (args.params[k].some((x) => x !== 0n)) return "the swap params carry rows after the route's end";
|
|
543
|
-
if (lc(args.route[0]) !== lc(tokenIn)) return `the route starts at ${args.route[0]}, not the source token
|
|
544
|
-
if (lc(args.route[2 * steps]) !== lc(tokenOut)) return `the route ends at ${args.route[2 * steps]}, not the destination token
|
|
552
|
+
if (steps === 0) return noted("the route has no step", "policy_refused");
|
|
553
|
+
if (steps > MAX_ROUTE_STEPS) return noted(`the route has ${steps} steps, more than ${MAX_ROUTE_STEPS}`, "policy_refused");
|
|
554
|
+
for (let k = 2 * steps + 1; k < 11; k++) if (lc(args.route[k]) !== zero) return noted("the route names addresses after its end", "policy_refused");
|
|
555
|
+
for (let k = steps; k < 5; k++) if (args.params[k].some((x) => x !== 0n)) return noted("the swap params carry rows after the route's end", "policy_refused");
|
|
556
|
+
if (lc(args.route[0]) !== lc(tokenIn)) return noted(`the route starts at ${args.route[0]}, not the source token`, "policy_refused");
|
|
557
|
+
if (lc(args.route[2 * steps]) !== lc(tokenOut)) return noted(`the route ends at ${args.route[2 * steps]}, not the destination token`, "price_check_failed");
|
|
545
558
|
for (let k = 1; k < steps; k++) {
|
|
546
|
-
if (!intermediateAllowed(chainId, deployment, args.route[2 * k])) return `step ${k} hands the swap through ${args.route[2 * k]}, a token the catalog does not list on this chain
|
|
559
|
+
if (!intermediateAllowed(chainId, deployment, args.route[2 * k])) return noted(`step ${k} hands the swap through ${args.route[2 * k]}, a token the catalog does not list on this chain`, "policy_refused");
|
|
547
560
|
}
|
|
548
561
|
const pools = /* @__PURE__ */ new Set();
|
|
549
562
|
let wraps = 0;
|
|
@@ -554,49 +567,50 @@ function checkRoute(chainId, deployment, table, args, tokenIn, tokenOut) {
|
|
|
554
567
|
const b = lc(args.route[2 * k + 2]);
|
|
555
568
|
const row = args.params[k];
|
|
556
569
|
const type = row[2];
|
|
557
|
-
if (a === b) return `step ${k + 1} trades a token for itself
|
|
570
|
+
if (a === b) return noted(`step ${k + 1} trades a token for itself`, "policy_refused");
|
|
558
571
|
if (type === 1n || type === 2n) {
|
|
559
|
-
if (CURVE_DENIED_POOLS[chainId]?.has(swap)) return `step ${k + 1} crosses ${args.route[2 * k + 1]}, a pool this module refuses (fake coin, drained in 2023, or over a dead venue)
|
|
572
|
+
if (CURVE_DENIED_POOLS[chainId]?.has(swap)) return noted(`step ${k + 1} crosses ${args.route[2 * k + 1]}, a pool this module refuses (fake coin, drained in 2023, or over a dead venue)`, "vault_mismatch");
|
|
560
573
|
const pool = index.get(swap);
|
|
561
|
-
if (!pool) return `step ${k + 1} crosses ${args.route[2 * k + 1]}, which is not in the pool allowlist
|
|
562
|
-
if (pools.has(swap)) return `the route crosses ${args.route[2 * k + 1]} twice
|
|
574
|
+
if (!pool) return noted(`step ${k + 1} crosses ${args.route[2 * k + 1]}, which is not in the pool allowlist`, "vault_mismatch");
|
|
575
|
+
if (pools.has(swap)) return noted(`the route crosses ${args.route[2 * k + 1]} twice`, "policy_refused");
|
|
563
576
|
pools.add(swap);
|
|
564
|
-
if (!pool.swapTypes.includes(type === 1n ? 1 : 2)) return `step ${k + 1} uses swap_type ${type}, which pool ${pool.name} does not allow
|
|
565
|
-
if (row[3] !== BigInt(pool.poolType)) return `step ${k + 1} names pool_type ${row[3]}, not pool ${pool.name}'s ${pool.poolType}
|
|
566
|
-
if (type === 2n && deployment.family === "lite4" && pool.poolType !== 10) return `step ${k + 1}: exchange_underlying is for stable-ng metapools on this router
|
|
567
|
-
if (type === 2n && !underlyingAllowed(pool, index)) return `step ${k + 1}: exchange_underlying on pool ${pool.name} needs its base pool in the allowlist
|
|
577
|
+
if (!pool.swapTypes.includes(type === 1n ? 1 : 2)) return noted(`step ${k + 1} uses swap_type ${type}, which pool ${pool.name} does not allow`, "policy_refused");
|
|
578
|
+
if (row[3] !== BigInt(pool.poolType)) return noted(`step ${k + 1} names pool_type ${row[3]}, not pool ${pool.name}'s ${pool.poolType}`, "policy_refused");
|
|
579
|
+
if (type === 2n && deployment.family === "lite4" && pool.poolType !== 10) return noted(`step ${k + 1}: exchange_underlying is for stable-ng metapools on this router`, "policy_refused");
|
|
580
|
+
if (type === 2n && !underlyingAllowed(pool, index)) return noted(`step ${k + 1}: exchange_underlying on pool ${pool.name} needs its base pool in the allowlist`, "policy_refused");
|
|
568
581
|
const list = type === 1n ? pool.coins : pool.underlying;
|
|
569
|
-
if (!list) return `step ${k + 1}: pool ${pool.name} lists no underlying coins
|
|
582
|
+
if (!list) return noted(`step ${k + 1}: pool ${pool.name} lists no underlying coins`, "policy_refused");
|
|
570
583
|
const i = row[0], j = row[1];
|
|
571
|
-
if (i >= BigInt(list.length) || j >= BigInt(list.length) || i === j) return `step ${k + 1}: coin indexes ${i}/${j} are not two coins of pool ${pool.name}
|
|
572
|
-
if (lc(list[Number(i.toString())]) !== a || lc(list[Number(j.toString())]) !== b) return `step ${k + 1}: pool ${pool.name}'s coins ${i}/${j} are not the hop's tokens
|
|
573
|
-
if (deployment.family === "ng5" && row[4] !== BigInt(list.length)) return `step ${k + 1} names ${row[4]} coins, not pool ${pool.name}'s ${list.length}
|
|
574
|
-
if (deployment.family === "lite4" && (a === eth || b === eth)) return `step ${k + 1}: this router does not pass the native coin to pools
|
|
575
|
-
if (STABLE_POOL_TYPES.has(pool.poolType) && (i > 127n || j > 127n)) return `step ${k + 1}: coin index out of range
|
|
584
|
+
if (i >= BigInt(list.length) || j >= BigInt(list.length) || i === j) return noted(`step ${k + 1}: coin indexes ${i}/${j} are not two coins of pool ${pool.name}`, "policy_refused");
|
|
585
|
+
if (lc(list[Number(i.toString())]) !== a || lc(list[Number(j.toString())]) !== b) return noted(`step ${k + 1}: pool ${pool.name}'s coins ${i}/${j} are not the hop's tokens`, "policy_refused");
|
|
586
|
+
if (deployment.family === "ng5" && row[4] !== BigInt(list.length)) return noted(`step ${k + 1} names ${row[4]} coins, not pool ${pool.name}'s ${list.length}`, "policy_refused");
|
|
587
|
+
if (deployment.family === "lite4" && (a === eth || b === eth)) return noted(`step ${k + 1}: this router does not pass the native coin to pools`, "policy_refused");
|
|
588
|
+
if (STABLE_POOL_TYPES.has(pool.poolType) && (i > 127n || j > 127n)) return noted(`step ${k + 1}: coin index out of range`, "policy_refused");
|
|
576
589
|
} else if (type === 8n) {
|
|
577
|
-
if (weth === zero) return "this chain's router has no wrapped native";
|
|
578
|
-
if (swap !== weth) return `step ${k + 1} wraps through ${args.route[2 * k + 1]}, not the pinned wrapped native
|
|
579
|
-
if (!(a === eth && b === weth || a === weth && b === eth)) return `step ${k + 1}: swap_type 8 is only native \u2194 the pinned wrapped native here
|
|
580
|
-
if (row.some((x, c) => c !== 2 && x !== 0n)) return `step ${k + 1}: a wrap carries no other swap params
|
|
581
|
-
if (++wraps > 1) return "the route wraps more than once";
|
|
590
|
+
if (weth === zero) return noted("this chain's router has no wrapped native", "policy_refused");
|
|
591
|
+
if (swap !== weth) return noted(`step ${k + 1} wraps through ${args.route[2 * k + 1]}, not the pinned wrapped native`, "vault_mismatch");
|
|
592
|
+
if (!(a === eth && b === weth || a === weth && b === eth)) return noted(`step ${k + 1}: swap_type 8 is only native \u2194 the pinned wrapped native here`, "policy_refused");
|
|
593
|
+
if (row.some((x, c) => c !== 2 && x !== 0n)) return noted(`step ${k + 1}: a wrap carries no other swap params`, "policy_refused");
|
|
594
|
+
if (++wraps > 1) return noted("the route wraps more than once", "policy_refused");
|
|
582
595
|
} else if (type === 9n) {
|
|
583
|
-
if (!v) return `swap_type 9 (ERC-4626) is not available on ${deployment.label}'s router ${deployment.version}
|
|
584
|
-
if (swap !== lc(v.vault)) return `step ${k + 1}: ERC-4626 hop through ${args.route[2 * k + 1]}, not the pinned vault
|
|
596
|
+
if (!v) return noted(`swap_type 9 (ERC-4626) is not available on ${deployment.label}'s router ${deployment.version}`, "policy_refused");
|
|
597
|
+
if (swap !== lc(v.vault)) return noted(`step ${k + 1}: ERC-4626 hop through ${args.route[2 * k + 1]}, not the pinned vault`, "vault_mismatch");
|
|
585
598
|
const deposit = row[0] === 0n && a === lc(v.asset) && b === lc(v.vault);
|
|
586
599
|
const redeem = row[0] === 1n && a === lc(v.vault) && b === lc(v.asset);
|
|
587
|
-
if (!deposit && !redeem) return `step ${k + 1}: the ERC-4626 hop is not a deposit of the vault's asset or a redeem of its shares
|
|
588
|
-
if (row.some((x, c) => c > 0 && c !== 2 && x !== 0n)) return `step ${k + 1}: an ERC-4626 hop carries no other swap params
|
|
589
|
-
if (++vaults > 1) return "the route crosses the vault more than once";
|
|
600
|
+
if (!deposit && !redeem) return noted(`step ${k + 1}: the ERC-4626 hop is not a deposit of the vault's asset or a redeem of its shares`, "policy_refused");
|
|
601
|
+
if (row.some((x, c) => c > 0 && c !== 2 && x !== 0n)) return noted(`step ${k + 1}: an ERC-4626 hop carries no other swap params`, "policy_refused");
|
|
602
|
+
if (++vaults > 1) return noted("the route crosses the vault more than once", "policy_refused");
|
|
590
603
|
} else {
|
|
591
|
-
return `step ${k + 1} uses swap_type ${type}, which this module never builds
|
|
604
|
+
return noted(`step ${k + 1} uses swap_type ${type}, which this module never builds`, "policy_refused");
|
|
592
605
|
}
|
|
593
606
|
}
|
|
594
|
-
if (pools.size > MAX_ROUTE_POOLS) return `the route crosses ${pools.size} pools, more than ${MAX_ROUTE_POOLS}
|
|
595
|
-
if (pools.size === 0 && vaults === 0) return "the route crosses no pool";
|
|
607
|
+
if (pools.size > MAX_ROUTE_POOLS) return noted(`the route crosses ${pools.size} pools, more than ${MAX_ROUTE_POOLS}`, "policy_refused");
|
|
608
|
+
if (pools.size === 0 && vaults === 0) return noted("the route crosses no pool", "policy_refused");
|
|
596
609
|
return null;
|
|
597
610
|
}
|
|
598
611
|
|
|
599
612
|
// src/rpc.ts
|
|
613
|
+
var import_errors2 = require("@anywalletkit/protocol-swap2-core/errors");
|
|
600
614
|
var import_util2 = require("@anywalletkit/protocol-swap2-core/util");
|
|
601
615
|
var RPC_BATCH_MAX = 10;
|
|
602
616
|
var RPC_RATE_LIMIT_PAUSE_MS = 1e3;
|
|
@@ -622,18 +636,18 @@ function rpcClient(rpcUrls) {
|
|
|
622
636
|
body: JSON.stringify(calls.map((c, id) => ({ jsonrpc: "2.0", id, ...c }))),
|
|
623
637
|
signal: signal ?? (0, import_util2.timeoutSignal)(12e3)
|
|
624
638
|
});
|
|
625
|
-
if (!res.ok) throw Object.assign(new Error(`HTTP ${res.status}`), { limited: res.status === 429 });
|
|
639
|
+
if (!res.ok) throw (0, import_errors2.withReason)(Object.assign(new Error(`HTTP ${res.status}`), { limited: res.status === 429 }), (0, import_errors2.httpRefusal)(res.status, res.headers?.get?.("retry-after")) ?? { reason: "upstream_unavailable", httpStatus: res.status });
|
|
626
640
|
const body = await res.json();
|
|
627
|
-
if (!Array.isArray(body)) throw Object.assign(new Error(body.error?.message ?? "batch refused"), { limited: isRateLimit(body.error) });
|
|
641
|
+
if (!Array.isArray(body)) throw (0, import_errors2.withReason)(Object.assign(new Error(body.error?.message ?? "batch refused"), { limited: isRateLimit(body.error) }), isRateLimit(body.error) ? "rate_limited" : "upstream_unavailable");
|
|
628
642
|
const out = calls.map(() => null);
|
|
629
643
|
const answered = /* @__PURE__ */ new Set();
|
|
630
644
|
for (const r of body) {
|
|
631
645
|
if (typeof r.id !== "number" || r.id < 0 || r.id >= out.length) continue;
|
|
632
|
-
if (r.error !== void 0 && r.error !== null && !isExecutionError(r.error)) throw Object.assign(new Error(errorText(r.error)), { limited: isRateLimit(r.error) });
|
|
646
|
+
if (r.error !== void 0 && r.error !== null && !isExecutionError(r.error)) throw (0, import_errors2.withReason)(Object.assign(new Error(errorText(r.error)), { limited: isRateLimit(r.error) }), isRateLimit(r.error) ? "rate_limited" : "upstream_unavailable");
|
|
633
647
|
answered.add(r.id);
|
|
634
648
|
if (r.error === void 0 || r.error === null) out[r.id] = r.result ?? null;
|
|
635
649
|
}
|
|
636
|
-
if (answered.size !== calls.length) throw
|
|
650
|
+
if (answered.size !== calls.length) throw (0, import_errors2.refusal)("upstream_unavailable", `answered ${answered.size} of ${calls.length} calls`);
|
|
637
651
|
return out;
|
|
638
652
|
}
|
|
639
653
|
async function batch(chainId, calls, signal) {
|
|
@@ -651,7 +665,9 @@ function rpcClient(rpcUrls) {
|
|
|
651
665
|
}
|
|
652
666
|
}
|
|
653
667
|
}
|
|
654
|
-
|
|
668
|
+
const error = new Error(`curve: no RPC answered on chain ${chainId} (${lastErr instanceof Error ? lastErr.message : String(lastErr)})`);
|
|
669
|
+
if (lastErr === void 0 || signal?.aborted) throw error;
|
|
670
|
+
throw (0, import_errors2.withReason)(error, (0, import_errors2.refusalOf)(lastErr) ?? "upstream_unavailable");
|
|
655
671
|
}
|
|
656
672
|
return async function rpc(chainId, calls, signal) {
|
|
657
673
|
if (calls.length <= RPC_BATCH_MAX) return batch(chainId, calls, signal);
|
|
@@ -711,6 +727,7 @@ function curveIntentChain(intentId) {
|
|
|
711
727
|
var import_viem3 = require("viem");
|
|
712
728
|
var import_util4 = require("@anywalletkit/protocol-swap2-core/util");
|
|
713
729
|
var import_validate = require("@anywalletkit/protocol-swap2-core/validate");
|
|
730
|
+
var import_errors3 = require("@anywalletkit/protocol-swap2-core/errors");
|
|
714
731
|
var lc2 = (a) => a.toLowerCase();
|
|
715
732
|
var isAddress = (a) => /^0x[0-9a-fA-F]{40}$/.test(a);
|
|
716
733
|
var own = (o, k) => Object.prototype.hasOwnProperty.call(o, k) ? o[k] : void 0;
|
|
@@ -720,19 +737,19 @@ function curveQuoteCheck(tables = {}) {
|
|
|
720
737
|
return function verify(params, quote) {
|
|
721
738
|
const chainId = chainIdOf(params.sourceChain);
|
|
722
739
|
const d2 = deploymentOf(deployments, chainId);
|
|
723
|
-
if (!d2 || params.destChain !== params.sourceChain) return "not a same-chain swap on a pinned Curve router";
|
|
740
|
+
if (!d2 || params.destChain !== params.sourceChain) return noted("not a same-chain swap on a pinned Curve router", "unsupported_pair");
|
|
724
741
|
const tx = quote.txRequest;
|
|
725
|
-
if (!tx?.data || !tx.to || tx.serializedTx !== void 0) return "the quote carries no router call";
|
|
726
|
-
if (!isAddress(tx.to) || lc2(tx.to) !== lc2(d2.router)) return `the call targets ${tx.to}, not the Curve router
|
|
727
|
-
if (tx.chainId !== void 0 && tx.chainId !== chainId) return `the call is for chain ${tx.chainId}
|
|
728
|
-
if (curveIntentChain(quote.intentId) !== chainId) return `the quote names ${String(quote.intentId).slice(0, 40)}, not a curve:${chainId} swap
|
|
742
|
+
if (!tx?.data || !tx.to || tx.serializedTx !== void 0) return noted("the quote carries no router call", "policy_refused");
|
|
743
|
+
if (!isAddress(tx.to) || lc2(tx.to) !== lc2(d2.router)) return noted(`the call targets ${tx.to}, not the Curve router`, "vault_mismatch");
|
|
744
|
+
if (tx.chainId !== void 0 && tx.chainId !== chainId) return noted(`the call is for chain ${tx.chainId}`, "policy_refused");
|
|
745
|
+
if (curveIntentChain(quote.intentId) !== chainId) return noted(`the quote names ${String(quote.intentId).slice(0, 40)}, not a curve:${chainId} swap`, "policy_refused");
|
|
729
746
|
const src = (0, import_util4.sourceAssetOf)(params);
|
|
730
|
-
if (!src) return `unknown source token ${params.sourceToken}
|
|
747
|
+
if (!src) return noted(`unknown source token ${params.sourceToken}`, "unsupported_pair");
|
|
731
748
|
const nativeIn = !src.contract;
|
|
732
749
|
const nativeOut = /^0x0+$/.test(params.destToken);
|
|
733
|
-
if (!nativeOut && !isAddress(params.destToken)) return "destination token must be an EVM address";
|
|
734
|
-
if (!isAddress(params.destAddress)) return "recipient must be an EVM address";
|
|
735
|
-
if (!nativeIn && lc2(src.contract) === lc2(d2.eth) || !nativeOut && lc2(params.destToken) === lc2(d2.eth)) return "the router's native sentinel is not a token";
|
|
750
|
+
if (!nativeOut && !isAddress(params.destToken)) return noted("destination token must be an EVM address", "unsupported_pair");
|
|
751
|
+
if (!isAddress(params.destAddress)) return noted("recipient must be an EVM address", "recipient_mismatch");
|
|
752
|
+
if (!nativeIn && lc2(src.contract) === lc2(d2.eth) || !nativeOut && lc2(params.destToken) === lc2(d2.eth)) return noted("the router's native sentinel is not a token", "unsupported_pair");
|
|
736
753
|
const tokenIn = nativeIn ? d2.eth : src.contract;
|
|
737
754
|
const tokenOut = nativeOut ? d2.eth : params.destToken;
|
|
738
755
|
let amountIn, minOut, value;
|
|
@@ -741,20 +758,20 @@ function curveQuoteCheck(tables = {}) {
|
|
|
741
758
|
minOut = BigInt(quote.minDestAmount);
|
|
742
759
|
value = BigInt(tx.value ?? "0");
|
|
743
760
|
} catch {
|
|
744
|
-
return "the request does not name amounts";
|
|
761
|
+
return noted("the request does not name amounts", "policy_refused");
|
|
745
762
|
}
|
|
746
763
|
if (nativeIn ? value !== amountIn || tx.approvalAddress !== void 0 : value !== 0n || !tx.approvalAddress || !isAddress(tx.approvalAddress) || lc2(tx.approvalAddress) !== lc2(d2.router)) {
|
|
747
|
-
return nativeIn ? "a native input is paid as exactly the input in value, with no approval" : "a token input attaches no value and approves exactly the Curve router";
|
|
764
|
+
return noted(nativeIn ? "a native input is paid as exactly the input in value, with no approval" : "a token input attaches no value and approves exactly the Curve router", "policy_refused");
|
|
748
765
|
}
|
|
749
766
|
const call = decodeExchange(d2.family, tx.data);
|
|
750
|
-
if (!call) return "the router call is not exchange(route, params, amount, min, receiver)";
|
|
751
|
-
if (call.selector !== EXCHANGE_SELECTOR[d2.family]) return `selector ${call.selector} is not the explicit-receiver exchange ${EXCHANGE_SELECTOR[d2.family]}
|
|
752
|
-
if (!call.receiver || lc2(call.receiver) !== lc2(params.destAddress)) return `the swap pays ${call.receiver ?? "the sender"}, not the recipient
|
|
753
|
-
if (call.pools && call.pools.some((p) => lc2(p) !== lc2(ZERO_ADDRESS))) return "the call names zap pools this module never uses";
|
|
754
|
-
if (call.amount !== amountIn) return `the swap sells ${call.amount}, not the ${amountIn} input
|
|
755
|
-
if (call.minDy <= 0n) return "the swap has no minimum (min_dy 0)";
|
|
756
|
-
if (call.minDy !== minOut) return `the swap's minimum ${call.minDy} is not the quote's ${minOut}
|
|
757
|
-
if (quote.providerData?.minOut !== void 0 && quote.providerData.minOut !== minOut.toString()) return "the quote's facts disagree on the minimum";
|
|
767
|
+
if (!call) return noted("the router call is not exchange(route, params, amount, min, receiver)", "policy_refused");
|
|
768
|
+
if (call.selector !== EXCHANGE_SELECTOR[d2.family]) return noted(`selector ${call.selector} is not the explicit-receiver exchange ${EXCHANGE_SELECTOR[d2.family]}`, "policy_refused");
|
|
769
|
+
if (!call.receiver || lc2(call.receiver) !== lc2(params.destAddress)) return noted(`the swap pays ${call.receiver ?? "the sender"}, not the recipient`, "recipient_mismatch");
|
|
770
|
+
if (call.pools && call.pools.some((p) => lc2(p) !== lc2(ZERO_ADDRESS))) return noted("the call names zap pools this module never uses", "policy_refused");
|
|
771
|
+
if (call.amount !== amountIn) return noted(`the swap sells ${call.amount}, not the ${amountIn} input`, "policy_refused");
|
|
772
|
+
if (call.minDy <= 0n) return noted("the swap has no minimum (min_dy 0)", "price_check_failed");
|
|
773
|
+
if (call.minDy !== minOut) return noted(`the swap's minimum ${call.minDy} is not the quote's ${minOut}`, "price_check_failed");
|
|
774
|
+
if (quote.providerData?.minOut !== void 0 && quote.providerData.minOut !== minOut.toString()) return noted("the quote's facts disagree on the minimum", "price_check_failed");
|
|
758
775
|
return checkRoute(chainId, d2, own(pools, chainId), call, tokenIn, tokenOut);
|
|
759
776
|
};
|
|
760
777
|
}
|
|
@@ -762,7 +779,9 @@ var curveVerifyQuote = curveQuoteCheck();
|
|
|
762
779
|
var curvePins = Object.freeze({
|
|
763
780
|
callTargets: (chain) => routerFor(CURVE_DEPLOYMENTS, chain),
|
|
764
781
|
spenders: (chain) => routerFor(CURVE_DEPLOYMENTS, chain),
|
|
765
|
-
verifyQuote: curveVerifyQuote
|
|
782
|
+
verifyQuote: curveVerifyQuote,
|
|
783
|
+
// The reason of each text verifyQuote returned (remoteProvider puts it on its refusal).
|
|
784
|
+
reasonOf: notes.reasonOf
|
|
766
785
|
});
|
|
767
786
|
function routerFor(deployments, chain) {
|
|
768
787
|
const d2 = deploymentOf(deployments, chainIdOf(chain));
|
|
@@ -779,21 +798,21 @@ function curveOnChain(opts) {
|
|
|
779
798
|
if (!p) {
|
|
780
799
|
const d2 = deploymentOf(deployments, chainId);
|
|
781
800
|
p = (async () => {
|
|
782
|
-
if (!d2) throw
|
|
801
|
+
if (!d2) throw (0, import_errors3.refusal)("unsupported_pair", `curve: no pinned router on chain ${chainId}`);
|
|
783
802
|
const [code, version, listed] = await rpc(chainId, [
|
|
784
803
|
{ method: "eth_getCode", params: [d2.router, "latest"] },
|
|
785
804
|
ethCall(d2.router, (0, import_viem3.encodeFunctionData)({ abi: VERSION_ABI, functionName: "version" })),
|
|
786
805
|
ethCall(d2.addressProvider, (0, import_viem3.encodeFunctionData)({ abi: ADDRESS_PROVIDER_ABI, functionName: "get_address", args: [2n] }))
|
|
787
806
|
]);
|
|
788
807
|
if (typeof code !== "string" || code.length <= 2 || lc2((0, import_viem3.keccak256)(code)) !== lc2(d2.codeHash)) {
|
|
789
|
-
throw
|
|
808
|
+
throw (0, import_errors3.refusal)("vault_mismatch", `curve: the router on chain ${chainId} is not the pinned code (code hash mismatch) \u2014 refusing`);
|
|
790
809
|
}
|
|
791
810
|
let v = "";
|
|
792
811
|
try {
|
|
793
812
|
v = typeof version === "string" ? (0, import_viem3.decodeFunctionResult)({ abi: VERSION_ABI, functionName: "version", data: version }) : "";
|
|
794
813
|
} catch {
|
|
795
814
|
}
|
|
796
|
-
if (v !== d2.version) throw
|
|
815
|
+
if (v !== d2.version) throw (0, import_errors3.refusal)("vault_mismatch", `curve: the router on chain ${chainId} reports version ${v || "none"}, not ${d2.version} \u2014 refusing`);
|
|
797
816
|
const ap = wordAddress(listed);
|
|
798
817
|
if (ap && ap !== lc2(d2.addressProviderRouter) && ap !== lc2(d2.router)) {
|
|
799
818
|
warn(`curve: the AddressProvider on ${d2.label} lists router ${ap} (id 2); this module keeps the pinned ${d2.router}`);
|
|
@@ -811,17 +830,17 @@ function curveOnChain(opts) {
|
|
|
811
830
|
async function inspectSealed(quote, tx, probe) {
|
|
812
831
|
const chainId = curveIntentChain(quote.intentId);
|
|
813
832
|
const d2 = chainId === null ? void 0 : deploymentOf(deployments, chainId);
|
|
814
|
-
if (chainId === null || !d2) return "the quote is not a Curve swap on a pinned chain";
|
|
833
|
+
if (chainId === null || !d2) return noted("the quote is not a Curve swap on a pinned chain", "policy_refused");
|
|
815
834
|
const quoted = quote.txRequest;
|
|
816
|
-
if (!quoted?.data || !quoted.to || !tx.data) return "the quote or the transaction carries no router call";
|
|
817
|
-
if (tx.data.length !== quoted.data.length || lc2(tx.data) !== lc2(quoted.data)) return "the transaction is not the quoted router call";
|
|
818
|
-
if (!isAddress(tx.to) || lc2(tx.to) !== lc2(d2.router) || lc2(quoted.to) !== lc2(d2.router)) return `the transaction calls ${tx.to}, not the Curve router
|
|
819
|
-
if ((tx.value ?? "0") !== (quoted.value ?? "0")) return "the transaction's value is not the quote's";
|
|
820
|
-
if (tx.chainId !== void 0 && tx.chainId !== chainId || quoted.chainId !== void 0 && quoted.chainId !== chainId) return `the transaction is not for chain ${chainId}
|
|
821
|
-
if ((tx.approvalAddress ?? "").toLowerCase() !== (quoted.approvalAddress ?? "").toLowerCase()) return "the transaction's approval is not the quote's";
|
|
835
|
+
if (!quoted?.data || !quoted.to || !tx.data) return noted("the quote or the transaction carries no router call", "policy_refused");
|
|
836
|
+
if (tx.data.length !== quoted.data.length || lc2(tx.data) !== lc2(quoted.data)) return noted("the transaction is not the quoted router call", "policy_refused");
|
|
837
|
+
if (!isAddress(tx.to) || lc2(tx.to) !== lc2(d2.router) || lc2(quoted.to) !== lc2(d2.router)) return noted(`the transaction calls ${tx.to}, not the Curve router`, "vault_mismatch");
|
|
838
|
+
if ((tx.value ?? "0") !== (quoted.value ?? "0")) return noted("the transaction's value is not the quote's", "policy_refused");
|
|
839
|
+
if (tx.chainId !== void 0 && tx.chainId !== chainId || quoted.chainId !== void 0 && quoted.chainId !== chainId) return noted(`the transaction is not for chain ${chainId}`, "policy_refused");
|
|
840
|
+
if ((tx.approvalAddress ?? "").toLowerCase() !== (quoted.approvalAddress ?? "").toLowerCase()) return noted("the transaction's approval is not the quote's", "vault_mismatch");
|
|
822
841
|
const call = decodeExchange(d2.family, quoted.data);
|
|
823
|
-
if (!call || call.selector !== EXCHANGE_SELECTOR[d2.family]) return "the quoted call is not the explicit-receiver exchange";
|
|
824
|
-
if (call.minDy <= 0n) return "the swap has no minimum (min_dy 0)";
|
|
842
|
+
if (!call || call.selector !== EXCHANGE_SELECTOR[d2.family]) return noted("the quoted call is not the explicit-receiver exchange", "policy_refused");
|
|
843
|
+
if (call.minDy <= 0n) return noted("the swap has no minimum (min_dy 0)", "price_check_failed");
|
|
825
844
|
const last = lastToken(call);
|
|
826
845
|
const reason = checkRoute(chainId, d2, own(pools, chainId), call, call.route[0], last);
|
|
827
846
|
if (reason) return reason;
|
|
@@ -837,8 +856,8 @@ function curveOnChain(opts) {
|
|
|
837
856
|
const poolReason = await poolsReason(chainId, hops, res.slice(1, 1 + reads.length));
|
|
838
857
|
if (poolReason) return poolReason;
|
|
839
858
|
const out = wordUint(res[0]);
|
|
840
|
-
if (out === null) return "the router no longer quotes this route";
|
|
841
|
-
if (out < call.minDy) return `price moved \u2014 the pools now pay ${out}, under your minimum ${call.minDy}
|
|
859
|
+
if (out === null) return noted("the router no longer quotes this route", "simulation_failed");
|
|
860
|
+
if (out < call.minDy) return noted(`price moved \u2014 the pools now pay ${out}, under your minimum ${call.minDy}`, "price_check_failed");
|
|
842
861
|
return { out, probeOut: probe && probeIn > 0n ? wordUint(res[probeAt]) : null, amount: call.amount, minDy: call.minDy };
|
|
843
862
|
}
|
|
844
863
|
function poolCalls(list) {
|
|
@@ -852,19 +871,19 @@ function curveOnChain(opts) {
|
|
|
852
871
|
const retry = [];
|
|
853
872
|
for (const pool of list) {
|
|
854
873
|
const code = res[at++];
|
|
855
|
-
if (typeof code !== "string") return `could not read pool ${pool.name} (${pool.address}) over RPC
|
|
856
|
-
if (code.length <= 2 || lc2((0, import_viem3.keccak256)(code)) !== lc2(pool.codeHash)) return `pool ${pool.name} (${pool.address}) is not the pinned code
|
|
874
|
+
if (typeof code !== "string") return noted(`could not read pool ${pool.name} (${pool.address}) over RPC`, "upstream_unavailable");
|
|
875
|
+
if (code.length <= 2 || lc2((0, import_viem3.keccak256)(code)) !== lc2(pool.codeHash)) return noted(`pool ${pool.name} (${pool.address}) is not the pinned code`, "vault_mismatch");
|
|
857
876
|
for (let k = 0; k < pool.coins.length; k++) {
|
|
858
877
|
const coin = wordAddress(res[at++]);
|
|
859
878
|
if (coin === null) retry.push({ pool, k });
|
|
860
|
-
else if (coin !== lc2(pool.coins[k])) return `pool ${pool.name} now reports coin ${k} = ${coin}, not the pinned ${pool.coins[k]}
|
|
879
|
+
else if (coin !== lc2(pool.coins[k])) return noted(`pool ${pool.name} now reports coin ${k} = ${coin}, not the pinned ${pool.coins[k]}`, "vault_mismatch");
|
|
861
880
|
}
|
|
862
881
|
}
|
|
863
882
|
if (retry.length) {
|
|
864
883
|
const again = await rpc(chainId, retry.map(({ pool, k }) => ethCall(pool.address, `${COINS_INT}${k.toString(16).padStart(64, "0")}`)));
|
|
865
884
|
for (const [n, { pool, k }] of retry.entries()) {
|
|
866
885
|
const coin = wordAddress(again[n]);
|
|
867
|
-
if (coin !== lc2(pool.coins[k])) return `pool ${pool.name} does not report its pinned coin ${k}
|
|
886
|
+
if (coin !== lc2(pool.coins[k])) return noted(`pool ${pool.name} does not report its pinned coin ${k}`, "vault_mismatch");
|
|
868
887
|
}
|
|
869
888
|
}
|
|
870
889
|
return null;
|
|
@@ -912,46 +931,47 @@ function curveProvider(config) {
|
|
|
912
931
|
return from.chain === to.chain && chainIdOf2(from.chain) !== null;
|
|
913
932
|
},
|
|
914
933
|
async getQuote(params, signal) {
|
|
915
|
-
const refuse = (why) => {
|
|
916
|
-
params.onRefusal?.(`curve: ${why}`);
|
|
934
|
+
const refuse = (why, info) => {
|
|
935
|
+
if (info === void 0) params.onRefusal?.(`curve: ${why}`);
|
|
936
|
+
else params.onRefusal?.(`curve: ${why}`, typeof info === "string" ? { reason: info } : info);
|
|
917
937
|
return null;
|
|
918
938
|
};
|
|
919
|
-
if (params.flex) return refuse("amount-flexible deposits are not supported");
|
|
920
|
-
if (params.sourceChain !== params.destChain) return refuse("same-chain swaps only");
|
|
939
|
+
if (params.flex) return refuse("amount-flexible deposits are not supported", "unsupported_pair");
|
|
940
|
+
if (params.sourceChain !== params.destChain) return refuse("same-chain swaps only", "unsupported_pair");
|
|
921
941
|
const chainId = chainIdOf2(params.sourceChain);
|
|
922
|
-
if (chainId === null) return refuse(`no Curve router, pool allowlist or RPC for ${params.sourceChain}
|
|
942
|
+
if (chainId === null) return refuse(`no Curve router, pool allowlist or RPC for ${params.sourceChain}`, "unsupported_pair");
|
|
923
943
|
const d2 = deploymentOf(deployments, chainId);
|
|
924
944
|
const src = (0, import_util5.sourceAssetOf)(params);
|
|
925
|
-
if (!src) return refuse(`unknown source token ${params.sourceToken}
|
|
945
|
+
if (!src) return refuse(`unknown source token ${params.sourceToken}`, "unsupported_pair");
|
|
926
946
|
const nativeIn = !src.contract;
|
|
927
947
|
const nativeOut = /^0x0+$/.test(params.destToken);
|
|
928
|
-
if (!nativeOut && !/^0x[0-9a-fA-F]{40}$/.test(params.destToken)) return refuse("destination token must be an EVM address");
|
|
929
|
-
if (!/^0x[0-9a-fA-F]{40}$/.test(params.destAddress)) return refuse("recipient must be an EVM address");
|
|
948
|
+
if (!nativeOut && !/^0x[0-9a-fA-F]{40}$/.test(params.destToken)) return refuse("destination token must be an EVM address", "unsupported_pair");
|
|
949
|
+
if (!/^0x[0-9a-fA-F]{40}$/.test(params.destAddress)) return refuse("recipient must be an EVM address", "recipient_mismatch");
|
|
930
950
|
let amountIn;
|
|
931
951
|
try {
|
|
932
952
|
amountIn = BigInt(params.amountIn);
|
|
933
953
|
} catch {
|
|
934
954
|
return refuse("amount is not an integer");
|
|
935
955
|
}
|
|
936
|
-
if (amountIn <= 0n) return refuse("amount must be positive");
|
|
956
|
+
if (amountIn <= 0n) return refuse("amount must be positive", "amount_too_small");
|
|
937
957
|
if (params.slippageBps !== void 0 && (!Number.isInteger(params.slippageBps) || params.slippageBps < 0 || params.slippageBps >= 1e4)) return refuse("slippage must be 0..9999 bps");
|
|
938
|
-
if (!nativeIn && src.contract.toLowerCase() === d2.eth.toLowerCase()) return refuse("the native coin is named by the catalog, not by the router's 0xEeee sentinel");
|
|
939
|
-
if (!nativeOut && params.destToken.toLowerCase() === d2.eth.toLowerCase()) return refuse("name a native output with the zero address, not the router's 0xEeee sentinel");
|
|
958
|
+
if (!nativeIn && src.contract.toLowerCase() === d2.eth.toLowerCase()) return refuse("the native coin is named by the catalog, not by the router's 0xEeee sentinel", "unsupported_pair");
|
|
959
|
+
if (!nativeOut && params.destToken.toLowerCase() === d2.eth.toLowerCase()) return refuse("name a native output with the zero address, not the router's 0xEeee sentinel", "unsupported_pair");
|
|
940
960
|
const tokenIn = (0, import_viem4.getAddress)(nativeIn ? d2.eth : src.contract);
|
|
941
961
|
const tokenOut = (0, import_viem4.getAddress)(nativeOut ? d2.eth : params.destToken);
|
|
942
|
-
if (tokenIn === tokenOut) return refuse("same token in and out");
|
|
962
|
+
if (tokenIn === tokenOut) return refuse("same token in and out", "unsupported_pair");
|
|
943
963
|
const wrapPair = [(0, import_viem4.getAddress)(d2.eth), (0, import_viem4.getAddress)(d2.weth)];
|
|
944
|
-
if (wrapPair.includes(tokenIn) && wrapPair.includes(tokenOut)) return refuse("that is a wrap/unwrap, not a swap");
|
|
964
|
+
if (wrapPair.includes(tokenIn) && wrapPair.includes(tokenOut)) return refuse("that is a wrap/unwrap, not a swap", "unsupported_pair");
|
|
945
965
|
try {
|
|
946
966
|
await chain.verifyDeployment(chainId);
|
|
947
967
|
const { routes, truncated } = findRoutes(chainId, d2, tableOf(chainId), tokenIn, tokenOut);
|
|
948
|
-
if (!routes.length) return refuse("no allowlisted Curve pool route for this pair");
|
|
968
|
+
if (!routes.length) return refuse("no allowlisted Curve pool route for this pair", "no_route");
|
|
949
969
|
const outs = await price(chainId, d2, routes, amountIn, signal);
|
|
950
970
|
let best = null;
|
|
951
971
|
outs.forEach((out2, i) => {
|
|
952
972
|
if (out2 !== null && out2 > 0n && (!best || out2 > best.out)) best = { route: routes[i], out: out2 };
|
|
953
973
|
});
|
|
954
|
-
if (!best) return refuse("no allowlisted Curve pool quotes this pair and amount");
|
|
974
|
+
if (!best) return refuse("no allowlisted Curve pool quotes this pair and amount", "no_route");
|
|
955
975
|
const { route, out } = best;
|
|
956
976
|
let priceImpactBps;
|
|
957
977
|
const probeIn = amountIn / 1000n;
|
|
@@ -964,9 +984,9 @@ function curveProvider(config) {
|
|
|
964
984
|
}
|
|
965
985
|
const bps = params.slippageBps ?? fixedSlippage ?? curveAutoSlippageBps(params, priceImpactBps);
|
|
966
986
|
const minOut = out * BigInt(1e4 - bps) / 10000n;
|
|
967
|
-
if (minOut <= 0n) return refuse("the minimum rounds to zero for this amount");
|
|
987
|
+
if (minOut <= 0n) return refuse("the minimum rounds to zero for this amount", "amount_too_small");
|
|
968
988
|
if (params.minAmountOut !== void 0 && minOut < BigInt(params.minAmountOut)) {
|
|
969
|
-
return refuse(`floor breach (min=${minOut} < floor=${params.minAmountOut})
|
|
989
|
+
return refuse(`floor breach (min=${minOut} < floor=${params.minAmountOut})`, "price_check_failed");
|
|
970
990
|
}
|
|
971
991
|
const args = routeArgs(d2.family, route);
|
|
972
992
|
const recipient = (0, import_viem4.getAddress)(params.destAddress);
|
|
@@ -1008,10 +1028,10 @@ function curveProvider(config) {
|
|
|
1008
1028
|
}
|
|
1009
1029
|
};
|
|
1010
1030
|
const why = selfCheck(params, quote);
|
|
1011
|
-
if (why) return refuse(`built a call its own check refuses (${why})
|
|
1031
|
+
if (why) return refuse(`built a call its own check refuses (${why})`, notes.reasonOf(why) ?? "policy_refused");
|
|
1012
1032
|
return quote;
|
|
1013
1033
|
} catch (e) {
|
|
1014
|
-
return refuse(e instanceof Error ? e.message.replace(/^curve: /, "") : String(e));
|
|
1034
|
+
return refuse(e instanceof Error ? e.message.replace(/^curve: /, "") : String(e), (0, import_errors4.refusalOf)(e));
|
|
1015
1035
|
}
|
|
1016
1036
|
},
|
|
1017
1037
|
/**
|
|
@@ -1021,11 +1041,11 @@ function curveProvider(config) {
|
|
|
1021
1041
|
*/
|
|
1022
1042
|
async preflightDeposit(quote, tx) {
|
|
1023
1043
|
const reason = await chain.checkSealed(quote, tx);
|
|
1024
|
-
if (reason) throw new Error(`curve: ${reason}. Nothing was sent; refresh the quote.`);
|
|
1044
|
+
if (reason) throw tagged(new Error(`curve: ${reason}. Nothing was sent; refresh the quote.`), notes.reasonOf(reason));
|
|
1025
1045
|
},
|
|
1026
1046
|
async buildDeposit(quote) {
|
|
1027
1047
|
const tx = quote.txRequest;
|
|
1028
|
-
if (!tx?.to || !tx.data) throw
|
|
1048
|
+
if (!tx?.to || !tx.data) throw (0, import_errors4.refusal)("policy_refused", "swap2: Curve quote has no transaction request");
|
|
1029
1049
|
return {
|
|
1030
1050
|
chain: "",
|
|
1031
1051
|
to: tx.to,
|