@unicitylabs/sphere-sdk 0.14.10 → 0.14.11-dev.1

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.cjs CHANGED
@@ -12417,6 +12417,90 @@ async function splitOpIndex(deps, envelope) {
12417
12417
  return Array.isArray(payload.direct) ? payload.direct.length : 0;
12418
12418
  }
12419
12419
 
12420
+ // modules/payments-v2/mint-params.ts
12421
+ function mintParams(recipientPubkey, coinId, amount) {
12422
+ return { recipientPubkey, value: { assets: [{ coinId, amount }] } };
12423
+ }
12424
+
12425
+ // modules/payments-v2/prewarm-cache.ts
12426
+ var PrewarmCache = class _PrewarmCache {
12427
+ entries = /* @__PURE__ */ new Map();
12428
+ generation = 0;
12429
+ static key(tokenId, stateHash) {
12430
+ return `${tokenId}:${stateHash}`;
12431
+ }
12432
+ /** Claim the cache for a warm about to start; supersedes any warm already running. */
12433
+ begin() {
12434
+ this.entries.clear();
12435
+ this.generation += 1;
12436
+ return this.generation;
12437
+ }
12438
+ /**
12439
+ * Publish a warm's result, unless it was cancelled or superseded while its
12440
+ * fetch was in flight — a cancel that only cleared what had landed so far
12441
+ * would be undone by the continuation of the very warm it cancelled.
12442
+ */
12443
+ commit(generation, warmed) {
12444
+ if (generation !== this.generation) return;
12445
+ for (const [tokenId, stateHash, bytes] of warmed) {
12446
+ this.entries.set(_PrewarmCache.key(tokenId, stateHash), bytes);
12447
+ }
12448
+ }
12449
+ get(tokenId, stateHash) {
12450
+ if (stateHash === void 0) return void 0;
12451
+ return this.entries.get(_PrewarmCache.key(tokenId, stateHash));
12452
+ }
12453
+ clear() {
12454
+ this.entries.clear();
12455
+ this.generation += 1;
12456
+ }
12457
+ get size() {
12458
+ return this.entries.size;
12459
+ }
12460
+ };
12461
+ async function warmSendSources(deps, request) {
12462
+ const ids = deps.queue.previewSelection(request);
12463
+ if (ids.length === 0) return;
12464
+ const generation = deps.cache.begin();
12465
+ const at = new Map(ids.map((id) => [id, deps.view.stateHashOf(id)]));
12466
+ let blobs;
12467
+ try {
12468
+ blobs = await deps.storagePort.getBlobs([...ids]);
12469
+ } catch {
12470
+ return;
12471
+ }
12472
+ const warmed = [];
12473
+ for (const [tokenId, bytes] of blobs) {
12474
+ const stateHash = at.get(tokenId);
12475
+ if (stateHash !== void 0) warmed.push([tokenId, stateHash, bytes]);
12476
+ }
12477
+ deps.cache.commit(generation, warmed);
12478
+ }
12479
+ async function takeSourceBlobs(deps, sourceIds) {
12480
+ const blobs = /* @__PURE__ */ new Map();
12481
+ const missing = [];
12482
+ let trustCache = deps.cache.size > 0;
12483
+ if (trustCache) {
12484
+ try {
12485
+ await deps.view.delta();
12486
+ } catch {
12487
+ trustCache = false;
12488
+ }
12489
+ }
12490
+ for (const tokenId of sourceIds) {
12491
+ const warm = trustCache ? deps.cache.get(tokenId, deps.view.stateHashOf(tokenId)) : void 0;
12492
+ if (warm === void 0) missing.push(tokenId);
12493
+ else blobs.set(tokenId, warm);
12494
+ }
12495
+ deps.cache.clear();
12496
+ if (missing.length > 0) {
12497
+ for (const [tokenId, bytes] of await deps.storagePort.getBlobs(missing)) {
12498
+ blobs.set(tokenId, bytes);
12499
+ }
12500
+ }
12501
+ return blobs;
12502
+ }
12503
+
12420
12504
  // modules/payments-v2/inventory/presentation.ts
