@unicitylabs/sphere-sdk 0.14.1-dev.1 → 0.14.1-dev.2

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.
Files changed (47) hide show
  1. package/dist/connect/index.cjs +20 -5
  2. package/dist/connect/index.cjs.map +1 -1
  3. package/dist/connect/index.d.cts +0 -1
  4. package/dist/connect/index.d.ts +0 -1
  5. package/dist/connect/index.js +20 -5
  6. package/dist/connect/index.js.map +1 -1
  7. package/dist/core/index.cjs +395 -213
  8. package/dist/core/index.cjs.map +1 -1
  9. package/dist/core/index.d.cts +10 -3
  10. package/dist/core/index.d.ts +10 -3
  11. package/dist/core/index.js +395 -213
  12. package/dist/core/index.js.map +1 -1
  13. package/dist/impl/browser/connect/index.cjs +10 -2
  14. package/dist/impl/browser/connect/index.cjs.map +1 -1
  15. package/dist/impl/browser/connect/index.d.cts +0 -1
  16. package/dist/impl/browser/connect/index.d.ts +0 -1
  17. package/dist/impl/browser/connect/index.js +10 -2
  18. package/dist/impl/browser/connect/index.js.map +1 -1
  19. package/dist/impl/nodejs/connect/index.cjs +9 -1
  20. package/dist/impl/nodejs/connect/index.cjs.map +1 -1
  21. package/dist/impl/nodejs/connect/index.d.cts +0 -1
  22. package/dist/impl/nodejs/connect/index.d.ts +0 -1
  23. package/dist/impl/nodejs/connect/index.js +9 -1
  24. package/dist/impl/nodejs/connect/index.js.map +1 -1
  25. package/dist/impl/nodejs/index.d.cts +8 -1
  26. package/dist/impl/nodejs/index.d.ts +8 -1
  27. package/dist/impl/shared/wallet-api/index.d.cts +8 -1
  28. package/dist/impl/shared/wallet-api/index.d.ts +8 -1
  29. package/dist/impl/wallet-api-v2/index.cjs +38 -5
  30. package/dist/impl/wallet-api-v2/index.cjs.map +1 -1
  31. package/dist/impl/wallet-api-v2/index.d.cts +23 -2
  32. package/dist/impl/wallet-api-v2/index.d.ts +23 -2
  33. package/dist/impl/wallet-api-v2/index.js +38 -5
  34. package/dist/impl/wallet-api-v2/index.js.map +1 -1
  35. package/dist/index.cjs +395 -213
  36. package/dist/index.cjs.map +1 -1
  37. package/dist/index.d.cts +10 -3
  38. package/dist/index.d.ts +10 -3
  39. package/dist/index.js +395 -213
  40. package/dist/index.js.map +1 -1
  41. package/dist/modules/payments-v2/index.cjs +340 -199
  42. package/dist/modules/payments-v2/index.cjs.map +1 -1
  43. package/dist/modules/payments-v2/index.d.cts +68 -19
  44. package/dist/modules/payments-v2/index.d.ts +68 -19
  45. package/dist/modules/payments-v2/index.js +339 -199
  46. package/dist/modules/payments-v2/index.js.map +1 -1
  47. package/package.json +1 -1
@@ -253,7 +253,8 @@ interface PaymentsV2Events {
253
253
  detail?: string;
254
254
  };
255
255
  'inventory:updated': Record<string, never>;
256
- 'history:updated': Record<string, never>;
256
+ /** The just-recorded entry, client-shaped (the same mapping history() serves). */
257
+ 'history:updated': HistoryEntry;
257
258
  'payment_request:incoming': PaymentRequestView;
