@satora/swap 0.0.5 → 0.2.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,4 +1,1177 @@
1
+ import { SwapStatus, Client as Client$1, ClientBuilder as ClientBuilder$1, StoredSwap } from '@lendasat/lendaswap-sdk-pure';
1
2
  export * from '@lendasat/lendaswap-sdk-pure';
2
- import '@lendasat/lendaswap-sdk-pure/node';
3
- import '@lendasat/lendaswap-sdk-pure/delegate';
4
- import '@lendasat/lendaswap-sdk-pure/cctp-bridge';
3
+ import { VirtualCoin } from '@arkade-os/sdk';
4
+ import { Log, Block } from 'viem';
5
+
6
+ /**
7
+ * The derived "what should the client do next?" model.
8
+ *
9
+ * `SwapStatus` says what *happened*; `SwapActions` says what the client *can or
10
+ * should do*. Actions are produced by the pure resolver {@link deriveSwapActions}
11
+ * from a plain {@link SwapActionInput} snapshot — no I/O, so the whole decision
12
+ * matrix is testable with mocked states.
13
+ */
14
+
15
+ /**
16
+ * How the background worker may run an action — the load-bearing safety field.
17
+ * `fund` is never `auto` (spends new money); claim / cctp-mint completion are
18
+ * safe to `auto` (they protect or continue existing funds); refunds are
19
+ * `confirm` (the user should know).
20
+ */
21
+ type SwapActionAutomation = "auto" | "confirm" | "manual";
22
+ /** Typed reasons an action can't run yet — no free strings. */
23
+ type BlockedReason = "timelock_not_expired";
24
+ type BlockedBy = {
25
+ kind: BlockedReason;
26
+ message: string;
27
+ };
28
+ /** Fields common to every action variant. */
29
+ type SwapActionBase = {
30
+ /** The SDK's suggested pick among the set. Exactly one action is recommended. */
31
+ recommended: boolean;
32
+ automation: SwapActionAutomation;
33
+ /** Human-readable why — for UI copy and worker logs. */
34
+ reason: string;
35
+ /**
36
+ * Why the action can't run yet. Absent ⟺ the action is runnable now; present
37
+ * ⟺ it's a valid-but-blocked option the UI can surface (e.g. "refund after the
38
+ * timelock"). The recommended action is always runnable (no `blockedBy`).
39
+ */
40
+ blockedBy?: BlockedBy;
41
+ };
42
+ /** Strongly-typed evidence, only where an action needs it. */
43
+ type CctpClaimEvidence = {
44
+ burnTxHash: `0x${string}`;
45
+ attestationAvailable: boolean;
46
+ };
47
+ type WaitAction = SwapActionBase & {
48
+ id: "wait";
49
+ };
50
+ type FundAction = SwapActionBase & {
51
+ id: "fund";
52
+ };
53
+ type ClaimAction = SwapActionBase & {
54
+ id: "claim";
55
+ };
56
+ type RefundCollaborativeAction = SwapActionBase & {
57
+ id: "refund_collaborative";
58
+ };
59
+ /** Reclaim the deposit yourself once the timelock passes — venue-agnostic (BTC L1, EVM, or Arkade). */
60
+ type RefundUnilateralAction = SwapActionBase & {
61
+ id: "refund_unilateral";
62
+ };
63
+ type RecoverCctpClaimAction = SwapActionBase & {
64
+ id: "recover_cctp_claim";
65
+ evidence: CctpClaimEvidence;
66
+ };
67
+ type NoneAction = SwapActionBase & {
68
+ id: "none";
69
+ };
70
+ /** A single next-action option. Discriminated union on `id`. */
71
+ type SwapAction = WaitAction | FundAction | ClaimAction | RefundCollaborativeAction | RefundUnilateralAction | RecoverCctpClaimAction | NoneAction;
72
+ /** Every valid action id — derived from the union so it can't drift. */
73
+ type SwapActionId = SwapAction["id"];
74
+ /** The resolver's result: what happened is `status`; what to do is here. */
75
+ type SwapActions = {
76
+ /** id of the recommended action, if any; convenience pointer into `actions`. */
77
+ recommended?: SwapActionId;
78
+ actions: SwapAction[];
79
+ };
80
+ /**
81
+ * The pure input to {@link deriveSwapActions}: a plain, fully-resolved snapshot.
82
+ *
83
+ * The resolver reads nothing ambient — `now` is injected (never `Date.now()`),
84
+ * and every environment fact is a field here. Grows as more states are modelled;
85
+ * unknown/omitted live facts make the resolver return a provisional or blocked
86
+ * action rather than reaching out for them.
87
+ */
88
+ type SwapActionInput = {
89
+ /**
90
+ * The swap's lifecycle status. Trusted source is the chain-derived status from
91
+ * {@link deriveSwapStatus}; the server's status is only a hint the wrapper
92
+ * cross-checks against it.
93
+ */
94
+ status: SwapStatus;
95
+ /**
96
+ * Current time (ms) on the chain holding the CLIENT's HTLC — the chain's own
97
+ * clock used to evaluate its timelock, NOT wall-clock: MTP (median-time-past)
98
+ * for Bitcoin/Arkade, block.timestamp for EVM, invoice-clock for Lightning.
99
+ * Injected so the resolver stays deterministic. (A wall-clock approximation
100
+ * works for a UI hint, but the chain's clock is what actually gates the swap.)
101
+ */
102
+ clientChainNow: number;
103
+ /** As {@link clientChainNow}, for the chain holding the SERVER's HTLC. */
104
+ serverChainNow: number;
105
+ /**
106
+ * Timelock (ms) on the client's HTLC, evaluated against {@link clientChainNow}.
107
+ * Bounds funding (pending: fund before it) and unlocks the unilateral refund
108
+ * (clientfunded: refund after it) — the same locktime, two readings.
109
+ */
110
+ clientRefundLocktime: number;
111
+ /**
112
+ * Timelock (ms) on the server's HTLC, evaluated against {@link serverChainNow}
113
+ * — the client's deadline to claim. Past it the server may reclaim its funds,
114
+ * so claiming becomes a race.
115
+ */
116
+ serverRefundLocktime: number;
117
+ /**
118
+ * Whether the client's deposit is an on-chain HTLC it funds itself (the
119
+ * default). `false` for pay-on-Lightning swaps (`lightning_to_*`): there the
120
+ * client "funds" by paying a Lightning invoice off-chain, so there is nothing
121
+ * for us to recommend funding and nothing to unilaterally refund — the
122
+ * Lightning wallet auto-unwinds a hold invoice that never settles. When
123
+ * `false`, `pending` resolves to `wait` (not `fund`) and no refund is offered.
124
+ */
125
+ clientFunds?: boolean;
126
+ };
127
+ /** On-chain state of one HTLC in a swap. */
128
+ type HtlcObservation = "absent" | "mempool" | "confirmed" | "invalid" | "spent_claim" | "spent_refund";
129
+ /**
130
+ * On-chain facts about a swap's two HTLCs, from the client's own observation (no
131
+ * server trust). `clientHtlc` is the HTLC the client funded; `serverHtlc` the one
132
+ * the server funded and the client claims.
133
+ *
134
+ * Spend semantics differ per side: on `clientHtlc`, `spent_claim` = the server
135
+ * swept it with the revealed preimage and `spent_refund` = the client reclaimed
136
+ * its deposit; on `serverHtlc`, `spent_claim` = the client redeemed and
137
+ * `spent_refund` = the server reclaimed its funds.
138
+ *
139
+ * One leg may be `undefined` for Lightning swaps. These are Boltz submarine swaps
140
+ * between an Arkade VHTLC and Lightning, so the "Lightning side" is itself realized
141
+ * on-chain as an Arkade VHTLC (`boltz_vhtlc_address`, or reconstructed for the EVM
142
+ * directions) — except `lightning_to_arkade`, whose Boltz reverse swap outputs the
143
+ * very VHTLC the client claims. That Boltz-side VHTLC is observable but not tracked
144
+ * yet: the client never funds or claims it, so it doesn't change the next action.
145
+ * We watch only the single leg the client funds or claims:
146
+ * - `serverHtlc` undefined (receive-on-LN, `*_to_lightning`): the client funds
147
+ * the on-chain leg and receives via Lightning; the server sweeping the
148
+ * client's leg (`spent_claim`) is the completion signal.
149
+ * - `clientHtlc` undefined (pay-on-LN, `lightning_to_*`): the client pays a
150
+ * Lightning invoice and claims the on-chain leg; that claim completes the swap.
151
+ */
152
+ type SwapObservations = {
153
+ clientHtlc?: HtlcObservation;
154
+ serverHtlc?: HtlcObservation;
155
+ };
156
+
157
+ declare function deriveSwapActions(input: SwapActionInput): SwapActions;
158
+
159
+ /**
160
+ * Derive a swap's lifecycle status from on-chain observations alone — the
161
+ * trusted, server-independent source of truth.
162
+ *
163
+ * Pure: `deriveSwapStatus(observations) -> SwapStatus | undefined`. The wrapper
164
+ * gathers the {@link SwapObservations} from chain (the server's status is only a
165
+ * hint used to fetch efficiently), then cross-checks: if this disagrees with the
166
+ * server, act on this. If the server is unavailable, derive from the local
167
+ * recovery bundle's HTLC addresses — recovery still works.
168
+ *
169
+ * Returns `undefined` for contradictory/indeterminate observations (e.g. the
170
+ * server's HTLC swept-by-claim while the client's HTLC was never involved — the
171
+ * server can't hold the preimage before the client reveals it).
172
+ *
173
+ * Scope: this maps *settled* HTLC state to a status. Time- or param-dependent
174
+ * refinements aren't derivable from HTLC state alone and are layered on
175
+ * afterwards (or collapse into the state here):
176
+ * - `expired` / `clientfundedtoolate`: `pending`/`clientfunded` + a clock check.
177
+ * - `clientinvalidfunded`: needs the expected HTLC params, not just "confirmed".
178
+ * - `serverwontfund`: a server intention, invisible on-chain — acts like
179
+ * `clientfunded` (refund after the timelock), so it never needs deriving.
180
+ * - `clientredeeming`: an in-flight (mempool) claim — a transient, not modelled
181
+ * from settled state here.
182
+ */
183
+
184
+ declare function deriveSwapStatus(obs: SwapObservations): SwapStatus | undefined;
185
+
186
+ /**
187
+ * The contract-monitoring layer — modeled on @arkade-os/sdk's
188
+ * `ContractManager`.
189
+ *
190
+ * A {@link ContractManager} is the per-ledger, stateful I/O layer that watches a
191
+ * set of HTLCs and maps their on-chain state to {@link HtlcObservation}. The pure
192
+ * pipeline (`deriveSwapStatus` → `deriveSwapActions`) consumes the observations
193
+ * it produces; a `SwapTracker` (built later) combines the two ledgers of each
194
+ * swap and notifies subscribers of the next action.
195
+ */
196
+
197
+ /** Ledgers a swap HTLC can live on. */
198
+ type Ledger = "arkade" | "bitcoin" | "evm" | "lightning";
199
+ /**
200
+ * Identifies and parameterises one HTLC on its ledger — enough for a
201
+ * {@link ContractManager} to observe it. Sourced from the swap's recovery
202
+ * bundle. Discriminated by `ledger`.
203
+ */
204
+ type HtlcRef = {
205
+ ledger: "arkade";
206
+ /** VHTLC pkScript (hex) — the contract's unique id. */
207
+ script: string;
208
+ address: string;
209
+ /** SHA256 of the preimage, to tell a claim spend from a refund spend. */
210
+ preimageHash: string;
211
+ /**
212
+ * Expected funding amount in sats. A funding below this is `invalid` (not on
213
+ * the swap's terms) rather than `confirmed` — so the client never claims a
214
+ * server leg the server short-funded.
215
+ */
216
+ expectedSats: number;
217
+ /**
218
+ * Serialized VHTLC parameters (`@arkade-os/sdk`'s `Contract.params`) — the
219
+ * sender/receiver/server keys, preimage hash, and timelocks needed to
220
+ * register the contract for watching. Opaque here: the Client serializes
221
+ * it from the swap's recovery bundle so this layer stays VHTLC-agnostic.
222
+ */
223
+ params: Record<string, string>;
224
+ } | {
225
+ ledger: "bitcoin";
226
+ /** HTLC witness-script address — the contract's unique id. */
227
+ address: string;
228
+ preimageHash: string;
229
+ /** Expected funding amount in sats; a funding below this is `invalid`. */
230
+ expectedSats: number;
231
+ } | {
232
+ ledger: "evm";
233
+ chainId: number;
234
+ /** The HTLCErc20 contract holding this leg — where its events are emitted. */
235
+ htlc: `0x${string}`;
236
+ /**
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.
240
+ */
241
+ preimageHash: `0x${string}`;
242
+ /**
243
+ * The address that can claim this HTLC — the second indexed topic used to
244
+ * filter `SwapCreated`, so only the HTLC actually claimable on the swap's
245
+ * terms is observed (an HTLC with our hash but a different recipient is
246
+ * never seen). Client's EVM address for a server-funded leg; the server's
247
+ * for the client's own leg.
248
+ */
249
+ claimAddress: `0x${string}`;
250
+ /**
251
+ * Expected locked amount (token's smallest unit). A `SwapCreated` below this
252
+ * → `invalid`, so the client never claims a short-funded leg.
253
+ */
254
+ expectedAmount: bigint;
255
+ /** Expected token address; the funding is `invalid` if it locks another token. */
256
+ expectedToken?: `0x${string}`;
257
+ } | {
258
+ ledger: "lightning";
259
+ /** Payment hash — the contract's unique id. */
260
+ paymentHash: string;
261
+ };
262
+ /** Stable, cross-ledger unique key for an {@link HtlcRef}. */
263
+ declare function htlcKey(ref: HtlcRef): string;
264
+ /**
265
+ * Per-ledger contract monitor. Implementations keep an internal, event-driven
266
+ * cache: `register` seeds it, `onEvent` pushes changes, `getState` reads it
267
+ * synchronously, `dispose` tears down subscriptions/loops.
268
+ *
269
+ * One manager exists per ledger actually in use and watches every tracked HTLC
270
+ * on that ledger at once (one connection, not one per HTLC).
271
+ */
272
+ interface ContractManager {
273
+ /** The ledger this manager observes. */
274
+ readonly ledger: Ledger;
275
+ /**
276
+ * Whether this manager can observe `ref` at all — a permanent capability check,
277
+ * not a transient health probe. `false` means the ledger/chain is not configured
278
+ * (e.g. an EVM chain with no RPC), so the ref can never be observed and the
279
+ * tracker should skip the swap rather than {@link register} it (which throws).
280
+ * Distinct from a register failure, which is a recoverable error on an
281
+ * observable ref.
282
+ */
283
+ canObserve(ref: HtlcRef): boolean;
284
+ /** Start tracking an HTLC (idempotent); seeds its state from the ledger. */
285
+ register(ref: HtlcRef): Promise<void>;
286
+ /** Stop tracking an HTLC. */
287
+ unregister(ref: HtlcRef): Promise<void>;
288
+ /**
289
+ * Current known observation for a tracked HTLC, or `undefined` if it isn't
290
+ * tracked or its state isn't known yet.
291
+ */
292
+ getState(ref: HtlcRef): HtlcObservation | undefined;
293
+ /**
294
+ * The current clock (ms) for evaluating this HTLC's timelock — MTP for
295
+ * Bitcoin/Arkade, `block.timestamp` for EVM. Ref-scoped because one ledger can
296
+ * span several chains with independent clocks (EVM chains); managers whose
297
+ * ledger has a single clock (Arkade) ignore the ref. `undefined` until known.
298
+ */
299
+ chainNow(ref: HtlcRef): number | undefined;
300
+ /** Subscribe to observation changes for any tracked HTLC. Returns unsubscribe. */
301
+ onEvent(cb: (ref: HtlcRef, state: HtlcObservation) => void): () => void;
302
+ /** Force a reconcile from the ledger. */
303
+ refresh(): Promise<void>;
304
+ /**
305
+ * Re-observe a single tracked HTLC from the ledger now — a targeted refresh, so
306
+ * a faster server hint can trigger an immediate on-chain verification of just
307
+ * that swap's legs without re-scanning every tracked HTLC. A no-op if the ref
308
+ * isn't tracked.
309
+ */
310
+ reconcile(ref: HtlcRef): Promise<void>;
311
+ /** Tear down subscriptions and background work. */
312
+ dispose(): void;
313
+ }
314
+
315
+ /**
316
+ * A swap the tracker watches: its HTLC legs + refund locktimes (from the recovery
317
+ * bundle). Chain↔chain swaps have both legs; Lightning swaps have exactly one
318
+ * on-chain leg (the other side is an off-chain Lightning payment) — see
319
+ * {@link SwapObservations}.
320
+ */
321
+ type TrackedSwap = {
322
+ swapId: string;
323
+ clientHtlc?: HtlcRef;
324
+ serverHtlc?: HtlcRef;
325
+ clientRefundLocktime: number;
326
+ serverRefundLocktime: number;
327
+ };
328
+ type ActionSubscriber = (swapId: string, actions: SwapActions) => void;
329
+ type SwapTrackerOptions = {
330
+ /**
331
+ * How often (ms) to re-poll every manager to advance clocks and reconcile
332
+ * observations. Needed because some ledgers (Arkade) have no event push, so
333
+ * state only moves on a poll. `0` disables the timer (tests drive `refresh`).
334
+ */
335
+ refreshIntervalMs?: number;
336
+ };
337
+ declare class SwapTracker {
338
+ #private;
339
+ constructor(managers: Map<Ledger, ContractManager>, options?: SwapTrackerOptions);
340
+ /**
341
+ * Whether every on-chain leg of `swap` is observable — i.e. the tracker can
342
+ * derive its status from chain. `false` means a leg is on a ledger/chain this
343
+ * client can't reach (no manager, or an EVM chain with no RPC), so the swap
344
+ * can't be tracked. Pure predicate — callers decide the policy (skip vs reject).
345
+ */
346
+ canObserve(swap: TrackedSwap): boolean;
347
+ /** Register each swap's on-chain leg(s), subscribe to manager events, and seed state. */
348
+ startTracking(swaps: TrackedSwap[]): Promise<void>;
349
+ /**
350
+ * Track one more swap after {@link startTracking} — register its on-chain
351
+ * leg(s), seed the managers they touch, and derive its first action. Idempotent:
352
+ * a swap already tracked, or one already seen through to a terminal action, is
353
+ * ignored. New swaps are picked up by the existing per-manager event listeners
354
+ * and poll timer, so nothing else needs re-wiring. Lets a swap created after
355
+ * start (e.g. via `createSwap`) be tracked without a restart.
356
+ */
357
+ track(swap: TrackedSwap): Promise<void>;
358
+ /** The swap ids currently tracked — e.g. to subscribe them to a hint feed. */
359
+ trackedSwapIds(): string[];
360
+ /**
361
+ * Re-verify one swap against the chain right now — the hook a faster server
362
+ * status hint drives. Reconciles just that swap's legs (targeted, on the
363
+ * managers), then recomputes its action. The hint is only a *trigger*: the
364
+ * chain stays the source of truth, so a premature hint that the chain doesn't
365
+ * yet reflect changes nothing. A no-op for an untracked swap.
366
+ */
367
+ applyHint(swapId: string, opts?: {
368
+ force?: boolean;
369
+ }): Promise<void>;
370
+ /** Notify `cb` of the current action for each tracked swap, then on every change. */
371
+ subscribeToActions(cb: ActionSubscriber): () => void;
372
+ /**
373
+ * Stop tracking: unregister every still-tracked leg (releasing its manager's
374
+ * watch — e.g. the EVM per-chain block watch that only `unregister` tears down),
375
+ * drop the poll timer, and detach event listeners + subscribers. Mirrors
376
+ * `startTracking`'s `register`, so no manager keeps watching a stopped tracker's
377
+ * swaps. The managers themselves are not owned here, so they are unregistered but
378
+ * never disposed.
379
+ */
380
+ stop(): void;
381
+ }
382
+
383
+ /**
384
+ * @satora/swap — the Satora swap client.
385
+ *
386
+ * A standalone `Client` with the same public surface as the legacy
387
+ * `@lendasat/lendaswap-sdk-pure` client, so it is a drop-in replacement. For
388
+ * now every legacy method is forwarded to an internally-owned legacy client
389
+ * instance (see the `@deprecated` delegators below). New Satora-native features
390
+ * are added directly on this class, and
391
+ * individual delegators get replaced with native implementations over time.
392
+ */
393
+
394
+ /**
395
+ * Hint-driven auto-execution, opt-in via {@link ClientBuilder.withAutoClaim}.
396
+ *
397
+ * When set, tracking also opens the server status WebSocket (a faster trigger
398
+ * than the chain poll) and runs a {@link SwapWorker}: it auto-claims a swap the
399
+ * moment the chain confirms it is claimable, and surfaces the rest — a manual
400
+ * `fund`, or a refund the user must confirm — via {@link onActionRequired}.
401
+ */
402
+ type AutoClaimConfig = {
403
+ /**
404
+ * Surface an action that needs the user rather than being auto-run — a manual
405
+ * `fund`, or a refund to confirm. The seam a frontend notification center
406
+ * plugs into. Omit to only auto-claim and ignore the rest.
407
+ */
408
+ onActionRequired?: (swapId: string, actions: SwapActions) => void;
409
+ };
410
+ /** How the client should set up observe-mode tracking. */
411
+ type TrackingConfig = {
412
+ /** Whether tracking is enabled at all (default on; `withoutTracking()` clears). */
413
+ enabled: boolean;
414
+ /** Explicit per-ledger managers, bypassing auto-construction (advanced/testing). */
415
+ managers?: Map<Ledger, ContractManager>;
416
+ /** Ark server URL override; defaults to the mainnet server when unset. */
417
+ arkadeServerUrl?: string;
418
+ /** Per-chain EVM RPC overrides; the EVM manager is auto-built either way. */
419
+ evmRpcUrls?: Record<number, string>;
420
+ /** Esplora REST URL override for the Bitcoin manager; defaults to mainnet mempool.space. */
421
+ esploraUrl?: string;
422
+ /** Poll interval (ms) for advancing observations/clocks; defaults to 5s. */
423
+ refreshIntervalMs?: number;
424
+ /** Opt-in hint-driven auto-claim; unset leaves tracking observe-only. */
425
+ autoClaim?: AutoClaimConfig;
426
+ };
427
+ declare class Client {
428
+ #private;
429
+ /**
430
+ * Creates a new Client instance.
431
+ *
432
+ * Prefer {@link Client.builder} for new code.
433
+ */
434
+ constructor(...args: ConstructorParameters<typeof Client$1>);
435
+ /** @internal Wrap an already-built legacy client, optionally with tracking. */
436
+ constructor(legacy: Client$1, tracking?: TrackingConfig);
437
+ /** Start building a {@link Client}. */
438
+ static builder(): ClientBuilder;
439
+ /** Escape hatch: the underlying legacy client, during migration. */
440
+ get legacy(): Client$1;
441
+ /** Delegated to the legacy client (migration checkpoint). */
442
+ get api(): Client$1["api"];
443
+ /** Delegated to the legacy client (migration checkpoint). */
444
+ get baseUrl(): Client$1["baseUrl"];
445
+ /** Delegated to the legacy client (migration checkpoint). */
446
+ get cctpInbound(): Client$1["cctpInbound"];
447
+ /** Delegated to the legacy client (migration checkpoint). */
448
+ get swapStorage(): Client$1["swapStorage"];
449
+ /** Delegated to the legacy client (migration checkpoint). */
450
+ subscribeToSwaps(...args: Parameters<Client$1["subscribeToSwaps"]>): ReturnType<Client$1["subscribeToSwaps"]>;
451
+ /** Delegated to the legacy client (migration checkpoint). */
452
+ unsubscribeFromSwaps(...args: Parameters<Client$1["unsubscribeFromSwaps"]>): ReturnType<Client$1["unsubscribeFromSwaps"]>;
453
+ /** Delegated to the legacy client (migration checkpoint). */
454
+ closeSwapStatusSocket(...args: Parameters<Client$1["closeSwapStatusSocket"]>): ReturnType<Client$1["closeSwapStatusSocket"]>;
455
+ /** Delegated to the legacy client (migration checkpoint). */
456
+ getMnemonic(...args: Parameters<Client$1["getMnemonic"]>): ReturnType<Client$1["getMnemonic"]>;
457
+ /** Delegated to the legacy client (migration checkpoint). */
458
+ loadMnemonic(...args: Parameters<Client$1["loadMnemonic"]>): ReturnType<Client$1["loadMnemonic"]>;
459
+ /** Delegated to the legacy client (migration checkpoint). */
460
+ getNostrKeyHex(...args: Parameters<Client$1["getNostrKeyHex"]>): ReturnType<Client$1["getNostrKeyHex"]>;
461
+ /** Delegated to the legacy client (migration checkpoint). */
462
+ getUserIdXpub(...args: Parameters<Client$1["getUserIdXpub"]>): ReturnType<Client$1["getUserIdXpub"]>;
463
+ /** Delegated to the legacy client (migration checkpoint). */
464
+ deriveSwapParams(...args: Parameters<Client$1["deriveSwapParams"]>): ReturnType<Client$1["deriveSwapParams"]>;
465
+ /** Delegated to the legacy client (migration checkpoint). */
466
+ deriveSwapParamsAtIndex(...args: Parameters<Client$1["deriveSwapParamsAtIndex"]>): ReturnType<Client$1["deriveSwapParamsAtIndex"]>;
467
+ /** Delegated to the legacy client (migration checkpoint). */
468
+ getEvmAddress(...args: Parameters<Client$1["getEvmAddress"]>): ReturnType<Client$1["getEvmAddress"]>;
469
+ /** Delegated to the legacy client (migration checkpoint). */
470
+ getKeyIndex(...args: Parameters<Client$1["getKeyIndex"]>): ReturnType<Client$1["getKeyIndex"]>;
471
+ /** Delegated to the legacy client (migration checkpoint). */
472
+ setKeyIndex(...args: Parameters<Client$1["setKeyIndex"]>): ReturnType<Client$1["setKeyIndex"]>;
473
+ /** Delegated to the legacy client (migration checkpoint). */
474
+ getStatus(...args: Parameters<Client$1["getStatus"]>): ReturnType<Client$1["getStatus"]>;
475
+ /** Delegated to the legacy client (migration checkpoint). */
476
+ healthCheck(...args: Parameters<Client$1["healthCheck"]>): ReturnType<Client$1["healthCheck"]>;
477
+ /** Delegated to the legacy client (migration checkpoint). */
478
+ getVersion(...args: Parameters<Client$1["getVersion"]>): ReturnType<Client$1["getVersion"]>;
479
+ /** Delegated to the legacy client (migration checkpoint). */
480
+ getSupportAgents(...args: Parameters<Client$1["getSupportAgents"]>): ReturnType<Client$1["getSupportAgents"]>;
481
+ /** Delegated to the legacy client (migration checkpoint). */
482
+ getMtp(...args: Parameters<Client$1["getMtp"]>): ReturnType<Client$1["getMtp"]>;
483
+ /** Delegated to the legacy client (migration checkpoint). */
484
+ getTokens(...args: Parameters<Client$1["getTokens"]>): ReturnType<Client$1["getTokens"]>;
485
+ /** Delegated to the legacy client (migration checkpoint). */
486
+ getSwapPairs(...args: Parameters<Client$1["getSwapPairs"]>): ReturnType<Client$1["getSwapPairs"]>;
487
+ /** Delegated to the legacy client (migration checkpoint). */
488
+ getChainConfig(...args: Parameters<Client$1["getChainConfig"]>): ReturnType<Client$1["getChainConfig"]>;
489
+ /** Delegated to the legacy client (migration checkpoint). */
490
+ getReferralFee(...args: Parameters<Client$1["getReferralFee"]>): ReturnType<Client$1["getReferralFee"]>;
491
+ /** Delegated to the legacy client (migration checkpoint). */
492
+ getNetworkFees(...args: Parameters<Client$1["getNetworkFees"]>): ReturnType<Client$1["getNetworkFees"]>;
493
+ /** Delegated to the legacy client (migration checkpoint). */
494
+ getDexQuote(...args: Parameters<Client$1["getDexQuote"]>): ReturnType<Client$1["getDexQuote"]>;
495
+ /** Delegated to the legacy client (migration checkpoint). */
496
+ composeQuote(...args: Parameters<Client$1["composeQuote"]>): ReturnType<Client$1["composeQuote"]>;
497
+ /** Delegated to the legacy client (migration checkpoint). */
498
+ getQuote(...args: Parameters<Client$1["getQuote"]>): ReturnType<Client$1["getQuote"]>;
499
+ /** Delegated to the legacy client (migration checkpoint). */
500
+ getSwap(...args: Parameters<Client$1["getSwap"]>): ReturnType<Client$1["getSwap"]>;
501
+ /** Delegated to the legacy client (migration checkpoint). */
502
+ getBulkStatus(...args: Parameters<Client$1["getBulkStatus"]>): ReturnType<Client$1["getBulkStatus"]>;
503
+ /** Delegated to the legacy client (migration checkpoint). */
504
+ getStoredSwap(...args: Parameters<Client$1["getStoredSwap"]>): ReturnType<Client$1["getStoredSwap"]>;
505
+ /** Delegated to the legacy client (migration checkpoint). */
506
+ listAllSwaps(...args: Parameters<Client$1["listAllSwaps"]>): ReturnType<Client$1["listAllSwaps"]>;
507
+ /** Delegated to the legacy client (migration checkpoint). */
508
+ deleteSwap(...args: Parameters<Client$1["deleteSwap"]>): ReturnType<Client$1["deleteSwap"]>;
509
+ /** Delegated to the legacy client (migration checkpoint). */
510
+ clearSwapStorage(...args: Parameters<Client$1["clearSwapStorage"]>): ReturnType<Client$1["clearSwapStorage"]>;
511
+ /** Delegated to the legacy client (migration checkpoint). */
512
+ recoverSwaps(...args: Parameters<Client$1["recoverSwaps"]>): ReturnType<Client$1["recoverSwaps"]>;
513
+ /** Delegated to the legacy client (migration checkpoint). */
514
+ recoverAllSwaps(...args: Parameters<Client$1["recoverAllSwaps"]>): ReturnType<Client$1["recoverAllSwaps"]>;
515
+ /** Delegated to the legacy client (migration checkpoint). */
516
+ amountsForSwap(...args: Parameters<Client$1["amountsForSwap"]>): ReturnType<Client$1["amountsForSwap"]>;
517
+ /** Delegated to the legacy client (migration checkpoint). */
518
+ claim(...args: Parameters<Client$1["claim"]>): ReturnType<Client$1["claim"]>;
519
+ /** Delegated to the legacy client (migration checkpoint). */
520
+ claimViaGasless(...args: Parameters<Client$1["claimViaGasless"]>): ReturnType<Client$1["claimViaGasless"]>;
521
+ /** Delegated to the legacy client (migration checkpoint). */
522
+ claimArkade(...args: Parameters<Client$1["claimArkade"]>): ReturnType<Client$1["claimArkade"]>;
523
+ /** Delegated to the legacy client (migration checkpoint). */
524
+ continueArkadeClaimSwap(...args: Parameters<Client$1["continueArkadeClaimSwap"]>): ReturnType<Client$1["continueArkadeClaimSwap"]>;
525
+ /** Delegated to the legacy client (migration checkpoint). */
526
+ refundSwap(...args: Parameters<Client$1["refundSwap"]>): ReturnType<Client$1["refundSwap"]>;
527
+ /** Delegated to the legacy client (migration checkpoint). */
528
+ getCollabRefundEvmParams(...args: Parameters<Client$1["getCollabRefundEvmParams"]>): ReturnType<Client$1["getCollabRefundEvmParams"]>;
529
+ /** Delegated to the legacy client (migration checkpoint). */
530
+ buildCollabRefundEvmTypedData(...args: Parameters<Client$1["buildCollabRefundEvmTypedData"]>): ReturnType<Client$1["buildCollabRefundEvmTypedData"]>;
531
+ /** Delegated to the legacy client (migration checkpoint). */
532
+ collabRefundEvmSwap(...args: Parameters<Client$1["collabRefundEvmSwap"]>): ReturnType<Client$1["collabRefundEvmSwap"]>;
533
+ /** Delegated to the legacy client (migration checkpoint). */
534
+ submitCollabRefundEvm(...args: Parameters<Client$1["submitCollabRefundEvm"]>): ReturnType<Client$1["submitCollabRefundEvm"]>;
535
+ /** Delegated to the legacy client (migration checkpoint). */
536
+ createSwap(...args: Parameters<Client$1["createSwap"]>): ReturnType<Client$1["createSwap"]>;
537
+ /** Delegated to the legacy client (migration checkpoint). */
538
+ createArkadeToEvmSwapGeneric(...args: Parameters<Client$1["createArkadeToEvmSwapGeneric"]>): ReturnType<Client$1["createArkadeToEvmSwapGeneric"]>;
539
+ /** Delegated to the legacy client (migration checkpoint). */
540
+ createLightningToEvmSwapGeneric(...args: Parameters<Client$1["createLightningToEvmSwapGeneric"]>): ReturnType<Client$1["createLightningToEvmSwapGeneric"]>;
541
+ /** Delegated to the legacy client (migration checkpoint). */
542
+ createBitcoinToEvmSwap(...args: Parameters<Client$1["createBitcoinToEvmSwap"]>): ReturnType<Client$1["createBitcoinToEvmSwap"]>;
543
+ /** Delegated to the legacy client (migration checkpoint). */
544
+ createBitcoinToArkadeSwap(...args: Parameters<Client$1["createBitcoinToArkadeSwap"]>): ReturnType<Client$1["createBitcoinToArkadeSwap"]>;
545
+ /** Delegated to the legacy client (migration checkpoint). */
546
+ createLightningToArkadeSwap(...args: Parameters<Client$1["createLightningToArkadeSwap"]>): ReturnType<Client$1["createLightningToArkadeSwap"]>;
547
+ /** Delegated to the legacy client (migration checkpoint). */
548
+ createArkadeToLightningSwap(...args: Parameters<Client$1["createArkadeToLightningSwap"]>): ReturnType<Client$1["createArkadeToLightningSwap"]>;
549
+ /** Delegated to the legacy client (migration checkpoint). */
550
+ getArkadeToLightningQuote(...args: Parameters<Client$1["getArkadeToLightningQuote"]>): ReturnType<Client$1["getArkadeToLightningQuote"]>;
551
+ /** Delegated to the legacy client (migration checkpoint). */
552
+ retryArkadeToLightningSwap(...args: Parameters<Client$1["retryArkadeToLightningSwap"]>): ReturnType<Client$1["retryArkadeToLightningSwap"]>;
553
+ /** Delegated to the legacy client (migration checkpoint). */
554
+ createEvmToArkadeSwapGeneric(...args: Parameters<Client$1["createEvmToArkadeSwapGeneric"]>): ReturnType<Client$1["createEvmToArkadeSwapGeneric"]>;
555
+ /** Delegated to the legacy client (migration checkpoint). */
556
+ createEvmToBitcoinSwap(...args: Parameters<Client$1["createEvmToBitcoinSwap"]>): ReturnType<Client$1["createEvmToBitcoinSwap"]>;
557
+ /** Delegated to the legacy client (migration checkpoint). */
558
+ createEvmToLightningSwapGeneric(...args: Parameters<Client$1["createEvmToLightningSwapGeneric"]>): ReturnType<Client$1["createEvmToLightningSwapGeneric"]>;
559
+ /** Delegated to the legacy client (migration checkpoint). */
560
+ getCoordinatorFundingCallDataPermit2(...args: Parameters<Client$1["getCoordinatorFundingCallDataPermit2"]>): ReturnType<Client$1["getCoordinatorFundingCallDataPermit2"]>;
561
+ /** Delegated to the legacy client (migration checkpoint). */
562
+ getPermit2FundingParamsUnsigned(...args: Parameters<Client$1["getPermit2FundingParamsUnsigned"]>): ReturnType<Client$1["getPermit2FundingParamsUnsigned"]>;
563
+ /** Delegated to the legacy client (migration checkpoint). */
564
+ fundSwap(...args: Parameters<Client$1["fundSwap"]>): ReturnType<Client$1["fundSwap"]>;
565
+ /** Delegated to the legacy client (migration checkpoint). */
566
+ refundEvmWithSigner(...args: Parameters<Client$1["refundEvmWithSigner"]>): ReturnType<Client$1["refundEvmWithSigner"]>;
567
+ /** Delegated to the legacy client (migration checkpoint). */
568
+ collabRefundEvmWithSigner(...args: Parameters<Client$1["collabRefundEvmWithSigner"]>): ReturnType<Client$1["collabRefundEvmWithSigner"]>;
569
+ /** Delegated to the legacy client (migration checkpoint). */
570
+ fundSwapGasless(...args: Parameters<Client$1["fundSwapGasless"]>): ReturnType<Client$1["fundSwapGasless"]>;
571
+ /** Delegated to the legacy client (migration checkpoint). */
572
+ getSwapDepositorKey(...args: Parameters<Client$1["getSwapDepositorKey"]>): ReturnType<Client$1["getSwapDepositorKey"]>;
573
+ /** Delegated to the legacy client (migration checkpoint). */
574
+ getEvmDepositorKey(...args: Parameters<Client$1["getEvmDepositorKey"]>): ReturnType<Client$1["getEvmDepositorKey"]>;
575
+ /** Delegated to the legacy client (migration checkpoint). */
576
+ getRefundedEvmSwapContinuation(...args: Parameters<Client$1["getRefundedEvmSwapContinuation"]>): ReturnType<Client$1["getRefundedEvmSwapContinuation"]>;
577
+ /** Delegated to the legacy client (migration checkpoint). */
578
+ continueRefundedEvmSwap(...args: Parameters<Client$1["continueRefundedEvmSwap"]>): ReturnType<Client$1["continueRefundedEvmSwap"]>;
579
+ /** Delegated to the legacy client (migration checkpoint). */
580
+ recoverGaslessFunds(...args: Parameters<Client$1["recoverGaslessFunds"]>): ReturnType<Client$1["recoverGaslessFunds"]>;
581
+ /** Delegated to the legacy client (migration checkpoint). */
582
+ hasReceivedVtxo(...args: Parameters<Client$1["hasReceivedVtxo"]>): ReturnType<Client$1["hasReceivedVtxo"]>;
583
+ /**
584
+ * Start observing the user's active swaps and deriving each one's next action.
585
+ *
586
+ * Loads stored swaps, maps the ones whose ledgers are observable to
587
+ * {@link TrackedSwap}s, and runs the per-ledger monitors. Idempotent. Requires
588
+ * the client to have been built with {@link ClientBuilder.withTracking}.
589
+ * Subscribe with {@link subscribeToActions}; release with
590
+ * {@link stopTracking}.
591
+ */
592
+ startTracking(): Promise<void>;
593
+ /**
594
+ * Subscribe to next-action updates: `cb` fires with the current action for each
595
+ * tracked swap immediately, then on every change. Call after
596
+ * {@link startTracking}. Returns an unsubscribe fn.
597
+ */
598
+ subscribeToActions(cb: ActionSubscriber): () => void;
599
+ /** Stop tracking and drop subscribers. The managers themselves are not disposed. */
600
+ stopTracking(): void;
601
+ }
602
+ /**
603
+ * Builds a {@link Client}. Mirrors the legacy `ClientBuilder` fluent surface and
604
+ * constructs the legacy client internally, then wraps it.
605
+ */
606
+ declare class ClientBuilder {
607
+ #private;
608
+ /**
609
+ * Override the EVM JSON-RPC endpoint per chainId. Optional — tracking uses
610
+ * tested public defaults otherwise; an override is tried first, with the
611
+ * defaults kept as fallbacks.
612
+ */
613
+ withEvmRpcUrls(urls: Record<number, string>): this;
614
+ /**
615
+ * Opt in to hint-driven auto-claim. Tracking then also subscribes to the
616
+ * server status WebSocket (a faster trigger than the chain poll) and, when the
617
+ * chain confirms a swap is claimable, claims it automatically. Actions that
618
+ * need the user — a manual `fund`, or a refund to confirm — are surfaced via
619
+ * `onActionRequired` instead of being run. Off by default: this auto-spends on
620
+ * the user's behalf, so it must be explicit.
621
+ */
622
+ withAutoClaim(options?: {
623
+ onActionRequired?: (swapId: string, actions: SwapActions) => void;
624
+ }): this;
625
+ /** Turn observe-mode tracking off. */
626
+ withoutTracking(): this;
627
+ /**
628
+ * Advanced: supply per-ledger {@link ContractManager}s explicitly instead of
629
+ * letting the client auto-build them from config (useful for tests or custom
630
+ * chain sources).
631
+ */
632
+ withContractManagers(managers: Map<Ledger, ContractManager>): this;
633
+ withBaseUrl(...args: Parameters<ClientBuilder$1["withBaseUrl"]>): this;
634
+ withReferralCode(...args: Parameters<ClientBuilder$1["withReferralCode"]>): this;
635
+ withOrgCode(...args: Parameters<ClientBuilder$1["withOrgCode"]>): this;
636
+ withDefaultHeaders(...args: Parameters<ClientBuilder$1["withDefaultHeaders"]>): this;
637
+ /**
638
+ * Override the Esplora REST URL. Optional for tracking — the Bitcoin manager
639
+ * otherwise defaults to mainnet mempool.space; set this for a dev/regtest node.
640
+ */
641
+ withEsploraUrl(url: string): this;
642
+ /**
643
+ * Override the Ark server URL. Optional for tracking — it otherwise defaults to
644
+ * the mainnet server; set this to track a non-mainnet (dev/signet) deployment.
645
+ */
646
+ withArkadeServerUrl(url: string): this;
647
+ withSignerStorage(...args: Parameters<ClientBuilder$1["withSignerStorage"]>): this;
648
+ withSwapStorage(...args: Parameters<ClientBuilder$1["withSwapStorage"]>): this;
649
+ withMnemonic(...args: Parameters<ClientBuilder$1["withMnemonic"]>): this;
650
+ withXprv(...args: Parameters<ClientBuilder$1["withXprv"]>): this;
651
+ withAa(...args: Parameters<ClientBuilder$1["withAa"]>): this;
652
+ withLogger(...args: Parameters<ClientBuilder$1["withLogger"]>): this;
653
+ withLogLevel(...args: Parameters<ClientBuilder$1["withLogLevel"]>): this;
654
+ build(): Promise<Client>;
655
+ }
656
+
657
+ /** The slice of `@arkade-os/sdk`'s indexer the spend classifier needs. */
658
+ type VirtualTxSource = {
659
+ getVirtualTxs(txids: string[]): Promise<{
660
+ txs: string[];
661
+ }>;
662
+ };
663
+
664
+ /**
665
+ * The Arkade {@link ContractManager} — the stateful I/O adapter that observes a
666
+ * swap's VHTLC by polling the Ark indexer.
667
+ *
668
+ * We deliberately do NOT use `@arkade-os/sdk`'s `ContractManager`: its vtxo
669
+ * annotation calls `script.forfeit()` on every contract, but a VHTLC script has
670
+ * no `forfeit()` leaf (it exposes `claim()`/`refund()`), so watching a VHTLC
671
+ * through it throws. Instead we read vtxos directly by pkScript — exactly what
672
+ * the legacy SDK's claim/refund paths do — and map them with the pure helpers in
673
+ * `./arkade.js`.
674
+ *
675
+ * There is no event push here, so observations advance when {@link refresh} runs;
676
+ * the SwapTracker drives that (initial prime + periodic poll).
677
+ */
678
+
679
+ /** The Ark indexer surface the observer needs (RestIndexerProvider satisfies it). */
680
+ type ArkadeIndexer = VirtualTxSource & {
681
+ getVtxos(opts: {
682
+ scripts: string[];
683
+ spendableOnly?: boolean;
684
+ }): Promise<{
685
+ vtxos: VirtualCoin[];
686
+ }>;
687
+ };
688
+ type ArkadeCreateConfig = {
689
+ /** Ark server base URL — used for the indexer. */
690
+ serverUrl: string;
691
+ /**
692
+ * The current Bitcoin MTP (ms) for evaluating VHTLC timelocks. Typically
693
+ * `async () => (await client.getMtp()).mtp * 1000`.
694
+ */
695
+ chainTime?: () => Promise<number>;
696
+ };
697
+ type ArkadeContractManagerDeps = {
698
+ indexer: ArkadeIndexer;
699
+ /**
700
+ * The current Bitcoin MTP (ms) — arkade CLTV timelocks are evaluated against
701
+ * Bitcoin's clock, not the Ark server's. Optional: until wired, `chainNow()`
702
+ * reports `undefined` and the tracker holds swaps as provisional.
703
+ */
704
+ chainTime?: () => Promise<number>;
705
+ };
706
+ declare class ArkadeContractManager implements ContractManager {
707
+ #private;
708
+ readonly ledger: Ledger;
709
+ private constructor();
710
+ static fromDeps(deps: ArkadeContractManagerDeps): ArkadeContractManager;
711
+ /** Construct a manager backed by a `RestIndexerProvider` over the Ark server. */
712
+ static create(config: ArkadeCreateConfig): Promise<ArkadeContractManager>;
713
+ canObserve(ref: HtlcRef): boolean;
714
+ register(ref: HtlcRef): Promise<void>;
715
+ unregister(ref: HtlcRef): Promise<void>;
716
+ getState(ref: HtlcRef): HtlcObservation | undefined;
717
+ chainNow(_ref: HtlcRef): number | undefined;
718
+ onEvent(cb: (ref: HtlcRef, state: HtlcObservation) => void): () => void;
719
+ refresh(): Promise<void>;
720
+ reconcile(ref: HtlcRef): Promise<void>;
721
+ dispose(): void;
722
+ /** The verified preimage recovered from a claim spend, if one was seen. */
723
+ getPreimage(ref: HtlcRef): Uint8Array | undefined;
724
+ }
725
+
726
+ /**
727
+ * The default Ark server URL for a Bitcoin network name (case-insensitive), or
728
+ * `undefined` for networks without a public default (e.g. regtest/testnet),
729
+ * where a URL must be supplied explicitly.
730
+ */
731
+ declare function defaultArkadeServerUrl(network: string): string | undefined;
732
+
733
+ type ArkadeRef = Extract<HtlcRef, {
734
+ ledger: "arkade";
735
+ }>;
736
+ type ArkadeVhtlcInput = {
737
+ /** Pubkey hex (x-only or compressed) for each VHTLC role. */
738
+ senderPk: string;
739
+ receiverPk: string;
740
+ serverPk: string;
741
+ /**
742
+ * The swap's `hash_lock`, hex, `0x`-prefixed or not. Either `sha256(preimage)`
743
+ * (32 bytes — most directions) or the `ripemd160(sha256(preimage))` HASH160 the
744
+ * VHTLC commits to directly (20 bytes — `btc_to_arkade`). Length disambiguates.
745
+ */
746
+ hashLock: string;
747
+ /** The VHTLC address the server reported. */
748
+ address: string;
749
+ /** Absolute CLTV refund locktime (unix seconds). */
750
+ refundLocktime: number;
751
+ /** Relative unilateral delays (seconds). */
752
+ unilateralClaimDelay: number;
753
+ unilateralRefundDelay: number;
754
+ unilateralRefundWithoutReceiverDelay: number;
755
+ /** Expected funding amount in sats (a short funding is `invalid`, not confirmed). */
756
+ expectedSats: number;
757
+ };
758
+ /**
759
+ * The VHTLC always commits to `ripemd160(sha256(preimage))` (HASH160). When
760
+ * `hashLock` is `sha256(preimage)` (32 bytes) we hash it once more to get that;
761
+ * when it is already the HASH160 (20 bytes, `btc_to_arkade`) we use it as-is. The
762
+ * ref keeps `preimageHash` as the raw `hashLock` — whatever length — and the spend
763
+ * classifier verifies a revealed preimage against it, auto-detecting the algorithm
764
+ * from that length.
765
+ */
766
+ declare function buildArkadeVhtlcRef(input: ArkadeVhtlcInput): ArkadeRef;
767
+
768
+ /** On-chain funding state of the HTLC address. */
769
+ type BitcoinFunding = "absent" | "mempool" | "confirmed";
770
+ /** Facts gathered for an HTLC address from esplora. */
771
+ type BitcoinHtlcFacts = {
772
+ funding: BitcoinFunding;
773
+ /** Total sats paid to the HTLC address by the funding tx (0 when absent). */
774
+ fundedSats: number;
775
+ /**
776
+ * The witness stack (hex items) of the input that spent the HTLC output, if it
777
+ * was spent. Absent while the output is still unspent.
778
+ */
779
+ spendWitness?: string[];
780
+ };
781
+
782
+ /** The esplora surface the observer needs (a `BitcoinReaderEsplora` satisfies it). */
783
+ type BitcoinChainReader = {
784
+ /** Funding + spend facts for an HTLC witness-script address. */
785
+ getHtlcFacts(address: string): Promise<BitcoinHtlcFacts>;
786
+ };
787
+ type BitcoinCreateConfig = {
788
+ /** One or more esplora REST base URLs; several are tried in rotation with failover. */
789
+ esploraUrl: string | string[];
790
+ /** The current Bitcoin MTP (ms); typically `async () => (await client.getMtp()).mtp * 1000`. */
791
+ chainTime?: () => Promise<number>;
792
+ };
793
+ type BitcoinContractManagerDeps = {
794
+ reader: BitcoinChainReader;
795
+ chainTime?: () => Promise<number>;
796
+ };
797
+ declare class BitcoinContractManager implements ContractManager {
798
+ #private;
799
+ readonly ledger: Ledger;
800
+ private constructor();
801
+ static fromDeps(deps: BitcoinContractManagerDeps): BitcoinContractManager;
802
+ static create(config: BitcoinCreateConfig): Promise<BitcoinContractManager>;
803
+ canObserve(ref: HtlcRef): boolean;
804
+ register(ref: HtlcRef): Promise<void>;
805
+ unregister(ref: HtlcRef): Promise<void>;
806
+ getState(ref: HtlcRef): HtlcObservation | undefined;
807
+ chainNow(_ref: HtlcRef): number | undefined;
808
+ onEvent(cb: (ref: HtlcRef, state: HtlcObservation) => void): () => void;
809
+ refresh(): Promise<void>;
810
+ reconcile(ref: HtlcRef): Promise<void>;
811
+ dispose(): void;
812
+ /** The preimage a claim revealed on this HTLC, if one was seen. */
813
+ getPreimage(ref: HtlcRef): Uint8Array | undefined;
814
+ }
815
+
816
+ /**
817
+ * A esplora-backed {@link BitcoinChainReader} — the concrete chain source the
818
+ * {@link BitcoinContractManager} uses in production.
819
+ *
820
+ * Kept separate from the manager so the manager stays free of any HTTP/esplora
821
+ * dependency (and unit-testable against a fake reader). A single
822
+ * `GET /address/{addr}/txs` yields both the funding state and the spending
823
+ * input's witness, so no extra round-trips are needed.
824
+ */
825
+
826
+ /** The slice of esplora's tx JSON we read. */
827
+ type EsploraTx = {
828
+ vin: Array<{
829
+ witness?: string[];
830
+ prevout?: {
831
+ scriptpubkey_address?: string;
832
+ } | null;
833
+ }>;
834
+ vout: Array<{
835
+ scriptpubkey_address?: string;
836
+ value?: number;
837
+ }>;
838
+ status?: {
839
+ confirmed?: boolean;
840
+ };
841
+ };
842
+ /**
843
+ * Reduce an address's esplora tx history to HTLC facts. If a tx spends an output
844
+ * at the address, that's the resolving spend (its witness classifies claim vs
845
+ * refund); otherwise a tx paying the address is the funding.
846
+ */
847
+ declare function htlcFactsFromEsploraTxs(txs: EsploraTx[], address: string): BitcoinHtlcFacts;
848
+ /**
849
+ * Public esplora endpoints tried in rotation (mainnet). blockstream.info runs the
850
+ * reference esplora, so it's API-compatible with mempool.space; spreading calls
851
+ * across both halves the per-provider load and survives one being throttled.
852
+ */
853
+ declare const DEFAULT_ESPLORA_URLS: string[];
854
+ /**
855
+ * Build a {@link BitcoinChainReader} over one or more esplora REST endpoints.
856
+ * With several, each call starts at a rotating endpoint (to spread load across
857
+ * providers) and fails over to the rest on error — so a throttled or failing
858
+ * provider doesn't stall tracking.
859
+ */
860
+ declare function esploraReader(esploraUrls: string | string[], fetchImpl?: typeof fetch): BitcoinChainReader;
861
+
862
+ /**
863
+ * The `EvmContractManager`'s pure core.
864
+ *
865
+ * An EVM HTLC (an `HTLCErc20` swap) exposes its lifecycle as three events, all
866
+ * indexed by the swap's `preimageHash`: `SwapCreated` (funded), `SwapRedeemed`
867
+ * (claimed — reveals the preimage), and `SwapRefunded` (reclaimed after the
868
+ * timelock). This maps a decoded event history to an {@link HtlcObservation}.
869
+ */
870
+
871
+ /** One decoded `HTLCErc20` lifecycle event for a single swap's `preimageHash`. */
872
+ type EvmHtlcEvent = {
873
+ kind: "created";
874
+ amount: bigint;
875
+ token: `0x${string}`;
876
+ } | {
877
+ kind: "redeemed";
878
+ preimage: `0x${string}`;
879
+ } | {
880
+ kind: "refunded";
881
+ };
882
+
883
+ /**
884
+ * The EVM {@link ContractManager} — the stateful I/O adapter that observes
885
+ * `HTLCErc20` swaps across one or more EVM chains.
886
+ *
887
+ * Reading chain state is delegated to an injected {@link EvmChainReader} (one per
888
+ * chainId) rather than a bundled viem/ethers client, so the swap package stays
889
+ * chain-library-agnostic and this adapter is testable against a fake. The pure
890
+ * event→observation mapping lives in `./evm.js`.
891
+ *
892
+ * Unlike Arkade, EVM spans multiple chains with independent `block.timestamp`
893
+ * clocks, so this manager is chain-aware: it routes each ref by `chainId` and
894
+ * {@link chainNow} is ref-scoped.
895
+ */
896
+
897
+ /** Reads `HTLCErc20` state for one EVM chain. Implemented over viem/ethers/etc. */
898
+ type EvmChainReader = {
899
+ /**
900
+ * The decoded lifecycle events (oldest→newest) for the HTLC identified by
901
+ * `(htlc contract, preimageHash)`.
902
+ */
903
+ getHtlcEvents(htlc: `0x${string}`, preimageHash: `0x${string}`, claimAddress: `0x${string}`): Promise<EvmHtlcEvent[]>;
904
+ /** The latest block's `block.timestamp`, in ms. */
905
+ getBlockTimeMs(): Promise<number>;
906
+ /** Fire `cb` when new blocks/logs may have changed watched HTLCs; returns unsubscribe. */
907
+ watch(cb: () => void): () => void;
908
+ };
909
+ type EvmContractManagerDeps = {
910
+ /** A chain reader per EVM `chainId` this manager serves. */
911
+ readers: Map<number, EvmChainReader>;
912
+ };
913
+ declare class EvmContractManager implements ContractManager {
914
+ #private;
915
+ readonly ledger: Ledger;
916
+ private constructor();
917
+ static fromDeps(deps: EvmContractManagerDeps): EvmContractManager;
918
+ canObserve(ref: HtlcRef): boolean;
919
+ register(ref: HtlcRef): Promise<void>;
920
+ unregister(ref: HtlcRef): Promise<void>;
921
+ getState(ref: HtlcRef): HtlcObservation | undefined;
922
+ chainNow(ref: HtlcRef): number | undefined;
923
+ onEvent(cb: (ref: HtlcRef, state: HtlcObservation) => void): () => void;
924
+ refresh(): Promise<void>;
925
+ reconcile(ref: HtlcRef): Promise<void>;
926
+ dispose(): void;
927
+ /** The preimage a claim revealed on this HTLC, if one was seen. */
928
+ getPreimage(ref: HtlcRef): `0x${string}` | undefined;
929
+ }
930
+
931
+ /**
932
+ * A viem-backed {@link EvmChainReader} — the concrete chain source the
933
+ * {@link EvmContractManager} uses in production.
934
+ *
935
+ * Kept separate from the manager so the manager itself stays free of any
936
+ * chain-library dependency (and unit-testable against a fake reader). This module
937
+ * is the only place that touches viem and the `HTLCErc20` ABI.
938
+ */
939
+
940
+ /**
941
+ * Tested public RPC endpoints per supported chainId, tried in order via viem's
942
+ * ranked `fallback`. Mirrors the frontend's `evmTransport.ts` list — the client
943
+ * uses these by default so Arkade↔EVM tracking works out of the box; a caller can
944
+ * override per chain with `ClientBuilder.withEvmRpcUrls`.
945
+ */
946
+ declare const DEFAULT_EVM_RPCS: Record<number, string[]>;
947
+ declare const SWAP_CREATED: {
948
+ readonly name: "SwapCreated";
949
+ readonly type: "event";
950
+ readonly inputs: readonly [{
951
+ readonly type: "bytes32";
952
+ readonly name: "preimageHash";
953
+ readonly indexed: true;
954
+ }, {
955
+ readonly type: "address";
956
+ readonly name: "refundAddress";
957
+ readonly indexed: true;
958
+ }, {
959
+ readonly type: "address";
960
+ readonly name: "claimAddress";
961
+ readonly indexed: true;
962
+ }, {
963
+ readonly type: "address";
964
+ readonly name: "token";
965
+ }, {
966
+ readonly type: "uint256";
967
+ readonly name: "amount";
968
+ }, {
969
+ readonly type: "uint256";
970
+ readonly name: "timelock";
971
+ }];
972
+ };
973
+ declare const SWAP_REDEEMED: {
974
+ readonly name: "SwapRedeemed";
975
+ readonly type: "event";
976
+ readonly inputs: readonly [{
977
+ readonly type: "bytes32";
978
+ readonly name: "preimageHash";
979
+ readonly indexed: true;
980
+ }, {
981
+ readonly type: "bytes32";
982
+ readonly name: "preimage";
983
+ }];
984
+ };
985
+ declare const SWAP_REFUNDED: {
986
+ readonly name: "SwapRefunded";
987
+ readonly type: "event";
988
+ readonly inputs: readonly [{
989
+ readonly type: "bytes32";
990
+ readonly name: "preimageHash";
991
+ readonly indexed: true;
992
+ }];
993
+ };
994
+ /** The viem surface the reader needs — a seam so it can be faked in tests. */
995
+ type EvmLogClient = {
996
+ getLogs(args: {
997
+ address: `0x${string}`;
998
+ event: typeof SWAP_CREATED | typeof SWAP_REDEEMED | typeof SWAP_REFUNDED;
999
+ args: {
1000
+ preimageHash: `0x${string}`;
1001
+ claimAddress?: `0x${string}`;
1002
+ };
1003
+ fromBlock: bigint | "earliest";
1004
+ }): Promise<Log[]>;
1005
+ getBlock(): Promise<Pick<Block, "timestamp">>;
1006
+ watchBlocks(args: {
1007
+ onBlock: () => void;
1008
+ }): () => void;
1009
+ };
1010
+ /** Build an {@link EvmChainReader} over an existing viem-like client. */
1011
+ declare function evmReaderFromClient(client: EvmLogClient): EvmChainReader;
1012
+ /**
1013
+ * Build an {@link EvmChainReader} over one or more EVM JSON-RPC endpoints. With
1014
+ * several, viem's ranked `fallback` picks the healthiest and fails over.
1015
+ */
1016
+ declare function createEvmRpcReader(rpcUrls: string | string[]): EvmChainReader;
1017
+ /**
1018
+ * Resolve the per-chain readers used for tracking: the tested {@link
1019
+ * DEFAULT_EVM_RPCS} by default, with any `overrides` taking priority for their
1020
+ * chain (kept ahead of the defaults, which remain as fallbacks).
1021
+ */
1022
+ declare function defaultEvmReaders(overrides?: Record<number, string>): Map<number, EvmChainReader>;
1023
+
1024
+ /**
1025
+ * Ties the hint feed and the action model together into auto-execution.
1026
+ *
1027
+ * On a server status hint it verifies the swap against the chain
1028
+ * (`tracker.applyHint`) — the hint only makes the derivation faster, the chain
1029
+ * stays the source of truth. Then, on every derived action, it AUTO-RUNS the
1030
+ * recommended action when it is auto-safe (a claim) and SURFACES the rest — a
1031
+ * manual `fund`, or a refund the user must confirm — via `onActionRequired`, the
1032
+ * seam a frontend notification center plugs into.
1033
+ *
1034
+ * A failed auto-claim is RETRIED with backoff. Each retry first re-verifies
1035
+ * against chain (a forced `applyHint`) and only re-runs if the swap is still
1036
+ * claimable — so we never blindly re-submit a claim (which on EVM would waste gas
1037
+ * or double-submit). After the attempt budget is spent the swap is surfaced via
1038
+ * `onActionRequired`, so it is never silently stuck.
1039
+ */
1040
+
1041
+ /** The tracker surface the worker drives. */
1042
+ interface WorkerTracker {
1043
+ trackedSwapIds(): string[];
1044
+ /** Reconcile a swap's legs from chain, then recompute. `force` re-emits even if unchanged. */
1045
+ applyHint(swapId: string, opts?: {
1046
+ force?: boolean;
1047
+ }): Promise<void>;
1048
+ subscribeToActions(cb: (swapId: string, actions: SwapActions) => void): () => void;
1049
+ }
1050
+ /** The hint feed surface the worker consumes (`WsStatusSource` satisfies it). */
1051
+ interface WorkerHintSource {
1052
+ start(): void;
1053
+ stop(): void;
1054
+ subscribe(swapIds: string[]): void;
1055
+ unsubscribe(swapIds: string[]): void;
1056
+ onStatus(cb: (update: {
1057
+ swapId: string;
1058
+ }) => void): () => void;
1059
+ }
1060
+ /** Backoff policy for retrying a failed auto-claim. */
1061
+ type SwapWorkerRetryOptions = {
1062
+ /** Attempts before giving up and surfacing the swap. Default 5. */
1063
+ maxAttempts?: number;
1064
+ /** Base backoff (ms), doubled each attempt. Default 2000. */
1065
+ baseDelayMs?: number;
1066
+ /** Backoff ceiling (ms). Default 30000. */
1067
+ maxDelayMs?: number;
1068
+ };
1069
+ type SwapWorkerOptions = {
1070
+ tracker: WorkerTracker;
1071
+ hintSource: WorkerHintSource;
1072
+ /**
1073
+ * Run the recommended auto action for a swap (today: claim). Reject to signal
1074
+ * failure — the worker re-verifies against chain and retries with backoff.
1075
+ * Resolve once submitted.
1076
+ */
1077
+ execute: (swapId: string, actionId: SwapActionId) => Promise<void>;
1078
+ /**
1079
+ * Surface an action that needs the user rather than being auto-run — a manual
1080
+ * `fund`, a refund the user must confirm, or a claim whose retries are spent.
1081
+ * Where a UI notification hooks in.
1082
+ */
1083
+ onActionRequired?: (swapId: string, actions: SwapActions) => void;
1084
+ /** Auto-claim retry policy; sensible defaults otherwise. */
1085
+ retry?: SwapWorkerRetryOptions;
1086
+ };
1087
+ declare class SwapWorker {
1088
+ #private;
1089
+ constructor(options: SwapWorkerOptions);
1090
+ /** Watch tracked swaps on the hint feed, wire hints → verify, react to actions. */
1091
+ start(): void;
1092
+ /** Stop reacting, cancel pending retries, and close the hint feed. */
1093
+ stop(): void;
1094
+ }
1095
+
1096
+ /**
1097
+ * A standalone subscriber to the server's swap-status WebSocket (`GET /ws`).
1098
+ *
1099
+ * Holds a single reconnecting connection, tracks a live set of subscribed swap
1100
+ * ids, and emits `{ swapId, status }` on every status transition the server
1101
+ * pushes — including the snapshot the server sends right after a subscribe. This
1102
+ * is a *hint* feed only: it neither trusts nor acts on the status. A consumer is
1103
+ * expected to verify against the chain (the contract managers) before doing
1104
+ * anything with it.
1105
+ *
1106
+ * Protocol (see `swap/src/api/ws.rs`):
1107
+ * → {"op":"subscribe","channel":"swap_status","args":[<uuid>,...]} (≤64 ids/frame)
1108
+ * ← {"channel":"swap_status","data":{"<uuid>":"<status>",...}}
1109
+ * ← {"op":"subscribed",...} (ack) ← {"op":...,"message":...} (error)
1110
+ * A single connection carries up to 256 subscriptions; that cap is enforced
1111
+ * client-side (see {@link WsStatusSource.subscribe}) because the server silently
1112
+ * drops the overflow.
1113
+ */
1114
+
1115
+ /** A status update pushed by the server for one swap. */
1116
+ type SwapStatusUpdate = {
1117
+ swapId: string;
1118
+ status: SwapStatus;
1119
+ };
1120
+ /** The minimal WebSocket surface the source needs (the global `WebSocket` satisfies it). */
1121
+ interface WebSocketLike {
1122
+ send(data: string): void;
1123
+ close(): void;
1124
+ onopen: (() => void) | null;
1125
+ onmessage: ((event: {
1126
+ data: unknown;
1127
+ }) => void) | null;
1128
+ onclose: (() => void) | null;
1129
+ onerror: ((event: unknown) => void) | null;
1130
+ }
1131
+ type WsStatusSourceOptions = {
1132
+ /** Server base URL (http/https); the `ws(s)://…/ws` endpoint is derived from it. */
1133
+ serverUrl: string;
1134
+ /** WebSocket factory — defaults to the global `WebSocket` (browsers, Node ≥ 22). */
1135
+ webSocketFactory?: (url: string) => WebSocketLike;
1136
+ /** Reconnect backoff floor/ceiling (ms). Defaults: 1_000 / 30_000. */
1137
+ reconnectMinMs?: number;
1138
+ reconnectMaxMs?: number;
1139
+ };
1140
+ declare class WsStatusSource {
1141
+ #private;
1142
+ constructor(options: WsStatusSourceOptions);
1143
+ /** Open the connection and begin (re)subscribing. Idempotent. */
1144
+ start(): void;
1145
+ /** Close the connection and stop reconnecting. */
1146
+ stop(): void;
1147
+ /** Emit for each pushed status update. Returns an unsubscribe fn. */
1148
+ onStatus(cb: (update: SwapStatusUpdate) => void): () => void;
1149
+ /**
1150
+ * Track these swap ids — subscribe on the socket now if open, else on next open.
1151
+ *
1152
+ * Enforces the server's per-connection cap locally. The server silently stops
1153
+ * adding once it is full (it breaks out of the loop but still acks every id),
1154
+ * so sending more would leave us believing ids are subscribed that will never
1155
+ * receive an update. Overflow is queued instead and promoted as slots free.
1156
+ */
1157
+ subscribe(swapIds: string[]): void;
1158
+ /** Stop tracking these swap ids, promoting queued ones into the freed slots. */
1159
+ unsubscribe(swapIds: string[]): void;
1160
+ }
1161
+
1162
+ /**
1163
+ * Map a stored swap to the {@link TrackedSwap} the {@link SwapTracker} watches.
1164
+ *
1165
+ * The recovery bundle (`StoredSwap.response`) is a discriminated union over swap
1166
+ * directions; each carries the two HTLC legs and their refund locktimes. This
1167
+ * extracts the client-funded and server-funded HTLCs plus their locktimes so the
1168
+ * pure pipeline can derive the next action.
1169
+ *
1170
+ * Directions whose both legs are observable today (Arkade↔EVM, Bitcoin↔EVM) are
1171
+ * mapped; others return `undefined` until their ledger managers exist, rather than
1172
+ * producing a half-watchable swap.
1173
+ */
1174
+
1175
+ declare function swapToTracked(stored: StoredSwap): TrackedSwap | undefined;
1176
+
1177
+ 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, type SwapStatusUpdate, SwapTracker, SwapWorker, type SwapWorkerOptions, type TrackedSwap, type WaitAction, type WebSocketLike, type WorkerHintSource, type WorkerTracker, WsStatusSource, type WsStatusSourceOptions, buildArkadeVhtlcRef, createEvmRpcReader, defaultArkadeServerUrl, defaultEvmReaders, deriveSwapActions, deriveSwapStatus, esploraReader, evmReaderFromClient, htlcFactsFromEsploraTxs, htlcKey, swapToTracked };