@unicitylabs/sphere-sdk 0.14.0-dev.5 → 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 +713 -252
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +111 -87
- package/dist/core/index.d.ts +111 -87
- package/dist/core/index.js +713 -252
- 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 +45 -5
- package/dist/impl/wallet-api-v2/index.cjs.map +1 -1
- package/dist/impl/wallet-api-v2/index.d.cts +26 -2
- package/dist/impl/wallet-api-v2/index.d.ts +26 -2
- package/dist/impl/wallet-api-v2/index.js +45 -5
- package/dist/impl/wallet-api-v2/index.js.map +1 -1
- package/dist/index.cjs +713 -252
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +112 -88
- package/dist/index.d.ts +112 -88
- package/dist/index.js +713 -252
- package/dist/index.js.map +1 -1
- package/dist/modules/payments-v2/index.cjs +634 -228
- package/dist/modules/payments-v2/index.cjs.map +1 -1
- package/dist/modules/payments-v2/index.d.cts +112 -20
- package/dist/modules/payments-v2/index.d.ts +112 -20
- package/dist/modules/payments-v2/index.js +631 -228
- package/dist/modules/payments-v2/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -12083,14 +12083,14 @@ var IntentResume = class {
|
|
|
12083
12083
|
}
|
|
12084
12084
|
let changeOutput = null;
|
|
12085
12085
|
if (payload.split) {
|
|
12086
|
-
const
|
|
12086
|
+
const splitOpIndex2 = payload.direct.length;
|
|
12087
12087
|
let splitDelivered = false;
|
|
12088
12088
|
{
|
|
12089
12089
|
const source = await engine.decodeToken(await provider.getToken(payload.split.tokenId));
|
|
12090
12090
|
const outcome = await this.host.certifyOperation(
|
|
12091
12091
|
{
|
|
12092
12092
|
kind: "split",
|
|
12093
|
-
opIndex:
|
|
12093
|
+
opIndex: splitOpIndex2,
|
|
12094
12094
|
uiTokenId: `v2_${payload.split.tokenId}`,
|
|
12095
12095
|
genesisId: payload.split.tokenId,
|
|
12096
12096
|
sourceSdkData: "",
|
|
@@ -22201,6 +22201,7 @@ var AccountingModule = class _AccountingModule {
|
|
|
22201
22201
|
const unsubHistory = deps.on(
|
|
22202
22202
|
"history:updated",
|
|
22203
22203
|
(entry) => {
|
|
22204
|
+
if (!("dedupKey" in entry)) return;
|
|
22204
22205
|
this._handleHistoryUpdated(entry).catch((err) => {
|
|
22205
22206
|
logger.warn(LOG_TAG2, "Error handling history:updated event:", err);
|
|
22206
22207
|
});
|
|
@@ -27463,7 +27464,10 @@ var STORE_KEYS = {
|
|
|
27463
27464
|
shortfalls: "shortfalls",
|
|
27464
27465
|
settlingLinks: "settling",
|
|
27465
27466
|
streamCursor: (s) => `cursor:${s}`,
|
|
27466
|
-
epochLatch: "epoch-latch"
|
|
27467
|
+
epochLatch: "epoch-latch",
|
|
27468
|
+
// §5.2 InventoryView durable overlays (#625/#679).
|
|
27469
|
+
suspectedSpent: "suspected-spent",
|
|
27470
|
+
knownSpends: "known-spends"
|
|
27467
27471
|
};
|
|
27468
27472
|
|
|
27469
27473
|
// modules/payments-v2/machine/journal.ts
|
|
@@ -27486,6 +27490,10 @@ function isValidationReject(err) {
|
|
|
27486
27490
|
if (e === null) return false;
|
|
27487
27491
|
return e.status === 422 || e.statusCode === 422 || e.code === "VALIDATION" || e.code === "VALIDATION_FAILED";
|
|
27488
27492
|
}
|
|
27493
|
+
function retryAfterMsOf(err) {
|
|
27494
|
+
const retryAfter = err !== null && typeof err === "object" ? err.retryAfter : void 0;
|
|
27495
|
+
return typeof retryAfter === "number" && Number.isFinite(retryAfter) && retryAfter >= 0 ? retryAfter : null;
|
|
27496
|
+
}
|
|
27489
27497
|
var chainRegistry = /* @__PURE__ */ new WeakMap();
|
|
27490
27498
|
var replayRegistry = /* @__PURE__ */ new WeakMap();
|
|
27491
27499
|
function registryFor(registry, kv, storeKey, make) {
|
|
@@ -27709,7 +27717,7 @@ var TransferMachine = class {
|
|
|
27709
27717
|
}
|
|
27710
27718
|
if (cls === null) {
|
|
27711
27719
|
await this.applyCommitted(engine, plan.transferId, committed);
|
|
27712
|
-
this.closeTail(plan.transferId, plan.payload);
|
|
27720
|
+
this.closeTail(engine, plan.transferId, plan.payload, committed);
|
|
27713
27721
|
return { committed, deliveryPending };
|
|
27714
27722
|
}
|
|
27715
27723
|
if (cls === "conflict" && committed.length > 0) {
|
|
@@ -27735,10 +27743,10 @@ var TransferMachine = class {
|
|
|
27735
27743
|
}
|
|
27736
27744
|
await this.applyCommitted(engine, r.transferId, committed);
|
|
27737
27745
|
if (conflicts.length > 0) {
|
|
27738
|
-
return { deliveryPending, partial: await this.settlePartial(r, committed, conflicts) };
|
|
27746
|
+
return { deliveryPending, partial: await this.settlePartial(engine, r, committed, conflicts) };
|
|
27739
27747
|
}
|
|
27740
27748
|
await this.completeIntent(r.transferId, r.payload);
|
|
27741
|
-
await this.finishBookkeeping(r.transferId, r.payload, "resumed");
|
|
27749
|
+
await this.finishBookkeeping(engine, r.transferId, r.payload, "resumed", committed);
|
|
27742
27750
|
return { deliveryPending, partial: null };
|
|
27743
27751
|
}
|
|
27744
27752
|
// ── shared transitions ─────────────────────────────────────────────────────
|
|
@@ -27920,7 +27928,7 @@ var TransferMachine = class {
|
|
|
27920
27928
|
}
|
|
27921
27929
|
return added;
|
|
27922
27930
|
}
|
|
27923
|
-
async settlePartial(r, committed, conflicts) {
|
|
27931
|
+
async settlePartial(engine, r, committed, conflicts) {
|
|
27924
27932
|
const undelivered = conflicts.reduce((sum, c) => sum + c.amount, 0n);
|
|
27925
27933
|
const entry = {
|
|
27926
27934
|
transferId: r.transferId,
|
|
@@ -27932,26 +27940,27 @@ var TransferMachine = class {
|
|
|
27932
27940
|
};
|
|
27933
27941
|
await this.stores.shortfalls.upsert(entry);
|
|
27934
27942
|
await this.completeIntent(r.transferId, r.payload);
|
|
27935
|
-
await this.finishBookkeeping(r.transferId, r.payload, "resumed");
|
|
27943
|
+
await this.finishBookkeeping(engine, r.transferId, r.payload, "resumed", committed);
|
|
27936
27944
|
return entry;
|
|
27937
27945
|
}
|
|
27938
|
-
closeTail(transferId, payload) {
|
|
27946
|
+
closeTail(engine, transferId, payload, committed) {
|
|
27939
27947
|
void (async () => {
|
|
27940
27948
|
try {
|
|
27941
27949
|
await this.completeIntent(transferId, payload);
|
|
27942
27950
|
} catch {
|
|
27943
27951
|
return;
|
|
27944
27952
|
}
|
|
27945
|
-
await this.finishBookkeeping(transferId, payload, "sent");
|
|
27953
|
+
await this.finishBookkeeping(engine, transferId, payload, "sent", committed);
|
|
27946
27954
|
})();
|
|
27947
27955
|
}
|
|
27948
27956
|
async completeIntent(transferId, payload) {
|
|
27949
27957
|
const signature = payload.split !== void 0 ? await this.deps.signComplete(transferId) : void 0;
|
|
27950
27958
|
await this.deps.intents.complete(transferId, signature);
|
|
27951
27959
|
}
|
|
27952
|
-
async finishBookkeeping(transferId, payload, phase) {
|
|
27960
|
+
async finishBookkeeping(engine, transferId, payload, phase, committed) {
|
|
27953
27961
|
try {
|
|
27954
|
-
await this.
|
|
27962
|
+
const committedAmount = await this.settledAmount(engine, payload.coinId, committed);
|
|
27963
|
+
await this.deps.recordHistory?.({ transferId, payload, phase, committedAmount });
|
|
27955
27964
|
} catch {
|
|
27956
27965
|
}
|
|
27957
27966
|
try {
|
|
@@ -27959,6 +27968,26 @@ var TransferMachine = class {
|
|
|
27959
27968
|
} catch {
|
|
27960
27969
|
}
|
|
27961
27970
|
}
|
|
27971
|
+
/**
|
|
27972
|
+
* §5.9: SENT history records what SETTLED, never the plan — the value of the
|
|
27973
|
+
* certified recipient blobs (summarize()'s committed set is the only source;
|
|
27974
|
+
* a journal-rehydrated resume leg has no source token, but every committed
|
|
27975
|
+
* leg carries its recipient blob, so the outcome-derived rule is uniform).
|
|
27976
|
+
*/
|
|
27977
|
+
async settledAmount(engine, coinId, committed) {
|
|
27978
|
+
let total = 0n;
|
|
27979
|
+
for (const outcome of committed) {
|
|
27980
|
+
if (outcome.recipientBlob === void 0) continue;
|
|
27981
|
+
const token = await engine.decodeToken({
|
|
27982
|
+
v: TOKEN_BLOB_VERSION,
|
|
27983
|
+
network: 0,
|
|
27984
|
+
tokenId: "",
|
|
27985
|
+
token: outcome.recipientBlob
|
|
27986
|
+
});
|
|
27987
|
+
total += engine.balanceOf(token, coinId);
|
|
27988
|
+
}
|
|
27989
|
+
return total.toString();
|
|
27990
|
+
}
|
|
27962
27991
|
async disposeOnFailure(transferId, committed, cls) {
|
|
27963
27992
|
if (committed.length > 0 || cls === "keep-open") return;
|
|
27964
27993
|
try {
|
|
@@ -27983,7 +28012,7 @@ function conflictAmount(engine, payload, op, source) {
|
|
|
27983
28012
|
}
|
|
27984
28013
|
|
|
27985
28014
|
// modules/payments-v2/machine/resume.ts
|
|
27986
|
-
async function resumeAll(deps) {
|
|
28015
|
+
async function resumeAll(deps, options = {}) {
|
|
27987
28016
|
const report = { resumed: [], conflicted: [], failed: [] };
|
|
27988
28017
|
const stores = createMachineStores(deps.kv);
|
|
27989
28018
|
const open = await deps.intents.listOpen();
|
|
@@ -27993,7 +28022,12 @@ async function resumeAll(deps) {
|
|
|
27993
28022
|
} catch {
|
|
27994
28023
|
return report;
|
|
27995
28024
|
}
|
|
27996
|
-
await
|
|
28025
|
+
await dropStaleBackstops(
|
|
28026
|
+
stores,
|
|
28027
|
+
locals,
|
|
28028
|
+
new Set(open.map((i) => i.transferId)),
|
|
28029
|
+
options.isLocallyActive ?? (() => false)
|
|
28030
|
+
);
|
|
27997
28031
|
const jobs = await gateAndDecode(deps, stores, open, locals, report);
|
|
27998
28032
|
const ctx = {
|
|
27999
28033
|
deps,
|
|
@@ -28006,11 +28040,16 @@ async function resumeAll(deps) {
|
|
|
28006
28040
|
}
|
|
28007
28041
|
return report;
|
|
28008
28042
|
}
|
|
28009
|
-
async function
|
|
28043
|
+
async function dropStaleBackstops(stores, locals, openIds, isLocallyActive) {
|
|
28010
28044
|
for (const entry of locals.values()) {
|
|
28011
|
-
if (
|
|
28045
|
+
if (openIds.has(entry.transferId)) continue;
|
|
28046
|
+
if (entry.disposition !== "open") {
|
|
28012
28047
|
await stores.backstop.removeByKey(entry.transferId);
|
|
28048
|
+
continue;
|
|
28013
28049
|
}
|
|
28050
|
+
if (isLocallyActive(entry.transferId)) continue;
|
|
28051
|
+
if ((await stores.deliveryJournal.byTransfer(entry.transferId)).size > 0) continue;
|
|
28052
|
+
await stores.backstop.removeByKey(entry.transferId);
|
|
28014
28053
|
}
|
|
28015
28054
|
}
|
|
28016
28055
|
async function gateAndDecode(deps, stores, open, locals, report) {
|
|
@@ -28123,6 +28162,349 @@ function validatePayload(raw) {
|
|
|
28123
28162
|
return p;
|
|
28124
28163
|
}
|
|
28125
28164
|
|
|
28165
|
+
// modules/payments-v2/convergence.ts
|
|
28166
|
+
var HEARTBEAT_SEED_MS = 5e3;
|
|
28167
|
+
var HEARTBEAT_CAP_MS = 12e4;
|
|
28168
|
+
var ConvergenceHeartbeat = class {
|
|
28169
|
+
constructor(deps) {
|
|
28170
|
+
this.deps = deps;
|
|
28171
|
+
}
|
|
28172
|
+
timer = null;
|
|
28173
|
+
delayMs = HEARTBEAT_SEED_MS;
|
|
28174
|
+
notBefore = 0;
|
|
28175
|
+
running = false;
|
|
28176
|
+
start() {
|
|
28177
|
+
this.running = true;
|
|
28178
|
+
this.delayMs = HEARTBEAT_SEED_MS;
|
|
28179
|
+
this.notBefore = 0;
|
|
28180
|
+
}
|
|
28181
|
+
/** §7 quiescence unchanged: a mid-backoff stop() cancels cleanly. */
|
|
28182
|
+
stop() {
|
|
28183
|
+
this.running = false;
|
|
28184
|
+
this.clearTimer();
|
|
28185
|
+
}
|
|
28186
|
+
/** Reset to seed on progress, ×2 to the cap otherwise; idle stops rescheduling. */
|
|
28187
|
+
settle(outcome, pending) {
|
|
28188
|
+
if (outcome.retryAfterMs !== null) {
|
|
28189
|
+
this.notBefore = Math.max(this.notBefore, this.deps.now() + outcome.retryAfterMs);
|
|
28190
|
+
}
|
|
28191
|
+
if (!pending) {
|
|
28192
|
+
this.delayMs = HEARTBEAT_SEED_MS;
|
|
28193
|
+
return;
|
|
28194
|
+
}
|
|
28195
|
+
const delay = outcome.progress ? HEARTBEAT_SEED_MS : this.delayMs;
|
|
28196
|
+
this.delayMs = Math.min(delay * 2, HEARTBEAT_CAP_MS);
|
|
28197
|
+
this.schedule(delay);
|
|
28198
|
+
}
|
|
28199
|
+
/** New pending work (keep-open park / journaled delivery / mint hold): prompt retry. */
|
|
28200
|
+
arm() {
|
|
28201
|
+
if (!this.running) return;
|
|
28202
|
+
this.delayMs = Math.min(HEARTBEAT_SEED_MS * 2, HEARTBEAT_CAP_MS);
|
|
28203
|
+
this.schedule(HEARTBEAT_SEED_MS);
|
|
28204
|
+
}
|
|
28205
|
+
/** connection:status recovery: the backoff resets; a pending tick re-fires at the seed. */
|
|
28206
|
+
recovered() {
|
|
28207
|
+
this.delayMs = HEARTBEAT_SEED_MS;
|
|
28208
|
+
if (this.timer !== null) this.arm();
|
|
28209
|
+
}
|
|
28210
|
+
schedule(delayMs) {
|
|
28211
|
+
this.clearTimer();
|
|
28212
|
+
if (!this.running) return;
|
|
28213
|
+
const floored = Math.max(delayMs, this.notBefore - this.deps.now());
|
|
28214
|
+
this.timer = setTimeout(() => {
|
|
28215
|
+
this.timer = null;
|
|
28216
|
+
this.deps.onTick();
|
|
28217
|
+
}, floored);
|
|
28218
|
+
}
|
|
28219
|
+
clearTimer() {
|
|
28220
|
+
if (this.timer !== null) clearTimeout(this.timer);
|
|
28221
|
+
this.timer = null;
|
|
28222
|
+
}
|
|
28223
|
+
};
|
|
28224
|
+
var Converger = class {
|
|
28225
|
+
constructor(deps) {
|
|
28226
|
+
this.deps = deps;
|
|
28227
|
+
}
|
|
28228
|
+
/** Resume open intents, then drain the delivery + mint journals (all idempotent). */
|
|
28229
|
+
async convergeOnce() {
|
|
28230
|
+
let progress = false;
|
|
28231
|
+
let retryAfterMs = null;
|
|
28232
|
+
try {
|
|
28233
|
+
progress = await this.resumePass();
|
|
28234
|
+
} catch (err) {
|
|
28235
|
+
retryAfterMs = retryAfterMsOf(err);
|
|
28236
|
+
}
|
|
28237
|
+
try {
|
|
28238
|
+
const stats = await this.deps.stores.deliveryJournal.replay(this.replayDeps());
|
|
28239
|
+
progress = progress || stats.delivered > 0;
|
|
28240
|
+
} catch (err) {
|
|
28241
|
+
retryAfterMs ??= retryAfterMsOf(err);
|
|
28242
|
+
}
|
|
28243
|
+
try {
|
|
28244
|
+
progress = await this.deps.replayMints() > 0 || progress;
|
|
28245
|
+
} catch (err) {
|
|
28246
|
+
retryAfterMs ??= retryAfterMsOf(err);
|
|
28247
|
+
}
|
|
28248
|
+
return { progress, retryAfterMs };
|
|
28249
|
+
}
|
|
28250
|
+
/** §4 heartbeat predicate — read fresh from the §6 stores, never cached. */
|
|
28251
|
+
async pendingWork() {
|
|
28252
|
+
const [backstop, journal, mints] = await Promise.all([
|
|
28253
|
+
this.deps.stores.backstop.list(),
|
|
28254
|
+
this.deps.stores.deliveryJournal.list(),
|
|
28255
|
+
this.deps.stores.mintJournal.list()
|
|
28256
|
+
]);
|
|
28257
|
+
return backstop.length > 0 || journal.some((e) => e.undeliverable !== true) || mints.length > 0;
|
|
28258
|
+
}
|
|
28259
|
+
async resumePass() {
|
|
28260
|
+
const report = await resumeAll(this.filteredMachineDeps(), {
|
|
28261
|
+
isLocallyActive: (id) => this.deps.isActiveOp(id)
|
|
28262
|
+
});
|
|
28263
|
+
const progress = report.resumed.length > 0 || report.conflicted.length > 0;
|
|
28264
|
+
await this.deps.reconcile({
|
|
28265
|
+
resumed: report.resumed,
|
|
28266
|
+
conflicted: report.conflicted,
|
|
28267
|
+
open: report.failed
|
|
28268
|
+
});
|
|
28269
|
+
if (progress) await this.deps.refreshView().catch(() => void 0);
|
|
28270
|
+
return progress;
|
|
28271
|
+
}
|
|
28272
|
+
filteredMachineDeps() {
|
|
28273
|
+
const intents = this.deps.machineDeps.intents;
|
|
28274
|
+
return {
|
|
28275
|
+
...this.deps.machineDeps,
|
|
28276
|
+
intents: {
|
|
28277
|
+
...intents,
|
|
28278
|
+
listOpen: async () => (await intents.listOpen()).filter((intent) => !this.deps.isActiveOp(intent.transferId))
|
|
28279
|
+
}
|
|
28280
|
+
};
|
|
28281
|
+
}
|
|
28282
|
+
replayDeps() {
|
|
28283
|
+
return {
|
|
28284
|
+
delivery: this.deps.delivery,
|
|
28285
|
+
now: () => this.deps.now(),
|
|
28286
|
+
attention: (transferId, code, detail) => {
|
|
28287
|
+
this.deps.emit(
|
|
28288
|
+
"transfer:attention",
|
|
28289
|
+
detail === void 0 ? { transferId, code } : { transferId, code, detail }
|
|
28290
|
+
);
|
|
28291
|
+
}
|
|
28292
|
+
};
|
|
28293
|
+
}
|
|
28294
|
+
};
|
|
28295
|
+
async function derivePendingTransfers(stores, decryptPayload) {
|
|
28296
|
+
const [backstop, journal, shortfalls] = await Promise.all([
|
|
28297
|
+
stores.backstop.list(),
|
|
28298
|
+
stores.deliveryJournal.list(),
|
|
28299
|
+
stores.shortfalls.list()
|
|
28300
|
+
]);
|
|
28301
|
+
const byTransfer = groupJournal(journal);
|
|
28302
|
+
const out = [];
|
|
28303
|
+
const seen = /* @__PURE__ */ new Set();
|
|
28304
|
+
for (const entry of backstop) {
|
|
28305
|
+
if (entry.disposition !== "open") continue;
|
|
28306
|
+
seen.add(entry.transferId);
|
|
28307
|
+
out.push(await openRow(entry, byTransfer.get(entry.transferId) ?? [], decryptPayload));
|
|
28308
|
+
}
|
|
28309
|
+
for (const [transferId, entries] of byTransfer) {
|
|
28310
|
+
if (seen.has(transferId)) continue;
|
|
28311
|
+
seen.add(transferId);
|
|
28312
|
+
out.push(journalOnlyRow(transferId, entries));
|
|
28313
|
+
}
|
|
28314
|
+
for (const entry of shortfalls) {
|
|
28315
|
+
if (!seen.has(entry.transferId)) out.push(shortfallRow(entry));
|
|
28316
|
+
}
|
|
28317
|
+
return out;
|
|
28318
|
+
}
|
|
28319
|
+
function groupJournal(journal) {
|
|
28320
|
+
const byTransfer = /* @__PURE__ */ new Map();
|
|
28321
|
+
for (const entry of journal) {
|
|
28322
|
+
const mine = byTransfer.get(entry.transferId);
|
|
28323
|
+
if (mine === void 0) byTransfer.set(entry.transferId, [entry]);
|
|
28324
|
+
else mine.push(entry);
|
|
28325
|
+
}
|
|
28326
|
+
return byTransfer;
|
|
28327
|
+
}
|
|
28328
|
+
async function openRow(entry, journal, decryptPayload) {
|
|
28329
|
+
let payload = null;
|
|
28330
|
+
try {
|
|
28331
|
+
const raw = await decryptPayload(entry.payloadEnvelope);
|
|
28332
|
+
if (raw !== null && typeof raw === "object") payload = raw;
|
|
28333
|
+
} catch {
|
|
28334
|
+
payload = null;
|
|
28335
|
+
}
|
|
28336
|
+
const total = Array.isArray(payload?.direct) ? payload.direct.length + (payload.split !== void 0 ? 1 : 0) : 0;
|
|
28337
|
+
const certified = distinctOpCount(journal);
|
|
28338
|
+
return {
|
|
28339
|
+
transferId: entry.transferId,
|
|
28340
|
+
kind: "open",
|
|
28341
|
+
recipient: typeof payload?.recipient === "string" ? payload.recipient : "",
|
|
28342
|
+
coinId: typeof payload?.coinId === "string" ? payload.coinId : "",
|
|
28343
|
+
amount: typeof payload?.amount === "string" ? payload.amount : "",
|
|
28344
|
+
legs: { certified, total: Math.max(total, certified) },
|
|
28345
|
+
deliveryPending: journal.length > 0,
|
|
28346
|
+
createdAt: entry.createdAt
|
|
28347
|
+
};
|
|
28348
|
+
}
|
|
28349
|
+
function journalOnlyRow(transferId, entries) {
|
|
28350
|
+
const certified = distinctOpCount(entries);
|
|
28351
|
+
return {
|
|
28352
|
+
transferId,
|
|
28353
|
+
kind: "open",
|
|
28354
|
+
recipient: entries[0].recipientPubkey,
|
|
28355
|
+
coinId: "",
|
|
28356
|
+
amount: "",
|
|
28357
|
+
legs: { certified, total: certified },
|
|
28358
|
+
deliveryPending: true,
|
|
28359
|
+
createdAt: 0
|
|
28360
|
+
};
|
|
28361
|
+
}
|
|
28362
|
+
function shortfallRow(entry) {
|
|
28363
|
+
return {
|
|
28364
|
+
transferId: entry.transferId,
|
|
28365
|
+
kind: "shortfall",
|
|
28366
|
+
recipient: entry.recipient,
|
|
28367
|
+
coinId: entry.coinId,
|
|
28368
|
+
amount: entry.remainingAmount,
|
|
28369
|
+
legs: { certified: entry.committedTokenIds.length, total: entry.committedTokenIds.length },
|
|
28370
|
+
deliveryPending: false,
|
|
28371
|
+
createdAt: entry.createdAt
|
|
28372
|
+
};
|
|
28373
|
+
}
|
|
28374
|
+
function distinctOpCount(entries) {
|
|
28375
|
+
return new Set(entries.map((entry) => entry.opIndex)).size;
|
|
28376
|
+
}
|
|
28377
|
+
|
|
28378
|
+
// modules/payments-v2/restore.ts
|
|
28379
|
+
var ATTENTION_RESEED_REJECTED = "intent:reseed-rejected";
|
|
28380
|
+
var ALL_STREAMS = ["inventory", "mailbox", "payment_requests", "history"];
|
|
28381
|
+
async function reseedAndReset(deps) {
|
|
28382
|
+
for (const entry of await deps.stores.backstop.list()) {
|
|
28383
|
+
if (entry.disposition !== "open") continue;
|
|
28384
|
+
try {
|
|
28385
|
+
await deps.reput(entry.transferId, entry.payloadEnvelope, entry.requiresSeedClose);
|
|
28386
|
+
if (entry.requiresSeedClose) {
|
|
28387
|
+
await deps.reseedCheckpoint(entry.transferId, await splitOpIndex(deps, entry.payloadEnvelope));
|
|
28388
|
+
}
|
|
28389
|
+
} catch (err) {
|
|
28390
|
+
deps.attention(entry.transferId, ATTENTION_RESEED_REJECTED, err instanceof Error ? err.message : String(err));
|
|
28391
|
+
}
|
|
28392
|
+
}
|
|
28393
|
+
for (const stream of ALL_STREAMS) await deps.kv.remove(STORE_KEYS.streamCursor(stream));
|
|
28394
|
+
await deps.fullRePull();
|
|
28395
|
+
}
|
|
28396
|
+
async function splitOpIndex(deps, envelope) {
|
|
28397
|
+
const payload = await deps.decryptPayload(envelope);
|
|
28398
|
+
return Array.isArray(payload.direct) ? payload.direct.length : 0;
|
|
28399
|
+
}
|
|
28400
|
+
|
|
28401
|
+
// modules/payments-v2/inventory/presentation.ts
|
|
28402
|
+
function toToken(tokenId, snapshot, asset, registry, flags) {
|
|
28403
|
+
const iconUrl = registry.getIconUrl(asset.coinId);
|
|
28404
|
+
return {
|
|
28405
|
+
id: tokenId,
|
|
28406
|
+
coinId: asset.coinId,
|
|
28407
|
+
symbol: registry.getSymbol(asset.coinId),
|
|
28408
|
+
name: registry.getName(asset.coinId),
|
|
28409
|
+
decimals: registry.getDecimals(asset.coinId),
|
|
28410
|
+
...iconUrl !== null ? { iconUrl } : {},
|
|
28411
|
+
amount: asset.amount,
|
|
28412
|
+
status: flags.transferring ? "transferring" : "confirmed",
|
|
28413
|
+
createdAt: snapshot.createdAt,
|
|
28414
|
+
updatedAt: snapshot.updatedAt,
|
|
28415
|
+
lazy: true,
|
|
28416
|
+
...flags.suspectedSpent ? { suspectedSpent: true } : {}
|
|
28417
|
+
};
|
|
28418
|
+
}
|
|
28419
|
+
function aggregateAssets(entries, isTransferring, registry) {
|
|
28420
|
+
const groups = /* @__PURE__ */ new Map();
|
|
28421
|
+
for (const [tokenId, entry] of entries) {
|
|
28422
|
+
const moving = isTransferring(tokenId);
|
|
28423
|
+
for (const asset of entry.assets) {
|
|
28424
|
+
let group = groups.get(asset.coinId);
|
|
28425
|
+
if (!group) {
|
|
28426
|
+
group = { total: 0n, count: 0, transferring: 0n, transferringCount: 0 };
|
|
28427
|
+
groups.set(asset.coinId, group);
|
|
28428
|
+
}
|
|
28429
|
+
if (moving) {
|
|
28430
|
+
group.transferring += BigInt(asset.amount);
|
|
28431
|
+
group.transferringCount += 1;
|
|
28432
|
+
} else {
|
|
28433
|
+
group.total += BigInt(asset.amount);
|
|
28434
|
+
group.count += 1;
|
|
28435
|
+
}
|
|
28436
|
+
}
|
|
28437
|
+
}
|
|
28438
|
+
return [...groups].map(([coinId, totals]) => toAsset(coinId, totals, registry));
|
|
28439
|
+
}
|
|
28440
|
+
function toAsset(coinId, totals, registry) {
|
|
28441
|
+
const iconUrl = registry.getIconUrl(coinId);
|
|
28442
|
+
return {
|
|
28443
|
+
coinId,
|
|
28444
|
+
symbol: registry.getSymbol(coinId),
|
|
28445
|
+
name: registry.getName(coinId),
|
|
28446
|
+
decimals: registry.getDecimals(coinId),
|
|
28447
|
+
...iconUrl !== null ? { iconUrl } : {},
|
|
28448
|
+
totalAmount: totals.total.toString(),
|
|
28449
|
+
tokenCount: totals.count,
|
|
28450
|
+
confirmedAmount: totals.total.toString(),
|
|
28451
|
+
unconfirmedAmount: "0",
|
|
28452
|
+
confirmedTokenCount: totals.count,
|
|
28453
|
+
unconfirmedTokenCount: 0,
|
|
28454
|
+
transferringTokenCount: totals.transferringCount,
|
|
28455
|
+
transferringAmount: totals.transferring.toString(),
|
|
28456
|
+
priceUsd: null,
|
|
28457
|
+
priceEur: null,
|
|
28458
|
+
change24h: null,
|
|
28459
|
+
fiatValueUsd: null,
|
|
28460
|
+
fiatValueEur: null
|
|
28461
|
+
};
|
|
28462
|
+
}
|
|
28463
|
+
async function withPrices(raw, registry, price) {
|
|
28464
|
+
try {
|
|
28465
|
+
const names = [...new Set(raw.map((a) => registry.getName(a.coinId)))];
|
|
28466
|
+
const quotes = await price.getPrices(names);
|
|
28467
|
+
return raw.map((a) => priceOne(a, quotes.get(registry.getName(a.coinId))));
|
|
28468
|
+
} catch {
|
|
28469
|
+
return raw;
|
|
28470
|
+
}
|
|
28471
|
+
}
|
|
28472
|
+
function priceOne(asset, quote) {
|
|
28473
|
+
if (!quote) return asset;
|
|
28474
|
+
const human = Number(asset.totalAmount) / Math.pow(10, asset.decimals);
|
|
28475
|
+
return {
|
|
28476
|
+
...asset,
|
|
28477
|
+
priceUsd: quote.priceUsd,
|
|
28478
|
+
priceEur: quote.priceEur ?? null,
|
|
28479
|
+
change24h: quote.change24h ?? null,
|
|
28480
|
+
fiatValueUsd: human * quote.priceUsd,
|
|
28481
|
+
fiatValueEur: quote.priceEur != null ? human * quote.priceEur : null
|
|
28482
|
+
};
|
|
28483
|
+
}
|
|
28484
|
+
|
|
28485
|
+
// modules/payments-v2/machine/payload.ts
|
|
28486
|
+
function buildPayload(recipientPubkey, request, spend, spentStates) {
|
|
28487
|
+
return {
|
|
28488
|
+
v: 2,
|
|
28489
|
+
recipient: recipientPubkey,
|
|
28490
|
+
coinId: request.coinId,
|
|
28491
|
+
amount: request.amount,
|
|
28492
|
+
...request.memo !== void 0 ? { memo: request.memo } : {},
|
|
28493
|
+
direct: [...spend.plan.direct],
|
|
28494
|
+
...spend.plan.split !== void 0 ? {
|
|
28495
|
+
split: {
|
|
28496
|
+
tokenId: spend.plan.split.tokenId,
|
|
28497
|
+
splitAmount: spend.plan.split.splitAmount.toString(),
|
|
28498
|
+
remainderAmount: spend.plan.split.remainderAmount.toString()
|
|
28499
|
+
}
|
|
28500
|
+
} : {},
|
|
28501
|
+
spentStates
|
|
28502
|
+
};
|
|
28503
|
+
}
|
|
28504
|
+
function messageOf(err) {
|
|
28505
|
+
return err instanceof Error ? err.message : String(err);
|
|
28506
|
+
}
|
|
28507
|
+
|
|
28126
28508
|
// modules/payments-v2/history/History.ts
|
|
28127
28509
|
var WIRE_TOKEN_ID = /^(?:[0-9a-f]{2}){1,64}$/;
|
|
28128
28510
|
var WIRE_PUBKEY = /^0[23][0-9a-f]{64}$/;
|
|
@@ -28181,13 +28563,14 @@ var History = class {
|
|
|
28181
28563
|
}
|
|
28182
28564
|
// ── internals ────────────────────────────────────────────────────────────────
|
|
28183
28565
|
async post(build) {
|
|
28566
|
+
const record = build();
|
|
28184
28567
|
try {
|
|
28185
|
-
await this.deps.client.postHistory([
|
|
28568
|
+
await this.deps.client.postHistory([record]);
|
|
28186
28569
|
} catch (err) {
|
|
28187
28570
|
this.deps.log?.("history POST failed (dedupKey makes retry safe)", err);
|
|
28188
28571
|
return;
|
|
28189
28572
|
}
|
|
28190
|
-
this.deps.emit("history:updated");
|
|
28573
|
+
this.deps.emit("history:updated", this.toEntry(record));
|
|
28191
28574
|
}
|
|
28192
28575
|
ts(timestamp) {
|
|
28193
28576
|
return new Date(timestamp ?? (this.deps.now ?? Date.now)()).toISOString();
|
|
@@ -28244,93 +28627,9 @@ var History = class {
|
|
|
28244
28627
|
}
|
|
28245
28628
|
};
|
|
28246
28629
|
|
|
28247
|
-
// modules/payments-v2/inventory/presentation.ts
|
|
28248
|
-
function toToken(tokenId, snapshot, asset, registry, flags) {
|
|
28249
|
-
const iconUrl = registry.getIconUrl(asset.coinId);
|
|
28250
|
-
return {
|
|
28251
|
-
id: tokenId,
|
|
28252
|
-
coinId: asset.coinId,
|
|
28253
|
-
symbol: registry.getSymbol(asset.coinId),
|
|
28254
|
-
name: registry.getName(asset.coinId),
|
|
28255
|
-
decimals: registry.getDecimals(asset.coinId),
|
|
28256
|
-
...iconUrl !== null ? { iconUrl } : {},
|
|
28257
|
-
amount: asset.amount,
|
|
28258
|
-
status: flags.transferring ? "transferring" : "confirmed",
|
|
28259
|
-
createdAt: snapshot.createdAt,
|
|
28260
|
-
updatedAt: snapshot.updatedAt,
|
|
28261
|
-
lazy: true,
|
|
28262
|
-
...flags.suspectedSpent ? { suspectedSpent: true } : {}
|
|
28263
|
-
};
|
|
28264
|
-
}
|
|
28265
|
-
function aggregateAssets(entries, isTransferring, registry) {
|
|
28266
|
-
const groups = /* @__PURE__ */ new Map();
|
|
28267
|
-
for (const [tokenId, entry] of entries) {
|
|
28268
|
-
const moving = isTransferring(tokenId);
|
|
28269
|
-
for (const asset of entry.assets) {
|
|
28270
|
-
let group = groups.get(asset.coinId);
|
|
28271
|
-
if (!group) {
|
|
28272
|
-
group = { total: 0n, count: 0, transferring: 0n, transferringCount: 0 };
|
|
28273
|
-
groups.set(asset.coinId, group);
|
|
28274
|
-
}
|
|
28275
|
-
if (moving) {
|
|
28276
|
-
group.transferring += BigInt(asset.amount);
|
|
28277
|
-
group.transferringCount += 1;
|
|
28278
|
-
} else {
|
|
28279
|
-
group.total += BigInt(asset.amount);
|
|
28280
|
-
group.count += 1;
|
|
28281
|
-
}
|
|
28282
|
-
}
|
|
28283
|
-
}
|
|
28284
|
-
return [...groups].map(([coinId, totals]) => toAsset(coinId, totals, registry));
|
|
28285
|
-
}
|
|
28286
|
-
function toAsset(coinId, totals, registry) {
|
|
28287
|
-
const iconUrl = registry.getIconUrl(coinId);
|
|
28288
|
-
return {
|
|
28289
|
-
coinId,
|
|
28290
|
-
symbol: registry.getSymbol(coinId),
|
|
28291
|
-
name: registry.getName(coinId),
|
|
28292
|
-
decimals: registry.getDecimals(coinId),
|
|
28293
|
-
...iconUrl !== null ? { iconUrl } : {},
|
|
28294
|
-
totalAmount: totals.total.toString(),
|
|
28295
|
-
tokenCount: totals.count,
|
|
28296
|
-
confirmedAmount: totals.total.toString(),
|
|
28297
|
-
unconfirmedAmount: "0",
|
|
28298
|
-
confirmedTokenCount: totals.count,
|
|
28299
|
-
unconfirmedTokenCount: 0,
|
|
28300
|
-
transferringTokenCount: totals.transferringCount,
|
|
28301
|
-
transferringAmount: totals.transferring.toString(),
|
|
28302
|
-
priceUsd: null,
|
|
28303
|
-
priceEur: null,
|
|
28304
|
-
change24h: null,
|
|
28305
|
-
fiatValueUsd: null,
|
|
28306
|
-
fiatValueEur: null
|
|
28307
|
-
};
|
|
28308
|
-
}
|
|
28309
|
-
async function withPrices(raw, registry, price) {
|
|
28310
|
-
try {
|
|
28311
|
-
const names = [...new Set(raw.map((a) => registry.getName(a.coinId)))];
|
|
28312
|
-
const quotes = await price.getPrices(names);
|
|
28313
|
-
return raw.map((a) => priceOne(a, quotes.get(registry.getName(a.coinId))));
|
|
28314
|
-
} catch {
|
|
28315
|
-
return raw;
|
|
28316
|
-
}
|
|
28317
|
-
}
|
|
28318
|
-
function priceOne(asset, quote) {
|
|
28319
|
-
if (!quote) return asset;
|
|
28320
|
-
const human = Number(asset.totalAmount) / Math.pow(10, asset.decimals);
|
|
28321
|
-
return {
|
|
28322
|
-
...asset,
|
|
28323
|
-
priceUsd: quote.priceUsd,
|
|
28324
|
-
priceEur: quote.priceEur ?? null,
|
|
28325
|
-
change24h: quote.change24h ?? null,
|
|
28326
|
-
fiatValueUsd: human * quote.priceUsd,
|
|
28327
|
-
fiatValueEur: quote.priceEur != null ? human * quote.priceEur : null
|
|
28328
|
-
};
|
|
28329
|
-
}
|
|
28330
|
-
|
|
28331
28630
|
// modules/payments-v2/inventory/InventoryView.ts
|
|
28332
|
-
var SUSPECTED_KEY =
|
|
28333
|
-
var KNOWN_SPENDS_KEY =
|
|
28631
|
+
var SUSPECTED_KEY = STORE_KEYS.suspectedSpent;
|
|
28632
|
+
var KNOWN_SPENDS_KEY = STORE_KEYS.knownSpends;
|
|
28334
28633
|
var CURSOR_KEY = STORE_KEYS.streamCursor("inventory");
|
|
28335
28634
|
function stateKey(tokenId, stateHash) {
|
|
28336
28635
|
return `${tokenId}:${stateHash}`;
|
|
@@ -28634,19 +28933,24 @@ var Receive = class {
|
|
|
28634
28933
|
const engine = deps.engine();
|
|
28635
28934
|
const stored = [];
|
|
28636
28935
|
const pending = [];
|
|
28637
|
-
|
|
28936
|
+
const pageEpoch = () => deps.delivery.incomingEpoch() ?? deps.syncEpoch();
|
|
28638
28937
|
try {
|
|
28639
|
-
epoch = deps.syncEpoch();
|
|
28640
28938
|
const record = await deps.kv.get(STORE_KEYS.streamCursor("mailbox"));
|
|
28641
|
-
|
|
28642
|
-
for
|
|
28643
|
-
await
|
|
28644
|
-
|
|
28939
|
+
let basis = record !== null && record.syncEpoch === deps.syncEpoch() ? record : null;
|
|
28940
|
+
for (let pass = 0; pass < 2; pass++) {
|
|
28941
|
+
for await (const entry of deps.delivery.incoming(basis === null ? void 0 : String(basis.cursor))) {
|
|
28942
|
+
await this.processEntry(deps, engine, entry, pending, stored);
|
|
28943
|
+
if (pending.length >= ACK_BATCH_SIZE) await flushAcks(deps, pending, pageEpoch);
|
|
28944
|
+
}
|
|
28945
|
+
await flushAcks(deps, pending, pageEpoch);
|
|
28946
|
+
const served = deps.delivery.incomingEpoch();
|
|
28947
|
+
if (basis === null || served === null || served === basis.syncEpoch) break;
|
|
28948
|
+
await deps.kv.remove(STORE_KEYS.streamCursor("mailbox"));
|
|
28949
|
+
basis = null;
|
|
28645
28950
|
}
|
|
28646
|
-
await flushAcks(deps, pending, epoch);
|
|
28647
28951
|
} catch (err) {
|
|
28648
28952
|
logger.warn("PaymentsV2", "receive drain interrupted \u2014 unacked entries retry next drain:", err);
|
|
28649
|
-
await flushAcks(deps, pending,
|
|
28953
|
+
await flushAcks(deps, pending, pageEpoch).catch((flushErr) => {
|
|
28650
28954
|
logger.warn("PaymentsV2", "receive ack flush failed \u2014 cursor holds at the acked prefix:", flushErr);
|
|
28651
28955
|
});
|
|
28652
28956
|
}
|
|
@@ -28719,7 +29023,7 @@ async function announce(deps, entry, record) {
|
|
|
28719
29023
|
receivedAt
|
|
28720
29024
|
};
|
|
28721
29025
|
}
|
|
28722
|
-
async function flushAcks(deps, pending,
|
|
29026
|
+
async function flushAcks(deps, pending, epochOf) {
|
|
28723
29027
|
let lastAcked = null;
|
|
28724
29028
|
try {
|
|
28725
29029
|
while (pending.length > 0) {
|
|
@@ -28730,7 +29034,7 @@ async function flushAcks(deps, pending, epoch) {
|
|
|
28730
29034
|
}
|
|
28731
29035
|
} finally {
|
|
28732
29036
|
if (lastAcked !== null) {
|
|
28733
|
-
const record = { cursor: lastAcked, syncEpoch:
|
|
29037
|
+
const record = { cursor: lastAcked, syncEpoch: epochOf() };
|
|
28734
29038
|
await deps.kv.set(STORE_KEYS.streamCursor("mailbox"), record);
|
|
28735
29039
|
}
|
|
28736
29040
|
}
|
|
@@ -28831,9 +29135,19 @@ var Requests = class {
|
|
|
28831
29135
|
}
|
|
28832
29136
|
});
|
|
28833
29137
|
}
|
|
29138
|
+
/** Idempotent for a same-transferId re-write: committed only ratchets up, createdAt kept. */
|
|
28834
29139
|
writeLink(requestId, transferId, committed) {
|
|
28835
29140
|
return this.mutateJournal((m) => {
|
|
28836
|
-
|
|
29141
|
+
const prev = m.get(requestId);
|
|
29142
|
+
const same = prev !== void 0 && prev.transferId === transferId;
|
|
29143
|
+
const next = {
|
|
29144
|
+
requestId,
|
|
29145
|
+
transferId,
|
|
29146
|
+
committed: committed || same && prev.committed,
|
|
29147
|
+
createdAt: same ? prev.createdAt : this.now()
|
|
29148
|
+
};
|
|
29149
|
+
if (same && prev.committed === next.committed) return false;
|
|
29150
|
+
m.set(requestId, next);
|
|
28837
29151
|
return true;
|
|
28838
29152
|
});
|
|
28839
29153
|
}
|
|
@@ -28986,23 +29300,46 @@ var Requests = class {
|
|
|
28986
29300
|
const transferId = error instanceof SphereError && error.transferId !== void 0 ? error.transferId : void 0;
|
|
28987
29301
|
if (transferId !== void 0) {
|
|
28988
29302
|
const committed = error instanceof PartialSendConflictError;
|
|
28989
|
-
await this.
|
|
28990
|
-
this.setStatus(id, "settling");
|
|
29303
|
+
await this.settle(id, transferId, { committed, respond: false });
|
|
28991
29304
|
} else {
|
|
28992
29305
|
logger.error("PaymentsV2", `possibly-committed send for ${id} carried no transferId \u2014 paid (fail-closed)`);
|
|
28993
29306
|
this.setStatus(id, "paid");
|
|
28994
29307
|
}
|
|
28995
29308
|
} else {
|
|
28996
|
-
await this.
|
|
28997
|
-
this.setStatus(id, "pending");
|
|
29309
|
+
await this.revertPayable(id);
|
|
28998
29310
|
}
|
|
28999
29311
|
throw error;
|
|
29000
29312
|
}
|
|
29001
|
-
this.
|
|
29002
|
-
await this.respondPaid(id, result.id);
|
|
29003
|
-
await this.clearLink(id);
|
|
29313
|
+
await this.settle(id, result.id, { committed: true, respond: true });
|
|
29004
29314
|
return result;
|
|
29005
29315
|
}
|
|
29316
|
+
/**
|
|
29317
|
+
* THE settlement invariant (#441 + the failed-respond P1): a settling link is
|
|
29318
|
+
* removed ONLY by (a) a CONFIRMED paid respond — a 2xx, or the 409
|
|
29319
|
+
* already-resolved absorb — or (b) a proven clean pre-commit failure
|
|
29320
|
+
* (revertPayable). Nothing else removes one: not a network error, not a 5xx,
|
|
29321
|
+
* not a reload. Every path that binds a request to a transfer outcome funnels
|
|
29322
|
+
* through here — pay()'s clean success (respond now), pay()'s
|
|
29323
|
+
* possibly-committed throw (respond deferred), and reconcile's deferred arms
|
|
29324
|
+
* — so a failed respond always leaves the link + 'settling' and the next
|
|
29325
|
+
* reconcile pass (the committed-link override) retries the respond.
|
|
29326
|
+
*/
|
|
29327
|
+
async settle(requestId, transferId, opts) {
|
|
29328
|
+
let journalDown = false;
|
|
29329
|
+
try {
|
|
29330
|
+
await this.writeLink(requestId, transferId, opts.committed);
|
|
29331
|
+
} catch (err) {
|
|
29332
|
+
if (!opts.respond) throw err;
|
|
29333
|
+
journalDown = true;
|
|
29334
|
+
logger.error("PaymentsV2", `settling link write failed for ${requestId} \u2014 responding fail-closed:`, err);
|
|
29335
|
+
}
|
|
29336
|
+
if (!(opts.respond && await this.respondPaid(requestId, transferId))) {
|
|
29337
|
+
this.setStatus(requestId, journalDown ? "paid" : "settling");
|
|
29338
|
+
return;
|
|
29339
|
+
}
|
|
29340
|
+
if (!journalDown) await this.clearLink(requestId).catch(() => void 0);
|
|
29341
|
+
this.setStatus(requestId, "paid");
|
|
29342
|
+
}
|
|
29006
29343
|
/** 'paid' respond leg: 409 = already resolved = idempotent success; other errors defer. */
|
|
29007
29344
|
async respondPaid(id, transferId) {
|
|
29008
29345
|
try {
|
|
@@ -29038,32 +29375,32 @@ var Requests = class {
|
|
|
29038
29375
|
let aborted = null;
|
|
29039
29376
|
for (const [requestId, link] of [...this.journal]) {
|
|
29040
29377
|
if (link.committed || resumed.has(link.transferId)) {
|
|
29041
|
-
await this.resolvePaid(requestId, link
|
|
29378
|
+
await this.resolvePaid(requestId, link);
|
|
29042
29379
|
} else if (conflicted.has(link.transferId)) {
|
|
29043
29380
|
await this.revertPayable(requestId);
|
|
29044
29381
|
} else if (open.has(link.transferId)) {
|
|
29045
29382
|
} else {
|
|
29046
|
-
aborted = await this.reconcileUnaccounted(requestId, link
|
|
29383
|
+
aborted = await this.reconcileUnaccounted(requestId, link, aborted);
|
|
29047
29384
|
}
|
|
29048
29385
|
}
|
|
29049
29386
|
}
|
|
29050
29387
|
// Unaccounted link: consult the server aborted authority (lazy, memoized);
|
|
29051
29388
|
// a probe failure defers the link to the next session.
|
|
29052
|
-
async reconcileUnaccounted(requestId,
|
|
29389
|
+
async reconcileUnaccounted(requestId, link, aborted) {
|
|
29053
29390
|
try {
|
|
29054
29391
|
aborted ??= new Set(await this.deps.listAbortedTransferIds());
|
|
29055
|
-
if (aborted.has(transferId)) await this.revertPayable(requestId);
|
|
29056
|
-
else await this.resolvePaid(requestId,
|
|
29392
|
+
if (aborted.has(link.transferId)) await this.revertPayable(requestId);
|
|
29393
|
+
else await this.resolvePaid(requestId, link);
|
|
29057
29394
|
} catch (err) {
|
|
29058
29395
|
logger.warn("PaymentsV2", `aborted-intent probe failed \u2014 ${requestId} deferred:`, err);
|
|
29059
29396
|
}
|
|
29060
29397
|
return aborted;
|
|
29061
29398
|
}
|
|
29062
|
-
|
|
29063
|
-
|
|
29064
|
-
|
|
29065
|
-
this.setStatus(requestId, "paid");
|
|
29399
|
+
/** Deferred paid: the ONE settlement path again — a failed respond keeps the link. */
|
|
29400
|
+
resolvePaid(requestId, link) {
|
|
29401
|
+
return this.settle(requestId, link.transferId, { committed: link.committed, respond: true });
|
|
29066
29402
|
}
|
|
29403
|
+
/** Removal cause (b): a PROVEN clean outcome (pre-commit failure / server-aborted). */
|
|
29067
29404
|
async revertPayable(requestId) {
|
|
29068
29405
|
await this.clearLink(requestId);
|
|
29069
29406
|
this.setStatus(requestId, "pending");
|
|
@@ -29387,11 +29724,12 @@ function composeFacadeParts(deps, hooks) {
|
|
|
29387
29724
|
client: deps.client,
|
|
29388
29725
|
fieldKey: deps.fieldKey,
|
|
29389
29726
|
registry: deps.registry,
|
|
29390
|
-
emit: (event) => deps.emit(event,
|
|
29727
|
+
emit: (event, entry) => deps.emit(event, entry),
|
|
29391
29728
|
...deps.now !== void 0 ? { now: deps.now } : {},
|
|
29392
29729
|
...deps.newId !== void 0 ? { newId: deps.newId } : {}
|
|
29393
29730
|
});
|
|
29394
29731
|
const machineDeps = buildMachineDeps(deps, hooks, historyStore);
|
|
29732
|
+
const machineStores = createMachineStores(deps.kv);
|
|
29395
29733
|
const heldStates = /* @__PURE__ */ new Map();
|
|
29396
29734
|
return {
|
|
29397
29735
|
ownPubkeyBytes,
|
|
@@ -29399,12 +29737,29 @@ function composeFacadeParts(deps, hooks) {
|
|
|
29399
29737
|
ledger,
|
|
29400
29738
|
queue,
|
|
29401
29739
|
historyStore,
|
|
29402
|
-
machineStores
|
|
29740
|
+
machineStores,
|
|
29403
29741
|
machineDeps,
|
|
29404
29742
|
machine: new TransferMachine(machineDeps),
|
|
29405
29743
|
heldStates,
|
|
29406
29744
|
receiveLoop: buildReceive(deps, hooks, historyStore, heldStates),
|
|
29407
|
-
requests: buildRequests(deps, hooks)
|
|
29745
|
+
requests: buildRequests(deps, hooks),
|
|
29746
|
+
restoreDeps: buildRestoreDeps(deps, machineDeps, machineStores, view)
|
|
29747
|
+
};
|
|
29748
|
+
}
|
|
29749
|
+
function buildRestoreDeps(deps, machineDeps, machineStores, view) {
|
|
29750
|
+
return {
|
|
29751
|
+
stores: machineStores,
|
|
29752
|
+
kv: deps.kv,
|
|
29753
|
+
reput: (transferId, envelope, requiresSeedClose) => machineDeps.intents.put(transferId, envelope, requiresSeedClose),
|
|
29754
|
+
reseedCheckpoint: (transferId, opIndex) => deps.checkpointStore.reseedCheckpoint(transferId, opIndex),
|
|
29755
|
+
decryptPayload: machineDeps.decryptPayload,
|
|
29756
|
+
fullRePull: () => view.onEpochReset(),
|
|
29757
|
+
attention: (transferId, code, detail) => {
|
|
29758
|
+
deps.emit(
|
|
29759
|
+
"transfer:attention",
|
|
29760
|
+
detail === void 0 ? { transferId, code } : { transferId, code, detail }
|
|
29761
|
+
);
|
|
29762
|
+
}
|
|
29408
29763
|
};
|
|
29409
29764
|
}
|
|
29410
29765
|
function buildMachineDeps(deps, hooks, historyStore) {
|
|
@@ -29426,11 +29781,13 @@ function buildMachineDeps(deps, hooks, historyStore) {
|
|
|
29426
29781
|
ownPubkey: hexToBytes4(deps.ownPubkey),
|
|
29427
29782
|
emit: deps.emit,
|
|
29428
29783
|
now: deps.now ?? Date.now,
|
|
29429
|
-
recordHistory: async ({ transferId, payload }) => {
|
|
29784
|
+
recordHistory: async ({ transferId, payload, committedAmount }) => {
|
|
29430
29785
|
await historyStore.recordSent({
|
|
29431
29786
|
transferId,
|
|
29432
29787
|
coinId: payload.coinId,
|
|
29433
|
-
|
|
29788
|
+
// §5.9: the SETTLED amount (machine-computed from the certified
|
|
29789
|
+
// recipient blobs), never payload.amount — the plan.
|
|
29790
|
+
amount: committedAmount,
|
|
29434
29791
|
recipientPubkey: payload.recipient,
|
|
29435
29792
|
...payload.memo !== void 0 ? { memo: payload.memo } : {}
|
|
29436
29793
|
});
|
|
@@ -29457,7 +29814,7 @@ function buildReceive(deps, hooks, historyStore, heldStates) {
|
|
|
29457
29814
|
detail === void 0 ? { transferId, code } : { transferId, code, detail }
|
|
29458
29815
|
);
|
|
29459
29816
|
},
|
|
29460
|
-
syncEpoch: deps.syncEpoch
|
|
29817
|
+
syncEpoch: deps.syncEpoch,
|
|
29461
29818
|
...deps.now !== void 0 ? { now: deps.now } : {}
|
|
29462
29819
|
});
|
|
29463
29820
|
}
|
|
@@ -29510,8 +29867,24 @@ var PaymentsFacade = class {
|
|
|
29510
29867
|
this.receiveLoop = parts.receiveLoop;
|
|
29511
29868
|
this.heldStates = parts.heldStates;
|
|
29512
29869
|
this.ownPubkeyBytes = parts.ownPubkeyBytes;
|
|
29870
|
+
this.restoreDeps = parts.restoreDeps;
|
|
29513
29871
|
this.requests = parts.requests;
|
|
29514
29872
|
deps.deliveryPort.bindDeliveryKeys((blob) => this.engine().deliveryKeys(blob));
|
|
29873
|
+
this.heartbeat = new ConvergenceHeartbeat({
|
|
29874
|
+
now: () => this.nowMs(),
|
|
29875
|
+
onTick: () => this.trackTail(this.runConvergencePass())
|
|
29876
|
+
});
|
|
29877
|
+
this.converger = new Converger({
|
|
29878
|
+
machineDeps: this.machineDeps,
|
|
29879
|
+
stores: this.machineStores,
|
|
29880
|
+
delivery: deps.deliveryPort,
|
|
29881
|
+
reconcile: (report) => this.requests.reconcile(report),
|
|
29882
|
+
refreshView: () => this.view.delta(),
|
|
29883
|
+
replayMints: () => this.replayMints(),
|
|
29884
|
+
isActiveOp: (id) => this.activeMoneyOps.has(id),
|
|
29885
|
+
emit: deps.emit,
|
|
29886
|
+
now: () => this.nowMs()
|
|
29887
|
+
});
|
|
29515
29888
|
}
|
|
29516
29889
|
view;
|
|
29517
29890
|
ledger;
|
|
@@ -29523,17 +29896,28 @@ var PaymentsFacade = class {
|
|
|
29523
29896
|
receiveLoop;
|
|
29524
29897
|
heldStates;
|
|
29525
29898
|
ownPubkeyBytes;
|
|
29899
|
+
restoreDeps;
|
|
29526
29900
|
requests;
|
|
29527
29901
|
currentEngine = null;
|
|
29528
29902
|
pendingOps = /* @__PURE__ */ new Set();
|
|
29529
29903
|
unsubscribers = [];
|
|
29530
29904
|
started = false;
|
|
29905
|
+
// §7 heartbeat: in-memory timing cell + pass runner (see convergence.ts).
|
|
29906
|
+
heartbeat;
|
|
29907
|
+
converger;
|
|
29908
|
+
/** Resume single-flight (§7): ticks, start() and resumeNow() coalesce onto ONE pass. */
|
|
29909
|
+
resumeFlight = new SingleFlight();
|
|
29910
|
+
/** §5.1/§7: restore + convergence passes SERIALIZE here. */
|
|
29911
|
+
passChain = new SerialChain();
|
|
29912
|
+
/** §7 ownership: ids with an in-process machine attempt — the pass never adopts one. */
|
|
29913
|
+
activeMoneyOps = /* @__PURE__ */ new Set();
|
|
29531
29914
|
// ── lifecycle ──────────────────────────────────────────────────────────────
|
|
29532
29915
|
async start() {
|
|
29533
29916
|
if (this.started) return;
|
|
29534
29917
|
this.started = true;
|
|
29535
|
-
|
|
29918
|
+
this.heartbeat.start();
|
|
29536
29919
|
this.unsubscribers.push(
|
|
29920
|
+
this.deps.session.subscribeEpochChange((epoch) => this.handleEpochChange(epoch)),
|
|
29537
29921
|
this.deps.session.subscribeStream("mailbox", () => {
|
|
29538
29922
|
this.trackTail(this.receiveLoop.drainOnce());
|
|
29539
29923
|
}),
|
|
@@ -29544,20 +29928,12 @@ var PaymentsFacade = class {
|
|
|
29544
29928
|
this.trackTail(this.requests.drainIncoming());
|
|
29545
29929
|
})
|
|
29546
29930
|
);
|
|
29547
|
-
this.
|
|
29548
|
-
|
|
29549
|
-
|
|
29550
|
-
|
|
29551
|
-
|
|
29552
|
-
|
|
29553
|
-
this.deps.emit(
|
|
29554
|
-
"transfer:attention",
|
|
29555
|
-
detail === void 0 ? { transferId, code } : { transferId, code, detail }
|
|
29556
|
-
);
|
|
29557
|
-
}
|
|
29558
|
-
})
|
|
29559
|
-
);
|
|
29560
|
-
this.trackTail(this.replayMints());
|
|
29931
|
+
const statusUnsub = this.deps.session.subscribeStatus?.((status) => {
|
|
29932
|
+
if (status === "connected") this.heartbeat.recovered();
|
|
29933
|
+
});
|
|
29934
|
+
if (statusUnsub !== void 0) this.unsubscribers.push(statusUnsub);
|
|
29935
|
+
await this.deps.session.start();
|
|
29936
|
+
this.trackTail(this.runConvergencePass());
|
|
29561
29937
|
this.trackTail(this.seedHeldStates());
|
|
29562
29938
|
await this.view.fullPull().catch(() => void 0);
|
|
29563
29939
|
this.receiveLoop.start(this.deps.receivePollMs);
|
|
@@ -29565,6 +29941,7 @@ var PaymentsFacade = class {
|
|
|
29565
29941
|
/** §7 same-address restart gate: resolves only after in-flight ops settle. */
|
|
29566
29942
|
async stop() {
|
|
29567
29943
|
this.started = false;
|
|
29944
|
+
this.heartbeat.stop();
|
|
29568
29945
|
this.receiveLoop.stop();
|
|
29569
29946
|
for (const unsubscribe of this.unsubscribers) unsubscribe();
|
|
29570
29947
|
this.unsubscribers = [];
|
|
@@ -29590,9 +29967,13 @@ var PaymentsFacade = class {
|
|
|
29590
29967
|
history(page) {
|
|
29591
29968
|
return this.historyStore.page(page ?? {});
|
|
29592
29969
|
}
|
|
29970
|
+
/** §4 pending-transfers UI surface — derived on read, never cached (convergence.ts). */
|
|
29971
|
+
pendingTransfers() {
|
|
29972
|
+
return derivePendingTransfers(this.machineStores, this.machineDeps.decryptPayload);
|
|
29973
|
+
}
|
|
29593
29974
|
// ── money movement ─────────────────────────────────────────────────────────
|
|
29594
29975
|
send(request) {
|
|
29595
|
-
return this.track(this.
|
|
29976
|
+
return this.track(this.sendOutcome(request));
|
|
29596
29977
|
}
|
|
29597
29978
|
async receive() {
|
|
29598
29979
|
const transfers = await this.track(this.receiveLoop.drainOnce());
|
|
@@ -29602,9 +29983,32 @@ var PaymentsFacade = class {
|
|
|
29602
29983
|
mint(coinId, amount) {
|
|
29603
29984
|
return this.track(this.mintInner(coinId, amount));
|
|
29604
29985
|
}
|
|
29986
|
+
// §4 retry verb: coalesces onto the running pass — NEVER re-issue send() (#631/#676).
|
|
29987
|
+
async resumeNow() {
|
|
29988
|
+
if (!this.started) return;
|
|
29989
|
+
await this.track(this.runConvergencePass());
|
|
29990
|
+
}
|
|
29991
|
+
/** §5.1 restore — awaited by the session latch BEFORE streams resume; never coalesced onto a pre-restore pass. */
|
|
29992
|
+
async handleEpochChange(_newEpoch) {
|
|
29993
|
+
await this.passChain.enqueue(() => reseedAndReset(this.restoreDeps));
|
|
29994
|
+
await this.passChain.enqueue(() => this.convergeBody());
|
|
29995
|
+
}
|
|
29996
|
+
/** One single-flighted pass + its reschedule: concurrent callers coalesce. */
|
|
29997
|
+
runConvergencePass() {
|
|
29998
|
+
return this.resumeFlight.run(() => this.passChain.enqueue(() => this.convergeBody()));
|
|
29999
|
+
}
|
|
30000
|
+
async convergeBody() {
|
|
30001
|
+
const outcome = await this.converger.convergeOnce();
|
|
30002
|
+
const pending = await this.converger.pendingWork().catch(() => true);
|
|
30003
|
+
this.heartbeat.settle(outcome, pending);
|
|
30004
|
+
}
|
|
30005
|
+
nowMs() {
|
|
30006
|
+
return (this.deps.now ?? Date.now)();
|
|
30007
|
+
}
|
|
29605
30008
|
// ── send policy (§5.5 + old-loop parity; the machine stays policy-free) ────
|
|
29606
|
-
|
|
29607
|
-
|
|
30009
|
+
/** The ONE place a send() outcome is shaped: success emits in finishSend, a
|
|
30010
|
+
* CLEAN rejection emits `transfer:updated{status:'failed'}` here (§4). */
|
|
30011
|
+
async sendOutcome(request) {
|
|
29608
30012
|
const run = {
|
|
29609
30013
|
amount: request.amount,
|
|
29610
30014
|
delivered: [],
|
|
@@ -29612,12 +30016,36 @@ var PaymentsFacade = class {
|
|
|
29612
30016
|
tokenTransfers: [],
|
|
29613
30017
|
settledShortfalls: [],
|
|
29614
30018
|
firstPartialId: void 0,
|
|
29615
|
-
pendingAny: false
|
|
30019
|
+
pendingAny: false,
|
|
30020
|
+
lastTransferId: ""
|
|
29616
30021
|
};
|
|
30022
|
+
try {
|
|
30023
|
+
return await this.sendWithPolicy(request, run);
|
|
30024
|
+
} catch (err) {
|
|
30025
|
+
this.emitCleanFailure(err, run.lastTransferId);
|
|
30026
|
+
throw err;
|
|
30027
|
+
}
|
|
30028
|
+
}
|
|
30029
|
+
/** Only a CLEAN failure (nothing certified, classifyError 'other') is 'failed'.
|
|
30030
|
+
* Keep-open/partial/conflict outcomes are pending/converging — labelling them
|
|
30031
|
+
* 'failed' invites a dApp re-send, i.e. a double-pay (#631/#676). */
|
|
30032
|
+
emitCleanFailure(err, transferId) {
|
|
30033
|
+
if (isPossiblyCommittedSendOutcome(err) || classifyError(err) !== "other") return;
|
|
30034
|
+
const failed = {
|
|
30035
|
+
id: transferId,
|
|
30036
|
+
status: "failed",
|
|
30037
|
+
tokens: [],
|
|
30038
|
+
tokenTransfers: [],
|
|
30039
|
+
error: messageOf(err)
|
|
30040
|
+
};
|
|
30041
|
+
this.deps.emit("transfer:updated", failed);
|
|
30042
|
+
}
|
|
30043
|
+
async sendWithPolicy(request, run) {
|
|
30044
|
+
const recipient = await this.requireSameNetworkRecipient(request.recipient);
|
|
29617
30045
|
for (let attempt = 0; ; attempt++) {
|
|
29618
30046
|
let ctx;
|
|
29619
30047
|
try {
|
|
29620
|
-
ctx = await this.planAndMaterialize(recipient.chainPubkey, { ...request, amount: run.amount });
|
|
30048
|
+
ctx = await this.planAndMaterialize(recipient.chainPubkey, { ...request, amount: run.amount }, run);
|
|
29621
30049
|
} catch (err) {
|
|
29622
30050
|
throw this.partialize(err, run);
|
|
29623
30051
|
}
|
|
@@ -29651,21 +30079,24 @@ var PaymentsFacade = class {
|
|
|
29651
30079
|
return recipient;
|
|
29652
30080
|
}
|
|
29653
30081
|
async runAttempt(ctx) {
|
|
30082
|
+
this.activeMoneyOps.add(ctx.transferId);
|
|
29654
30083
|
try {
|
|
29655
30084
|
const { deliveryPending } = await this.machine.run(ctx.plan);
|
|
29656
30085
|
this.settleSuccess(ctx, ctx.sourceIds);
|
|
29657
30086
|
return { kind: "success", deliveryPending };
|
|
29658
30087
|
} catch (err) {
|
|
29659
|
-
return this.disposeFailedAttempt(ctx, err);
|
|
30088
|
+
return await this.disposeFailedAttempt(ctx, err);
|
|
30089
|
+
} finally {
|
|
30090
|
+
this.activeMoneyOps.delete(ctx.transferId);
|
|
29660
30091
|
}
|
|
29661
30092
|
}
|
|
29662
|
-
/**
|
|
29663
|
-
*
|
|
29664
|
-
* the machine BEFORE complete). Accumulate the settled set, then re-plan ONLY
|
|
29665
|
-
* the remainder under a NEW transferId — never the full amount.
|
|
29666
|
-
*/
|
|
30093
|
+
/** Partial (#677/#690): shortfall already durable (machine wrote it BEFORE
|
|
30094
|
+
* complete); accumulate settled, re-plan ONLY the remainder, NEW transferId. */
|
|
29667
30095
|
async consumePartial(ctx, disposition, run, attempt) {
|
|
29668
|
-
|
|
30096
|
+
if (disposition.deliveryPending) {
|
|
30097
|
+
run.pendingAny = true;
|
|
30098
|
+
this.heartbeat.arm();
|
|
30099
|
+
}
|
|
29669
30100
|
const partial = disposition.entry;
|
|
29670
30101
|
run.delivered.push(...partial.committedTokenIds);
|
|
29671
30102
|
run.firstPartialId ??= partial.transferId;
|
|
@@ -29686,11 +30117,8 @@ var PaymentsFacade = class {
|
|
|
29686
30117
|
run.amount = partial.remainingAmount;
|
|
29687
30118
|
return null;
|
|
29688
30119
|
}
|
|
29689
|
-
/**
|
|
29690
|
-
*
|
|
29691
|
-
* backstop still 'open' (committed>0) → converge via the same machine's
|
|
29692
|
-
* resumeIntent; clean conflict with a demoted source → bounded full re-plan.
|
|
29693
|
-
*/
|
|
30120
|
+
/** Failure disposition: possibly-committed → rethrow UNWRAPPED; backstop
|
|
30121
|
+
* 'open' → converge via resumeIntent; clean demoted conflict → full re-plan. */
|
|
29694
30122
|
async disposeFailedAttempt(ctx, err) {
|
|
29695
30123
|
if (isPossiblyCommittedSendOutcome(err)) {
|
|
29696
30124
|
this.settleKeepOpen(ctx);
|
|
@@ -29739,8 +30167,9 @@ var PaymentsFacade = class {
|
|
|
29739
30167
|
}
|
|
29740
30168
|
return { kind: "rethrow", error: err };
|
|
29741
30169
|
}
|
|
29742
|
-
async planAndMaterialize(recipientPubkey, request) {
|
|
30170
|
+
async planAndMaterialize(recipientPubkey, request, run) {
|
|
29743
30171
|
const transferId = this.newId();
|
|
30172
|
+
run.lastTransferId = transferId;
|
|
29744
30173
|
const planned = this.queue.plan(transferId, { coinId: request.coinId, amount: request.amount });
|
|
29745
30174
|
const spend = planned.kind === "planned" ? planned.spend : await planned.settled;
|
|
29746
30175
|
const sourceIds = this.markPlanned(transferId, request.coinId, spend);
|
|
@@ -29823,6 +30252,7 @@ var PaymentsFacade = class {
|
|
|
29823
30252
|
/** Open intent: sources stay reserved + in-flight (§5.2) until resume adopts them. */
|
|
29824
30253
|
settleKeepOpen(ctx) {
|
|
29825
30254
|
this.queue.clearExpectedChange(ctx.transferId);
|
|
30255
|
+
this.heartbeat.arm();
|
|
29826
30256
|
}
|
|
29827
30257
|
settlePartial(ctx, partial) {
|
|
29828
30258
|
this.ledger.cancel(ctx.transferId);
|
|
@@ -29859,6 +30289,7 @@ var PaymentsFacade = class {
|
|
|
29859
30289
|
for (const id of run.settledShortfalls.splice(0)) {
|
|
29860
30290
|
await this.machineStores.shortfalls.removeByKey(id).catch(() => void 0);
|
|
29861
30291
|
}
|
|
30292
|
+
if (run.pendingAny) this.heartbeat.arm();
|
|
29862
30293
|
const result = {
|
|
29863
30294
|
id: transferId,
|
|
29864
30295
|
status: run.pendingAny ? "confirmed" : "delivered",
|
|
@@ -29870,11 +30301,7 @@ var PaymentsFacade = class {
|
|
|
29870
30301
|
this.deps.emit("transfer:updated", result);
|
|
29871
30302
|
return result;
|
|
29872
30303
|
}
|
|
29873
|
-
/**
|
|
29874
|
-
* With nothing delivered the error passes UNWRAPPED (identity + cause kept);
|
|
29875
|
-
* after ≥1 delivered leg EVERY failure surfaces as PartialSendConflictError
|
|
29876
|
-
* over the accumulated settled set — never bare, never a full-amount retry.
|
|
29877
|
-
*/
|
|
30304
|
+
/** Nothing delivered → UNWRAPPED; after ≥1 delivered leg every failure surfaces as PartialSendConflictError over the settled set. */
|
|
29878
30305
|
partialize(err, run) {
|
|
29879
30306
|
if (run.delivered.length === 0) return err;
|
|
29880
30307
|
return new PartialSendConflictError(
|
|
@@ -29903,21 +30330,29 @@ var PaymentsFacade = class {
|
|
|
29903
30330
|
if (!/^(?:[0-9a-f]{2})+$/.test(coinId) || amount <= 0n) {
|
|
29904
30331
|
return { success: false, error: "coinId must be even-length lowercase hex and amount positive" };
|
|
29905
30332
|
}
|
|
29906
|
-
const engine = this.engine();
|
|
29907
30333
|
const mintId = this.newId();
|
|
29908
|
-
|
|
30334
|
+
this.activeMoneyOps.add(mintId);
|
|
30335
|
+
try {
|
|
30336
|
+
return await this.mintUnderJournal(mintId, coinId, amount);
|
|
30337
|
+
} finally {
|
|
30338
|
+
this.activeMoneyOps.delete(mintId);
|
|
30339
|
+
}
|
|
30340
|
+
}
|
|
30341
|
+
async mintUnderJournal(mintId, coinId, amount) {
|
|
30342
|
+
const engine = this.engine();
|
|
29909
30343
|
const entry = {
|
|
29910
30344
|
mintId,
|
|
29911
30345
|
coinId,
|
|
29912
30346
|
amount: amount.toString(),
|
|
29913
30347
|
tokenId: "",
|
|
29914
|
-
createdAt:
|
|
30348
|
+
createdAt: this.nowMs()
|
|
29915
30349
|
};
|
|
29916
30350
|
await this.machineStores.mintJournal.upsert(entry);
|
|
29917
30351
|
let token;
|
|
29918
30352
|
try {
|
|
29919
|
-
token = await engine.mint(
|
|
30353
|
+
token = await engine.mint(this.mintParams(coinId, amount), { transferId: mintId });
|
|
29920
30354
|
} catch (err) {
|
|
30355
|
+
this.heartbeat.arm();
|
|
29921
30356
|
return { success: false, error: messageOf(err) };
|
|
29922
30357
|
}
|
|
29923
30358
|
if (token.blob.tokenId !== entry.tokenId) {
|
|
@@ -29926,6 +30361,7 @@ var PaymentsFacade = class {
|
|
|
29926
30361
|
try {
|
|
29927
30362
|
await this.finalizeMint(engine, mintId, token, coinId, amount.toString());
|
|
29928
30363
|
} catch (err) {
|
|
30364
|
+
this.heartbeat.arm();
|
|
29929
30365
|
return { success: false, tokenId: token.blob.tokenId, error: messageOf(err) };
|
|
29930
30366
|
}
|
|
29931
30367
|
return { success: true, tokenId: token.blob.tokenId };
|
|
@@ -29948,18 +30384,22 @@ var PaymentsFacade = class {
|
|
|
29948
30384
|
this.heldStates.set(token.blob.tokenId, (await engine.deliveryKeys(bytes)).stateHash);
|
|
29949
30385
|
this.trackTail(this.view.delta());
|
|
29950
30386
|
}
|
|
30387
|
+
/** @returns how many journal entries were RESOLVED (cleared) — heartbeat progress. */
|
|
29951
30388
|
async replayMints() {
|
|
30389
|
+
let resolved = 0;
|
|
29952
30390
|
for (const entry of await this.machineStores.mintJournal.list()) {
|
|
30391
|
+
if (this.activeMoneyOps.has(entry.mintId)) continue;
|
|
29953
30392
|
try {
|
|
29954
|
-
await this.replayMint(entry);
|
|
30393
|
+
if (await this.replayMint(entry)) resolved += 1;
|
|
29955
30394
|
} catch {
|
|
29956
30395
|
}
|
|
29957
30396
|
}
|
|
30397
|
+
return resolved;
|
|
29958
30398
|
}
|
|
29959
30399
|
async replayMint(entry) {
|
|
29960
30400
|
if (entry.tokenId !== "" && await this.tokenInServerInventory(entry.tokenId)) {
|
|
29961
30401
|
await this.machineStores.mintJournal.removeByKey(entry.mintId);
|
|
29962
|
-
return;
|
|
30402
|
+
return true;
|
|
29963
30403
|
}
|
|
29964
30404
|
const engine = this.engine();
|
|
29965
30405
|
if (!supportsDeterministicMint(engine)) {
|
|
@@ -29967,7 +30407,7 @@ var PaymentsFacade = class {
|
|
|
29967
30407
|
transferId: entry.mintId,
|
|
29968
30408
|
code: ATTENTION_MINT_UNRESOLVED
|
|
29969
30409
|
});
|
|
29970
|
-
return;
|
|
30410
|
+
return false;
|
|
29971
30411
|
}
|
|
29972
30412
|
const token = await engine.mint(this.mintParams(entry.coinId, BigInt(entry.amount)), {
|
|
29973
30413
|
transferId: entry.mintId
|
|
@@ -29976,6 +30416,7 @@ var PaymentsFacade = class {
|
|
|
29976
30416
|
await this.machineStores.mintJournal.upsert({ ...entry, tokenId: token.blob.tokenId });
|
|
29977
30417
|
}
|
|
29978
30418
|
await this.finalizeMint(engine, entry.mintId, token, entry.coinId, entry.amount);
|
|
30419
|
+
return true;
|
|
29979
30420
|
}
|
|
29980
30421
|
mintParams(coinId, amount) {
|
|
29981
30422
|
return {
|
|
@@ -30001,17 +30442,6 @@ var PaymentsFacade = class {
|
|
|
30001
30442
|
newId() {
|
|
30002
30443
|
return (this.deps.newId ?? randomUUID)();
|
|
30003
30444
|
}
|
|
30004
|
-
async runResume() {
|
|
30005
|
-
const report = await resumeAll(this.machineDeps);
|
|
30006
|
-
await this.requests.reconcile({
|
|
30007
|
-
resumed: report.resumed,
|
|
30008
|
-
conflicted: report.conflicted,
|
|
30009
|
-
open: report.failed
|
|
30010
|
-
});
|
|
30011
|
-
if (report.resumed.length > 0 || report.conflicted.length > 0) {
|
|
30012
|
-
await this.view.delta().catch(() => void 0);
|
|
30013
|
-
}
|
|
30014
|
-
}
|
|
30015
30445
|
async seedHeldStates() {
|
|
30016
30446
|
let page = await this.deps.storagePort.listInventory();
|
|
30017
30447
|
for (let i = 0; i < INVENTORY_SCAN_PAGE_LIMIT; i++) {
|
|
@@ -30023,22 +30453,14 @@ var PaymentsFacade = class {
|
|
|
30023
30453
|
}
|
|
30024
30454
|
}
|
|
30025
30455
|
toUiToken(tokenId, coinId, amount) {
|
|
30026
|
-
const
|
|
30027
|
-
|
|
30028
|
-
|
|
30029
|
-
|
|
30030
|
-
|
|
30031
|
-
|
|
30032
|
-
|
|
30033
|
-
|
|
30034
|
-
decimals: registry.getDecimals(coinId),
|
|
30035
|
-
...iconUrl !== null ? { iconUrl } : {},
|
|
30036
|
-
amount: amount.toString(),
|
|
30037
|
-
status: "transferring",
|
|
30038
|
-
createdAt: now,
|
|
30039
|
-
updatedAt: now,
|
|
30040
|
-
lazy: true
|
|
30041
|
-
};
|
|
30456
|
+
const now = this.nowMs();
|
|
30457
|
+
return toToken(
|
|
30458
|
+
tokenId,
|
|
30459
|
+
{ assets: [], createdAt: now, updatedAt: now },
|
|
30460
|
+
{ coinId, amount: amount.toString() },
|
|
30461
|
+
this.deps.registry,
|
|
30462
|
+
{ transferring: true, suspectedSpent: false }
|
|
30463
|
+
);
|
|
30042
30464
|
}
|
|
30043
30465
|
track(op) {
|
|
30044
30466
|
const settled = op.then(
|
|
@@ -30053,27 +30475,6 @@ var PaymentsFacade = class {
|
|
|
30053
30475
|
this.track(op).catch(() => void 0);
|
|
30054
30476
|
}
|
|
30055
30477
|
};
|
|
30056
|
-
function buildPayload(recipientPubkey, request, spend, spentStates) {
|
|
30057
|
-
return {
|
|
30058
|
-
v: 2,
|
|
30059
|
-
recipient: recipientPubkey,
|
|
30060
|
-
coinId: request.coinId,
|
|
30061
|
-
amount: request.amount,
|
|
30062
|
-
...request.memo !== void 0 ? { memo: request.memo } : {},
|
|
30063
|
-
direct: [...spend.plan.direct],
|
|
30064
|
-
...spend.plan.split !== void 0 ? {
|
|
30065
|
-
split: {
|
|
30066
|
-
tokenId: spend.plan.split.tokenId,
|
|
30067
|
-
splitAmount: spend.plan.split.splitAmount.toString(),
|
|
30068
|
-
remainderAmount: spend.plan.split.remainderAmount.toString()
|
|
30069
|
-
}
|
|
30070
|
-
} : {},
|
|
30071
|
-
spentStates
|
|
30072
|
-
};
|
|
30073
|
-
}
|
|
30074
|
-
function messageOf(err) {
|
|
30075
|
-
return err instanceof Error ? err.message : String(err);
|
|
30076
|
-
}
|
|
30077
30478
|
|
|
30078
30479
|
// impl/wallet-api-v2/http.ts
|
|
30079
30480
|
var WalletApiHttpError = class extends Error {
|
|
@@ -30419,7 +30820,9 @@ var WalletApiSession = class {
|
|
|
30419
30820
|
epochLatch = null;
|
|
30420
30821
|
reseedChain = Promise.resolve();
|
|
30421
30822
|
reseedsPending = 0;
|
|
30823
|
+
epochHandlers = /* @__PURE__ */ new Set();
|
|
30422
30824
|
handlers = /* @__PURE__ */ new Map();
|
|
30825
|
+
statusHandlers = /* @__PURE__ */ new Set();
|
|
30423
30826
|
socket = null;
|
|
30424
30827
|
started = false;
|
|
30425
30828
|
stopped = false;
|
|
@@ -30520,6 +30923,18 @@ var WalletApiSession = class {
|
|
|
30520
30923
|
await this.deps.kv.set(this.refreshKey, tokens.refreshToken);
|
|
30521
30924
|
}
|
|
30522
30925
|
// ── epoch latch (F12) ─────────────────────────────────────────────────────
|
|
30926
|
+
/** §5.1: the latched server syncEpoch ('' before first server contact). */
|
|
30927
|
+
currentEpoch() {
|
|
30928
|
+
return this.epochLatch ?? "";
|
|
30929
|
+
}
|
|
30930
|
+
/**
|
|
30931
|
+
* §5.1 restore hook (FacadeSession contract): handlers are AWAITED inside
|
|
30932
|
+
* the reseed chain, BEFORE any stream nudge resumes.
|
|
30933
|
+
*/
|
|
30934
|
+
subscribeEpochChange(handler) {
|
|
30935
|
+
this.epochHandlers.add(handler);
|
|
30936
|
+
return () => this.epochHandlers.delete(handler);
|
|
30937
|
+
}
|
|
30523
30938
|
noteEpoch(epoch) {
|
|
30524
30939
|
if (this.epochLatch === epoch) return;
|
|
30525
30940
|
const previous = this.epochLatch;
|
|
@@ -30533,7 +30948,7 @@ var WalletApiSession = class {
|
|
|
30533
30948
|
}
|
|
30534
30949
|
async runReseed(epoch, previous) {
|
|
30535
30950
|
try {
|
|
30536
|
-
|
|
30951
|
+
for (const handler of [...this.epochHandlers]) await handler(epoch);
|
|
30537
30952
|
await this.deps.kv.set(STORE_KEYS.epochLatch, epoch);
|
|
30538
30953
|
} catch {
|
|
30539
30954
|
if (this.epochLatch === epoch) this.epochLatch = previous;
|
|
@@ -30570,10 +30985,16 @@ var WalletApiSession = class {
|
|
|
30570
30985
|
status() {
|
|
30571
30986
|
return this.lastStatus ?? "offline";
|
|
30572
30987
|
}
|
|
30988
|
+
/** Transition feed off the existing emitStatus point (FacadeSession.subscribeStatus). */
|
|
30989
|
+
subscribeStatus(handler) {
|
|
30990
|
+
this.statusHandlers.add(handler);
|
|
30991
|
+
return () => this.statusHandlers.delete(handler);
|
|
30992
|
+
}
|
|
30573
30993
|
emitStatus(status) {
|
|
30574
30994
|
if (this.stopped || this.lastStatus === status) return;
|
|
30575
30995
|
this.lastStatus = status;
|
|
30576
30996
|
this.deps.emitStatus(status);
|
|
30997
|
+
for (const handler of this.statusHandlers) handler(status);
|
|
30577
30998
|
}
|
|
30578
30999
|
async connect() {
|
|
30579
31000
|
if (this.stopped) return;
|
|
@@ -30789,6 +31210,7 @@ var WalletApiDeliveryPort = class {
|
|
|
30789
31210
|
wake;
|
|
30790
31211
|
deriveFn;
|
|
30791
31212
|
seenChain = new SerialChain();
|
|
31213
|
+
lastIncomingEpoch = null;
|
|
30792
31214
|
constructor(config) {
|
|
30793
31215
|
this.client = config.client;
|
|
30794
31216
|
this.kv = config.kv;
|
|
@@ -30883,6 +31305,7 @@ var WalletApiDeliveryPort = class {
|
|
|
30883
31305
|
const seen = await this.readSeen();
|
|
30884
31306
|
let page = await this.client.listMailbox(sinceCursor === void 0 ? 0 : Number(sinceCursor));
|
|
30885
31307
|
for (; ; ) {
|
|
31308
|
+
this.lastIncomingEpoch = String(page.syncEpoch);
|
|
30886
31309
|
for (const entry of page.entries) {
|
|
30887
31310
|
if (entry.status !== "unclaimed") continue;
|
|
30888
31311
|
if (seen.has(entry.entryId)) continue;
|
|
@@ -30892,6 +31315,10 @@ var WalletApiDeliveryPort = class {
|
|
|
30892
31315
|
page = await this.client.listMailbox(page.cursor);
|
|
30893
31316
|
}
|
|
30894
31317
|
}
|
|
31318
|
+
/** §5.7: the page-honest epoch — the restore-detection source Receive keys on. */
|
|
31319
|
+
incomingEpoch() {
|
|
31320
|
+
return this.lastIncomingEpoch;
|
|
31321
|
+
}
|
|
30895
31322
|
toIncoming(entry) {
|
|
30896
31323
|
const bundle = this.openEnvelope(entry);
|
|
30897
31324
|
return {
|
|
@@ -30969,7 +31396,7 @@ var WalletApiSplitCheckpointStore = class {
|
|
|
30969
31396
|
return utf8(`${transferId}:${String(opIndex)}`);
|
|
30970
31397
|
}
|
|
30971
31398
|
async put(transferId, opIndex, bytes) {
|
|
30972
|
-
const {
|
|
31399
|
+
const { kv, fieldKey } = this.config;
|
|
30973
31400
|
const aad = this.aad(transferId, opIndex);
|
|
30974
31401
|
const cacheKey = this.cacheKey(transferId, opIndex);
|
|
30975
31402
|
let envelope = await kv.get(cacheKey);
|
|
@@ -30977,10 +31404,21 @@ var WalletApiSplitCheckpointStore = class {
|
|
|
30977
31404
|
envelope = encryptFieldBytes(fieldKey, bytes, aad);
|
|
30978
31405
|
await kv.set(cacheKey, envelope);
|
|
30979
31406
|
}
|
|
30980
|
-
const
|
|
30981
|
-
const record = await client.postProgress(transferId, opIndex, envelope, signature);
|
|
31407
|
+
const record = await this.post(transferId, opIndex, envelope);
|
|
30982
31408
|
return this.open(record.payload, aad);
|
|
30983
31409
|
}
|
|
31410
|
+
/** §5.1 restore (CheckpointReseeder): re-POST the cached ciphertext byte-identical; false = none cached. */
|
|
31411
|
+
async reseedCheckpoint(transferId, opIndex) {
|
|
31412
|
+
const envelope = await this.config.kv.get(this.cacheKey(transferId, opIndex));
|
|
31413
|
+
if (envelope === null) return false;
|
|
31414
|
+
await this.post(transferId, opIndex, envelope);
|
|
31415
|
+
return true;
|
|
31416
|
+
}
|
|
31417
|
+
/** The ONE signed progress-append path put() and reseedCheckpoint() share. */
|
|
31418
|
+
async post(transferId, opIndex, envelope) {
|
|
31419
|
+
const signature = await this.config.signProgress(progressSignMessage(transferId, opIndex, envelope));
|
|
31420
|
+
return this.config.client.postProgress(transferId, opIndex, envelope, signature);
|
|
31421
|
+
}
|
|
30984
31422
|
async get(transferId, opIndex) {
|
|
30985
31423
|
const records = await this.config.client.getProgress(transferId);
|
|
30986
31424
|
const record = records.find((r) => r.opIndex === opIndex);
|
|
@@ -31003,7 +31441,7 @@ var WalletApiSplitCheckpointStore = class {
|
|
|
31003
31441
|
};
|
|
31004
31442
|
|
|
31005
31443
|
// core/payments-v2-wiring.ts
|
|
31006
|
-
function resolvePaymentsV2Composition(walletApi) {
|
|
31444
|
+
function resolvePaymentsV2Composition(walletApi, sphereNetwork) {
|
|
31007
31445
|
const source = walletApi;
|
|
31008
31446
|
const network = typeof source?.network === "string" && source.network !== "" ? source.network : null;
|
|
31009
31447
|
if (!source || network === null) {
|
|
@@ -31012,6 +31450,18 @@ function resolvePaymentsV2Composition(walletApi) {
|
|
|
31012
31450
|
"INVALID_CONFIG"
|
|
31013
31451
|
);
|
|
31014
31452
|
}
|
|
31453
|
+
if (!(network in NETWORKS)) {
|
|
31454
|
+
throw new SphereError(
|
|
31455
|
+
`paymentsV2: walletApi.network "${network}" is not a known network (${Object.keys(NETWORKS).join("/")}) \u2014 a payments composition is single-network (Sphere network "${String(sphereNetwork)}").`,
|
|
31456
|
+
"INVALID_CONFIG"
|
|
31457
|
+
);
|
|
31458
|
+
}
|
|
31459
|
+
if (network !== sphereNetwork) {
|
|
31460
|
+
throw new SphereError(
|
|
31461
|
+
`paymentsV2: walletApi.network "${network}" does not match the Sphere network "${String(sphereNetwork)}" the engine/registry run on \u2014 a payments composition is single-network.`,
|
|
31462
|
+
"INVALID_CONFIG"
|
|
31463
|
+
);
|
|
31464
|
+
}
|
|
31015
31465
|
if (typeof source.paymentsV2Transport === "function") {
|
|
31016
31466
|
return { network, factory: source.paymentsV2Transport.bind(source) };
|
|
31017
31467
|
}
|
|
@@ -31077,8 +31527,7 @@ function defaultPaymentsV2Transport(config) {
|
|
|
31077
31527
|
deviceId: `${config.deviceId}:pv2`,
|
|
31078
31528
|
kv: args.kv,
|
|
31079
31529
|
webSocketFactory,
|
|
31080
|
-
emitStatus: args.emitStatus
|
|
31081
|
-
onEpochChange: args.onEpochChange
|
|
31530
|
+
emitStatus: args.emitStatus
|
|
31082
31531
|
});
|
|
31083
31532
|
return { session, client: authedWireClient(session, client) };
|
|
31084
31533
|
};
|
|
@@ -31148,8 +31597,7 @@ function composePaymentsV2(spec) {
|
|
|
31148
31597
|
identity: identitySlice,
|
|
31149
31598
|
network,
|
|
31150
31599
|
kv,
|
|
31151
|
-
emitStatus: (status) => host.emit("connection:status", { status })
|
|
31152
|
-
onEpochChange: async () => void 0
|
|
31600
|
+
emitStatus: (status) => host.emit("connection:status", { status })
|
|
31153
31601
|
});
|
|
31154
31602
|
const deps = {
|
|
31155
31603
|
session: transport.session,
|
|
@@ -31179,7 +31627,10 @@ function composePaymentsV2(spec) {
|
|
|
31179
31627
|
network,
|
|
31180
31628
|
ownPubkey: identity.chainPubkey,
|
|
31181
31629
|
ownNametag: host.nametag,
|
|
31182
|
-
requestMemo: requestMemoCodec(identity.privateKey)
|
|
31630
|
+
requestMemo: requestMemoCodec(identity.privateKey),
|
|
31631
|
+
// §5.1: the session is the epoch authority — the facade registers its
|
|
31632
|
+
// restore hook on the session itself (FacadeSession.subscribeEpochChange).
|
|
31633
|
+
syncEpoch: () => transport.session.currentEpoch()
|
|
31183
31634
|
};
|
|
31184
31635
|
return new PaymentsFacade(deps);
|
|
31185
31636
|
}
|
|
@@ -31509,18 +31960,20 @@ var Sphere = class _Sphere {
|
|
|
31509
31960
|
}
|
|
31510
31961
|
/**
|
|
31511
31962
|
* P9: validate + resolve the paymentsV2 opt-in BEFORE any storage writes —
|
|
31512
|
-
* fail-closed at init, never a silently-degraded composition.
|
|
31963
|
+
* fail-closed at init, never a silently-degraded composition. Returns the
|
|
31964
|
+
* resolved composition (null = flag off); #728: resolving here also asserts
|
|
31965
|
+
* the single-network invariant (walletApi.network known + equal to the
|
|
31966
|
+
* Sphere network) before any session/KV/provider exists.
|
|
31513
31967
|
*/
|
|
31514
|
-
static resolvePaymentsV2Flag(flag, accountingConfig, swapConfig, walletApi) {
|
|
31515
|
-
if (flag !== true) return
|
|
31968
|
+
static resolvePaymentsV2Flag(flag, accountingConfig, swapConfig, walletApi, network) {
|
|
31969
|
+
if (flag !== true) return null;
|
|
31516
31970
|
if (accountingConfig || swapConfig) {
|
|
31517
31971
|
throw new SphereError(
|
|
31518
31972
|
"paymentsV2 is incompatible with the accounting/swap modules (both are built on the legacy PaymentsModule and are deleted with the P11 flip) \u2014 disable them or the flag.",
|
|
31519
31973
|
"INVALID_CONFIG"
|
|
31520
31974
|
);
|
|
31521
31975
|
}
|
|
31522
|
-
resolvePaymentsV2Composition(walletApi);
|
|
31523
|
-
return true;
|
|
31976
|
+
return resolvePaymentsV2Composition(walletApi, network);
|
|
31524
31977
|
}
|
|
31525
31978
|
/**
|
|
31526
31979
|
* Configure TokenRegistry in the main bundle context.
|
|
@@ -31575,12 +32028,14 @@ var Sphere = class _Sphere {
|
|
|
31575
32028
|
options.walletApi
|
|
31576
32029
|
);
|
|
31577
32030
|
sphere._verification = options.verification;
|
|
31578
|
-
sphere.
|
|
32031
|
+
sphere._paymentsV2Composition = _Sphere.resolvePaymentsV2Flag(
|
|
31579
32032
|
options.paymentsV2,
|
|
31580
32033
|
accountingConfig,
|
|
31581
32034
|
swapConfig,
|
|
31582
|
-
options.walletApi
|
|
32035
|
+
options.walletApi,
|
|
32036
|
+
options.network
|
|
31583
32037
|
);
|
|
32038
|
+
sphere._paymentsV2Enabled = sphere._paymentsV2Composition !== null;
|
|
31584
32039
|
sphere._password = options.password ?? null;
|
|
31585
32040
|
sphere._passwordProtected = sphere._password !== null;
|
|
31586
32041
|
progress?.({ step: "storing_keys", message: "Storing wallet keys..." });
|
|
@@ -31647,12 +32102,14 @@ var Sphere = class _Sphere {
|
|
|
31647
32102
|
options.walletApi
|
|
31648
32103
|
);
|
|
31649
32104
|
sphere._verification = options.verification;
|
|
31650
|
-
sphere.
|
|
32105
|
+
sphere._paymentsV2Composition = _Sphere.resolvePaymentsV2Flag(
|
|
31651
32106
|
options.paymentsV2,
|
|
31652
32107
|
accountingConfig,
|
|
31653
32108
|
swapConfig,
|
|
31654
|
-
options.walletApi
|
|
32109
|
+
options.walletApi,
|
|
32110
|
+
options.network
|
|
31655
32111
|
);
|
|
32112
|
+
sphere._paymentsV2Enabled = sphere._paymentsV2Composition !== null;
|
|
31656
32113
|
sphere._password = options.password ?? null;
|
|
31657
32114
|
sphere._passwordProtected = sphere._password !== null;
|
|
31658
32115
|
if (!options.storage.isConnected()) {
|
|
@@ -31726,12 +32183,14 @@ var Sphere = class _Sphere {
|
|
|
31726
32183
|
options.walletApi
|
|
31727
32184
|
);
|
|
31728
32185
|
sphere._verification = options.verification;
|
|
31729
|
-
sphere.
|
|
32186
|
+
sphere._paymentsV2Composition = _Sphere.resolvePaymentsV2Flag(
|
|
31730
32187
|
options.paymentsV2,
|
|
31731
32188
|
accountingConfig,
|
|
31732
32189
|
swapConfig,
|
|
31733
|
-
options.walletApi
|
|
32190
|
+
options.walletApi,
|
|
32191
|
+
options.network
|
|
31734
32192
|
);
|
|
32193
|
+
sphere._paymentsV2Enabled = sphere._paymentsV2Composition !== null;
|
|
31735
32194
|
sphere._password = options.password ?? null;
|
|
31736
32195
|
sphere._passwordProtected = sphere._password !== null;
|
|
31737
32196
|
progress?.({ step: "storing_keys", message: "Storing wallet keys..." });
|
|
@@ -34389,7 +34848,9 @@ var Sphere = class _Sphere {
|
|
|
34389
34848
|
* setOracleApiKey rebuild is what future operations snapshot.
|
|
34390
34849
|
*/
|
|
34391
34850
|
async startPaymentsV2Inner(index, identity) {
|
|
34392
|
-
this._paymentsV2Composition
|
|
34851
|
+
if (this._paymentsV2Composition === null) {
|
|
34852
|
+
throw new SphereError("paymentsV2: composition unresolved \u2014 init never ran the flag gate", "INVALID_CONFIG");
|
|
34853
|
+
}
|
|
34393
34854
|
if (!this._addressModules.get(index)?.tokenEngine) {
|
|
34394
34855
|
throw new SphereError(
|
|
34395
34856
|
"paymentsV2 requires the v2 token engine \u2014 the oracle must supply a trust base + gateway URL (and API key where required).",
|