258
259
  'payment_request:updated': {
259
260
  id: string;
@@ -326,6 +327,14 @@ interface DeliveryPort {
326
327
  deliver(recipientPubkey: string, blob: Uint8Array, options: DeliverOptions): Promise<DeliveryReceipt>;
327
328
  deliverBatch?(recipientPubkey: string, blobs: Uint8Array[], options: DeliverOptions): Promise<DeliveryReceipt[]>;
328
329
  incoming(sinceCursor?: string): AsyncIterable<IncomingDelivery>;
330
+ /**
331
+ * The syncEpoch of the most recent incoming() page — updated per page, null
332
+ * before the first. §5.7 restore self-detection: the mailbox page is the
333
+ * honest epoch source, so Receive voids its (cursor, epoch) continuity on a
334
+ * mismatch even when the wake socket missed a server restore. (Pinned by the
335
+ * S7 contract suite; wallet-api#119's S7 text carries the same sentence.)
336
+ */
337
+ incomingEpoch(): string | null;
329
338
  ack(deliveryId: string, disposition: 'claimed' | 'rejected', reason?: 'invalid' | 'not-owned' | 'storage-rejected' | 'other'): Promise<void>;
330
339
  onWake?(cb: () => void): () => void;
331
340
  }
@@ -468,6 +477,8 @@ declare const STORE_KEYS: {
468
477
  readonly settlingLinks: "settling";
469
478
  readonly streamCursor: (s: StreamName) => string;
470
479
  readonly epochLatch: "epoch-latch";
480
+ readonly suspectedSpent: "suspected-spent";
481
+ readonly knownSpends: "known-spends";
471
482
  };
472
483
 
473
484
  /**
@@ -841,6 +852,7 @@ declare class Requests implements PaymentsRequestsApi {
841
852
  private now;
842
853
  private ensureJournalLoaded;
843
854
  private mutateJournal;
855
+ /** Idempotent for a same-transferId re-write: committed only ratchets up, createdAt kept. */
844
856
  private writeLink;
845
857
  private clearLink;
846
858
  drainIncoming(): Promise<void>;
@@ -860,6 +872,18 @@ declare class Requests implements PaymentsRequestsApi {
860
872
  }>;
861
873
  pay(id: string): Promise<TransferResult>;
862
874
  private payInner;
875
+ /**
876
+ * THE settlement invariant (#441 + the failed-respond P1): a settling link is
877
+ * removed ONLY by (a) a CONFIRMED paid respond — a 2xx, or the 409
878
+ * already-resolved absorb — or (b) a proven clean pre-commit failure
879
+ * (revertPayable). Nothing else removes one: not a network error, not a 5xx,
880
+ * not a reload. Every path that binds a request to a transfer outcome funnels
881
+ * through here — pay()'s clean success (respond now), pay()'s
882
+ * possibly-committed throw (respond deferred), and reconcile's deferred arms
883
+ * — so a failed respond always leaves the link + 'settling' and the next
884
+ * reconcile pass (the committed-link override) retries the respond.
885
+ */
886
+ private settle;
863
887
  /** 'paid' respond leg: 409 = already resolved = idempotent success; other errors defer. */
864
888
  private respondPaid;
865
889
  decline(id: string): Promise<void>;
@@ -867,7 +891,9 @@ declare class Requests implements PaymentsRequestsApi {
867
891
  reconcile(outcomes: ResumeOutcomes): Promise<void>;
868
892
  private doReconcile;
869
893
  private reconcileUnaccounted;
894
+ /** Deferred paid: the ONE settlement path again — a failed respond keeps the link. */
870
895
  private resolvePaid;
896
+ /** Removal cause (b): a PROVEN clean outcome (pre-commit failure / server-aborted). */
871
897
  private revertPayable;
872
898
  }
873
899
 
@@ -914,6 +940,8 @@ interface PriceReader {
914
940
  getPrices(tokenNames: string[]): Promise<Map<string, PriceQuote>>;
915
941
  }
916
942
 
943
+ declare const ATTENTION_RESEED_REJECTED = "intent:reseed-rejected";
944
+
917
945
  /**
918
946
  * F13 makes `mint(params, { transferId, opIndex })` idempotent-recoverable — a
919
947
  * same-seed re-CALL recovers the existing certification via the E.2 probe
@@ -934,6 +962,14 @@ interface FacadeSession {
934
962
  start(): Promise<void>;
935
963
  stop(): Promise<void>;
936
964
  subscribeStream(stream: 'inventory' | 'mailbox' | 'payment_requests', handler: () => void): () => void;
965
+ /** §5.1: the latched server syncEpoch ('' before first server contact). */
966
+ currentEpoch(): string;
967
+ /**
968
+ * §5.1 restore hook — REQUIRED so an unwired restore protocol is a COMPILE
969
+ * ERROR: handlers run and are AWAITED on a syncEpoch change BEFORE any
970
+ * stream nudge resumes. The facade registers handleEpochChange here.
971
+ */
972
+ subscribeEpochChange(handler: (epoch: string) => Promise<void>): () => void;
937
973
  /**
938
974
  * Optional connection-status feed (same wiring pattern as the streams; the
939
975
  * emission point is the session's existing `connection:status` transition).
@@ -941,6 +977,14 @@ interface FacadeSession {
941
977
  */
942
978
  subscribeStatus?(handler: (status: 'connected' | 'degraded' | 'offline') => void): () => void;
943
979
  }
