@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.d.cts
CHANGED
|
@@ -7432,6 +7432,25 @@ interface PaymentsRequestsApi {
|
|
|
7432
7432
|
decline(id: string): Promise<void>;
|
|
7433
7433
|
dismissProcessed(): void;
|
|
7434
7434
|
}
|
|
7435
|
+
/**
|
|
7436
|
+
* A pending-transfers row, derived ON READ from the §6 stores (intent backstop
|
|
7437
|
+
* + delivery journal + shortfalls) — never a cached mirror. kind 'shortfall' =
|
|
7438
|
+
* a completed partial (#690) whose `amount` is the remainder still owed;
|
|
7439
|
+
* legs.certified counts journaled legs (certified, delivery still owed).
|
|
7440
|
+
*/
|
|
7441
|
+
interface PendingTransfer {
|
|
7442
|
+
transferId: string;
|
|
7443
|
+
kind: 'open' | 'shortfall';
|
|
7444
|
+
recipient: string;
|
|
7445
|
+
coinId: string;
|
|
7446
|
+
amount: string;
|
|
7447
|
+
legs: {
|
|
7448
|
+
certified: number;
|
|
7449
|
+
total: number;
|
|
7450
|
+
};
|
|
7451
|
+
deliveryPending: boolean;
|
|
7452
|
+
createdAt: number;
|
|
7453
|
+
}
|
|
7435
7454
|
interface PaymentsV2 {
|
|
7436
7455
|
assets(coinId?: string): Promise<Asset[]>;
|
|
7437
7456
|
tokens(filter?: {
|
|
@@ -7446,6 +7465,8 @@ interface PaymentsV2 {
|
|
|
7446
7465
|
receive(): Promise<{
|
|
7447
7466
|
transfers: IncomingTransfer[];
|
|
7448
7467
|
}>;
|
|
7468
|
+
pendingTransfers(): Promise<PendingTransfer[]>;
|
|
7469
|
+
resumeNow(): Promise<void>;
|
|
7449
7470
|
readonly requests: PaymentsRequestsApi;
|
|
7450
7471
|
}
|
|
7451
7472
|
|
package/dist/index.d.ts
CHANGED
|
@@ -7432,6 +7432,25 @@ interface PaymentsRequestsApi {
|
|
|
7432
7432
|
decline(id: string): Promise<void>;
|
|
7433
7433
|
dismissProcessed(): void;
|
|
7434
7434
|
}
|
|
7435
|
+
/**
|
|
7436
|
+
* A pending-transfers row, derived ON READ from the §6 stores (intent backstop
|
|
7437
|
+
* + delivery journal + shortfalls) — never a cached mirror. kind 'shortfall' =
|
|
7438
|
+
* a completed partial (#690) whose `amount` is the remainder still owed;
|
|
7439
|
+
* legs.certified counts journaled legs (certified, delivery still owed).
|
|
7440
|
+
*/
|
|
7441
|
+
interface PendingTransfer {
|
|
7442
|
+
transferId: string;
|
|
7443
|
+
kind: 'open' | 'shortfall';
|
|
7444
|
+
recipient: string;
|
|
7445
|
+
coinId: string;
|
|
7446
|
+
amount: string;
|
|
7447
|
+
legs: {
|
|
7448
|
+
certified: number;
|
|
7449
|
+
total: number;
|
|
7450
|
+
};
|
|
7451
|
+
deliveryPending: boolean;
|
|
7452
|
+
createdAt: number;
|
|
7453
|
+
}
|
|
7435
7454
|
interface PaymentsV2 {
|
|
7436
7455
|
assets(coinId?: string): Promise<Asset[]>;
|
|
7437
7456
|
tokens(filter?: {
|
|
@@ -7446,6 +7465,8 @@ interface PaymentsV2 {
|
|
|
7446
7465
|
receive(): Promise<{
|
|
7447
7466
|
transfers: IncomingTransfer[];
|
|
7448
7467
|
}>;
|
|
7468
|
+
pendingTransfers(): Promise<PendingTransfer[]>;
|
|
7469
|
+
resumeNow(): Promise<void>;
|
|
7449
7470
|
readonly requests: PaymentsRequestsApi;
|
|
7450
7471
|
}
|
|
7451
7472
|
|
package/dist/index.js
CHANGED
|
@@ -27486,6 +27486,10 @@ function isValidationReject(err) {
|
|
|
27486
27486
|
if (e === null) return false;
|
|
27487
27487
|
return e.status === 422 || e.statusCode === 422 || e.code === "VALIDATION" || e.code === "VALIDATION_FAILED";
|
|
27488
27488
|
}
|
|
27489
|
+
function retryAfterMsOf(err) {
|
|
27490
|
+
const retryAfter = err !== null && typeof err === "object" ? err.retryAfter : void 0;
|
|
27491
|
+
return typeof retryAfter === "number" && Number.isFinite(retryAfter) && retryAfter >= 0 ? retryAfter : null;
|
|
27492
|
+
}
|
|
27489
27493
|
var chainRegistry = /* @__PURE__ */ new WeakMap();
|
|
27490
27494
|
var replayRegistry = /* @__PURE__ */ new WeakMap();
|
|
27491
27495
|
function registryFor(registry, kv, storeKey, make) {
|
|
@@ -28123,6 +28127,217 @@ function validatePayload(raw) {
|
|
|
28123
28127
|
return p;
|
|
28124
28128
|
}
|
|
28125
28129
|
|
|
28130
|
+
// modules/payments-v2/convergence.ts
|
|
28131
|
+
var HEARTBEAT_SEED_MS = 5e3;
|
|
28132
|
+
var HEARTBEAT_CAP_MS = 12e4;
|
|
28133
|
+
var ConvergenceHeartbeat = class {
|
|
28134
|
+
constructor(deps) {
|
|
28135
|
+
this.deps = deps;
|
|
28136
|
+
}
|
|
28137
|
+
timer = null;
|
|
28138
|
+
delayMs = HEARTBEAT_SEED_MS;
|
|
28139
|
+
notBefore = 0;
|
|
28140
|
+
running = false;
|
|
28141
|
+
start() {
|
|
28142
|
+
this.running = true;
|
|
28143
|
+
this.delayMs = HEARTBEAT_SEED_MS;
|
|
28144
|
+
this.notBefore = 0;
|
|
28145
|
+
}
|
|
28146
|
+
/** §7 quiescence unchanged: a mid-backoff stop() cancels cleanly. */
|
|
28147
|
+
stop() {
|
|
28148
|
+
this.running = false;
|
|
28149
|
+
this.clearTimer();
|
|
28150
|
+
}
|
|
28151
|
+
/** Reset to seed on progress, ×2 to the cap otherwise; idle stops rescheduling. */
|
|
28152
|
+
settle(outcome, pending) {
|
|
28153
|
+
if (outcome.retryAfterMs !== null) {
|
|
28154
|
+
this.notBefore = Math.max(this.notBefore, this.deps.now() + outcome.retryAfterMs);
|
|
28155
|
+
}
|
|
28156
|
+
if (!pending) {
|
|
28157
|
+
this.delayMs = HEARTBEAT_SEED_MS;
|
|
28158
|
+
return;
|
|
28159
|
+
}
|
|
28160
|
+
const delay = outcome.progress ? HEARTBEAT_SEED_MS : this.delayMs;
|
|
28161
|
+
this.delayMs = Math.min(delay * 2, HEARTBEAT_CAP_MS);
|
|
28162
|
+
this.schedule(delay);
|
|
28163
|
+
}
|
|
28164
|
+
/** New pending work (keep-open park / journaled delivery / mint hold): prompt retry. */
|
|
28165
|
+
arm() {
|
|
28166
|
+
if (!this.running) return;
|
|
28167
|
+
this.delayMs = Math.min(HEARTBEAT_SEED_MS * 2, HEARTBEAT_CAP_MS);
|
|
28168
|
+
this.schedule(HEARTBEAT_SEED_MS);
|
|
28169
|
+
}
|
|
28170
|
+
/** connection:status recovery: the backoff resets; a pending tick re-fires at the seed. */
|
|
28171
|
+
recovered() {
|
|
28172
|
+
this.delayMs = HEARTBEAT_SEED_MS;
|
|
28173
|
+
if (this.timer !== null) this.arm();
|
|
28174
|
+
}
|
|
28175
|
+
schedule(delayMs) {
|
|
28176
|
+
this.clearTimer();
|
|
28177
|
+
if (!this.running) return;
|
|
28178
|
+
const floored = Math.max(delayMs, this.notBefore - this.deps.now());
|
|
28179
|
+
this.timer = setTimeout(() => {
|
|
28180
|
+
this.timer = null;
|
|
28181
|
+
this.deps.onTick();
|
|
28182
|
+
}, floored);
|
|
28183
|
+
}
|
|
28184
|
+
clearTimer() {
|
|
28185
|
+
if (this.timer !== null) clearTimeout(this.timer);
|
|
28186
|
+
this.timer = null;
|
|
28187
|
+
}
|
|
28188
|
+
};
|
|
28189
|
+
var Converger = class {
|
|
28190
|
+
constructor(deps) {
|
|
28191
|
+
this.deps = deps;
|
|
28192
|
+
}
|
|
28193
|
+
/** Resume open intents, then drain the delivery + mint journals (all idempotent). */
|
|
28194
|
+
async convergeOnce() {
|
|
28195
|
+
let progress = false;
|
|
28196
|
+
let retryAfterMs = null;
|
|
28197
|
+
try {
|
|
28198
|
+
progress = await this.resumePass();
|
|
28199
|
+
} catch (err) {
|
|
28200
|
+
retryAfterMs = retryAfterMsOf(err);
|
|
28201
|
+
}
|
|
28202
|
+
try {
|
|
28203
|
+
const stats = await this.deps.stores.deliveryJournal.replay(this.replayDeps());
|
|
28204
|
+
progress = progress || stats.delivered > 0;
|
|
28205
|
+
} catch (err) {
|
|
28206
|
+
retryAfterMs ??= retryAfterMsOf(err);
|
|
28207
|
+
}
|
|
28208
|
+
try {
|
|
28209
|
+
progress = await this.deps.replayMints() > 0 || progress;
|
|
28210
|
+
} catch (err) {
|
|
28211
|
+
retryAfterMs ??= retryAfterMsOf(err);
|
|
28212
|
+
}
|
|
28213
|
+
return { progress, retryAfterMs };
|
|
28214
|
+
}
|
|
28215
|
+
/** §4 heartbeat predicate — read fresh from the §6 stores, never cached. */
|
|
28216
|
+
async pendingWork() {
|
|
28217
|
+
const [backstop, journal, mints] = await Promise.all([
|
|
28218
|
+
this.deps.stores.backstop.list(),
|
|
28219
|
+
this.deps.stores.deliveryJournal.list(),
|
|
28220
|
+
this.deps.stores.mintJournal.list()
|
|
28221
|
+
]);
|
|
28222
|
+
return backstop.length > 0 || journal.some((e) => e.undeliverable !== true) || mints.length > 0;
|
|
28223
|
+
}
|
|
28224
|
+
async resumePass() {
|
|
28225
|
+
const report = await resumeAll(this.filteredMachineDeps());
|
|
28226
|
+
const progress = report.resumed.length > 0 || report.conflicted.length > 0;
|
|
28227
|
+
await this.deps.reconcile({
|
|
28228
|
+
resumed: report.resumed,
|
|
28229
|
+
conflicted: report.conflicted,
|
|
28230
|
+
open: report.failed
|
|
28231
|
+
});
|
|
28232
|
+
if (progress) await this.deps.refreshView().catch(() => void 0);
|
|
28233
|
+
return progress;
|
|
28234
|
+
}
|
|
28235
|
+
filteredMachineDeps() {
|
|
28236
|
+
const intents = this.deps.machineDeps.intents;
|
|
28237
|
+
return {
|
|
28238
|
+
...this.deps.machineDeps,
|
|
28239
|
+
intents: {
|
|
28240
|
+
...intents,
|
|
28241
|
+
listOpen: async () => (await intents.listOpen()).filter((intent) => !this.deps.isActiveOp(intent.transferId))
|
|
28242
|
+
}
|
|
28243
|
+
};
|
|
28244
|
+
}
|
|
28245
|
+
replayDeps() {
|
|
28246
|
+
return {
|
|
28247
|
+
delivery: this.deps.delivery,
|
|
28248
|
+
now: () => this.deps.now(),
|
|
28249
|
+
attention: (transferId, code, detail) => {
|
|
28250
|
+
this.deps.emit(
|
|
28251
|
+
"transfer:attention",
|
|
28252
|
+
detail === void 0 ? { transferId, code } : { transferId, code, detail }
|
|
28253
|
+
);
|
|
28254
|
+
}
|
|
28255
|
+
};
|
|
28256
|
+
}
|
|
28257
|
+
};
|
|
28258
|
+
async function derivePendingTransfers(stores, decryptPayload) {
|
|
28259
|
+
const [backstop, journal, shortfalls] = await Promise.all([
|
|
28260
|
+
stores.backstop.list(),
|
|
28261
|
+
stores.deliveryJournal.list(),
|
|
28262
|
+
stores.shortfalls.list()
|
|
28263
|
+
]);
|
|
28264
|
+
const byTransfer = groupJournal(journal);
|
|
28265
|
+
const out = [];
|
|
28266
|
+
const seen = /* @__PURE__ */ new Set();
|
|
28267
|
+
for (const entry of backstop) {
|
|
28268
|
+
if (entry.disposition !== "open") continue;
|
|
28269
|
+
seen.add(entry.transferId);
|
|
28270
|
+
out.push(await openRow(entry, byTransfer.get(entry.transferId) ?? [], decryptPayload));
|
|
28271
|
+
}
|
|
28272
|
+
for (const [transferId, entries] of byTransfer) {
|
|
28273
|
+
if (seen.has(transferId)) continue;
|
|
28274
|
+
seen.add(transferId);
|
|
28275
|
+
out.push(journalOnlyRow(transferId, entries));
|
|
28276
|
+
}
|
|
28277
|
+
for (const entry of shortfalls) {
|
|
28278
|
+
if (!seen.has(entry.transferId)) out.push(shortfallRow(entry));
|
|
28279
|
+
}
|
|
28280
|
+
return out;
|
|
28281
|
+
}
|
|
28282
|
+
function groupJournal(journal) {
|
|
28283
|
+
const byTransfer = /* @__PURE__ */ new Map();
|
|
28284
|
+
for (const entry of journal) {
|
|
28285
|
+
const mine = byTransfer.get(entry.transferId);
|
|
28286
|
+
if (mine === void 0) byTransfer.set(entry.transferId, [entry]);
|
|
28287
|
+
else mine.push(entry);
|
|
28288
|
+
}
|
|
28289
|
+
return byTransfer;
|
|
28290
|
+
}
|
|
28291
|
+
async function openRow(entry, journal, decryptPayload) {
|
|
28292
|
+
let payload = null;
|
|
28293
|
+
try {
|
|
28294
|
+
const raw = await decryptPayload(entry.payloadEnvelope);
|
|
28295
|
+
if (raw !== null && typeof raw === "object") payload = raw;
|
|
28296
|
+
} catch {
|
|
28297
|
+
payload = null;
|
|
28298
|
+
}
|
|
28299
|
+
const total = Array.isArray(payload?.direct) ? payload.direct.length + (payload.split !== void 0 ? 1 : 0) : 0;
|
|
28300
|
+
const certified = distinctOpCount(journal);
|
|
28301
|
+
return {
|
|
28302
|
+
transferId: entry.transferId,
|
|
28303
|
+
kind: "open",
|
|
28304
|
+
recipient: typeof payload?.recipient === "string" ? payload.recipient : "",
|
|
28305
|
+
coinId: typeof payload?.coinId === "string" ? payload.coinId : "",
|
|
28306
|
+
amount: typeof payload?.amount === "string" ? payload.amount : "",
|
|
28307
|
+
legs: { certified, total: Math.max(total, certified) },
|
|
28308
|
+
deliveryPending: journal.length > 0,
|
|
28309
|
+
createdAt: entry.createdAt
|
|
28310
|
+
};
|
|
28311
|
+
}
|
|
28312
|
+
function journalOnlyRow(transferId, entries) {
|
|
28313
|
+
const certified = distinctOpCount(entries);
|
|
28314
|
+
return {
|
|
28315
|
+
transferId,
|
|
28316
|
+
kind: "open",
|
|
28317
|
+
recipient: entries[0].recipientPubkey,
|
|
28318
|
+
coinId: "",
|
|
28319
|
+
amount: "",
|
|
28320
|
+
legs: { certified, total: certified },
|
|
28321
|
+
deliveryPending: true,
|
|
28322
|
+
createdAt: 0
|
|
28323
|
+
};
|
|
28324
|
+
}
|
|
28325
|
+
function shortfallRow(entry) {
|
|
28326
|
+
return {
|
|
28327
|
+
transferId: entry.transferId,
|
|
28328
|
+
kind: "shortfall",
|
|
28329
|
+
recipient: entry.recipient,
|
|
28330
|
+
coinId: entry.coinId,
|
|
28331
|
+
amount: entry.remainingAmount,
|
|
28332
|
+
legs: { certified: entry.committedTokenIds.length, total: entry.committedTokenIds.length },
|
|
28333
|
+
deliveryPending: false,
|
|
28334
|
+
createdAt: entry.createdAt
|
|
28335
|
+
};
|
|
28336
|
+
}
|
|
28337
|
+
function distinctOpCount(entries) {
|
|
28338
|
+
return new Set(entries.map((entry) => entry.opIndex)).size;
|
|
28339
|
+
}
|
|
28340
|
+
|
|
28126
28341
|
// modules/payments-v2/history/History.ts
|
|
28127
28342
|
var WIRE_TOKEN_ID = /^(?:[0-9a-f]{2}){1,64}$/;
|
|
28128
28343
|
var WIRE_PUBKEY = /^0[23][0-9a-f]{64}$/;
|
|
@@ -29512,6 +29727,21 @@ var PaymentsFacade = class {
|
|
|
29512
29727
|
this.ownPubkeyBytes = parts.ownPubkeyBytes;
|
|
29513
29728
|
this.requests = parts.requests;
|
|
29514
29729
|
deps.deliveryPort.bindDeliveryKeys((blob) => this.engine().deliveryKeys(blob));
|
|
29730
|
+
this.heartbeat = new ConvergenceHeartbeat({
|
|
29731
|
+
now: () => this.nowMs(),
|
|
29732
|
+
onTick: () => this.trackTail(this.runConvergencePass())
|
|
29733
|
+
});
|
|
29734
|
+
this.converger = new Converger({
|
|
29735
|
+
machineDeps: this.machineDeps,
|
|
29736
|
+
stores: this.machineStores,
|
|
29737
|
+
delivery: deps.deliveryPort,
|
|
29738
|
+
reconcile: (report) => this.requests.reconcile(report),
|
|
29739
|
+
refreshView: () => this.view.delta(),
|
|
29740
|
+
replayMints: () => this.replayMints(),
|
|
29741
|
+
isActiveOp: (id) => this.activeMoneyOps.has(id),
|
|
29742
|
+
emit: deps.emit,
|
|
29743
|
+
now: () => this.nowMs()
|
|
29744
|
+
});
|
|
29515
29745
|
}
|
|
29516
29746
|
view;
|
|
29517
29747
|
ledger;
|
|
@@ -29528,10 +29758,18 @@ var PaymentsFacade = class {
|
|
|
29528
29758
|
pendingOps = /* @__PURE__ */ new Set();
|
|
29529
29759
|
unsubscribers = [];
|
|
29530
29760
|
started = false;
|
|
29761
|
+
// §7 heartbeat: in-memory timing cell + pass runner (see convergence.ts).
|
|
29762
|
+
heartbeat;
|
|
29763
|
+
converger;
|
|
29764
|
+
/** Resume single-flight (§7): ticks, start() and resumeNow() coalesce onto ONE pass. */
|
|
29765
|
+
resumeFlight = new SingleFlight();
|
|
29766
|
+
/** §7 ownership: ids with an in-process machine attempt — the pass never adopts one. */
|
|
29767
|
+
activeMoneyOps = /* @__PURE__ */ new Set();
|
|
29531
29768
|
// ── lifecycle ──────────────────────────────────────────────────────────────
|
|
29532
29769
|
async start() {
|
|
29533
29770
|
if (this.started) return;
|
|
29534
29771
|
this.started = true;
|
|
29772
|
+
this.heartbeat.start();
|
|
29535
29773
|
await this.deps.session.start();
|
|
29536
29774
|
this.unsubscribers.push(
|
|
29537
29775
|
this.deps.session.subscribeStream("mailbox", () => {
|
|
@@ -29544,20 +29782,11 @@ var PaymentsFacade = class {
|
|
|
29544
29782
|
this.trackTail(this.requests.drainIncoming());
|
|
29545
29783
|
})
|
|
29546
29784
|
);
|
|
29547
|
-
this.
|
|
29548
|
-
|
|
29549
|
-
|
|
29550
|
-
|
|
29551
|
-
|
|
29552
|
-
attention: (transferId, code, detail) => {
|
|
29553
|
-
this.deps.emit(
|
|
29554
|
-
"transfer:attention",
|
|
29555
|
-
detail === void 0 ? { transferId, code } : { transferId, code, detail }
|
|
29556
|
-
);
|
|
29557
|
-
}
|
|
29558
|
-
})
|
|
29559
|
-
);
|
|
29560
|
-
this.trackTail(this.replayMints());
|
|
29785
|
+
const statusUnsub = this.deps.session.subscribeStatus?.((status) => {
|
|
29786
|
+
if (status === "connected") this.heartbeat.recovered();
|
|
29787
|
+
});
|
|
29788
|
+
if (statusUnsub !== void 0) this.unsubscribers.push(statusUnsub);
|
|
29789
|
+
this.trackTail(this.runConvergencePass());
|
|
29561
29790
|
this.trackTail(this.seedHeldStates());
|
|
29562
29791
|
await this.view.fullPull().catch(() => void 0);
|
|
29563
29792
|
this.receiveLoop.start(this.deps.receivePollMs);
|
|
@@ -29565,6 +29794,7 @@ var PaymentsFacade = class {
|
|
|
29565
29794
|
/** §7 same-address restart gate: resolves only after in-flight ops settle. */
|
|
29566
29795
|
async stop() {
|
|
29567
29796
|
this.started = false;
|
|
29797
|
+
this.heartbeat.stop();
|
|
29568
29798
|
this.receiveLoop.stop();
|
|
29569
29799
|
for (const unsubscribe of this.unsubscribers) unsubscribe();
|
|
29570
29800
|
this.unsubscribers = [];
|
|
@@ -29590,6 +29820,10 @@ var PaymentsFacade = class {
|
|
|
29590
29820
|
history(page) {
|
|
29591
29821
|
return this.historyStore.page(page ?? {});
|
|
29592
29822
|
}
|
|
29823
|
+
/** §4 pending-transfers UI surface — derived on read, never cached (convergence.ts). */
|
|
29824
|
+
pendingTransfers() {
|
|
29825
|
+
return derivePendingTransfers(this.machineStores, this.machineDeps.decryptPayload);
|
|
29826
|
+
}
|
|
29593
29827
|
// ── money movement ─────────────────────────────────────────────────────────
|
|
29594
29828
|
send(request) {
|
|
29595
29829
|
return this.track(this.sendWithPolicy(request));
|
|
@@ -29602,6 +29836,22 @@ var PaymentsFacade = class {
|
|
|
29602
29836
|
mint(coinId, amount) {
|
|
29603
29837
|
return this.track(this.mintInner(coinId, amount));
|
|
29604
29838
|
}
|
|
29839
|
+
// §4 retry verb: coalesces onto the running pass — NEVER re-issue send() (#631/#676).
|
|
29840
|
+
async resumeNow() {
|
|
29841
|
+
if (!this.started) return;
|
|
29842
|
+
await this.track(this.runConvergencePass());
|
|
29843
|
+
}
|
|
29844
|
+
/** One single-flighted pass + its reschedule: concurrent callers coalesce. */
|
|
29845
|
+
runConvergencePass() {
|
|
29846
|
+
return this.resumeFlight.run(async () => {
|
|
29847
|
+
const outcome = await this.converger.convergeOnce();
|
|
29848
|
+
const pending = await this.converger.pendingWork().catch(() => true);
|
|
29849
|
+
this.heartbeat.settle(outcome, pending);
|
|
29850
|
+
});
|
|
29851
|
+
}
|
|
29852
|
+
nowMs() {
|
|
29853
|
+
return (this.deps.now ?? Date.now)();
|
|
29854
|
+
}
|
|
29605
29855
|
// ── send policy (§5.5 + old-loop parity; the machine stays policy-free) ────
|
|
29606
29856
|
async sendWithPolicy(request) {
|
|
29607
29857
|
const recipient = await this.requireSameNetworkRecipient(request.recipient);
|
|
@@ -29651,12 +29901,15 @@ var PaymentsFacade = class {
|
|
|
29651
29901
|
return recipient;
|
|
29652
29902
|
}
|
|
29653
29903
|
async runAttempt(ctx) {
|
|
29904
|
+
this.activeMoneyOps.add(ctx.transferId);
|
|
29654
29905
|
try {
|
|
29655
29906
|
const { deliveryPending } = await this.machine.run(ctx.plan);
|
|
29656
29907
|
this.settleSuccess(ctx, ctx.sourceIds);
|
|
29657
29908
|
return { kind: "success", deliveryPending };
|
|
29658
29909
|
} catch (err) {
|
|
29659
|
-
return this.disposeFailedAttempt(ctx, err);
|
|
29910
|
+
return await this.disposeFailedAttempt(ctx, err);
|
|
29911
|
+
} finally {
|
|
29912
|
+
this.activeMoneyOps.delete(ctx.transferId);
|
|
29660
29913
|
}
|
|
29661
29914
|
}
|
|
29662
29915
|
/**
|
|
@@ -29665,7 +29918,10 @@ var PaymentsFacade = class {
|
|
|
29665
29918
|
* the remainder under a NEW transferId — never the full amount.
|
|
29666
29919
|
*/
|
|
29667
29920
|
async consumePartial(ctx, disposition, run, attempt) {
|
|
29668
|
-
|
|
29921
|
+
if (disposition.deliveryPending) {
|
|
29922
|
+
run.pendingAny = true;
|
|
29923
|
+
this.heartbeat.arm();
|
|
29924
|
+
}
|
|
29669
29925
|
const partial = disposition.entry;
|
|
29670
29926
|
run.delivered.push(...partial.committedTokenIds);
|
|
29671
29927
|
run.firstPartialId ??= partial.transferId;
|
|
@@ -29823,6 +30079,7 @@ var PaymentsFacade = class {
|
|
|
29823
30079
|
/** Open intent: sources stay reserved + in-flight (§5.2) until resume adopts them. */
|
|
29824
30080
|
settleKeepOpen(ctx) {
|
|
29825
30081
|
this.queue.clearExpectedChange(ctx.transferId);
|
|
30082
|
+
this.heartbeat.arm();
|
|
29826
30083
|
}
|
|
29827
30084
|
settlePartial(ctx, partial) {
|
|
29828
30085
|
this.ledger.cancel(ctx.transferId);
|
|
@@ -29859,6 +30116,7 @@ var PaymentsFacade = class {
|
|
|
29859
30116
|
for (const id of run.settledShortfalls.splice(0)) {
|
|
29860
30117
|
await this.machineStores.shortfalls.removeByKey(id).catch(() => void 0);
|
|
29861
30118
|
}
|
|
30119
|
+
if (run.pendingAny) this.heartbeat.arm();
|
|
29862
30120
|
const result = {
|
|
29863
30121
|
id: transferId,
|
|
29864
30122
|
status: run.pendingAny ? "confirmed" : "delivered",
|
|
@@ -29903,21 +30161,29 @@ var PaymentsFacade = class {
|
|
|
29903
30161
|
if (!/^(?:[0-9a-f]{2})+$/.test(coinId) || amount <= 0n) {
|
|
29904
30162
|
return { success: false, error: "coinId must be even-length lowercase hex and amount positive" };
|
|
29905
30163
|
}
|
|
29906
|
-
const engine = this.engine();
|
|
29907
30164
|
const mintId = this.newId();
|
|
29908
|
-
|
|
30165
|
+
this.activeMoneyOps.add(mintId);
|
|
30166
|
+
try {
|
|
30167
|
+
return await this.mintUnderJournal(mintId, coinId, amount);
|
|
30168
|
+
} finally {
|
|
30169
|
+
this.activeMoneyOps.delete(mintId);
|
|
30170
|
+
}
|
|
30171
|
+
}
|
|
30172
|
+
async mintUnderJournal(mintId, coinId, amount) {
|
|
30173
|
+
const engine = this.engine();
|
|
29909
30174
|
const entry = {
|
|
29910
30175
|
mintId,
|
|
29911
30176
|
coinId,
|
|
29912
30177
|
amount: amount.toString(),
|
|
29913
30178
|
tokenId: "",
|
|
29914
|
-
createdAt:
|
|
30179
|
+
createdAt: this.nowMs()
|
|
29915
30180
|
};
|
|
29916
30181
|
await this.machineStores.mintJournal.upsert(entry);
|
|
29917
30182
|
let token;
|
|
29918
30183
|
try {
|
|
29919
|
-
token = await engine.mint(
|
|
30184
|
+
token = await engine.mint(this.mintParams(coinId, amount), { transferId: mintId });
|
|
29920
30185
|
} catch (err) {
|
|
30186
|
+
this.heartbeat.arm();
|
|
29921
30187
|
return { success: false, error: messageOf(err) };
|
|
29922
30188
|
}
|
|
29923
30189
|
if (token.blob.tokenId !== entry.tokenId) {
|
|
@@ -29926,6 +30192,7 @@ var PaymentsFacade = class {
|
|
|
29926
30192
|
try {
|
|
29927
30193
|
await this.finalizeMint(engine, mintId, token, coinId, amount.toString());
|
|
29928
30194
|
} catch (err) {
|
|
30195
|
+
this.heartbeat.arm();
|
|
29929
30196
|
return { success: false, tokenId: token.blob.tokenId, error: messageOf(err) };
|
|
29930
30197
|
}
|
|
29931
30198
|
return { success: true, tokenId: token.blob.tokenId };
|
|
@@ -29948,18 +30215,22 @@ var PaymentsFacade = class {
|
|
|
29948
30215
|
this.heldStates.set(token.blob.tokenId, (await engine.deliveryKeys(bytes)).stateHash);
|
|
29949
30216
|
this.trackTail(this.view.delta());
|
|
29950
30217
|
}
|
|
30218
|
+
/** @returns how many journal entries were RESOLVED (cleared) — heartbeat progress. */
|
|
29951
30219
|
async replayMints() {
|
|
30220
|
+
let resolved = 0;
|
|
29952
30221
|
for (const entry of await this.machineStores.mintJournal.list()) {
|
|
30222
|
+
if (this.activeMoneyOps.has(entry.mintId)) continue;
|
|
29953
30223
|
try {
|
|
29954
|
-
await this.replayMint(entry);
|
|
30224
|
+
if (await this.replayMint(entry)) resolved += 1;
|
|
29955
30225
|
} catch {
|
|
29956
30226
|
}
|
|
29957
30227
|
}
|
|
30228
|
+
return resolved;
|
|
29958
30229
|
}
|
|
29959
30230
|
async replayMint(entry) {
|
|
29960
30231
|
if (entry.tokenId !== "" && await this.tokenInServerInventory(entry.tokenId)) {
|
|
29961
30232
|
await this.machineStores.mintJournal.removeByKey(entry.mintId);
|
|
29962
|
-
return;
|
|
30233
|
+
return true;
|
|
29963
30234
|
}
|
|
29964
30235
|
const engine = this.engine();
|
|
29965
30236
|
if (!supportsDeterministicMint(engine)) {
|
|
@@ -29967,7 +30238,7 @@ var PaymentsFacade = class {
|
|
|
29967
30238
|
transferId: entry.mintId,
|
|
29968
30239
|
code: ATTENTION_MINT_UNRESOLVED
|
|
29969
30240
|
});
|
|
29970
|
-
return;
|
|
30241
|
+
return false;
|
|
29971
30242
|
}
|
|
29972
30243
|
const token = await engine.mint(this.mintParams(entry.coinId, BigInt(entry.amount)), {
|
|
29973
30244
|
transferId: entry.mintId
|
|
@@ -29976,6 +30247,7 @@ var PaymentsFacade = class {
|
|
|
29976
30247
|
await this.machineStores.mintJournal.upsert({ ...entry, tokenId: token.blob.tokenId });
|
|
29977
30248
|
}
|
|
29978
30249
|
await this.finalizeMint(engine, entry.mintId, token, entry.coinId, entry.amount);
|
|
30250
|
+
return true;
|
|
29979
30251
|
}
|
|
29980
30252
|
mintParams(coinId, amount) {
|
|
29981
30253
|
return {
|
|
@@ -30001,17 +30273,6 @@ var PaymentsFacade = class {
|
|
|
30001
30273
|
newId() {
|
|
30002
30274
|
return (this.deps.newId ?? randomUUID)();
|
|
30003
30275
|
}
|
|
30004
|
-
async runResume() {
|
|
30005
|
-
const report = await resumeAll(this.machineDeps);
|
|
30006
|
-
await this.requests.reconcile({
|
|
30007
|
-
resumed: report.resumed,
|
|
30008
|
-
conflicted: report.conflicted,
|
|
30009
|
-
open: report.failed
|
|
30010
|
-
});
|
|
30011
|
-
if (report.resumed.length > 0 || report.conflicted.length > 0) {
|
|
30012
|
-
await this.view.delta().catch(() => void 0);
|
|
30013
|
-
}
|
|
30014
|
-
}
|
|
30015
30276
|
async seedHeldStates() {
|
|
30016
30277
|
let page = await this.deps.storagePort.listInventory();
|
|
30017
30278
|
for (let i = 0; i < INVENTORY_SCAN_PAGE_LIMIT; i++) {
|
|
@@ -30025,7 +30286,7 @@ var PaymentsFacade = class {
|
|
|
30025
30286
|
toUiToken(tokenId, coinId, amount) {
|
|
30026
30287
|
const registry = this.deps.registry;
|
|
30027
30288
|
const iconUrl = registry.getIconUrl(coinId);
|
|
30028
|
-
const now =
|
|
30289
|
+
const now = this.nowMs();
|
|
30029
30290
|
return {
|
|
30030
30291
|
id: tokenId,
|
|
30031
30292
|
coinId,
|
|
@@ -30420,6 +30681,7 @@ var WalletApiSession = class {
|
|
|
30420
30681
|
reseedChain = Promise.resolve();
|
|
30421
30682
|
reseedsPending = 0;
|
|
30422
30683
|
handlers = /* @__PURE__ */ new Map();
|
|
30684
|
+
statusHandlers = /* @__PURE__ */ new Set();
|
|
30423
30685
|
socket = null;
|
|
30424
30686
|
started = false;
|
|
30425
30687
|
stopped = false;
|
|
@@ -30570,10 +30832,16 @@ var WalletApiSession = class {
|
|
|
30570
30832
|
status() {
|
|
30571
30833
|
return this.lastStatus ?? "offline";
|
|
30572
30834
|
}
|
|
30835
|
+
/** Transition feed off the existing emitStatus point (FacadeSession.subscribeStatus). */
|
|
30836
|
+
subscribeStatus(handler) {
|
|
30837
|
+
this.statusHandlers.add(handler);
|
|
30838
|
+
return () => this.statusHandlers.delete(handler);
|
|
30839
|
+
}
|
|
30573
30840
|
emitStatus(status) {
|
|
30574
30841
|
if (this.stopped || this.lastStatus === status) return;
|
|
30575
30842
|
this.lastStatus = status;
|
|
30576
30843
|
this.deps.emitStatus(status);
|
|
30844
|
+
for (const handler of this.statusHandlers) handler(status);
|
|
30577
30845
|
}
|
|
30578
30846
|
async connect() {
|
|
30579
30847
|
if (this.stopped) return;
|