@anywalletkit/protocol-swap2-thorchain 0.4.0 → 0.4.1
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-AYP7O2NT.js → chunk-EIAXGWNG.js} +158 -89
- package/dist/chunk-EIAXGWNG.js.map +1 -0
- package/dist/{chunk-LP5BU2JS.js → chunk-YKA6IYKX.js} +2 -2
- package/dist/index.cjs +157 -88
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/protocol.cjs +121 -64
- package/dist/protocol.cjs.map +1 -1
- package/dist/protocol.js +2 -2
- package/dist/provider.cjs +157 -88
- package/dist/provider.cjs.map +1 -1
- package/dist/provider.d.cts +1 -0
- package/dist/provider.d.ts +1 -0
- package/dist/provider.js +1 -1
- package/package.json +5 -5
- package/dist/chunk-AYP7O2NT.js.map +0 -1
- /package/dist/{chunk-LP5BU2JS.js.map → chunk-YKA6IYKX.js.map} +0 -0
|
@@ -8,10 +8,12 @@ import {
|
|
|
8
8
|
// src/provider.ts
|
|
9
9
|
import { decodeFunctionData, encodeFunctionData } from "viem";
|
|
10
10
|
import { timeoutSignal } from "@anywalletkit/protocol-swap2-core/util";
|
|
11
|
+
import { combinedRefusal, httpRefusal as httpRefusal2, refusal, refusalNotes, refusalOf, withReason as withReason2 } from "@anywalletkit/protocol-swap2-core/errors";
|
|
11
12
|
import { ccAsset, ccAssetByToken } from "@anywalletkit/swap2-assets";
|
|
12
13
|
import { bigintToNumber } from "@anywalletkit/core";
|
|
13
14
|
|
|
14
15
|
// src/confirm.ts
|
|
16
|
+
import { httpRefusal, withReason } from "@anywalletkit/protocol-swap2-core/errors";
|
|
15
17
|
var THORNODE_CONFIRM_PATHS = Object.freeze({
|
|
16
18
|
lastblock: "/thorchain/lastblock",
|
|
17
19
|
inboundAddresses: "/thorchain/inbound_addresses",
|
|
@@ -34,10 +36,18 @@ function confirmOptions(o) {
|
|
|
34
36
|
return { maxLagBlocks, timeoutMs };
|
|
35
37
|
}
|
|
36
38
|
var HostUnavailable = class extends Error {
|
|
37
|
-
|
|
39
|
+
/**
|
|
40
|
+
* `info` classifies the failure (`refusalOf` reads it back): what the HTTP status says (429 `rate_limited`, 401 / 403
|
|
41
|
+
* `key_rejected`), else `upstream_unavailable` — unreachable, timed out, a 5xx, redirected or another shape.
|
|
42
|
+
*/
|
|
43
|
+
constructor(host, reason, info) {
|
|
38
44
|
super(`thorchain: ${host} unavailable (${reason})`);
|
|
45
|
+
withReason(this, info ?? "upstream_unavailable");
|
|
39
46
|
}
|
|
40
47
|
};
|
|
48
|
+
function statusRefusal(status, retryAfter) {
|
|
49
|
+
return httpRefusal(status, retryAfter) ?? { reason: "upstream_unavailable", httpStatus: status };
|
|
50
|
+
}
|
|
41
51
|
function paused(entry) {
|
|
42
52
|
return !entry || entry.halted === true || entry.global_trading_paused === true || entry.chain_trading_paused === true;
|
|
43
53
|
}
|
|
@@ -83,7 +93,7 @@ async function nodeRead(host, path, clientId, signal, outer) {
|
|
|
83
93
|
throw new HostUnavailable(host, error instanceof Error ? error.message : "fetch failed");
|
|
84
94
|
}
|
|
85
95
|
if (res.redirected === true || !sameRequestUrl(res.url, url)) throw new HostUnavailable(host, `${path} redirected`);
|
|
86
|
-
if (!res.ok) throw new HostUnavailable(host, `${path} HTTP ${res.status}
|
|
96
|
+
if (!res.ok) throw new HostUnavailable(host, `${path} HTTP ${res.status}`, statusRefusal(res.status, res.headers?.get?.("retry-after")));
|
|
87
97
|
try {
|
|
88
98
|
return await res.json();
|
|
89
99
|
} catch (error) {
|
|
@@ -315,7 +325,7 @@ async function hostGet(host, path, clientId, signal) {
|
|
|
315
325
|
throw new HostUnavailable(host, error instanceof Error ? error.message : "fetch failed");
|
|
316
326
|
}
|
|
317
327
|
if (res.status >= 500 || res.status === 429 || res.status === 403) {
|
|
318
|
-
throw new HostUnavailable(host, `HTTP ${res.status}
|
|
328
|
+
throw new HostUnavailable(host, `HTTP ${res.status}`, statusRefusal(res.status, res.headers?.get?.("retry-after")));
|
|
319
329
|
}
|
|
320
330
|
const body = await res.json().catch(() => null);
|
|
321
331
|
return { ok: res.ok, status: res.status, body };
|
|
@@ -340,30 +350,53 @@ function belowThorMinimums(amount1e8, inbound, quote) {
|
|
|
340
350
|
if (min !== void 0 && (!/^\d+$/.test(String(min)) || amount1e8 < BigInt(min))) return "amount below THORChain recommended minimum";
|
|
341
351
|
return null;
|
|
342
352
|
}
|
|
353
|
+
function nodesFailed(failures) {
|
|
354
|
+
const known = failures.filter((f) => f !== void 0);
|
|
355
|
+
if (!known.length || known.length !== failures.length) return { reason: "upstream_unavailable" };
|
|
356
|
+
const combined = combinedRefusal(known.map((f) => ({ message: "", ...f })));
|
|
357
|
+
return combined.reason === "no_route" ? { reason: "upstream_unavailable" } : combined;
|
|
358
|
+
}
|
|
359
|
+
function quoteRejection(text, status, bodyless) {
|
|
360
|
+
if (/emit asset 0 less than price limit/.test(text)) return { reason: "amount_too_small" };
|
|
361
|
+
if (/emit asset \d+ less than price limit/.test(text)) return { reason: "price_check_failed" };
|
|
362
|
+
if (/halt|paused/i.test(text)) return { reason: "chain_halted" };
|
|
363
|
+
if (/dust|too small|not enough [a-z ]*(?:to pay|for) (?:the )?fee/i.test(text)) return { reason: "amount_too_small" };
|
|
364
|
+
if (/insufficient liquidity|not enough liquidity/i.test(text)) return { reason: "insufficient_liquidity" };
|
|
365
|
+
if (/pool [^ ]* ?(?:does not exist|doesn't exist|not exist|not found|not available|is not available)|no pool/i.test(text)) return { reason: "unsupported_pair" };
|
|
366
|
+
if (/rate limit|too many requests/i.test(text)) return { reason: "rate_limited" };
|
|
367
|
+
if (/api key|unauthori[sz]ed/i.test(text)) return { reason: "key_rejected" };
|
|
368
|
+
const byStatus = status >= 400 ? httpRefusal2(status) : void 0;
|
|
369
|
+
if (byStatus) return byStatus;
|
|
370
|
+
return bodyless ? { reason: "upstream_unavailable", httpStatus: status } : { reason: "no_route" };
|
|
371
|
+
}
|
|
343
372
|
async function inboundAddressesOn(host, clientId) {
|
|
344
373
|
const res = await hostGet(host, "/thorchain/inbound_addresses", clientId);
|
|
345
|
-
if (!res.ok || !Array.isArray(res.body)) throw new HostUnavailable(host, `inbound_addresses HTTP ${res.status}
|
|
374
|
+
if (!res.ok || !Array.isArray(res.body)) throw new HostUnavailable(host, `inbound_addresses HTTP ${res.status}`, res.ok ? void 0 : statusRefusal(res.status));
|
|
346
375
|
const entries = inboundAddresses(res.body);
|
|
347
376
|
if (!entries) throw new HostUnavailable(host, "inbound_addresses is not a THORNode inbound address list");
|
|
348
377
|
return entries;
|
|
349
378
|
}
|
|
350
|
-
function verifiedMemo(memo, expectedAsset, expectedDestination, expectedRefund, expectedAffiliate, expectedAffiliateBps) {
|
|
351
|
-
|
|
379
|
+
function verifiedMemo(memo, expectedAsset, expectedDestination, expectedRefund, expectedAffiliate, expectedAffiliateBps, why) {
|
|
380
|
+
const refused = (reason) => {
|
|
381
|
+
why?.(reason);
|
|
382
|
+
return null;
|
|
383
|
+
};
|
|
384
|
+
if (memo.includes("|")) return refused("recipient_unverifiable");
|
|
352
385
|
const parts = memo.split(":");
|
|
353
|
-
if (parts.length !== 4 && parts.length !== 6) return
|
|
386
|
+
if (parts.length !== 4 && parts.length !== 6) return refused("policy_refused");
|
|
354
387
|
const [op, asset, destination, limit, affiliate, bps] = parts;
|
|
355
|
-
if (op !== "=" || !asset || !/^[A-Za-z0-9._~\-]+$/.test(asset)) return
|
|
388
|
+
if (op !== "=" || !asset || !/^[A-Za-z0-9._~\-]+$/.test(asset)) return refused("policy_refused");
|
|
356
389
|
const expectedTarget = expectedRefund ? `${expectedDestination}/${expectedRefund}` : expectedDestination;
|
|
357
|
-
if (!sameTarget(destination, expectedTarget)) return
|
|
390
|
+
if (!sameTarget(destination, expectedTarget)) return refused("recipient_mismatch");
|
|
358
391
|
if (parts.length === 6) {
|
|
359
|
-
if (!expectedAffiliate || affiliate !== expectedAffiliate || bps !== expectedAffiliateBps) return
|
|
392
|
+
if (!expectedAffiliate || affiliate !== expectedAffiliate || bps !== expectedAffiliateBps) return refused("policy_refused");
|
|
360
393
|
} else if (expectedAffiliate || expectedAffiliateBps) {
|
|
361
|
-
return
|
|
394
|
+
return refused("policy_refused");
|
|
362
395
|
}
|
|
363
396
|
const stream = (limit ?? "").split("/");
|
|
364
|
-
if (stream.length !== 1 || stream.some((v) => !/^\d+$/.test(v))) return
|
|
397
|
+
if (stream.length !== 1 || stream.some((v) => !/^\d+$/.test(v))) return refused("price_check_failed");
|
|
365
398
|
const floor = BigInt(stream[0] ?? "0");
|
|
366
|
-
if (floor <= 0n) return
|
|
399
|
+
if (floor <= 0n) return refused("price_check_failed");
|
|
367
400
|
const canonical = `=:${expectedAsset}:${expectedTarget}:${stream.join("/")}${expectedAffiliate ? `:${expectedAffiliate}:${expectedAffiliateBps}` : ""}`;
|
|
368
401
|
return { limit: floor, canonical };
|
|
369
402
|
}
|
|
@@ -376,7 +409,7 @@ function thorchainPins(opts = {}) {
|
|
|
376
409
|
for (const n of nodes) if (!/^https:\/\//i.test(n)) throw new Error("thorchain: thorchainPins nodeUrl must use https");
|
|
377
410
|
const clientId = opts.clientId?.trim() || DEFAULT_CLIENT_ID;
|
|
378
411
|
const { maxLagBlocks, timeoutMs } = confirmOptions(opts);
|
|
379
|
-
const askNode = async (host, claim) => {
|
|
412
|
+
const askNode = async (host, claim, failures) => {
|
|
380
413
|
const node = nodeDeadline(timeoutMs);
|
|
381
414
|
const read = (path) => nodeRead(host, path, clientId, node.signal);
|
|
382
415
|
try {
|
|
@@ -384,59 +417,71 @@ function thorchainPins(opts = {}) {
|
|
|
384
417
|
try {
|
|
385
418
|
return { height: head.height, verdict: await vaultVerdict(host, read, head.inbound, claim) };
|
|
386
419
|
} catch (error) {
|
|
387
|
-
if (error instanceof HostUnavailable)
|
|
420
|
+
if (error instanceof HostUnavailable) {
|
|
421
|
+
failures.push(refusalOf(error));
|
|
422
|
+
return { height: head.height, verdict: null };
|
|
423
|
+
}
|
|
388
424
|
throw error;
|
|
389
425
|
}
|
|
390
426
|
} catch (error) {
|
|
391
|
-
if (error instanceof HostUnavailable)
|
|
427
|
+
if (error instanceof HostUnavailable) {
|
|
428
|
+
failures.push(refusalOf(error));
|
|
429
|
+
return null;
|
|
430
|
+
}
|
|
392
431
|
throw error;
|
|
393
432
|
} finally {
|
|
394
433
|
node.clear();
|
|
395
434
|
}
|
|
396
435
|
};
|
|
397
436
|
const routersFor = (chain) => [...THORCHAIN_ROUTERS[chain] ?? [], ...opts.trustedRouters?.[chain] ?? []];
|
|
437
|
+
const { note, reasonOf } = refusalNotes();
|
|
398
438
|
return Object.freeze({
|
|
399
439
|
callTargets: routersFor,
|
|
400
440
|
spenders: routersFor,
|
|
441
|
+
reasonOf,
|
|
401
442
|
// A Tron source is a send to the inbound vault; verifyBuild confirms that vault with two THORNodes (scan 2026-10-28 F9).
|
|
402
443
|
depositAddressRoutes: true,
|
|
403
444
|
async verifyBuild(quote, tx, params) {
|
|
404
|
-
if (!params) return "the request this quote answered is unknown";
|
|
445
|
+
if (!params) return note("the request this quote answered is unknown", "policy_refused");
|
|
405
446
|
const src = ccAsset(params.sourceChain, params.sourceToken);
|
|
406
447
|
const dst = destAsset(params.destChain, params.destToken);
|
|
407
|
-
if (!src || !dst) return "unknown source or destination asset";
|
|
448
|
+
if (!src || !dst) return note("unknown source or destination asset", "unsupported_pair");
|
|
408
449
|
const chainId = CHAIN_ID[params.sourceChain];
|
|
409
450
|
let vault, memo, router;
|
|
410
451
|
if (chainId) {
|
|
411
|
-
if (!tx.data) return "no router calldata";
|
|
452
|
+
if (!tx.data) return note("no router calldata", "policy_refused");
|
|
412
453
|
let args;
|
|
413
454
|
try {
|
|
414
455
|
const d = decodeFunctionData({ abi: ROUTER_ABI, data: tx.data });
|
|
415
|
-
if (d.functionName !== "depositWithExpiry") return `${d.functionName} is not depositWithExpiry
|
|
456
|
+
if (d.functionName !== "depositWithExpiry") return note(`${d.functionName} is not depositWithExpiry`, "policy_refused");
|
|
416
457
|
args = d.args;
|
|
417
458
|
} catch {
|
|
418
|
-
return "the router calldata does not decode";
|
|
459
|
+
return note("the router calldata does not decode", "policy_refused");
|
|
419
460
|
}
|
|
420
|
-
if (!args || args.length !== 5) return "the router calldata does not decode";
|
|
461
|
+
if (!args || args.length !== 5) return note("the router calldata does not decode", "policy_refused");
|
|
421
462
|
router = tx.to;
|
|
422
|
-
if (!routersFor(params.sourceChain).some((r) => sameTarget(r, router))) return `router ${tx.to} is not pinned
|
|
463
|
+
if (!routersFor(params.sourceChain).some((r) => sameTarget(r, router))) return note(`router ${tx.to} is not pinned`, "vault_mismatch");
|
|
423
464
|
vault = String(args[0]);
|
|
424
465
|
const asset = String(args[1]);
|
|
425
|
-
if (!sameTarget(asset, src.contract ?? NATIVE)) return `the router call moves ${asset}, not the source asset
|
|
426
|
-
if (BigInt(args[2]) !== BigInt(quote.sourceAmount)) return `the router call moves ${String(args[2])}, not the ${quote.sourceAmount} input
|
|
466
|
+
if (!sameTarget(asset, src.contract ?? NATIVE)) return note(`the router call moves ${asset}, not the source asset`, "policy_refused");
|
|
467
|
+
if (BigInt(args[2]) !== BigInt(quote.sourceAmount)) return note(`the router call moves ${String(args[2])}, not the ${quote.sourceAmount} input`, "policy_refused");
|
|
427
468
|
memo = String(args[3]);
|
|
428
469
|
} else {
|
|
429
|
-
if (!tx.memo || tx.value !== quote.sourceAmount) return "the deposit is not a send of the input with a memo";
|
|
470
|
+
if (!tx.memo || tx.value !== quote.sourceAmount) return note("the deposit is not a send of the input with a memo", "policy_refused");
|
|
430
471
|
vault = tx.to;
|
|
431
472
|
memo = tx.memo;
|
|
432
473
|
}
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
474
|
+
let memoRefusal = "recipient_mismatch";
|
|
475
|
+
const checked = verifiedMemo(memo, dst.asset, params.destAddress, params.refundAddress ?? "", opts.affiliate ?? "", opts.affiliateBps ?? "", (why) => {
|
|
476
|
+
memoRefusal = why;
|
|
477
|
+
});
|
|
478
|
+
if (!checked) return note(`the memo does not pay ${params.destAddress} in ${dst.asset} with this app's refund and affiliate`, memoRefusal);
|
|
479
|
+
if (memo !== checked.canonical) return note(`the memo is not the canonical ${checked.canonical}`, "policy_refused");
|
|
480
|
+
if (checked.limit * 10n ** BigInt(dst.decimals) / 100000000n < BigInt(quote.minDestAmount)) return note(`the memo's limit ${checked.limit} is below the quoted minimum`, "price_check_failed");
|
|
437
481
|
const sourceChain = THOR_CHAIN[params.sourceChain] ?? params.sourceChain;
|
|
438
482
|
const claim = { chain: sourceChain, destChain: THOR_CHAIN[params.destChain] ?? params.destChain, vault, ...router !== void 0 ? { router } : {} };
|
|
439
|
-
const
|
|
483
|
+
const failures = [];
|
|
484
|
+
const views = await Promise.all(nodes.map((host) => askNode(host, claim, failures)));
|
|
440
485
|
const top = views.reduce((max, v) => v && v.height > max ? v.height : max, -1);
|
|
441
486
|
let agreeing = 0;
|
|
442
487
|
let lagging = false;
|
|
@@ -450,20 +495,20 @@ function thorchainPins(opts = {}) {
|
|
|
450
495
|
if (!v.ok) {
|
|
451
496
|
switch (v.why) {
|
|
452
497
|
case "halted":
|
|
453
|
-
return `${v.chain} is halted / paused
|
|
498
|
+
return note(`${v.chain} is halted / paused`, "chain_halted");
|
|
454
499
|
case "another-vault":
|
|
455
|
-
return `a THORNode reports another inbound vault than ${vault}
|
|
500
|
+
return note(`a THORNode reports another inbound vault than ${vault}`, "vault_mismatch");
|
|
456
501
|
case "not-active":
|
|
457
|
-
return `a THORNode lists the inbound vault as ${v.status}, not ActiveVault
|
|
502
|
+
return note(`a THORNode lists the inbound vault as ${v.status}, not ActiveVault`, "vault_mismatch");
|
|
458
503
|
case "flagged":
|
|
459
|
-
return `a THORNode's mimir flags the inbound vault as ${v.flag}
|
|
504
|
+
return note(`a THORNode's mimir flags the inbound vault as ${v.flag}`, "vault_mismatch");
|
|
460
505
|
case "another-router":
|
|
461
|
-
return "a THORNode reports another router";
|
|
506
|
+
return note("a THORNode reports another router", "vault_mismatch");
|
|
462
507
|
}
|
|
463
508
|
}
|
|
464
509
|
if (++agreeing >= 2) return null;
|
|
465
510
|
}
|
|
466
|
-
return lagging ? `two THORNodes within ${maxLagBlocks} blocks of the highest THORChain height they report could not confirm the inbound vault (behind or unreachable)
|
|
511
|
+
return lagging ? note(`two THORNodes within ${maxLagBlocks} blocks of the highest THORChain height they report could not confirm the inbound vault (behind or unreachable)`, "stale_node") : note("two THORNodes could not confirm the inbound vault", nodesFailed(failures));
|
|
467
512
|
}
|
|
468
513
|
});
|
|
469
514
|
}
|
|
@@ -544,15 +589,15 @@ function thorchainProvider(config = {}) {
|
|
|
544
589
|
const confirmText = (v) => {
|
|
545
590
|
switch (v.why) {
|
|
546
591
|
case "halted":
|
|
547
|
-
return `${v.chain} is halted / paused on the confirming node
|
|
592
|
+
return { text: `${v.chain} is halted / paused on the confirming node`, info: { reason: "chain_halted" } };
|
|
548
593
|
case "another-vault":
|
|
549
|
-
return "a second node reports another inbound vault \u2014 refused until they agree";
|
|
594
|
+
return { text: "a second node reports another inbound vault \u2014 refused until they agree", info: { reason: "vault_mismatch" } };
|
|
550
595
|
case "not-active":
|
|
551
|
-
return `a second node lists the inbound vault as ${v.status}, not ActiveVault \u2014 refused
|
|
596
|
+
return { text: `a second node lists the inbound vault as ${v.status}, not ActiveVault \u2014 refused`, info: { reason: "vault_mismatch" } };
|
|
552
597
|
case "flagged":
|
|
553
|
-
return `a second node's mimir flags the inbound vault as ${v.flag} \u2014 refused
|
|
598
|
+
return { text: `a second node's mimir flags the inbound vault as ${v.flag} \u2014 refused`, info: { reason: "vault_mismatch" } };
|
|
554
599
|
case "another-router":
|
|
555
|
-
return "a second node reports another router \u2014 refused until they agree";
|
|
600
|
+
return { text: "a second node reports another router \u2014 refused until they agree", info: { reason: "vault_mismatch" } };
|
|
556
601
|
}
|
|
557
602
|
};
|
|
558
603
|
const confirmOn = async (host, signal, outer, quoteHeight, claim) => {
|
|
@@ -571,10 +616,14 @@ function thorchainProvider(config = {}) {
|
|
|
571
616
|
};
|
|
572
617
|
let lagging = false;
|
|
573
618
|
let asked = 0;
|
|
619
|
+
const failures = [];
|
|
574
620
|
for (const host of confirmHosts) {
|
|
575
621
|
if (host === quoteHost) continue;
|
|
576
622
|
const budget = Math.min(confirmTimeoutMs, deadlineAt - Date.now());
|
|
577
|
-
if (budget < MIN_CONFIRM_TIMEOUT_MS)
|
|
623
|
+
if (budget < MIN_CONFIRM_TIMEOUT_MS) {
|
|
624
|
+
failures.push({ reason: "upstream_unavailable" });
|
|
625
|
+
break;
|
|
626
|
+
}
|
|
578
627
|
asked++;
|
|
579
628
|
const node = nodeDeadline(budget, outer);
|
|
580
629
|
try {
|
|
@@ -586,19 +635,22 @@ function thorchainProvider(config = {}) {
|
|
|
586
635
|
return verdict;
|
|
587
636
|
} catch (error) {
|
|
588
637
|
if (outer?.aborted) throw error;
|
|
589
|
-
if (error instanceof HostUnavailable)
|
|
638
|
+
if (error instanceof HostUnavailable) {
|
|
639
|
+
failures.push(refusalOf(error));
|
|
640
|
+
continue;
|
|
641
|
+
}
|
|
590
642
|
throw error;
|
|
591
643
|
} finally {
|
|
592
644
|
node.clear();
|
|
593
645
|
}
|
|
594
646
|
}
|
|
595
647
|
if (lagging) {
|
|
596
|
-
return `no confirming node is within ${confirmMaxLagBlocks} blocks of the quoting node's THORChain height (behind or unreachable) \u2014 refused until one catches up
|
|
648
|
+
return { text: `no confirming node is within ${confirmMaxLagBlocks} blocks of the quoting node's THORChain height (behind or unreachable) \u2014 refused until one catches up`, info: { reason: "stale_node" } };
|
|
597
649
|
}
|
|
598
650
|
if (asked === 0) {
|
|
599
|
-
return `the ${CONFIRM_BUDGET_MS / 1e3} s confirmation budget ran out before a second node could be asked to confirm the inbound vault \u2014 retry
|
|
651
|
+
return { text: `the ${CONFIRM_BUDGET_MS / 1e3} s confirmation budget ran out before a second node could be asked to confirm the inbound vault \u2014 retry`, info: { reason: "upstream_unavailable" } };
|
|
600
652
|
}
|
|
601
|
-
return "no second node answered to confirm the inbound vault (configure confirmNodeUrl)";
|
|
653
|
+
return { text: "no second node answered to confirm the inbound vault (configure confirmNodeUrl)", info: nodesFailed(failures) };
|
|
602
654
|
};
|
|
603
655
|
const configuredAffiliate = config.affiliate ?? "";
|
|
604
656
|
const configuredAffiliateBps = config.affiliateBps ?? "";
|
|
@@ -644,24 +696,27 @@ function thorchainProvider(config = {}) {
|
|
|
644
696
|
const fromAsset = thorAsset(params.sourceChain, params.sourceToken);
|
|
645
697
|
const dst = destAsset(params.destChain, params.destToken);
|
|
646
698
|
if (!src || !fromAsset || !dst || fromAsset === dst.asset) {
|
|
647
|
-
params.onRefusal?.(`thorchain: no pool for ${params.sourceToken} on ${params.sourceChain} \u2192 ${params.destToken} on ${params.destChain}
|
|
699
|
+
params.onRefusal?.(`thorchain: no pool for ${params.sourceToken} on ${params.sourceChain} \u2192 ${params.destToken} on ${params.destChain}`, { reason: "unsupported_pair" });
|
|
648
700
|
return null;
|
|
649
701
|
}
|
|
650
702
|
const amountIn = BigInt(params.amountIn);
|
|
651
703
|
const amount1e8 = amountIn * 100000000n / 10n ** BigInt(src.decimals);
|
|
652
704
|
if (amount1e8 <= 0n) return null;
|
|
653
705
|
if (amount1e8 * 10n ** BigInt(src.decimals) !== amountIn * 100000000n) {
|
|
654
|
-
params.onRefusal?.(`thorchain: amount has more precision than THORChain's 8 decimals \u2014 round it to 8 decimals
|
|
706
|
+
params.onRefusal?.(`thorchain: amount has more precision than THORChain's 8 decimals \u2014 round it to 8 decimals`, { reason: "policy_refused" });
|
|
655
707
|
return null;
|
|
656
708
|
}
|
|
657
709
|
const requestedTolerance = params.slippageBps ?? Number(TOLERANCE_BPS);
|
|
658
710
|
if (!Number.isInteger(requestedTolerance) || requestedTolerance < 0 || requestedTolerance > bigintToNumber(MAX_TOLERANCE_BPS)) {
|
|
659
|
-
params.onRefusal?.(`thorchain: slippage ${String(params.slippageBps)} bps is outside 0\u2013${MAX_TOLERANCE_BPS} bps
|
|
711
|
+
params.onRefusal?.(`thorchain: slippage ${String(params.slippageBps)} bps is outside 0\u2013${MAX_TOLERANCE_BPS} bps`, { reason: "policy_refused" });
|
|
660
712
|
return null;
|
|
661
713
|
}
|
|
662
714
|
const explicitSlippage = params.slippageBps !== void 0;
|
|
663
715
|
const liquidityBps = explicitSlippage ? dst.decimals > 8 ? requestedTolerance - 1 : requestedTolerance : 0;
|
|
664
716
|
const limitMode = liquidityBps > 0 ? "liquidity_tolerance_bps" : "tolerance_bps";
|
|
717
|
+
let quoteStatus = 0;
|
|
718
|
+
let quoteBodyless = false;
|
|
719
|
+
let quoteBodyAborted = false;
|
|
665
720
|
const quoteOn = async (host, bps, limitMode2 = "tolerance_bps") => {
|
|
666
721
|
const qs = new URLSearchParams({
|
|
667
722
|
from_asset: fromAsset,
|
|
@@ -682,6 +737,9 @@ function thorchainProvider(config = {}) {
|
|
|
682
737
|
}
|
|
683
738
|
if (params.refundAddress) qs.set("refund_address", params.refundAddress);
|
|
684
739
|
const res = await hostGet(host, `/thorchain/quote/swap?${qs.toString()}`, clientId, signal);
|
|
740
|
+
quoteStatus = res.status;
|
|
741
|
+
quoteBodyless = res.body === null && !res.ok;
|
|
742
|
+
quoteBodyAborted = res.body === null && signal?.aborted === true;
|
|
685
743
|
if (Array.isArray(res.body)) return null;
|
|
686
744
|
return res.body ?? (res.ok ? null : { error: `HTTP ${res.status}` });
|
|
687
745
|
};
|
|
@@ -692,13 +750,14 @@ function thorchainProvider(config = {}) {
|
|
|
692
750
|
let quoteHeight = 0;
|
|
693
751
|
let confirmDeadlineAt = 0;
|
|
694
752
|
let heightUnavailable = false;
|
|
753
|
+
const hostFailures = [];
|
|
695
754
|
for (const host of hosts) {
|
|
696
755
|
try {
|
|
697
756
|
if (limitMode === "liquidity_tolerance_bps") {
|
|
698
757
|
q = await quoteOn(host, String(liquidityBps), "liquidity_tolerance_bps");
|
|
699
758
|
const feeBps = Number(q?.fees?.total_bps);
|
|
700
759
|
if (Number.isFinite(feeBps) && feeBps > bigintToNumber(MAX_TOLERANCE_BPS)) {
|
|
701
|
-
params.onRefusal?.(`thorchain: THORChain fees ${(feeBps / 100).toFixed(2)}% \u2014 too expensive at this amount (cap ${bigintToNumber(MAX_TOLERANCE_BPS) / 100}%)
|
|
760
|
+
params.onRefusal?.(`thorchain: THORChain fees ${(feeBps / 100).toFixed(2)}% \u2014 too expensive at this amount (cap ${bigintToNumber(MAX_TOLERANCE_BPS) / 100}%)`, { reason: "policy_refused" });
|
|
702
761
|
return null;
|
|
703
762
|
}
|
|
704
763
|
} else {
|
|
@@ -715,25 +774,28 @@ function thorchainProvider(config = {}) {
|
|
|
715
774
|
}
|
|
716
775
|
}
|
|
717
776
|
if (q?.error || q?.message) {
|
|
718
|
-
params.onRefusal?.(`thorchain: ${String(q.error ?? q.message).slice(0, 200)}
|
|
777
|
+
params.onRefusal?.(`thorchain: ${String(q.error ?? q.message).slice(0, 200)}`, quoteBodyAborted ? httpRefusal2(quoteStatus) : quoteRejection(String(q.error ?? q.message), quoteStatus, quoteBodyless));
|
|
719
778
|
return null;
|
|
720
779
|
}
|
|
721
780
|
if (!q || !q.expected_amount_out || !q.inbound_address || !q.memo || !q.expiry) {
|
|
722
|
-
params.onRefusal?.(`thorchain: incomplete quote from ${host} (expected_amount_out / inbound_address / memo / expiry missing)
|
|
781
|
+
params.onRefusal?.(`thorchain: incomplete quote from ${host} (expected_amount_out / inbound_address / memo / expiry missing)`, quoteBodyAborted ? void 0 : { reason: "upstream_unavailable", ...quoteStatus >= 300 ? { httpStatus: quoteStatus } : {} });
|
|
723
782
|
return null;
|
|
724
783
|
}
|
|
725
784
|
if (chainId ? !q.router : !!q.router) {
|
|
726
|
-
params.onRefusal?.(chainId ? "thorchain: quote has no router for an EVM source" : "thorchain: quote names a router for a deposit source \u2014 execution model changed");
|
|
785
|
+
params.onRefusal?.(chainId ? "thorchain: quote has no router for an EVM source" : "thorchain: quote names a router for a deposit source \u2014 execution model changed", { reason: chainId ? "upstream_unavailable" : "policy_refused" });
|
|
727
786
|
return null;
|
|
728
787
|
}
|
|
729
|
-
|
|
788
|
+
let memoRefusal = "recipient_mismatch";
|
|
789
|
+
checkedMemo = verifiedMemo(q.memo, dst.asset, params.destAddress, params.refundAddress ?? "", configuredAffiliate, configuredAffiliateBps, (why) => {
|
|
790
|
+
memoRefusal = why;
|
|
791
|
+
});
|
|
730
792
|
if (!checkedMemo) {
|
|
731
|
-
params.onRefusal?.(`thorchain: memo failed verification (${String(q.memo).slice(0, 80)})
|
|
793
|
+
params.onRefusal?.(`thorchain: memo failed verification (${String(q.memo).slice(0, 80)})`, { reason: memoRefusal });
|
|
732
794
|
return null;
|
|
733
795
|
}
|
|
734
796
|
const floor1e8 = params.minAmountOut !== void 0 ? toThorUnitsCeil(BigInt(params.minAmountOut), dst.decimals) : void 0;
|
|
735
797
|
if (floor1e8 !== void 0 && checkedMemo.limit < floor1e8) {
|
|
736
|
-
params.onRefusal?.(`thorchain: floor breach (limit=${checkedMemo.limit} < floor=${floor1e8}, 1e8 units)
|
|
798
|
+
params.onRefusal?.(`thorchain: floor breach (limit=${checkedMemo.limit} < floor=${floor1e8}, 1e8 units)`, { reason: "price_check_failed" });
|
|
737
799
|
return null;
|
|
738
800
|
}
|
|
739
801
|
const deadlineAt = Date.now() + CONFIRM_BUDGET_MS;
|
|
@@ -749,6 +811,7 @@ function thorchainProvider(config = {}) {
|
|
|
749
811
|
break;
|
|
750
812
|
} catch (error) {
|
|
751
813
|
if (error instanceof HostUnavailable) {
|
|
814
|
+
hostFailures.push(refusalOf(error));
|
|
752
815
|
q = null;
|
|
753
816
|
checkedMemo = null;
|
|
754
817
|
inbounds = null;
|
|
@@ -759,7 +822,8 @@ function thorchainProvider(config = {}) {
|
|
|
759
822
|
}
|
|
760
823
|
if (!q?.expected_amount_out || !q.inbound_address || !q.expiry || !checkedMemo || !inbounds) {
|
|
761
824
|
params.onRefusal?.(
|
|
762
|
-
inbounds ? "thorchain: no quote from any node" : heightUnavailable ? "thorchain: THORChain height (/thorchain/lastblock) unavailable from every node that answered the inbound addresses" : "thorchain: inbound addresses unavailable from every node"
|
|
825
|
+
inbounds ? "thorchain: no quote from any node" : heightUnavailable ? "thorchain: THORChain height (/thorchain/lastblock) unavailable from every node that answered the inbound addresses" : "thorchain: inbound addresses unavailable from every node",
|
|
826
|
+
inbounds ? { reason: "no_route" } : nodesFailed(hostFailures)
|
|
763
827
|
);
|
|
764
828
|
return null;
|
|
765
829
|
}
|
|
@@ -767,19 +831,19 @@ function thorchainProvider(config = {}) {
|
|
|
767
831
|
const srcInbound = inbounds.find((entry) => entry.chain === THOR_CHAIN[params.sourceChain]);
|
|
768
832
|
const dstInbound = inbounds.find((entry) => entry.chain === THOR_CHAIN[params.destChain]);
|
|
769
833
|
if (paused(srcInbound) || paused(dstInbound)) {
|
|
770
|
-
params.onRefusal?.(`thorchain: ${paused(srcInbound) ? THOR_CHAIN[params.sourceChain] : THOR_CHAIN[params.destChain]} chain is halted / paused on THORChain
|
|
834
|
+
params.onRefusal?.(`thorchain: ${paused(srcInbound) ? THOR_CHAIN[params.sourceChain] : THOR_CHAIN[params.destChain]} chain is halted / paused on THORChain`, { reason: "chain_halted" });
|
|
771
835
|
return null;
|
|
772
836
|
}
|
|
773
837
|
if (!sameTarget(srcInbound?.address, q.inbound_address)) {
|
|
774
|
-
params.onRefusal?.("thorchain: quote inbound address differs from the node's current inbound (vault rotated) \u2014 retry");
|
|
838
|
+
params.onRefusal?.("thorchain: quote inbound address differs from the node's current inbound (vault rotated) \u2014 retry", { reason: "vault_mismatch" });
|
|
775
839
|
return null;
|
|
776
840
|
}
|
|
777
841
|
if (chainId && !sameTarget(srcInbound?.router, q.router)) {
|
|
778
|
-
params.onRefusal?.("thorchain: quote router differs from the node's current router \u2014 retry");
|
|
842
|
+
params.onRefusal?.("thorchain: quote router differs from the node's current router \u2014 retry", { reason: "vault_mismatch" });
|
|
779
843
|
return null;
|
|
780
844
|
}
|
|
781
845
|
if (chainId && !pinnedRouter(params.sourceChain, q.router)) {
|
|
782
|
-
params.onRefusal?.(`thorchain: router ${String(q.router)} is not a THORChain router this module trusts on ${params.sourceChain} (THORCHAIN_ROUTERS / trustedRouters)
|
|
846
|
+
params.onRefusal?.(`thorchain: router ${String(q.router)} is not a THORChain router this module trusts on ${params.sourceChain} (THORCHAIN_ROUTERS / trustedRouters)`, { reason: "vault_mismatch" });
|
|
783
847
|
return null;
|
|
784
848
|
}
|
|
785
849
|
const unconfirmed = await confirmedElsewhere(
|
|
@@ -793,36 +857,36 @@ function thorchainProvider(config = {}) {
|
|
|
793
857
|
signal
|
|
794
858
|
);
|
|
795
859
|
if (unconfirmed) {
|
|
796
|
-
params.onRefusal?.(`thorchain: ${unconfirmed}
|
|
860
|
+
params.onRefusal?.(`thorchain: ${unconfirmed.text}`, unconfirmed.info);
|
|
797
861
|
return null;
|
|
798
862
|
}
|
|
799
863
|
const tooSmall = belowThorMinimums(amount1e8, srcInbound, q);
|
|
800
864
|
if (tooSmall) {
|
|
801
|
-
params.onRefusal?.(`thorchain: ${tooSmall}
|
|
865
|
+
params.onRefusal?.(`thorchain: ${tooSmall}`, { reason: "amount_too_small" });
|
|
802
866
|
return null;
|
|
803
867
|
}
|
|
804
868
|
const outUnits = BigInt(q.expected_amount_out) * 10n ** BigInt(dst.decimals) / 100000000n;
|
|
805
869
|
if (outUnits <= 0n) {
|
|
806
|
-
params.onRefusal?.("thorchain: expected output rounds to zero");
|
|
870
|
+
params.onRefusal?.("thorchain: expected output rounds to zero", { reason: "amount_too_small" });
|
|
807
871
|
return null;
|
|
808
872
|
}
|
|
809
873
|
const memoMinUnits = limit1e8 * 10n ** BigInt(dst.decimals) / 100000000n;
|
|
810
874
|
const minUnits = memoMinUnits;
|
|
811
875
|
if (minUnits <= 0n || minUnits > outUnits) {
|
|
812
|
-
params.onRefusal?.(`thorchain: memo limit ${limit1e8} inconsistent with expected output ${q.expected_amount_out}
|
|
876
|
+
params.onRefusal?.(`thorchain: memo limit ${limit1e8} inconsistent with expected output ${q.expected_amount_out}`, { reason: "price_check_failed" });
|
|
813
877
|
return null;
|
|
814
878
|
}
|
|
815
879
|
if (params.slippageBps !== void 0) {
|
|
816
880
|
const slippageFloor = outUnits * BigInt(1e4 - params.slippageBps) / 10000n;
|
|
817
881
|
if (minUnits < slippageFloor) {
|
|
818
|
-
params.onRefusal?.(`thorchain: memo limit ${limit1e8} is below expected ${q.expected_amount_out} minus ${params.slippageBps} bps slippage (1e8 units)
|
|
882
|
+
params.onRefusal?.(`thorchain: memo limit ${limit1e8} is below expected ${q.expected_amount_out} minus ${params.slippageBps} bps slippage (1e8 units)`, { reason: "price_check_failed" });
|
|
819
883
|
return null;
|
|
820
884
|
}
|
|
821
885
|
}
|
|
822
886
|
const nowSeconds = Math.floor(Date.now() / 1e3);
|
|
823
887
|
const executionExpiry = Math.min(q.expiry, nowSeconds + 15 * 60);
|
|
824
888
|
if (!Number.isInteger(executionExpiry) || executionExpiry <= nowSeconds + 30) {
|
|
825
|
-
params.onRefusal?.(`thorchain: quote expires in ${Number.isInteger(executionExpiry) ? executionExpiry - nowSeconds : "?"} s \u2014 too soon to execute
|
|
889
|
+
params.onRefusal?.(`thorchain: quote expires in ${Number.isInteger(executionExpiry) ? executionExpiry - nowSeconds : "?"} s \u2014 too soon to execute`, { reason: "quote_expired" });
|
|
826
890
|
return null;
|
|
827
891
|
}
|
|
828
892
|
const assetAddr = src.contract ? src.contract : NATIVE;
|
|
@@ -878,7 +942,7 @@ function thorchainProvider(config = {}) {
|
|
|
878
942
|
if (!quote.txRequest) {
|
|
879
943
|
const sourceChain = quote.providerData?.sourceChain ?? "";
|
|
880
944
|
if (!isDepositSource(sourceChain) || !quote.depositAddress || !quote.depositMemo) {
|
|
881
|
-
throw
|
|
945
|
+
throw refusal("unsupported_pair", "thorchain: deposit-address execution is only supported for an opted-in Tron source");
|
|
882
946
|
}
|
|
883
947
|
return { chain: sourceChain, to: quote.depositAddress, value: quote.sourceAmount, memo: quote.depositMemo };
|
|
884
948
|
}
|
|
@@ -895,26 +959,28 @@ function thorchainProvider(config = {}) {
|
|
|
895
959
|
async preflightDeposit(quote, tx) {
|
|
896
960
|
const data = quote.providerData;
|
|
897
961
|
if (!data?.sourceChain || !data.destChain || !data.inboundAddress || !data.assetAddress || !data.expiry || !data.memo) {
|
|
898
|
-
throw
|
|
962
|
+
throw refusal("policy_refused", "thorchain: quote is missing execution-bound metadata");
|
|
899
963
|
}
|
|
900
964
|
const evmSource = CHAIN_ID[data.sourceChain] !== void 0;
|
|
901
|
-
if (evmSource && !data.router) throw
|
|
902
|
-
if (!evmSource && !isDepositSource(data.sourceChain)) throw
|
|
965
|
+
if (evmSource && !data.router) throw refusal("policy_refused", "thorchain: quote is missing execution-bound metadata");
|
|
966
|
+
if (!evmSource && !isDepositSource(data.sourceChain)) throw refusal("unsupported_pair", "thorchain: source chain has no executor");
|
|
903
967
|
if (!/^\d+$/.test(data.expiry) || BigInt(data.expiry) <= BigInt(Math.floor(Date.now() / 1e3))) {
|
|
904
|
-
throw
|
|
968
|
+
throw refusal("quote_expired", "thorchain: quote expired before broadcast");
|
|
905
969
|
}
|
|
906
|
-
if (evmSource && !pinnedRouter(data.sourceChain, data.router)) throw
|
|
970
|
+
if (evmSource && !pinnedRouter(data.sourceChain, data.router)) throw refusal("vault_mismatch", "thorchain: the quote's router is not one this module trusts");
|
|
907
971
|
let inbounds = null;
|
|
908
972
|
let snapshotHost = "";
|
|
909
973
|
let snapshotHeight = 0;
|
|
910
974
|
let confirmDeadlineAt = 0;
|
|
911
975
|
let heightUnavailable = false;
|
|
976
|
+
const hostFailures = [];
|
|
912
977
|
for (const host of hosts) {
|
|
913
978
|
const deadlineAt = Date.now() + CONFIRM_BUDGET_MS;
|
|
914
979
|
try {
|
|
915
980
|
const snapshot = await nodeSnapshot(host);
|
|
916
981
|
if ("heightError" in snapshot) {
|
|
917
982
|
heightUnavailable = true;
|
|
983
|
+
hostFailures.push(refusalOf(snapshot.heightError));
|
|
918
984
|
continue;
|
|
919
985
|
}
|
|
920
986
|
inbounds = snapshot.inbounds;
|
|
@@ -925,12 +991,13 @@ function thorchainProvider(config = {}) {
|
|
|
925
991
|
} catch (error) {
|
|
926
992
|
inbounds = null;
|
|
927
993
|
if (!(error instanceof HostUnavailable)) throw error;
|
|
994
|
+
hostFailures.push(refusalOf(error));
|
|
928
995
|
}
|
|
929
996
|
}
|
|
930
997
|
if (!inbounds) {
|
|
931
|
-
throw new Error(
|
|
998
|
+
throw withReason2(new Error(
|
|
932
999
|
heightUnavailable ? "thorchain: cannot read THORChain height (/thorchain/lastblock) before broadcast from any node that answered the inbound addresses" : "thorchain: cannot refresh inbound addresses before broadcast"
|
|
933
|
-
);
|
|
1000
|
+
), nodesFailed(hostFailures));
|
|
934
1001
|
}
|
|
935
1002
|
const confirm = async () => {
|
|
936
1003
|
const unconfirmed = await confirmedElsewhere(
|
|
@@ -942,44 +1009,46 @@ function thorchainProvider(config = {}) {
|
|
|
942
1009
|
evmSource ? data.router : void 0,
|
|
943
1010
|
confirmDeadlineAt
|
|
944
1011
|
);
|
|
945
|
-
if (unconfirmed) throw new Error(`thorchain: ${unconfirmed}`);
|
|
1012
|
+
if (unconfirmed) throw withReason2(new Error(`thorchain: ${unconfirmed.text}`), unconfirmed.info);
|
|
946
1013
|
};
|
|
947
1014
|
const src = inbounds.find((entry) => entry.chain === THOR_CHAIN[data.sourceChain]);
|
|
948
1015
|
const dst = inbounds.find((entry) => entry.chain === THOR_CHAIN[data.destChain]);
|
|
949
|
-
if (paused(src) || paused(dst)) throw
|
|
950
|
-
if (!sameTarget(src?.address, data.inboundAddress)) throw
|
|
1016
|
+
if (paused(src) || paused(dst)) throw refusal("chain_halted", "thorchain: source or destination chain trading is paused");
|
|
1017
|
+
if (!sameTarget(src?.address, data.inboundAddress)) throw refusal("vault_mismatch", "thorchain: inbound vault rotated after quote");
|
|
951
1018
|
if (!evmSource) {
|
|
952
1019
|
if (tx.data !== void 0 || tx.approvalAddress !== void 0 || tx.bodyBoc !== void 0 || tx.typedData !== void 0 || tx.serializedTx !== void 0 || tx.stateInitBoc !== void 0) {
|
|
953
|
-
throw
|
|
1020
|
+
throw refusal("policy_refused", "thorchain: deposit-address execution must be a plain send with memo");
|
|
954
1021
|
}
|
|
955
1022
|
if (!sameTarget(tx.to, data.inboundAddress) || tx.memo !== data.memo || tx.value !== quote.sourceAmount || tx.chain && tx.chain !== data.sourceChain) {
|
|
956
|
-
throw
|
|
1023
|
+
throw refusal(!sameTarget(tx.to, data.inboundAddress) ? "vault_mismatch" : "policy_refused", "thorchain: deposit does not match the validated quote");
|
|
957
1024
|
}
|
|
958
1025
|
const srcAsset = ccAssetByToken(data.sourceChain, sameTarget(data.assetAddress, NATIVE) ? "" : data.assetAddress);
|
|
959
|
-
if (!srcAsset) throw
|
|
1026
|
+
if (!srcAsset) throw refusal("unsupported_pair", "thorchain: unknown source asset");
|
|
960
1027
|
const amount1e8 = BigInt(quote.sourceAmount) * 100000000n / 10n ** BigInt(srcAsset.decimals);
|
|
961
1028
|
const tooSmall = belowThorMinimums(amount1e8, src, null);
|
|
962
|
-
if (tooSmall) throw
|
|
1029
|
+
if (tooSmall) throw refusal("amount_too_small", `thorchain: ${tooSmall}`);
|
|
963
1030
|
await confirm();
|
|
964
1031
|
return;
|
|
965
1032
|
}
|
|
966
1033
|
if (!sameTarget(src?.router, data.router) || !sameTarget(tx.to, data.router)) {
|
|
967
|
-
throw
|
|
1034
|
+
throw refusal("vault_mismatch", "thorchain: router rotated after quote");
|
|
968
1035
|
}
|
|
969
|
-
if (!tx.data) throw
|
|
1036
|
+
if (!tx.data) throw refusal("policy_refused", "thorchain: missing depositWithExpiry calldata");
|
|
970
1037
|
let decoded;
|
|
971
1038
|
try {
|
|
972
1039
|
decoded = decodeFunctionData({ abi: ROUTER_ABI, data: tx.data });
|
|
973
1040
|
} catch {
|
|
974
|
-
throw
|
|
1041
|
+
throw refusal("policy_refused", "thorchain: malformed depositWithExpiry calldata");
|
|
975
1042
|
}
|
|
976
1043
|
const args = decoded.args;
|
|
977
1044
|
if (decoded.functionName !== "depositWithExpiry" || !args || args.length !== 5 || !sameTarget(String(args[0]), data.inboundAddress) || !sameTarget(String(args[1]), data.assetAddress) || BigInt(args[2]) !== BigInt(quote.sourceAmount) || String(args[3]) !== data.memo || BigInt(args[4]) !== BigInt(data.expiry) || tx.chainId !== CHAIN_ID[data.sourceChain]) {
|
|
978
|
-
|
|
1045
|
+
const vaultChanged = decoded.functionName === "depositWithExpiry" && !!args && args.length === 5 && !sameTarget(String(args[0]), data.inboundAddress);
|
|
1046
|
+
throw refusal(vaultChanged ? "vault_mismatch" : "policy_refused", "thorchain: calldata does not match the validated quote");
|
|
979
1047
|
}
|
|
980
1048
|
const tokenSource = !sameTarget(data.assetAddress, NATIVE);
|
|
981
1049
|
if (tx.value !== (tokenSource ? "0" : quote.sourceAmount) || (tokenSource ? !sameTarget(tx.approvalAddress, data.router) : tx.approvalAddress !== void 0)) {
|
|
982
|
-
|
|
1050
|
+
const spenderChanged = tokenSource && tx.approvalAddress !== void 0 && !sameTarget(tx.approvalAddress, data.router);
|
|
1051
|
+
throw refusal(spenderChanged ? "vault_mismatch" : "policy_refused", "thorchain: value/approval does not match the validated source asset");
|
|
983
1052
|
}
|
|
984
1053
|
await confirm();
|
|
985
1054
|
},
|
|
@@ -1038,4 +1107,4 @@ export {
|
|
|
1038
1107
|
thorchainPins,
|
|
1039
1108
|
thorchainProvider
|
|
1040
1109
|
};
|
|
1041
|
-
//# sourceMappingURL=chunk-
|
|
1110
|
+
//# sourceMappingURL=chunk-EIAXGWNG.js.map
|