12421
12505
  function toToken(tokenId, snapshot, asset, registry, flags) {
12422
12506
  const iconUrl = registry.getIconUrl(asset.coinId);
@@ -12503,6 +12587,13 @@ function priceOne(asset, quote) {
12503
12587
  fiatValueEur: quote.priceEur != null ? human * quote.priceEur : null
12504
12588
  };
12505
12589
  }
12590
+ function transferringToken(tokenId, coinId, amount, registry, now) {
12591
+ const snapshot = { assets: [], createdAt: now, updatedAt: now };
12592
+ return toToken(tokenId, snapshot, { coinId, amount: amount.toString() }, registry, {
12593
+ transferring: true,
12594
+ suspectedSpent: false
12595
+ });
12596
+ }
12506
12597
 
12507
12598
  // modules/payments-v2/machine/payload.ts
12508
12599
  function buildPayload(recipientPubkey, request, spend, spentStates) {
@@ -12741,6 +12832,15 @@ var InventoryView = class {
12741
12832
  }
12742
12833
  return out;
12743
12834
  }
12835
+ /**
12836
+ * Current mirrored state of an ACTIVE token, or undefined when it is unknown
12837
+ * or tombstoned. Exists so a cached blob can be state-scoped (F6): a blob is
12838
+ * only reusable while the token still sits at the state it was fetched at.
12839
+ */
12840
+ stateHashOf(tokenId) {
12841
+ const entry = this.mirror.get(tokenId);
12842
+ return entry !== void 0 && entry.status === "active" ? entry.stateHash : void 0;
12843
+ }
12744
12844
  tokens(registry, filter) {
12745
12845
  const out = [];
12746
12846
  for (const [tokenId, entry] of this.mirror) {
@@ -13660,6 +13760,31 @@ var SpendQueue = class {
13660
13760
  }
13661
13761
  return { kind: "queued", settled: this.enqueue(reservationId, request.coinId, amount) };
13662
13762
  }
13763
+ /**
13764
+ * The sources `plan()` WOULD pick right now, reserving nothing and queueing
13765
+ * nothing — a read-only probe used to warm blobs while the user is still
13766
+ * looking at the confirm screen.
13767
+ *
13768
+ * Deliberately side-effect free: reserving here would pin the user's balance
13769
+ * across think-time and strand it if they never press send. The consequence is
13770
+ * that the real `plan()` may pick differently (inventory moves, another send
13771
+ * reserves first); callers must treat the result as a HINT and tolerate a miss,
13772
+ * never as a commitment that these sources will be spent.
13773
+ */
13774
+ previewSelection(request) {
13775
+ if (this.destroyed) return [];
13776
+ let amount;
13777
+ try {
13778
+ amount = parseAmount(request.amount);
13779
+ } catch {
13780
+ return [];
13781
+ }
13782
+ const { freeView, freeTotal } = this.freeView(request.coinId);
13783
+ if (freeTotal < amount) return [];
13784
+ const plan = selectCoins(freeView, amount, { workBudget: this.deps.workBudget });
13785
+ if (plan === null) return [];
13786
+ return [...plan.direct, ...plan.split !== void 0 ? [plan.split.tokenId] : []];
13787
+ }
13663
13788
  // Coverage math: free + Σ declared expected change of in-flight sends —
13664
13789
  // never the full amount of in-flight sources (§5.3).
13665
13790
  declareExpectedChange(transferId, coinId, amount) {
@@ -14009,6 +14134,7 @@ var PaymentsFacade = class {
14009
14134
  now: () => this.nowMs()
14010
14135
  });
14011
14136
  }
14137
+ prewarmed = new PrewarmCache();
14012
14138
  view;
14013
14139
  ledger;
14014
14140
  queue;
@@ -14102,6 +14228,15 @@ var PaymentsFacade = class {
14102
14228
  return derivePendingTransfers(this.machineStores, this.machineDeps.decryptPayload);
14103
14229
  }
14104
14230
  // ── money movement ─────────────────────────────────────────────────────────
