@unicitylabs/sphere-sdk 0.14.10 → 0.14.11-dev.2
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/connect/index.cjs +1 -1
- package/dist/connect/index.cjs.map +1 -1
- package/dist/connect/index.js +1 -1
- package/dist/connect/index.js.map +1 -1
- package/dist/core/index.cjs +157 -18
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +2 -0
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +157 -18
- package/dist/core/index.js.map +1 -1
- package/dist/impl/browser/connect/index.cjs +1 -1
- package/dist/impl/browser/connect/index.cjs.map +1 -1
- package/dist/impl/browser/connect/index.js +1 -1
- package/dist/impl/browser/connect/index.js.map +1 -1
- package/dist/index.cjs +157 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +157 -18
- package/dist/index.js.map +1 -1
- package/dist/modules/payments-v2/index.cjs +157 -18
- package/dist/modules/payments-v2/index.cjs.map +1 -1
- package/dist/modules/payments-v2/index.d.cts +6 -2
- package/dist/modules/payments-v2/index.d.ts +6 -2
- package/dist/modules/payments-v2/index.js +157 -18
- package/dist/modules/payments-v2/index.js.map +1 -1
- package/package.json +1 -1
package/dist/core/index.cjs
CHANGED
|
@@ -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) {
|
|
@@ -12870,6 +12970,7 @@ var InventoryView = class {
|
|
|
12870
12970
|
|
|
12871
12971
|
// modules/payments-v2/receive/Receive.ts
|
|
12872
12972
|
var ACK_BATCH_SIZE = 200;
|
|
12973
|
+
var REFRESH_INTERVAL_MS = 750;
|
|
12873
12974
|
var POLL_INTERVAL_MS = 3e4;
|
|
12874
12975
|
var ATTENTION_CLAIM_CONFLICT = "claim:conflict";
|
|
12875
12976
|
function receivedDedupKey(tokenId, stateHash) {
|
|
@@ -12886,6 +12987,7 @@ var Receive = class {
|
|
|
12886
12987
|
drainFlight = new SingleFlight();
|
|
12887
12988
|
pollTimer = null;
|
|
12888
12989
|
unsubscribeWake = null;
|
|
12990
|
+
lastRefreshAt = 0;
|
|
12889
12991
|
drainOnce() {
|
|
12890
12992
|
return this.drainFlight.run(() => this.doDrain());
|
|
12891
12993
|
}
|
|
@@ -12903,6 +13005,14 @@ var Receive = class {
|
|
|
12903
13005
|
this.unsubscribeWake?.();
|
|
12904
13006
|
this.unsubscribeWake = null;
|
|
12905
13007
|
}
|
|
13008
|
+
/** Ask for a mirror refresh at most once per REFRESH_INTERVAL_MS. */
|
|
13009
|
+
maybeRefresh(deps) {
|
|
13010
|
+
if (deps.refreshView === void 0) return;
|
|
13011
|
+
const now = deps.now?.() ?? Date.now();
|
|
13012
|
+
if (now - this.lastRefreshAt < REFRESH_INTERVAL_MS) return;
|
|
13013
|
+
this.lastRefreshAt = now;
|
|
13014
|
+
deps.refreshView();
|
|
13015
|
+
}
|
|
12906
13016
|
async doDrain() {
|
|
12907
13017
|
const deps = this.deps;
|
|
12908
13018
|
const engine = deps.engine();
|
|
@@ -12916,6 +13026,7 @@ var Receive = class {
|
|
|
12916
13026
|
for await (const entry of deps.delivery.incoming(basis === null ? void 0 : String(basis.cursor))) {
|
|
12917
13027
|
await this.processEntry(deps, engine, entry, pending, stored);
|
|
12918
13028
|
if (pending.length >= ACK_BATCH_SIZE) await flushAcks(deps, pending, pageEpoch);
|
|
13029
|
+
if (stored.length > 0) this.maybeRefresh(deps);
|
|
12919
13030
|
}
|
|
12920
13031
|
await flushAcks(deps, pending, pageEpoch);
|
|
12921
13032
|
const served = deps.delivery.incomingEpoch();
|
|
@@ -13602,6 +13713,31 @@ var SpendQueue = class {
|
|
|
13602
13713
|
}
|
|
13603
13714
|
return { kind: "queued", settled: this.enqueue(reservationId, request.coinId, amount) };
|
|
13604
13715
|
}
|
|
13716
|
+
/**
|
|
13717
|
+
* The sources `plan()` WOULD pick right now, reserving nothing and queueing
|
|
13718
|
+
* nothing — a read-only probe used to warm blobs while the user is still
|
|
13719
|
+
* looking at the confirm screen.
|
|
13720
|
+
*
|
|
13721
|
+
* Deliberately side-effect free: reserving here would pin the user's balance
|
|
13722
|
+
* across think-time and strand it if they never press send. The consequence is
|
|
13723
|
+
* that the real `plan()` may pick differently (inventory moves, another send
|
|
13724
|
+
* reserves first); callers must treat the result as a HINT and tolerate a miss,
|
|
13725
|
+
* never as a commitment that these sources will be spent.
|
|
13726
|
+
*/
|
|
13727
|
+
previewSelection(request) {
|
|
13728
|
+
if (this.destroyed) return [];
|
|
13729
|
+
let amount;
|
|
13730
|
+
try {
|
|
13731
|
+
amount = parseAmount(request.amount);
|
|
13732
|
+
} catch {
|
|
13733
|
+
return [];
|
|
13734
|
+
}
|
|
13735
|
+
const { freeView, freeTotal } = this.freeView(request.coinId);
|
|
13736
|
+
if (freeTotal < amount) return [];
|
|
13737
|
+
const plan = selectCoins(freeView, amount, { workBudget: this.deps.workBudget });
|
|
13738
|
+
if (plan === null) return [];
|
|
13739
|
+
return [...plan.direct, ...plan.split !== void 0 ? [plan.split.tokenId] : []];
|
|
13740
|
+
}
|
|
13605
13741
|
// Coverage math: free + Σ declared expected change of in-flight sends —
|
|
13606
13742
|
// never the full amount of in-flight sources (§5.3).
|
|
13607
13743
|
declareExpectedChange(transferId, coinId, amount) {
|
|
@@ -13790,7 +13926,7 @@ function composeFacadeParts(deps, hooks) {
|
|
|
13790
13926
|
machineDeps,
|
|
13791
13927
|
machine: new TransferMachine(machineDeps),
|
|
13792
13928
|
heldStates,
|
|
13793
|
-
receiveLoop: buildReceive(deps, hooks, historyStore, heldStates),
|
|
13929
|
+
receiveLoop: buildReceive(deps, hooks, historyStore, heldStates, view),
|
|
13794
13930
|
requests: buildRequests(deps, hooks),
|
|
13795
13931
|
restoreDeps: buildRestoreDeps(deps, machineDeps, machineStores, view)
|
|
13796
13932
|
};
|
|
@@ -13857,7 +13993,7 @@ function buildMachineDeps(deps, hooks, historyStore) {
|
|
|
13857
13993
|
}
|
|
13858
13994
|
};
|
|
13859
13995
|
}
|
|
13860
|
-
function buildReceive(deps, hooks, historyStore, heldStates) {
|
|
13996
|
+
function buildReceive(deps, hooks, historyStore, heldStates, view) {
|
|
13861
13997
|
return new Receive({
|
|
13862
13998
|
delivery: deps.deliveryPort,
|
|
13863
13999
|
engine: () => hooks.engine(),
|
|
@@ -13871,6 +14007,11 @@ function buildReceive(deps, hooks, historyStore, heldStates) {
|
|
|
13871
14007
|
registry: deps.registry,
|
|
13872
14008
|
recordReceived: (record) => recordReceived(historyStore, record),
|
|
13873
14009
|
emit: (event, transfer) => deps.emit(event, transfer),
|
|
14010
|
+
// The mirror is the balance the wallet renders; refresh it as the drain runs
|
|
14011
|
+
// so a multi-token receive lands progressively instead of all at the end.
|
|
14012
|
+
refreshView: () => {
|
|
14013
|
+
void view.delta().catch(() => void 0);
|
|
14014
|
+
},
|
|
13874
14015
|
attention: (transferId, code, detail) => {
|
|
13875
14016
|
deps.emit(
|
|
13876
14017
|
"transfer:attention",
|
|
@@ -13951,6 +14092,7 @@ var PaymentsFacade = class {
|
|
|
13951
14092
|
now: () => this.nowMs()
|
|
13952
14093
|
});
|
|
13953
14094
|
}
|
|
14095
|
+
prewarmed = new PrewarmCache();
|
|
13954
14096
|
view;
|
|
13955
14097
|
ledger;
|
|
13956
14098
|
queue;
|
|
@@ -14044,6 +14186,15 @@ var PaymentsFacade = class {
|
|
|
14044
14186
|
return derivePendingTransfers(this.machineStores, this.machineDeps.decryptPayload);
|
|
14045
14187
|
}
|
|
14046
14188
|
// ── money movement ─────────────────────────────────────────────────────────
|
|
14189
|
+
async prewarmSend(request) {
|
|
14190
|
+
await warmSendSources(this.warmDeps(), request);
|
|
14191
|
+
}
|
|
14192
|
+
discardPrewarm() {
|
|
14193
|
+
this.prewarmed.clear();
|
|
14194
|
+
}
|
|
14195
|
+
warmDeps() {
|
|
14196
|
+
return { queue: this.queue, view: this.view, storagePort: this.deps.storagePort, cache: this.prewarmed };
|
|
14197
|
+
}
|
|
14047
14198
|
send(request) {
|
|
14048
14199
|
return this.track(this.sendOutcome(request));
|
|
14049
14200
|
}
|
|
@@ -14253,7 +14404,7 @@ var PaymentsFacade = class {
|
|
|
14253
14404
|
}
|
|
14254
14405
|
async materialize(transferId, recipientPubkey, request, spend, sourceIds) {
|
|
14255
14406
|
const engine = this.engine();
|
|
14256
|
-
const blobs = await this.
|
|
14407
|
+
const blobs = await takeSourceBlobs(this.warmDeps(), sourceIds);
|
|
14257
14408
|
const sources = /* @__PURE__ */ new Map();
|
|
14258
14409
|
const sourceTokens = [];
|
|
14259
14410
|
const spentStates = {};
|
|
@@ -14266,7 +14417,7 @@ var PaymentsFacade = class {
|
|
|
14266
14417
|
const keys = await engine.deliveryKeys(bytes);
|
|
14267
14418
|
spentStates[tokenId] = { local: keys.stateHash, protocol: keys.stateHash };
|
|
14268
14419
|
sources.set(tokenId, token);
|
|
14269
|
-
sourceTokens.push(
|
|
14420
|
+
sourceTokens.push(transferringToken(tokenId, request.coinId, engine.balanceOf(token, request.coinId), this.deps.registry, this.nowMs()));
|
|
14270
14421
|
}
|
|
14271
14422
|
const payload = buildPayload(recipientPubkey, request, spend, spentStates);
|
|
14272
14423
|
const plan = {
|
|
@@ -14410,7 +14561,7 @@ var PaymentsFacade = class {
|
|
|
14410
14561
|
await this.machineStores.mintJournal.upsert(entry);
|
|
14411
14562
|
let token;
|
|
14412
14563
|
try {
|
|
14413
|
-
token = await engine.mint(this.
|
|
14564
|
+
token = await engine.mint(mintParams(this.ownPubkeyBytes, coinId, amount), { transferId: mintId });
|
|
14414
14565
|
} catch (err) {
|
|
14415
14566
|
this.heartbeat.arm();
|
|
14416
14567
|
return { success: false, error: messageOf(err) };
|
|
@@ -14469,7 +14620,7 @@ var PaymentsFacade = class {
|
|
|
14469
14620
|
});
|
|
14470
14621
|
return false;
|
|
14471
14622
|
}
|
|
14472
|
-
const token = await engine.mint(this.
|
|
14623
|
+
const token = await engine.mint(mintParams(this.ownPubkeyBytes, entry.coinId, BigInt(entry.amount)), {
|
|
14473
14624
|
transferId: entry.mintId
|
|
14474
14625
|
});
|
|
14475
14626
|
if (token.blob.tokenId !== entry.tokenId) {
|
|
@@ -14478,12 +14629,6 @@ var PaymentsFacade = class {
|
|
|
14478
14629
|
await this.finalizeMint(engine, entry.mintId, token, entry.coinId, entry.amount);
|
|
14479
14630
|
return true;
|
|
14480
14631
|
}
|
|
14481
|
-
mintParams(coinId, amount) {
|
|
14482
|
-
return {
|
|
14483
|
-
recipientPubkey: this.ownPubkeyBytes,
|
|
14484
|
-
value: { assets: [{ coinId, amount }] }
|
|
14485
|
-
};
|
|
14486
|
-
}
|
|
14487
14632
|
async tokenInServerInventory(tokenId) {
|
|
14488
14633
|
let page = await this.deps.storagePort.listInventory();
|
|
14489
14634
|
for (let i = 0; i < INVENTORY_SCAN_PAGE_LIMIT; i++) {
|
|
@@ -14512,12 +14657,6 @@ var PaymentsFacade = class {
|
|
|
14512
14657
|
page = await this.deps.storagePort.listInventory(page.cursor);
|
|
14513
14658
|
}
|
|
14514
14659
|
}
|
|
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
14660
|
track(op) {
|
|
14522
14661
|
const settled = op.then(() => void 0, () => void 0);
|
|
14523
14662
|
this.pendingOps.add(settled);
|