@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/README.md +4 -0
- package/dist/{bitcoin-reader-esplora-GRUP7RAG.js → bitcoin-reader-esplora-HV6CANAA.js} +2 -2
- package/dist/{chunk-J35RZBDW.js → chunk-EIWELJJK.js} +25 -9
- package/dist/chunk-EIWELJJK.js.map +1 -0
- package/dist/index.cjs +1277 -202
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +520 -85
- package/dist/index.d.ts +520 -85
- package/dist/index.js +1258 -193
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/dist/chunk-J35RZBDW.js.map +0 -1
- /package/dist/{bitcoin-reader-esplora-GRUP7RAG.js.map → bitcoin-reader-esplora-HV6CANAA.js.map} +0 -0
package/dist/index.d.ts
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 {
|
|
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
|
|
238
|
-
*
|
|
239
|
-
*
|
|
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
|
-
*
|
|
316
|
-
*
|
|
317
|
-
*
|
|
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
|
-
/**
|
|
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
|
|
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
|
-
*
|
|
599
|
-
*
|
|
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
|
-
/**
|
|
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
|
-
|
|
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
|
|
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
|
|
820
|
-
*
|
|
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
|
-
|
|
823
|
-
/**
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
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
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
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
|
|
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 };
|