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

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.d.cts CHANGED
@@ -7432,6 +7432,25 @@ interface PaymentsRequestsApi {
7432
7432
  decline(id: string): Promise<void>;
7433
7433
  dismissProcessed(): void;
7434
7434
  }
7435
+ /**
7436
+ * A pending-transfers row, derived ON READ from the §6 stores (intent backstop
7437
+ * + delivery journal + shortfalls) — never a cached mirror. kind 'shortfall' =
7438
+ * a completed partial (#690) whose `amount` is the remainder still owed;
7439
+ * legs.certified counts journaled legs (certified, delivery still owed).
7440
+ */
7441
+ interface PendingTransfer {
7442
+ transferId: string;
7443
+ kind: 'open' | 'shortfall';
7444
+ recipient: string;
7445
+ coinId: string;
7446
+ amount: string;
7447
+ legs: {
7448
+ certified: number;
7449
+ total: number;
7450
+ };
7451
+ deliveryPending: boolean;
7452
+ createdAt: number;
7453
+ }
7435
7454
  interface PaymentsV2 {
7436
7455
  assets(coinId?: string): Promise<Asset[]>;
7437
7456
  tokens(filter?: {
@@ -7446,6 +7465,8 @@ interface PaymentsV2 {
7446
7465
  receive(): Promise<{
7447
7466
  transfers: IncomingTransfer[];
7448
7467
  }>;
7468
+ pendingTransfers(): Promise<PendingTransfer[]>;
7469
+ resumeNow(): Promise<void>;
7449
7470
  readonly requests: PaymentsRequestsApi;
7450
7471
  }
7451
7472
 
package/dist/index.d.ts CHANGED
@@ -7432,6 +7432,25 @@ interface PaymentsRequestsApi {
7432
7432
  decline(id: string): Promise<void>;
7433
7433
  dismissProcessed(): void;
7434
7434
  }
7435
+ /**
7436
+ * A pending-transfers row, derived ON READ from the §6 stores (intent backstop
7437
+ * + delivery journal + shortfalls) — never a cached mirror. kind 'shortfall' =
7438
+ * a completed partial (#690) whose `amount` is the remainder still owed;
7439
+ * legs.certified counts journaled legs (certified, delivery still owed).
7440
+ */
7441
+ interface PendingTransfer {
7442
+ transferId: string;
7443
+ kind: 'open' | 'shortfall';
7444
+ recipient: string;
7445
+ coinId: string;
7446
+ amount: string;
7447
+ legs: {
7448
+ certified: number;
7449
+ total: number;
7450
+ };
7451
+ deliveryPending: boolean;
7452
+ createdAt: number;
7453
+ }
7435
7454
  interface PaymentsV2 {
7436
7455
  assets(coinId?: string): Promise<Asset[]>;
7437
7456
  tokens(filter?: {
@@ -7446,6 +7465,8 @@ interface PaymentsV2 {
7446
7465
  receive(): Promise<{
7447
7466
  transfers: IncomingTransfer[];
7448
7467
  }>;
7468
+ pendingTransfers(): Promise<PendingTransfer[]>;
7469
+ resumeNow(): Promise<void>;
7449
7470
  readonly requests: PaymentsRequestsApi;
7450
7471
  }
7451
7472
 
package/dist/index.js CHANGED
@@ -27486,6 +27486,10 @@ function isValidationReject(err) {
27486
27486
  if (e === null) return false;
27487
27487
  return e.status === 422 || e.statusCode === 422 || e.code === "VALIDATION" || e.code === "VALIDATION_FAILED";
27488
27488
  }
27489
+ function retryAfterMsOf(err) {
27490
+ const retryAfter = err !== null && typeof err === "object" ? err.retryAfter : void 0;
27491
+ return typeof retryAfter === "number" && Number.isFinite(retryAfter) && retryAfter >= 0 ? retryAfter : null;
27492
+ }
27489
27493
  var chainRegistry = /* @__PURE__ */ new WeakMap();
27490
27494
  var replayRegistry = /* @__PURE__ */ new WeakMap();
27491
27495
  function registryFor(registry, kv, storeKey, make) {
@@ -28123,6 +28127,217 @@ function validatePayload(raw) {
28123
28127
  return p;
28124
28128
  }
28125
28129
 
28130
+ // modules/payments-v2/convergence.ts
28131
+ var HEARTBEAT_SEED_MS = 5e3;
28132
+ var HEARTBEAT_CAP_MS = 12e4;
28133
+ var ConvergenceHeartbeat = class {
28134
+ constructor(deps) {
28135
+ this.deps = deps;
28136
+ }
28137
+ timer = null;
28138
+ delayMs = HEARTBEAT_SEED_MS;
28139
+ notBefore = 0;
28140
+ running = false;
28141
+ start() {
28142
+ this.running = true;
28143
+ this.delayMs = HEARTBEAT_SEED_MS;
28144
+ this.notBefore = 0;
28145
+ }
28146
+ /** §7 quiescence unchanged: a mid-backoff stop() cancels cleanly. */
28147
+ stop() {
28148
+ this.running = false;
28149
+ this.clearTimer();
28150
+ }
28151
+ /** Reset to seed on progress, ×2 to the cap otherwise; idle stops rescheduling. */
28152
+ settle(outcome, pending) {
28153
+ if (outcome.retryAfterMs !== null) {
28154
+ this.notBefore = Math.max(this.notBefore, this.deps.now() + outcome.retryAfterMs);
28155
+ }
28156
+ if (!pending) {
28157
+ this.delayMs = HEARTBEAT_SEED_MS;
28158
+ return;
28159
+ }
28160
+ const delay = outcome.progress ? HEARTBEAT_SEED_MS : this.delayMs;
28161
+ this.delayMs = Math.min(delay * 2, HEARTBEAT_CAP_MS);
28162
+ this.schedule(delay);
28163
+ }
28164
+ /** New pending work (keep-open park / journaled delivery / mint hold): prompt retry. */
28165
+ arm() {
28166
+ if (!this.running) return;
28167
+ this.delayMs = Math.min(HEARTBEAT_SEED_MS * 2, HEARTBEAT_CAP_MS);
28168
+ this.schedule(HEARTBEAT_SEED_MS);
28169
+ }
28170
+ /** connection:status recovery: the backoff resets; a pending tick re-fires at the seed. */
28171
+ recovered() {
28172
+ this.delayMs = HEARTBEAT_SEED_MS;
28173
+ if (this.timer !== null) this.arm();
28174
+ }
28175
+ schedule(delayMs) {
28176
+ this.clearTimer();
28177
+ if (!this.running) return;
28178
+ const floored = Math.max(delayMs, this.notBefore - this.deps.now());
28179
+ this.timer = setTimeout(() => {
28180
+ this.timer = null;
28181
+ this.deps.onTick();
28182
+ }, floored);
28183
+ }
28184
+ clearTimer() {
28185
+ if (this.timer !== null) clearTimeout(this.timer);
28186
+ this.timer = null;
28187
+ }
28188
+ };
28189
+ var Converger = class {
28190
+ constructor(deps) {
28191
+ this.deps = deps;
28192
+ }
28193
+ /** Resume open intents, then drain the delivery + mint journals (all idempotent). */
28194
+ async convergeOnce() {
28195
+ let progress = false;
28196
+ let retryAfterMs = null;
28197
+ try {
28198
+ progress = await this.resumePass();
28199
+ } catch (err) {
28200
+ retryAfterMs = retryAfterMsOf(err);
28201
+ }
28202
+ try {
28203
+ const stats = await this.deps.stores.deliveryJournal.replay(this.replayDeps());
28204
+ progress = progress || stats.delivered > 0;
28205
+ } catch (err) {
28206
+ retryAfterMs ??= retryAfterMsOf(err);
28207
+ }
28208
+ try {
28209
+ progress = await this.deps.replayMints() > 0 || progress;
28210
+ } catch (err) {
28211
+ retryAfterMs ??= retryAfterMsOf(err);
28212
+ }
28213
+ return { progress, retryAfterMs };
28214
+ }
28215
+ /** §4 heartbeat predicate — read fresh from the §6 stores, never cached. */
28216
+ async pendingWork() {
28217
+ const [backstop, journal, mints] = await Promise.all([
28218
+ this.deps.stores.backstop.list(),
28219
+ this.deps.stores.deliveryJournal.list(),
28220
+ this.deps.stores.mintJournal.list()
28221
+ ]);
28222
+ return backstop.length > 0 || journal.some((e) => e.undeliverable !== true) || mints.length > 0;
28223
+ }
28224
+ async resumePass() {
28225
+ const report = await resumeAll(this.filteredMachineDeps());
28226
+ const progress = report.resumed.length > 0 || report.conflicted.length > 0;
28227
+ await this.deps.reconcile({
28228
+ resumed: report.resumed,
28229
+ conflicted: report.conflicted,
28230
+ open: report.failed
28231
+ });
28232
+ if (progress) await this.deps.refreshView().catch(() => void 0);
28233
+ return progress;
28234
+ }
28235
+ filteredMachineDeps() {
28236
+ const intents = this.deps.machineDeps.intents;
28237
+ return {
28238
+ ...this.deps.machineDeps,
28239
+ intents: {
28240
+ ...intents,
28241
+ listOpen: async () => (await intents.listOpen()).filter((intent) => !this.deps.isActiveOp(intent.transferId))
28242
+ }
28243
+ };
28244
+ }
28245
+ replayDeps() {
28246
+ return {
28247
+ delivery: this.deps.delivery,
28248
+ now: () => this.deps.now(),
28249
+ attention: (transferId, code, detail) => {
28250
+ this.deps.emit(
28251
+ "transfer:attention",
28252
+ detail === void 0 ? { transferId, code } : { transferId, code, detail }
28253
+ );
28254
+ }
28255
+ };
28256
+ }
28257
+ };
28258
+ async function derivePendingTransfers(stores, decryptPayload) {
28259
+ const [backstop, journal, shortfalls] = await Promise.all([
28260
+ stores.backstop.list(),
28261
+ stores.deliveryJournal.list(),
28262
+ stores.shortfalls.list()
28263
+ ]);
28264
+ const byTransfer = groupJournal(journal);
28265
+ const out = [];
28266
+ const seen = /* @__PURE__ */ new Set();
28267
+ for (const entry of backstop) {
28268
+ if (entry.disposition !== "open") continue;
28269
+ seen.add(entry.transferId);
28270
+ out.push(await openRow(entry, byTransfer.get(entry.transferId) ?? [], decryptPayload));
28271
+ }
28272
+ for (const [transferId, entries] of byTransfer) {
28273
+ if (seen.has(transferId)) continue;
28274
+ seen.add(transferId);
28275
+ out.push(journalOnlyRow(transferId, entries));
28276
+ }
28277
+ for (const entry of shortfalls) {
28278
+ if (!seen.has(entry.transferId)) out.push(shortfallRow(entry));
28279
+ }
28280
+ return out;
28281
+ }
28282
+ function groupJournal(journal) {
28283
+ const byTransfer = /* @__PURE__ */ new Map();
28284
+ for (const entry of journal) {
28285
+ const mine = byTransfer.get(entry.transferId);
28286
+ if (mine === void 0) byTransfer.set(entry.transferId, [entry]);
28287
+ else mine.push(entry);
28288
+ }
28289
+ return byTransfer;
28290
+ }
28291
+ async function openRow(entry, journal, decryptPayload) {
28292
+ let payload = null;
28293
+ try {
28294
+ const raw = await decryptPayload(entry.payloadEnvelope);
28295
+ if (raw !== null && typeof raw === "object") payload = raw;
28296
+ } catch {
28297
+ payload = null;
28298
+ }
28299
+ const total = Array.isArray(payload?.direct) ? payload.direct.length + (payload.split !== void 0 ? 1 : 0) : 0;
28300
+ const certified = distinctOpCount(journal);
28301
+ return {
28302
+ transferId: entry.transferId,
28303
+ kind: "open",
28304
+ recipient: typeof payload?.recipient === "string" ? payload.recipient : "",
28305
+ coinId: typeof payload?.coinId === "string" ? payload.coinId : "",
28306
+ amount: typeof payload?.amount === "string" ? payload.amount : "",
28307
+ legs: { certified, total: Math.max(total, certified) },
28308
+ deliveryPending: journal.length > 0,
28309
+ createdAt: entry.createdAt
28310
+ };
28311
+ }
28312
+ function journalOnlyRow(transferId, entries) {
28313
+ const certified = distinctOpCount(entries);
28314
+ return {
28315
+ transferId,
28316
+ kind: "open",
28317
+ recipient: entries[0].recipientPubkey,
28318
+ coinId: "",
28319
+ amount: "",
28320
+ legs: { certified, total: certified },
28321
+ deliveryPending: true,
28322
+ createdAt: 0
28323
+ };
28324
+ }
28325
+ function shortfallRow(entry) {
28326
+ return {
28327
+ transferId: entry.transferId,
28328
+ kind: "shortfall",
28329
+ recipient: entry.recipient,
28330
+ coinId: entry.coinId,
28331
+ amount: entry.remainingAmount,
28332
+ legs: { certified: entry.committedTokenIds.length, total: entry.committedTokenIds.length },
28333
+ deliveryPending: false,
28334
+ createdAt: entry.createdAt
28335
+ };
28336
+ }
28337
+ function distinctOpCount(entries) {
28338
+ return new Set(entries.map((entry) => entry.opIndex)).size;
28339
+ }
28340
+
28126
28341
  // modules/payments-v2/history/History.ts
28127
28342
  var WIRE_TOKEN_ID = /^(?:[0-9a-f]{2}){1,64}$/;
28128
28343
  var WIRE_PUBKEY = /^0[23][0-9a-f]{64}$/;
@@ -29512,6 +29727,21 @@ var PaymentsFacade = class {
29512
29727
  this.ownPubkeyBytes = parts.ownPubkeyBytes;
29513
29728
  this.requests = parts.requests;
29514
29729
  deps.deliveryPort.bindDeliveryKeys((blob) => this.engine().deliveryKeys(blob));
29730
+ this.heartbeat = new ConvergenceHeartbeat({
29731
+ now: () => this.nowMs(),
29732
+ onTick: () => this.trackTail(this.runConvergencePass())
29733
+ });
29734
+ this.converger = new Converger({
29735
+ machineDeps: this.machineDeps,
29736
+ stores: this.machineStores,
29737
+ delivery: deps.deliveryPort,
29738
+ reconcile: (report) => this.requests.reconcile(report),
29739
+ refreshView: () => this.view.delta(),
29740
+ replayMints: () => this.replayMints(),
29741
+ isActiveOp: (id) => this.activeMoneyOps.has(id),
29742
+ emit: deps.emit,
29743
+ now: () => this.nowMs()
29744
+ });
29515
29745
  }
29516
29746
  view;
29517
29747
  ledger;
@@ -29528,10 +29758,18 @@ var PaymentsFacade = class {
29528
29758
  pendingOps = /* @__PURE__ */ new Set();
29529
29759
  unsubscribers = [];
29530
29760
  started = false;
29761
+ // §7 heartbeat: in-memory timing cell + pass runner (see convergence.ts).
29762
+ heartbeat;
29763
+ converger;
29764
+ /** Resume single-flight (§7): ticks, start() and resumeNow() coalesce onto ONE pass. */
29765
+ resumeFlight = new SingleFlight();
29766
+ /** §7 ownership: ids with an in-process machine attempt — the pass never adopts one. */
29767
+ activeMoneyOps = /* @__PURE__ */ new Set();
29531
29768
  // ── lifecycle ──────────────────────────────────────────────────────────────
29532
29769
  async start() {
29533
29770
  if (this.started) return;
29534
29771
  this.started = true;
29772
+ this.heartbeat.start();
29535
29773
  await this.deps.session.start();
29536
29774
  this.unsubscribers.push(
29537
29775
  this.deps.session.subscribeStream("mailbox", () => {
@@ -29544,20 +29782,11 @@ var PaymentsFacade = class {
29544
29782
  this.trackTail(this.requests.drainIncoming());
29545
29783
  })
29546
29784
  );
29547
- this.trackTail(this.runResume());
29548
- this.trackTail(
29549
- this.machineStores.deliveryJournal.replay({
29550
- delivery: this.deps.deliveryPort,
29551
- now: this.deps.now ?? Date.now,
29552
- attention: (transferId, code, detail) => {
29553
- this.deps.emit(
29554
- "transfer:attention",
29555
- detail === void 0 ? { transferId, code } : { transferId, code, detail }
29556
- );
29557
- }
29558
- })
29559
- );
29560
- this.trackTail(this.replayMints());
29785
+ const statusUnsub = this.deps.session.subscribeStatus?.((status) => {
29786
+ if (status === "connected") this.heartbeat.recovered();
29787
+ });
29788
+ if (statusUnsub !== void 0) this.unsubscribers.push(statusUnsub);
29789
+ this.trackTail(this.runConvergencePass());
29561
29790
  this.trackTail(this.seedHeldStates());
29562
29791
  await this.view.fullPull().catch(() => void 0);
29563
29792
  this.receiveLoop.start(this.deps.receivePollMs);
@@ -29565,6 +29794,7 @@ var PaymentsFacade = class {
29565
29794
  /** §7 same-address restart gate: resolves only after in-flight ops settle. */
29566
29795
  async stop() {
29567
29796
  this.started = false;
29797
+ this.heartbeat.stop();
29568
29798
  this.receiveLoop.stop();
29569
29799
  for (const unsubscribe of this.unsubscribers) unsubscribe();
29570
29800
  this.unsubscribers = [];
@@ -29590,6 +29820,10 @@ var PaymentsFacade = class {
29590
29820
  history(page) {
29591
29821
  return this.historyStore.page(page ?? {});
29592
29822
  }
29823
+ /** §4 pending-transfers UI surface — derived on read, never cached (convergence.ts). */
29824
+ pendingTransfers() {
29825
+ return derivePendingTransfers(this.machineStores, this.machineDeps.decryptPayload);
29826
+ }
29593
29827
  // ── money movement ─────────────────────────────────────────────────────────
29594
29828
  send(request) {
29595
29829
  return this.track(this.sendWithPolicy(request));
@@ -29602,6 +29836,22 @@ var PaymentsFacade = class {
29602
29836
  mint(coinId, amount) {
29603
29837
  return this.track(this.mintInner(coinId, amount));
29604
29838
  }
29839
+ // §4 retry verb: coalesces onto the running pass — NEVER re-issue send() (#631/#676).
29840
+ async resumeNow() {
29841
+ if (!this.started) return;
29842
+ await this.track(this.runConvergencePass());
29843
+ }
29844
+ /** One single-flighted pass + its reschedule: concurrent callers coalesce. */
29845
+ runConvergencePass() {
29846
+ return this.resumeFlight.run(async () => {
29847
+ const outcome = await this.converger.convergeOnce();
29848
+ const pending = await this.converger.pendingWork().catch(() => true);
29849
+ this.heartbeat.settle(outcome, pending);
29850
+ });
29851
+ }
29852
+ nowMs() {
29853
+ return (this.deps.now ?? Date.now)();
29854
+ }
29605
29855
  // ── send policy (§5.5 + old-loop parity; the machine stays policy-free) ────
29606
29856
  async sendWithPolicy(request) {
29607
29857
  const recipient = await this.requireSameNetworkRecipient(request.recipient);
@@ -29651,12 +29901,15 @@ var PaymentsFacade = class {
29651
29901
  return recipient;
29652
29902
  }
29653
29903
  async runAttempt(ctx) {
29904
+ this.activeMoneyOps.add(ctx.transferId);
29654
29905
  try {
29655
29906
  const { deliveryPending } = await this.machine.run(ctx.plan);
29656
29907
  this.settleSuccess(ctx, ctx.sourceIds);
29657
29908
  return { kind: "success", deliveryPending };
29658
29909
  } catch (err) {
29659
- return this.disposeFailedAttempt(ctx, err);
29910
+ return await this.disposeFailedAttempt(ctx, err);
29911
+ } finally {
29912
+ this.activeMoneyOps.delete(ctx.transferId);
29660
29913
  }
29661
29914
  }
29662
29915
  /**
@@ -29665,7 +29918,10 @@ var PaymentsFacade = class {
29665
29918
  * the remainder under a NEW transferId — never the full amount.
29666
29919
  */
29667
29920
  async consumePartial(ctx, disposition, run, attempt) {
29668
- run.pendingAny = run.pendingAny || disposition.deliveryPending;
29921
+ if (disposition.deliveryPending) {
29922
+ run.pendingAny = true;
29923
+ this.heartbeat.arm();
29924
+ }
29669
29925
  const partial = disposition.entry;
29670
29926
  run.delivered.push(...partial.committedTokenIds);
29671
29927
  run.firstPartialId ??= partial.transferId;
@@ -29823,6 +30079,7 @@ var PaymentsFacade = class {
29823
30079
  /** Open intent: sources stay reserved + in-flight (§5.2) until resume adopts them. */
29824
30080
  settleKeepOpen(ctx) {
29825
30081
  this.queue.clearExpectedChange(ctx.transferId);
30082
+ this.heartbeat.arm();
29826
30083
  }
29827
30084
  settlePartial(ctx, partial) {
29828
30085
  this.ledger.cancel(ctx.transferId);
@@ -29859,6 +30116,7 @@ var PaymentsFacade = class {
29859
30116
  for (const id of run.settledShortfalls.splice(0)) {
29860
30117
  await this.machineStores.shortfalls.removeByKey(id).catch(() => void 0);
29861
30118
  }
30119
+ if (run.pendingAny) this.heartbeat.arm();
29862
30120
  const result = {
29863
30121
  id: transferId,
29864
30122
  status: run.pendingAny ? "confirmed" : "delivered",
@@ -29903,21 +30161,29 @@ var PaymentsFacade = class {
29903
30161
  if (!/^(?:[0-9a-f]{2})+$/.test(coinId) || amount <= 0n) {
29904
30162
  return { success: false, error: "coinId must be even-length lowercase hex and amount positive" };
29905
30163
  }
29906
- const engine = this.engine();
29907
30164
  const mintId = this.newId();
29908
- const params = this.mintParams(coinId, amount);
30165
+ this.activeMoneyOps.add(mintId);
30166
+ try {
30167
+ return await this.mintUnderJournal(mintId, coinId, amount);
30168
+ } finally {
30169
+ this.activeMoneyOps.delete(mintId);
30170
+ }
30171
+ }
30172
+ async mintUnderJournal(mintId, coinId, amount) {
30173
+ const engine = this.engine();
29909
30174
  const entry = {
29910
30175
  mintId,
29911
30176
  coinId,
29912
30177
  amount: amount.toString(),
29913
30178
  tokenId: "",
29914
- createdAt: (this.deps.now ?? Date.now)()
30179
+ createdAt: this.nowMs()
29915
30180
  };
29916
30181
  await this.machineStores.mintJournal.upsert(entry);
29917
30182
  let token;
29918
30183
  try {
29919
- token = await engine.mint(params, { transferId: mintId });
30184
+ token = await engine.mint(this.mintParams(coinId, amount), { transferId: mintId });
29920
30185
  } catch (err) {
30186
+ this.heartbeat.arm();
29921
30187
  return { success: false, error: messageOf(err) };
29922
30188
  }
29923
30189
  if (token.blob.tokenId !== entry.tokenId) {
@@ -29926,6 +30192,7 @@ var PaymentsFacade = class {
29926
30192
  try {
29927
30193
  await this.finalizeMint(engine, mintId, token, coinId, amount.toString());
29928
30194
  } catch (err) {
30195
+ this.heartbeat.arm();
29929
30196
  return { success: false, tokenId: token.blob.tokenId, error: messageOf(err) };
29930
30197
  }
29931
30198
  return { success: true, tokenId: token.blob.tokenId };
@@ -29948,18 +30215,22 @@ var PaymentsFacade = class {
29948
30215
  this.heldStates.set(token.blob.tokenId, (await engine.deliveryKeys(bytes)).stateHash);
29949
30216
  this.trackTail(this.view.delta());
29950
30217
  }
30218
+ /** @returns how many journal entries were RESOLVED (cleared) — heartbeat progress. */
29951
30219
  async replayMints() {
30220
+ let resolved = 0;
29952
30221
  for (const entry of await this.machineStores.mintJournal.list()) {
30222
+ if (this.activeMoneyOps.has(entry.mintId)) continue;
29953
30223
  try {
29954
- await this.replayMint(entry);
30224
+ if (await this.replayMint(entry)) resolved += 1;
29955
30225
  } catch {
29956
30226
  }
29957
30227
  }
30228
+ return resolved;
29958
30229
  }
29959
30230
  async replayMint(entry) {
29960
30231
  if (entry.tokenId !== "" && await this.tokenInServerInventory(entry.tokenId)) {
29961
30232
  await this.machineStores.mintJournal.removeByKey(entry.mintId);
29962
- return;
30233
+ return true;
29963
30234
  }
29964
30235
  const engine = this.engine();
29965
30236
  if (!supportsDeterministicMint(engine)) {
@@ -29967,7 +30238,7 @@ var PaymentsFacade = class {
29967
30238
  transferId: entry.mintId,
29968
30239
  code: ATTENTION_MINT_UNRESOLVED
29969
30240
  });
29970
- return;
30241
+ return false;
29971
30242
  }
29972
30243
  const token = await engine.mint(this.mintParams(entry.coinId, BigInt(entry.amount)), {
29973
30244
  transferId: entry.mintId
@@ -29976,6 +30247,7 @@ var PaymentsFacade = class {
29976
30247
  await this.machineStores.mintJournal.upsert({ ...entry, tokenId: token.blob.tokenId });
29977
30248
  }
29978
30249
  await this.finalizeMint(engine, entry.mintId, token, entry.coinId, entry.amount);
30250
+ return true;
29979
30251
  }
29980
30252
  mintParams(coinId, amount) {
29981
30253
  return {
@@ -30001,17 +30273,6 @@ var PaymentsFacade = class {
30001
30273
  newId() {
30002
30274
  return (this.deps.newId ?? randomUUID)();
30003
30275
  }
30004
- async runResume() {
30005
- const report = await resumeAll(this.machineDeps);
30006
- await this.requests.reconcile({
30007
- resumed: report.resumed,
30008
- conflicted: report.conflicted,
30009
- open: report.failed
30010
- });
30011
- if (report.resumed.length > 0 || report.conflicted.length > 0) {
30012
- await this.view.delta().catch(() => void 0);
30013
- }
30014
- }
30015
30276
  async seedHeldStates() {
30016
30277
  let page = await this.deps.storagePort.listInventory();
30017
30278
  for (let i = 0; i < INVENTORY_SCAN_PAGE_LIMIT; i++) {
@@ -30025,7 +30286,7 @@ var PaymentsFacade = class {
30025
30286
  toUiToken(tokenId, coinId, amount) {
30026
30287
  const registry = this.deps.registry;
30027
30288
  const iconUrl = registry.getIconUrl(coinId);
30028
- const now = (this.deps.now ?? Date.now)();
30289
+ const now = this.nowMs();
30029
30290
  return {
30030
30291
  id: tokenId,
30031
30292
  coinId,
@@ -30420,6 +30681,7 @@ var WalletApiSession = class {
30420
30681
  reseedChain = Promise.resolve();
30421
30682
  reseedsPending = 0;
30422
30683
  handlers = /* @__PURE__ */ new Map();
30684
+ statusHandlers = /* @__PURE__ */ new Set();
30423
30685
  socket = null;
30424
30686
  started = false;
30425
30687
  stopped = false;
@@ -30570,10 +30832,16 @@ var WalletApiSession = class {
30570
30832
  status() {
30571
30833
  return this.lastStatus ?? "offline";
30572
30834
  }
30835
+ /** Transition feed off the existing emitStatus point (FacadeSession.subscribeStatus). */
30836
+ subscribeStatus(handler) {
30837
+ this.statusHandlers.add(handler);
30838
+ return () => this.statusHandlers.delete(handler);
30839
+ }
30573
30840
  emitStatus(status) {
30574
30841
  if (this.stopped || this.lastStatus === status) return;
30575
30842
  this.lastStatus = status;
30576
30843
  this.deps.emitStatus(status);
30844
+ for (const handler of this.statusHandlers) handler(status);
30577
30845
  }
30578
30846
  async connect() {
30579
30847
  if (this.stopped) return;