@satora/swap 1.0.0 → 1.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
@@ -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;
@@ -648,6 +676,8 @@ declare class Client {
648
676
  /** Delegated to the legacy client (migration checkpoint). */
649
677
  createLightningToArkadeSwap(...args: Parameters<Client$1["createLightningToArkadeSwap"]>): ReturnType<Client$1["createLightningToArkadeSwap"]>;
650
678
  /** Delegated to the legacy client (migration checkpoint). */
679
+ createArkadeToLightningSwap(...args: Parameters<Client$1["createArkadeToLightningSwap"]>): ReturnType<Client$1["createArkadeToLightningSwap"]>;
680
+ /** Delegated to the legacy client (migration checkpoint). */
651
681
  createEvmToArkadeSwapGeneric(...args: Parameters<Client$1["createEvmToArkadeSwapGeneric"]>): ReturnType<Client$1["createEvmToArkadeSwapGeneric"]>;
652
682
  /** Delegated to the legacy client (migration checkpoint). */
653
683
  createEvmToBitcoinSwap(...args: Parameters<Client$1["createEvmToBitcoinSwap"]>): ReturnType<Client$1["createEvmToBitcoinSwap"]>;
@@ -678,8 +708,11 @@ declare class Client {
678
708
  /**
679
709
  * Start observing the user's active swaps and deriving each one's next action.
680
710
  *
681
- * Loads stored swaps, maps the ones whose ledgers are observable to
682
- * {@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
683
716
  * the client to have been built with {@link ClientBuilder.withTracking}.
684
717
  * Subscribe with {@link subscribeToActions}; release with
685
718
  * {@link stopTracking}.
@@ -719,6 +752,13 @@ declare class ClientBuilder {
719
752
  }): this;
720
753
  /** Turn observe-mode tracking off. */
721
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;
722
762
  /**
723
763
  * Advanced: supply per-ledger {@link ContractManager}s explicitly instead of
724
764
  * letting the client auto-build them from config (useful for tests or custom
@@ -1291,6 +1331,74 @@ declare function createEvmRpcReader(rpcUrls: string | string[]): EvmChainReader;
1291
1331
  */
1292
1332
  declare function defaultEvmReaders(overrides?: Record<number, string>): Map<number, EvmChainReader>;
1293
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
+
1294
1402
  /**
1295
1403
  * Ties the hint feed and the action model together into auto-execution.
1296
1404
  *
@@ -1311,9 +1419,15 @@ declare function defaultEvmReaders(overrides?: Record<number, string>): Map<numb
1311
1419
  /** The tracker surface the worker drives. */
1312
1420
  interface WorkerTracker {
1313
1421
  trackedSwapIds(): string[];
1314
- /** 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
+ */
1315
1428
  applyHint(swapId: string, opts?: {
1316
1429
  force?: boolean;
1430
+ status?: SwapStatus;
1317
1431
  }): Promise<void>;
1318
1432
  subscribeToActions(cb: (swapId: string, actions: SwapActions) => void): () => void;
1319
1433
  }
@@ -1325,6 +1439,7 @@ interface WorkerHintSource {
1325
1439
  unsubscribe(swapIds: string[]): void;
1326
1440
  onStatus(cb: (update: {
1327
1441
  swapId: string;
1442
+ status?: SwapStatus;
1328
1443
  }) => void): () => void;
1329
1444
  /**
1330
1445
  * Optional: fires when the feed comes back up after an outage, during which
@@ -1460,4 +1575,4 @@ declare class WsStatusSource {
1460
1575
 
1461
1576
  declare function swapToTracked(stored: StoredSwap): TrackedSwap | undefined;
1462
1577
 
1463
- 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;
@@ -648,6 +676,8 @@ declare class Client {
648
676
  /** Delegated to the legacy client (migration checkpoint). */
649
677
  createLightningToArkadeSwap(...args: Parameters<Client$1["createLightningToArkadeSwap"]>): ReturnType<Client$1["createLightningToArkadeSwap"]>;
650
678
  /** Delegated to the legacy client (migration checkpoint). */
679
+ createArkadeToLightningSwap(...args: Parameters<Client$1["createArkadeToLightningSwap"]>): ReturnType<Client$1["createArkadeToLightningSwap"]>;
680
+ /** Delegated to the legacy client (migration checkpoint). */
651
681
  createEvmToArkadeSwapGeneric(...args: Parameters<Client$1["createEvmToArkadeSwapGeneric"]>): ReturnType<Client$1["createEvmToArkadeSwapGeneric"]>;
652
682
  /** Delegated to the legacy client (migration checkpoint). */
653
683
  createEvmToBitcoinSwap(...args: Parameters<Client$1["createEvmToBitcoinSwap"]>): ReturnType<Client$1["createEvmToBitcoinSwap"]>;
@@ -678,8 +708,11 @@ declare class Client {
678
708
  /**
679
709
  * Start observing the user's active swaps and deriving each one's next action.
680
710
  *
681
- * Loads stored swaps, maps the ones whose ledgers are observable to
682
- * {@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
683
716
  * the client to have been built with {@link ClientBuilder.withTracking}.
684
717
  * Subscribe with {@link subscribeToActions}; release with
685
718
  * {@link stopTracking}.
@@ -719,6 +752,13 @@ declare class ClientBuilder {
719
752
  }): this;
720
753
  /** Turn observe-mode tracking off. */
721
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;
722
762
  /**
723
763
  * Advanced: supply per-ledger {@link ContractManager}s explicitly instead of
724
764
  * letting the client auto-build them from config (useful for tests or custom
@@ -1291,6 +1331,74 @@ declare function createEvmRpcReader(rpcUrls: string | string[]): EvmChainReader;
1291
1331
  */
1292
1332
  declare function defaultEvmReaders(overrides?: Record<number, string>): Map<number, EvmChainReader>;
1293
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
+
1294
1402
  /**
1295
1403
  * Ties the hint feed and the action model together into auto-execution.
1296
1404
  *
@@ -1311,9 +1419,15 @@ declare function defaultEvmReaders(overrides?: Record<number, string>): Map<numb
1311
1419
  /** The tracker surface the worker drives. */
1312
1420
  interface WorkerTracker {
1313
1421
  trackedSwapIds(): string[];
1314
- /** 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
+ */
1315
1428
  applyHint(swapId: string, opts?: {
1316
1429
  force?: boolean;
1430
+ status?: SwapStatus;
1317
1431
  }): Promise<void>;
1318
1432
  subscribeToActions(cb: (swapId: string, actions: SwapActions) => void): () => void;
1319
1433
  }
@@ -1325,6 +1439,7 @@ interface WorkerHintSource {
1325
1439
  unsubscribe(swapIds: string[]): void;
1326
1440
  onStatus(cb: (update: {
1327
1441
  swapId: string;
1442
+ status?: SwapStatus;
1328
1443
  }) => void): () => void;
1329
1444
  /**
1330
1445
  * Optional: fires when the feed comes back up after an outage, during which
@@ -1460,4 +1575,4 @@ declare class WsStatusSource {
1460
1575
 
1461
1576
  declare function swapToTracked(stored: StoredSwap): TrackedSwap | undefined;
1462
1577
 
1463
- 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
@@ -2,10 +2,10 @@ import {
2
2
  DEFAULT_ESPLORA_URLS,
3
3
  esploraReader,
4
4
  htlcFactsFromEsploraTxs
5
- } from "./chunk-EIWELJJK.js";
5
+ } from "./chunk-THI4UO6R.js";
6
6
  import {
7
7
  electrumReader
8
- } from "./chunk-VHXEU534.js";
8
+ } from "./chunk-FTPIQSRW.js";
9
9
 
10
10
  // src/index.ts
11
11
  export * from "@lendasat/lendaswap-sdk-pure";
@@ -570,7 +570,7 @@ var BitcoinContractManager = class _BitcoinContractManager {
570
570
  return new _BitcoinContractManager(deps);
571
571
  }
572
572
  static async create(config) {
573
- const { esploraReader: esploraReader2 } = await import("./bitcoin-reader-esplora-HV6CANAA.js");
573
+ const { esploraReader: esploraReader2 } = await import("./bitcoin-reader-esplora-ZLPMM5FF.js");
574
574
  let reader = esploraReader2(
575
575
  config.esploraUrl,
576
576
  void 0,
@@ -578,7 +578,7 @@ var BitcoinContractManager = class _BitcoinContractManager {
578
578
  );
579
579
  if (config.electrumWsUrl) {
580
580
  const [{ electrumReader: electrumReader2 }, { ElectrumWsClient }] = await Promise.all([
581
- import("./bitcoin-reader-electrum-4DOJ7NGH.js"),
581
+ import("./bitcoin-reader-electrum-PY7DHD6T.js"),
582
582
  import("@lendasat/lendaswap-sdk-pure")
583
583
  ]);
584
584
  reader = electrumReader2(new ElectrumWsClient(config.electrumWsUrl), {
@@ -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
  })
@@ -1807,6 +1914,21 @@ function swapToTracked(stored) {
1807
1914
  // no on-chain client leg
1808
1915
  serverRefundLocktime: ms(r.vhtlc_refund_locktime)
1809
1916
  };
1917
+ // Client funds the Arkade VHTLC (source_amount); the server pays the
1918
+ // Lightning invoice off-chain and claims the VHTLC — no server leg to
1919
+ // watch.
1920
+ case "arkade_to_lightning":
1921
+ return {
1922
+ swapId: r.id,
1923
+ clientHtlc: arkadeLeg(
1924
+ r,
1925
+ r.arkade_vhtlc_address,
1926
+ Number(r.source_amount)
1927
+ ),
1928
+ clientRefundLocktime: ms(r.vhtlc_refund_locktime),
1929
+ serverRefundLocktime: 0
1930
+ // no on-chain server leg
1931
+ };
1810
1932
  default:
1811
1933
  return void 0;
1812
1934
  }
@@ -2333,6 +2455,12 @@ var Client = class {
2333
2455
  );
2334
2456
  }
2335
2457
  /** Delegated to the legacy client (migration checkpoint). */
2458
+ createArkadeToLightningSwap(...args) {
2459
+ return this.#trackAfterCreate(
2460
+ this.#legacy.createArkadeToLightningSwap(...args)
2461
+ );
2462
+ }
2463
+ /** Delegated to the legacy client (migration checkpoint). */
2336
2464
  createEvmToArkadeSwapGeneric(...args) {
2337
2465
  return this.#trackAfterCreate(
2338
2466
  this.#legacy.createEvmToArkadeSwapGeneric(...args)
@@ -2394,8 +2522,11 @@ var Client = class {
2394
2522
  /**
2395
2523
  * Start observing the user's active swaps and deriving each one's next action.
2396
2524
  *
2397
- * Loads stored swaps, maps the ones whose ledgers are observable to
2398
- * {@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
2399
2530
  * the client to have been built with {@link ClientBuilder.withTracking}.
2400
2531
  * Subscribe with {@link subscribeToActions}; release with
2401
2532
  * {@link stopTracking}.
@@ -2415,7 +2546,11 @@ var Client = class {
2415
2546
  }
2416
2547
  async #doStartTracking() {
2417
2548
  try {
2418
- 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,
2419
2554
  refreshIntervalMs: this.#tracking.refreshIntervalMs ?? 5e3
2420
2555
  });
2421
2556
  this.#tracker = tracker;
@@ -2464,7 +2599,9 @@ var Client = class {
2464
2599
  const swaps = await this.listAllSwaps();
2465
2600
  return swaps.filter((s) => !SETTLED_STORED_STATUSES.has(s.response.status)).map((s) => {
2466
2601
  try {
2467
- return swapToTracked(s);
2602
+ const tracked = swapToTracked(s);
2603
+ if (!tracked) return void 0;
2604
+ return { ...tracked, storedStatus: s.response.status };
2468
2605
  } catch (error) {
2469
2606
  console.warn(
2470
2607
  `Client: skipping untrackable stored swap ${s.response.id}:`,
@@ -2542,7 +2679,7 @@ var Client = class {
2542
2679
  * otherwise it runs observe-only, hints in, no spends.
2543
2680
  */
2544
2681
  #startWorker(tracker) {
2545
- const serverUrl = this.#tracking.serverUrl;
2682
+ const serverUrl = this.#tracking.serverUrl ?? legacyBaseUrl(this.#legacy);
2546
2683
  if (!serverUrl) return;
2547
2684
  const autoClaim = this.#tracking.autoClaim;
2548
2685
  const hintSource = new WsStatusSource({ serverUrl });
@@ -2640,6 +2777,7 @@ var ClientBuilder = class {
2640
2777
  #evmRpcUrls;
2641
2778
  #managers;
2642
2779
  #autoClaim;
2780
+ #chainVerified = false;
2643
2781
  /**
2644
2782
  * Override the EVM JSON-RPC endpoint per chainId. Optional — tracking uses
2645
2783
  * tested public defaults otherwise; an override is tried first, with the
@@ -2666,6 +2804,16 @@ var ClientBuilder = class {
2666
2804
  this.#trackingEnabled = false;
2667
2805
  return this;
2668
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
+ }
2669
2817
  /**
2670
2818
  * Advanced: supply per-ledger {@link ContractManager}s explicitly instead of
2671
2819
  * letting the client auto-build them from config (useful for tests or custom
@@ -2783,10 +2931,18 @@ var ClientBuilder = class {
2783
2931
  bitcoinNetwork: this.#bitcoinNetwork,
2784
2932
  bitcoinMinConfirmations: this.#bitcoinMinConfirmations,
2785
2933
  evmRpcUrls: this.#evmRpcUrls,
2786
- autoClaim: this.#autoClaim
2934
+ autoClaim: this.#autoClaim,
2935
+ chainVerified: this.#chainVerified
2787
2936
  });
2788
2937
  }
2789
2938
  };
2939
+ function legacyBaseUrl(legacy) {
2940
+ try {
2941
+ return legacy.baseUrl;
2942
+ } catch {
2943
+ return void 0;
2944
+ }
2945
+ }
2790
2946
  var SETTLED_STORED_STATUSES = /* @__PURE__ */ new Set([
2791
2947
  "serverredeemed",
2792
2948
  "clientrefunded",
@@ -2807,6 +2963,7 @@ export {
2807
2963
  DEFAULT_ESPLORA_URLS,
2808
2964
  DEFAULT_EVM_RPCS,
2809
2965
  EvmContractManager,
2966
+ HintTracker,
2810
2967
  SwapTracker,
2811
2968
  SwapWorker,
2812
2969
  WsStatusSource,