@anywalletkit/protocol-swap2-thorchain 0.0.0-stage → 0.1.3

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.
@@ -0,0 +1,565 @@
1
+ import {
2
+ describeThorchainActivity
3
+ } from "./chunk-DIQDMNP7.js";
4
+ import {
5
+ thorchain
6
+ } from "./chunk-X6XXFQ2M.js";
7
+
8
+ // src/provider.ts
9
+ import { decodeFunctionData, encodeFunctionData } from "viem";
10
+ import { timeoutSignal } from "@anywalletkit/protocol-swap2-core/util";
11
+ import { ccAsset, ccAssetByToken } from "@anywalletkit/swap2-assets";
12
+ var HOSTS = [
13
+ "https://gateway.liquify.com/chain/thorchain_api",
14
+ "https://thornode.thorchain.network"
15
+ ];
16
+ var DEFAULT_CLIENT_ID = "anywalletkit";
17
+ var NATIVE = "0x0000000000000000000000000000000000000000";
18
+ var TOLERANCE_BPS = "300";
19
+ var MAX_TOLERANCE_BPS = 500n;
20
+ var QUOTE_VALIDITY_MS = 5 * 6e4;
21
+ var THOR_CHAIN = {
22
+ "eip155:1": "ETH",
23
+ "eip155:8453": "BASE",
24
+ "eip155:56": "BSC",
25
+ // 2026-09-16: AVAX.AVAX/USDC/USDT pools Available (≈$2.5M/$0.3M/$0.2M),
26
+ // router 0x00dc61…f1d4 live in inbound_addresses; same depositWithExpiry ABI.
27
+ // Polygon has no THORChain pool any more — deliberately absent.
28
+ "eip155:43114": "AVAX",
29
+ "tron:728126428": "TRON",
30
+ "bitcoin:mainnet": "BTC",
31
+ // 2026-08 (pools verified live via the Liquify gateway): THORChain added
32
+ // SOL/XRP, and our catalog now carries LTC/DOGE/BCH/ATOM — destination legs
33
+ // for the step-2 off-ramp (source stays EVM/Tron, see supportsRoute).
34
+ "solana:mainnet": "SOL",
35
+ "xrp:mainnet": "XRP",
36
+ "litecoin:mainnet": "LTC",
37
+ "doge:mainnet": "DOGE",
38
+ "bch:mainnet": "BCH",
39
+ "cosmos:cosmoshub-4": "GAIA"
40
+ };
41
+ var CHAIN_ID = { "eip155:1": 1, "eip155:8453": 8453, "eip155:56": 56, "eip155:43114": 43114 };
42
+ var TRON_CAIP2 = "tron:728126428";
43
+ var DEPOSIT_SOURCES = /* @__PURE__ */ new Set([TRON_CAIP2]);
44
+ var NATIVE_ASSET = {
45
+ ETH: "ETH.ETH",
46
+ BASE: "BASE.ETH",
47
+ BSC: "BSC.BNB",
48
+ AVAX: "AVAX.AVAX",
49
+ TRON: "TRON.TRX",
50
+ BTC: "BTC.BTC",
51
+ SOL: "SOL.SOL",
52
+ XRP: "XRP.XRP",
53
+ LTC: "LTC.LTC",
54
+ DOGE: "DOGE.DOGE",
55
+ BCH: "BCH.BCH",
56
+ GAIA: "GAIA.ATOM"
57
+ };
58
+ var TOKEN_ASSET = {
59
+ "ETH:USDC": "ETH.USDC-0XA0B86991C6218B36C1D19D4A2E9EB0CE3606EB48",
60
+ "ETH:USDT": "ETH.USDT-0XDAC17F958D2EE523A2206206994597C13D831EC7",
61
+ "BASE:USDC": "BASE.USDC-0X833589FCD6EDB6E08F4C7C32D4F71B54BDA02913",
62
+ "BSC:USDC": "BSC.USDC-0X8AC76A51CC950D9822D68B83FE1AD97B32CD580D",
63
+ "BSC:USDT": "BSC.USDT-0X55D398326F99059FF775485246999027B3197955",
64
+ "AVAX:USDC": "AVAX.USDC-0XB97EF9EF8734C71904D8002F8B6BC66DD9C48A6E",
65
+ "AVAX:USDT": "AVAX.USDT-0X9702230A8EA53601F5CD2DC00FDBC13D4DF4A8C7",
66
+ "TRON:USDT": "TRON.USDT-TR7NHQJEKQXGTCI8Q8ZY4PL8OTSZGJLJ6T"
67
+ };
68
+ var ROUTER_ABI = [
69
+ {
70
+ type: "function",
71
+ name: "depositWithExpiry",
72
+ stateMutability: "payable",
73
+ inputs: [
74
+ { name: "vault", type: "address" },
75
+ { name: "asset", type: "address" },
76
+ { name: "amount", type: "uint256" },
77
+ { name: "memo", type: "string" },
78
+ { name: "expiration", type: "uint256" }
79
+ ],
80
+ outputs: []
81
+ }
82
+ ];
83
+ function isEvm(caip2) {
84
+ return caip2.startsWith("eip155:");
85
+ }
86
+ function thorAsset(caip2, symbol) {
87
+ const chain = THOR_CHAIN[caip2];
88
+ if (!chain) return void 0;
89
+ const sym = symbol.toUpperCase();
90
+ const token = TOKEN_ASSET[`${chain}:${sym}`];
91
+ if (token) return token;
92
+ const native = NATIVE_ASSET[chain];
93
+ return native && native.split(".")[1] === sym ? native : void 0;
94
+ }
95
+ function destAsset(destChain, destToken) {
96
+ const a = ccAssetByToken(destChain, destToken);
97
+ if (!a) return void 0;
98
+ const asset = thorAsset(destChain, a.symbol);
99
+ return asset ? { asset, decimals: a.decimals } : void 0;
100
+ }
101
+ var HostUnavailable = class extends Error {
102
+ constructor(host, reason) {
103
+ super(`thorchain: ${host} unavailable (${reason})`);
104
+ }
105
+ };
106
+ async function hostGet(host, path, clientId, signal) {
107
+ let res;
108
+ try {
109
+ res = await fetch(`${host}${path}`, {
110
+ headers: { accept: "application/json", "x-client-id": clientId },
111
+ cache: "no-store",
112
+ signal: signal ?? timeoutSignal(8e3)
113
+ });
114
+ } catch (error) {
115
+ if (signal?.aborted) throw error;
116
+ throw new HostUnavailable(host, error instanceof Error ? error.message : "fetch failed");
117
+ }
118
+ if (res.status >= 500 || res.status === 429 || res.status === 403) {
119
+ throw new HostUnavailable(host, `HTTP ${res.status}`);
120
+ }
121
+ const body = await res.json().catch(() => null);
122
+ return { ok: res.ok, status: res.status, body };
123
+ }
124
+ async function thornodeGet(path, signal, hosts, clientId) {
125
+ for (const host of hosts) {
126
+ try {
127
+ const res = await hostGet(host, path, clientId, signal);
128
+ if (!res.ok) continue;
129
+ return Array.isArray(res.body) ? null : res.body;
130
+ } catch (error) {
131
+ if (error instanceof HostUnavailable) continue;
132
+ throw error;
133
+ }
134
+ }
135
+ return null;
136
+ }
137
+ function belowThorMinimums(amount1e8, inbound, quote) {
138
+ const dust = inbound?.dust_threshold;
139
+ if (dust !== void 0 && (!/^\d+$/.test(String(dust)) || amount1e8 <= BigInt(dust))) return "amount at or below THORChain dust threshold";
140
+ const min = quote?.recommended_min_amount_in;
141
+ if (min !== void 0 && (!/^\d+$/.test(String(min)) || amount1e8 < BigInt(min))) return "amount below THORChain recommended minimum";
142
+ return null;
143
+ }
144
+ async function inboundAddressesOn(host, clientId) {
145
+ const res = await hostGet(host, "/thorchain/inbound_addresses", clientId);
146
+ if (!res.ok || !Array.isArray(res.body)) throw new HostUnavailable(host, `inbound_addresses HTTP ${res.status}`);
147
+ return res.body;
148
+ }
149
+ async function inboundAddresses(hosts, clientId) {
150
+ for (const host of hosts) {
151
+ try {
152
+ return await inboundAddressesOn(host, clientId);
153
+ } catch (error) {
154
+ if (error instanceof HostUnavailable) continue;
155
+ throw error;
156
+ }
157
+ }
158
+ return null;
159
+ }
160
+ function paused(entry) {
161
+ return !entry || entry.halted === true || entry.global_trading_paused === true || entry.chain_trading_paused === true;
162
+ }
163
+ function sameTarget(a, b) {
164
+ if (!a || !b) return false;
165
+ return a.startsWith("0x") || b.startsWith("0x") ? a.toLowerCase() === b.toLowerCase() : a === b;
166
+ }
167
+ function verifiedMemo(memo, expectedAsset, expectedDestination, expectedRefund, expectedAffiliate, expectedAffiliateBps) {
168
+ if (memo.includes("|")) return null;
169
+ const parts = memo.split(":");
170
+ if (parts.length !== 4 && parts.length !== 6) return null;
171
+ const [op, asset, destination, limit, affiliate, bps] = parts;
172
+ if (op !== "=" || !asset || !/^[A-Za-z0-9._~\-]+$/.test(asset)) return null;
173
+ const expectedTarget = expectedRefund ? `${expectedDestination}/${expectedRefund}` : expectedDestination;
174
+ if (!sameTarget(destination, expectedTarget)) return null;
175
+ if (parts.length === 6) {
176
+ if (!expectedAffiliate || affiliate !== expectedAffiliate || bps !== expectedAffiliateBps) return null;
177
+ } else if (expectedAffiliate || expectedAffiliateBps) {
178
+ return null;
179
+ }
180
+ const stream = (limit ?? "").split("/");
181
+ if (stream.length !== 1 || stream.some((v) => !/^\d+$/.test(v))) return null;
182
+ const floor = BigInt(stream[0] ?? "0");
183
+ if (floor <= 0n) return null;
184
+ const canonical = `=:${expectedAsset}:${expectedTarget}:${stream.join("/")}${expectedAffiliate ? `:${expectedAffiliate}:${expectedAffiliateBps}` : ""}`;
185
+ return { limit: floor, canonical };
186
+ }
187
+ function toThorUnitsCeil(amount, decimals) {
188
+ const scale = 10n ** BigInt(decimals);
189
+ return (amount * 100000000n + scale - 1n) / scale;
190
+ }
191
+ function thorchainProvider(config = {}) {
192
+ config = Object.freeze({ ...config });
193
+ const configuredHosts = (Array.isArray(config.nodeUrl) ? config.nodeUrl : (config.nodeUrl ?? "").split(",")).map((h) => h.trim()).filter(Boolean);
194
+ const hosts = [...new Set((configuredHosts.length ? configuredHosts : HOSTS).map((h) => {
195
+ let url;
196
+ try {
197
+ url = new URL(h);
198
+ } catch {
199
+ throw new Error("thorchain: nodeUrl must be an absolute http(s) URL");
200
+ }
201
+ if (url.protocol !== "https:" && url.protocol !== "http:") throw new Error("thorchain: nodeUrl must use http or https");
202
+ return url.toString().replace(/\/$/, "");
203
+ }))];
204
+ const configuredAffiliate = config.affiliate ?? "";
205
+ const configuredAffiliateBps = config.affiliateBps ?? "";
206
+ if (!!configuredAffiliate !== !!configuredAffiliateBps) {
207
+ throw new Error("thorchain: affiliate and affiliateBps must be configured together");
208
+ }
209
+ if (configuredAffiliate && !/^[A-Za-z0-9._~-]+$/.test(configuredAffiliate)) {
210
+ throw new Error("thorchain: affiliate contains unsupported memo characters");
211
+ }
212
+ if (configuredAffiliateBps && (!/^\d+$/.test(configuredAffiliateBps) || BigInt(configuredAffiliateBps) > 10000n)) {
213
+ throw new Error("thorchain: affiliateBps must be an integer between 0 and 10000");
214
+ }
215
+ const clientId = config.clientId?.trim() || DEFAULT_CLIENT_ID;
216
+ if (!/^[\x21-\x7e]{1,128}$/.test(clientId)) throw new Error("thorchain: clientId must contain 1-128 visible ASCII characters");
217
+ const tronSource = config.tronSource === true;
218
+ const isDepositSource = (caip2) => tronSource && DEPOSIT_SOURCES.has(caip2);
219
+ return {
220
+ name: "thorchain",
221
+ describeActivity: describeThorchainActivity,
222
+ enabled: () => config.enabled ?? true,
223
+ // Capability discovery comes from the provider itself, so it can run alone
224
+ // or inside the optional aggregator without a central capability table.
225
+ supportsChain: (chain) => thorchain.supportsChain(chain),
226
+ supportsChainPair: (fromChain, toChain) => thorchain.supports(fromChain, toChain),
227
+ // Inbound: EVM/Tron source → the mainnet vault. Outbound (step-2 off-ramp):
228
+ // EVM source (Ethereum) → any THOR-supported chain. The SOURCE must be EVM
229
+ // (router calldata) or, when opted in, Tron (send + memo); both legs must
230
+ // be THOR chains and differ.
231
+ supportsRoute(from, to) {
232
+ const srcExec = isEvm(from.chain) || isDepositSource(from.chain);
233
+ if (!(srcExec && !!THOR_CHAIN[from.chain] && !!THOR_CHAIN[to.chain] && from.chain !== to.chain)) return false;
234
+ const fromAsset = thorAsset(from.chain, from.token) ?? thorAsset(from.chain, ccAssetByToken(from.chain, from.token)?.symbol ?? "");
235
+ const toAsset = destAsset(to.chain, to.token) ?? (thorAsset(to.chain, to.token) ? { asset: thorAsset(to.chain, to.token) } : void 0);
236
+ return !!fromAsset && !!toAsset && fromAsset !== toAsset.asset;
237
+ },
238
+ async getQuote(params, signal) {
239
+ if (!(config.enabled ?? true)) return null;
240
+ if (params.flex) return null;
241
+ const chainId = CHAIN_ID[params.sourceChain];
242
+ if (!chainId && !isDepositSource(params.sourceChain)) return null;
243
+ if (params.sourceChain === params.destChain) return null;
244
+ const src = ccAsset(params.sourceChain, params.sourceToken);
245
+ const fromAsset = thorAsset(params.sourceChain, params.sourceToken);
246
+ const dst = destAsset(params.destChain, params.destToken);
247
+ if (!src || !fromAsset || !dst || fromAsset === dst.asset) {
248
+ params.onRefusal?.(`thorchain: no pool for ${params.sourceToken} on ${params.sourceChain} \u2192 ${params.destToken} on ${params.destChain}`);
249
+ return null;
250
+ }
251
+ const amountIn = BigInt(params.amountIn);
252
+ const amount1e8 = amountIn * 100000000n / 10n ** BigInt(src.decimals);
253
+ if (amount1e8 <= 0n) return null;
254
+ if (amount1e8 * 10n ** BigInt(src.decimals) !== amountIn * 100000000n) {
255
+ params.onRefusal?.(`thorchain: amount has more precision than THORChain's 8 decimals \u2014 round it to 8 decimals`);
256
+ return null;
257
+ }
258
+ const requestedTolerance = params.slippageBps ?? Number(TOLERANCE_BPS);
259
+ if (!Number.isInteger(requestedTolerance) || requestedTolerance < 0 || requestedTolerance > Number(MAX_TOLERANCE_BPS)) {
260
+ params.onRefusal?.(`thorchain: slippage ${String(params.slippageBps)} bps is outside 0\u2013${MAX_TOLERANCE_BPS} bps`);
261
+ return null;
262
+ }
263
+ const explicitSlippage = params.slippageBps !== void 0;
264
+ const liquidityBps = explicitSlippage ? dst.decimals > 8 ? requestedTolerance - 1 : requestedTolerance : 0;
265
+ const limitMode = liquidityBps > 0 ? "liquidity_tolerance_bps" : "tolerance_bps";
266
+ const quoteOn = async (host, bps, limitMode2 = "tolerance_bps") => {
267
+ const qs = new URLSearchParams({
268
+ from_asset: fromAsset,
269
+ to_asset: dst.asset,
270
+ amount: amount1e8.toString(),
271
+ destination: params.destAddress,
272
+ [limitMode2]: bps,
273
+ // Pin a single (non-streaming) swap. Left to itself the node picks
274
+ // streaming for thin pools (max_streaming_quantity > 1) and then
275
+ // simulates the LIM per sub-swap — the adaptive retry could never
276
+ // pass on BASE.ETH / BSC stables (verified 2026-09-16). We reject
277
+ // streaming memos anyway, so the quote must be simulated atomically.
278
+ streaming_quantity: "1"
279
+ });
280
+ if (configuredAffiliate && configuredAffiliateBps) {
281
+ qs.set("affiliate", configuredAffiliate);
282
+ qs.set("affiliate_bps", configuredAffiliateBps);
283
+ }
284
+ if (params.refundAddress) qs.set("refund_address", params.refundAddress);
285
+ const res = await hostGet(host, `/thorchain/quote/swap?${qs.toString()}`, clientId, signal);
286
+ if (Array.isArray(res.body)) return null;
287
+ return res.body ?? (res.ok ? null : { error: `HTTP ${res.status}` });
288
+ };
289
+ let q = null;
290
+ let checkedMemo = null;
291
+ let inbounds = null;
292
+ for (const host of hosts) {
293
+ try {
294
+ if (limitMode === "liquidity_tolerance_bps") {
295
+ q = await quoteOn(host, String(liquidityBps), "liquidity_tolerance_bps");
296
+ const feeBps = Number(q?.fees?.total_bps);
297
+ if (Number.isFinite(feeBps) && feeBps > Number(MAX_TOLERANCE_BPS)) {
298
+ params.onRefusal?.(`thorchain: THORChain fees ${(feeBps / 100).toFixed(2)}% \u2014 too expensive at this amount (cap ${Number(MAX_TOLERANCE_BPS) / 100}%)`);
299
+ return null;
300
+ }
301
+ } else {
302
+ q = await quoteOn(host, String(requestedTolerance));
303
+ const rejected = (q?.error ?? q?.message)?.match(/emit asset (\d+) less than price limit (\d+)/);
304
+ if (rejected && !explicitSlippage) {
305
+ const emit = BigInt(rejected[1] ?? "0");
306
+ const limit = BigInt(rejected[2] ?? "0");
307
+ if (emit > 0n && limit > emit) {
308
+ const spot = limit * 10000n / (10000n - BigInt(requestedTolerance));
309
+ const needed = 10000n - emit * 10000n / spot + 50n;
310
+ if (needed <= MAX_TOLERANCE_BPS) q = await quoteOn(host, needed.toString());
311
+ }
312
+ }
313
+ }
314
+ if (q?.error || q?.message) {
315
+ params.onRefusal?.(`thorchain: ${String(q.error ?? q.message).slice(0, 200)}`);
316
+ return null;
317
+ }
318
+ if (!q || !q.expected_amount_out || !q.inbound_address || !q.memo || !q.expiry) {
319
+ params.onRefusal?.(`thorchain: incomplete quote from ${host} (expected_amount_out / inbound_address / memo / expiry missing)`);
320
+ return null;
321
+ }
322
+ if (chainId ? !q.router : !!q.router) {
323
+ 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");
324
+ return null;
325
+ }
326
+ checkedMemo = verifiedMemo(q.memo, dst.asset, params.destAddress, params.refundAddress ?? "", configuredAffiliate, configuredAffiliateBps);
327
+ if (!checkedMemo) {
328
+ params.onRefusal?.(`thorchain: memo failed verification (${String(q.memo).slice(0, 80)})`);
329
+ return null;
330
+ }
331
+ const floor1e8 = params.minAmountOut !== void 0 ? toThorUnitsCeil(BigInt(params.minAmountOut), dst.decimals) : void 0;
332
+ if (floor1e8 !== void 0 && checkedMemo.limit < floor1e8) {
333
+ params.onRefusal?.(`thorchain: floor breach (limit=${checkedMemo.limit} < floor=${floor1e8}, 1e8 units)`);
334
+ return null;
335
+ }
336
+ inbounds = await inboundAddressesOn(host, clientId);
337
+ break;
338
+ } catch (error) {
339
+ if (error instanceof HostUnavailable) {
340
+ q = null;
341
+ checkedMemo = null;
342
+ inbounds = null;
343
+ continue;
344
+ }
345
+ throw error;
346
+ }
347
+ }
348
+ if (!q?.expected_amount_out || !q.inbound_address || !q.expiry || !checkedMemo || !inbounds) {
349
+ params.onRefusal?.(!inbounds ? "thorchain: inbound addresses unavailable from every node" : "thorchain: no quote from any node");
350
+ return null;
351
+ }
352
+ const limit1e8 = checkedMemo.limit;
353
+ const srcInbound = inbounds.find((entry) => entry.chain === THOR_CHAIN[params.sourceChain]);
354
+ const dstInbound = inbounds.find((entry) => entry.chain === THOR_CHAIN[params.destChain]);
355
+ if (paused(srcInbound) || paused(dstInbound)) {
356
+ params.onRefusal?.(`thorchain: ${paused(srcInbound) ? THOR_CHAIN[params.sourceChain] : THOR_CHAIN[params.destChain]} chain is halted / paused on THORChain`);
357
+ return null;
358
+ }
359
+ if (!sameTarget(srcInbound?.address, q.inbound_address)) {
360
+ params.onRefusal?.("thorchain: quote inbound address differs from the node's current inbound (vault rotated) \u2014 retry");
361
+ return null;
362
+ }
363
+ if (chainId && !sameTarget(srcInbound?.router, q.router)) {
364
+ params.onRefusal?.("thorchain: quote router differs from the node's current router \u2014 retry");
365
+ return null;
366
+ }
367
+ const tooSmall = belowThorMinimums(amount1e8, srcInbound, q);
368
+ if (tooSmall) {
369
+ params.onRefusal?.(`thorchain: ${tooSmall}`);
370
+ return null;
371
+ }
372
+ const outUnits = BigInt(q.expected_amount_out) * 10n ** BigInt(dst.decimals) / 100000000n;
373
+ if (outUnits <= 0n) {
374
+ params.onRefusal?.("thorchain: expected output rounds to zero");
375
+ return null;
376
+ }
377
+ const memoMinUnits = limit1e8 * 10n ** BigInt(dst.decimals) / 100000000n;
378
+ const minUnits = memoMinUnits;
379
+ if (minUnits <= 0n || minUnits > outUnits) {
380
+ params.onRefusal?.(`thorchain: memo limit ${limit1e8} inconsistent with expected output ${q.expected_amount_out}`);
381
+ return null;
382
+ }
383
+ if (params.slippageBps !== void 0) {
384
+ const slippageFloor = outUnits * BigInt(1e4 - params.slippageBps) / 10000n;
385
+ if (minUnits < slippageFloor) {
386
+ params.onRefusal?.(`thorchain: memo limit ${limit1e8} is below expected ${q.expected_amount_out} minus ${params.slippageBps} bps slippage (1e8 units)`);
387
+ return null;
388
+ }
389
+ }
390
+ const nowSeconds = Math.floor(Date.now() / 1e3);
391
+ const executionExpiry = Math.min(q.expiry, nowSeconds + 15 * 60);
392
+ if (!Number.isInteger(executionExpiry) || executionExpiry <= nowSeconds + 30) {
393
+ params.onRefusal?.(`thorchain: quote expires in ${Number.isInteger(executionExpiry) ? executionExpiry - nowSeconds : "?"} s \u2014 too soon to execute`);
394
+ return null;
395
+ }
396
+ const assetAddr = src.contract ? src.contract : NATIVE;
397
+ const common = {
398
+ provider: "thorchain",
399
+ intentId: `thorchain:${chainId ?? THOR_CHAIN[params.sourceChain]}`,
400
+ sourceAmount: params.amountIn,
401
+ destAmount: outUnits.toString(),
402
+ minDestAmount: minUnits.toString(),
403
+ feeUsd: 0,
404
+ // destAmount is already net of THORChain fees
405
+ etaSeconds: q.total_swap_seconds ?? 120,
406
+ reliability: 0.9,
407
+ // Validity must outlive the ERC-20 approval leg: for USDT that is
408
+ // approve(0) + approve(n), each awaited to inclusion (≈2 blocks), so a
409
+ // 60 s window expired before the deposit and only the revoke went out
410
+ // (2026-09-16). The on-chain LIM bounds price movement and preflight
411
+ // re-checks vault/router/halts right before broadcast, so a longer
412
+ // window costs nothing in safety.
413
+ validUntilMs: Math.min(Date.now() + QUOTE_VALIDITY_MS, executionExpiry * 1e3),
414
+ providerData: {
415
+ sourceChain: params.sourceChain,
416
+ destChain: params.destChain,
417
+ inboundAddress: q.inbound_address,
418
+ router: q.router ?? "",
419
+ assetAddress: assetAddr,
420
+ expiry: String(executionExpiry),
421
+ memo: checkedMemo.canonical
422
+ }
423
+ };
424
+ if (!chainId) {
425
+ return { ...common, depositAddress: q.inbound_address, depositMemo: checkedMemo.canonical };
426
+ }
427
+ const data = encodeFunctionData({
428
+ abi: ROUTER_ABI,
429
+ functionName: "depositWithExpiry",
430
+ args: [q.inbound_address, assetAddr, amountIn, checkedMemo.canonical, BigInt(executionExpiry)]
431
+ });
432
+ return {
433
+ ...common,
434
+ depositAddress: "",
435
+ depositMemo: null,
436
+ txRequest: {
437
+ to: q.router,
438
+ data,
439
+ value: src.contract ? "0" : params.amountIn,
440
+ chainId,
441
+ ...src.contract ? { approvalAddress: q.router } : {}
442
+ }
443
+ };
444
+ },
445
+ async buildDeposit(quote) {
446
+ if (!quote.txRequest) {
447
+ const sourceChain = quote.providerData?.sourceChain ?? "";
448
+ if (!isDepositSource(sourceChain) || !quote.depositAddress || !quote.depositMemo) {
449
+ throw new Error("thorchain: deposit-address execution is only supported for an opted-in Tron source");
450
+ }
451
+ return { chain: sourceChain, to: quote.depositAddress, value: quote.sourceAmount, memo: quote.depositMemo };
452
+ }
453
+ return {
454
+ chain: "",
455
+ to: quote.txRequest?.to ?? "",
456
+ value: quote.txRequest?.value ?? "0",
457
+ ...quote.txRequest?.data ? { data: quote.txRequest.data } : {},
458
+ ...quote.txRequest?.chainId !== void 0 ? { chainId: quote.txRequest.chainId } : {},
459
+ ...quote.txRequest?.gasLimit ? { gasLimit: quote.txRequest.gasLimit } : {},
460
+ ...quote.txRequest?.approvalAddress ? { approvalAddress: quote.txRequest.approvalAddress } : {}
461
+ };
462
+ },
463
+ async preflightDeposit(quote, tx) {
464
+ const data = quote.providerData;
465
+ if (!data?.sourceChain || !data.destChain || !data.inboundAddress || !data.assetAddress || !data.expiry || !data.memo) {
466
+ throw new Error("thorchain: quote is missing execution-bound metadata");
467
+ }
468
+ const evmSource = CHAIN_ID[data.sourceChain] !== void 0;
469
+ if (evmSource && !data.router) throw new Error("thorchain: quote is missing execution-bound metadata");
470
+ if (!evmSource && !isDepositSource(data.sourceChain)) throw new Error("thorchain: source chain has no executor");
471
+ if (!/^\d+$/.test(data.expiry) || BigInt(data.expiry) <= BigInt(Math.floor(Date.now() / 1e3))) {
472
+ throw new Error("thorchain: quote expired before broadcast");
473
+ }
474
+ const inbounds = await inboundAddresses(hosts, clientId);
475
+ if (!inbounds) throw new Error("thorchain: cannot refresh inbound addresses before broadcast");
476
+ const src = inbounds.find((entry) => entry.chain === THOR_CHAIN[data.sourceChain]);
477
+ const dst = inbounds.find((entry) => entry.chain === THOR_CHAIN[data.destChain]);
478
+ if (paused(src) || paused(dst)) throw new Error("thorchain: source or destination chain trading is paused");
479
+ if (!sameTarget(src?.address, data.inboundAddress)) throw new Error("thorchain: inbound vault rotated after quote");
480
+ if (!evmSource) {
481
+ 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) {
482
+ throw new Error("thorchain: deposit-address execution must be a plain send with memo");
483
+ }
484
+ if (!sameTarget(tx.to, data.inboundAddress) || tx.memo !== data.memo || tx.value !== quote.sourceAmount || tx.chain && tx.chain !== data.sourceChain) {
485
+ throw new Error("thorchain: deposit does not match the validated quote");
486
+ }
487
+ const srcAsset = ccAssetByToken(data.sourceChain, sameTarget(data.assetAddress, NATIVE) ? "" : data.assetAddress);
488
+ if (!srcAsset) throw new Error("thorchain: unknown source asset");
489
+ const amount1e8 = BigInt(quote.sourceAmount) * 100000000n / 10n ** BigInt(srcAsset.decimals);
490
+ const tooSmall = belowThorMinimums(amount1e8, src, null);
491
+ if (tooSmall) throw new Error(`thorchain: ${tooSmall}`);
492
+ return;
493
+ }
494
+ if (!sameTarget(src?.router, data.router) || !sameTarget(tx.to, data.router)) {
495
+ throw new Error("thorchain: router rotated after quote");
496
+ }
497
+ if (!tx.data) throw new Error("thorchain: missing depositWithExpiry calldata");
498
+ let decoded;
499
+ try {
500
+ decoded = decodeFunctionData({ abi: ROUTER_ABI, data: tx.data });
501
+ } catch {
502
+ throw new Error("thorchain: malformed depositWithExpiry calldata");
503
+ }
504
+ const args = decoded.args;
505
+ 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]) {
506
+ throw new Error("thorchain: calldata does not match the validated quote");
507
+ }
508
+ const tokenSource = !sameTarget(data.assetAddress, NATIVE);
509
+ if (tx.value !== (tokenSource ? "0" : quote.sourceAmount) || (tokenSource ? !sameTarget(tx.approvalAddress, data.router) : tx.approvalAddress !== void 0)) {
510
+ throw new Error("thorchain: value/approval does not match the validated source asset");
511
+ }
512
+ },
513
+ async trackIntent(ref) {
514
+ if (!ref.sourceTxHash) return { state: "pending" };
515
+ if (!/^(?:0x)?[0-9a-fA-F]{64}$/.test(ref.sourceTxHash)) {
516
+ throw new Error("thorchain: source transaction hash must be 32-byte hex");
517
+ }
518
+ const hash = ref.sourceTxHash.replace(/^0x/i, "").toUpperCase();
519
+ const j = await thornodeGet(`/thorchain/tx/status/${hash}`, void 0, hosts, clientId) ?? await thornodeGet(`/thorchain/tx/${hash}`, void 0, hosts, clientId);
520
+ if (!j) return { state: "pending" };
521
+ const stages = j.stages;
522
+ if (stages) {
523
+ const planned = j.planned_out_txs ?? [];
524
+ const refunded = planned.some((out) => out.refund === true);
525
+ const delivered = planned.some((out) => out.refund === false);
526
+ const outTxs = j.out_txs ?? [];
527
+ const outHash2 = outTxs.find((out) => typeof out.id === "string" && out.id.length > 0 && !/^0+$/.test(out.id))?.id;
528
+ const deliveredHash = outTxs.find(
529
+ (out) => typeof out.id === "string" && out.id.length > 0 && !/^0+$/.test(out.id) && typeof out.memo === "string" && !/^REFUND/i.test(out.memo)
530
+ )?.id;
531
+ if (stages.outbound_signed?.completed) {
532
+ if (!refunded && !delivered || !outHash2) {
533
+ return { state: "pending", depositReceived: true };
534
+ }
535
+ return refunded && delivered ? { state: "partial", depositReceived: true, ...deliveredHash ? { destTxHash: deliveredHash } : {} } : refunded ? { state: "refunded", depositReceived: true } : { state: "completed", depositReceived: true, ...outHash2 ? { destTxHash: outHash2 } : {} };
536
+ }
537
+ return {
538
+ state: "pending",
539
+ depositReceived: !!(stages.inbound_observed?.started || stages.inbound_observed?.completed || stages.inbound_finalised?.completed)
540
+ };
541
+ }
542
+ const obs = j.observed_tx;
543
+ const outHash = obs?.out_hashes?.find((h) => typeof h === "string" && h.length > 0 && !/^0+$/.test(h));
544
+ if (obs?.status === "done") {
545
+ const planned = j.planned_out_txs ?? j.out_txs ?? [];
546
+ const refunded = planned.some(
547
+ (o) => o?.refund === true || /^REFUND/i.test(o?.memo ?? "")
548
+ );
549
+ const delivered = planned.some(
550
+ (o) => o?.refund === false && !/^REFUND/i.test(o?.memo ?? "")
551
+ );
552
+ if (!refunded && !delivered || !outHash) return { state: "pending", depositReceived: true };
553
+ if (refunded && delivered) return { state: "partial", depositReceived: true };
554
+ if (refunded) return { state: "refunded", depositReceived: true };
555
+ return { state: "completed", ...outHash ? { destTxHash: outHash } : {} };
556
+ }
557
+ return { state: "pending", depositReceived: !!obs };
558
+ }
559
+ };
560
+ }
561
+
562
+ export {
563
+ thorchainProvider
564
+ };
565
+ //# sourceMappingURL=chunk-AVFULD4D.js.map