@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.d.cts
CHANGED
|
@@ -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;
|
package/dist/core/index.d.ts
CHANGED
|
@@ -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;
|
package/dist/core/index.js
CHANGED
|
@@ -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) {
|
|
@@ -12784,6 +12884,7 @@ var InventoryView = class {
|
|
|
12784
12884
|
|
|
12785
12885
|
// modules/payments-v2/receive/Receive.ts
|
|
12786
12886
|
var ACK_BATCH_SIZE = 200;
|
|
12887
|
+
var REFRESH_INTERVAL_MS = 750;
|
|
12787
12888
|
var POLL_INTERVAL_MS = 3e4;
|
|
12788
12889
|
var ATTENTION_CLAIM_CONFLICT = "claim:conflict";
|
|
12789
12890
|
function receivedDedupKey(tokenId, stateHash) {
|
|
@@ -12800,6 +12901,7 @@ var Receive = class {
|
|
|
12800
12901
|
drainFlight = new SingleFlight();
|
|
12801
12902
|
pollTimer = null;
|
|
12802
12903
|
unsubscribeWake = null;
|
|
12904
|
+
lastRefreshAt = 0;
|
|
12803
12905
|
drainOnce() {
|
|
12804
12906
|
return this.drainFlight.run(() => this.doDrain());
|
|
12805
12907
|
}
|
|
@@ -12817,6 +12919,14 @@ var Receive = class {
|
|
|
12817
12919
|
this.unsubscribeWake?.();
|
|
12818
12920
|
this.unsubscribeWake = null;
|
|
12819
12921
|
}
|
|
12922
|
+
/** Ask for a mirror refresh at most once per REFRESH_INTERVAL_MS. */
|
|
12923
|
+
maybeRefresh(deps) {
|
|
12924
|
+
if (deps.refreshView === void 0) return;
|
|
12925
|
+
const now = deps.now?.() ?? Date.now();
|
|
12926
|
+
if (now - this.lastRefreshAt < REFRESH_INTERVAL_MS) return;
|
|
12927
|
+
this.lastRefreshAt = now;
|
|
12928
|
+
deps.refreshView();
|
|
12929
|
+
}
|
|
12820
12930
|
async doDrain() {
|
|
12821
12931
|
const deps = this.deps;
|
|
12822
12932
|
const engine = deps.engine();
|
|
@@ -12830,6 +12940,7 @@ var Receive = class {
|
|
|
12830
12940
|
for await (const entry of deps.delivery.incoming(basis === null ? void 0 : String(basis.cursor))) {
|
|
12831
12941
|
await this.processEntry(deps, engine, entry, pending, stored);
|
|
12832
12942
|
if (pending.length >= ACK_BATCH_SIZE) await flushAcks(deps, pending, pageEpoch);
|
|
12943
|
+
if (stored.length > 0) this.maybeRefresh(deps);
|
|
12833
12944
|
}
|
|
12834
12945
|
await flushAcks(deps, pending, pageEpoch);
|
|
12835
12946
|
const served = deps.delivery.incomingEpoch();
|
|
@@ -13516,6 +13627,31 @@ var SpendQueue = class {
|
|
|
13516
13627
|
}
|
|
13517
13628
|
return { kind: "queued", settled: this.enqueue(reservationId, request.coinId, amount) };
|
|
13518
13629
|
}
|
|
13630
|
+
/**
|
|
13631
|
+
* The sources `plan()` WOULD pick right now, reserving nothing and queueing
|
|
13632
|
+
* nothing — a read-only probe used to warm blobs while the user is still
|
|
13633
|
+
* looking at the confirm screen.
|
|
13634
|
+
*
|
|
13635
|
+
* Deliberately side-effect free: reserving here would pin the user's balance
|
|
13636
|
+
* across think-time and strand it if they never press send. The consequence is
|
|
13637
|
+
* that the real `plan()` may pick differently (inventory moves, another send
|
|
13638
|
+
* reserves first); callers must treat the result as a HINT and tolerate a miss,
|
|
13639
|
+
* never as a commitment that these sources will be spent.
|
|
13640
|
+
*/
|
|
13641
|
+
previewSelection(request) {
|
|
13642
|
+
if (this.destroyed) return [];
|
|
13643
|
+
let amount;
|
|
13644
|
+
try {
|
|
13645
|
+
amount = parseAmount(request.amount);
|
|
13646
|
+
} catch {
|
|
13647
|
+
return [];
|
|
13648
|
+
}
|
|
13649
|
+
const { freeView, freeTotal } = this.freeView(request.coinId);
|
|
13650
|
+
if (freeTotal < amount) return [];
|
|
13651
|
+
const plan = selectCoins(freeView, amount, { workBudget: this.deps.workBudget });
|
|
13652
|
+
if (plan === null) return [];
|
|
13653
|
+
return [...plan.direct, ...plan.split !== void 0 ? [plan.split.tokenId] : []];
|
|
13654
|
+
}
|
|
13519
13655
|
// Coverage math: free + Σ declared expected change of in-flight sends —
|
|
13520
13656
|
// never the full amount of in-flight sources (§5.3).
|
|
13521
13657
|
declareExpectedChange(transferId, coinId, amount) {
|
|
@@ -13704,7 +13840,7 @@ function composeFacadeParts(deps, hooks) {
|
|
|
13704
13840
|
machineDeps,
|
|
13705
13841
|
machine: new TransferMachine(machineDeps),
|
|
13706
13842
|
heldStates,
|
|
13707
|
-
receiveLoop: buildReceive(deps, hooks, historyStore, heldStates),
|
|
13843
|
+
receiveLoop: buildReceive(deps, hooks, historyStore, heldStates, view),
|
|
13708
13844
|
requests: buildRequests(deps, hooks),
|
|
13709
13845
|
restoreDeps: buildRestoreDeps(deps, machineDeps, machineStores, view)
|
|
13710
13846
|
};
|
|
@@ -13771,7 +13907,7 @@ function buildMachineDeps(deps, hooks, historyStore) {
|
|
|
13771
13907
|
}
|
|
13772
13908
|
};
|
|
13773
13909
|
}
|
|
13774
|
-
function buildReceive(deps, hooks, historyStore, heldStates) {
|
|
13910
|
+
function buildReceive(deps, hooks, historyStore, heldStates, view) {
|
|
13775
13911
|
return new Receive({
|
|
13776
13912
|
delivery: deps.deliveryPort,
|
|
13777
13913
|
engine: () => hooks.engine(),
|
|
@@ -13785,6 +13921,11 @@ function buildReceive(deps, hooks, historyStore, heldStates) {
|
|
|
13785
13921
|
registry: deps.registry,
|
|
13786
13922
|
recordReceived: (record) => recordReceived(historyStore, record),
|
|
13787
13923
|
emit: (event, transfer) => deps.emit(event, transfer),
|
|
13924
|
+
// The mirror is the balance the wallet renders; refresh it as the drain runs
|
|
13925
|
+
// so a multi-token receive lands progressively instead of all at the end.
|
|
13926
|
+
refreshView: () => {
|
|
13927
|
+
void view.delta().catch(() => void 0);
|
|
13928
|
+
},
|
|
13788
13929
|
attention: (transferId, code, detail) => {
|
|
13789
13930
|
deps.emit(
|
|
13790
13931
|
"transfer:attention",
|
|
@@ -13865,6 +14006,7 @@ var PaymentsFacade = class {
|
|
|
13865
14006
|
now: () => this.nowMs()
|
|
13866
14007
|
});
|
|
13867
14008
|
}
|
|
14009
|
+
prewarmed = new PrewarmCache();
|
|
13868
14010
|
view;
|
|
13869
14011
|
ledger;
|
|
13870
14012
|
queue;
|
|
@@ -13958,6 +14100,15 @@ var PaymentsFacade = class {
|
|
|
13958
14100
|
return derivePendingTransfers(this.machineStores, this.machineDeps.decryptPayload);
|
|
13959
14101
|
}
|
|
13960
14102
|
// ── money movement ─────────────────────────────────────────────────────────
|
|
14103
|
+
async prewarmSend(request) {
|
|
14104
|
+
await warmSendSources(this.warmDeps(), request);
|
|
14105
|
+
}
|
|
14106
|
+
discardPrewarm() {
|
|
14107
|
+
this.prewarmed.clear();
|
|
14108
|
+
}
|
|
14109
|
+
warmDeps() {
|
|
14110
|
+
return { queue: this.queue, view: this.view, storagePort: this.deps.storagePort, cache: this.prewarmed };
|
|
14111
|
+
}
|
|
13961
14112
|
send(request) {
|
|
13962
14113
|
return this.track(this.sendOutcome(request));
|
|
13963
14114
|
}
|
|
@@ -14167,7 +14318,7 @@ var PaymentsFacade = class {
|
|
|
14167
14318
|
}
|
|
14168
14319
|
async materialize(transferId, recipientPubkey, request, spend, sourceIds) {
|
|
14169
14320
|
const engine = this.engine();
|
|
14170
|
-
const blobs = await this.
|
|
14321
|
+
const blobs = await takeSourceBlobs(this.warmDeps(), sourceIds);
|
|
14171
14322
|
const sources = /* @__PURE__ */ new Map();
|
|
14172
14323
|
const sourceTokens = [];
|
|
14173
14324
|
const spentStates = {};
|
|
@@ -14180,7 +14331,7 @@ var PaymentsFacade = class {
|
|
|
14180
14331
|
const keys = await engine.deliveryKeys(bytes);
|
|
14181
14332
|
spentStates[tokenId] = { local: keys.stateHash, protocol: keys.stateHash };
|
|
14182
14333
|
sources.set(tokenId, token);
|
|
14183
|
-
sourceTokens.push(
|
|
14334
|
+
sourceTokens.push(transferringToken(tokenId, request.coinId, engine.balanceOf(token, request.coinId), this.deps.registry, this.nowMs()));
|
|
14184
14335
|
}
|
|
14185
14336
|
const payload = buildPayload(recipientPubkey, request, spend, spentStates);
|
|
14186
14337
|
const plan = {
|
|
@@ -14324,7 +14475,7 @@ var PaymentsFacade = class {
|
|
|
14324
14475
|
await this.machineStores.mintJournal.upsert(entry);
|
|
14325
14476
|
let token;
|
|
14326
14477
|
try {
|
|
14327
|
-
token = await engine.mint(this.
|
|
14478
|
+
token = await engine.mint(mintParams(this.ownPubkeyBytes, coinId, amount), { transferId: mintId });
|
|
14328
14479
|
} catch (err) {
|
|
14329
14480
|
this.heartbeat.arm();
|
|
14330
14481
|
return { success: false, error: messageOf(err) };
|
|
@@ -14383,7 +14534,7 @@ var PaymentsFacade = class {
|
|
|
14383
14534
|
});
|
|
14384
14535
|
return false;
|
|
14385
14536
|
}
|
|
14386
|
-
const token = await engine.mint(this.
|
|
14537
|
+
const token = await engine.mint(mintParams(this.ownPubkeyBytes, entry.coinId, BigInt(entry.amount)), {
|
|
14387
14538
|
transferId: entry.mintId
|
|
14388
14539
|
});
|
|
14389
14540
|
if (token.blob.tokenId !== entry.tokenId) {
|
|
@@ -14392,12 +14543,6 @@ var PaymentsFacade = class {
|
|
|
14392
14543
|
await this.finalizeMint(engine, entry.mintId, token, entry.coinId, entry.amount);
|
|
14393
14544
|
return true;
|
|
14394
14545
|
}
|
|
14395
|
-
mintParams(coinId, amount) {
|
|
14396
|
-
return {
|
|
14397
|
-
recipientPubkey: this.ownPubkeyBytes,
|
|
14398
|
-
value: { assets: [{ coinId, amount }] }
|
|
14399
|
-
};
|
|
14400
|
-
}
|
|
14401
14546
|
async tokenInServerInventory(tokenId) {
|
|
14402
14547
|
let page = await this.deps.storagePort.listInventory();
|
|
14403
14548
|
for (let i = 0; i < INVENTORY_SCAN_PAGE_LIMIT; i++) {
|
|
@@ -14426,12 +14571,6 @@ var PaymentsFacade = class {
|
|
|
14426
14571
|
page = await this.deps.storagePort.listInventory(page.cursor);
|
|
14427
14572
|
}
|
|
14428
14573
|
}
|
|
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
14574
|
track(op) {
|
|
14436
14575
|
const settled = op.then(() => void 0, () => void 0);
|
|
14437
14576
|
this.pendingOps.add(settled);
|