@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.
@@ -1645,6 +1645,90 @@ async function splitOpIndex(deps, envelope) {
1645
1645
  return Array.isArray(payload.direct) ? payload.direct.length : 0;
1646
1646
  }
1647
1647
 
1648
+ // modules/payments-v2/mint-params.ts
1649
+ function mintParams(recipientPubkey, coinId, amount) {
1650
+ return { recipientPubkey, value: { assets: [{ coinId, amount }] } };
1651
+ }
1652
+
1653
+ // modules/payments-v2/prewarm-cache.ts
1654
+ var PrewarmCache = class _PrewarmCache {
1655
+ entries = /* @__PURE__ */ new Map();
1656
+ generation = 0;
1657
+ static key(tokenId, stateHash) {
1658
+ return `${tokenId}:${stateHash}`;
1659
+ }
1660
+ /** Claim the cache for a warm about to start; supersedes any warm already running. */
1661
+ begin() {
1662
+ this.entries.clear();
1663
+ this.generation += 1;
1664
+ return this.generation;
1665
+ }
1666
+ /**
1667
+ * Publish a warm's result, unless it was cancelled or superseded while its
1668
+ * fetch was in flight — a cancel that only cleared what had landed so far
1669
+ * would be undone by the continuation of the very warm it cancelled.
1670
+ */
1671
+ commit(generation, warmed) {
1672
+ if (generation !== this.generation) return;
1673
+ for (const [tokenId, stateHash, bytes] of warmed) {
1674
+ this.entries.set(_PrewarmCache.key(tokenId, stateHash), bytes);
1675
+ }
1676
+ }
1677
+ get(tokenId, stateHash) {
1678
+ if (stateHash === void 0) return void 0;
1679
+ return this.entries.get(_PrewarmCache.key(tokenId, stateHash));
1680
+ }
1681
+ clear() {
1682
+ this.entries.clear();
1683
+ this.generation += 1;
1684
+ }
1685
+ get size() {
1686
+ return this.entries.size;
1687
+ }
1688
+ };
1689
+ async function warmSendSources(deps, request) {
1690
+ const ids = deps.queue.previewSelection(request);
1691
+ if (ids.length === 0) return;
1692
+ const generation = deps.cache.begin();
1693
+ const at = new Map(ids.map((id) => [id, deps.view.stateHashOf(id)]));
1694
+ let blobs;
1695
+ try {
1696
+ blobs = await deps.storagePort.getBlobs([...ids]);
1697
+ } catch {
1698
+ return;
1699
+ }
1700
+ const warmed = [];
1701
+ for (const [tokenId, bytes] of blobs) {
1702
+ const stateHash = at.get(tokenId);
1703
+ if (stateHash !== void 0) warmed.push([tokenId, stateHash, bytes]);
1704
+ }
1705
+ deps.cache.commit(generation, warmed);
1706
+ }
1707
+ async function takeSourceBlobs(deps, sourceIds) {
1708
+ const blobs = /* @__PURE__ */ new Map();
1709
+ const missing = [];
1710
+ let trustCache = deps.cache.size > 0;
1711
+ if (trustCache) {
1712
+ try {
1713
+ await deps.view.delta();
1714
+ } catch {
1715
+ trustCache = false;
1716
+ }
1717
+ }
1718
+ for (const tokenId of sourceIds) {
1719
+ const warm = trustCache ? deps.cache.get(tokenId, deps.view.stateHashOf(tokenId)) : void 0;
1720
+ if (warm === void 0) missing.push(tokenId);
1721
+ else blobs.set(tokenId, warm);
1722
+ }
1723
+ deps.cache.clear();
1724
+ if (missing.length > 0) {
1725
+ for (const [tokenId, bytes] of await deps.storagePort.getBlobs(missing)) {
1726
+ blobs.set(tokenId, bytes);
1727
+ }
1728
+ }
1729
+ return blobs;
1730
+ }
1731
+
1648
1732
  // modules/payments-v2/inventory/presentation.ts