980
+ /**
981
+ * §5.1/§6 restore surface of the checkpoint store: re-POST the slot's cached
982
+ * encrypt-once ciphertext byte-identical after a server restore (insert-once,
983
+ * first-write-wins server-side). Returns false when no ciphertext is cached.
984
+ */
985
+ interface CheckpointReseeder {
986
+ reseedCheckpoint(transferId: string, opIndex: number): Promise<boolean>;
987
+ }
944
988
  interface IntentWireLike {
945
989
  transferId: string;
946
990
  payload: string;
@@ -958,7 +1002,8 @@ interface PaymentsFacadeDeps {
958
1002
  client: FacadeClient;
959
1003
  storagePort: StoragePort;
960
1004
  deliveryPort: DeliveryPort;
961
- checkpointStore: SplitCheckpointStore;
1005
+ /** Reseeder REQUIRED: the restore protocol re-POSTs cached ciphertexts (§5.1). */
1006
+ checkpointStore: SplitCheckpointStore & CheckpointReseeder;
962
1007
  /** Initial engine source; setEngine() swaps what FUTURE operations snapshot. */
963
1008
  engineRef: () => ITokenEngine;
964
1009
  kv: ScopedKV;
@@ -974,7 +1019,8 @@ interface PaymentsFacadeDeps {
974
1019
  ownPubkey: string;
975
1020
  ownNametag?: () => string | undefined;
976
1021
  requestMemo: RequestMemoCodec;
977
- syncEpoch?: () => string;
1022
+ /** REQUIRED (§5.1): reads the session's current epoch — never a default. */
1023
+ syncEpoch: () => string;
978
1024
  now?: () => number;
979
1025
  newId?: () => string;
980
1026
  workBudget?: number;
@@ -996,6 +1042,7 @@ declare class PaymentsFacade implements PaymentsV2 {
996
1042
  private readonly receiveLoop;
997
1043
  private readonly heldStates;
998
1044
  private readonly ownPubkeyBytes;
1045
+ private readonly restoreDeps;
999
1046
  readonly requests: Requests;
1000
1047
  private currentEngine;
1001
1048
  private readonly pendingOps;
@@ -1005,6 +1052,8 @@ declare class PaymentsFacade implements PaymentsV2 {
1005
1052
  private readonly converger;
1006
1053
  /** Resume single-flight (§7): ticks, start() and resumeNow() coalesce onto ONE pass. */
1007
1054
  private readonly resumeFlight;
1055
+ /** §5.1/§7: restore + convergence passes SERIALIZE here. */
1056
+ private readonly passChain;
1008
1057
  /** §7 ownership: ids with an in-process machine attempt — the pass never adopts one. */
1009
1058
  private readonly activeMoneyOps;
1010
1059
  constructor(deps: PaymentsFacadeDeps);
@@ -1029,24 +1078,28 @@ declare class PaymentsFacade implements PaymentsV2 {
1029
1078
  }>;
1030
1079
  mint(coinId: string, amount: bigint): Promise<MintResult>;
1031
1080
  resumeNow(): Promise<void>;
1081
+ /** §5.1 restore — awaited by the session latch BEFORE streams resume; never coalesced onto a pre-restore pass. */
1082
+ handleEpochChange(_newEpoch: string): Promise<void>;
1032
1083
  /** One single-flighted pass + its reschedule: concurrent callers coalesce. */
1033
1084
  private runConvergencePass;
1085
+ private convergeBody;
1034
1086
  private nowMs;
1087
+ /** The ONE place a send() outcome is shaped: success emits in finishSend, a
1088
+ * CLEAN rejection emits `transfer:updated{status:'failed'}` here (§4). */
1089
+ private sendOutcome;
1090
+ /** Only a CLEAN failure (nothing certified, classifyError 'other') is 'failed'.
1091
+ * Keep-open/partial/conflict outcomes are pending/converging — labelling them
1092
+ * 'failed' invites a dApp re-send, i.e. a double-pay (#631/#676). */
1093
+ private emitCleanFailure;
1035
1094
  private sendWithPolicy;
1036
1095
  /** §5.6 cross-network deposit trap: refused BEFORE any reserve/certification. */
1037
1096
  private requireSameNetworkRecipient;
1038
1097
  private runAttempt;
1039
- /**
1040
- * Partial outcome (#677/#690): the shortfall is already durable (written by
1041
- * the machine BEFORE complete). Accumulate the settled set, then re-plan ONLY
1042
- * the remainder under a NEW transferId — never the full amount.
1043
- */
1098
+ /** Partial (#677/#690): shortfall already durable (machine wrote it BEFORE
1099
+ * complete); accumulate settled, re-plan ONLY the remainder, NEW transferId. */
1044
1100
  private consumePartial;
1045
- /**
1046
- * One attempt's failure disposition: possibly-committed → rethrow UNWRAPPED;
1047
- * backstop still 'open' (committed>0) → converge via the same machine's
1048
- * resumeIntent; clean conflict with a demoted source → bounded full re-plan.
1049
- */
1101
+ /** Failure disposition: possibly-committed → rethrow UNWRAPPED; backstop
1102
+ * 'open' → converge via resumeIntent; clean demoted conflict → full re-plan. */
1050
1103
  private disposeFailedAttempt;
1051
1104
  /** #677/#690: ≥1 leg committed — the SAME machine, resumed in-process, converges it. */
1052
1105
  private convergePartial;
@@ -1066,11 +1119,7 @@ declare class PaymentsFacade implements PaymentsV2 {
1066
1119
  private refreshThenRelease;
1067
1120
  private accumulate;
1068
1121
  private finishSend;
1069
- /**
1070
- * With nothing delivered the error passes UNWRAPPED (identity + cause kept);
1071
- * after ≥1 delivered leg EVERY failure surfaces as PartialSendConflictError
1072
- * over the accumulated settled set — never bare, never a full-amount retry.
1073
- */
1122
+ /** Nothing delivered → UNWRAPPED; after ≥1 delivered leg every failure surfaces as PartialSendConflictError over the settled set. */
1074
1123
  private partialize;
1075
1124
  /** #441: possibly-committed errors must carry the transferId for the settling journal. */
1076
1125
  private stampTransferId;
@@ -1094,4 +1143,4 @@ declare class PaymentsFacade implements PaymentsV2 {
1094
1143
  declare const HEARTBEAT_SEED_MS = 5000;
1095
1144
  declare const HEARTBEAT_CAP_MS = 120000;
1096
1145
 
1097
- export { ATTENTION_MINT_UNRESOLVED, type ApplyDeltaResult, 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 };
1146
+ 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 };
@@ -253,7 +253,8 @@ interface PaymentsV2Events {
253
253
  detail?: string;
254
254
  };
255
255
  'inventory:updated': Record<string, never>;
256
- 'history:updated': Record<string, never>;
256
+ /** The just-recorded entry, client-shaped (the same mapping history() serves). */
257
+ 'history:updated': HistoryEntry;
257
258
  'payment_request:incoming': PaymentRequestView;
258
259
  'payment_request:updated': {
259
260
  id: string;
@@ -326,6 +327,14 @@ interface DeliveryPort {
326
327
  deliver(recipientPubkey: string, blob: Uint8Array, options: DeliverOptions): Promise<DeliveryReceipt>;
327
328
  deliverBatch?(recipientPubkey: string, blobs: Uint8Array[], options: DeliverOptions): Promise<DeliveryReceipt[]>;
328
329
  incoming(sinceCursor?: string): AsyncIterable<IncomingDelivery>;
330
+ /**
331
+ * The syncEpoch of the most recent incoming() page — updated per page, null
332
+ * before the first. §5.7 restore self-detection: the mailbox page is the
333
+ * honest epoch source, so Receive voids its (cursor, epoch) continuity on a
334
+ * mismatch even when the wake socket missed a server restore. (Pinned by the
335
+ * S7 contract suite; wallet-api#119's S7 text carries the same sentence.)
336
+ */
337
+ incomingEpoch(): string | null;
329
338
  ack(deliveryId: string, disposition: 'claimed' | 'rejected', reason?: 'invalid' | 'not-owned' | 'storage-rejected' | 'other'): Promise<void>;
330
339
  onWake?(cb: () => void): () => void;
331
340
  }
@@ -468,6 +477,8 @@ declare const STORE_KEYS: {
468
477
  readonly settlingLinks: "settling";
469
478
  readonly streamCursor: (s: StreamName) => string;
470
479
  readonly epochLatch: "epoch-latch";
480
+ readonly suspectedSpent: "suspected-spent";
481
+ readonly knownSpends: "known-spends";
471
482
  };
472
483
 
473
484
  /**
@@ -841,6 +852,7 @@ declare class Requests implements PaymentsRequestsApi {
841
852
  private now;
842
853
  private ensureJournalLoaded;
843
854
  private mutateJournal;
855
+ /** Idempotent for a same-transferId re-write: committed only ratchets up, createdAt kept. */
844
856
  private writeLink;
845
857
  private clearLink;
846
858
  drainIncoming(): Promise<void>;
@@ -860,6 +872,18 @@ declare class Requests implements PaymentsRequestsApi {
860
872
  }>;
861
873
  pay(id: string): Promise<TransferResult>;
862
874
  private payInner;
875
+ /**
876
+ * THE settlement invariant (#441 + the failed-respond P1): a settling link is
877
+ * removed ONLY by (a) a CONFIRMED paid respond — a 2xx, or the 409
878
+ * already-resolved absorb — or (b) a proven clean pre-commit failure
879
+ * (revertPayable). Nothing else removes one: not a network error, not a 5xx,
880
+ * not a reload. Every path that binds a request to a transfer outcome funnels
881
+ * through here — pay()'s clean success (respond now), pay()'s
882
+ * possibly-committed throw (respond deferred), and reconcile's deferred arms
883
+ * — so a failed respond always leaves the link + 'settling' and the next
884
+ * reconcile pass (the committed-link override) retries the respond.
885
+ */
886
+ private settle;
863
887
  /** 'paid' respond leg: 409 = already resolved = idempotent success; other errors defer. */
864
888
  private respondPaid;
865
889
  decline(id: string): Promise<void>;
@@ -867,7 +891,9 @@ declare class Requests implements PaymentsRequestsApi {
867
891
  reconcile(outcomes: ResumeOutcomes): Promise<void>;
868
892
  private doReconcile;
869
893
  private reconcileUnaccounted;
894
+ /** Deferred paid: the ONE settlement path again — a failed respond keeps the link. */
870
895
  private resolvePaid;
896
+ /** Removal cause (b): a PROVEN clean outcome (pre-commit failure / server-aborted). */
871
897
  private revertPayable;
872
898
  }
873
899
 
@@ -914,6 +940,8 @@ interface PriceReader {
914
940
  getPrices(tokenNames: string[]): Promise<Map<string, PriceQuote>>;
915
941
  }
916
942
 
943
+ declare const ATTENTION_RESEED_REJECTED = "intent:reseed-rejected";
944
+
917
945
  /**
918
946
  * F13 makes `mint(params, { transferId, opIndex })` idempotent-recoverable — a
919
947
  * same-seed re-CALL recovers the existing certification via the E.2 probe
@@ -934,6 +962,14 @@ interface FacadeSession {
934
962
  start(): Promise<void>;
935
963
  stop(): Promise<void>;
936
964
  subscribeStream(stream: 'inventory' | 'mailbox' | 'payment_requests', handler: () => void): () => void;
965
+ /** §5.1: the latched server syncEpoch ('' before first server contact). */
966
+ currentEpoch(): string;
967
+ /**
968
+ * §5.1 restore hook — REQUIRED so an unwired restore protocol is a COMPILE
969
+ * ERROR: handlers run and are AWAITED on a syncEpoch change BEFORE any
970
+ * stream nudge resumes. The facade registers handleEpochChange here.
971
+ */
972
+ subscribeEpochChange(handler: (epoch: string) => Promise<void>): () => void;
937
973
  /**
938
974
  * Optional connection-status feed (same wiring pattern as the streams; the
939
975
  * emission point is the session's existing `connection:status` transition).
@@ -941,6 +977,14 @@ interface FacadeSession {
941
977
  */
942
978
  subscribeStatus?(handler: (status: 'connected' | 'degraded' | 'offline') => void): () => void;
943
979
  }
980
+ /**
981
+ * §5.1/§6 restore surface of the checkpoint store: re-POST the slot's cached
982
+ * encrypt-once ciphertext byte-identical after a server restore (insert-once,
983
+ * first-write-wins server-side). Returns false when no ciphertext is cached.
984
+ */
985
+ interface CheckpointReseeder {
986
+ reseedCheckpoint(transferId: string, opIndex: number): Promise<boolean>;
987
+ }
944
988
  interface IntentWireLike {
945
989
  transferId: string;
946
990
  payload: string;
@@ -958,7 +1002,8 @@ interface PaymentsFacadeDeps {
958
1002
  client: FacadeClient;
959
1003
  storagePort: StoragePort;
960
1004
  deliveryPort: DeliveryPort;
961
- checkpointStore: SplitCheckpointStore;
1005
+ /** Reseeder REQUIRED: the restore protocol re-POSTs cached ciphertexts (§5.1). */
1006
+ checkpointStore: SplitCheckpointStore & CheckpointReseeder;
962
1007
  /** Initial engine source; setEngine() swaps what FUTURE operations snapshot. */
963
1008
  engineRef: () => ITokenEngine;
964
1009
  kv: ScopedKV;
@@ -974,7 +1019,8 @@ interface PaymentsFacadeDeps {
974
1019
  ownPubkey: string;
975
1020
  ownNametag?: () => string | undefined;
976
1021
  requestMemo: RequestMemoCodec;
977
- syncEpoch?: () => string;
1022
+ /** REQUIRED (§5.1): reads the session's current epoch — never a default. */
1023
+ syncEpoch: () => string;
978
1024
  now?: () => number;
979
1025
  newId?: () => string;
980
1026
  workBudget?: number;
@@ -996,6 +1042,7 @@ declare class PaymentsFacade implements PaymentsV2 {
996
1042
  private readonly receiveLoop;
997
1043
  private readonly heldStates;
998
1044
  private readonly ownPubkeyBytes;
1045
+ private readonly restoreDeps;
999
1046
  readonly requests: Requests;
1000
1047
  private currentEngine;
1001
1048
  private readonly pendingOps;
@@ -1005,6 +1052,8 @@ declare class PaymentsFacade implements PaymentsV2 {
1005
1052
  private readonly converger;
1006
1053
  /** Resume single-flight (§7): ticks, start() and resumeNow() coalesce onto ONE pass. */
1007
1054
  private readonly resumeFlight;
1055
+ /** §5.1/§7: restore + convergence passes SERIALIZE here. */
1056
+ private readonly passChain;
1008
1057
  /** §7 ownership: ids with an in-process machine attempt — the pass never adopts one. */
1009
1058
  private readonly activeMoneyOps;
1010
1059
  constructor(deps: PaymentsFacadeDeps);
@@ -1029,24 +1078,28 @@ declare class PaymentsFacade implements PaymentsV2 {
1029
1078
  }>;
1030
1079
  mint(coinId: string, amount: bigint): Promise<MintResult>;
1031
1080
  resumeNow(): Promise<void>;
1081
+ /** §5.1 restore — awaited by the session latch BEFORE streams resume; never coalesced onto a pre-restore pass. */
1082
+ handleEpochChange(_newEpoch: string): Promise<void>;
1032
1083
  /** One single-flighted pass + its reschedule: concurrent callers coalesce. */
1033
1084
  private runConvergencePass;
1085
+ private convergeBody;
1034
1086
  private nowMs;
1087
+ /** The ONE place a send() outcome is shaped: success emits in finishSend, a
1088
+ * CLEAN rejection emits `transfer:updated{status:'failed'}` here (§4). */
1089
+ private sendOutcome;
1090
+ /** Only a CLEAN failure (nothing certified, classifyError 'other') is 'failed'.
1091
+ * Keep-open/partial/conflict outcomes are pending/converging — labelling them
1092
+ * 'failed' invites a dApp re-send, i.e. a double-pay (#631/#676). */
1093
+ private emitCleanFailure;
1035
1094
  private sendWithPolicy;
1036
1095
  /** §5.6 cross-network deposit trap: refused BEFORE any reserve/certification. */
1037
1096
  private requireSameNetworkRecipient;
1038
1097
  private runAttempt;
1039
- /**
1040
- * Partial outcome (#677/#690): the shortfall is already durable (written by
1041
- * the machine BEFORE complete). Accumulate the settled set, then re-plan ONLY
1042
- * the remainder under a NEW transferId — never the full amount.
1043
- */
1098
+ /** Partial (#677/#690): shortfall already durable (machine wrote it BEFORE
1099
+ * complete); accumulate settled, re-plan ONLY the remainder, NEW transferId. */
1044
1100
  private consumePartial;
1045
- /**
1046
- * One attempt's failure disposition: possibly-committed → rethrow UNWRAPPED;
1047
- * backstop still 'open' (committed>0) → converge via the same machine's
1048
- * resumeIntent; clean conflict with a demoted source → bounded full re-plan.
1049
- */
1101
+ /** Failure disposition: possibly-committed → rethrow UNWRAPPED; backstop
1102
+ * 'open' → converge via resumeIntent; clean demoted conflict → full re-plan. */
1050
1103
  private disposeFailedAttempt;
1051
1104
  /** #677/#690: ≥1 leg committed — the SAME machine, resumed in-process, converges it. */
1052
1105
  private convergePartial;
@@ -1066,11 +1119,7 @@ declare class PaymentsFacade implements PaymentsV2 {
1066
1119
  private refreshThenRelease;
1067
1120
  private accumulate;
1068
1121
  private finishSend;
1069
- /**
1070
- * With nothing delivered the error passes UNWRAPPED (identity + cause kept);
1071
- * after ≥1 delivered leg EVERY failure surfaces as PartialSendConflictError
1072
- * over the accumulated settled set — never bare, never a full-amount retry.
1073
- */
1122
+ /** Nothing delivered → UNWRAPPED; after ≥1 delivered leg every failure surfaces as PartialSendConflictError over the settled set. */
1074
1123
  private partialize;
1075
1124
  /** #441: possibly-committed errors must carry the transferId for the settling journal. */
1076
1125
  private stampTransferId;
@@ -1094,4 +1143,4 @@ declare class PaymentsFacade implements PaymentsV2 {
1094
1143
  declare const HEARTBEAT_SEED_MS = 5000;
1095
1144
  declare const HEARTBEAT_CAP_MS = 120000;
1096
1145
 
1097
- export { ATTENTION_MINT_UNRESOLVED, type ApplyDeltaResult, 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 };
1146
+ 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 };