@unicitylabs/sphere-sdk 0.14.0-dev.6 → 0.14.0
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 +416 -223
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +111 -108
- package/dist/core/index.d.ts +111 -108
- package/dist/core/index.js +416 -223
- 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/impl/wallet-api-v2/index.cjs +38 -5
- package/dist/impl/wallet-api-v2/index.cjs.map +1 -1
- package/dist/impl/wallet-api-v2/index.d.cts +23 -2
- package/dist/impl/wallet-api-v2/index.d.ts +23 -2
- package/dist/impl/wallet-api-v2/index.js +38 -5
- package/dist/impl/wallet-api-v2/index.js.map +1 -1
- package/dist/index.cjs +416 -223
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +112 -109
- package/dist/index.d.ts +112 -109
- package/dist/index.js +416 -223
- package/dist/index.js.map +1 -1
- package/dist/modules/payments-v2/index.cjs +340 -199
- package/dist/modules/payments-v2/index.cjs.map +1 -1
- package/dist/modules/payments-v2/index.d.cts +68 -19
- package/dist/modules/payments-v2/index.d.ts +68 -19
- package/dist/modules/payments-v2/index.js +339 -199
- package/dist/modules/payments-v2/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -12083,14 +12083,14 @@ var IntentResume = class {
|
|
|
12083
12083
|
}
|
|
12084
12084
|
let changeOutput = null;
|
|
12085
12085
|
if (payload.split) {
|
|
12086
|
-
const
|
|
12086
|
+
const splitOpIndex2 = payload.direct.length;
|
|
12087
12087
|
let splitDelivered = false;
|
|
12088
12088
|
{
|
|
12089
12089
|
const source = await engine.decodeToken(await provider.getToken(payload.split.tokenId));
|
|
12090
12090
|
const outcome = await this.host.certifyOperation(
|
|
12091
12091
|
{
|
|
12092
12092
|
kind: "split",
|
|
12093
|
-
opIndex:
|
|
12093
|
+
opIndex: splitOpIndex2,
|
|
12094
12094
|
uiTokenId: `v2_${payload.split.tokenId}`,
|
|
12095
12095
|
genesisId: payload.split.tokenId,
|
|
12096
12096
|
sourceSdkData: "",
|
|
@@ -22201,6 +22201,7 @@ var AccountingModule = class _AccountingModule {
|
|
|
22201
22201
|
const unsubHistory = deps.on(
|
|
22202
22202
|
"history:updated",
|
|
22203
22203
|
(entry) => {
|
|
22204
|
+
if (!("dedupKey" in entry)) return;
|
|
22204
22205
|
this._handleHistoryUpdated(entry).catch((err) => {
|
|
22205
22206
|
logger.warn(LOG_TAG2, "Error handling history:updated event:", err);
|
|
22206
22207
|
});
|
|
@@ -27463,7 +27464,10 @@ var STORE_KEYS = {
|
|
|
27463
27464
|
shortfalls: "shortfalls",
|
|
27464
27465
|
settlingLinks: "settling",
|
|
27465
27466
|
streamCursor: (s) => `cursor:${s}`,
|
|
27466
|
-
epochLatch: "epoch-latch"
|
|
27467
|
+
epochLatch: "epoch-latch",
|
|
27468
|
+
// §5.2 InventoryView durable overlays (#625/#679).
|
|
27469
|
+
suspectedSpent: "suspected-spent",
|
|
27470
|
+
knownSpends: "known-spends"
|
|
27467
27471
|
};
|
|
27468
27472
|
|
|
27469
27473
|
// modules/payments-v2/machine/journal.ts
|
|
@@ -27713,7 +27717,7 @@ var TransferMachine = class {
|
|
|
27713
27717
|
}
|
|
27714
27718
|
if (cls === null) {
|
|
27715
27719
|
await this.applyCommitted(engine, plan.transferId, committed);
|
|
27716
|
-
this.closeTail(plan.transferId, plan.payload);
|
|
27720
|
+
this.closeTail(engine, plan.transferId, plan.payload, committed);
|
|
27717
27721
|
return { committed, deliveryPending };
|
|
27718
27722
|
}
|
|
27719
27723
|
if (cls === "conflict" && committed.length > 0) {
|
|
@@ -27739,10 +27743,10 @@ var TransferMachine = class {
|
|
|
27739
27743
|
}
|
|
27740
27744
|
await this.applyCommitted(engine, r.transferId, committed);
|
|
27741
27745
|
if (conflicts.length > 0) {
|
|
27742
|
-
return { deliveryPending, partial: await this.settlePartial(r, committed, conflicts) };
|
|
27746
|
+
return { deliveryPending, partial: await this.settlePartial(engine, r, committed, conflicts) };
|
|
27743
27747
|
}
|
|
27744
27748
|
await this.completeIntent(r.transferId, r.payload);
|
|
27745
|
-
await this.finishBookkeeping(r.transferId, r.payload, "resumed");
|
|
27749
|
+
await this.finishBookkeeping(engine, r.transferId, r.payload, "resumed", committed);
|
|
27746
27750
|
return { deliveryPending, partial: null };
|
|
27747
27751
|
}
|
|
27748
27752
|
// ── shared transitions ─────────────────────────────────────────────────────
|
|
@@ -27924,7 +27928,7 @@ var TransferMachine = class {
|
|
|
27924
27928
|
}
|
|
27925
27929
|
return added;
|
|
27926
27930
|
}
|
|
27927
|
-
async settlePartial(r, committed, conflicts) {
|
|
27931
|
+
async settlePartial(engine, r, committed, conflicts) {
|
|
27928
27932
|
const undelivered = conflicts.reduce((sum, c) => sum + c.amount, 0n);
|
|
27929
27933
|
const entry = {
|
|
27930
27934
|
transferId: r.transferId,
|
|
@@ -27936,26 +27940,27 @@ var TransferMachine = class {
|
|
|
27936
27940
|
};
|
|
27937
27941
|
await this.stores.shortfalls.upsert(entry);
|
|
27938
27942
|
await this.completeIntent(r.transferId, r.payload);
|
|
27939
|
-
await this.finishBookkeeping(r.transferId, r.payload, "resumed");
|
|
27943
|
+
await this.finishBookkeeping(engine, r.transferId, r.payload, "resumed", committed);
|
|
27940
27944
|
return entry;
|
|
27941
27945
|
}
|
|
27942
|
-
closeTail(transferId, payload) {
|
|
27946
|
+
closeTail(engine, transferId, payload, committed) {
|
|
27943
27947
|
void (async () => {
|
|
27944
27948
|
try {
|
|
27945
27949
|
await this.completeIntent(transferId, payload);
|
|
27946
27950
|
} catch {
|
|
27947
27951
|
return;
|
|
27948
27952
|
}
|
|
27949
|
-
await this.finishBookkeeping(transferId, payload, "sent");
|
|
27953
|
+
await this.finishBookkeeping(engine, transferId, payload, "sent", committed);
|
|
27950
27954
|
})();
|
|
27951
27955
|
}
|
|
27952
27956
|
async completeIntent(transferId, payload) {
|
|
27953
27957
|
const signature = payload.split !== void 0 ? await this.deps.signComplete(transferId) : void 0;
|
|
27954
27958
|
await this.deps.intents.complete(transferId, signature);
|
|
27955
27959
|
}
|
|
27956
|
-
async finishBookkeeping(transferId, payload, phase) {
|
|
27960
|
+
async finishBookkeeping(engine, transferId, payload, phase, committed) {
|
|
27957
27961
|
try {
|
|
27958
|
-
await this.
|
|
27962
|
+
const committedAmount = await this.settledAmount(engine, payload.coinId, committed);
|
|
27963
|
+
await this.deps.recordHistory?.({ transferId, payload, phase, committedAmount });
|
|
27959
27964
|
} catch {
|
|
27960
27965
|
}
|
|
27961
27966
|
try {
|
|
@@ -27963,6 +27968,26 @@ var TransferMachine = class {
|
|
|
27963
27968
|
} catch {
|
|
27964
27969
|
}
|
|
27965
27970
|
}
|
|
27971
|
+
/**
|
|
27972
|
+
* §5.9: SENT history records what SETTLED, never the plan — the value of the
|
|
27973
|
+
* certified recipient blobs (summarize()'s committed set is the only source;
|
|
27974
|
+
* a journal-rehydrated resume leg has no source token, but every committed
|
|
27975
|
+
* leg carries its recipient blob, so the outcome-derived rule is uniform).
|
|
27976
|
+
*/
|
|
27977
|
+
async settledAmount(engine, coinId, committed) {
|
|
27978
|
+
let total = 0n;
|
|
27979
|
+
for (const outcome of committed) {
|
|
27980
|
+
if (outcome.recipientBlob === void 0) continue;
|
|
27981
|
+
const token = await engine.decodeToken({
|
|
27982
|
+
v: TOKEN_BLOB_VERSION,
|
|
27983
|
+
network: 0,
|
|
27984
|
+
tokenId: "",
|
|
27985
|
+
token: outcome.recipientBlob
|
|
27986
|
+
});
|
|
27987
|
+
total += engine.balanceOf(token, coinId);
|
|
27988
|
+
}
|
|
27989
|
+
return total.toString();
|
|
27990
|
+
}
|
|
27966
27991
|
async disposeOnFailure(transferId, committed, cls) {
|
|
27967
27992
|
if (committed.length > 0 || cls === "keep-open") return;
|
|
27968
27993
|
try {
|
|
@@ -27987,7 +28012,7 @@ function conflictAmount(engine, payload, op, source) {
|
|
|
27987
28012
|
}
|
|
27988
28013
|
|
|
27989
28014
|
// modules/payments-v2/machine/resume.ts
|
|
27990
|
-
async function resumeAll(deps) {
|
|
28015
|
+
async function resumeAll(deps, options = {}) {
|
|
27991
28016
|
const report = { resumed: [], conflicted: [], failed: [] };
|
|
27992
28017
|
const stores = createMachineStores(deps.kv);
|
|
27993
28018
|
const open = await deps.intents.listOpen();
|
|
@@ -27997,7 +28022,12 @@ async function resumeAll(deps) {
|
|
|
27997
28022
|
} catch {
|
|
27998
28023
|
return report;
|
|
27999
28024
|
}
|
|
28000
|
-
await
|
|
28025
|
+
await dropStaleBackstops(
|
|
28026
|
+
stores,
|
|
28027
|
+
locals,
|
|
28028
|
+
new Set(open.map((i) => i.transferId)),
|
|
28029
|
+
options.isLocallyActive ?? (() => false)
|
|
28030
|
+
);
|
|
28001
28031
|
const jobs = await gateAndDecode(deps, stores, open, locals, report);
|
|
28002
28032
|
const ctx = {
|
|
28003
28033
|
deps,
|
|
@@ -28010,11 +28040,16 @@ async function resumeAll(deps) {
|
|
|
28010
28040
|
}
|
|
28011
28041
|
return report;
|
|
28012
28042
|
}
|
|
28013
|
-
async function
|
|
28043
|
+
async function dropStaleBackstops(stores, locals, openIds, isLocallyActive) {
|
|
28014
28044
|
for (const entry of locals.values()) {
|
|
28015
|
-
if (
|
|
28045
|
+
if (openIds.has(entry.transferId)) continue;
|
|
28046
|
+
if (entry.disposition !== "open") {
|
|
28016
28047
|
await stores.backstop.removeByKey(entry.transferId);
|
|
28048
|
+
continue;
|
|
28017
28049
|
}
|
|
28050
|
+
if (isLocallyActive(entry.transferId)) continue;
|
|
28051
|
+
if ((await stores.deliveryJournal.byTransfer(entry.transferId)).size > 0) continue;
|
|
28052
|
+
await stores.backstop.removeByKey(entry.transferId);
|
|
28018
28053
|
}
|
|
28019
28054
|
}
|
|
28020
28055
|
async function gateAndDecode(deps, stores, open, locals, report) {
|
|
@@ -28222,7 +28257,9 @@ var Converger = class {
|
|
|
28222
28257
|
return backstop.length > 0 || journal.some((e) => e.undeliverable !== true) || mints.length > 0;
|
|
28223
28258
|
}
|
|
28224
28259
|
async resumePass() {
|
|
28225
|
-
const report = await resumeAll(this.filteredMachineDeps()
|
|
28260
|
+
const report = await resumeAll(this.filteredMachineDeps(), {
|
|
28261
|
+
isLocallyActive: (id) => this.deps.isActiveOp(id)
|
|
28262
|
+
});
|
|
28226
28263
|
const progress = report.resumed.length > 0 || report.conflicted.length > 0;
|
|
28227
28264
|
await this.deps.reconcile({
|
|
28228
28265
|
resumed: report.resumed,
|
|
@@ -28338,6 +28375,136 @@ function distinctOpCount(entries) {
|
|
|
28338
28375
|
return new Set(entries.map((entry) => entry.opIndex)).size;
|
|
28339
28376
|
}
|
|
28340
28377
|
|
|
28378
|
+
// modules/payments-v2/restore.ts
|
|
28379
|
+
var ATTENTION_RESEED_REJECTED = "intent:reseed-rejected";
|
|
28380
|
+
var ALL_STREAMS = ["inventory", "mailbox", "payment_requests", "history"];
|
|
28381
|
+
async function reseedAndReset(deps) {
|
|
28382
|
+
for (const entry of await deps.stores.backstop.list()) {
|
|
28383
|
+
if (entry.disposition !== "open") continue;
|
|
28384
|
+
try {
|
|
28385
|
+
await deps.reput(entry.transferId, entry.payloadEnvelope, entry.requiresSeedClose);
|
|
28386
|
+
if (entry.requiresSeedClose) {
|
|
28387
|
+
await deps.reseedCheckpoint(entry.transferId, await splitOpIndex(deps, entry.payloadEnvelope));
|
|
28388
|
+
}
|
|
28389
|
+
} catch (err) {
|
|
28390
|
+
deps.attention(entry.transferId, ATTENTION_RESEED_REJECTED, err instanceof Error ? err.message : String(err));
|
|
28391
|
+
}
|
|
28392
|
+
}
|
|
28393
|
+
for (const stream of ALL_STREAMS) await deps.kv.remove(STORE_KEYS.streamCursor(stream));
|
|
28394
|
+
await deps.fullRePull();
|
|
28395
|
+
}
|
|
28396
|
+
async function splitOpIndex(deps, envelope) {
|
|
28397
|
+
const payload = await deps.decryptPayload(envelope);
|
|
28398
|
+
return Array.isArray(payload.direct) ? payload.direct.length : 0;
|
|
28399
|
+
}
|
|
28400
|
+
|
|
28401
|
+
// modules/payments-v2/inventory/presentation.ts
|
|
28402
|
+
function toToken(tokenId, snapshot, asset, registry, flags) {
|
|
28403
|
+
const iconUrl = registry.getIconUrl(asset.coinId);
|
|
28404
|
+
return {
|
|
28405
|
+
id: tokenId,
|
|
28406
|
+
coinId: asset.coinId,
|
|
28407
|
+
symbol: registry.getSymbol(asset.coinId),
|
|
28408
|
+
name: registry.getName(asset.coinId),
|
|
28409
|
+
decimals: registry.getDecimals(asset.coinId),
|
|
28410
|
+
...iconUrl !== null ? { iconUrl } : {},
|
|
28411
|
+
amount: asset.amount,
|
|
28412
|
+
status: flags.transferring ? "transferring" : "confirmed",
|
|
28413
|
+
createdAt: snapshot.createdAt,
|
|
28414
|
+
updatedAt: snapshot.updatedAt,
|
|
28415
|
+
lazy: true,
|
|
28416
|
+
...flags.suspectedSpent ? { suspectedSpent: true } : {}
|
|
28417
|
+
};
|
|
28418
|
+
}
|
|
28419
|
+
function aggregateAssets(entries, isTransferring, registry) {
|
|
28420
|
+
const groups = /* @__PURE__ */ new Map();
|
|
28421
|
+
for (const [tokenId, entry] of entries) {
|
|
28422
|
+
const moving = isTransferring(tokenId);
|
|
28423
|
+
for (const asset of entry.assets) {
|
|
28424
|
+
let group = groups.get(asset.coinId);
|
|
28425
|
+
if (!group) {
|
|
28426
|
+
group = { total: 0n, count: 0, transferring: 0n, transferringCount: 0 };
|
|
28427
|
+
groups.set(asset.coinId, group);
|
|
28428
|
+
}
|
|
28429
|
+
if (moving) {
|
|
28430
|
+
group.transferring += BigInt(asset.amount);
|
|
28431
|
+
group.transferringCount += 1;
|
|
28432
|
+
} else {
|
|
28433
|
+
group.total += BigInt(asset.amount);
|
|
28434
|
+
group.count += 1;
|
|
28435
|
+
}
|
|
28436
|
+
}
|
|
28437
|
+
}
|
|
28438
|
+
return [...groups].map(([coinId, totals]) => toAsset(coinId, totals, registry));
|
|
28439
|
+
}
|
|
28440
|
+
function toAsset(coinId, totals, registry) {
|
|
28441
|
+
const iconUrl = registry.getIconUrl(coinId);
|
|
28442
|
+
return {
|
|
28443
|
+
coinId,
|
|
28444
|
+
symbol: registry.getSymbol(coinId),
|
|
28445
|
+
name: registry.getName(coinId),
|
|
28446
|
+
decimals: registry.getDecimals(coinId),
|
|
28447
|
+
...iconUrl !== null ? { iconUrl } : {},
|
|
28448
|
+
totalAmount: totals.total.toString(),
|
|
28449
|
+
tokenCount: totals.count,
|
|
28450
|
+
confirmedAmount: totals.total.toString(),
|
|
28451
|
+
unconfirmedAmount: "0",
|
|
28452
|
+
confirmedTokenCount: totals.count,
|
|
28453
|
+
unconfirmedTokenCount: 0,
|
|
28454
|
+
transferringTokenCount: totals.transferringCount,
|
|
28455
|
+
transferringAmount: totals.transferring.toString(),
|
|
28456
|
+
priceUsd: null,
|
|
28457
|
+
priceEur: null,
|
|
28458
|
+
change24h: null,
|
|
28459
|
+
fiatValueUsd: null,
|
|
28460
|
+
fiatValueEur: null
|
|
28461
|
+
};
|
|
28462
|
+
}
|
|
28463
|
+
async function withPrices(raw, registry, price) {
|
|
28464
|
+
try {
|
|
28465
|
+
const names = [...new Set(raw.map((a) => registry.getName(a.coinId)))];
|
|
28466
|
+
const quotes = await price.getPrices(names);
|
|
28467
|
+
return raw.map((a) => priceOne(a, quotes.get(registry.getName(a.coinId))));
|
|
28468
|
+
} catch {
|
|
28469
|
+
return raw;
|
|
28470
|
+
}
|
|
28471
|
+
}
|
|
28472
|
+
function priceOne(asset, quote) {
|
|
28473
|
+
if (!quote) return asset;
|
|
28474
|
+
const human = Number(asset.totalAmount) / Math.pow(10, asset.decimals);
|
|
28475
|
+
return {
|
|
28476
|
+
...asset,
|
|
28477
|
+
priceUsd: quote.priceUsd,
|
|
28478
|
+
priceEur: quote.priceEur ?? null,
|
|
28479
|
+
change24h: quote.change24h ?? null,
|
|
28480
|
+
fiatValueUsd: human * quote.priceUsd,
|
|
28481
|
+
fiatValueEur: quote.priceEur != null ? human * quote.priceEur : null
|
|
28482
|
+
};
|
|
28483
|
+
}
|
|
28484
|
+
|
|
28485
|
+
// modules/payments-v2/machine/payload.ts
|
|
28486
|
+
function buildPayload(recipientPubkey, request, spend, spentStates) {
|
|
28487
|
+
return {
|
|
28488
|
+
v: 2,
|
|
28489
|
+
recipient: recipientPubkey,
|
|
28490
|
+
coinId: request.coinId,
|
|
28491
|
+
amount: request.amount,
|
|
28492
|
+
...request.memo !== void 0 ? { memo: request.memo } : {},
|
|
28493
|
+
direct: [...spend.plan.direct],
|
|
28494
|
+
...spend.plan.split !== void 0 ? {
|
|
28495
|
+
split: {
|
|
28496
|
+
tokenId: spend.plan.split.tokenId,
|
|
28497
|
+
splitAmount: spend.plan.split.splitAmount.toString(),
|
|
28498
|
+
remainderAmount: spend.plan.split.remainderAmount.toString()
|
|
28499
|
+
}
|
|
28500
|
+
} : {},
|
|
28501
|
+
spentStates
|
|
28502
|
+
};
|
|
28503
|
+
}
|
|
28504
|
+
function messageOf(err) {
|
|
28505
|
+
return err instanceof Error ? err.message : String(err);
|
|
28506
|
+
}
|
|
28507
|
+
|
|
28341
28508
|
// modules/payments-v2/history/History.ts
|
|
28342
28509
|
var WIRE_TOKEN_ID = /^(?:[0-9a-f]{2}){1,64}$/;
|
|
28343
28510
|
var WIRE_PUBKEY = /^0[23][0-9a-f]{64}$/;
|
|
@@ -28396,13 +28563,14 @@ var History = class {
|
|
|
28396
28563
|
}
|
|
28397
28564
|
// ── internals ────────────────────────────────────────────────────────────────
|
|
28398
28565
|
async post(build) {
|
|
28566
|
+
const record = build();
|
|
28399
28567
|
try {
|
|
28400
|
-
await this.deps.client.postHistory([
|
|
28568
|
+
await this.deps.client.postHistory([record]);
|
|
28401
28569
|
} catch (err) {
|
|
28402
28570
|
this.deps.log?.("history POST failed (dedupKey makes retry safe)", err);
|
|
28403
28571
|
return;
|
|
28404
28572
|
}
|
|
28405
|
-
this.deps.emit("history:updated");
|
|
28573
|
+
this.deps.emit("history:updated", this.toEntry(record));
|
|
28406
28574
|
}
|
|
28407
28575
|
ts(timestamp) {
|
|
28408
28576
|
return new Date(timestamp ?? (this.deps.now ?? Date.now)()).toISOString();
|
|
@@ -28459,93 +28627,9 @@ var History = class {
|
|
|
28459
28627
|
}
|
|
28460
28628
|
};
|
|
28461
28629
|
|
|
28462
|
-
// modules/payments-v2/inventory/presentation.ts
|
|
28463
|
-
function toToken(tokenId, snapshot, asset, registry, flags) {
|
|
28464
|
-
const iconUrl = registry.getIconUrl(asset.coinId);
|
|
28465
|
-
return {
|
|
28466
|
-
id: tokenId,
|
|
28467
|
-
coinId: asset.coinId,
|
|
28468
|
-
symbol: registry.getSymbol(asset.coinId),
|
|
28469
|
-
name: registry.getName(asset.coinId),
|
|
28470
|
-
decimals: registry.getDecimals(asset.coinId),
|
|
28471
|
-
...iconUrl !== null ? { iconUrl } : {},
|
|
28472
|
-
amount: asset.amount,
|
|
28473
|
-
status: flags.transferring ? "transferring" : "confirmed",
|
|
28474
|
-
createdAt: snapshot.createdAt,
|
|
28475
|
-
updatedAt: snapshot.updatedAt,
|
|
28476
|
-
lazy: true,
|
|
28477
|
-
...flags.suspectedSpent ? { suspectedSpent: true } : {}
|
|
28478
|
-
};
|
|
28479
|
-
}
|
|
28480
|
-
function aggregateAssets(entries, isTransferring, registry) {
|
|
28481
|
-
const groups = /* @__PURE__ */ new Map();
|
|
28482
|
-
for (const [tokenId, entry] of entries) {
|
|
28483
|
-
const moving = isTransferring(tokenId);
|
|
28484
|
-
for (const asset of entry.assets) {
|
|
28485
|
-
let group = groups.get(asset.coinId);
|
|
28486
|
-
if (!group) {
|
|
28487
|
-
group = { total: 0n, count: 0, transferring: 0n, transferringCount: 0 };
|
|
28488
|
-
groups.set(asset.coinId, group);
|
|
28489
|
-
}
|
|
28490
|
-
if (moving) {
|
|
28491
|
-
group.transferring += BigInt(asset.amount);
|
|
28492
|
-
group.transferringCount += 1;
|
|
28493
|
-
} else {
|
|
28494
|
-
group.total += BigInt(asset.amount);
|
|
28495
|
-
group.count += 1;
|
|
28496
|
-
}
|
|
28497
|
-
}
|
|
28498
|
-
}
|
|
28499
|
-
return [...groups].map(([coinId, totals]) => toAsset(coinId, totals, registry));
|
|
28500
|
-
}
|
|
28501
|
-
function toAsset(coinId, totals, registry) {
|
|
28502
|
-
const iconUrl = registry.getIconUrl(coinId);
|
|
28503
|
-
return {
|
|
28504
|
-
coinId,
|
|
28505
|
-
symbol: registry.getSymbol(coinId),
|
|
28506
|
-
name: registry.getName(coinId),
|
|
28507
|
-
decimals: registry.getDecimals(coinId),
|
|
28508
|
-
...iconUrl !== null ? { iconUrl } : {},
|
|
28509
|
-
totalAmount: totals.total.toString(),
|
|
28510
|
-
tokenCount: totals.count,
|
|
28511
|
-
confirmedAmount: totals.total.toString(),
|
|
28512
|
-
unconfirmedAmount: "0",
|
|
28513
|
-
confirmedTokenCount: totals.count,
|
|
28514
|
-
unconfirmedTokenCount: 0,
|
|
28515
|
-
transferringTokenCount: totals.transferringCount,
|
|
28516
|
-
transferringAmount: totals.transferring.toString(),
|
|
28517
|
-
priceUsd: null,
|
|
28518
|
-
priceEur: null,
|
|
28519
|
-
change24h: null,
|
|
28520
|
-
fiatValueUsd: null,
|
|
28521
|
-
fiatValueEur: null
|
|
28522
|
-
};
|
|
28523
|
-
}
|
|
28524
|
-
async function withPrices(raw, registry, price) {
|
|
28525
|
-
try {
|
|
28526
|
-
const names = [...new Set(raw.map((a) => registry.getName(a.coinId)))];
|
|
28527
|
-
const quotes = await price.getPrices(names);
|
|
28528
|
-
return raw.map((a) => priceOne(a, quotes.get(registry.getName(a.coinId))));
|
|
28529
|
-
} catch {
|
|
28530
|
-
return raw;
|
|
28531
|
-
}
|
|
28532
|
-
}
|
|
28533
|
-
function priceOne(asset, quote) {
|
|
28534
|
-
if (!quote) return asset;
|
|
28535
|
-
const human = Number(asset.totalAmount) / Math.pow(10, asset.decimals);
|
|
28536
|
-
return {
|
|
28537
|
-
...asset,
|
|
28538
|
-
priceUsd: quote.priceUsd,
|
|
28539
|
-
priceEur: quote.priceEur ?? null,
|
|
28540
|
-
change24h: quote.change24h ?? null,
|
|
28541
|
-
fiatValueUsd: human * quote.priceUsd,
|
|
28542
|
-
fiatValueEur: quote.priceEur != null ? human * quote.priceEur : null
|
|
28543
|
-
};
|
|
28544
|
-
}
|
|
28545
|
-
|
|
28546
28630
|
// modules/payments-v2/inventory/InventoryView.ts
|
|
28547
|
-
var SUSPECTED_KEY =
|
|
28548
|
-
var KNOWN_SPENDS_KEY =
|
|
28631
|
+
var SUSPECTED_KEY = STORE_KEYS.suspectedSpent;
|
|
28632
|
+
var KNOWN_SPENDS_KEY = STORE_KEYS.knownSpends;
|
|
28549
28633
|
var CURSOR_KEY = STORE_KEYS.streamCursor("inventory");
|
|
28550
28634
|
function stateKey(tokenId, stateHash) {
|
|
28551
28635
|
return `${tokenId}:${stateHash}`;
|
|
@@ -28849,19 +28933,24 @@ var Receive = class {
|
|
|
28849
28933
|
const engine = deps.engine();
|
|
28850
28934
|
const stored = [];
|
|
28851
28935
|
const pending = [];
|
|
28852
|
-
|
|
28936
|
+
const pageEpoch = () => deps.delivery.incomingEpoch() ?? deps.syncEpoch();
|
|
28853
28937
|
try {
|
|
28854
|
-
epoch = deps.syncEpoch();
|
|
28855
28938
|
const record = await deps.kv.get(STORE_KEYS.streamCursor("mailbox"));
|
|
28856
|
-
|
|
28857
|
-
for
|
|
28858
|
-
await
|
|
28859
|
-
|
|
28939
|
+
let basis = record !== null && record.syncEpoch === deps.syncEpoch() ? record : null;
|
|
28940
|
+
for (let pass = 0; pass < 2; pass++) {
|
|
28941
|
+
for await (const entry of deps.delivery.incoming(basis === null ? void 0 : String(basis.cursor))) {
|
|
28942
|
+
await this.processEntry(deps, engine, entry, pending, stored);
|
|
28943
|
+
if (pending.length >= ACK_BATCH_SIZE) await flushAcks(deps, pending, pageEpoch);
|
|
28944
|
+
}
|
|
28945
|
+
await flushAcks(deps, pending, pageEpoch);
|
|
28946
|
+
const served = deps.delivery.incomingEpoch();
|
|
28947
|
+
if (basis === null || served === null || served === basis.syncEpoch) break;
|
|
28948
|
+
await deps.kv.remove(STORE_KEYS.streamCursor("mailbox"));
|
|
28949
|
+
basis = null;
|
|
28860
28950
|
}
|
|
28861
|
-
await flushAcks(deps, pending, epoch);
|
|
28862
28951
|
} catch (err) {
|
|
28863
28952
|
logger.warn("PaymentsV2", "receive drain interrupted \u2014 unacked entries retry next drain:", err);
|
|
28864
|
-
await flushAcks(deps, pending,
|
|
28953
|
+
await flushAcks(deps, pending, pageEpoch).catch((flushErr) => {
|
|
28865
28954
|
logger.warn("PaymentsV2", "receive ack flush failed \u2014 cursor holds at the acked prefix:", flushErr);
|
|
28866
28955
|
});
|
|
28867
28956
|
}
|
|
@@ -28934,7 +29023,7 @@ async function announce(deps, entry, record) {
|
|
|
28934
29023
|
receivedAt
|
|
28935
29024
|
};
|
|
28936
29025
|
}
|
|
28937
|
-
async function flushAcks(deps, pending,
|
|
29026
|
+
async function flushAcks(deps, pending, epochOf) {
|
|
28938
29027
|
let lastAcked = null;
|
|
28939
29028
|
try {
|
|
28940
29029
|
while (pending.length > 0) {
|
|
@@ -28945,7 +29034,7 @@ async function flushAcks(deps, pending, epoch) {
|
|
|
28945
29034
|
}
|
|
28946
29035
|
} finally {
|
|
28947
29036
|
if (lastAcked !== null) {
|
|
28948
|
-
const record = { cursor: lastAcked, syncEpoch:
|
|
29037
|
+
const record = { cursor: lastAcked, syncEpoch: epochOf() };
|
|
28949
29038
|
await deps.kv.set(STORE_KEYS.streamCursor("mailbox"), record);
|
|
28950
29039
|
}
|
|
28951
29040
|
}
|
|
@@ -29046,9 +29135,19 @@ var Requests = class {
|
|
|
29046
29135
|
}
|
|
29047
29136
|
});
|
|
29048
29137
|
}
|
|
29138
|
+
/** Idempotent for a same-transferId re-write: committed only ratchets up, createdAt kept. */
|
|
29049
29139
|
writeLink(requestId, transferId, committed) {
|
|
29050
29140
|
return this.mutateJournal((m) => {
|
|
29051
|
-
|
|
29141
|
+
const prev = m.get(requestId);
|
|
29142
|
+
const same = prev !== void 0 && prev.transferId === transferId;
|
|
29143
|
+
const next = {
|
|
29144
|
+
requestId,
|
|
29145
|
+
transferId,
|
|
29146
|
+
committed: committed || same && prev.committed,
|
|
29147
|
+
createdAt: same ? prev.createdAt : this.now()
|
|
29148
|
+
};
|
|
29149
|
+
if (same && prev.committed === next.committed) return false;
|
|
29150
|
+
m.set(requestId, next);
|
|
29052
29151
|
return true;
|
|
29053
29152
|
});
|
|
29054
29153
|
}
|
|
@@ -29201,23 +29300,46 @@ var Requests = class {
|
|
|
29201
29300
|
const transferId = error instanceof SphereError && error.transferId !== void 0 ? error.transferId : void 0;
|
|
29202
29301
|
if (transferId !== void 0) {
|
|
29203
29302
|
const committed = error instanceof PartialSendConflictError;
|
|
29204
|
-
await this.
|
|
29205
|
-
this.setStatus(id, "settling");
|
|
29303
|
+
await this.settle(id, transferId, { committed, respond: false });
|
|
29206
29304
|
} else {
|
|
29207
29305
|
logger.error("PaymentsV2", `possibly-committed send for ${id} carried no transferId \u2014 paid (fail-closed)`);
|
|
29208
29306
|
this.setStatus(id, "paid");
|
|
29209
29307
|
}
|
|
29210
29308
|
} else {
|
|
29211
|
-
await this.
|
|
29212
|
-
this.setStatus(id, "pending");
|
|
29309
|
+
await this.revertPayable(id);
|
|
29213
29310
|
}
|
|
29214
29311
|
throw error;
|
|
29215
29312
|
}
|
|
29216
|
-
this.
|
|
29217
|
-
await this.respondPaid(id, result.id);
|
|
29218
|
-
await this.clearLink(id);
|
|
29313
|
+
await this.settle(id, result.id, { committed: true, respond: true });
|
|
29219
29314
|
return result;
|
|
29220
29315
|
}
|
|
29316
|
+
/**
|
|
29317
|
+
* THE settlement invariant (#441 + the failed-respond P1): a settling link is
|
|
29318
|
+
* removed ONLY by (a) a CONFIRMED paid respond — a 2xx, or the 409
|
|
29319
|
+
* already-resolved absorb — or (b) a proven clean pre-commit failure
|
|
29320
|
+
* (revertPayable). Nothing else removes one: not a network error, not a 5xx,
|
|
29321
|
+
* not a reload. Every path that binds a request to a transfer outcome funnels
|
|
29322
|
+
* through here — pay()'s clean success (respond now), pay()'s
|
|
29323
|
+
* possibly-committed throw (respond deferred), and reconcile's deferred arms
|
|
29324
|
+
* — so a failed respond always leaves the link + 'settling' and the next
|
|
29325
|
+
* reconcile pass (the committed-link override) retries the respond.
|
|
29326
|
+
*/
|
|
29327
|
+
async settle(requestId, transferId, opts) {
|
|
29328
|
+
let journalDown = false;
|
|
29329
|
+
try {
|
|
29330
|
+
await this.writeLink(requestId, transferId, opts.committed);
|
|
29331
|
+
} catch (err) {
|
|
29332
|
+
if (!opts.respond) throw err;
|
|
29333
|
+
journalDown = true;
|
|
29334
|
+
logger.error("PaymentsV2", `settling link write failed for ${requestId} \u2014 responding fail-closed:`, err);
|
|
29335
|
+
}
|
|
29336
|
+
if (!(opts.respond && await this.respondPaid(requestId, transferId))) {
|
|
29337
|
+
this.setStatus(requestId, journalDown ? "paid" : "settling");
|
|
29338
|
+
return;
|
|
29339
|
+
}
|
|
29340
|
+
if (!journalDown) await this.clearLink(requestId).catch(() => void 0);
|
|
29341
|
+
this.setStatus(requestId, "paid");
|
|
29342
|
+
}
|
|
29221
29343
|
/** 'paid' respond leg: 409 = already resolved = idempotent success; other errors defer. */
|
|
29222
29344
|
async respondPaid(id, transferId) {
|
|
29223
29345
|
try {
|
|
@@ -29253,32 +29375,32 @@ var Requests = class {
|
|
|
29253
29375
|
let aborted = null;
|
|
29254
29376
|
for (const [requestId, link] of [...this.journal]) {
|
|
29255
29377
|
if (link.committed || resumed.has(link.transferId)) {
|
|
29256
|
-
await this.resolvePaid(requestId, link
|
|
29378
|
+
await this.resolvePaid(requestId, link);
|
|
29257
29379
|
} else if (conflicted.has(link.transferId)) {
|
|
29258
29380
|
await this.revertPayable(requestId);
|
|
29259
29381
|
} else if (open.has(link.transferId)) {
|
|
29260
29382
|
} else {
|
|
29261
|
-
aborted = await this.reconcileUnaccounted(requestId, link
|
|
29383
|
+
aborted = await this.reconcileUnaccounted(requestId, link, aborted);
|
|
29262
29384
|
}
|
|
29263
29385
|
}
|
|
29264
29386
|
}
|
|
29265
29387
|
// Unaccounted link: consult the server aborted authority (lazy, memoized);
|
|
29266
29388
|
// a probe failure defers the link to the next session.
|
|
29267
|
-
async reconcileUnaccounted(requestId,
|
|
29389
|
+
async reconcileUnaccounted(requestId, link, aborted) {
|
|
29268
29390
|
try {
|
|
29269
29391
|
aborted ??= new Set(await this.deps.listAbortedTransferIds());
|
|
29270
|
-
if (aborted.has(transferId)) await this.revertPayable(requestId);
|
|
29271
|
-
else await this.resolvePaid(requestId,
|
|
29392
|
+
if (aborted.has(link.transferId)) await this.revertPayable(requestId);
|
|
29393
|
+
else await this.resolvePaid(requestId, link);
|
|
29272
29394
|
} catch (err) {
|
|
29273
29395
|
logger.warn("PaymentsV2", `aborted-intent probe failed \u2014 ${requestId} deferred:`, err);
|
|
29274
29396
|
}
|
|
29275
29397
|
return aborted;
|
|
29276
29398
|
}
|
|
29277
|
-
|
|
29278
|
-
|
|
29279
|
-
|
|
29280
|
-
this.setStatus(requestId, "paid");
|
|
29399
|
+
/** Deferred paid: the ONE settlement path again — a failed respond keeps the link. */
|
|
29400
|
+
resolvePaid(requestId, link) {
|
|
29401
|
+
return this.settle(requestId, link.transferId, { committed: link.committed, respond: true });
|
|
29281
29402
|
}
|
|
29403
|
+
/** Removal cause (b): a PROVEN clean outcome (pre-commit failure / server-aborted). */
|
|
29282
29404
|
async revertPayable(requestId) {
|
|
29283
29405
|
await this.clearLink(requestId);
|
|
29284
29406
|
this.setStatus(requestId, "pending");
|
|
@@ -29602,11 +29724,12 @@ function composeFacadeParts(deps, hooks) {
|
|
|
29602
29724
|
client: deps.client,
|
|
29603
29725
|
fieldKey: deps.fieldKey,
|
|
29604
29726
|
registry: deps.registry,
|
|
29605
|
-
emit: (event) => deps.emit(event,
|
|
29727
|
+
emit: (event, entry) => deps.emit(event, entry),
|
|
29606
29728
|
...deps.now !== void 0 ? { now: deps.now } : {},
|
|
29607
29729
|
...deps.newId !== void 0 ? { newId: deps.newId } : {}
|
|
29608
29730
|
});
|
|
29609
29731
|
const machineDeps = buildMachineDeps(deps, hooks, historyStore);
|
|
29732
|
+
const machineStores = createMachineStores(deps.kv);
|
|
29610
29733
|
const heldStates = /* @__PURE__ */ new Map();
|
|
29611
29734
|
return {
|
|
29612
29735
|
ownPubkeyBytes,
|
|
@@ -29614,12 +29737,29 @@ function composeFacadeParts(deps, hooks) {
|
|
|
29614
29737
|
ledger,
|
|
29615
29738
|
queue,
|
|
29616
29739
|
historyStore,
|
|
29617
|
-
machineStores
|
|
29740
|
+
machineStores,
|
|
29618
29741
|
machineDeps,
|
|
29619
29742
|
machine: new TransferMachine(machineDeps),
|
|
29620
29743
|
heldStates,
|
|
29621
29744
|
receiveLoop: buildReceive(deps, hooks, historyStore, heldStates),
|
|
29622
|
-
requests: buildRequests(deps, hooks)
|
|
29745
|
+
requests: buildRequests(deps, hooks),
|
|
29746
|
+
restoreDeps: buildRestoreDeps(deps, machineDeps, machineStores, view)
|
|
29747
|
+
};
|
|
29748
|
+
}
|
|
29749
|
+
function buildRestoreDeps(deps, machineDeps, machineStores, view) {
|
|
29750
|
+
return {
|
|
29751
|
+
stores: machineStores,
|
|
29752
|
+
kv: deps.kv,
|
|
29753
|
+
reput: (transferId, envelope, requiresSeedClose) => machineDeps.intents.put(transferId, envelope, requiresSeedClose),
|
|
29754
|
+
reseedCheckpoint: (transferId, opIndex) => deps.checkpointStore.reseedCheckpoint(transferId, opIndex),
|
|
29755
|
+
decryptPayload: machineDeps.decryptPayload,
|
|
29756
|
+
fullRePull: () => view.onEpochReset(),
|
|
29757
|
+
attention: (transferId, code, detail) => {
|
|
29758
|
+
deps.emit(
|
|
29759
|
+
"transfer:attention",
|
|
29760
|
+
detail === void 0 ? { transferId, code } : { transferId, code, detail }
|
|
29761
|
+
);
|
|
29762
|
+
}
|
|
29623
29763
|
};
|
|
29624
29764
|
}
|
|
29625
29765
|
function buildMachineDeps(deps, hooks, historyStore) {
|
|
@@ -29641,11 +29781,13 @@ function buildMachineDeps(deps, hooks, historyStore) {
|
|
|
29641
29781
|
ownPubkey: hexToBytes4(deps.ownPubkey),
|
|
29642
29782
|
emit: deps.emit,
|
|
29643
29783
|
now: deps.now ?? Date.now,
|
|
29644
|
-
recordHistory: async ({ transferId, payload }) => {
|
|
29784
|
+
recordHistory: async ({ transferId, payload, committedAmount }) => {
|
|
29645
29785
|
await historyStore.recordSent({
|
|
29646
29786
|
transferId,
|
|
29647
29787
|
coinId: payload.coinId,
|
|
29648
|
-
|
|
29788
|
+
// §5.9: the SETTLED amount (machine-computed from the certified
|
|
29789
|
+
// recipient blobs), never payload.amount — the plan.
|
|
29790
|
+
amount: committedAmount,
|
|
29649
29791
|
recipientPubkey: payload.recipient,
|
|
29650
29792
|
...payload.memo !== void 0 ? { memo: payload.memo } : {}
|
|
29651
29793
|
});
|
|
@@ -29672,7 +29814,7 @@ function buildReceive(deps, hooks, historyStore, heldStates) {
|
|
|
29672
29814
|
detail === void 0 ? { transferId, code } : { transferId, code, detail }
|
|
29673
29815
|
);
|
|
29674
29816
|
},
|
|
29675
|
-
syncEpoch: deps.syncEpoch
|
|
29817
|
+
syncEpoch: deps.syncEpoch,
|
|
29676
29818
|
...deps.now !== void 0 ? { now: deps.now } : {}
|
|
29677
29819
|
});
|
|
29678
29820
|
}
|
|
@@ -29725,6 +29867,7 @@ var PaymentsFacade = class {
|
|
|
29725
29867
|
this.receiveLoop = parts.receiveLoop;
|
|
29726
29868
|
this.heldStates = parts.heldStates;
|
|
29727
29869
|
this.ownPubkeyBytes = parts.ownPubkeyBytes;
|
|
29870
|
+
this.restoreDeps = parts.restoreDeps;
|
|
29728
29871
|
this.requests = parts.requests;
|
|
29729
29872
|
deps.deliveryPort.bindDeliveryKeys((blob) => this.engine().deliveryKeys(blob));
|
|
29730
29873
|
this.heartbeat = new ConvergenceHeartbeat({
|
|
@@ -29753,6 +29896,7 @@ var PaymentsFacade = class {
|
|
|
29753
29896
|
receiveLoop;
|
|
29754
29897
|
heldStates;
|
|
29755
29898
|
ownPubkeyBytes;
|
|
29899
|
+
restoreDeps;
|
|
29756
29900
|
requests;
|
|
29757
29901
|
currentEngine = null;
|
|
29758
29902
|
pendingOps = /* @__PURE__ */ new Set();
|
|
@@ -29763,6 +29907,8 @@ var PaymentsFacade = class {
|
|
|
29763
29907
|
converger;
|
|
29764
29908
|
/** Resume single-flight (§7): ticks, start() and resumeNow() coalesce onto ONE pass. */
|
|
29765
29909
|
resumeFlight = new SingleFlight();
|
|
29910
|
+
/** §5.1/§7: restore + convergence passes SERIALIZE here. */
|
|
29911
|
+
passChain = new SerialChain();
|
|
29766
29912
|
/** §7 ownership: ids with an in-process machine attempt — the pass never adopts one. */
|
|
29767
29913
|
activeMoneyOps = /* @__PURE__ */ new Set();
|
|
29768
29914
|
// ── lifecycle ──────────────────────────────────────────────────────────────
|
|
@@ -29770,8 +29916,8 @@ var PaymentsFacade = class {
|
|
|
29770
29916
|
if (this.started) return;
|
|
29771
29917
|
this.started = true;
|
|
29772
29918
|
this.heartbeat.start();
|
|
29773
|
-
await this.deps.session.start();
|
|
29774
29919
|
this.unsubscribers.push(
|
|
29920
|
+
this.deps.session.subscribeEpochChange((epoch) => this.handleEpochChange(epoch)),
|
|
29775
29921
|
this.deps.session.subscribeStream("mailbox", () => {
|
|
29776
29922
|
this.trackTail(this.receiveLoop.drainOnce());
|
|
29777
29923
|
}),
|
|
@@ -29786,6 +29932,7 @@ var PaymentsFacade = class {
|
|
|
29786
29932
|
if (status === "connected") this.heartbeat.recovered();
|
|
29787
29933
|
});
|
|
29788
29934
|
if (statusUnsub !== void 0) this.unsubscribers.push(statusUnsub);
|
|
29935
|
+
await this.deps.session.start();
|
|
29789
29936
|
this.trackTail(this.runConvergencePass());
|
|
29790
29937
|
this.trackTail(this.seedHeldStates());
|
|
29791
29938
|
await this.view.fullPull().catch(() => void 0);
|
|
@@ -29826,7 +29973,7 @@ var PaymentsFacade = class {
|
|
|
29826
29973
|
}
|
|
29827
29974
|
// ── money movement ─────────────────────────────────────────────────────────
|
|
29828
29975
|
send(request) {
|
|
29829
|
-
return this.track(this.
|
|
29976
|
+
return this.track(this.sendOutcome(request));
|
|
29830
29977
|
}
|
|
29831
29978
|
async receive() {
|
|
29832
29979
|
const transfers = await this.track(this.receiveLoop.drainOnce());
|
|
@@ -29841,20 +29988,27 @@ var PaymentsFacade = class {
|
|
|
29841
29988
|
if (!this.started) return;
|
|
29842
29989
|
await this.track(this.runConvergencePass());
|
|
29843
29990
|
}
|
|
29991
|
+
/** §5.1 restore — awaited by the session latch BEFORE streams resume; never coalesced onto a pre-restore pass. */
|
|
29992
|
+
async handleEpochChange(_newEpoch) {
|
|
29993
|
+
await this.passChain.enqueue(() => reseedAndReset(this.restoreDeps));
|
|
29994
|
+
await this.passChain.enqueue(() => this.convergeBody());
|
|
29995
|
+
}
|
|
29844
29996
|
/** One single-flighted pass + its reschedule: concurrent callers coalesce. */
|
|
29845
29997
|
runConvergencePass() {
|
|
29846
|
-
return this.resumeFlight.run(
|
|
29847
|
-
|
|
29848
|
-
|
|
29849
|
-
|
|
29850
|
-
|
|
29998
|
+
return this.resumeFlight.run(() => this.passChain.enqueue(() => this.convergeBody()));
|
|
29999
|
+
}
|
|
30000
|
+
async convergeBody() {
|
|
30001
|
+
const outcome = await this.converger.convergeOnce();
|
|
30002
|
+
const pending = await this.converger.pendingWork().catch(() => true);
|
|
30003
|
+
this.heartbeat.settle(outcome, pending);
|
|
29851
30004
|
}
|
|
29852
30005
|
nowMs() {
|
|
29853
30006
|
return (this.deps.now ?? Date.now)();
|
|
29854
30007
|
}
|
|
29855
30008
|
// ── send policy (§5.5 + old-loop parity; the machine stays policy-free) ────
|
|
29856
|
-
|
|
29857
|
-
|
|
30009
|
+
/** The ONE place a send() outcome is shaped: success emits in finishSend, a
|
|
30010
|
+
* CLEAN rejection emits `transfer:updated{status:'failed'}` here (§4). */
|
|
30011
|
+
async sendOutcome(request) {
|
|
29858
30012
|
const run = {
|
|
29859
30013
|
amount: request.amount,
|
|
29860
30014
|
delivered: [],
|
|
@@ -29862,12 +30016,36 @@ var PaymentsFacade = class {
|
|
|
29862
30016
|
tokenTransfers: [],
|
|
29863
30017
|
settledShortfalls: [],
|
|
29864
30018
|
firstPartialId: void 0,
|
|
29865
|
-
pendingAny: false
|
|
30019
|
+
pendingAny: false,
|
|
30020
|
+
lastTransferId: ""
|
|
29866
30021
|
};
|
|
30022
|
+
try {
|
|
30023
|
+
return await this.sendWithPolicy(request, run);
|
|
30024
|
+
} catch (err) {
|
|
30025
|
+
this.emitCleanFailure(err, run.lastTransferId);
|
|
30026
|
+
throw err;
|
|
30027
|
+
}
|
|
30028
|
+
}
|
|
30029
|
+
/** Only a CLEAN failure (nothing certified, classifyError 'other') is 'failed'.
|
|
30030
|
+
* Keep-open/partial/conflict outcomes are pending/converging — labelling them
|
|
30031
|
+
* 'failed' invites a dApp re-send, i.e. a double-pay (#631/#676). */
|
|
30032
|
+
emitCleanFailure(err, transferId) {
|
|
30033
|
+
if (isPossiblyCommittedSendOutcome(err) || classifyError(err) !== "other") return;
|
|
30034
|
+
const failed = {
|
|
30035
|
+
id: transferId,
|
|
30036
|
+
status: "failed",
|
|
30037
|
+
tokens: [],
|
|
30038
|
+
tokenTransfers: [],
|
|
30039
|
+
error: messageOf(err)
|
|
30040
|
+
};
|
|
30041
|
+
this.deps.emit("transfer:updated", failed);
|
|
30042
|
+
}
|
|
30043
|
+
async sendWithPolicy(request, run) {
|
|
30044
|
+
const recipient = await this.requireSameNetworkRecipient(request.recipient);
|
|
29867
30045
|
for (let attempt = 0; ; attempt++) {
|
|
29868
30046
|
let ctx;
|
|
29869
30047
|
try {
|
|
29870
|
-
ctx = await this.planAndMaterialize(recipient.chainPubkey, { ...request, amount: run.amount });
|
|
30048
|
+
ctx = await this.planAndMaterialize(recipient.chainPubkey, { ...request, amount: run.amount }, run);
|
|
29871
30049
|
} catch (err) {
|
|
29872
30050
|
throw this.partialize(err, run);
|
|
29873
30051
|
}
|
|
@@ -29912,11 +30090,8 @@ var PaymentsFacade = class {
|
|
|
29912
30090
|
this.activeMoneyOps.delete(ctx.transferId);
|
|
29913
30091
|
}
|
|
29914
30092
|
}
|
|
29915
|
-
/**
|
|
29916
|
-
*
|
|
29917
|
-
* the machine BEFORE complete). Accumulate the settled set, then re-plan ONLY
|
|
29918
|
-
* the remainder under a NEW transferId — never the full amount.
|
|
29919
|
-
*/
|
|
30093
|
+
/** Partial (#677/#690): shortfall already durable (machine wrote it BEFORE
|
|
30094
|
+
* complete); accumulate settled, re-plan ONLY the remainder, NEW transferId. */
|
|
29920
30095
|
async consumePartial(ctx, disposition, run, attempt) {
|
|
29921
30096
|
if (disposition.deliveryPending) {
|
|
29922
30097
|
run.pendingAny = true;
|
|
@@ -29942,11 +30117,8 @@ var PaymentsFacade = class {
|
|
|
29942
30117
|
run.amount = partial.remainingAmount;
|
|
29943
30118
|
return null;
|
|
29944
30119
|
}
|
|
29945
|
-
/**
|
|
29946
|
-
*
|
|
29947
|
-
* backstop still 'open' (committed>0) → converge via the same machine's
|
|
29948
|
-
* resumeIntent; clean conflict with a demoted source → bounded full re-plan.
|
|
29949
|
-
*/
|
|
30120
|
+
/** Failure disposition: possibly-committed → rethrow UNWRAPPED; backstop
|
|
30121
|
+
* 'open' → converge via resumeIntent; clean demoted conflict → full re-plan. */
|
|
29950
30122
|
async disposeFailedAttempt(ctx, err) {
|
|
29951
30123
|
if (isPossiblyCommittedSendOutcome(err)) {
|
|
29952
30124
|
this.settleKeepOpen(ctx);
|
|
@@ -29995,8 +30167,9 @@ var PaymentsFacade = class {
|
|
|
29995
30167
|
}
|
|
29996
30168
|
return { kind: "rethrow", error: err };
|
|
29997
30169
|
}
|
|
29998
|
-
async planAndMaterialize(recipientPubkey, request) {
|
|
30170
|
+
async planAndMaterialize(recipientPubkey, request, run) {
|
|
29999
30171
|
const transferId = this.newId();
|
|
30172
|
+
run.lastTransferId = transferId;
|
|
30000
30173
|
const planned = this.queue.plan(transferId, { coinId: request.coinId, amount: request.amount });
|
|
30001
30174
|
const spend = planned.kind === "planned" ? planned.spend : await planned.settled;
|
|
30002
30175
|
const sourceIds = this.markPlanned(transferId, request.coinId, spend);
|
|
@@ -30128,11 +30301,7 @@ var PaymentsFacade = class {
|
|
|
30128
30301
|
this.deps.emit("transfer:updated", result);
|
|
30129
30302
|
return result;
|
|
30130
30303
|
}
|
|
30131
|
-
/**
|
|
30132
|
-
* With nothing delivered the error passes UNWRAPPED (identity + cause kept);
|
|
30133
|
-
* after ≥1 delivered leg EVERY failure surfaces as PartialSendConflictError
|
|
30134
|
-
* over the accumulated settled set — never bare, never a full-amount retry.
|
|
30135
|
-
*/
|
|
30304
|
+
/** Nothing delivered → UNWRAPPED; after ≥1 delivered leg every failure surfaces as PartialSendConflictError over the settled set. */
|
|
30136
30305
|
partialize(err, run) {
|
|
30137
30306
|
if (run.delivered.length === 0) return err;
|
|
30138
30307
|
return new PartialSendConflictError(
|
|
@@ -30284,22 +30453,14 @@ var PaymentsFacade = class {
|
|
|
30284
30453
|
}
|
|
30285
30454
|
}
|
|
30286
30455
|
toUiToken(tokenId, coinId, amount) {
|
|
30287
|
-
const registry = this.deps.registry;
|
|
30288
|
-
const iconUrl = registry.getIconUrl(coinId);
|
|
30289
30456
|
const now = this.nowMs();
|
|
30290
|
-
return
|
|
30291
|
-
|
|
30292
|
-
|
|
30293
|
-
|
|
30294
|
-
|
|
30295
|
-
|
|
30296
|
-
|
|
30297
|
-
amount: amount.toString(),
|
|
30298
|
-
status: "transferring",
|
|
30299
|
-
createdAt: now,
|
|
30300
|
-
updatedAt: now,
|
|
30301
|
-
lazy: true
|
|
30302
|
-
};
|
|
30457
|
+
return toToken(
|
|
30458
|
+
tokenId,
|
|
30459
|
+
{ assets: [], createdAt: now, updatedAt: now },
|
|
30460
|
+
{ coinId, amount: amount.toString() },
|
|
30461
|
+
this.deps.registry,
|
|
30462
|
+
{ transferring: true, suspectedSpent: false }
|
|
30463
|
+
);
|
|
30303
30464
|
}
|
|
30304
30465
|
track(op) {
|
|
30305
30466
|
const settled = op.then(
|
|
@@ -30314,27 +30475,6 @@ var PaymentsFacade = class {
|
|
|
30314
30475
|
this.track(op).catch(() => void 0);
|
|
30315
30476
|
}
|
|
30316
30477
|
};
|
|
30317
|
-
function buildPayload(recipientPubkey, request, spend, spentStates) {
|
|
30318
|
-
return {
|
|
30319
|
-
v: 2,
|
|
30320
|
-
recipient: recipientPubkey,
|
|
30321
|
-
coinId: request.coinId,
|
|
30322
|
-
amount: request.amount,
|
|
30323
|
-
...request.memo !== void 0 ? { memo: request.memo } : {},
|
|
30324
|
-
direct: [...spend.plan.direct],
|
|
30325
|
-
...spend.plan.split !== void 0 ? {
|
|
30326
|
-
split: {
|
|
30327
|
-
tokenId: spend.plan.split.tokenId,
|
|
30328
|
-
splitAmount: spend.plan.split.splitAmount.toString(),
|
|
30329
|
-
remainderAmount: spend.plan.split.remainderAmount.toString()
|
|
30330
|
-
}
|
|
30331
|
-
} : {},
|
|
30332
|
-
spentStates
|
|
30333
|
-
};
|
|
30334
|
-
}
|
|
30335
|
-
function messageOf(err) {
|
|
30336
|
-
return err instanceof Error ? err.message : String(err);
|
|
30337
|
-
}
|
|
30338
30478
|
|
|
30339
30479
|
// impl/wallet-api-v2/http.ts
|
|
30340
30480
|
var WalletApiHttpError = class extends Error {
|
|
@@ -30680,6 +30820,7 @@ var WalletApiSession = class {
|
|
|
30680
30820
|
epochLatch = null;
|
|
30681
30821
|
reseedChain = Promise.resolve();
|
|
30682
30822
|
reseedsPending = 0;
|
|
30823
|
+
epochHandlers = /* @__PURE__ */ new Set();
|
|
30683
30824
|
handlers = /* @__PURE__ */ new Map();
|
|
30684
30825
|
statusHandlers = /* @__PURE__ */ new Set();
|
|
30685
30826
|
socket = null;
|
|
@@ -30782,6 +30923,18 @@ var WalletApiSession = class {
|
|
|
30782
30923
|
await this.deps.kv.set(this.refreshKey, tokens.refreshToken);
|
|
30783
30924
|
}
|
|
30784
30925
|
// ── epoch latch (F12) ─────────────────────────────────────────────────────
|
|
30926
|
+
/** §5.1: the latched server syncEpoch ('' before first server contact). */
|
|
30927
|
+
currentEpoch() {
|
|
30928
|
+
return this.epochLatch ?? "";
|
|
30929
|
+
}
|
|
30930
|
+
/**
|
|
30931
|
+
* §5.1 restore hook (FacadeSession contract): handlers are AWAITED inside
|
|
30932
|
+
* the reseed chain, BEFORE any stream nudge resumes.
|
|
30933
|
+
*/
|
|
30934
|
+
subscribeEpochChange(handler) {
|
|
30935
|
+
this.epochHandlers.add(handler);
|
|
30936
|
+
return () => this.epochHandlers.delete(handler);
|
|
30937
|
+
}
|
|
30785
30938
|
noteEpoch(epoch) {
|
|
30786
30939
|
if (this.epochLatch === epoch) return;
|
|
30787
30940
|
const previous = this.epochLatch;
|
|
@@ -30795,7 +30948,7 @@ var WalletApiSession = class {
|
|
|
30795
30948
|
}
|
|
30796
30949
|
async runReseed(epoch, previous) {
|
|
30797
30950
|
try {
|
|
30798
|
-
|
|
30951
|
+
for (const handler of [...this.epochHandlers]) await handler(epoch);
|
|
30799
30952
|
await this.deps.kv.set(STORE_KEYS.epochLatch, epoch);
|
|
30800
30953
|
} catch {
|
|
30801
30954
|
if (this.epochLatch === epoch) this.epochLatch = previous;
|
|
@@ -31057,6 +31210,7 @@ var WalletApiDeliveryPort = class {
|
|
|
31057
31210
|
wake;
|
|
31058
31211
|
deriveFn;
|
|
31059
31212
|
seenChain = new SerialChain();
|
|
31213
|
+
lastIncomingEpoch = null;
|
|
31060
31214
|
constructor(config) {
|
|
31061
31215
|
this.client = config.client;
|
|
31062
31216
|
this.kv = config.kv;
|
|
@@ -31151,6 +31305,7 @@ var WalletApiDeliveryPort = class {
|
|
|
31151
31305
|
const seen = await this.readSeen();
|
|
31152
31306
|
let page = await this.client.listMailbox(sinceCursor === void 0 ? 0 : Number(sinceCursor));
|
|
31153
31307
|
for (; ; ) {
|
|
31308
|
+
this.lastIncomingEpoch = String(page.syncEpoch);
|
|
31154
31309
|
for (const entry of page.entries) {
|
|
31155
31310
|
if (entry.status !== "unclaimed") continue;
|
|
31156
31311
|
if (seen.has(entry.entryId)) continue;
|
|
@@ -31160,6 +31315,10 @@ var WalletApiDeliveryPort = class {
|
|
|
31160
31315
|
page = await this.client.listMailbox(page.cursor);
|
|
31161
31316
|
}
|
|
31162
31317
|
}
|
|
31318
|
+
/** §5.7: the page-honest epoch — the restore-detection source Receive keys on. */
|
|
31319
|
+
incomingEpoch() {
|
|
31320
|
+
return this.lastIncomingEpoch;
|
|
31321
|
+
}
|
|
31163
31322
|
toIncoming(entry) {
|
|
31164
31323
|
const bundle = this.openEnvelope(entry);
|
|
31165
31324
|
return {
|
|
@@ -31237,7 +31396,7 @@ var WalletApiSplitCheckpointStore = class {
|
|
|
31237
31396
|
return utf8(`${transferId}:${String(opIndex)}`);
|
|
31238
31397
|
}
|
|
31239
31398
|
async put(transferId, opIndex, bytes) {
|
|
31240
|
-
const {
|
|
31399
|
+
const { kv, fieldKey } = this.config;
|
|
31241
31400
|
const aad = this.aad(transferId, opIndex);
|
|
31242
31401
|
const cacheKey = this.cacheKey(transferId, opIndex);
|
|
31243
31402
|
let envelope = await kv.get(cacheKey);
|
|
@@ -31245,10 +31404,21 @@ var WalletApiSplitCheckpointStore = class {
|
|
|
31245
31404
|
envelope = encryptFieldBytes(fieldKey, bytes, aad);
|
|
31246
31405
|
await kv.set(cacheKey, envelope);
|
|
31247
31406
|
}
|
|
31248
|
-
const
|
|
31249
|
-
const record = await client.postProgress(transferId, opIndex, envelope, signature);
|
|
31407
|
+
const record = await this.post(transferId, opIndex, envelope);
|
|
31250
31408
|
return this.open(record.payload, aad);
|
|
31251
31409
|
}
|
|
31410
|
+
/** §5.1 restore (CheckpointReseeder): re-POST the cached ciphertext byte-identical; false = none cached. */
|
|
31411
|
+
async reseedCheckpoint(transferId, opIndex) {
|
|
31412
|
+
const envelope = await this.config.kv.get(this.cacheKey(transferId, opIndex));
|
|
31413
|
+
if (envelope === null) return false;
|
|
31414
|
+
await this.post(transferId, opIndex, envelope);
|
|
31415
|
+
return true;
|
|
31416
|
+
}
|
|
31417
|
+
/** The ONE signed progress-append path put() and reseedCheckpoint() share. */
|
|
31418
|
+
async post(transferId, opIndex, envelope) {
|
|
31419
|
+
const signature = await this.config.signProgress(progressSignMessage(transferId, opIndex, envelope));
|
|
31420
|
+
return this.config.client.postProgress(transferId, opIndex, envelope, signature);
|
|
31421
|
+
}
|
|
31252
31422
|
async get(transferId, opIndex) {
|
|
31253
31423
|
const records = await this.config.client.getProgress(transferId);
|
|
31254
31424
|
const record = records.find((r) => r.opIndex === opIndex);
|
|
@@ -31271,7 +31441,7 @@ var WalletApiSplitCheckpointStore = class {
|
|
|
31271
31441
|
};
|
|
31272
31442
|
|
|
31273
31443
|
// core/payments-v2-wiring.ts
|
|
31274
|
-
function resolvePaymentsV2Composition(walletApi) {
|
|
31444
|
+
function resolvePaymentsV2Composition(walletApi, sphereNetwork) {
|
|
31275
31445
|
const source = walletApi;
|
|
31276
31446
|
const network = typeof source?.network === "string" && source.network !== "" ? source.network : null;
|
|
31277
31447
|
if (!source || network === null) {
|
|
@@ -31280,6 +31450,18 @@ function resolvePaymentsV2Composition(walletApi) {
|
|
|
31280
31450
|
"INVALID_CONFIG"
|
|
31281
31451
|
);
|
|
31282
31452
|
}
|
|
31453
|
+
if (!(network in NETWORKS)) {
|
|
31454
|
+
throw new SphereError(
|
|
31455
|
+
`paymentsV2: walletApi.network "${network}" is not a known network (${Object.keys(NETWORKS).join("/")}) \u2014 a payments composition is single-network (Sphere network "${String(sphereNetwork)}").`,
|
|
31456
|
+
"INVALID_CONFIG"
|
|
31457
|
+
);
|
|
31458
|
+
}
|
|
31459
|
+
if (network !== sphereNetwork) {
|
|
31460
|
+
throw new SphereError(
|
|
31461
|
+
`paymentsV2: walletApi.network "${network}" does not match the Sphere network "${String(sphereNetwork)}" the engine/registry run on \u2014 a payments composition is single-network.`,
|
|
31462
|
+
"INVALID_CONFIG"
|
|
31463
|
+
);
|
|
31464
|
+
}
|
|
31283
31465
|
if (typeof source.paymentsV2Transport === "function") {
|
|
31284
31466
|
return { network, factory: source.paymentsV2Transport.bind(source) };
|
|
31285
31467
|
}
|
|
@@ -31345,8 +31527,7 @@ function defaultPaymentsV2Transport(config) {
|
|
|
31345
31527
|
deviceId: `${config.deviceId}:pv2`,
|
|
31346
31528
|
kv: args.kv,
|
|
31347
31529
|
webSocketFactory,
|
|
31348
|
-
emitStatus: args.emitStatus
|
|
31349
|
-
onEpochChange: args.onEpochChange
|
|
31530
|
+
emitStatus: args.emitStatus
|
|
31350
31531
|
});
|
|
31351
31532
|
return { session, client: authedWireClient(session, client) };
|
|
31352
31533
|
};
|
|
@@ -31416,8 +31597,7 @@ function composePaymentsV2(spec) {
|
|
|
31416
31597
|
identity: identitySlice,
|
|
31417
31598
|
network,
|
|
31418
31599
|
kv,
|
|
31419
|
-
emitStatus: (status) => host.emit("connection:status", { status })
|
|
31420
|
-
onEpochChange: async () => void 0
|
|
31600
|
+
emitStatus: (status) => host.emit("connection:status", { status })
|
|
31421
31601
|
});
|
|
31422
31602
|
const deps = {
|
|
31423
31603
|
session: transport.session,
|
|
@@ -31447,7 +31627,10 @@ function composePaymentsV2(spec) {
|
|
|
31447
31627
|
network,
|
|
31448
31628
|
ownPubkey: identity.chainPubkey,
|
|
31449
31629
|
ownNametag: host.nametag,
|
|
31450
|
-
requestMemo: requestMemoCodec(identity.privateKey)
|
|
31630
|
+
requestMemo: requestMemoCodec(identity.privateKey),
|
|
31631
|
+
// §5.1: the session is the epoch authority — the facade registers its
|
|
31632
|
+
// restore hook on the session itself (FacadeSession.subscribeEpochChange).
|
|
31633
|
+
syncEpoch: () => transport.session.currentEpoch()
|
|
31451
31634
|
};
|
|
31452
31635
|
return new PaymentsFacade(deps);
|
|
31453
31636
|
}
|
|
@@ -31777,18 +31960,20 @@ var Sphere = class _Sphere {
|
|
|
31777
31960
|
}
|
|
31778
31961
|
/**
|
|
31779
31962
|
* P9: validate + resolve the paymentsV2 opt-in BEFORE any storage writes —
|
|
31780
|
-
* fail-closed at init, never a silently-degraded composition.
|
|
31963
|
+
* fail-closed at init, never a silently-degraded composition. Returns the
|
|
31964
|
+
* resolved composition (null = flag off); #728: resolving here also asserts
|
|
31965
|
+
* the single-network invariant (walletApi.network known + equal to the
|
|
31966
|
+
* Sphere network) before any session/KV/provider exists.
|
|
31781
31967
|
*/
|
|
31782
|
-
static resolvePaymentsV2Flag(flag, accountingConfig, swapConfig, walletApi) {
|
|
31783
|
-
if (flag !== true) return
|
|
31968
|
+
static resolvePaymentsV2Flag(flag, accountingConfig, swapConfig, walletApi, network) {
|
|
31969
|
+
if (flag !== true) return null;
|
|
31784
31970
|
if (accountingConfig || swapConfig) {
|
|
31785
31971
|
throw new SphereError(
|
|
31786
31972
|
"paymentsV2 is incompatible with the accounting/swap modules (both are built on the legacy PaymentsModule and are deleted with the P11 flip) \u2014 disable them or the flag.",
|
|
31787
31973
|
"INVALID_CONFIG"
|
|
31788
31974
|
);
|
|
31789
31975
|
}
|
|
31790
|
-
resolvePaymentsV2Composition(walletApi);
|
|
31791
|
-
return true;
|
|
31976
|
+
return resolvePaymentsV2Composition(walletApi, network);
|
|
31792
31977
|
}
|
|
31793
31978
|
/**
|
|
31794
31979
|
* Configure TokenRegistry in the main bundle context.
|
|
@@ -31843,12 +32028,14 @@ var Sphere = class _Sphere {
|
|
|
31843
32028
|
options.walletApi
|
|
31844
32029
|
);
|
|
31845
32030
|
sphere._verification = options.verification;
|
|
31846
|
-
sphere.
|
|
32031
|
+
sphere._paymentsV2Composition = _Sphere.resolvePaymentsV2Flag(
|
|
31847
32032
|
options.paymentsV2,
|
|
31848
32033
|
accountingConfig,
|
|
31849
32034
|
swapConfig,
|
|
31850
|
-
options.walletApi
|
|
32035
|
+
options.walletApi,
|
|
32036
|
+
options.network
|
|
31851
32037
|
);
|
|
32038
|
+
sphere._paymentsV2Enabled = sphere._paymentsV2Composition !== null;
|
|
31852
32039
|
sphere._password = options.password ?? null;
|
|
31853
32040
|
sphere._passwordProtected = sphere._password !== null;
|
|
31854
32041
|
progress?.({ step: "storing_keys", message: "Storing wallet keys..." });
|
|
@@ -31915,12 +32102,14 @@ var Sphere = class _Sphere {
|
|
|
31915
32102
|
options.walletApi
|
|
31916
32103
|
);
|
|
31917
32104
|
sphere._verification = options.verification;
|
|
31918
|
-
sphere.
|
|
32105
|
+
sphere._paymentsV2Composition = _Sphere.resolvePaymentsV2Flag(
|
|
31919
32106
|
options.paymentsV2,
|
|
31920
32107
|
accountingConfig,
|
|
31921
32108
|
swapConfig,
|
|
31922
|
-
options.walletApi
|
|
32109
|
+
options.walletApi,
|
|
32110
|
+
options.network
|
|
31923
32111
|
);
|
|
32112
|
+
sphere._paymentsV2Enabled = sphere._paymentsV2Composition !== null;
|
|
31924
32113
|
sphere._password = options.password ?? null;
|
|
31925
32114
|
sphere._passwordProtected = sphere._password !== null;
|
|
31926
32115
|
if (!options.storage.isConnected()) {
|
|
@@ -31994,12 +32183,14 @@ var Sphere = class _Sphere {
|
|
|
31994
32183
|
options.walletApi
|
|
31995
32184
|
);
|
|
31996
32185
|
sphere._verification = options.verification;
|
|
31997
|
-
sphere.
|
|
32186
|
+
sphere._paymentsV2Composition = _Sphere.resolvePaymentsV2Flag(
|
|
31998
32187
|
options.paymentsV2,
|
|
31999
32188
|
accountingConfig,
|
|
32000
32189
|
swapConfig,
|
|
32001
|
-
options.walletApi
|
|
32190
|
+
options.walletApi,
|
|
32191
|
+
options.network
|
|
32002
32192
|
);
|
|
32193
|
+
sphere._paymentsV2Enabled = sphere._paymentsV2Composition !== null;
|
|
32003
32194
|
sphere._password = options.password ?? null;
|
|
32004
32195
|
sphere._passwordProtected = sphere._password !== null;
|
|
32005
32196
|
progress?.({ step: "storing_keys", message: "Storing wallet keys..." });
|
|
@@ -34657,7 +34848,9 @@ var Sphere = class _Sphere {
|
|
|
34657
34848
|
* setOracleApiKey rebuild is what future operations snapshot.
|
|
34658
34849
|
*/
|
|
34659
34850
|
async startPaymentsV2Inner(index, identity) {
|
|
34660
|
-
this._paymentsV2Composition
|
|
34851
|
+
if (this._paymentsV2Composition === null) {
|
|
34852
|
+
throw new SphereError("paymentsV2: composition unresolved \u2014 init never ran the flag gate", "INVALID_CONFIG");
|
|
34853
|
+
}
|
|
34661
34854
|
if (!this._addressModules.get(index)?.tokenEngine) {
|
|
34662
34855
|
throw new SphereError(
|
|
34663
34856
|
"paymentsV2 requires the v2 token engine \u2014 the oracle must supply a trust base + gateway URL (and API key where required).",
|