@unicitylabs/sphere-sdk 0.14.0-dev.5 → 0.14.0-dev.7

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.
@@ -547,6 +547,25 @@ interface PaymentsRequestsApi {
547
547
  decline(id: string): Promise<void>;
548
548
  dismissProcessed(): void;
549
549
  }
550
+ /**
551
+ * A pending-transfers row, derived ON READ from the §6 stores (intent backstop
552
+ * + delivery journal + shortfalls) — never a cached mirror. kind 'shortfall' =
553
+ * a completed partial (#690) whose `amount` is the remainder still owed;
554
+ * legs.certified counts journaled legs (certified, delivery still owed).
555
+ */
556
+ interface PendingTransfer {
557
+ transferId: string;
558
+ kind: 'open' | 'shortfall';
559
+ recipient: string;
560
+ coinId: string;
561
+ amount: string;
562
+ legs: {
563
+ certified: number;
564
+ total: number;
565
+ };
566
+ deliveryPending: boolean;
567
+ createdAt: number;
568
+ }
550
569
  interface PaymentsV2 {
551
570
  assets(coinId?: string): Promise<Asset[]>;
552
571
  tokens(filter?: {
@@ -561,6 +580,8 @@ interface PaymentsV2 {
561
580
  receive(): Promise<{
562
581
  transfers: IncomingTransfer[];
563
582
  }>;
583
+ pendingTransfers(): Promise<PendingTransfer[]>;
584
+ resumeNow(): Promise<void>;
564
585
  readonly requests: PaymentsRequestsApi;
565
586
  }
566
587
  interface PaymentsV2Events {
@@ -572,7 +593,8 @@ interface PaymentsV2Events {
572
593
  detail?: string;
573
594
  };
574
595
  'inventory:updated': Record<string, never>;
575
- 'history:updated': Record<string, never>;
596
+ /** The just-recorded entry, client-shaped (the same mapping history() serves). */
597
+ 'history:updated': HistoryEntry;
576
598
  'payment_request:incoming': PaymentRequestView;
577
599
  'payment_request:updated': {
578
600
  id: string;
@@ -645,6 +667,14 @@ interface DeliveryPort {
645
667
  deliver(recipientPubkey: string, blob: Uint8Array, options: DeliverOptions): Promise<DeliveryReceipt>;
646
668
  deliverBatch?(recipientPubkey: string, blobs: Uint8Array[], options: DeliverOptions): Promise<DeliveryReceipt[]>;
647
669
  incoming(sinceCursor?: string): AsyncIterable<IncomingDelivery>;
670
+ /**
671
+ * The syncEpoch of the most recent incoming() page — updated per page, null
672
+ * before the first. §5.7 restore self-detection: the mailbox page is the
673
+ * honest epoch source, so Receive voids its (cursor, epoch) continuity on a
674
+ * mismatch even when the wake socket missed a server restore. (Pinned by the
675
+ * S7 contract suite; wallet-api#119's S7 text carries the same sentence.)
676
+ */
677
+ incomingEpoch(): string | null;
648
678
  ack(deliveryId: string, disposition: 'claimed' | 'rejected', reason?: 'invalid' | 'not-owned' | 'storage-rejected' | 'other'): Promise<void>;
649
679
  onWake?(cb: () => void): () => void;
650
680
  }
@@ -739,6 +769,8 @@ declare const STORE_KEYS: {
739
769
  readonly settlingLinks: "settling";
740
770
  readonly streamCursor: (s: StreamName) => string;
741
771
  readonly epochLatch: "epoch-latch";
772
+ readonly suspectedSpent: "suspected-spent";
773
+ readonly knownSpends: "known-spends";
742
774
  };
743
775
 
744
776
  type RequestWireStatus = 'open' | 'paid' | 'declined' | 'expired';
@@ -826,6 +858,7 @@ declare class Requests implements PaymentsRequestsApi {
826
858
  private now;
827
859
  private ensureJournalLoaded;
828
860
  private mutateJournal;
861
+ /** Idempotent for a same-transferId re-write: committed only ratchets up, createdAt kept. */
829
862
  private writeLink;
830
863
  private clearLink;
831
864
  drainIncoming(): Promise<void>;
@@ -845,6 +878,18 @@ declare class Requests implements PaymentsRequestsApi {
845
878
  }>;
846
879
  pay(id: string): Promise<TransferResult>;
847
880
  private payInner;
881
+ /**
882
+ * THE settlement invariant (#441 + the failed-respond P1): a settling link is
883
+ * removed ONLY by (a) a CONFIRMED paid respond — a 2xx, or the 409
884
+ * already-resolved absorb — or (b) a proven clean pre-commit failure
885
+ * (revertPayable). Nothing else removes one: not a network error, not a 5xx,
886
+ * not a reload. Every path that binds a request to a transfer outcome funnels
887
+ * through here — pay()'s clean success (respond now), pay()'s
888
+ * possibly-committed throw (respond deferred), and reconcile's deferred arms
889
+ * — so a failed respond always leaves the link + 'settling' and the next
890
+ * reconcile pass (the committed-link override) retries the respond.
891
+ */
892
+ private settle;
848
893
  /** 'paid' respond leg: 409 = already resolved = idempotent success; other errors defer. */
849
894
  private respondPaid;
850
895
  decline(id: string): Promise<void>;
@@ -852,7 +897,9 @@ declare class Requests implements PaymentsRequestsApi {
852
897
  reconcile(outcomes: ResumeOutcomes): Promise<void>;
853
898
  private doReconcile;
854
899
  private reconcileUnaccounted;
900
+ /** Deferred paid: the ONE settlement path again — a failed respond keeps the link. */
855
901
  private resolvePaid;
902
+ /** Removal cause (b): a PROVEN clean outcome (pre-commit failure / server-aborted). */
856
903
  private revertPayable;
857
904
  }
858
905
 
@@ -899,6 +946,8 @@ interface PriceReader {
899
946
  getPrices(tokenNames: string[]): Promise<Map<string, PriceQuote>>;
900
947
  }
901
948
 
949
+ declare const ATTENTION_RESEED_REJECTED = "intent:reseed-rejected";
950
+
902
951
  /**
903
952
  * F13 makes `mint(params, { transferId, opIndex })` idempotent-recoverable — a
904
953
  * same-seed re-CALL recovers the existing certification via the E.2 probe
@@ -919,6 +968,28 @@ interface FacadeSession {
919
968
  start(): Promise<void>;
920
969
  stop(): Promise<void>;
921
970
  subscribeStream(stream: 'inventory' | 'mailbox' | 'payment_requests', handler: () => void): () => void;
971
+ /** §5.1: the latched server syncEpoch ('' before first server contact). */
972
+ currentEpoch(): string;
973
+ /**
974
+ * §5.1 restore hook — REQUIRED so an unwired restore protocol is a COMPILE
975
+ * ERROR: handlers run and are AWAITED on a syncEpoch change BEFORE any
976
+ * stream nudge resumes. The facade registers handleEpochChange here.
977
+ */
978
+ subscribeEpochChange(handler: (epoch: string) => Promise<void>): () => void;
979
+ /**
980
+ * Optional connection-status feed (same wiring pattern as the streams; the
981
+ * emission point is the session's existing `connection:status` transition).
982
+ * The facade's heartbeat resets its backoff on a 'connected' recovery.
983
+ */
984
+ subscribeStatus?(handler: (status: 'connected' | 'degraded' | 'offline') => void): () => void;
985
+ }
986
+ /**
987
+ * §5.1/§6 restore surface of the checkpoint store: re-POST the slot's cached
988
+ * encrypt-once ciphertext byte-identical after a server restore (insert-once,
989
+ * first-write-wins server-side). Returns false when no ciphertext is cached.
990
+ */
991
+ interface CheckpointReseeder {
992
+ reseedCheckpoint(transferId: string, opIndex: number): Promise<boolean>;
922
993
  }
923
994
  interface IntentWireLike {
924
995
  transferId: string;
@@ -937,7 +1008,8 @@ interface PaymentsFacadeDeps {
937
1008
  client: FacadeClient;
938
1009
  storagePort: StoragePort;
939
1010
  deliveryPort: DeliveryPort;
940
- checkpointStore: SplitCheckpointStore;
1011
+ /** Reseeder REQUIRED: the restore protocol re-POSTs cached ciphertexts (§5.1). */
1012
+ checkpointStore: SplitCheckpointStore & CheckpointReseeder;
941
1013
  /** Initial engine source; setEngine() swaps what FUTURE operations snapshot. */
942
1014
  engineRef: () => ITokenEngine;
943
1015
  kv: ScopedKV;
@@ -953,7 +1025,8 @@ interface PaymentsFacadeDeps {
953
1025
  ownPubkey: string;
954
1026
  ownNametag?: () => string | undefined;
955
1027
  requestMemo: RequestMemoCodec;
956
- syncEpoch?: () => string;
1028
+ /** REQUIRED (§5.1): reads the session's current epoch — never a default. */
1029
+ syncEpoch: () => string;
957
1030
  now?: () => number;
958
1031
  newId?: () => string;
959
1032
  workBudget?: number;
@@ -975,11 +1048,20 @@ declare class PaymentsFacade implements PaymentsV2 {
975
1048
  private readonly receiveLoop;
976
1049
  private readonly heldStates;
977
1050
  private readonly ownPubkeyBytes;
1051
+ private readonly restoreDeps;
978
1052
  readonly requests: Requests;
979
1053
  private currentEngine;
980
1054
  private readonly pendingOps;
981
1055
  private unsubscribers;
982
1056
  private started;
1057
+ private readonly heartbeat;
1058
+ private readonly converger;
1059
+ /** Resume single-flight (§7): ticks, start() and resumeNow() coalesce onto ONE pass. */
1060
+ private readonly resumeFlight;
1061
+ /** §5.1/§7: restore + convergence passes SERIALIZE here. */
1062
+ private readonly passChain;
1063
+ /** §7 ownership: ids with an in-process machine attempt — the pass never adopts one. */
1064
+ private readonly activeMoneyOps;
983
1065
  constructor(deps: PaymentsFacadeDeps);
984
1066
  start(): Promise<void>;
985
1067
  /** §7 same-address restart gate: resolves only after in-flight ops settle. */
@@ -994,26 +1076,36 @@ declare class PaymentsFacade implements PaymentsV2 {
994
1076
  before?: string;
995
1077
  limit?: number;
996
1078
  }): Promise<HistoryPage>;
1079
+ /** §4 pending-transfers UI surface — derived on read, never cached (convergence.ts). */
1080
+ pendingTransfers(): Promise<PendingTransfer[]>;
997
1081
  send(request: SendRequest): Promise<TransferResult>;
998
1082
  receive(): Promise<{
999
1083
  transfers: IncomingTransfer[];
1000
1084
  }>;
1001
1085
  mint(coinId: string, amount: bigint): Promise<MintResult>;
1086
+ resumeNow(): Promise<void>;
1087
+ /** §5.1 restore — awaited by the session latch BEFORE streams resume; never coalesced onto a pre-restore pass. */
1088
+ handleEpochChange(_newEpoch: string): Promise<void>;
1089
+ /** One single-flighted pass + its reschedule: concurrent callers coalesce. */
1090
+ private runConvergencePass;
1091
+ private convergeBody;
1092
+ private nowMs;
1093
+ /** The ONE place a send() outcome is shaped: success emits in finishSend, a
1094
+ * CLEAN rejection emits `transfer:updated{status:'failed'}` here (§4). */
1095
+ private sendOutcome;
1096
+ /** Only a CLEAN failure (nothing certified, classifyError 'other') is 'failed'.
1097
+ * Keep-open/partial/conflict outcomes are pending/converging — labelling them
1098
+ * 'failed' invites a dApp re-send, i.e. a double-pay (#631/#676). */
1099
+ private emitCleanFailure;
1002
1100
  private sendWithPolicy;
1003
1101
  /** §5.6 cross-network deposit trap: refused BEFORE any reserve/certification. */
1004
1102
  private requireSameNetworkRecipient;
1005
1103
  private runAttempt;
1006
- /**
1007
- * Partial outcome (#677/#690): the shortfall is already durable (written by
1008
- * the machine BEFORE complete). Accumulate the settled set, then re-plan ONLY
1009
- * the remainder under a NEW transferId — never the full amount.
1010
- */
1104
+ /** Partial (#677/#690): shortfall already durable (machine wrote it BEFORE
1105
+ * complete); accumulate settled, re-plan ONLY the remainder, NEW transferId. */
1011
1106
  private consumePartial;
1012
- /**
1013
- * One attempt's failure disposition: possibly-committed → rethrow UNWRAPPED;
1014
- * backstop still 'open' (committed>0) → converge via the same machine's
1015
- * resumeIntent; clean conflict with a demoted source → bounded full re-plan.
1016
- */
1107
+ /** Failure disposition: possibly-committed → rethrow UNWRAPPED; backstop
1108
+ * 'open' → converge via resumeIntent; clean demoted conflict → full re-plan. */
1017
1109
  private disposeFailedAttempt;
1018
1110
  /** #677/#690: ≥1 leg committed — the SAME machine, resumed in-process, converges it. */
1019
1111
  private convergePartial;
@@ -1033,28 +1125,28 @@ declare class PaymentsFacade implements PaymentsV2 {
1033
1125
  private refreshThenRelease;
1034
1126
  private accumulate;
1035
1127
  private finishSend;
1036
- /**
1037
- * With nothing delivered the error passes UNWRAPPED (identity + cause kept);
1038
- * after ≥1 delivered leg EVERY failure surfaces as PartialSendConflictError
1039
- * over the accumulated settled set — never bare, never a full-amount retry.
1040
- */
1128
+ /** Nothing delivered → UNWRAPPED; after ≥1 delivered leg every failure surfaces as PartialSendConflictError over the settled set. */
1041
1129
  private partialize;
1042
1130
  /** #441: possibly-committed errors must carry the transferId for the settling journal. */
1043
1131
  private stampTransferId;
1044
1132
  private softAbort;
1045
1133
  private mintInner;
1134
+ private mintUnderJournal;
1046
1135
  private finalizeMint;
1136
+ /** @returns how many journal entries were RESOLVED (cleared) — heartbeat progress. */
1047
1137
  private replayMints;
1048
1138
  private replayMint;
1049
1139
  private mintParams;
1050
1140
  private tokenInServerInventory;
1051
1141
  private engine;
1052
1142
  private newId;
1053
- private runResume;
1054
1143
  private seedHeldStates;
1055
1144
  private toUiToken;
1056
1145
  private track;
1057
1146
  private trackTail;
1058
1147
  }
1059
1148
 
1060
- export { ATTENTION_MINT_UNRESOLVED, type ApplyDeltaResult, type DeliverOptions, type DeliveryJournalEntry, type DeliveryPort, type DeliveryReceipt, type DeterministicMintCapable, type FacadeClient, type FacadeSession, type HistoryEntry, type HistoryPage, type IncomingDelivery, type IntentBackstopEntry, type IntentPayload, type InventoryAsset, type InventoryItem, type InventoryPage, MAX_RESELECT, type MintJournalEntry, type MintResult, type OpOutcome, type OutcomeClass, type PaymentRequestStatus, type PaymentRequestView, PaymentsFacade, type PaymentsFacadeDeps, type PaymentsRequestsApi, type PaymentsV2, type PaymentsV2Events, type PlannedOp, type RecipientInfo, STORE_KEYS, type ScopedKV, type SendRequest, type SettlingLink, type ShortfallEntry, type StoragePort, type StreamCursor, type StreamName, createScopedKV, supportsDeterministicMint };
1149
+ declare const HEARTBEAT_SEED_MS = 5000;
1150
+ declare const HEARTBEAT_CAP_MS = 120000;
1151
+
1152
+ export { ATTENTION_MINT_UNRESOLVED, ATTENTION_RESEED_REJECTED, type ApplyDeltaResult, type CheckpointReseeder, type DeliverOptions, type DeliveryJournalEntry, type DeliveryPort, type DeliveryReceipt, type DeterministicMintCapable, type FacadeClient, type FacadeSession, HEARTBEAT_CAP_MS, HEARTBEAT_SEED_MS, type HistoryEntry, type HistoryPage, type IncomingDelivery, type IntentBackstopEntry, type IntentPayload, type InventoryAsset, type InventoryItem, type InventoryPage, MAX_RESELECT, type MintJournalEntry, type MintResult, type OpOutcome, type OutcomeClass, type PaymentRequestStatus, type PaymentRequestView, PaymentsFacade, type PaymentsFacadeDeps, type PaymentsRequestsApi, type PaymentsV2, type PaymentsV2Events, type PendingTransfer, type PlannedOp, type RecipientInfo, STORE_KEYS, type ScopedKV, type SendRequest, type SettlingLink, type ShortfallEntry, type StoragePort, type StreamCursor, type StreamName, createScopedKV, supportsDeterministicMint };
@@ -547,6 +547,25 @@ interface PaymentsRequestsApi {
547
547
  decline(id: string): Promise<void>;
548
548
  dismissProcessed(): void;
549
549
  }
550
+ /**
551
+ * A pending-transfers row, derived ON READ from the §6 stores (intent backstop
552
+ * + delivery journal + shortfalls) — never a cached mirror. kind 'shortfall' =
553
+ * a completed partial (#690) whose `amount` is the remainder still owed;
554
+ * legs.certified counts journaled legs (certified, delivery still owed).
555
+ */
556
+ interface PendingTransfer {
557
+ transferId: string;
558
+ kind: 'open' | 'shortfall';
559
+ recipient: string;
560
+ coinId: string;
561
+ amount: string;
562
+ legs: {
563
+ certified: number;
564
+ total: number;
565
+ };
566
+ deliveryPending: boolean;
567
+ createdAt: number;
568
+ }
550
569
  interface PaymentsV2 {
551
570
  assets(coinId?: string): Promise<Asset[]>;
552
571
  tokens(filter?: {
@@ -561,6 +580,8 @@ interface PaymentsV2 {
561
580
  receive(): Promise<{
562
581
  transfers: IncomingTransfer[];
563
582
  }>;
583
+ pendingTransfers(): Promise<PendingTransfer[]>;
584
+ resumeNow(): Promise<void>;
564
585
  readonly requests: PaymentsRequestsApi;
565
586
  }
566
587
  interface PaymentsV2Events {
@@ -572,7 +593,8 @@ interface PaymentsV2Events {
572
593
  detail?: string;
573
594
  };
574
595
  'inventory:updated': Record<string, never>;
575
- 'history:updated': Record<string, never>;
596
+ /** The just-recorded entry, client-shaped (the same mapping history() serves). */
597
+ 'history:updated': HistoryEntry;
576
598
  'payment_request:incoming': PaymentRequestView;
577
599
  'payment_request:updated': {
578
600
  id: string;
@@ -645,6 +667,14 @@ interface DeliveryPort {
645
667
  deliver(recipientPubkey: string, blob: Uint8Array, options: DeliverOptions): Promise<DeliveryReceipt>;
646
668
  deliverBatch?(recipientPubkey: string, blobs: Uint8Array[], options: DeliverOptions): Promise<DeliveryReceipt[]>;
647
669
  incoming(sinceCursor?: string): AsyncIterable<IncomingDelivery>;
670
+ /**
671
+ * The syncEpoch of the most recent incoming() page — updated per page, null
672
+ * before the first. §5.7 restore self-detection: the mailbox page is the
673
+ * honest epoch source, so Receive voids its (cursor, epoch) continuity on a
674
+ * mismatch even when the wake socket missed a server restore. (Pinned by the
675
+ * S7 contract suite; wallet-api#119's S7 text carries the same sentence.)
676
+ */
677
+ incomingEpoch(): string | null;
648
678
  ack(deliveryId: string, disposition: 'claimed' | 'rejected', reason?: 'invalid' | 'not-owned' | 'storage-rejected' | 'other'): Promise<void>;
649
679
  onWake?(cb: () => void): () => void;
650
680
  }
@@ -739,6 +769,8 @@ declare const STORE_KEYS: {
739
769
  readonly settlingLinks: "settling";
740
770
  readonly streamCursor: (s: StreamName) => string;
741
771
  readonly epochLatch: "epoch-latch";
772
+ readonly suspectedSpent: "suspected-spent";
773
+ readonly knownSpends: "known-spends";
742
774
  };
743
775
 
744
776
  type RequestWireStatus = 'open' | 'paid' | 'declined' | 'expired';
@@ -826,6 +858,7 @@ declare class Requests implements PaymentsRequestsApi {
826
858
  private now;
827
859
  private ensureJournalLoaded;
828
860
  private mutateJournal;
861
+ /** Idempotent for a same-transferId re-write: committed only ratchets up, createdAt kept. */
829
862
  private writeLink;
830
863
  private clearLink;
831
864
  drainIncoming(): Promise<void>;
@@ -845,6 +878,18 @@ declare class Requests implements PaymentsRequestsApi {
845
878
  }>;
846
879
  pay(id: string): Promise<TransferResult>;
847
880
  private payInner;
881
+ /**
882
+ * THE settlement invariant (#441 + the failed-respond P1): a settling link is
883
+ * removed ONLY by (a) a CONFIRMED paid respond — a 2xx, or the 409
884
+ * already-resolved absorb — or (b) a proven clean pre-commit failure
885
+ * (revertPayable). Nothing else removes one: not a network error, not a 5xx,
886
+ * not a reload. Every path that binds a request to a transfer outcome funnels
887
+ * through here — pay()'s clean success (respond now), pay()'s
888
+ * possibly-committed throw (respond deferred), and reconcile's deferred arms
889
+ * — so a failed respond always leaves the link + 'settling' and the next
890
+ * reconcile pass (the committed-link override) retries the respond.
891
+ */
892
+ private settle;
848
893
  /** 'paid' respond leg: 409 = already resolved = idempotent success; other errors defer. */
849
894
  private respondPaid;
850
895
  decline(id: string): Promise<void>;
@@ -852,7 +897,9 @@ declare class Requests implements PaymentsRequestsApi {
852
897
  reconcile(outcomes: ResumeOutcomes): Promise<void>;
853
898
  private doReconcile;
854
899
  private reconcileUnaccounted;
900
+ /** Deferred paid: the ONE settlement path again — a failed respond keeps the link. */
855
901
  private resolvePaid;
902
+ /** Removal cause (b): a PROVEN clean outcome (pre-commit failure / server-aborted). */
856
903
  private revertPayable;
857
904
  }
858
905
 
@@ -899,6 +946,8 @@ interface PriceReader {
899
946
  getPrices(tokenNames: string[]): Promise<Map<string, PriceQuote>>;
900
947
  }
901
948
 
949
+ declare const ATTENTION_RESEED_REJECTED = "intent:reseed-rejected";
950
+
902
951
  /**
903
952
  * F13 makes `mint(params, { transferId, opIndex })` idempotent-recoverable — a
904
953
  * same-seed re-CALL recovers the existing certification via the E.2 probe
@@ -919,6 +968,28 @@ interface FacadeSession {
919
968
  start(): Promise<void>;
920
969
  stop(): Promise<void>;
921
970
  subscribeStream(stream: 'inventory' | 'mailbox' | 'payment_requests', handler: () => void): () => void;
971
+ /** §5.1: the latched server syncEpoch ('' before first server contact). */
972
+ currentEpoch(): string;
973
+ /**
974
+ * §5.1 restore hook — REQUIRED so an unwired restore protocol is a COMPILE
975
+ * ERROR: handlers run and are AWAITED on a syncEpoch change BEFORE any
976
+ * stream nudge resumes. The facade registers handleEpochChange here.
977
+ */
978
+ subscribeEpochChange(handler: (epoch: string) => Promise<void>): () => void;
979
+ /**
980
+ * Optional connection-status feed (same wiring pattern as the streams; the
981
+ * emission point is the session's existing `connection:status` transition).
982
+ * The facade's heartbeat resets its backoff on a 'connected' recovery.
983
+ */
984
+ subscribeStatus?(handler: (status: 'connected' | 'degraded' | 'offline') => void): () => void;
985
+ }
986
+ /**
987
+ * §5.1/§6 restore surface of the checkpoint store: re-POST the slot's cached
988
+ * encrypt-once ciphertext byte-identical after a server restore (insert-once,
989
+ * first-write-wins server-side). Returns false when no ciphertext is cached.
990
+ */
991
+ interface CheckpointReseeder {
992
+ reseedCheckpoint(transferId: string, opIndex: number): Promise<boolean>;
922
993
  }
923
994
  interface IntentWireLike {
924
995
  transferId: string;
@@ -937,7 +1008,8 @@ interface PaymentsFacadeDeps {
937
1008
  client: FacadeClient;
938
1009
  storagePort: StoragePort;
939
1010
  deliveryPort: DeliveryPort;
940
- checkpointStore: SplitCheckpointStore;
1011
+ /** Reseeder REQUIRED: the restore protocol re-POSTs cached ciphertexts (§5.1). */
1012
+ checkpointStore: SplitCheckpointStore & CheckpointReseeder;
941
1013
  /** Initial engine source; setEngine() swaps what FUTURE operations snapshot. */
942
1014
  engineRef: () => ITokenEngine;
943
1015
  kv: ScopedKV;
@@ -953,7 +1025,8 @@ interface PaymentsFacadeDeps {
953
1025
  ownPubkey: string;
954
1026
  ownNametag?: () => string | undefined;
955
1027
  requestMemo: RequestMemoCodec;
956
- syncEpoch?: () => string;
1028
+ /** REQUIRED (§5.1): reads the session's current epoch — never a default. */
1029
+ syncEpoch: () => string;
957
1030
  now?: () => number;
958
1031
  newId?: () => string;
959
1032
  workBudget?: number;
@@ -975,11 +1048,20 @@ declare class PaymentsFacade implements PaymentsV2 {
975
1048
  private readonly receiveLoop;
976
1049
  private readonly heldStates;
977
1050
  private readonly ownPubkeyBytes;
1051
+ private readonly restoreDeps;
978
1052
  readonly requests: Requests;
979
1053
  private currentEngine;
980
1054
  private readonly pendingOps;
981
1055
  private unsubscribers;
982
1056
  private started;
1057
+ private readonly heartbeat;
1058
+ private readonly converger;
1059
+ /** Resume single-flight (§7): ticks, start() and resumeNow() coalesce onto ONE pass. */
1060
+ private readonly resumeFlight;
1061
+ /** §5.1/§7: restore + convergence passes SERIALIZE here. */
1062
+ private readonly passChain;
1063
+ /** §7 ownership: ids with an in-process machine attempt — the pass never adopts one. */
1064
+ private readonly activeMoneyOps;
983
1065
  constructor(deps: PaymentsFacadeDeps);
984
1066
  start(): Promise<void>;
985
1067
  /** §7 same-address restart gate: resolves only after in-flight ops settle. */
@@ -994,26 +1076,36 @@ declare class PaymentsFacade implements PaymentsV2 {
994
1076
  before?: string;
995
1077
  limit?: number;
996
1078
  }): Promise<HistoryPage>;
1079
+ /** §4 pending-transfers UI surface — derived on read, never cached (convergence.ts). */
1080
+ pendingTransfers(): Promise<PendingTransfer[]>;
997
1081
  send(request: SendRequest): Promise<TransferResult>;
998
1082
  receive(): Promise<{
999
1083
  transfers: IncomingTransfer[];
1000
1084
  }>;
1001
1085
  mint(coinId: string, amount: bigint): Promise<MintResult>;
1086
+ resumeNow(): Promise<void>;
1087
+ /** §5.1 restore — awaited by the session latch BEFORE streams resume; never coalesced onto a pre-restore pass. */
1088
+ handleEpochChange(_newEpoch: string): Promise<void>;
1089
+ /** One single-flighted pass + its reschedule: concurrent callers coalesce. */
1090
+ private runConvergencePass;
1091
+ private convergeBody;
1092
+ private nowMs;
1093
+ /** The ONE place a send() outcome is shaped: success emits in finishSend, a
1094
+ * CLEAN rejection emits `transfer:updated{status:'failed'}` here (§4). */
1095
+ private sendOutcome;
1096
+ /** Only a CLEAN failure (nothing certified, classifyError 'other') is 'failed'.
1097
+ * Keep-open/partial/conflict outcomes are pending/converging — labelling them
1098
+ * 'failed' invites a dApp re-send, i.e. a double-pay (#631/#676). */
1099
+ private emitCleanFailure;
1002
1100
  private sendWithPolicy;
1003
1101
  /** §5.6 cross-network deposit trap: refused BEFORE any reserve/certification. */
1004
1102
  private requireSameNetworkRecipient;
1005
1103
  private runAttempt;
1006
- /**
1007
- * Partial outcome (#677/#690): the shortfall is already durable (written by
1008
- * the machine BEFORE complete). Accumulate the settled set, then re-plan ONLY
1009
- * the remainder under a NEW transferId — never the full amount.
1010
- */
1104
+ /** Partial (#677/#690): shortfall already durable (machine wrote it BEFORE
1105
+ * complete); accumulate settled, re-plan ONLY the remainder, NEW transferId. */
1011
1106
  private consumePartial;
1012
- /**
1013
- * One attempt's failure disposition: possibly-committed → rethrow UNWRAPPED;
1014
- * backstop still 'open' (committed>0) → converge via the same machine's
1015
- * resumeIntent; clean conflict with a demoted source → bounded full re-plan.
1016
- */
1107
+ /** Failure disposition: possibly-committed → rethrow UNWRAPPED; backstop
1108
+ * 'open' → converge via resumeIntent; clean demoted conflict → full re-plan. */
1017
1109
  private disposeFailedAttempt;
1018
1110
  /** #677/#690: ≥1 leg committed — the SAME machine, resumed in-process, converges it. */
1019
1111
  private convergePartial;
@@ -1033,28 +1125,28 @@ declare class PaymentsFacade implements PaymentsV2 {
1033
1125
  private refreshThenRelease;
1034
1126
  private accumulate;
1035
1127
  private finishSend;
1036
- /**
1037
- * With nothing delivered the error passes UNWRAPPED (identity + cause kept);
1038
- * after ≥1 delivered leg EVERY failure surfaces as PartialSendConflictError
1039
- * over the accumulated settled set — never bare, never a full-amount retry.
1040
- */
1128
+ /** Nothing delivered → UNWRAPPED; after ≥1 delivered leg every failure surfaces as PartialSendConflictError over the settled set. */
1041
1129
  private partialize;
1042
1130
  /** #441: possibly-committed errors must carry the transferId for the settling journal. */
1043
1131
  private stampTransferId;
1044
1132
  private softAbort;
1045
1133
  private mintInner;
1134
+ private mintUnderJournal;
1046
1135
  private finalizeMint;
1136
+ /** @returns how many journal entries were RESOLVED (cleared) — heartbeat progress. */
1047
1137
  private replayMints;
1048
1138
  private replayMint;
1049
1139
  private mintParams;
1050
1140
  private tokenInServerInventory;
1051
1141
  private engine;
1052
1142
  private newId;
1053
- private runResume;
1054
1143
  private seedHeldStates;
1055
1144
  private toUiToken;
1056
1145
  private track;
1057
1146
  private trackTail;
1058
1147
  }
1059
1148
 
1060
- export { ATTENTION_MINT_UNRESOLVED, type ApplyDeltaResult, type DeliverOptions, type DeliveryJournalEntry, type DeliveryPort, type DeliveryReceipt, type DeterministicMintCapable, type FacadeClient, type FacadeSession, type HistoryEntry, type HistoryPage, type IncomingDelivery, type IntentBackstopEntry, type IntentPayload, type InventoryAsset, type InventoryItem, type InventoryPage, MAX_RESELECT, type MintJournalEntry, type MintResult, type OpOutcome, type OutcomeClass, type PaymentRequestStatus, type PaymentRequestView, PaymentsFacade, type PaymentsFacadeDeps, type PaymentsRequestsApi, type PaymentsV2, type PaymentsV2Events, type PlannedOp, type RecipientInfo, STORE_KEYS, type ScopedKV, type SendRequest, type SettlingLink, type ShortfallEntry, type StoragePort, type StreamCursor, type StreamName, createScopedKV, supportsDeterministicMint };
1149
+ declare const HEARTBEAT_SEED_MS = 5000;
1150
+ declare const HEARTBEAT_CAP_MS = 120000;
1151
+
1152
+ export { ATTENTION_MINT_UNRESOLVED, ATTENTION_RESEED_REJECTED, type ApplyDeltaResult, type CheckpointReseeder, type DeliverOptions, type DeliveryJournalEntry, type DeliveryPort, type DeliveryReceipt, type DeterministicMintCapable, type FacadeClient, type FacadeSession, HEARTBEAT_CAP_MS, HEARTBEAT_SEED_MS, type HistoryEntry, type HistoryPage, type IncomingDelivery, type IntentBackstopEntry, type IntentPayload, type InventoryAsset, type InventoryItem, type InventoryPage, MAX_RESELECT, type MintJournalEntry, type MintResult, type OpOutcome, type OutcomeClass, type PaymentRequestStatus, type PaymentRequestView, PaymentsFacade, type PaymentsFacadeDeps, type PaymentsRequestsApi, type PaymentsV2, type PaymentsV2Events, type PendingTransfer, type PlannedOp, type RecipientInfo, STORE_KEYS, type ScopedKV, type SendRequest, type SettlingLink, type ShortfallEntry, type StoragePort, type StreamCursor, type StreamName, createScopedKV, supportsDeterministicMint };