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