@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.
@@ -12359,6 +12359,90 @@ async function splitOpIndex(deps, envelope) {
12359
12359
  return Array.isArray(payload.direct) ? payload.direct.length : 0;
12360
12360
  }
12361
12361
 
12362
+ // modules/payments-v2/mint-params.ts
12363
+ function mintParams(recipientPubkey, coinId, amount) {
12364
+ return { recipientPubkey, value: { assets: [{ coinId, amount }] } };
12365
+ }
12366
+
12367
+ // modules/payments-v2/prewarm-cache.ts
12368
+ var PrewarmCache = class _PrewarmCache {
12369
+ entries = /* @__PURE__ */ new Map();
12370
+ generation = 0;
12371
+ static key(tokenId, stateHash) {
12372
+ return `${tokenId}:${stateHash}`;
12373
+ }
12374
+ /** Claim the cache for a warm about to start; supersedes any warm already running. */
12375
+ begin() {
12376
+ this.entries.clear();
12377
+ this.generation += 1;
12378
+ return this.generation;
12379
+ }
12380
+ /**
12381
+ * Publish a warm's result, unless it was cancelled or superseded while its
12382
+ * fetch was in flight — a cancel that only cleared what had landed so far
12383
+ * would be undone by the continuation of the very warm it cancelled.
12384
+ */
12385
+ commit(generation, warmed) {
12386
+ if (generation !== this.generation) return;
12387
+ for (const [tokenId, stateHash, bytes] of warmed) {
12388
+ this.entries.set(_PrewarmCache.key(tokenId, stateHash), bytes);
12389
+ }
12390
+ }
12391
+ get(tokenId, stateHash) {
12392
+ if (stateHash === void 0) return void 0;
12393
+ return this.entries.get(_PrewarmCache.key(tokenId, stateHash));
12394
+ }
12395
+ clear() {
12396
+ this.entries.clear();
12397
+ this.generation += 1;
12398
+ }
12399
+ get size() {
12400
+ return this.entries.size;
12401
+ }
12402
+ };
12403
+ async function warmSendSources(deps, request) {
12404
+ const ids = deps.queue.previewSelection(request);
12405
+ if (ids.length === 0) return;
12406
+ const generation = deps.cache.begin();
12407
+ const at = new Map(ids.map((id) => [id, deps.view.stateHashOf(id)]));
12408
+ let blobs;
12409
+ try {
12410
+ blobs = await deps.storagePort.getBlobs([...ids]);
12411
+ } catch {
12412
+ return;
12413
+ }
12414
+ const warmed = [];
12415
+ for (const [tokenId, bytes] of blobs) {
12416
+ const stateHash = at.get(tokenId);
12417
+ if (stateHash !== void 0) warmed.push([tokenId, stateHash, bytes]);
12418
+ }
12419
+ deps.cache.commit(generation, warmed);
12420
+ }
12421
+ async function takeSourceBlobs(deps, sourceIds) {
12422
+ const blobs = /* @__PURE__ */ new Map();
12423
+ const missing = [];
12424
+ let trustCache = deps.cache.size > 0;
12425
+ if (trustCache) {
12426
+ try {
12427
+ await deps.view.delta();
12428
+ } catch {
12429
+ trustCache = false;
12430
+ }
12431
+ }
12432
+ for (const tokenId of sourceIds) {
12433
+ const warm = trustCache ? deps.cache.get(tokenId, deps.view.stateHashOf(tokenId)) : void 0;
12434
+ if (warm === void 0) missing.push(tokenId);
12435
+ else blobs.set(tokenId, warm);
12436
+ }
12437
+ deps.cache.clear();
12438
+ if (missing.length > 0) {
12439
+ for (const [tokenId, bytes] of await deps.storagePort.getBlobs(missing)) {
12440
+ blobs.set(tokenId, bytes);
12441
+ }
12442
+ }
12443
+ return blobs;
12444
+ }
12445
+
12362
12446
  // modules/payments-v2/inventory/presentation.ts