1649
1733
  function toToken(tokenId, snapshot, asset, registry, flags) {
1650
1734
  const iconUrl = registry.getIconUrl(asset.coinId);
@@ -1731,6 +1815,13 @@ function priceOne(asset, quote) {
1731
1815
  fiatValueEur: quote.priceEur != null ? human * quote.priceEur : null
1732
1816
  };
1733
1817
  }
1818
+ function transferringToken(tokenId, coinId, amount, registry, now) {
1819
+ const snapshot = { assets: [], createdAt: now, updatedAt: now };
1820
+ return toToken(tokenId, snapshot, { coinId, amount: amount.toString() }, registry, {
1821
+ transferring: true,
1822
+ suspectedSpent: false
1823
+ });
1824
+ }
1734
1825
 
1735
1826
  // modules/payments-v2/machine/payload.ts
1736
1827
  function buildPayload(recipientPubkey, request, spend, spentStates) {
@@ -2819,6 +2910,15 @@ var InventoryView = class {
2819
2910
  }
2820
2911
  return out;
2821
2912
  }
2913
+ /**
2914
+ * Current mirrored state of an ACTIVE token, or undefined when it is unknown
2915
+ * or tombstoned. Exists so a cached blob can be state-scoped (F6): a blob is
2916
+ * only reusable while the token still sits at the state it was fetched at.
2917
+ */
2918
+ stateHashOf(tokenId) {
2919
+ const entry = this.mirror.get(tokenId);
2920
+ return entry !== void 0 && entry.status === "active" ? entry.stateHash : void 0;
2921
+ }
2822
2922
  tokens(registry, filter) {
2823
2923
  const out = [];
2824
2924
  for (const [tokenId, entry] of this.mirror) {
@@ -3830,6 +3930,31 @@ var SpendQueue = class {
3830
3930
  }
3831
3931
  return { kind: "queued", settled: this.enqueue(reservationId, request.coinId, amount) };
3832
3932
  }
3933
+ /**
3934
+ * The sources `plan()` WOULD pick right now, reserving nothing and queueing
3935
+ * nothing — a read-only probe used to warm blobs while the user is still
3936
+ * looking at the confirm screen.
3937
+ *
3938
+ * Deliberately side-effect free: reserving here would pin the user's balance
3939
+ * across think-time and strand it if they never press send. The consequence is
3940
+ * that the real `plan()` may pick differently (inventory moves, another send
3941
+ * reserves first); callers must treat the result as a HINT and tolerate a miss,
3942
+ * never as a commitment that these sources will be spent.
3943
+ */
3944
+ previewSelection(request) {
3945
+ if (this.destroyed) return [];
3946
+ let amount;
3947
+ try {
3948
+ amount = parseAmount(request.amount);
3949
+ } catch {
3950
+ return [];
3951
+ }
3952
+ const { freeView, freeTotal } = this.freeView(request.coinId);
3953
+ if (freeTotal < amount) return [];
3954
+ const plan = selectCoins(freeView, amount, { workBudget: this.deps.workBudget });
3955
+ if (plan === null) return [];
3956
+ return [...plan.direct, ...plan.split !== void 0 ? [plan.split.tokenId] : []];
3957
+ }
3833
3958
  // Coverage math: free + Σ declared expected change of in-flight sends —
3834
3959
  // never the full amount of in-flight sources (§5.3).
3835
3960
  declareExpectedChange(transferId, coinId, amount) {
@@ -4179,6 +4304,7 @@ var PaymentsFacade = class {
4179
4304
  now: () => this.nowMs()
4180
4305
  });
4181
4306
  }
4307
+ prewarmed = new PrewarmCache();
4182
4308
  view;
4183
4309
  ledger;
4184
4310
  queue;
@@ -4272,6 +4398,15 @@ var PaymentsFacade = class {
4272
4398
  return derivePendingTransfers(this.machineStores, this.machineDeps.decryptPayload);
4273
4399
  }
4274
4400
  // ── money movement ─────────────────────────────────────────────────────────
