@satora/swap 1.1.0 → 1.2.1

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
@@ -379,6 +379,20 @@ interface ContractManager {
379
379
  dispose(): void;
380
380
  }
381
381
 
382
+ /**
383
+ * The reactive orchestration.
384
+ *
385
+ * A {@link SwapTracker} watches each tracked swap's on-chain HTLC leg(s) via
386
+ * per-ledger {@link ContractManager}s (two legs for chain↔chain swaps, one for
387
+ * Lightning). On any change it recomputes the next action
388
+ * (`deriveSwapStatus` → `deriveSwapActions`), and — deduped — notifies
389
+ * subscribers, dropping a swap once it reaches a terminal state.
390
+ *
391
+ * This is the observe-mode layer: it *notifies*. Auto-execution (recover/auto
392
+ * mode running the action) is a policy layer built on top of these same
393
+ * notifications, gated by each action's `automation`.
394
+ */
395
+
382
396
  /**
383
397
  * A swap the tracker watches: its HTLC legs + refund locktimes (from the recovery
384
398
  * bundle). Chain↔chain swaps have both legs; Lightning swaps have exactly one
@@ -391,6 +405,12 @@ type TrackedSwap = {
391
405
  serverHtlc?: HtlcRef;
392
406
  clientRefundLocktime: number;
393
407
  serverRefundLocktime: number;
408
+ /**
409
+ * The swap's last STORED server status, seeding hints-only tracking
410
+ * (`HintTracker`) with a first derivation before any hint arrives. Ignored by
411
+ * {@link SwapTracker}, which derives from chain observations only.
412
+ */
413
+ storedStatus?: SwapStatus;
394
414
  };
395
415
  type ActionSubscriber = (swapId: string, actions: SwapActions) => void;
