@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.
@@ -349,8 +349,6 @@ interface WalletApiSessionDeps {
349
349
  kv: ScopedKV;
350
350
  webSocketFactory: WebSocketFactory;
351
351
  emitStatus: (status: ConnectionStatus) => void;
352
- /** Runs the syncEpoch re-seed protocol BEFORE any stream resumes. */
353
- onEpochChange: (epoch: string) => Promise<void>;
354
352
  timing?: Partial<SessionTiming>;
355
353
  timers?: SessionTimers;
356
354
  random?: () => number;
@@ -366,7 +364,9 @@ declare class WalletApiSession {
366
364
  private epochLatch;
367
365
  private reseedChain;
368
366
  private reseedsPending;
367
+ private readonly epochHandlers;
369
368
  private readonly handlers;
369
+ private readonly statusHandlers;
370
370
  private socket;
371
371
  private started;
372
372
  private stopped;
@@ -388,6 +388,13 @@ declare class WalletApiSession {
388
388
  private authenticate;
389
389
  private tryRefresh;
390
390
  private challengeSignIn;
391
+ /** §5.1: the latched server syncEpoch ('' before first server contact). */
392
+ currentEpoch(): string;
393
+ /**
394
+ * §5.1 restore hook (FacadeSession contract): handlers are AWAITED inside
395
+ * the reseed chain, BEFORE any stream nudge resumes.
396
+ */
397
+ subscribeEpochChange(handler: (epoch: string) => Promise<void>): () => void;
391
398
  noteEpoch(epoch: string): void;
392
399
  private runReseed;
393
400
  subscribeStream(stream: WakeStream, handler: () => void): () => void;
@@ -395,6 +402,8 @@ declare class WalletApiSession {
395
402
  private nudgeAllStreams;
396
403
  private startPullTimers;
397
404
  status(): ConnectionStatus;
405
+ /** Transition feed off the existing emitStatus point (FacadeSession.subscribeStatus). */
406
+ subscribeStatus(handler: (status: ConnectionStatus) => void): () => void;
398
407
  private emitStatus;
399
408
  private connect;
400
409
  private openSocket;
@@ -468,6 +477,14 @@ interface DeliveryPort {
468
477
  deliver(recipientPubkey: string, blob: Uint8Array, options: DeliverOptions): Promise<DeliveryReceipt>;
469
478
  deliverBatch?(recipientPubkey: string, blobs: Uint8Array[], options: DeliverOptions): Promise<DeliveryReceipt[]>;
470
479
  incoming(sinceCursor?: string): AsyncIterable<IncomingDelivery>;
480
+ /**
481
+ * The syncEpoch of the most recent incoming() page — updated per page, null
482
+ * before the first. §5.7 restore self-detection: the mailbox page is the
483
+ * honest epoch source, so Receive voids its (cursor, epoch) continuity on a
484
+ * mismatch even when the wake socket missed a server restore. (Pinned by the
485
+ * S7 contract suite; wallet-api#119's S7 text carries the same sentence.)
486
+ */
487
+ incomingEpoch(): string | null;
471
488
  ack(deliveryId: string, disposition: 'claimed' | 'rejected', reason?: 'invalid' | 'not-owned' | 'storage-rejected' | 'other'): Promise<void>;
472
489
  onWake?(cb: () => void): () => void;
473
490
  }
@@ -522,6 +539,7 @@ declare class WalletApiDeliveryPort implements DeliveryPort {
522
539
  private readonly wake;
523
540
  private deriveFn;
524
541
  private readonly seenChain;
542
+ private lastIncomingEpoch;
525
543
  constructor(config: WalletApiDeliveryPortConfig);
526
544
  bindDeliveryKeys(derive: (blob: Uint8Array) => Promise<{
527
545
  tokenId: string;
@@ -535,6 +553,8 @@ declare class WalletApiDeliveryPort implements DeliveryPort {
535
553
  private depositAll;
536
554
  private depositOne;
537
555
  incoming(sinceCursor?: string): AsyncIterable<IncomingDelivery>;
556
+ /** §5.7: the page-honest epoch — the restore-detection source Receive keys on. */
557
+ incomingEpoch(): string | null;
538
558
  private toIncoming;
539
559
  private openEnvelope;
540
560
  private fetchVerified;
@@ -560,6 +580,10 @@ declare class WalletApiSplitCheckpointStore implements SplitCheckpointStore {
560
580
  private cacheKey;
561
581
  private aad;
562
582
  put(transferId: string, opIndex: number, bytes: Uint8Array): Promise<Uint8Array>;
583
+ /** §5.1 restore (CheckpointReseeder): re-POST the cached ciphertext byte-identical; false = none cached. */
584
+ reseedCheckpoint(transferId: string, opIndex: number): Promise<boolean>;
585
+ /** The ONE signed progress-append path put() and reseedCheckpoint() share. */
586
+ private post;
563
587
  get(transferId: string, opIndex: number): Promise<Uint8Array | null>;
564
588
  private open;
565
589
  }
@@ -349,8 +349,6 @@ interface WalletApiSessionDeps {
349
349
  kv: ScopedKV;
350
350
  webSocketFactory: WebSocketFactory;
351
351
  emitStatus: (status: ConnectionStatus) => void;
352
- /** Runs the syncEpoch re-seed protocol BEFORE any stream resumes. */
353
- onEpochChange: (epoch: string) => Promise<void>;
354
352
  timing?: Partial<SessionTiming>;
355
353
  timers?: SessionTimers;
356
354
  random?: () => number;
@@ -366,7 +364,9 @@ declare class WalletApiSession {
366
364
  private epochLatch;
367
365
  private reseedChain;
368
366
  private reseedsPending;
367
+ private readonly epochHandlers;
369
368
  private readonly handlers;
369
+ private readonly statusHandlers;
370
370
  private socket;
371
371
  private started;
372
372
  private stopped;
@@ -388,6 +388,13 @@ declare class WalletApiSession {
388
388
  private authenticate;
389
389
  private tryRefresh;
390
390
  private challengeSignIn;
391
+ /** §5.1: the latched server syncEpoch ('' before first server contact). */
392
+ currentEpoch(): string;
393
+ /**
394
+ * §5.1 restore hook (FacadeSession contract): handlers are AWAITED inside
395
+ * the reseed chain, BEFORE any stream nudge resumes.
396
+ */
397
+ subscribeEpochChange(handler: (epoch: string) => Promise<void>): () => void;
391
398
  noteEpoch(epoch: string): void;
392
399
  private runReseed;
393
400
  subscribeStream(stream: WakeStream, handler: () => void): () => void;
@@ -395,6 +402,8 @@ declare class WalletApiSession {
395
402
  private nudgeAllStreams;
396
403
  private startPullTimers;
397
404
  status(): ConnectionStatus;
405
+ /** Transition feed off the existing emitStatus point (FacadeSession.subscribeStatus). */
406
+ subscribeStatus(handler: (status: ConnectionStatus) => void): () => void;
398
407
  private emitStatus;
399
408
  private connect;
400
409
  private openSocket;
@@ -468,6 +477,14 @@ interface DeliveryPort {
468
477
  deliver(recipientPubkey: string, blob: Uint8Array, options: DeliverOptions): Promise<DeliveryReceipt>;
469
478
  deliverBatch?(recipientPubkey: string, blobs: Uint8Array[], options: DeliverOptions): Promise<DeliveryReceipt[]>;
470
479
  incoming(sinceCursor?: string): AsyncIterable<IncomingDelivery>;
480
+ /**
481
+ * The syncEpoch of the most recent incoming() page — updated per page, null
482
+ * before the first. §5.7 restore self-detection: the mailbox page is the
483
+ * honest epoch source, so Receive voids its (cursor, epoch) continuity on a
484
+ * mismatch even when the wake socket missed a server restore. (Pinned by the
485
+ * S7 contract suite; wallet-api#119's S7 text carries the same sentence.)
486
+ */
487
+ incomingEpoch(): string | null;
471
488
  ack(deliveryId: string, disposition: 'claimed' | 'rejected', reason?: 'invalid' | 'not-owned' | 'storage-rejected' | 'other'): Promise<void>;
472
489
  onWake?(cb: () => void): () => void;
473
490
  }
@@ -522,6 +539,7 @@ declare class WalletApiDeliveryPort implements DeliveryPort {
522
539
  private readonly wake;
523
540
  private deriveFn;
524
541
  private readonly seenChain;
542
+ private lastIncomingEpoch;
525
543
  constructor(config: WalletApiDeliveryPortConfig);
526
544
  bindDeliveryKeys(derive: (blob: Uint8Array) => Promise<{
527
545
  tokenId: string;
@@ -535,6 +553,8 @@ declare class WalletApiDeliveryPort implements DeliveryPort {
535
553
  private depositAll;
536
554
  private depositOne;
537
555
  incoming(sinceCursor?: string): AsyncIterable<IncomingDelivery>;
556
+ /** §5.7: the page-honest epoch — the restore-detection source Receive keys on. */
557
+ incomingEpoch(): string | null;
538
558
  private toIncoming;
539
559
  private openEnvelope;
540
560
  private fetchVerified;
@@ -560,6 +580,10 @@ declare class WalletApiSplitCheckpointStore implements SplitCheckpointStore {
560
580
  private cacheKey;
561
581
  private aad;
562
582
  put(transferId: string, opIndex: number, bytes: Uint8Array): Promise<Uint8Array>;
583
+ /** §5.1 restore (CheckpointReseeder): re-POST the cached ciphertext byte-identical; false = none cached. */
584
+ reseedCheckpoint(transferId: string, opIndex: number): Promise<boolean>;
585
+ /** The ONE signed progress-append path put() and reseedCheckpoint() share. */
586
+ private post;
563
587
  get(transferId: string, opIndex: number): Promise<Uint8Array | null>;
564
588
  private open;
565
589
  }
@@ -694,7 +694,10 @@ var STORE_KEYS = {
694
694
  shortfalls: "shortfalls",
695
695
  settlingLinks: "settling",
696
696
  streamCursor: (s) => `cursor:${s}`,
697
- epochLatch: "epoch-latch"
697
+ epochLatch: "epoch-latch",
698
+ // §5.2 InventoryView durable overlays (#625/#679).
699
+ suspectedSpent: "suspected-spent",
700
+ knownSpends: "known-spends"
698
701
  };
699
702
 
700
703
  // node_modules/@noble/curves/utils.js
@@ -2718,7 +2721,9 @@ var WalletApiSession = class {
2718
2721
  epochLatch = null;
2719
2722
  reseedChain = Promise.resolve();
2720
2723
  reseedsPending = 0;
2724
+ epochHandlers = /* @__PURE__ */ new Set();
2721
2725
  handlers = /* @__PURE__ */ new Map();
2726
+ statusHandlers = /* @__PURE__ */ new Set();
2722
2727
  socket = null;
2723
2728
  started = false;
2724
2729
  stopped = false;
@@ -2819,6 +2824,18 @@ var WalletApiSession = class {
2819
2824
  await this.deps.kv.set(this.refreshKey, tokens.refreshToken);
2820
2825
  }
2821
2826
  // ── epoch latch (F12) ─────────────────────────────────────────────────────
2827
+ /** §5.1: the latched server syncEpoch ('' before first server contact). */
2828
+ currentEpoch() {
2829
+ return this.epochLatch ?? "";
2830
+ }
2831
+ /**
2832
+ * §5.1 restore hook (FacadeSession contract): handlers are AWAITED inside
2833
+ * the reseed chain, BEFORE any stream nudge resumes.
2834
+ */
2835
+ subscribeEpochChange(handler) {
2836
+ this.epochHandlers.add(handler);
2837
+ return () => this.epochHandlers.delete(handler);
2838
+ }
2822
2839
  noteEpoch(epoch) {
2823
2840
  if (this.epochLatch === epoch) return;
2824
2841
  const previous = this.epochLatch;
@@ -2832,7 +2849,7 @@ var WalletApiSession = class {
2832
2849
  }
2833
2850
  async runReseed(epoch, previous) {
2834
2851
  try {
2835
- await this.deps.onEpochChange(epoch);
2852
+ for (const handler of [...this.epochHandlers]) await handler(epoch);
2836
2853
  await this.deps.kv.set(STORE_KEYS.epochLatch, epoch);
2837
2854
  } catch {
2838
2855
  if (this.epochLatch === epoch) this.epochLatch = previous;
@@ -2869,10 +2886,16 @@ var WalletApiSession = class {
2869
2886
  status() {
2870
2887
  return this.lastStatus ?? "offline";
2871
2888
  }
2889
+ /** Transition feed off the existing emitStatus point (FacadeSession.subscribeStatus). */
2890
+ subscribeStatus(handler) {
2891
+ this.statusHandlers.add(handler);
2892
+ return () => this.statusHandlers.delete(handler);
2893
+ }
2872
2894
  emitStatus(status) {
2873
2895
  if (this.stopped || this.lastStatus === status) return;
2874
2896
  this.lastStatus = status;
2875
2897
  this.deps.emitStatus(status);
2898
+ for (const handler of this.statusHandlers) handler(status);
2876
2899
  }
2877
2900
  async connect() {
2878
2901
  if (this.stopped) return;
@@ -4034,6 +4057,7 @@ var WalletApiDeliveryPort = class {
4034
4057
  wake;
4035
4058
  deriveFn;
4036
4059
  seenChain = new SerialChain();
4060
+ lastIncomingEpoch = null;
4037
4061
  constructor(config) {
4038
4062
  this.client = config.client;
4039
4063
  this.kv = config.kv;
@@ -4128,6 +4152,7 @@ var WalletApiDeliveryPort = class {
4128
4152
  const seen = await this.readSeen();
4129
4153
  let page = await this.client.listMailbox(sinceCursor === void 0 ? 0 : Number(sinceCursor));
4130
4154
  for (; ; ) {
4155
+ this.lastIncomingEpoch = String(page.syncEpoch);
4131
4156
  for (const entry of page.entries) {
4132
4157
  if (entry.status !== "unclaimed") continue;
4133
4158
  if (seen.has(entry.entryId)) continue;
@@ -4137,6 +4162,10 @@ var WalletApiDeliveryPort = class {
4137
4162
  page = await this.client.listMailbox(page.cursor);
4138
4163
  }
4139
4164
  }
4165
+ /** §5.7: the page-honest epoch — the restore-detection source Receive keys on. */
4166
+ incomingEpoch() {
4167
+ return this.lastIncomingEpoch;
4168
+ }
4140
4169
  toIncoming(entry) {
4141
4170
  const bundle = this.openEnvelope(entry);
4142
4171
  return {
@@ -4292,7 +4321,7 @@ var WalletApiSplitCheckpointStore = class {
4292
4321
  return utf8(`${transferId}:${String(opIndex)}`);
4293
4322
  }
4294
4323
  async put(transferId, opIndex, bytes) {
4295
- const { client, kv, fieldKey, signProgress } = this.config;
4324
+ const { kv, fieldKey } = this.config;
4296
4325
  const aad = this.aad(transferId, opIndex);
4297
4326
  const cacheKey = this.cacheKey(transferId, opIndex);
4298
4327
  let envelope = await kv.get(cacheKey);
@@ -4300,10 +4329,21 @@ var WalletApiSplitCheckpointStore = class {
4300
4329
  envelope = encryptFieldBytes(fieldKey, bytes, aad);
4301
4330
  await kv.set(cacheKey, envelope);
4302
4331
  }
4303
- const signature = await signProgress(progressSignMessage(transferId, opIndex, envelope));
4304
- const record = await client.postProgress(transferId, opIndex, envelope, signature);
4332
+ const record = await this.post(transferId, opIndex, envelope);
4305
4333
  return this.open(record.payload, aad);
4306
4334
  }
4335
+ /** §5.1 restore (CheckpointReseeder): re-POST the cached ciphertext byte-identical; false = none cached. */
4336
+ async reseedCheckpoint(transferId, opIndex) {
4337
+ const envelope = await this.config.kv.get(this.cacheKey(transferId, opIndex));
4338
+ if (envelope === null) return false;
4339
+ await this.post(transferId, opIndex, envelope);
4340
+ return true;
4341
+ }
4342
+ /** The ONE signed progress-append path put() and reseedCheckpoint() share. */
4343
+ async post(transferId, opIndex, envelope) {
4344
+ const signature = await this.config.signProgress(progressSignMessage(transferId, opIndex, envelope));
4345
+ return this.config.client.postProgress(transferId, opIndex, envelope, signature);
4346
+ }
4307
4347
  async get(transferId, opIndex) {
4308
4348
  const records = await this.config.client.getProgress(transferId);
4309
4349
  const record = records.find((r) => r.opIndex === opIndex);