12363
12447
  function toToken(tokenId, snapshot, asset, registry, flags) {
12364
12448
  const iconUrl = registry.getIconUrl(asset.coinId);
@@ -12445,6 +12529,13 @@ function priceOne(asset, quote) {
12445
12529
  fiatValueEur: quote.priceEur != null ? human * quote.priceEur : null
12446
12530
  };
12447
12531
  }
12532
+ function transferringToken(tokenId, coinId, amount, registry, now) {
12533
+ const snapshot = { assets: [], createdAt: now, updatedAt: now };
12534
+ return toToken(tokenId, snapshot, { coinId, amount: amount.toString() }, registry, {
12535
+ transferring: true,
12536
+ suspectedSpent: false
12537
+ });
12538
+ }
12448
12539
 
12449
12540
  // modules/payments-v2/machine/payload.ts
12450
12541
  function buildPayload(recipientPubkey, request, spend, spentStates) {
@@ -12683,6 +12774,15 @@ var InventoryView = class {
12683
12774
  }
12684
12775
  return out;
12685
12776
  }
12777
+ /**
12778
+ * Current mirrored state of an ACTIVE token, or undefined when it is unknown
12779
+ * or tombstoned. Exists so a cached blob can be state-scoped (F6): a blob is
12780
+ * only reusable while the token still sits at the state it was fetched at.
12781
+ */
12782
+ stateHashOf(tokenId) {
12783
+ const entry = this.mirror.get(tokenId);
12784
+ return entry !== void 0 && entry.status === "active" ? entry.stateHash : void 0;
12785
+ }
12686
12786
  tokens(registry, filter) {
12687
12787
  const out = [];
12688
12788
  for (const [tokenId, entry] of this.mirror) {
@@ -13602,6 +13702,31 @@ var SpendQueue = class {
13602
13702
  }
13603
13703
  return { kind: "queued", settled: this.enqueue(reservationId, request.coinId, amount) };
13604
13704
  }
13705
+ /**
13706
+ * The sources `plan()` WOULD pick right now, reserving nothing and queueing
13707
+ * nothing — a read-only probe used to warm blobs while the user is still
13708
+ * looking at the confirm screen.
13709
+ *
13710
+ * Deliberately side-effect free: reserving here would pin the user's balance
13711
+ * across think-time and strand it if they never press send. The consequence is
13712
+ * that the real `plan()` may pick differently (inventory moves, another send
13713
+ * reserves first); callers must treat the result as a HINT and tolerate a miss,
13714
+ * never as a commitment that these sources will be spent.
13715
+ */
13716
+ previewSelection(request) {
13717
+ if (this.destroyed) return [];
13718
+ let amount;
13719
+ try {
13720
+ amount = parseAmount(request.amount);
13721
+ } catch {
13722
+ return [];
13723
+ }
13724
+ const { freeView, freeTotal } = this.freeView(request.coinId);
13725
+ if (freeTotal < amount) return [];
13726
+ const plan = selectCoins(freeView, amount, { workBudget: this.deps.workBudget });
13727
+ if (plan === null) return [];
13728
+ return [...plan.direct, ...plan.split !== void 0 ? [plan.split.tokenId] : []];
13729
+ }
13605
13730
  // Coverage math: free + Σ declared expected change of in-flight sends —
13606
13731
  // never the full amount of in-flight sources (§5.3).
13607
13732
  declareExpectedChange(transferId, coinId, amount) {
@@ -13951,6 +14076,7 @@ var PaymentsFacade = class {
13951
14076
  now: () => this.nowMs()
13952
14077
  });
13953
14078
  }
14079
+ prewarmed = new PrewarmCache();
13954
14080
  view;
13955
14081
  ledger;
13956
14082
  queue;
@@ -14044,6 +14170,15 @@ var PaymentsFacade = class {
14044
14170
  return derivePendingTransfers(this.machineStores, this.machineDeps.decryptPayload);
14045
14171
  }
14046
14172
  // ── money movement ─────────────────────────────────────────────────────────
