@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
|
@@ -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) {
|
|
@@ -3098,6 +3198,7 @@ var logger = {
|
|
|
3098
3198
|
|
|
3099
3199
|
// modules/payments-v2/receive/Receive.ts
|
|
3100
3200
|
var ACK_BATCH_SIZE = 200;
|
|
3201
|
+
var REFRESH_INTERVAL_MS = 750;
|
|
3101
3202
|
var POLL_INTERVAL_MS = 3e4;
|
|
3102
3203
|
var ATTENTION_CLAIM_CONFLICT = "claim:conflict";
|
|
3103
3204
|
function receivedDedupKey(tokenId, stateHash) {
|
|
@@ -3114,6 +3215,7 @@ var Receive = class {
|
|
|
3114
3215
|
drainFlight = new SingleFlight();
|
|
3115
3216
|
pollTimer = null;
|
|
3116
3217
|
unsubscribeWake = null;
|
|
3218
|
+
lastRefreshAt = 0;
|
|
3117
3219
|
drainOnce() {
|
|
3118
3220
|
return this.drainFlight.run(() => this.doDrain());
|
|
3119
3221
|
}
|
|
@@ -3131,6 +3233,14 @@ var Receive = class {
|
|
|
3131
3233
|
this.unsubscribeWake?.();
|
|
3132
3234
|
this.unsubscribeWake = null;
|
|
3133
3235
|
}
|
|
3236
|
+
/** Ask for a mirror refresh at most once per REFRESH_INTERVAL_MS. */
|
|
3237
|
+
maybeRefresh(deps) {
|
|
3238
|
+
if (deps.refreshView === void 0) return;
|
|
3239
|
+
const now = deps.now?.() ?? Date.now();
|
|
3240
|
+
if (now - this.lastRefreshAt < REFRESH_INTERVAL_MS) return;
|
|
3241
|
+
this.lastRefreshAt = now;
|
|
3242
|
+
deps.refreshView();
|
|
3243
|
+
}
|
|
3134
3244
|
async doDrain() {
|
|
3135
3245
|
const deps = this.deps;
|
|
3136
3246
|
const engine = deps.engine();
|
|
@@ -3144,6 +3254,7 @@ var Receive = class {
|
|
|
3144
3254
|
for await (const entry of deps.delivery.incoming(basis === null ? void 0 : String(basis.cursor))) {
|
|
3145
3255
|
await this.processEntry(deps, engine, entry, pending, stored);
|
|
3146
3256
|
if (pending.length >= ACK_BATCH_SIZE) await flushAcks(deps, pending, pageEpoch);
|
|
3257
|
+
if (stored.length > 0) this.maybeRefresh(deps);
|
|
3147
3258
|
}
|
|
3148
3259
|
await flushAcks(deps, pending, pageEpoch);
|
|
3149
3260
|
const served = deps.delivery.incomingEpoch();
|
|
@@ -3830,6 +3941,31 @@ var SpendQueue = class {
|
|
|
3830
3941
|
}
|
|
3831
3942
|
return { kind: "queued", settled: this.enqueue(reservationId, request.coinId, amount) };
|
|
3832
3943
|
}
|
|
3944
|
+
/**
|
|
3945
|
+
* The sources `plan()` WOULD pick right now, reserving nothing and queueing
|
|
3946
|
+
* nothing — a read-only probe used to warm blobs while the user is still
|
|
3947
|
+
* looking at the confirm screen.
|
|
3948
|
+
*
|
|
3949
|
+
* Deliberately side-effect free: reserving here would pin the user's balance
|
|
3950
|
+
* across think-time and strand it if they never press send. The consequence is
|
|
3951
|
+
* that the real `plan()` may pick differently (inventory moves, another send
|
|
3952
|
+
* reserves first); callers must treat the result as a HINT and tolerate a miss,
|
|
3953
|
+
* never as a commitment that these sources will be spent.
|
|
3954
|
+
*/
|
|
3955
|
+
previewSelection(request) {
|
|
3956
|
+
if (this.destroyed) return [];
|
|
3957
|
+
let amount;
|
|
3958
|
+
try {
|
|
3959
|
+
amount = parseAmount(request.amount);
|
|
3960
|
+
} catch {
|
|
3961
|
+
return [];
|
|
3962
|
+
}
|
|
3963
|
+
const { freeView, freeTotal } = this.freeView(request.coinId);
|
|
3964
|
+
if (freeTotal < amount) return [];
|
|
3965
|
+
const plan = selectCoins(freeView, amount, { workBudget: this.deps.workBudget });
|
|
3966
|
+
if (plan === null) return [];
|
|
3967
|
+
return [...plan.direct, ...plan.split !== void 0 ? [plan.split.tokenId] : []];
|
|
3968
|
+
}
|
|
3833
3969
|
// Coverage math: free + Σ declared expected change of in-flight sends —
|
|
3834
3970
|
// never the full amount of in-flight sources (§5.3).
|
|
3835
3971
|
declareExpectedChange(transferId, coinId, amount) {
|
|
@@ -4018,7 +4154,7 @@ function composeFacadeParts(deps, hooks) {
|
|
|
4018
4154
|
machineDeps,
|
|
4019
4155
|
machine: new TransferMachine(machineDeps),
|
|
4020
4156
|
heldStates,
|
|
4021
|
-
receiveLoop: buildReceive(deps, hooks, historyStore, heldStates),
|
|
4157
|
+
receiveLoop: buildReceive(deps, hooks, historyStore, heldStates, view),
|
|
4022
4158
|
requests: buildRequests(deps, hooks),
|
|
4023
4159
|
restoreDeps: buildRestoreDeps(deps, machineDeps, machineStores, view)
|
|
4024
4160
|
};
|
|
@@ -4085,7 +4221,7 @@ function buildMachineDeps(deps, hooks, historyStore) {
|
|
|
4085
4221
|
}
|
|
4086
4222
|
};
|
|
4087
4223
|
}
|
|
4088
|
-
function buildReceive(deps, hooks, historyStore, heldStates) {
|
|
4224
|
+
function buildReceive(deps, hooks, historyStore, heldStates, view) {
|
|
4089
4225
|
return new Receive({
|
|
4090
4226
|
delivery: deps.deliveryPort,
|
|
4091
4227
|
engine: () => hooks.engine(),
|
|
@@ -4099,6 +4235,11 @@ function buildReceive(deps, hooks, historyStore, heldStates) {
|
|
|
4099
4235
|
registry: deps.registry,
|
|
4100
4236
|
recordReceived: (record) => recordReceived(historyStore, record),
|
|
4101
4237
|
emit: (event, transfer) => deps.emit(event, transfer),
|
|
4238
|
+
// The mirror is the balance the wallet renders; refresh it as the drain runs
|
|
4239
|
+
// so a multi-token receive lands progressively instead of all at the end.
|
|
4240
|
+
refreshView: () => {
|
|
4241
|
+
void view.delta().catch(() => void 0);
|
|
4242
|
+
},
|
|
4102
4243
|
attention: (transferId, code, detail) => {
|
|
4103
4244
|
deps.emit(
|
|
4104
4245
|
"transfer:attention",
|
|
@@ -4179,6 +4320,7 @@ var PaymentsFacade = class {
|
|
|
4179
4320
|
now: () => this.nowMs()
|
|
4180
4321
|
});
|
|
4181
4322
|
}
|
|
4323
|
+
prewarmed = new PrewarmCache();
|
|
4182
4324
|
view;
|
|
4183
4325
|
ledger;
|
|
4184
4326
|
queue;
|
|
@@ -4272,6 +4414,15 @@ var PaymentsFacade = class {
|
|
|
4272
4414
|
return derivePendingTransfers(this.machineStores, this.machineDeps.decryptPayload);
|
|
4273
4415
|
}
|
|
4274
4416
|
// ── money movement ─────────────────────────────────────────────────────────
|
|
4417
|
+
async prewarmSend(request) {
|
|
4418
|
+
await warmSendSources(this.warmDeps(), request);
|
|
4419
|
+
}
|
|
4420
|
+
discardPrewarm() {
|
|
4421
|
+
this.prewarmed.clear();
|
|
4422
|
+
}
|
|
4423
|
+
warmDeps() {
|
|
4424
|
+
return { queue: this.queue, view: this.view, storagePort: this.deps.storagePort, cache: this.prewarmed };
|
|
4425
|
+
}
|
|
4275
4426
|
send(request) {
|
|
4276
4427
|
return this.track(this.sendOutcome(request));
|
|
4277
4428
|
}
|
|
@@ -4481,7 +4632,7 @@ var PaymentsFacade = class {
|
|
|
4481
4632
|
}
|
|
4482
4633
|
async materialize(transferId, recipientPubkey, request, spend, sourceIds) {
|
|
4483
4634
|
const engine = this.engine();
|
|
4484
|
-
const blobs = await this.
|
|
4635
|
+
const blobs = await takeSourceBlobs(this.warmDeps(), sourceIds);
|
|
4485
4636
|
const sources = /* @__PURE__ */ new Map();
|
|
4486
4637
|
const sourceTokens = [];
|
|
4487
4638
|
const spentStates = {};
|
|
@@ -4494,7 +4645,7 @@ var PaymentsFacade = class {
|
|
|
4494
4645
|
const keys = await engine.deliveryKeys(bytes);
|
|
4495
4646
|
spentStates[tokenId] = { local: keys.stateHash, protocol: keys.stateHash };
|
|
4496
4647
|
sources.set(tokenId, token);
|
|
4497
|
-
sourceTokens.push(
|
|
4648
|
+
sourceTokens.push(transferringToken(tokenId, request.coinId, engine.balanceOf(token, request.coinId), this.deps.registry, this.nowMs()));
|
|
4498
4649
|
}
|
|
4499
4650
|
const payload = buildPayload(recipientPubkey, request, spend, spentStates);
|
|
4500
4651
|
const plan = {
|
|
@@ -4638,7 +4789,7 @@ var PaymentsFacade = class {
|
|
|
4638
4789
|
await this.machineStores.mintJournal.upsert(entry);
|
|
4639
4790
|
let token;
|
|
4640
4791
|
try {
|
|
4641
|
-
token = await engine.mint(this.
|
|
4792
|
+
token = await engine.mint(mintParams(this.ownPubkeyBytes, coinId, amount), { transferId: mintId });
|
|
4642
4793
|
} catch (err) {
|
|
4643
4794
|
this.heartbeat.arm();
|
|
4644
4795
|
return { success: false, error: messageOf(err) };
|
|
@@ -4697,7 +4848,7 @@ var PaymentsFacade = class {
|
|
|
4697
4848
|
});
|
|
4698
4849
|
return false;
|
|
4699
4850
|
}
|
|
4700
|
-
const token = await engine.mint(this.
|
|
4851
|
+
const token = await engine.mint(mintParams(this.ownPubkeyBytes, entry.coinId, BigInt(entry.amount)), {
|
|
4701
4852
|
transferId: entry.mintId
|
|
4702
4853
|
});
|
|
4703
4854
|
if (token.blob.tokenId !== entry.tokenId) {
|
|
@@ -4706,12 +4857,6 @@ var PaymentsFacade = class {
|
|
|
4706
4857
|
await this.finalizeMint(engine, entry.mintId, token, entry.coinId, entry.amount);
|
|
4707
4858
|
return true;
|
|
4708
4859
|
}
|
|
4709
|
-
mintParams(coinId, amount) {
|
|
4710
|
-
return {
|
|
4711
|
-
recipientPubkey: this.ownPubkeyBytes,
|
|
4712
|
-
value: { assets: [{ coinId, amount }] }
|
|
4713
|
-
};
|
|
4714
|
-
}
|
|
4715
4860
|
async tokenInServerInventory(tokenId) {
|
|
4716
4861
|
let page = await this.deps.storagePort.listInventory();
|
|
4717
4862
|
for (let i = 0; i < INVENTORY_SCAN_PAGE_LIMIT; i++) {
|
|
@@ -4740,12 +4885,6 @@ var PaymentsFacade = class {
|
|
|
4740
4885
|
page = await this.deps.storagePort.listInventory(page.cursor);
|
|
4741
4886
|
}
|
|
4742
4887
|
}
|
|
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
4888
|
track(op) {
|
|
4750
4889
|
const settled = op.then(() => void 0, () => void 0);
|
|
4751
4890
|
this.pendingOps.add(settled);
|