4401
+ async prewarmSend(request) {
4402
+ await warmSendSources(this.warmDeps(), request);
4403
+ }
4404
+ discardPrewarm() {
4405
+ this.prewarmed.clear();
4406
+ }
4407
+ warmDeps() {
4408
+ return { queue: this.queue, view: this.view, storagePort: this.deps.storagePort, cache: this.prewarmed };
4409
+ }
4275
4410
  send(request) {
4276
4411
  return this.track(this.sendOutcome(request));
4277
4412
  }
@@ -4481,7 +4616,7 @@ var PaymentsFacade = class {
4481
4616
  }
4482
4617
  async materialize(transferId, recipientPubkey, request, spend, sourceIds) {
4483
4618
  const engine = this.engine();
4484
- const blobs = await this.deps.storagePort.getBlobs([...sourceIds]);
4619
+ const blobs = await takeSourceBlobs(this.warmDeps(), sourceIds);
4485
4620
  const sources = /* @__PURE__ */ new Map();
4486
4621
  const sourceTokens = [];
4487
4622
  const spentStates = {};
@@ -4494,7 +4629,7 @@ var PaymentsFacade = class {
4494
4629
  const keys = await engine.deliveryKeys(bytes);
4495
4630
  spentStates[tokenId] = { local: keys.stateHash, protocol: keys.stateHash };
4496
4631
  sources.set(tokenId, token);
4497
- sourceTokens.push(this.toUiToken(tokenId, request.coinId, engine.balanceOf(token, request.coinId)));
4632
+ sourceTokens.push(transferringToken(tokenId, request.coinId, engine.balanceOf(token, request.coinId), this.deps.registry, this.nowMs()));
4498
4633
  }
4499
4634
  const payload = buildPayload(recipientPubkey, request, spend, spentStates);
4500
4635
  const plan = {
@@ -4638,7 +4773,7 @@ var PaymentsFacade = class {
4638
4773
  await this.machineStores.mintJournal.upsert(entry);
4639
4774
  let token;
4640
4775
  try {
4641
- token = await engine.mint(this.mintParams(coinId, amount), { transferId: mintId });
4776
+ token = await engine.mint(mintParams(this.ownPubkeyBytes, coinId, amount), { transferId: mintId });
4642
4777
  } catch (err) {
4643
4778
  this.heartbeat.arm();
4644
4779
  return { success: false, error: messageOf(err) };
@@ -4697,7 +4832,7 @@ var PaymentsFacade = class {
4697
4832
  });
4698
4833
  return false;
4699
4834
  }
4700
- const token = await engine.mint(this.mintParams(entry.coinId, BigInt(entry.amount)), {
4835
+ const token = await engine.mint(mintParams(this.ownPubkeyBytes, entry.coinId, BigInt(entry.amount)), {
4701
4836
  transferId: entry.mintId
4702
4837
  });
4703
4838
  if (token.blob.tokenId !== entry.tokenId) {
@@ -4706,12 +4841,6 @@ var PaymentsFacade = class {
4706
4841
  await this.finalizeMint(engine, entry.mintId, token, entry.coinId, entry.amount);
4707
4842
  return true;
4708
4843
  }
4709
- mintParams(coinId, amount) {
4710
- return {
4711
- recipientPubkey: this.ownPubkeyBytes,
4712
- value: { assets: [{ coinId, amount }] }
4713
- };
4714
- }
4715
4844
  async tokenInServerInventory(tokenId) {
4716
4845
  let page = await this.deps.storagePort.listInventory();
4717
4846
  for (let i = 0; i < INVENTORY_SCAN_PAGE_LIMIT; i++) {
@@ -4740,12 +4869,6 @@ var PaymentsFacade = class {
4740
4869
  page = await this.deps.storagePort.listInventory(page.cursor);
4741
4870
  }
4742
4871
  }
4743
- toUiToken(tokenId, coinId, amount) {
4744
- const now = this.nowMs();
4745
- const snapshot = { assets: [], createdAt: now, updatedAt: now };
4746
- const flags = { transferring: true, suspectedSpent: false };
4747
- return toToken(tokenId, snapshot, { coinId, amount: amount.toString() }, this.deps.registry, flags);
4748
- }
4749
4872
  track(op) {
4750
4873
  const settled = op.then(() => void 0, () => void 0);
4751
4874
  this.pendingOps.add(settled);