@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.cjs
CHANGED
|
@@ -12244,14 +12244,14 @@ var IntentResume = class {
|
|
|
12244
12244
|
}
|
|
12245
12245
|
let changeOutput = null;
|
|
12246
12246
|
if (payload.split) {
|
|
12247
|
-
const
|
|
12247
|
+
const splitOpIndex2 = payload.direct.length;
|
|
12248
12248
|
let splitDelivered = false;
|
|
12249
12249
|
{
|
|
12250
12250
|
const source = await engine.decodeToken(await provider.getToken(payload.split.tokenId));
|
|
12251
12251
|
const outcome = await this.host.certifyOperation(
|
|
12252
12252
|
{
|
|
12253
12253
|
kind: "split",
|
|
12254
|
-
opIndex:
|
|
12254
|
+
opIndex: splitOpIndex2,
|
|
12255
12255
|
uiTokenId: `v2_${payload.split.tokenId}`,
|
|
12256
12256
|
genesisId: payload.split.tokenId,
|
|
12257
12257
|
sourceSdkData: "",
|
|
@@ -22358,6 +22358,7 @@ var AccountingModule = class _AccountingModule {
|
|
|
22358
22358
|
const unsubHistory = deps.on(
|
|
22359
22359
|
"history:updated",
|
|
22360
22360
|
(entry) => {
|
|
22361
|
+
if (!("dedupKey" in entry)) return;
|
|
22361
22362
|
this._handleHistoryUpdated(entry).catch((err) => {
|
|
22362
22363
|
logger.warn(LOG_TAG2, "Error handling history:updated event:", err);
|
|
22363
22364
|
});
|
|
@@ -27620,7 +27621,10 @@ var STORE_KEYS = {
|
|
|
27620
27621
|
shortfalls: "shortfalls",
|
|
27621
27622
|
settlingLinks: "settling",
|
|
27622
27623
|
streamCursor: (s) => `cursor:${s}`,
|
|
27623
|
-
epochLatch: "epoch-latch"
|
|
27624
|
+
epochLatch: "epoch-latch",
|
|
27625
|
+
// §5.2 InventoryView durable overlays (#625/#679).
|
|
27626
|
+
suspectedSpent: "suspected-spent",
|
|
27627
|
+
knownSpends: "known-spends"
|
|
27624
27628
|
};
|
|
27625
27629
|
|
|
27626
27630
|
// modules/payments-v2/machine/journal.ts
|
|
@@ -27870,7 +27874,7 @@ var TransferMachine = class {
|
|
|
27870
27874
|
}
|
|
27871
27875
|
if (cls === null) {
|
|
27872
27876
|
await this.applyCommitted(engine, plan.transferId, committed);
|
|
27873
|
-
this.closeTail(plan.transferId, plan.payload);
|
|
27877
|
+
this.closeTail(engine, plan.transferId, plan.payload, committed);
|
|
27874
27878
|
return { committed, deliveryPending };
|
|
27875
27879
|
}
|
|
27876
27880
|
if (cls === "conflict" && committed.length > 0) {
|
|
@@ -27896,10 +27900,10 @@ var TransferMachine = class {
|
|
|
27896
27900
|
}
|
|
27897
27901
|
await this.applyCommitted(engine, r.transferId, committed);
|
|
27898
27902
|
if (conflicts.length > 0) {
|
|
27899
|
-
return { deliveryPending, partial: await this.settlePartial(r, committed, conflicts) };
|
|
27903
|
+
return { deliveryPending, partial: await this.settlePartial(engine, r, committed, conflicts) };
|
|
27900
27904
|
}
|
|
27901
27905
|
await this.completeIntent(r.transferId, r.payload);
|
|
27902
|
-
await this.finishBookkeeping(r.transferId, r.payload, "resumed");
|
|
27906
|
+
await this.finishBookkeeping(engine, r.transferId, r.payload, "resumed", committed);
|
|
27903
27907
|
return { deliveryPending, partial: null };
|
|
27904
27908
|
}
|
|
27905
27909
|
// ── shared transitions ─────────────────────────────────────────────────────
|
|
@@ -28081,7 +28085,7 @@ var TransferMachine = class {
|
|
|
28081
28085
|
}
|
|
28082
28086
|
return added;
|
|
28083
28087
|
}
|
|
28084
|
-
async settlePartial(r, committed, conflicts) {
|
|
28088
|
+
async settlePartial(engine, r, committed, conflicts) {
|
|
28085
28089
|
const undelivered = conflicts.reduce((sum, c) => sum + c.amount, 0n);
|
|
28086
28090
|
const entry = {
|
|
28087
28091
|
transferId: r.transferId,
|
|
@@ -28093,26 +28097,27 @@ var TransferMachine = class {
|
|
|
28093
28097
|
};
|
|
28094
28098
|
await this.stores.shortfalls.upsert(entry);
|
|
28095
28099
|
await this.completeIntent(r.transferId, r.payload);
|
|
28096
|
-
await this.finishBookkeeping(r.transferId, r.payload, "resumed");
|
|
28100
|
+
await this.finishBookkeeping(engine, r.transferId, r.payload, "resumed", committed);
|
|
28097
28101
|
return entry;
|
|
28098
28102
|
}
|
|
28099
|
-
closeTail(transferId, payload) {
|
|
28103
|
+
closeTail(engine, transferId, payload, committed) {
|
|
28100
28104
|
void (async () => {
|
|
28101
28105
|
try {
|
|
28102
28106
|
await this.completeIntent(transferId, payload);
|
|
28103
28107
|
} catch {
|
|
28104
28108
|
return;
|
|
28105
28109
|
}
|
|
28106
|
-
await this.finishBookkeeping(transferId, payload, "sent");
|
|
28110
|
+
await this.finishBookkeeping(engine, transferId, payload, "sent", committed);
|
|
28107
28111
|
})();
|
|
28108
28112
|
}
|
|
28109
28113
|
async completeIntent(transferId, payload) {
|
|
28110
28114
|
const signature = payload.split !== void 0 ? await this.deps.signComplete(transferId) : void 0;
|
|
28111
28115
|
await this.deps.intents.complete(transferId, signature);
|
|
28112
28116
|
}
|
|
28113
|
-
async finishBookkeeping(transferId, payload, phase) {
|
|
28117
|
+
async finishBookkeeping(engine, transferId, payload, phase, committed) {
|
|
28114
28118
|
try {
|
|
28115
|
-
await this.
|
|
28119
|
+
const committedAmount = await this.settledAmount(engine, payload.coinId, committed);
|
|
28120
|
+
await this.deps.recordHistory?.({ transferId, payload, phase, committedAmount });
|
|
28116
28121
|
} catch {
|
|
28117
28122
|
}
|
|
28118
28123
|
try {
|
|
@@ -28120,6 +28125,26 @@ var TransferMachine = class {
|
|
|
28120
28125
|
} catch {
|
|
28121
28126
|
}
|
|
28122
28127
|
}
|
|
28128
|
+
/**
|
|
28129
|
+
* §5.9: SENT history records what SETTLED, never the plan — the value of the
|
|
28130
|
+
* certified recipient blobs (summarize()'s committed set is the only source;
|
|
28131
|
+
* a journal-rehydrated resume leg has no source token, but every committed
|
|
28132
|
+
* leg carries its recipient blob, so the outcome-derived rule is uniform).
|
|
28133
|
+
*/
|
|
28134
|
+
async settledAmount(engine, coinId, committed) {
|
|
28135
|
+
let total = 0n;
|
|
28136
|
+
for (const outcome of committed) {
|
|
28137
|
+
if (outcome.recipientBlob === void 0) continue;
|
|
28138
|
+
const token = await engine.decodeToken({
|
|
28139
|
+
v: TOKEN_BLOB_VERSION,
|
|
28140
|
+
network: 0,
|
|
28141
|
+
tokenId: "",
|
|
28142
|
+
token: outcome.recipientBlob
|
|
28143
|
+
});
|
|
28144
|
+
total += engine.balanceOf(token, coinId);
|
|
28145
|
+
}
|
|
28146
|
+
return total.toString();
|
|
28147
|
+
}
|
|
28123
28148
|
async disposeOnFailure(transferId, committed, cls) {
|
|
28124
28149
|
if (committed.length > 0 || cls === "keep-open") return;
|
|
28125
28150
|
try {
|
|
@@ -28144,7 +28169,7 @@ function conflictAmount(engine, payload, op, source) {
|
|
|
28144
28169
|
}
|
|
28145
28170
|
|
|
28146
28171
|
// modules/payments-v2/machine/resume.ts
|
|
28147
|
-
async function resumeAll(deps) {
|
|
28172
|
+
async function resumeAll(deps, options = {}) {
|
|
28148
28173
|
const report = { resumed: [], conflicted: [], failed: [] };
|
|
28149
28174
|
const stores = createMachineStores(deps.kv);
|
|
28150
28175
|
const open = await deps.intents.listOpen();
|
|
@@ -28154,7 +28179,12 @@ async function resumeAll(deps) {
|
|
|
28154
28179
|
} catch {
|
|
28155
28180
|
return report;
|
|
28156
28181
|
}
|
|
28157
|
-
await
|
|
28182
|
+
await dropStaleBackstops(
|
|
28183
|
+
stores,
|
|
28184
|
+
locals,
|
|
28185
|
+
new Set(open.map((i) => i.transferId)),
|
|
28186
|
+
options.isLocallyActive ?? (() => false)
|
|
28187
|
+
);
|
|
28158
28188
|
const jobs = await gateAndDecode(deps, stores, open, locals, report);
|
|
28159
28189
|
const ctx = {
|
|
28160
28190
|
deps,
|
|
@@ -28167,11 +28197,16 @@ async function resumeAll(deps) {
|
|
|
28167
28197
|
}
|
|
28168
28198
|
return report;
|
|
28169
28199
|
}
|
|
28170
|
-
async function
|
|
28200
|
+
async function dropStaleBackstops(stores, locals, openIds, isLocallyActive) {
|
|
28171
28201
|
for (const entry of locals.values()) {
|
|
28172
|
-
if (
|
|
28202
|
+
if (openIds.has(entry.transferId)) continue;
|
|
28203
|
+
if (entry.disposition !== "open") {
|
|
28173
28204
|
await stores.backstop.removeByKey(entry.transferId);
|
|
28205
|
+
continue;
|
|
28174
28206
|
}
|
|
28207
|
+
if (isLocallyActive(entry.transferId)) continue;
|
|
28208
|
+
if ((await stores.deliveryJournal.byTransfer(entry.transferId)).size > 0) continue;
|
|
28209
|
+
await stores.backstop.removeByKey(entry.transferId);
|
|
28175
28210
|
}
|
|
28176
28211
|
}
|
|
28177
28212
|
async function gateAndDecode(deps, stores, open, locals, report) {
|
|
@@ -28379,7 +28414,9 @@ var Converger = class {
|
|
|
28379
28414
|
return backstop.length > 0 || journal.some((e) => e.undeliverable !== true) || mints.length > 0;
|
|
28380
28415
|
}
|
|
28381
28416
|
async resumePass() {
|
|
28382
|
-
const report = await resumeAll(this.filteredMachineDeps()
|
|
28417
|
+
const report = await resumeAll(this.filteredMachineDeps(), {
|
|
28418
|
+
isLocallyActive: (id) => this.deps.isActiveOp(id)
|
|
28419
|
+
});
|
|
28383
28420
|
const progress = report.resumed.length > 0 || report.conflicted.length > 0;
|
|
28384
28421
|
await this.deps.reconcile({
|
|
28385
28422
|
resumed: report.resumed,
|
|
@@ -28495,6 +28532,136 @@ function distinctOpCount(entries) {
|
|
|
28495
28532
|
return new Set(entries.map((entry) => entry.opIndex)).size;
|
|
28496
28533
|
}
|
|
28497
28534
|
|
|
28535
|
+
// modules/payments-v2/restore.ts
|
|
28536
|
+
var ATTENTION_RESEED_REJECTED = "intent:reseed-rejected";
|
|
28537
|
+
var ALL_STREAMS = ["inventory", "mailbox", "payment_requests", "history"];
|
|
28538
|
+
async function reseedAndReset(deps) {
|
|
28539
|
+
for (const entry of await deps.stores.backstop.list()) {
|
|
28540
|
+
if (entry.disposition !== "open") continue;
|
|
28541
|
+
try {
|
|
28542
|
+
await deps.reput(entry.transferId, entry.payloadEnvelope, entry.requiresSeedClose);
|
|
28543
|
+
if (entry.requiresSeedClose) {
|
|
28544
|
+
await deps.reseedCheckpoint(entry.transferId, await splitOpIndex(deps, entry.payloadEnvelope));
|
|
28545
|
+
}
|
|
28546
|
+
} catch (err) {
|
|
28547
|
+
deps.attention(entry.transferId, ATTENTION_RESEED_REJECTED, err instanceof Error ? err.message : String(err));
|
|
28548
|
+
}
|
|
28549
|
+
}
|
|
28550
|
+
for (const stream of ALL_STREAMS) await deps.kv.remove(STORE_KEYS.streamCursor(stream));
|
|
28551
|
+
await deps.fullRePull();
|
|
28552
|
+
}
|
|
28553
|
+
async function splitOpIndex(deps, envelope) {
|
|
28554
|
+
const payload = await deps.decryptPayload(envelope);
|
|
28555
|
+
return Array.isArray(payload.direct) ? payload.direct.length : 0;
|
|
28556
|
+
}
|
|
28557
|
+
|
|
28558
|
+
// modules/payments-v2/inventory/presentation.ts
|
|
28559
|
+
function toToken(tokenId, snapshot, asset, registry, flags) {
|
|
28560
|
+
const iconUrl = registry.getIconUrl(asset.coinId);
|
|
28561
|
+
return {
|
|
28562
|
+
id: tokenId,
|
|
28563
|
+
coinId: asset.coinId,
|
|
28564
|
+
symbol: registry.getSymbol(asset.coinId),
|
|
28565
|
+
name: registry.getName(asset.coinId),
|
|
28566
|
+
decimals: registry.getDecimals(asset.coinId),
|
|
28567
|
+
...iconUrl !== null ? { iconUrl } : {},
|
|
28568
|
+
amount: asset.amount,
|
|
28569
|
+
status: flags.transferring ? "transferring" : "confirmed",
|
|
28570
|
+
createdAt: snapshot.createdAt,
|
|
28571
|
+
updatedAt: snapshot.updatedAt,
|
|
28572
|
+
lazy: true,
|
|
28573
|
+
...flags.suspectedSpent ? { suspectedSpent: true } : {}
|
|
28574
|
+
};
|
|
28575
|
+
}
|
|
28576
|
+
function aggregateAssets(entries, isTransferring, registry) {
|
|
28577
|
+
const groups = /* @__PURE__ */ new Map();
|
|
28578
|
+
for (const [tokenId, entry] of entries) {
|
|
28579
|
+
const moving = isTransferring(tokenId);
|
|
28580
|
+
for (const asset of entry.assets) {
|
|
28581
|
+
let group = groups.get(asset.coinId);
|
|
28582
|
+
if (!group) {
|
|
28583
|
+
group = { total: 0n, count: 0, transferring: 0n, transferringCount: 0 };
|
|
28584
|
+
groups.set(asset.coinId, group);
|
|
28585
|
+
}
|
|
28586
|
+
if (moving) {
|
|
28587
|
+
group.transferring += BigInt(asset.amount);
|
|
28588
|
+
group.transferringCount += 1;
|
|
28589
|
+
} else {
|
|
28590
|
+
group.total += BigInt(asset.amount);
|
|
28591
|
+
group.count += 1;
|
|
28592
|
+
}
|
|
28593
|
+
}
|
|
28594
|
+
}
|
|
28595
|
+
return [...groups].map(([coinId, totals]) => toAsset(coinId, totals, registry));
|
|
28596
|
+
}
|
|
28597
|
+
function toAsset(coinId, totals, registry) {
|
|
28598
|
+
const iconUrl = registry.getIconUrl(coinId);
|
|
28599
|
+
return {
|
|
28600
|
+
coinId,
|
|
28601
|
+
symbol: registry.getSymbol(coinId),
|
|
28602
|
+
name: registry.getName(coinId),
|
|
28603
|
+
decimals: registry.getDecimals(coinId),
|
|
28604
|
+
...iconUrl !== null ? { iconUrl } : {},
|
|
28605
|
+
totalAmount: totals.total.toString(),
|
|
28606
|
+
tokenCount: totals.count,
|
|
28607
|
+
confirmedAmount: totals.total.toString(),
|
|
28608
|
+
unconfirmedAmount: "0",
|
|
28609
|
+
confirmedTokenCount: totals.count,
|
|
28610
|
+
unconfirmedTokenCount: 0,
|
|
28611
|
+
transferringTokenCount: totals.transferringCount,
|
|
28612
|
+
transferringAmount: totals.transferring.toString(),
|
|
28613
|
+
priceUsd: null,
|
|
28614
|
+
priceEur: null,
|
|
28615
|
+
change24h: null,
|
|
28616
|
+
fiatValueUsd: null,
|
|
28617
|
+
fiatValueEur: null
|
|
28618
|
+
};
|
|
28619
|
+
}
|
|
28620
|
+
async function withPrices(raw, registry, price) {
|
|
28621
|
+
try {
|
|
28622
|
+
const names = [...new Set(raw.map((a) => registry.getName(a.coinId)))];
|
|
28623
|
+
const quotes = await price.getPrices(names);
|
|
28624
|
+
return raw.map((a) => priceOne(a, quotes.get(registry.getName(a.coinId))));
|
|
28625
|
+
} catch {
|
|
28626
|
+
return raw;
|
|
28627
|
+
}
|
|
28628
|
+
}
|
|
28629
|
+
function priceOne(asset, quote) {
|
|
28630
|
+
if (!quote) return asset;
|
|
28631
|
+
const human = Number(asset.totalAmount) / Math.pow(10, asset.decimals);
|
|
28632
|
+
return {
|
|
28633
|
+
...asset,
|
|
28634
|
+
priceUsd: quote.priceUsd,
|
|
28635
|
+
priceEur: quote.priceEur ?? null,
|
|
28636
|
+
change24h: quote.change24h ?? null,
|
|
28637
|
+
fiatValueUsd: human * quote.priceUsd,
|
|
28638
|
+
fiatValueEur: quote.priceEur != null ? human * quote.priceEur : null
|
|
28639
|
+
};
|
|
28640
|
+
}
|
|
28641
|
+
|
|
28642
|
+
// modules/payments-v2/machine/payload.ts
|
|
28643
|
+
function buildPayload(recipientPubkey, request, spend, spentStates) {
|
|
28644
|
+
return {
|
|
28645
|
+
v: 2,
|
|
28646
|
+
recipient: recipientPubkey,
|
|
28647
|
+
coinId: request.coinId,
|
|
28648
|
+
amount: request.amount,
|
|
28649
|
+
...request.memo !== void 0 ? { memo: request.memo } : {},
|
|
28650
|
+
direct: [...spend.plan.direct],
|
|
28651
|
+
...spend.plan.split !== void 0 ? {
|
|
28652
|
+
split: {
|
|
28653
|
+
tokenId: spend.plan.split.tokenId,
|
|
28654
|
+
splitAmount: spend.plan.split.splitAmount.toString(),
|
|
28655
|
+
remainderAmount: spend.plan.split.remainderAmount.toString()
|
|
28656
|
+
}
|
|
28657
|
+
} : {},
|
|
28658
|
+
spentStates
|
|
28659
|
+
};
|
|
28660
|
+
}
|
|
28661
|
+
function messageOf(err) {
|
|
28662
|
+
return err instanceof Error ? err.message : String(err);
|
|
28663
|
+
}
|
|
28664
|
+
|
|
28498
28665
|
// modules/payments-v2/history/History.ts
|
|
28499
28666
|
var WIRE_TOKEN_ID = /^(?:[0-9a-f]{2}){1,64}$/;
|
|
28500
28667
|
var WIRE_PUBKEY = /^0[23][0-9a-f]{64}$/;
|
|
@@ -28553,13 +28720,14 @@ var History = class {
|
|
|
28553
28720
|
}
|
|
28554
28721
|
// ── internals ────────────────────────────────────────────────────────────────
|
|
28555
28722
|
async post(build) {
|
|
28723
|
+
const record = build();
|
|
28556
28724
|
try {
|
|
28557
|
-
await this.deps.client.postHistory([
|
|
28725
|
+
await this.deps.client.postHistory([record]);
|
|
28558
28726
|
} catch (err) {
|
|
28559
28727
|
this.deps.log?.("history POST failed (dedupKey makes retry safe)", err);
|
|
28560
28728
|
return;
|
|
28561
28729
|
}
|
|
28562
|
-
this.deps.emit("history:updated");
|
|
28730
|
+
this.deps.emit("history:updated", this.toEntry(record));
|
|
28563
28731
|
}
|
|
28564
28732
|
ts(timestamp) {
|
|
28565
28733
|
return new Date(timestamp ?? (this.deps.now ?? Date.now)()).toISOString();
|
|
@@ -28616,93 +28784,9 @@ var History = class {
|
|
|
28616
28784
|
}
|
|
28617
28785
|
};
|
|
28618
28786
|
|
|
28619
|
-
// modules/payments-v2/inventory/presentation.ts
|
|
28620
|
-
function toToken(tokenId, snapshot, asset, registry, flags) {
|
|
28621
|
-
const iconUrl = registry.getIconUrl(asset.coinId);
|
|
28622
|
-
return {
|
|
28623
|
-
id: tokenId,
|
|
28624
|
-
coinId: asset.coinId,
|
|
28625
|
-
symbol: registry.getSymbol(asset.coinId),
|
|
28626
|
-
name: registry.getName(asset.coinId),
|
|
28627
|
-
decimals: registry.getDecimals(asset.coinId),
|
|
28628
|
-
...iconUrl !== null ? { iconUrl } : {},
|
|
28629
|
-
amount: asset.amount,
|
|
28630
|
-
status: flags.transferring ? "transferring" : "confirmed",
|
|
28631
|
-
createdAt: snapshot.createdAt,
|
|
28632
|
-
updatedAt: snapshot.updatedAt,
|
|
28633
|
-
lazy: true,
|
|
28634
|
-
...flags.suspectedSpent ? { suspectedSpent: true } : {}
|
|
28635
|
-
};
|
|
28636
|
-
}
|
|
28637
|
-
function aggregateAssets(entries, isTransferring, registry) {
|
|
28638
|
-
const groups = /* @__PURE__ */ new Map();
|
|
28639
|
-
for (const [tokenId, entry] of entries) {
|
|
28640
|
-
const moving = isTransferring(tokenId);
|
|
28641
|
-
for (const asset of entry.assets) {
|
|
28642
|
-
let group = groups.get(asset.coinId);
|
|
28643
|
-
if (!group) {
|
|
28644
|
-
group = { total: 0n, count: 0, transferring: 0n, transferringCount: 0 };
|
|
28645
|
-
groups.set(asset.coinId, group);
|
|
28646
|
-
}
|
|
28647
|
-
if (moving) {
|
|
28648
|
-
group.transferring += BigInt(asset.amount);
|
|
28649
|
-
group.transferringCount += 1;
|
|
28650
|
-
} else {
|
|
28651
|
-
group.total += BigInt(asset.amount);
|
|
28652
|
-
group.count += 1;
|
|
28653
|
-
}
|
|
28654
|
-
}
|
|
28655
|
-
}
|
|
28656
|
-
return [...groups].map(([coinId, totals]) => toAsset(coinId, totals, registry));
|
|
28657
|
-
}
|
|
28658
|
-
function toAsset(coinId, totals, registry) {
|
|
28659
|
-
const iconUrl = registry.getIconUrl(coinId);
|
|
28660
|
-
return {
|
|
28661
|
-
coinId,
|
|
28662
|
-
symbol: registry.getSymbol(coinId),
|
|
28663
|
-
name: registry.getName(coinId),
|
|
28664
|
-
decimals: registry.getDecimals(coinId),
|
|
28665
|
-
...iconUrl !== null ? { iconUrl } : {},
|
|
28666
|
-
totalAmount: totals.total.toString(),
|
|
28667
|
-
tokenCount: totals.count,
|
|
28668
|
-
confirmedAmount: totals.total.toString(),
|
|
28669
|
-
unconfirmedAmount: "0",
|
|
28670
|
-
confirmedTokenCount: totals.count,
|
|
28671
|
-
unconfirmedTokenCount: 0,
|
|
28672
|
-
transferringTokenCount: totals.transferringCount,
|
|
28673
|
-
transferringAmount: totals.transferring.toString(),
|
|
28674
|
-
priceUsd: null,
|
|
28675
|
-
priceEur: null,
|
|
28676
|
-
change24h: null,
|
|
28677
|
-
fiatValueUsd: null,
|
|
28678
|
-
fiatValueEur: null
|
|
28679
|
-
};
|
|
28680
|
-
}
|
|
28681
|
-
async function withPrices(raw, registry, price) {
|
|
28682
|
-
try {
|
|
28683
|
-
const names = [...new Set(raw.map((a) => registry.getName(a.coinId)))];
|
|
28684
|
-
const quotes = await price.getPrices(names);
|
|
28685
|
-
return raw.map((a) => priceOne(a, quotes.get(registry.getName(a.coinId))));
|
|
28686
|
-
} catch {
|
|
28687
|
-
return raw;
|
|
28688
|
-
}
|
|
28689
|
-
}
|
|
28690
|
-
function priceOne(asset, quote) {
|
|
28691
|
-
if (!quote) return asset;
|
|
28692
|
-
const human = Number(asset.totalAmount) / Math.pow(10, asset.decimals);
|
|
28693
|
-
return {
|
|
28694
|
-
...asset,
|
|
28695
|
-
priceUsd: quote.priceUsd,
|
|
28696
|
-
priceEur: quote.priceEur ?? null,
|
|
28697
|
-
change24h: quote.change24h ?? null,
|
|
28698
|
-
fiatValueUsd: human * quote.priceUsd,
|
|
28699
|
-
fiatValueEur: quote.priceEur != null ? human * quote.priceEur : null
|
|
28700
|
-
};
|
|
28701
|
-
}
|
|
28702
|
-
|
|
28703
28787
|
// modules/payments-v2/inventory/InventoryView.ts
|
|
28704
|
-
var SUSPECTED_KEY =
|
|
28705
|
-
var KNOWN_SPENDS_KEY =
|
|
28788
|
+
var SUSPECTED_KEY = STORE_KEYS.suspectedSpent;
|
|
28789
|
+
var KNOWN_SPENDS_KEY = STORE_KEYS.knownSpends;
|
|
28706
28790
|
var CURSOR_KEY = STORE_KEYS.streamCursor("inventory");
|
|
28707
28791
|
function stateKey(tokenId, stateHash) {
|
|
28708
28792
|
return `${tokenId}:${stateHash}`;
|
|
@@ -29006,19 +29090,24 @@ var Receive = class {
|
|
|
29006
29090
|
const engine = deps.engine();
|
|
29007
29091
|
const stored = [];
|
|
29008
29092
|
const pending = [];
|
|
29009
|
-
|
|
29093
|
+
const pageEpoch = () => deps.delivery.incomingEpoch() ?? deps.syncEpoch();
|
|
29010
29094
|
try {
|
|
29011
|
-
epoch = deps.syncEpoch();
|
|
29012
29095
|
const record = await deps.kv.get(STORE_KEYS.streamCursor("mailbox"));
|
|
29013
|
-
|
|
29014
|
-
for
|
|
29015
|
-
await
|
|
29016
|
-
|
|
29096
|
+
let basis = record !== null && record.syncEpoch === deps.syncEpoch() ? record : null;
|
|
29097
|
+
for (let pass = 0; pass < 2; pass++) {
|
|
29098
|
+
for await (const entry of deps.delivery.incoming(basis === null ? void 0 : String(basis.cursor))) {
|
|
29099
|
+
await this.processEntry(deps, engine, entry, pending, stored);
|
|
29100
|
+
if (pending.length >= ACK_BATCH_SIZE) await flushAcks(deps, pending, pageEpoch);
|
|
29101
|
+
}
|
|
29102
|
+
await flushAcks(deps, pending, pageEpoch);
|
|
29103
|
+
const served = deps.delivery.incomingEpoch();
|
|
29104
|
+
if (basis === null || served === null || served === basis.syncEpoch) break;
|
|
29105
|
+
await deps.kv.remove(STORE_KEYS.streamCursor("mailbox"));
|
|
29106
|
+
basis = null;
|
|
29017
29107
|
}
|
|
29018
|
-
await flushAcks(deps, pending, epoch);
|
|
29019
29108
|
} catch (err) {
|
|
29020
29109
|
logger.warn("PaymentsV2", "receive drain interrupted \u2014 unacked entries retry next drain:", err);
|
|
29021
|
-
await flushAcks(deps, pending,
|
|
29110
|
+
await flushAcks(deps, pending, pageEpoch).catch((flushErr) => {
|
|
29022
29111
|
logger.warn("PaymentsV2", "receive ack flush failed \u2014 cursor holds at the acked prefix:", flushErr);
|
|
29023
29112
|
});
|
|
29024
29113
|
}
|
|
@@ -29091,7 +29180,7 @@ async function announce(deps, entry, record) {
|
|
|
29091
29180
|
receivedAt
|
|
29092
29181
|
};
|
|
29093
29182
|
}
|
|
29094
|
-
async function flushAcks(deps, pending,
|
|
29183
|
+
async function flushAcks(deps, pending, epochOf) {
|
|
29095
29184
|
let lastAcked = null;
|
|
29096
29185
|
try {
|
|
29097
29186
|
while (pending.length > 0) {
|
|
@@ -29102,7 +29191,7 @@ async function flushAcks(deps, pending, epoch) {
|
|
|
29102
29191
|
}
|
|
29103
29192
|
} finally {
|
|
29104
29193
|
if (lastAcked !== null) {
|
|
29105
|
-
const record = { cursor: lastAcked, syncEpoch:
|
|
29194
|
+
const record = { cursor: lastAcked, syncEpoch: epochOf() };
|
|
29106
29195
|
await deps.kv.set(STORE_KEYS.streamCursor("mailbox"), record);
|
|
29107
29196
|
}
|
|
29108
29197
|
}
|
|
@@ -29203,9 +29292,19 @@ var Requests = class {
|
|
|
29203
29292
|
}
|
|
29204
29293
|
});
|
|
29205
29294
|
}
|
|
29295
|
+
/** Idempotent for a same-transferId re-write: committed only ratchets up, createdAt kept. */
|
|
29206
29296
|
writeLink(requestId, transferId, committed) {
|
|
29207
29297
|
return this.mutateJournal((m) => {
|
|
29208
|
-
|
|
29298
|
+
const prev = m.get(requestId);
|
|
29299
|
+
const same = prev !== void 0 && prev.transferId === transferId;
|
|
29300
|
+
const next = {
|
|
29301
|
+
requestId,
|
|
29302
|
+
transferId,
|
|
29303
|
+
committed: committed || same && prev.committed,
|
|
29304
|
+
createdAt: same ? prev.createdAt : this.now()
|
|
29305
|
+
};
|
|
29306
|
+
if (same && prev.committed === next.committed) return false;
|
|
29307
|
+
m.set(requestId, next);
|
|
29209
29308
|
return true;
|
|
29210
29309
|
});
|
|
29211
29310
|
}
|
|
@@ -29358,23 +29457,46 @@ var Requests = class {
|
|
|
29358
29457
|
const transferId = error instanceof SphereError && error.transferId !== void 0 ? error.transferId : void 0;
|
|
29359
29458
|
if (transferId !== void 0) {
|
|
29360
29459
|
const committed = error instanceof PartialSendConflictError;
|
|
29361
|
-
await this.
|
|
29362
|
-
this.setStatus(id, "settling");
|
|
29460
|
+
await this.settle(id, transferId, { committed, respond: false });
|
|
29363
29461
|
} else {
|
|
29364
29462
|
logger.error("PaymentsV2", `possibly-committed send for ${id} carried no transferId \u2014 paid (fail-closed)`);
|
|
29365
29463
|
this.setStatus(id, "paid");
|
|
29366
29464
|
}
|
|
29367
29465
|
} else {
|
|
29368
|
-
await this.
|
|
29369
|
-
this.setStatus(id, "pending");
|
|
29466
|
+
await this.revertPayable(id);
|
|
29370
29467
|
}
|
|
29371
29468
|
throw error;
|
|
29372
29469
|
}
|
|
29373
|
-
this.
|
|
29374
|
-
await this.respondPaid(id, result.id);
|
|
29375
|
-
await this.clearLink(id);
|
|
29470
|
+
await this.settle(id, result.id, { committed: true, respond: true });
|
|
29376
29471
|
return result;
|
|
29377
29472
|
}
|
|
29473
|
+
/**
|
|
29474
|
+
* THE settlement invariant (#441 + the failed-respond P1): a settling link is
|
|
29475
|
+
* removed ONLY by (a) a CONFIRMED paid respond — a 2xx, or the 409
|
|
29476
|
+
* already-resolved absorb — or (b) a proven clean pre-commit failure
|
|
29477
|
+
* (revertPayable). Nothing else removes one: not a network error, not a 5xx,
|
|
29478
|
+
* not a reload. Every path that binds a request to a transfer outcome funnels
|
|
29479
|
+
* through here — pay()'s clean success (respond now), pay()'s
|
|
29480
|
+
* possibly-committed throw (respond deferred), and reconcile's deferred arms
|
|
29481
|
+
* — so a failed respond always leaves the link + 'settling' and the next
|
|
29482
|
+
* reconcile pass (the committed-link override) retries the respond.
|
|
29483
|
+
*/
|
|
29484
|
+
async settle(requestId, transferId, opts) {
|
|
29485
|
+
let journalDown = false;
|
|
29486
|
+
try {
|
|
29487
|
+
await this.writeLink(requestId, transferId, opts.committed);
|
|
29488
|
+
} catch (err) {
|
|
29489
|
+
if (!opts.respond) throw err;
|
|
29490
|
+
journalDown = true;
|
|
29491
|
+
logger.error("PaymentsV2", `settling link write failed for ${requestId} \u2014 responding fail-closed:`, err);
|
|
29492
|
+
}
|
|
29493
|
+
if (!(opts.respond && await this.respondPaid(requestId, transferId))) {
|
|
29494
|
+
this.setStatus(requestId, journalDown ? "paid" : "settling");
|
|
29495
|
+
return;
|
|
29496
|
+
}
|
|
29497
|
+
if (!journalDown) await this.clearLink(requestId).catch(() => void 0);
|
|
29498
|
+
this.setStatus(requestId, "paid");
|
|
29499
|
+
}
|
|
29378
29500
|
/** 'paid' respond leg: 409 = already resolved = idempotent success; other errors defer. */
|
|
29379
29501
|
async respondPaid(id, transferId) {
|
|
29380
29502
|
try {
|
|
@@ -29410,32 +29532,32 @@ var Requests = class {
|
|
|
29410
29532
|
let aborted = null;
|
|
29411
29533
|
for (const [requestId, link] of [...this.journal]) {
|
|
29412
29534
|
if (link.committed || resumed.has(link.transferId)) {
|
|
29413
|
-
await this.resolvePaid(requestId, link
|
|
29535
|
+
await this.resolvePaid(requestId, link);
|
|
29414
29536
|
} else if (conflicted.has(link.transferId)) {
|
|
29415
29537
|
await this.revertPayable(requestId);
|
|
29416
29538
|
} else if (open.has(link.transferId)) {
|
|
29417
29539
|
} else {
|
|
29418
|
-
aborted = await this.reconcileUnaccounted(requestId, link
|
|
29540
|
+
aborted = await this.reconcileUnaccounted(requestId, link, aborted);
|
|
29419
29541
|
}
|
|
29420
29542
|
}
|
|
29421
29543
|
}
|
|
29422
29544
|
// Unaccounted link: consult the server aborted authority (lazy, memoized);
|
|
29423
29545
|
// a probe failure defers the link to the next session.
|
|
29424
|
-
async reconcileUnaccounted(requestId,
|
|
29546
|
+
async reconcileUnaccounted(requestId, link, aborted) {
|
|
29425
29547
|
try {
|
|
29426
29548
|
aborted ??= new Set(await this.deps.listAbortedTransferIds());
|
|
29427
|
-
if (aborted.has(transferId)) await this.revertPayable(requestId);
|
|
29428
|
-
else await this.resolvePaid(requestId,
|
|
29549
|
+
if (aborted.has(link.transferId)) await this.revertPayable(requestId);
|
|
29550
|
+
else await this.resolvePaid(requestId, link);
|
|
29429
29551
|
} catch (err) {
|
|
29430
29552
|
logger.warn("PaymentsV2", `aborted-intent probe failed \u2014 ${requestId} deferred:`, err);
|
|
29431
29553
|
}
|
|
29432
29554
|
return aborted;
|
|
29433
29555
|
}
|
|
29434
|
-
|
|
29435
|
-
|
|
29436
|
-
|
|
29437
|
-
this.setStatus(requestId, "paid");
|
|
29556
|
+
/** Deferred paid: the ONE settlement path again — a failed respond keeps the link. */
|
|
29557
|
+
resolvePaid(requestId, link) {
|
|
29558
|
+
return this.settle(requestId, link.transferId, { committed: link.committed, respond: true });
|
|
29438
29559
|
}
|
|
29560
|
+
/** Removal cause (b): a PROVEN clean outcome (pre-commit failure / server-aborted). */
|
|
29439
29561
|
async revertPayable(requestId) {
|
|
29440
29562
|
await this.clearLink(requestId);
|
|
29441
29563
|
this.setStatus(requestId, "pending");
|
|
@@ -29759,11 +29881,12 @@ function composeFacadeParts(deps, hooks) {
|
|
|
29759
29881
|
client: deps.client,
|
|
29760
29882
|
fieldKey: deps.fieldKey,
|
|
29761
29883
|
registry: deps.registry,
|
|
29762
|
-
emit: (event) => deps.emit(event,
|
|
29884
|
+
emit: (event, entry) => deps.emit(event, entry),
|
|
29763
29885
|
...deps.now !== void 0 ? { now: deps.now } : {},
|
|
29764
29886
|
...deps.newId !== void 0 ? { newId: deps.newId } : {}
|
|
29765
29887
|
});
|
|
29766
29888
|
const machineDeps = buildMachineDeps(deps, hooks, historyStore);
|
|
29889
|
+
const machineStores = createMachineStores(deps.kv);
|
|
29767
29890
|
const heldStates = /* @__PURE__ */ new Map();
|
|
29768
29891
|
return {
|
|
29769
29892
|
ownPubkeyBytes,
|
|
@@ -29771,12 +29894,29 @@ function composeFacadeParts(deps, hooks) {
|
|
|
29771
29894
|
ledger,
|
|
29772
29895
|
queue,
|
|
29773
29896
|
historyStore,
|
|
29774
|
-
machineStores
|
|
29897
|
+
machineStores,
|
|
29775
29898
|
machineDeps,
|
|
29776
29899
|
machine: new TransferMachine(machineDeps),
|
|
29777
29900
|
heldStates,
|
|
29778
29901
|
receiveLoop: buildReceive(deps, hooks, historyStore, heldStates),
|
|
29779
|
-
requests: buildRequests(deps, hooks)
|
|
29902
|
+
requests: buildRequests(deps, hooks),
|
|
29903
|
+
restoreDeps: buildRestoreDeps(deps, machineDeps, machineStores, view)
|
|
29904
|
+
};
|
|
29905
|
+
}
|
|
29906
|
+
function buildRestoreDeps(deps, machineDeps, machineStores, view) {
|
|
29907
|
+
return {
|
|
29908
|
+
stores: machineStores,
|
|
29909
|
+
kv: deps.kv,
|
|
29910
|
+
reput: (transferId, envelope, requiresSeedClose) => machineDeps.intents.put(transferId, envelope, requiresSeedClose),
|
|
29911
|
+
reseedCheckpoint: (transferId, opIndex) => deps.checkpointStore.reseedCheckpoint(transferId, opIndex),
|
|
29912
|
+
decryptPayload: machineDeps.decryptPayload,
|
|
29913
|
+
fullRePull: () => view.onEpochReset(),
|
|
29914
|
+
attention: (transferId, code, detail) => {
|
|
29915
|
+
deps.emit(
|
|
29916
|
+
"transfer:attention",
|
|
29917
|
+
detail === void 0 ? { transferId, code } : { transferId, code, detail }
|
|
29918
|
+
);
|
|
29919
|
+
}
|
|
29780
29920
|
};
|
|
29781
29921
|
}
|
|
29782
29922
|
function buildMachineDeps(deps, hooks, historyStore) {
|
|
@@ -29798,11 +29938,13 @@ function buildMachineDeps(deps, hooks, historyStore) {
|
|
|
29798
29938
|
ownPubkey: hexToBytes4(deps.ownPubkey),
|
|
29799
29939
|
emit: deps.emit,
|
|
29800
29940
|
now: deps.now ?? Date.now,
|
|
29801
|
-
recordHistory: async ({ transferId, payload }) => {
|
|
29941
|
+
recordHistory: async ({ transferId, payload, committedAmount }) => {
|
|
29802
29942
|
await historyStore.recordSent({
|
|
29803
29943
|
transferId,
|
|
29804
29944
|
coinId: payload.coinId,
|
|
29805
|
-
|
|
29945
|
+
// §5.9: the SETTLED amount (machine-computed from the certified
|
|
29946
|
+
// recipient blobs), never payload.amount — the plan.
|
|
29947
|
+
amount: committedAmount,
|
|
29806
29948
|
recipientPubkey: payload.recipient,
|
|
29807
29949
|
...payload.memo !== void 0 ? { memo: payload.memo } : {}
|
|
29808
29950
|
});
|
|
@@ -29829,7 +29971,7 @@ function buildReceive(deps, hooks, historyStore, heldStates) {
|
|
|
29829
29971
|
detail === void 0 ? { transferId, code } : { transferId, code, detail }
|
|
29830
29972
|
);
|
|
29831
29973
|
},
|
|
29832
|
-
syncEpoch: deps.syncEpoch
|
|
29974
|
+
syncEpoch: deps.syncEpoch,
|
|
29833
29975
|
...deps.now !== void 0 ? { now: deps.now } : {}
|
|
29834
29976
|
});
|
|
29835
29977
|
}
|
|
@@ -29882,6 +30024,7 @@ var PaymentsFacade = class {
|
|
|
29882
30024
|
this.receiveLoop = parts.receiveLoop;
|
|
29883
30025
|
this.heldStates = parts.heldStates;
|
|
29884
30026
|
this.ownPubkeyBytes = parts.ownPubkeyBytes;
|
|
30027
|
+
this.restoreDeps = parts.restoreDeps;
|
|
29885
30028
|
this.requests = parts.requests;
|
|
29886
30029
|
deps.deliveryPort.bindDeliveryKeys((blob) => this.engine().deliveryKeys(blob));
|
|
29887
30030
|
this.heartbeat = new ConvergenceHeartbeat({
|
|
@@ -29910,6 +30053,7 @@ var PaymentsFacade = class {
|
|
|
29910
30053
|
receiveLoop;
|
|
29911
30054
|
heldStates;
|
|
29912
30055
|
ownPubkeyBytes;
|
|
30056
|
+
restoreDeps;
|
|
29913
30057
|
requests;
|
|
29914
30058
|
currentEngine = null;
|
|
29915
30059
|
pendingOps = /* @__PURE__ */ new Set();
|
|
@@ -29920,6 +30064,8 @@ var PaymentsFacade = class {
|
|
|
29920
30064
|
converger;
|
|
29921
30065
|
/** Resume single-flight (§7): ticks, start() and resumeNow() coalesce onto ONE pass. */
|
|
29922
30066
|
resumeFlight = new SingleFlight();
|
|
30067
|
+
/** §5.1/§7: restore + convergence passes SERIALIZE here. */
|
|
30068
|
+
passChain = new SerialChain();
|
|
29923
30069
|
/** §7 ownership: ids with an in-process machine attempt — the pass never adopts one. */
|
|
29924
30070
|
activeMoneyOps = /* @__PURE__ */ new Set();
|
|
29925
30071
|
// ── lifecycle ──────────────────────────────────────────────────────────────
|
|
@@ -29927,8 +30073,8 @@ var PaymentsFacade = class {
|
|
|
29927
30073
|
if (this.started) return;
|
|
29928
30074
|
this.started = true;
|
|
29929
30075
|
this.heartbeat.start();
|
|
29930
|
-
await this.deps.session.start();
|
|
29931
30076
|
this.unsubscribers.push(
|
|
30077
|
+
this.deps.session.subscribeEpochChange((epoch) => this.handleEpochChange(epoch)),
|
|
29932
30078
|
this.deps.session.subscribeStream("mailbox", () => {
|
|
29933
30079
|
this.trackTail(this.receiveLoop.drainOnce());
|
|
29934
30080
|
}),
|
|
@@ -29943,6 +30089,7 @@ var PaymentsFacade = class {
|
|
|
29943
30089
|
if (status === "connected") this.heartbeat.recovered();
|
|
29944
30090
|
});
|
|
29945
30091
|
if (statusUnsub !== void 0) this.unsubscribers.push(statusUnsub);
|
|
30092
|
+
await this.deps.session.start();
|
|
29946
30093
|
this.trackTail(this.runConvergencePass());
|
|
29947
30094
|
this.trackTail(this.seedHeldStates());
|
|
29948
30095
|
await this.view.fullPull().catch(() => void 0);
|
|
@@ -29983,7 +30130,7 @@ var PaymentsFacade = class {
|
|
|
29983
30130
|
}
|
|
29984
30131
|
// ── money movement ─────────────────────────────────────────────────────────
|
|
29985
30132
|
send(request) {
|
|
29986
|
-
return this.track(this.
|
|
30133
|
+
return this.track(this.sendOutcome(request));
|
|
29987
30134
|
}
|
|
29988
30135
|
async receive() {
|
|
29989
30136
|
const transfers = await this.track(this.receiveLoop.drainOnce());
|
|
@@ -29998,20 +30145,27 @@ var PaymentsFacade = class {
|
|
|
29998
30145
|
if (!this.started) return;
|
|
29999
30146
|
await this.track(this.runConvergencePass());
|
|
30000
30147
|
}
|
|
30148
|
+
/** §5.1 restore — awaited by the session latch BEFORE streams resume; never coalesced onto a pre-restore pass. */
|
|
30149
|
+
async handleEpochChange(_newEpoch) {
|
|
30150
|
+
await this.passChain.enqueue(() => reseedAndReset(this.restoreDeps));
|
|
30151
|
+
await this.passChain.enqueue(() => this.convergeBody());
|
|
30152
|
+
}
|
|
30001
30153
|
/** One single-flighted pass + its reschedule: concurrent callers coalesce. */
|
|
30002
30154
|
runConvergencePass() {
|
|
30003
|
-
return this.resumeFlight.run(
|
|
30004
|
-
|
|
30005
|
-
|
|
30006
|
-
|
|
30007
|
-
|
|
30155
|
+
return this.resumeFlight.run(() => this.passChain.enqueue(() => this.convergeBody()));
|
|
30156
|
+
}
|
|
30157
|
+
async convergeBody() {
|
|
30158
|
+
const outcome = await this.converger.convergeOnce();
|
|
30159
|
+
const pending = await this.converger.pendingWork().catch(() => true);
|
|
30160
|
+
this.heartbeat.settle(outcome, pending);
|
|
30008
30161
|
}
|
|
30009
30162
|
nowMs() {
|
|
30010
30163
|
return (this.deps.now ?? Date.now)();
|
|
30011
30164
|
}
|
|
30012
30165
|
// ── send policy (§5.5 + old-loop parity; the machine stays policy-free) ────
|
|
30013
|
-
|
|
30014
|
-
|
|
30166
|
+
/** The ONE place a send() outcome is shaped: success emits in finishSend, a
|
|
30167
|
+
* CLEAN rejection emits `transfer:updated{status:'failed'}` here (§4). */
|
|
30168
|
+
async sendOutcome(request) {
|
|
30015
30169
|
const run = {
|
|
30016
30170
|
amount: request.amount,
|
|
30017
30171
|
delivered: [],
|
|
@@ -30019,12 +30173,36 @@ var PaymentsFacade = class {
|
|
|
30019
30173
|
tokenTransfers: [],
|
|
30020
30174
|
settledShortfalls: [],
|
|
30021
30175
|
firstPartialId: void 0,
|
|
30022
|
-
pendingAny: false
|
|
30176
|
+
pendingAny: false,
|
|
30177
|
+
lastTransferId: ""
|
|
30023
30178
|
};
|
|
30179
|
+
try {
|
|
30180
|
+
return await this.sendWithPolicy(request, run);
|
|
30181
|
+
} catch (err) {
|
|
30182
|
+
this.emitCleanFailure(err, run.lastTransferId);
|
|
30183
|
+
throw err;
|
|
30184
|
+
}
|
|
30185
|
+
}
|
|
30186
|
+
/** Only a CLEAN failure (nothing certified, classifyError 'other') is 'failed'.
|
|
30187
|
+
* Keep-open/partial/conflict outcomes are pending/converging — labelling them
|
|
30188
|
+
* 'failed' invites a dApp re-send, i.e. a double-pay (#631/#676). */
|
|
30189
|
+
emitCleanFailure(err, transferId) {
|
|
30190
|
+
if (isPossiblyCommittedSendOutcome(err) || classifyError(err) !== "other") return;
|
|
30191
|
+
const failed = {
|
|
30192
|
+
id: transferId,
|
|
30193
|
+
status: "failed",
|
|
30194
|
+
tokens: [],
|
|
30195
|
+
tokenTransfers: [],
|
|
30196
|
+
error: messageOf(err)
|
|
30197
|
+
};
|
|
30198
|
+
this.deps.emit("transfer:updated", failed);
|
|
30199
|
+
}
|
|
30200
|
+
async sendWithPolicy(request, run) {
|
|
30201
|
+
const recipient = await this.requireSameNetworkRecipient(request.recipient);
|
|
30024
30202
|
for (let attempt = 0; ; attempt++) {
|
|
30025
30203
|
let ctx;
|
|
30026
30204
|
try {
|
|
30027
|
-
ctx = await this.planAndMaterialize(recipient.chainPubkey, { ...request, amount: run.amount });
|
|
30205
|
+
ctx = await this.planAndMaterialize(recipient.chainPubkey, { ...request, amount: run.amount }, run);
|
|
30028
30206
|
} catch (err) {
|
|
30029
30207
|
throw this.partialize(err, run);
|
|
30030
30208
|
}
|
|
@@ -30069,11 +30247,8 @@ var PaymentsFacade = class {
|
|
|
30069
30247
|
this.activeMoneyOps.delete(ctx.transferId);
|
|
30070
30248
|
}
|
|
30071
30249
|
}
|
|
30072
|
-
/**
|
|
30073
|
-
*
|
|
30074
|
-
* the machine BEFORE complete). Accumulate the settled set, then re-plan ONLY
|
|
30075
|
-
* the remainder under a NEW transferId — never the full amount.
|
|
30076
|
-
*/
|
|
30250
|
+
/** Partial (#677/#690): shortfall already durable (machine wrote it BEFORE
|
|
30251
|
+
* complete); accumulate settled, re-plan ONLY the remainder, NEW transferId. */
|
|
30077
30252
|
async consumePartial(ctx, disposition, run, attempt) {
|
|
30078
30253
|
if (disposition.deliveryPending) {
|
|
30079
30254
|
run.pendingAny = true;
|
|
@@ -30099,11 +30274,8 @@ var PaymentsFacade = class {
|
|
|
30099
30274
|
run.amount = partial.remainingAmount;
|
|
30100
30275
|
return null;
|
|
30101
30276
|
}
|
|
30102
|
-
/**
|
|
30103
|
-
*
|
|
30104
|
-
* backstop still 'open' (committed>0) → converge via the same machine's
|
|
30105
|
-
* resumeIntent; clean conflict with a demoted source → bounded full re-plan.
|
|
30106
|
-
*/
|
|
30277
|
+
/** Failure disposition: possibly-committed → rethrow UNWRAPPED; backstop
|
|
30278
|
+
* 'open' → converge via resumeIntent; clean demoted conflict → full re-plan. */
|
|
30107
30279
|
async disposeFailedAttempt(ctx, err) {
|
|
30108
30280
|
if (isPossiblyCommittedSendOutcome(err)) {
|
|
30109
30281
|
this.settleKeepOpen(ctx);
|
|
@@ -30152,8 +30324,9 @@ var PaymentsFacade = class {
|
|
|
30152
30324
|
}
|
|
30153
30325
|
return { kind: "rethrow", error: err };
|
|
30154
30326
|
}
|
|
30155
|
-
async planAndMaterialize(recipientPubkey, request) {
|
|
30327
|
+
async planAndMaterialize(recipientPubkey, request, run) {
|
|
30156
30328
|
const transferId = this.newId();
|
|
30329
|
+
run.lastTransferId = transferId;
|
|
30157
30330
|
const planned = this.queue.plan(transferId, { coinId: request.coinId, amount: request.amount });
|
|
30158
30331
|
const spend = planned.kind === "planned" ? planned.spend : await planned.settled;
|
|
30159
30332
|
const sourceIds = this.markPlanned(transferId, request.coinId, spend);
|
|
@@ -30285,11 +30458,7 @@ var PaymentsFacade = class {
|
|
|
30285
30458
|
this.deps.emit("transfer:updated", result);
|
|
30286
30459
|
return result;
|
|
30287
30460
|
}
|
|
30288
|
-
/**
|
|
30289
|
-
* With nothing delivered the error passes UNWRAPPED (identity + cause kept);
|
|
30290
|
-
* after ≥1 delivered leg EVERY failure surfaces as PartialSendConflictError
|
|
30291
|
-
* over the accumulated settled set — never bare, never a full-amount retry.
|
|
30292
|
-
*/
|
|
30461
|
+
/** Nothing delivered → UNWRAPPED; after ≥1 delivered leg every failure surfaces as PartialSendConflictError over the settled set. */
|
|
30293
30462
|
partialize(err, run) {
|
|
30294
30463
|
if (run.delivered.length === 0) return err;
|
|
30295
30464
|
return new PartialSendConflictError(
|
|
@@ -30441,22 +30610,14 @@ var PaymentsFacade = class {
|
|
|
30441
30610
|
}
|
|
30442
30611
|
}
|
|
30443
30612
|
toUiToken(tokenId, coinId, amount) {
|
|
30444
|
-
const registry = this.deps.registry;
|
|
30445
|
-
const iconUrl = registry.getIconUrl(coinId);
|
|
30446
30613
|
const now = this.nowMs();
|
|
30447
|
-
return
|
|
30448
|
-
|
|
30449
|
-
|
|
30450
|
-
|
|
30451
|
-
|
|
30452
|
-
|
|
30453
|
-
|
|
30454
|
-
amount: amount.toString(),
|
|
30455
|
-
status: "transferring",
|
|
30456
|
-
createdAt: now,
|
|
30457
|
-
updatedAt: now,
|
|
30458
|
-
lazy: true
|
|
30459
|
-
};
|
|
30614
|
+
return toToken(
|
|
30615
|
+
tokenId,
|
|
30616
|
+
{ assets: [], createdAt: now, updatedAt: now },
|
|
30617
|
+
{ coinId, amount: amount.toString() },
|
|
30618
|
+
this.deps.registry,
|
|
30619
|
+
{ transferring: true, suspectedSpent: false }
|
|
30620
|
+
);
|
|
30460
30621
|
}
|
|
30461
30622
|
track(op) {
|
|
30462
30623
|
const settled = op.then(
|
|
@@ -30471,27 +30632,6 @@ var PaymentsFacade = class {
|
|
|
30471
30632
|
this.track(op).catch(() => void 0);
|
|
30472
30633
|
}
|
|
30473
30634
|
};
|
|
30474
|
-
function buildPayload(recipientPubkey, request, spend, spentStates) {
|
|
30475
|
-
return {
|
|
30476
|
-
v: 2,
|
|
30477
|
-
recipient: recipientPubkey,
|
|
30478
|
-
coinId: request.coinId,
|
|
30479
|
-
amount: request.amount,
|
|
30480
|
-
...request.memo !== void 0 ? { memo: request.memo } : {},
|
|
30481
|
-
direct: [...spend.plan.direct],
|
|
30482
|
-
...spend.plan.split !== void 0 ? {
|
|
30483
|
-
split: {
|
|
30484
|
-
tokenId: spend.plan.split.tokenId,
|
|
30485
|
-
splitAmount: spend.plan.split.splitAmount.toString(),
|
|
30486
|
-
remainderAmount: spend.plan.split.remainderAmount.toString()
|
|
30487
|
-
}
|
|
30488
|
-
} : {},
|
|
30489
|
-
spentStates
|
|
30490
|
-
};
|
|
30491
|
-
}
|
|
30492
|
-
function messageOf(err) {
|
|
30493
|
-
return err instanceof Error ? err.message : String(err);
|
|
30494
|
-
}
|
|
30495
30635
|
|
|
30496
30636
|
// impl/wallet-api-v2/http.ts
|
|
30497
30637
|
var WalletApiHttpError = class extends Error {
|
|
@@ -30837,6 +30977,7 @@ var WalletApiSession = class {
|
|
|
30837
30977
|
epochLatch = null;
|
|
30838
30978
|
reseedChain = Promise.resolve();
|
|
30839
30979
|
reseedsPending = 0;
|
|
30980
|
+
epochHandlers = /* @__PURE__ */ new Set();
|
|
30840
30981
|
handlers = /* @__PURE__ */ new Map();
|
|
30841
30982
|
statusHandlers = /* @__PURE__ */ new Set();
|
|
30842
30983
|
socket = null;
|
|
@@ -30939,6 +31080,18 @@ var WalletApiSession = class {
|
|
|
30939
31080
|
await this.deps.kv.set(this.refreshKey, tokens.refreshToken);
|
|
30940
31081
|
}
|
|
30941
31082
|
// ── epoch latch (F12) ─────────────────────────────────────────────────────
|
|
31083
|
+
/** §5.1: the latched server syncEpoch ('' before first server contact). */
|
|
31084
|
+
currentEpoch() {
|
|
31085
|
+
return this.epochLatch ?? "";
|
|
31086
|
+
}
|
|
31087
|
+
/**
|
|
31088
|
+
* §5.1 restore hook (FacadeSession contract): handlers are AWAITED inside
|
|
31089
|
+
* the reseed chain, BEFORE any stream nudge resumes.
|
|
31090
|
+
*/
|
|
31091
|
+
subscribeEpochChange(handler) {
|
|
31092
|
+
this.epochHandlers.add(handler);
|
|
31093
|
+
return () => this.epochHandlers.delete(handler);
|
|
31094
|
+
}
|
|
30942
31095
|
noteEpoch(epoch) {
|
|
30943
31096
|
if (this.epochLatch === epoch) return;
|
|
30944
31097
|
const previous = this.epochLatch;
|
|
@@ -30952,7 +31105,7 @@ var WalletApiSession = class {
|
|
|
30952
31105
|
}
|
|
30953
31106
|
async runReseed(epoch, previous) {
|
|
30954
31107
|
try {
|
|
30955
|
-
|
|
31108
|
+
for (const handler of [...this.epochHandlers]) await handler(epoch);
|
|
30956
31109
|
await this.deps.kv.set(STORE_KEYS.epochLatch, epoch);
|
|
30957
31110
|
} catch {
|
|
30958
31111
|
if (this.epochLatch === epoch) this.epochLatch = previous;
|
|
@@ -31214,6 +31367,7 @@ var WalletApiDeliveryPort = class {
|
|
|
31214
31367
|
wake;
|
|
31215
31368
|
deriveFn;
|
|
31216
31369
|
seenChain = new SerialChain();
|
|
31370
|
+
lastIncomingEpoch = null;
|
|
31217
31371
|
constructor(config) {
|
|
31218
31372
|
this.client = config.client;
|
|
31219
31373
|
this.kv = config.kv;
|
|
@@ -31308,6 +31462,7 @@ var WalletApiDeliveryPort = class {
|
|
|
31308
31462
|
const seen = await this.readSeen();
|
|
31309
31463
|
let page = await this.client.listMailbox(sinceCursor === void 0 ? 0 : Number(sinceCursor));
|
|
31310
31464
|
for (; ; ) {
|
|
31465
|
+
this.lastIncomingEpoch = String(page.syncEpoch);
|
|
31311
31466
|
for (const entry of page.entries) {
|
|
31312
31467
|
if (entry.status !== "unclaimed") continue;
|
|
31313
31468
|
if (seen.has(entry.entryId)) continue;
|
|
@@ -31317,6 +31472,10 @@ var WalletApiDeliveryPort = class {
|
|
|
31317
31472
|
page = await this.client.listMailbox(page.cursor);
|
|
31318
31473
|
}
|
|
31319
31474
|
}
|
|
31475
|
+
/** §5.7: the page-honest epoch — the restore-detection source Receive keys on. */
|
|
31476
|
+
incomingEpoch() {
|
|
31477
|
+
return this.lastIncomingEpoch;
|
|
31478
|
+
}
|
|
31320
31479
|
toIncoming(entry) {
|
|
31321
31480
|
const bundle = this.openEnvelope(entry);
|
|
31322
31481
|
return {
|
|
@@ -31394,7 +31553,7 @@ var WalletApiSplitCheckpointStore = class {
|
|
|
31394
31553
|
return utf8(`${transferId}:${String(opIndex)}`);
|
|
31395
31554
|
}
|
|
31396
31555
|
async put(transferId, opIndex, bytes) {
|
|
31397
|
-
const {
|
|
31556
|
+
const { kv, fieldKey } = this.config;
|
|
31398
31557
|
const aad = this.aad(transferId, opIndex);
|
|
31399
31558
|
const cacheKey = this.cacheKey(transferId, opIndex);
|
|
31400
31559
|
let envelope = await kv.get(cacheKey);
|
|
@@ -31402,10 +31561,21 @@ var WalletApiSplitCheckpointStore = class {
|
|
|
31402
31561
|
envelope = encryptFieldBytes(fieldKey, bytes, aad);
|
|
31403
31562
|
await kv.set(cacheKey, envelope);
|
|
31404
31563
|
}
|
|
31405
|
-
const
|
|
31406
|
-
const record = await client.postProgress(transferId, opIndex, envelope, signature);
|
|
31564
|
+
const record = await this.post(transferId, opIndex, envelope);
|
|
31407
31565
|
return this.open(record.payload, aad);
|
|
31408
31566
|
}
|
|
31567
|
+
/** §5.1 restore (CheckpointReseeder): re-POST the cached ciphertext byte-identical; false = none cached. */
|
|
31568
|
+
async reseedCheckpoint(transferId, opIndex) {
|
|
31569
|
+
const envelope = await this.config.kv.get(this.cacheKey(transferId, opIndex));
|
|
31570
|
+
if (envelope === null) return false;
|
|
31571
|
+
await this.post(transferId, opIndex, envelope);
|
|
31572
|
+
return true;
|
|
31573
|
+
}
|
|
31574
|
+
/** The ONE signed progress-append path put() and reseedCheckpoint() share. */
|
|
31575
|
+
async post(transferId, opIndex, envelope) {
|
|
31576
|
+
const signature = await this.config.signProgress(progressSignMessage(transferId, opIndex, envelope));
|
|
31577
|
+
return this.config.client.postProgress(transferId, opIndex, envelope, signature);
|
|
31578
|
+
}
|
|
31409
31579
|
async get(transferId, opIndex) {
|
|
31410
31580
|
const records = await this.config.client.getProgress(transferId);
|
|
31411
31581
|
const record = records.find((r) => r.opIndex === opIndex);
|
|
@@ -31428,7 +31598,7 @@ var WalletApiSplitCheckpointStore = class {
|
|
|
31428
31598
|
};
|
|
31429
31599
|
|
|
31430
31600
|
// core/payments-v2-wiring.ts
|
|
31431
|
-
function resolvePaymentsV2Composition(walletApi) {
|
|
31601
|
+
function resolvePaymentsV2Composition(walletApi, sphereNetwork) {
|
|
31432
31602
|
const source = walletApi;
|
|
31433
31603
|
const network = typeof source?.network === "string" && source.network !== "" ? source.network : null;
|
|
31434
31604
|
if (!source || network === null) {
|
|
@@ -31437,6 +31607,18 @@ function resolvePaymentsV2Composition(walletApi) {
|
|
|
31437
31607
|
"INVALID_CONFIG"
|
|
31438
31608
|
);
|
|
31439
31609
|
}
|
|
31610
|
+
if (!(network in NETWORKS)) {
|
|
31611
|
+
throw new SphereError(
|
|
31612
|
+
`paymentsV2: walletApi.network "${network}" is not a known network (${Object.keys(NETWORKS).join("/")}) \u2014 a payments composition is single-network (Sphere network "${String(sphereNetwork)}").`,
|
|
31613
|
+
"INVALID_CONFIG"
|
|
31614
|
+
);
|
|
31615
|
+
}
|
|
31616
|
+
if (network !== sphereNetwork) {
|
|
31617
|
+
throw new SphereError(
|
|
31618
|
+
`paymentsV2: walletApi.network "${network}" does not match the Sphere network "${String(sphereNetwork)}" the engine/registry run on \u2014 a payments composition is single-network.`,
|
|
31619
|
+
"INVALID_CONFIG"
|
|
31620
|
+
);
|
|
31621
|
+
}
|
|
31440
31622
|
if (typeof source.paymentsV2Transport === "function") {
|
|
31441
31623
|
return { network, factory: source.paymentsV2Transport.bind(source) };
|
|
31442
31624
|
}
|
|
@@ -31502,8 +31684,7 @@ function defaultPaymentsV2Transport(config) {
|
|
|
31502
31684
|
deviceId: `${config.deviceId}:pv2`,
|
|
31503
31685
|
kv: args.kv,
|
|
31504
31686
|
webSocketFactory,
|
|
31505
|
-
emitStatus: args.emitStatus
|
|
31506
|
-
onEpochChange: args.onEpochChange
|
|
31687
|
+
emitStatus: args.emitStatus
|
|
31507
31688
|
});
|
|
31508
31689
|
return { session, client: authedWireClient(session, client) };
|
|
31509
31690
|
};
|
|
@@ -31573,8 +31754,7 @@ function composePaymentsV2(spec) {
|
|
|
31573
31754
|
identity: identitySlice,
|
|
31574
31755
|
network,
|
|
31575
31756
|
kv,
|
|
31576
|
-
emitStatus: (status) => host.emit("connection:status", { status })
|
|
31577
|
-
onEpochChange: async () => void 0
|
|
31757
|
+
emitStatus: (status) => host.emit("connection:status", { status })
|
|
31578
31758
|
});
|
|
31579
31759
|
const deps = {
|
|
31580
31760
|
session: transport.session,
|
|
@@ -31604,7 +31784,10 @@ function composePaymentsV2(spec) {
|
|
|
31604
31784
|
network,
|
|
31605
31785
|
ownPubkey: identity.chainPubkey,
|
|
31606
31786
|
ownNametag: host.nametag,
|
|
31607
|
-
requestMemo: requestMemoCodec(identity.privateKey)
|
|
31787
|
+
requestMemo: requestMemoCodec(identity.privateKey),
|
|
31788
|
+
// §5.1: the session is the epoch authority — the facade registers its
|
|
31789
|
+
// restore hook on the session itself (FacadeSession.subscribeEpochChange).
|
|
31790
|
+
syncEpoch: () => transport.session.currentEpoch()
|
|
31608
31791
|
};
|
|
31609
31792
|
return new PaymentsFacade(deps);
|
|
31610
31793
|
}
|
|
@@ -31934,18 +32117,20 @@ var Sphere = class _Sphere {
|
|
|
31934
32117
|
}
|
|
31935
32118
|
/**
|
|
31936
32119
|
* P9: validate + resolve the paymentsV2 opt-in BEFORE any storage writes —
|
|
31937
|
-
* fail-closed at init, never a silently-degraded composition.
|
|
32120
|
+
* fail-closed at init, never a silently-degraded composition. Returns the
|
|
32121
|
+
* resolved composition (null = flag off); #728: resolving here also asserts
|
|
32122
|
+
* the single-network invariant (walletApi.network known + equal to the
|
|
32123
|
+
* Sphere network) before any session/KV/provider exists.
|
|
31938
32124
|
*/
|
|
31939
|
-
static resolvePaymentsV2Flag(flag, accountingConfig, swapConfig, walletApi) {
|
|
31940
|
-
if (flag !== true) return
|
|
32125
|
+
static resolvePaymentsV2Flag(flag, accountingConfig, swapConfig, walletApi, network) {
|
|
32126
|
+
if (flag !== true) return null;
|
|
31941
32127
|
if (accountingConfig || swapConfig) {
|
|
31942
32128
|
throw new SphereError(
|
|
31943
32129
|
"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.",
|
|
31944
32130
|
"INVALID_CONFIG"
|
|
31945
32131
|
);
|
|
31946
32132
|
}
|
|
31947
|
-
resolvePaymentsV2Composition(walletApi);
|
|
31948
|
-
return true;
|
|
32133
|
+
return resolvePaymentsV2Composition(walletApi, network);
|
|
31949
32134
|
}
|
|
31950
32135
|
/**
|
|
31951
32136
|
* Configure TokenRegistry in the main bundle context.
|
|
@@ -32000,12 +32185,14 @@ var Sphere = class _Sphere {
|
|
|
32000
32185
|
options.walletApi
|
|
32001
32186
|
);
|
|
32002
32187
|
sphere._verification = options.verification;
|
|
32003
|
-
sphere.
|
|
32188
|
+
sphere._paymentsV2Composition = _Sphere.resolvePaymentsV2Flag(
|
|
32004
32189
|
options.paymentsV2,
|
|
32005
32190
|
accountingConfig,
|
|
32006
32191
|
swapConfig,
|
|
32007
|
-
options.walletApi
|
|
32192
|
+
options.walletApi,
|
|
32193
|
+
options.network
|
|
32008
32194
|
);
|
|
32195
|
+
sphere._paymentsV2Enabled = sphere._paymentsV2Composition !== null;
|
|
32009
32196
|
sphere._password = options.password ?? null;
|
|
32010
32197
|
sphere._passwordProtected = sphere._password !== null;
|
|
32011
32198
|
progress?.({ step: "storing_keys", message: "Storing wallet keys..." });
|
|
@@ -32072,12 +32259,14 @@ var Sphere = class _Sphere {
|
|
|
32072
32259
|
options.walletApi
|
|
32073
32260
|
);
|
|
32074
32261
|
sphere._verification = options.verification;
|
|
32075
|
-
sphere.
|
|
32262
|
+
sphere._paymentsV2Composition = _Sphere.resolvePaymentsV2Flag(
|
|
32076
32263
|
options.paymentsV2,
|
|
32077
32264
|
accountingConfig,
|
|
32078
32265
|
swapConfig,
|
|
32079
|
-
options.walletApi
|
|
32266
|
+
options.walletApi,
|
|
32267
|
+
options.network
|
|
32080
32268
|
);
|
|
32269
|
+
sphere._paymentsV2Enabled = sphere._paymentsV2Composition !== null;
|
|
32081
32270
|
sphere._password = options.password ?? null;
|
|
32082
32271
|
sphere._passwordProtected = sphere._password !== null;
|
|
32083
32272
|
if (!options.storage.isConnected()) {
|
|
@@ -32151,12 +32340,14 @@ var Sphere = class _Sphere {
|
|
|
32151
32340
|
options.walletApi
|
|
32152
32341
|
);
|
|
32153
32342
|
sphere._verification = options.verification;
|
|
32154
|
-
sphere.
|
|
32343
|
+
sphere._paymentsV2Composition = _Sphere.resolvePaymentsV2Flag(
|
|
32155
32344
|
options.paymentsV2,
|
|
32156
32345
|
accountingConfig,
|
|
32157
32346
|
swapConfig,
|
|
32158
|
-
options.walletApi
|
|
32347
|
+
options.walletApi,
|
|
32348
|
+
options.network
|
|
32159
32349
|
);
|
|
32350
|
+
sphere._paymentsV2Enabled = sphere._paymentsV2Composition !== null;
|
|
32160
32351
|
sphere._password = options.password ?? null;
|
|
32161
32352
|
sphere._passwordProtected = sphere._password !== null;
|
|
32162
32353
|
progress?.({ step: "storing_keys", message: "Storing wallet keys..." });
|
|
@@ -34814,7 +35005,9 @@ var Sphere = class _Sphere {
|
|
|
34814
35005
|
* setOracleApiKey rebuild is what future operations snapshot.
|
|
34815
35006
|
*/
|
|
34816
35007
|
async startPaymentsV2Inner(index, identity) {
|
|
34817
|
-
this._paymentsV2Composition
|
|
35008
|
+
if (this._paymentsV2Composition === null) {
|
|
35009
|
+
throw new SphereError("paymentsV2: composition unresolved \u2014 init never ran the flag gate", "INVALID_CONFIG");
|
|
35010
|
+
}
|
|
34818
35011
|
if (!this._addressModules.get(index)?.tokenEngine) {
|
|
34819
35012
|
throw new SphereError(
|
|
34820
35013
|
"paymentsV2 requires the v2 token engine \u2014 the oracle must supply a trust base + gateway URL (and API key where required).",
|