@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/index.d.cts
CHANGED
|
@@ -166,6 +166,8 @@ interface PendingTransfer {
|
|
|
166
166
|
}
|
|
167
167
|
type ConnectionStatus$1 = 'connected' | 'degraded' | 'offline';
|
|
168
168
|
interface PaymentsV2 {
|
|
169
|
+
prewarmSend(request: SendRequest): Promise<void>;
|
|
170
|
+
discardPrewarm(): void;
|
|
169
171
|
assets(coinId?: string): Promise<Asset[]>;
|
|
170
172
|
tokens(filter?: {
|
|
171
173
|
coinId?: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -166,6 +166,8 @@ interface PendingTransfer {
|
|
|
166
166
|
}
|
|
167
167
|
type ConnectionStatus$1 = 'connected' | 'degraded' | 'offline';
|
|
168
168
|
interface PaymentsV2 {
|
|
169
|
+
prewarmSend(request: SendRequest): Promise<void>;
|
|
170
|
+
discardPrewarm(): void;
|
|
169
171
|
assets(coinId?: string): Promise<Asset[]>;
|
|
170
172
|
tokens(filter?: {
|
|
171
173
|
coinId?: string;
|
package/dist/index.js
CHANGED
|
@@ -12289,6 +12289,90 @@ async function splitOpIndex(deps, envelope) {
|
|
|
12289
12289
|
return Array.isArray(payload.direct) ? payload.direct.length : 0;
|
|
12290
12290
|
}
|
|
12291
12291
|
|
|
12292
|
+
// modules/payments-v2/mint-params.ts
|
|
12293
|
+
function mintParams(recipientPubkey, coinId, amount) {
|
|
12294
|
+
return { recipientPubkey, value: { assets: [{ coinId, amount }] } };
|
|
12295
|
+
}
|
|
12296
|
+
|
|
12297
|
+
// modules/payments-v2/prewarm-cache.ts
|
|
12298
|
+
var PrewarmCache = class _PrewarmCache {
|
|
12299
|
+
entries = /* @__PURE__ */ new Map();
|
|
12300
|
+
generation = 0;
|
|
12301
|
+
static key(tokenId, stateHash) {
|
|
12302
|
+
return `${tokenId}:${stateHash}`;
|
|
12303
|
+
}
|
|
12304
|
+
/** Claim the cache for a warm about to start; supersedes any warm already running. */
|
|
12305
|
+
begin() {
|
|
12306
|
+
this.entries.clear();
|
|
12307
|
+
this.generation += 1;
|
|
12308
|
+
return this.generation;
|
|
12309
|
+
}
|
|
12310
|
+
/**
|
|
12311
|
+
* Publish a warm's result, unless it was cancelled or superseded while its
|
|
12312
|
+
* fetch was in flight — a cancel that only cleared what had landed so far
|
|
12313
|
+
* would be undone by the continuation of the very warm it cancelled.
|
|
12314
|
+
*/
|
|
12315
|
+
commit(generation, warmed) {
|
|
12316
|
+
if (generation !== this.generation) return;
|
|
12317
|
+
for (const [tokenId, stateHash, bytes] of warmed) {
|
|
12318
|
+
this.entries.set(_PrewarmCache.key(tokenId, stateHash), bytes);
|
|
12319
|
+
}
|
|
12320
|
+
}
|
|
12321
|
+
get(tokenId, stateHash) {
|
|
12322
|
+
if (stateHash === void 0) return void 0;
|
|
12323
|
+
return this.entries.get(_PrewarmCache.key(tokenId, stateHash));
|
|
12324
|
+
}
|
|
12325
|
+
clear() {
|
|
12326
|
+
this.entries.clear();
|
|
12327
|
+
this.generation += 1;
|
|
12328
|
+
}
|
|
12329
|
+
get size() {
|
|
12330
|
+
return this.entries.size;
|
|
12331
|
+
}
|
|
12332
|
+
};
|
|
12333
|
+
async function warmSendSources(deps, request) {
|
|
12334
|
+
const ids = deps.queue.previewSelection(request);
|
|
12335
|
+
if (ids.length === 0) return;
|
|
12336
|
+
const generation = deps.cache.begin();
|
|
12337
|
+
const at = new Map(ids.map((id) => [id, deps.view.stateHashOf(id)]));
|
|
12338
|
+
let blobs;
|
|
12339
|
+
try {
|
|
12340
|
+
blobs = await deps.storagePort.getBlobs([...ids]);
|
|
12341
|
+
} catch {
|
|
12342
|
+
return;
|
|
12343
|
+
}
|
|
12344
|
+
const warmed = [];
|
|
12345
|
+
for (const [tokenId, bytes] of blobs) {
|
|
12346
|
+
const stateHash = at.get(tokenId);
|
|
12347
|
+
if (stateHash !== void 0) warmed.push([tokenId, stateHash, bytes]);
|
|
12348
|
+
}
|
|
12349
|
+
deps.cache.commit(generation, warmed);
|
|
12350
|
+
}
|
|
12351
|
+
async function takeSourceBlobs(deps, sourceIds) {
|
|
12352
|
+
const blobs = /* @__PURE__ */ new Map();
|
|
12353
|
+
const missing = [];
|
|
12354
|
+
let trustCache = deps.cache.size > 0;
|
|
12355
|
+
if (trustCache) {
|
|
12356
|
+
try {
|
|
12357
|
+
await deps.view.delta();
|
|
12358
|
+
} catch {
|
|
12359
|
+
trustCache = false;
|
|
12360
|
+
}
|
|
12361
|
+
}
|
|
12362
|
+
for (const tokenId of sourceIds) {
|
|
12363
|
+
const warm = trustCache ? deps.cache.get(tokenId, deps.view.stateHashOf(tokenId)) : void 0;
|
|
12364
|
+
if (warm === void 0) missing.push(tokenId);
|
|
12365
|
+
else blobs.set(tokenId, warm);
|
|
12366
|
+
}
|
|
12367
|
+
deps.cache.clear();
|
|
12368
|
+
if (missing.length > 0) {
|
|
12369
|
+
for (const [tokenId, bytes] of await deps.storagePort.getBlobs(missing)) {
|
|
12370
|
+
blobs.set(tokenId, bytes);
|
|
12371
|
+
}
|
|
12372
|
+
}
|
|
12373
|
+
return blobs;
|
|
12374
|
+
}
|
|
12375
|
+
|
|
12292
12376
|
// modules/payments-v2/inventory/presentation.ts
|
|
12293
12377
|
function toToken(tokenId, snapshot, asset, registry, flags) {
|
|
12294
12378
|
const iconUrl = registry.getIconUrl(asset.coinId);
|
|
@@ -12375,6 +12459,13 @@ function priceOne(asset, quote) {
|
|
|
12375
12459
|
fiatValueEur: quote.priceEur != null ? human * quote.priceEur : null
|
|
12376
12460
|
};
|
|
12377
12461
|
}
|
|
12462
|
+
function transferringToken(tokenId, coinId, amount, registry, now) {
|
|
12463
|
+
const snapshot = { assets: [], createdAt: now, updatedAt: now };
|
|
12464
|
+
return toToken(tokenId, snapshot, { coinId, amount: amount.toString() }, registry, {
|
|
12465
|
+
transferring: true,
|
|
12466
|
+
suspectedSpent: false
|
|
12467
|
+
});
|
|
12468
|
+
}
|
|
12378
12469
|
|
|
12379
12470
|
// modules/payments-v2/machine/payload.ts
|
|
12380
12471
|
function buildPayload(recipientPubkey, request, spend, spentStates) {
|
|
@@ -12613,6 +12704,15 @@ var InventoryView = class {
|
|
|
12613
12704
|
}
|
|
12614
12705
|
return out;
|
|
12615
12706
|
}
|
|
12707
|
+
/**
|
|
12708
|
+
* Current mirrored state of an ACTIVE token, or undefined when it is unknown
|
|
12709
|
+
* or tombstoned. Exists so a cached blob can be state-scoped (F6): a blob is
|
|
12710
|
+
* only reusable while the token still sits at the state it was fetched at.
|
|
12711
|
+
*/
|
|
12712
|
+
stateHashOf(tokenId) {
|
|
12713
|
+
const entry = this.mirror.get(tokenId);
|
|
12714
|
+
return entry !== void 0 && entry.status === "active" ? entry.stateHash : void 0;
|
|
12715
|
+
}
|
|
12616
12716
|
tokens(registry, filter) {
|
|
12617
12717
|
const out = [];
|
|
12618
12718
|
for (const [tokenId, entry] of this.mirror) {
|
|
@@ -12800,6 +12900,7 @@ var InventoryView = class {
|
|
|
12800
12900
|
|
|
12801
12901
|
// modules/payments-v2/receive/Receive.ts
|
|
12802
12902
|
var ACK_BATCH_SIZE = 200;
|
|
12903
|
+
var REFRESH_INTERVAL_MS = 750;
|
|
12803
12904
|
var POLL_INTERVAL_MS = 3e4;
|
|
12804
12905
|
var ATTENTION_CLAIM_CONFLICT = "claim:conflict";
|
|
12805
12906
|
function receivedDedupKey(tokenId, stateHash) {
|
|
@@ -12816,6 +12917,7 @@ var Receive = class {
|
|
|
12816
12917
|
drainFlight = new SingleFlight();
|
|
12817
12918
|
pollTimer = null;
|
|
12818
12919
|
unsubscribeWake = null;
|
|
12920
|
+
lastRefreshAt = 0;
|
|
12819
12921
|
drainOnce() {
|
|
12820
12922
|
return this.drainFlight.run(() => this.doDrain());
|
|
12821
12923
|
}
|
|
@@ -12833,6 +12935,14 @@ var Receive = class {
|
|
|
12833
12935
|
this.unsubscribeWake?.();
|
|
12834
12936
|
this.unsubscribeWake = null;
|
|
12835
12937
|
}
|
|
12938
|
+
/** Ask for a mirror refresh at most once per REFRESH_INTERVAL_MS. */
|
|
12939
|
+
maybeRefresh(deps) {
|
|
12940
|
+
if (deps.refreshView === void 0) return;
|
|
12941
|
+
const now = deps.now?.() ?? Date.now();
|
|
12942
|
+
if (now - this.lastRefreshAt < REFRESH_INTERVAL_MS) return;
|
|
12943
|
+
this.lastRefreshAt = now;
|
|
12944
|
+
deps.refreshView();
|
|
12945
|
+
}
|
|
12836
12946
|
async doDrain() {
|
|
12837
12947
|
const deps = this.deps;
|
|
12838
12948
|
const engine = deps.engine();
|
|
@@ -12846,6 +12956,7 @@ var Receive = class {
|
|
|
12846
12956
|
for await (const entry of deps.delivery.incoming(basis === null ? void 0 : String(basis.cursor))) {
|
|
12847
12957
|
await this.processEntry(deps, engine, entry, pending, stored);
|
|
12848
12958
|
if (pending.length >= ACK_BATCH_SIZE) await flushAcks(deps, pending, pageEpoch);
|
|
12959
|
+
if (stored.length > 0) this.maybeRefresh(deps);
|
|
12849
12960
|
}
|
|
12850
12961
|
await flushAcks(deps, pending, pageEpoch);
|
|
12851
12962
|
const served = deps.delivery.incomingEpoch();
|
|
@@ -13532,6 +13643,31 @@ var SpendQueue = class {
|
|
|
13532
13643
|
}
|
|
13533
13644
|
return { kind: "queued", settled: this.enqueue(reservationId, request.coinId, amount) };
|
|
13534
13645
|
}
|
|
13646
|
+
/**
|
|
13647
|
+
* The sources `plan()` WOULD pick right now, reserving nothing and queueing
|
|
13648
|
+
* nothing — a read-only probe used to warm blobs while the user is still
|
|
13649
|
+
* looking at the confirm screen.
|
|
13650
|
+
*
|
|
13651
|
+
* Deliberately side-effect free: reserving here would pin the user's balance
|
|
13652
|
+
* across think-time and strand it if they never press send. The consequence is
|
|
13653
|
+
* that the real `plan()` may pick differently (inventory moves, another send
|
|
13654
|
+
* reserves first); callers must treat the result as a HINT and tolerate a miss,
|
|
13655
|
+
* never as a commitment that these sources will be spent.
|
|
13656
|
+
*/
|
|
13657
|
+
previewSelection(request) {
|
|
13658
|
+
if (this.destroyed) return [];
|
|
13659
|
+
let amount;
|
|
13660
|
+
try {
|
|
13661
|
+
amount = parseAmount(request.amount);
|
|
13662
|
+
} catch {
|
|
13663
|
+
return [];
|
|
13664
|
+
}
|
|
13665
|
+
const { freeView, freeTotal } = this.freeView(request.coinId);
|
|
13666
|
+
if (freeTotal < amount) return [];
|
|
13667
|
+
const plan = selectCoins(freeView, amount, { workBudget: this.deps.workBudget });
|
|
13668
|
+
if (plan === null) return [];
|
|
13669
|
+
return [...plan.direct, ...plan.split !== void 0 ? [plan.split.tokenId] : []];
|
|
13670
|
+
}
|
|
13535
13671
|
// Coverage math: free + Σ declared expected change of in-flight sends —
|
|
13536
13672
|
// never the full amount of in-flight sources (§5.3).
|
|
13537
13673
|
declareExpectedChange(transferId, coinId, amount) {
|
|
@@ -13720,7 +13856,7 @@ function composeFacadeParts(deps, hooks) {
|
|
|
13720
13856
|
machineDeps,
|
|
13721
13857
|
machine: new TransferMachine(machineDeps),
|
|
13722
13858
|
heldStates,
|
|
13723
|
-
receiveLoop: buildReceive(deps, hooks, historyStore, heldStates),
|
|
13859
|
+
receiveLoop: buildReceive(deps, hooks, historyStore, heldStates, view),
|
|
13724
13860
|
requests: buildRequests(deps, hooks),
|
|
13725
13861
|
restoreDeps: buildRestoreDeps(deps, machineDeps, machineStores, view)
|
|
13726
13862
|
};
|
|
@@ -13787,7 +13923,7 @@ function buildMachineDeps(deps, hooks, historyStore) {
|
|
|
13787
13923
|
}
|
|
13788
13924
|
};
|
|
13789
13925
|
}
|
|
13790
|
-
function buildReceive(deps, hooks, historyStore, heldStates) {
|
|
13926
|
+
function buildReceive(deps, hooks, historyStore, heldStates, view) {
|
|
13791
13927
|
return new Receive({
|
|
13792
13928
|
delivery: deps.deliveryPort,
|
|
13793
13929
|
engine: () => hooks.engine(),
|
|
@@ -13801,6 +13937,11 @@ function buildReceive(deps, hooks, historyStore, heldStates) {
|
|
|
13801
13937
|
registry: deps.registry,
|
|
13802
13938
|
recordReceived: (record) => recordReceived(historyStore, record),
|
|
13803
13939
|
emit: (event, transfer) => deps.emit(event, transfer),
|
|
13940
|
+
// The mirror is the balance the wallet renders; refresh it as the drain runs
|
|
13941
|
+
// so a multi-token receive lands progressively instead of all at the end.
|
|
13942
|
+
refreshView: () => {
|
|
13943
|
+
void view.delta().catch(() => void 0);
|
|
13944
|
+
},
|
|
13804
13945
|
attention: (transferId, code, detail) => {
|
|
13805
13946
|
deps.emit(
|
|
13806
13947
|
"transfer:attention",
|
|
@@ -13881,6 +14022,7 @@ var PaymentsFacade = class {
|
|
|
13881
14022
|
now: () => this.nowMs()
|
|
13882
14023
|
});
|
|
13883
14024
|
}
|
|
14025
|
+
prewarmed = new PrewarmCache();
|
|
13884
14026
|
view;
|
|
13885
14027
|
ledger;
|
|
13886
14028
|
queue;
|
|
@@ -13974,6 +14116,15 @@ var PaymentsFacade = class {
|
|
|
13974
14116
|
return derivePendingTransfers(this.machineStores, this.machineDeps.decryptPayload);
|
|
13975
14117
|
}
|
|
13976
14118
|
// ── money movement ─────────────────────────────────────────────────────────
|
|
14119
|
+
async prewarmSend(request) {
|
|
14120
|
+
await warmSendSources(this.warmDeps(), request);
|
|
14121
|
+
}
|
|
14122
|
+
discardPrewarm() {
|
|
14123
|
+
this.prewarmed.clear();
|
|
14124
|
+
}
|
|
14125
|
+
warmDeps() {
|
|
14126
|
+
return { queue: this.queue, view: this.view, storagePort: this.deps.storagePort, cache: this.prewarmed };
|
|
14127
|
+
}
|
|
13977
14128
|
send(request) {
|
|
13978
14129
|
return this.track(this.sendOutcome(request));
|
|
13979
14130
|
}
|
|
@@ -14183,7 +14334,7 @@ var PaymentsFacade = class {
|
|
|
14183
14334
|
}
|
|
14184
14335
|
async materialize(transferId, recipientPubkey, request, spend, sourceIds) {
|
|
14185
14336
|
const engine = this.engine();
|
|
14186
|
-
const blobs = await this.
|
|
14337
|
+
const blobs = await takeSourceBlobs(this.warmDeps(), sourceIds);
|
|
14187
14338
|
const sources = /* @__PURE__ */ new Map();
|
|
14188
14339
|
const sourceTokens = [];
|
|
14189
14340
|
const spentStates = {};
|
|
@@ -14196,7 +14347,7 @@ var PaymentsFacade = class {
|
|
|
14196
14347
|
const keys = await engine.deliveryKeys(bytes);
|
|
14197
14348
|
spentStates[tokenId] = { local: keys.stateHash, protocol: keys.stateHash };
|
|
14198
14349
|
sources.set(tokenId, token);
|
|
14199
|
-
sourceTokens.push(
|
|
14350
|
+
sourceTokens.push(transferringToken(tokenId, request.coinId, engine.balanceOf(token, request.coinId), this.deps.registry, this.nowMs()));
|
|
14200
14351
|
}
|
|
14201
14352
|
const payload = buildPayload(recipientPubkey, request, spend, spentStates);
|
|
14202
14353
|
const plan = {
|
|
@@ -14340,7 +14491,7 @@ var PaymentsFacade = class {
|
|
|
14340
14491
|
await this.machineStores.mintJournal.upsert(entry);
|
|
14341
14492
|
let token;
|
|
14342
14493
|
try {
|
|
14343
|
-
token = await engine.mint(this.
|
|
14494
|
+
token = await engine.mint(mintParams(this.ownPubkeyBytes, coinId, amount), { transferId: mintId });
|
|
14344
14495
|
} catch (err) {
|
|
14345
14496
|
this.heartbeat.arm();
|
|
14346
14497
|
return { success: false, error: messageOf(err) };
|
|
@@ -14399,7 +14550,7 @@ var PaymentsFacade = class {
|
|
|
14399
14550
|
});
|
|
14400
14551
|
return false;
|
|
14401
14552
|
}
|
|
14402
|
-
const token = await engine.mint(this.
|
|
14553
|
+
const token = await engine.mint(mintParams(this.ownPubkeyBytes, entry.coinId, BigInt(entry.amount)), {
|
|
14403
14554
|
transferId: entry.mintId
|
|
14404
14555
|
});
|
|
14405
14556
|
if (token.blob.tokenId !== entry.tokenId) {
|
|
@@ -14408,12 +14559,6 @@ var PaymentsFacade = class {
|
|
|
14408
14559
|
await this.finalizeMint(engine, entry.mintId, token, entry.coinId, entry.amount);
|
|
14409
14560
|
return true;
|
|
14410
14561
|
}
|
|
14411
|
-
mintParams(coinId, amount) {
|
|
14412
|
-
return {
|
|
14413
|
-
recipientPubkey: this.ownPubkeyBytes,
|
|
14414
|
-
value: { assets: [{ coinId, amount }] }
|
|
14415
|
-
};
|
|
14416
|
-
}
|
|
14417
14562
|
async tokenInServerInventory(tokenId) {
|
|
14418
14563
|
let page = await this.deps.storagePort.listInventory();
|
|
14419
14564
|
for (let i = 0; i < INVENTORY_SCAN_PAGE_LIMIT; i++) {
|
|
@@ -14442,12 +14587,6 @@ var PaymentsFacade = class {
|
|
|
14442
14587
|
page = await this.deps.storagePort.listInventory(page.cursor);
|
|
14443
14588
|
}
|
|
14444
14589
|
}
|
|
14445
|
-
toUiToken(tokenId, coinId, amount) {
|
|
14446
|
-
const now = this.nowMs();
|
|
14447
|
-
const snapshot = { assets: [], createdAt: now, updatedAt: now };
|
|
14448
|
-
const flags = { transferring: true, suspectedSpent: false };
|
|
14449
|
-
return toToken(tokenId, snapshot, { coinId, amount: amount.toString() }, this.deps.registry, flags);
|
|
14450
|
-
}
|
|
14451
14590
|
track(op) {
|
|
14452
14591
|
const settled = op.then(() => void 0, () => void 0);
|
|
14453
14592
|
this.pendingOps.add(settled);
|