@unicitylabs/sphere-sdk 0.16.0 → 0.17.1
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 +11 -5
- package/dist/connect/index.cjs.map +1 -1
- package/dist/connect/index.d.cts +4 -1
- package/dist/connect/index.d.ts +4 -1
- package/dist/connect/index.js +11 -5
- package/dist/connect/index.js.map +1 -1
- package/dist/core/index.cjs +692 -268
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +69 -0
- package/dist/core/index.d.ts +69 -0
- package/dist/core/index.js +692 -268
- package/dist/core/index.js.map +1 -1
- package/dist/impl/browser/connect/index.cjs +11 -5
- package/dist/impl/browser/connect/index.cjs.map +1 -1
- package/dist/impl/browser/connect/index.d.cts +3 -1
- package/dist/impl/browser/connect/index.d.ts +3 -1
- package/dist/impl/browser/connect/index.js +11 -5
- package/dist/impl/browser/connect/index.js.map +1 -1
- package/dist/impl/nodejs/connect/index.cjs +10 -4
- package/dist/impl/nodejs/connect/index.cjs.map +1 -1
- package/dist/impl/nodejs/connect/index.d.cts +1 -1
- package/dist/impl/nodejs/connect/index.d.ts +1 -1
- package/dist/impl/nodejs/connect/index.js +10 -4
- package/dist/impl/nodejs/connect/index.js.map +1 -1
- package/dist/impl/nodejs/index.d.cts +11 -0
- package/dist/impl/nodejs/index.d.ts +11 -0
- package/dist/impl/shared/wallet-api/index.d.cts +11 -0
- package/dist/impl/shared/wallet-api/index.d.ts +11 -0
- package/dist/impl/wallet-api-v2/index.cjs +2 -0
- package/dist/impl/wallet-api-v2/index.cjs.map +1 -1
- package/dist/impl/wallet-api-v2/index.d.cts +13 -0
- package/dist/impl/wallet-api-v2/index.d.ts +13 -0
- package/dist/impl/wallet-api-v2/index.js +2 -0
- package/dist/impl/wallet-api-v2/index.js.map +1 -1
- package/dist/index.cjs +692 -268
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +63 -1
- package/dist/index.d.ts +63 -1
- package/dist/index.js +692 -268
- package/dist/index.js.map +1 -1
- package/dist/modules/payments-v2/index.cjs +767 -398
- package/dist/modules/payments-v2/index.cjs.map +1 -1
- package/dist/modules/payments-v2/index.d.cts +91 -14
- package/dist/modules/payments-v2/index.d.ts +91 -14
- package/dist/modules/payments-v2/index.js +767 -398
- package/dist/modules/payments-v2/index.js.map +1 -1
- package/dist/token-engine/index.cjs +132 -34
- package/dist/token-engine/index.cjs.map +1 -1
- package/dist/token-engine/index.d.cts +19 -0
- package/dist/token-engine/index.d.ts +19 -0
- package/dist/token-engine/index.js +132 -34
- package/dist/token-engine/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -184,6 +184,135 @@ var STORE_KEYS = {
|
|
|
184
184
|
knownSpends: "known-spends"
|
|
185
185
|
};
|
|
186
186
|
|
|
187
|
+
// core/errors.ts
|
|
188
|
+
var SphereError = class extends Error {
|
|
189
|
+
code;
|
|
190
|
+
cause;
|
|
191
|
+
/**
|
|
192
|
+
* #441 deferred-paid linkage: the transferId of the possibly-committed send,
|
|
193
|
+
* stamped by `sendOnce` before the error leaves so a payment-request consumer
|
|
194
|
+
* can durably journal request→transfer and resolve 'paid' only when that
|
|
195
|
+
* transfer actually completes (never optimistically). Present ONLY on
|
|
196
|
+
* possibly-committed outcomes ({@link isPossiblyCommittedSendOutcome}).
|
|
197
|
+
*/
|
|
198
|
+
transferId;
|
|
199
|
+
constructor(message, code, cause) {
|
|
200
|
+
super(message);
|
|
201
|
+
this.name = "SphereError";
|
|
202
|
+
this.code = code;
|
|
203
|
+
this.cause = cause;
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
var PartialSendConflictError = class extends SphereError {
|
|
207
|
+
/**
|
|
208
|
+
* The FIRST partial attempt's transferId — its intent stays open for resume,
|
|
209
|
+
* and it is the §6 handoff anchor. NOTE: `send()` may re-plan the remainder across
|
|
210
|
+
* SEVERAL internal attempts before giving up, so {@link committedTokenIds} can
|
|
211
|
+
* span MULTIPLE transferIds — they do NOT all map to this single id.
|
|
212
|
+
*/
|
|
213
|
+
transferId;
|
|
214
|
+
/**
|
|
215
|
+
* Source token ids whose spend already certified on-chain (and whose finished
|
|
216
|
+
* output blob is journaled for delivery). Their value has already been
|
|
217
|
+
* delivered; NEVER re-send these. May accumulate across several internal
|
|
218
|
+
* remainder-re-plan attempts (so not all are journaled under {@link transferId}).
|
|
219
|
+
*/
|
|
220
|
+
committedTokenIds;
|
|
221
|
+
/**
|
|
222
|
+
* The still-undelivered portion (base units, decimal string) — `send()` could
|
|
223
|
+
* not COMPLETE it from the remaining live sources (insufficient funds or a
|
|
224
|
+
* transient error; see `cause`). Re-plan ONLY this amount, under a new
|
|
225
|
+
* transferId; the delivered legs converge via the recipient's §6 claim.
|
|
226
|
+
*/
|
|
227
|
+
remainingAmount;
|
|
228
|
+
constructor(message, transferId, committedTokenIds, remainingAmount, cause) {
|
|
229
|
+
super(message, "SEND_PARTIALLY_COMPLETED", cause);
|
|
230
|
+
this.name = "PartialSendConflictError";
|
|
231
|
+
this.transferId = transferId;
|
|
232
|
+
this.committedTokenIds = [...committedTokenIds];
|
|
233
|
+
this.remainingAmount = remainingAmount;
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
var POSSIBLY_COMMITTED_SEND_CODES = /* @__PURE__ */ new Set([
|
|
237
|
+
"SEND_SYNC_PENDING",
|
|
238
|
+
"CERTIFICATION_UNCONFIRMED",
|
|
239
|
+
"CHECKPOINT_PERSIST_FAILED",
|
|
240
|
+
"SPLIT_CHECKPOINT_LOST",
|
|
241
|
+
"CHECKPOINT_TRUSTBASE_MISMATCH",
|
|
242
|
+
"SEND_PARTIALLY_COMPLETED"
|
|
243
|
+
]);
|
|
244
|
+
function isPossiblyCommittedSendOutcome(err) {
|
|
245
|
+
return isSphereError(err) && POSSIBLY_COMMITTED_SEND_CODES.has(err.code);
|
|
246
|
+
}
|
|
247
|
+
function isSphereError(err) {
|
|
248
|
+
return err instanceof SphereError;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// core/uuid.ts
|
|
252
|
+
function randomUUID() {
|
|
253
|
+
const c = globalThis.crypto;
|
|
254
|
+
if (typeof c !== "undefined" && typeof c.randomUUID === "function") {
|
|
255
|
+
return c.randomUUID();
|
|
256
|
+
}
|
|
257
|
+
const b = c.getRandomValues(new Uint8Array(16));
|
|
258
|
+
b[6] = b[6] & 15 | 64;
|
|
259
|
+
b[8] = b[8] & 63 | 128;
|
|
260
|
+
const h = Array.from(b, (x) => x.toString(16).padStart(2, "0"));
|
|
261
|
+
return `${h[0]}${h[1]}${h[2]}${h[3]}-${h[4]}${h[5]}-${h[6]}${h[7]}-${h[8]}${h[9]}-${h[10]}${h[11]}${h[12]}${h[13]}${h[14]}${h[15]}`;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// modules/payments-v2/async.ts
|
|
265
|
+
var SingleFlight = class {
|
|
266
|
+
flight = null;
|
|
267
|
+
run(fn) {
|
|
268
|
+
if (this.flight !== null) return this.flight;
|
|
269
|
+
const current = fn().finally(() => {
|
|
270
|
+
this.flight = null;
|
|
271
|
+
});
|
|
272
|
+
this.flight = current;
|
|
273
|
+
return current;
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
var SerialChain = class {
|
|
277
|
+
tail = Promise.resolve();
|
|
278
|
+
/** FIFO; starts after every prior fn settled; returns this fn's own outcome. */
|
|
279
|
+
enqueue(fn) {
|
|
280
|
+
const run = this.tail.then(fn, fn);
|
|
281
|
+
this.tail = run.then(
|
|
282
|
+
() => void 0,
|
|
283
|
+
() => void 0
|
|
284
|
+
);
|
|
285
|
+
return run;
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
function coalesced(run, observe) {
|
|
289
|
+
let inFlight = false;
|
|
290
|
+
let queued = false;
|
|
291
|
+
const start = () => {
|
|
292
|
+
inFlight = true;
|
|
293
|
+
let running;
|
|
294
|
+
try {
|
|
295
|
+
running = run();
|
|
296
|
+
} catch {
|
|
297
|
+
running = Promise.resolve();
|
|
298
|
+
}
|
|
299
|
+
observe?.(running);
|
|
300
|
+
void running.catch(() => void 0).finally(() => {
|
|
301
|
+
inFlight = false;
|
|
302
|
+
if (!queued) return;
|
|
303
|
+
queued = false;
|
|
304
|
+
start();
|
|
305
|
+
});
|
|
306
|
+
};
|
|
307
|
+
return () => {
|
|
308
|
+
if (inFlight) {
|
|
309
|
+
queued = true;
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
start();
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
187
316
|
// node_modules/@noble/hashes/utils.js
|
|
188
317
|
function isBytes(a) {
|
|
189
318
|
return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array" && "BYTES_PER_ELEMENT" in a && a.BYTES_PER_ELEMENT === 1;
|
|
@@ -511,72 +640,6 @@ var sha256 = /* @__PURE__ */ createHasher(
|
|
|
511
640
|
// core/crypto.ts
|
|
512
641
|
var bip39 = __toESM(require("bip39"), 1);
|
|
513
642
|
var import_crypto_js = __toESM(require("crypto-js"), 1);
|
|
514
|
-
|
|
515
|
-
// core/errors.ts
|
|
516
|
-
var SphereError = class extends Error {
|
|
517
|
-
code;
|
|
518
|
-
cause;
|
|
519
|
-
/**
|
|
520
|
-
* #441 deferred-paid linkage: the transferId of the possibly-committed send,
|
|
521
|
-
* stamped by `sendOnce` before the error leaves so a payment-request consumer
|
|
522
|
-
* can durably journal request→transfer and resolve 'paid' only when that
|
|
523
|
-
* transfer actually completes (never optimistically). Present ONLY on
|
|
524
|
-
* possibly-committed outcomes ({@link isPossiblyCommittedSendOutcome}).
|
|
525
|
-
*/
|
|
526
|
-
transferId;
|
|
527
|
-
constructor(message, code, cause) {
|
|
528
|
-
super(message);
|
|
529
|
-
this.name = "SphereError";
|
|
530
|
-
this.code = code;
|
|
531
|
-
this.cause = cause;
|
|
532
|
-
}
|
|
533
|
-
};
|
|
534
|
-
var PartialSendConflictError = class extends SphereError {
|
|
535
|
-
/**
|
|
536
|
-
* The FIRST partial attempt's transferId — its intent stays open for resume,
|
|
537
|
-
* and it is the §6 handoff anchor. NOTE: `send()` may re-plan the remainder across
|
|
538
|
-
* SEVERAL internal attempts before giving up, so {@link committedTokenIds} can
|
|
539
|
-
* span MULTIPLE transferIds — they do NOT all map to this single id.
|
|
540
|
-
*/
|
|
541
|
-
transferId;
|
|
542
|
-
/**
|
|
543
|
-
* Source token ids whose spend already certified on-chain (and whose finished
|
|
544
|
-
* output blob is journaled for delivery). Their value has already been
|
|
545
|
-
* delivered; NEVER re-send these. May accumulate across several internal
|
|
546
|
-
* remainder-re-plan attempts (so not all are journaled under {@link transferId}).
|
|
547
|
-
*/
|
|
548
|
-
committedTokenIds;
|
|
549
|
-
/**
|
|
550
|
-
* The still-undelivered portion (base units, decimal string) — `send()` could
|
|
551
|
-
* not COMPLETE it from the remaining live sources (insufficient funds or a
|
|
552
|
-
* transient error; see `cause`). Re-plan ONLY this amount, under a new
|
|
553
|
-
* transferId; the delivered legs converge via the recipient's §6 claim.
|
|
554
|
-
*/
|
|
555
|
-
remainingAmount;
|
|
556
|
-
constructor(message, transferId, committedTokenIds, remainingAmount, cause) {
|
|
557
|
-
super(message, "SEND_PARTIALLY_COMPLETED", cause);
|
|
558
|
-
this.name = "PartialSendConflictError";
|
|
559
|
-
this.transferId = transferId;
|
|
560
|
-
this.committedTokenIds = [...committedTokenIds];
|
|
561
|
-
this.remainingAmount = remainingAmount;
|
|
562
|
-
}
|
|
563
|
-
};
|
|
564
|
-
var POSSIBLY_COMMITTED_SEND_CODES = /* @__PURE__ */ new Set([
|
|
565
|
-
"SEND_SYNC_PENDING",
|
|
566
|
-
"CERTIFICATION_UNCONFIRMED",
|
|
567
|
-
"CHECKPOINT_PERSIST_FAILED",
|
|
568
|
-
"SPLIT_CHECKPOINT_LOST",
|
|
569
|
-
"CHECKPOINT_TRUSTBASE_MISMATCH",
|
|
570
|
-
"SEND_PARTIALLY_COMPLETED"
|
|
571
|
-
]);
|
|
572
|
-
function isPossiblyCommittedSendOutcome(err) {
|
|
573
|
-
return isSphereError(err) && POSSIBLY_COMMITTED_SEND_CODES.has(err.code);
|
|
574
|
-
}
|
|
575
|
-
function isSphereError(err) {
|
|
576
|
-
return err instanceof SphereError;
|
|
577
|
-
}
|
|
578
|
-
|
|
579
|
-
// core/crypto.ts
|
|
580
643
|
var CURVE_ORDER = BigInt(
|
|
581
644
|
"0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"
|
|
582
645
|
);
|
|
@@ -591,71 +654,6 @@ function bytesToHex(bytes) {
|
|
|
591
654
|
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
592
655
|
}
|
|
593
656
|
|
|
594
|
-
// core/uuid.ts
|
|
595
|
-
function randomUUID() {
|
|
596
|
-
const c = globalThis.crypto;
|
|
597
|
-
if (typeof c !== "undefined" && typeof c.randomUUID === "function") {
|
|
598
|
-
return c.randomUUID();
|
|
599
|
-
}
|
|
600
|
-
const b = c.getRandomValues(new Uint8Array(16));
|
|
601
|
-
b[6] = b[6] & 15 | 64;
|
|
602
|
-
b[8] = b[8] & 63 | 128;
|
|
603
|
-
const h = Array.from(b, (x) => x.toString(16).padStart(2, "0"));
|
|
604
|
-
return `${h[0]}${h[1]}${h[2]}${h[3]}-${h[4]}${h[5]}-${h[6]}${h[7]}-${h[8]}${h[9]}-${h[10]}${h[11]}${h[12]}${h[13]}${h[14]}${h[15]}`;
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
// modules/payments-v2/async.ts
|
|
608
|
-
var SingleFlight = class {
|
|
609
|
-
flight = null;
|
|
610
|
-
run(fn) {
|
|
611
|
-
if (this.flight !== null) return this.flight;
|
|
612
|
-
const current = fn().finally(() => {
|
|
613
|
-
this.flight = null;
|
|
614
|
-
});
|
|
615
|
-
this.flight = current;
|
|
616
|
-
return current;
|
|
617
|
-
}
|
|
618
|
-
};
|
|
619
|
-
var SerialChain = class {
|
|
620
|
-
tail = Promise.resolve();
|
|
621
|
-
/** FIFO; starts after every prior fn settled; returns this fn's own outcome. */
|
|
622
|
-
enqueue(fn) {
|
|
623
|
-
const run = this.tail.then(fn, fn);
|
|
624
|
-
this.tail = run.then(
|
|
625
|
-
() => void 0,
|
|
626
|
-
() => void 0
|
|
627
|
-
);
|
|
628
|
-
return run;
|
|
629
|
-
}
|
|
630
|
-
};
|
|
631
|
-
function coalesced(run, observe) {
|
|
632
|
-
let inFlight = false;
|
|
633
|
-
let queued = false;
|
|
634
|
-
const start = () => {
|
|
635
|
-
inFlight = true;
|
|
636
|
-
let running;
|
|
637
|
-
try {
|
|
638
|
-
running = run();
|
|
639
|
-
} catch {
|
|
640
|
-
running = Promise.resolve();
|
|
641
|
-
}
|
|
642
|
-
observe?.(running);
|
|
643
|
-
void running.catch(() => void 0).finally(() => {
|
|
644
|
-
inFlight = false;
|
|
645
|
-
if (!queued) return;
|
|
646
|
-
queued = false;
|
|
647
|
-
start();
|
|
648
|
-
});
|
|
649
|
-
};
|
|
650
|
-
return () => {
|
|
651
|
-
if (inFlight) {
|
|
652
|
-
queued = true;
|
|
653
|
-
return;
|
|
654
|
-
}
|
|
655
|
-
start();
|
|
656
|
-
};
|
|
657
|
-
}
|
|
658
|
-
|
|
659
657
|
// modules/payments-v2/machine/journal.ts
|
|
660
658
|
var MAX_DELIVERY_ATTEMPTS = 6;
|
|
661
659
|
var RATE_LIMIT_DEFER_MS = 3e4;
|
|
@@ -834,6 +832,17 @@ function createMachineStores(kv) {
|
|
|
834
832
|
};
|
|
835
833
|
}
|
|
836
834
|
|
|
835
|
+
// modules/payments-v2/machine/payload-view.ts
|
|
836
|
+
function isWholeIntent(p) {
|
|
837
|
+
return p.kind === "whole" || p.kind === "coinless";
|
|
838
|
+
}
|
|
839
|
+
function splitOf(p) {
|
|
840
|
+
return p.kind === "coin" ? p.split : void 0;
|
|
841
|
+
}
|
|
842
|
+
function coinIdOf(p) {
|
|
843
|
+
return p.kind === "coin" ? p.coinId : void 0;
|
|
844
|
+
}
|
|
845
|
+
|
|
837
846
|
// token-engine/errors.ts
|
|
838
847
|
var TransferConflictError = class extends SphereError {
|
|
839
848
|
/**
|
|
@@ -1075,13 +1084,14 @@ var TransferMachine = class {
|
|
|
1075
1084
|
const finished = await engine.transfer({ token: source, recipientPubkey }, opts);
|
|
1076
1085
|
return { recipientBlob: finished.blob.token };
|
|
1077
1086
|
}
|
|
1078
|
-
const split = ctx.payload
|
|
1087
|
+
const split = splitOf(ctx.payload);
|
|
1088
|
+
const splitCoinId = coinIdOf(ctx.payload);
|
|
1079
1089
|
const { outputs } = await engine.split(
|
|
1080
1090
|
{
|
|
1081
1091
|
token: source,
|
|
1082
1092
|
outputs: [
|
|
1083
|
-
{ recipientPubkey, coinId:
|
|
1084
|
-
{ recipientPubkey: this.deps.ownPubkey, coinId:
|
|
1093
|
+
{ recipientPubkey, coinId: splitCoinId, amount: BigInt(split.splitAmount) },
|
|
1094
|
+
{ recipientPubkey: this.deps.ownPubkey, coinId: splitCoinId, amount: BigInt(split.remainderAmount) }
|
|
1085
1095
|
]
|
|
1086
1096
|
},
|
|
1087
1097
|
opts
|
|
@@ -1203,7 +1213,9 @@ var TransferMachine = class {
|
|
|
1203
1213
|
const entry = {
|
|
1204
1214
|
transferId: r.transferId,
|
|
1205
1215
|
remainingAmount: undelivered.toString(),
|
|
1206
|
-
|
|
1216
|
+
// '' only on a token-addressed intent, which is one leg: it either lands or
|
|
1217
|
+
// conflicts, so a shortfall row is unreachable there (settlePartial needs >1).
|
|
1218
|
+
coinId: coinIdOf(r.payload) ?? "",
|
|
1207
1219
|
recipient: r.payload.recipient,
|
|
1208
1220
|
committedTokenIds: committed.map((o) => o.op.sourceTokenId),
|
|
1209
1221
|
createdAt: this.deps.now()
|
|
@@ -1229,8 +1241,16 @@ var TransferMachine = class {
|
|
|
1229
1241
|
}
|
|
1230
1242
|
async finishBookkeeping(engine, transferId, payload, phase, committed) {
|
|
1231
1243
|
try {
|
|
1232
|
-
const
|
|
1233
|
-
await this.
|
|
1244
|
+
const coinId = coinIdOf(payload);
|
|
1245
|
+
const committedAmount = coinId === void 0 ? "0" : await this.settledAmount(engine, coinId, committed);
|
|
1246
|
+
const committedAssets = coinId === void 0 ? await this.settledAssets(engine, committed) : void 0;
|
|
1247
|
+
await this.deps.recordHistory?.({
|
|
1248
|
+
transferId,
|
|
1249
|
+
payload,
|
|
1250
|
+
phase,
|
|
1251
|
+
committedAmount,
|
|
1252
|
+
...committedAssets !== void 0 ? { committedAssets } : {}
|
|
1253
|
+
});
|
|
1234
1254
|
} catch {
|
|
1235
1255
|
}
|
|
1236
1256
|
try {
|
|
@@ -1244,6 +1264,18 @@ var TransferMachine = class {
|
|
|
1244
1264
|
* a journal-rehydrated resume leg has no source token, but every committed
|
|
1245
1265
|
* leg carries its recipient blob, so the outcome-derived rule is uniform).
|
|
1246
1266
|
*/
|
|
1267
|
+
/** Every asset across the certified recipient blobs — the whole-spend twin of settledAmount. */
|
|
1268
|
+
async settledAssets(engine, committed) {
|
|
1269
|
+
const total = /* @__PURE__ */ new Map();
|
|
1270
|
+
for (const outcome of committed) {
|
|
1271
|
+
if (outcome.recipientBlob === void 0) continue;
|
|
1272
|
+
const token = await engine.decodeToken({ tokenId: "", token: outcome.recipientBlob });
|
|
1273
|
+
for (const asset of engine.readValue(token)?.assets ?? []) {
|
|
1274
|
+
total.set(asset.coinId, (total.get(asset.coinId) ?? 0n) + asset.amount);
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
return [...total].map(([coinId, amount]) => ({ coinId, amount: amount.toString() }));
|
|
1278
|
+
}
|
|
1247
1279
|
async settledAmount(engine, coinId, committed) {
|
|
1248
1280
|
let total = 0n;
|
|
1249
1281
|
for (const outcome of committed) {
|
|
@@ -1272,8 +1304,11 @@ var TransferMachine = class {
|
|
|
1272
1304
|
}
|
|
1273
1305
|
};
|
|
1274
1306
|
function conflictAmount(engine, payload, op, source) {
|
|
1275
|
-
|
|
1276
|
-
|
|
1307
|
+
const split = splitOf(payload);
|
|
1308
|
+
if (op.kind === "split") return BigInt(split.splitAmount);
|
|
1309
|
+
const coinId = coinIdOf(payload);
|
|
1310
|
+
if (coinId === void 0) return 0n;
|
|
1311
|
+
return source !== void 0 ? engine.balanceOf(source, coinId) : 0n;
|
|
1277
1312
|
}
|
|
1278
1313
|
|
|
1279
1314
|
// modules/payments-v2/machine/resume.ts
|
|
@@ -1380,6 +1415,14 @@ async function runOne(ctx, job, report) {
|
|
|
1380
1415
|
report.failed.push(job.transferId);
|
|
1381
1416
|
return;
|
|
1382
1417
|
}
|
|
1418
|
+
if (isWholeIntent(job.payload) && token.valueEnvelope === "bare_collection") {
|
|
1419
|
+
logger.warn(
|
|
1420
|
+
"PaymentsV2",
|
|
1421
|
+
`resume: token intent ${job.transferId} names a source carrying coin value \u2014 refusing`
|
|
1422
|
+
);
|
|
1423
|
+
report.failed.push(job.transferId);
|
|
1424
|
+
return;
|
|
1425
|
+
}
|
|
1383
1426
|
mine.set(op.sourceTokenId, token);
|
|
1384
1427
|
}
|
|
1385
1428
|
try {
|
|
@@ -1410,6 +1453,28 @@ async function classifyResumeFailure(deps, stores, transferId, err, report) {
|
|
|
1410
1453
|
}
|
|
1411
1454
|
report.failed.push(transferId);
|
|
1412
1455
|
}
|
|
1456
|
+
function validateCoinPayload(c) {
|
|
1457
|
+
if (typeof c.coinId !== "string" || typeof c.amount !== "string") {
|
|
1458
|
+
throw new SphereError("intent payload is missing coinId/amount", "VALIDATION_ERROR");
|
|
1459
|
+
}
|
|
1460
|
+
const s = c.split;
|
|
1461
|
+
if (s !== void 0 && (typeof s.tokenId !== "string" || typeof s.splitAmount !== "string" || typeof s.remainderAmount !== "string")) {
|
|
1462
|
+
throw new SphereError("intent payload split spec is malformed", "VALIDATION_ERROR");
|
|
1463
|
+
}
|
|
1464
|
+
return { ...c, kind: "coin" };
|
|
1465
|
+
}
|
|
1466
|
+
function validateWholePayload(p) {
|
|
1467
|
+
if (p.direct?.length !== 1 || typeof p.direct[0] !== "string" || p.direct[0] === "") {
|
|
1468
|
+
throw new SphereError(
|
|
1469
|
+
"token intent payload must name exactly one source token",
|
|
1470
|
+
"VALIDATION_ERROR"
|
|
1471
|
+
);
|
|
1472
|
+
}
|
|
1473
|
+
if (p.split !== void 0) {
|
|
1474
|
+
throw new SphereError("token intent payload cannot carry a split", "VALIDATION_ERROR");
|
|
1475
|
+
}
|
|
1476
|
+
return { ...p, kind: "whole", direct: [p.direct[0]] };
|
|
1477
|
+
}
|
|
1413
1478
|
function validatePayload(raw) {
|
|
1414
1479
|
const p = raw !== null && typeof raw === "object" ? raw : null;
|
|
1415
1480
|
if (p === null || p.v !== 2 || !Array.isArray(p.direct) || p.direct.some((t) => typeof t !== "string")) {
|
|
@@ -1418,14 +1483,17 @@ function validatePayload(raw) {
|
|
|
1418
1483
|
"VALIDATION_ERROR"
|
|
1419
1484
|
);
|
|
1420
1485
|
}
|
|
1421
|
-
if (typeof p.recipient !== "string"
|
|
1422
|
-
throw new SphereError("intent payload is missing recipient
|
|
1486
|
+
if (typeof p.recipient !== "string") {
|
|
1487
|
+
throw new SphereError("intent payload is missing recipient", "VALIDATION_ERROR");
|
|
1423
1488
|
}
|
|
1424
|
-
const
|
|
1425
|
-
if (
|
|
1426
|
-
throw new SphereError(
|
|
1489
|
+
const kind = p.kind ?? "coin";
|
|
1490
|
+
if (kind !== "coin" && kind !== "whole" && kind !== "coinless") {
|
|
1491
|
+
throw new SphereError(
|
|
1492
|
+
`unsupported intent kind '${String(kind)}' \u2014 not resumable by this client`,
|
|
1493
|
+
"VALIDATION_ERROR"
|
|
1494
|
+
);
|
|
1427
1495
|
}
|
|
1428
|
-
return p;
|
|
1496
|
+
return kind === "coin" ? validateCoinPayload(p) : validateWholePayload(p);
|
|
1429
1497
|
}
|
|
1430
1498
|
|
|
1431
1499
|
// modules/payments-v2/convergence.ts
|
|
@@ -1625,46 +1693,314 @@ async function openRow(entry, journal, decryptPayload) {
|
|
|
1625
1693
|
} catch {
|
|
1626
1694
|
payload = null;
|
|
1627
1695
|
}
|
|
1628
|
-
const total = Array.isArray(payload?.direct) ? payload.direct.length + (payload.split !== void 0 ? 1 : 0) : 0;
|
|
1629
|
-
const certified = distinctOpCount(journal);
|
|
1630
|
-
return {
|
|
1631
|
-
transferId: entry.transferId,
|
|
1632
|
-
kind: "open",
|
|
1633
|
-
recipient: typeof payload?.recipient === "string" ? payload.recipient : "",
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1696
|
+
const total = Array.isArray(payload?.direct) ? payload.direct.length + (payload.split !== void 0 ? 1 : 0) : 0;
|
|
1697
|
+
const certified = distinctOpCount(journal);
|
|
1698
|
+
return {
|
|
1699
|
+
transferId: entry.transferId,
|
|
1700
|
+
kind: "open",
|
|
1701
|
+
recipient: typeof payload?.recipient === "string" ? payload.recipient : "",
|
|
1702
|
+
...subject(payload),
|
|
1703
|
+
legs: { certified, total: Math.max(total, certified) },
|
|
1704
|
+
deliveryPending: journal.length > 0,
|
|
1705
|
+
createdAt: entry.createdAt
|
|
1706
|
+
};
|
|
1707
|
+
}
|
|
1708
|
+
function subject(payload) {
|
|
1709
|
+
const rawKind = payload?.kind;
|
|
1710
|
+
if (rawKind === "whole" || rawKind === "coinless") {
|
|
1711
|
+
const tokenId = payload?.direct?.[0];
|
|
1712
|
+
return { coinId: "", amount: "", ...typeof tokenId === "string" ? { tokenId } : {} };
|
|
1713
|
+
}
|
|
1714
|
+
const coin = payload;
|
|
1715
|
+
return {
|
|
1716
|
+
coinId: typeof coin?.coinId === "string" ? coin.coinId : "",
|
|
1717
|
+
amount: typeof coin?.amount === "string" ? coin.amount : ""
|
|
1718
|
+
};
|
|
1719
|
+
}
|
|
1720
|
+
function journalOnlyRow(transferId, entries) {
|
|
1721
|
+
const certified = distinctOpCount(entries);
|
|
1722
|
+
return {
|
|
1723
|
+
transferId,
|
|
1724
|
+
kind: "open",
|
|
1725
|
+
recipient: entries[0].recipientPubkey,
|
|
1726
|
+
coinId: "",
|
|
1727
|
+
amount: "",
|
|
1728
|
+
legs: { certified, total: certified },
|
|
1729
|
+
deliveryPending: true,
|
|
1730
|
+
createdAt: 0
|
|
1731
|
+
};
|
|
1732
|
+
}
|
|
1733
|
+
function shortfallRow(entry) {
|
|
1734
|
+
return {
|
|
1735
|
+
transferId: entry.transferId,
|
|
1736
|
+
kind: "shortfall",
|
|
1737
|
+
recipient: entry.recipient,
|
|
1738
|
+
coinId: entry.coinId,
|
|
1739
|
+
amount: entry.remainingAmount,
|
|
1740
|
+
legs: { certified: entry.committedTokenIds.length, total: entry.committedTokenIds.length },
|
|
1741
|
+
deliveryPending: false,
|
|
1742
|
+
createdAt: entry.createdAt
|
|
1743
|
+
};
|
|
1744
|
+
}
|
|
1745
|
+
function distinctOpCount(entries) {
|
|
1746
|
+
return new Set(entries.map((entry) => entry.opIndex)).size;
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
// modules/payments-v2/inventory/token-data.ts
|
|
1750
|
+
async function readTokenData(deps, tokenId) {
|
|
1751
|
+
if (deps.view.stateHashOf(tokenId) === void 0) {
|
|
1752
|
+
throw new SphereError(`Token ${tokenId} is not in inventory`, "VALIDATION_ERROR");
|
|
1753
|
+
}
|
|
1754
|
+
const bytes = (await deps.storagePort.getBlobs([tokenId])).get(tokenId);
|
|
1755
|
+
if (bytes === void 0) {
|
|
1756
|
+
throw new SphereError(`Token ${tokenId} has no blob in storage`, "STORAGE_ERROR");
|
|
1757
|
+
}
|
|
1758
|
+
return deps.engine.readTokenData(await deps.engine.decodeToken({ tokenId, token: bytes }));
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
// modules/payments-v2/machine/payload.ts
|
|
1762
|
+
function buildPayload(recipientPubkey, request, spend, spentStates) {
|
|
1763
|
+
return {
|
|
1764
|
+
v: 2,
|
|
1765
|
+
kind: "coin",
|
|
1766
|
+
recipient: recipientPubkey,
|
|
1767
|
+
coinId: request.coinId,
|
|
1768
|
+
amount: request.amount,
|
|
1769
|
+
...request.memo !== void 0 ? { memo: request.memo } : {},
|
|
1770
|
+
direct: [...spend.plan.direct],
|
|
1771
|
+
...spend.plan.split !== void 0 ? {
|
|
1772
|
+
split: {
|
|
1773
|
+
tokenId: spend.plan.split.tokenId,
|
|
1774
|
+
splitAmount: spend.plan.split.splitAmount.toString(),
|
|
1775
|
+
remainderAmount: spend.plan.split.remainderAmount.toString()
|
|
1776
|
+
}
|
|
1777
|
+
} : {},
|
|
1778
|
+
spentStates
|
|
1779
|
+
};
|
|
1780
|
+
}
|
|
1781
|
+
function messageOf(err) {
|
|
1782
|
+
return err instanceof Error ? err.message : String(err);
|
|
1783
|
+
}
|
|
1784
|
+
function buildWholePayload(recipientPubkey, request, spentStates) {
|
|
1785
|
+
return {
|
|
1786
|
+
v: 2,
|
|
1787
|
+
kind: "whole",
|
|
1788
|
+
recipient: recipientPubkey,
|
|
1789
|
+
...request.memo !== void 0 ? { memo: request.memo } : {},
|
|
1790
|
+
direct: [request.tokenId],
|
|
1791
|
+
spentStates
|
|
1792
|
+
};
|
|
1793
|
+
}
|
|
1794
|
+
|
|
1795
|
+
// modules/payments-v2/mint-params.ts
|
|
1796
|
+
function mintParams(recipientPubkey, coinId, amount) {
|
|
1797
|
+
return { recipientPubkey, value: { assets: [{ coinId, amount }] } };
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
// modules/payments-v2/mint.ts
|
|
1801
|
+
function runMintUnderJournal(deps, input) {
|
|
1802
|
+
return mintUnderJournal(deps, input);
|
|
1803
|
+
}
|
|
1804
|
+
async function mintUnderJournal(deps, input) {
|
|
1805
|
+
const { mintId, coinId, amount } = input;
|
|
1806
|
+
const engine = deps.engine;
|
|
1807
|
+
const entry = {
|
|
1808
|
+
mintId,
|
|
1809
|
+
coinId,
|
|
1810
|
+
amount: amount.toString(),
|
|
1811
|
+
tokenId: "",
|
|
1812
|
+
createdAt: deps.now()
|
|
1813
|
+
};
|
|
1814
|
+
await deps.mintJournal.upsert(entry);
|
|
1815
|
+
let token;
|
|
1816
|
+
try {
|
|
1817
|
+
token = await engine.mint(mintParams(deps.ownPubkeyBytes, coinId, amount), { transferId: mintId });
|
|
1818
|
+
} catch (err) {
|
|
1819
|
+
deps.armHeartbeat();
|
|
1820
|
+
return { success: false, error: messageOf(err) };
|
|
1821
|
+
}
|
|
1822
|
+
if (token.blob.tokenId !== entry.tokenId) {
|
|
1823
|
+
await deps.mintJournal.upsert({ ...entry, tokenId: token.blob.tokenId });
|
|
1824
|
+
}
|
|
1825
|
+
try {
|
|
1826
|
+
await finalizeMint(deps, mintId, token, coinId, amount.toString());
|
|
1827
|
+
} catch (err) {
|
|
1828
|
+
deps.armHeartbeat();
|
|
1829
|
+
return { success: false, tokenId: token.blob.tokenId, error: messageOf(err) };
|
|
1830
|
+
}
|
|
1831
|
+
return { success: true, tokenId: token.blob.tokenId };
|
|
1832
|
+
}
|
|
1833
|
+
async function finalizeMint(deps, mintId, token, coinId, amount) {
|
|
1834
|
+
const engine = deps.engine;
|
|
1835
|
+
const bytes = token.blob.token;
|
|
1836
|
+
const digest = bytesToHex(sha256(bytes));
|
|
1837
|
+
const keys = await deps.storagePort.uploadBlobs([{ sha256: digest, bytes }]);
|
|
1838
|
+
const key = keys.get(digest);
|
|
1839
|
+
if (key === void 0) {
|
|
1840
|
+
throw new SphereError(`no upload key returned for mint blob ${digest}`, "STORAGE_ERROR");
|
|
1841
|
+
}
|
|
1842
|
+
await deps.storagePort.applyDelta({
|
|
1843
|
+
transferId: mintId,
|
|
1844
|
+
spent: [],
|
|
1845
|
+
added: [{ tokenId: token.blob.tokenId, key }]
|
|
1846
|
+
});
|
|
1847
|
+
await deps.recordMint({
|
|
1848
|
+
tokenId: token.blob.tokenId,
|
|
1849
|
+
assets: [{ coinId, amount }]
|
|
1850
|
+
});
|
|
1851
|
+
await deps.mintJournal.removeByKey(mintId);
|
|
1852
|
+
deps.noteHeldState(token.blob.tokenId, (await engine.deliveryKeys(bytes)).stateHash);
|
|
1853
|
+
deps.refreshView();
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1856
|
+
// modules/payments-v2/inventory/presentation.ts
|
|
1857
|
+
function toToken(tokenId, snapshot, asset, registry, flags) {
|
|
1858
|
+
const iconUrl = registry.getIconUrl(asset.coinId);
|
|
1859
|
+
return {
|
|
1860
|
+
id: tokenId,
|
|
1861
|
+
coinId: asset.coinId,
|
|
1862
|
+
symbol: registry.getSymbol(asset.coinId),
|
|
1863
|
+
name: registry.getName(asset.coinId),
|
|
1864
|
+
decimals: registry.getDecimals(asset.coinId),
|
|
1865
|
+
...iconUrl !== null ? { iconUrl } : {},
|
|
1866
|
+
amount: asset.amount,
|
|
1867
|
+
status: flags.transferring ? "transferring" : "confirmed",
|
|
1868
|
+
createdAt: snapshot.createdAt,
|
|
1869
|
+
updatedAt: snapshot.updatedAt,
|
|
1870
|
+
lazy: true,
|
|
1871
|
+
...flags.suspectedSpent ? { suspectedSpent: true } : {}
|
|
1872
|
+
};
|
|
1873
|
+
}
|
|
1874
|
+
function aggregateAssets(entries, isTransferring, registry) {
|
|
1875
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1876
|
+
for (const [tokenId, entry] of entries) {
|
|
1877
|
+
const moving = isTransferring(tokenId);
|
|
1878
|
+
for (const asset of entry.assets) {
|
|
1879
|
+
let group = groups.get(asset.coinId);
|
|
1880
|
+
if (!group) {
|
|
1881
|
+
group = { total: 0n, count: 0, transferring: 0n, transferringCount: 0 };
|
|
1882
|
+
groups.set(asset.coinId, group);
|
|
1883
|
+
}
|
|
1884
|
+
if (moving) {
|
|
1885
|
+
group.transferring += BigInt(asset.amount);
|
|
1886
|
+
group.transferringCount += 1;
|
|
1887
|
+
} else {
|
|
1888
|
+
group.total += BigInt(asset.amount);
|
|
1889
|
+
group.count += 1;
|
|
1890
|
+
}
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1893
|
+
return [...groups].map(([coinId, totals]) => toAsset(coinId, totals, registry));
|
|
1894
|
+
}
|
|
1895
|
+
function toAsset(coinId, totals, registry) {
|
|
1896
|
+
const iconUrl = registry.getIconUrl(coinId);
|
|
1897
|
+
return {
|
|
1898
|
+
coinId,
|
|
1899
|
+
symbol: registry.getSymbol(coinId),
|
|
1900
|
+
name: registry.getName(coinId),
|
|
1901
|
+
decimals: registry.getDecimals(coinId),
|
|
1902
|
+
...iconUrl !== null ? { iconUrl } : {},
|
|
1903
|
+
totalAmount: totals.total.toString(),
|
|
1904
|
+
tokenCount: totals.count,
|
|
1905
|
+
confirmedAmount: totals.total.toString(),
|
|
1906
|
+
unconfirmedAmount: "0",
|
|
1907
|
+
confirmedTokenCount: totals.count,
|
|
1908
|
+
unconfirmedTokenCount: 0,
|
|
1909
|
+
transferringTokenCount: totals.transferringCount,
|
|
1910
|
+
transferringAmount: totals.transferring.toString(),
|
|
1911
|
+
priceUsd: null,
|
|
1912
|
+
priceEur: null,
|
|
1913
|
+
change24h: null,
|
|
1914
|
+
fiatValueUsd: null,
|
|
1915
|
+
fiatValueEur: null
|
|
1916
|
+
};
|
|
1917
|
+
}
|
|
1918
|
+
function priceIdOf(registry, coinId) {
|
|
1919
|
+
return registry.getName(coinId).toLowerCase();
|
|
1920
|
+
}
|
|
1921
|
+
async function withPrices(raw, registry, price) {
|
|
1922
|
+
try {
|
|
1923
|
+
const ids = [...new Set(raw.map((a) => priceIdOf(registry, a.coinId)))];
|
|
1924
|
+
const quotes = await price.getPrices(ids);
|
|
1925
|
+
return raw.map((a) => priceOne(a, quotes.get(priceIdOf(registry, a.coinId))));
|
|
1926
|
+
} catch {
|
|
1927
|
+
return raw;
|
|
1928
|
+
}
|
|
1640
1929
|
}
|
|
1641
|
-
function
|
|
1642
|
-
|
|
1930
|
+
function priceOne(asset, quote) {
|
|
1931
|
+
if (!quote) return asset;
|
|
1932
|
+
const human = Number(asset.totalAmount) / Math.pow(10, asset.decimals);
|
|
1643
1933
|
return {
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
deliveryPending: true,
|
|
1651
|
-
createdAt: 0
|
|
1934
|
+
...asset,
|
|
1935
|
+
priceUsd: quote.priceUsd,
|
|
1936
|
+
priceEur: quote.priceEur ?? null,
|
|
1937
|
+
change24h: quote.change24h ?? null,
|
|
1938
|
+
fiatValueUsd: human * quote.priceUsd,
|
|
1939
|
+
fiatValueEur: quote.priceEur != null ? human * quote.priceEur : null
|
|
1652
1940
|
};
|
|
1653
1941
|
}
|
|
1654
|
-
function
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1942
|
+
function transferringToken(tokenId, coinId, amount, registry, now) {
|
|
1943
|
+
const snapshot = { assets: [], createdAt: now, updatedAt: now };
|
|
1944
|
+
return toToken(tokenId, snapshot, { coinId, amount: amount.toString() }, registry, {
|
|
1945
|
+
transferring: true,
|
|
1946
|
+
suspectedSpent: false
|
|
1947
|
+
});
|
|
1948
|
+
}
|
|
1949
|
+
|
|
1950
|
+
// modules/payments-v2/send-whole.ts
|
|
1951
|
+
async function materializeWholeSpend(deps, input) {
|
|
1952
|
+
const { engine } = deps;
|
|
1953
|
+
const tokenId = input.request.tokenId;
|
|
1954
|
+
const bytes = (await deps.storagePort.getBlobs([tokenId])).get(tokenId);
|
|
1955
|
+
if (bytes === void 0) {
|
|
1956
|
+
throw new SphereError(`Selected source ${tokenId} has no blob in storage`, "STORAGE_ERROR");
|
|
1957
|
+
}
|
|
1958
|
+
const token = await engine.decodeToken({ tokenId, token: bytes });
|
|
1959
|
+
if (token.valueEnvelope === "bare_collection") {
|
|
1960
|
+
throw new SphereError(
|
|
1961
|
+
`Token ${tokenId} carries a value envelope this SDK cannot read, so its coins cannot be accounted for; it cannot be sent`,
|
|
1962
|
+
"VALIDATION_ERROR"
|
|
1963
|
+
);
|
|
1964
|
+
}
|
|
1965
|
+
if (input.requireCoinless && token.value !== null) {
|
|
1966
|
+
throw new SphereError(
|
|
1967
|
+
`Token ${tokenId} carries coin value and cannot be sent with sendCoinless \u2014 use sendWholeToken, which requires coin-transfer authority`,
|
|
1968
|
+
"VALIDATION_ERROR"
|
|
1969
|
+
);
|
|
1970
|
+
}
|
|
1971
|
+
const keys = await engine.deliveryKeys(bytes);
|
|
1972
|
+
const payload = buildWholePayload(input.recipientPubkey, input.request, {
|
|
1973
|
+
[tokenId]: { local: keys.stateHash, protocol: keys.stateHash }
|
|
1974
|
+
});
|
|
1975
|
+
const plan = {
|
|
1976
|
+
transferId: input.transferId,
|
|
1977
|
+
recipientPubkey: input.recipientPubkey,
|
|
1978
|
+
payload,
|
|
1979
|
+
ops: buildOps(payload),
|
|
1980
|
+
sources: /* @__PURE__ */ new Map([[tokenId, token]]),
|
|
1981
|
+
...input.request.memo !== void 0 ? { memo: input.request.memo } : {}
|
|
1664
1982
|
};
|
|
1983
|
+
const sourceTokens = (token.value?.assets ?? []).map(
|
|
1984
|
+
(asset) => transferringToken(tokenId, asset.coinId, asset.amount, deps.registry, deps.now)
|
|
1985
|
+
);
|
|
1986
|
+
return { transferId: input.transferId, coinId: "", plan, sourceIds: input.sourceIds, sourceTokens };
|
|
1665
1987
|
}
|
|
1666
|
-
|
|
1667
|
-
|
|
1988
|
+
|
|
1989
|
+
// modules/payments-v2/send-errors.ts
|
|
1990
|
+
function partialize(err, run) {
|
|
1991
|
+
if (run.delivered.length === 0) return err;
|
|
1992
|
+
return new PartialSendConflictError(
|
|
1993
|
+
"Part of your payment was sent; the remaining amount could not be completed (see cause). The delivered portion is final \u2014 re-plan only the shortfall, never the full amount.",
|
|
1994
|
+
run.firstPartialId ?? "",
|
|
1995
|
+
[...run.delivered],
|
|
1996
|
+
run.amount,
|
|
1997
|
+
err
|
|
1998
|
+
);
|
|
1999
|
+
}
|
|
2000
|
+
function stampTransferId(err, transferId) {
|
|
2001
|
+
if (err instanceof SphereError && isPossiblyCommittedSendOutcome(err) && err.transferId === void 0) {
|
|
2002
|
+
err.transferId = transferId;
|
|
2003
|
+
}
|
|
1668
2004
|
}
|
|
1669
2005
|
|
|
1670
2006
|
// modules/payments-v2/recipient.ts
|
|
@@ -1715,11 +2051,6 @@ async function splitOpIndex(deps, envelope) {
|
|
|
1715
2051
|
return Array.isArray(payload.direct) ? payload.direct.length : 0;
|
|
1716
2052
|
}
|
|
1717
2053
|
|
|
1718
|
-
// modules/payments-v2/mint-params.ts
|
|
1719
|
-
function mintParams(recipientPubkey, coinId, amount) {
|
|
1720
|
-
return { recipientPubkey, value: { assets: [{ coinId, amount }] } };
|
|
1721
|
-
}
|
|
1722
|
-
|
|
1723
2054
|
// modules/payments-v2/prewarm-cache.ts
|
|
1724
2055
|
var PrewarmCache = class _PrewarmCache {
|
|
1725
2056
|
entries = /* @__PURE__ */ new Map();
|
|
@@ -1799,123 +2130,6 @@ async function takeSourceBlobs(deps, sourceIds) {
|
|
|
1799
2130
|
return blobs;
|
|
1800
2131
|
}
|
|
1801
2132
|
|
|
1802
|
-
// modules/payments-v2/inventory/presentation.ts
|
|
1803
|
-
function toToken(tokenId, snapshot, asset, registry, flags) {
|
|
1804
|
-
const iconUrl = registry.getIconUrl(asset.coinId);
|
|
1805
|
-
return {
|
|
1806
|
-
id: tokenId,
|
|
1807
|
-
coinId: asset.coinId,
|
|
1808
|
-
symbol: registry.getSymbol(asset.coinId),
|
|
1809
|
-
name: registry.getName(asset.coinId),
|
|
1810
|
-
decimals: registry.getDecimals(asset.coinId),
|
|
1811
|
-
...iconUrl !== null ? { iconUrl } : {},
|
|
1812
|
-
amount: asset.amount,
|
|
1813
|
-
status: flags.transferring ? "transferring" : "confirmed",
|
|
1814
|
-
createdAt: snapshot.createdAt,
|
|
1815
|
-
updatedAt: snapshot.updatedAt,
|
|
1816
|
-
lazy: true,
|
|
1817
|
-
...flags.suspectedSpent ? { suspectedSpent: true } : {}
|
|
1818
|
-
};
|
|
1819
|
-
}
|
|
1820
|
-
function aggregateAssets(entries, isTransferring, registry) {
|
|
1821
|
-
const groups = /* @__PURE__ */ new Map();
|
|
1822
|
-
for (const [tokenId, entry] of entries) {
|
|
1823
|
-
const moving = isTransferring(tokenId);
|
|
1824
|
-
for (const asset of entry.assets) {
|
|
1825
|
-
let group = groups.get(asset.coinId);
|
|
1826
|
-
if (!group) {
|
|
1827
|
-
group = { total: 0n, count: 0, transferring: 0n, transferringCount: 0 };
|
|
1828
|
-
groups.set(asset.coinId, group);
|
|
1829
|
-
}
|
|
1830
|
-
if (moving) {
|
|
1831
|
-
group.transferring += BigInt(asset.amount);
|
|
1832
|
-
group.transferringCount += 1;
|
|
1833
|
-
} else {
|
|
1834
|
-
group.total += BigInt(asset.amount);
|
|
1835
|
-
group.count += 1;
|
|
1836
|
-
}
|
|
1837
|
-
}
|
|
1838
|
-
}
|
|
1839
|
-
return [...groups].map(([coinId, totals]) => toAsset(coinId, totals, registry));
|
|
1840
|
-
}
|
|
1841
|
-
function toAsset(coinId, totals, registry) {
|
|
1842
|
-
const iconUrl = registry.getIconUrl(coinId);
|
|
1843
|
-
return {
|
|
1844
|
-
coinId,
|
|
1845
|
-
symbol: registry.getSymbol(coinId),
|
|
1846
|
-
name: registry.getName(coinId),
|
|
1847
|
-
decimals: registry.getDecimals(coinId),
|
|
1848
|
-
...iconUrl !== null ? { iconUrl } : {},
|
|
1849
|
-
totalAmount: totals.total.toString(),
|
|
1850
|
-
tokenCount: totals.count,
|
|
1851
|
-
confirmedAmount: totals.total.toString(),
|
|
1852
|
-
unconfirmedAmount: "0",
|
|
1853
|
-
confirmedTokenCount: totals.count,
|
|
1854
|
-
unconfirmedTokenCount: 0,
|
|
1855
|
-
transferringTokenCount: totals.transferringCount,
|
|
1856
|
-
transferringAmount: totals.transferring.toString(),
|
|
1857
|
-
priceUsd: null,
|
|
1858
|
-
priceEur: null,
|
|
1859
|
-
change24h: null,
|
|
1860
|
-
fiatValueUsd: null,
|
|
1861
|
-
fiatValueEur: null
|
|
1862
|
-
};
|
|
1863
|
-
}
|
|
1864
|
-
function priceIdOf(registry, coinId) {
|
|
1865
|
-
return registry.getName(coinId).toLowerCase();
|
|
1866
|
-
}
|
|
1867
|
-
async function withPrices(raw, registry, price) {
|
|
1868
|
-
try {
|
|
1869
|
-
const ids = [...new Set(raw.map((a) => priceIdOf(registry, a.coinId)))];
|
|
1870
|
-
const quotes = await price.getPrices(ids);
|
|
1871
|
-
return raw.map((a) => priceOne(a, quotes.get(priceIdOf(registry, a.coinId))));
|
|
1872
|
-
} catch {
|
|
1873
|
-
return raw;
|
|
1874
|
-
}
|
|
1875
|
-
}
|
|
1876
|
-
function priceOne(asset, quote) {
|
|
1877
|
-
if (!quote) return asset;
|
|
1878
|
-
const human = Number(asset.totalAmount) / Math.pow(10, asset.decimals);
|
|
1879
|
-
return {
|
|
1880
|
-
...asset,
|
|
1881
|
-
priceUsd: quote.priceUsd,
|
|
1882
|
-
priceEur: quote.priceEur ?? null,
|
|
1883
|
-
change24h: quote.change24h ?? null,
|
|
1884
|
-
fiatValueUsd: human * quote.priceUsd,
|
|
1885
|
-
fiatValueEur: quote.priceEur != null ? human * quote.priceEur : null
|
|
1886
|
-
};
|
|
1887
|
-
}
|
|
1888
|
-
function transferringToken(tokenId, coinId, amount, registry, now) {
|
|
1889
|
-
const snapshot = { assets: [], createdAt: now, updatedAt: now };
|
|
1890
|
-
return toToken(tokenId, snapshot, { coinId, amount: amount.toString() }, registry, {
|
|
1891
|
-
transferring: true,
|
|
1892
|
-
suspectedSpent: false
|
|
1893
|
-
});
|
|
1894
|
-
}
|
|
1895
|
-
|
|
1896
|
-
// modules/payments-v2/machine/payload.ts
|
|
1897
|
-
function buildPayload(recipientPubkey, request, spend, spentStates) {
|
|
1898
|
-
return {
|
|
1899
|
-
v: 2,
|
|
1900
|
-
recipient: recipientPubkey,
|
|
1901
|
-
coinId: request.coinId,
|
|
1902
|
-
amount: request.amount,
|
|
1903
|
-
...request.memo !== void 0 ? { memo: request.memo } : {},
|
|
1904
|
-
direct: [...spend.plan.direct],
|
|
1905
|
-
...spend.plan.split !== void 0 ? {
|
|
1906
|
-
split: {
|
|
1907
|
-
tokenId: spend.plan.split.tokenId,
|
|
1908
|
-
splitAmount: spend.plan.split.splitAmount.toString(),
|
|
1909
|
-
remainderAmount: spend.plan.split.remainderAmount.toString()
|
|
1910
|
-
}
|
|
1911
|
-
} : {},
|
|
1912
|
-
spentStates
|
|
1913
|
-
};
|
|
1914
|
-
}
|
|
1915
|
-
function messageOf(err) {
|
|
1916
|
-
return err instanceof Error ? err.message : String(err);
|
|
1917
|
-
}
|
|
1918
|
-
|
|
1919
2133
|
// node_modules/@noble/ciphers/utils.js
|
|
1920
2134
|
function isBytes2(a) {
|
|
1921
2135
|
return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array" && "BYTES_PER_ELEMENT" in a && a.BYTES_PER_ELEMENT === 1;
|
|
@@ -2767,6 +2981,14 @@ function decryptField(key, envelope) {
|
|
|
2767
2981
|
}
|
|
2768
2982
|
|
|
2769
2983
|
// modules/payments-v2/history/History.ts
|
|
2984
|
+
function wireAssets(assets) {
|
|
2985
|
+
return assets.map((a) => ({ coinId: a.coinId, amount: a.amount }));
|
|
2986
|
+
}
|
|
2987
|
+
function isPermanentReject(err) {
|
|
2988
|
+
const status = err?.status;
|
|
2989
|
+
if (typeof status !== "number") return false;
|
|
2990
|
+
return status >= 400 && status < 500 && status !== 408 && status !== 429;
|
|
2991
|
+
}
|
|
2770
2992
|
var WIRE_TOKEN_ID = /^(?:[0-9a-f]{2}){1,64}$/;
|
|
2771
2993
|
var WIRE_PUBKEY = /^0[23][0-9a-f]{64}$/;
|
|
2772
2994
|
var History = class {
|
|
@@ -2791,7 +3013,7 @@ var History = class {
|
|
|
2791
3013
|
dedupKey: input.transferId,
|
|
2792
3014
|
id: (this.deps.newId ?? randomUUID)(),
|
|
2793
3015
|
type: "SENT",
|
|
2794
|
-
assets:
|
|
3016
|
+
assets: wireAssets(input.assets),
|
|
2795
3017
|
ts: this.ts(input.timestamp),
|
|
2796
3018
|
transferId: input.transferId,
|
|
2797
3019
|
...this.wireTokenId(input.tokenId),
|
|
@@ -2804,7 +3026,7 @@ var History = class {
|
|
|
2804
3026
|
dedupKey: `RECEIVED:${input.tokenId.toLowerCase()}:${input.stateHash.toLowerCase()}`,
|
|
2805
3027
|
id: (this.deps.newId ?? randomUUID)(),
|
|
2806
3028
|
type: "RECEIVED",
|
|
2807
|
-
assets:
|
|
3029
|
+
assets: wireAssets(input.assets),
|
|
2808
3030
|
ts: this.ts(input.timestamp),
|
|
2809
3031
|
...input.transferId !== void 0 ? { transferId: input.transferId } : {},
|
|
2810
3032
|
...this.wireTokenId(input.tokenId),
|
|
@@ -2817,7 +3039,7 @@ var History = class {
|
|
|
2817
3039
|
dedupKey: `MINT:${input.tokenId.toLowerCase()}`,
|
|
2818
3040
|
id: (this.deps.newId ?? randomUUID)(),
|
|
2819
3041
|
type: "MINT",
|
|
2820
|
-
assets:
|
|
3042
|
+
assets: wireAssets(input.assets),
|
|
2821
3043
|
ts: this.ts(input.timestamp),
|
|
2822
3044
|
...this.wireTokenId(input.tokenId)
|
|
2823
3045
|
}));
|
|
@@ -2828,7 +3050,10 @@ var History = class {
|
|
|
2828
3050
|
try {
|
|
2829
3051
|
await this.deps.client.postHistory([record]);
|
|
2830
3052
|
} catch (err) {
|
|
2831
|
-
this.deps.log?.(
|
|
3053
|
+
this.deps.log?.(
|
|
3054
|
+
isPermanentReject(err) ? "history POST REJECTED \u2014 the record shape is refused; no retry can fix it" : "history POST failed (dedupKey makes retry safe)",
|
|
3055
|
+
err
|
|
3056
|
+
);
|
|
2832
3057
|
return;
|
|
2833
3058
|
}
|
|
2834
3059
|
this.deps.emit("history:updated", this.toEntry(record));
|
|
@@ -2895,6 +3120,13 @@ var CURSOR_KEY = STORE_KEYS.streamCursor("inventory");
|
|
|
2895
3120
|
function stateKey(tokenId, stateHash) {
|
|
2896
3121
|
return `${tokenId}:${stateHash}`;
|
|
2897
3122
|
}
|
|
3123
|
+
function unchanged(prev, item, tokenType) {
|
|
3124
|
+
return prev.seq === item.seq && prev.status === item.status && prev.stateHash === item.stateHash && tokenType === prev.tokenType;
|
|
3125
|
+
}
|
|
3126
|
+
function isCoinless(item, prev) {
|
|
3127
|
+
if (item.status !== "active") return prev?.coinless ?? false;
|
|
3128
|
+
return (item.assets?.length ?? 0) === 0;
|
|
3129
|
+
}
|
|
2898
3130
|
var InventoryView = class {
|
|
2899
3131
|
constructor(deps) {
|
|
2900
3132
|
this.deps = deps;
|
|
@@ -2988,14 +3220,26 @@ var InventoryView = class {
|
|
|
2988
3220
|
pool(coinId) {
|
|
2989
3221
|
const out = [];
|
|
2990
3222
|
for (const [tokenId, entry] of this.mirror) {
|
|
2991
|
-
if (
|
|
2992
|
-
if (this.inFlight.has(tokenId) && !this.pinned(tokenId)) continue;
|
|
2993
|
-
if (this.suspected.has(stateKey(tokenId, entry.stateHash))) continue;
|
|
3223
|
+
if (!this.isSpendable(tokenId, entry)) continue;
|
|
2994
3224
|
const asset = entry.assets.find((a) => a.coinId === coinId);
|
|
2995
3225
|
if (asset) out.push({ tokenId, amount: BigInt(asset.amount) });
|
|
2996
3226
|
}
|
|
2997
3227
|
return out;
|
|
2998
3228
|
}
|
|
3229
|
+
/**
|
|
3230
|
+
* The eligibility gates every spend shares, coin or token — ONE definition, so a
|
|
3231
|
+
* single probe covers both verbs and neither can drift from the other.
|
|
3232
|
+
*/
|
|
3233
|
+
isSpendable(tokenId, entry) {
|
|
3234
|
+
if (entry.status !== "active") return false;
|
|
3235
|
+
if (this.inFlight.has(tokenId) && !this.pinned(tokenId)) return false;
|
|
3236
|
+
return !this.suspected.has(stateKey(tokenId, entry.stateHash));
|
|
3237
|
+
}
|
|
3238
|
+
/** Is this NAMED token spendable whole — coinless or valued (#777)? */
|
|
3239
|
+
spendableToken(tokenId) {
|
|
3240
|
+
const entry = this.mirror.get(tokenId);
|
|
3241
|
+
return entry !== void 0 && this.isSpendable(tokenId, entry);
|
|
3242
|
+
}
|
|
2999
3243
|
/**
|
|
3000
3244
|
* Current mirrored state of an ACTIVE token, or undefined when it is unknown
|
|
3001
3245
|
* or tombstoned. Exists so a cached blob can be state-scoped (F6): a blob is
|
|
@@ -3021,6 +3265,29 @@ var InventoryView = class {
|
|
|
3021
3265
|
}
|
|
3022
3266
|
return out;
|
|
3023
3267
|
}
|
|
3268
|
+
/**
|
|
3269
|
+
* Coinless holdings (#777) — DISJOINT from tokens(): an entry is coinless
|
|
3270
|
+
* exactly when it is not, so no mirror row can appear in both reads.
|
|
3271
|
+
*/
|
|
3272
|
+
coinless(registry) {
|
|
3273
|
+
const out = [];
|
|
3274
|
+
for (const [tokenId, entry] of this.mirror) {
|
|
3275
|
+
if (entry.status !== "active" || !entry.coinless) continue;
|
|
3276
|
+
const meta = entry.tokenType !== void 0 ? registry.getTypeMeta?.(entry.tokenType) : null;
|
|
3277
|
+
out.push({
|
|
3278
|
+
tokenId,
|
|
3279
|
+
...entry.tokenType !== void 0 ? { tokenType: entry.tokenType } : {},
|
|
3280
|
+
...meta ? { name: meta.name } : {},
|
|
3281
|
+
...meta?.iconUrl != null ? { iconUrl: meta.iconUrl } : {},
|
|
3282
|
+
stateHash: entry.stateHash,
|
|
3283
|
+
transferring: this.held(tokenId),
|
|
3284
|
+
...this.suspected.has(stateKey(tokenId, entry.stateHash)) ? { suspectedSpent: true } : {},
|
|
3285
|
+
createdAt: entry.createdAt,
|
|
3286
|
+
updatedAt: entry.updatedAt
|
|
3287
|
+
});
|
|
3288
|
+
}
|
|
3289
|
+
return out;
|
|
3290
|
+
}
|
|
3024
3291
|
async assets(registry, price) {
|
|
3025
3292
|
const raw = aggregateAssets(this.activeEntries(), (tokenId) => this.held(tokenId), registry);
|
|
3026
3293
|
if (!price || raw.length === 0) return raw;
|
|
@@ -3092,14 +3359,19 @@ var InventoryView = class {
|
|
|
3092
3359
|
applyOne(item, now) {
|
|
3093
3360
|
const prev = this.mirror.get(item.tokenId);
|
|
3094
3361
|
if (prev && item.seq < prev.seq) return false;
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
}
|
|
3362
|
+
const tokenType = item.tokenType ?? prev?.tokenType;
|
|
3363
|
+
if (prev && unchanged(prev, item, tokenType)) return false;
|
|
3098
3364
|
this.mirror.set(item.tokenId, {
|
|
3099
3365
|
stateHash: item.stateHash,
|
|
3100
3366
|
seq: item.seq,
|
|
3101
3367
|
status: item.status,
|
|
3102
|
-
|
|
3368
|
+
// ONLY a tombstone inherits: it omits assets for an unrelated reason and
|
|
3369
|
+
// recoverRemoved must know the amount it restores. An ACTIVE row that omits
|
|
3370
|
+
// them is stating coinlessness, and inheriting there would keep stale assets
|
|
3371
|
+
// in tokens() while `coinless` is true — the row in BOTH reads (§16).
|
|
3372
|
+
assets: item.assets ?? (item.status === "removed" ? prev?.assets ?? [] : []),
|
|
3373
|
+
coinless: isCoinless(item, prev),
|
|
3374
|
+
...tokenType !== void 0 ? { tokenType } : {},
|
|
3103
3375
|
createdAt: prev?.createdAt ?? now,
|
|
3104
3376
|
updatedAt: now
|
|
3105
3377
|
});
|
|
@@ -3190,6 +3462,73 @@ var InventoryView = class {
|
|
|
3190
3462
|
}
|
|
3191
3463
|
};
|
|
3192
3464
|
|
|
3465
|
+
// token-engine/sdk.ts
|
|
3466
|
+
var import_StateTransitionClient = require("@unicitylabs/state-transition-sdk/lib/StateTransitionClient.js");
|
|
3467
|
+
var import_AggregatorClient = require("@unicitylabs/state-transition-sdk/lib/api/AggregatorClient.js");
|
|
3468
|
+
var import_NetworkId = require("@unicitylabs/state-transition-sdk/lib/api/NetworkId.js");
|
|
3469
|
+
var import_CertificationData = require("@unicitylabs/state-transition-sdk/lib/api/CertificationData.js");
|
|
3470
|
+
var import_CertificationResponse = require("@unicitylabs/state-transition-sdk/lib/api/CertificationResponse.js");
|
|
3471
|
+
var import_StateId = require("@unicitylabs/state-transition-sdk/lib/api/StateId.js");
|
|
3472
|
+
var import_InclusionProof = require("@unicitylabs/state-transition-sdk/lib/api/InclusionProof.js");
|
|
3473
|
+
var import_InclusionProofResponse = require("@unicitylabs/state-transition-sdk/lib/api/InclusionProofResponse.js");
|
|
3474
|
+
var import_JsonRpcNetworkError = require("@unicitylabs/state-transition-sdk/lib/api/json-rpc/JsonRpcNetworkError.js");
|
|
3475
|
+
var import_RootTrustBase = require("@unicitylabs/state-transition-sdk/lib/api/bft/RootTrustBase.js");
|
|
3476
|
+
var import_InclusionProofUtils = require("@unicitylabs/state-transition-sdk/lib/util/InclusionProofUtils.js");
|
|
3477
|
+
var import_InclusionProofVerificationRule = require("@unicitylabs/state-transition-sdk/lib/transaction/verification/rule/InclusionProofVerificationRule.js");
|
|
3478
|
+
var import_Token = require("@unicitylabs/state-transition-sdk/lib/transaction/Token.js");
|
|
3479
|
+
var import_MintTransaction = require("@unicitylabs/state-transition-sdk/lib/transaction/MintTransaction.js");
|
|
3480
|
+
var import_TransferTransaction = require("@unicitylabs/state-transition-sdk/lib/transaction/TransferTransaction.js");
|
|
3481
|
+
var import_CertifiedMintTransaction = require("@unicitylabs/state-transition-sdk/lib/transaction/CertifiedMintTransaction.js");
|
|
3482
|
+
var import_CertifiedTransferTransaction = require("@unicitylabs/state-transition-sdk/lib/transaction/CertifiedTransferTransaction.js");
|
|
3483
|
+
var import_TokenId = require("@unicitylabs/state-transition-sdk/lib/transaction/TokenId.js");
|
|
3484
|
+
var import_TokenType = require("@unicitylabs/state-transition-sdk/lib/transaction/TokenType.js");
|
|
3485
|
+
var import_TokenSalt = require("@unicitylabs/state-transition-sdk/lib/transaction/TokenSalt.js");
|
|
3486
|
+
var import_StateMask = require("@unicitylabs/state-transition-sdk/lib/transaction/StateMask.js");
|
|
3487
|
+
var import_MintJustificationVerifierService = require("@unicitylabs/state-transition-sdk/lib/transaction/verification/MintJustificationVerifierService.js");
|
|
3488
|
+
var import_TokenIssuanceVerifierService = require("@unicitylabs/state-transition-sdk/lib/transaction/verification/TokenIssuanceVerifierService.js");
|
|
3489
|
+
var import_VerificationContext = require("@unicitylabs/state-transition-sdk/lib/transaction/verification/VerificationContext.js");
|
|
3490
|
+
var import_UnicityCertificateVerifier = require("@unicitylabs/state-transition-sdk/lib/api/bft/verification/UnicityCertificateVerifier.js");
|
|
3491
|
+
var import_UnicitySealQuorumSignaturesVerificationRule = require("@unicitylabs/state-transition-sdk/lib/api/bft/verification/rule/UnicitySealQuorumSignaturesVerificationRule.js");
|
|
3492
|
+
var import_VerifiedSealCache = require("@unicitylabs/state-transition-sdk/lib/api/bft/verification/VerifiedSealCache.js");
|
|
3493
|
+
var import_Secp256k1SignatureVerifier = require("@unicitylabs/state-transition-sdk/lib/crypto/secp256k1/Secp256k1SignatureVerifier.js");
|
|
3494
|
+
var import_WorkerTokenVerifier = require("@unicitylabs/state-transition-sdk/lib/transaction/verification/worker/WorkerTokenVerifier.js");
|
|
3495
|
+
var import_EncodedPredicate = require("@unicitylabs/state-transition-sdk/lib/predicate/EncodedPredicate.js");
|
|
3496
|
+
var import_SignaturePredicate = require("@unicitylabs/state-transition-sdk/lib/predicate/builtin/SignaturePredicate.js");
|
|
3497
|
+
var import_SignaturePredicateUnlockScript = require("@unicitylabs/state-transition-sdk/lib/predicate/builtin/SignaturePredicateUnlockScript.js");
|
|
3498
|
+
var import_BurnPredicate = require("@unicitylabs/state-transition-sdk/lib/predicate/builtin/BurnPredicate.js");
|
|
3499
|
+
var import_PredicateVerifierService = require("@unicitylabs/state-transition-sdk/lib/predicate/verification/PredicateVerifierService.js");
|
|
3500
|
+
var import_SigningService = require("@unicitylabs/state-transition-sdk/lib/crypto/secp256k1/SigningService.js");
|
|
3501
|
+
var import_Signature = require("@unicitylabs/state-transition-sdk/lib/crypto/secp256k1/Signature.js");
|
|
3502
|
+
var import_MintSigningService = require("@unicitylabs/state-transition-sdk/lib/crypto/MintSigningService.js");
|
|
3503
|
+
var import_HashAlgorithm = require("@unicitylabs/state-transition-sdk/lib/crypto/hash/HashAlgorithm.js");
|
|
3504
|
+
var import_DataHash = require("@unicitylabs/state-transition-sdk/lib/crypto/hash/DataHash.js");
|
|
3505
|
+
var import_DataHasher = require("@unicitylabs/state-transition-sdk/lib/crypto/hash/DataHasher.js");
|
|
3506
|
+
var import_DataHasherFactory = require("@unicitylabs/state-transition-sdk/lib/crypto/hash/DataHasherFactory.js");
|
|
3507
|
+
var import_CborSerializer = require("@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborSerializer.js");
|
|
3508
|
+
var import_CborDeserializer = require("@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborDeserializer.js");
|
|
3509
|
+
var import_CborError = require("@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborError.js");
|
|
3510
|
+
var import_CborReader = require("@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborReader.js");
|
|
3511
|
+
var import_MajorType = require("@unicitylabs/state-transition-sdk/lib/serialization/cbor/MajorType.js");
|
|
3512
|
+
var import_Asset = require("@unicitylabs/state-transition-sdk/lib/payment/asset/Asset.js");
|
|
3513
|
+
var import_AssetId = require("@unicitylabs/state-transition-sdk/lib/payment/asset/AssetId.js");
|
|
3514
|
+
var import_PaymentAssetCollection = require("@unicitylabs/state-transition-sdk/lib/payment/asset/PaymentAssetCollection.js");
|
|
3515
|
+
var import_TokenSplit = require("@unicitylabs/state-transition-sdk/lib/payment/TokenSplit.js");
|
|
3516
|
+
var import_SplitTokenRequest = require("@unicitylabs/state-transition-sdk/lib/payment/SplitTokenRequest.js");
|
|
3517
|
+
var import_SplitToken = require("@unicitylabs/state-transition-sdk/lib/payment/SplitToken.js");
|
|
3518
|
+
var import_SplitAllocationProof = require("@unicitylabs/state-transition-sdk/lib/payment/SplitAllocationProof.js");
|
|
3519
|
+
var import_SplitMintJustification = require("@unicitylabs/state-transition-sdk/lib/payment/SplitMintJustification.js");
|
|
3520
|
+
var import_SplitMintJustificationVerifier = require("@unicitylabs/state-transition-sdk/lib/payment/SplitMintJustificationVerifier.js");
|
|
3521
|
+
var import_VerificationStatus = require("@unicitylabs/state-transition-sdk/lib/verification/VerificationStatus.js");
|
|
3522
|
+
var import_VerificationResult = require("@unicitylabs/state-transition-sdk/lib/verification/VerificationResult.js");
|
|
3523
|
+
var import_HexConverter = require("@unicitylabs/state-transition-sdk/lib/util/HexConverter.js");
|
|
3524
|
+
var import_BigintConverter = require("@unicitylabs/state-transition-sdk/lib/util/BigintConverter.js");
|
|
3525
|
+
var import_BitString = require("@unicitylabs/state-transition-sdk/lib/util/BitString.js");
|
|
3526
|
+
|
|
3527
|
+
// token-engine/value-envelope.ts
|
|
3528
|
+
function isCoinlessEnvelope(envelope) {
|
|
3529
|
+
return envelope.startsWith("none_");
|
|
3530
|
+
}
|
|
3531
|
+
|
|
3193
3532
|
// modules/payments-v2/receive/Receive.ts
|
|
3194
3533
|
var ACK_BATCH_SIZE = 200;
|
|
3195
3534
|
var REFRESH_INTERVAL_MS = 2500;
|
|
@@ -3330,7 +3669,12 @@ async function screen(deps, engine, entry) {
|
|
|
3330
3669
|
}
|
|
3331
3670
|
return {
|
|
3332
3671
|
kind: "accept",
|
|
3333
|
-
record: {
|
|
3672
|
+
record: {
|
|
3673
|
+
tokenId: keys.tokenId,
|
|
3674
|
+
stateHash: keys.stateHash,
|
|
3675
|
+
assets: toAssetAmounts(token),
|
|
3676
|
+
...isCoinlessEnvelope(token.valueEnvelope) ? { tokenType: token.tokenType } : {}
|
|
3677
|
+
}
|
|
3334
3678
|
};
|
|
3335
3679
|
}
|
|
3336
3680
|
async function announce(deps, entry, record) {
|
|
@@ -3341,6 +3685,7 @@ async function announce(deps, entry, record) {
|
|
|
3341
3685
|
tokenId: record.tokenId,
|
|
3342
3686
|
stateHash: record.stateHash,
|
|
3343
3687
|
assets: record.assets,
|
|
3688
|
+
...record.tokenType !== void 0 ? { tokenType: record.tokenType } : {},
|
|
3344
3689
|
...entry.senderPubkey !== void 0 ? { senderPubkey: entry.senderPubkey } : {},
|
|
3345
3690
|
...entry.senderNametag !== void 0 ? { senderNametag: entry.senderNametag } : {},
|
|
3346
3691
|
...entry.memo !== void 0 ? { memo: entry.memo } : {},
|
|
@@ -3354,6 +3699,19 @@ async function announce(deps, entry, record) {
|
|
|
3354
3699
|
senderPubkey: entry.senderPubkey ?? "",
|
|
3355
3700
|
...entry.senderNametag !== void 0 ? { senderNametag: entry.senderNametag } : {},
|
|
3356
3701
|
tokens: record.assets.map((asset) => toUiToken(record.tokenId, asset, deps.registry, receivedAt)),
|
|
3702
|
+
// #777: named here rather than mapped from assets, which announced an EMPTY list.
|
|
3703
|
+
...record.tokenType !== void 0 ? {
|
|
3704
|
+
coinless: [
|
|
3705
|
+
{
|
|
3706
|
+
tokenId: record.tokenId,
|
|
3707
|
+
tokenType: record.tokenType,
|
|
3708
|
+
stateHash: record.stateHash,
|
|
3709
|
+
transferring: false,
|
|
3710
|
+
createdAt: receivedAt,
|
|
3711
|
+
updatedAt: receivedAt
|
|
3712
|
+
}
|
|
3713
|
+
]
|
|
3714
|
+
} : {},
|
|
3357
3715
|
...entry.memo !== void 0 ? { memo: entry.memo } : {},
|
|
3358
3716
|
receivedAt
|
|
3359
3717
|
};
|
|
@@ -4019,6 +4377,34 @@ var SpendQueue = class {
|
|
|
4019
4377
|
}
|
|
4020
4378
|
return { kind: "queued", settled: this.enqueue(reservationId, request.coinId, amount) };
|
|
4021
4379
|
}
|
|
4380
|
+
/**
|
|
4381
|
+
* #777: reserve a NAMED coinless source. Never queues — there is nothing to wait
|
|
4382
|
+
* for. A coin plan waits because some OTHER combination may free up; a named
|
|
4383
|
+
* token is either free now or held by a reservation that only its own transfer
|
|
4384
|
+
* can release, so queueing would block until a timeout on a spend that cannot
|
|
4385
|
+
* become possible.
|
|
4386
|
+
*/
|
|
4387
|
+
planWhole(reservationId, tokenId) {
|
|
4388
|
+
if (this.destroyed) {
|
|
4389
|
+
throw new SphereError("Module has been destroyed", "MODULE_DESTROYED");
|
|
4390
|
+
}
|
|
4391
|
+
const unproven = this.deps.ledger.unprovenReason();
|
|
4392
|
+
if (unproven !== null) {
|
|
4393
|
+
throw new SphereError(`Cannot spend yet: ${unproven}`, "SEND_SYNC_PENDING");
|
|
4394
|
+
}
|
|
4395
|
+
if (this.deps.spendableToken?.(tokenId) !== true) {
|
|
4396
|
+
throw new SphereError(`Token ${tokenId} is not a spendable holding`, "VALIDATION_ERROR");
|
|
4397
|
+
}
|
|
4398
|
+
const holder = this.deps.ledger.holderOf(tokenId);
|
|
4399
|
+
if (holder !== void 0) {
|
|
4400
|
+
throw new SphereError(
|
|
4401
|
+
`Token ${tokenId} is already reserved by transfer ${holder}`,
|
|
4402
|
+
"VALIDATION_ERROR"
|
|
4403
|
+
);
|
|
4404
|
+
}
|
|
4405
|
+
this.deps.ledger.reserve(reservationId, [{ tokenId }]);
|
|
4406
|
+
return { reservationId, plan: { direct: [tokenId] } };
|
|
4407
|
+
}
|
|
4022
4408
|
/**
|
|
4023
4409
|
* The sources `plan()` WOULD pick right now, reserving nothing and queueing
|
|
4024
4410
|
* nothing — a read-only probe used to warm blobs while the user is still
|
|
@@ -4208,6 +4594,7 @@ function composeFacadeParts(deps, hooks) {
|
|
|
4208
4594
|
const queue = new SpendQueue({
|
|
4209
4595
|
ledger,
|
|
4210
4596
|
getPool: (coinId) => view.pool(coinId),
|
|
4597
|
+
spendableToken: (tokenId) => view.spendableToken(tokenId),
|
|
4211
4598
|
...deps.workBudget !== void 0 ? { workBudget: deps.workBudget } : {}
|
|
4212
4599
|
});
|
|
4213
4600
|
const historyStore = new History({
|
|
@@ -4288,13 +4675,12 @@ function buildMachineDeps(deps, hooks, historyStore) {
|
|
|
4288
4675
|
...deps.ownNametag !== void 0 ? { ownNametag: deps.ownNametag } : {},
|
|
4289
4676
|
emit: deps.emit,
|
|
4290
4677
|
now: deps.now ?? Date.now,
|
|
4291
|
-
recordHistory: async ({ transferId, payload, committedAmount }) => {
|
|
4678
|
+
recordHistory: async ({ transferId, payload, committedAmount, committedAssets }) => {
|
|
4292
4679
|
await historyStore.recordSent({
|
|
4293
4680
|
transferId,
|
|
4294
|
-
|
|
4295
|
-
//
|
|
4296
|
-
|
|
4297
|
-
amount: committedAmount,
|
|
4681
|
+
// §5.9: the SETTLED amount, never payload.amount — the plan. A token
|
|
4682
|
+
// spend moved no coin: `assets: []` + tokenId (wallet-api#151 / §10).
|
|
4683
|
+
...isWholeIntent(payload) ? { assets: committedAssets ?? [], tokenId: payload.direct[0] } : { assets: [{ coinId: payload.coinId, amount: committedAmount }] },
|
|
4298
4684
|
recipientPubkey: payload.recipient,
|
|
4299
4685
|
...payload.memo !== void 0 ? { memo: payload.memo } : {}
|
|
4300
4686
|
});
|
|
@@ -4329,12 +4715,10 @@ function buildReceive(deps, hooks, historyStore, heldStates, refreshView) {
|
|
|
4329
4715
|
});
|
|
4330
4716
|
}
|
|
4331
4717
|
async function recordReceived(historyStore, record) {
|
|
4332
|
-
const first = record.assets[0];
|
|
4333
4718
|
await historyStore.recordReceived({
|
|
4334
4719
|
tokenId: record.tokenId,
|
|
4335
4720
|
stateHash: record.stateHash,
|
|
4336
|
-
|
|
4337
|
-
amount: first?.amount ?? "0",
|
|
4721
|
+
assets: record.assets,
|
|
4338
4722
|
...record.senderPubkey !== void 0 ? { senderPubkey: record.senderPubkey } : {},
|
|
4339
4723
|
...record.senderNametag !== void 0 ? { senderNametag: record.senderNametag } : {},
|
|
4340
4724
|
...record.memo !== void 0 ? { memo: record.memo } : {},
|
|
@@ -4482,6 +4866,13 @@ var PaymentsFacade = class {
|
|
|
4482
4866
|
tokens(filter) {
|
|
4483
4867
|
return this.view.tokens(this.deps.registry, filter);
|
|
4484
4868
|
}
|
|
4869
|
+
coinless() {
|
|
4870
|
+
return this.view.coinless(this.deps.registry);
|
|
4871
|
+
}
|
|
4872
|
+
tokenData(tokenId) {
|
|
4873
|
+
const deps = { engine: this.engine(), view: this.view, storagePort: this.deps.storagePort };
|
|
4874
|
+
return readTokenData(deps, tokenId);
|
|
4875
|
+
}
|
|
4485
4876
|
history(page) {
|
|
4486
4877
|
return this.historyStore.page(page ?? {});
|
|
4487
4878
|
}
|
|
@@ -4504,7 +4895,14 @@ var PaymentsFacade = class {
|
|
|
4504
4895
|
return { queue: this.queue, view: this.view, storagePort: this.deps.storagePort, cache: this.prewarmed };
|
|
4505
4896
|
}
|
|
4506
4897
|
send(request) {
|
|
4507
|
-
return this.track(this.
|
|
4898
|
+
return this.track(this.runJob({ kind: "coin", request }, request.amount));
|
|
4899
|
+
}
|
|
4900
|
+
sendWholeToken(request) {
|
|
4901
|
+
return this.track(this.runJob({ kind: "whole", request, requireCoinless: false }, "0"));
|
|
4902
|
+
}
|
|
4903
|
+
/** NFT-scoped twin: refuses a valued source. */
|
|
4904
|
+
sendCoinless(request) {
|
|
4905
|
+
return this.track(this.runJob({ kind: "whole", request, requireCoinless: true }, "0"));
|
|
4508
4906
|
}
|
|
4509
4907
|
async receive() {
|
|
4510
4908
|
const transfers = await this.track(this.receiveLoop.drainOnce());
|
|
@@ -4536,12 +4934,25 @@ var PaymentsFacade = class {
|
|
|
4536
4934
|
nowMs() {
|
|
4537
4935
|
return (this.deps.now ?? Date.now)();
|
|
4538
4936
|
}
|
|
4937
|
+
mintDeps() {
|
|
4938
|
+
return {
|
|
4939
|
+
engine: this.engine(),
|
|
4940
|
+
mintJournal: this.machineStores.mintJournal,
|
|
4941
|
+
storagePort: this.deps.storagePort,
|
|
4942
|
+
recordMint: (input) => this.historyStore.recordMint(input),
|
|
4943
|
+
armHeartbeat: () => this.heartbeat.arm(),
|
|
4944
|
+
noteHeldState: (tokenId, stateHash) => void this.heldStates.set(tokenId, stateHash),
|
|
4945
|
+
refreshView: () => this.trackTail(this.view.delta()),
|
|
4946
|
+
ownPubkeyBytes: this.ownPubkeyBytes,
|
|
4947
|
+
now: () => this.nowMs()
|
|
4948
|
+
};
|
|
4949
|
+
}
|
|
4539
4950
|
// ── send policy (§5.5 + old-loop parity; the machine stays policy-free) ────
|
|
4540
4951
|
/** The ONE place a send() outcome is shaped: success emits in finishSend, a
|
|
4541
4952
|
* CLEAN rejection emits `transfer:updated{status:'failed'}` here (§4). */
|
|
4542
|
-
async
|
|
4953
|
+
async runJob(job, amount) {
|
|
4543
4954
|
const run = {
|
|
4544
|
-
amount
|
|
4955
|
+
amount,
|
|
4545
4956
|
delivered: [],
|
|
4546
4957
|
sentTokens: [],
|
|
4547
4958
|
tokenTransfers: [],
|
|
@@ -4551,7 +4962,7 @@ var PaymentsFacade = class {
|
|
|
4551
4962
|
lastTransferId: ""
|
|
4552
4963
|
};
|
|
4553
4964
|
try {
|
|
4554
|
-
return await this.sendWithPolicy(
|
|
4965
|
+
return await this.sendWithPolicy(job, run);
|
|
4555
4966
|
} catch (err) {
|
|
4556
4967
|
this.emitCleanFailure(err, run.lastTransferId);
|
|
4557
4968
|
throw err;
|
|
@@ -4571,19 +4982,19 @@ var PaymentsFacade = class {
|
|
|
4571
4982
|
};
|
|
4572
4983
|
this.deps.emit("transfer:updated", failed);
|
|
4573
4984
|
}
|
|
4574
|
-
async sendWithPolicy(
|
|
4575
|
-
const recipient = await requireSameNetworkRecipient(this.deps, request.recipient);
|
|
4985
|
+
async sendWithPolicy(job, run) {
|
|
4986
|
+
const recipient = await requireSameNetworkRecipient(this.deps, job.request.recipient);
|
|
4576
4987
|
for (let attempt = 0; ; attempt++) {
|
|
4577
4988
|
let ctx;
|
|
4578
4989
|
try {
|
|
4579
|
-
ctx = await this.planAndMaterialize(recipient.chainPubkey,
|
|
4990
|
+
ctx = await this.planAndMaterialize(recipient.chainPubkey, job, run);
|
|
4580
4991
|
} catch (err) {
|
|
4581
|
-
throw
|
|
4992
|
+
throw partialize(err, run);
|
|
4582
4993
|
}
|
|
4583
4994
|
const disposition = await this.runAttempt(ctx);
|
|
4584
|
-
if (disposition.kind === "rethrow") throw
|
|
4995
|
+
if (disposition.kind === "rethrow") throw partialize(disposition.error, run);
|
|
4585
4996
|
if (disposition.kind === "retry-full") {
|
|
4586
|
-
if (attempt >= MAX_RESELECT) throw
|
|
4997
|
+
if (job.kind === "whole" || attempt >= MAX_RESELECT) throw partialize(disposition.error, run);
|
|
4587
4998
|
continue;
|
|
4588
4999
|
}
|
|
4589
5000
|
if (disposition.kind === "success") {
|
|
@@ -4639,7 +5050,7 @@ var PaymentsFacade = class {
|
|
|
4639
5050
|
async disposeFailedAttempt(ctx, err) {
|
|
4640
5051
|
if (isPossiblyCommittedSendOutcome(err)) {
|
|
4641
5052
|
this.settleKeepOpen(ctx);
|
|
4642
|
-
|
|
5053
|
+
stampTransferId(err, ctx.transferId);
|
|
4643
5054
|
return { kind: "rethrow", error: err };
|
|
4644
5055
|
}
|
|
4645
5056
|
const backstop = await this.machineStores.backstop.getByKey(ctx.transferId);
|
|
@@ -4673,7 +5084,7 @@ var PaymentsFacade = class {
|
|
|
4673
5084
|
async disposeConvergeFailure(ctx, err) {
|
|
4674
5085
|
if (isPossiblyCommittedSendOutcome(err)) {
|
|
4675
5086
|
this.settleKeepOpen(ctx);
|
|
4676
|
-
|
|
5087
|
+
stampTransferId(err, ctx.transferId);
|
|
4677
5088
|
return { kind: "rethrow", error: err };
|
|
4678
5089
|
}
|
|
4679
5090
|
if (classifyError(err) === "conflict") {
|
|
@@ -4684,9 +5095,29 @@ var PaymentsFacade = class {
|
|
|
4684
5095
|
}
|
|
4685
5096
|
return { kind: "rethrow", error: err };
|
|
4686
5097
|
}
|
|
4687
|
-
async planAndMaterialize(recipientPubkey,
|
|
5098
|
+
async planAndMaterialize(recipientPubkey, job, run) {
|
|
4688
5099
|
const transferId = this.newId();
|
|
4689
5100
|
run.lastTransferId = transferId;
|
|
5101
|
+
if (job.kind === "whole") {
|
|
5102
|
+
const spend2 = this.queue.planWhole(transferId, job.request.tokenId);
|
|
5103
|
+
const sourceIds2 = this.markPlanned(transferId, "", spend2);
|
|
5104
|
+
try {
|
|
5105
|
+
return await materializeWholeSpend(
|
|
5106
|
+
{ engine: this.engine(), storagePort: this.deps.storagePort, registry: this.deps.registry, now: this.nowMs() },
|
|
5107
|
+
{
|
|
5108
|
+
transferId,
|
|
5109
|
+
recipientPubkey,
|
|
5110
|
+
request: job.request,
|
|
5111
|
+
sourceIds: sourceIds2,
|
|
5112
|
+
requireCoinless: job.requireCoinless
|
|
5113
|
+
}
|
|
5114
|
+
);
|
|
5115
|
+
} catch (err) {
|
|
5116
|
+
this.settleFailure(transferId, "", sourceIds2);
|
|
5117
|
+
throw err;
|
|
5118
|
+
}
|
|
5119
|
+
}
|
|
5120
|
+
const request = { ...job.request, amount: run.amount };
|
|
4690
5121
|
const planned = this.queue.plan(transferId, { coinId: request.coinId, amount: request.amount });
|
|
4691
5122
|
const spend = planned.kind === "planned" ? planned.spend : await planned.settled;
|
|
4692
5123
|
const sourceIds = this.markPlanned(transferId, request.coinId, spend);
|
|
@@ -4820,22 +5251,6 @@ var PaymentsFacade = class {
|
|
|
4820
5251
|
return result;
|
|
4821
5252
|
}
|
|
4822
5253
|
/** Nothing delivered → UNWRAPPED; after ≥1 delivered leg every failure surfaces as PartialSendConflictError over the settled set. */
|
|
4823
|
-
partialize(err, run) {
|
|
4824
|
-
if (run.delivered.length === 0) return err;
|
|
4825
|
-
return new PartialSendConflictError(
|
|
4826
|
-
"Part of your payment was sent; the remaining amount could not be completed (see cause). The delivered portion is final \u2014 re-plan only the shortfall, never the full amount.",
|
|
4827
|
-
run.firstPartialId ?? "",
|
|
4828
|
-
run.delivered,
|
|
4829
|
-
run.amount,
|
|
4830
|
-
err
|
|
4831
|
-
);
|
|
4832
|
-
}
|
|
4833
|
-
/** #441: possibly-committed errors must carry the transferId for the settling journal. */
|
|
4834
|
-
stampTransferId(err, transferId) {
|
|
4835
|
-
if (err instanceof SphereError && isPossiblyCommittedSendOutcome(err) && err.transferId === void 0) {
|
|
4836
|
-
err.transferId = transferId;
|
|
4837
|
-
}
|
|
4838
|
-
}
|
|
4839
5254
|
async softAbort(transferId) {
|
|
4840
5255
|
try {
|
|
4841
5256
|
await this.deps.client.abortIntent(transferId);
|
|
@@ -4851,57 +5266,11 @@ var PaymentsFacade = class {
|
|
|
4851
5266
|
const mintId = this.newId();
|
|
4852
5267
|
this.activeMoneyOps.add(mintId);
|
|
4853
5268
|
try {
|
|
4854
|
-
return await this.
|
|
5269
|
+
return await runMintUnderJournal(this.mintDeps(), { mintId, coinId, amount });
|
|
4855
5270
|
} finally {
|
|
4856
5271
|
this.activeMoneyOps.delete(mintId);
|
|
4857
5272
|
}
|
|
4858
5273
|
}
|
|
4859
|
-
async mintUnderJournal(mintId, coinId, amount) {
|
|
4860
|
-
const engine = this.engine();
|
|
4861
|
-
const entry = {
|
|
4862
|
-
mintId,
|
|
4863
|
-
coinId,
|
|
4864
|
-
amount: amount.toString(),
|
|
4865
|
-
tokenId: "",
|
|
4866
|
-
createdAt: this.nowMs()
|
|
4867
|
-
};
|
|
4868
|
-
await this.machineStores.mintJournal.upsert(entry);
|
|
4869
|
-
let token;
|
|
4870
|
-
try {
|
|
4871
|
-
token = await engine.mint(mintParams(this.ownPubkeyBytes, coinId, amount), { transferId: mintId });
|
|
4872
|
-
} catch (err) {
|
|
4873
|
-
this.heartbeat.arm();
|
|
4874
|
-
return { success: false, error: messageOf(err) };
|
|
4875
|
-
}
|
|
4876
|
-
if (token.blob.tokenId !== entry.tokenId) {
|
|
4877
|
-
await this.machineStores.mintJournal.upsert({ ...entry, tokenId: token.blob.tokenId });
|
|
4878
|
-
}
|
|
4879
|
-
try {
|
|
4880
|
-
await this.finalizeMint(engine, mintId, token, coinId, amount.toString());
|
|
4881
|
-
} catch (err) {
|
|
4882
|
-
this.heartbeat.arm();
|
|
4883
|
-
return { success: false, tokenId: token.blob.tokenId, error: messageOf(err) };
|
|
4884
|
-
}
|
|
4885
|
-
return { success: true, tokenId: token.blob.tokenId };
|
|
4886
|
-
}
|
|
4887
|
-
async finalizeMint(engine, mintId, token, coinId, amount) {
|
|
4888
|
-
const bytes = token.blob.token;
|
|
4889
|
-
const digest = bytesToHex(sha256(bytes));
|
|
4890
|
-
const keys = await this.deps.storagePort.uploadBlobs([{ sha256: digest, bytes }]);
|
|
4891
|
-
const key = keys.get(digest);
|
|
4892
|
-
if (key === void 0) {
|
|
4893
|
-
throw new SphereError(`no upload key returned for mint blob ${digest}`, "STORAGE_ERROR");
|
|
4894
|
-
}
|
|
4895
|
-
await this.deps.storagePort.applyDelta({
|
|
4896
|
-
transferId: mintId,
|
|
4897
|
-
spent: [],
|
|
4898
|
-
added: [{ tokenId: token.blob.tokenId, key }]
|
|
4899
|
-
});
|
|
4900
|
-
await this.historyStore.recordMint({ tokenId: token.blob.tokenId, coinId, amount });
|
|
4901
|
-
await this.machineStores.mintJournal.removeByKey(mintId);
|
|
4902
|
-
this.heldStates.set(token.blob.tokenId, (await engine.deliveryKeys(bytes)).stateHash);
|
|
4903
|
-
this.trackTail(this.view.delta());
|
|
4904
|
-
}
|
|
4905
5274
|
/** @returns how many journal entries were RESOLVED (cleared) — heartbeat progress. */
|
|
4906
5275
|
async replayMints() {
|
|
4907
5276
|
let resolved = 0;
|
|
@@ -4933,7 +5302,7 @@ var PaymentsFacade = class {
|
|
|
4933
5302
|
if (token.blob.tokenId !== entry.tokenId) {
|
|
4934
5303
|
await this.machineStores.mintJournal.upsert({ ...entry, tokenId: token.blob.tokenId });
|
|
4935
5304
|
}
|
|
4936
|
-
await this.
|
|
5305
|
+
await finalizeMint(this.mintDeps(), entry.mintId, token, entry.coinId, entry.amount);
|
|
4937
5306
|
return true;
|
|
4938
5307
|
}
|
|
4939
5308
|
async tokenInServerInventory(tokenId) {
|