@unicitylabs/sphere-sdk 0.14.0-dev.6 → 0.14.0-dev.7
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/core/index.cjs
CHANGED
|
@@ -10674,14 +10674,14 @@ var IntentResume = class {
|
|
|
10674
10674
|
}
|
|
10675
10675
|
let changeOutput = null;
|
|
10676
10676
|
if (payload.split) {
|
|
10677
|
-
const
|
|
10677
|
+
const splitOpIndex2 = payload.direct.length;
|
|
10678
10678
|
let splitDelivered = false;
|
|
10679
10679
|
{
|
|
10680
10680
|
const source = await engine.decodeToken(await provider.getToken(payload.split.tokenId));
|
|
10681
10681
|
const outcome = await this.host.certifyOperation(
|
|
10682
10682
|
{
|
|
10683
10683
|
kind: "split",
|
|
10684
|
-
opIndex:
|
|
10684
|
+
opIndex: splitOpIndex2,
|
|
10685
10685
|
uiTokenId: `v2_${payload.split.tokenId}`,
|
|
10686
10686
|
genesisId: payload.split.tokenId,
|
|
10687
10687
|
sourceSdkData: "",
|
|
@@ -20785,6 +20785,7 @@ var AccountingModule = class _AccountingModule {
|
|
|
20785
20785
|
const unsubHistory = deps.on(
|
|
20786
20786
|
"history:updated",
|
|
20787
20787
|
(entry) => {
|
|
20788
|
+
if (!("dedupKey" in entry)) return;
|
|
20788
20789
|
this._handleHistoryUpdated(entry).catch((err) => {
|
|
20789
20790
|
logger.warn(LOG_TAG2, "Error handling history:updated event:", err);
|
|
20790
20791
|
});
|
|
@@ -26054,7 +26055,10 @@ var STORE_KEYS = {
|
|
|
26054
26055
|
shortfalls: "shortfalls",
|
|
26055
26056
|
settlingLinks: "settling",
|
|
26056
26057
|
streamCursor: (s) => `cursor:${s}`,
|
|
26057
|
-
epochLatch: "epoch-latch"
|
|
26058
|
+
epochLatch: "epoch-latch",
|
|
26059
|
+
// §5.2 InventoryView durable overlays (#625/#679).
|
|
26060
|
+
suspectedSpent: "suspected-spent",
|
|
26061
|
+
knownSpends: "known-spends"
|
|
26058
26062
|
};
|
|
26059
26063
|
|
|
26060
26064
|
// modules/payments-v2/machine/journal.ts
|
|
@@ -26304,7 +26308,7 @@ var TransferMachine = class {
|
|
|
26304
26308
|
}
|
|
26305
26309
|
if (cls === null) {
|
|
26306
26310
|
await this.applyCommitted(engine, plan.transferId, committed);
|
|
26307
|
-
this.closeTail(plan.transferId, plan.payload);
|
|
26311
|
+
this.closeTail(engine, plan.transferId, plan.payload, committed);
|
|
26308
26312
|
return { committed, deliveryPending };
|
|
26309
26313
|
}
|
|
26310
26314
|
if (cls === "conflict" && committed.length > 0) {
|
|
@@ -26330,10 +26334,10 @@ var TransferMachine = class {
|
|
|
26330
26334
|
}
|
|
26331
26335
|
await this.applyCommitted(engine, r.transferId, committed);
|
|
26332
26336
|
if (conflicts.length > 0) {
|
|
26333
|
-
return { deliveryPending, partial: await this.settlePartial(r, committed, conflicts) };
|
|
26337
|
+
return { deliveryPending, partial: await this.settlePartial(engine, r, committed, conflicts) };
|
|
26334
26338
|
}
|
|
26335
26339
|
await this.completeIntent(r.transferId, r.payload);
|
|
26336
|
-
await this.finishBookkeeping(r.transferId, r.payload, "resumed");
|
|
26340
|
+
await this.finishBookkeeping(engine, r.transferId, r.payload, "resumed", committed);
|
|
26337
26341
|
return { deliveryPending, partial: null };
|
|
26338
26342
|
}
|
|
26339
26343
|
// ── shared transitions ─────────────────────────────────────────────────────
|
|
@@ -26515,7 +26519,7 @@ var TransferMachine = class {
|
|
|
26515
26519
|
}
|
|
26516
26520
|
return added;
|
|
26517
26521
|
}
|
|
26518
|
-
async settlePartial(r, committed, conflicts) {
|
|
26522
|
+
async settlePartial(engine, r, committed, conflicts) {
|
|
26519
26523
|
const undelivered = conflicts.reduce((sum, c) => sum + c.amount, 0n);
|
|
26520
26524
|
const entry = {
|
|
26521
26525
|
transferId: r.transferId,
|
|
@@ -26527,26 +26531,27 @@ var TransferMachine = class {
|
|
|
26527
26531
|
};
|
|
26528
26532
|
await this.stores.shortfalls.upsert(entry);
|
|
26529
26533
|
await this.completeIntent(r.transferId, r.payload);
|
|
26530
|
-
await this.finishBookkeeping(r.transferId, r.payload, "resumed");
|
|
26534
|
+
await this.finishBookkeeping(engine, r.transferId, r.payload, "resumed", committed);
|
|
26531
26535
|
return entry;
|
|
26532
26536
|
}
|
|
26533
|
-
closeTail(transferId, payload) {
|
|
26537
|
+
closeTail(engine, transferId, payload, committed) {
|
|
26534
26538
|
void (async () => {
|
|
26535
26539
|
try {
|
|
26536
26540
|
await this.completeIntent(transferId, payload);
|
|
26537
26541
|
} catch {
|
|
26538
26542
|
return;
|
|
26539
26543
|
}
|
|
26540
|
-
await this.finishBookkeeping(transferId, payload, "sent");
|
|
26544
|
+
await this.finishBookkeeping(engine, transferId, payload, "sent", committed);
|
|
26541
26545
|
})();
|
|
26542
26546
|
}
|
|
26543
26547
|
async completeIntent(transferId, payload) {
|
|
26544
26548
|
const signature = payload.split !== void 0 ? await this.deps.signComplete(transferId) : void 0;
|
|
26545
26549
|
await this.deps.intents.complete(transferId, signature);
|
|
26546
26550
|
}
|
|
26547
|
-
async finishBookkeeping(transferId, payload, phase) {
|
|
26551
|
+
async finishBookkeeping(engine, transferId, payload, phase, committed) {
|
|
26548
26552
|
try {
|
|
26549
|
-
await this.
|
|
26553
|
+
const committedAmount = await this.settledAmount(engine, payload.coinId, committed);
|
|
26554
|
+
await this.deps.recordHistory?.({ transferId, payload, phase, committedAmount });
|
|
26550
26555
|
} catch {
|
|
26551
26556
|
}
|
|
26552
26557
|
try {
|
|
@@ -26554,6 +26559,26 @@ var TransferMachine = class {
|
|
|
26554
26559
|
} catch {
|
|
26555
26560
|
}
|
|
26556
26561
|
}
|
|
26562
|
+
/**
|
|
26563
|
+
* §5.9: SENT history records what SETTLED, never the plan — the value of the
|
|
26564
|
+
* certified recipient blobs (summarize()'s committed set is the only source;
|
|
26565
|
+
* a journal-rehydrated resume leg has no source token, but every committed
|
|
26566
|
+
* leg carries its recipient blob, so the outcome-derived rule is uniform).
|
|
26567
|
+
*/
|
|
26568
|
+
async settledAmount(engine, coinId, committed) {
|
|
26569
|
+
let total = 0n;
|
|
26570
|
+
for (const outcome of committed) {
|
|
26571
|
+
if (outcome.recipientBlob === void 0) continue;
|
|
26572
|
+
const token = await engine.decodeToken({
|
|
26573
|
+
v: TOKEN_BLOB_VERSION,
|
|
26574
|
+
network: 0,
|
|
26575
|
+
tokenId: "",
|
|
26576
|
+
token: outcome.recipientBlob
|
|
26577
|
+
});
|
|
26578
|
+
total += engine.balanceOf(token, coinId);
|
|
26579
|
+
}
|
|
26580
|
+
return total.toString();
|
|
26581
|
+
}
|
|
26557
26582
|
async disposeOnFailure(transferId, committed, cls) {
|
|
26558
26583
|
if (committed.length > 0 || cls === "keep-open") return;
|
|
26559
26584
|
try {
|
|
@@ -26578,7 +26603,7 @@ function conflictAmount(engine, payload, op, source) {
|
|
|
26578
26603
|
}
|
|
26579
26604
|
|
|
26580
26605
|
// modules/payments-v2/machine/resume.ts
|
|
26581
|
-
async function resumeAll(deps) {
|
|
26606
|
+
async function resumeAll(deps, options = {}) {
|
|
26582
26607
|
const report = { resumed: [], conflicted: [], failed: [] };
|
|
26583
26608
|
const stores = createMachineStores(deps.kv);
|
|
26584
26609
|
const open = await deps.intents.listOpen();
|
|
@@ -26588,7 +26613,12 @@ async function resumeAll(deps) {
|
|
|
26588
26613
|
} catch {
|
|
26589
26614
|
return report;
|
|
26590
26615
|
}
|
|
26591
|
-
await
|
|
26616
|
+
await dropStaleBackstops(
|
|
26617
|
+
stores,
|
|
26618
|
+
locals,
|
|
26619
|
+
new Set(open.map((i) => i.transferId)),
|
|
26620
|
+
options.isLocallyActive ?? (() => false)
|
|
26621
|
+
);
|
|
26592
26622
|
const jobs = await gateAndDecode(deps, stores, open, locals, report);
|
|
26593
26623
|
const ctx = {
|
|
26594
26624
|
deps,
|
|
@@ -26601,11 +26631,16 @@ async function resumeAll(deps) {
|
|
|
26601
26631
|
}
|
|
26602
26632
|
return report;
|
|
26603
26633
|
}
|
|
26604
|
-
async function
|
|
26634
|
+
async function dropStaleBackstops(stores, locals, openIds, isLocallyActive) {
|
|
26605
26635
|
for (const entry of locals.values()) {
|
|
26606
|
-
if (
|
|
26636
|
+
if (openIds.has(entry.transferId)) continue;
|
|
26637
|
+
if (entry.disposition !== "open") {
|
|
26607
26638
|
await stores.backstop.removeByKey(entry.transferId);
|
|
26639
|
+
continue;
|
|
26608
26640
|
}
|
|
26641
|
+
if (isLocallyActive(entry.transferId)) continue;
|
|
26642
|
+
if ((await stores.deliveryJournal.byTransfer(entry.transferId)).size > 0) continue;
|
|
26643
|
+
await stores.backstop.removeByKey(entry.transferId);
|
|
26609
26644
|
}
|
|
26610
26645
|
}
|
|
26611
26646
|
async function gateAndDecode(deps, stores, open, locals, report) {
|
|
@@ -26813,7 +26848,9 @@ var Converger = class {
|
|
|
26813
26848
|
return backstop.length > 0 || journal.some((e) => e.undeliverable !== true) || mints.length > 0;
|
|
26814
26849
|
}
|
|
26815
26850
|
async resumePass() {
|
|
26816
|
-
const report = await resumeAll(this.filteredMachineDeps()
|
|
26851
|
+
const report = await resumeAll(this.filteredMachineDeps(), {
|
|
26852
|
+
isLocallyActive: (id) => this.deps.isActiveOp(id)
|
|
26853
|
+
});
|
|
26817
26854
|
const progress = report.resumed.length > 0 || report.conflicted.length > 0;
|
|
26818
26855
|
await this.deps.reconcile({
|
|
26819
26856
|
resumed: report.resumed,
|
|
@@ -26929,6 +26966,136 @@ function distinctOpCount(entries) {
|
|
|
26929
26966
|
return new Set(entries.map((entry) => entry.opIndex)).size;
|
|
26930
26967
|
}
|
|
26931
26968
|
|
|
26969
|
+
// modules/payments-v2/restore.ts
|
|
26970
|
+
var ATTENTION_RESEED_REJECTED = "intent:reseed-rejected";
|
|
26971
|
+
var ALL_STREAMS = ["inventory", "mailbox", "payment_requests", "history"];
|
|
26972
|
+
async function reseedAndReset(deps) {
|
|
26973
|
+
for (const entry of await deps.stores.backstop.list()) {
|
|
26974
|
+
if (entry.disposition !== "open") continue;
|
|
26975
|
+
try {
|
|
26976
|
+
await deps.reput(entry.transferId, entry.payloadEnvelope, entry.requiresSeedClose);
|
|
26977
|
+
if (entry.requiresSeedClose) {
|
|
26978
|
+
await deps.reseedCheckpoint(entry.transferId, await splitOpIndex(deps, entry.payloadEnvelope));
|
|
26979
|
+
}
|
|
26980
|
+
} catch (err) {
|
|
26981
|
+
deps.attention(entry.transferId, ATTENTION_RESEED_REJECTED, err instanceof Error ? err.message : String(err));
|
|
26982
|
+
}
|
|
26983
|
+
}
|
|
26984
|
+
for (const stream of ALL_STREAMS) await deps.kv.remove(STORE_KEYS.streamCursor(stream));
|
|
26985
|
+
await deps.fullRePull();
|
|
26986
|
+
}
|
|
26987
|
+
async function splitOpIndex(deps, envelope) {
|
|
26988
|
+
const payload = await deps.decryptPayload(envelope);
|
|
26989
|
+
return Array.isArray(payload.direct) ? payload.direct.length : 0;
|
|
26990
|
+
}
|
|
26991
|
+
|
|
26992
|
+
// modules/payments-v2/inventory/presentation.ts
|
|
26993
|
+
function toToken(tokenId, snapshot, asset, registry, flags) {
|
|
26994
|
+
const iconUrl = registry.getIconUrl(asset.coinId);
|
|
26995
|
+
return {
|
|
26996
|
+
id: tokenId,
|
|
26997
|
+
coinId: asset.coinId,
|
|
26998
|
+
symbol: registry.getSymbol(asset.coinId),
|
|
26999
|
+
name: registry.getName(asset.coinId),
|
|
27000
|
+
decimals: registry.getDecimals(asset.coinId),
|
|
27001
|
+
...iconUrl !== null ? { iconUrl } : {},
|
|
27002
|
+
amount: asset.amount,
|
|
27003
|
+
status: flags.transferring ? "transferring" : "confirmed",
|
|
27004
|
+
createdAt: snapshot.createdAt,
|
|
27005
|
+
updatedAt: snapshot.updatedAt,
|
|
27006
|
+
lazy: true,
|
|
27007
|
+
...flags.suspectedSpent ? { suspectedSpent: true } : {}
|
|
27008
|
+
};
|
|
27009
|
+
}
|
|
27010
|
+
function aggregateAssets(entries, isTransferring, registry) {
|
|
27011
|
+
const groups = /* @__PURE__ */ new Map();
|
|
27012
|
+
for (const [tokenId, entry] of entries) {
|
|
27013
|
+
const moving = isTransferring(tokenId);
|
|
27014
|
+
for (const asset of entry.assets) {
|
|
27015
|
+
let group = groups.get(asset.coinId);
|
|
27016
|
+
if (!group) {
|
|
27017
|
+
group = { total: 0n, count: 0, transferring: 0n, transferringCount: 0 };
|
|
27018
|
+
groups.set(asset.coinId, group);
|
|
27019
|
+
}
|
|
27020
|
+
if (moving) {
|
|
27021
|
+
group.transferring += BigInt(asset.amount);
|
|
27022
|
+
group.transferringCount += 1;
|
|
27023
|
+
} else {
|
|
27024
|
+
group.total += BigInt(asset.amount);
|
|
27025
|
+
group.count += 1;
|
|
27026
|
+
}
|
|
27027
|
+
}
|
|
27028
|
+
}
|
|
27029
|
+
return [...groups].map(([coinId, totals]) => toAsset(coinId, totals, registry));
|
|
27030
|
+
}
|
|
27031
|
+
function toAsset(coinId, totals, registry) {
|
|
27032
|
+
const iconUrl = registry.getIconUrl(coinId);
|
|
27033
|
+
return {
|
|
27034
|
+
coinId,
|
|
27035
|
+
symbol: registry.getSymbol(coinId),
|
|
27036
|
+
name: registry.getName(coinId),
|
|
27037
|
+
decimals: registry.getDecimals(coinId),
|
|
27038
|
+
...iconUrl !== null ? { iconUrl } : {},
|
|
27039
|
+
totalAmount: totals.total.toString(),
|
|
27040
|
+
tokenCount: totals.count,
|
|
27041
|
+
confirmedAmount: totals.total.toString(),
|
|
27042
|
+
unconfirmedAmount: "0",
|
|
27043
|
+
confirmedTokenCount: totals.count,
|
|
27044
|
+
unconfirmedTokenCount: 0,
|
|
27045
|
+
transferringTokenCount: totals.transferringCount,
|
|
27046
|
+
transferringAmount: totals.transferring.toString(),
|
|
27047
|
+
priceUsd: null,
|
|
27048
|
+
priceEur: null,
|
|
27049
|
+
change24h: null,
|
|
27050
|
+
fiatValueUsd: null,
|
|
27051
|
+
fiatValueEur: null
|
|
27052
|
+
};
|
|
27053
|
+
}
|
|
27054
|
+
async function withPrices(raw, registry, price) {
|
|
27055
|
+
try {
|
|
27056
|
+
const names = [...new Set(raw.map((a) => registry.getName(a.coinId)))];
|
|
27057
|
+
const quotes = await price.getPrices(names);
|
|
27058
|
+
return raw.map((a) => priceOne(a, quotes.get(registry.getName(a.coinId))));
|
|
27059
|
+
} catch {
|
|
27060
|
+
return raw;
|
|
27061
|
+
}
|
|
27062
|
+
}
|
|
27063
|
+
function priceOne(asset, quote) {
|
|
27064
|
+
if (!quote) return asset;
|
|
27065
|
+
const human = Number(asset.totalAmount) / Math.pow(10, asset.decimals);
|
|
27066
|
+
return {
|
|
27067
|
+
...asset,
|
|
27068
|
+
priceUsd: quote.priceUsd,
|
|
27069
|
+
priceEur: quote.priceEur ?? null,
|
|
27070
|
+
change24h: quote.change24h ?? null,
|
|
27071
|
+
fiatValueUsd: human * quote.priceUsd,
|
|
27072
|
+
fiatValueEur: quote.priceEur != null ? human * quote.priceEur : null
|
|
27073
|
+
};
|
|
27074
|
+
}
|
|
27075
|
+
|
|
27076
|
+
// modules/payments-v2/machine/payload.ts
|
|
27077
|
+
function buildPayload(recipientPubkey, request, spend, spentStates) {
|
|
27078
|
+
return {
|
|
27079
|
+
v: 2,
|
|
27080
|
+
recipient: recipientPubkey,
|
|
27081
|
+
coinId: request.coinId,
|
|
27082
|
+
amount: request.amount,
|
|
27083
|
+
...request.memo !== void 0 ? { memo: request.memo } : {},
|
|
27084
|
+
direct: [...spend.plan.direct],
|
|
27085
|
+
...spend.plan.split !== void 0 ? {
|
|
27086
|
+
split: {
|
|
27087
|
+
tokenId: spend.plan.split.tokenId,
|
|
27088
|
+
splitAmount: spend.plan.split.splitAmount.toString(),
|
|
27089
|
+
remainderAmount: spend.plan.split.remainderAmount.toString()
|
|
27090
|
+
}
|
|
27091
|
+
} : {},
|
|
27092
|
+
spentStates
|
|
27093
|
+
};
|
|
27094
|
+
}
|
|
27095
|
+
function messageOf(err) {
|
|
27096
|
+
return err instanceof Error ? err.message : String(err);
|
|
27097
|
+
}
|
|
27098
|
+
|
|
26932
27099
|
// modules/payments-v2/history/History.ts
|
|
26933
27100
|
var WIRE_TOKEN_ID = /^(?:[0-9a-f]{2}){1,64}$/;
|
|
26934
27101
|
var WIRE_PUBKEY = /^0[23][0-9a-f]{64}$/;
|
|
@@ -26987,13 +27154,14 @@ var History = class {
|
|
|
26987
27154
|
}
|
|
26988
27155
|
// ── internals ────────────────────────────────────────────────────────────────
|
|
26989
27156
|
async post(build) {
|
|
27157
|
+
const record = build();
|
|
26990
27158
|
try {
|
|
26991
|
-
await this.deps.client.postHistory([
|
|
27159
|
+
await this.deps.client.postHistory([record]);
|
|
26992
27160
|
} catch (err) {
|
|
26993
27161
|
this.deps.log?.("history POST failed (dedupKey makes retry safe)", err);
|
|
26994
27162
|
return;
|
|
26995
27163
|
}
|
|
26996
|
-
this.deps.emit("history:updated");
|
|
27164
|
+
this.deps.emit("history:updated", this.toEntry(record));
|
|
26997
27165
|
}
|
|
26998
27166
|
ts(timestamp) {
|
|
26999
27167
|
return new Date(timestamp ?? (this.deps.now ?? Date.now)()).toISOString();
|
|
@@ -27050,93 +27218,9 @@ var History = class {
|
|
|
27050
27218
|
}
|
|
27051
27219
|
};
|
|
27052
27220
|
|
|
27053
|
-
// modules/payments-v2/inventory/presentation.ts
|
|
27054
|
-
function toToken(tokenId, snapshot, asset, registry, flags) {
|
|
27055
|
-
const iconUrl = registry.getIconUrl(asset.coinId);
|
|
27056
|
-
return {
|
|
27057
|
-
id: tokenId,
|
|
27058
|
-
coinId: asset.coinId,
|
|
27059
|
-
symbol: registry.getSymbol(asset.coinId),
|
|
27060
|
-
name: registry.getName(asset.coinId),
|
|
27061
|
-
decimals: registry.getDecimals(asset.coinId),
|
|
27062
|
-
...iconUrl !== null ? { iconUrl } : {},
|
|
27063
|
-
amount: asset.amount,
|
|
27064
|
-
status: flags.transferring ? "transferring" : "confirmed",
|
|
27065
|
-
createdAt: snapshot.createdAt,
|
|
27066
|
-
updatedAt: snapshot.updatedAt,
|
|
27067
|
-
lazy: true,
|
|
27068
|
-
...flags.suspectedSpent ? { suspectedSpent: true } : {}
|
|
27069
|
-
};
|
|
27070
|
-
}
|
|
27071
|
-
function aggregateAssets(entries, isTransferring, registry) {
|
|
27072
|
-
const groups = /* @__PURE__ */ new Map();
|
|
27073
|
-
for (const [tokenId, entry] of entries) {
|
|
27074
|
-
const moving = isTransferring(tokenId);
|
|
27075
|
-
for (const asset of entry.assets) {
|
|
27076
|
-
let group = groups.get(asset.coinId);
|
|
27077
|
-
if (!group) {
|
|
27078
|
-
group = { total: 0n, count: 0, transferring: 0n, transferringCount: 0 };
|
|
27079
|
-
groups.set(asset.coinId, group);
|
|
27080
|
-
}
|
|
27081
|
-
if (moving) {
|
|
27082
|
-
group.transferring += BigInt(asset.amount);
|
|
27083
|
-
group.transferringCount += 1;
|
|
27084
|
-
} else {
|
|
27085
|
-
group.total += BigInt(asset.amount);
|
|
27086
|
-
group.count += 1;
|
|
27087
|
-
}
|
|
27088
|
-
}
|
|
27089
|
-
}
|
|
27090
|
-
return [...groups].map(([coinId, totals]) => toAsset(coinId, totals, registry));
|
|
27091
|
-
}
|
|
27092
|
-
function toAsset(coinId, totals, registry) {
|
|
27093
|
-
const iconUrl = registry.getIconUrl(coinId);
|
|
27094
|
-
return {
|
|
27095
|
-
coinId,
|
|
27096
|
-
symbol: registry.getSymbol(coinId),
|
|
27097
|
-
name: registry.getName(coinId),
|
|
27098
|
-
decimals: registry.getDecimals(coinId),
|
|
27099
|
-
...iconUrl !== null ? { iconUrl } : {},
|
|
27100
|
-
totalAmount: totals.total.toString(),
|
|
27101
|
-
tokenCount: totals.count,
|
|
27102
|
-
confirmedAmount: totals.total.toString(),
|
|
27103
|
-
unconfirmedAmount: "0",
|
|
27104
|
-
confirmedTokenCount: totals.count,
|
|
27105
|
-
unconfirmedTokenCount: 0,
|
|
27106
|
-
transferringTokenCount: totals.transferringCount,
|
|
27107
|
-
transferringAmount: totals.transferring.toString(),
|
|
27108
|
-
priceUsd: null,
|
|
27109
|
-
priceEur: null,
|
|
27110
|
-
change24h: null,
|
|
27111
|
-
fiatValueUsd: null,
|
|
27112
|
-
fiatValueEur: null
|
|
27113
|
-
};
|
|
27114
|
-
}
|
|
27115
|
-
async function withPrices(raw, registry, price) {
|
|
27116
|
-
try {
|
|
27117
|
-
const names = [...new Set(raw.map((a) => registry.getName(a.coinId)))];
|
|
27118
|
-
const quotes = await price.getPrices(names);
|
|
27119
|
-
return raw.map((a) => priceOne(a, quotes.get(registry.getName(a.coinId))));
|
|
27120
|
-
} catch {
|
|
27121
|
-
return raw;
|
|
27122
|
-
}
|
|
27123
|
-
}
|
|
27124
|
-
function priceOne(asset, quote) {
|
|
27125
|
-
if (!quote) return asset;
|
|
27126
|
-
const human = Number(asset.totalAmount) / Math.pow(10, asset.decimals);
|
|
27127
|
-
return {
|
|
27128
|
-
...asset,
|
|
27129
|
-
priceUsd: quote.priceUsd,
|
|
27130
|
-
priceEur: quote.priceEur ?? null,
|
|
27131
|
-
change24h: quote.change24h ?? null,
|
|
27132
|
-
fiatValueUsd: human * quote.priceUsd,
|
|
27133
|
-
fiatValueEur: quote.priceEur != null ? human * quote.priceEur : null
|
|
27134
|
-
};
|
|
27135
|
-
}
|
|
27136
|
-
|
|
27137
27221
|
// modules/payments-v2/inventory/InventoryView.ts
|
|
27138
|
-
var SUSPECTED_KEY =
|
|
27139
|
-
var KNOWN_SPENDS_KEY =
|
|
27222
|
+
var SUSPECTED_KEY = STORE_KEYS.suspectedSpent;
|
|
27223
|
+
var KNOWN_SPENDS_KEY = STORE_KEYS.knownSpends;
|
|
27140
27224
|
var CURSOR_KEY = STORE_KEYS.streamCursor("inventory");
|
|
27141
27225
|
function stateKey(tokenId, stateHash) {
|
|
27142
27226
|
return `${tokenId}:${stateHash}`;
|
|
@@ -27440,19 +27524,24 @@ var Receive = class {
|
|
|
27440
27524
|
const engine = deps.engine();
|
|
27441
27525
|
const stored = [];
|
|
27442
27526
|
const pending = [];
|
|
27443
|
-
|
|
27527
|
+
const pageEpoch = () => deps.delivery.incomingEpoch() ?? deps.syncEpoch();
|
|
27444
27528
|
try {
|
|
27445
|
-
epoch = deps.syncEpoch();
|
|
27446
27529
|
const record = await deps.kv.get(STORE_KEYS.streamCursor("mailbox"));
|
|
27447
|
-
|
|
27448
|
-
for
|
|
27449
|
-
await
|
|
27450
|
-
|
|
27530
|
+
let basis = record !== null && record.syncEpoch === deps.syncEpoch() ? record : null;
|
|
27531
|
+
for (let pass = 0; pass < 2; pass++) {
|
|
27532
|
+
for await (const entry of deps.delivery.incoming(basis === null ? void 0 : String(basis.cursor))) {
|
|
27533
|
+
await this.processEntry(deps, engine, entry, pending, stored);
|
|
27534
|
+
if (pending.length >= ACK_BATCH_SIZE) await flushAcks(deps, pending, pageEpoch);
|
|
27535
|
+
}
|
|
27536
|
+
await flushAcks(deps, pending, pageEpoch);
|
|
27537
|
+
const served = deps.delivery.incomingEpoch();
|
|
27538
|
+
if (basis === null || served === null || served === basis.syncEpoch) break;
|
|
27539
|
+
await deps.kv.remove(STORE_KEYS.streamCursor("mailbox"));
|
|
27540
|
+
basis = null;
|
|
27451
27541
|
}
|
|
27452
|
-
await flushAcks(deps, pending, epoch);
|
|
27453
27542
|
} catch (err) {
|
|
27454
27543
|
logger.warn("PaymentsV2", "receive drain interrupted \u2014 unacked entries retry next drain:", err);
|
|
27455
|
-
await flushAcks(deps, pending,
|
|
27544
|
+
await flushAcks(deps, pending, pageEpoch).catch((flushErr) => {
|
|
27456
27545
|
logger.warn("PaymentsV2", "receive ack flush failed \u2014 cursor holds at the acked prefix:", flushErr);
|
|
27457
27546
|
});
|
|
27458
27547
|
}
|
|
@@ -27525,7 +27614,7 @@ async function announce(deps, entry, record) {
|
|
|
27525
27614
|
receivedAt
|
|
27526
27615
|
};
|
|
27527
27616
|
}
|
|
27528
|
-
async function flushAcks(deps, pending,
|
|
27617
|
+
async function flushAcks(deps, pending, epochOf) {
|
|
27529
27618
|
let lastAcked = null;
|
|
27530
27619
|
try {
|
|
27531
27620
|
while (pending.length > 0) {
|
|
@@ -27536,7 +27625,7 @@ async function flushAcks(deps, pending, epoch) {
|
|
|
27536
27625
|
}
|
|
27537
27626
|
} finally {
|
|
27538
27627
|
if (lastAcked !== null) {
|
|
27539
|
-
const record = { cursor: lastAcked, syncEpoch:
|
|
27628
|
+
const record = { cursor: lastAcked, syncEpoch: epochOf() };
|
|
27540
27629
|
await deps.kv.set(STORE_KEYS.streamCursor("mailbox"), record);
|
|
27541
27630
|
}
|
|
27542
27631
|
}
|
|
@@ -27637,9 +27726,19 @@ var Requests = class {
|
|
|
27637
27726
|
}
|
|
27638
27727
|
});
|
|
27639
27728
|
}
|
|
27729
|
+
/** Idempotent for a same-transferId re-write: committed only ratchets up, createdAt kept. */
|
|
27640
27730
|
writeLink(requestId, transferId, committed) {
|
|
27641
27731
|
return this.mutateJournal((m) => {
|
|
27642
|
-
|
|
27732
|
+
const prev = m.get(requestId);
|
|
27733
|
+
const same = prev !== void 0 && prev.transferId === transferId;
|
|
27734
|
+
const next = {
|
|
27735
|
+
requestId,
|
|
27736
|
+
transferId,
|
|
27737
|
+
committed: committed || same && prev.committed,
|
|
27738
|
+
createdAt: same ? prev.createdAt : this.now()
|
|
27739
|
+
};
|
|
27740
|
+
if (same && prev.committed === next.committed) return false;
|
|
27741
|
+
m.set(requestId, next);
|
|
27643
27742
|
return true;
|
|
27644
27743
|
});
|
|
27645
27744
|
}
|
|
@@ -27792,23 +27891,46 @@ var Requests = class {
|
|
|
27792
27891
|
const transferId = error instanceof SphereError && error.transferId !== void 0 ? error.transferId : void 0;
|
|
27793
27892
|
if (transferId !== void 0) {
|
|
27794
27893
|
const committed = error instanceof PartialSendConflictError;
|
|
27795
|
-
await this.
|
|
27796
|
-
this.setStatus(id, "settling");
|
|
27894
|
+
await this.settle(id, transferId, { committed, respond: false });
|
|
27797
27895
|
} else {
|
|
27798
27896
|
logger.error("PaymentsV2", `possibly-committed send for ${id} carried no transferId \u2014 paid (fail-closed)`);
|
|
27799
27897
|
this.setStatus(id, "paid");
|
|
27800
27898
|
}
|
|
27801
27899
|
} else {
|
|
27802
|
-
await this.
|
|
27803
|
-
this.setStatus(id, "pending");
|
|
27900
|
+
await this.revertPayable(id);
|
|
27804
27901
|
}
|
|
27805
27902
|
throw error;
|
|
27806
27903
|
}
|
|
27807
|
-
this.
|
|
27808
|
-
await this.respondPaid(id, result.id);
|
|
27809
|
-
await this.clearLink(id);
|
|
27904
|
+
await this.settle(id, result.id, { committed: true, respond: true });
|
|
27810
27905
|
return result;
|
|
27811
27906
|
}
|
|
27907
|
+
/**
|
|
27908
|
+
* THE settlement invariant (#441 + the failed-respond P1): a settling link is
|
|
27909
|
+
* removed ONLY by (a) a CONFIRMED paid respond — a 2xx, or the 409
|
|
27910
|
+
* already-resolved absorb — or (b) a proven clean pre-commit failure
|
|
27911
|
+
* (revertPayable). Nothing else removes one: not a network error, not a 5xx,
|
|
27912
|
+
* not a reload. Every path that binds a request to a transfer outcome funnels
|
|
27913
|
+
* through here — pay()'s clean success (respond now), pay()'s
|
|
27914
|
+
* possibly-committed throw (respond deferred), and reconcile's deferred arms
|
|
27915
|
+
* — so a failed respond always leaves the link + 'settling' and the next
|
|
27916
|
+
* reconcile pass (the committed-link override) retries the respond.
|
|
27917
|
+
*/
|
|
27918
|
+
async settle(requestId, transferId, opts) {
|
|
27919
|
+
let journalDown = false;
|
|
27920
|
+
try {
|
|
27921
|
+
await this.writeLink(requestId, transferId, opts.committed);
|
|
27922
|
+
} catch (err) {
|
|
27923
|
+
if (!opts.respond) throw err;
|
|
27924
|
+
journalDown = true;
|
|
27925
|
+
logger.error("PaymentsV2", `settling link write failed for ${requestId} \u2014 responding fail-closed:`, err);
|
|
27926
|
+
}
|
|
27927
|
+
if (!(opts.respond && await this.respondPaid(requestId, transferId))) {
|
|
27928
|
+
this.setStatus(requestId, journalDown ? "paid" : "settling");
|
|
27929
|
+
return;
|
|
27930
|
+
}
|
|
27931
|
+
if (!journalDown) await this.clearLink(requestId).catch(() => void 0);
|
|
27932
|
+
this.setStatus(requestId, "paid");
|
|
27933
|
+
}
|
|
27812
27934
|
/** 'paid' respond leg: 409 = already resolved = idempotent success; other errors defer. */
|
|
27813
27935
|
async respondPaid(id, transferId) {
|
|
27814
27936
|
try {
|
|
@@ -27844,32 +27966,32 @@ var Requests = class {
|
|
|
27844
27966
|
let aborted = null;
|
|
27845
27967
|
for (const [requestId, link] of [...this.journal]) {
|
|
27846
27968
|
if (link.committed || resumed.has(link.transferId)) {
|
|
27847
|
-
await this.resolvePaid(requestId, link
|
|
27969
|
+
await this.resolvePaid(requestId, link);
|
|
27848
27970
|
} else if (conflicted.has(link.transferId)) {
|
|
27849
27971
|
await this.revertPayable(requestId);
|
|
27850
27972
|
} else if (open.has(link.transferId)) {
|
|
27851
27973
|
} else {
|
|
27852
|
-
aborted = await this.reconcileUnaccounted(requestId, link
|
|
27974
|
+
aborted = await this.reconcileUnaccounted(requestId, link, aborted);
|
|
27853
27975
|
}
|
|
27854
27976
|
}
|
|
27855
27977
|
}
|
|
27856
27978
|
// Unaccounted link: consult the server aborted authority (lazy, memoized);
|
|
27857
27979
|
// a probe failure defers the link to the next session.
|
|
27858
|
-
async reconcileUnaccounted(requestId,
|
|
27980
|
+
async reconcileUnaccounted(requestId, link, aborted) {
|
|
27859
27981
|
try {
|
|
27860
27982
|
aborted ??= new Set(await this.deps.listAbortedTransferIds());
|
|
27861
|
-
if (aborted.has(transferId)) await this.revertPayable(requestId);
|
|
27862
|
-
else await this.resolvePaid(requestId,
|
|
27983
|
+
if (aborted.has(link.transferId)) await this.revertPayable(requestId);
|
|
27984
|
+
else await this.resolvePaid(requestId, link);
|
|
27863
27985
|
} catch (err) {
|
|
27864
27986
|
logger.warn("PaymentsV2", `aborted-intent probe failed \u2014 ${requestId} deferred:`, err);
|
|
27865
27987
|
}
|
|
27866
27988
|
return aborted;
|
|
27867
27989
|
}
|
|
27868
|
-
|
|
27869
|
-
|
|
27870
|
-
|
|
27871
|
-
this.setStatus(requestId, "paid");
|
|
27990
|
+
/** Deferred paid: the ONE settlement path again — a failed respond keeps the link. */
|
|
27991
|
+
resolvePaid(requestId, link) {
|
|
27992
|
+
return this.settle(requestId, link.transferId, { committed: link.committed, respond: true });
|
|
27872
27993
|
}
|
|
27994
|
+
/** Removal cause (b): a PROVEN clean outcome (pre-commit failure / server-aborted). */
|
|
27873
27995
|
async revertPayable(requestId) {
|
|
27874
27996
|
await this.clearLink(requestId);
|
|
27875
27997
|
this.setStatus(requestId, "pending");
|
|
@@ -28193,11 +28315,12 @@ function composeFacadeParts(deps, hooks) {
|
|
|
28193
28315
|
client: deps.client,
|
|
28194
28316
|
fieldKey: deps.fieldKey,
|
|
28195
28317
|
registry: deps.registry,
|
|
28196
|
-
emit: (event) => deps.emit(event,
|
|
28318
|
+
emit: (event, entry) => deps.emit(event, entry),
|
|
28197
28319
|
...deps.now !== void 0 ? { now: deps.now } : {},
|
|
28198
28320
|
...deps.newId !== void 0 ? { newId: deps.newId } : {}
|
|
28199
28321
|
});
|
|
28200
28322
|
const machineDeps = buildMachineDeps(deps, hooks, historyStore);
|
|
28323
|
+
const machineStores = createMachineStores(deps.kv);
|
|
28201
28324
|
const heldStates = /* @__PURE__ */ new Map();
|
|
28202
28325
|
return {
|
|
28203
28326
|
ownPubkeyBytes,
|
|
@@ -28205,12 +28328,29 @@ function composeFacadeParts(deps, hooks) {
|
|
|
28205
28328
|
ledger,
|
|
28206
28329
|
queue,
|
|
28207
28330
|
historyStore,
|
|
28208
|
-
machineStores
|
|
28331
|
+
machineStores,
|
|
28209
28332
|
machineDeps,
|
|
28210
28333
|
machine: new TransferMachine(machineDeps),
|
|
28211
28334
|
heldStates,
|
|
28212
28335
|
receiveLoop: buildReceive(deps, hooks, historyStore, heldStates),
|
|
28213
|
-
requests: buildRequests(deps, hooks)
|
|
28336
|
+
requests: buildRequests(deps, hooks),
|
|
28337
|
+
restoreDeps: buildRestoreDeps(deps, machineDeps, machineStores, view)
|
|
28338
|
+
};
|
|
28339
|
+
}
|
|
28340
|
+
function buildRestoreDeps(deps, machineDeps, machineStores, view) {
|
|
28341
|
+
return {
|
|
28342
|
+
stores: machineStores,
|
|
28343
|
+
kv: deps.kv,
|
|
28344
|
+
reput: (transferId, envelope, requiresSeedClose) => machineDeps.intents.put(transferId, envelope, requiresSeedClose),
|
|
28345
|
+
reseedCheckpoint: (transferId, opIndex) => deps.checkpointStore.reseedCheckpoint(transferId, opIndex),
|
|
28346
|
+
decryptPayload: machineDeps.decryptPayload,
|
|
28347
|
+
fullRePull: () => view.onEpochReset(),
|
|
28348
|
+
attention: (transferId, code, detail) => {
|
|
28349
|
+
deps.emit(
|
|
28350
|
+
"transfer:attention",
|
|
28351
|
+
detail === void 0 ? { transferId, code } : { transferId, code, detail }
|
|
28352
|
+
);
|
|
28353
|
+
}
|
|
28214
28354
|
};
|
|
28215
28355
|
}
|
|
28216
28356
|
function buildMachineDeps(deps, hooks, historyStore) {
|
|
@@ -28232,11 +28372,13 @@ function buildMachineDeps(deps, hooks, historyStore) {
|
|
|
28232
28372
|
ownPubkey: hexToBytes4(deps.ownPubkey),
|
|
28233
28373
|
emit: deps.emit,
|
|
28234
28374
|
now: deps.now ?? Date.now,
|
|
28235
|
-
recordHistory: async ({ transferId, payload }) => {
|
|
28375
|
+
recordHistory: async ({ transferId, payload, committedAmount }) => {
|
|
28236
28376
|
await historyStore.recordSent({
|
|
28237
28377
|
transferId,
|
|
28238
28378
|
coinId: payload.coinId,
|
|
28239
|
-
|
|
28379
|
+
// §5.9: the SETTLED amount (machine-computed from the certified
|
|
28380
|
+
// recipient blobs), never payload.amount — the plan.
|
|
28381
|
+
amount: committedAmount,
|
|
28240
28382
|
recipientPubkey: payload.recipient,
|
|
28241
28383
|
...payload.memo !== void 0 ? { memo: payload.memo } : {}
|
|
28242
28384
|
});
|
|
@@ -28263,7 +28405,7 @@ function buildReceive(deps, hooks, historyStore, heldStates) {
|
|
|
28263
28405
|
detail === void 0 ? { transferId, code } : { transferId, code, detail }
|
|
28264
28406
|
);
|
|
28265
28407
|
},
|
|
28266
|
-
syncEpoch: deps.syncEpoch
|
|
28408
|
+
syncEpoch: deps.syncEpoch,
|
|
28267
28409
|
...deps.now !== void 0 ? { now: deps.now } : {}
|
|
28268
28410
|
});
|
|
28269
28411
|
}
|
|
@@ -28316,6 +28458,7 @@ var PaymentsFacade = class {
|
|
|
28316
28458
|
this.receiveLoop = parts.receiveLoop;
|
|
28317
28459
|
this.heldStates = parts.heldStates;
|
|
28318
28460
|
this.ownPubkeyBytes = parts.ownPubkeyBytes;
|
|
28461
|
+
this.restoreDeps = parts.restoreDeps;
|
|
28319
28462
|
this.requests = parts.requests;
|
|
28320
28463
|
deps.deliveryPort.bindDeliveryKeys((blob) => this.engine().deliveryKeys(blob));
|
|
28321
28464
|
this.heartbeat = new ConvergenceHeartbeat({
|
|
@@ -28344,6 +28487,7 @@ var PaymentsFacade = class {
|
|
|
28344
28487
|
receiveLoop;
|
|
28345
28488
|
heldStates;
|
|
28346
28489
|
ownPubkeyBytes;
|
|
28490
|
+
restoreDeps;
|
|
28347
28491
|
requests;
|
|
28348
28492
|
currentEngine = null;
|
|
28349
28493
|
pendingOps = /* @__PURE__ */ new Set();
|
|
@@ -28354,6 +28498,8 @@ var PaymentsFacade = class {
|
|
|
28354
28498
|
converger;
|
|
28355
28499
|
/** Resume single-flight (§7): ticks, start() and resumeNow() coalesce onto ONE pass. */
|
|
28356
28500
|
resumeFlight = new SingleFlight();
|
|
28501
|
+
/** §5.1/§7: restore + convergence passes SERIALIZE here. */
|
|
28502
|
+
passChain = new SerialChain();
|
|
28357
28503
|
/** §7 ownership: ids with an in-process machine attempt — the pass never adopts one. */
|
|
28358
28504
|
activeMoneyOps = /* @__PURE__ */ new Set();
|
|
28359
28505
|
// ── lifecycle ──────────────────────────────────────────────────────────────
|
|
@@ -28361,8 +28507,8 @@ var PaymentsFacade = class {
|
|
|
28361
28507
|
if (this.started) return;
|
|
28362
28508
|
this.started = true;
|
|
28363
28509
|
this.heartbeat.start();
|
|
28364
|
-
await this.deps.session.start();
|
|
28365
28510
|
this.unsubscribers.push(
|
|
28511
|
+
this.deps.session.subscribeEpochChange((epoch) => this.handleEpochChange(epoch)),
|
|
28366
28512
|
this.deps.session.subscribeStream("mailbox", () => {
|
|
28367
28513
|
this.trackTail(this.receiveLoop.drainOnce());
|
|
28368
28514
|
}),
|
|
@@ -28377,6 +28523,7 @@ var PaymentsFacade = class {
|
|
|
28377
28523
|
if (status === "connected") this.heartbeat.recovered();
|
|
28378
28524
|
});
|
|
28379
28525
|
if (statusUnsub !== void 0) this.unsubscribers.push(statusUnsub);
|
|
28526
|
+
await this.deps.session.start();
|
|
28380
28527
|
this.trackTail(this.runConvergencePass());
|
|
28381
28528
|
this.trackTail(this.seedHeldStates());
|
|
28382
28529
|
await this.view.fullPull().catch(() => void 0);
|
|
@@ -28417,7 +28564,7 @@ var PaymentsFacade = class {
|
|
|
28417
28564
|
}
|
|
28418
28565
|
// ── money movement ─────────────────────────────────────────────────────────
|
|
28419
28566
|
send(request) {
|
|
28420
|
-
return this.track(this.
|
|
28567
|
+
return this.track(this.sendOutcome(request));
|
|
28421
28568
|
}
|
|
28422
28569
|
async receive() {
|
|
28423
28570
|
const transfers = await this.track(this.receiveLoop.drainOnce());
|
|
@@ -28432,20 +28579,27 @@ var PaymentsFacade = class {
|
|
|
28432
28579
|
if (!this.started) return;
|
|
28433
28580
|
await this.track(this.runConvergencePass());
|
|
28434
28581
|
}
|
|
28582
|
+
/** §5.1 restore — awaited by the session latch BEFORE streams resume; never coalesced onto a pre-restore pass. */
|
|
28583
|
+
async handleEpochChange(_newEpoch) {
|
|
28584
|
+
await this.passChain.enqueue(() => reseedAndReset(this.restoreDeps));
|
|
28585
|
+
await this.passChain.enqueue(() => this.convergeBody());
|
|
28586
|
+
}
|
|
28435
28587
|
/** One single-flighted pass + its reschedule: concurrent callers coalesce. */
|
|
28436
28588
|
runConvergencePass() {
|
|
28437
|
-
return this.resumeFlight.run(
|
|
28438
|
-
|
|
28439
|
-
|
|
28440
|
-
|
|
28441
|
-
|
|
28589
|
+
return this.resumeFlight.run(() => this.passChain.enqueue(() => this.convergeBody()));
|
|
28590
|
+
}
|
|
28591
|
+
async convergeBody() {
|
|
28592
|
+
const outcome = await this.converger.convergeOnce();
|
|
28593
|
+
const pending = await this.converger.pendingWork().catch(() => true);
|
|
28594
|
+
this.heartbeat.settle(outcome, pending);
|
|
28442
28595
|
}
|
|
28443
28596
|
nowMs() {
|
|
28444
28597
|
return (this.deps.now ?? Date.now)();
|
|
28445
28598
|
}
|
|
28446
28599
|
// ── send policy (§5.5 + old-loop parity; the machine stays policy-free) ────
|
|
28447
|
-
|
|
28448
|
-
|
|
28600
|
+
/** The ONE place a send() outcome is shaped: success emits in finishSend, a
|
|
28601
|
+
* CLEAN rejection emits `transfer:updated{status:'failed'}` here (§4). */
|
|
28602
|
+
async sendOutcome(request) {
|
|
28449
28603
|
const run = {
|
|
28450
28604
|
amount: request.amount,
|
|
28451
28605
|
delivered: [],
|
|
@@ -28453,12 +28607,36 @@ var PaymentsFacade = class {
|
|
|
28453
28607
|
tokenTransfers: [],
|
|
28454
28608
|
settledShortfalls: [],
|
|
28455
28609
|
firstPartialId: void 0,
|
|
28456
|
-
pendingAny: false
|
|
28610
|
+
pendingAny: false,
|
|
28611
|
+
lastTransferId: ""
|
|
28612
|
+
};
|
|
28613
|
+
try {
|
|
28614
|
+
return await this.sendWithPolicy(request, run);
|
|
28615
|
+
} catch (err) {
|
|
28616
|
+
this.emitCleanFailure(err, run.lastTransferId);
|
|
28617
|
+
throw err;
|
|
28618
|
+
}
|
|
28619
|
+
}
|
|
28620
|
+
/** Only a CLEAN failure (nothing certified, classifyError 'other') is 'failed'.
|
|
28621
|
+
* Keep-open/partial/conflict outcomes are pending/converging — labelling them
|
|
28622
|
+
* 'failed' invites a dApp re-send, i.e. a double-pay (#631/#676). */
|
|
28623
|
+
emitCleanFailure(err, transferId) {
|
|
28624
|
+
if (isPossiblyCommittedSendOutcome(err) || classifyError(err) !== "other") return;
|
|
28625
|
+
const failed = {
|
|
28626
|
+
id: transferId,
|
|
28627
|
+
status: "failed",
|
|
28628
|
+
tokens: [],
|
|
28629
|
+
tokenTransfers: [],
|
|
28630
|
+
error: messageOf(err)
|
|
28457
28631
|
};
|
|
28632
|
+
this.deps.emit("transfer:updated", failed);
|
|
28633
|
+
}
|
|
28634
|
+
async sendWithPolicy(request, run) {
|
|
28635
|
+
const recipient = await this.requireSameNetworkRecipient(request.recipient);
|
|
28458
28636
|
for (let attempt = 0; ; attempt++) {
|
|
28459
28637
|
let ctx;
|
|
28460
28638
|
try {
|
|
28461
|
-
ctx = await this.planAndMaterialize(recipient.chainPubkey, { ...request, amount: run.amount });
|
|
28639
|
+
ctx = await this.planAndMaterialize(recipient.chainPubkey, { ...request, amount: run.amount }, run);
|
|
28462
28640
|
} catch (err) {
|
|
28463
28641
|
throw this.partialize(err, run);
|
|
28464
28642
|
}
|
|
@@ -28503,11 +28681,8 @@ var PaymentsFacade = class {
|
|
|
28503
28681
|
this.activeMoneyOps.delete(ctx.transferId);
|
|
28504
28682
|
}
|
|
28505
28683
|
}
|
|
28506
|
-
/**
|
|
28507
|
-
*
|
|
28508
|
-
* the machine BEFORE complete). Accumulate the settled set, then re-plan ONLY
|
|
28509
|
-
* the remainder under a NEW transferId — never the full amount.
|
|
28510
|
-
*/
|
|
28684
|
+
/** Partial (#677/#690): shortfall already durable (machine wrote it BEFORE
|
|
28685
|
+
* complete); accumulate settled, re-plan ONLY the remainder, NEW transferId. */
|
|
28511
28686
|
async consumePartial(ctx, disposition, run, attempt) {
|
|
28512
28687
|
if (disposition.deliveryPending) {
|
|
28513
28688
|
run.pendingAny = true;
|
|
@@ -28533,11 +28708,8 @@ var PaymentsFacade = class {
|
|
|
28533
28708
|
run.amount = partial.remainingAmount;
|
|
28534
28709
|
return null;
|
|
28535
28710
|
}
|
|
28536
|
-
/**
|
|
28537
|
-
*
|
|
28538
|
-
* backstop still 'open' (committed>0) → converge via the same machine's
|
|
28539
|
-
* resumeIntent; clean conflict with a demoted source → bounded full re-plan.
|
|
28540
|
-
*/
|
|
28711
|
+
/** Failure disposition: possibly-committed → rethrow UNWRAPPED; backstop
|
|
28712
|
+
* 'open' → converge via resumeIntent; clean demoted conflict → full re-plan. */
|
|
28541
28713
|
async disposeFailedAttempt(ctx, err) {
|
|
28542
28714
|
if (isPossiblyCommittedSendOutcome(err)) {
|
|
28543
28715
|
this.settleKeepOpen(ctx);
|
|
@@ -28586,8 +28758,9 @@ var PaymentsFacade = class {
|
|
|
28586
28758
|
}
|
|
28587
28759
|
return { kind: "rethrow", error: err };
|
|
28588
28760
|
}
|
|
28589
|
-
async planAndMaterialize(recipientPubkey, request) {
|
|
28761
|
+
async planAndMaterialize(recipientPubkey, request, run) {
|
|
28590
28762
|
const transferId = this.newId();
|
|
28763
|
+
run.lastTransferId = transferId;
|
|
28591
28764
|
const planned = this.queue.plan(transferId, { coinId: request.coinId, amount: request.amount });
|
|
28592
28765
|
const spend = planned.kind === "planned" ? planned.spend : await planned.settled;
|
|
28593
28766
|
const sourceIds = this.markPlanned(transferId, request.coinId, spend);
|
|
@@ -28719,11 +28892,7 @@ var PaymentsFacade = class {
|
|
|
28719
28892
|
this.deps.emit("transfer:updated", result);
|
|
28720
28893
|
return result;
|
|
28721
28894
|
}
|
|
28722
|
-
/**
|
|
28723
|
-
* With nothing delivered the error passes UNWRAPPED (identity + cause kept);
|
|
28724
|
-
* after ≥1 delivered leg EVERY failure surfaces as PartialSendConflictError
|
|
28725
|
-
* over the accumulated settled set — never bare, never a full-amount retry.
|
|
28726
|
-
*/
|
|
28895
|
+
/** Nothing delivered → UNWRAPPED; after ≥1 delivered leg every failure surfaces as PartialSendConflictError over the settled set. */
|
|
28727
28896
|
partialize(err, run) {
|
|
28728
28897
|
if (run.delivered.length === 0) return err;
|
|
28729
28898
|
return new PartialSendConflictError(
|
|
@@ -28875,22 +29044,14 @@ var PaymentsFacade = class {
|
|
|
28875
29044
|
}
|
|
28876
29045
|
}
|
|
28877
29046
|
toUiToken(tokenId, coinId, amount) {
|
|
28878
|
-
const registry = this.deps.registry;
|
|
28879
|
-
const iconUrl = registry.getIconUrl(coinId);
|
|
28880
29047
|
const now = this.nowMs();
|
|
28881
|
-
return
|
|
28882
|
-
|
|
28883
|
-
|
|
28884
|
-
|
|
28885
|
-
|
|
28886
|
-
|
|
28887
|
-
|
|
28888
|
-
amount: amount.toString(),
|
|
28889
|
-
status: "transferring",
|
|
28890
|
-
createdAt: now,
|
|
28891
|
-
updatedAt: now,
|
|
28892
|
-
lazy: true
|
|
28893
|
-
};
|
|
29048
|
+
return toToken(
|
|
29049
|
+
tokenId,
|
|
29050
|
+
{ assets: [], createdAt: now, updatedAt: now },
|
|
29051
|
+
{ coinId, amount: amount.toString() },
|
|
29052
|
+
this.deps.registry,
|
|
29053
|
+
{ transferring: true, suspectedSpent: false }
|
|
29054
|
+
);
|
|
28894
29055
|
}
|
|
28895
29056
|
track(op) {
|
|
28896
29057
|
const settled = op.then(
|
|
@@ -28905,27 +29066,6 @@ var PaymentsFacade = class {
|
|
|
28905
29066
|
this.track(op).catch(() => void 0);
|
|
28906
29067
|
}
|
|
28907
29068
|
};
|
|
28908
|
-
function buildPayload(recipientPubkey, request, spend, spentStates) {
|
|
28909
|
-
return {
|
|
28910
|
-
v: 2,
|
|
28911
|
-
recipient: recipientPubkey,
|
|
28912
|
-
coinId: request.coinId,
|
|
28913
|
-
amount: request.amount,
|
|
28914
|
-
...request.memo !== void 0 ? { memo: request.memo } : {},
|
|
28915
|
-
direct: [...spend.plan.direct],
|
|
28916
|
-
...spend.plan.split !== void 0 ? {
|
|
28917
|
-
split: {
|
|
28918
|
-
tokenId: spend.plan.split.tokenId,
|
|
28919
|
-
splitAmount: spend.plan.split.splitAmount.toString(),
|
|
28920
|
-
remainderAmount: spend.plan.split.remainderAmount.toString()
|
|
28921
|
-
}
|
|
28922
|
-
} : {},
|
|
28923
|
-
spentStates
|
|
28924
|
-
};
|
|
28925
|
-
}
|
|
28926
|
-
function messageOf(err) {
|
|
28927
|
-
return err instanceof Error ? err.message : String(err);
|
|
28928
|
-
}
|
|
28929
29069
|
|
|
28930
29070
|
// impl/wallet-api-v2/http.ts
|
|
28931
29071
|
var WalletApiHttpError = class extends Error {
|
|
@@ -29271,6 +29411,7 @@ var WalletApiSession = class {
|
|
|
29271
29411
|
epochLatch = null;
|
|
29272
29412
|
reseedChain = Promise.resolve();
|
|
29273
29413
|
reseedsPending = 0;
|
|
29414
|
+
epochHandlers = /* @__PURE__ */ new Set();
|
|
29274
29415
|
handlers = /* @__PURE__ */ new Map();
|
|
29275
29416
|
statusHandlers = /* @__PURE__ */ new Set();
|
|
29276
29417
|
socket = null;
|
|
@@ -29373,6 +29514,18 @@ var WalletApiSession = class {
|
|
|
29373
29514
|
await this.deps.kv.set(this.refreshKey, tokens.refreshToken);
|
|
29374
29515
|
}
|
|
29375
29516
|
// ── epoch latch (F12) ─────────────────────────────────────────────────────
|
|
29517
|
+
/** §5.1: the latched server syncEpoch ('' before first server contact). */
|
|
29518
|
+
currentEpoch() {
|
|
29519
|
+
return this.epochLatch ?? "";
|
|
29520
|
+
}
|
|
29521
|
+
/**
|
|
29522
|
+
* §5.1 restore hook (FacadeSession contract): handlers are AWAITED inside
|
|
29523
|
+
* the reseed chain, BEFORE any stream nudge resumes.
|
|
29524
|
+
*/
|
|
29525
|
+
subscribeEpochChange(handler) {
|
|
29526
|
+
this.epochHandlers.add(handler);
|
|
29527
|
+
return () => this.epochHandlers.delete(handler);
|
|
29528
|
+
}
|
|
29376
29529
|
noteEpoch(epoch) {
|
|
29377
29530
|
if (this.epochLatch === epoch) return;
|
|
29378
29531
|
const previous = this.epochLatch;
|
|
@@ -29386,7 +29539,7 @@ var WalletApiSession = class {
|
|
|
29386
29539
|
}
|
|
29387
29540
|
async runReseed(epoch, previous) {
|
|
29388
29541
|
try {
|
|
29389
|
-
|
|
29542
|
+
for (const handler of [...this.epochHandlers]) await handler(epoch);
|
|
29390
29543
|
await this.deps.kv.set(STORE_KEYS.epochLatch, epoch);
|
|
29391
29544
|
} catch {
|
|
29392
29545
|
if (this.epochLatch === epoch) this.epochLatch = previous;
|
|
@@ -29648,6 +29801,7 @@ var WalletApiDeliveryPort = class {
|
|
|
29648
29801
|
wake;
|
|
29649
29802
|
deriveFn;
|
|
29650
29803
|
seenChain = new SerialChain();
|
|
29804
|
+
lastIncomingEpoch = null;
|
|
29651
29805
|
constructor(config) {
|
|
29652
29806
|
this.client = config.client;
|
|
29653
29807
|
this.kv = config.kv;
|
|
@@ -29742,6 +29896,7 @@ var WalletApiDeliveryPort = class {
|
|
|
29742
29896
|
const seen = await this.readSeen();
|
|
29743
29897
|
let page = await this.client.listMailbox(sinceCursor === void 0 ? 0 : Number(sinceCursor));
|
|
29744
29898
|
for (; ; ) {
|
|
29899
|
+
this.lastIncomingEpoch = String(page.syncEpoch);
|
|
29745
29900
|
for (const entry of page.entries) {
|
|
29746
29901
|
if (entry.status !== "unclaimed") continue;
|
|
29747
29902
|
if (seen.has(entry.entryId)) continue;
|
|
@@ -29751,6 +29906,10 @@ var WalletApiDeliveryPort = class {
|
|
|
29751
29906
|
page = await this.client.listMailbox(page.cursor);
|
|
29752
29907
|
}
|
|
29753
29908
|
}
|
|
29909
|
+
/** §5.7: the page-honest epoch — the restore-detection source Receive keys on. */
|
|
29910
|
+
incomingEpoch() {
|
|
29911
|
+
return this.lastIncomingEpoch;
|
|
29912
|
+
}
|
|
29754
29913
|
toIncoming(entry) {
|
|
29755
29914
|
const bundle = this.openEnvelope(entry);
|
|
29756
29915
|
return {
|
|
@@ -29828,7 +29987,7 @@ var WalletApiSplitCheckpointStore = class {
|
|
|
29828
29987
|
return utf8(`${transferId}:${String(opIndex)}`);
|
|
29829
29988
|
}
|
|
29830
29989
|
async put(transferId, opIndex, bytes) {
|
|
29831
|
-
const {
|
|
29990
|
+
const { kv, fieldKey } = this.config;
|
|
29832
29991
|
const aad = this.aad(transferId, opIndex);
|
|
29833
29992
|
const cacheKey = this.cacheKey(transferId, opIndex);
|
|
29834
29993
|
let envelope = await kv.get(cacheKey);
|
|
@@ -29836,10 +29995,21 @@ var WalletApiSplitCheckpointStore = class {
|
|
|
29836
29995
|
envelope = encryptFieldBytes(fieldKey, bytes, aad);
|
|
29837
29996
|
await kv.set(cacheKey, envelope);
|
|
29838
29997
|
}
|
|
29839
|
-
const
|
|
29840
|
-
const record = await client.postProgress(transferId, opIndex, envelope, signature);
|
|
29998
|
+
const record = await this.post(transferId, opIndex, envelope);
|
|
29841
29999
|
return this.open(record.payload, aad);
|
|
29842
30000
|
}
|
|
30001
|
+
/** §5.1 restore (CheckpointReseeder): re-POST the cached ciphertext byte-identical; false = none cached. */
|
|
30002
|
+
async reseedCheckpoint(transferId, opIndex) {
|
|
30003
|
+
const envelope = await this.config.kv.get(this.cacheKey(transferId, opIndex));
|
|
30004
|
+
if (envelope === null) return false;
|
|
30005
|
+
await this.post(transferId, opIndex, envelope);
|
|
30006
|
+
return true;
|
|
30007
|
+
}
|
|
30008
|
+
/** The ONE signed progress-append path put() and reseedCheckpoint() share. */
|
|
30009
|
+
async post(transferId, opIndex, envelope) {
|
|
30010
|
+
const signature = await this.config.signProgress(progressSignMessage(transferId, opIndex, envelope));
|
|
30011
|
+
return this.config.client.postProgress(transferId, opIndex, envelope, signature);
|
|
30012
|
+
}
|
|
29843
30013
|
async get(transferId, opIndex) {
|
|
29844
30014
|
const records = await this.config.client.getProgress(transferId);
|
|
29845
30015
|
const record = records.find((r) => r.opIndex === opIndex);
|
|
@@ -29862,7 +30032,7 @@ var WalletApiSplitCheckpointStore = class {
|
|
|
29862
30032
|
};
|
|
29863
30033
|
|
|
29864
30034
|
// core/payments-v2-wiring.ts
|
|
29865
|
-
function resolvePaymentsV2Composition(walletApi) {
|
|
30035
|
+
function resolvePaymentsV2Composition(walletApi, sphereNetwork) {
|
|
29866
30036
|
const source = walletApi;
|
|
29867
30037
|
const network = typeof source?.network === "string" && source.network !== "" ? source.network : null;
|
|
29868
30038
|
if (!source || network === null) {
|
|
@@ -29871,6 +30041,18 @@ function resolvePaymentsV2Composition(walletApi) {
|
|
|
29871
30041
|
"INVALID_CONFIG"
|
|
29872
30042
|
);
|
|
29873
30043
|
}
|
|
30044
|
+
if (!(network in NETWORKS)) {
|
|
30045
|
+
throw new SphereError(
|
|
30046
|
+
`paymentsV2: walletApi.network "${network}" is not a known network (${Object.keys(NETWORKS).join("/")}) \u2014 a payments composition is single-network (Sphere network "${String(sphereNetwork)}").`,
|
|
30047
|
+
"INVALID_CONFIG"
|
|
30048
|
+
);
|
|
30049
|
+
}
|
|
30050
|
+
if (network !== sphereNetwork) {
|
|
30051
|
+
throw new SphereError(
|
|
30052
|
+
`paymentsV2: walletApi.network "${network}" does not match the Sphere network "${String(sphereNetwork)}" the engine/registry run on \u2014 a payments composition is single-network.`,
|
|
30053
|
+
"INVALID_CONFIG"
|
|
30054
|
+
);
|
|
30055
|
+
}
|
|
29874
30056
|
if (typeof source.paymentsV2Transport === "function") {
|
|
29875
30057
|
return { network, factory: source.paymentsV2Transport.bind(source) };
|
|
29876
30058
|
}
|
|
@@ -29936,8 +30118,7 @@ function defaultPaymentsV2Transport(config) {
|
|
|
29936
30118
|
deviceId: `${config.deviceId}:pv2`,
|
|
29937
30119
|
kv: args.kv,
|
|
29938
30120
|
webSocketFactory,
|
|
29939
|
-
emitStatus: args.emitStatus
|
|
29940
|
-
onEpochChange: args.onEpochChange
|
|
30121
|
+
emitStatus: args.emitStatus
|
|
29941
30122
|
});
|
|
29942
30123
|
return { session, client: authedWireClient(session, client) };
|
|
29943
30124
|
};
|
|
@@ -30007,8 +30188,7 @@ function composePaymentsV2(spec) {
|
|
|
30007
30188
|
identity: identitySlice,
|
|
30008
30189
|
network,
|
|
30009
30190
|
kv,
|
|
30010
|
-
emitStatus: (status) => host.emit("connection:status", { status })
|
|
30011
|
-
onEpochChange: async () => void 0
|
|
30191
|
+
emitStatus: (status) => host.emit("connection:status", { status })
|
|
30012
30192
|
});
|
|
30013
30193
|
const deps = {
|
|
30014
30194
|
session: transport.session,
|
|
@@ -30038,7 +30218,10 @@ function composePaymentsV2(spec) {
|
|
|
30038
30218
|
network,
|
|
30039
30219
|
ownPubkey: identity.chainPubkey,
|
|
30040
30220
|
ownNametag: host.nametag,
|
|
30041
|
-
requestMemo: requestMemoCodec(identity.privateKey)
|
|
30221
|
+
requestMemo: requestMemoCodec(identity.privateKey),
|
|
30222
|
+
// §5.1: the session is the epoch authority — the facade registers its
|
|
30223
|
+
// restore hook on the session itself (FacadeSession.subscribeEpochChange).
|
|
30224
|
+
syncEpoch: () => transport.session.currentEpoch()
|
|
30042
30225
|
};
|
|
30043
30226
|
return new PaymentsFacade(deps);
|
|
30044
30227
|
}
|
|
@@ -30368,18 +30551,20 @@ var Sphere = class _Sphere {
|
|
|
30368
30551
|
}
|
|
30369
30552
|
/**
|
|
30370
30553
|
* P9: validate + resolve the paymentsV2 opt-in BEFORE any storage writes —
|
|
30371
|
-
* fail-closed at init, never a silently-degraded composition.
|
|
30554
|
+
* fail-closed at init, never a silently-degraded composition. Returns the
|
|
30555
|
+
* resolved composition (null = flag off); #728: resolving here also asserts
|
|
30556
|
+
* the single-network invariant (walletApi.network known + equal to the
|
|
30557
|
+
* Sphere network) before any session/KV/provider exists.
|
|
30372
30558
|
*/
|
|
30373
|
-
static resolvePaymentsV2Flag(flag, accountingConfig, swapConfig, walletApi) {
|
|
30374
|
-
if (flag !== true) return
|
|
30559
|
+
static resolvePaymentsV2Flag(flag, accountingConfig, swapConfig, walletApi, network) {
|
|
30560
|
+
if (flag !== true) return null;
|
|
30375
30561
|
if (accountingConfig || swapConfig) {
|
|
30376
30562
|
throw new SphereError(
|
|
30377
30563
|
"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.",
|
|
30378
30564
|
"INVALID_CONFIG"
|
|
30379
30565
|
);
|
|
30380
30566
|
}
|
|
30381
|
-
resolvePaymentsV2Composition(walletApi);
|
|
30382
|
-
return true;
|
|
30567
|
+
return resolvePaymentsV2Composition(walletApi, network);
|
|
30383
30568
|
}
|
|
30384
30569
|
/**
|
|
30385
30570
|
* Configure TokenRegistry in the main bundle context.
|
|
@@ -30434,12 +30619,14 @@ var Sphere = class _Sphere {
|
|
|
30434
30619
|
options.walletApi
|
|
30435
30620
|
);
|
|
30436
30621
|
sphere._verification = options.verification;
|
|
30437
|
-
sphere.
|
|
30622
|
+
sphere._paymentsV2Composition = _Sphere.resolvePaymentsV2Flag(
|
|
30438
30623
|
options.paymentsV2,
|
|
30439
30624
|
accountingConfig,
|
|
30440
30625
|
swapConfig,
|
|
30441
|
-
options.walletApi
|
|
30626
|
+
options.walletApi,
|
|
30627
|
+
options.network
|
|
30442
30628
|
);
|
|
30629
|
+
sphere._paymentsV2Enabled = sphere._paymentsV2Composition !== null;
|
|
30443
30630
|
sphere._password = options.password ?? null;
|
|
30444
30631
|
sphere._passwordProtected = sphere._password !== null;
|
|
30445
30632
|
progress?.({ step: "storing_keys", message: "Storing wallet keys..." });
|
|
@@ -30506,12 +30693,14 @@ var Sphere = class _Sphere {
|
|
|
30506
30693
|
options.walletApi
|
|
30507
30694
|
);
|
|
30508
30695
|
sphere._verification = options.verification;
|
|
30509
|
-
sphere.
|
|
30696
|
+
sphere._paymentsV2Composition = _Sphere.resolvePaymentsV2Flag(
|
|
30510
30697
|
options.paymentsV2,
|
|
30511
30698
|
accountingConfig,
|
|
30512
30699
|
swapConfig,
|
|
30513
|
-
options.walletApi
|
|
30700
|
+
options.walletApi,
|
|
30701
|
+
options.network
|
|
30514
30702
|
);
|
|
30703
|
+
sphere._paymentsV2Enabled = sphere._paymentsV2Composition !== null;
|
|
30515
30704
|
sphere._password = options.password ?? null;
|
|
30516
30705
|
sphere._passwordProtected = sphere._password !== null;
|
|
30517
30706
|
if (!options.storage.isConnected()) {
|
|
@@ -30585,12 +30774,14 @@ var Sphere = class _Sphere {
|
|
|
30585
30774
|
options.walletApi
|
|
30586
30775
|
);
|
|
30587
30776
|
sphere._verification = options.verification;
|
|
30588
|
-
sphere.
|
|
30777
|
+
sphere._paymentsV2Composition = _Sphere.resolvePaymentsV2Flag(
|
|
30589
30778
|
options.paymentsV2,
|
|
30590
30779
|
accountingConfig,
|
|
30591
30780
|
swapConfig,
|
|
30592
|
-
options.walletApi
|
|
30781
|
+
options.walletApi,
|
|
30782
|
+
options.network
|
|
30593
30783
|
);
|
|
30784
|
+
sphere._paymentsV2Enabled = sphere._paymentsV2Composition !== null;
|
|
30594
30785
|
sphere._password = options.password ?? null;
|
|
30595
30786
|
sphere._passwordProtected = sphere._password !== null;
|
|
30596
30787
|
progress?.({ step: "storing_keys", message: "Storing wallet keys..." });
|
|
@@ -33248,7 +33439,9 @@ var Sphere = class _Sphere {
|
|
|
33248
33439
|
* setOracleApiKey rebuild is what future operations snapshot.
|
|
33249
33440
|
*/
|
|
33250
33441
|
async startPaymentsV2Inner(index, identity) {
|
|
33251
|
-
this._paymentsV2Composition
|
|
33442
|
+
if (this._paymentsV2Composition === null) {
|
|
33443
|
+
throw new SphereError("paymentsV2: composition unresolved \u2014 init never ran the flag gate", "INVALID_CONFIG");
|
|
33444
|
+
}
|
|
33252
33445
|
if (!this._addressModules.get(index)?.tokenEngine) {
|
|
33253
33446
|
throw new SphereError(
|
|
33254
33447
|
"paymentsV2 requires the v2 token engine \u2014 the oracle must supply a trust base + gateway URL (and API key where required).",
|