@satora/swap 0.0.5 → 0.1.0

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/index.js CHANGED
@@ -1,3 +1,1773 @@
1
+ import {
2
+ DEFAULT_ESPLORA_URLS,
3
+ esploraReader,
4
+ htlcFactsFromEsploraTxs
5
+ } from "./chunk-J35RZBDW.js";
6
+
1
7
  // src/index.ts
2
8
  export * from "@lendasat/lendaswap-sdk-pure";
9
+
10
+ // src/actions/derive.ts
11
+ function deriveSwapActions(input) {
12
+ const {
13
+ status,
14
+ clientChainNow,
15
+ serverChainNow,
16
+ clientRefundLocktime,
17
+ serverRefundLocktime,
18
+ clientFunds = true
19
+ } = input;
20
+ switch (status) {
21
+ case "pending": {
22
+ if (!clientFunds) {
23
+ const wait = {
24
+ id: "wait",
25
+ recommended: true,
26
+ automation: "auto",
27
+ reason: "Waiting for the Lightning payment to be locked in."
28
+ };
29
+ return { recommended: "wait", actions: [wait] };
30
+ }
31
+ if (clientChainNow >= clientRefundLocktime) return { actions: [] };
32
+ const fund = {
33
+ id: "fund",
34
+ recommended: true,
35
+ // Funding spends new user money, so it is never auto-run.
36
+ automation: "manual",
37
+ reason: "Send the deposit to start the swap."
38
+ };
39
+ return { recommended: "fund", actions: [fund] };
40
+ }
41
+ case "clientfundingseen": {
42
+ const wait = {
43
+ id: "wait",
44
+ recommended: true,
45
+ automation: "auto",
46
+ reason: "Waiting for your funding transaction to confirm."
47
+ };
48
+ return { recommended: "wait", actions: [wait] };
49
+ }
50
+ case "clientfunded": {
51
+ const refundUnlocked = clientChainNow >= clientRefundLocktime;
52
+ if (refundUnlocked) {
53
+ const refund2 = {
54
+ id: "refund_unilateral",
55
+ recommended: true,
56
+ automation: "confirm",
57
+ reason: "Reclaim your deposit."
58
+ };
59
+ return { recommended: "refund_unilateral", actions: [refund2] };
60
+ }
61
+ const wait = {
62
+ id: "wait",
63
+ recommended: true,
64
+ automation: "auto",
65
+ reason: "Waiting for the server to fund the swap."
66
+ };
67
+ const refund = {
68
+ id: "refund_unilateral",
69
+ recommended: false,
70
+ automation: "confirm",
71
+ reason: "Refund becomes available once the timelock passes.",
72
+ blockedBy: {
73
+ kind: "timelock_not_expired",
74
+ message: "The refund timelock has not passed yet."
75
+ }
76
+ };
77
+ return { recommended: "wait", actions: [wait, refund] };
78
+ }
79
+ case "serverfunded": {
80
+ const claimWindowClosed = serverChainNow >= serverRefundLocktime;
81
+ if (!claimWindowClosed) {
82
+ const claim = {
83
+ id: "claim",
84
+ recommended: true,
85
+ automation: "auto",
86
+ reason: "Redeem the swap with your preimage."
87
+ };
88
+ return { recommended: "claim", actions: [claim] };
89
+ }
90
+ if (!clientFunds) {
91
+ const done = {
92
+ id: "none",
93
+ recommended: true,
94
+ automation: "auto",
95
+ reason: "The claim window passed \u2014 your Lightning payment will be refunded automatically."
96
+ };
97
+ return { recommended: "none", actions: [done] };
98
+ }
99
+ const refundUnlocked = clientChainNow >= clientRefundLocktime;
100
+ if (refundUnlocked) {
101
+ const refund2 = {
102
+ id: "refund_unilateral",
103
+ recommended: true,
104
+ automation: "confirm",
105
+ reason: "Reclaim your deposit \u2014 do not claim, as revealing the preimage would let the server take it."
106
+ };
107
+ return { recommended: "refund_unilateral", actions: [refund2] };
108
+ }
109
+ const wait = {
110
+ id: "wait",
111
+ recommended: true,
112
+ automation: "auto",
113
+ reason: "Wait for your refund timelock \u2014 do not claim, as revealing the preimage would let the server take your deposit."
114
+ };
115
+ const refund = {
116
+ id: "refund_unilateral",
117
+ recommended: false,
118
+ automation: "confirm",
119
+ reason: "Refund becomes available once the timelock passes.",
120
+ blockedBy: {
121
+ kind: "timelock_not_expired",
122
+ message: "The refund timelock has not passed yet."
123
+ }
124
+ };
125
+ return { recommended: "wait", actions: [wait, refund] };
126
+ }
127
+ case "clientredeeming": {
128
+ const wait = {
129
+ id: "wait",
130
+ recommended: true,
131
+ automation: "auto",
132
+ reason: "Redeeming your funds \u2014 waiting for the claim to confirm."
133
+ };
134
+ return { recommended: "wait", actions: [wait] };
135
+ }
136
+ case "clientredeemed":
137
+ case "serverredeemed": {
138
+ const done = {
139
+ id: "none",
140
+ recommended: true,
141
+ automation: "auto",
142
+ reason: "Swap complete \u2014 you've received your funds."
143
+ };
144
+ return { recommended: "none", actions: [done] };
145
+ }
146
+ case "expired": {
147
+ const done = {
148
+ id: "none",
149
+ recommended: true,
150
+ automation: "auto",
151
+ reason: "The swap expired without funding \u2014 nothing was locked."
152
+ };
153
+ return { recommended: "none", actions: [done] };
154
+ }
155
+ case "clientrefunded":
156
+ case "clientrefundedserverrefunded":
157
+ case "clientrefundedserverfunded": {
158
+ const done = {
159
+ id: "none",
160
+ recommended: true,
161
+ automation: "auto",
162
+ reason: "Refunded \u2014 your deposit was returned."
163
+ };
164
+ return { recommended: "none", actions: [done] };
165
+ }
166
+ case "clientredeemedandclientrefunded": {
167
+ const done = {
168
+ id: "none",
169
+ recommended: true,
170
+ automation: "auto",
171
+ reason: "This swap ended in an anomalous state (redeemed and refunded); no action needed."
172
+ };
173
+ return { recommended: "none", actions: [done] };
174
+ }
175
+ case "clientfundedserverrefunded":
176
+ case "serverwontfund":
177
+ case "clientfundedtoolate":
178
+ case "clientinvalidfunded": {
179
+ const refundUnlocked = clientChainNow >= clientRefundLocktime;
180
+ if (refundUnlocked) {
181
+ const refund2 = {
182
+ id: "refund_unilateral",
183
+ recommended: true,
184
+ automation: "confirm",
185
+ reason: "Reclaim your deposit \u2014 the swap did not complete."
186
+ };
187
+ return { recommended: "refund_unilateral", actions: [refund2] };
188
+ }
189
+ const wait = {
190
+ id: "wait",
191
+ recommended: true,
192
+ automation: "auto",
193
+ reason: "The swap did not complete; waiting for the refund timelock to reclaim your deposit."
194
+ };
195
+ const refund = {
196
+ id: "refund_unilateral",
197
+ recommended: false,
198
+ automation: "confirm",
199
+ reason: "Refund becomes available once the timelock passes.",
200
+ blockedBy: {
201
+ kind: "timelock_not_expired",
202
+ message: "The refund timelock has not passed yet."
203
+ }
204
+ };
205
+ return { recommended: "wait", actions: [wait, refund] };
206
+ }
207
+ default:
208
+ return assertNever(status);
209
+ }
210
+ }
211
+ function assertNever(status) {
212
+ throw new Error(`unhandled swap status: ${String(status)}`);
213
+ }
214
+
215
+ // src/actions/status.ts
216
+ function deriveSwapStatus(obs) {
217
+ const { clientHtlc, serverHtlc } = obs;
218
+ if (serverHtlc === void 0 && clientHtlc !== void 0) {
219
+ return receiveOnLightningStatus(clientHtlc);
220
+ }
221
+ if (clientHtlc === void 0 && serverHtlc !== void 0) {
222
+ return payOnLightningStatus(serverHtlc);
223
+ }
224
+ if (clientHtlc === void 0 || serverHtlc === void 0) return void 0;
225
+ if (clientHtlc === "spent_claim" && serverHtlc === "spent_claim") {
226
+ return "serverredeemed";
227
+ }
228
+ if (clientHtlc === "spent_refund" && serverHtlc === "spent_claim") {
229
+ return "clientredeemedandclientrefunded";
230
+ }
231
+ if (clientHtlc === "spent_refund" && serverHtlc === "spent_refund") {
232
+ return "clientrefundedserverrefunded";
233
+ }
234
+ if (clientHtlc === "spent_refund" && serverHtlc === "confirmed") {
235
+ return "clientrefundedserverfunded";
236
+ }
237
+ if (clientHtlc === "spent_refund" && (serverHtlc === "absent" || serverHtlc === "mempool")) {
238
+ return "clientrefunded";
239
+ }
240
+ if (clientHtlc === "confirmed" && serverHtlc === "spent_claim") {
241
+ return "clientredeemed";
242
+ }
243
+ if (clientHtlc === "confirmed" && serverHtlc === "spent_refund") {
244
+ return "clientfundedserverrefunded";
245
+ }
246
+ if (clientHtlc === "invalid") return "clientinvalidfunded";
247
+ if (clientHtlc === "absent") return "pending";
248
+ if (clientHtlc === "mempool") return "clientfundingseen";
249
+ if (clientHtlc === "confirmed") {
250
+ return serverHtlc === "confirmed" ? "serverfunded" : "clientfunded";
251
+ }
252
+ return void 0;
253
+ }
254
+ function receiveOnLightningStatus(clientHtlc) {
255
+ switch (clientHtlc) {
256
+ case "spent_claim":
257
+ return "serverredeemed";
258
+ // server swept the deposit ⟹ it paid the invoice
259
+ case "spent_refund":
260
+ return "clientrefunded";
261
+ case "invalid":
262
+ return "clientinvalidfunded";
263
+ case "confirmed":
264
+ return "clientfunded";
265
+ // funded; waiting for the server to pay + sweep
266
+ case "mempool":
267
+ return "clientfundingseen";
268
+ case "absent":
269
+ return "pending";
270
+ }
271
+ }
272
+ function payOnLightningStatus(serverHtlc) {
273
+ switch (serverHtlc) {
274
+ case "spent_claim":
275
+ return "clientredeemed";
276
+ // client claimed the on-chain leg ⟹ complete
277
+ case "confirmed":
278
+ return "serverfunded";
279
+ // ready to claim
280
+ case "spent_refund":
281
+ return "clientrefunded";
282
+ case "invalid":
283
+ case "mempool":
284
+ case "absent":
285
+ return "pending";
286
+ }
287
+ }
288
+
289
+ // src/client.ts
290
+ import {
291
+ Client as LegacyClient
292
+ } from "@lendasat/lendaswap-sdk-pure";
293
+
294
+ // src/contracts/arkade-manager.ts
295
+ import { RestIndexerProvider } from "@arkade-os/sdk";
296
+ import { hex as hex2 } from "@scure/base";
297
+
298
+ // src/contracts/arkade.ts
299
+ import {
300
+ ConditionWitness,
301
+ getArkPsbtFields,
302
+ Transaction
303
+ } from "@arkade-os/sdk";
304
+ import { base64 } from "@scure/base";
305
+
306
+ // src/contracts/preimage.ts
307
+ import { ripemd160 } from "@noble/hashes/legacy.js";
308
+ import { sha256 } from "@noble/hashes/sha2.js";
309
+ import { hex } from "@scure/base";
310
+ function preimageMatches(element, hashLock) {
311
+ const digest = hashLock.length === 20 ? ripemd160(sha256(element)) : sha256(element);
312
+ return hex.encode(digest) === hex.encode(hashLock);
313
+ }
314
+
315
+ // src/contracts/arkade.ts
316
+ function arkadeObservation(facts) {
317
+ if (facts.spend === "claim") return "spent_claim";
318
+ if (facts.spend === "refund") return "spent_refund";
319
+ if (!facts.funded) return "absent";
320
+ return facts.sufficient === false ? "invalid" : "confirmed";
321
+ }
322
+ function classifyArkadeSpend(encodedSpendTx, paymentHash) {
323
+ const tx = Transaction.fromPSBT(base64.decode(encodedSpendTx));
324
+ for (const witness of getArkPsbtFields(tx, 0, ConditionWitness)) {
325
+ for (const element of witness) {
326
+ if (element.length === 32 && preimageMatches(element, paymentHash)) {
327
+ return { spend: "claim", preimage: element };
328
+ }
329
+ }
330
+ }
331
+ return { spend: "refund" };
332
+ }
333
+ async function fetchArkadeSpend(source, spentByTxid, paymentHash) {
334
+ const { txs } = await source.getVirtualTxs([spentByTxid]);
335
+ const encoded = txs[0];
336
+ return encoded ? classifyArkadeSpend(encoded, paymentHash) : void 0;
337
+ }
338
+
339
+ // src/contracts/arkade-manager.ts
340
+ function isFunded(vtxo) {
341
+ const { state } = vtxo.virtualStatus;
342
+ return state === "preconfirmed" || state === "settled";
343
+ }
344
+ function spendTxid(vtxos) {
345
+ const spent = vtxos.find(
346
+ (v) => v.arkTxId || v.spentBy || v.isSpent || v.virtualStatus.state === "spent"
347
+ );
348
+ return spent?.arkTxId ?? spent?.spentBy;
349
+ }
350
+ var ArkadeContractManager = class _ArkadeContractManager {
351
+ ledger = "arkade";
352
+ #indexer;
353
+ #chainTime;
354
+ /** pkScript → the ref we're tracking. */
355
+ #refs = /* @__PURE__ */ new Map();
356
+ /** pkScript → last known observation. */
357
+ #obs = /* @__PURE__ */ new Map();
358
+ /** pkScript → the verified preimage recovered from a claim spend. */
359
+ #preimages = /* @__PURE__ */ new Map();
360
+ #listeners = /* @__PURE__ */ new Set();
361
+ #now;
362
+ constructor(deps) {
363
+ this.#indexer = deps.indexer;
364
+ this.#chainTime = deps.chainTime;
365
+ }
366
+ static fromDeps(deps) {
367
+ return new _ArkadeContractManager(deps);
368
+ }
369
+ /** Construct a manager backed by a `RestIndexerProvider` over the Ark server. */
370
+ static async create(config) {
371
+ const indexer = new RestIndexerProvider(config.serverUrl);
372
+ return _ArkadeContractManager.fromDeps({
373
+ indexer,
374
+ chainTime: config.chainTime
375
+ });
376
+ }
377
+ async register(ref) {
378
+ if (ref.ledger !== "arkade")
379
+ throw new Error(
380
+ `ArkadeContractManager can't track a '${ref.ledger}' HTLC`
381
+ );
382
+ this.#refs.set(ref.script, ref);
383
+ await this.#reconcileRef(ref);
384
+ }
385
+ async unregister(ref) {
386
+ if (ref.ledger !== "arkade") return;
387
+ this.#refs.delete(ref.script);
388
+ this.#obs.delete(ref.script);
389
+ this.#preimages.delete(ref.script);
390
+ }
391
+ getState(ref) {
392
+ return ref.ledger === "arkade" ? this.#obs.get(ref.script) : void 0;
393
+ }
394
+ chainNow(_ref) {
395
+ return this.#now;
396
+ }
397
+ onEvent(cb) {
398
+ this.#listeners.add(cb);
399
+ return () => this.#listeners.delete(cb);
400
+ }
401
+ async refresh() {
402
+ if (this.#chainTime) this.#now = await this.#chainTime();
403
+ await Promise.all(
404
+ [...this.#refs.values()].map((ref) => this.#reconcileRef(ref))
405
+ );
406
+ }
407
+ dispose() {
408
+ this.#listeners.clear();
409
+ }
410
+ /** The verified preimage recovered from a claim spend, if one was seen. */
411
+ getPreimage(ref) {
412
+ return ref.ledger === "arkade" ? this.#preimages.get(ref.script) : void 0;
413
+ }
414
+ /** Read the VHTLC's vtxos and map them to an observation. */
415
+ async #reconcileRef(ref) {
416
+ const { vtxos } = await this.#indexer.getVtxos({ scripts: [ref.script] });
417
+ const spend = spendTxid(vtxos);
418
+ if (spend) {
419
+ const resolved = await fetchArkadeSpend(
420
+ this.#indexer,
421
+ spend,
422
+ hex2.decode(ref.preimageHash)
423
+ );
424
+ if (resolved) {
425
+ if (resolved.spend === "claim")
426
+ this.#preimages.set(ref.script, resolved.preimage);
427
+ this.#set(
428
+ ref.script,
429
+ arkadeObservation({ funded: true, spend: resolved.spend })
430
+ );
431
+ return;
432
+ }
433
+ }
434
+ const funded = vtxos.filter(isFunded);
435
+ const total = funded.reduce((sum, vtxo) => sum + vtxo.value, 0);
436
+ this.#set(
437
+ ref.script,
438
+ arkadeObservation({
439
+ funded: funded.length > 0,
440
+ sufficient: total >= ref.expectedSats
441
+ })
442
+ );
443
+ }
444
+ /** Update an observation and notify on change; never downgrade a resolved spend. */
445
+ #set(script, observation) {
446
+ const ref = this.#refs.get(script);
447
+ if (!ref) return;
448
+ const current = this.#obs.get(script);
449
+ if (current === observation) return;
450
+ const spendStates = ["spent_claim", "spent_refund"];
451
+ if (current && spendStates.includes(current) && !spendStates.includes(observation))
452
+ return;
453
+ this.#obs.set(script, observation);
454
+ for (const listener of this.#listeners) listener(ref, observation);
455
+ }
456
+ };
457
+
458
+ // src/contracts/arkade-network.ts
459
+ var DEFAULT_ARKADE_URLS = {
460
+ bitcoin: "https://arkade.computer",
461
+ mainnet: "https://arkade.computer",
462
+ signet: "https://mutinynet.arkade.sh",
463
+ mutinynet: "https://mutinynet.arkade.sh"
464
+ };
465
+ function defaultArkadeServerUrl(network) {
466
+ return DEFAULT_ARKADE_URLS[network.toLowerCase()];
467
+ }
468
+
469
+ // src/contracts/bitcoin-manager.ts
470
+ import { hex as hex4 } from "@scure/base";
471
+
472
+ // src/contracts/bitcoin.ts
473
+ import { hex as hex3 } from "@scure/base";
474
+ function classifyBitcoinSpend(witness, paymentHash) {
475
+ for (const item of witness) {
476
+ if (item.length !== 64) continue;
477
+ const bytes = hex3.decode(item);
478
+ if (preimageMatches(bytes, paymentHash))
479
+ return { spend: "claim", preimage: bytes };
480
+ }
481
+ return { spend: "refund" };
482
+ }
483
+ function bitcoinObservation(facts, paymentHash, expectedSats) {
484
+ if (facts.spendWitness) {
485
+ const spend = classifyBitcoinSpend(facts.spendWitness, paymentHash);
486
+ return spend.spend === "claim" ? { observation: "spent_claim", preimage: spend.preimage } : { observation: "spent_refund" };
487
+ }
488
+ if (facts.funding === "confirmed" && facts.fundedSats < expectedSats) {
489
+ return { observation: "invalid" };
490
+ }
491
+ return { observation: facts.funding };
492
+ }
493
+
494
+ // src/contracts/bitcoin-manager.ts
495
+ var BitcoinContractManager = class _BitcoinContractManager {
496
+ ledger = "bitcoin";
497
+ #reader;
498
+ #chainTime;
499
+ /** address → the ref we're tracking. */
500
+ #refs = /* @__PURE__ */ new Map();
501
+ /** address → last known observation. */
502
+ #obs = /* @__PURE__ */ new Map();
503
+ /** address → the preimage a claim revealed. */
504
+ #preimages = /* @__PURE__ */ new Map();
505
+ #listeners = /* @__PURE__ */ new Set();
506
+ #now;
507
+ constructor(deps) {
508
+ this.#reader = deps.reader;
509
+ this.#chainTime = deps.chainTime;
510
+ }
511
+ static fromDeps(deps) {
512
+ return new _BitcoinContractManager(deps);
513
+ }
514
+ static async create(config) {
515
+ const { esploraReader: esploraReader2 } = await import("./bitcoin-reader-esplora-GRUP7RAG.js");
516
+ return _BitcoinContractManager.fromDeps({
517
+ reader: esploraReader2(config.esploraUrl),
518
+ chainTime: config.chainTime
519
+ });
520
+ }
521
+ async register(ref) {
522
+ if (ref.ledger !== "bitcoin")
523
+ throw new Error(
524
+ `BitcoinContractManager can't track a '${ref.ledger}' HTLC`
525
+ );
526
+ this.#refs.set(ref.address, ref);
527
+ await this.#reconcileRef(ref);
528
+ }
529
+ async unregister(ref) {
530
+ if (ref.ledger !== "bitcoin") return;
531
+ this.#refs.delete(ref.address);
532
+ this.#obs.delete(ref.address);
533
+ this.#preimages.delete(ref.address);
534
+ }
535
+ getState(ref) {
536
+ return ref.ledger === "bitcoin" ? this.#obs.get(ref.address) : void 0;
537
+ }
538
+ chainNow(_ref) {
539
+ return this.#now;
540
+ }
541
+ onEvent(cb) {
542
+ this.#listeners.add(cb);
543
+ return () => this.#listeners.delete(cb);
544
+ }
545
+ async refresh() {
546
+ if (this.#chainTime) this.#now = await this.#chainTime();
547
+ await Promise.all(
548
+ [...this.#refs.values()].map((ref) => this.#reconcileRef(ref))
549
+ );
550
+ }
551
+ dispose() {
552
+ this.#listeners.clear();
553
+ }
554
+ /** The preimage a claim revealed on this HTLC, if one was seen. */
555
+ getPreimage(ref) {
556
+ return ref.ledger === "bitcoin" ? this.#preimages.get(ref.address) : void 0;
557
+ }
558
+ async #reconcileRef(ref) {
559
+ const facts = await this.#reader.getHtlcFacts(ref.address);
560
+ const { observation, preimage } = bitcoinObservation(
561
+ facts,
562
+ hex4.decode(ref.preimageHash),
563
+ ref.expectedSats
564
+ );
565
+ if (preimage) this.#preimages.set(ref.address, preimage);
566
+ this.#set(ref.address, observation);
567
+ }
568
+ /** Update an observation and notify on change; never downgrade a resolved spend. */
569
+ #set(address, observation) {
570
+ const ref = this.#refs.get(address);
571
+ if (!ref) return;
572
+ const current = this.#obs.get(address);
573
+ if (current === observation) return;
574
+ const spendStates = ["spent_claim", "spent_refund"];
575
+ if (current && spendStates.includes(current) && !spendStates.includes(observation))
576
+ return;
577
+ this.#obs.set(address, observation);
578
+ for (const listener of this.#listeners) listener(ref, observation);
579
+ }
580
+ };
581
+
582
+ // src/contracts/evm.ts
583
+ function evmObservation(events, expected, pending = false) {
584
+ const redeemed = events.find((e) => e.kind === "redeemed");
585
+ if (redeemed?.kind === "redeemed")
586
+ return { observation: "spent_claim", preimage: redeemed.preimage };
587
+ if (events.some((e) => e.kind === "refunded"))
588
+ return { observation: "spent_refund" };
589
+ const created = events.find((e) => e.kind === "created");
590
+ if (created?.kind === "created") {
591
+ const amountOk = created.amount >= expected.amount;
592
+ const tokenOk = expected.token === void 0 || created.token.toLowerCase() === expected.token.toLowerCase();
593
+ return { observation: amountOk && tokenOk ? "confirmed" : "invalid" };
594
+ }
595
+ return { observation: pending ? "mempool" : "absent" };
596
+ }
597
+
598
+ // src/contracts/types.ts
599
+ function htlcKey(ref) {
600
+ switch (ref.ledger) {
601
+ case "arkade":
602
+ return `arkade:${ref.script}`;
603
+ case "bitcoin":
604
+ return `bitcoin:${ref.address}`;
605
+ case "evm":
606
+ return `evm:${ref.chainId}:${ref.preimageHash}`;
607
+ case "lightning":
608
+ return `lightning:${ref.paymentHash}`;
609
+ }
610
+ }
611
+
612
+ // src/contracts/evm-manager.ts
613
+ var EvmContractManager = class _EvmContractManager {
614
+ ledger = "evm";
615
+ #readers;
616
+ /** htlcKey → the ref we're tracking. */
617
+ #refs = /* @__PURE__ */ new Map();
618
+ /** htlcKey → last known observation. */
619
+ #obs = /* @__PURE__ */ new Map();
620
+ /** htlcKey → the preimage a claim revealed. */
621
+ #preimages = /* @__PURE__ */ new Map();
622
+ /** chainId → its latest block time (ms). */
623
+ #now = /* @__PURE__ */ new Map();
624
+ /** chainId → unsubscribe for its (single, shared) watch. */
625
+ #watchUnsubs = /* @__PURE__ */ new Map();
626
+ /** chainId → a block-event reconcile is currently running (coalescing). */
627
+ #reconciling = /* @__PURE__ */ new Set();
628
+ /** chainId → a block event arrived mid-reconcile; run exactly one more. */
629
+ #reconcileAgain = /* @__PURE__ */ new Set();
630
+ #listeners = /* @__PURE__ */ new Set();
631
+ constructor(deps) {
632
+ this.#readers = deps.readers;
633
+ }
634
+ static fromDeps(deps) {
635
+ return new _EvmContractManager(deps);
636
+ }
637
+ async register(ref) {
638
+ if (ref.ledger !== "evm")
639
+ throw new Error(`EvmContractManager can't track a '${ref.ledger}' HTLC`);
640
+ if (!this.#readers.has(ref.chainId))
641
+ throw new Error(
642
+ `no EVM reader for chain ${ref.chainId} \u2014 configure it via ClientBuilder.withEvmRpcUrls()`
643
+ );
644
+ this.#refs.set(htlcKey(ref), ref);
645
+ this.#ensureWatch(ref.chainId);
646
+ await this.#reconcileChain(ref.chainId);
647
+ }
648
+ async unregister(ref) {
649
+ if (ref.ledger !== "evm") return;
650
+ const key = htlcKey(ref);
651
+ this.#refs.delete(key);
652
+ this.#obs.delete(key);
653
+ this.#preimages.delete(key);
654
+ if (![...this.#refs.values()].some((r) => r.chainId === ref.chainId)) {
655
+ this.#watchUnsubs.get(ref.chainId)?.();
656
+ this.#watchUnsubs.delete(ref.chainId);
657
+ this.#now.delete(ref.chainId);
658
+ this.#reconcileAgain.delete(ref.chainId);
659
+ }
660
+ }
661
+ getState(ref) {
662
+ return ref.ledger === "evm" ? this.#obs.get(htlcKey(ref)) : void 0;
663
+ }
664
+ chainNow(ref) {
665
+ return ref.ledger === "evm" ? this.#now.get(ref.chainId) : void 0;
666
+ }
667
+ onEvent(cb) {
668
+ this.#listeners.add(cb);
669
+ return () => this.#listeners.delete(cb);
670
+ }
671
+ async refresh() {
672
+ const chainIds = new Set([...this.#refs.values()].map((r) => r.chainId));
673
+ await Promise.all([...chainIds].map((c) => this.#reconcileChain(c)));
674
+ }
675
+ dispose() {
676
+ for (const unsub of this.#watchUnsubs.values()) unsub();
677
+ this.#watchUnsubs.clear();
678
+ this.#reconciling.clear();
679
+ this.#reconcileAgain.clear();
680
+ this.#listeners.clear();
681
+ }
682
+ /** The preimage a claim revealed on this HTLC, if one was seen. */
683
+ getPreimage(ref) {
684
+ return ref.ledger === "evm" ? this.#preimages.get(htlcKey(ref)) : void 0;
685
+ }
686
+ #ensureWatch(chainId) {
687
+ if (this.#watchUnsubs.has(chainId)) return;
688
+ const reader = this.#readers.get(chainId);
689
+ if (!reader) return;
690
+ this.#watchUnsubs.set(
691
+ chainId,
692
+ reader.watch(() => this.#scheduleReconcile(chainId))
693
+ );
694
+ }
695
+ /**
696
+ * Reconcile a chain in response to a block/log event, coalesced: at most one
697
+ * reconcile runs per chain at a time, with at most one queued rerun to pick up
698
+ * changes that arrived mid-run. This stops fast chains / slow RPCs from stacking
699
+ * redundant full-log scans (which amplify rate limits), and the `.catch` keeps a
700
+ * transient RPC failure in a background reconcile from becoming an unhandled
701
+ * rejection. `register`/`refresh` still await `#reconcileChain` directly.
702
+ */
703
+ #scheduleReconcile(chainId) {
704
+ if (this.#reconciling.has(chainId)) {
705
+ this.#reconcileAgain.add(chainId);
706
+ return;
707
+ }
708
+ this.#reconciling.add(chainId);
709
+ void this.#reconcileChain(chainId).catch((error) => {
710
+ console.warn(`EVM reconcile failed for chain ${chainId}:`, error);
711
+ }).finally(() => {
712
+ this.#reconciling.delete(chainId);
713
+ if (this.#reconcileAgain.delete(chainId))
714
+ this.#scheduleReconcile(chainId);
715
+ });
716
+ }
717
+ /** Refresh one chain's clock and re-observe every HTLC tracked on it. */
718
+ async #reconcileChain(chainId) {
719
+ const reader = this.#readers.get(chainId);
720
+ if (!reader) return;
721
+ this.#now.set(chainId, await reader.getBlockTimeMs());
722
+ await Promise.all(
723
+ [...this.#refs.values()].filter((r) => r.chainId === chainId).map((r) => this.#reconcileRef(reader, r))
724
+ );
725
+ }
726
+ async #reconcileRef(reader, ref) {
727
+ const events = await reader.getHtlcEvents(
728
+ ref.htlc,
729
+ ref.preimageHash,
730
+ ref.claimAddress
731
+ );
732
+ const { observation, preimage } = evmObservation(events, {
733
+ amount: ref.expectedAmount,
734
+ token: ref.expectedToken
735
+ });
736
+ const key = htlcKey(ref);
737
+ if (preimage) this.#preimages.set(key, preimage);
738
+ this.#set(key, observation);
739
+ }
740
+ /** Update an observation and notify on change; never downgrade a resolved spend. */
741
+ #set(key, observation) {
742
+ const ref = this.#refs.get(key);
743
+ if (!ref) return;
744
+ const current = this.#obs.get(key);
745
+ if (current === observation) return;
746
+ const spendStates = ["spent_claim", "spent_refund"];
747
+ if (current && spendStates.includes(current) && !spendStates.includes(observation))
748
+ return;
749
+ this.#obs.set(key, observation);
750
+ for (const listener of this.#listeners) listener(ref, observation);
751
+ }
752
+ };
753
+
754
+ // src/contracts/evm-reader-viem.ts
755
+ import {
756
+ createPublicClient,
757
+ fallback,
758
+ http,
759
+ parseAbiItem
760
+ } from "viem";
761
+ var DEFAULT_EVM_RPCS = {
762
+ // Polygon — viem's default (polygon.drpc.org) misbehaves for some calls, so
763
+ // list working public RPCs explicitly.
764
+ 137: [
765
+ "https://polygon.drpc.org",
766
+ "https://tenderly.rpc.polygon.community",
767
+ "https://polygon-bor-rpc.publicnode.com"
768
+ ],
769
+ 1: [
770
+ "https://ethereum-rpc.publicnode.com",
771
+ "https://rpc.ankr.com/eth",
772
+ "https://eth.drpc.org"
773
+ ],
774
+ 42161: [
775
+ "https://arbitrum-one-rpc.publicnode.com",
776
+ "https://rpc.ankr.com/arbitrum",
777
+ "https://arb1.arbitrum.io/rpc"
778
+ ]
779
+ };
780
+ var SWAP_CREATED = parseAbiItem(
781
+ "event SwapCreated(bytes32 indexed preimageHash, address indexed refundAddress, address indexed claimAddress, address token, uint256 amount, uint256 timelock)"
782
+ );
783
+ var SWAP_REDEEMED = parseAbiItem(
784
+ "event SwapRedeemed(bytes32 indexed preimageHash, bytes32 preimage)"
785
+ );
786
+ var SWAP_REFUNDED = parseAbiItem(
787
+ "event SwapRefunded(bytes32 indexed preimageHash)"
788
+ );
789
+ function evmReaderFromClient(client) {
790
+ return {
791
+ async getHtlcEvents(htlc, preimageHash, claimAddress) {
792
+ const [created, redeemed, refunded] = await Promise.all([
793
+ // Filter by claimAddress too (also indexed), so only the HTLC actually
794
+ // claimable on the swap's terms is seen.
795
+ client.getLogs({
796
+ address: htlc,
797
+ event: SWAP_CREATED,
798
+ args: { preimageHash, claimAddress },
799
+ fromBlock: "earliest"
800
+ }),
801
+ client.getLogs({
802
+ address: htlc,
803
+ event: SWAP_REDEEMED,
804
+ args: { preimageHash },
805
+ fromBlock: "earliest"
806
+ }),
807
+ client.getLogs({
808
+ address: htlc,
809
+ event: SWAP_REFUNDED,
810
+ args: { preimageHash },
811
+ fromBlock: "earliest"
812
+ })
813
+ ]);
814
+ const events = [];
815
+ for (const log of created) {
816
+ const args = log.args;
817
+ events.push({
818
+ kind: "created",
819
+ amount: args?.amount ?? 0n,
820
+ token: args?.token ?? "0x"
821
+ });
822
+ }
823
+ for (const log of redeemed) {
824
+ const preimage = log.args?.preimage;
825
+ if (preimage) events.push({ kind: "redeemed", preimage });
826
+ }
827
+ if (refunded.length > 0) events.push({ kind: "refunded" });
828
+ return events;
829
+ },
830
+ async getBlockTimeMs() {
831
+ const block = await client.getBlock();
832
+ return Number(block.timestamp) * 1e3;
833
+ },
834
+ watch(cb) {
835
+ return client.watchBlocks({ onBlock: () => cb() });
836
+ }
837
+ };
838
+ }
839
+ function createEvmRpcReader(rpcUrls) {
840
+ const urls = Array.isArray(rpcUrls) ? rpcUrls : [rpcUrls];
841
+ const transport = urls.length > 1 ? fallback(
842
+ urls.map((url) => http(url)),
843
+ { rank: { interval: 6e4, sampleCount: 3, timeout: 1e3 } }
844
+ ) : http(urls[0]);
845
+ const client = createPublicClient({ transport });
846
+ return evmReaderFromClient(client);
847
+ }
848
+ function defaultEvmReaders(overrides) {
849
+ const chainIds = /* @__PURE__ */ new Set([
850
+ ...Object.keys(DEFAULT_EVM_RPCS).map(Number),
851
+ ...Object.keys(overrides ?? {}).map(Number)
852
+ ]);
853
+ const readers = /* @__PURE__ */ new Map();
854
+ for (const chainId of chainIds) {
855
+ const override = overrides?.[chainId];
856
+ const defaults = DEFAULT_EVM_RPCS[chainId] ?? [];
857
+ const urls = override ? [override, ...defaults] : defaults;
858
+ if (urls.length > 0) readers.set(chainId, createEvmRpcReader(urls));
859
+ }
860
+ return readers;
861
+ }
862
+
863
+ // src/contracts/arkade-vhtlc.ts
864
+ import { VHTLC, VHTLCContractHandler } from "@arkade-os/sdk";
865
+ import { ripemd160 as ripemd1602 } from "@noble/hashes/legacy.js";
866
+ import { hex as hex5 } from "@scure/base";
867
+ function strip0x(value) {
868
+ return value.startsWith("0x") ? value.slice(2) : value;
869
+ }
870
+ function xOnly(pubKeyHex) {
871
+ const bytes = hex5.decode(strip0x(pubKeyHex));
872
+ if (bytes.length === 33) return bytes.slice(1);
873
+ if (bytes.length === 32) return bytes;
874
+ throw new Error(`invalid public key length: ${bytes.length}`);
875
+ }
876
+ var seconds = (value) => ({ type: "seconds", value: BigInt(value) });
877
+ function buildArkadeVhtlcRef(input) {
878
+ const hashLockBytes = hex5.decode(strip0x(input.hashLock));
879
+ const params = {
880
+ sender: xOnly(input.senderPk),
881
+ receiver: xOnly(input.receiverPk),
882
+ server: xOnly(input.serverPk),
883
+ preimageHash: hashLockBytes.length === 20 ? hashLockBytes : ripemd1602(hashLockBytes),
884
+ refundLocktime: BigInt(input.refundLocktime),
885
+ unilateralClaimDelay: seconds(input.unilateralClaimDelay),
886
+ unilateralRefundDelay: seconds(input.unilateralRefundDelay),
887
+ unilateralRefundWithoutReceiverDelay: seconds(
888
+ input.unilateralRefundWithoutReceiverDelay
889
+ )
890
+ };
891
+ const vhtlc = new VHTLC.Script(params);
892
+ return {
893
+ ledger: "arkade",
894
+ script: hex5.encode(vhtlc.pkScript),
895
+ address: input.address,
896
+ preimageHash: strip0x(input.hashLock),
897
+ expectedSats: input.expectedSats,
898
+ params: VHTLCContractHandler.serializeParams(params)
899
+ };
900
+ }
901
+
902
+ // src/tracker/from-swap.ts
903
+ var ms = (seconds2) => seconds2 * 1e3;
904
+ var ensure0x = (value) => value.startsWith("0x") ? value : `0x${value}`;
905
+ var strip0x2 = (value) => value.startsWith("0x") ? value.slice(2) : value;
906
+ function arkadeLeg(r, address, expectedSats) {
907
+ return buildArkadeVhtlcRef({
908
+ senderPk: r.sender_pk,
909
+ receiverPk: r.receiver_pk,
910
+ serverPk: r.arkade_server_pk,
911
+ hashLock: r.hash_lock,
912
+ address,
913
+ refundLocktime: r.vhtlc_refund_locktime,
914
+ unilateralClaimDelay: r.unilateral_claim_delay,
915
+ unilateralRefundDelay: r.unilateral_refund_delay,
916
+ unilateralRefundWithoutReceiverDelay: r.unilateral_refund_without_receiver_delay,
917
+ expectedSats
918
+ });
919
+ }
920
+ function evmLeg(args) {
921
+ return {
922
+ ledger: "evm",
923
+ chainId: args.chainId,
924
+ htlc: ensure0x(args.htlc),
925
+ preimageHash: ensure0x(args.hashLock),
926
+ claimAddress: ensure0x(args.claimAddress),
927
+ expectedAmount: BigInt(args.expectedSats),
928
+ expectedToken: args.token ? ensure0x(args.token) : void 0
929
+ };
930
+ }
931
+ function bitcoinLeg(address, hashLock, expectedSats) {
932
+ return {
933
+ ledger: "bitcoin",
934
+ address,
935
+ preimageHash: strip0x2(hashLock),
936
+ expectedSats
937
+ };
938
+ }
939
+ function swapToTracked(stored) {
940
+ const r = stored.response;
941
+ switch (r.direction) {
942
+ // Client sends Arkade (funds the VHTLC with source_amount sats) and claims EVM.
943
+ case "arkade_to_evm":
944
+ return {
945
+ swapId: r.id,
946
+ clientHtlc: arkadeLeg(r, r.btc_vhtlc_address, Number(r.source_amount)),
947
+ serverHtlc: evmLeg({
948
+ chainId: r.evm_chain_id,
949
+ htlc: r.evm_htlc_address,
950
+ hashLock: r.hash_lock,
951
+ claimAddress: r.client_evm_address,
952
+ // the client claims the server's EVM HTLC
953
+ expectedSats: r.evm_expected_sats,
954
+ token: r.wbtc_address
955
+ }),
956
+ clientRefundLocktime: ms(r.vhtlc_refund_locktime),
957
+ serverRefundLocktime: ms(r.evm_refund_locktime)
958
+ };
959
+ // Client sends EVM and claims the Arkade VHTLC (server funds it with target_amount sats).
960
+ case "evm_to_arkade":
961
+ return {
962
+ swapId: r.id,
963
+ clientHtlc: evmLeg({
964
+ chainId: r.evm_chain_id,
965
+ htlc: r.evm_htlc_address,
966
+ hashLock: r.hash_lock,
967
+ claimAddress: r.server_evm_address,
968
+ // the server claims the client's EVM HTLC
969
+ expectedSats: r.evm_expected_sats
970
+ // evm_to_arkade doesn't expose the locked token — verify amount only.
971
+ }),
972
+ serverHtlc: arkadeLeg(r, r.btc_vhtlc_address, Number(r.target_amount)),
973
+ clientRefundLocktime: ms(r.evm_refund_locktime),
974
+ serverRefundLocktime: ms(r.vhtlc_refund_locktime)
975
+ };
976
+ // Client sends BTC (funds the on-chain HTLC with source_amount sats) and claims EVM.
977
+ case "bitcoin_to_evm":
978
+ return {
979
+ swapId: r.id,
980
+ clientHtlc: bitcoinLeg(
981
+ r.btc_htlc_address,
982
+ r.evm_hash_lock,
983
+ Number(r.source_amount)
984
+ ),
985
+ serverHtlc: evmLeg({
986
+ chainId: r.evm_chain_id,
987
+ htlc: r.evm_htlc_address,
988
+ hashLock: r.evm_hash_lock,
989
+ claimAddress: r.client_evm_address,
990
+ // the client claims the server's EVM HTLC
991
+ expectedSats: r.evm_expected_sats,
992
+ token: r.wbtc_address
993
+ }),
994
+ clientRefundLocktime: ms(r.btc_refund_locktime),
995
+ serverRefundLocktime: ms(r.evm_refund_locktime)
996
+ };
997
+ // Client sends EVM and claims the BTC HTLC (server funds it with target_amount sats).
998
+ case "evm_to_bitcoin":
999
+ return {
1000
+ swapId: r.id,
1001
+ clientHtlc: evmLeg({
1002
+ chainId: r.evm_chain_id,
1003
+ htlc: r.evm_htlc_address,
1004
+ hashLock: r.evm_hash_lock,
1005
+ claimAddress: r.server_evm_address,
1006
+ // the server claims the client's EVM HTLC
1007
+ expectedSats: r.evm_expected_sats,
1008
+ token: r.wbtc_address
1009
+ }),
1010
+ serverHtlc: bitcoinLeg(
1011
+ r.btc_htlc_address,
1012
+ r.evm_hash_lock,
1013
+ Number(r.target_amount)
1014
+ ),
1015
+ clientRefundLocktime: ms(r.evm_refund_locktime),
1016
+ serverRefundLocktime: ms(r.btc_refund_locktime)
1017
+ };
1018
+ // Client sends BTC (funds the on-chain HTLC) and claims the Arkade VHTLC (the
1019
+ // server funds it). Unlike the other Arkade directions, the VHTLC's receiver is
1020
+ // the client's own key (not in the response) and its funder is `server_vhtlc_pk`;
1021
+ // the hash lock is a 20-byte HASH160, which buildArkadeVhtlcRef takes as-is.
1022
+ case "btc_to_arkade":
1023
+ return {
1024
+ swapId: r.id,
1025
+ clientHtlc: bitcoinLeg(
1026
+ r.btc_htlc_address,
1027
+ r.hash_lock,
1028
+ Number(r.source_amount)
1029
+ ),
1030
+ serverHtlc: buildArkadeVhtlcRef({
1031
+ senderPk: r.server_vhtlc_pk,
1032
+ // the server funds the VHTLC
1033
+ receiverPk: stored.publicKey,
1034
+ // the client claims it
1035
+ serverPk: r.arkade_server_pk,
1036
+ hashLock: r.hash_lock,
1037
+ address: r.arkade_vhtlc_address,
1038
+ refundLocktime: r.vhtlc_refund_locktime,
1039
+ unilateralClaimDelay: r.unilateral_claim_delay,
1040
+ unilateralRefundDelay: r.unilateral_refund_delay,
1041
+ unilateralRefundWithoutReceiverDelay: r.unilateral_refund_without_receiver_delay,
1042
+ expectedSats: Number(r.target_amount)
1043
+ }),
1044
+ clientRefundLocktime: ms(r.btc_refund_locktime),
1045
+ serverRefundLocktime: ms(r.vhtlc_refund_locktime)
1046
+ };
1047
+ // ─── Lightning: one on-chain leg, one off-chain LN payment ───────────────
1048
+ // Receive-on-Lightning: the client funds an on-chain HTLC and is paid over
1049
+ // Lightning. No server leg to watch — the server sweeping the client's leg
1050
+ // (with the preimage it got by paying the invoice) is the done signal.
1051
+ // Client funds the Arkade VHTLC; Boltz pays the invoice. The deposit is
1052
+ // `boltz_amount_sats` (source minus fees) — the amount actually locked, not
1053
+ // `source_amount` — else the client's own funding reads as under-funded.
1054
+ case "arkade_to_lightning":
1055
+ return {
1056
+ swapId: r.id,
1057
+ clientHtlc: arkadeLeg(
1058
+ r,
1059
+ r.arkade_vhtlc_address,
1060
+ Number(r.boltz_amount_sats)
1061
+ ),
1062
+ clientRefundLocktime: ms(r.vhtlc_refund_locktime),
1063
+ serverRefundLocktime: 0
1064
+ // no on-chain server leg
1065
+ };
1066
+ // Client funds the EVM HTLC (server claims it after paying the invoice).
1067
+ case "evm_to_lightning":
1068
+ return {
1069
+ swapId: r.id,
1070
+ clientHtlc: evmLeg({
1071
+ chainId: r.evm_chain_id,
1072
+ htlc: r.evm_htlc_address,
1073
+ hashLock: r.hash_lock,
1074
+ claimAddress: r.server_evm_address,
1075
+ // the server claims the client's leg
1076
+ expectedSats: r.evm_expected_sats
1077
+ // evm_to_lightning doesn't expose the locked token — verify amount only.
1078
+ }),
1079
+ clientRefundLocktime: ms(r.evm_refund_locktime),
1080
+ serverRefundLocktime: 0
1081
+ // no on-chain server leg
1082
+ };
1083
+ // Pay-on-Lightning: the client pays a Lightning invoice (off-chain, nothing to
1084
+ // watch) and claims an on-chain HTLC. No client-funded leg — the client's
1085
+ // claim completes the swap; a hold invoice that never settles auto-unwinds.
1086
+ // Client pays the invoice, then claims the Arkade VHTLC (target_amount).
1087
+ case "lightning_to_arkade":
1088
+ return {
1089
+ swapId: r.id,
1090
+ serverHtlc: arkadeLeg(
1091
+ r,
1092
+ r.arkade_vhtlc_address,
1093
+ Number(r.target_amount)
1094
+ ),
1095
+ clientRefundLocktime: 0,
1096
+ // no on-chain client leg
1097
+ serverRefundLocktime: ms(r.vhtlc_refund_locktime)
1098
+ };
1099
+ // Client pays the invoice, then claims the EVM HTLC.
1100
+ case "lightning_to_evm":
1101
+ return {
1102
+ swapId: r.id,
1103
+ serverHtlc: evmLeg({
1104
+ chainId: r.evm_chain_id,
1105
+ htlc: r.evm_htlc_address,
1106
+ hashLock: r.hash_lock,
1107
+ claimAddress: r.client_evm_address,
1108
+ // the client claims the server's leg
1109
+ expectedSats: r.evm_expected_sats,
1110
+ token: r.wbtc_address
1111
+ }),
1112
+ clientRefundLocktime: 0,
1113
+ // no on-chain client leg
1114
+ serverRefundLocktime: ms(r.evm_refund_locktime)
1115
+ };
1116
+ default:
1117
+ return void 0;
1118
+ }
1119
+ }
1120
+
1121
+ // src/tracker/swap-tracker.ts
1122
+ var SwapTracker = class {
1123
+ #managers;
1124
+ #swaps = /* @__PURE__ */ new Map();
1125
+ /** Last actions emitted per swap — for dedupe and the subscribe-time snapshot. */
1126
+ #lastActions = /* @__PURE__ */ new Map();
1127
+ #subscribers = /* @__PURE__ */ new Set();
1128
+ #refreshIntervalMs;
1129
+ #eventUnsubs = [];
1130
+ #timer;
1131
+ constructor(managers, options) {
1132
+ this.#managers = managers;
1133
+ this.#refreshIntervalMs = options?.refreshIntervalMs ?? 0;
1134
+ }
1135
+ /** Register each swap's on-chain leg(s), subscribe to manager events, and seed state. */
1136
+ async startTracking(swaps) {
1137
+ for (const swap of swaps) {
1138
+ this.#swaps.set(swap.swapId, swap);
1139
+ for (const leg of legsOf(swap)) await this.#managerFor(leg).register(leg);
1140
+ }
1141
+ for (const manager of new Set(this.#managers.values())) {
1142
+ this.#eventUnsubs.push(manager.onEvent(() => this.#recomputeAll()));
1143
+ }
1144
+ await this.#refreshManagers();
1145
+ this.#recomputeAll();
1146
+ if (this.#refreshIntervalMs > 0)
1147
+ this.#timer = setInterval(
1148
+ () => void this.#tick(),
1149
+ this.#refreshIntervalMs
1150
+ );
1151
+ }
1152
+ async #tick() {
1153
+ await this.#refreshManagers();
1154
+ this.#recomputeAll();
1155
+ }
1156
+ /**
1157
+ * Re-poll the managers that have a registered leg — seeding/advancing their
1158
+ * clocks and reconciling observations. Managers with nothing tracked are
1159
+ * skipped: refreshing them still hits their clock source (e.g. Arkade/Bitcoin
1160
+ * MTP via the API), so a swap that doesn't touch a ledger can't fail on that
1161
+ * ledger's endpoint being down (the default client builds every manager).
1162
+ */
1163
+ async #refreshManagers() {
1164
+ const active = /* @__PURE__ */ new Set();
1165
+ for (const swap of this.#swaps.values())
1166
+ for (const leg of legsOf(swap)) active.add(leg.ledger);
1167
+ const managers = new Set(
1168
+ [...active].map((ledger) => this.#managers.get(ledger)).filter((m) => m !== void 0)
1169
+ );
1170
+ await Promise.all([...managers].map((manager) => manager.refresh()));
1171
+ }
1172
+ /** Notify `cb` of the current action for each tracked swap, then on every change. */
1173
+ subscribeToActions(cb) {
1174
+ this.#subscribers.add(cb);
1175
+ for (const [swapId, actions] of this.#lastActions) cb(swapId, actions);
1176
+ return () => this.#subscribers.delete(cb);
1177
+ }
1178
+ /**
1179
+ * Stop tracking: unregister every still-tracked leg (releasing its manager's
1180
+ * watch — e.g. the EVM per-chain block watch that only `unregister` tears down),
1181
+ * drop the poll timer, and detach event listeners + subscribers. Mirrors
1182
+ * `startTracking`'s `register`, so no manager keeps watching a stopped tracker's
1183
+ * swaps. The managers themselves are not owned here, so they are unregistered but
1184
+ * never disposed.
1185
+ */
1186
+ stop() {
1187
+ if (this.#timer) clearInterval(this.#timer);
1188
+ this.#timer = void 0;
1189
+ for (const unsub of this.#eventUnsubs) unsub();
1190
+ this.#eventUnsubs = [];
1191
+ for (const swap of this.#swaps.values())
1192
+ for (const leg of legsOf(swap))
1193
+ void this.#managerFor(leg).unregister(leg);
1194
+ this.#swaps.clear();
1195
+ this.#subscribers.clear();
1196
+ }
1197
+ #managerFor(ref) {
1198
+ const manager = this.#managers.get(ref.ledger);
1199
+ if (!manager)
1200
+ throw new Error(`no ContractManager for ledger '${ref.ledger}'`);
1201
+ return manager;
1202
+ }
1203
+ #recomputeAll() {
1204
+ for (const swap of this.#swaps.values()) this.#recompute(swap);
1205
+ }
1206
+ #recompute(swap) {
1207
+ let clientHtlc;
1208
+ let serverHtlc;
1209
+ let clientChainNow = 0;
1210
+ let serverChainNow = 0;
1211
+ if (swap.clientHtlc) {
1212
+ const m = this.#managerFor(swap.clientHtlc);
1213
+ clientHtlc = m.getState(swap.clientHtlc);
1214
+ const now = m.chainNow(swap.clientHtlc);
1215
+ if (clientHtlc === void 0 || now === void 0) return;
1216
+ clientChainNow = now;
1217
+ }
1218
+ if (swap.serverHtlc) {
1219
+ const m = this.#managerFor(swap.serverHtlc);
1220
+ serverHtlc = m.getState(swap.serverHtlc);
1221
+ const now = m.chainNow(swap.serverHtlc);
1222
+ if (serverHtlc === void 0 || now === void 0) return;
1223
+ serverChainNow = now;
1224
+ }
1225
+ const status = deriveSwapStatus({ clientHtlc, serverHtlc });
1226
+ if (status === void 0) return;
1227
+ const actions = deriveSwapActions({
1228
+ status,
1229
+ clientChainNow,
1230
+ serverChainNow,
1231
+ clientRefundLocktime: swap.clientRefundLocktime,
1232
+ serverRefundLocktime: swap.serverRefundLocktime,
1233
+ // Pay-on-Lightning swaps have no client-funded on-chain leg.
1234
+ clientFunds: swap.clientHtlc !== void 0
1235
+ });
1236
+ const previous = this.#lastActions.get(swap.swapId);
1237
+ if (previous && JSON.stringify(previous) === JSON.stringify(actions))
1238
+ return;
1239
+ this.#lastActions.set(swap.swapId, actions);
1240
+ for (const cb of this.#subscribers) cb(swap.swapId, actions);
1241
+ if (actions.recommended === "none") this.#untrack(swap);
1242
+ }
1243
+ #untrack(swap) {
1244
+ this.#swaps.delete(swap.swapId);
1245
+ for (const leg of legsOf(swap)) void this.#managerFor(leg).unregister(leg);
1246
+ }
1247
+ };
1248
+ function legsOf(swap) {
1249
+ const legs = [];
1250
+ if (swap.clientHtlc) legs.push(swap.clientHtlc);
1251
+ if (swap.serverHtlc) legs.push(swap.serverHtlc);
1252
+ return legs;
1253
+ }
1254
+
1255
+ // src/client.ts
1256
+ var Client = class {
1257
+ /** The wrapped legacy client. Calls are forwarded here until migrated. */
1258
+ #legacy;
1259
+ /** Tracking configuration; managers are built lazily from it on first use. */
1260
+ #tracking;
1261
+ /** Per-ledger monitors, resolved once from {@link #tracking}. */
1262
+ #managers;
1263
+ #tracker;
1264
+ #started = false;
1265
+ constructor(...args) {
1266
+ if (args[0] instanceof LegacyClient) {
1267
+ this.#legacy = args[0];
1268
+ this.#tracking = args[1] ?? {
1269
+ enabled: false
1270
+ };
1271
+ } else {
1272
+ this.#legacy = new LegacyClient(
1273
+ ...args
1274
+ );
1275
+ this.#tracking = { enabled: false };
1276
+ }
1277
+ }
1278
+ /** Start building a {@link Client}. */
1279
+ static builder() {
1280
+ return new ClientBuilder();
1281
+ }
1282
+ /** Escape hatch: the underlying legacy client, during migration. */
1283
+ get legacy() {
1284
+ return this.#legacy;
1285
+ }
1286
+ // --- delegated legacy surface (getters) ---
1287
+ /** Delegated to the legacy client (migration checkpoint). */
1288
+ get api() {
1289
+ return this.#legacy.api;
1290
+ }
1291
+ /** Delegated to the legacy client (migration checkpoint). */
1292
+ get baseUrl() {
1293
+ return this.#legacy.baseUrl;
1294
+ }
1295
+ /** Delegated to the legacy client (migration checkpoint). */
1296
+ get cctpInbound() {
1297
+ return this.#legacy.cctpInbound;
1298
+ }
1299
+ /** Delegated to the legacy client (migration checkpoint). */
1300
+ get swapStorage() {
1301
+ return this.#legacy.swapStorage;
1302
+ }
1303
+ // --- delegated legacy surface (methods) ---
1304
+ /** Delegated to the legacy client (migration checkpoint). */
1305
+ subscribeToSwaps(...args) {
1306
+ return this.#legacy.subscribeToSwaps(...args);
1307
+ }
1308
+ /** Delegated to the legacy client (migration checkpoint). */
1309
+ unsubscribeFromSwaps(...args) {
1310
+ return this.#legacy.unsubscribeFromSwaps(...args);
1311
+ }
1312
+ /** Delegated to the legacy client (migration checkpoint). */
1313
+ closeSwapStatusSocket(...args) {
1314
+ return this.#legacy.closeSwapStatusSocket(...args);
1315
+ }
1316
+ /** Delegated to the legacy client (migration checkpoint). */
1317
+ getMnemonic(...args) {
1318
+ return this.#legacy.getMnemonic(...args);
1319
+ }
1320
+ /** Delegated to the legacy client (migration checkpoint). */
1321
+ loadMnemonic(...args) {
1322
+ return this.#legacy.loadMnemonic(...args);
1323
+ }
1324
+ /** Delegated to the legacy client (migration checkpoint). */
1325
+ getNostrKeyHex(...args) {
1326
+ return this.#legacy.getNostrKeyHex(...args);
1327
+ }
1328
+ /** Delegated to the legacy client (migration checkpoint). */
1329
+ getUserIdXpub(...args) {
1330
+ return this.#legacy.getUserIdXpub(...args);
1331
+ }
1332
+ /** Delegated to the legacy client (migration checkpoint). */
1333
+ deriveSwapParams(...args) {
1334
+ return this.#legacy.deriveSwapParams(...args);
1335
+ }
1336
+ /** Delegated to the legacy client (migration checkpoint). */
1337
+ deriveSwapParamsAtIndex(...args) {
1338
+ return this.#legacy.deriveSwapParamsAtIndex(...args);
1339
+ }
1340
+ /** Delegated to the legacy client (migration checkpoint). */
1341
+ getEvmAddress(...args) {
1342
+ return this.#legacy.getEvmAddress(...args);
1343
+ }
1344
+ /** Delegated to the legacy client (migration checkpoint). */
1345
+ getKeyIndex(...args) {
1346
+ return this.#legacy.getKeyIndex(...args);
1347
+ }
1348
+ /** Delegated to the legacy client (migration checkpoint). */
1349
+ setKeyIndex(...args) {
1350
+ return this.#legacy.setKeyIndex(...args);
1351
+ }
1352
+ /** Delegated to the legacy client (migration checkpoint). */
1353
+ getStatus(...args) {
1354
+ return this.#legacy.getStatus(...args);
1355
+ }
1356
+ /** Delegated to the legacy client (migration checkpoint). */
1357
+ healthCheck(...args) {
1358
+ return this.#legacy.healthCheck(...args);
1359
+ }
1360
+ /** Delegated to the legacy client (migration checkpoint). */
1361
+ getVersion(...args) {
1362
+ return this.#legacy.getVersion(...args);
1363
+ }
1364
+ /** Delegated to the legacy client (migration checkpoint). */
1365
+ getSupportAgents(...args) {
1366
+ return this.#legacy.getSupportAgents(...args);
1367
+ }
1368
+ /** Delegated to the legacy client (migration checkpoint). */
1369
+ getMtp(...args) {
1370
+ return this.#legacy.getMtp(...args);
1371
+ }
1372
+ /** Delegated to the legacy client (migration checkpoint). */
1373
+ getTokens(...args) {
1374
+ return this.#legacy.getTokens(...args);
1375
+ }
1376
+ /** Delegated to the legacy client (migration checkpoint). */
1377
+ getSwapPairs(...args) {
1378
+ return this.#legacy.getSwapPairs(...args);
1379
+ }
1380
+ /** Delegated to the legacy client (migration checkpoint). */
1381
+ getChainConfig(...args) {
1382
+ return this.#legacy.getChainConfig(...args);
1383
+ }
1384
+ /** Delegated to the legacy client (migration checkpoint). */
1385
+ getReferralFee(...args) {
1386
+ return this.#legacy.getReferralFee(...args);
1387
+ }
1388
+ /** Delegated to the legacy client (migration checkpoint). */
1389
+ getNetworkFees(...args) {
1390
+ return this.#legacy.getNetworkFees(...args);
1391
+ }
1392
+ /** Delegated to the legacy client (migration checkpoint). */
1393
+ getDexQuote(...args) {
1394
+ return this.#legacy.getDexQuote(...args);
1395
+ }
1396
+ /** Delegated to the legacy client (migration checkpoint). */
1397
+ composeQuote(...args) {
1398
+ return this.#legacy.composeQuote(...args);
1399
+ }
1400
+ /** Delegated to the legacy client (migration checkpoint). */
1401
+ getQuote(...args) {
1402
+ return this.#legacy.getQuote(...args);
1403
+ }
1404
+ /** Delegated to the legacy client (migration checkpoint). */
1405
+ getSwap(...args) {
1406
+ return this.#legacy.getSwap(...args);
1407
+ }
1408
+ /** Delegated to the legacy client (migration checkpoint). */
1409
+ getBulkStatus(...args) {
1410
+ return this.#legacy.getBulkStatus(...args);
1411
+ }
1412
+ /** Delegated to the legacy client (migration checkpoint). */
1413
+ getStoredSwap(...args) {
1414
+ return this.#legacy.getStoredSwap(...args);
1415
+ }
1416
+ /** Delegated to the legacy client (migration checkpoint). */
1417
+ listAllSwaps(...args) {
1418
+ return this.#legacy.listAllSwaps(...args);
1419
+ }
1420
+ /** Delegated to the legacy client (migration checkpoint). */
1421
+ deleteSwap(...args) {
1422
+ return this.#legacy.deleteSwap(...args);
1423
+ }
1424
+ /** Delegated to the legacy client (migration checkpoint). */
1425
+ clearSwapStorage(...args) {
1426
+ return this.#legacy.clearSwapStorage(...args);
1427
+ }
1428
+ /** Delegated to the legacy client (migration checkpoint). */
1429
+ recoverSwaps(...args) {
1430
+ return this.#legacy.recoverSwaps(...args);
1431
+ }
1432
+ /** Delegated to the legacy client (migration checkpoint). */
1433
+ recoverAllSwaps(...args) {
1434
+ return this.#legacy.recoverAllSwaps(...args);
1435
+ }
1436
+ /** Delegated to the legacy client (migration checkpoint). */
1437
+ amountsForSwap(...args) {
1438
+ return this.#legacy.amountsForSwap(...args);
1439
+ }
1440
+ /** Delegated to the legacy client (migration checkpoint). */
1441
+ claim(...args) {
1442
+ return this.#legacy.claim(...args);
1443
+ }
1444
+ /** Delegated to the legacy client (migration checkpoint). */
1445
+ claimViaGasless(...args) {
1446
+ return this.#legacy.claimViaGasless(...args);
1447
+ }
1448
+ /** Delegated to the legacy client (migration checkpoint). */
1449
+ claimArkade(...args) {
1450
+ return this.#legacy.claimArkade(...args);
1451
+ }
1452
+ /** Delegated to the legacy client (migration checkpoint). */
1453
+ continueArkadeClaimSwap(...args) {
1454
+ return this.#legacy.continueArkadeClaimSwap(...args);
1455
+ }
1456
+ /** Delegated to the legacy client (migration checkpoint). */
1457
+ refundSwap(...args) {
1458
+ return this.#legacy.refundSwap(...args);
1459
+ }
1460
+ /** Delegated to the legacy client (migration checkpoint). */
1461
+ getCollabRefundEvmParams(...args) {
1462
+ return this.#legacy.getCollabRefundEvmParams(...args);
1463
+ }
1464
+ /** Delegated to the legacy client (migration checkpoint). */
1465
+ buildCollabRefundEvmTypedData(...args) {
1466
+ return this.#legacy.buildCollabRefundEvmTypedData(...args);
1467
+ }
1468
+ /** Delegated to the legacy client (migration checkpoint). */
1469
+ collabRefundEvmSwap(...args) {
1470
+ return this.#legacy.collabRefundEvmSwap(...args);
1471
+ }
1472
+ /** Delegated to the legacy client (migration checkpoint). */
1473
+ submitCollabRefundEvm(...args) {
1474
+ return this.#legacy.submitCollabRefundEvm(...args);
1475
+ }
1476
+ /** Delegated to the legacy client (migration checkpoint). */
1477
+ createSwap(...args) {
1478
+ return this.#legacy.createSwap(...args);
1479
+ }
1480
+ /** Delegated to the legacy client (migration checkpoint). */
1481
+ createArkadeToEvmSwapGeneric(...args) {
1482
+ return this.#legacy.createArkadeToEvmSwapGeneric(...args);
1483
+ }
1484
+ /** Delegated to the legacy client (migration checkpoint). */
1485
+ createLightningToEvmSwapGeneric(...args) {
1486
+ return this.#legacy.createLightningToEvmSwapGeneric(...args);
1487
+ }
1488
+ /** Delegated to the legacy client (migration checkpoint). */
1489
+ createBitcoinToEvmSwap(...args) {
1490
+ return this.#legacy.createBitcoinToEvmSwap(...args);
1491
+ }
1492
+ /** Delegated to the legacy client (migration checkpoint). */
1493
+ createBitcoinToArkadeSwap(...args) {
1494
+ return this.#legacy.createBitcoinToArkadeSwap(...args);
1495
+ }
1496
+ /** Delegated to the legacy client (migration checkpoint). */
1497
+ createLightningToArkadeSwap(...args) {
1498
+ return this.#legacy.createLightningToArkadeSwap(...args);
1499
+ }
1500
+ /** Delegated to the legacy client (migration checkpoint). */
1501
+ createArkadeToLightningSwap(...args) {
1502
+ return this.#legacy.createArkadeToLightningSwap(...args);
1503
+ }
1504
+ /** Delegated to the legacy client (migration checkpoint). */
1505
+ getArkadeToLightningQuote(...args) {
1506
+ return this.#legacy.getArkadeToLightningQuote(...args);
1507
+ }
1508
+ /** Delegated to the legacy client (migration checkpoint). */
1509
+ retryArkadeToLightningSwap(...args) {
1510
+ return this.#legacy.retryArkadeToLightningSwap(...args);
1511
+ }
1512
+ /** Delegated to the legacy client (migration checkpoint). */
1513
+ createEvmToArkadeSwapGeneric(...args) {
1514
+ return this.#legacy.createEvmToArkadeSwapGeneric(...args);
1515
+ }
1516
+ /** Delegated to the legacy client (migration checkpoint). */
1517
+ createEvmToBitcoinSwap(...args) {
1518
+ return this.#legacy.createEvmToBitcoinSwap(...args);
1519
+ }
1520
+ /** Delegated to the legacy client (migration checkpoint). */
1521
+ createEvmToLightningSwapGeneric(...args) {
1522
+ return this.#legacy.createEvmToLightningSwapGeneric(...args);
1523
+ }
1524
+ /** Delegated to the legacy client (migration checkpoint). */
1525
+ getCoordinatorFundingCallDataPermit2(...args) {
1526
+ return this.#legacy.getCoordinatorFundingCallDataPermit2(...args);
1527
+ }
1528
+ /** Delegated to the legacy client (migration checkpoint). */
1529
+ getPermit2FundingParamsUnsigned(...args) {
1530
+ return this.#legacy.getPermit2FundingParamsUnsigned(...args);
1531
+ }
1532
+ /** Delegated to the legacy client (migration checkpoint). */
1533
+ fundSwap(...args) {
1534
+ return this.#legacy.fundSwap(...args);
1535
+ }
1536
+ /** Delegated to the legacy client (migration checkpoint). */
1537
+ refundEvmWithSigner(...args) {
1538
+ return this.#legacy.refundEvmWithSigner(...args);
1539
+ }
1540
+ /** Delegated to the legacy client (migration checkpoint). */
1541
+ collabRefundEvmWithSigner(...args) {
1542
+ return this.#legacy.collabRefundEvmWithSigner(...args);
1543
+ }
1544
+ /** Delegated to the legacy client (migration checkpoint). */
1545
+ fundSwapGasless(...args) {
1546
+ return this.#legacy.fundSwapGasless(...args);
1547
+ }
1548
+ /** Delegated to the legacy client (migration checkpoint). */
1549
+ getSwapDepositorKey(...args) {
1550
+ return this.#legacy.getSwapDepositorKey(...args);
1551
+ }
1552
+ /** Delegated to the legacy client (migration checkpoint). */
1553
+ getEvmDepositorKey(...args) {
1554
+ return this.#legacy.getEvmDepositorKey(...args);
1555
+ }
1556
+ /** Delegated to the legacy client (migration checkpoint). */
1557
+ recoverGaslessFunds(...args) {
1558
+ return this.#legacy.recoverGaslessFunds(...args);
1559
+ }
1560
+ /** Delegated to the legacy client (migration checkpoint). */
1561
+ hasReceivedVtxo(...args) {
1562
+ return this.#legacy.hasReceivedVtxo(...args);
1563
+ }
1564
+ // --- Satora-native features go below ---
1565
+ /**
1566
+ * Start observing the user's active swaps and deriving each one's next action.
1567
+ *
1568
+ * Loads stored swaps, maps the ones whose ledgers are observable to
1569
+ * {@link TrackedSwap}s, and runs the per-ledger monitors. Idempotent. Requires
1570
+ * the client to have been built with {@link ClientBuilder.withTracking}.
1571
+ * Subscribe with {@link subscribeToActions}; release with
1572
+ * {@link stopTracking}.
1573
+ */
1574
+ async startTracking() {
1575
+ if (!this.#tracking.enabled)
1576
+ throw new Error(
1577
+ "tracking is disabled \u2014 remove .withoutTracking() to enable it"
1578
+ );
1579
+ if (this.#started) return;
1580
+ this.#started = true;
1581
+ try {
1582
+ const tracker = new SwapTracker(await this.#ensureManagers(), {
1583
+ refreshIntervalMs: this.#tracking.refreshIntervalMs ?? 5e3
1584
+ });
1585
+ this.#tracker = tracker;
1586
+ const swaps = await this.listAllSwaps();
1587
+ const tracked = swaps.map(swapToTracked).filter((s) => s !== void 0);
1588
+ await tracker.startTracking(tracked);
1589
+ } catch (error) {
1590
+ this.#tracker?.stop();
1591
+ this.#tracker = void 0;
1592
+ this.#started = false;
1593
+ throw error;
1594
+ }
1595
+ }
1596
+ /**
1597
+ * Subscribe to next-action updates: `cb` fires with the current action for each
1598
+ * tracked swap immediately, then on every change. Call after
1599
+ * {@link startTracking}. Returns an unsubscribe fn.
1600
+ */
1601
+ subscribeToActions(cb) {
1602
+ if (!this.#tracker)
1603
+ throw new Error("call startTracking() before subscribeToActions()");
1604
+ return this.#tracker.subscribeToActions(cb);
1605
+ }
1606
+ /** Stop tracking and drop subscribers. The managers themselves are not disposed. */
1607
+ stopTracking() {
1608
+ this.#tracker?.stop();
1609
+ this.#tracker = void 0;
1610
+ this.#started = false;
1611
+ }
1612
+ /**
1613
+ * Resolve the per-ledger managers once — the explicit override if given, else
1614
+ * auto-built from config: an {@link ArkadeContractManager} from the configured
1615
+ * Ark server URL (defaulting to the mainnet server), clocked by `getMtp`, and an
1616
+ * {@link EvmContractManager} from the tested default RPCs.
1617
+ */
1618
+ async #ensureManagers() {
1619
+ if (this.#managers) return this.#managers;
1620
+ if (this.#tracking.managers) {
1621
+ this.#managers = this.#tracking.managers;
1622
+ return this.#managers;
1623
+ }
1624
+ const managers = /* @__PURE__ */ new Map();
1625
+ const { arkadeServerUrl, evmRpcUrls, esploraUrl } = this.#tracking;
1626
+ const chainTime = async () => (await this.getMtp()).mtp * 1e3;
1627
+ const arkadeUrl = arkadeServerUrl ?? defaultArkadeServerUrl("bitcoin");
1628
+ if (arkadeUrl) {
1629
+ managers.set(
1630
+ "arkade",
1631
+ await ArkadeContractManager.create({ serverUrl: arkadeUrl, chainTime })
1632
+ );
1633
+ }
1634
+ const readers = defaultEvmReaders(evmRpcUrls);
1635
+ if (readers.size > 0)
1636
+ managers.set("evm", EvmContractManager.fromDeps({ readers }));
1637
+ managers.set(
1638
+ "bitcoin",
1639
+ await BitcoinContractManager.create({
1640
+ esploraUrl: esploraUrl ? [esploraUrl] : DEFAULT_ESPLORA_URLS,
1641
+ chainTime
1642
+ })
1643
+ );
1644
+ this.#managers = managers;
1645
+ return managers;
1646
+ }
1647
+ };
1648
+ var ClientBuilder = class {
1649
+ #inner = LegacyClient.builder();
1650
+ // Observe-mode tracking is on by default; the built client auto-builds its
1651
+ // managers from config unless disabled or given an explicit override.
1652
+ #trackingEnabled = true;
1653
+ #arkadeServerUrl;
1654
+ #esploraUrl;
1655
+ #evmRpcUrls;
1656
+ #managers;
1657
+ /**
1658
+ * Override the EVM JSON-RPC endpoint per chainId. Optional — tracking uses
1659
+ * tested public defaults otherwise; an override is tried first, with the
1660
+ * defaults kept as fallbacks.
1661
+ */
1662
+ withEvmRpcUrls(urls) {
1663
+ this.#evmRpcUrls = urls;
1664
+ return this;
1665
+ }
1666
+ /** Turn observe-mode tracking off. */
1667
+ withoutTracking() {
1668
+ this.#trackingEnabled = false;
1669
+ return this;
1670
+ }
1671
+ /**
1672
+ * Advanced: supply per-ledger {@link ContractManager}s explicitly instead of
1673
+ * letting the client auto-build them from config (useful for tests or custom
1674
+ * chain sources).
1675
+ */
1676
+ withContractManagers(managers) {
1677
+ this.#managers = managers;
1678
+ return this;
1679
+ }
1680
+ withBaseUrl(...args) {
1681
+ this.#inner.withBaseUrl(...args);
1682
+ return this;
1683
+ }
1684
+ withReferralCode(...args) {
1685
+ this.#inner.withReferralCode(...args);
1686
+ return this;
1687
+ }
1688
+ withOrgCode(...args) {
1689
+ this.#inner.withOrgCode(...args);
1690
+ return this;
1691
+ }
1692
+ withDefaultHeaders(...args) {
1693
+ this.#inner.withDefaultHeaders(...args);
1694
+ return this;
1695
+ }
1696
+ /**
1697
+ * Override the Esplora REST URL. Optional for tracking — the Bitcoin manager
1698
+ * otherwise defaults to mainnet mempool.space; set this for a dev/regtest node.
1699
+ */
1700
+ withEsploraUrl(url) {
1701
+ this.#inner.withEsploraUrl(url);
1702
+ this.#esploraUrl = url;
1703
+ return this;
1704
+ }
1705
+ /**
1706
+ * Override the Ark server URL. Optional for tracking — it otherwise defaults to
1707
+ * the mainnet server; set this to track a non-mainnet (dev/signet) deployment.
1708
+ */
1709
+ withArkadeServerUrl(url) {
1710
+ this.#inner.withArkadeServerUrl(url);
1711
+ this.#arkadeServerUrl = url;
1712
+ return this;
1713
+ }
1714
+ withSignerStorage(...args) {
1715
+ this.#inner.withSignerStorage(...args);
1716
+ return this;
1717
+ }
1718
+ withSwapStorage(...args) {
1719
+ this.#inner.withSwapStorage(...args);
1720
+ return this;
1721
+ }
1722
+ withMnemonic(...args) {
1723
+ this.#inner.withMnemonic(...args);
1724
+ return this;
1725
+ }
1726
+ withXprv(...args) {
1727
+ this.#inner.withXprv(...args);
1728
+ return this;
1729
+ }
1730
+ withAa(...args) {
1731
+ this.#inner.withAa(...args);
1732
+ return this;
1733
+ }
1734
+ withLogger(...args) {
1735
+ this.#inner.withLogger(...args);
1736
+ return this;
1737
+ }
1738
+ withLogLevel(...args) {
1739
+ this.#inner.withLogLevel(...args);
1740
+ return this;
1741
+ }
1742
+ async build() {
1743
+ return new Client(await this.#inner.build(), {
1744
+ enabled: this.#trackingEnabled,
1745
+ managers: this.#managers,
1746
+ arkadeServerUrl: this.#arkadeServerUrl,
1747
+ esploraUrl: this.#esploraUrl,
1748
+ evmRpcUrls: this.#evmRpcUrls
1749
+ });
1750
+ }
1751
+ };
1752
+ export {
1753
+ ArkadeContractManager,
1754
+ BitcoinContractManager,
1755
+ Client,
1756
+ ClientBuilder,
1757
+ DEFAULT_ESPLORA_URLS,
1758
+ DEFAULT_EVM_RPCS,
1759
+ EvmContractManager,
1760
+ SwapTracker,
1761
+ buildArkadeVhtlcRef,
1762
+ createEvmRpcReader,
1763
+ defaultArkadeServerUrl,
1764
+ defaultEvmReaders,
1765
+ deriveSwapActions,
1766
+ deriveSwapStatus,
1767
+ esploraReader,
1768
+ evmReaderFromClient,
1769
+ htlcFactsFromEsploraTxs,
1770
+ htlcKey,
1771
+ swapToTracked
1772
+ };
3
1773
  //# sourceMappingURL=index.js.map