@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.
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  CURVE_POOLS,
3
3
  curveProvider
4
- } from "./chunk-IFREGXMV.js";
4
+ } from "./chunk-NW2OLRIK.js";
5
5
  import {
6
6
  CURVE_DEPLOYMENTS,
7
7
  deploymentOf
@@ -35,4 +35,4 @@ var CurveSwap2Protocol = class extends SingleProviderSwap2 {
35
35
  export {
36
36
  CurveSwap2Protocol
37
37
  };
38
- //# sourceMappingURL=chunk-JCWXGKPJ.js.map
38
+ //# sourceMappingURL=chunk-7AXQMAHJ.js.map
@@ -20,6 +20,7 @@ import {
20
20
  // src/provider.ts
21
21
  import { getAddress as getAddress2 } from "viem";
22
22
  import { evmRef, isEvm, sourceAssetOf as sourceAssetOf3 } from "@anywalletkit/protocol-swap2-core/util";
23
+ import { refusal as refusal3, refusalOf as refusalOf3 } from "@anywalletkit/protocol-swap2-core/errors";
23
24
 
24
25
  // src/pools/1.ts
25
26
  var POOLS_1 = {
@@ -179,6 +180,18 @@ var CURVE_POOLS = Object.freeze({
179
180
  // src/routes.ts
180
181
  import { getAddress } from "viem";
181
182
  import { ccAssetByToken } from "@anywalletkit/swap2-assets";
183
+
184
+ // src/reasons.ts
185
+ import { refusalNotes, withReason } from "@anywalletkit/protocol-swap2-core/errors";
186
+ var notes = refusalNotes(256);
187
+ function noted(text, info) {
188
+ return info === void 0 ? text : notes.note(text, info);
189
+ }
190
+ function tagged(error, info) {
191
+ return info === void 0 ? error : withReason(error, info);
192
+ }
193
+
194
+ // src/routes.ts
182
195
  var MAX_QUOTE_CANDIDATES = 30;
183
196
  var MAX_EXPLORED = 2e4;
184
197
  var lc = (a) => a.toLowerCase();
@@ -329,7 +342,7 @@ function routePools(args) {
329
342
  }
330
343
  function checkRoute(chainId, deployment, table, args, tokenIn, tokenOut) {
331
344
  const cols = deployment.family === "ng5" ? 5 : 4;
332
- 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]`;
345
+ 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");
333
346
  const index = poolIndex(chainId, deployment, table);
334
347
  const zero = lc(ZERO_ADDRESS);
335
348
  const eth = lc(deployment.eth);
@@ -337,14 +350,14 @@ function checkRoute(chainId, deployment, table, args, tokenIn, tokenOut) {
337
350
  const v = erc4626Of(deployment);
338
351
  let steps = 0;
339
352
  while (steps < 5 && lc(args.route[2 * steps + 1]) !== zero) steps++;
340
- if (steps === 0) return "the route has no step";
341
- if (steps > MAX_ROUTE_STEPS) return `the route has ${steps} steps, more than ${MAX_ROUTE_STEPS}`;
342
- for (let k = 2 * steps + 1; k < 11; k++) if (lc(args.route[k]) !== zero) return "the route names addresses after its end";
343
- 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";
344
- if (lc(args.route[0]) !== lc(tokenIn)) return `the route starts at ${args.route[0]}, not the source token`;
345
- if (lc(args.route[2 * steps]) !== lc(tokenOut)) return `the route ends at ${args.route[2 * steps]}, not the destination token`;
353
+ if (steps === 0) return noted("the route has no step", "policy_refused");
354
+ if (steps > MAX_ROUTE_STEPS) return noted(`the route has ${steps} steps, more than ${MAX_ROUTE_STEPS}`, "policy_refused");
355
+ 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");
356
+ 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");
357
+ if (lc(args.route[0]) !== lc(tokenIn)) return noted(`the route starts at ${args.route[0]}, not the source token`, "policy_refused");
358
+ 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");
346
359
  for (let k = 1; k < steps; k++) {
347
- 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`;
360
+ 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");
348
361
  }
349
362
  const pools = /* @__PURE__ */ new Set();
350
363
  let wraps = 0;
@@ -355,49 +368,50 @@ function checkRoute(chainId, deployment, table, args, tokenIn, tokenOut) {
355
368
  const b = lc(args.route[2 * k + 2]);
356
369
  const row = args.params[k];
357
370
  const type = row[2];
358
- if (a === b) return `step ${k + 1} trades a token for itself`;
371
+ if (a === b) return noted(`step ${k + 1} trades a token for itself`, "policy_refused");
359
372
  if (type === 1n || type === 2n) {
360
- 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)`;
373
+ 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");
361
374
  const pool = index.get(swap);
362
- if (!pool) return `step ${k + 1} crosses ${args.route[2 * k + 1]}, which is not in the pool allowlist`;
363
- if (pools.has(swap)) return `the route crosses ${args.route[2 * k + 1]} twice`;
375
+ if (!pool) return noted(`step ${k + 1} crosses ${args.route[2 * k + 1]}, which is not in the pool allowlist`, "vault_mismatch");
376
+ if (pools.has(swap)) return noted(`the route crosses ${args.route[2 * k + 1]} twice`, "policy_refused");
364
377
  pools.add(swap);
365
- if (!pool.swapTypes.includes(type === 1n ? 1 : 2)) return `step ${k + 1} uses swap_type ${type}, which pool ${pool.name} does not allow`;
366
- if (row[3] !== BigInt(pool.poolType)) return `step ${k + 1} names pool_type ${row[3]}, not pool ${pool.name}'s ${pool.poolType}`;
367
- if (type === 2n && deployment.family === "lite4" && pool.poolType !== 10) return `step ${k + 1}: exchange_underlying is for stable-ng metapools on this router`;
368
- if (type === 2n && !underlyingAllowed(pool, index)) return `step ${k + 1}: exchange_underlying on pool ${pool.name} needs its base pool in the allowlist`;
378
+ 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");
379
+ 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");
380
+ 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");
381
+ 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");
369
382
  const list = type === 1n ? pool.coins : pool.underlying;
370
- if (!list) return `step ${k + 1}: pool ${pool.name} lists no underlying coins`;
383
+ if (!list) return noted(`step ${k + 1}: pool ${pool.name} lists no underlying coins`, "policy_refused");
371
384
  const i = row[0], j = row[1];
372
- 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}`;
373
- 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`;
374
- if (deployment.family === "ng5" && row[4] !== BigInt(list.length)) return `step ${k + 1} names ${row[4]} coins, not pool ${pool.name}'s ${list.length}`;
375
- if (deployment.family === "lite4" && (a === eth || b === eth)) return `step ${k + 1}: this router does not pass the native coin to pools`;
376
- if (STABLE_POOL_TYPES.has(pool.poolType) && (i > 127n || j > 127n)) return `step ${k + 1}: coin index out of range`;
385
+ 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");
386
+ 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");
387
+ 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");
388
+ 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");
389
+ if (STABLE_POOL_TYPES.has(pool.poolType) && (i > 127n || j > 127n)) return noted(`step ${k + 1}: coin index out of range`, "policy_refused");
377
390
  } else if (type === 8n) {
378
- if (weth === zero) return "this chain's router has no wrapped native";
379
- if (swap !== weth) return `step ${k + 1} wraps through ${args.route[2 * k + 1]}, not the pinned wrapped native`;
380
- 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`;
381
- if (row.some((x, c) => c !== 2 && x !== 0n)) return `step ${k + 1}: a wrap carries no other swap params`;
382
- if (++wraps > 1) return "the route wraps more than once";
391
+ if (weth === zero) return noted("this chain's router has no wrapped native", "policy_refused");
392
+ if (swap !== weth) return noted(`step ${k + 1} wraps through ${args.route[2 * k + 1]}, not the pinned wrapped native`, "vault_mismatch");
393
+ 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");
394
+ if (row.some((x, c) => c !== 2 && x !== 0n)) return noted(`step ${k + 1}: a wrap carries no other swap params`, "policy_refused");
395
+ if (++wraps > 1) return noted("the route wraps more than once", "policy_refused");
383
396
  } else if (type === 9n) {
384
- if (!v) return `swap_type 9 (ERC-4626) is not available on ${deployment.label}'s router ${deployment.version}`;
385
- if (swap !== lc(v.vault)) return `step ${k + 1}: ERC-4626 hop through ${args.route[2 * k + 1]}, not the pinned vault`;
397
+ if (!v) return noted(`swap_type 9 (ERC-4626) is not available on ${deployment.label}'s router ${deployment.version}`, "policy_refused");
398
+ 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");
386
399
  const deposit = row[0] === 0n && a === lc(v.asset) && b === lc(v.vault);
387
400
  const redeem = row[0] === 1n && a === lc(v.vault) && b === lc(v.asset);
388
- 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`;
389
- if (row.some((x, c) => c > 0 && c !== 2 && x !== 0n)) return `step ${k + 1}: an ERC-4626 hop carries no other swap params`;
390
- if (++vaults > 1) return "the route crosses the vault more than once";
401
+ 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");
402
+ 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");
403
+ if (++vaults > 1) return noted("the route crosses the vault more than once", "policy_refused");
391
404
  } else {
392
- return `step ${k + 1} uses swap_type ${type}, which this module never builds`;
405
+ return noted(`step ${k + 1} uses swap_type ${type}, which this module never builds`, "policy_refused");
393
406
  }
394
407
  }
395
- if (pools.size > MAX_ROUTE_POOLS) return `the route crosses ${pools.size} pools, more than ${MAX_ROUTE_POOLS}`;
396
- if (pools.size === 0 && vaults === 0) return "the route crosses no pool";
408
+ if (pools.size > MAX_ROUTE_POOLS) return noted(`the route crosses ${pools.size} pools, more than ${MAX_ROUTE_POOLS}`, "policy_refused");
409
+ if (pools.size === 0 && vaults === 0) return noted("the route crosses no pool", "policy_refused");
397
410
  return null;
398
411
  }
399
412
 
400
413
  // src/rpc.ts
414
+ import { httpRefusal, refusal, refusalOf, withReason as withReason2 } from "@anywalletkit/protocol-swap2-core/errors";
401
415
  import { redactUrls, timeoutSignal } from "@anywalletkit/protocol-swap2-core/util";
402
416
  var RPC_BATCH_MAX = 10;
403
417
  var RPC_RATE_LIMIT_PAUSE_MS = 1e3;
@@ -423,18 +437,18 @@ function rpcClient(rpcUrls) {
423
437
  body: JSON.stringify(calls.map((c, id) => ({ jsonrpc: "2.0", id, ...c }))),
424
438
  signal: signal ?? timeoutSignal(12e3)
425
439
  });
426
- if (!res.ok) throw Object.assign(new Error(`HTTP ${res.status}`), { limited: res.status === 429 });
440
+ if (!res.ok) throw withReason2(Object.assign(new Error(`HTTP ${res.status}`), { limited: res.status === 429 }), httpRefusal(res.status, res.headers?.get?.("retry-after")) ?? { reason: "upstream_unavailable", httpStatus: res.status });
427
441
  const body = await res.json();
428
- if (!Array.isArray(body)) throw Object.assign(new Error(body.error?.message ?? "batch refused"), { limited: isRateLimit(body.error) });
442
+ if (!Array.isArray(body)) throw withReason2(Object.assign(new Error(body.error?.message ?? "batch refused"), { limited: isRateLimit(body.error) }), isRateLimit(body.error) ? "rate_limited" : "upstream_unavailable");
429
443
  const out = calls.map(() => null);
430
444
  const answered = /* @__PURE__ */ new Set();
431
445
  for (const r of body) {
432
446
  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) });
447
+ if (r.error !== void 0 && r.error !== null && !isExecutionError(r.error)) throw withReason2(Object.assign(new Error(errorText(r.error)), { limited: isRateLimit(r.error) }), isRateLimit(r.error) ? "rate_limited" : "upstream_unavailable");
434
448
  answered.add(r.id);
435
449
  if (r.error === void 0 || r.error === null) out[r.id] = r.result ?? null;
436
450
  }
437
- if (answered.size !== calls.length) throw new Error(`answered ${answered.size} of ${calls.length} calls`);
451
+ if (answered.size !== calls.length) throw refusal("upstream_unavailable", `answered ${answered.size} of ${calls.length} calls`);
438
452
  return out;
439
453
  }
440
454
  async function batch(chainId, calls, signal) {
@@ -452,7 +466,9 @@ function rpcClient(rpcUrls) {
452
466
  }
453
467
  }
454
468
  }
455
- throw new Error(`curve: no RPC answered on chain ${chainId} (${lastErr instanceof Error ? lastErr.message : String(lastErr)})`);
469
+ const error = new Error(`curve: no RPC answered on chain ${chainId} (${lastErr instanceof Error ? lastErr.message : String(lastErr)})`);
470
+ if (lastErr === void 0 || signal?.aborted) throw error;
471
+ throw withReason2(error, refusalOf(lastErr) ?? "upstream_unavailable");
456
472
  }
457
473
  return async function rpc(chainId, calls, signal) {
458
474
  if (calls.length <= RPC_BATCH_MAX) return batch(chainId, calls, signal);
@@ -512,6 +528,7 @@ function curveIntentChain(intentId) {
512
528
  import { decodeFunctionResult, encodeFunctionData, keccak256 } from "viem";
513
529
  import { sourceAssetOf as sourceAssetOf2 } from "@anywalletkit/protocol-swap2-core/util";
514
530
  import { slippageFloor } from "@anywalletkit/protocol-swap2-core/validate";
531
+ import { refusal as refusal2, refusalOf as refusalOf2 } from "@anywalletkit/protocol-swap2-core/errors";
515
532
  var lc2 = (a) => a.toLowerCase();
516
533
  var isAddress = (a) => /^0x[0-9a-fA-F]{40}$/.test(a);
517
534
  var own = (o, k) => Object.prototype.hasOwnProperty.call(o, k) ? o[k] : void 0;
@@ -521,19 +538,19 @@ function curveQuoteCheck(tables = {}) {
521
538
  return function verify(params, quote) {
522
539
  const chainId = chainIdOf(params.sourceChain);
523
540
  const d = deploymentOf(deployments, chainId);
524
- if (!d || params.destChain !== params.sourceChain) return "not a same-chain swap on a pinned Curve router";
541
+ if (!d || params.destChain !== params.sourceChain) return noted("not a same-chain swap on a pinned Curve router", "unsupported_pair");
525
542
  const tx = quote.txRequest;
526
- if (!tx?.data || !tx.to || tx.serializedTx !== void 0) return "the quote carries no router call";
527
- if (!isAddress(tx.to) || lc2(tx.to) !== lc2(d.router)) return `the call targets ${tx.to}, not the Curve router`;
528
- if (tx.chainId !== void 0 && tx.chainId !== chainId) return `the call is for chain ${tx.chainId}`;
529
- if (curveIntentChain(quote.intentId) !== chainId) return `the quote names ${String(quote.intentId).slice(0, 40)}, not a curve:${chainId} swap`;
543
+ if (!tx?.data || !tx.to || tx.serializedTx !== void 0) return noted("the quote carries no router call", "policy_refused");
544
+ if (!isAddress(tx.to) || lc2(tx.to) !== lc2(d.router)) return noted(`the call targets ${tx.to}, not the Curve router`, "vault_mismatch");
545
+ if (tx.chainId !== void 0 && tx.chainId !== chainId) return noted(`the call is for chain ${tx.chainId}`, "policy_refused");
546
+ if (curveIntentChain(quote.intentId) !== chainId) return noted(`the quote names ${String(quote.intentId).slice(0, 40)}, not a curve:${chainId} swap`, "policy_refused");
530
547
  const src = sourceAssetOf2(params);
531
- if (!src) return `unknown source token ${params.sourceToken}`;
548
+ if (!src) return noted(`unknown source token ${params.sourceToken}`, "unsupported_pair");
532
549
  const nativeIn = !src.contract;
533
550
  const nativeOut = /^0x0+$/.test(params.destToken);
534
- if (!nativeOut && !isAddress(params.destToken)) return "destination token must be an EVM address";
535
- if (!isAddress(params.destAddress)) return "recipient must be an EVM address";
536
- if (!nativeIn && lc2(src.contract) === lc2(d.eth) || !nativeOut && lc2(params.destToken) === lc2(d.eth)) return "the router's native sentinel is not a token";
551
+ if (!nativeOut && !isAddress(params.destToken)) return noted("destination token must be an EVM address", "unsupported_pair");
552
+ if (!isAddress(params.destAddress)) return noted("recipient must be an EVM address", "recipient_mismatch");
553
+ if (!nativeIn && lc2(src.contract) === lc2(d.eth) || !nativeOut && lc2(params.destToken) === lc2(d.eth)) return noted("the router's native sentinel is not a token", "unsupported_pair");
537
554
  const tokenIn = nativeIn ? d.eth : src.contract;
538
555
  const tokenOut = nativeOut ? d.eth : params.destToken;
539
556
  let amountIn, minOut, value;
@@ -542,20 +559,20 @@ function curveQuoteCheck(tables = {}) {
542
559
  minOut = BigInt(quote.minDestAmount);
543
560
  value = BigInt(tx.value ?? "0");
544
561
  } catch {
545
- return "the request does not name amounts";
562
+ return noted("the request does not name amounts", "policy_refused");
546
563
  }
547
564
  if (nativeIn ? value !== amountIn || tx.approvalAddress !== void 0 : value !== 0n || !tx.approvalAddress || !isAddress(tx.approvalAddress) || lc2(tx.approvalAddress) !== lc2(d.router)) {
548
- 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";
565
+ 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");
549
566
  }
550
567
  const call = decodeExchange(d.family, tx.data);
551
- if (!call) return "the router call is not exchange(route, params, amount, min, receiver)";
552
- if (call.selector !== EXCHANGE_SELECTOR[d.family]) return `selector ${call.selector} is not the explicit-receiver exchange ${EXCHANGE_SELECTOR[d.family]}`;
553
- if (!call.receiver || lc2(call.receiver) !== lc2(params.destAddress)) return `the swap pays ${call.receiver ?? "the sender"}, not the recipient`;
554
- if (call.pools && call.pools.some((p) => lc2(p) !== lc2(ZERO_ADDRESS))) return "the call names zap pools this module never uses";
555
- if (call.amount !== amountIn) return `the swap sells ${call.amount}, not the ${amountIn} input`;
556
- if (call.minDy <= 0n) return "the swap has no minimum (min_dy 0)";
557
- if (call.minDy !== minOut) return `the swap's minimum ${call.minDy} is not the quote's ${minOut}`;
558
- if (quote.providerData?.minOut !== void 0 && quote.providerData.minOut !== minOut.toString()) return "the quote's facts disagree on the minimum";
568
+ if (!call) return noted("the router call is not exchange(route, params, amount, min, receiver)", "policy_refused");
569
+ if (call.selector !== EXCHANGE_SELECTOR[d.family]) return noted(`selector ${call.selector} is not the explicit-receiver exchange ${EXCHANGE_SELECTOR[d.family]}`, "policy_refused");
570
+ if (!call.receiver || lc2(call.receiver) !== lc2(params.destAddress)) return noted(`the swap pays ${call.receiver ?? "the sender"}, not the recipient`, "recipient_mismatch");
571
+ 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");
572
+ if (call.amount !== amountIn) return noted(`the swap sells ${call.amount}, not the ${amountIn} input`, "policy_refused");
573
+ if (call.minDy <= 0n) return noted("the swap has no minimum (min_dy 0)", "price_check_failed");
574
+ if (call.minDy !== minOut) return noted(`the swap's minimum ${call.minDy} is not the quote's ${minOut}`, "price_check_failed");
575
+ if (quote.providerData?.minOut !== void 0 && quote.providerData.minOut !== minOut.toString()) return noted("the quote's facts disagree on the minimum", "price_check_failed");
559
576
  return checkRoute(chainId, d, own(pools, chainId), call, tokenIn, tokenOut);
560
577
  };
561
578
  }
@@ -563,7 +580,9 @@ var curveVerifyQuote = curveQuoteCheck();
563
580
  var curvePins = Object.freeze({
564
581
  callTargets: (chain) => routerFor(CURVE_DEPLOYMENTS, chain),
565
582
  spenders: (chain) => routerFor(CURVE_DEPLOYMENTS, chain),
566
- verifyQuote: curveVerifyQuote
583
+ verifyQuote: curveVerifyQuote,
584
+ // The reason of each text verifyQuote returned (remoteProvider puts it on its refusal).
585
+ reasonOf: notes.reasonOf
567
586
  });
568
587
  function routerFor(deployments, chain) {
569
588
  const d = deploymentOf(deployments, chainIdOf(chain));
@@ -580,21 +599,21 @@ function curveOnChain(opts) {
580
599
  if (!p) {
581
600
  const d = deploymentOf(deployments, chainId);
582
601
  p = (async () => {
583
- if (!d) throw new Error(`curve: no pinned router on chain ${chainId}`);
602
+ if (!d) throw refusal2("unsupported_pair", `curve: no pinned router on chain ${chainId}`);
584
603
  const [code, version, listed] = await rpc(chainId, [
585
604
  { method: "eth_getCode", params: [d.router, "latest"] },
586
605
  ethCall(d.router, encodeFunctionData({ abi: VERSION_ABI, functionName: "version" })),
587
606
  ethCall(d.addressProvider, encodeFunctionData({ abi: ADDRESS_PROVIDER_ABI, functionName: "get_address", args: [2n] }))
588
607
  ]);
589
608
  if (typeof code !== "string" || code.length <= 2 || lc2(keccak256(code)) !== lc2(d.codeHash)) {
590
- throw new Error(`curve: the router on chain ${chainId} is not the pinned code (code hash mismatch) \u2014 refusing`);
609
+ throw refusal2("vault_mismatch", `curve: the router on chain ${chainId} is not the pinned code (code hash mismatch) \u2014 refusing`);
591
610
  }
592
611
  let v = "";
593
612
  try {
594
613
  v = typeof version === "string" ? decodeFunctionResult({ abi: VERSION_ABI, functionName: "version", data: version }) : "";
595
614
  } catch {
596
615
  }
597
- if (v !== d.version) throw new Error(`curve: the router on chain ${chainId} reports version ${v || "none"}, not ${d.version} \u2014 refusing`);
616
+ if (v !== d.version) throw refusal2("vault_mismatch", `curve: the router on chain ${chainId} reports version ${v || "none"}, not ${d.version} \u2014 refusing`);
598
617
  const ap = wordAddress(listed);
599
618
  if (ap && ap !== lc2(d.addressProviderRouter) && ap !== lc2(d.router)) {
600
619
  warn(`curve: the AddressProvider on ${d.label} lists router ${ap} (id 2); this module keeps the pinned ${d.router}`);
@@ -612,17 +631,17 @@ function curveOnChain(opts) {
612
631
  async function inspectSealed(quote, tx, probe) {
613
632
  const chainId = curveIntentChain(quote.intentId);
614
633
  const d = chainId === null ? void 0 : deploymentOf(deployments, chainId);
615
- if (chainId === null || !d) return "the quote is not a Curve swap on a pinned chain";
634
+ if (chainId === null || !d) return noted("the quote is not a Curve swap on a pinned chain", "policy_refused");
616
635
  const quoted = quote.txRequest;
617
- if (!quoted?.data || !quoted.to || !tx.data) return "the quote or the transaction carries no router call";
618
- if (tx.data.length !== quoted.data.length || lc2(tx.data) !== lc2(quoted.data)) return "the transaction is not the quoted router call";
619
- if (!isAddress(tx.to) || lc2(tx.to) !== lc2(d.router) || lc2(quoted.to) !== lc2(d.router)) return `the transaction calls ${tx.to}, not the Curve router`;
620
- if ((tx.value ?? "0") !== (quoted.value ?? "0")) return "the transaction's value is not the quote's";
621
- if (tx.chainId !== void 0 && tx.chainId !== chainId || quoted.chainId !== void 0 && quoted.chainId !== chainId) return `the transaction is not for chain ${chainId}`;
622
- if ((tx.approvalAddress ?? "").toLowerCase() !== (quoted.approvalAddress ?? "").toLowerCase()) return "the transaction's approval is not the quote's";
636
+ if (!quoted?.data || !quoted.to || !tx.data) return noted("the quote or the transaction carries no router call", "policy_refused");
637
+ 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");
638
+ if (!isAddress(tx.to) || lc2(tx.to) !== lc2(d.router) || lc2(quoted.to) !== lc2(d.router)) return noted(`the transaction calls ${tx.to}, not the Curve router`, "vault_mismatch");
639
+ if ((tx.value ?? "0") !== (quoted.value ?? "0")) return noted("the transaction's value is not the quote's", "policy_refused");
640
+ 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");
641
+ if ((tx.approvalAddress ?? "").toLowerCase() !== (quoted.approvalAddress ?? "").toLowerCase()) return noted("the transaction's approval is not the quote's", "vault_mismatch");
623
642
  const call = decodeExchange(d.family, quoted.data);
624
- if (!call || call.selector !== EXCHANGE_SELECTOR[d.family]) return "the quoted call is not the explicit-receiver exchange";
625
- if (call.minDy <= 0n) return "the swap has no minimum (min_dy 0)";
643
+ if (!call || call.selector !== EXCHANGE_SELECTOR[d.family]) return noted("the quoted call is not the explicit-receiver exchange", "policy_refused");
644
+ if (call.minDy <= 0n) return noted("the swap has no minimum (min_dy 0)", "price_check_failed");
626
645
  const last = lastToken(call);
627
646
  const reason = checkRoute(chainId, d, own(pools, chainId), call, call.route[0], last);
628
647
  if (reason) return reason;
@@ -638,8 +657,8 @@ function curveOnChain(opts) {
638
657
  const poolReason = await poolsReason(chainId, hops, res.slice(1, 1 + reads.length));
639
658
  if (poolReason) return poolReason;
640
659
  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}`;
660
+ if (out === null) return noted("the router no longer quotes this route", "simulation_failed");
661
+ if (out < call.minDy) return noted(`price moved \u2014 the pools now pay ${out}, under your minimum ${call.minDy}`, "price_check_failed");
643
662
  return { out, probeOut: probe && probeIn > 0n ? wordUint(res[probeAt]) : null, amount: call.amount, minDy: call.minDy };
644
663
  }
645
664
  function poolCalls(list) {
@@ -653,19 +672,19 @@ function curveOnChain(opts) {
653
672
  const retry = [];
654
673
  for (const pool of list) {
655
674
  const code = res[at++];
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`;
675
+ if (typeof code !== "string") return noted(`could not read pool ${pool.name} (${pool.address}) over RPC`, "upstream_unavailable");
676
+ if (code.length <= 2 || lc2(keccak256(code)) !== lc2(pool.codeHash)) return noted(`pool ${pool.name} (${pool.address}) is not the pinned code`, "vault_mismatch");
658
677
  for (let k = 0; k < pool.coins.length; k++) {
659
678
  const coin = wordAddress(res[at++]);
660
679
  if (coin === null) retry.push({ pool, k });
661
- else if (coin !== lc2(pool.coins[k])) return `pool ${pool.name} now reports coin ${k} = ${coin}, not the pinned ${pool.coins[k]}`;
680
+ 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");
662
681
  }
663
682
  }
664
683
  if (retry.length) {
665
684
  const again = await rpc(chainId, retry.map(({ pool, k }) => ethCall(pool.address, `${COINS_INT}${k.toString(16).padStart(64, "0")}`)));
666
685
  for (const [n, { pool, k }] of retry.entries()) {
667
686
  const coin = wordAddress(again[n]);
668
- if (coin !== lc2(pool.coins[k])) return `pool ${pool.name} does not report its pinned coin ${k}`;
687
+ if (coin !== lc2(pool.coins[k])) return noted(`pool ${pool.name} does not report its pinned coin ${k}`, "vault_mismatch");
669
688
  }
670
689
  }
671
690
  return null;
@@ -691,8 +710,10 @@ function curveRemotePins(opts) {
691
710
  callTargets: (c) => routerFor(deployments, c),
692
711
  spenders: (c) => routerFor(deployments, c),
693
712
  verifyQuote,
713
+ // The reason of each text verifyQuote / verifyBuild returned (remoteProvider puts it on its refusal).
714
+ reasonOf: notes.reasonOf,
694
715
  async verifyBuild(quote, tx, params) {
695
- if (!params) return "the request this quote answered is unknown";
716
+ if (!params) return noted("the request this quote answered is unknown", "policy_refused");
696
717
  const reason = verifyQuote(params, quote);
697
718
  if (reason) return reason;
698
719
  try {
@@ -704,15 +725,15 @@ function curveRemotePins(opts) {
704
725
  try {
705
726
  dest = BigInt(quote.destAmount);
706
727
  } catch {
707
- return "the quote names no output";
728
+ return noted("the quote names no output", "upstream_unavailable");
708
729
  }
709
730
  const bps = params.slippageBps ?? fixedSlippage ?? Math.min(9999, curveAutoSlippageBps(params, curveImpactBps(amount, out, probeOut)) + CURVE_AUTO_SLIPPAGE_DRIFT_BPS);
710
- if (slippageFloor(dest, bps) > out) return `the quote promises ${dest}, more than ${bps} bps above the ${out} the pools pay now`;
731
+ if (slippageFloor(dest, bps) > out) return noted(`the quote promises ${dest}, more than ${bps} bps above the ${out} the pools pay now`, "price_check_failed");
711
732
  const fresh = dest < out ? dest : out;
712
- if (minDy < slippageFloor(fresh, bps)) return `the swap's minimum ${minDy} is more than ${bps} bps under the ${fresh} the pools pay now`;
733
+ if (minDy < slippageFloor(fresh, bps)) return noted(`the swap's minimum ${minDy} is more than ${bps} bps under the ${fresh} the pools pay now`, "price_check_failed");
713
734
  return null;
714
735
  } catch (e) {
715
- return e instanceof Error ? e.message.replace(/^curve: /, "") : String(e);
736
+ return noted(e instanceof Error ? e.message.replace(/^curve: /, "") : String(e), refusalOf2(e));
716
737
  }
717
738
  }
718
739
  });
@@ -750,46 +771,47 @@ function curveProvider(config) {
750
771
  return from.chain === to.chain && chainIdOf2(from.chain) !== null;
751
772
  },
752
773
  async getQuote(params, signal) {
753
- const refuse = (why) => {
754
- params.onRefusal?.(`curve: ${why}`);
774
+ const refuse = (why, info) => {
775
+ if (info === void 0) params.onRefusal?.(`curve: ${why}`);
776
+ else params.onRefusal?.(`curve: ${why}`, typeof info === "string" ? { reason: info } : info);
755
777
  return null;
756
778
  };
757
- if (params.flex) return refuse("amount-flexible deposits are not supported");
758
- if (params.sourceChain !== params.destChain) return refuse("same-chain swaps only");
779
+ if (params.flex) return refuse("amount-flexible deposits are not supported", "unsupported_pair");
780
+ if (params.sourceChain !== params.destChain) return refuse("same-chain swaps only", "unsupported_pair");
759
781
  const chainId = chainIdOf2(params.sourceChain);
760
- if (chainId === null) return refuse(`no Curve router, pool allowlist or RPC for ${params.sourceChain}`);
782
+ if (chainId === null) return refuse(`no Curve router, pool allowlist or RPC for ${params.sourceChain}`, "unsupported_pair");
761
783
  const d = deploymentOf(deployments, chainId);
762
784
  const src = sourceAssetOf3(params);
763
- if (!src) return refuse(`unknown source token ${params.sourceToken}`);
785
+ if (!src) return refuse(`unknown source token ${params.sourceToken}`, "unsupported_pair");
764
786
  const nativeIn = !src.contract;
765
787
  const nativeOut = /^0x0+$/.test(params.destToken);
766
- if (!nativeOut && !/^0x[0-9a-fA-F]{40}$/.test(params.destToken)) return refuse("destination token must be an EVM address");
767
- if (!/^0x[0-9a-fA-F]{40}$/.test(params.destAddress)) return refuse("recipient must be an EVM address");
788
+ if (!nativeOut && !/^0x[0-9a-fA-F]{40}$/.test(params.destToken)) return refuse("destination token must be an EVM address", "unsupported_pair");
789
+ if (!/^0x[0-9a-fA-F]{40}$/.test(params.destAddress)) return refuse("recipient must be an EVM address", "recipient_mismatch");
768
790
  let amountIn;
769
791
  try {
770
792
  amountIn = BigInt(params.amountIn);
771
793
  } catch {
772
794
  return refuse("amount is not an integer");
773
795
  }
774
- if (amountIn <= 0n) return refuse("amount must be positive");
796
+ if (amountIn <= 0n) return refuse("amount must be positive", "amount_too_small");
775
797
  if (params.slippageBps !== void 0 && (!Number.isInteger(params.slippageBps) || params.slippageBps < 0 || params.slippageBps >= 1e4)) return refuse("slippage must be 0..9999 bps");
776
- if (!nativeIn && src.contract.toLowerCase() === d.eth.toLowerCase()) return refuse("the native coin is named by the catalog, not by the router's 0xEeee sentinel");
777
- if (!nativeOut && params.destToken.toLowerCase() === d.eth.toLowerCase()) return refuse("name a native output with the zero address, not the router's 0xEeee sentinel");
798
+ if (!nativeIn && src.contract.toLowerCase() === d.eth.toLowerCase()) return refuse("the native coin is named by the catalog, not by the router's 0xEeee sentinel", "unsupported_pair");
799
+ if (!nativeOut && params.destToken.toLowerCase() === d.eth.toLowerCase()) return refuse("name a native output with the zero address, not the router's 0xEeee sentinel", "unsupported_pair");
778
800
  const tokenIn = getAddress2(nativeIn ? d.eth : src.contract);
779
801
  const tokenOut = getAddress2(nativeOut ? d.eth : params.destToken);
780
- if (tokenIn === tokenOut) return refuse("same token in and out");
802
+ if (tokenIn === tokenOut) return refuse("same token in and out", "unsupported_pair");
781
803
  const wrapPair = [getAddress2(d.eth), getAddress2(d.weth)];
782
- if (wrapPair.includes(tokenIn) && wrapPair.includes(tokenOut)) return refuse("that is a wrap/unwrap, not a swap");
804
+ if (wrapPair.includes(tokenIn) && wrapPair.includes(tokenOut)) return refuse("that is a wrap/unwrap, not a swap", "unsupported_pair");
783
805
  try {
784
806
  await chain.verifyDeployment(chainId);
785
807
  const { routes, truncated } = findRoutes(chainId, d, tableOf(chainId), tokenIn, tokenOut);
786
- if (!routes.length) return refuse("no allowlisted Curve pool route for this pair");
808
+ if (!routes.length) return refuse("no allowlisted Curve pool route for this pair", "no_route");
787
809
  const outs = await price(chainId, d, routes, amountIn, signal);
788
810
  let best = null;
789
811
  outs.forEach((out2, i) => {
790
812
  if (out2 !== null && out2 > 0n && (!best || out2 > best.out)) best = { route: routes[i], out: out2 };
791
813
  });
792
- if (!best) return refuse("no allowlisted Curve pool quotes this pair and amount");
814
+ if (!best) return refuse("no allowlisted Curve pool quotes this pair and amount", "no_route");
793
815
  const { route, out } = best;
794
816
  let priceImpactBps;
795
817
  const probeIn = amountIn / 1000n;
@@ -802,9 +824,9 @@ function curveProvider(config) {
802
824
  }
803
825
  const bps = params.slippageBps ?? fixedSlippage ?? curveAutoSlippageBps(params, priceImpactBps);
804
826
  const minOut = out * BigInt(1e4 - bps) / 10000n;
805
- if (minOut <= 0n) return refuse("the minimum rounds to zero for this amount");
827
+ if (minOut <= 0n) return refuse("the minimum rounds to zero for this amount", "amount_too_small");
806
828
  if (params.minAmountOut !== void 0 && minOut < BigInt(params.minAmountOut)) {
807
- return refuse(`floor breach (min=${minOut} < floor=${params.minAmountOut})`);
829
+ return refuse(`floor breach (min=${minOut} < floor=${params.minAmountOut})`, "price_check_failed");
808
830
  }
809
831
  const args = routeArgs(d.family, route);
810
832
  const recipient = getAddress2(params.destAddress);
@@ -846,10 +868,10 @@ function curveProvider(config) {
846
868
  }
847
869
  };
848
870
  const why = selfCheck(params, quote);
849
- if (why) return refuse(`built a call its own check refuses (${why})`);
871
+ if (why) return refuse(`built a call its own check refuses (${why})`, notes.reasonOf(why) ?? "policy_refused");
850
872
  return quote;
851
873
  } catch (e) {
852
- return refuse(e instanceof Error ? e.message.replace(/^curve: /, "") : String(e));
874
+ return refuse(e instanceof Error ? e.message.replace(/^curve: /, "") : String(e), refusalOf3(e));
853
875
  }
854
876
  },
855
877
  /**
@@ -859,11 +881,11 @@ function curveProvider(config) {
859
881
  */
860
882
  async preflightDeposit(quote, tx) {
861
883
  const reason = await chain.checkSealed(quote, tx);
862
- if (reason) throw new Error(`curve: ${reason}. Nothing was sent; refresh the quote.`);
884
+ if (reason) throw tagged(new Error(`curve: ${reason}. Nothing was sent; refresh the quote.`), notes.reasonOf(reason));
863
885
  },
864
886
  async buildDeposit(quote) {
865
887
  const tx = quote.txRequest;
866
- if (!tx?.to || !tx.data) throw new Error("swap2: Curve quote has no transaction request");
888
+ if (!tx?.to || !tx.data) throw refusal3("policy_refused", "swap2: Curve quote has no transaction request");
867
889
  return {
868
890
  chain: "",
869
891
  to: tx.to,
@@ -909,4 +931,4 @@ export {
909
931
  curveRemotePins,
910
932
  curveProvider
911
933
  };
912
- //# sourceMappingURL=chunk-IFREGXMV.js.map
934
+ //# sourceMappingURL=chunk-NW2OLRIK.js.map