14231
+ async prewarmSend(request) {
14232
+ await warmSendSources(this.warmDeps(), request);
14233
+ }
14234
+ discardPrewarm() {
14235
+ this.prewarmed.clear();
14236
+ }
14237
+ warmDeps() {
14238
+ return { queue: this.queue, view: this.view, storagePort: this.deps.storagePort, cache: this.prewarmed };
14239
+ }
14105
14240
  send(request) {
14106
14241
  return this.track(this.sendOutcome(request));
14107
14242
  }
@@ -14311,7 +14446,7 @@ var PaymentsFacade = class {
14311
14446
  }
14312
14447
  async materialize(transferId, recipientPubkey, request, spend, sourceIds) {
14313
14448
  const engine = this.engine();
14314
- const blobs = await this.deps.storagePort.getBlobs([...sourceIds]);
14449
+ const blobs = await takeSourceBlobs(this.warmDeps(), sourceIds);
14315
14450
  const sources = /* @__PURE__ */ new Map();
14316
14451
  const sourceTokens = [];
14317
14452
  const spentStates = {};
@@ -14324,7 +14459,7 @@ var PaymentsFacade = class {
14324
14459
  const keys = await engine.deliveryKeys(bytes);
14325
14460
  spentStates[tokenId] = { local: keys.stateHash, protocol: keys.stateHash };
14326
14461
  sources.set(tokenId, token);
14327
- sourceTokens.push(this.toUiToken(tokenId, request.coinId, engine.balanceOf(token, request.coinId)));
14462
+ sourceTokens.push(transferringToken(tokenId, request.coinId, engine.balanceOf(token, request.coinId), this.deps.registry, this.nowMs()));
14328
14463
  }
14329
14464
  const payload = buildPayload(recipientPubkey, request, spend, spentStates);
14330
14465
  const plan = {
@@ -14468,7 +14603,7 @@ var PaymentsFacade = class {
14468
14603
  await this.machineStores.mintJournal.upsert(entry);
14469
14604
  let token;
14470
14605
  try {
14471
- token = await engine.mint(this.mintParams(coinId, amount), { transferId: mintId });
14606
+ token = await engine.mint(mintParams(this.ownPubkeyBytes, coinId, amount), { transferId: mintId });
14472
14607
  } catch (err) {
14473
14608
  this.heartbeat.arm();
14474
14609
  return { success: false, error: messageOf(err) };
@@ -14527,7 +14662,7 @@ var PaymentsFacade = class {
14527
14662
  });
14528
14663
  return false;
14529
14664
  }
14530
- const token = await engine.mint(this.mintParams(entry.coinId, BigInt(entry.amount)), {
14665
+ const token = await engine.mint(mintParams(this.ownPubkeyBytes, entry.coinId, BigInt(entry.amount)), {
14531
14666
  transferId: entry.mintId
14532
14667
  });
14533
14668
  if (token.blob.tokenId !== entry.tokenId) {
@@ -14536,12 +14671,6 @@ var PaymentsFacade = class {
14536
14671
  await this.finalizeMint(engine, entry.mintId, token, entry.coinId, entry.amount);
14537
14672
  return true;
14538
14673
  }
14539
- mintParams(coinId, amount) {
14540
- return {
14541
- recipientPubkey: this.ownPubkeyBytes,
14542
- value: { assets: [{ coinId, amount }] }
14543
- };
14544
- }
14545
14674
  async tokenInServerInventory(tokenId) {
14546
14675
  let page = await this.deps.storagePort.listInventory();
14547
14676
  for (let i = 0; i < INVENTORY_SCAN_PAGE_LIMIT; i++) {
@@ -14570,12 +14699,6 @@ var PaymentsFacade = class {
14570
14699
  page = await this.deps.storagePort.listInventory(page.cursor);
14571
14700
  }
14572
14701
  }
14573
- toUiToken(tokenId, coinId, amount) {
14574
- const now = this.nowMs();
14575
- const snapshot = { assets: [], createdAt: now, updatedAt: now };
14576
- const flags = { transferring: true, suspectedSpent: false };
14577
- return toToken(tokenId, snapshot, { coinId, amount: amount.toString() }, this.deps.registry, flags);
14578
- }
14579
14702
  track(op) {
14580
14703
  const settled = op.then(() => void 0, () => void 0);
14581
14704
  this.pendingOps.add(settled);