@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.
@@ -167,6 +167,8 @@ interface PendingTransfer {
167
167
  }
168
168
  type ConnectionStatus$1 = 'connected' | 'degraded' | 'offline';
169
169
  interface PaymentsV2 {
170
+ prewarmSend(request: SendRequest): Promise<void>;
171
+ discardPrewarm(): void;
170
172
  assets(coinId?: string): Promise<Asset[]>;
171
173
  tokens(filter?: {
172
174
  coinId?: string;
@@ -167,6 +167,8 @@ interface PendingTransfer {
167
167
  }
168
168
  type ConnectionStatus$1 = 'connected' | 'degraded' | 'offline';
169
169
  interface PaymentsV2 {
170
+ prewarmSend(request: SendRequest): Promise<void>;
171
+ discardPrewarm(): void;
170
172
  assets(coinId?: string): Promise<Asset[]>;
171
173
  tokens(filter?: {
172
174
  coinId?: string;
@@ -12273,6 +12273,90 @@ async function splitOpIndex(deps, envelope) {
12273
12273
  return Array.isArray(payload.direct) ? payload.direct.length : 0;
12274
12274
  }
12275
12275
 
12276
+ // modules/payments-v2/mint-params.ts
12277
+ function mintParams(recipientPubkey, coinId, amount) {
12278
+ return { recipientPubkey, value: { assets: [{ coinId, amount }] } };
12279
+ }
12280
+
12281
+ // modules/payments-v2/prewarm-cache.ts
12282
+ var PrewarmCache = class _PrewarmCache {
12283
+ entries = /* @__PURE__ */ new Map();
12284
+ generation = 0;
12285
+ static key(tokenId, stateHash) {
12286
+ return `${tokenId}:${stateHash}`;
12287
+ }
12288
+ /** Claim the cache for a warm about to start; supersedes any warm already running. */
12289
+ begin() {
12290
+ this.entries.clear();
12291
+ this.generation += 1;
12292
+ return this.generation;
12293
+ }
12294
+ /**
12295
+ * Publish a warm's result, unless it was cancelled or superseded while its
12296
+ * fetch was in flight — a cancel that only cleared what had landed so far
12297
+ * would be undone by the continuation of the very warm it cancelled.
12298
+ */
12299
+ commit(generation, warmed) {
12300
+ if (generation !== this.generation) return;
12301
+ for (const [tokenId, stateHash, bytes] of warmed) {
12302
+ this.entries.set(_PrewarmCache.key(tokenId, stateHash), bytes);
12303
+ }
12304
+ }
12305
+ get(tokenId, stateHash) {
12306
+ if (stateHash === void 0) return void 0;
12307
+ return this.entries.get(_PrewarmCache.key(tokenId, stateHash));
12308
+ }
12309
+ clear() {
12310
+ this.entries.clear();
12311
+ this.generation += 1;
12312
+ }
12313
+ get size() {
12314
+ return this.entries.size;
12315
+ }
12316
+ };
12317
+ async function warmSendSources(deps, request) {
12318
+ const ids = deps.queue.previewSelection(request);
12319
+ if (ids.length === 0) return;
12320
+ const generation = deps.cache.begin();
12321
+ const at = new Map(ids.map((id) => [id, deps.view.stateHashOf(id)]));
12322
+ let blobs;
12323
+ try {
12324
+ blobs = await deps.storagePort.getBlobs([...ids]);
12325
+ } catch {
12326
+ return;
12327
+ }
12328
+ const warmed = [];
12329
+ for (const [tokenId, bytes] of blobs) {
12330
+ const stateHash = at.get(tokenId);
12331
+ if (stateHash !== void 0) warmed.push([tokenId, stateHash, bytes]);
12332
+ }
12333
+ deps.cache.commit(generation, warmed);
12334
+ }
12335
+ async function takeSourceBlobs(deps, sourceIds) {
12336
+ const blobs = /* @__PURE__ */ new Map();
12337
+ const missing = [];
12338
+ let trustCache = deps.cache.size > 0;
12339
+ if (trustCache) {
12340
+ try {
12341
+ await deps.view.delta();
12342
+ } catch {
12343
+ trustCache = false;
12344
+ }
12345
+ }
12346
+ for (const tokenId of sourceIds) {
12347
+ const warm = trustCache ? deps.cache.get(tokenId, deps.view.stateHashOf(tokenId)) : void 0;
12348
+ if (warm === void 0) missing.push(tokenId);
12349
+ else blobs.set(tokenId, warm);
12350
+ }
12351
+ deps.cache.clear();
12352
+ if (missing.length > 0) {
12353
+ for (const [tokenId, bytes] of await deps.storagePort.getBlobs(missing)) {
12354
+ blobs.set(tokenId, bytes);
12355
+ }
12356
+ }
12357
+ return blobs;
12358
+ }
12359
+
12276
12360
  // modules/payments-v2/inventory/presentation.ts
12277
12361
  function toToken(tokenId, snapshot, asset, registry, flags) {
12278
12362
  const iconUrl = registry.getIconUrl(asset.coinId);
@@ -12359,6 +12443,13 @@ function priceOne(asset, quote) {
12359
12443
  fiatValueEur: quote.priceEur != null ? human * quote.priceEur : null
12360
12444
  };
12361
12445
  }
12446
+ function transferringToken(tokenId, coinId, amount, registry, now) {
12447
+ const snapshot = { assets: [], createdAt: now, updatedAt: now };
12448
+ return toToken(tokenId, snapshot, { coinId, amount: amount.toString() }, registry, {
12449
+ transferring: true,
12450
+ suspectedSpent: false
12451
+ });
12452
+ }
12362
12453
 
12363
12454
  // modules/payments-v2/machine/payload.ts
12364
12455
  function buildPayload(recipientPubkey, request, spend, spentStates) {
@@ -12597,6 +12688,15 @@ var InventoryView = class {
12597
12688
  }
12598
12689
  return out;
12599
12690
  }
12691
+ /**
12692
+ * Current mirrored state of an ACTIVE token, or undefined when it is unknown
12693
+ * or tombstoned. Exists so a cached blob can be state-scoped (F6): a blob is
12694
+ * only reusable while the token still sits at the state it was fetched at.
12695
+ */
12696
+ stateHashOf(tokenId) {
12697
+ const entry = this.mirror.get(tokenId);
12698
+ return entry !== void 0 && entry.status === "active" ? entry.stateHash : void 0;
12699
+ }
12600
12700
  tokens(registry, filter) {
12601
12701
  const out = [];
12602
12702
  for (const [tokenId, entry] of this.mirror) {
@@ -13516,6 +13616,31 @@ var SpendQueue = class {
13516
13616
  }
13517
13617
  return { kind: "queued", settled: this.enqueue(reservationId, request.coinId, amount) };
13518
13618
  }
13619
+ /**
13620
+ * The sources `plan()` WOULD pick right now, reserving nothing and queueing
13621
+ * nothing — a read-only probe used to warm blobs while the user is still
13622
+ * looking at the confirm screen.
13623
+ *
13624
+ * Deliberately side-effect free: reserving here would pin the user's balance
13625
+ * across think-time and strand it if they never press send. The consequence is
13626
+ * that the real `plan()` may pick differently (inventory moves, another send
13627
+ * reserves first); callers must treat the result as a HINT and tolerate a miss,
13628
+ * never as a commitment that these sources will be spent.
13629
+ */
13630
+ previewSelection(request) {
13631
+ if (this.destroyed) return [];
13632
+ let amount;
13633
+ try {
13634
+ amount = parseAmount(request.amount);
13635
+ } catch {
13636
+ return [];
13637
+ }
13638
+ const { freeView, freeTotal } = this.freeView(request.coinId);
13639
+ if (freeTotal < amount) return [];
13640
+ const plan = selectCoins(freeView, amount, { workBudget: this.deps.workBudget });
13641
+ if (plan === null) return [];
13642
+ return [...plan.direct, ...plan.split !== void 0 ? [plan.split.tokenId] : []];
13643
+ }
13519
13644
  // Coverage math: free + Σ declared expected change of in-flight sends —
13520
13645
  // never the full amount of in-flight sources (§5.3).
13521
13646
  declareExpectedChange(transferId, coinId, amount) {
@@ -13865,6 +13990,7 @@ var PaymentsFacade = class {
13865
13990
  now: () => this.nowMs()
13866
13991
  });
13867
13992
  }
13993
+ prewarmed = new PrewarmCache();
13868
13994
  view;
13869
13995
  ledger;
13870
13996
  queue;
@@ -13958,6 +14084,15 @@ var PaymentsFacade = class {
13958
14084
  return derivePendingTransfers(this.machineStores, this.machineDeps.decryptPayload);
13959
14085
  }
13960
14086
  // ── money movement ─────────────────────────────────────────────────────────
14087
+ async prewarmSend(request) {
14088
+ await warmSendSources(this.warmDeps(), request);
14089
+ }
14090
+ discardPrewarm() {
14091
+ this.prewarmed.clear();
14092
+ }
14093
+ warmDeps() {
14094
+ return { queue: this.queue, view: this.view, storagePort: this.deps.storagePort, cache: this.prewarmed };
14095
+ }
13961
14096
  send(request) {
13962
14097
  return this.track(this.sendOutcome(request));
13963
14098
  }
@@ -14167,7 +14302,7 @@ var PaymentsFacade = class {
14167
14302
  }
14168
14303
  async materialize(transferId, recipientPubkey, request, spend, sourceIds) {
14169
14304
  const engine = this.engine();
14170
- const blobs = await this.deps.storagePort.getBlobs([...sourceIds]);
14305
+ const blobs = await takeSourceBlobs(this.warmDeps(), sourceIds);
14171
14306
  const sources = /* @__PURE__ */ new Map();
14172
14307
  const sourceTokens = [];
14173
14308
  const spentStates = {};
@@ -14180,7 +14315,7 @@ var PaymentsFacade = class {
14180
14315
  const keys = await engine.deliveryKeys(bytes);
14181
14316
  spentStates[tokenId] = { local: keys.stateHash, protocol: keys.stateHash };
14182
14317
  sources.set(tokenId, token);
14183
- sourceTokens.push(this.toUiToken(tokenId, request.coinId, engine.balanceOf(token, request.coinId)));
14318
+ sourceTokens.push(transferringToken(tokenId, request.coinId, engine.balanceOf(token, request.coinId), this.deps.registry, this.nowMs()));
14184
14319
  }
14185
14320
  const payload = buildPayload(recipientPubkey, request, spend, spentStates);
14186
14321
  const plan = {
@@ -14324,7 +14459,7 @@ var PaymentsFacade = class {
14324
14459
  await this.machineStores.mintJournal.upsert(entry);
14325
14460
  let token;
14326
14461
  try {
14327
- token = await engine.mint(this.mintParams(coinId, amount), { transferId: mintId });
14462
+ token = await engine.mint(mintParams(this.ownPubkeyBytes, coinId, amount), { transferId: mintId });
14328
14463
  } catch (err) {
14329
14464
  this.heartbeat.arm();
14330
14465
  return { success: false, error: messageOf(err) };
@@ -14383,7 +14518,7 @@ var PaymentsFacade = class {
14383
14518
  });
14384
14519
  return false;
14385
14520
  }
14386
- const token = await engine.mint(this.mintParams(entry.coinId, BigInt(entry.amount)), {
14521
+ const token = await engine.mint(mintParams(this.ownPubkeyBytes, entry.coinId, BigInt(entry.amount)), {
14387
14522
  transferId: entry.mintId
14388
14523
  });
14389
14524
  if (token.blob.tokenId !== entry.tokenId) {
@@ -14392,12 +14527,6 @@ var PaymentsFacade = class {
14392
14527
  await this.finalizeMint(engine, entry.mintId, token, entry.coinId, entry.amount);
14393
14528
  return true;
14394
14529
  }
14395
- mintParams(coinId, amount) {
14396
- return {
14397
- recipientPubkey: this.ownPubkeyBytes,
14398
- value: { assets: [{ coinId, amount }] }
14399
- };
14400
- }
14401
14530
  async tokenInServerInventory(tokenId) {
14402
14531
  let page = await this.deps.storagePort.listInventory();
14403
14532
  for (let i = 0; i < INVENTORY_SCAN_PAGE_LIMIT; i++) {
@@ -14426,12 +14555,6 @@ var PaymentsFacade = class {
14426
14555
  page = await this.deps.storagePort.listInventory(page.cursor);
14427
14556
  }
14428
14557
  }
14429
- toUiToken(tokenId, coinId, amount) {
14430
- const now = this.nowMs();
14431
- const snapshot = { assets: [], createdAt: now, updatedAt: now };
14432
- const flags = { transferring: true, suspectedSpent: false };
14433
- return toToken(tokenId, snapshot, { coinId, amount: amount.toString() }, this.deps.registry, flags);
14434
- }
14435
14558
  track(op) {
14436
14559
  const settled = op.then(() => void 0, () => void 0);
14437
14560
  this.pendingOps.add(settled);