@satora/swap 0.1.0 → 0.3.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.d.cts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { SwapStatus, Client as Client$1, ClientBuilder as ClientBuilder$1, StoredSwap } from '@lendasat/lendaswap-sdk-pure';
2
2
  export * from '@lendasat/lendaswap-sdk-pure';
3
3
  import { VirtualCoin } from '@arkade-os/sdk';
4
- import { Log, Block } from 'viem';
4
+ import { Block } from 'viem';
5
5
 
6
6
  /**
7
7
  * The derived "what should the client do next?" model.
@@ -44,8 +44,37 @@ type CctpClaimEvidence = {
44
44
  burnTxHash: `0x${string}`;
45
45
  attestationAvailable: boolean;
46
46
  };
47
+ /**
48
+ * What a `wait` is waiting FOR — machine-readable, so a consumer can pick the
49
+ * right surface (invoice page, "deposit seen…", processing spinner, refund
50
+ * countdown) by switching on this field instead of re-deriving it from
51
+ * status + observations.
52
+ *
53
+ * One caveat: `client_payment` covers the whole Lightning-funded window — the
54
+ * chain cannot see whether the invoice was already paid (the server is the
55
+ * only witness for that split), so a consumer that wants "invoice shown" vs
56
+ * "payment in flight" as separate screens still refines this one value with
57
+ * the server status.
58
+ */
59
+ type WaitingOn =
60
+ /** A Lightning payment to be made / locked in as a funded HTLC. */
61
+ "client_payment"
62
+ /** The client's funding tx was seen and is awaiting confirmation. */
63
+ | "client_funding_confirmation"
64
+ /** The client funded; the server's HTLC hasn't appeared yet. */
65
+ | "server_funding"
66
+ /** The client's claim is in flight, awaiting confirmation. */
67
+ | "claim_confirmation"
68
+ /** Nothing will progress — waiting for the client refund timelock to pass. */
69
+ | "refund_timelock";
70
+ /**
71
+ * How a finished swap ended — so a consumer can render success vs refund vs
72
+ * expiry from the terminal `none` action without interpreting `SwapStatus`.
73
+ */
74
+ type SwapOutcome = "completed" | "refunded" | "expired";
47
75
  type WaitAction = SwapActionBase & {
48
76
  id: "wait";
77
+ waitingOn: WaitingOn;
49
78
  };
