@unicitylabs/sphere-sdk 0.14.0-dev.5 → 0.14.0-dev.6
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 +303 -35
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +21 -0
- package/dist/core/index.d.ts +21 -0
- package/dist/core/index.js +303 -35
- 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 +7 -0
- package/dist/impl/wallet-api-v2/index.cjs.map +1 -1
- package/dist/impl/wallet-api-v2/index.d.cts +3 -0
- package/dist/impl/wallet-api-v2/index.d.ts +3 -0
- package/dist/impl/wallet-api-v2/index.js +7 -0
- package/dist/impl/wallet-api-v2/index.js.map +1 -1
- package/dist/index.cjs +303 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +303 -35
- package/dist/index.js.map +1 -1
- package/dist/modules/payments-v2/index.cjs +300 -35
- package/dist/modules/payments-v2/index.cjs.map +1 -1
- package/dist/modules/payments-v2/index.d.cts +45 -2
- package/dist/modules/payments-v2/index.d.ts +45 -2
- package/dist/modules/payments-v2/index.js +298 -35
- package/dist/modules/payments-v2/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -27643,6 +27643,10 @@ function isValidationReject(err) {
|
|
|
27643
27643
|
if (e === null) return false;
|
|
27644
27644
|
return e.status === 422 || e.statusCode === 422 || e.code === "VALIDATION" || e.code === "VALIDATION_FAILED";
|
|
27645
27645
|
}
|
|
27646
|
+
function retryAfterMsOf(err) {
|
|
27647
|
+
const retryAfter = err !== null && typeof err === "object" ? err.retryAfter : void 0;
|
|
27648
|
+
return typeof retryAfter === "number" && Number.isFinite(retryAfter) && retryAfter >= 0 ? retryAfter : null;
|
|
27649
|
+
}
|
|
27646
27650
|
var chainRegistry = /* @__PURE__ */ new WeakMap();
|
|
27647
27651
|
var replayRegistry = /* @__PURE__ */ new WeakMap();
|
|
27648
27652
|
function registryFor(registry, kv, storeKey, make) {
|
|
@@ -28280,6 +28284,217 @@ function validatePayload(raw) {
|
|
|
28280
28284
|
return p;
|
|
28281
28285
|
}
|
|
28282
28286
|
|
|
28287
|
+
// modules/payments-v2/convergence.ts
|
|
28288
|
+
var HEARTBEAT_SEED_MS = 5e3;
|
|
28289
|
+
var HEARTBEAT_CAP_MS = 12e4;
|
|
28290
|
+
var ConvergenceHeartbeat = class {
|
|
28291
|
+
constructor(deps) {
|
|
28292
|
+
this.deps = deps;
|
|
28293
|
+
}
|
|
28294
|
+
timer = null;
|
|
28295
|
+
delayMs = HEARTBEAT_SEED_MS;
|
|
28296
|
+
notBefore = 0;
|
|
28297
|
+
running = false;
|
|
28298
|
+
start() {
|
|
28299
|
+
this.running = true;
|
|
28300
|
+
this.delayMs = HEARTBEAT_SEED_MS;
|
|
28301
|
+
this.notBefore = 0;
|
|
28302
|
+
}
|
|
28303
|
+
/** §7 quiescence unchanged: a mid-backoff stop() cancels cleanly. */
|
|
28304
|
+
stop() {
|
|
28305
|
+
this.running = false;
|
|
28306
|
+
this.clearTimer();
|
|
28307
|
+
}
|
|
28308
|
+
/** Reset to seed on progress, ×2 to the cap otherwise; idle stops rescheduling. */
|
|
28309
|
+
settle(outcome, pending) {
|
|
28310
|
+
if (outcome.retryAfterMs !== null) {
|
|
28311
|
+
this.notBefore = Math.max(this.notBefore, this.deps.now() + outcome.retryAfterMs);
|
|
28312
|
+
}
|
|
28313
|
+
if (!pending) {
|
|
28314
|
+
this.delayMs = HEARTBEAT_SEED_MS;
|
|
28315
|
+
return;
|
|
28316
|
+
}
|
|
28317
|
+
const delay = outcome.progress ? HEARTBEAT_SEED_MS : this.delayMs;
|
|
28318
|
+
this.delayMs = Math.min(delay * 2, HEARTBEAT_CAP_MS);
|
|
28319
|
+
this.schedule(delay);
|
|
28320
|
+
}
|
|
28321
|
+
/** New pending work (keep-open park / journaled delivery / mint hold): prompt retry. */
|
|
28322
|
+
arm() {
|
|
28323
|
+
if (!this.running) return;
|
|
28324
|
+
this.delayMs = Math.min(HEARTBEAT_SEED_MS * 2, HEARTBEAT_CAP_MS);
|
|
28325
|
+
this.schedule(HEARTBEAT_SEED_MS);
|
|
28326
|
+
}
|
|
28327
|
+
/** connection:status recovery: the backoff resets; a pending tick re-fires at the seed. */
|
|
28328
|
+
recovered() {
|
|
28329
|
+
this.delayMs = HEARTBEAT_SEED_MS;
|
|
28330
|
+
if (this.timer !== null) this.arm();
|
|
28331
|
+
}
|
|
28332
|
+
schedule(delayMs) {
|
|
28333
|
+
this.clearTimer();
|
|
28334
|
+
if (!this.running) return;
|
|
28335
|
+
const floored = Math.max(delayMs, this.notBefore - this.deps.now());
|
|
28336
|
+
this.timer = setTimeout(() => {
|
|
28337
|
+
this.timer = null;
|
|
28338
|
+
this.deps.onTick();
|
|
28339
|
+
}, floored);
|
|
28340
|
+
}
|
|
28341
|
+
clearTimer() {
|
|
28342
|
+
if (this.timer !== null) clearTimeout(this.timer);
|
|
28343
|
+
this.timer = null;
|
|
28344
|
+
}
|
|
28345
|
+
};
|
|
28346
|
+
var Converger = class {
|
|
28347
|
+
constructor(deps) {
|
|
28348
|
+
this.deps = deps;
|
|
28349
|
+
}
|
|
28350
|
+
/** Resume open intents, then drain the delivery + mint journals (all idempotent). */
|
|
28351
|
+
async convergeOnce() {
|
|
28352
|
+
let progress = false;
|
|
28353
|
+
let retryAfterMs = null;
|
|
28354
|
+
try {
|
|
28355
|
+
progress = await this.resumePass();
|
|
28356
|
+
} catch (err) {
|
|
28357
|
+
retryAfterMs = retryAfterMsOf(err);
|
|
28358
|
+
}
|
|
28359
|
+
try {
|
|
28360
|
+
const stats = await this.deps.stores.deliveryJournal.replay(this.replayDeps());
|
|
28361
|
+
progress = progress || stats.delivered > 0;
|
|
28362
|
+
} catch (err) {
|
|
28363
|
+
retryAfterMs ??= retryAfterMsOf(err);
|
|
28364
|
+
}
|
|
28365
|
+
try {
|
|
28366
|
+
progress = await this.deps.replayMints() > 0 || progress;
|
|
28367
|
+
} catch (err) {
|
|
28368
|
+
retryAfterMs ??= retryAfterMsOf(err);
|
|
28369
|
+
}
|
|
28370
|
+
return { progress, retryAfterMs };
|
|
28371
|
+
}
|
|
28372
|
+
/** §4 heartbeat predicate — read fresh from the §6 stores, never cached. */
|
|
28373
|
+
async pendingWork() {
|
|
28374
|
+
const [backstop, journal, mints] = await Promise.all([
|
|
28375
|
+
this.deps.stores.backstop.list(),
|
|
28376
|
+
this.deps.stores.deliveryJournal.list(),
|
|
28377
|
+
this.deps.stores.mintJournal.list()
|
|
28378
|
+
]);
|
|
28379
|
+
return backstop.length > 0 || journal.some((e) => e.undeliverable !== true) || mints.length > 0;
|
|
28380
|
+
}
|
|
28381
|
+
async resumePass() {
|
|
28382
|
+
const report = await resumeAll(this.filteredMachineDeps());
|
|
28383
|
+
const progress = report.resumed.length > 0 || report.conflicted.length > 0;
|
|
28384
|
+
await this.deps.reconcile({
|
|
28385
|
+
resumed: report.resumed,
|
|
28386
|
+
conflicted: report.conflicted,
|
|
28387
|
+
open: report.failed
|
|
28388
|
+
});
|
|
28389
|
+
if (progress) await this.deps.refreshView().catch(() => void 0);
|
|
28390
|
+
return progress;
|
|
28391
|
+
}
|
|
28392
|
+
filteredMachineDeps() {
|
|
28393
|
+
const intents = this.deps.machineDeps.intents;
|
|
28394
|
+
return {
|
|
28395
|
+
...this.deps.machineDeps,
|
|
28396
|
+
intents: {
|
|
28397
|
+
...intents,
|
|
28398
|
+
listOpen: async () => (await intents.listOpen()).filter((intent) => !this.deps.isActiveOp(intent.transferId))
|
|
28399
|
+
}
|
|
28400
|
+
};
|
|
28401
|
+
}
|
|
28402
|
+
replayDeps() {
|
|
28403
|
+
return {
|
|
28404
|
+
delivery: this.deps.delivery,
|
|
28405
|
+
now: () => this.deps.now(),
|
|
28406
|
+
attention: (transferId, code, detail) => {
|
|
28407
|
+
this.deps.emit(
|
|
28408
|
+
"transfer:attention",
|
|
28409
|
+
detail === void 0 ? { transferId, code } : { transferId, code, detail }
|
|
28410
|
+
);
|
|
28411
|
+
}
|
|
28412
|
+
};
|
|
28413
|
+
}
|
|
28414
|
+
};
|
|
28415
|
+
async function derivePendingTransfers(stores, decryptPayload) {
|
|
28416
|
+
const [backstop, journal, shortfalls] = await Promise.all([
|
|
28417
|
+
stores.backstop.list(),
|
|
28418
|
+
stores.deliveryJournal.list(),
|
|
28419
|
+
stores.shortfalls.list()
|
|
28420
|
+
]);
|
|
28421
|
+
const byTransfer = groupJournal(journal);
|
|
28422
|
+
const out = [];
|
|
28423
|
+
const seen = /* @__PURE__ */ new Set();
|
|
28424
|
+
for (const entry of backstop) {
|
|
28425
|
+
if (entry.disposition !== "open") continue;
|
|
28426
|
+
seen.add(entry.transferId);
|
|
28427
|
+
out.push(await openRow(entry, byTransfer.get(entry.transferId) ?? [], decryptPayload));
|
|
28428
|
+
}
|
|
28429
|
+
for (const [transferId, entries] of byTransfer) {
|
|
28430
|
+
if (seen.has(transferId)) continue;
|
|
28431
|
+
seen.add(transferId);
|
|
28432
|
+
out.push(journalOnlyRow(transferId, entries));
|
|
28433
|
+
}
|
|
28434
|
+
for (const entry of shortfalls) {
|
|
28435
|
+
if (!seen.has(entry.transferId)) out.push(shortfallRow(entry));
|
|
28436
|
+
}
|
|
28437
|
+
return out;
|
|
28438
|
+
}
|
|
28439
|
+
function groupJournal(journal) {
|
|
28440
|
+
const byTransfer = /* @__PURE__ */ new Map();
|
|
28441
|
+
for (const entry of journal) {
|
|
28442
|
+
const mine = byTransfer.get(entry.transferId);
|
|
28443
|
+
if (mine === void 0) byTransfer.set(entry.transferId, [entry]);
|
|
28444
|
+
else mine.push(entry);
|
|
28445
|
+
}
|
|
28446
|
+
return byTransfer;
|
|
28447
|
+
}
|
|
28448
|
+
async function openRow(entry, journal, decryptPayload) {
|
|
28449
|
+
let payload = null;
|
|
28450
|
+
try {
|
|
28451
|
+
const raw = await decryptPayload(entry.payloadEnvelope);
|
|
28452
|
+
if (raw !== null && typeof raw === "object") payload = raw;
|
|
28453
|
+
} catch {
|
|
28454
|
+
payload = null;
|
|
28455
|
+
}
|
|
28456
|
+
const total = Array.isArray(payload?.direct) ? payload.direct.length + (payload.split !== void 0 ? 1 : 0) : 0;
|
|
28457
|
+
const certified = distinctOpCount(journal);
|
|
28458
|
+
return {
|
|
28459
|
+
transferId: entry.transferId,
|
|
28460
|
+
kind: "open",
|
|
28461
|
+
recipient: typeof payload?.recipient === "string" ? payload.recipient : "",
|
|
28462
|
+
coinId: typeof payload?.coinId === "string" ? payload.coinId : "",
|
|
28463
|
+
amount: typeof payload?.amount === "string" ? payload.amount : "",
|
|
28464
|
+
legs: { certified, total: Math.max(total, certified) },
|
|
28465
|
+
deliveryPending: journal.length > 0,
|
|
28466
|
+
createdAt: entry.createdAt
|
|
28467
|
+
};
|
|
28468
|
+
}
|
|
28469
|
+
function journalOnlyRow(transferId, entries) {
|
|
28470
|
+
const certified = distinctOpCount(entries);
|
|
28471
|
+
return {
|
|
28472
|
+
transferId,
|
|
28473
|
+
kind: "open",
|
|
28474
|
+
recipient: entries[0].recipientPubkey,
|
|
28475
|
+
coinId: "",
|
|
28476
|
+
amount: "",
|
|
28477
|
+
legs: { certified, total: certified },
|
|
28478
|
+
deliveryPending: true,
|
|
28479
|
+
createdAt: 0
|
|
28480
|
+
};
|
|
28481
|
+
}
|
|
28482
|
+
function shortfallRow(entry) {
|
|
28483
|
+
return {
|
|
28484
|
+
transferId: entry.transferId,
|
|
28485
|
+
kind: "shortfall",
|
|
28486
|
+
recipient: entry.recipient,
|
|
28487
|
+
coinId: entry.coinId,
|
|
28488
|
+
amount: entry.remainingAmount,
|
|
28489
|
+
legs: { certified: entry.committedTokenIds.length, total: entry.committedTokenIds.length },
|
|
28490
|
+
deliveryPending: false,
|
|
28491
|
+
createdAt: entry.createdAt
|
|
28492
|
+
};
|
|
28493
|
+
}
|
|
28494
|
+
function distinctOpCount(entries) {
|
|
28495
|
+
return new Set(entries.map((entry) => entry.opIndex)).size;
|
|
28496
|
+
}
|
|
28497
|
+
|
|
28283
28498
|
// modules/payments-v2/history/History.ts
|
|
28284
28499
|
var WIRE_TOKEN_ID = /^(?:[0-9a-f]{2}){1,64}$/;
|
|
28285
28500
|
var WIRE_PUBKEY = /^0[23][0-9a-f]{64}$/;
|
|
@@ -29669,6 +29884,21 @@ var PaymentsFacade = class {
|
|
|
29669
29884
|
this.ownPubkeyBytes = parts.ownPubkeyBytes;
|
|
29670
29885
|
this.requests = parts.requests;
|
|
29671
29886
|
deps.deliveryPort.bindDeliveryKeys((blob) => this.engine().deliveryKeys(blob));
|
|
29887
|
+
this.heartbeat = new ConvergenceHeartbeat({
|
|
29888
|
+
now: () => this.nowMs(),
|
|
29889
|
+
onTick: () => this.trackTail(this.runConvergencePass())
|
|
29890
|
+
});
|
|
29891
|
+
this.converger = new Converger({
|
|
29892
|
+
machineDeps: this.machineDeps,
|
|
29893
|
+
stores: this.machineStores,
|
|
29894
|
+
delivery: deps.deliveryPort,
|
|
29895
|
+
reconcile: (report) => this.requests.reconcile(report),
|
|
29896
|
+
refreshView: () => this.view.delta(),
|
|
29897
|
+
replayMints: () => this.replayMints(),
|
|
29898
|
+
isActiveOp: (id) => this.activeMoneyOps.has(id),
|
|
29899
|
+
emit: deps.emit,
|
|
29900
|
+
now: () => this.nowMs()
|
|
29901
|
+
});
|
|
29672
29902
|
}
|
|
29673
29903
|
view;
|
|
29674
29904
|
ledger;
|
|
@@ -29685,10 +29915,18 @@ var PaymentsFacade = class {
|
|
|
29685
29915
|
pendingOps = /* @__PURE__ */ new Set();
|
|
29686
29916
|
unsubscribers = [];
|
|
29687
29917
|
started = false;
|
|
29918
|
+
// §7 heartbeat: in-memory timing cell + pass runner (see convergence.ts).
|
|
29919
|
+
heartbeat;
|
|
29920
|
+
converger;
|
|
29921
|
+
/** Resume single-flight (§7): ticks, start() and resumeNow() coalesce onto ONE pass. */
|
|
29922
|
+
resumeFlight = new SingleFlight();
|
|
29923
|
+
/** §7 ownership: ids with an in-process machine attempt — the pass never adopts one. */
|
|
29924
|
+
activeMoneyOps = /* @__PURE__ */ new Set();
|
|
29688
29925
|
// ── lifecycle ──────────────────────────────────────────────────────────────
|
|
29689
29926
|
async start() {
|
|
29690
29927
|
if (this.started) return;
|
|
29691
29928
|
this.started = true;
|
|
29929
|
+
this.heartbeat.start();
|
|
29692
29930
|
await this.deps.session.start();
|
|
29693
29931
|
this.unsubscribers.push(
|
|
29694
29932
|
this.deps.session.subscribeStream("mailbox", () => {
|
|
@@ -29701,20 +29939,11 @@ var PaymentsFacade = class {
|
|
|
29701
29939
|
this.trackTail(this.requests.drainIncoming());
|
|
29702
29940
|
})
|
|
29703
29941
|
);
|
|
29704
|
-
this.
|
|
29705
|
-
|
|
29706
|
-
|
|
29707
|
-
|
|
29708
|
-
|
|
29709
|
-
attention: (transferId, code, detail) => {
|
|
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());
|
|
29942
|
+
const statusUnsub = this.deps.session.subscribeStatus?.((status) => {
|
|
29943
|
+
if (status === "connected") this.heartbeat.recovered();
|
|
29944
|
+
});
|
|
29945
|
+
if (statusUnsub !== void 0) this.unsubscribers.push(statusUnsub);
|
|
29946
|
+
this.trackTail(this.runConvergencePass());
|
|
29718
29947
|
this.trackTail(this.seedHeldStates());
|
|
29719
29948
|
await this.view.fullPull().catch(() => void 0);
|
|
29720
29949
|
this.receiveLoop.start(this.deps.receivePollMs);
|
|
@@ -29722,6 +29951,7 @@ var PaymentsFacade = class {
|
|
|
29722
29951
|
/** §7 same-address restart gate: resolves only after in-flight ops settle. */
|
|
29723
29952
|
async stop() {
|
|
29724
29953
|
this.started = false;
|
|
29954
|
+
this.heartbeat.stop();
|
|
29725
29955
|
this.receiveLoop.stop();
|
|
29726
29956
|
for (const unsubscribe of this.unsubscribers) unsubscribe();
|
|
29727
29957
|
this.unsubscribers = [];
|
|
@@ -29747,6 +29977,10 @@ var PaymentsFacade = class {
|
|
|
29747
29977
|
history(page) {
|
|
29748
29978
|
return this.historyStore.page(page ?? {});
|
|
29749
29979
|
}
|
|
29980
|
+
/** §4 pending-transfers UI surface — derived on read, never cached (convergence.ts). */
|
|
29981
|
+
pendingTransfers() {
|
|
29982
|
+
return derivePendingTransfers(this.machineStores, this.machineDeps.decryptPayload);
|
|
29983
|
+
}
|
|
29750
29984
|
// ── money movement ─────────────────────────────────────────────────────────
|
|
29751
29985
|
send(request) {
|
|
29752
29986
|
return this.track(this.sendWithPolicy(request));
|
|
@@ -29759,6 +29993,22 @@ var PaymentsFacade = class {
|
|
|
29759
29993
|
mint(coinId, amount) {
|
|
29760
29994
|
return this.track(this.mintInner(coinId, amount));
|
|
29761
29995
|
}
|
|
29996
|
+
// §4 retry verb: coalesces onto the running pass — NEVER re-issue send() (#631/#676).
|
|
29997
|
+
async resumeNow() {
|
|
29998
|
+
if (!this.started) return;
|
|
29999
|
+
await this.track(this.runConvergencePass());
|
|
30000
|
+
}
|
|
30001
|
+
/** One single-flighted pass + its reschedule: concurrent callers coalesce. */
|
|
30002
|
+
runConvergencePass() {
|
|
30003
|
+
return this.resumeFlight.run(async () => {
|
|
30004
|
+
const outcome = await this.converger.convergeOnce();
|
|
30005
|
+
const pending = await this.converger.pendingWork().catch(() => true);
|
|
30006
|
+
this.heartbeat.settle(outcome, pending);
|
|
30007
|
+
});
|
|
30008
|
+
}
|
|
30009
|
+
nowMs() {
|
|
30010
|
+
return (this.deps.now ?? Date.now)();
|
|
30011
|
+
}
|
|
29762
30012
|
// ── send policy (§5.5 + old-loop parity; the machine stays policy-free) ────
|
|
29763
30013
|
async sendWithPolicy(request) {
|
|
29764
30014
|
const recipient = await this.requireSameNetworkRecipient(request.recipient);
|
|
@@ -29808,12 +30058,15 @@ var PaymentsFacade = class {
|
|
|
29808
30058
|
return recipient;
|
|
29809
30059
|
}
|
|
29810
30060
|
async runAttempt(ctx) {
|
|
30061
|
+
this.activeMoneyOps.add(ctx.transferId);
|
|
29811
30062
|
try {
|
|
29812
30063
|
const { deliveryPending } = await this.machine.run(ctx.plan);
|
|
29813
30064
|
this.settleSuccess(ctx, ctx.sourceIds);
|
|
29814
30065
|
return { kind: "success", deliveryPending };
|
|
29815
30066
|
} catch (err) {
|
|
29816
|
-
return this.disposeFailedAttempt(ctx, err);
|
|
30067
|
+
return await this.disposeFailedAttempt(ctx, err);
|
|
30068
|
+
} finally {
|
|
30069
|
+
this.activeMoneyOps.delete(ctx.transferId);
|
|
29817
30070
|
}
|
|
29818
30071
|
}
|
|
29819
30072
|
/**
|
|
@@ -29822,7 +30075,10 @@ var PaymentsFacade = class {
|
|
|
29822
30075
|
* the remainder under a NEW transferId — never the full amount.
|
|
29823
30076
|
*/
|
|
29824
30077
|
async consumePartial(ctx, disposition, run, attempt) {
|
|
29825
|
-
|
|
30078
|
+
if (disposition.deliveryPending) {
|
|
30079
|
+
run.pendingAny = true;
|
|
30080
|
+
this.heartbeat.arm();
|
|
30081
|
+
}
|
|
29826
30082
|
const partial = disposition.entry;
|
|
29827
30083
|
run.delivered.push(...partial.committedTokenIds);
|
|
29828
30084
|
run.firstPartialId ??= partial.transferId;
|
|
@@ -29980,6 +30236,7 @@ var PaymentsFacade = class {
|
|
|
29980
30236
|
/** Open intent: sources stay reserved + in-flight (§5.2) until resume adopts them. */
|
|
29981
30237
|
settleKeepOpen(ctx) {
|
|
29982
30238
|
this.queue.clearExpectedChange(ctx.transferId);
|
|
30239
|
+
this.heartbeat.arm();
|
|
29983
30240
|
}
|
|
29984
30241
|
settlePartial(ctx, partial) {
|
|
29985
30242
|
this.ledger.cancel(ctx.transferId);
|
|
@@ -30016,6 +30273,7 @@ var PaymentsFacade = class {
|
|
|
30016
30273
|
for (const id of run.settledShortfalls.splice(0)) {
|
|
30017
30274
|
await this.machineStores.shortfalls.removeByKey(id).catch(() => void 0);
|
|
30018
30275
|
}
|
|
30276
|
+
if (run.pendingAny) this.heartbeat.arm();
|
|
30019
30277
|
const result = {
|
|
30020
30278
|
id: transferId,
|
|
30021
30279
|
status: run.pendingAny ? "confirmed" : "delivered",
|
|
@@ -30060,21 +30318,29 @@ var PaymentsFacade = class {
|
|
|
30060
30318
|
if (!/^(?:[0-9a-f]{2})+$/.test(coinId) || amount <= 0n) {
|
|
30061
30319
|
return { success: false, error: "coinId must be even-length lowercase hex and amount positive" };
|
|
30062
30320
|
}
|
|
30063
|
-
const engine = this.engine();
|
|
30064
30321
|
const mintId = this.newId();
|
|
30065
|
-
|
|
30322
|
+
this.activeMoneyOps.add(mintId);
|
|
30323
|
+
try {
|
|
30324
|
+
return await this.mintUnderJournal(mintId, coinId, amount);
|
|
30325
|
+
} finally {
|
|
30326
|
+
this.activeMoneyOps.delete(mintId);
|
|
30327
|
+
}
|
|
30328
|
+
}
|
|
30329
|
+
async mintUnderJournal(mintId, coinId, amount) {
|
|
30330
|
+
const engine = this.engine();
|
|
30066
30331
|
const entry = {
|
|
30067
30332
|
mintId,
|
|
30068
30333
|
coinId,
|
|
30069
30334
|
amount: amount.toString(),
|
|
30070
30335
|
tokenId: "",
|
|
30071
|
-
createdAt:
|
|
30336
|
+
createdAt: this.nowMs()
|
|
30072
30337
|
};
|
|
30073
30338
|
await this.machineStores.mintJournal.upsert(entry);
|
|
30074
30339
|
let token;
|
|
30075
30340
|
try {
|
|
30076
|
-
token = await engine.mint(
|
|
30341
|
+
token = await engine.mint(this.mintParams(coinId, amount), { transferId: mintId });
|
|
30077
30342
|
} catch (err) {
|
|
30343
|
+
this.heartbeat.arm();
|
|
30078
30344
|
return { success: false, error: messageOf(err) };
|
|
30079
30345
|
}
|
|
30080
30346
|
if (token.blob.tokenId !== entry.tokenId) {
|
|
@@ -30083,6 +30349,7 @@ var PaymentsFacade = class {
|
|
|
30083
30349
|
try {
|
|
30084
30350
|
await this.finalizeMint(engine, mintId, token, coinId, amount.toString());
|
|
30085
30351
|
} catch (err) {
|
|
30352
|
+
this.heartbeat.arm();
|
|
30086
30353
|
return { success: false, tokenId: token.blob.tokenId, error: messageOf(err) };
|
|
30087
30354
|
}
|
|
30088
30355
|
return { success: true, tokenId: token.blob.tokenId };
|
|
@@ -30105,18 +30372,22 @@ var PaymentsFacade = class {
|
|
|
30105
30372
|
this.heldStates.set(token.blob.tokenId, (await engine.deliveryKeys(bytes)).stateHash);
|
|
30106
30373
|
this.trackTail(this.view.delta());
|
|
30107
30374
|
}
|
|
30375
|
+
/** @returns how many journal entries were RESOLVED (cleared) — heartbeat progress. */
|
|
30108
30376
|
async replayMints() {
|
|
30377
|
+
let resolved = 0;
|
|
30109
30378
|
for (const entry of await this.machineStores.mintJournal.list()) {
|
|
30379
|
+
if (this.activeMoneyOps.has(entry.mintId)) continue;
|
|
30110
30380
|
try {
|
|
30111
|
-
await this.replayMint(entry);
|
|
30381
|
+
if (await this.replayMint(entry)) resolved += 1;
|
|
30112
30382
|
} catch {
|
|
30113
30383
|
}
|
|
30114
30384
|
}
|
|
30385
|
+
return resolved;
|
|
30115
30386
|
}
|
|
30116
30387
|
async replayMint(entry) {
|
|
30117
30388
|
if (entry.tokenId !== "" && await this.tokenInServerInventory(entry.tokenId)) {
|
|
30118
30389
|
await this.machineStores.mintJournal.removeByKey(entry.mintId);
|
|
30119
|
-
return;
|
|
30390
|
+
return true;
|
|
30120
30391
|
}
|
|
30121
30392
|
const engine = this.engine();
|
|
30122
30393
|
if (!supportsDeterministicMint(engine)) {
|
|
@@ -30124,7 +30395,7 @@ var PaymentsFacade = class {
|
|
|
30124
30395
|
transferId: entry.mintId,
|
|
30125
30396
|
code: ATTENTION_MINT_UNRESOLVED
|
|
30126
30397
|
});
|
|
30127
|
-
return;
|
|
30398
|
+
return false;
|
|
30128
30399
|
}
|
|
30129
30400
|
const token = await engine.mint(this.mintParams(entry.coinId, BigInt(entry.amount)), {
|
|
30130
30401
|
transferId: entry.mintId
|
|
@@ -30133,6 +30404,7 @@ var PaymentsFacade = class {
|
|
|
30133
30404
|
await this.machineStores.mintJournal.upsert({ ...entry, tokenId: token.blob.tokenId });
|
|
30134
30405
|
}
|
|
30135
30406
|
await this.finalizeMint(engine, entry.mintId, token, entry.coinId, entry.amount);
|
|
30407
|
+
return true;
|
|
30136
30408
|
}
|
|
30137
30409
|
mintParams(coinId, amount) {
|
|
30138
30410
|
return {
|
|
@@ -30158,17 +30430,6 @@ var PaymentsFacade = class {
|
|
|
30158
30430
|
newId() {
|
|
30159
30431
|
return (this.deps.newId ?? randomUUID)();
|
|
30160
30432
|
}
|
|
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
30433
|
async seedHeldStates() {
|
|
30173
30434
|
let page = await this.deps.storagePort.listInventory();
|
|
30174
30435
|
for (let i = 0; i < INVENTORY_SCAN_PAGE_LIMIT; i++) {
|
|
@@ -30182,7 +30443,7 @@ var PaymentsFacade = class {
|
|
|
30182
30443
|
toUiToken(tokenId, coinId, amount) {
|
|
30183
30444
|
const registry = this.deps.registry;
|
|
30184
30445
|
const iconUrl = registry.getIconUrl(coinId);
|
|
30185
|
-
const now =
|
|
30446
|
+
const now = this.nowMs();
|
|
30186
30447
|
return {
|
|
30187
30448
|
id: tokenId,
|
|
30188
30449
|
coinId,
|
|
@@ -30577,6 +30838,7 @@ var WalletApiSession = class {
|
|
|
30577
30838
|
reseedChain = Promise.resolve();
|
|
30578
30839
|
reseedsPending = 0;
|
|
30579
30840
|
handlers = /* @__PURE__ */ new Map();
|
|
30841
|
+
statusHandlers = /* @__PURE__ */ new Set();
|
|
30580
30842
|
socket = null;
|
|
30581
30843
|
started = false;
|
|
30582
30844
|
stopped = false;
|
|
@@ -30727,10 +30989,16 @@ var WalletApiSession = class {
|
|
|
30727
30989
|
status() {
|
|
30728
30990
|
return this.lastStatus ?? "offline";
|
|
30729
30991
|
}
|
|
30992
|
+
/** Transition feed off the existing emitStatus point (FacadeSession.subscribeStatus). */
|
|
30993
|
+
subscribeStatus(handler) {
|
|
30994
|
+
this.statusHandlers.add(handler);
|
|
30995
|
+
return () => this.statusHandlers.delete(handler);
|
|
30996
|
+
}
|
|
30730
30997
|
emitStatus(status) {
|
|
30731
30998
|
if (this.stopped || this.lastStatus === status) return;
|
|
30732
30999
|
this.lastStatus = status;
|
|
30733
31000
|
this.deps.emitStatus(status);
|
|
31001
|
+
for (const handler of this.statusHandlers) handler(status);
|
|
30734
31002
|
}
|
|
30735
31003
|
async connect() {
|
|
30736
31004
|
if (this.stopped) return;
|