@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
|
@@ -31,6 +31,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
31
31
|
var payments_v2_exports = {};
|
|
32
32
|
__export(payments_v2_exports, {
|
|
33
33
|
ATTENTION_MINT_UNRESOLVED: () => ATTENTION_MINT_UNRESOLVED,
|
|
34
|
+
HEARTBEAT_CAP_MS: () => HEARTBEAT_CAP_MS,
|
|
35
|
+
HEARTBEAT_SEED_MS: () => HEARTBEAT_SEED_MS,
|
|
34
36
|
MAX_RESELECT: () => MAX_RESELECT,
|
|
35
37
|
PaymentsFacade: () => PaymentsFacade,
|
|
36
38
|
STORE_KEYS: () => STORE_KEYS,
|
|
@@ -590,6 +592,10 @@ function isValidationReject(err) {
|
|
|
590
592
|
if (e === null) return false;
|
|
591
593
|
return e.status === 422 || e.statusCode === 422 || e.code === "VALIDATION" || e.code === "VALIDATION_FAILED";
|
|
592
594
|
}
|
|
595
|
+
function retryAfterMsOf(err) {
|
|
596
|
+
const retryAfter = err !== null && typeof err === "object" ? err.retryAfter : void 0;
|
|
597
|
+
return typeof retryAfter === "number" && Number.isFinite(retryAfter) && retryAfter >= 0 ? retryAfter : null;
|
|
598
|
+
}
|
|
593
599
|
var chainRegistry = /* @__PURE__ */ new WeakMap();
|
|
594
600
|
var replayRegistry = /* @__PURE__ */ new WeakMap();
|
|
595
601
|
function registryFor(registry, kv, storeKey, make) {
|
|
@@ -1266,6 +1272,217 @@ function validatePayload(raw) {
|
|
|
1266
1272
|
return p;
|
|
1267
1273
|
}
|
|
1268
1274
|
|
|
1275
|
+
// modules/payments-v2/convergence.ts
|
|
1276
|
+
var HEARTBEAT_SEED_MS = 5e3;
|
|
1277
|
+
var HEARTBEAT_CAP_MS = 12e4;
|
|
1278
|
+
var ConvergenceHeartbeat = class {
|
|
1279
|
+
constructor(deps) {
|
|
1280
|
+
this.deps = deps;
|
|
1281
|
+
}
|
|
1282
|
+
timer = null;
|
|
1283
|
+
delayMs = HEARTBEAT_SEED_MS;
|
|
1284
|
+
notBefore = 0;
|
|
1285
|
+
running = false;
|
|
1286
|
+
start() {
|
|
1287
|
+
this.running = true;
|
|
1288
|
+
this.delayMs = HEARTBEAT_SEED_MS;
|
|
1289
|
+
this.notBefore = 0;
|
|
1290
|
+
}
|
|
1291
|
+
/** §7 quiescence unchanged: a mid-backoff stop() cancels cleanly. */
|
|
1292
|
+
stop() {
|
|
1293
|
+
this.running = false;
|
|
1294
|
+
this.clearTimer();
|
|
1295
|
+
}
|
|
1296
|
+
/** Reset to seed on progress, ×2 to the cap otherwise; idle stops rescheduling. */
|
|
1297
|
+
settle(outcome, pending) {
|
|
1298
|
+
if (outcome.retryAfterMs !== null) {
|
|
1299
|
+
this.notBefore = Math.max(this.notBefore, this.deps.now() + outcome.retryAfterMs);
|
|
1300
|
+
}
|
|
1301
|
+
if (!pending) {
|
|
1302
|
+
this.delayMs = HEARTBEAT_SEED_MS;
|
|
1303
|
+
return;
|
|
1304
|
+
}
|
|
1305
|
+
const delay = outcome.progress ? HEARTBEAT_SEED_MS : this.delayMs;
|
|
1306
|
+
this.delayMs = Math.min(delay * 2, HEARTBEAT_CAP_MS);
|
|
1307
|
+
this.schedule(delay);
|
|
1308
|
+
}
|
|
1309
|
+
/** New pending work (keep-open park / journaled delivery / mint hold): prompt retry. */
|
|
1310
|
+
arm() {
|
|
1311
|
+
if (!this.running) return;
|
|
1312
|
+
this.delayMs = Math.min(HEARTBEAT_SEED_MS * 2, HEARTBEAT_CAP_MS);
|
|
1313
|
+
this.schedule(HEARTBEAT_SEED_MS);
|
|
1314
|
+
}
|
|
1315
|
+
/** connection:status recovery: the backoff resets; a pending tick re-fires at the seed. */
|
|
1316
|
+
recovered() {
|
|
1317
|
+
this.delayMs = HEARTBEAT_SEED_MS;
|
|
1318
|
+
if (this.timer !== null) this.arm();
|
|
1319
|
+
}
|
|
1320
|
+
schedule(delayMs) {
|
|
1321
|
+
this.clearTimer();
|
|
1322
|
+
if (!this.running) return;
|
|
1323
|
+
const floored = Math.max(delayMs, this.notBefore - this.deps.now());
|
|
1324
|
+
this.timer = setTimeout(() => {
|
|
1325
|
+
this.timer = null;
|
|
1326
|
+
this.deps.onTick();
|
|
1327
|
+
}, floored);
|
|
1328
|
+
}
|
|
1329
|
+
clearTimer() {
|
|
1330
|
+
if (this.timer !== null) clearTimeout(this.timer);
|
|
1331
|
+
this.timer = null;
|
|
1332
|
+
}
|
|
1333
|
+
};
|
|
1334
|
+
var Converger = class {
|
|
1335
|
+
constructor(deps) {
|
|
1336
|
+
this.deps = deps;
|
|
1337
|
+
}
|
|
1338
|
+
/** Resume open intents, then drain the delivery + mint journals (all idempotent). */
|
|
1339
|
+
async convergeOnce() {
|
|
1340
|
+
let progress = false;
|
|
1341
|
+
let retryAfterMs = null;
|
|
1342
|
+
try {
|
|
1343
|
+
progress = await this.resumePass();
|
|
1344
|
+
} catch (err) {
|
|
1345
|
+
retryAfterMs = retryAfterMsOf(err);
|
|
1346
|
+
}
|
|
1347
|
+
try {
|
|
1348
|
+
const stats = await this.deps.stores.deliveryJournal.replay(this.replayDeps());
|
|
1349
|
+
progress = progress || stats.delivered > 0;
|
|
1350
|
+
} catch (err) {
|
|
1351
|
+
retryAfterMs ??= retryAfterMsOf(err);
|
|
1352
|
+
}
|
|
1353
|
+
try {
|
|
1354
|
+
progress = await this.deps.replayMints() > 0 || progress;
|
|
1355
|
+
} catch (err) {
|
|
1356
|
+
retryAfterMs ??= retryAfterMsOf(err);
|
|
1357
|
+
}
|
|
1358
|
+
return { progress, retryAfterMs };
|
|
1359
|
+
}
|
|
1360
|
+
/** §4 heartbeat predicate — read fresh from the §6 stores, never cached. */
|
|
1361
|
+
async pendingWork() {
|
|
1362
|
+
const [backstop, journal, mints] = await Promise.all([
|
|
1363
|
+
this.deps.stores.backstop.list(),
|
|
1364
|
+
this.deps.stores.deliveryJournal.list(),
|
|
1365
|
+
this.deps.stores.mintJournal.list()
|
|
1366
|
+
]);
|
|
1367
|
+
return backstop.length > 0 || journal.some((e) => e.undeliverable !== true) || mints.length > 0;
|
|
1368
|
+
}
|
|
1369
|
+
async resumePass() {
|
|
1370
|
+
const report = await resumeAll(this.filteredMachineDeps());
|
|
1371
|
+
const progress = report.resumed.length > 0 || report.conflicted.length > 0;
|
|
1372
|
+
await this.deps.reconcile({
|
|
1373
|
+
resumed: report.resumed,
|
|
1374
|
+
conflicted: report.conflicted,
|
|
1375
|
+
open: report.failed
|
|
1376
|
+
});
|
|
1377
|
+
if (progress) await this.deps.refreshView().catch(() => void 0);
|
|
1378
|
+
return progress;
|
|
1379
|
+
}
|
|
1380
|
+
filteredMachineDeps() {
|
|
1381
|
+
const intents = this.deps.machineDeps.intents;
|
|
1382
|
+
return {
|
|
1383
|
+
...this.deps.machineDeps,
|
|
1384
|
+
intents: {
|
|
1385
|
+
...intents,
|
|
1386
|
+
listOpen: async () => (await intents.listOpen()).filter((intent) => !this.deps.isActiveOp(intent.transferId))
|
|
1387
|
+
}
|
|
1388
|
+
};
|
|
1389
|
+
}
|
|
1390
|
+
replayDeps() {
|
|
1391
|
+
return {
|
|
1392
|
+
delivery: this.deps.delivery,
|
|
1393
|
+
now: () => this.deps.now(),
|
|
1394
|
+
attention: (transferId, code, detail) => {
|
|
1395
|
+
this.deps.emit(
|
|
1396
|
+
"transfer:attention",
|
|
1397
|
+
detail === void 0 ? { transferId, code } : { transferId, code, detail }
|
|
1398
|
+
);
|
|
1399
|
+
}
|
|
1400
|
+
};
|
|
1401
|
+
}
|
|
1402
|
+
};
|
|
1403
|
+
async function derivePendingTransfers(stores, decryptPayload) {
|
|
1404
|
+
const [backstop, journal, shortfalls] = await Promise.all([
|
|
1405
|
+
stores.backstop.list(),
|
|
1406
|
+
stores.deliveryJournal.list(),
|
|
1407
|
+
stores.shortfalls.list()
|
|
1408
|
+
]);
|
|
1409
|
+
const byTransfer = groupJournal(journal);
|
|
1410
|
+
const out = [];
|
|
1411
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1412
|
+
for (const entry of backstop) {
|
|
1413
|
+
if (entry.disposition !== "open") continue;
|
|
1414
|
+
seen.add(entry.transferId);
|
|
1415
|
+
out.push(await openRow(entry, byTransfer.get(entry.transferId) ?? [], decryptPayload));
|
|
1416
|
+
}
|
|
1417
|
+
for (const [transferId, entries] of byTransfer) {
|
|
1418
|
+
if (seen.has(transferId)) continue;
|
|
1419
|
+
seen.add(transferId);
|
|
1420
|
+
out.push(journalOnlyRow(transferId, entries));
|
|
1421
|
+
}
|
|
1422
|
+
for (const entry of shortfalls) {
|
|
1423
|
+
if (!seen.has(entry.transferId)) out.push(shortfallRow(entry));
|
|
1424
|
+
}
|
|
1425
|
+
return out;
|
|
1426
|
+
}
|
|
1427
|
+
function groupJournal(journal) {
|
|
1428
|
+
const byTransfer = /* @__PURE__ */ new Map();
|
|
1429
|
+
for (const entry of journal) {
|
|
1430
|
+
const mine = byTransfer.get(entry.transferId);
|
|
1431
|
+
if (mine === void 0) byTransfer.set(entry.transferId, [entry]);
|
|
1432
|
+
else mine.push(entry);
|
|
1433
|
+
}
|
|
1434
|
+
return byTransfer;
|
|
1435
|
+
}
|
|
1436
|
+
async function openRow(entry, journal, decryptPayload) {
|
|
1437
|
+
let payload = null;
|
|
1438
|
+
try {
|
|
1439
|
+
const raw = await decryptPayload(entry.payloadEnvelope);
|
|
1440
|
+
if (raw !== null && typeof raw === "object") payload = raw;
|
|
1441
|
+
} catch {
|
|
1442
|
+
payload = null;
|
|
1443
|
+
}
|
|
1444
|
+
const total = Array.isArray(payload?.direct) ? payload.direct.length + (payload.split !== void 0 ? 1 : 0) : 0;
|
|
1445
|
+
const certified = distinctOpCount(journal);
|
|
1446
|
+
return {
|
|
1447
|
+
transferId: entry.transferId,
|
|
1448
|
+
kind: "open",
|
|
1449
|
+
recipient: typeof payload?.recipient === "string" ? payload.recipient : "",
|
|
1450
|
+
coinId: typeof payload?.coinId === "string" ? payload.coinId : "",
|
|
1451
|
+
amount: typeof payload?.amount === "string" ? payload.amount : "",
|
|
1452
|
+
legs: { certified, total: Math.max(total, certified) },
|
|
1453
|
+
deliveryPending: journal.length > 0,
|
|
1454
|
+
createdAt: entry.createdAt
|
|
1455
|
+
};
|
|
1456
|
+
}
|
|
1457
|
+
function journalOnlyRow(transferId, entries) {
|
|
1458
|
+
const certified = distinctOpCount(entries);
|
|
1459
|
+
return {
|
|
1460
|
+
transferId,
|
|
1461
|
+
kind: "open",
|
|
1462
|
+
recipient: entries[0].recipientPubkey,
|
|
1463
|
+
coinId: "",
|
|
1464
|
+
amount: "",
|
|
1465
|
+
legs: { certified, total: certified },
|
|
1466
|
+
deliveryPending: true,
|
|
1467
|
+
createdAt: 0
|
|
1468
|
+
};
|
|
1469
|
+
}
|
|
1470
|
+
function shortfallRow(entry) {
|
|
1471
|
+
return {
|
|
1472
|
+
transferId: entry.transferId,
|
|
1473
|
+
kind: "shortfall",
|
|
1474
|
+
recipient: entry.recipient,
|
|
1475
|
+
coinId: entry.coinId,
|
|
1476
|
+
amount: entry.remainingAmount,
|
|
1477
|
+
legs: { certified: entry.committedTokenIds.length, total: entry.committedTokenIds.length },
|
|
1478
|
+
deliveryPending: false,
|
|
1479
|
+
createdAt: entry.createdAt
|
|
1480
|
+
};
|
|
1481
|
+
}
|
|
1482
|
+
function distinctOpCount(entries) {
|
|
1483
|
+
return new Set(entries.map((entry) => entry.opIndex)).size;
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1269
1486
|
// node_modules/@noble/ciphers/utils.js
|
|
1270
1487
|
function isBytes2(a) {
|
|
1271
1488
|
return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array" && "BYTES_PER_ELEMENT" in a && a.BYTES_PER_ELEMENT === 1;
|
|
@@ -3597,6 +3814,21 @@ var PaymentsFacade = class {
|
|
|
3597
3814
|
this.ownPubkeyBytes = parts.ownPubkeyBytes;
|
|
3598
3815
|
this.requests = parts.requests;
|
|
3599
3816
|
deps.deliveryPort.bindDeliveryKeys((blob) => this.engine().deliveryKeys(blob));
|
|
3817
|
+
this.heartbeat = new ConvergenceHeartbeat({
|
|
3818
|
+
now: () => this.nowMs(),
|
|
3819
|
+
onTick: () => this.trackTail(this.runConvergencePass())
|
|
3820
|
+
});
|
|
3821
|
+
this.converger = new Converger({
|
|
3822
|
+
machineDeps: this.machineDeps,
|
|
3823
|
+
stores: this.machineStores,
|
|
3824
|
+
delivery: deps.deliveryPort,
|
|
3825
|
+
reconcile: (report) => this.requests.reconcile(report),
|
|
3826
|
+
refreshView: () => this.view.delta(),
|
|
3827
|
+
replayMints: () => this.replayMints(),
|
|
3828
|
+
isActiveOp: (id) => this.activeMoneyOps.has(id),
|
|
3829
|
+
emit: deps.emit,
|
|
3830
|
+
now: () => this.nowMs()
|
|
3831
|
+
});
|
|
3600
3832
|
}
|
|
3601
3833
|
view;
|
|
3602
3834
|
ledger;
|
|
@@ -3613,10 +3845,18 @@ var PaymentsFacade = class {
|
|
|
3613
3845
|
pendingOps = /* @__PURE__ */ new Set();
|
|
3614
3846
|
unsubscribers = [];
|
|
3615
3847
|
started = false;
|
|
3848
|
+
// §7 heartbeat: in-memory timing cell + pass runner (see convergence.ts).
|
|
3849
|
+
heartbeat;
|
|
3850
|
+
converger;
|
|
3851
|
+
/** Resume single-flight (§7): ticks, start() and resumeNow() coalesce onto ONE pass. */
|
|
3852
|
+
resumeFlight = new SingleFlight();
|
|
3853
|
+
/** §7 ownership: ids with an in-process machine attempt — the pass never adopts one. */
|
|
3854
|
+
activeMoneyOps = /* @__PURE__ */ new Set();
|
|
3616
3855
|
// ── lifecycle ──────────────────────────────────────────────────────────────
|
|
3617
3856
|
async start() {
|
|
3618
3857
|
if (this.started) return;
|
|
3619
3858
|
this.started = true;
|
|
3859
|
+
this.heartbeat.start();
|
|
3620
3860
|
await this.deps.session.start();
|
|
3621
3861
|
this.unsubscribers.push(
|
|
3622
3862
|
this.deps.session.subscribeStream("mailbox", () => {
|
|
@@ -3629,20 +3869,11 @@ var PaymentsFacade = class {
|
|
|
3629
3869
|
this.trackTail(this.requests.drainIncoming());
|
|
3630
3870
|
})
|
|
3631
3871
|
);
|
|
3632
|
-
this.
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
attention: (transferId, code, detail) => {
|
|
3638
|
-
this.deps.emit(
|
|
3639
|
-
"transfer:attention",
|
|
3640
|
-
detail === void 0 ? { transferId, code } : { transferId, code, detail }
|
|
3641
|
-
);
|
|
3642
|
-
}
|
|
3643
|
-
})
|
|
3644
|
-
);
|
|
3645
|
-
this.trackTail(this.replayMints());
|
|
3872
|
+
const statusUnsub = this.deps.session.subscribeStatus?.((status) => {
|
|
3873
|
+
if (status === "connected") this.heartbeat.recovered();
|
|
3874
|
+
});
|
|
3875
|
+
if (statusUnsub !== void 0) this.unsubscribers.push(statusUnsub);
|
|
3876
|
+
this.trackTail(this.runConvergencePass());
|
|
3646
3877
|
this.trackTail(this.seedHeldStates());
|
|
3647
3878
|
await this.view.fullPull().catch(() => void 0);
|
|
3648
3879
|
this.receiveLoop.start(this.deps.receivePollMs);
|
|
@@ -3650,6 +3881,7 @@ var PaymentsFacade = class {
|
|
|
3650
3881
|
/** §7 same-address restart gate: resolves only after in-flight ops settle. */
|
|
3651
3882
|
async stop() {
|
|
3652
3883
|
this.started = false;
|
|
3884
|
+
this.heartbeat.stop();
|
|
3653
3885
|
this.receiveLoop.stop();
|
|
3654
3886
|
for (const unsubscribe of this.unsubscribers) unsubscribe();
|
|
3655
3887
|
this.unsubscribers = [];
|
|
@@ -3675,6 +3907,10 @@ var PaymentsFacade = class {
|
|
|
3675
3907
|
history(page) {
|
|
3676
3908
|
return this.historyStore.page(page ?? {});
|
|
3677
3909
|
}
|
|
3910
|
+
/** §4 pending-transfers UI surface — derived on read, never cached (convergence.ts). */
|
|
3911
|
+
pendingTransfers() {
|
|
3912
|
+
return derivePendingTransfers(this.machineStores, this.machineDeps.decryptPayload);
|
|
3913
|
+
}
|
|
3678
3914
|
// ── money movement ─────────────────────────────────────────────────────────
|
|
3679
3915
|
send(request) {
|
|
3680
3916
|
return this.track(this.sendWithPolicy(request));
|
|
@@ -3687,6 +3923,22 @@ var PaymentsFacade = class {
|
|
|
3687
3923
|
mint(coinId, amount) {
|
|
3688
3924
|
return this.track(this.mintInner(coinId, amount));
|
|
3689
3925
|
}
|
|
3926
|
+
// §4 retry verb: coalesces onto the running pass — NEVER re-issue send() (#631/#676).
|
|
3927
|
+
async resumeNow() {
|
|
3928
|
+
if (!this.started) return;
|
|
3929
|
+
await this.track(this.runConvergencePass());
|
|
3930
|
+
}
|
|
3931
|
+
/** One single-flighted pass + its reschedule: concurrent callers coalesce. */
|
|
3932
|
+
runConvergencePass() {
|
|
3933
|
+
return this.resumeFlight.run(async () => {
|
|
3934
|
+
const outcome = await this.converger.convergeOnce();
|
|
3935
|
+
const pending = await this.converger.pendingWork().catch(() => true);
|
|
3936
|
+
this.heartbeat.settle(outcome, pending);
|
|
3937
|
+
});
|
|
3938
|
+
}
|
|
3939
|
+
nowMs() {
|
|
3940
|
+
return (this.deps.now ?? Date.now)();
|
|
3941
|
+
}
|
|
3690
3942
|
// ── send policy (§5.5 + old-loop parity; the machine stays policy-free) ────
|
|
3691
3943
|
async sendWithPolicy(request) {
|
|
3692
3944
|
const recipient = await this.requireSameNetworkRecipient(request.recipient);
|
|
@@ -3736,12 +3988,15 @@ var PaymentsFacade = class {
|
|
|
3736
3988
|
return recipient;
|
|
3737
3989
|
}
|
|
3738
3990
|
async runAttempt(ctx) {
|
|
3991
|
+
this.activeMoneyOps.add(ctx.transferId);
|
|
3739
3992
|
try {
|
|
3740
3993
|
const { deliveryPending } = await this.machine.run(ctx.plan);
|
|
3741
3994
|
this.settleSuccess(ctx, ctx.sourceIds);
|
|
3742
3995
|
return { kind: "success", deliveryPending };
|
|
3743
3996
|
} catch (err) {
|
|
3744
|
-
return this.disposeFailedAttempt(ctx, err);
|
|
3997
|
+
return await this.disposeFailedAttempt(ctx, err);
|
|
3998
|
+
} finally {
|
|
3999
|
+
this.activeMoneyOps.delete(ctx.transferId);
|
|
3745
4000
|
}
|
|
3746
4001
|
}
|
|
3747
4002
|
/**
|
|
@@ -3750,7 +4005,10 @@ var PaymentsFacade = class {
|
|
|
3750
4005
|
* the remainder under a NEW transferId — never the full amount.
|
|
3751
4006
|
*/
|
|
3752
4007
|
async consumePartial(ctx, disposition, run, attempt) {
|
|
3753
|
-
|
|
4008
|
+
if (disposition.deliveryPending) {
|
|
4009
|
+
run.pendingAny = true;
|
|
4010
|
+
this.heartbeat.arm();
|
|
4011
|
+
}
|
|
3754
4012
|
const partial = disposition.entry;
|
|
3755
4013
|
run.delivered.push(...partial.committedTokenIds);
|
|
3756
4014
|
run.firstPartialId ??= partial.transferId;
|
|
@@ -3908,6 +4166,7 @@ var PaymentsFacade = class {
|
|
|
3908
4166
|
/** Open intent: sources stay reserved + in-flight (§5.2) until resume adopts them. */
|
|
3909
4167
|
settleKeepOpen(ctx) {
|
|
3910
4168
|
this.queue.clearExpectedChange(ctx.transferId);
|
|
4169
|
+
this.heartbeat.arm();
|
|
3911
4170
|
}
|
|
3912
4171
|
settlePartial(ctx, partial) {
|
|
3913
4172
|
this.ledger.cancel(ctx.transferId);
|
|
@@ -3944,6 +4203,7 @@ var PaymentsFacade = class {
|
|
|
3944
4203
|
for (const id of run.settledShortfalls.splice(0)) {
|
|
3945
4204
|
await this.machineStores.shortfalls.removeByKey(id).catch(() => void 0);
|
|
3946
4205
|
}
|
|
4206
|
+
if (run.pendingAny) this.heartbeat.arm();
|
|
3947
4207
|
const result = {
|
|
3948
4208
|
id: transferId,
|
|
3949
4209
|
status: run.pendingAny ? "confirmed" : "delivered",
|
|
@@ -3988,21 +4248,29 @@ var PaymentsFacade = class {
|
|
|
3988
4248
|
if (!/^(?:[0-9a-f]{2})+$/.test(coinId) || amount <= 0n) {
|
|
3989
4249
|
return { success: false, error: "coinId must be even-length lowercase hex and amount positive" };
|
|
3990
4250
|
}
|
|
3991
|
-
const engine = this.engine();
|
|
3992
4251
|
const mintId = this.newId();
|
|
3993
|
-
|
|
4252
|
+
this.activeMoneyOps.add(mintId);
|
|
4253
|
+
try {
|
|
4254
|
+
return await this.mintUnderJournal(mintId, coinId, amount);
|
|
4255
|
+
} finally {
|
|
4256
|
+
this.activeMoneyOps.delete(mintId);
|
|
4257
|
+
}
|
|
4258
|
+
}
|
|
4259
|
+
async mintUnderJournal(mintId, coinId, amount) {
|
|
4260
|
+
const engine = this.engine();
|
|
3994
4261
|
const entry = {
|
|
3995
4262
|
mintId,
|
|
3996
4263
|
coinId,
|
|
3997
4264
|
amount: amount.toString(),
|
|
3998
4265
|
tokenId: "",
|
|
3999
|
-
createdAt:
|
|
4266
|
+
createdAt: this.nowMs()
|
|
4000
4267
|
};
|
|
4001
4268
|
await this.machineStores.mintJournal.upsert(entry);
|
|
4002
4269
|
let token;
|
|
4003
4270
|
try {
|
|
4004
|
-
token = await engine.mint(
|
|
4271
|
+
token = await engine.mint(this.mintParams(coinId, amount), { transferId: mintId });
|
|
4005
4272
|
} catch (err) {
|
|
4273
|
+
this.heartbeat.arm();
|
|
4006
4274
|
return { success: false, error: messageOf(err) };
|
|
4007
4275
|
}
|
|
4008
4276
|
if (token.blob.tokenId !== entry.tokenId) {
|
|
@@ -4011,6 +4279,7 @@ var PaymentsFacade = class {
|
|
|
4011
4279
|
try {
|
|
4012
4280
|
await this.finalizeMint(engine, mintId, token, coinId, amount.toString());
|
|
4013
4281
|
} catch (err) {
|
|
4282
|
+
this.heartbeat.arm();
|
|
4014
4283
|
return { success: false, tokenId: token.blob.tokenId, error: messageOf(err) };
|
|
4015
4284
|
}
|
|
4016
4285
|
return { success: true, tokenId: token.blob.tokenId };
|
|
@@ -4033,18 +4302,22 @@ var PaymentsFacade = class {
|
|
|
4033
4302
|
this.heldStates.set(token.blob.tokenId, (await engine.deliveryKeys(bytes)).stateHash);
|
|
4034
4303
|
this.trackTail(this.view.delta());
|
|
4035
4304
|
}
|
|
4305
|
+
/** @returns how many journal entries were RESOLVED (cleared) — heartbeat progress. */
|
|
4036
4306
|
async replayMints() {
|
|
4307
|
+
let resolved = 0;
|
|
4037
4308
|
for (const entry of await this.machineStores.mintJournal.list()) {
|
|
4309
|
+
if (this.activeMoneyOps.has(entry.mintId)) continue;
|
|
4038
4310
|
try {
|
|
4039
|
-
await this.replayMint(entry);
|
|
4311
|
+
if (await this.replayMint(entry)) resolved += 1;
|
|
4040
4312
|
} catch {
|
|
4041
4313
|
}
|
|
4042
4314
|
}
|
|
4315
|
+
return resolved;
|
|
4043
4316
|
}
|
|
4044
4317
|
async replayMint(entry) {
|
|
4045
4318
|
if (entry.tokenId !== "" && await this.tokenInServerInventory(entry.tokenId)) {
|
|
4046
4319
|
await this.machineStores.mintJournal.removeByKey(entry.mintId);
|
|
4047
|
-
return;
|
|
4320
|
+
return true;
|
|
4048
4321
|
}
|
|
4049
4322
|
const engine = this.engine();
|
|
4050
4323
|
if (!supportsDeterministicMint(engine)) {
|
|
@@ -4052,7 +4325,7 @@ var PaymentsFacade = class {
|
|
|
4052
4325
|
transferId: entry.mintId,
|
|
4053
4326
|
code: ATTENTION_MINT_UNRESOLVED
|
|
4054
4327
|
});
|
|
4055
|
-
return;
|
|
4328
|
+
return false;
|
|
4056
4329
|
}
|
|
4057
4330
|
const token = await engine.mint(this.mintParams(entry.coinId, BigInt(entry.amount)), {
|
|
4058
4331
|
transferId: entry.mintId
|
|
@@ -4061,6 +4334,7 @@ var PaymentsFacade = class {
|
|
|
4061
4334
|
await this.machineStores.mintJournal.upsert({ ...entry, tokenId: token.blob.tokenId });
|
|
4062
4335
|
}
|
|
4063
4336
|
await this.finalizeMint(engine, entry.mintId, token, entry.coinId, entry.amount);
|
|
4337
|
+
return true;
|
|
4064
4338
|
}
|
|
4065
4339
|
mintParams(coinId, amount) {
|
|
4066
4340
|
return {
|
|
@@ -4086,17 +4360,6 @@ var PaymentsFacade = class {
|
|
|
4086
4360
|
newId() {
|
|
4087
4361
|
return (this.deps.newId ?? randomUUID)();
|
|
4088
4362
|
}
|
|
4089
|
-
async runResume() {
|
|
4090
|
-
const report = await resumeAll(this.machineDeps);
|
|
4091
|
-
await this.requests.reconcile({
|
|
4092
|
-
resumed: report.resumed,
|
|
4093
|
-
conflicted: report.conflicted,
|
|
4094
|
-
open: report.failed
|
|
4095
|
-
});
|
|
4096
|
-
if (report.resumed.length > 0 || report.conflicted.length > 0) {
|
|
4097
|
-
await this.view.delta().catch(() => void 0);
|
|
4098
|
-
}
|
|
4099
|
-
}
|
|
4100
4363
|
async seedHeldStates() {
|
|
4101
4364
|
let page = await this.deps.storagePort.listInventory();
|
|
4102
4365
|
for (let i = 0; i < INVENTORY_SCAN_PAGE_LIMIT; i++) {
|
|
@@ -4110,7 +4373,7 @@ var PaymentsFacade = class {
|
|
|
4110
4373
|
toUiToken(tokenId, coinId, amount) {
|
|
4111
4374
|
const registry = this.deps.registry;
|
|
4112
4375
|
const iconUrl = registry.getIconUrl(coinId);
|
|
4113
|
-
const now =
|
|
4376
|
+
const now = this.nowMs();
|
|
4114
4377
|
return {
|
|
4115
4378
|
id: tokenId,
|
|
4116
4379
|
coinId,
|
|
@@ -4162,6 +4425,8 @@ function messageOf(err) {
|
|
|
4162
4425
|
// Annotate the CommonJS export names for ESM import in node:
|
|
4163
4426
|
0 && (module.exports = {
|
|
4164
4427
|
ATTENTION_MINT_UNRESOLVED,
|
|
4428
|
+
HEARTBEAT_CAP_MS,
|
|
4429
|
+
HEARTBEAT_SEED_MS,
|
|
4165
4430
|
MAX_RESELECT,
|
|
4166
4431
|
PaymentsFacade,
|
|
4167
4432
|
STORE_KEYS,
|