@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.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
|
|
@@ -27643,6 +27647,10 @@ function isValidationReject(err) {
|
|
|
27643
27647
|
if (e === null) return false;
|
|
27644
27648
|
return e.status === 422 || e.statusCode === 422 || e.code === "VALIDATION" || e.code === "VALIDATION_FAILED";
|
|
27645
27649
|
}
|
|
27650
|
+
function retryAfterMsOf(err) {
|
|
27651
|
+
const retryAfter = err !== null && typeof err === "object" ? err.retryAfter : void 0;
|
|
27652
|
+
return typeof retryAfter === "number" && Number.isFinite(retryAfter) && retryAfter >= 0 ? retryAfter : null;
|
|
27653
|
+
}
|
|
27646
27654
|
var chainRegistry = /* @__PURE__ */ new WeakMap();
|
|
27647
27655
|
var replayRegistry = /* @__PURE__ */ new WeakMap();
|
|
27648
27656
|
function registryFor(registry, kv, storeKey, make) {
|
|
@@ -27866,7 +27874,7 @@ var TransferMachine = class {
|
|
|
27866
27874
|
}
|
|
27867
27875
|
if (cls === null) {
|
|
27868
27876
|
await this.applyCommitted(engine, plan.transferId, committed);
|
|
27869
|
-
this.closeTail(plan.transferId, plan.payload);
|
|
27877
|
+
this.closeTail(engine, plan.transferId, plan.payload, committed);
|
|
27870
27878
|
return { committed, deliveryPending };
|
|
27871
27879
|
}
|
|
27872
27880
|
if (cls === "conflict" && committed.length > 0) {
|
|
@@ -27892,10 +27900,10 @@ var TransferMachine = class {
|
|
|
27892
27900
|
}
|
|
27893
27901
|
await this.applyCommitted(engine, r.transferId, committed);
|
|
27894
27902
|
if (conflicts.length > 0) {
|
|
27895
|
-
return { deliveryPending, partial: await this.settlePartial(r, committed, conflicts) };
|
|
27903
|
+
return { deliveryPending, partial: await this.settlePartial(engine, r, committed, conflicts) };
|
|
27896
27904
|
}
|
|
27897
27905
|
await this.completeIntent(r.transferId, r.payload);
|
|
27898
|
-
await this.finishBookkeeping(r.transferId, r.payload, "resumed");
|
|
27906
|
+
await this.finishBookkeeping(engine, r.transferId, r.payload, "resumed", committed);
|
|
27899
27907
|
return { deliveryPending, partial: null };
|
|
27900
27908
|
}
|
|
27901
27909
|
// ── shared transitions ─────────────────────────────────────────────────────
|
|
@@ -28077,7 +28085,7 @@ var TransferMachine = class {
|
|
|
28077
28085
|
}
|
|
28078
28086
|
return added;
|
|
28079
28087
|
}
|
|
28080
|
-
async settlePartial(r, committed, conflicts) {
|
|
28088
|
+
async settlePartial(engine, r, committed, conflicts) {
|
|
28081
28089
|
const undelivered = conflicts.reduce((sum, c) => sum + c.amount, 0n);
|
|
28082
28090
|
const entry = {
|
|
28083
28091
|
transferId: r.transferId,
|
|
@@ -28089,26 +28097,27 @@ var TransferMachine = class {
|
|
|
28089
28097
|
};
|
|
28090
28098
|
await this.stores.shortfalls.upsert(entry);
|
|
28091
28099
|
await this.completeIntent(r.transferId, r.payload);
|
|
28092
|
-
await this.finishBookkeeping(r.transferId, r.payload, "resumed");
|
|
28100
|
+
await this.finishBookkeeping(engine, r.transferId, r.payload, "resumed", committed);
|
|
28093
28101
|
return entry;
|
|
28094
28102
|
}
|
|
28095
|
-
closeTail(transferId, payload) {
|
|
28103
|
+
closeTail(engine, transferId, payload, committed) {
|
|
28096
28104
|
void (async () => {
|
|
28097
28105
|
try {
|
|
28098
28106
|
await this.completeIntent(transferId, payload);
|
|
28099
28107
|
} catch {
|
|
28100
28108
|
return;
|
|
28101
28109
|
}
|
|
28102
|
-
await this.finishBookkeeping(transferId, payload, "sent");
|
|
28110
|
+
await this.finishBookkeeping(engine, transferId, payload, "sent", committed);
|
|
28103
28111
|
})();
|
|
28104
28112
|
}
|
|
28105
28113
|
async completeIntent(transferId, payload) {
|
|
28106
28114
|
const signature = payload.split !== void 0 ? await this.deps.signComplete(transferId) : void 0;
|
|
28107
28115
|
await this.deps.intents.complete(transferId, signature);
|
|
28108
28116
|
}
|
|
28109
|
-
async finishBookkeeping(transferId, payload, phase) {
|
|
28117
|
+
async finishBookkeeping(engine, transferId, payload, phase, committed) {
|
|
28110
28118
|
try {
|
|
28111
|
-
await this.
|
|
28119
|
+
const committedAmount = await this.settledAmount(engine, payload.coinId, committed);
|
|
28120
|
+
await this.deps.recordHistory?.({ transferId, payload, phase, committedAmount });
|
|
28112
28121
|
} catch {
|
|
28113
28122
|
}
|
|
28114
28123
|
try {
|
|
@@ -28116,6 +28125,26 @@ var TransferMachine = class {
|
|
|
28116
28125
|
} catch {
|
|
28117
28126
|
}
|
|
28118
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
|
+
}
|
|
28119
28148
|
async disposeOnFailure(transferId, committed, cls) {
|
|
28120
28149
|
if (committed.length > 0 || cls === "keep-open") return;
|
|
28121
28150
|
try {
|
|
@@ -28140,7 +28169,7 @@ function conflictAmount(engine, payload, op, source) {
|
|
|
28140
28169
|
}
|
|
28141
28170
|
|
|
28142
28171
|
// modules/payments-v2/machine/resume.ts
|
|
28143
|
-
async function resumeAll(deps) {
|
|
28172
|
+
async function resumeAll(deps, options = {}) {
|
|
28144
28173
|
const report = { resumed: [], conflicted: [], failed: [] };
|
|
28145
28174
|
const stores = createMachineStores(deps.kv);
|
|
28146
28175
|
const open = await deps.intents.listOpen();
|
|
@@ -28150,7 +28179,12 @@ async function resumeAll(deps) {
|
|
|
28150
28179
|
} catch {
|
|
28151
28180
|
return report;
|
|
28152
28181
|
}
|
|
28153
|
-
await
|
|
28182
|
+
await dropStaleBackstops(
|
|
28183
|
+
stores,
|
|
28184
|
+
locals,
|
|
28185
|
+
new Set(open.map((i) => i.transferId)),
|
|
28186
|
+
options.isLocallyActive ?? (() => false)
|
|
28187
|
+
);
|
|
28154
28188
|
const jobs = await gateAndDecode(deps, stores, open, locals, report);
|
|
28155
28189
|
const ctx = {
|
|
28156
28190
|
deps,
|
|
@@ -28163,11 +28197,16 @@ async function resumeAll(deps) {
|
|
|
28163
28197
|
}
|
|
28164
28198
|
return report;
|
|
28165
28199
|
}
|
|
28166
|
-
async function
|
|
28200
|
+
async function dropStaleBackstops(stores, locals, openIds, isLocallyActive) {
|
|
28167
28201
|
for (const entry of locals.values()) {
|
|
28168
|
-
if (
|
|
28202
|
+
if (openIds.has(entry.transferId)) continue;
|
|
28203
|
+
if (entry.disposition !== "open") {
|
|
28169
28204
|
await stores.backstop.removeByKey(entry.transferId);
|
|
28205
|
+
continue;
|
|
28170
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);
|
|
28171
28210
|
}
|
|
28172
28211
|
}
|
|
28173
28212
|
async function gateAndDecode(deps, stores, open, locals, report) {
|
|
@@ -28280,6 +28319,349 @@ function validatePayload(raw) {
|
|
|
28280
28319
|
return p;
|
|
28281
28320
|
}
|
|
28282
28321
|
|
|
28322
|
+
// modules/payments-v2/convergence.ts
|
|
28323
|
+
var HEARTBEAT_SEED_MS = 5e3;
|
|
28324
|
+
var HEARTBEAT_CAP_MS = 12e4;
|
|
28325
|
+
var ConvergenceHeartbeat = class {
|
|
28326
|
+
constructor(deps) {
|
|
28327
|
+
this.deps = deps;
|
|
28328
|
+
}
|
|
28329
|
+
timer = null;
|
|
28330
|
+
delayMs = HEARTBEAT_SEED_MS;
|
|
28331
|
+
notBefore = 0;
|
|
28332
|
+
running = false;
|
|
28333
|
+
start() {
|
|
28334
|
+
this.running = true;
|
|
28335
|
+
this.delayMs = HEARTBEAT_SEED_MS;
|
|
28336
|
+
this.notBefore = 0;
|
|
28337
|
+
}
|
|
28338
|
+
/** §7 quiescence unchanged: a mid-backoff stop() cancels cleanly. */
|
|
28339
|
+
stop() {
|
|
28340
|
+
this.running = false;
|
|
28341
|
+
this.clearTimer();
|
|
28342
|
+
}
|
|
28343
|
+
/** Reset to seed on progress, ×2 to the cap otherwise; idle stops rescheduling. */
|
|
28344
|
+
settle(outcome, pending) {
|
|
28345
|
+
if (outcome.retryAfterMs !== null) {
|
|
28346
|
+
this.notBefore = Math.max(this.notBefore, this.deps.now() + outcome.retryAfterMs);
|
|
28347
|
+
}
|
|
28348
|
+
if (!pending) {
|
|
28349
|
+
this.delayMs = HEARTBEAT_SEED_MS;
|
|
28350
|
+
return;
|
|
28351
|
+
}
|
|
28352
|
+
const delay = outcome.progress ? HEARTBEAT_SEED_MS : this.delayMs;
|
|
28353
|
+
this.delayMs = Math.min(delay * 2, HEARTBEAT_CAP_MS);
|
|
28354
|
+
this.schedule(delay);
|
|
28355
|
+
}
|
|
28356
|
+
/** New pending work (keep-open park / journaled delivery / mint hold): prompt retry. */
|
|
28357
|
+
arm() {
|
|
28358
|
+
if (!this.running) return;
|
|
28359
|
+
this.delayMs = Math.min(HEARTBEAT_SEED_MS * 2, HEARTBEAT_CAP_MS);
|
|
28360
|
+
this.schedule(HEARTBEAT_SEED_MS);
|
|
28361
|
+
}
|
|
28362
|
+
/** connection:status recovery: the backoff resets; a pending tick re-fires at the seed. */
|
|
28363
|
+
recovered() {
|
|
28364
|
+
this.delayMs = HEARTBEAT_SEED_MS;
|
|
28365
|
+
if (this.timer !== null) this.arm();
|
|
28366
|
+
}
|
|
28367
|
+
schedule(delayMs) {
|
|
28368
|
+
this.clearTimer();
|
|
28369
|
+
if (!this.running) return;
|
|
28370
|
+
const floored = Math.max(delayMs, this.notBefore - this.deps.now());
|
|
28371
|
+
this.timer = setTimeout(() => {
|
|
28372
|
+
this.timer = null;
|
|
28373
|
+
this.deps.onTick();
|
|
28374
|
+
}, floored);
|
|
28375
|
+
}
|
|
28376
|
+
clearTimer() {
|
|
28377
|
+
if (this.timer !== null) clearTimeout(this.timer);
|
|
28378
|
+
this.timer = null;
|
|
28379
|
+
}
|
|
28380
|
+
};
|
|
28381
|
+
var Converger = class {
|
|
28382
|
+
constructor(deps) {
|
|
28383
|
+
this.deps = deps;
|
|
28384
|
+
}
|
|
28385
|
+
/** Resume open intents, then drain the delivery + mint journals (all idempotent). */
|
|
28386
|
+
async convergeOnce() {
|
|
28387
|
+
let progress = false;
|
|
28388
|
+
let retryAfterMs = null;
|
|
28389
|
+
try {
|
|
28390
|
+
progress = await this.resumePass();
|
|
28391
|
+
} catch (err) {
|
|
28392
|
+
retryAfterMs = retryAfterMsOf(err);
|
|
28393
|
+
}
|
|
28394
|
+
try {
|
|
28395
|
+
const stats = await this.deps.stores.deliveryJournal.replay(this.replayDeps());
|
|
28396
|
+
progress = progress || stats.delivered > 0;
|
|
28397
|
+
} catch (err) {
|
|
28398
|
+
retryAfterMs ??= retryAfterMsOf(err);
|
|
28399
|
+
}
|
|
28400
|
+
try {
|
|
28401
|
+
progress = await this.deps.replayMints() > 0 || progress;
|
|
28402
|
+
} catch (err) {
|
|
28403
|
+
retryAfterMs ??= retryAfterMsOf(err);
|
|
28404
|
+
}
|
|
28405
|
+
return { progress, retryAfterMs };
|
|
28406
|
+
}
|
|
28407
|
+
/** §4 heartbeat predicate — read fresh from the §6 stores, never cached. */
|
|
28408
|
+
async pendingWork() {
|
|
28409
|
+
const [backstop, journal, mints] = await Promise.all([
|
|
28410
|
+
this.deps.stores.backstop.list(),
|
|
28411
|
+
this.deps.stores.deliveryJournal.list(),
|
|
28412
|
+
this.deps.stores.mintJournal.list()
|
|
28413
|
+
]);
|
|
28414
|
+
return backstop.length > 0 || journal.some((e) => e.undeliverable !== true) || mints.length > 0;
|
|
28415
|
+
}
|
|
28416
|
+
async resumePass() {
|
|
28417
|
+
const report = await resumeAll(this.filteredMachineDeps(), {
|
|
28418
|
+
isLocallyActive: (id) => this.deps.isActiveOp(id)
|
|
28419
|
+
});
|
|
28420
|
+
const progress = report.resumed.length > 0 || report.conflicted.length > 0;
|
|
28421
|
+
await this.deps.reconcile({
|
|
28422
|
+
resumed: report.resumed,
|
|
28423
|
+
conflicted: report.conflicted,
|
|
28424
|
+
open: report.failed
|
|
28425
|
+
});
|
|
28426
|
+
if (progress) await this.deps.refreshView().catch(() => void 0);
|
|
28427
|
+
return progress;
|
|
28428
|
+
}
|
|
28429
|
+
filteredMachineDeps() {
|
|
28430
|
+
const intents = this.deps.machineDeps.intents;
|
|
28431
|
+
return {
|
|
28432
|
+
...this.deps.machineDeps,
|
|
28433
|
+
intents: {
|
|
28434
|
+
...intents,
|
|
28435
|
+
listOpen: async () => (await intents.listOpen()).filter((intent) => !this.deps.isActiveOp(intent.transferId))
|
|
28436
|
+
}
|
|
28437
|
+
};
|
|
28438
|
+
}
|
|
28439
|
+
replayDeps() {
|
|
28440
|
+
return {
|
|
28441
|
+
delivery: this.deps.delivery,
|
|
28442
|
+
now: () => this.deps.now(),
|
|
28443
|
+
attention: (transferId, code, detail) => {
|
|
28444
|
+
this.deps.emit(
|
|
28445
|
+
"transfer:attention",
|
|
28446
|
+
detail === void 0 ? { transferId, code } : { transferId, code, detail }
|
|
28447
|
+
);
|
|
28448
|
+
}
|
|
28449
|
+
};
|
|
28450
|
+
}
|
|
28451
|
+
};
|
|
28452
|
+
async function derivePendingTransfers(stores, decryptPayload) {
|
|
28453
|
+
const [backstop, journal, shortfalls] = await Promise.all([
|
|
28454
|
+
stores.backstop.list(),
|
|
28455
|
+
stores.deliveryJournal.list(),
|
|
28456
|
+
stores.shortfalls.list()
|
|
28457
|
+
]);
|
|
28458
|
+
const byTransfer = groupJournal(journal);
|
|
28459
|
+
const out = [];
|
|
28460
|
+
const seen = /* @__PURE__ */ new Set();
|
|
28461
|
+
for (const entry of backstop) {
|
|
28462
|
+
if (entry.disposition !== "open") continue;
|
|
28463
|
+
seen.add(entry.transferId);
|
|
28464
|
+
out.push(await openRow(entry, byTransfer.get(entry.transferId) ?? [], decryptPayload));
|
|
28465
|
+
}
|
|
28466
|
+
for (const [transferId, entries] of byTransfer) {
|
|
28467
|
+
if (seen.has(transferId)) continue;
|
|
28468
|
+
seen.add(transferId);
|
|
28469
|
+
out.push(journalOnlyRow(transferId, entries));
|
|
28470
|
+
}
|
|
28471
|
+
for (const entry of shortfalls) {
|
|
28472
|
+
if (!seen.has(entry.transferId)) out.push(shortfallRow(entry));
|
|
28473
|
+
}
|
|
28474
|
+
return out;
|
|
28475
|
+
}
|
|
28476
|
+
function groupJournal(journal) {
|
|
28477
|
+
const byTransfer = /* @__PURE__ */ new Map();
|
|
28478
|
+
for (const entry of journal) {
|
|
28479
|
+
const mine = byTransfer.get(entry.transferId);
|
|
28480
|
+
if (mine === void 0) byTransfer.set(entry.transferId, [entry]);
|
|
28481
|
+
else mine.push(entry);
|
|
28482
|
+
}
|
|
28483
|
+
return byTransfer;
|
|
28484
|
+
}
|
|
28485
|
+
async function openRow(entry, journal, decryptPayload) {
|
|
28486
|
+
let payload = null;
|
|
28487
|
+
try {
|
|
28488
|
+
const raw = await decryptPayload(entry.payloadEnvelope);
|
|
28489
|
+
if (raw !== null && typeof raw === "object") payload = raw;
|
|
28490
|
+
} catch {
|
|
28491
|
+
payload = null;
|
|
28492
|
+
}
|
|
28493
|
+
const total = Array.isArray(payload?.direct) ? payload.direct.length + (payload.split !== void 0 ? 1 : 0) : 0;
|
|
28494
|
+
const certified = distinctOpCount(journal);
|
|
28495
|
+
return {
|
|
28496
|
+
transferId: entry.transferId,
|
|
28497
|
+
kind: "open",
|
|
28498
|
+
recipient: typeof payload?.recipient === "string" ? payload.recipient : "",
|
|
28499
|
+
coinId: typeof payload?.coinId === "string" ? payload.coinId : "",
|
|
28500
|
+
amount: typeof payload?.amount === "string" ? payload.amount : "",
|
|
28501
|
+
legs: { certified, total: Math.max(total, certified) },
|
|
28502
|
+
deliveryPending: journal.length > 0,
|
|
28503
|
+
createdAt: entry.createdAt
|
|
28504
|
+
};
|
|
28505
|
+
}
|
|
28506
|
+
function journalOnlyRow(transferId, entries) {
|
|
28507
|
+
const certified = distinctOpCount(entries);
|
|
28508
|
+
return {
|
|
28509
|
+
transferId,
|
|
28510
|
+
kind: "open",
|
|
28511
|
+
recipient: entries[0].recipientPubkey,
|
|
28512
|
+
coinId: "",
|
|
28513
|
+
amount: "",
|
|
28514
|
+
legs: { certified, total: certified },
|
|
28515
|
+
deliveryPending: true,
|
|
28516
|
+
createdAt: 0
|
|
28517
|
+
};
|
|
28518
|
+
}
|
|
28519
|
+
function shortfallRow(entry) {
|
|
28520
|
+
return {
|
|
28521
|
+
transferId: entry.transferId,
|
|
28522
|
+
kind: "shortfall",
|
|
28523
|
+
recipient: entry.recipient,
|
|
28524
|
+
coinId: entry.coinId,
|
|
28525
|
+
amount: entry.remainingAmount,
|
|
28526
|
+
legs: { certified: entry.committedTokenIds.length, total: entry.committedTokenIds.length },
|
|
28527
|
+
deliveryPending: false,
|
|
28528
|
+
createdAt: entry.createdAt
|
|
28529
|
+
};
|
|
28530
|
+
}
|
|
28531
|
+
function distinctOpCount(entries) {
|
|
28532
|
+
return new Set(entries.map((entry) => entry.opIndex)).size;
|
|
28533
|
+
}
|
|
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
|
+
|
|
28283
28665
|
// modules/payments-v2/history/History.ts
|
|
28284
28666
|
var WIRE_TOKEN_ID = /^(?:[0-9a-f]{2}){1,64}$/;
|
|
28285
28667
|
var WIRE_PUBKEY = /^0[23][0-9a-f]{64}$/;
|
|
@@ -28338,13 +28720,14 @@ var History = class {
|
|
|
28338
28720
|
}
|
|
28339
28721
|
// ── internals ────────────────────────────────────────────────────────────────
|
|
28340
28722
|
async post(build) {
|
|
28723
|
+
const record = build();
|
|
28341
28724
|
try {
|
|
28342
|
-
await this.deps.client.postHistory([
|
|
28725
|
+
await this.deps.client.postHistory([record]);
|
|
28343
28726
|
} catch (err) {
|
|
28344
28727
|
this.deps.log?.("history POST failed (dedupKey makes retry safe)", err);
|
|
28345
28728
|
return;
|
|
28346
28729
|
}
|
|
28347
|
-
this.deps.emit("history:updated");
|
|
28730
|
+
this.deps.emit("history:updated", this.toEntry(record));
|
|
28348
28731
|
}
|
|
28349
28732
|
ts(timestamp) {
|
|
28350
28733
|
return new Date(timestamp ?? (this.deps.now ?? Date.now)()).toISOString();
|
|
@@ -28401,93 +28784,9 @@ var History = class {
|
|
|
28401
28784
|
}
|
|
28402
28785
|
};
|
|
28403
28786
|
|
|
28404
|
-
// modules/payments-v2/inventory/presentation.ts
|
|
28405
|
-
function toToken(tokenId, snapshot, asset, registry, flags) {
|
|
28406
|
-
const iconUrl = registry.getIconUrl(asset.coinId);
|
|
28407
|
-
return {
|
|
28408
|
-
id: tokenId,
|
|
28409
|
-
coinId: asset.coinId,
|
|
28410
|
-
symbol: registry.getSymbol(asset.coinId),
|
|
28411
|
-
name: registry.getName(asset.coinId),
|
|
28412
|
-
decimals: registry.getDecimals(asset.coinId),
|
|
28413
|
-
...iconUrl !== null ? { iconUrl } : {},
|
|
28414
|
-
amount: asset.amount,
|
|
28415
|
-
status: flags.transferring ? "transferring" : "confirmed",
|
|
28416
|
-
createdAt: snapshot.createdAt,
|
|
28417
|
-
updatedAt: snapshot.updatedAt,
|
|
28418
|
-
lazy: true,
|
|
28419
|
-
...flags.suspectedSpent ? { suspectedSpent: true } : {}
|
|
28420
|
-
};
|
|
28421
|
-
}
|
|
28422
|
-
function aggregateAssets(entries, isTransferring, registry) {
|
|
28423
|
-
const groups = /* @__PURE__ */ new Map();
|
|
28424
|
-
for (const [tokenId, entry] of entries) {
|
|
28425
|
-
const moving = isTransferring(tokenId);
|
|
28426
|
-
for (const asset of entry.assets) {
|
|
28427
|
-
let group = groups.get(asset.coinId);
|
|
28428
|
-
if (!group) {
|
|
28429
|
-
group = { total: 0n, count: 0, transferring: 0n, transferringCount: 0 };
|
|
28430
|
-
groups.set(asset.coinId, group);
|
|
28431
|
-
}
|
|
28432
|
-
if (moving) {
|
|
28433
|
-
group.transferring += BigInt(asset.amount);
|
|
28434
|
-
group.transferringCount += 1;
|
|
28435
|
-
} else {
|
|
28436
|
-
group.total += BigInt(asset.amount);
|
|
28437
|
-
group.count += 1;
|
|
28438
|
-
}
|
|
28439
|
-
}
|
|
28440
|
-
}
|
|
28441
|
-
return [...groups].map(([coinId, totals]) => toAsset(coinId, totals, registry));
|
|
28442
|
-
}
|
|
28443
|
-
function toAsset(coinId, totals, registry) {
|
|
28444
|
-
const iconUrl = registry.getIconUrl(coinId);
|
|
28445
|
-
return {
|
|
28446
|
-
coinId,
|
|
28447
|
-
symbol: registry.getSymbol(coinId),
|
|
28448
|
-
name: registry.getName(coinId),
|
|
28449
|
-
decimals: registry.getDecimals(coinId),
|
|
28450
|
-
...iconUrl !== null ? { iconUrl } : {},
|
|
28451
|
-
totalAmount: totals.total.toString(),
|
|
28452
|
-
tokenCount: totals.count,
|
|
28453
|
-
confirmedAmount: totals.total.toString(),
|
|
28454
|
-
unconfirmedAmount: "0",
|
|
28455
|
-
confirmedTokenCount: totals.count,
|
|
28456
|
-
unconfirmedTokenCount: 0,
|
|
28457
|
-
transferringTokenCount: totals.transferringCount,
|
|
28458
|
-
transferringAmount: totals.transferring.toString(),
|
|
28459
|
-
priceUsd: null,
|
|
28460
|
-
priceEur: null,
|
|
28461
|
-
change24h: null,
|
|
28462
|
-
fiatValueUsd: null,
|
|
28463
|
-
fiatValueEur: null
|
|
28464
|
-
};
|
|
28465
|
-
}
|
|
28466
|
-
async function withPrices(raw, registry, price) {
|
|
28467
|
-
try {
|
|
28468
|
-
const names = [...new Set(raw.map((a) => registry.getName(a.coinId)))];
|
|
28469
|
-
const quotes = await price.getPrices(names);
|
|
28470
|
-
return raw.map((a) => priceOne(a, quotes.get(registry.getName(a.coinId))));
|
|
28471
|
-
} catch {
|
|
28472
|
-
return raw;
|
|
28473
|
-
}
|
|
28474
|
-
}
|
|
28475
|
-
function priceOne(asset, quote) {
|
|
28476
|
-
if (!quote) return asset;
|
|
28477
|
-
const human = Number(asset.totalAmount) / Math.pow(10, asset.decimals);
|
|
28478
|
-
return {
|
|
28479
|
-
...asset,
|
|
28480
|
-
priceUsd: quote.priceUsd,
|
|
28481
|
-
priceEur: quote.priceEur ?? null,
|
|
28482
|
-
change24h: quote.change24h ?? null,
|
|
28483
|
-
fiatValueUsd: human * quote.priceUsd,
|
|
28484
|
-
fiatValueEur: quote.priceEur != null ? human * quote.priceEur : null
|
|
28485
|
-
};
|
|
28486
|
-
}
|
|
28487
|
-
|
|
28488
28787
|
// modules/payments-v2/inventory/InventoryView.ts
|
|
28489
|
-
var SUSPECTED_KEY =
|
|
28490
|
-
var KNOWN_SPENDS_KEY =
|
|
28788
|
+
var SUSPECTED_KEY = STORE_KEYS.suspectedSpent;
|
|
28789
|
+
var KNOWN_SPENDS_KEY = STORE_KEYS.knownSpends;
|
|
28491
28790
|
var CURSOR_KEY = STORE_KEYS.streamCursor("inventory");
|
|
28492
28791
|
function stateKey(tokenId, stateHash) {
|
|
28493
28792
|
return `${tokenId}:${stateHash}`;
|
|
@@ -28791,19 +29090,24 @@ var Receive = class {
|
|
|
28791
29090
|
const engine = deps.engine();
|
|
28792
29091
|
const stored = [];
|
|
28793
29092
|
const pending = [];
|
|
28794
|
-
|
|
29093
|
+
const pageEpoch = () => deps.delivery.incomingEpoch() ?? deps.syncEpoch();
|
|
28795
29094
|
try {
|
|
28796
|
-
epoch = deps.syncEpoch();
|
|
28797
29095
|
const record = await deps.kv.get(STORE_KEYS.streamCursor("mailbox"));
|
|
28798
|
-
|
|
28799
|
-
for
|
|
28800
|
-
await
|
|
28801
|
-
|
|
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;
|
|
28802
29107
|
}
|
|
28803
|
-
await flushAcks(deps, pending, epoch);
|
|
28804
29108
|
} catch (err) {
|
|
28805
29109
|
logger.warn("PaymentsV2", "receive drain interrupted \u2014 unacked entries retry next drain:", err);
|
|
28806
|
-
await flushAcks(deps, pending,
|
|
29110
|
+
await flushAcks(deps, pending, pageEpoch).catch((flushErr) => {
|
|
28807
29111
|
logger.warn("PaymentsV2", "receive ack flush failed \u2014 cursor holds at the acked prefix:", flushErr);
|
|
28808
29112
|
});
|
|
28809
29113
|
}
|
|
@@ -28876,7 +29180,7 @@ async function announce(deps, entry, record) {
|
|
|
28876
29180
|
receivedAt
|
|
28877
29181
|
};
|
|
28878
29182
|
}
|
|
28879
|
-
async function flushAcks(deps, pending,
|
|
29183
|
+
async function flushAcks(deps, pending, epochOf) {
|
|
28880
29184
|
let lastAcked = null;
|
|
28881
29185
|
try {
|
|
28882
29186
|
while (pending.length > 0) {
|
|
@@ -28887,7 +29191,7 @@ async function flushAcks(deps, pending, epoch) {
|
|
|
28887
29191
|
}
|
|
28888
29192
|
} finally {
|
|
28889
29193
|
if (lastAcked !== null) {
|
|
28890
|
-
const record = { cursor: lastAcked, syncEpoch:
|
|
29194
|
+
const record = { cursor: lastAcked, syncEpoch: epochOf() };
|
|
28891
29195
|
await deps.kv.set(STORE_KEYS.streamCursor("mailbox"), record);
|
|
28892
29196
|
}
|
|
28893
29197
|
}
|
|
@@ -28988,9 +29292,19 @@ var Requests = class {
|
|
|
28988
29292
|
}
|
|
28989
29293
|
});
|
|
28990
29294
|
}
|
|
29295
|
+
/** Idempotent for a same-transferId re-write: committed only ratchets up, createdAt kept. */
|
|
28991
29296
|
writeLink(requestId, transferId, committed) {
|
|
28992
29297
|
return this.mutateJournal((m) => {
|
|
28993
|
-
|
|
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);
|
|
28994
29308
|
return true;
|
|
28995
29309
|
});
|
|
28996
29310
|
}
|
|
@@ -29143,23 +29457,46 @@ var Requests = class {
|
|
|
29143
29457
|
const transferId = error instanceof SphereError && error.transferId !== void 0 ? error.transferId : void 0;
|
|
29144
29458
|
if (transferId !== void 0) {
|
|
29145
29459
|
const committed = error instanceof PartialSendConflictError;
|
|
29146
|
-
await this.
|
|
29147
|
-
this.setStatus(id, "settling");
|
|
29460
|
+
await this.settle(id, transferId, { committed, respond: false });
|
|
29148
29461
|
} else {
|
|
29149
29462
|
logger.error("PaymentsV2", `possibly-committed send for ${id} carried no transferId \u2014 paid (fail-closed)`);
|
|
29150
29463
|
this.setStatus(id, "paid");
|
|
29151
29464
|
}
|
|
29152
29465
|
} else {
|
|
29153
|
-
await this.
|
|
29154
|
-
this.setStatus(id, "pending");
|
|
29466
|
+
await this.revertPayable(id);
|
|
29155
29467
|
}
|
|
29156
29468
|
throw error;
|
|
29157
29469
|
}
|
|
29158
|
-
this.
|
|
29159
|
-
await this.respondPaid(id, result.id);
|
|
29160
|
-
await this.clearLink(id);
|
|
29470
|
+
await this.settle(id, result.id, { committed: true, respond: true });
|
|
29161
29471
|
return result;
|
|
29162
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
|
+
}
|
|
29163
29500
|
/** 'paid' respond leg: 409 = already resolved = idempotent success; other errors defer. */
|
|
29164
29501
|
async respondPaid(id, transferId) {
|
|
29165
29502
|
try {
|
|
@@ -29195,32 +29532,32 @@ var Requests = class {
|
|
|
29195
29532
|
let aborted = null;
|
|
29196
29533
|
for (const [requestId, link] of [...this.journal]) {
|
|
29197
29534
|
if (link.committed || resumed.has(link.transferId)) {
|
|
29198
|
-
await this.resolvePaid(requestId, link
|
|
29535
|
+
await this.resolvePaid(requestId, link);
|
|
29199
29536
|
} else if (conflicted.has(link.transferId)) {
|
|
29200
29537
|
await this.revertPayable(requestId);
|
|
29201
29538
|
} else if (open.has(link.transferId)) {
|
|
29202
29539
|
} else {
|
|
29203
|
-
aborted = await this.reconcileUnaccounted(requestId, link
|
|
29540
|
+
aborted = await this.reconcileUnaccounted(requestId, link, aborted);
|
|
29204
29541
|
}
|
|
29205
29542
|
}
|
|
29206
29543
|
}
|
|
29207
29544
|
// Unaccounted link: consult the server aborted authority (lazy, memoized);
|
|
29208
29545
|
// a probe failure defers the link to the next session.
|
|
29209
|
-
async reconcileUnaccounted(requestId,
|
|
29546
|
+
async reconcileUnaccounted(requestId, link, aborted) {
|
|
29210
29547
|
try {
|
|
29211
29548
|
aborted ??= new Set(await this.deps.listAbortedTransferIds());
|
|
29212
|
-
if (aborted.has(transferId)) await this.revertPayable(requestId);
|
|
29213
|
-
else await this.resolvePaid(requestId,
|
|
29549
|
+
if (aborted.has(link.transferId)) await this.revertPayable(requestId);
|
|
29550
|
+
else await this.resolvePaid(requestId, link);
|
|
29214
29551
|
} catch (err) {
|
|
29215
29552
|
logger.warn("PaymentsV2", `aborted-intent probe failed \u2014 ${requestId} deferred:`, err);
|
|
29216
29553
|
}
|
|
29217
29554
|
return aborted;
|
|
29218
29555
|
}
|
|
29219
|
-
|
|
29220
|
-
|
|
29221
|
-
|
|
29222
|
-
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 });
|
|
29223
29559
|
}
|
|
29560
|
+
/** Removal cause (b): a PROVEN clean outcome (pre-commit failure / server-aborted). */
|
|
29224
29561
|
async revertPayable(requestId) {
|
|
29225
29562
|
await this.clearLink(requestId);
|
|
29226
29563
|
this.setStatus(requestId, "pending");
|
|
@@ -29544,11 +29881,12 @@ function composeFacadeParts(deps, hooks) {
|
|
|
29544
29881
|
client: deps.client,
|
|
29545
29882
|
fieldKey: deps.fieldKey,
|
|
29546
29883
|
registry: deps.registry,
|
|
29547
|
-
emit: (event) => deps.emit(event,
|
|
29884
|
+
emit: (event, entry) => deps.emit(event, entry),
|
|
29548
29885
|
...deps.now !== void 0 ? { now: deps.now } : {},
|
|
29549
29886
|
...deps.newId !== void 0 ? { newId: deps.newId } : {}
|
|
29550
29887
|
});
|
|
29551
29888
|
const machineDeps = buildMachineDeps(deps, hooks, historyStore);
|
|
29889
|
+
const machineStores = createMachineStores(deps.kv);
|
|
29552
29890
|
const heldStates = /* @__PURE__ */ new Map();
|
|
29553
29891
|
return {
|
|
29554
29892
|
ownPubkeyBytes,
|
|
@@ -29556,12 +29894,29 @@ function composeFacadeParts(deps, hooks) {
|
|
|
29556
29894
|
ledger,
|
|
29557
29895
|
queue,
|
|
29558
29896
|
historyStore,
|
|
29559
|
-
machineStores
|
|
29897
|
+
machineStores,
|
|
29560
29898
|
machineDeps,
|
|
29561
29899
|
machine: new TransferMachine(machineDeps),
|
|
29562
29900
|
heldStates,
|
|
29563
29901
|
receiveLoop: buildReceive(deps, hooks, historyStore, heldStates),
|
|
29564
|
-
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
|
+
}
|
|
29565
29920
|
};
|
|
29566
29921
|
}
|
|
29567
29922
|
function buildMachineDeps(deps, hooks, historyStore) {
|
|
@@ -29583,11 +29938,13 @@ function buildMachineDeps(deps, hooks, historyStore) {
|
|
|
29583
29938
|
ownPubkey: hexToBytes4(deps.ownPubkey),
|
|
29584
29939
|
emit: deps.emit,
|
|
29585
29940
|
now: deps.now ?? Date.now,
|
|
29586
|
-
recordHistory: async ({ transferId, payload }) => {
|
|
29941
|
+
recordHistory: async ({ transferId, payload, committedAmount }) => {
|
|
29587
29942
|
await historyStore.recordSent({
|
|
29588
29943
|
transferId,
|
|
29589
29944
|
coinId: payload.coinId,
|
|
29590
|
-
|
|
29945
|
+
// §5.9: the SETTLED amount (machine-computed from the certified
|
|
29946
|
+
// recipient blobs), never payload.amount — the plan.
|
|
29947
|
+
amount: committedAmount,
|
|
29591
29948
|
recipientPubkey: payload.recipient,
|
|
29592
29949
|
...payload.memo !== void 0 ? { memo: payload.memo } : {}
|
|
29593
29950
|
});
|
|
@@ -29614,7 +29971,7 @@ function buildReceive(deps, hooks, historyStore, heldStates) {
|
|
|
29614
29971
|
detail === void 0 ? { transferId, code } : { transferId, code, detail }
|
|
29615
29972
|
);
|
|
29616
29973
|
},
|
|
29617
|
-
syncEpoch: deps.syncEpoch
|
|
29974
|
+
syncEpoch: deps.syncEpoch,
|
|
29618
29975
|
...deps.now !== void 0 ? { now: deps.now } : {}
|
|
29619
29976
|
});
|
|
29620
29977
|
}
|
|
@@ -29667,8 +30024,24 @@ var PaymentsFacade = class {
|
|
|
29667
30024
|
this.receiveLoop = parts.receiveLoop;
|
|
29668
30025
|
this.heldStates = parts.heldStates;
|
|
29669
30026
|
this.ownPubkeyBytes = parts.ownPubkeyBytes;
|
|
30027
|
+
this.restoreDeps = parts.restoreDeps;
|
|
29670
30028
|
this.requests = parts.requests;
|
|
29671
30029
|
deps.deliveryPort.bindDeliveryKeys((blob) => this.engine().deliveryKeys(blob));
|
|
30030
|
+
this.heartbeat = new ConvergenceHeartbeat({
|
|
30031
|
+
now: () => this.nowMs(),
|
|
30032
|
+
onTick: () => this.trackTail(this.runConvergencePass())
|
|
30033
|
+
});
|
|
30034
|
+
this.converger = new Converger({
|
|
30035
|
+
machineDeps: this.machineDeps,
|
|
30036
|
+
stores: this.machineStores,
|
|
30037
|
+
delivery: deps.deliveryPort,
|
|
30038
|
+
reconcile: (report) => this.requests.reconcile(report),
|
|
30039
|
+
refreshView: () => this.view.delta(),
|
|
30040
|
+
replayMints: () => this.replayMints(),
|
|
30041
|
+
isActiveOp: (id) => this.activeMoneyOps.has(id),
|
|
30042
|
+
emit: deps.emit,
|
|
30043
|
+
now: () => this.nowMs()
|
|
30044
|
+
});
|
|
29672
30045
|
}
|
|
29673
30046
|
view;
|
|
29674
30047
|
ledger;
|
|
@@ -29680,17 +30053,28 @@ var PaymentsFacade = class {
|
|
|
29680
30053
|
receiveLoop;
|
|
29681
30054
|
heldStates;
|
|
29682
30055
|
ownPubkeyBytes;
|
|
30056
|
+
restoreDeps;
|
|
29683
30057
|
requests;
|
|
29684
30058
|
currentEngine = null;
|
|
29685
30059
|
pendingOps = /* @__PURE__ */ new Set();
|
|
29686
30060
|
unsubscribers = [];
|
|
29687
30061
|
started = false;
|
|
30062
|
+
// §7 heartbeat: in-memory timing cell + pass runner (see convergence.ts).
|
|
30063
|
+
heartbeat;
|
|
30064
|
+
converger;
|
|
30065
|
+
/** Resume single-flight (§7): ticks, start() and resumeNow() coalesce onto ONE pass. */
|
|
30066
|
+
resumeFlight = new SingleFlight();
|
|
30067
|
+
/** §5.1/§7: restore + convergence passes SERIALIZE here. */
|
|
30068
|
+
passChain = new SerialChain();
|
|
30069
|
+
/** §7 ownership: ids with an in-process machine attempt — the pass never adopts one. */
|
|
30070
|
+
activeMoneyOps = /* @__PURE__ */ new Set();
|
|
29688
30071
|
// ── lifecycle ──────────────────────────────────────────────────────────────
|
|
29689
30072
|
async start() {
|
|
29690
30073
|
if (this.started) return;
|
|
29691
30074
|
this.started = true;
|
|
29692
|
-
|
|
30075
|
+
this.heartbeat.start();
|
|
29693
30076
|
this.unsubscribers.push(
|
|
30077
|
+
this.deps.session.subscribeEpochChange((epoch) => this.handleEpochChange(epoch)),
|
|
29694
30078
|
this.deps.session.subscribeStream("mailbox", () => {
|
|
29695
30079
|
this.trackTail(this.receiveLoop.drainOnce());
|
|
29696
30080
|
}),
|
|
@@ -29701,20 +30085,12 @@ var PaymentsFacade = class {
|
|
|
29701
30085
|
this.trackTail(this.requests.drainIncoming());
|
|
29702
30086
|
})
|
|
29703
30087
|
);
|
|
29704
|
-
this.
|
|
29705
|
-
|
|
29706
|
-
|
|
29707
|
-
|
|
29708
|
-
|
|
29709
|
-
|
|
29710
|
-
this.deps.emit(
|
|
29711
|
-
"transfer:attention",
|
|
29712
|
-
detail === void 0 ? { transferId, code } : { transferId, code, detail }
|
|
29713
|
-
);
|
|
29714
|
-
}
|
|
29715
|
-
})
|
|
29716
|
-
);
|
|
29717
|
-
this.trackTail(this.replayMints());
|
|
30088
|
+
const statusUnsub = this.deps.session.subscribeStatus?.((status) => {
|
|
30089
|
+
if (status === "connected") this.heartbeat.recovered();
|
|
30090
|
+
});
|
|
30091
|
+
if (statusUnsub !== void 0) this.unsubscribers.push(statusUnsub);
|
|
30092
|
+
await this.deps.session.start();
|
|
30093
|
+
this.trackTail(this.runConvergencePass());
|
|
29718
30094
|
this.trackTail(this.seedHeldStates());
|
|
29719
30095
|
await this.view.fullPull().catch(() => void 0);
|
|
29720
30096
|
this.receiveLoop.start(this.deps.receivePollMs);
|
|
@@ -29722,6 +30098,7 @@ var PaymentsFacade = class {
|
|
|
29722
30098
|
/** §7 same-address restart gate: resolves only after in-flight ops settle. */
|
|
29723
30099
|
async stop() {
|
|
29724
30100
|
this.started = false;
|
|
30101
|
+
this.heartbeat.stop();
|
|
29725
30102
|
this.receiveLoop.stop();
|
|
29726
30103
|
for (const unsubscribe of this.unsubscribers) unsubscribe();
|
|
29727
30104
|
this.unsubscribers = [];
|
|
@@ -29747,9 +30124,13 @@ var PaymentsFacade = class {
|
|
|
29747
30124
|
history(page) {
|
|
29748
30125
|
return this.historyStore.page(page ?? {});
|
|
29749
30126
|
}
|
|
30127
|
+
/** §4 pending-transfers UI surface — derived on read, never cached (convergence.ts). */
|
|
30128
|
+
pendingTransfers() {
|
|
30129
|
+
return derivePendingTransfers(this.machineStores, this.machineDeps.decryptPayload);
|
|
30130
|
+
}
|
|
29750
30131
|
// ── money movement ─────────────────────────────────────────────────────────
|
|
29751
30132
|
send(request) {
|
|
29752
|
-
return this.track(this.
|
|
30133
|
+
return this.track(this.sendOutcome(request));
|
|
29753
30134
|
}
|
|
29754
30135
|
async receive() {
|
|
29755
30136
|
const transfers = await this.track(this.receiveLoop.drainOnce());
|
|
@@ -29759,9 +30140,32 @@ var PaymentsFacade = class {
|
|
|
29759
30140
|
mint(coinId, amount) {
|
|
29760
30141
|
return this.track(this.mintInner(coinId, amount));
|
|
29761
30142
|
}
|
|
30143
|
+
// §4 retry verb: coalesces onto the running pass — NEVER re-issue send() (#631/#676).
|
|
30144
|
+
async resumeNow() {
|
|
30145
|
+
if (!this.started) return;
|
|
30146
|
+
await this.track(this.runConvergencePass());
|
|
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
|
+
}
|
|
30153
|
+
/** One single-flighted pass + its reschedule: concurrent callers coalesce. */
|
|
30154
|
+
runConvergencePass() {
|
|
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);
|
|
30161
|
+
}
|
|
30162
|
+
nowMs() {
|
|
30163
|
+
return (this.deps.now ?? Date.now)();
|
|
30164
|
+
}
|
|
29762
30165
|
// ── send policy (§5.5 + old-loop parity; the machine stays policy-free) ────
|
|
29763
|
-
|
|
29764
|
-
|
|
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) {
|
|
29765
30169
|
const run = {
|
|
29766
30170
|
amount: request.amount,
|
|
29767
30171
|
delivered: [],
|
|
@@ -29769,12 +30173,36 @@ var PaymentsFacade = class {
|
|
|
29769
30173
|
tokenTransfers: [],
|
|
29770
30174
|
settledShortfalls: [],
|
|
29771
30175
|
firstPartialId: void 0,
|
|
29772
|
-
pendingAny: false
|
|
30176
|
+
pendingAny: false,
|
|
30177
|
+
lastTransferId: ""
|
|
29773
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);
|
|
29774
30202
|
for (let attempt = 0; ; attempt++) {
|
|
29775
30203
|
let ctx;
|
|
29776
30204
|
try {
|
|
29777
|
-
ctx = await this.planAndMaterialize(recipient.chainPubkey, { ...request, amount: run.amount });
|
|
30205
|
+
ctx = await this.planAndMaterialize(recipient.chainPubkey, { ...request, amount: run.amount }, run);
|
|
29778
30206
|
} catch (err) {
|
|
29779
30207
|
throw this.partialize(err, run);
|
|
29780
30208
|
}
|
|
@@ -29808,21 +30236,24 @@ var PaymentsFacade = class {
|
|
|
29808
30236
|
return recipient;
|
|
29809
30237
|
}
|
|
29810
30238
|
async runAttempt(ctx) {
|
|
30239
|
+
this.activeMoneyOps.add(ctx.transferId);
|
|
29811
30240
|
try {
|
|
29812
30241
|
const { deliveryPending } = await this.machine.run(ctx.plan);
|
|
29813
30242
|
this.settleSuccess(ctx, ctx.sourceIds);
|
|
29814
30243
|
return { kind: "success", deliveryPending };
|
|
29815
30244
|
} catch (err) {
|
|
29816
|
-
return this.disposeFailedAttempt(ctx, err);
|
|
30245
|
+
return await this.disposeFailedAttempt(ctx, err);
|
|
30246
|
+
} finally {
|
|
30247
|
+
this.activeMoneyOps.delete(ctx.transferId);
|
|
29817
30248
|
}
|
|
29818
30249
|
}
|
|
29819
|
-
/**
|
|
29820
|
-
*
|
|
29821
|
-
* the machine BEFORE complete). Accumulate the settled set, then re-plan ONLY
|
|
29822
|
-
* the remainder under a NEW transferId — never the full amount.
|
|
29823
|
-
*/
|
|
30250
|
+
/** Partial (#677/#690): shortfall already durable (machine wrote it BEFORE
|
|
30251
|
+
* complete); accumulate settled, re-plan ONLY the remainder, NEW transferId. */
|
|
29824
30252
|
async consumePartial(ctx, disposition, run, attempt) {
|
|
29825
|
-
|
|
30253
|
+
if (disposition.deliveryPending) {
|
|
30254
|
+
run.pendingAny = true;
|
|
30255
|
+
this.heartbeat.arm();
|
|
30256
|
+
}
|
|
29826
30257
|
const partial = disposition.entry;
|
|
29827
30258
|
run.delivered.push(...partial.committedTokenIds);
|
|
29828
30259
|
run.firstPartialId ??= partial.transferId;
|
|
@@ -29843,11 +30274,8 @@ var PaymentsFacade = class {
|
|
|
29843
30274
|
run.amount = partial.remainingAmount;
|
|
29844
30275
|
return null;
|
|
29845
30276
|
}
|
|
29846
|
-
/**
|
|
29847
|
-
*
|
|
29848
|
-
* backstop still 'open' (committed>0) → converge via the same machine's
|
|
29849
|
-
* resumeIntent; clean conflict with a demoted source → bounded full re-plan.
|
|
29850
|
-
*/
|
|
30277
|
+
/** Failure disposition: possibly-committed → rethrow UNWRAPPED; backstop
|
|
30278
|
+
* 'open' → converge via resumeIntent; clean demoted conflict → full re-plan. */
|
|
29851
30279
|
async disposeFailedAttempt(ctx, err) {
|
|
29852
30280
|
if (isPossiblyCommittedSendOutcome(err)) {
|
|
29853
30281
|
this.settleKeepOpen(ctx);
|
|
@@ -29896,8 +30324,9 @@ var PaymentsFacade = class {
|
|
|
29896
30324
|
}
|
|
29897
30325
|
return { kind: "rethrow", error: err };
|
|
29898
30326
|
}
|
|
29899
|
-
async planAndMaterialize(recipientPubkey, request) {
|
|
30327
|
+
async planAndMaterialize(recipientPubkey, request, run) {
|
|
29900
30328
|
const transferId = this.newId();
|
|
30329
|
+
run.lastTransferId = transferId;
|
|
29901
30330
|
const planned = this.queue.plan(transferId, { coinId: request.coinId, amount: request.amount });
|
|
29902
30331
|
const spend = planned.kind === "planned" ? planned.spend : await planned.settled;
|
|
29903
30332
|
const sourceIds = this.markPlanned(transferId, request.coinId, spend);
|
|
@@ -29980,6 +30409,7 @@ var PaymentsFacade = class {
|
|
|
29980
30409
|
/** Open intent: sources stay reserved + in-flight (§5.2) until resume adopts them. */
|
|
29981
30410
|
settleKeepOpen(ctx) {
|
|
29982
30411
|
this.queue.clearExpectedChange(ctx.transferId);
|
|
30412
|
+
this.heartbeat.arm();
|
|
29983
30413
|
}
|
|
29984
30414
|
settlePartial(ctx, partial) {
|
|
29985
30415
|
this.ledger.cancel(ctx.transferId);
|
|
@@ -30016,6 +30446,7 @@ var PaymentsFacade = class {
|
|
|
30016
30446
|
for (const id of run.settledShortfalls.splice(0)) {
|
|
30017
30447
|
await this.machineStores.shortfalls.removeByKey(id).catch(() => void 0);
|
|
30018
30448
|
}
|
|
30449
|
+
if (run.pendingAny) this.heartbeat.arm();
|
|
30019
30450
|
const result = {
|
|
30020
30451
|
id: transferId,
|
|
30021
30452
|
status: run.pendingAny ? "confirmed" : "delivered",
|
|
@@ -30027,11 +30458,7 @@ var PaymentsFacade = class {
|
|
|
30027
30458
|
this.deps.emit("transfer:updated", result);
|
|
30028
30459
|
return result;
|
|
30029
30460
|
}
|
|
30030
|
-
/**
|
|
30031
|
-
* With nothing delivered the error passes UNWRAPPED (identity + cause kept);
|
|
30032
|
-
* after ≥1 delivered leg EVERY failure surfaces as PartialSendConflictError
|
|
30033
|
-
* over the accumulated settled set — never bare, never a full-amount retry.
|
|
30034
|
-
*/
|
|
30461
|
+
/** Nothing delivered → UNWRAPPED; after ≥1 delivered leg every failure surfaces as PartialSendConflictError over the settled set. */
|
|
30035
30462
|
partialize(err, run) {
|
|
30036
30463
|
if (run.delivered.length === 0) return err;
|
|
30037
30464
|
return new PartialSendConflictError(
|
|
@@ -30060,21 +30487,29 @@ var PaymentsFacade = class {
|
|
|
30060
30487
|
if (!/^(?:[0-9a-f]{2})+$/.test(coinId) || amount <= 0n) {
|
|
30061
30488
|
return { success: false, error: "coinId must be even-length lowercase hex and amount positive" };
|
|
30062
30489
|
}
|
|
30063
|
-
const engine = this.engine();
|
|
30064
30490
|
const mintId = this.newId();
|
|
30065
|
-
|
|
30491
|
+
this.activeMoneyOps.add(mintId);
|
|
30492
|
+
try {
|
|
30493
|
+
return await this.mintUnderJournal(mintId, coinId, amount);
|
|
30494
|
+
} finally {
|
|
30495
|
+
this.activeMoneyOps.delete(mintId);
|
|
30496
|
+
}
|
|
30497
|
+
}
|
|
30498
|
+
async mintUnderJournal(mintId, coinId, amount) {
|
|
30499
|
+
const engine = this.engine();
|
|
30066
30500
|
const entry = {
|
|
30067
30501
|
mintId,
|
|
30068
30502
|
coinId,
|
|
30069
30503
|
amount: amount.toString(),
|
|
30070
30504
|
tokenId: "",
|
|
30071
|
-
createdAt:
|
|
30505
|
+
createdAt: this.nowMs()
|
|
30072
30506
|
};
|
|
30073
30507
|
await this.machineStores.mintJournal.upsert(entry);
|
|
30074
30508
|
let token;
|
|
30075
30509
|
try {
|
|
30076
|
-
token = await engine.mint(
|
|
30510
|
+
token = await engine.mint(this.mintParams(coinId, amount), { transferId: mintId });
|
|
30077
30511
|
} catch (err) {
|
|
30512
|
+
this.heartbeat.arm();
|
|
30078
30513
|
return { success: false, error: messageOf(err) };
|
|
30079
30514
|
}
|
|
30080
30515
|
if (token.blob.tokenId !== entry.tokenId) {
|
|
@@ -30083,6 +30518,7 @@ var PaymentsFacade = class {
|
|
|
30083
30518
|
try {
|
|
30084
30519
|
await this.finalizeMint(engine, mintId, token, coinId, amount.toString());
|
|
30085
30520
|
} catch (err) {
|
|
30521
|
+
this.heartbeat.arm();
|
|
30086
30522
|
return { success: false, tokenId: token.blob.tokenId, error: messageOf(err) };
|
|
30087
30523
|
}
|
|
30088
30524
|
return { success: true, tokenId: token.blob.tokenId };
|
|
@@ -30105,18 +30541,22 @@ var PaymentsFacade = class {
|
|
|
30105
30541
|
this.heldStates.set(token.blob.tokenId, (await engine.deliveryKeys(bytes)).stateHash);
|
|
30106
30542
|
this.trackTail(this.view.delta());
|
|
30107
30543
|
}
|
|
30544
|
+
/** @returns how many journal entries were RESOLVED (cleared) — heartbeat progress. */
|
|
30108
30545
|
async replayMints() {
|
|
30546
|
+
let resolved = 0;
|
|
30109
30547
|
for (const entry of await this.machineStores.mintJournal.list()) {
|
|
30548
|
+
if (this.activeMoneyOps.has(entry.mintId)) continue;
|
|
30110
30549
|
try {
|
|
30111
|
-
await this.replayMint(entry);
|
|
30550
|
+
if (await this.replayMint(entry)) resolved += 1;
|
|
30112
30551
|
} catch {
|
|
30113
30552
|
}
|
|
30114
30553
|
}
|
|
30554
|
+
return resolved;
|
|
30115
30555
|
}
|
|
30116
30556
|
async replayMint(entry) {
|
|
30117
30557
|
if (entry.tokenId !== "" && await this.tokenInServerInventory(entry.tokenId)) {
|
|
30118
30558
|
await this.machineStores.mintJournal.removeByKey(entry.mintId);
|
|
30119
|
-
return;
|
|
30559
|
+
return true;
|
|
30120
30560
|
}
|
|
30121
30561
|
const engine = this.engine();
|
|
30122
30562
|
if (!supportsDeterministicMint(engine)) {
|
|
@@ -30124,7 +30564,7 @@ var PaymentsFacade = class {
|
|
|
30124
30564
|
transferId: entry.mintId,
|
|
30125
30565
|
code: ATTENTION_MINT_UNRESOLVED
|
|
30126
30566
|
});
|
|
30127
|
-
return;
|
|
30567
|
+
return false;
|
|
30128
30568
|
}
|
|
30129
30569
|
const token = await engine.mint(this.mintParams(entry.coinId, BigInt(entry.amount)), {
|
|
30130
30570
|
transferId: entry.mintId
|
|
@@ -30133,6 +30573,7 @@ var PaymentsFacade = class {
|
|
|
30133
30573
|
await this.machineStores.mintJournal.upsert({ ...entry, tokenId: token.blob.tokenId });
|
|
30134
30574
|
}
|
|
30135
30575
|
await this.finalizeMint(engine, entry.mintId, token, entry.coinId, entry.amount);
|
|
30576
|
+
return true;
|
|
30136
30577
|
}
|
|
30137
30578
|
mintParams(coinId, amount) {
|
|
30138
30579
|
return {
|
|
@@ -30158,17 +30599,6 @@ var PaymentsFacade = class {
|
|
|
30158
30599
|
newId() {
|
|
30159
30600
|
return (this.deps.newId ?? randomUUID)();
|
|
30160
30601
|
}
|
|
30161
|
-
async runResume() {
|
|
30162
|
-
const report = await resumeAll(this.machineDeps);
|
|
30163
|
-
await this.requests.reconcile({
|
|
30164
|
-
resumed: report.resumed,
|
|
30165
|
-
conflicted: report.conflicted,
|
|
30166
|
-
open: report.failed
|
|
30167
|
-
});
|
|
30168
|
-
if (report.resumed.length > 0 || report.conflicted.length > 0) {
|
|
30169
|
-
await this.view.delta().catch(() => void 0);
|
|
30170
|
-
}
|
|
30171
|
-
}
|
|
30172
30602
|
async seedHeldStates() {
|
|
30173
30603
|
let page = await this.deps.storagePort.listInventory();
|
|
30174
30604
|
for (let i = 0; i < INVENTORY_SCAN_PAGE_LIMIT; i++) {
|
|
@@ -30180,22 +30610,14 @@ var PaymentsFacade = class {
|
|
|
30180
30610
|
}
|
|
30181
30611
|
}
|
|
30182
30612
|
toUiToken(tokenId, coinId, amount) {
|
|
30183
|
-
const
|
|
30184
|
-
|
|
30185
|
-
|
|
30186
|
-
|
|
30187
|
-
|
|
30188
|
-
|
|
30189
|
-
|
|
30190
|
-
|
|
30191
|
-
decimals: registry.getDecimals(coinId),
|
|
30192
|
-
...iconUrl !== null ? { iconUrl } : {},
|
|
30193
|
-
amount: amount.toString(),
|
|
30194
|
-
status: "transferring",
|
|
30195
|
-
createdAt: now,
|
|
30196
|
-
updatedAt: now,
|
|
30197
|
-
lazy: true
|
|
30198
|
-
};
|
|
30613
|
+
const now = this.nowMs();
|
|
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
|
+
);
|
|
30199
30621
|
}
|
|
30200
30622
|
track(op) {
|
|
30201
30623
|
const settled = op.then(
|
|
@@ -30210,27 +30632,6 @@ var PaymentsFacade = class {
|
|
|
30210
30632
|
this.track(op).catch(() => void 0);
|
|
30211
30633
|
}
|
|
30212
30634
|
};
|
|
30213
|
-
function buildPayload(recipientPubkey, request, spend, spentStates) {
|
|
30214
|
-
return {
|
|
30215
|
-
v: 2,
|
|
30216
|
-
recipient: recipientPubkey,
|
|
30217
|
-
coinId: request.coinId,
|
|
30218
|
-
amount: request.amount,
|
|
30219
|
-
...request.memo !== void 0 ? { memo: request.memo } : {},
|
|
30220
|
-
direct: [...spend.plan.direct],
|
|
30221
|
-
...spend.plan.split !== void 0 ? {
|
|
30222
|
-
split: {
|
|
30223
|
-
tokenId: spend.plan.split.tokenId,
|
|
30224
|
-
splitAmount: spend.plan.split.splitAmount.toString(),
|
|
30225
|
-
remainderAmount: spend.plan.split.remainderAmount.toString()
|
|
30226
|
-
}
|
|
30227
|
-
} : {},
|
|
30228
|
-
spentStates
|
|
30229
|
-
};
|
|
30230
|
-
}
|
|
30231
|
-
function messageOf(err) {
|
|
30232
|
-
return err instanceof Error ? err.message : String(err);
|
|
30233
|
-
}
|
|
30234
30635
|
|
|
30235
30636
|
// impl/wallet-api-v2/http.ts
|
|
30236
30637
|
var WalletApiHttpError = class extends Error {
|
|
@@ -30576,7 +30977,9 @@ var WalletApiSession = class {
|
|
|
30576
30977
|
epochLatch = null;
|
|
30577
30978
|
reseedChain = Promise.resolve();
|
|
30578
30979
|
reseedsPending = 0;
|
|
30980
|
+
epochHandlers = /* @__PURE__ */ new Set();
|
|
30579
30981
|
handlers = /* @__PURE__ */ new Map();
|
|
30982
|
+
statusHandlers = /* @__PURE__ */ new Set();
|
|
30580
30983
|
socket = null;
|
|
30581
30984
|
started = false;
|
|
30582
30985
|
stopped = false;
|
|
@@ -30677,6 +31080,18 @@ var WalletApiSession = class {
|
|
|
30677
31080
|
await this.deps.kv.set(this.refreshKey, tokens.refreshToken);
|
|
30678
31081
|
}
|
|
30679
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
|
+
}
|
|
30680
31095
|
noteEpoch(epoch) {
|
|
30681
31096
|
if (this.epochLatch === epoch) return;
|
|
30682
31097
|
const previous = this.epochLatch;
|
|
@@ -30690,7 +31105,7 @@ var WalletApiSession = class {
|
|
|
30690
31105
|
}
|
|
30691
31106
|
async runReseed(epoch, previous) {
|
|
30692
31107
|
try {
|
|
30693
|
-
|
|
31108
|
+
for (const handler of [...this.epochHandlers]) await handler(epoch);
|
|
30694
31109
|
await this.deps.kv.set(STORE_KEYS.epochLatch, epoch);
|
|
30695
31110
|
} catch {
|
|
30696
31111
|
if (this.epochLatch === epoch) this.epochLatch = previous;
|
|
@@ -30727,10 +31142,16 @@ var WalletApiSession = class {
|
|
|
30727
31142
|
status() {
|
|
30728
31143
|
return this.lastStatus ?? "offline";
|
|
30729
31144
|
}
|
|
31145
|
+
/** Transition feed off the existing emitStatus point (FacadeSession.subscribeStatus). */
|
|
31146
|
+
subscribeStatus(handler) {
|
|
31147
|
+
this.statusHandlers.add(handler);
|
|
31148
|
+
return () => this.statusHandlers.delete(handler);
|
|
31149
|
+
}
|
|
30730
31150
|
emitStatus(status) {
|
|
30731
31151
|
if (this.stopped || this.lastStatus === status) return;
|
|
30732
31152
|
this.lastStatus = status;
|
|
30733
31153
|
this.deps.emitStatus(status);
|
|
31154
|
+
for (const handler of this.statusHandlers) handler(status);
|
|
30734
31155
|
}
|
|
30735
31156
|
async connect() {
|
|
30736
31157
|
if (this.stopped) return;
|
|
@@ -30946,6 +31367,7 @@ var WalletApiDeliveryPort = class {
|
|
|
30946
31367
|
wake;
|
|
30947
31368
|
deriveFn;
|
|
30948
31369
|
seenChain = new SerialChain();
|
|
31370
|
+
lastIncomingEpoch = null;
|
|
30949
31371
|
constructor(config) {
|
|
30950
31372
|
this.client = config.client;
|
|
30951
31373
|
this.kv = config.kv;
|
|
@@ -31040,6 +31462,7 @@ var WalletApiDeliveryPort = class {
|
|
|
31040
31462
|
const seen = await this.readSeen();
|
|
31041
31463
|
let page = await this.client.listMailbox(sinceCursor === void 0 ? 0 : Number(sinceCursor));
|
|
31042
31464
|
for (; ; ) {
|
|
31465
|
+
this.lastIncomingEpoch = String(page.syncEpoch);
|
|
31043
31466
|
for (const entry of page.entries) {
|
|
31044
31467
|
if (entry.status !== "unclaimed") continue;
|
|
31045
31468
|
if (seen.has(entry.entryId)) continue;
|
|
@@ -31049,6 +31472,10 @@ var WalletApiDeliveryPort = class {
|
|
|
31049
31472
|
page = await this.client.listMailbox(page.cursor);
|
|
31050
31473
|
}
|
|
31051
31474
|
}
|
|
31475
|
+
/** §5.7: the page-honest epoch — the restore-detection source Receive keys on. */
|
|
31476
|
+
incomingEpoch() {
|
|
31477
|
+
return this.lastIncomingEpoch;
|
|
31478
|
+
}
|
|
31052
31479
|
toIncoming(entry) {
|
|
31053
31480
|
const bundle = this.openEnvelope(entry);
|
|
31054
31481
|
return {
|
|
@@ -31126,7 +31553,7 @@ var WalletApiSplitCheckpointStore = class {
|
|
|
31126
31553
|
return utf8(`${transferId}:${String(opIndex)}`);
|
|
31127
31554
|
}
|
|
31128
31555
|
async put(transferId, opIndex, bytes) {
|
|
31129
|
-
const {
|
|
31556
|
+
const { kv, fieldKey } = this.config;
|
|
31130
31557
|
const aad = this.aad(transferId, opIndex);
|
|
31131
31558
|
const cacheKey = this.cacheKey(transferId, opIndex);
|
|
31132
31559
|
let envelope = await kv.get(cacheKey);
|
|
@@ -31134,10 +31561,21 @@ var WalletApiSplitCheckpointStore = class {
|
|
|
31134
31561
|
envelope = encryptFieldBytes(fieldKey, bytes, aad);
|
|
31135
31562
|
await kv.set(cacheKey, envelope);
|
|
31136
31563
|
}
|
|
31137
|
-
const
|
|
31138
|
-
const record = await client.postProgress(transferId, opIndex, envelope, signature);
|
|
31564
|
+
const record = await this.post(transferId, opIndex, envelope);
|
|
31139
31565
|
return this.open(record.payload, aad);
|
|
31140
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
|
+
}
|
|
31141
31579
|
async get(transferId, opIndex) {
|
|
31142
31580
|
const records = await this.config.client.getProgress(transferId);
|
|
31143
31581
|
const record = records.find((r) => r.opIndex === opIndex);
|
|
@@ -31160,7 +31598,7 @@ var WalletApiSplitCheckpointStore = class {
|
|
|
31160
31598
|
};
|
|
31161
31599
|
|
|
31162
31600
|
// core/payments-v2-wiring.ts
|
|
31163
|
-
function resolvePaymentsV2Composition(walletApi) {
|
|
31601
|
+
function resolvePaymentsV2Composition(walletApi, sphereNetwork) {
|
|
31164
31602
|
const source = walletApi;
|
|
31165
31603
|
const network = typeof source?.network === "string" && source.network !== "" ? source.network : null;
|
|
31166
31604
|
if (!source || network === null) {
|
|
@@ -31169,6 +31607,18 @@ function resolvePaymentsV2Composition(walletApi) {
|
|
|
31169
31607
|
"INVALID_CONFIG"
|
|
31170
31608
|
);
|
|
31171
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
|
+
}
|
|
31172
31622
|
if (typeof source.paymentsV2Transport === "function") {
|
|
31173
31623
|
return { network, factory: source.paymentsV2Transport.bind(source) };
|
|
31174
31624
|
}
|
|
@@ -31234,8 +31684,7 @@ function defaultPaymentsV2Transport(config) {
|
|
|
31234
31684
|
deviceId: `${config.deviceId}:pv2`,
|
|
31235
31685
|
kv: args.kv,
|
|
31236
31686
|
webSocketFactory,
|
|
31237
|
-
emitStatus: args.emitStatus
|
|
31238
|
-
onEpochChange: args.onEpochChange
|
|
31687
|
+
emitStatus: args.emitStatus
|
|
31239
31688
|
});
|
|
31240
31689
|
return { session, client: authedWireClient(session, client) };
|
|
31241
31690
|
};
|
|
@@ -31305,8 +31754,7 @@ function composePaymentsV2(spec) {
|
|
|
31305
31754
|
identity: identitySlice,
|
|
31306
31755
|
network,
|
|
31307
31756
|
kv,
|
|
31308
|
-
emitStatus: (status) => host.emit("connection:status", { status })
|
|
31309
|
-
onEpochChange: async () => void 0
|
|
31757
|
+
emitStatus: (status) => host.emit("connection:status", { status })
|
|
31310
31758
|
});
|
|
31311
31759
|
const deps = {
|
|
31312
31760
|
session: transport.session,
|
|
@@ -31336,7 +31784,10 @@ function composePaymentsV2(spec) {
|
|
|
31336
31784
|
network,
|
|
31337
31785
|
ownPubkey: identity.chainPubkey,
|
|
31338
31786
|
ownNametag: host.nametag,
|
|
31339
|
-
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()
|
|
31340
31791
|
};
|
|
31341
31792
|
return new PaymentsFacade(deps);
|
|
31342
31793
|
}
|
|
@@ -31666,18 +32117,20 @@ var Sphere = class _Sphere {
|
|
|
31666
32117
|
}
|
|
31667
32118
|
/**
|
|
31668
32119
|
* P9: validate + resolve the paymentsV2 opt-in BEFORE any storage writes —
|
|
31669
|
-
* 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.
|
|
31670
32124
|
*/
|
|
31671
|
-
static resolvePaymentsV2Flag(flag, accountingConfig, swapConfig, walletApi) {
|
|
31672
|
-
if (flag !== true) return
|
|
32125
|
+
static resolvePaymentsV2Flag(flag, accountingConfig, swapConfig, walletApi, network) {
|
|
32126
|
+
if (flag !== true) return null;
|
|
31673
32127
|
if (accountingConfig || swapConfig) {
|
|
31674
32128
|
throw new SphereError(
|
|
31675
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.",
|
|
31676
32130
|
"INVALID_CONFIG"
|
|
31677
32131
|
);
|
|
31678
32132
|
}
|
|
31679
|
-
resolvePaymentsV2Composition(walletApi);
|
|
31680
|
-
return true;
|
|
32133
|
+
return resolvePaymentsV2Composition(walletApi, network);
|
|
31681
32134
|
}
|
|
31682
32135
|
/**
|
|
31683
32136
|
* Configure TokenRegistry in the main bundle context.
|
|
@@ -31732,12 +32185,14 @@ var Sphere = class _Sphere {
|
|
|
31732
32185
|
options.walletApi
|
|
31733
32186
|
);
|
|
31734
32187
|
sphere._verification = options.verification;
|
|
31735
|
-
sphere.
|
|
32188
|
+
sphere._paymentsV2Composition = _Sphere.resolvePaymentsV2Flag(
|
|
31736
32189
|
options.paymentsV2,
|
|
31737
32190
|
accountingConfig,
|
|
31738
32191
|
swapConfig,
|
|
31739
|
-
options.walletApi
|
|
32192
|
+
options.walletApi,
|
|
32193
|
+
options.network
|
|
31740
32194
|
);
|
|
32195
|
+
sphere._paymentsV2Enabled = sphere._paymentsV2Composition !== null;
|
|
31741
32196
|
sphere._password = options.password ?? null;
|
|
31742
32197
|
sphere._passwordProtected = sphere._password !== null;
|
|
31743
32198
|
progress?.({ step: "storing_keys", message: "Storing wallet keys..." });
|
|
@@ -31804,12 +32259,14 @@ var Sphere = class _Sphere {
|
|
|
31804
32259
|
options.walletApi
|
|
31805
32260
|
);
|
|
31806
32261
|
sphere._verification = options.verification;
|
|
31807
|
-
sphere.
|
|
32262
|
+
sphere._paymentsV2Composition = _Sphere.resolvePaymentsV2Flag(
|
|
31808
32263
|
options.paymentsV2,
|
|
31809
32264
|
accountingConfig,
|
|
31810
32265
|
swapConfig,
|
|
31811
|
-
options.walletApi
|
|
32266
|
+
options.walletApi,
|
|
32267
|
+
options.network
|
|
31812
32268
|
);
|
|
32269
|
+
sphere._paymentsV2Enabled = sphere._paymentsV2Composition !== null;
|
|
31813
32270
|
sphere._password = options.password ?? null;
|
|
31814
32271
|
sphere._passwordProtected = sphere._password !== null;
|
|
31815
32272
|
if (!options.storage.isConnected()) {
|
|
@@ -31883,12 +32340,14 @@ var Sphere = class _Sphere {
|
|
|
31883
32340
|
options.walletApi
|
|
31884
32341
|
);
|
|
31885
32342
|
sphere._verification = options.verification;
|
|
31886
|
-
sphere.
|
|
32343
|
+
sphere._paymentsV2Composition = _Sphere.resolvePaymentsV2Flag(
|
|
31887
32344
|
options.paymentsV2,
|
|
31888
32345
|
accountingConfig,
|
|
31889
32346
|
swapConfig,
|
|
31890
|
-
options.walletApi
|
|
32347
|
+
options.walletApi,
|
|
32348
|
+
options.network
|
|
31891
32349
|
);
|
|
32350
|
+
sphere._paymentsV2Enabled = sphere._paymentsV2Composition !== null;
|
|
31892
32351
|
sphere._password = options.password ?? null;
|
|
31893
32352
|
sphere._passwordProtected = sphere._password !== null;
|
|
31894
32353
|
progress?.({ step: "storing_keys", message: "Storing wallet keys..." });
|
|
@@ -34546,7 +35005,9 @@ var Sphere = class _Sphere {
|
|
|
34546
35005
|
* setOracleApiKey rebuild is what future operations snapshot.
|
|
34547
35006
|
*/
|
|
34548
35007
|
async startPaymentsV2Inner(index, identity) {
|
|
34549
|
-
this._paymentsV2Composition
|
|
35008
|
+
if (this._paymentsV2Composition === null) {
|
|
35009
|
+
throw new SphereError("paymentsV2: composition unresolved \u2014 init never ran the flag gate", "INVALID_CONFIG");
|
|
35010
|
+
}
|
|
34550
35011
|
if (!this._addressModules.get(index)?.tokenEngine) {
|
|
34551
35012
|
throw new SphereError(
|
|
34552
35013
|
"paymentsV2 requires the v2 token engine \u2014 the oracle must supply a trust base + gateway URL (and API key where required).",
|