@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/core/index.cjs
CHANGED
|
@@ -26077,6 +26077,10 @@ function isValidationReject(err) {
|
|
|
26077
26077
|
if (e === null) return false;
|
|
26078
26078
|
return e.status === 422 || e.statusCode === 422 || e.code === "VALIDATION" || e.code === "VALIDATION_FAILED";
|
|
26079
26079
|
}
|
|
26080
|
+
function retryAfterMsOf(err) {
|
|
26081
|
+
const retryAfter = err !== null && typeof err === "object" ? err.retryAfter : void 0;
|
|
26082
|
+
return typeof retryAfter === "number" && Number.isFinite(retryAfter) && retryAfter >= 0 ? retryAfter : null;
|
|
26083
|
+
}
|
|
26080
26084
|
var chainRegistry = /* @__PURE__ */ new WeakMap();
|
|
26081
26085
|
var replayRegistry = /* @__PURE__ */ new WeakMap();
|
|
26082
26086
|
function registryFor(registry, kv, storeKey, make) {
|
|
@@ -26714,6 +26718,217 @@ function validatePayload(raw) {
|
|
|
26714
26718
|
return p;
|
|
26715
26719
|
}
|
|
26716
26720
|
|
|
26721
|
+
// modules/payments-v2/convergence.ts
|
|
26722
|
+
var HEARTBEAT_SEED_MS = 5e3;
|
|
26723
|
+
var HEARTBEAT_CAP_MS = 12e4;
|
|
26724
|
+
var ConvergenceHeartbeat = class {
|
|
26725
|
+
constructor(deps) {
|
|
26726
|
+
this.deps = deps;
|
|
26727
|
+
}
|
|
26728
|
+
timer = null;
|
|
26729
|
+
delayMs = HEARTBEAT_SEED_MS;
|
|
26730
|
+
notBefore = 0;
|
|
26731
|
+
running = false;
|
|
26732
|
+
start() {
|
|
26733
|
+
this.running = true;
|
|
26734
|
+
this.delayMs = HEARTBEAT_SEED_MS;
|
|
26735
|
+
this.notBefore = 0;
|
|
26736
|
+
}
|
|
26737
|
+
/** §7 quiescence unchanged: a mid-backoff stop() cancels cleanly. */
|
|
26738
|
+
stop() {
|
|
26739
|
+
this.running = false;
|
|
26740
|
+
this.clearTimer();
|
|
26741
|
+
}
|
|
26742
|
+
/** Reset to seed on progress, ×2 to the cap otherwise; idle stops rescheduling. */
|
|
26743
|
+
settle(outcome, pending) {
|
|
26744
|
+
if (outcome.retryAfterMs !== null) {
|
|
26745
|
+
this.notBefore = Math.max(this.notBefore, this.deps.now() + outcome.retryAfterMs);
|
|
26746
|
+
}
|
|
26747
|
+
if (!pending) {
|
|
26748
|
+
this.delayMs = HEARTBEAT_SEED_MS;
|
|
26749
|
+
return;
|
|
26750
|
+
}
|
|
26751
|
+
const delay = outcome.progress ? HEARTBEAT_SEED_MS : this.delayMs;
|
|
26752
|
+
this.delayMs = Math.min(delay * 2, HEARTBEAT_CAP_MS);
|
|
26753
|
+
this.schedule(delay);
|
|
26754
|
+
}
|
|
26755
|
+
/** New pending work (keep-open park / journaled delivery / mint hold): prompt retry. */
|
|
26756
|
+
arm() {
|
|
26757
|
+
if (!this.running) return;
|
|
26758
|
+
this.delayMs = Math.min(HEARTBEAT_SEED_MS * 2, HEARTBEAT_CAP_MS);
|
|
26759
|
+
this.schedule(HEARTBEAT_SEED_MS);
|
|
26760
|
+
}
|
|
26761
|
+
/** connection:status recovery: the backoff resets; a pending tick re-fires at the seed. */
|
|
26762
|
+
recovered() {
|
|
26763
|
+
this.delayMs = HEARTBEAT_SEED_MS;
|
|
26764
|
+
if (this.timer !== null) this.arm();
|
|
26765
|
+
}
|
|
26766
|
+
schedule(delayMs) {
|
|
26767
|
+
this.clearTimer();
|
|
26768
|
+
if (!this.running) return;
|
|
26769
|
+
const floored = Math.max(delayMs, this.notBefore - this.deps.now());
|
|
26770
|
+
this.timer = setTimeout(() => {
|
|
26771
|
+
this.timer = null;
|
|
26772
|
+
this.deps.onTick();
|
|
26773
|
+
}, floored);
|
|
26774
|
+
}
|
|
26775
|
+
clearTimer() {
|
|
26776
|
+
if (this.timer !== null) clearTimeout(this.timer);
|
|
26777
|
+
this.timer = null;
|
|
26778
|
+
}
|
|
26779
|
+
};
|
|
26780
|
+
var Converger = class {
|
|
26781
|
+
constructor(deps) {
|
|
26782
|
+
this.deps = deps;
|
|
26783
|
+
}
|
|
26784
|
+
/** Resume open intents, then drain the delivery + mint journals (all idempotent). */
|
|
26785
|
+
async convergeOnce() {
|
|
26786
|
+
let progress = false;
|
|
26787
|
+
let retryAfterMs = null;
|
|
26788
|
+
try {
|
|
26789
|
+
progress = await this.resumePass();
|
|
26790
|
+
} catch (err) {
|
|
26791
|
+
retryAfterMs = retryAfterMsOf(err);
|
|
26792
|
+
}
|
|
26793
|
+
try {
|
|
26794
|
+
const stats = await this.deps.stores.deliveryJournal.replay(this.replayDeps());
|
|
26795
|
+
progress = progress || stats.delivered > 0;
|
|
26796
|
+
} catch (err) {
|
|
26797
|
+
retryAfterMs ??= retryAfterMsOf(err);
|
|
26798
|
+
}
|
|
26799
|
+
try {
|
|
26800
|
+
progress = await this.deps.replayMints() > 0 || progress;
|
|
26801
|
+
} catch (err) {
|
|
26802
|
+
retryAfterMs ??= retryAfterMsOf(err);
|
|
26803
|
+
}
|
|
26804
|
+
return { progress, retryAfterMs };
|
|
26805
|
+
}
|
|
26806
|
+
/** §4 heartbeat predicate — read fresh from the §6 stores, never cached. */
|
|
26807
|
+
async pendingWork() {
|
|
26808
|
+
const [backstop, journal, mints] = await Promise.all([
|
|
26809
|
+
this.deps.stores.backstop.list(),
|
|
26810
|
+
this.deps.stores.deliveryJournal.list(),
|
|
26811
|
+
this.deps.stores.mintJournal.list()
|
|
26812
|
+
]);
|
|
26813
|
+
return backstop.length > 0 || journal.some((e) => e.undeliverable !== true) || mints.length > 0;
|
|
26814
|
+
}
|
|
26815
|
+
async resumePass() {
|
|
26816
|
+
const report = await resumeAll(this.filteredMachineDeps());
|
|
26817
|
+
const progress = report.resumed.length > 0 || report.conflicted.length > 0;
|
|
26818
|
+
await this.deps.reconcile({
|
|
26819
|
+
resumed: report.resumed,
|
|
26820
|
+
conflicted: report.conflicted,
|
|
26821
|
+
open: report.failed
|
|
26822
|
+
});
|
|
26823
|
+
if (progress) await this.deps.refreshView().catch(() => void 0);
|
|
26824
|
+
return progress;
|
|
26825
|
+
}
|
|
26826
|
+
filteredMachineDeps() {
|
|
26827
|
+
const intents = this.deps.machineDeps.intents;
|
|
26828
|
+
return {
|
|
26829
|
+
...this.deps.machineDeps,
|
|
26830
|
+
intents: {
|
|
26831
|
+
...intents,
|
|
26832
|
+
listOpen: async () => (await intents.listOpen()).filter((intent) => !this.deps.isActiveOp(intent.transferId))
|
|
26833
|
+
}
|
|
26834
|
+
};
|
|
26835
|
+
}
|
|
26836
|
+
replayDeps() {
|
|
26837
|
+
return {
|
|
26838
|
+
delivery: this.deps.delivery,
|
|
26839
|
+
now: () => this.deps.now(),
|
|
26840
|
+
attention: (transferId, code, detail) => {
|
|
26841
|
+
this.deps.emit(
|
|
26842
|
+
"transfer:attention",
|
|
26843
|
+
detail === void 0 ? { transferId, code } : { transferId, code, detail }
|
|
26844
|
+
);
|
|
26845
|
+
}
|
|
26846
|
+
};
|
|
26847
|
+
}
|
|
26848
|
+
};
|
|
26849
|
+
async function derivePendingTransfers(stores, decryptPayload) {
|
|
26850
|
+
const [backstop, journal, shortfalls] = await Promise.all([
|
|
26851
|
+
stores.backstop.list(),
|
|
26852
|
+
stores.deliveryJournal.list(),
|
|
26853
|
+
stores.shortfalls.list()
|
|
26854
|
+
]);
|
|
26855
|
+
const byTransfer = groupJournal(journal);
|
|
26856
|
+
const out = [];
|
|
26857
|
+
const seen = /* @__PURE__ */ new Set();
|
|
26858
|
+
for (const entry of backstop) {
|
|
26859
|
+
if (entry.disposition !== "open") continue;
|
|
26860
|
+
seen.add(entry.transferId);
|
|
26861
|
+
out.push(await openRow(entry, byTransfer.get(entry.transferId) ?? [], decryptPayload));
|
|
26862
|
+
}
|
|
26863
|
+
for (const [transferId, entries] of byTransfer) {
|
|
26864
|
+
if (seen.has(transferId)) continue;
|
|
26865
|
+
seen.add(transferId);
|
|
26866
|
+
out.push(journalOnlyRow(transferId, entries));
|
|
26867
|
+
}
|
|
26868
|
+
for (const entry of shortfalls) {
|
|
26869
|
+
if (!seen.has(entry.transferId)) out.push(shortfallRow(entry));
|
|
26870
|
+
}
|
|
26871
|
+
return out;
|
|
26872
|
+
}
|
|
26873
|
+
function groupJournal(journal) {
|
|
26874
|
+
const byTransfer = /* @__PURE__ */ new Map();
|
|
26875
|
+
for (const entry of journal) {
|
|
26876
|
+
const mine = byTransfer.get(entry.transferId);
|
|
26877
|
+
if (mine === void 0) byTransfer.set(entry.transferId, [entry]);
|
|
26878
|
+
else mine.push(entry);
|
|
26879
|
+
}
|
|
26880
|
+
return byTransfer;
|
|
26881
|
+
}
|
|
26882
|
+
async function openRow(entry, journal, decryptPayload) {
|
|
26883
|
+
let payload = null;
|
|
26884
|
+
try {
|
|
26885
|
+
const raw = await decryptPayload(entry.payloadEnvelope);
|
|
26886
|
+
if (raw !== null && typeof raw === "object") payload = raw;
|
|
26887
|
+
} catch {
|
|
26888
|
+
payload = null;
|
|
26889
|
+
}
|
|
26890
|
+
const total = Array.isArray(payload?.direct) ? payload.direct.length + (payload.split !== void 0 ? 1 : 0) : 0;
|
|
26891
|
+
const certified = distinctOpCount(journal);
|
|
26892
|
+
return {
|
|
26893
|
+
transferId: entry.transferId,
|
|
26894
|
+
kind: "open",
|
|
26895
|
+
recipient: typeof payload?.recipient === "string" ? payload.recipient : "",
|
|
26896
|
+
coinId: typeof payload?.coinId === "string" ? payload.coinId : "",
|
|
26897
|
+
amount: typeof payload?.amount === "string" ? payload.amount : "",
|
|
26898
|
+
legs: { certified, total: Math.max(total, certified) },
|
|
26899
|
+
deliveryPending: journal.length > 0,
|
|
26900
|
+
createdAt: entry.createdAt
|
|
26901
|
+
};
|
|
26902
|
+
}
|
|
26903
|
+
function journalOnlyRow(transferId, entries) {
|
|
26904
|
+
const certified = distinctOpCount(entries);
|
|
26905
|
+
return {
|
|
26906
|
+
transferId,
|
|
26907
|
+
kind: "open",
|
|
26908
|
+
recipient: entries[0].recipientPubkey,
|
|
26909
|
+
coinId: "",
|
|
26910
|
+
amount: "",
|
|
26911
|
+
legs: { certified, total: certified },
|
|
26912
|
+
deliveryPending: true,
|
|
26913
|
+
createdAt: 0
|
|
26914
|
+
};
|
|
26915
|
+
}
|
|
26916
|
+
function shortfallRow(entry) {
|
|
26917
|
+
return {
|
|
26918
|
+
transferId: entry.transferId,
|
|
26919
|
+
kind: "shortfall",
|
|
26920
|
+
recipient: entry.recipient,
|
|
26921
|
+
coinId: entry.coinId,
|
|
26922
|
+
amount: entry.remainingAmount,
|
|
26923
|
+
legs: { certified: entry.committedTokenIds.length, total: entry.committedTokenIds.length },
|
|
26924
|
+
deliveryPending: false,
|
|
26925
|
+
createdAt: entry.createdAt
|
|
26926
|
+
};
|
|
26927
|
+
}
|
|
26928
|
+
function distinctOpCount(entries) {
|
|
26929
|
+
return new Set(entries.map((entry) => entry.opIndex)).size;
|
|
26930
|
+
}
|
|
26931
|
+
|
|
26717
26932
|
// modules/payments-v2/history/History.ts
|
|
26718
26933
|
var WIRE_TOKEN_ID = /^(?:[0-9a-f]{2}){1,64}$/;
|
|
26719
26934
|
var WIRE_PUBKEY = /^0[23][0-9a-f]{64}$/;
|
|
@@ -28103,6 +28318,21 @@ var PaymentsFacade = class {
|
|
|
28103
28318
|
this.ownPubkeyBytes = parts.ownPubkeyBytes;
|
|
28104
28319
|
this.requests = parts.requests;
|
|
28105
28320
|
deps.deliveryPort.bindDeliveryKeys((blob) => this.engine().deliveryKeys(blob));
|
|
28321
|
+
this.heartbeat = new ConvergenceHeartbeat({
|
|
28322
|
+
now: () => this.nowMs(),
|
|
28323
|
+
onTick: () => this.trackTail(this.runConvergencePass())
|
|
28324
|
+
});
|
|
28325
|
+
this.converger = new Converger({
|
|
28326
|
+
machineDeps: this.machineDeps,
|
|
28327
|
+
stores: this.machineStores,
|
|
28328
|
+
delivery: deps.deliveryPort,
|
|
28329
|
+
reconcile: (report) => this.requests.reconcile(report),
|
|
28330
|
+
refreshView: () => this.view.delta(),
|
|
28331
|
+
replayMints: () => this.replayMints(),
|
|
28332
|
+
isActiveOp: (id) => this.activeMoneyOps.has(id),
|
|
28333
|
+
emit: deps.emit,
|
|
28334
|
+
now: () => this.nowMs()
|
|
28335
|
+
});
|
|
28106
28336
|
}
|
|
28107
28337
|
view;
|
|
28108
28338
|
ledger;
|
|
@@ -28119,10 +28349,18 @@ var PaymentsFacade = class {
|
|
|
28119
28349
|
pendingOps = /* @__PURE__ */ new Set();
|
|
28120
28350
|
unsubscribers = [];
|
|
28121
28351
|
started = false;
|
|
28352
|
+
// §7 heartbeat: in-memory timing cell + pass runner (see convergence.ts).
|
|
28353
|
+
heartbeat;
|
|
28354
|
+
converger;
|
|
28355
|
+
/** Resume single-flight (§7): ticks, start() and resumeNow() coalesce onto ONE pass. */
|
|
28356
|
+
resumeFlight = new SingleFlight();
|
|
28357
|
+
/** §7 ownership: ids with an in-process machine attempt — the pass never adopts one. */
|
|
28358
|
+
activeMoneyOps = /* @__PURE__ */ new Set();
|
|
28122
28359
|
// ── lifecycle ──────────────────────────────────────────────────────────────
|
|
28123
28360
|
async start() {
|
|
28124
28361
|
if (this.started) return;
|
|
28125
28362
|
this.started = true;
|
|
28363
|
+
this.heartbeat.start();
|
|
28126
28364
|
await this.deps.session.start();
|
|
28127
28365
|
this.unsubscribers.push(
|
|
28128
28366
|
this.deps.session.subscribeStream("mailbox", () => {
|
|
@@ -28135,20 +28373,11 @@ var PaymentsFacade = class {
|
|
|
28135
28373
|
this.trackTail(this.requests.drainIncoming());
|
|
28136
28374
|
})
|
|
28137
28375
|
);
|
|
28138
|
-
this.
|
|
28139
|
-
|
|
28140
|
-
|
|
28141
|
-
|
|
28142
|
-
|
|
28143
|
-
attention: (transferId, code, detail) => {
|
|
28144
|
-
this.deps.emit(
|
|
28145
|
-
"transfer:attention",
|
|
28146
|
-
detail === void 0 ? { transferId, code } : { transferId, code, detail }
|
|
28147
|
-
);
|
|
28148
|
-
}
|
|
28149
|
-
})
|
|
28150
|
-
);
|
|
28151
|
-
this.trackTail(this.replayMints());
|
|
28376
|
+
const statusUnsub = this.deps.session.subscribeStatus?.((status) => {
|
|
28377
|
+
if (status === "connected") this.heartbeat.recovered();
|
|
28378
|
+
});
|
|
28379
|
+
if (statusUnsub !== void 0) this.unsubscribers.push(statusUnsub);
|
|
28380
|
+
this.trackTail(this.runConvergencePass());
|
|
28152
28381
|
this.trackTail(this.seedHeldStates());
|
|
28153
28382
|
await this.view.fullPull().catch(() => void 0);
|
|
28154
28383
|
this.receiveLoop.start(this.deps.receivePollMs);
|
|
@@ -28156,6 +28385,7 @@ var PaymentsFacade = class {
|
|
|
28156
28385
|
/** §7 same-address restart gate: resolves only after in-flight ops settle. */
|
|
28157
28386
|
async stop() {
|
|
28158
28387
|
this.started = false;
|
|
28388
|
+
this.heartbeat.stop();
|
|
28159
28389
|
this.receiveLoop.stop();
|
|
28160
28390
|
for (const unsubscribe of this.unsubscribers) unsubscribe();
|
|
28161
28391
|
this.unsubscribers = [];
|
|
@@ -28181,6 +28411,10 @@ var PaymentsFacade = class {
|
|
|
28181
28411
|
history(page) {
|
|
28182
28412
|
return this.historyStore.page(page ?? {});
|
|
28183
28413
|
}
|
|
28414
|
+
/** §4 pending-transfers UI surface — derived on read, never cached (convergence.ts). */
|
|
28415
|
+
pendingTransfers() {
|
|
28416
|
+
return derivePendingTransfers(this.machineStores, this.machineDeps.decryptPayload);
|
|
28417
|
+
}
|
|
28184
28418
|
// ── money movement ─────────────────────────────────────────────────────────
|
|
28185
28419
|
send(request) {
|
|
28186
28420
|
return this.track(this.sendWithPolicy(request));
|
|
@@ -28193,6 +28427,22 @@ var PaymentsFacade = class {
|
|
|
28193
28427
|
mint(coinId, amount) {
|
|
28194
28428
|
return this.track(this.mintInner(coinId, amount));
|
|
28195
28429
|
}
|
|
28430
|
+
// §4 retry verb: coalesces onto the running pass — NEVER re-issue send() (#631/#676).
|
|
28431
|
+
async resumeNow() {
|
|
28432
|
+
if (!this.started) return;
|
|
28433
|
+
await this.track(this.runConvergencePass());
|
|
28434
|
+
}
|
|
28435
|
+
/** One single-flighted pass + its reschedule: concurrent callers coalesce. */
|
|
28436
|
+
runConvergencePass() {
|
|
28437
|
+
return this.resumeFlight.run(async () => {
|
|
28438
|
+
const outcome = await this.converger.convergeOnce();
|
|
28439
|
+
const pending = await this.converger.pendingWork().catch(() => true);
|
|
28440
|
+
this.heartbeat.settle(outcome, pending);
|
|
28441
|
+
});
|
|
28442
|
+
}
|
|
28443
|
+
nowMs() {
|
|
28444
|
+
return (this.deps.now ?? Date.now)();
|
|
28445
|
+
}
|
|
28196
28446
|
// ── send policy (§5.5 + old-loop parity; the machine stays policy-free) ────
|
|
28197
28447
|
async sendWithPolicy(request) {
|
|
28198
28448
|
const recipient = await this.requireSameNetworkRecipient(request.recipient);
|
|
@@ -28242,12 +28492,15 @@ var PaymentsFacade = class {
|
|
|
28242
28492
|
return recipient;
|
|
28243
28493
|
}
|
|
28244
28494
|
async runAttempt(ctx) {
|
|
28495
|
+
this.activeMoneyOps.add(ctx.transferId);
|
|
28245
28496
|
try {
|
|
28246
28497
|
const { deliveryPending } = await this.machine.run(ctx.plan);
|
|
28247
28498
|
this.settleSuccess(ctx, ctx.sourceIds);
|
|
28248
28499
|
return { kind: "success", deliveryPending };
|
|
28249
28500
|
} catch (err) {
|
|
28250
|
-
return this.disposeFailedAttempt(ctx, err);
|
|
28501
|
+
return await this.disposeFailedAttempt(ctx, err);
|
|
28502
|
+
} finally {
|
|
28503
|
+
this.activeMoneyOps.delete(ctx.transferId);
|
|
28251
28504
|
}
|
|
28252
28505
|
}
|
|
28253
28506
|
/**
|
|
@@ -28256,7 +28509,10 @@ var PaymentsFacade = class {
|
|
|
28256
28509
|
* the remainder under a NEW transferId — never the full amount.
|
|
28257
28510
|
*/
|
|
28258
28511
|
async consumePartial(ctx, disposition, run, attempt) {
|
|
28259
|
-
|
|
28512
|
+
if (disposition.deliveryPending) {
|
|
28513
|
+
run.pendingAny = true;
|
|
28514
|
+
this.heartbeat.arm();
|
|
28515
|
+
}
|
|
28260
28516
|
const partial = disposition.entry;
|
|
28261
28517
|
run.delivered.push(...partial.committedTokenIds);
|
|
28262
28518
|
run.firstPartialId ??= partial.transferId;
|
|
@@ -28414,6 +28670,7 @@ var PaymentsFacade = class {
|
|
|
28414
28670
|
/** Open intent: sources stay reserved + in-flight (§5.2) until resume adopts them. */
|
|
28415
28671
|
settleKeepOpen(ctx) {
|
|
28416
28672
|
this.queue.clearExpectedChange(ctx.transferId);
|
|
28673
|
+
this.heartbeat.arm();
|
|
28417
28674
|
}
|
|
28418
28675
|
settlePartial(ctx, partial) {
|
|
28419
28676
|
this.ledger.cancel(ctx.transferId);
|
|
@@ -28450,6 +28707,7 @@ var PaymentsFacade = class {
|
|
|
28450
28707
|
for (const id of run.settledShortfalls.splice(0)) {
|
|
28451
28708
|
await this.machineStores.shortfalls.removeByKey(id).catch(() => void 0);
|
|
28452
28709
|
}
|
|
28710
|
+
if (run.pendingAny) this.heartbeat.arm();
|
|
28453
28711
|
const result = {
|
|
28454
28712
|
id: transferId,
|
|
28455
28713
|
status: run.pendingAny ? "confirmed" : "delivered",
|
|
@@ -28494,21 +28752,29 @@ var PaymentsFacade = class {
|
|
|
28494
28752
|
if (!/^(?:[0-9a-f]{2})+$/.test(coinId) || amount <= 0n) {
|
|
28495
28753
|
return { success: false, error: "coinId must be even-length lowercase hex and amount positive" };
|
|
28496
28754
|
}
|
|
28497
|
-
const engine = this.engine();
|
|
28498
28755
|
const mintId = this.newId();
|
|
28499
|
-
|
|
28756
|
+
this.activeMoneyOps.add(mintId);
|
|
28757
|
+
try {
|
|
28758
|
+
return await this.mintUnderJournal(mintId, coinId, amount);
|
|
28759
|
+
} finally {
|
|
28760
|
+
this.activeMoneyOps.delete(mintId);
|
|
28761
|
+
}
|
|
28762
|
+
}
|
|
28763
|
+
async mintUnderJournal(mintId, coinId, amount) {
|
|
28764
|
+
const engine = this.engine();
|
|
28500
28765
|
const entry = {
|
|
28501
28766
|
mintId,
|
|
28502
28767
|
coinId,
|
|
28503
28768
|
amount: amount.toString(),
|
|
28504
28769
|
tokenId: "",
|
|
28505
|
-
createdAt:
|
|
28770
|
+
createdAt: this.nowMs()
|
|
28506
28771
|
};
|
|
28507
28772
|
await this.machineStores.mintJournal.upsert(entry);
|
|
28508
28773
|
let token;
|
|
28509
28774
|
try {
|
|
28510
|
-
token = await engine.mint(
|
|
28775
|
+
token = await engine.mint(this.mintParams(coinId, amount), { transferId: mintId });
|
|
28511
28776
|
} catch (err) {
|
|
28777
|
+
this.heartbeat.arm();
|
|
28512
28778
|
return { success: false, error: messageOf(err) };
|
|
28513
28779
|
}
|
|
28514
28780
|
if (token.blob.tokenId !== entry.tokenId) {
|
|
@@ -28517,6 +28783,7 @@ var PaymentsFacade = class {
|
|
|
28517
28783
|
try {
|
|
28518
28784
|
await this.finalizeMint(engine, mintId, token, coinId, amount.toString());
|
|
28519
28785
|
} catch (err) {
|
|
28786
|
+
this.heartbeat.arm();
|
|
28520
28787
|
return { success: false, tokenId: token.blob.tokenId, error: messageOf(err) };
|
|
28521
28788
|
}
|
|
28522
28789
|
return { success: true, tokenId: token.blob.tokenId };
|
|
@@ -28539,18 +28806,22 @@ var PaymentsFacade = class {
|
|
|
28539
28806
|
this.heldStates.set(token.blob.tokenId, (await engine.deliveryKeys(bytes)).stateHash);
|
|
28540
28807
|
this.trackTail(this.view.delta());
|
|
28541
28808
|
}
|
|
28809
|
+
/** @returns how many journal entries were RESOLVED (cleared) — heartbeat progress. */
|
|
28542
28810
|
async replayMints() {
|
|
28811
|
+
let resolved = 0;
|
|
28543
28812
|
for (const entry of await this.machineStores.mintJournal.list()) {
|
|
28813
|
+
if (this.activeMoneyOps.has(entry.mintId)) continue;
|
|
28544
28814
|
try {
|
|
28545
|
-
await this.replayMint(entry);
|
|
28815
|
+
if (await this.replayMint(entry)) resolved += 1;
|
|
28546
28816
|
} catch {
|
|
28547
28817
|
}
|
|
28548
28818
|
}
|
|
28819
|
+
return resolved;
|
|
28549
28820
|
}
|
|
28550
28821
|
async replayMint(entry) {
|
|
28551
28822
|
if (entry.tokenId !== "" && await this.tokenInServerInventory(entry.tokenId)) {
|
|
28552
28823
|
await this.machineStores.mintJournal.removeByKey(entry.mintId);
|
|
28553
|
-
return;
|
|
28824
|
+
return true;
|
|
28554
28825
|
}
|
|
28555
28826
|
const engine = this.engine();
|
|
28556
28827
|
if (!supportsDeterministicMint(engine)) {
|
|
@@ -28558,7 +28829,7 @@ var PaymentsFacade = class {
|
|
|
28558
28829
|
transferId: entry.mintId,
|
|
28559
28830
|
code: ATTENTION_MINT_UNRESOLVED
|
|
28560
28831
|
});
|
|
28561
|
-
return;
|
|
28832
|
+
return false;
|
|
28562
28833
|
}
|
|
28563
28834
|
const token = await engine.mint(this.mintParams(entry.coinId, BigInt(entry.amount)), {
|
|
28564
28835
|
transferId: entry.mintId
|
|
@@ -28567,6 +28838,7 @@ var PaymentsFacade = class {
|
|
|
28567
28838
|
await this.machineStores.mintJournal.upsert({ ...entry, tokenId: token.blob.tokenId });
|
|
28568
28839
|
}
|
|
28569
28840
|
await this.finalizeMint(engine, entry.mintId, token, entry.coinId, entry.amount);
|
|
28841
|
+
return true;
|
|
28570
28842
|
}
|
|
28571
28843
|
mintParams(coinId, amount) {
|
|
28572
28844
|
return {
|
|
@@ -28592,17 +28864,6 @@ var PaymentsFacade = class {
|
|
|
28592
28864
|
newId() {
|
|
28593
28865
|
return (this.deps.newId ?? randomUUID)();
|
|
28594
28866
|
}
|
|
28595
|
-
async runResume() {
|
|
28596
|
-
const report = await resumeAll(this.machineDeps);
|
|
28597
|
-
await this.requests.reconcile({
|
|
28598
|
-
resumed: report.resumed,
|
|
28599
|
-
conflicted: report.conflicted,
|
|
28600
|
-
open: report.failed
|
|
28601
|
-
});
|
|
28602
|
-
if (report.resumed.length > 0 || report.conflicted.length > 0) {
|
|
28603
|
-
await this.view.delta().catch(() => void 0);
|
|
28604
|
-
}
|
|
28605
|
-
}
|
|
28606
28867
|
async seedHeldStates() {
|
|
28607
28868
|
let page = await this.deps.storagePort.listInventory();
|
|
28608
28869
|
for (let i = 0; i < INVENTORY_SCAN_PAGE_LIMIT; i++) {
|
|
@@ -28616,7 +28877,7 @@ var PaymentsFacade = class {
|
|
|
28616
28877
|
toUiToken(tokenId, coinId, amount) {
|
|
28617
28878
|
const registry = this.deps.registry;
|
|
28618
28879
|
const iconUrl = registry.getIconUrl(coinId);
|
|
28619
|
-
const now =
|
|
28880
|
+
const now = this.nowMs();
|
|
28620
28881
|
return {
|
|
28621
28882
|
id: tokenId,
|
|
28622
28883
|
coinId,
|
|
@@ -29011,6 +29272,7 @@ var WalletApiSession = class {
|
|
|
29011
29272
|
reseedChain = Promise.resolve();
|
|
29012
29273
|
reseedsPending = 0;
|
|
29013
29274
|
handlers = /* @__PURE__ */ new Map();
|
|
29275
|
+
statusHandlers = /* @__PURE__ */ new Set();
|
|
29014
29276
|
socket = null;
|
|
29015
29277
|
started = false;
|
|
29016
29278
|
stopped = false;
|
|
@@ -29161,10 +29423,16 @@ var WalletApiSession = class {
|
|
|
29161
29423
|
status() {
|
|
29162
29424
|
return this.lastStatus ?? "offline";
|
|
29163
29425
|
}
|
|
29426
|
+
/** Transition feed off the existing emitStatus point (FacadeSession.subscribeStatus). */
|
|
29427
|
+
subscribeStatus(handler) {
|
|
29428
|
+
this.statusHandlers.add(handler);
|
|
29429
|
+
return () => this.statusHandlers.delete(handler);
|
|
29430
|
+
}
|
|
29164
29431
|
emitStatus(status) {
|
|
29165
29432
|
if (this.stopped || this.lastStatus === status) return;
|
|
29166
29433
|
this.lastStatus = status;
|
|
29167
29434
|
this.deps.emitStatus(status);
|
|
29435
|
+
for (const handler of this.statusHandlers) handler(status);
|
|
29168
29436
|
}
|
|
29169
29437
|
async connect() {
|
|
29170
29438
|
if (this.stopped) return;
|