@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.d.cts
CHANGED
|
@@ -7143,6 +7143,25 @@ interface PaymentsRequestsApi {
|
|
|
7143
7143
|
decline(id: string): Promise<void>;
|
|
7144
7144
|
dismissProcessed(): void;
|
|
7145
7145
|
}
|
|
7146
|
+
/**
|
|
7147
|
+
* A pending-transfers row, derived ON READ from the §6 stores (intent backstop
|
|
7148
|
+
* + delivery journal + shortfalls) — never a cached mirror. kind 'shortfall' =
|
|
7149
|
+
* a completed partial (#690) whose `amount` is the remainder still owed;
|
|
7150
|
+
* legs.certified counts journaled legs (certified, delivery still owed).
|
|
7151
|
+
*/
|
|
7152
|
+
interface PendingTransfer {
|
|
7153
|
+
transferId: string;
|
|
7154
|
+
kind: 'open' | 'shortfall';
|
|
7155
|
+
recipient: string;
|
|
7156
|
+
coinId: string;
|
|
7157
|
+
amount: string;
|
|
7158
|
+
legs: {
|
|
7159
|
+
certified: number;
|
|
7160
|
+
total: number;
|
|
7161
|
+
};
|
|
7162
|
+
deliveryPending: boolean;
|
|
7163
|
+
createdAt: number;
|
|
7164
|
+
}
|
|
7146
7165
|
interface PaymentsV2 {
|
|
7147
7166
|
assets(coinId?: string): Promise<Asset[]>;
|
|
7148
7167
|
tokens(filter?: {
|
|
@@ -7157,6 +7176,8 @@ interface PaymentsV2 {
|
|
|
7157
7176
|
receive(): Promise<{
|
|
7158
7177
|
transfers: IncomingTransfer[];
|
|
7159
7178
|
}>;
|
|
7179
|
+
pendingTransfers(): Promise<PendingTransfer[]>;
|
|
7180
|
+
resumeNow(): Promise<void>;
|
|
7160
7181
|
readonly requests: PaymentsRequestsApi;
|
|
7161
7182
|
}
|
|
7162
7183
|
|
package/dist/core/index.d.ts
CHANGED
|
@@ -7143,6 +7143,25 @@ interface PaymentsRequestsApi {
|
|
|
7143
7143
|
decline(id: string): Promise<void>;
|
|
7144
7144
|
dismissProcessed(): void;
|
|
7145
7145
|
}
|
|
7146
|
+
/**
|
|
7147
|
+
* A pending-transfers row, derived ON READ from the §6 stores (intent backstop
|
|
7148
|
+
* + delivery journal + shortfalls) — never a cached mirror. kind 'shortfall' =
|
|
7149
|
+
* a completed partial (#690) whose `amount` is the remainder still owed;
|
|
7150
|
+
* legs.certified counts journaled legs (certified, delivery still owed).
|
|
7151
|
+
*/
|
|
7152
|
+
interface PendingTransfer {
|
|
7153
|
+
transferId: string;
|
|
7154
|
+
kind: 'open' | 'shortfall';
|
|
7155
|
+
recipient: string;
|
|
7156
|
+
coinId: string;
|
|
7157
|
+
amount: string;
|
|
7158
|
+
legs: {
|
|
7159
|
+
certified: number;
|
|
7160
|
+
total: number;
|
|
7161
|
+
};
|
|
7162
|
+
deliveryPending: boolean;
|
|
7163
|
+
createdAt: number;
|
|
7164
|
+
}
|
|
7146
7165
|
interface PaymentsV2 {
|
|
7147
7166
|
assets(coinId?: string): Promise<Asset[]>;
|
|
7148
7167
|
tokens(filter?: {
|
|
@@ -7157,6 +7176,8 @@ interface PaymentsV2 {
|
|
|
7157
7176
|
receive(): Promise<{
|
|
7158
7177
|
transfers: IncomingTransfer[];
|
|
7159
7178
|
}>;
|
|
7179
|
+
pendingTransfers(): Promise<PendingTransfer[]>;
|
|
7180
|
+
resumeNow(): Promise<void>;
|
|
7160
7181
|
readonly requests: PaymentsRequestsApi;
|
|
7161
7182
|
}
|
|
7162
7183
|
|
package/dist/core/index.js
CHANGED
|
@@ -25991,6 +25991,10 @@ function isValidationReject(err) {
|
|
|
25991
25991
|
if (e === null) return false;
|
|
25992
25992
|
return e.status === 422 || e.statusCode === 422 || e.code === "VALIDATION" || e.code === "VALIDATION_FAILED";
|
|
25993
25993
|
}
|
|
25994
|
+
function retryAfterMsOf(err) {
|
|
25995
|
+
const retryAfter = err !== null && typeof err === "object" ? err.retryAfter : void 0;
|
|
25996
|
+
return typeof retryAfter === "number" && Number.isFinite(retryAfter) && retryAfter >= 0 ? retryAfter : null;
|
|
25997
|
+
}
|
|
25994
25998
|
var chainRegistry = /* @__PURE__ */ new WeakMap();
|
|
25995
25999
|
var replayRegistry = /* @__PURE__ */ new WeakMap();
|
|
25996
26000
|
function registryFor(registry, kv, storeKey, make) {
|
|
@@ -26628,6 +26632,217 @@ function validatePayload(raw) {
|
|
|
26628
26632
|
return p;
|
|
26629
26633
|
}
|
|
26630
26634
|
|
|
26635
|
+
// modules/payments-v2/convergence.ts
|
|
26636
|
+
var HEARTBEAT_SEED_MS = 5e3;
|
|
26637
|
+
var HEARTBEAT_CAP_MS = 12e4;
|
|
26638
|
+
var ConvergenceHeartbeat = class {
|
|
26639
|
+
constructor(deps) {
|
|
26640
|
+
this.deps = deps;
|
|
26641
|
+
}
|
|
26642
|
+
timer = null;
|
|
26643
|
+
delayMs = HEARTBEAT_SEED_MS;
|
|
26644
|
+
notBefore = 0;
|
|
26645
|
+
running = false;
|
|
26646
|
+
start() {
|
|
26647
|
+
this.running = true;
|
|
26648
|
+
this.delayMs = HEARTBEAT_SEED_MS;
|
|
26649
|
+
this.notBefore = 0;
|
|
26650
|
+
}
|
|
26651
|
+
/** §7 quiescence unchanged: a mid-backoff stop() cancels cleanly. */
|
|
26652
|
+
stop() {
|
|
26653
|
+
this.running = false;
|
|
26654
|
+
this.clearTimer();
|
|
26655
|
+
}
|
|
26656
|
+
/** Reset to seed on progress, ×2 to the cap otherwise; idle stops rescheduling. */
|
|
26657
|
+
settle(outcome, pending) {
|
|
26658
|
+
if (outcome.retryAfterMs !== null) {
|
|
26659
|
+
this.notBefore = Math.max(this.notBefore, this.deps.now() + outcome.retryAfterMs);
|
|
26660
|
+
}
|
|
26661
|
+
if (!pending) {
|
|
26662
|
+
this.delayMs = HEARTBEAT_SEED_MS;
|
|
26663
|
+
return;
|
|
26664
|
+
}
|
|
26665
|
+
const delay = outcome.progress ? HEARTBEAT_SEED_MS : this.delayMs;
|
|
26666
|
+
this.delayMs = Math.min(delay * 2, HEARTBEAT_CAP_MS);
|
|
26667
|
+
this.schedule(delay);
|
|
26668
|
+
}
|
|
26669
|
+
/** New pending work (keep-open park / journaled delivery / mint hold): prompt retry. */
|
|
26670
|
+
arm() {
|
|
26671
|
+
if (!this.running) return;
|
|
26672
|
+
this.delayMs = Math.min(HEARTBEAT_SEED_MS * 2, HEARTBEAT_CAP_MS);
|
|
26673
|
+
this.schedule(HEARTBEAT_SEED_MS);
|
|
26674
|
+
}
|
|
26675
|
+
/** connection:status recovery: the backoff resets; a pending tick re-fires at the seed. */
|
|
26676
|
+
recovered() {
|
|
26677
|
+
this.delayMs = HEARTBEAT_SEED_MS;
|
|
26678
|
+
if (this.timer !== null) this.arm();
|
|
26679
|
+
}
|
|
26680
|
+
schedule(delayMs) {
|
|
26681
|
+
this.clearTimer();
|
|
26682
|
+
if (!this.running) return;
|
|
26683
|
+
const floored = Math.max(delayMs, this.notBefore - this.deps.now());
|
|
26684
|
+
this.timer = setTimeout(() => {
|
|
26685
|
+
this.timer = null;
|
|
26686
|
+
this.deps.onTick();
|
|
26687
|
+
}, floored);
|
|
26688
|
+
}
|
|
26689
|
+
clearTimer() {
|
|
26690
|
+
if (this.timer !== null) clearTimeout(this.timer);
|
|
26691
|
+
this.timer = null;
|
|
26692
|
+
}
|
|
26693
|
+
};
|
|
26694
|
+
var Converger = class {
|
|
26695
|
+
constructor(deps) {
|
|
26696
|
+
this.deps = deps;
|
|
26697
|
+
}
|
|
26698
|
+
/** Resume open intents, then drain the delivery + mint journals (all idempotent). */
|
|
26699
|
+
async convergeOnce() {
|
|
26700
|
+
let progress = false;
|
|
26701
|
+
let retryAfterMs = null;
|
|
26702
|
+
try {
|
|
26703
|
+
progress = await this.resumePass();
|
|
26704
|
+
} catch (err) {
|
|
26705
|
+
retryAfterMs = retryAfterMsOf(err);
|
|
26706
|
+
}
|
|
26707
|
+
try {
|
|
26708
|
+
const stats = await this.deps.stores.deliveryJournal.replay(this.replayDeps());
|
|
26709
|
+
progress = progress || stats.delivered > 0;
|
|
26710
|
+
} catch (err) {
|
|
26711
|
+
retryAfterMs ??= retryAfterMsOf(err);
|
|
26712
|
+
}
|
|
26713
|
+
try {
|
|
26714
|
+
progress = await this.deps.replayMints() > 0 || progress;
|
|
26715
|
+
} catch (err) {
|
|
26716
|
+
retryAfterMs ??= retryAfterMsOf(err);
|
|
26717
|
+
}
|
|
26718
|
+
return { progress, retryAfterMs };
|
|
26719
|
+
}
|
|
26720
|
+
/** §4 heartbeat predicate — read fresh from the §6 stores, never cached. */
|
|
26721
|
+
async pendingWork() {
|
|
26722
|
+
const [backstop, journal, mints] = await Promise.all([
|
|
26723
|
+
this.deps.stores.backstop.list(),
|
|
26724
|
+
this.deps.stores.deliveryJournal.list(),
|
|
26725
|
+
this.deps.stores.mintJournal.list()
|
|
26726
|
+
]);
|
|
26727
|
+
return backstop.length > 0 || journal.some((e) => e.undeliverable !== true) || mints.length > 0;
|
|
26728
|
+
}
|
|
26729
|
+
async resumePass() {
|
|
26730
|
+
const report = await resumeAll(this.filteredMachineDeps());
|
|
26731
|
+
const progress = report.resumed.length > 0 || report.conflicted.length > 0;
|
|
26732
|
+
await this.deps.reconcile({
|
|
26733
|
+
resumed: report.resumed,
|
|
26734
|
+
conflicted: report.conflicted,
|
|
26735
|
+
open: report.failed
|
|
26736
|
+
});
|
|
26737
|
+
if (progress) await this.deps.refreshView().catch(() => void 0);
|
|
26738
|
+
return progress;
|
|
26739
|
+
}
|
|
26740
|
+
filteredMachineDeps() {
|
|
26741
|
+
const intents = this.deps.machineDeps.intents;
|
|
26742
|
+
return {
|
|
26743
|
+
...this.deps.machineDeps,
|
|
26744
|
+
intents: {
|
|
26745
|
+
...intents,
|
|
26746
|
+
listOpen: async () => (await intents.listOpen()).filter((intent) => !this.deps.isActiveOp(intent.transferId))
|
|
26747
|
+
}
|
|
26748
|
+
};
|
|
26749
|
+
}
|
|
26750
|
+
replayDeps() {
|
|
26751
|
+
return {
|
|
26752
|
+
delivery: this.deps.delivery,
|
|
26753
|
+
now: () => this.deps.now(),
|
|
26754
|
+
attention: (transferId, code, detail) => {
|
|
26755
|
+
this.deps.emit(
|
|
26756
|
+
"transfer:attention",
|
|
26757
|
+
detail === void 0 ? { transferId, code } : { transferId, code, detail }
|
|
26758
|
+
);
|
|
26759
|
+
}
|
|
26760
|
+
};
|
|
26761
|
+
}
|
|
26762
|
+
};
|
|
26763
|
+
async function derivePendingTransfers(stores, decryptPayload) {
|
|
26764
|
+
const [backstop, journal, shortfalls] = await Promise.all([
|
|
26765
|
+
stores.backstop.list(),
|
|
26766
|
+
stores.deliveryJournal.list(),
|
|
26767
|
+
stores.shortfalls.list()
|
|
26768
|
+
]);
|
|
26769
|
+
const byTransfer = groupJournal(journal);
|
|
26770
|
+
const out = [];
|
|
26771
|
+
const seen = /* @__PURE__ */ new Set();
|
|
26772
|
+
for (const entry of backstop) {
|
|
26773
|
+
if (entry.disposition !== "open") continue;
|
|
26774
|
+
seen.add(entry.transferId);
|
|
26775
|
+
out.push(await openRow(entry, byTransfer.get(entry.transferId) ?? [], decryptPayload));
|
|
26776
|
+
}
|
|
26777
|
+
for (const [transferId, entries] of byTransfer) {
|
|
26778
|
+
if (seen.has(transferId)) continue;
|
|
26779
|
+
seen.add(transferId);
|
|
26780
|
+
out.push(journalOnlyRow(transferId, entries));
|
|
26781
|
+
}
|
|
26782
|
+
for (const entry of shortfalls) {
|
|
26783
|
+
if (!seen.has(entry.transferId)) out.push(shortfallRow(entry));
|
|
26784
|
+
}
|
|
26785
|
+
return out;
|
|
26786
|
+
}
|
|
26787
|
+
function groupJournal(journal) {
|
|
26788
|
+
const byTransfer = /* @__PURE__ */ new Map();
|
|
26789
|
+
for (const entry of journal) {
|
|
26790
|
+
const mine = byTransfer.get(entry.transferId);
|
|
26791
|
+
if (mine === void 0) byTransfer.set(entry.transferId, [entry]);
|
|
26792
|
+
else mine.push(entry);
|
|
26793
|
+
}
|
|
26794
|
+
return byTransfer;
|
|
26795
|
+
}
|
|
26796
|
+
async function openRow(entry, journal, decryptPayload) {
|
|
26797
|
+
let payload = null;
|
|
26798
|
+
try {
|
|
26799
|
+
const raw = await decryptPayload(entry.payloadEnvelope);
|
|
26800
|
+
if (raw !== null && typeof raw === "object") payload = raw;
|
|
26801
|
+
} catch {
|
|
26802
|
+
payload = null;
|
|
26803
|
+
}
|
|
26804
|
+
const total = Array.isArray(payload?.direct) ? payload.direct.length + (payload.split !== void 0 ? 1 : 0) : 0;
|
|
26805
|
+
const certified = distinctOpCount(journal);
|
|
26806
|
+
return {
|
|
26807
|
+
transferId: entry.transferId,
|
|
26808
|
+
kind: "open",
|
|
26809
|
+
recipient: typeof payload?.recipient === "string" ? payload.recipient : "",
|
|
26810
|
+
coinId: typeof payload?.coinId === "string" ? payload.coinId : "",
|
|
26811
|
+
amount: typeof payload?.amount === "string" ? payload.amount : "",
|
|
26812
|
+
legs: { certified, total: Math.max(total, certified) },
|
|
26813
|
+
deliveryPending: journal.length > 0,
|
|
26814
|
+
createdAt: entry.createdAt
|
|
26815
|
+
};
|
|
26816
|
+
}
|
|
26817
|
+
function journalOnlyRow(transferId, entries) {
|
|
26818
|
+
const certified = distinctOpCount(entries);
|
|
26819
|
+
return {
|
|
26820
|
+
transferId,
|
|
26821
|
+
kind: "open",
|
|
26822
|
+
recipient: entries[0].recipientPubkey,
|
|
26823
|
+
coinId: "",
|
|
26824
|
+
amount: "",
|
|
26825
|
+
legs: { certified, total: certified },
|
|
26826
|
+
deliveryPending: true,
|
|
26827
|
+
createdAt: 0
|
|
26828
|
+
};
|
|
26829
|
+
}
|
|
26830
|
+
function shortfallRow(entry) {
|
|
26831
|
+
return {
|
|
26832
|
+
transferId: entry.transferId,
|
|
26833
|
+
kind: "shortfall",
|
|
26834
|
+
recipient: entry.recipient,
|
|
26835
|
+
coinId: entry.coinId,
|
|
26836
|
+
amount: entry.remainingAmount,
|
|
26837
|
+
legs: { certified: entry.committedTokenIds.length, total: entry.committedTokenIds.length },
|
|
26838
|
+
deliveryPending: false,
|
|
26839
|
+
createdAt: entry.createdAt
|
|
26840
|
+
};
|
|
26841
|
+
}
|
|
26842
|
+
function distinctOpCount(entries) {
|
|
26843
|
+
return new Set(entries.map((entry) => entry.opIndex)).size;
|
|
26844
|
+
}
|
|
26845
|
+
|
|
26631
26846
|
// modules/payments-v2/history/History.ts
|
|
26632
26847
|
var WIRE_TOKEN_ID = /^(?:[0-9a-f]{2}){1,64}$/;
|
|
26633
26848
|
var WIRE_PUBKEY = /^0[23][0-9a-f]{64}$/;
|
|
@@ -28017,6 +28232,21 @@ var PaymentsFacade = class {
|
|
|
28017
28232
|
this.ownPubkeyBytes = parts.ownPubkeyBytes;
|
|
28018
28233
|
this.requests = parts.requests;
|
|
28019
28234
|
deps.deliveryPort.bindDeliveryKeys((blob) => this.engine().deliveryKeys(blob));
|
|
28235
|
+
this.heartbeat = new ConvergenceHeartbeat({
|
|
28236
|
+
now: () => this.nowMs(),
|
|
28237
|
+
onTick: () => this.trackTail(this.runConvergencePass())
|
|
28238
|
+
});
|
|
28239
|
+
this.converger = new Converger({
|
|
28240
|
+
machineDeps: this.machineDeps,
|
|
28241
|
+
stores: this.machineStores,
|
|
28242
|
+
delivery: deps.deliveryPort,
|
|
28243
|
+
reconcile: (report) => this.requests.reconcile(report),
|
|
28244
|
+
refreshView: () => this.view.delta(),
|
|
28245
|
+
replayMints: () => this.replayMints(),
|
|
28246
|
+
isActiveOp: (id) => this.activeMoneyOps.has(id),
|
|
28247
|
+
emit: deps.emit,
|
|
28248
|
+
now: () => this.nowMs()
|
|
28249
|
+
});
|
|
28020
28250
|
}
|
|
28021
28251
|
view;
|
|
28022
28252
|
ledger;
|
|
@@ -28033,10 +28263,18 @@ var PaymentsFacade = class {
|
|
|
28033
28263
|
pendingOps = /* @__PURE__ */ new Set();
|
|
28034
28264
|
unsubscribers = [];
|
|
28035
28265
|
started = false;
|
|
28266
|
+
// §7 heartbeat: in-memory timing cell + pass runner (see convergence.ts).
|
|
28267
|
+
heartbeat;
|
|
28268
|
+
converger;
|
|
28269
|
+
/** Resume single-flight (§7): ticks, start() and resumeNow() coalesce onto ONE pass. */
|
|
28270
|
+
resumeFlight = new SingleFlight();
|
|
28271
|
+
/** §7 ownership: ids with an in-process machine attempt — the pass never adopts one. */
|
|
28272
|
+
activeMoneyOps = /* @__PURE__ */ new Set();
|
|
28036
28273
|
// ── lifecycle ──────────────────────────────────────────────────────────────
|
|
28037
28274
|
async start() {
|
|
28038
28275
|
if (this.started) return;
|
|
28039
28276
|
this.started = true;
|
|
28277
|
+
this.heartbeat.start();
|
|
28040
28278
|
await this.deps.session.start();
|
|
28041
28279
|
this.unsubscribers.push(
|
|
28042
28280
|
this.deps.session.subscribeStream("mailbox", () => {
|
|
@@ -28049,20 +28287,11 @@ var PaymentsFacade = class {
|
|
|
28049
28287
|
this.trackTail(this.requests.drainIncoming());
|
|
28050
28288
|
})
|
|
28051
28289
|
);
|
|
28052
|
-
this.
|
|
28053
|
-
|
|
28054
|
-
|
|
28055
|
-
|
|
28056
|
-
|
|
28057
|
-
attention: (transferId, code, detail) => {
|
|
28058
|
-
this.deps.emit(
|
|
28059
|
-
"transfer:attention",
|
|
28060
|
-
detail === void 0 ? { transferId, code } : { transferId, code, detail }
|
|
28061
|
-
);
|
|
28062
|
-
}
|
|
28063
|
-
})
|
|
28064
|
-
);
|
|
28065
|
-
this.trackTail(this.replayMints());
|
|
28290
|
+
const statusUnsub = this.deps.session.subscribeStatus?.((status) => {
|
|
28291
|
+
if (status === "connected") this.heartbeat.recovered();
|
|
28292
|
+
});
|
|
28293
|
+
if (statusUnsub !== void 0) this.unsubscribers.push(statusUnsub);
|
|
28294
|
+
this.trackTail(this.runConvergencePass());
|
|
28066
28295
|
this.trackTail(this.seedHeldStates());
|
|
28067
28296
|
await this.view.fullPull().catch(() => void 0);
|
|
28068
28297
|
this.receiveLoop.start(this.deps.receivePollMs);
|
|
@@ -28070,6 +28299,7 @@ var PaymentsFacade = class {
|
|
|
28070
28299
|
/** §7 same-address restart gate: resolves only after in-flight ops settle. */
|
|
28071
28300
|
async stop() {
|
|
28072
28301
|
this.started = false;
|
|
28302
|
+
this.heartbeat.stop();
|
|
28073
28303
|
this.receiveLoop.stop();
|
|
28074
28304
|
for (const unsubscribe of this.unsubscribers) unsubscribe();
|
|
28075
28305
|
this.unsubscribers = [];
|
|
@@ -28095,6 +28325,10 @@ var PaymentsFacade = class {
|
|
|
28095
28325
|
history(page) {
|
|
28096
28326
|
return this.historyStore.page(page ?? {});
|
|
28097
28327
|
}
|
|
28328
|
+
/** §4 pending-transfers UI surface — derived on read, never cached (convergence.ts). */
|
|
28329
|
+
pendingTransfers() {
|
|
28330
|
+
return derivePendingTransfers(this.machineStores, this.machineDeps.decryptPayload);
|
|
28331
|
+
}
|
|
28098
28332
|
// ── money movement ─────────────────────────────────────────────────────────
|
|
28099
28333
|
send(request) {
|
|
28100
28334
|
return this.track(this.sendWithPolicy(request));
|
|
@@ -28107,6 +28341,22 @@ var PaymentsFacade = class {
|
|
|
28107
28341
|
mint(coinId, amount) {
|
|
28108
28342
|
return this.track(this.mintInner(coinId, amount));
|
|
28109
28343
|
}
|
|
28344
|
+
// §4 retry verb: coalesces onto the running pass — NEVER re-issue send() (#631/#676).
|
|
28345
|
+
async resumeNow() {
|
|
28346
|
+
if (!this.started) return;
|
|
28347
|
+
await this.track(this.runConvergencePass());
|
|
28348
|
+
}
|
|
28349
|
+
/** One single-flighted pass + its reschedule: concurrent callers coalesce. */
|
|
28350
|
+
runConvergencePass() {
|
|
28351
|
+
return this.resumeFlight.run(async () => {
|
|
28352
|
+
const outcome = await this.converger.convergeOnce();
|
|
28353
|
+
const pending = await this.converger.pendingWork().catch(() => true);
|
|
28354
|
+
this.heartbeat.settle(outcome, pending);
|
|
28355
|
+
});
|
|
28356
|
+
}
|
|
28357
|
+
nowMs() {
|
|
28358
|
+
return (this.deps.now ?? Date.now)();
|
|
28359
|
+
}
|
|
28110
28360
|
// ── send policy (§5.5 + old-loop parity; the machine stays policy-free) ────
|
|
28111
28361
|
async sendWithPolicy(request) {
|
|
28112
28362
|
const recipient = await this.requireSameNetworkRecipient(request.recipient);
|
|
@@ -28156,12 +28406,15 @@ var PaymentsFacade = class {
|
|
|
28156
28406
|
return recipient;
|
|
28157
28407
|
}
|
|
28158
28408
|
async runAttempt(ctx) {
|
|
28409
|
+
this.activeMoneyOps.add(ctx.transferId);
|
|
28159
28410
|
try {
|
|
28160
28411
|
const { deliveryPending } = await this.machine.run(ctx.plan);
|
|
28161
28412
|
this.settleSuccess(ctx, ctx.sourceIds);
|
|
28162
28413
|
return { kind: "success", deliveryPending };
|
|
28163
28414
|
} catch (err) {
|
|
28164
|
-
return this.disposeFailedAttempt(ctx, err);
|
|
28415
|
+
return await this.disposeFailedAttempt(ctx, err);
|
|
28416
|
+
} finally {
|
|
28417
|
+
this.activeMoneyOps.delete(ctx.transferId);
|
|
28165
28418
|
}
|
|
28166
28419
|
}
|
|
28167
28420
|
/**
|
|
@@ -28170,7 +28423,10 @@ var PaymentsFacade = class {
|
|
|
28170
28423
|
* the remainder under a NEW transferId — never the full amount.
|
|
28171
28424
|
*/
|
|
28172
28425
|
async consumePartial(ctx, disposition, run, attempt) {
|
|
28173
|
-
|
|
28426
|
+
if (disposition.deliveryPending) {
|
|
28427
|
+
run.pendingAny = true;
|
|
28428
|
+
this.heartbeat.arm();
|
|
28429
|
+
}
|
|
28174
28430
|
const partial = disposition.entry;
|
|
28175
28431
|
run.delivered.push(...partial.committedTokenIds);
|
|
28176
28432
|
run.firstPartialId ??= partial.transferId;
|
|
@@ -28328,6 +28584,7 @@ var PaymentsFacade = class {
|
|
|
28328
28584
|
/** Open intent: sources stay reserved + in-flight (§5.2) until resume adopts them. */
|
|
28329
28585
|
settleKeepOpen(ctx) {
|
|
28330
28586
|
this.queue.clearExpectedChange(ctx.transferId);
|
|
28587
|
+
this.heartbeat.arm();
|
|
28331
28588
|
}
|
|
28332
28589
|
settlePartial(ctx, partial) {
|
|
28333
28590
|
this.ledger.cancel(ctx.transferId);
|
|
@@ -28364,6 +28621,7 @@ var PaymentsFacade = class {
|
|
|
28364
28621
|
for (const id of run.settledShortfalls.splice(0)) {
|
|
28365
28622
|
await this.machineStores.shortfalls.removeByKey(id).catch(() => void 0);
|
|
28366
28623
|
}
|
|
28624
|
+
if (run.pendingAny) this.heartbeat.arm();
|
|
28367
28625
|
const result = {
|
|
28368
28626
|
id: transferId,
|
|
28369
28627
|
status: run.pendingAny ? "confirmed" : "delivered",
|
|
@@ -28408,21 +28666,29 @@ var PaymentsFacade = class {
|
|
|
28408
28666
|
if (!/^(?:[0-9a-f]{2})+$/.test(coinId) || amount <= 0n) {
|
|
28409
28667
|
return { success: false, error: "coinId must be even-length lowercase hex and amount positive" };
|
|
28410
28668
|
}
|
|
28411
|
-
const engine = this.engine();
|
|
28412
28669
|
const mintId = this.newId();
|
|
28413
|
-
|
|
28670
|
+
this.activeMoneyOps.add(mintId);
|
|
28671
|
+
try {
|
|
28672
|
+
return await this.mintUnderJournal(mintId, coinId, amount);
|
|
28673
|
+
} finally {
|
|
28674
|
+
this.activeMoneyOps.delete(mintId);
|
|
28675
|
+
}
|
|
28676
|
+
}
|
|
28677
|
+
async mintUnderJournal(mintId, coinId, amount) {
|
|
28678
|
+
const engine = this.engine();
|
|
28414
28679
|
const entry = {
|
|
28415
28680
|
mintId,
|
|
28416
28681
|
coinId,
|
|
28417
28682
|
amount: amount.toString(),
|
|
28418
28683
|
tokenId: "",
|
|
28419
|
-
createdAt:
|
|
28684
|
+
createdAt: this.nowMs()
|
|
28420
28685
|
};
|
|
28421
28686
|
await this.machineStores.mintJournal.upsert(entry);
|
|
28422
28687
|
let token;
|
|
28423
28688
|
try {
|
|
28424
|
-
token = await engine.mint(
|
|
28689
|
+
token = await engine.mint(this.mintParams(coinId, amount), { transferId: mintId });
|
|
28425
28690
|
} catch (err) {
|
|
28691
|
+
this.heartbeat.arm();
|
|
28426
28692
|
return { success: false, error: messageOf(err) };
|
|
28427
28693
|
}
|
|
28428
28694
|
if (token.blob.tokenId !== entry.tokenId) {
|
|
@@ -28431,6 +28697,7 @@ var PaymentsFacade = class {
|
|
|
28431
28697
|
try {
|
|
28432
28698
|
await this.finalizeMint(engine, mintId, token, coinId, amount.toString());
|
|
28433
28699
|
} catch (err) {
|
|
28700
|
+
this.heartbeat.arm();
|
|
28434
28701
|
return { success: false, tokenId: token.blob.tokenId, error: messageOf(err) };
|
|
28435
28702
|
}
|
|
28436
28703
|
return { success: true, tokenId: token.blob.tokenId };
|
|
@@ -28453,18 +28720,22 @@ var PaymentsFacade = class {
|
|
|
28453
28720
|
this.heldStates.set(token.blob.tokenId, (await engine.deliveryKeys(bytes)).stateHash);
|
|
28454
28721
|
this.trackTail(this.view.delta());
|
|
28455
28722
|
}
|
|
28723
|
+
/** @returns how many journal entries were RESOLVED (cleared) — heartbeat progress. */
|
|
28456
28724
|
async replayMints() {
|
|
28725
|
+
let resolved = 0;
|
|
28457
28726
|
for (const entry of await this.machineStores.mintJournal.list()) {
|
|
28727
|
+
if (this.activeMoneyOps.has(entry.mintId)) continue;
|
|
28458
28728
|
try {
|
|
28459
|
-
await this.replayMint(entry);
|
|
28729
|
+
if (await this.replayMint(entry)) resolved += 1;
|
|
28460
28730
|
} catch {
|
|
28461
28731
|
}
|
|
28462
28732
|
}
|
|
28733
|
+
return resolved;
|
|
28463
28734
|
}
|
|
28464
28735
|
async replayMint(entry) {
|
|
28465
28736
|
if (entry.tokenId !== "" && await this.tokenInServerInventory(entry.tokenId)) {
|
|
28466
28737
|
await this.machineStores.mintJournal.removeByKey(entry.mintId);
|
|
28467
|
-
return;
|
|
28738
|
+
return true;
|
|
28468
28739
|
}
|
|
28469
28740
|
const engine = this.engine();
|
|
28470
28741
|
if (!supportsDeterministicMint(engine)) {
|
|
@@ -28472,7 +28743,7 @@ var PaymentsFacade = class {
|
|
|
28472
28743
|
transferId: entry.mintId,
|
|
28473
28744
|
code: ATTENTION_MINT_UNRESOLVED
|
|
28474
28745
|
});
|
|
28475
|
-
return;
|
|
28746
|
+
return false;
|
|
28476
28747
|
}
|
|
28477
28748
|
const token = await engine.mint(this.mintParams(entry.coinId, BigInt(entry.amount)), {
|
|
28478
28749
|
transferId: entry.mintId
|
|
@@ -28481,6 +28752,7 @@ var PaymentsFacade = class {
|
|
|
28481
28752
|
await this.machineStores.mintJournal.upsert({ ...entry, tokenId: token.blob.tokenId });
|
|
28482
28753
|
}
|
|
28483
28754
|
await this.finalizeMint(engine, entry.mintId, token, entry.coinId, entry.amount);
|
|
28755
|
+
return true;
|
|
28484
28756
|
}
|
|
28485
28757
|
mintParams(coinId, amount) {
|
|
28486
28758
|
return {
|
|
@@ -28506,17 +28778,6 @@ var PaymentsFacade = class {
|
|
|
28506
28778
|
newId() {
|
|
28507
28779
|
return (this.deps.newId ?? randomUUID)();
|
|
28508
28780
|
}
|
|
28509
|
-
async runResume() {
|
|
28510
|
-
const report = await resumeAll(this.machineDeps);
|
|
28511
|
-
await this.requests.reconcile({
|
|
28512
|
-
resumed: report.resumed,
|
|
28513
|
-
conflicted: report.conflicted,
|
|
28514
|
-
open: report.failed
|
|
28515
|
-
});
|
|
28516
|
-
if (report.resumed.length > 0 || report.conflicted.length > 0) {
|
|
28517
|
-
await this.view.delta().catch(() => void 0);
|
|
28518
|
-
}
|
|
28519
|
-
}
|
|
28520
28781
|
async seedHeldStates() {
|
|
28521
28782
|
let page = await this.deps.storagePort.listInventory();
|
|
28522
28783
|
for (let i = 0; i < INVENTORY_SCAN_PAGE_LIMIT; i++) {
|
|
@@ -28530,7 +28791,7 @@ var PaymentsFacade = class {
|
|
|
28530
28791
|
toUiToken(tokenId, coinId, amount) {
|
|
28531
28792
|
const registry = this.deps.registry;
|
|
28532
28793
|
const iconUrl = registry.getIconUrl(coinId);
|
|
28533
|
-
const now =
|
|
28794
|
+
const now = this.nowMs();
|
|
28534
28795
|
return {
|
|
28535
28796
|
id: tokenId,
|
|
28536
28797
|
coinId,
|
|
@@ -28925,6 +29186,7 @@ var WalletApiSession = class {
|
|
|
28925
29186
|
reseedChain = Promise.resolve();
|
|
28926
29187
|
reseedsPending = 0;
|
|
28927
29188
|
handlers = /* @__PURE__ */ new Map();
|
|
29189
|
+
statusHandlers = /* @__PURE__ */ new Set();
|
|
28928
29190
|
socket = null;
|
|
28929
29191
|
started = false;
|
|
28930
29192
|
stopped = false;
|
|
@@ -29075,10 +29337,16 @@ var WalletApiSession = class {
|
|
|
29075
29337
|
status() {
|
|
29076
29338
|
return this.lastStatus ?? "offline";
|
|
29077
29339
|
}
|
|
29340
|
+
/** Transition feed off the existing emitStatus point (FacadeSession.subscribeStatus). */
|
|
29341
|
+
subscribeStatus(handler) {
|
|
29342
|
+
this.statusHandlers.add(handler);
|
|
29343
|
+
return () => this.statusHandlers.delete(handler);
|
|
29344
|
+
}
|
|
29078
29345
|
emitStatus(status) {
|
|
29079
29346
|
if (this.stopped || this.lastStatus === status) return;
|
|
29080
29347
|
this.lastStatus = status;
|
|
29081
29348
|
this.deps.emitStatus(status);
|
|
29349
|
+
for (const handler of this.statusHandlers) handler(status);
|
|
29082
29350
|
}
|
|
29083
29351
|
async connect() {
|
|
29084
29352
|
if (this.stopped) return;
|