50
79
  type FundAction = SwapActionBase & {
51
80
  id: "fund";
@@ -66,6 +95,7 @@ type RecoverCctpClaimAction = SwapActionBase & {
66
95
  };
67
96
  type NoneAction = SwapActionBase & {
68
97
  id: "none";
98
+ outcome: SwapOutcome;
69
99
  };
70
100
  /** A single next-action option. Discriminated union on `id`. */
71
101
  type SwapAction = WaitAction | FundAction | ClaimAction | RefundCollaborativeAction | RefundUnilateralAction | RecoverCctpClaimAction | NoneAction;
@@ -76,6 +106,14 @@ type SwapActions = {
76
106
  /** id of the recommended action, if any; convenience pointer into `actions`. */
77
107
  recommended?: SwapActionId;
78
108
  actions: SwapAction[];
109
+ /**
110
+ * The raw chain observations these actions were derived from (set by the
111
+ * tracker; absent from a bare `deriveSwapActions` call). Chain FACTS — which
112
+ * leg is funded/spent — not protocol states: lets a UI render progress from
113
+ * chain truth (even when the server is stale or unreachable) without ever
114
+ * interpreting the `SwapStatus` enum.
115
+ */
116
+ observations?: SwapObservations;
79
117
  };
80
118
  /**
81
119
  * The pure input to {@link deriveSwapActions}: a plain, fully-resolved snapshot.
@@ -228,15 +266,28 @@ type HtlcRef = {
228
266
  preimageHash: string;
229
267
  /** Expected funding amount in sats; a funding below this is `invalid`. */
230
268
  expectedSats: number;
269
+ /**
270
+ * Confirmations this leg's funding needs before it observes as
271
+ * `confirmed`, overriding the reader's default.
272
+ *
273
+ * Set per leg because the two directions are not symmetric. A funding the
274
+ * SERVER made can be taken at 0-conf: the server does not double-spend its
275
+ * own HTLC, and waiting a block would stall every claim. A funding the
276
+ * CLIENT made is read back under the rule the server applies to it — the
277
+ * server does not act until the tx has a blocktime, so calling it funded
278
+ * sooner reports progress the other side has not agreed to.
279
+ */
280
+ minConfirmations?: number;
231
281
  } | {
232
282
  ledger: "evm";
233
283
  chainId: number;
234
284
  /** The HTLCErc20 contract holding this leg — where its events are emitted. */
235
285
  htlc: `0x${string}`;
236
286
  /**
237
- * SHA256 preimage hash (`0x`-prefixed) — the indexed topic that identifies
238
- * this swap's HTLC in the contract's `SwapCreated`/`SwapRedeemed`/
239
- * `SwapRefunded` events. Serves as the contract's unique id here.
287
+ * SHA256 preimage hash (`0x`-prefixed) — the indexed topic the contract's
288
+ * `SwapCreated`/`SwapRedeemed`/`SwapRefunded` events are filtered by. It
289
+ * narrows a log scan but identifies no single HTLC: any number can share
290
+ * one hash, so a swap is told apart by the rest of the tuple below.
240
291
  */
241
292
  preimageHash: `0x${string}`;
242
293
  /**
@@ -254,6 +305,22 @@ type HtlcRef = {
254
305
  expectedAmount: bigint;
255
306
  /** Expected token address; the funding is `invalid` if it locks another token. */
256
307
  expectedToken?: `0x${string}`;
308
+ /**
309
+ * The funder (the HTLC's refund address). Together with the other fields it
310
+ * completes the contract's swap-key tuple, enabling the cheap `isActive`
311
+ * eth_call fast path (which verifies the terms implicitly — the key hashes
312
+ * amount/token/addresses/timelock). Legs whose response omits a tuple field
313
+ * fall back to log scans.
314
+ */
315
+ sender?: `0x${string}`;
316
+ /** The HTLC refund timelock in unix SECONDS, exactly as funded on-chain. */
317
+ timelockSec?: number;
318
+ /**
319
+ * Swap creation time (ms). Lower-bounds log scans: the HTLC can have no
320
+ * events before the swap existed, so the scan starts near the corresponding
321
+ * block instead of genesis.
322
+ */
323
+ createdAtMs?: number;
257
324
  } | {
258
325
  ledger: "lightning";
259
326
  /** Payment hash — the contract's unique id. */
@@ -272,6 +339,15 @@ declare function htlcKey(ref: HtlcRef): string;
272
339
  interface ContractManager {
273
340
  /** The ledger this manager observes. */
274
341
  readonly ledger: Ledger;
342
+ /**
343
+ * Whether this manager can observe `ref` at all — a permanent capability check,
344
+ * not a transient health probe. `false` means the ledger/chain is not configured
345
+ * (e.g. an EVM chain with no RPC), so the ref can never be observed and the
346
+ * tracker should skip the swap rather than {@link register} it (which throws).
347
+ * Distinct from a register failure, which is a recoverable error on an
348
+ * observable ref.
349
+ */
350
+ canObserve(ref: HtlcRef): boolean;
275
351
  /** Start tracking an HTLC (idempotent); seeds its state from the ledger. */
276
352
  register(ref: HtlcRef): Promise<void>;
277
353
  /** Stop tracking an HTLC. */
@@ -292,6 +368,13 @@ interface ContractManager {
292
368
  onEvent(cb: (ref: HtlcRef, state: HtlcObservation) => void): () => void;
293
369
  /** Force a reconcile from the ledger. */
294
370
  refresh(): Promise<void>;
371
+ /**
372
+ * Re-observe a single tracked HTLC from the ledger now — a targeted refresh, so
373
+ * a faster server hint can trigger an immediate on-chain verification of just
374
+ * that swap's legs without re-scanning every tracked HTLC. A no-op if the ref
375
+ * isn't tracked.
376
+ */
377
+ reconcile(ref: HtlcRef): Promise<void>;
295
378
  /** Tear down subscriptions and background work. */
296
379
  dispose(): void;
297
380
  }
@@ -312,17 +395,54 @@ type TrackedSwap = {
312
395
  type ActionSubscriber = (swapId: string, actions: SwapActions) => void;
313
396
  type SwapTrackerOptions = {
314
397
  /**
315
- * How often (ms) to re-poll every manager to advance clocks and reconcile
316
- * observations. Needed because some ledgers (Arkade) have no event push, so
317
- * state only moves on a poll. `0` disables the timer (tests drive `refresh`).
398
+ * The local tick interval (ms). Each tick recomputes every tracked swap from
399
+ * extrapolated chain clocks — pure local work, NO chain reads — so timelock
400
+ * flips (a refund unlocking, a stale pending swap turning reapable) surface
401
+ * between hints. `0` disables the timer (tests drive `refresh` manually).
318
402
  */
319
403
  refreshIntervalMs?: number;
404
+ /**
405
+ * Minimum ms between chain reconciles of AT-RISK swaps — those whose client
406
+ * leg holds funds on-chain (≥ clientfunded, unresolved), where refund safety
407
+ * must not depend on the server. Everything else advances on server hints
408
+ * only ({@link applyHint}); a swap with nothing at stake costs zero chain
409
+ * reads. Default 60s.
410
+ */
411
+ atRiskReconcileIntervalMs?: number;
320
412
  };
321
413
  declare class SwapTracker {
322
414
  #private;
323
415
  constructor(managers: Map<Ledger, ContractManager>, options?: SwapTrackerOptions);
416
+ /**
417
+ * Whether every on-chain leg of `swap` is observable — i.e. the tracker can
418
+ * derive its status from chain. `false` means a leg is on a ledger/chain this
419
+ * client can't reach (no manager, or an EVM chain with no RPC), so the swap
420
+ * can't be tracked. Pure predicate — callers decide the policy (skip vs reject).
421
+ */
422
+ canObserve(swap: TrackedSwap): boolean;
324
423
  /** Register each swap's on-chain leg(s), subscribe to manager events, and seed state. */
325
424
  startTracking(swaps: TrackedSwap[]): Promise<void>;
425
+ /**
426
+ * Track one more swap after {@link startTracking} — register its on-chain
427
+ * leg(s), seed the managers they touch, and derive its first action. Idempotent:
428
+ * a swap already tracked, or one already seen through to a terminal action, is
429
+ * ignored. New swaps are picked up by the existing per-manager event listeners
430
+ * and poll timer, so nothing else needs re-wiring. Lets a swap created after
431
+ * start (e.g. via `createSwap`) be tracked without a restart.
432
+ */
433
+ track(swap: TrackedSwap): Promise<void>;
434
+ /** The swap ids currently tracked — e.g. to subscribe them to a hint feed. */
435
+ trackedSwapIds(): string[];
436
+ /**
437
+ * Re-verify one swap against the chain right now — the hook a faster server
438
+ * status hint drives. Reconciles just that swap's legs (targeted, on the
439
+ * managers), then recomputes its action. The hint is only a *trigger*: the
440
+ * chain stays the source of truth, so a premature hint that the chain doesn't
441
+ * yet reflect changes nothing. A no-op for an untracked swap.
442
+ */
443
+ applyHint(swapId: string, opts?: {
444
+ force?: boolean;
445
+ }): Promise<void>;
326
446
  /** Notify `cb` of the current action for each tracked swap, then on every change. */
327
447
  subscribeToActions(cb: ActionSubscriber): () => void;
328
448
  /**
@@ -347,10 +467,33 @@ declare class SwapTracker {
347
467
  * individual delegators get replaced with native implementations over time.
348
468
  */
349
469
 
470
+ /**
471
+ * Hint-driven auto-execution, opt-in via {@link ClientBuilder.withAutoClaim}.
472
+ *
473
+ * When set, tracking also opens the server status WebSocket (a faster trigger
474
+ * than the chain poll) and runs a {@link SwapWorker}: it auto-claims a swap the
475
+ * moment the chain confirms it is claimable, and surfaces the rest — a manual
476
+ * `fund`, or a refund the user must confirm — via {@link onActionRequired}.
477
+ */
478
+ type AutoClaimConfig = {
479
+ /**
480
+ * Surface an action that needs the user rather than being auto-run — a manual
481
+ * `fund`, or a refund to confirm. The seam a frontend notification center
482
+ * plugs into. Omit to only auto-claim and ignore the rest.
483
+ */
484
+ onActionRequired?: (swapId: string, actions: SwapActions) => void;
485
+ };
350
486
  /** How the client should set up observe-mode tracking. */
351
487
  type TrackingConfig = {
352
488
  /** Whether tracking is enabled at all (default on; `withoutTracking()` clears). */
353
489
  enabled: boolean;
490
+ /**
491
+ * The swap server URL (from `withBaseUrl`). When set, tracking opens the
492
+ * server's status WebSocket as a HINT feed: a pushed transition triggers a
493
+ * targeted chain verify, so the chain pollers can stay slow. Chain reads
494
+ * remain the source of truth.
495
+ */
496
+ serverUrl?: string;
354
497
  /** Explicit per-ledger managers, bypassing auto-construction (advanced/testing). */
355
498
  managers?: Map<Ledger, ContractManager>;
356
499
  /** Ark server URL override; defaults to the mainnet server when unset. */
@@ -359,8 +502,20 @@ type TrackingConfig = {
359
502
  evmRpcUrls?: Record<number, string>;
360
503
  /** Esplora REST URL override for the Bitcoin manager; defaults to mainnet mempool.space. */
361
504
  esploraUrl?: string;
362
- /** Poll interval (ms) for advancing observations/clocks; defaults to 5s. */
505
+ /**
506
+ * Confirmations a Bitcoin funding tx needs before it observes as
507
+ * `confirmed` (gating e.g. the evm→bitcoin claim). Default `0`: accept
508
+ * 0-conf, trusting the funder not to double-spend. `1` = wait for a block.
509
+ */
510
+ bitcoinMinConfirmations?: number;
511
+ /**
512
+ * Local tick interval (ms) — recomputes actions off extrapolated clocks, no
513
+ * chain reads. Chain reads happen on server hints and, for swaps whose
514
+ * client leg holds funds, on the tracker's at-risk cadence. Defaults to 5s.
515
+ */
363
516
  refreshIntervalMs?: number;
517
+ /** Opt-in hint-driven auto-claim; unset leaves tracking observe-only. */
518
+ autoClaim?: AutoClaimConfig;
364
519
  };
365
520
  declare class Client {
366
521
  #private;
@@ -511,6 +666,10 @@ declare class Client {
511
666
  /** Delegated to the legacy client (migration checkpoint). */
512
667
  getEvmDepositorKey(...args: Parameters<Client$1["getEvmDepositorKey"]>): ReturnType<Client$1["getEvmDepositorKey"]>;
513
668
  /** Delegated to the legacy client (migration checkpoint). */
669
+ getRefundedEvmSwapContinuation(...args: Parameters<Client$1["getRefundedEvmSwapContinuation"]>): ReturnType<Client$1["getRefundedEvmSwapContinuation"]>;
670
+ /** Delegated to the legacy client (migration checkpoint). */
671
+ continueRefundedEvmSwap(...args: Parameters<Client$1["continueRefundedEvmSwap"]>): ReturnType<Client$1["continueRefundedEvmSwap"]>;
672
+ /** Delegated to the legacy client (migration checkpoint). */
514
673
  recoverGaslessFunds(...args: Parameters<Client$1["recoverGaslessFunds"]>): ReturnType<Client$1["recoverGaslessFunds"]>;
515
674
  /** Delegated to the legacy client (migration checkpoint). */
516
675
  hasReceivedVtxo(...args: Parameters<Client$1["hasReceivedVtxo"]>): ReturnType<Client$1["hasReceivedVtxo"]>;
@@ -545,6 +704,17 @@ declare class ClientBuilder {
545
704
  * defaults kept as fallbacks.
546
705
  */
547
706
  withEvmRpcUrls(urls: Record<number, string>): this;
707
+ /**
708
+ * Opt in to hint-driven auto-claim. Tracking then also subscribes to the
709
+ * server status WebSocket (a faster trigger than the chain poll) and, when the
710
+ * chain confirms a swap is claimable, claims it automatically. Actions that
711
+ * need the user — a manual `fund`, or a refund to confirm — are surfaced via
712
+ * `onActionRequired` instead of being run. Off by default: this auto-spends on
713
+ * the user's behalf, so it must be explicit.
714
+ */
715
+ withAutoClaim(options?: {
716
+ onActionRequired?: (swapId: string, actions: SwapActions) => void;
717
+ }): this;
548
718
  /** Turn observe-mode tracking off. */
549
719
  withoutTracking(): this;
550
720
  /**
@@ -562,6 +732,15 @@ declare class ClientBuilder {
562
732
  * otherwise defaults to mainnet mempool.space; set this for a dev/regtest node.
563
733
  */
564
734
  withEsploraUrl(url: string): this;
735
+ /**
736
+ * Confirmations a Bitcoin funding tx needs before it observes as `confirmed`
737
+ * (gating e.g. the evm→bitcoin claim). Default `0`: claim as soon as the
738
+ * funding hits the mempool, which TRUSTS the funder not to double-spend it
739
+ * (claiming publishes the preimage, so a funder that then replaced its
740
+ * funding could take both legs). Set `1` (or more) for a block-depth policy
741
+ * that doesn't rely on the funder's good behaviour.
742
+ */
743
+ withBitcoinMinConfirmations(minConfirmations: number): this;
565
744
  /**
566
745
  * Override the Ark server URL. Optional for tracking — it otherwise defaults to
567
746
  * the mainnet server; set this to track a non-mainnet (dev/signet) deployment.
@@ -586,7 +765,7 @@ type VirtualTxSource = {
586
765
 
587
766
  /**
588
767
  * The Arkade {@link ContractManager} — the stateful I/O adapter that observes a
589
- * swap's VHTLC by polling the Ark indexer.
768
+ * swap's VHTLC via the Ark indexer.
590
769
  *
591
770
  * We deliberately do NOT use `@arkade-os/sdk`'s `ContractManager`: its vtxo
592
771
  * annotation calls `script.forfeit()` on every contract, but a VHTLC script has
@@ -595,11 +774,14 @@ type VirtualTxSource = {
595
774
  * the legacy SDK's claim/refund paths do — and map them with the pure helpers in
596
775
  * `./arkade.js`.
597
776
  *
598
- * There is no event push here, so observations advance when {@link refresh} runs;
599
- * the SwapTracker drives that (initial prime + periodic poll).
777
+ * Observation is on-demand: {@link refresh} (tracker start / newly tracked
778
+ * swap) and targeted {@link reconcile}s (server hints, the tracker's at-risk
779
+ * safety net). There is no standing subscription or periodic poll.
600
780
  */
601
781
 
602
- /** The Ark indexer surface the observer needs (RestIndexerProvider satisfies it). */
782
+ /**
783
+ * The Ark indexer surface the observer needs (RestIndexerProvider satisfies it).
784
+ */
603
785
  type ArkadeIndexer = VirtualTxSource & {
604
786
  getVtxos(opts: {
605
787
  scripts: string[];
@@ -633,12 +815,20 @@ declare class ArkadeContractManager implements ContractManager {
633
815
  static fromDeps(deps: ArkadeContractManagerDeps): ArkadeContractManager;
634
816
  /** Construct a manager backed by a `RestIndexerProvider` over the Ark server. */
635
817
  static create(config: ArkadeCreateConfig): Promise<ArkadeContractManager>;
818
+ canObserve(ref: HtlcRef): boolean;
636
819
  register(ref: HtlcRef): Promise<void>;
637
820
  unregister(ref: HtlcRef): Promise<void>;
638
821
  getState(ref: HtlcRef): HtlcObservation | undefined;
639
822
  chainNow(_ref: HtlcRef): number | undefined;
640
823
  onEvent(cb: (ref: HtlcRef, state: HtlcObservation) => void): () => void;
824
+ /**
825
+ * Full reconcile of every tracked ref (clock + vtxos). Called on demand
826
+ * (tracker start / newly tracked swap), NOT periodically: onward, hints and
827
+ * the tracker's at-risk safety net drive targeted `reconcile`s.
828
+ */
641
829
  refresh(): Promise<void>;
830
+ /** Targeted verify (hint / pre-action path) — never gated. */
831
+ reconcile(ref: HtlcRef): Promise<void>;
642
832
  dispose(): void;
643
833
  /** The verified preimage recovered from a claim spend, if one was seen. */
644
834
  getPreimage(ref: HtlcRef): Uint8Array | undefined;
@@ -651,6 +841,15 @@ declare class ArkadeContractManager implements ContractManager {
651
841
  */
652
842
  declare function defaultArkadeServerUrl(network: string): string | undefined;
653
843
 
844
+ /**
845
+ * Build the Arkade {@link HtlcRef} for a swap's VHTLC leg from the plain swap
846
+ * fields, deriving the pkScript and serialized contract params the
847
+ * `ArkadeContractManager` needs to register it for watching.
848
+ *
849
+ * This is the one place that reconstructs the VHTLC from a swap, mirroring the
850
+ * SDK VHTLC construction, so the mapper and manager stay free of VHTLC internals.
851
+ */
852
+
654
853
  type ArkadeRef = Extract<HtlcRef, {
655
854
  ledger: "arkade";
656
855
  }>;
@@ -675,6 +874,8 @@ type ArkadeVhtlcInput = {
675
874
  unilateralRefundWithoutReceiverDelay: number;
676
875
  /** Expected funding amount in sats (a short funding is `invalid`, not confirmed). */
677
876
  expectedSats: number;
877
+ /** Arkade HTLC script version. Missing means legacy v0 for old stored swaps. */
878
+ arkadeHtlcScriptVersion?: number;
678
879
  };
679
880
  /**
680
881
  * The VHTLC always commits to `ripemd160(sha256(preimage))` (HASH160). When
@@ -703,13 +904,25 @@ type BitcoinHtlcFacts = {
703
904
  /** The esplora surface the observer needs (a `BitcoinReaderEsplora` satisfies it). */
704
905
  type BitcoinChainReader = {
705
906
  /** Funding + spend facts for an HTLC witness-script address. */
706
- getHtlcFacts(address: string): Promise<BitcoinHtlcFacts>;
907
+ /**
908
+ * Funding + spend facts for an HTLC witness-script address. `minConfirmations`
909
+ * overrides the reader's default for this address alone — the two legs of a
910
+ * swap do not share one rule.
911
+ */
912
+ getHtlcFacts(address: string, minConfirmations?: number): Promise<BitcoinHtlcFacts>;
707
913
  };
708
914
  type BitcoinCreateConfig = {
709
915
  /** One or more esplora REST base URLs; several are tried in rotation with failover. */
710
916
  esploraUrl: string | string[];
711
917
  /** The current Bitcoin MTP (ms); typically `async () => (await client.getMtp()).mtp * 1000`. */
712
918
  chainTime?: () => Promise<number>;
919
+ /**
920
+ * Confirmations a funding tx needs before it observes as `confirmed`.
921
+ * Default `0`: accept an unconfirmed funding, trusting the funder not to
922
+ * double-spend it (the claim reveals the preimage against it). `1` = wait
923
+ * for a block.
924
+ */
925
+ minConfirmations?: number;
713
926
  };
714
927
  type BitcoinContractManagerDeps = {
715
928
  reader: BitcoinChainReader;
@@ -721,12 +934,14 @@ declare class BitcoinContractManager implements ContractManager {
721
934
  private constructor();
722
935
  static fromDeps(deps: BitcoinContractManagerDeps): BitcoinContractManager;
723
936
  static create(config: BitcoinCreateConfig): Promise<BitcoinContractManager>;
937
+ canObserve(ref: HtlcRef): boolean;
724
938
  register(ref: HtlcRef): Promise<void>;
725
939
  unregister(ref: HtlcRef): Promise<void>;
726
940
  getState(ref: HtlcRef): HtlcObservation | undefined;
727
941
  chainNow(_ref: HtlcRef): number | undefined;
728
942
  onEvent(cb: (ref: HtlcRef, state: HtlcObservation) => void): () => void;
729
943
  refresh(): Promise<void>;
944
+ reconcile(ref: HtlcRef): Promise<void>;
730
945
  dispose(): void;
731
946
  /** The preimage a claim revealed on this HTLC, if one was seen. */
732
947
  getPreimage(ref: HtlcRef): Uint8Array | undefined;
@@ -756,14 +971,33 @@ type EsploraTx = {
756
971
  }>;
757
972
  status?: {
758
973
  confirmed?: boolean;
974
+ block_height?: number;
759
975
  };
760
976
  };
977
+ /**
978
+ * When a funding tx counts as `confirmed` for observation purposes.
979
+ *
980
+ * `minConfirmations: 0` (the default) accepts an UNCONFIRMED funding, so a swap
981
+ * claims as soon as the funding hits the mempool instead of waiting ~10min for
982
+ * a block. This TRUSTS the funder not to double-spend: claiming publishes the
983
+ * preimage, so a funder who then replaces its funding tx could take both legs.
984
+ * Our own server therefore broadcasts HTLC fundings without signalling RBF.
985
+ * Note that a non-signalling tx is still replaceable by full-RBF miners — set
986
+ * `1` (or more) for a policy that doesn't rely on the funder's good behaviour;
987
+ * values above 1 make the reader fetch the tip height to compute depth.
988
+ */
989
+ type BitcoinConfirmationPolicy = {
990
+ minConfirmations?: number;
991
+ };
761
992
  /**
762
993
  * Reduce an address's esplora tx history to HTLC facts. If a tx spends an output
763
994
  * at the address, that's the resolving spend (its witness classifies claim vs
764
- * refund); otherwise a tx paying the address is the funding.
995
+ * refund); otherwise a tx paying the address is the funding, gated by the
996
+ * confirmation policy (see {@link BitcoinConfirmationPolicy}).
765
997
  */
766
- declare function htlcFactsFromEsploraTxs(txs: EsploraTx[], address: string): BitcoinHtlcFacts;
998
+ declare function htlcFactsFromEsploraTxs(txs: EsploraTx[], address: string, opts?: BitcoinConfirmationPolicy & {
999
+ tipHeight?: number;
1000
+ }): BitcoinHtlcFacts;
767
1001
  /**
768
1002
  * Public esplora endpoints tried in rotation (mainnet). blockstream.info runs the
769
1003
  * reference esplora, so it's API-compatible with mempool.space; spreading calls
@@ -776,7 +1010,7 @@ declare const DEFAULT_ESPLORA_URLS: string[];
776
1010
  * providers) and fails over to the rest on error — so a throttled or failing
777
1011
  * provider doesn't stall tracking.
778
1012
  */
779
- declare function esploraReader(esploraUrls: string | string[], fetchImpl?: typeof fetch): BitcoinChainReader;
1013
+ declare function esploraReader(esploraUrls: string | string[], fetchImpl?: typeof fetch, policy?: BitcoinConfirmationPolicy): BitcoinChainReader;
780
1014
 
781
1015
  /**
782
1016
  * The `EvmContractManager`'s pure core.
@@ -813,17 +1047,80 @@ type EvmHtlcEvent = {
813
1047
  * {@link chainNow} is ref-scoped.
814
1048
  */
815
1049
 
1050
+ /** Identifies one HTLC to read: contract + preimageHash (+ the claim guard). */
1051
+ type EvmHtlcQuery = {
1052
+ htlc: `0x${string}`;
1053
+ preimageHash: `0x${string}`;
1054
+ /** A `SwapCreated` counts only when it pays this address (term check). */
1055
+ claimAddress: `0x${string}`;
1056
+ /**
1057
+ * The rest of the swap-key tuple, when the swap response exposed it. It makes
1058
+ * {@link htlcQueryKey} unique to this HTLC, so two swaps sharing a preimage
1059
+ * hash hold separate entries rather than one overwriting the other. It does
1060
+ * not change how settlements are attributed — those go by hash either way.
1061
+ */
1062
+ terms?: EvmHtlcTerms;
1063
+ };
1064
+ /**
1065
+ * The result key for one query in a batch.
1066
+ *
1067
+ * `preimageHash` identifies no single HTLC — any number can share one — so the
1068
+ * rest of the swap-key tuple is what separates two HTLCs on the same contract.
1069
+ * A caller that knows the tuple gets a key unique to its HTLC; one that does not
1070
+ * falls back to contract + hash, which is ambiguous by construction — two such
1071
+ * callers on one hash share an entry.
1072
+ */
1073
+ declare function htlcQueryKey(q: {
1074
+ htlc: `0x${string}`;
1075
+ preimageHash: `0x${string}`;
1076
+ claimAddress?: `0x${string}`;
1077
+ terms?: EvmHtlcTerms;
1078
+ }): string;
1079
+ /** The swap-key tuple fields beyond contract, hash and claim address. */
1080
+ type EvmHtlcTerms = {
1081
+ amount: bigint;
1082
+ token: `0x${string}`;
1083
+ sender: `0x${string}`;
1084
+ timelockSec: number;
1085
+ };
1086
+ /**
1087
+ * The contract's full swap-key tuple for one HTLC. `isActive` hashes all of it
1088
+ * into the swap key, so an `isActive == true` with these EXPECTED values also
1089
+ * proves the HTLC was funded on exactly the expected terms.
1090
+ */
1091
+ type EvmActiveQuery = {
1092
+ htlc: `0x${string}`;
1093
+ preimageHash: `0x${string}`;
1094
+ amount: bigint;
1095
+ token: `0x${string}`;
1096
+ sender: `0x${string}`;
1097
+ claimAddress: `0x${string}`;
1098
+ timelockSec: number;
1099
+ };
816
1100
  /** Reads `HTLCErc20` state for one EVM chain. Implemented over viem/ethers/etc. */
817
1101
  type EvmChainReader = {
818
1102
  /**
819
- * The decoded lifecycle events (oldest→newest) for the HTLC identified by
820
- * `(htlc contract, preimageHash)`.
1103
+ * The decoded lifecycle events for every queried HTLC, keyed by
1104
+ * {@link htlcQueryKey}. Batched so a whole chain scan costs one RPC request
1105
+ * regardless of how many swaps are tracked; a queried HTLC with no events maps
1106
+ * to an empty array. `fromBlock` lower-bounds the scan (default genesis) —
1107
+ * pass the swaps' creation-block estimate so providers never see an
1108
+ * unbounded log query.
821
1109
  */
822
- getHtlcEvents(htlc: `0x${string}`, preimageHash: `0x${string}`, claimAddress: `0x${string}`): Promise<EvmHtlcEvent[]>;
823
- /** The latest block's `block.timestamp`, in ms. */
824
- getBlockTimeMs(): Promise<number>;
825
- /** Fire `cb` when new blocks/logs may have changed watched HTLCs; returns unsubscribe. */
826
- watch(cb: () => void): () => void;
1110
+ getHtlcEventsBatch(queries: EvmHtlcQuery[], fromBlock?: bigint): Promise<Map<string, EvmHtlcEvent[]>>;
1111
+ /**
1112
+ * Whether each queried HTLC is currently open, keyed by {@link htlcQueryKey}.
1113
+ * The cheap routine check (`eth_call`, batched via Multicall3): `true` means
1114
+ * funded-and-unspent on exactly the queried terms; `false` is ambiguous
1115
+ * (never funded / claimed / refunded) and needs {@link getHtlcEventsBatch}
1116
+ * to classify.
1117
+ */
1118
+ isActiveBatch(queries: EvmActiveQuery[]): Promise<Map<string, boolean>>;
1119
+ /** The latest block's timestamp (ms) and number. */
1120
+ getLatestBlock(): Promise<{
1121
+ timeMs: number;
1122
+ number: bigint;
1123
+ }>;
827
1124
  };
828
1125
  type EvmContractManagerDeps = {
829
1126
  /** A chain reader per EVM `chainId` this manager serves. */
@@ -834,12 +1131,21 @@ declare class EvmContractManager implements ContractManager {
834
1131
  readonly ledger: Ledger;
835
1132
  private constructor();
836
1133
  static fromDeps(deps: EvmContractManagerDeps): EvmContractManager;
1134
+ canObserve(ref: HtlcRef): boolean;
837
1135
  register(ref: HtlcRef): Promise<void>;
838
1136
  unregister(ref: HtlcRef): Promise<void>;
839
1137
  getState(ref: HtlcRef): HtlcObservation | undefined;
840
1138
  chainNow(ref: HtlcRef): number | undefined;
841
1139
  onEvent(cb: (ref: HtlcRef, state: HtlcObservation) => void): () => void;
1140
+ /**
1141
+ * Full reconcile of every tracked chain — seeds clocks + observations. Called
1142
+ * on demand (tracker start / newly tracked swap), NOT periodically: onward,
1143
+ * hints drive targeted `reconcile`s and the tracker's at-risk safety net
1144
+ * re-reads only swaps with client funds on the line.
1145
+ */
842
1146
  refresh(): Promise<void>;
1147
+ /** Targeted verify (hint / pre-action path) — never gated. */
1148
+ reconcile(ref: HtlcRef): Promise<void>;
843
1149
  dispose(): void;
844
1150
  /** The preimage a claim revealed on this HTLC, if one was seen. */
845
1151
  getPreimage(ref: HtlcRef): `0x${string}` | undefined;
@@ -852,83 +1158,58 @@ declare class EvmContractManager implements ContractManager {
852
1158
  * Kept separate from the manager so the manager itself stays free of any
853
1159
  * chain-library dependency (and unit-testable against a fake reader). This module
854
1160
  * is the only place that touches viem and the `HTLCErc20` ABI.
1161
+ *
1162
+ * All HTLCs on a chain are read with a SINGLE `eth_getLogs` call: the three
1163
+ * `HTLCErc20` lifecycle events all index `preimageHash` as topic1, so one filter
1164
+ * ORs the three event signatures on topic0 and the tracked preimage hashes on
1165
+ * topic1 across all HTLC contract addresses. Request count per scan is constant
1166
+ * in the number of tracked swaps — this matters, since public RPCs rate-limit by
1167
+ * request count.
855
1168
  */
856
1169
 
857
1170
  /**
858
1171
  * Tested public RPC endpoints per supported chainId, tried in order via viem's
859
- * ranked `fallback`. Mirrors the frontend's `evmTransport.ts` list — the client
1172
+ * `fallback`. Mirrors the frontend's `evmTransport.ts` list — the client
860
1173
  * uses these by default so Arkade↔EVM tracking works out of the box; a caller can
861
1174
  * override per chain with `ClientBuilder.withEvmRpcUrls`.
862
1175
  */
863
1176
  declare const DEFAULT_EVM_RPCS: Record<number, string[]>;
864
- declare const SWAP_CREATED: {
865
- readonly name: "SwapCreated";
866
- readonly type: "event";
867
- readonly inputs: readonly [{
868
- readonly type: "bytes32";
869
- readonly name: "preimageHash";
870
- readonly indexed: true;
871
- }, {
872
- readonly type: "address";
873
- readonly name: "refundAddress";
874
- readonly indexed: true;
875
- }, {
876
- readonly type: "address";
877
- readonly name: "claimAddress";
878
- readonly indexed: true;
879
- }, {
880
- readonly type: "address";
881
- readonly name: "token";
882
- }, {
883
- readonly type: "uint256";
884
- readonly name: "amount";
885
- }, {
886
- readonly type: "uint256";
887
- readonly name: "timelock";
888
- }];
889
- };
890
- declare const SWAP_REDEEMED: {
891
- readonly name: "SwapRedeemed";
892
- readonly type: "event";
893
- readonly inputs: readonly [{
894
- readonly type: "bytes32";
895
- readonly name: "preimageHash";
896
- readonly indexed: true;
897
- }, {
898
- readonly type: "bytes32";
899
- readonly name: "preimage";
900
- }];
901
- };
902
- declare const SWAP_REFUNDED: {
903
- readonly name: "SwapRefunded";
904
- readonly type: "event";
905
- readonly inputs: readonly [{
906
- readonly type: "bytes32";
907
- readonly name: "preimageHash";
908
- readonly indexed: true;
909
- }];
1177
+ /** The undecoded log shape `eth_getLogs` returns. */
1178
+ type RawLog = {
1179
+ address: `0x${string}`;
1180
+ topics: [`0x${string}`, ...`0x${string}`[]];
1181
+ data: `0x${string}`;
910
1182
  };
911
1183
  /** The viem surface the reader needs — a seam so it can be faked in tests. */
912
1184
  type EvmLogClient = {
913
- getLogs(args: {
914
- address: `0x${string}`;
915
- event: typeof SWAP_CREATED | typeof SWAP_REDEEMED | typeof SWAP_REFUNDED;
916
- args: {
917
- preimageHash: `0x${string}`;
918
- claimAddress?: `0x${string}`;
919
- };
920
- fromBlock: bigint | "earliest";
921
- }): Promise<Log[]>;
922
- getBlock(): Promise<Pick<Block, "timestamp">>;
923
- watchBlocks(args: {
924
- onBlock: () => void;
925
- }): () => void;
1185
+ request(args: {
1186
+ method: "eth_getLogs";
1187
+ params: [
1188
+ {
1189
+ address: `0x${string}`[];
1190
+ topics: (`0x${string}`[] | null)[];
1191
+ fromBlock: `0x${string}`;
1192
+ }
1193
+ ];
1194
+ }): Promise<RawLog[]>;
1195
+ /** A read-only `eth_call`. */
1196
+ call(args: {
1197
+ to: `0x${string}`;
1198
+ data: `0x${string}`;
1199
+ }): Promise<{
1200
+ data?: `0x${string}`;
1201
+ }>;
1202
+ getBlock(): Promise<Pick<Block, "timestamp" | "number">>;
926
1203
  };
927
1204
  /** Build an {@link EvmChainReader} over an existing viem-like client. */
928
1205
  declare function evmReaderFromClient(client: EvmLogClient): EvmChainReader;
929
1206
  /**
930
1207
  * Build an {@link EvmChainReader} over one or more EVM JSON-RPC endpoints. With
931
- * several, viem's ranked `fallback` picks the healthiest and fails over.
1208
+ * several, viem's `fallback` tries them in order and fails over on error.
1209
+ * Deliberately NOT ranked: ranking health-pings every listed endpoint on an
1210
+ * interval from every open tab, which is exactly the background burst that got
1211
+ * public RPCs rate-limiting us (publicnode 403s) — and it contradicts this
1212
+ * package's near-zero-RPC design.
932
1213
  */
933
1214
  declare function createEvmRpcReader(rpcUrls: string | string[]): EvmChainReader;
934
1215
  /**
@@ -938,6 +1219,160 @@ declare function createEvmRpcReader(rpcUrls: string | string[]): EvmChainReader;
938
1219
  */
939
1220
  declare function defaultEvmReaders(overrides?: Record<number, string>): Map<number, EvmChainReader>;
940
1221
 
1222
+ /**
1223
+ * Ties the hint feed and the action model together into auto-execution.
1224
+ *
1225
+ * On a server status hint it verifies the swap against the chain
1226
+ * (`tracker.applyHint`) — the hint only makes the derivation faster, the chain
1227
+ * stays the source of truth. Then, on every derived action, it AUTO-RUNS the
1228
+ * recommended action when it is auto-safe (a claim) and SURFACES the rest — a
1229
+ * manual `fund`, or a refund the user must confirm — via `onActionRequired`, the
1230
+ * seam a frontend notification center plugs into.
1231
+ *
1232
+ * A failed auto-claim is RETRIED with backoff. Each retry first re-verifies
1233
+ * against chain (a forced `applyHint`) and only re-runs if the swap is still
1234
+ * claimable — so we never blindly re-submit a claim (which on EVM would waste gas
1235
+ * or double-submit). After the attempt budget is spent the swap is surfaced via
1236
+ * `onActionRequired`, so it is never silently stuck.
1237
+ */
1238
+
1239
+ /** The tracker surface the worker drives. */
1240
+ interface WorkerTracker {
1241
+ trackedSwapIds(): string[];
1242
+ /** Reconcile a swap's legs from chain, then recompute. `force` re-emits even if unchanged. */
1243
+ applyHint(swapId: string, opts?: {
1244
+ force?: boolean;
1245
+ }): Promise<void>;
1246
+ subscribeToActions(cb: (swapId: string, actions: SwapActions) => void): () => void;
1247
+ }
1248
+ /** The hint feed surface the worker consumes (`WsStatusSource` satisfies it). */
1249
+ interface WorkerHintSource {
1250
+ start(): void;
1251
+ stop(): void;
1252
+ subscribe(swapIds: string[]): void;
1253
+ unsubscribe(swapIds: string[]): void;
1254
+ onStatus(cb: (update: {
1255
+ swapId: string;
1256
+ }) => void): () => void;
1257
+ /**
1258
+ * Optional: fires when the feed comes back up after an outage, during which
1259
+ * pushed transitions were lost for good. The worker answers by re-verifying
1260
+ * every tracked swap against the chain.
1261
+ */
1262
+ onReconnect?(cb: () => void): () => void;
1263
+ }
1264
+ /** Backoff policy for retrying a failed auto-claim. */
1265
+ type SwapWorkerRetryOptions = {
1266
+ /** Attempts before giving up and surfacing the swap. Default 5. */
1267
+ maxAttempts?: number;
1268
+ /** Base backoff (ms), doubled each attempt. Default 2000. */
1269
+ baseDelayMs?: number;
1270
+ /** Backoff ceiling (ms). Default 30000. */
1271
+ maxDelayMs?: number;
1272
+ };
1273
+ type SwapWorkerOptions = {
1274
+ tracker: WorkerTracker;
1275
+ hintSource: WorkerHintSource;
1276
+ /**
1277
+ * Run the recommended auto action for a swap (today: claim). Reject to signal
1278
+ * failure — the worker re-verifies against chain and retries with backoff.
1279
+ * Resolve once submitted. OMIT for observe-only mode: hints still trigger
1280
+ * chain re-verifies (fast, cheap derivations), but nothing is ever run.
1281
+ */
1282
+ execute?: (swapId: string, actionId: SwapActionId) => Promise<void>;
1283
+ /**
1284
+ * Surface an action that needs the user rather than being auto-run — a manual
1285
+ * `fund`, a refund the user must confirm, or a claim whose retries are spent.
1286
+ * Where a UI notification hooks in.
1287
+ */
1288
+ onActionRequired?: (swapId: string, actions: SwapActions) => void;
1289
+ /** Auto-claim retry policy; sensible defaults otherwise. */
1290
+ retry?: SwapWorkerRetryOptions;
1291
+ };
1292
+ declare class SwapWorker {
1293
+ #private;
1294
+ constructor(options: SwapWorkerOptions);
1295
+ /** Watch tracked swaps on the hint feed, wire hints → verify, react to actions. */
1296
+ start(): void;
1297
+ /** Stop reacting, cancel pending retries, and close the hint feed. */
1298
+ stop(): void;
1299
+ }
1300
+
1301
+ /**
1302
+ * A standalone subscriber to the server's swap-status WebSocket (`GET /ws`).
1303
+ *
1304
+ * Holds a single reconnecting connection, tracks a live set of subscribed swap
1305
+ * ids, and emits `{ swapId, status }` on every status transition the server
1306
+ * pushes — including the snapshot the server sends right after a subscribe. This
1307
+ * is a *hint* feed only: it neither trusts nor acts on the status. A consumer is
1308
+ * expected to verify against the chain (the contract managers) before doing
1309
+ * anything with it.
1310
+ *
1311
+ * Protocol (see `swap/src/api/ws.rs`):
1312
+ * → {"op":"subscribe","channel":"swap_status","args":[<uuid>,...]} (≤64 ids/frame)
1313
+ * ← {"channel":"swap_status","data":{"<uuid>":"<status>",...}}
1314
+ * ← {"op":"subscribed",...} (ack) ← {"op":...,"message":...} (error)
1315
+ * A single connection carries up to 256 subscriptions; that cap is enforced
1316
+ * client-side (see {@link WsStatusSource.subscribe}) because the server silently
1317
+ * drops the overflow.
1318
+ */
1319
+
1320
+ /** A status update pushed by the server for one swap. */
1321
+ type SwapStatusUpdate = {
1322
+ swapId: string;
1323
+ status: SwapStatus;
1324
+ };
1325
+ /** The minimal WebSocket surface the source needs (the global `WebSocket` satisfies it). */
1326
+ interface WebSocketLike {
1327
+ send(data: string): void;
1328
+ close(): void;
1329
+ onopen: (() => void) | null;
1330
+ onmessage: ((event: {
1331
+ data: unknown;
1332
+ }) => void) | null;
1333
+ onclose: (() => void) | null;
1334
+ onerror: ((event: unknown) => void) | null;
1335
+ }
1336
+ type WsStatusSourceOptions = {
1337
+ /** Server base URL (http/https); the `ws(s)://…/ws` endpoint is derived from it. */
1338
+ serverUrl: string;
1339
+ /** WebSocket factory — defaults to the global `WebSocket` (browsers, Node ≥ 22). */
1340
+ webSocketFactory?: (url: string) => WebSocketLike;
1341
+ /** Reconnect backoff floor/ceiling (ms). Defaults: 1_000 / 30_000. */
1342
+ reconnectMinMs?: number;
1343
+ reconnectMaxMs?: number;
1344
+ };
1345
+ declare class WsStatusSource {
1346
+ #private;
1347
+ constructor(options: WsStatusSourceOptions);
1348
+ /** Open the connection and begin (re)subscribing. Idempotent. */
1349
+ start(): void;
1350
+ /** Close the connection and stop reconnecting. */
1351
+ stop(): void;
1352
+ /** Emit for each pushed status update. Returns an unsubscribe fn. */
1353
+ onStatus(cb: (update: SwapStatusUpdate) => void): () => void;
1354
+ /**
1355
+ * Emit whenever the socket comes (back) up after a disruption — a drop, or a
1356
+ * first connect that had to be retried. NOT emitted on a pristine first open:
1357
+ * hints were never live before it, so there is no gap to recover from. While
1358
+ * the socket was down, pushed transitions were lost for good (the server only
1359
+ * snapshots on subscribe), so a consumer should re-verify its world against
1360
+ * the chain on this signal.
1361
+ */
1362
+ onReconnect(cb: () => void): () => void;
1363
+ /**
1364
+ * Track these swap ids — subscribe on the socket now if open, else on next open.
1365
+ *
1366
+ * Enforces the server's per-connection cap locally. The server silently stops
1367
+ * adding once it is full (it breaks out of the loop but still acks every id),
1368
+ * so sending more would leave us believing ids are subscribed that will never
1369
+ * receive an update. Overflow is queued instead and promoted as slots free.
1370
+ */
1371
+ subscribe(swapIds: string[]): void;
1372
+ /** Stop tracking these swap ids, promoting queued ones into the freed slots. */
1373
+ unsubscribe(swapIds: string[]): void;
1374
+ }
1375
+
941
1376
  /**
942
1377
  * Map a stored swap to the {@link TrackedSwap} the {@link SwapTracker} watches.
943
1378
  *
@@ -953,4 +1388,4 @@ declare function defaultEvmReaders(overrides?: Record<number, string>): Map<numb
953
1388
 
954
1389
  declare function swapToTracked(stored: StoredSwap): TrackedSwap | undefined;
955
1390
 
956
- export { type ActionSubscriber, ArkadeContractManager, type ArkadeContractManagerDeps, type ArkadeCreateConfig, type ArkadeVhtlcInput, type BitcoinChainReader, BitcoinContractManager, type BitcoinContractManagerDeps, type BitcoinCreateConfig, type BlockedBy, type BlockedReason, type CctpClaimEvidence, type ClaimAction, Client, ClientBuilder, type ContractManager, DEFAULT_ESPLORA_URLS, DEFAULT_EVM_RPCS, type EvmChainReader, EvmContractManager, type EvmContractManagerDeps, type EvmLogClient, type FundAction, type HtlcObservation, type HtlcRef, type Ledger, type NoneAction, type RecoverCctpClaimAction, type RefundCollaborativeAction, type RefundUnilateralAction, type SwapAction, type SwapActionAutomation, type SwapActionId, type SwapActionInput, type SwapActions, type SwapObservations, SwapTracker, type TrackedSwap, type WaitAction, buildArkadeVhtlcRef, createEvmRpcReader, defaultArkadeServerUrl, defaultEvmReaders, deriveSwapActions, deriveSwapStatus, esploraReader, evmReaderFromClient, htlcFactsFromEsploraTxs, htlcKey, swapToTracked };
1391
+ export { type ActionSubscriber, ArkadeContractManager, type ArkadeContractManagerDeps, type ArkadeCreateConfig, type ArkadeVhtlcInput, type BitcoinChainReader, type BitcoinConfirmationPolicy, BitcoinContractManager, type BitcoinContractManagerDeps, type BitcoinCreateConfig, type BlockedBy, type BlockedReason, type CctpClaimEvidence, type ClaimAction, Client, ClientBuilder, type ContractManager, DEFAULT_ESPLORA_URLS, DEFAULT_EVM_RPCS, type EvmChainReader, EvmContractManager, type EvmContractManagerDeps, type EvmHtlcQuery, type EvmLogClient, type FundAction, type HtlcObservation, type HtlcRef, type Ledger, type NoneAction, type RawLog, type RecoverCctpClaimAction, type RefundCollaborativeAction, type RefundUnilateralAction, type SwapAction, type SwapActionAutomation, type SwapActionId, type SwapActionInput, type SwapActions, type SwapObservations, type SwapOutcome, type SwapStatusUpdate, SwapTracker, SwapWorker, type SwapWorkerOptions, type TrackedSwap, type WaitAction, type WaitingOn, type WebSocketLike, type WorkerHintSource, type WorkerTracker, WsStatusSource, type WsStatusSourceOptions, buildArkadeVhtlcRef, createEvmRpcReader, defaultArkadeServerUrl, defaultEvmReaders, deriveSwapActions, deriveSwapStatus, esploraReader, evmReaderFromClient, htlcFactsFromEsploraTxs, htlcKey, htlcQueryKey, swapToTracked };