@unicitylabs/sphere-sdk 0.14.0-dev.6 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,6 +364,7 @@ declare class WalletApiSession {
366
364
  private epochLatch;
367
365
  private reseedChain;
368
366
  private reseedsPending;
367
+ private readonly epochHandlers;
369
368
  private readonly handlers;
370
369
  private readonly statusHandlers;
371
370
  private socket;
@@ -389,6 +388,13 @@ declare class WalletApiSession {
389
388
  private authenticate;
390
389
  private tryRefresh;
391
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;
392
398
  noteEpoch(epoch: string): void;
393
399
  private runReseed;
394
400
  subscribeStream(stream: WakeStream, handler: () => void): () => void;
@@ -471,6 +477,14 @@ interface DeliveryPort {
471
477
  deliver(recipientPubkey: string, blob: Uint8Array, options: DeliverOptions): Promise<DeliveryReceipt>;
472
478
  deliverBatch?(recipientPubkey: string, blobs: Uint8Array[], options: DeliverOptions): Promise<DeliveryReceipt[]>;
473
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;
474
488
  ack(deliveryId: string, disposition: 'claimed' | 'rejected', reason?: 'invalid' | 'not-owned' | 'storage-rejected' | 'other'): Promise<void>;
475
489
  onWake?(cb: () => void): () => void;
476
490
  }
@@ -525,6 +539,7 @@ declare class WalletApiDeliveryPort implements DeliveryPort {
525
539
  private readonly wake;
526
540
  private deriveFn;
527
541
  private readonly seenChain;
542
+ private lastIncomingEpoch;
528
543
  constructor(config: WalletApiDeliveryPortConfig);
529
544
  bindDeliveryKeys(derive: (blob: Uint8Array) => Promise<{
530
545
  tokenId: string;
@@ -538,6 +553,8 @@ declare class WalletApiDeliveryPort implements DeliveryPort {
538
553
  private depositAll;
539
554
  private depositOne;
540
555
  incoming(sinceCursor?: string): AsyncIterable<IncomingDelivery>;
556
+ /** §5.7: the page-honest epoch — the restore-detection source Receive keys on. */
557
+ incomingEpoch(): string | null;
541
558
  private toIncoming;
542
559
  private openEnvelope;
543
560
  private fetchVerified;
@@ -563,6 +580,10 @@ declare class WalletApiSplitCheckpointStore implements SplitCheckpointStore {
563
580
  private cacheKey;
564
581
  private aad;
565
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;
566
587
  get(transferId: string, opIndex: number): Promise<Uint8Array | null>;
567
588
  private open;
568
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,6 +364,7 @@ declare class WalletApiSession {
366
364
  private epochLatch;
367
365
  private reseedChain;
368
366
  private reseedsPending;
367
+ private readonly epochHandlers;
369
368
  private readonly handlers;
370
369
  private readonly statusHandlers;
371
370
  private socket;
@@ -389,6 +388,13 @@ declare class WalletApiSession {
389
388
  private authenticate;
390
389
  private tryRefresh;
391
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;
392
398
  noteEpoch(epoch: string): void;
393
399
  private runReseed;
394
400
  subscribeStream(stream: WakeStream, handler: () => void): () => void;
@@ -471,6 +477,14 @@ interface DeliveryPort {
471
477
  deliver(recipientPubkey: string, blob: Uint8Array, options: DeliverOptions): Promise<DeliveryReceipt>;
472
478
  deliverBatch?(recipientPubkey: string, blobs: Uint8Array[], options: DeliverOptions): Promise<DeliveryReceipt[]>;
473
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;
474
488
  ack(deliveryId: string, disposition: 'claimed' | 'rejected', reason?: 'invalid' | 'not-owned' | 'storage-rejected' | 'other'): Promise<void>;
475
489
  onWake?(cb: () => void): () => void;
476
490
  }
@@ -525,6 +539,7 @@ declare class WalletApiDeliveryPort implements DeliveryPort {
525
539
  private readonly wake;
526
540
  private deriveFn;
527
541
  private readonly seenChain;
542
+ private lastIncomingEpoch;
528
543
  constructor(config: WalletApiDeliveryPortConfig);
529
544
  bindDeliveryKeys(derive: (blob: Uint8Array) => Promise<{
530
545
  tokenId: string;
@@ -538,6 +553,8 @@ declare class WalletApiDeliveryPort implements DeliveryPort {
538
553
  private depositAll;
539
554
  private depositOne;
540
555
  incoming(sinceCursor?: string): AsyncIterable<IncomingDelivery>;
556
+ /** §5.7: the page-honest epoch — the restore-detection source Receive keys on. */
557
+ incomingEpoch(): string | null;
541
558
  private toIncoming;
542
559
  private openEnvelope;
543
560
  private fetchVerified;
@@ -563,6 +580,10 @@ declare class WalletApiSplitCheckpointStore implements SplitCheckpointStore {
563
580
  private cacheKey;
564
581
  private aad;
565
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;
566
587
  get(transferId: string, opIndex: number): Promise<Uint8Array | null>;
567
588
  private open;
568
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,6 +2721,7 @@ 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();
2722
2726
  statusHandlers = /* @__PURE__ */ new Set();
2723
2727
  socket = null;
@@ -2820,6 +2824,18 @@ var WalletApiSession = class {
2820
2824
  await this.deps.kv.set(this.refreshKey, tokens.refreshToken);
2821
2825
  }
2822
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
+ }
2823
2839
  noteEpoch(epoch) {
2824
2840
  if (this.epochLatch === epoch) return;
2825
2841
  const previous = this.epochLatch;
@@ -2833,7 +2849,7 @@ var WalletApiSession = class {
2833
2849
  }
2834
2850
  async runReseed(epoch, previous) {
2835
2851
  try {
2836
- await this.deps.onEpochChange(epoch);
2852
+ for (const handler of [...this.epochHandlers]) await handler(epoch);
2837
2853
  await this.deps.kv.set(STORE_KEYS.epochLatch, epoch);
2838
2854
  } catch {
2839
2855
  if (this.epochLatch === epoch) this.epochLatch = previous;
@@ -4041,6 +4057,7 @@ var WalletApiDeliveryPort = class {
4041
4057
  wake;
4042
4058
  deriveFn;
4043
4059
  seenChain = new SerialChain();
4060
+ lastIncomingEpoch = null;
4044
4061
  constructor(config) {
4045
4062
  this.client = config.client;
4046
4063
  this.kv = config.kv;
@@ -4135,6 +4152,7 @@ var WalletApiDeliveryPort = class {
4135
4152
  const seen = await this.readSeen();
4136
4153
  let page = await this.client.listMailbox(sinceCursor === void 0 ? 0 : Number(sinceCursor));
4137
4154
  for (; ; ) {
4155
+ this.lastIncomingEpoch = String(page.syncEpoch);
4138
4156
  for (const entry of page.entries) {
4139
4157
  if (entry.status !== "unclaimed") continue;
4140
4158
  if (seen.has(entry.entryId)) continue;
@@ -4144,6 +4162,10 @@ var WalletApiDeliveryPort = class {
4144
4162
  page = await this.client.listMailbox(page.cursor);
4145
4163
  }
4146
4164
  }
4165
+ /** §5.7: the page-honest epoch — the restore-detection source Receive keys on. */
4166
+ incomingEpoch() {
4167
+ return this.lastIncomingEpoch;
4168
+ }
4147
4169
  toIncoming(entry) {
4148
4170
  const bundle = this.openEnvelope(entry);
4149
4171
  return {
@@ -4299,7 +4321,7 @@ var WalletApiSplitCheckpointStore = class {
4299
4321
  return utf8(`${transferId}:${String(opIndex)}`);
4300
4322
  }
4301
4323
  async put(transferId, opIndex, bytes) {
4302
- const { client, kv, fieldKey, signProgress } = this.config;
4324
+ const { kv, fieldKey } = this.config;
4303
4325
  const aad = this.aad(transferId, opIndex);
4304
4326
  const cacheKey = this.cacheKey(transferId, opIndex);
4305
4327
  let envelope = await kv.get(cacheKey);
@@ -4307,10 +4329,21 @@ var WalletApiSplitCheckpointStore = class {
4307
4329
  envelope = encryptFieldBytes(fieldKey, bytes, aad);
4308
4330
  await kv.set(cacheKey, envelope);
4309
4331
  }
4310
- const signature = await signProgress(progressSignMessage(transferId, opIndex, envelope));
4311
- const record = await client.postProgress(transferId, opIndex, envelope, signature);
4332
+ const record = await this.post(transferId, opIndex, envelope);
4312
4333
  return this.open(record.payload, aad);
4313
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
+ }
4314
4347
  async get(transferId, opIndex) {
4315
4348
  const records = await this.config.client.getProgress(transferId);
4316
4349
  const record = records.find((r) => r.opIndex === opIndex);