14173
+ async prewarmSend(request) {
14174
+ await warmSendSources(this.warmDeps(), request);
14175
+ }
14176
+ discardPrewarm() {
14177
+ this.prewarmed.clear();
14178
+ }
14179
+ warmDeps() {
14180
+ return { queue: this.queue, view: this.view, storagePort: this.deps.storagePort, cache: this.prewarmed };
14181
+ }
14047
14182
  send(request) {
14048
14183
  return this.track(this.sendOutcome(request));
14049
14184
  }
@@ -14253,7 +14388,7 @@ var PaymentsFacade = class {
14253
14388
  }
14254
14389
  async materialize(transferId, recipientPubkey, request, spend, sourceIds) {
14255
14390
  const engine = this.engine();
14256
- const blobs = await this.deps.storagePort.getBlobs([...sourceIds]);
14391
+ const blobs = await takeSourceBlobs(this.warmDeps(), sourceIds);
14257
14392
  const sources = /* @__PURE__ */ new Map();
14258
14393
  const sourceTokens = [];
14259
14394
  const spentStates = {};
@@ -14266,7 +14401,7 @@ var PaymentsFacade = class {
14266
14401
  const keys = await engine.deliveryKeys(bytes);
14267
14402
  spentStates[tokenId] = { local: keys.stateHash, protocol: keys.stateHash };
14268
14403
  sources.set(tokenId, token);
14269
- sourceTokens.push(this.toUiToken(tokenId, request.coinId, engine.balanceOf(token, request.coinId)));
14404
+ sourceTokens.push(transferringToken(tokenId, request.coinId, engine.balanceOf(token, request.coinId), this.deps.registry, this.nowMs()));
14270
14405
  }
14271
14406
  const payload = buildPayload(recipientPubkey, request, spend, spentStates);
14272
14407
  const plan = {
@@ -14410,7 +14545,7 @@ var PaymentsFacade = class {
14410
14545
  await this.machineStores.mintJournal.upsert(entry);
14411
14546
  let token;
14412
14547
  try {
14413
- token = await engine.mint(this.mintParams(coinId, amount), { transferId: mintId });
14548
+ token = await engine.mint(mintParams(this.ownPubkeyBytes, coinId, amount), { transferId: mintId });
14414
14549
  } catch (err) {
14415
14550
  this.heartbeat.arm();
14416
14551
  return { success: false, error: messageOf(err) };
@@ -14469,7 +14604,7 @@ var PaymentsFacade = class {
14469
14604
  });
14470
14605
  return false;
14471
14606
  }
14472
- const token = await engine.mint(this.mintParams(entry.coinId, BigInt(entry.amount)), {
14607
+ const token = await engine.mint(mintParams(this.ownPubkeyBytes, entry.coinId, BigInt(entry.amount)), {
14473
14608
  transferId: entry.mintId
14474
14609
  });
14475
14610
  if (token.blob.tokenId !== entry.tokenId) {
@@ -14478,12 +14613,6 @@ var PaymentsFacade = class {
14478
14613
  await this.finalizeMint(engine, entry.mintId, token, entry.coinId, entry.amount);
14479
14614
  return true;
14480
14615
  }
14481
- mintParams(coinId, amount) {
14482
- return {
14483
- recipientPubkey: this.ownPubkeyBytes,
14484
- value: { assets: [{ coinId, amount }] }
14485
- };
14486
- }
14487
14616
  async tokenInServerInventory(tokenId) {
14488
14617
  let page = await this.deps.storagePort.listInventory();
14489
14618
  for (let i = 0; i < INVENTORY_SCAN_PAGE_LIMIT; i++) {
@@ -14512,12 +14641,6 @@ var PaymentsFacade = class {
14512
14641
  page = await this.deps.storagePort.listInventory(page.cursor);
14513
14642
  }
14514
14643
  }
14515
- toUiToken(tokenId, coinId, amount) {
14516
- const now = this.nowMs();
14517
- const snapshot = { assets: [], createdAt: now, updatedAt: now };
14518
- const flags = { transferring: true, suspectedSpent: false };
14519
- return toToken(tokenId, snapshot, { coinId, amount: amount.toString() }, this.deps.registry, flags);
14520
- }
14521
14644
  track(op) {
14522
14645
  const settled = op.then(() => void 0, () => void 0);
14523
14646
  this.pendingOps.add(settled);