@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.cjs +145 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +117 -4
- package/dist/index.d.ts +117 -4
- package/dist/index.js +144 -8
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
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
|
|
684
|
-
*
|
|
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
|
-
/**
|
|
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
|
|
684
|
-
*
|
|
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
|
-
/**
|
|
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
|
|
2419
|
-
*
|
|
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
|
|
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
|
-
|
|
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,
|