396
416
  type SwapTrackerOptions = {
@@ -528,6 +548,14 @@ type TrackingConfig = {
528
548
  refreshIntervalMs?: number;
529
549
  /** Opt-in hint-driven auto-claim; unset leaves tracking observe-only. */
530
550
  autoClaim?: AutoClaimConfig;
551
+ /**
552
+ * Verify against the chain instead of trusting server hints. Off by default
553
+ * (TEMP while the chain monitors are buggy): tracking then runs a
554
+ * {@link HintTracker} that derives actions from the server's pushed status,
555
+ * with zero chain access. Setting explicit `managers` implies chain-verified
556
+ * tracking (they exist for nothing else).
557
+ */
558
+ chainVerified?: boolean;
531
559
  };
532
560
  declare class Client {
533
561
  #private;
@@ -680,8 +708,11 @@ declare class Client {
680
708
  /**
681
709
  * Start observing the user's active swaps and deriving each one's next action.
682
710
  *
683
- * Loads stored swaps, maps the ones whose ledgers are observable to
684
- * {@link TrackedSwap}s, and runs the per-ledger monitors. Idempotent. Requires
711
+ * Loads stored swaps, maps them to {@link TrackedSwap}s, and derives each
712
+ * one's actions — by default from the server's status hints alone (TEMP while
713
+ * the chain monitors are buggy; see
714
+ * {@link ClientBuilder.withChainVerifiedTracking} to re-enable the per-ledger
715
+ * chain monitors). Idempotent. Requires
685
716
  * the client to have been built with {@link ClientBuilder.withTracking}.
686
717
  * Subscribe with {@link subscribeToActions}; release with
687
718
  * {@link stopTracking}.
@@ -721,6 +752,13 @@ declare class ClientBuilder {
721
752
  }): this;
722
753
  /** Turn observe-mode tracking off. */
723
754
  withoutTracking(): this;
755
+ /**
756
+ * Verify swap state against the chain (per-ledger contract managers) instead
757
+ * of trusting the server's status hints. TEMPORARILY off by default while the
758
+ * chain monitors are being fixed — without it, tracking derives actions from
759
+ * the server's pushed status alone, with zero chain access.
760
+ */
761
+ withChainVerifiedTracking(): this;
724
762
  /**
725
763
  * Advanced: supply per-ledger {@link ContractManager}s explicitly instead of
726
764
  * letting the client auto-build them from config (useful for tests or custom
@@ -1293,6 +1331,74 @@ declare function createEvmRpcReader(rpcUrls: string | string[]): EvmChainReader;
1293
1331
  */
1294
1332
  declare function defaultEvmReaders(overrides?: Record<number, string>): Map<number, EvmChainReader>;
1295
1333
 
1334
+ /**
1335
+ * A server-trusting stand-in for {@link SwapTracker}: derives each swap's next
1336
+ * action from the SERVER's status instead of chain observations — zero chain
1337
+ * access (no EVM RPCs, no Esplora/Electrum, no Arkade reads).
1338
+ *
1339
+ * TEMPORARY default while the chain monitors are being fixed. Unlike
1340
+ * {@link SwapTracker} there is no independent verification: the client acts on
1341
+ * the server's word. The pushed status is the same `SwapStatus` enum the chain
1342
+ * derivation produces, so it feeds straight into {@link deriveSwapActions}.
1343
+ * Failure containment: a claim run on a false `serverfunded` fails locally
1344
+ * (nothing to spend / gas estimation reverts) rather than losing funds.
1345
+ *
1346
+ * Timelocks are evaluated against the wall clock, not chain clocks. The wall
1347
+ * clock runs AHEAD of Bitcoin's MTP (by up to ~an hour), so a refund can look
1348
+ * unlocked slightly early — the broadcast simply fails until the chain agrees,
1349
+ * and refunds are user-confirmed anyway — while the claim window closes
1350
+ * conservatively early, which is the safe direction.
1351
+ */
1352
+
1353
+ type HintTrackerOptions = {
1354
+ /**
1355
+ * Fetch a swap's current status from the server — the fallback for hints
1356
+ * that don't carry one (the worker's retry re-verifies and its
1357
+ * post-reconnect sweep).
1358
+ */
1359
+ fetchStatus: (swapId: string) => Promise<SwapStatus>;
1360
+ /**
1361
+ * Local tick interval (ms): recomputes every swap against the wall clock so
1362
+ * timelock flips (a refund unlocking, a stale pending swap turning reapable)
1363
+ * surface between hints. Pure local work. `0` disables the timer (tests
1364
+ * drive recomputes via {@link applyHint}).
1365
+ */
1366
+ refreshIntervalMs?: number;
1367
+ };
1368
+ declare class HintTracker {
1369
+ #private;
1370
+ constructor(options: HintTrackerOptions);
1371
+ /**
1372
+ * Always `true`: with no chain readers there is no reachability constraint.
1373
+ * (Claim/refund execution has its own chain configuration, independent of
1374
+ * tracking.)
1375
+ */
1376
+ canObserve(_swap: TrackedSwap): boolean;
1377
+ /** Register the swaps, derive each one's first action from its stored status. */
1378
+ startTracking(swaps: TrackedSwap[]): Promise<void>;
1379
+ /**
1380
+ * Track one more swap after {@link startTracking}. Idempotent: a swap already
1381
+ * tracked, or one already seen through to a terminal action, is ignored.
1382
+ */
1383
+ track(swap: TrackedSwap): Promise<void>;
1384
+ /** The swap ids currently tracked — e.g. to subscribe them to the hint feed. */
1385
+ trackedSwapIds(): string[];
1386
+ /**
1387
+ * Apply a server status to one swap and recompute its action. A hint that
1388
+ * carries the pushed `status` is applied as-is; without one (the worker's
1389
+ * retry and post-reconnect paths) the status is re-fetched from the server.
1390
+ * A no-op for an untracked swap.
1391
+ */
1392
+ applyHint(swapId: string, opts?: {
1393
+ force?: boolean;
1394
+ status?: SwapStatus;
1395
+ }): Promise<void>;
1396
+ /** Notify `cb` of the current action for each tracked swap, then on every change. */
1397
+ subscribeToActions(cb: ActionSubscriber): () => void;
1398
+ /** Stop tracking: drop the tick timer, tracked swaps, and subscribers. */
1399
+ stop(): void;
1400
+ }
1401
+
1296
1402
  /**
1297
1403
  * Ties the hint feed and the action model together into auto-execution.
1298
1404
  *
@@ -1313,9 +1419,15 @@ declare function defaultEvmReaders(overrides?: Record<number, string>): Map<numb
1313
1419
  /** The tracker surface the worker drives. */
1314
1420
  interface WorkerTracker {
1315
1421
  trackedSwapIds(): string[];
1316
- /** Reconcile a swap's legs from chain, then recompute. `force` re-emits even if unchanged. */
1422
+ /**
1423
+ * Re-verify a swap, then recompute. `force` re-emits even if unchanged.
1424
+ * `status` is the hint's pushed status: a chain-verifying tracker ignores it
1425
+ * (the chain stays its source of truth); a hints-only tracker applies it
1426
+ * directly, saving a server round-trip.
1427
+ */
1317
1428
  applyHint(swapId: string, opts?: {
1318
1429
  force?: boolean;
1430
+ status?: SwapStatus;
1319
1431
  }): Promise<void>;
1320
1432
  subscribeToActions(cb: (swapId: string, actions: SwapActions) => void): () => void;
1321
1433
  }
@@ -1327,6 +1439,7 @@ interface WorkerHintSource {
1327
1439
  unsubscribe(swapIds: string[]): void;
1328
1440
  onStatus(cb: (update: {
1329
1441
  swapId: string;
1442
+ status?: SwapStatus;
1330
1443
  }) => void): () => void;
1331
1444
  /**
1332
1445
  * Optional: fires when the feed comes back up after an outage, during which
@@ -1462,4 +1575,4 @@ declare class WsStatusSource {
1462
1575
 
1463
1576
  declare function swapToTracked(stored: StoredSwap): TrackedSwap | undefined;
1464
1577
 
1465
- 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 ElectrumReaderNetwork, type ElectrumRpc, 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, electrumReader, esploraReader, evmReaderFromClient, htlcFactsFromEsploraTxs, htlcKey, htlcQueryKey, swapToTracked };
1578
+ 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 ElectrumReaderNetwork, type ElectrumRpc, type EvmChainReader, EvmContractManager, type EvmContractManagerDeps, type EvmHtlcQuery, type EvmLogClient, type FundAction, HintTracker, type HintTrackerOptions, 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, electrumReader, esploraReader, evmReaderFromClient, htlcFactsFromEsploraTxs, htlcKey, htlcQueryKey, swapToTracked };
package/dist/index.d.ts CHANGED
@@ -379,6 +379,20 @@ interface ContractManager {
379
379
  dispose(): void;
380
380
  }
381
381
 
382
+ /**
383
+ * The reactive orchestration.
384
+ *
385
+ * A {@link SwapTracker} watches each tracked swap's on-chain HTLC leg(s) via
386
+ * per-ledger {@link ContractManager}s (two legs for chain↔chain swaps, one for
387
+ * Lightning). On any change it recomputes the next action
388
+ * (`deriveSwapStatus` → `deriveSwapActions`), and — deduped — notifies
389
+ * subscribers, dropping a swap once it reaches a terminal state.
390
+ *
391
+ * This is the observe-mode layer: it *notifies*. Auto-execution (recover/auto
392
+ * mode running the action) is a policy layer built on top of these same
393
+ * notifications, gated by each action's `automation`.
394
+ */
395
+
382
396
  /**
383
397
  * A swap the tracker watches: its HTLC legs + refund locktimes (from the recovery
384
398
  * bundle). Chain↔chain swaps have both legs; Lightning swaps have exactly one
@@ -391,6 +405,12 @@ type TrackedSwap = {
391
405
  serverHtlc?: HtlcRef;
392
406
  clientRefundLocktime: number;
393
407
  serverRefundLocktime: number;
408
+ /**
409
+ * The swap's last STORED server status, seeding hints-only tracking
410
+ * (`HintTracker`) with a first derivation before any hint arrives. Ignored by
411
+ * {@link SwapTracker}, which derives from chain observations only.
412
+ */
413
+ storedStatus?: SwapStatus;
394
414
  };
395
415
  type ActionSubscriber = (swapId: string, actions: SwapActions) => void;
396
416
  type SwapTrackerOptions = {
@@ -528,6 +548,14 @@ type TrackingConfig = {
528
548
  refreshIntervalMs?: number;
529
549
  /** Opt-in hint-driven auto-claim; unset leaves tracking observe-only. */
530
550
  autoClaim?: AutoClaimConfig;
551
+ /**
552
+ * Verify against the chain instead of trusting server hints. Off by default
553
+ * (TEMP while the chain monitors are buggy): tracking then runs a
554
+ * {@link HintTracker} that derives actions from the server's pushed status,
555
+ * with zero chain access. Setting explicit `managers` implies chain-verified
556
+ * tracking (they exist for nothing else).
557
+ */
558
+ chainVerified?: boolean;
531
559
  };
532
560
  declare class Client {
533
561
  #private;
@@ -680,8 +708,11 @@ declare class Client {
680
708
  /**
681
709
  * Start observing the user's active swaps and deriving each one's next action.
682
710
  *
683
- * Loads stored swaps, maps the ones whose ledgers are observable to
684
- * {@link TrackedSwap}s, and runs the per-ledger monitors. Idempotent. Requires
711
+ * Loads stored swaps, maps them to {@link TrackedSwap}s, and derives each
712
+ * one's actions — by default from the server's status hints alone (TEMP while
713
+ * the chain monitors are buggy; see
714
+ * {@link ClientBuilder.withChainVerifiedTracking} to re-enable the per-ledger
715
+ * chain monitors). Idempotent. Requires
685
716
  * the client to have been built with {@link ClientBuilder.withTracking}.
686
717
  * Subscribe with {@link subscribeToActions}; release with
687
718
  * {@link stopTracking}.
@@ -721,6 +752,13 @@ declare class ClientBuilder {
721
752
  }): this;
722
753
  /** Turn observe-mode tracking off. */
723
754
  withoutTracking(): this;
755
+ /**
756
+ * Verify swap state against the chain (per-ledger contract managers) instead
757
+ * of trusting the server's status hints. TEMPORARILY off by default while the
758
+ * chain monitors are being fixed — without it, tracking derives actions from
759
+ * the server's pushed status alone, with zero chain access.
760
+ */
761
+ withChainVerifiedTracking(): this;
724
762
  /**
725
763
  * Advanced: supply per-ledger {@link ContractManager}s explicitly instead of
726
764
  * letting the client auto-build them from config (useful for tests or custom
@@ -1293,6 +1331,74 @@ declare function createEvmRpcReader(rpcUrls: string | string[]): EvmChainReader;
1293
1331
  */
1294
1332
  declare function defaultEvmReaders(overrides?: Record<number, string>): Map<number, EvmChainReader>;
1295
1333
 
1334
+ /**
1335
+ * A server-trusting stand-in for {@link SwapTracker}: derives each swap's next
1336
+ * action from the SERVER's status instead of chain observations — zero chain
1337
+ * access (no EVM RPCs, no Esplora/Electrum, no Arkade reads).
1338
+ *
1339
+ * TEMPORARY default while the chain monitors are being fixed. Unlike
1340
+ * {@link SwapTracker} there is no independent verification: the client acts on
1341
+ * the server's word. The pushed status is the same `SwapStatus` enum the chain
1342
+ * derivation produces, so it feeds straight into {@link deriveSwapActions}.
1343
+ * Failure containment: a claim run on a false `serverfunded` fails locally
1344
+ * (nothing to spend / gas estimation reverts) rather than losing funds.
1345
+ *
1346
+ * Timelocks are evaluated against the wall clock, not chain clocks. The wall
1347
+ * clock runs AHEAD of Bitcoin's MTP (by up to ~an hour), so a refund can look
1348
+ * unlocked slightly early — the broadcast simply fails until the chain agrees,
1349
+ * and refunds are user-confirmed anyway — while the claim window closes
1350
+ * conservatively early, which is the safe direction.
1351
+ */
1352
+
1353
+ type HintTrackerOptions = {
1354
+ /**
1355
+ * Fetch a swap's current status from the server — the fallback for hints
1356
+ * that don't carry one (the worker's retry re-verifies and its
1357
+ * post-reconnect sweep).
1358
+ */
1359
+ fetchStatus: (swapId: string) => Promise<SwapStatus>;
1360
+ /**
1361
+ * Local tick interval (ms): recomputes every swap against the wall clock so
1362
+ * timelock flips (a refund unlocking, a stale pending swap turning reapable)
1363
+ * surface between hints. Pure local work. `0` disables the timer (tests
1364
+ * drive recomputes via {@link applyHint}).
1365
+ */
1366
+ refreshIntervalMs?: number;
1367
+ };
1368
+ declare class HintTracker {
1369
+ #private;
1370
+ constructor(options: HintTrackerOptions);
1371
+ /**
1372
+ * Always `true`: with no chain readers there is no reachability constraint.
1373
+ * (Claim/refund execution has its own chain configuration, independent of
1374
+ * tracking.)
1375
+ */
1376
+ canObserve(_swap: TrackedSwap): boolean;
1377
+ /** Register the swaps, derive each one's first action from its stored status. */
1378
+ startTracking(swaps: TrackedSwap[]): Promise<void>;
1379
+ /**
1380
+ * Track one more swap after {@link startTracking}. Idempotent: a swap already
1381
+ * tracked, or one already seen through to a terminal action, is ignored.
1382
+ */
1383
+ track(swap: TrackedSwap): Promise<void>;
1384
+ /** The swap ids currently tracked — e.g. to subscribe them to the hint feed. */
1385
+ trackedSwapIds(): string[];
1386
+ /**
1387
+ * Apply a server status to one swap and recompute its action. A hint that
1388
+ * carries the pushed `status` is applied as-is; without one (the worker's
1389
+ * retry and post-reconnect paths) the status is re-fetched from the server.
1390
+ * A no-op for an untracked swap.
1391
+ */
1392
+ applyHint(swapId: string, opts?: {
1393
+ force?: boolean;
1394
+ status?: SwapStatus;
1395
+ }): Promise<void>;
1396
+ /** Notify `cb` of the current action for each tracked swap, then on every change. */
1397
+ subscribeToActions(cb: ActionSubscriber): () => void;
1398
+ /** Stop tracking: drop the tick timer, tracked swaps, and subscribers. */
1399
+ stop(): void;
1400
+ }
1401
+
1296
1402
  /**
1297
1403
  * Ties the hint feed and the action model together into auto-execution.
1298
1404
  *
@@ -1313,9 +1419,15 @@ declare function defaultEvmReaders(overrides?: Record<number, string>): Map<numb
1313
1419
  /** The tracker surface the worker drives. */
1314
1420
  interface WorkerTracker {
1315
1421
  trackedSwapIds(): string[];
1316
- /** Reconcile a swap's legs from chain, then recompute. `force` re-emits even if unchanged. */
1422
+ /**
1423
+ * Re-verify a swap, then recompute. `force` re-emits even if unchanged.
1424
+ * `status` is the hint's pushed status: a chain-verifying tracker ignores it
1425
+ * (the chain stays its source of truth); a hints-only tracker applies it
1426
+ * directly, saving a server round-trip.
1427
+ */
1317
1428
  applyHint(swapId: string, opts?: {
1318
1429
  force?: boolean;
1430
+ status?: SwapStatus;
1319
1431
  }): Promise<void>;
1320
1432
  subscribeToActions(cb: (swapId: string, actions: SwapActions) => void): () => void;
1321
1433
  }
@@ -1327,6 +1439,7 @@ interface WorkerHintSource {
1327
1439
  unsubscribe(swapIds: string[]): void;
1328
1440
  onStatus(cb: (update: {
1329
1441
  swapId: string;
1442
+ status?: SwapStatus;
1330
1443
  }) => void): () => void;
1331
1444
  /**
1332
1445
  * Optional: fires when the feed comes back up after an outage, during which
@@ -1462,4 +1575,4 @@ declare class WsStatusSource {
1462
1575
 
1463
1576
  declare function swapToTracked(stored: StoredSwap): TrackedSwap | undefined;
1464
1577
 
1465
- 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 ElectrumReaderNetwork, type ElectrumRpc, 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, electrumReader, esploraReader, evmReaderFromClient, htlcFactsFromEsploraTxs, htlcKey, htlcQueryKey, swapToTracked };
1578
+ 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 ElectrumReaderNetwork, type ElectrumRpc, type EvmChainReader, EvmContractManager, type EvmContractManagerDeps, type EvmHtlcQuery, type EvmLogClient, type FundAction, HintTracker, type HintTrackerOptions, 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, electrumReader, esploraReader, evmReaderFromClient, htlcFactsFromEsploraTxs, htlcKey, htlcQueryKey, swapToTracked };
package/dist/index.js CHANGED
@@ -1216,6 +1216,113 @@ function defaultEvmReaders(overrides) {
1216
1216
  return readers;
1217
1217
  }
1218
1218
 
1219
+ // src/hints/hint-tracker.ts
1220
+ var HintTracker = class {
1221
+ #fetchStatus;
1222
+ #refreshIntervalMs;
1223
+ #swaps = /* @__PURE__ */ new Map();
1224
+ /** Last server-reported status per swap (seeded from storage, updated by hints). */
1225
+ #statuses = /* @__PURE__ */ new Map();
1226
+ /** Last actions emitted per swap — for dedupe and the subscribe-time snapshot. */
1227
+ #lastActions = /* @__PURE__ */ new Map();
1228
+ #subscribers = /* @__PURE__ */ new Set();
1229
+ #timer;
1230
+ constructor(options) {
1231
+ this.#fetchStatus = options.fetchStatus;
1232
+ this.#refreshIntervalMs = options.refreshIntervalMs ?? 5e3;
1233
+ }
1234
+ /**
1235
+ * Always `true`: with no chain readers there is no reachability constraint.
1236
+ * (Claim/refund execution has its own chain configuration, independent of
1237
+ * tracking.)
1238
+ */
1239
+ canObserve(_swap) {
1240
+ return true;
1241
+ }
1242
+ /** Register the swaps, derive each one's first action from its stored status. */
1243
+ async startTracking(swaps) {
1244
+ for (const swap of swaps) this.#admit(swap);
1245
+ this.#recomputeAll();
1246
+ if (this.#refreshIntervalMs > 0)
1247
+ this.#timer = setInterval(
1248
+ () => this.#recomputeAll(),
1249
+ this.#refreshIntervalMs
1250
+ );
1251
+ }
1252
+ /**
1253
+ * Track one more swap after {@link startTracking}. Idempotent: a swap already
1254
+ * tracked, or one already seen through to a terminal action, is ignored.
1255
+ */
1256
+ async track(swap) {
1257
+ if (this.#swaps.has(swap.swapId) || this.#lastActions.has(swap.swapId))
1258
+ return;
1259
+ this.#admit(swap);
1260
+ this.#recompute(swap);
1261
+ }
1262
+ #admit(swap) {
1263
+ this.#swaps.set(swap.swapId, swap);
1264
+ if (swap.storedStatus !== void 0 && !this.#statuses.has(swap.swapId))
1265
+ this.#statuses.set(swap.swapId, swap.storedStatus);
1266
+ }
1267
+ /** The swap ids currently tracked — e.g. to subscribe them to the hint feed. */
1268
+ trackedSwapIds() {
1269
+ return [...this.#swaps.keys()];
1270
+ }
1271
+ /**
1272
+ * Apply a server status to one swap and recompute its action. A hint that
1273
+ * carries the pushed `status` is applied as-is; without one (the worker's
1274
+ * retry and post-reconnect paths) the status is re-fetched from the server.
1275
+ * A no-op for an untracked swap.
1276
+ */
1277
+ async applyHint(swapId, opts) {
1278
+ const swap = this.#swaps.get(swapId);
1279
+ if (!swap) return;
1280
+ const status = opts?.status ?? await this.#fetchStatus(swapId);
1281
+ if (!this.#swaps.has(swapId)) return;
1282
+ this.#statuses.set(swapId, status);
1283
+ this.#recompute(swap, opts?.force ?? false);
1284
+ }
1285
+ /** Notify `cb` of the current action for each tracked swap, then on every change. */
1286
+ subscribeToActions(cb) {
1287
+ this.#subscribers.add(cb);
1288
+ for (const [swapId, actions] of this.#lastActions) cb(swapId, actions);
1289
+ return () => this.#subscribers.delete(cb);
1290
+ }
1291
+ /** Stop tracking: drop the tick timer, tracked swaps, and subscribers. */
1292
+ stop() {
1293
+ if (this.#timer) clearInterval(this.#timer);
1294
+ this.#timer = void 0;
1295
+ this.#swaps.clear();
1296
+ this.#subscribers.clear();
1297
+ }
1298
+ #recomputeAll() {
1299
+ for (const swap of this.#swaps.values()) this.#recompute(swap);
1300
+ }
1301
+ #recompute(swap, force = false) {
1302
+ const status = this.#statuses.get(swap.swapId);
1303
+ if (status === void 0) return;
1304
+ const now = Date.now();
1305
+ const actions = deriveSwapActions({
1306
+ status,
1307
+ clientChainNow: now,
1308
+ serverChainNow: now,
1309
+ clientRefundLocktime: swap.clientRefundLocktime,
1310
+ serverRefundLocktime: swap.serverRefundLocktime,
1311
+ // Pay-on-Lightning swaps have no client-funded on-chain leg.
1312
+ clientFunds: swap.clientHtlc !== void 0
1313
+ });
1314
+ const previous = this.#lastActions.get(swap.swapId);
1315
+ if (!force && previous && JSON.stringify(previous) === JSON.stringify(actions))
1316
+ return;
1317
+ this.#lastActions.set(swap.swapId, actions);
1318
+ for (const cb of this.#subscribers) cb(swap.swapId, actions);
1319
+ if (actions.recommended === "none") {
1320
+ this.#swaps.delete(swap.swapId);
1321
+ this.#statuses.delete(swap.swapId);
1322
+ }
1323
+ }
1324
+ };
1325
+
1219
1326
  // src/hints/swap-worker.ts
1220
1327
  var AUTO_EXECUTABLE = /* @__PURE__ */ new Set([
1221
1328
  "claim"
@@ -1249,8 +1356,8 @@ var SwapWorker = class {
1249
1356
  this.#started = true;
1250
1357
  this.#hintSource.subscribe(this.#tracker.trackedSwapIds());
1251
1358
  this.#unsubs.push(
1252
- this.#hintSource.onStatus(({ swapId }) => {
1253
- void this.#tracker.applyHint(swapId).catch((error) => {
1359
+ this.#hintSource.onStatus(({ swapId, status }) => {
1360
+ void this.#tracker.applyHint(swapId, { status }).catch((error) => {
1254
1361
  console.warn(`SwapWorker: applyHint(${swapId}) failed:`, error);
1255
1362
  });
1256
1363
  })
@@ -2415,8 +2522,11 @@ var Client = class {
2415
2522
  /**
2416
2523
  * Start observing the user's active swaps and deriving each one's next action.
2417
2524
  *
2418
- * Loads stored swaps, maps the ones whose ledgers are observable to
2419
- * {@link TrackedSwap}s, and runs the per-ledger monitors. Idempotent. Requires
2525
+ * Loads stored swaps, maps them to {@link TrackedSwap}s, and derives each
2526
+ * one's actions — by default from the server's status hints alone (TEMP while
2527
+ * the chain monitors are buggy; see
2528
+ * {@link ClientBuilder.withChainVerifiedTracking} to re-enable the per-ledger
2529
+ * chain monitors). Idempotent. Requires
2420
2530
  * the client to have been built with {@link ClientBuilder.withTracking}.
2421
2531
  * Subscribe with {@link subscribeToActions}; release with
2422
2532
  * {@link stopTracking}.
@@ -2436,7 +2546,11 @@ var Client = class {
2436
2546
  }
2437
2547
  async #doStartTracking() {
2438
2548
  try {
2439
- const tracker = new SwapTracker(await this.#ensureManagers(), {
2549
+ const chainVerified = this.#tracking.chainVerified || this.#tracking.managers !== void 0;
2550
+ const tracker = chainVerified ? new SwapTracker(await this.#ensureManagers(), {
2551
+ refreshIntervalMs: this.#tracking.refreshIntervalMs ?? 5e3
2552
+ }) : new HintTracker({
2553
+ fetchStatus: async (swapId) => (await this.getSwap(swapId, { updateStorage: true })).status,
2440
2554
  refreshIntervalMs: this.#tracking.refreshIntervalMs ?? 5e3
2441
2555
  });
2442
2556
  this.#tracker = tracker;
@@ -2485,7 +2599,9 @@ var Client = class {
2485
2599
  const swaps = await this.listAllSwaps();
2486
2600
  return swaps.filter((s) => !SETTLED_STORED_STATUSES.has(s.response.status)).map((s) => {
2487
2601
  try {
2488
- return swapToTracked(s);
2602
+ const tracked = swapToTracked(s);
2603
+ if (!tracked) return void 0;
2604
+ return { ...tracked, storedStatus: s.response.status };
2489
2605
  } catch (error) {
2490
2606
  console.warn(
2491
2607
  `Client: skipping untrackable stored swap ${s.response.id}:`,
@@ -2563,7 +2679,7 @@ var Client = class {
2563
2679
  * otherwise it runs observe-only, hints in, no spends.
2564
2680
  */
2565
2681
  #startWorker(tracker) {
2566
- const serverUrl = this.#tracking.serverUrl;
2682
+ const serverUrl = this.#tracking.serverUrl ?? legacyBaseUrl(this.#legacy);
2567
2683
  if (!serverUrl) return;
2568
2684
  const autoClaim = this.#tracking.autoClaim;
2569
2685
  const hintSource = new WsStatusSource({ serverUrl });
@@ -2661,6 +2777,7 @@ var ClientBuilder = class {
2661
2777
  #evmRpcUrls;
2662
2778
  #managers;
2663
2779
  #autoClaim;
2780
+ #chainVerified = false;
2664
2781
  /**
2665
2782
  * Override the EVM JSON-RPC endpoint per chainId. Optional — tracking uses
2666
2783
  * tested public defaults otherwise; an override is tried first, with the
@@ -2687,6 +2804,16 @@ var ClientBuilder = class {
2687
2804
  this.#trackingEnabled = false;
2688
2805
  return this;
2689
2806
  }
2807
+ /**
2808
+ * Verify swap state against the chain (per-ledger contract managers) instead
2809
+ * of trusting the server's status hints. TEMPORARILY off by default while the
2810
+ * chain monitors are being fixed — without it, tracking derives actions from
2811
+ * the server's pushed status alone, with zero chain access.
2812
+ */
2813
+ withChainVerifiedTracking() {
2814
+ this.#chainVerified = true;
2815
+ return this;
2816
+ }
2690
2817
  /**
2691
2818
  * Advanced: supply per-ledger {@link ContractManager}s explicitly instead of
2692
2819
  * letting the client auto-build them from config (useful for tests or custom
@@ -2804,10 +2931,18 @@ var ClientBuilder = class {
2804
2931
  bitcoinNetwork: this.#bitcoinNetwork,
2805
2932
  bitcoinMinConfirmations: this.#bitcoinMinConfirmations,
2806
2933
  evmRpcUrls: this.#evmRpcUrls,
2807
- autoClaim: this.#autoClaim
2934
+ autoClaim: this.#autoClaim,
2935
+ chainVerified: this.#chainVerified
2808
2936
  });
2809
2937
  }
2810
2938
  };
2939
+ function legacyBaseUrl(legacy) {
2940
+ try {
2941
+ return legacy.baseUrl;
2942
+ } catch {
2943
+ return void 0;
2944
+ }
2945
+ }
2811
2946
  var SETTLED_STORED_STATUSES = /* @__PURE__ */ new Set([
2812
2947
  "serverredeemed",
2813
2948
  "clientrefunded",
@@ -2828,6 +2963,7 @@ export {
2828
2963
  DEFAULT_ESPLORA_URLS,
2829
2964
  DEFAULT_EVM_RPCS,
2830
2965
  EvmContractManager,
2966
+ HintTracker,
2831
2967
  SwapTracker,
2832
2968
  SwapWorker,
2833
2969
  WsStatusSource,