@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
|
@@ -137,6 +137,135 @@ var STORE_KEYS = {
|
|
|
137
137
|
knownSpends: "known-spends"
|
|
138
138
|
};
|
|
139
139
|
|
|
140
|
+
// core/errors.ts
|
|
141
|
+
var SphereError = class extends Error {
|
|
142
|
+
code;
|
|
143
|
+
cause;
|
|
144
|
+
/**
|
|
145
|
+
* #441 deferred-paid linkage: the transferId of the possibly-committed send,
|
|
146
|
+
* stamped by `sendOnce` before the error leaves so a payment-request consumer
|
|
147
|
+
* can durably journal request→transfer and resolve 'paid' only when that
|
|
148
|
+
* transfer actually completes (never optimistically). Present ONLY on
|
|
149
|
+
* possibly-committed outcomes ({@link isPossiblyCommittedSendOutcome}).
|
|
150
|
+
*/
|
|
151
|
+
transferId;
|
|
152
|
+
constructor(message, code, cause) {
|
|
153
|
+
super(message);
|
|
154
|
+
this.name = "SphereError";
|
|
155
|
+
this.code = code;
|
|
156
|
+
this.cause = cause;
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
var PartialSendConflictError = class extends SphereError {
|
|
160
|
+
/**
|
|
161
|
+
* The FIRST partial attempt's transferId — its intent stays open for resume,
|
|
162
|
+
* and it is the §6 handoff anchor. NOTE: `send()` may re-plan the remainder across
|
|
163
|
+
* SEVERAL internal attempts before giving up, so {@link committedTokenIds} can
|
|
164
|
+
* span MULTIPLE transferIds — they do NOT all map to this single id.
|
|
165
|
+
*/
|
|
166
|
+
transferId;
|
|
167
|
+
/**
|
|
168
|
+
* Source token ids whose spend already certified on-chain (and whose finished
|
|
169
|
+
* output blob is journaled for delivery). Their value has already been
|
|
170
|
+
* delivered; NEVER re-send these. May accumulate across several internal
|
|
171
|
+
* remainder-re-plan attempts (so not all are journaled under {@link transferId}).
|
|
172
|
+
*/
|
|
173
|
+
committedTokenIds;
|
|
174
|
+
/**
|
|
175
|
+
* The still-undelivered portion (base units, decimal string) — `send()` could
|
|
176
|
+
* not COMPLETE it from the remaining live sources (insufficient funds or a
|
|
177
|
+
* transient error; see `cause`). Re-plan ONLY this amount, under a new
|
|
178
|
+
* transferId; the delivered legs converge via the recipient's §6 claim.
|
|
179
|
+
*/
|
|
180
|
+
remainingAmount;
|
|
181
|
+
constructor(message, transferId, committedTokenIds, remainingAmount, cause) {
|
|
182
|
+
super(message, "SEND_PARTIALLY_COMPLETED", cause);
|
|
183
|
+
this.name = "PartialSendConflictError";
|
|
184
|
+
this.transferId = transferId;
|
|
185
|
+
this.committedTokenIds = [...committedTokenIds];
|
|
186
|
+
this.remainingAmount = remainingAmount;
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
var POSSIBLY_COMMITTED_SEND_CODES = /* @__PURE__ */ new Set([
|
|
190
|
+
"SEND_SYNC_PENDING",
|
|
191
|
+
"CERTIFICATION_UNCONFIRMED",
|
|
192
|
+
"CHECKPOINT_PERSIST_FAILED",
|
|
193
|
+
"SPLIT_CHECKPOINT_LOST",
|
|
194
|
+
"CHECKPOINT_TRUSTBASE_MISMATCH",
|
|
195
|
+
"SEND_PARTIALLY_COMPLETED"
|
|
196
|
+
]);
|
|
197
|
+
function isPossiblyCommittedSendOutcome(err) {
|
|
198
|
+
return isSphereError(err) && POSSIBLY_COMMITTED_SEND_CODES.has(err.code);
|
|
199
|
+
}
|
|
200
|
+
function isSphereError(err) {
|
|
201
|
+
return err instanceof SphereError;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// core/uuid.ts
|
|
205
|
+
function randomUUID() {
|
|
206
|
+
const c = globalThis.crypto;
|
|
207
|
+
if (typeof c !== "undefined" && typeof c.randomUUID === "function") {
|
|
208
|
+
return c.randomUUID();
|
|
209
|
+
}
|
|
210
|
+
const b = c.getRandomValues(new Uint8Array(16));
|
|
211
|
+
b[6] = b[6] & 15 | 64;
|
|
212
|
+
b[8] = b[8] & 63 | 128;
|
|
213
|
+
const h = Array.from(b, (x) => x.toString(16).padStart(2, "0"));
|
|
214
|
+
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]}`;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// modules/payments-v2/async.ts
|
|
218
|
+
var SingleFlight = class {
|
|
219
|
+
flight = null;
|
|
220
|
+
run(fn) {
|
|
221
|
+
if (this.flight !== null) return this.flight;
|
|
222
|
+
const current = fn().finally(() => {
|
|
223
|
+
this.flight = null;
|
|
224
|
+
});
|
|
225
|
+
this.flight = current;
|
|
226
|
+
return current;
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
var SerialChain = class {
|
|
230
|
+
tail = Promise.resolve();
|
|
231
|
+
/** FIFO; starts after every prior fn settled; returns this fn's own outcome. */
|
|
232
|
+
enqueue(fn) {
|
|
233
|
+
const run = this.tail.then(fn, fn);
|
|
234
|
+
this.tail = run.then(
|
|
235
|
+
() => void 0,
|
|
236
|
+
() => void 0
|
|
237
|
+
);
|
|
238
|
+
return run;
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
function coalesced(run, observe) {
|
|
242
|
+
let inFlight = false;
|
|
243
|
+
let queued = false;
|
|
244
|
+
const start = () => {
|
|
245
|
+
inFlight = true;
|
|
246
|
+
let running;
|
|
247
|
+
try {
|
|
248
|
+
running = run();
|
|
249
|
+
} catch {
|
|
250
|
+
running = Promise.resolve();
|
|
251
|
+
}
|
|
252
|
+
observe?.(running);
|
|
253
|
+
void running.catch(() => void 0).finally(() => {
|
|
254
|
+
inFlight = false;
|
|
255
|
+
if (!queued) return;
|
|
256
|
+
queued = false;
|
|
257
|
+
start();
|
|
258
|
+
});
|
|
259
|
+
};
|
|
260
|
+
return () => {
|
|
261
|
+
if (inFlight) {
|
|
262
|
+
queued = true;
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
start();
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
140
269
|
// node_modules/@noble/hashes/utils.js
|
|
141
270
|
function isBytes(a) {
|
|
142
271
|
return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array" && "BYTES_PER_ELEMENT" in a && a.BYTES_PER_ELEMENT === 1;
|
|
@@ -464,72 +593,6 @@ var sha256 = /* @__PURE__ */ createHasher(
|
|
|
464
593
|
// core/crypto.ts
|
|
465
594
|
import * as bip39 from "bip39";
|
|
466
595
|
import CryptoJS from "crypto-js";
|
|
467
|
-
|
|
468
|
-
// core/errors.ts
|
|
469
|
-
var SphereError = class extends Error {
|
|
470
|
-
code;
|
|
471
|
-
cause;
|
|
472
|
-
/**
|
|
473
|
-
* #441 deferred-paid linkage: the transferId of the possibly-committed send,
|
|
474
|
-
* stamped by `sendOnce` before the error leaves so a payment-request consumer
|
|
475
|
-
* can durably journal request→transfer and resolve 'paid' only when that
|
|
476
|
-
* transfer actually completes (never optimistically). Present ONLY on
|
|
477
|
-
* possibly-committed outcomes ({@link isPossiblyCommittedSendOutcome}).
|
|
478
|
-
*/
|
|
479
|
-
transferId;
|
|
480
|
-
constructor(message, code, cause) {
|
|
481
|
-
super(message);
|
|
482
|
-
this.name = "SphereError";
|
|
483
|
-
this.code = code;
|
|
484
|
-
this.cause = cause;
|
|
485
|
-
}
|
|
486
|
-
};
|
|
487
|
-
var PartialSendConflictError = class extends SphereError {
|
|
488
|
-
/**
|
|
489
|
-
* The FIRST partial attempt's transferId — its intent stays open for resume,
|
|
490
|
-
* and it is the §6 handoff anchor. NOTE: `send()` may re-plan the remainder across
|
|
491
|
-
* SEVERAL internal attempts before giving up, so {@link committedTokenIds} can
|
|
492
|
-
* span MULTIPLE transferIds — they do NOT all map to this single id.
|
|
493
|
-
*/
|
|
494
|
-
transferId;
|
|
495
|
-
/**
|
|
496
|
-
* Source token ids whose spend already certified on-chain (and whose finished
|
|
497
|
-
* output blob is journaled for delivery). Their value has already been
|
|
498
|
-
* delivered; NEVER re-send these. May accumulate across several internal
|
|
499
|
-
* remainder-re-plan attempts (so not all are journaled under {@link transferId}).
|
|
500
|
-
*/
|
|
501
|
-
committedTokenIds;
|
|
502
|
-
/**
|
|
503
|
-
* The still-undelivered portion (base units, decimal string) — `send()` could
|
|
504
|
-
* not COMPLETE it from the remaining live sources (insufficient funds or a
|
|
505
|
-
* transient error; see `cause`). Re-plan ONLY this amount, under a new
|
|
506
|
-
* transferId; the delivered legs converge via the recipient's §6 claim.
|
|
507
|
-
*/
|
|
508
|
-
remainingAmount;
|
|
509
|
-
constructor(message, transferId, committedTokenIds, remainingAmount, cause) {
|
|
510
|
-
super(message, "SEND_PARTIALLY_COMPLETED", cause);
|
|
511
|
-
this.name = "PartialSendConflictError";
|
|
512
|
-
this.transferId = transferId;
|
|
513
|
-
this.committedTokenIds = [...committedTokenIds];
|
|
514
|
-
this.remainingAmount = remainingAmount;
|
|
515
|
-
}
|
|
516
|
-
};
|
|
517
|
-
var POSSIBLY_COMMITTED_SEND_CODES = /* @__PURE__ */ new Set([
|
|
518
|
-
"SEND_SYNC_PENDING",
|
|
519
|
-
"CERTIFICATION_UNCONFIRMED",
|
|
520
|
-
"CHECKPOINT_PERSIST_FAILED",
|
|
521
|
-
"SPLIT_CHECKPOINT_LOST",
|
|
522
|
-
"CHECKPOINT_TRUSTBASE_MISMATCH",
|
|
523
|
-
"SEND_PARTIALLY_COMPLETED"
|
|
524
|
-
]);
|
|
525
|
-
function isPossiblyCommittedSendOutcome(err) {
|
|
526
|
-
return isSphereError(err) && POSSIBLY_COMMITTED_SEND_CODES.has(err.code);
|
|
527
|
-
}
|
|
528
|
-
function isSphereError(err) {
|
|
529
|
-
return err instanceof SphereError;
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
// core/crypto.ts
|
|
533
596
|
var CURVE_ORDER = BigInt(
|
|
534
597
|
"0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141"
|
|
535
598
|
);
|
|
@@ -544,71 +607,6 @@ function bytesToHex(bytes) {
|
|
|
544
607
|
return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
545
608
|
}
|
|
546
609
|
|
|
547
|
-
// core/uuid.ts
|
|
548
|
-
function randomUUID() {
|
|
549
|
-
const c = globalThis.crypto;
|
|
550
|
-
if (typeof c !== "undefined" && typeof c.randomUUID === "function") {
|
|
551
|
-
return c.randomUUID();
|
|
552
|
-
}
|
|
553
|
-
const b = c.getRandomValues(new Uint8Array(16));
|
|
554
|
-
b[6] = b[6] & 15 | 64;
|
|
555
|
-
b[8] = b[8] & 63 | 128;
|
|
556
|
-
const h = Array.from(b, (x) => x.toString(16).padStart(2, "0"));
|
|
557
|
-
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]}`;
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
// modules/payments-v2/async.ts
|
|
561
|
-
var SingleFlight = class {
|
|
562
|
-
flight = null;
|
|
563
|
-
run(fn) {
|
|
564
|
-
if (this.flight !== null) return this.flight;
|
|
565
|
-
const current = fn().finally(() => {
|
|
566
|
-
this.flight = null;
|
|
567
|
-
});
|
|
568
|
-
this.flight = current;
|
|
569
|
-
return current;
|
|
570
|
-
}
|
|
571
|
-
};
|
|
572
|
-
var SerialChain = class {
|
|
573
|
-
tail = Promise.resolve();
|
|
574
|
-
/** FIFO; starts after every prior fn settled; returns this fn's own outcome. */
|
|
575
|
-
enqueue(fn) {
|
|
576
|
-
const run = this.tail.then(fn, fn);
|
|
577
|
-
this.tail = run.then(
|
|
578
|
-
() => void 0,
|
|
579
|
-
() => void 0
|
|
580
|
-
);
|
|
581
|
-
return run;
|
|
582
|
-
}
|
|
583
|
-
};
|
|
584
|
-
function coalesced(run, observe) {
|
|
585
|
-
let inFlight = false;
|
|
586
|
-
let queued = false;
|
|
587
|
-
const start = () => {
|
|
588
|
-
inFlight = true;
|
|
589
|
-
let running;
|
|
590
|
-
try {
|
|
591
|
-
running = run();
|
|
592
|
-
} catch {
|
|
593
|
-
running = Promise.resolve();
|
|
594
|
-
}
|
|
595
|
-
observe?.(running);
|
|
596
|
-
void running.catch(() => void 0).finally(() => {
|
|
597
|
-
inFlight = false;
|
|
598
|
-
if (!queued) return;
|
|
599
|
-
queued = false;
|
|
600
|
-
start();
|
|
601
|
-
});
|
|
602
|
-
};
|
|
603
|
-
return () => {
|
|
604
|
-
if (inFlight) {
|
|
605
|
-
queued = true;
|
|
606
|
-
return;
|
|
607
|
-
}
|
|
608
|
-
start();
|
|
609
|
-
};
|
|
610
|
-
}
|
|
611
|
-
|
|
612
610
|
// modules/payments-v2/machine/journal.ts
|
|
613
611
|
var MAX_DELIVERY_ATTEMPTS = 6;
|
|
614
612
|
var RATE_LIMIT_DEFER_MS = 3e4;
|
|
@@ -787,6 +785,17 @@ function createMachineStores(kv) {
|
|
|
787
785
|
};
|
|
788
786
|
}
|
|
789
787
|
|
|
788
|
+
// modules/payments-v2/machine/payload-view.ts
|
|
789
|
+
function isWholeIntent(p) {
|
|
790
|
+
return p.kind === "whole" || p.kind === "coinless";
|
|
791
|
+
}
|
|
792
|
+
function splitOf(p) {
|
|
793
|
+
return p.kind === "coin" ? p.split : void 0;
|
|
794
|
+
}
|
|
795
|
+
function coinIdOf(p) {
|
|
796
|
+
return p.kind === "coin" ? p.coinId : void 0;
|
|
797
|
+
}
|
|
798
|
+
|
|
790
799
|
// token-engine/errors.ts
|
|
791
800
|
var TransferConflictError = class extends SphereError {
|
|
792
801
|
/**
|
|
@@ -1028,13 +1037,14 @@ var TransferMachine = class {
|
|
|
1028
1037
|
const finished = await engine.transfer({ token: source, recipientPubkey }, opts);
|
|
1029
1038
|
return { recipientBlob: finished.blob.token };
|
|
1030
1039
|
}
|
|
1031
|
-
const split = ctx.payload
|
|
1040
|
+
const split = splitOf(ctx.payload);
|
|
1041
|
+
const splitCoinId = coinIdOf(ctx.payload);
|
|
1032
1042
|
const { outputs } = await engine.split(
|
|
1033
1043
|
{
|
|
1034
1044
|
token: source,
|
|
1035
1045
|
outputs: [
|
|
1036
|
-
{ recipientPubkey, coinId:
|
|
1037
|
-
{ recipientPubkey: this.deps.ownPubkey, coinId:
|
|
1046
|
+
{ recipientPubkey, coinId: splitCoinId, amount: BigInt(split.splitAmount) },
|
|
1047
|
+
{ recipientPubkey: this.deps.ownPubkey, coinId: splitCoinId, amount: BigInt(split.remainderAmount) }
|
|
1038
1048
|
]
|
|
1039
1049
|
},
|
|
1040
1050
|
opts
|
|
@@ -1156,7 +1166,9 @@ var TransferMachine = class {
|
|
|
1156
1166
|
const entry = {
|
|
1157
1167
|
transferId: r.transferId,
|
|
1158
1168
|
remainingAmount: undelivered.toString(),
|
|
1159
|
-
|
|
1169
|
+
// '' only on a token-addressed intent, which is one leg: it either lands or
|
|
1170
|
+
// conflicts, so a shortfall row is unreachable there (settlePartial needs >1).
|
|
1171
|
+
coinId: coinIdOf(r.payload) ?? "",
|
|
1160
1172
|
recipient: r.payload.recipient,
|
|
1161
1173
|
committedTokenIds: committed.map((o) => o.op.sourceTokenId),
|
|
1162
1174
|
createdAt: this.deps.now()
|
|
@@ -1182,8 +1194,16 @@ var TransferMachine = class {
|
|
|
1182
1194
|
}
|
|
1183
1195
|
async finishBookkeeping(engine, transferId, payload, phase, committed) {
|
|
1184
1196
|
try {
|
|
1185
|
-
const
|
|
1186
|
-
await this.
|
|
1197
|
+
const coinId = coinIdOf(payload);
|
|
1198
|
+
const committedAmount = coinId === void 0 ? "0" : await this.settledAmount(engine, coinId, committed);
|
|
1199
|
+
const committedAssets = coinId === void 0 ? await this.settledAssets(engine, committed) : void 0;
|
|
1200
|
+
await this.deps.recordHistory?.({
|
|
1201
|
+
transferId,
|
|
1202
|
+
payload,
|
|
1203
|
+
phase,
|
|
1204
|
+
committedAmount,
|
|
1205
|
+
...committedAssets !== void 0 ? { committedAssets } : {}
|
|
1206
|
+
});
|
|
1187
1207
|
} catch {
|
|
1188
1208
|
}
|
|
1189
1209
|
try {
|
|
@@ -1197,6 +1217,18 @@ var TransferMachine = class {
|
|
|
1197
1217
|
* a journal-rehydrated resume leg has no source token, but every committed
|
|
1198
1218
|
* leg carries its recipient blob, so the outcome-derived rule is uniform).
|
|
1199
1219
|
*/
|
|
1220
|
+
/** Every asset across the certified recipient blobs — the whole-spend twin of settledAmount. */
|
|
1221
|
+
async settledAssets(engine, committed) {
|
|
1222
|
+
const total = /* @__PURE__ */ new Map();
|
|
1223
|
+
for (const outcome of committed) {
|
|
1224
|
+
if (outcome.recipientBlob === void 0) continue;
|
|
1225
|
+
const token = await engine.decodeToken({ tokenId: "", token: outcome.recipientBlob });
|
|
1226
|
+
for (const asset of engine.readValue(token)?.assets ?? []) {
|
|
1227
|
+
total.set(asset.coinId, (total.get(asset.coinId) ?? 0n) + asset.amount);
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
return [...total].map(([coinId, amount]) => ({ coinId, amount: amount.toString() }));
|
|
1231
|
+
}
|
|
1200
1232
|
async settledAmount(engine, coinId, committed) {
|
|
1201
1233
|
let total = 0n;
|
|
1202
1234
|
for (const outcome of committed) {
|
|
@@ -1225,8 +1257,11 @@ var TransferMachine = class {
|
|
|
1225
1257
|
}
|
|
1226
1258
|
};
|
|
1227
1259
|
function conflictAmount(engine, payload, op, source) {
|
|
1228
|
-
|
|
1229
|
-
|
|
1260
|
+
const split = splitOf(payload);
|
|
1261
|
+
if (op.kind === "split") return BigInt(split.splitAmount);
|
|
1262
|
+
const coinId = coinIdOf(payload);
|
|
1263
|
+
if (coinId === void 0) return 0n;
|
|
1264
|
+
return source !== void 0 ? engine.balanceOf(source, coinId) : 0n;
|
|
1230
1265
|
}
|
|
1231
1266
|
|
|
1232
1267
|
// modules/payments-v2/machine/resume.ts
|
|
@@ -1333,6 +1368,14 @@ async function runOne(ctx, job, report) {
|
|
|
1333
1368
|
report.failed.push(job.transferId);
|
|
1334
1369
|
return;
|
|
1335
1370
|
}
|
|
1371
|
+
if (isWholeIntent(job.payload) && token.valueEnvelope === "bare_collection") {
|
|
1372
|
+
logger.warn(
|
|
1373
|
+
"PaymentsV2",
|
|
1374
|
+
`resume: token intent ${job.transferId} names a source carrying coin value \u2014 refusing`
|
|
1375
|
+
);
|
|
1376
|
+
report.failed.push(job.transferId);
|
|
1377
|
+
return;
|
|
1378
|
+
}
|
|
1336
1379
|
mine.set(op.sourceTokenId, token);
|
|
1337
1380
|
}
|
|
1338
1381
|
try {
|
|
@@ -1363,6 +1406,28 @@ async function classifyResumeFailure(deps, stores, transferId, err, report) {
|
|
|
1363
1406
|
}
|
|
1364
1407
|
report.failed.push(transferId);
|
|
1365
1408
|
}
|
|
1409
|
+
function validateCoinPayload(c) {
|
|
1410
|
+
if (typeof c.coinId !== "string" || typeof c.amount !== "string") {
|
|
1411
|
+
throw new SphereError("intent payload is missing coinId/amount", "VALIDATION_ERROR");
|
|
1412
|
+
}
|
|
1413
|
+
const s = c.split;
|
|
1414
|
+
if (s !== void 0 && (typeof s.tokenId !== "string" || typeof s.splitAmount !== "string" || typeof s.remainderAmount !== "string")) {
|
|
1415
|
+
throw new SphereError("intent payload split spec is malformed", "VALIDATION_ERROR");
|
|
1416
|
+
}
|
|
1417
|
+
return { ...c, kind: "coin" };
|
|
1418
|
+
}
|
|
1419
|
+
function validateWholePayload(p) {
|
|
1420
|
+
if (p.direct?.length !== 1 || typeof p.direct[0] !== "string" || p.direct[0] === "") {
|
|
1421
|
+
throw new SphereError(
|
|
1422
|
+
"token intent payload must name exactly one source token",
|
|
1423
|
+
"VALIDATION_ERROR"
|
|
1424
|
+
);
|
|
1425
|
+
}
|
|
1426
|
+
if (p.split !== void 0) {
|
|
1427
|
+
throw new SphereError("token intent payload cannot carry a split", "VALIDATION_ERROR");
|
|
1428
|
+
}
|
|
1429
|
+
return { ...p, kind: "whole", direct: [p.direct[0]] };
|
|
1430
|
+
}
|
|
1366
1431
|
function validatePayload(raw) {
|
|
1367
1432
|
const p = raw !== null && typeof raw === "object" ? raw : null;
|
|
1368
1433
|
if (p === null || p.v !== 2 || !Array.isArray(p.direct) || p.direct.some((t) => typeof t !== "string")) {
|
|
@@ -1371,14 +1436,17 @@ function validatePayload(raw) {
|
|
|
1371
1436
|
"VALIDATION_ERROR"
|
|
1372
1437
|
);
|
|
1373
1438
|
}
|
|
1374
|
-
if (typeof p.recipient !== "string"
|
|
1375
|
-
throw new SphereError("intent payload is missing recipient
|
|
1439
|
+
if (typeof p.recipient !== "string") {
|
|
1440
|
+
throw new SphereError("intent payload is missing recipient", "VALIDATION_ERROR");
|
|
1376
1441
|
}
|
|
1377
|
-
const
|
|
1378
|
-
if (
|
|
1379
|
-
throw new SphereError(
|
|
1442
|
+
const kind = p.kind ?? "coin";
|
|
1443
|
+
if (kind !== "coin" && kind !== "whole" && kind !== "coinless") {
|
|
1444
|
+
throw new SphereError(
|
|
1445
|
+
`unsupported intent kind '${String(kind)}' \u2014 not resumable by this client`,
|
|
1446
|
+
"VALIDATION_ERROR"
|
|
1447
|
+
);
|
|
1380
1448
|
}
|
|
1381
|
-
return p;
|
|
1449
|
+
return kind === "coin" ? validateCoinPayload(p) : validateWholePayload(p);
|
|
1382
1450
|
}
|
|
1383
1451
|
|
|
1384
1452
|
// modules/payments-v2/convergence.ts
|
|
@@ -1578,46 +1646,314 @@ async function openRow(entry, journal, decryptPayload) {
|
|
|
1578
1646
|
} catch {
|
|
1579
1647
|
payload = null;
|
|
1580
1648
|
}
|
|
1581
|
-
const total = Array.isArray(payload?.direct) ? payload.direct.length + (payload.split !== void 0 ? 1 : 0) : 0;
|
|
1582
|
-
const certified = distinctOpCount(journal);
|
|
1583
|
-
return {
|
|
1584
|
-
transferId: entry.transferId,
|
|
1585
|
-
kind: "open",
|
|
1586
|
-
recipient: typeof payload?.recipient === "string" ? payload.recipient : "",
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1649
|
+
const total = Array.isArray(payload?.direct) ? payload.direct.length + (payload.split !== void 0 ? 1 : 0) : 0;
|
|
1650
|
+
const certified = distinctOpCount(journal);
|
|
1651
|
+
return {
|
|
1652
|
+
transferId: entry.transferId,
|
|
1653
|
+
kind: "open",
|
|
1654
|
+
recipient: typeof payload?.recipient === "string" ? payload.recipient : "",
|
|
1655
|
+
...subject(payload),
|
|
1656
|
+
legs: { certified, total: Math.max(total, certified) },
|
|
1657
|
+
deliveryPending: journal.length > 0,
|
|
1658
|
+
createdAt: entry.createdAt
|
|
1659
|
+
};
|
|
1660
|
+
}
|
|
1661
|
+
function subject(payload) {
|
|
1662
|
+
const rawKind = payload?.kind;
|
|
1663
|
+
if (rawKind === "whole" || rawKind === "coinless") {
|
|
1664
|
+
const tokenId = payload?.direct?.[0];
|
|
1665
|
+
return { coinId: "", amount: "", ...typeof tokenId === "string" ? { tokenId } : {} };
|
|
1666
|
+
}
|
|
1667
|
+
const coin = payload;
|
|
1668
|
+
return {
|
|
1669
|
+
coinId: typeof coin?.coinId === "string" ? coin.coinId : "",
|
|
1670
|
+
amount: typeof coin?.amount === "string" ? coin.amount : ""
|
|
1671
|
+
};
|
|
1672
|
+
}
|
|
1673
|
+
function journalOnlyRow(transferId, entries) {
|
|
1674
|
+
const certified = distinctOpCount(entries);
|
|
1675
|
+
return {
|
|
1676
|
+
transferId,
|
|
1677
|
+
kind: "open",
|
|
1678
|
+
recipient: entries[0].recipientPubkey,
|
|
1679
|
+
coinId: "",
|
|
1680
|
+
amount: "",
|
|
1681
|
+
legs: { certified, total: certified },
|
|
1682
|
+
deliveryPending: true,
|
|
1683
|
+
createdAt: 0
|
|
1684
|
+
};
|
|
1685
|
+
}
|
|
1686
|
+
function shortfallRow(entry) {
|
|
1687
|
+
return {
|
|
1688
|
+
transferId: entry.transferId,
|
|
1689
|
+
kind: "shortfall",
|
|
1690
|
+
recipient: entry.recipient,
|
|
1691
|
+
coinId: entry.coinId,
|
|
1692
|
+
amount: entry.remainingAmount,
|
|
1693
|
+
legs: { certified: entry.committedTokenIds.length, total: entry.committedTokenIds.length },
|
|
1694
|
+
deliveryPending: false,
|
|
1695
|
+
createdAt: entry.createdAt
|
|
1696
|
+
};
|
|
1697
|
+
}
|
|
1698
|
+
function distinctOpCount(entries) {
|
|
1699
|
+
return new Set(entries.map((entry) => entry.opIndex)).size;
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
// modules/payments-v2/inventory/token-data.ts
|
|
1703
|
+
async function readTokenData(deps, tokenId) {
|
|
1704
|
+
if (deps.view.stateHashOf(tokenId) === void 0) {
|
|
1705
|
+
throw new SphereError(`Token ${tokenId} is not in inventory`, "VALIDATION_ERROR");
|
|
1706
|
+
}
|
|
1707
|
+
const bytes = (await deps.storagePort.getBlobs([tokenId])).get(tokenId);
|
|
1708
|
+
if (bytes === void 0) {
|
|
1709
|
+
throw new SphereError(`Token ${tokenId} has no blob in storage`, "STORAGE_ERROR");
|
|
1710
|
+
}
|
|
1711
|
+
return deps.engine.readTokenData(await deps.engine.decodeToken({ tokenId, token: bytes }));
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
// modules/payments-v2/machine/payload.ts
|
|
1715
|
+
function buildPayload(recipientPubkey, request, spend, spentStates) {
|
|
1716
|
+
return {
|
|
1717
|
+
v: 2,
|
|
1718
|
+
kind: "coin",
|
|
1719
|
+
recipient: recipientPubkey,
|
|
1720
|
+
coinId: request.coinId,
|
|
1721
|
+
amount: request.amount,
|
|
1722
|
+
...request.memo !== void 0 ? { memo: request.memo } : {},
|
|
1723
|
+
direct: [...spend.plan.direct],
|
|
1724
|
+
...spend.plan.split !== void 0 ? {
|
|
1725
|
+
split: {
|
|
1726
|
+
tokenId: spend.plan.split.tokenId,
|
|
1727
|
+
splitAmount: spend.plan.split.splitAmount.toString(),
|
|
1728
|
+
remainderAmount: spend.plan.split.remainderAmount.toString()
|
|
1729
|
+
}
|
|
1730
|
+
} : {},
|
|
1731
|
+
spentStates
|
|
1732
|
+
};
|
|
1733
|
+
}
|
|
1734
|
+
function messageOf(err) {
|
|
1735
|
+
return err instanceof Error ? err.message : String(err);
|
|
1736
|
+
}
|
|
1737
|
+
function buildWholePayload(recipientPubkey, request, spentStates) {
|
|
1738
|
+
return {
|
|
1739
|
+
v: 2,
|
|
1740
|
+
kind: "whole",
|
|
1741
|
+
recipient: recipientPubkey,
|
|
1742
|
+
...request.memo !== void 0 ? { memo: request.memo } : {},
|
|
1743
|
+
direct: [request.tokenId],
|
|
1744
|
+
spentStates
|
|
1745
|
+
};
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
// modules/payments-v2/mint-params.ts
|
|
1749
|
+
function mintParams(recipientPubkey, coinId, amount) {
|
|
1750
|
+
return { recipientPubkey, value: { assets: [{ coinId, amount }] } };
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
// modules/payments-v2/mint.ts
|
|
1754
|
+
function runMintUnderJournal(deps, input) {
|
|
1755
|
+
return mintUnderJournal(deps, input);
|
|
1756
|
+
}
|
|
1757
|
+
async function mintUnderJournal(deps, input) {
|
|
1758
|
+
const { mintId, coinId, amount } = input;
|
|
1759
|
+
const engine = deps.engine;
|
|
1760
|
+
const entry = {
|
|
1761
|
+
mintId,
|
|
1762
|
+
coinId,
|
|
1763
|
+
amount: amount.toString(),
|
|
1764
|
+
tokenId: "",
|
|
1765
|
+
createdAt: deps.now()
|
|
1766
|
+
};
|
|
1767
|
+
await deps.mintJournal.upsert(entry);
|
|
1768
|
+
let token;
|
|
1769
|
+
try {
|
|
1770
|
+
token = await engine.mint(mintParams(deps.ownPubkeyBytes, coinId, amount), { transferId: mintId });
|
|
1771
|
+
} catch (err) {
|
|
1772
|
+
deps.armHeartbeat();
|
|
1773
|
+
return { success: false, error: messageOf(err) };
|
|
1774
|
+
}
|
|
1775
|
+
if (token.blob.tokenId !== entry.tokenId) {
|
|
1776
|
+
await deps.mintJournal.upsert({ ...entry, tokenId: token.blob.tokenId });
|
|
1777
|
+
}
|
|
1778
|
+
try {
|
|
1779
|
+
await finalizeMint(deps, mintId, token, coinId, amount.toString());
|
|
1780
|
+
} catch (err) {
|
|
1781
|
+
deps.armHeartbeat();
|
|
1782
|
+
return { success: false, tokenId: token.blob.tokenId, error: messageOf(err) };
|
|
1783
|
+
}
|
|
1784
|
+
return { success: true, tokenId: token.blob.tokenId };
|
|
1785
|
+
}
|
|
1786
|
+
async function finalizeMint(deps, mintId, token, coinId, amount) {
|
|
1787
|
+
const engine = deps.engine;
|
|
1788
|
+
const bytes = token.blob.token;
|
|
1789
|
+
const digest = bytesToHex(sha256(bytes));
|
|
1790
|
+
const keys = await deps.storagePort.uploadBlobs([{ sha256: digest, bytes }]);
|
|
1791
|
+
const key = keys.get(digest);
|
|
1792
|
+
if (key === void 0) {
|
|
1793
|
+
throw new SphereError(`no upload key returned for mint blob ${digest}`, "STORAGE_ERROR");
|
|
1794
|
+
}
|
|
1795
|
+
await deps.storagePort.applyDelta({
|
|
1796
|
+
transferId: mintId,
|
|
1797
|
+
spent: [],
|
|
1798
|
+
added: [{ tokenId: token.blob.tokenId, key }]
|
|
1799
|
+
});
|
|
1800
|
+
await deps.recordMint({
|
|
1801
|
+
tokenId: token.blob.tokenId,
|
|
1802
|
+
assets: [{ coinId, amount }]
|
|
1803
|
+
});
|
|
1804
|
+
await deps.mintJournal.removeByKey(mintId);
|
|
1805
|
+
deps.noteHeldState(token.blob.tokenId, (await engine.deliveryKeys(bytes)).stateHash);
|
|
1806
|
+
deps.refreshView();
|
|
1807
|
+
}
|
|
1808
|
+
|
|
1809
|
+
// modules/payments-v2/inventory/presentation.ts
|
|
1810
|
+
function toToken(tokenId, snapshot, asset, registry, flags) {
|
|
1811
|
+
const iconUrl = registry.getIconUrl(asset.coinId);
|
|
1812
|
+
return {
|
|
1813
|
+
id: tokenId,
|
|
1814
|
+
coinId: asset.coinId,
|
|
1815
|
+
symbol: registry.getSymbol(asset.coinId),
|
|
1816
|
+
name: registry.getName(asset.coinId),
|
|
1817
|
+
decimals: registry.getDecimals(asset.coinId),
|
|
1818
|
+
...iconUrl !== null ? { iconUrl } : {},
|
|
1819
|
+
amount: asset.amount,
|
|
1820
|
+
status: flags.transferring ? "transferring" : "confirmed",
|
|
1821
|
+
createdAt: snapshot.createdAt,
|
|
1822
|
+
updatedAt: snapshot.updatedAt,
|
|
1823
|
+
lazy: true,
|
|
1824
|
+
...flags.suspectedSpent ? { suspectedSpent: true } : {}
|
|
1825
|
+
};
|
|
1826
|
+
}
|
|
1827
|
+
function aggregateAssets(entries, isTransferring, registry) {
|
|
1828
|
+
const groups = /* @__PURE__ */ new Map();
|
|
1829
|
+
for (const [tokenId, entry] of entries) {
|
|
1830
|
+
const moving = isTransferring(tokenId);
|
|
1831
|
+
for (const asset of entry.assets) {
|
|
1832
|
+
let group = groups.get(asset.coinId);
|
|
1833
|
+
if (!group) {
|
|
1834
|
+
group = { total: 0n, count: 0, transferring: 0n, transferringCount: 0 };
|
|
1835
|
+
groups.set(asset.coinId, group);
|
|
1836
|
+
}
|
|
1837
|
+
if (moving) {
|
|
1838
|
+
group.transferring += BigInt(asset.amount);
|
|
1839
|
+
group.transferringCount += 1;
|
|
1840
|
+
} else {
|
|
1841
|
+
group.total += BigInt(asset.amount);
|
|
1842
|
+
group.count += 1;
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
return [...groups].map(([coinId, totals]) => toAsset(coinId, totals, registry));
|
|
1847
|
+
}
|
|
1848
|
+
function toAsset(coinId, totals, registry) {
|
|
1849
|
+
const iconUrl = registry.getIconUrl(coinId);
|
|
1850
|
+
return {
|
|
1851
|
+
coinId,
|
|
1852
|
+
symbol: registry.getSymbol(coinId),
|
|
1853
|
+
name: registry.getName(coinId),
|
|
1854
|
+
decimals: registry.getDecimals(coinId),
|
|
1855
|
+
...iconUrl !== null ? { iconUrl } : {},
|
|
1856
|
+
totalAmount: totals.total.toString(),
|
|
1857
|
+
tokenCount: totals.count,
|
|
1858
|
+
confirmedAmount: totals.total.toString(),
|
|
1859
|
+
unconfirmedAmount: "0",
|
|
1860
|
+
confirmedTokenCount: totals.count,
|
|
1861
|
+
unconfirmedTokenCount: 0,
|
|
1862
|
+
transferringTokenCount: totals.transferringCount,
|
|
1863
|
+
transferringAmount: totals.transferring.toString(),
|
|
1864
|
+
priceUsd: null,
|
|
1865
|
+
priceEur: null,
|
|
1866
|
+
change24h: null,
|
|
1867
|
+
fiatValueUsd: null,
|
|
1868
|
+
fiatValueEur: null
|
|
1869
|
+
};
|
|
1870
|
+
}
|
|
1871
|
+
function priceIdOf(registry, coinId) {
|
|
1872
|
+
return registry.getName(coinId).toLowerCase();
|
|
1873
|
+
}
|
|
1874
|
+
async function withPrices(raw, registry, price) {
|
|
1875
|
+
try {
|
|
1876
|
+
const ids = [...new Set(raw.map((a) => priceIdOf(registry, a.coinId)))];
|
|
1877
|
+
const quotes = await price.getPrices(ids);
|
|
1878
|
+
return raw.map((a) => priceOne(a, quotes.get(priceIdOf(registry, a.coinId))));
|
|
1879
|
+
} catch {
|
|
1880
|
+
return raw;
|
|
1881
|
+
}
|
|
1593
1882
|
}
|
|
1594
|
-
function
|
|
1595
|
-
|
|
1883
|
+
function priceOne(asset, quote) {
|
|
1884
|
+
if (!quote) return asset;
|
|
1885
|
+
const human = Number(asset.totalAmount) / Math.pow(10, asset.decimals);
|
|
1596
1886
|
return {
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
deliveryPending: true,
|
|
1604
|
-
createdAt: 0
|
|
1887
|
+
...asset,
|
|
1888
|
+
priceUsd: quote.priceUsd,
|
|
1889
|
+
priceEur: quote.priceEur ?? null,
|
|
1890
|
+
change24h: quote.change24h ?? null,
|
|
1891
|
+
fiatValueUsd: human * quote.priceUsd,
|
|
1892
|
+
fiatValueEur: quote.priceEur != null ? human * quote.priceEur : null
|
|
1605
1893
|
};
|
|
1606
1894
|
}
|
|
1607
|
-
function
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1895
|
+
function transferringToken(tokenId, coinId, amount, registry, now) {
|
|
1896
|
+
const snapshot = { assets: [], createdAt: now, updatedAt: now };
|
|
1897
|
+
return toToken(tokenId, snapshot, { coinId, amount: amount.toString() }, registry, {
|
|
1898
|
+
transferring: true,
|
|
1899
|
+
suspectedSpent: false
|
|
1900
|
+
});
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
// modules/payments-v2/send-whole.ts
|
|
1904
|
+
async function materializeWholeSpend(deps, input) {
|
|
1905
|
+
const { engine } = deps;
|
|
1906
|
+
const tokenId = input.request.tokenId;
|
|
1907
|
+
const bytes = (await deps.storagePort.getBlobs([tokenId])).get(tokenId);
|
|
1908
|
+
if (bytes === void 0) {
|
|
1909
|
+
throw new SphereError(`Selected source ${tokenId} has no blob in storage`, "STORAGE_ERROR");
|
|
1910
|
+
}
|
|
1911
|
+
const token = await engine.decodeToken({ tokenId, token: bytes });
|
|
1912
|
+
if (token.valueEnvelope === "bare_collection") {
|
|
1913
|
+
throw new SphereError(
|
|
1914
|
+
`Token ${tokenId} carries a value envelope this SDK cannot read, so its coins cannot be accounted for; it cannot be sent`,
|
|
1915
|
+
"VALIDATION_ERROR"
|
|
1916
|
+
);
|
|
1917
|
+
}
|
|
1918
|
+
if (input.requireCoinless && token.value !== null) {
|
|
1919
|
+
throw new SphereError(
|
|
1920
|
+
`Token ${tokenId} carries coin value and cannot be sent with sendCoinless \u2014 use sendWholeToken, which requires coin-transfer authority`,
|
|
1921
|
+
"VALIDATION_ERROR"
|
|
1922
|
+
);
|
|
1923
|
+
}
|
|
1924
|
+
const keys = await engine.deliveryKeys(bytes);
|
|
1925
|
+
const payload = buildWholePayload(input.recipientPubkey, input.request, {
|
|
1926
|
+
[tokenId]: { local: keys.stateHash, protocol: keys.stateHash }
|
|
1927
|
+
});
|
|
1928
|
+
const plan = {
|
|
1929
|
+
transferId: input.transferId,
|
|
1930
|
+
recipientPubkey: input.recipientPubkey,
|
|
1931
|
+
payload,
|
|
1932
|
+
ops: buildOps(payload),
|
|
1933
|
+
sources: /* @__PURE__ */ new Map([[tokenId, token]]),
|
|
1934
|
+
...input.request.memo !== void 0 ? { memo: input.request.memo } : {}
|
|
1617
1935
|
};
|
|
1936
|
+
const sourceTokens = (token.value?.assets ?? []).map(
|
|
1937
|
+
(asset) => transferringToken(tokenId, asset.coinId, asset.amount, deps.registry, deps.now)
|
|
1938
|
+
);
|
|
1939
|
+
return { transferId: input.transferId, coinId: "", plan, sourceIds: input.sourceIds, sourceTokens };
|
|
1618
1940
|
}
|
|
1619
|
-
|
|
1620
|
-
|
|
1941
|
+
|
|
1942
|
+
// modules/payments-v2/send-errors.ts
|
|
1943
|
+
function partialize(err, run) {
|
|
1944
|
+
if (run.delivered.length === 0) return err;
|
|
1945
|
+
return new PartialSendConflictError(
|
|
1946
|
+
"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.",
|
|
1947
|
+
run.firstPartialId ?? "",
|
|
1948
|
+
[...run.delivered],
|
|
1949
|
+
run.amount,
|
|
1950
|
+
err
|
|
1951
|
+
);
|
|
1952
|
+
}
|
|
1953
|
+
function stampTransferId(err, transferId) {
|
|
1954
|
+
if (err instanceof SphereError && isPossiblyCommittedSendOutcome(err) && err.transferId === void 0) {
|
|
1955
|
+
err.transferId = transferId;
|
|
1956
|
+
}
|
|
1621
1957
|
}
|
|
1622
1958
|
|
|
1623
1959
|
// modules/payments-v2/recipient.ts
|
|
@@ -1668,11 +2004,6 @@ async function splitOpIndex(deps, envelope) {
|
|
|
1668
2004
|
return Array.isArray(payload.direct) ? payload.direct.length : 0;
|
|
1669
2005
|
}
|
|
1670
2006
|
|
|
1671
|
-
// modules/payments-v2/mint-params.ts
|
|
1672
|
-
function mintParams(recipientPubkey, coinId, amount) {
|
|
1673
|
-
return { recipientPubkey, value: { assets: [{ coinId, amount }] } };
|
|
1674
|
-
}
|
|
1675
|
-
|
|
1676
2007
|
// modules/payments-v2/prewarm-cache.ts
|
|
1677
2008
|
var PrewarmCache = class _PrewarmCache {
|
|
1678
2009
|
entries = /* @__PURE__ */ new Map();
|
|
@@ -1752,123 +2083,6 @@ async function takeSourceBlobs(deps, sourceIds) {
|
|
|
1752
2083
|
return blobs;
|
|
1753
2084
|
}
|
|
1754
2085
|
|
|
1755
|
-
// modules/payments-v2/inventory/presentation.ts
|
|
1756
|
-
function toToken(tokenId, snapshot, asset, registry, flags) {
|
|
1757
|
-
const iconUrl = registry.getIconUrl(asset.coinId);
|
|
1758
|
-
return {
|
|
1759
|
-
id: tokenId,
|
|
1760
|
-
coinId: asset.coinId,
|
|
1761
|
-
symbol: registry.getSymbol(asset.coinId),
|
|
1762
|
-
name: registry.getName(asset.coinId),
|
|
1763
|
-
decimals: registry.getDecimals(asset.coinId),
|
|
1764
|
-
...iconUrl !== null ? { iconUrl } : {},
|
|
1765
|
-
amount: asset.amount,
|
|
1766
|
-
status: flags.transferring ? "transferring" : "confirmed",
|
|
1767
|
-
createdAt: snapshot.createdAt,
|
|
1768
|
-
updatedAt: snapshot.updatedAt,
|
|
1769
|
-
lazy: true,
|
|
1770
|
-
...flags.suspectedSpent ? { suspectedSpent: true } : {}
|
|
1771
|
-
};
|
|
1772
|
-
}
|
|
1773
|
-
function aggregateAssets(entries, isTransferring, registry) {
|
|
1774
|
-
const groups = /* @__PURE__ */ new Map();
|
|
1775
|
-
for (const [tokenId, entry] of entries) {
|
|
1776
|
-
const moving = isTransferring(tokenId);
|
|
1777
|
-
for (const asset of entry.assets) {
|
|
1778
|
-
let group = groups.get(asset.coinId);
|
|
1779
|
-
if (!group) {
|
|
1780
|
-
group = { total: 0n, count: 0, transferring: 0n, transferringCount: 0 };
|
|
1781
|
-
groups.set(asset.coinId, group);
|
|
1782
|
-
}
|
|
1783
|
-
if (moving) {
|
|
1784
|
-
group.transferring += BigInt(asset.amount);
|
|
1785
|
-
group.transferringCount += 1;
|
|
1786
|
-
} else {
|
|
1787
|
-
group.total += BigInt(asset.amount);
|
|
1788
|
-
group.count += 1;
|
|
1789
|
-
}
|
|
1790
|
-
}
|
|
1791
|
-
}
|
|
1792
|
-
return [...groups].map(([coinId, totals]) => toAsset(coinId, totals, registry));
|
|
1793
|
-
}
|
|
1794
|
-
function toAsset(coinId, totals, registry) {
|
|
1795
|
-
const iconUrl = registry.getIconUrl(coinId);
|
|
1796
|
-
return {
|
|
1797
|
-
coinId,
|
|
1798
|
-
symbol: registry.getSymbol(coinId),
|
|
1799
|
-
name: registry.getName(coinId),
|
|
1800
|
-
decimals: registry.getDecimals(coinId),
|
|
1801
|
-
...iconUrl !== null ? { iconUrl } : {},
|
|
1802
|
-
totalAmount: totals.total.toString(),
|
|
1803
|
-
tokenCount: totals.count,
|
|
1804
|
-
confirmedAmount: totals.total.toString(),
|
|
1805
|
-
unconfirmedAmount: "0",
|
|
1806
|
-
confirmedTokenCount: totals.count,
|
|
1807
|
-
unconfirmedTokenCount: 0,
|
|
1808
|
-
transferringTokenCount: totals.transferringCount,
|
|
1809
|
-
transferringAmount: totals.transferring.toString(),
|
|
1810
|
-
priceUsd: null,
|
|
1811
|
-
priceEur: null,
|
|
1812
|
-
change24h: null,
|
|
1813
|
-
fiatValueUsd: null,
|
|
1814
|
-
fiatValueEur: null
|
|
1815
|
-
};
|
|
1816
|
-
}
|
|
1817
|
-
function priceIdOf(registry, coinId) {
|
|
1818
|
-
return registry.getName(coinId).toLowerCase();
|
|
1819
|
-
}
|
|
1820
|
-
async function withPrices(raw, registry, price) {
|
|
1821
|
-
try {
|
|
1822
|
-
const ids = [...new Set(raw.map((a) => priceIdOf(registry, a.coinId)))];
|
|
1823
|
-
const quotes = await price.getPrices(ids);
|
|
1824
|
-
return raw.map((a) => priceOne(a, quotes.get(priceIdOf(registry, a.coinId))));
|
|
1825
|
-
} catch {
|
|
1826
|
-
return raw;
|
|
1827
|
-
}
|
|
1828
|
-
}
|
|
1829
|
-
function priceOne(asset, quote) {
|
|
1830
|
-
if (!quote) return asset;
|
|
1831
|
-
const human = Number(asset.totalAmount) / Math.pow(10, asset.decimals);
|
|
1832
|
-
return {
|
|
1833
|
-
...asset,
|
|
1834
|
-
priceUsd: quote.priceUsd,
|
|
1835
|
-
priceEur: quote.priceEur ?? null,
|
|
1836
|
-
change24h: quote.change24h ?? null,
|
|
1837
|
-
fiatValueUsd: human * quote.priceUsd,
|
|
1838
|
-
fiatValueEur: quote.priceEur != null ? human * quote.priceEur : null
|
|
1839
|
-
};
|
|
1840
|
-
}
|
|
1841
|
-
function transferringToken(tokenId, coinId, amount, registry, now) {
|
|
1842
|
-
const snapshot = { assets: [], createdAt: now, updatedAt: now };
|
|
1843
|
-
return toToken(tokenId, snapshot, { coinId, amount: amount.toString() }, registry, {
|
|
1844
|
-
transferring: true,
|
|
1845
|
-
suspectedSpent: false
|
|
1846
|
-
});
|
|
1847
|
-
}
|
|
1848
|
-
|
|
1849
|
-
// modules/payments-v2/machine/payload.ts
|
|
1850
|
-
function buildPayload(recipientPubkey, request, spend, spentStates) {
|
|
1851
|
-
return {
|
|
1852
|
-
v: 2,
|
|
1853
|
-
recipient: recipientPubkey,
|
|
1854
|
-
coinId: request.coinId,
|
|
1855
|
-
amount: request.amount,
|
|
1856
|
-
...request.memo !== void 0 ? { memo: request.memo } : {},
|
|
1857
|
-
direct: [...spend.plan.direct],
|
|
1858
|
-
...spend.plan.split !== void 0 ? {
|
|
1859
|
-
split: {
|
|
1860
|
-
tokenId: spend.plan.split.tokenId,
|
|
1861
|
-
splitAmount: spend.plan.split.splitAmount.toString(),
|
|
1862
|
-
remainderAmount: spend.plan.split.remainderAmount.toString()
|
|
1863
|
-
}
|
|
1864
|
-
} : {},
|
|
1865
|
-
spentStates
|
|
1866
|
-
};
|
|
1867
|
-
}
|
|
1868
|
-
function messageOf(err) {
|
|
1869
|
-
return err instanceof Error ? err.message : String(err);
|
|
1870
|
-
}
|
|
1871
|
-
|
|
1872
2086
|
// node_modules/@noble/ciphers/utils.js
|
|
1873
2087
|
function isBytes2(a) {
|
|
1874
2088
|
return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array" && "BYTES_PER_ELEMENT" in a && a.BYTES_PER_ELEMENT === 1;
|
|
@@ -2720,6 +2934,14 @@ function decryptField(key, envelope) {
|
|
|
2720
2934
|
}
|
|
2721
2935
|
|
|
2722
2936
|
// modules/payments-v2/history/History.ts
|
|
2937
|
+
function wireAssets(assets) {
|
|
2938
|
+
return assets.map((a) => ({ coinId: a.coinId, amount: a.amount }));
|
|
2939
|
+
}
|
|
2940
|
+
function isPermanentReject(err) {
|
|
2941
|
+
const status = err?.status;
|
|
2942
|
+
if (typeof status !== "number") return false;
|
|
2943
|
+
return status >= 400 && status < 500 && status !== 408 && status !== 429;
|
|
2944
|
+
}
|
|
2723
2945
|
var WIRE_TOKEN_ID = /^(?:[0-9a-f]{2}){1,64}$/;
|
|
2724
2946
|
var WIRE_PUBKEY = /^0[23][0-9a-f]{64}$/;
|
|
2725
2947
|
var History = class {
|
|
@@ -2744,7 +2966,7 @@ var History = class {
|
|
|
2744
2966
|
dedupKey: input.transferId,
|
|
2745
2967
|
id: (this.deps.newId ?? randomUUID)(),
|
|
2746
2968
|
type: "SENT",
|
|
2747
|
-
assets:
|
|
2969
|
+
assets: wireAssets(input.assets),
|
|
2748
2970
|
ts: this.ts(input.timestamp),
|
|
2749
2971
|
transferId: input.transferId,
|
|
2750
2972
|
...this.wireTokenId(input.tokenId),
|
|
@@ -2757,7 +2979,7 @@ var History = class {
|
|
|
2757
2979
|
dedupKey: `RECEIVED:${input.tokenId.toLowerCase()}:${input.stateHash.toLowerCase()}`,
|
|
2758
2980
|
id: (this.deps.newId ?? randomUUID)(),
|
|
2759
2981
|
type: "RECEIVED",
|
|
2760
|
-
assets:
|
|
2982
|
+
assets: wireAssets(input.assets),
|
|
2761
2983
|
ts: this.ts(input.timestamp),
|
|
2762
2984
|
...input.transferId !== void 0 ? { transferId: input.transferId } : {},
|
|
2763
2985
|
...this.wireTokenId(input.tokenId),
|
|
@@ -2770,7 +2992,7 @@ var History = class {
|
|
|
2770
2992
|
dedupKey: `MINT:${input.tokenId.toLowerCase()}`,
|
|
2771
2993
|
id: (this.deps.newId ?? randomUUID)(),
|
|
2772
2994
|
type: "MINT",
|
|
2773
|
-
assets:
|
|
2995
|
+
assets: wireAssets(input.assets),
|
|
2774
2996
|
ts: this.ts(input.timestamp),
|
|
2775
2997
|
...this.wireTokenId(input.tokenId)
|
|
2776
2998
|
}));
|
|
@@ -2781,7 +3003,10 @@ var History = class {
|
|
|
2781
3003
|
try {
|
|
2782
3004
|
await this.deps.client.postHistory([record]);
|
|
2783
3005
|
} catch (err) {
|
|
2784
|
-
this.deps.log?.(
|
|
3006
|
+
this.deps.log?.(
|
|
3007
|
+
isPermanentReject(err) ? "history POST REJECTED \u2014 the record shape is refused; no retry can fix it" : "history POST failed (dedupKey makes retry safe)",
|
|
3008
|
+
err
|
|
3009
|
+
);
|
|
2785
3010
|
return;
|
|
2786
3011
|
}
|
|
2787
3012
|
this.deps.emit("history:updated", this.toEntry(record));
|
|
@@ -2848,6 +3073,13 @@ var CURSOR_KEY = STORE_KEYS.streamCursor("inventory");
|
|
|
2848
3073
|
function stateKey(tokenId, stateHash) {
|
|
2849
3074
|
return `${tokenId}:${stateHash}`;
|
|
2850
3075
|
}
|
|
3076
|
+
function unchanged(prev, item, tokenType) {
|
|
3077
|
+
return prev.seq === item.seq && prev.status === item.status && prev.stateHash === item.stateHash && tokenType === prev.tokenType;
|
|
3078
|
+
}
|
|
3079
|
+
function isCoinless(item, prev) {
|
|
3080
|
+
if (item.status !== "active") return prev?.coinless ?? false;
|
|
3081
|
+
return (item.assets?.length ?? 0) === 0;
|
|
3082
|
+
}
|
|
2851
3083
|
var InventoryView = class {
|
|
2852
3084
|
constructor(deps) {
|
|
2853
3085
|
this.deps = deps;
|
|
@@ -2941,14 +3173,26 @@ var InventoryView = class {
|
|
|
2941
3173
|
pool(coinId) {
|
|
2942
3174
|
const out = [];
|
|
2943
3175
|
for (const [tokenId, entry] of this.mirror) {
|
|
2944
|
-
if (
|
|
2945
|
-
if (this.inFlight.has(tokenId) && !this.pinned(tokenId)) continue;
|
|
2946
|
-
if (this.suspected.has(stateKey(tokenId, entry.stateHash))) continue;
|
|
3176
|
+
if (!this.isSpendable(tokenId, entry)) continue;
|
|
2947
3177
|
const asset = entry.assets.find((a) => a.coinId === coinId);
|
|
2948
3178
|
if (asset) out.push({ tokenId, amount: BigInt(asset.amount) });
|
|
2949
3179
|
}
|
|
2950
3180
|
return out;
|
|
2951
3181
|
}
|
|
3182
|
+
/**
|
|
3183
|
+
* The eligibility gates every spend shares, coin or token — ONE definition, so a
|
|
3184
|
+
* single probe covers both verbs and neither can drift from the other.
|
|
3185
|
+
*/
|
|
3186
|
+
isSpendable(tokenId, entry) {
|
|
3187
|
+
if (entry.status !== "active") return false;
|
|
3188
|
+
if (this.inFlight.has(tokenId) && !this.pinned(tokenId)) return false;
|
|
3189
|
+
return !this.suspected.has(stateKey(tokenId, entry.stateHash));
|
|
3190
|
+
}
|
|
3191
|
+
/** Is this NAMED token spendable whole — coinless or valued (#777)? */
|
|
3192
|
+
spendableToken(tokenId) {
|
|
3193
|
+
const entry = this.mirror.get(tokenId);
|
|
3194
|
+
return entry !== void 0 && this.isSpendable(tokenId, entry);
|
|
3195
|
+
}
|
|
2952
3196
|
/**
|
|
2953
3197
|
* Current mirrored state of an ACTIVE token, or undefined when it is unknown
|
|
2954
3198
|
* or tombstoned. Exists so a cached blob can be state-scoped (F6): a blob is
|
|
@@ -2974,6 +3218,29 @@ var InventoryView = class {
|
|
|
2974
3218
|
}
|
|
2975
3219
|
return out;
|
|
2976
3220
|
}
|
|
3221
|
+
/**
|
|
3222
|
+
* Coinless holdings (#777) — DISJOINT from tokens(): an entry is coinless
|
|
3223
|
+
* exactly when it is not, so no mirror row can appear in both reads.
|
|
3224
|
+
*/
|
|
3225
|
+
coinless(registry) {
|
|
3226
|
+
const out = [];
|
|
3227
|
+
for (const [tokenId, entry] of this.mirror) {
|
|
3228
|
+
if (entry.status !== "active" || !entry.coinless) continue;
|
|
3229
|
+
const meta = entry.tokenType !== void 0 ? registry.getTypeMeta?.(entry.tokenType) : null;
|
|
3230
|
+
out.push({
|
|
3231
|
+
tokenId,
|
|
3232
|
+
...entry.tokenType !== void 0 ? { tokenType: entry.tokenType } : {},
|
|
3233
|
+
...meta ? { name: meta.name } : {},
|
|
3234
|
+
...meta?.iconUrl != null ? { iconUrl: meta.iconUrl } : {},
|
|
3235
|
+
stateHash: entry.stateHash,
|
|
3236
|
+
transferring: this.held(tokenId),
|
|
3237
|
+
...this.suspected.has(stateKey(tokenId, entry.stateHash)) ? { suspectedSpent: true } : {},
|
|
3238
|
+
createdAt: entry.createdAt,
|
|
3239
|
+
updatedAt: entry.updatedAt
|
|
3240
|
+
});
|
|
3241
|
+
}
|
|
3242
|
+
return out;
|
|
3243
|
+
}
|
|
2977
3244
|
async assets(registry, price) {
|
|
2978
3245
|
const raw = aggregateAssets(this.activeEntries(), (tokenId) => this.held(tokenId), registry);
|
|
2979
3246
|
if (!price || raw.length === 0) return raw;
|
|
@@ -3045,14 +3312,19 @@ var InventoryView = class {
|
|
|
3045
3312
|
applyOne(item, now) {
|
|
3046
3313
|
const prev = this.mirror.get(item.tokenId);
|
|
3047
3314
|
if (prev && item.seq < prev.seq) return false;
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
}
|
|
3315
|
+
const tokenType = item.tokenType ?? prev?.tokenType;
|
|
3316
|
+
if (prev && unchanged(prev, item, tokenType)) return false;
|
|
3051
3317
|
this.mirror.set(item.tokenId, {
|
|
3052
3318
|
stateHash: item.stateHash,
|
|
3053
3319
|
seq: item.seq,
|
|
3054
3320
|
status: item.status,
|
|
3055
|
-
|
|
3321
|
+
// ONLY a tombstone inherits: it omits assets for an unrelated reason and
|
|
3322
|
+
// recoverRemoved must know the amount it restores. An ACTIVE row that omits
|
|
3323
|
+
// them is stating coinlessness, and inheriting there would keep stale assets
|
|
3324
|
+
// in tokens() while `coinless` is true — the row in BOTH reads (§16).
|
|
3325
|
+
assets: item.assets ?? (item.status === "removed" ? prev?.assets ?? [] : []),
|
|
3326
|
+
coinless: isCoinless(item, prev),
|
|
3327
|
+
...tokenType !== void 0 ? { tokenType } : {},
|
|
3056
3328
|
createdAt: prev?.createdAt ?? now,
|
|
3057
3329
|
updatedAt: now
|
|
3058
3330
|
});
|
|
@@ -3143,6 +3415,73 @@ var InventoryView = class {
|
|
|
3143
3415
|
}
|
|
3144
3416
|
};
|
|
3145
3417
|
|
|
3418
|
+
// token-engine/sdk.ts
|
|
3419
|
+
import { StateTransitionClient } from "@unicitylabs/state-transition-sdk/lib/StateTransitionClient.js";
|
|
3420
|
+
import { AggregatorClient } from "@unicitylabs/state-transition-sdk/lib/api/AggregatorClient.js";
|
|
3421
|
+
import { NetworkId } from "@unicitylabs/state-transition-sdk/lib/api/NetworkId.js";
|
|
3422
|
+
import { CertificationData } from "@unicitylabs/state-transition-sdk/lib/api/CertificationData.js";
|
|
3423
|
+
import { CertificationResponse, CertificationStatus } from "@unicitylabs/state-transition-sdk/lib/api/CertificationResponse.js";
|
|
3424
|
+
import { StateId } from "@unicitylabs/state-transition-sdk/lib/api/StateId.js";
|
|
3425
|
+
import { InclusionProof } from "@unicitylabs/state-transition-sdk/lib/api/InclusionProof.js";
|
|
3426
|
+
import { InclusionProofResponse } from "@unicitylabs/state-transition-sdk/lib/api/InclusionProofResponse.js";
|
|
3427
|
+
import { JsonRpcNetworkError } from "@unicitylabs/state-transition-sdk/lib/api/json-rpc/JsonRpcNetworkError.js";
|
|
3428
|
+
import { RootTrustBase } from "@unicitylabs/state-transition-sdk/lib/api/bft/RootTrustBase.js";
|
|
3429
|
+
import { waitInclusionProof } from "@unicitylabs/state-transition-sdk/lib/util/InclusionProofUtils.js";
|
|
3430
|
+
import { InclusionProofVerificationStatus } from "@unicitylabs/state-transition-sdk/lib/transaction/verification/rule/InclusionProofVerificationRule.js";
|
|
3431
|
+
import { Token } from "@unicitylabs/state-transition-sdk/lib/transaction/Token.js";
|
|
3432
|
+
import { MintTransaction } from "@unicitylabs/state-transition-sdk/lib/transaction/MintTransaction.js";
|
|
3433
|
+
import { TransferTransaction } from "@unicitylabs/state-transition-sdk/lib/transaction/TransferTransaction.js";
|
|
3434
|
+
import { CertifiedMintTransaction } from "@unicitylabs/state-transition-sdk/lib/transaction/CertifiedMintTransaction.js";
|
|
3435
|
+
import { CertifiedTransferTransaction } from "@unicitylabs/state-transition-sdk/lib/transaction/CertifiedTransferTransaction.js";
|
|
3436
|
+
import { TokenId } from "@unicitylabs/state-transition-sdk/lib/transaction/TokenId.js";
|
|
3437
|
+
import { TokenType } from "@unicitylabs/state-transition-sdk/lib/transaction/TokenType.js";
|
|
3438
|
+
import { TokenSalt } from "@unicitylabs/state-transition-sdk/lib/transaction/TokenSalt.js";
|
|
3439
|
+
import { StateMask } from "@unicitylabs/state-transition-sdk/lib/transaction/StateMask.js";
|
|
3440
|
+
import { MintJustificationVerifierService } from "@unicitylabs/state-transition-sdk/lib/transaction/verification/MintJustificationVerifierService.js";
|
|
3441
|
+
import { TokenIssuanceVerifierService } from "@unicitylabs/state-transition-sdk/lib/transaction/verification/TokenIssuanceVerifierService.js";
|
|
3442
|
+
import { VerificationContext } from "@unicitylabs/state-transition-sdk/lib/transaction/verification/VerificationContext.js";
|
|
3443
|
+
import { UnicityCertificateVerifier } from "@unicitylabs/state-transition-sdk/lib/api/bft/verification/UnicityCertificateVerifier.js";
|
|
3444
|
+
import { UnicitySealQuorumSignaturesVerificationRule } from "@unicitylabs/state-transition-sdk/lib/api/bft/verification/rule/UnicitySealQuorumSignaturesVerificationRule.js";
|
|
3445
|
+
import { VerifiedSealCache } from "@unicitylabs/state-transition-sdk/lib/api/bft/verification/VerifiedSealCache.js";
|
|
3446
|
+
import { Secp256k1SignatureVerifier } from "@unicitylabs/state-transition-sdk/lib/crypto/secp256k1/Secp256k1SignatureVerifier.js";
|
|
3447
|
+
import { WorkerTokenVerifier } from "@unicitylabs/state-transition-sdk/lib/transaction/verification/worker/WorkerTokenVerifier.js";
|
|
3448
|
+
import { EncodedPredicate } from "@unicitylabs/state-transition-sdk/lib/predicate/EncodedPredicate.js";
|
|
3449
|
+
import { SignaturePredicate } from "@unicitylabs/state-transition-sdk/lib/predicate/builtin/SignaturePredicate.js";
|
|
3450
|
+
import { SignaturePredicateUnlockScript } from "@unicitylabs/state-transition-sdk/lib/predicate/builtin/SignaturePredicateUnlockScript.js";
|
|
3451
|
+
import { BurnPredicate } from "@unicitylabs/state-transition-sdk/lib/predicate/builtin/BurnPredicate.js";
|
|
3452
|
+
import { PredicateVerifierService } from "@unicitylabs/state-transition-sdk/lib/predicate/verification/PredicateVerifierService.js";
|
|
3453
|
+
import { SigningService } from "@unicitylabs/state-transition-sdk/lib/crypto/secp256k1/SigningService.js";
|
|
3454
|
+
import { Signature } from "@unicitylabs/state-transition-sdk/lib/crypto/secp256k1/Signature.js";
|
|
3455
|
+
import { MintSigningService } from "@unicitylabs/state-transition-sdk/lib/crypto/MintSigningService.js";
|
|
3456
|
+
import { HashAlgorithm } from "@unicitylabs/state-transition-sdk/lib/crypto/hash/HashAlgorithm.js";
|
|
3457
|
+
import { DataHash } from "@unicitylabs/state-transition-sdk/lib/crypto/hash/DataHash.js";
|
|
3458
|
+
import { DataHasher } from "@unicitylabs/state-transition-sdk/lib/crypto/hash/DataHasher.js";
|
|
3459
|
+
import { DataHasherFactory } from "@unicitylabs/state-transition-sdk/lib/crypto/hash/DataHasherFactory.js";
|
|
3460
|
+
import { CborSerializer } from "@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborSerializer.js";
|
|
3461
|
+
import { CborDeserializer } from "@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborDeserializer.js";
|
|
3462
|
+
import { CborError } from "@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborError.js";
|
|
3463
|
+
import { CborReader } from "@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborReader.js";
|
|
3464
|
+
import { MajorType } from "@unicitylabs/state-transition-sdk/lib/serialization/cbor/MajorType.js";
|
|
3465
|
+
import { Asset } from "@unicitylabs/state-transition-sdk/lib/payment/asset/Asset.js";
|
|
3466
|
+
import { AssetId } from "@unicitylabs/state-transition-sdk/lib/payment/asset/AssetId.js";
|
|
3467
|
+
import { PaymentAssetCollection } from "@unicitylabs/state-transition-sdk/lib/payment/asset/PaymentAssetCollection.js";
|
|
3468
|
+
import { TokenSplit } from "@unicitylabs/state-transition-sdk/lib/payment/TokenSplit.js";
|
|
3469
|
+
import { SplitTokenRequest } from "@unicitylabs/state-transition-sdk/lib/payment/SplitTokenRequest.js";
|
|
3470
|
+
import { SplitToken } from "@unicitylabs/state-transition-sdk/lib/payment/SplitToken.js";
|
|
3471
|
+
import { SplitAllocationProof } from "@unicitylabs/state-transition-sdk/lib/payment/SplitAllocationProof.js";
|
|
3472
|
+
import { SplitMintJustification } from "@unicitylabs/state-transition-sdk/lib/payment/SplitMintJustification.js";
|
|
3473
|
+
import { SplitMintJustificationVerifier } from "@unicitylabs/state-transition-sdk/lib/payment/SplitMintJustificationVerifier.js";
|
|
3474
|
+
import { VerificationStatus } from "@unicitylabs/state-transition-sdk/lib/verification/VerificationStatus.js";
|
|
3475
|
+
import { VerificationResult } from "@unicitylabs/state-transition-sdk/lib/verification/VerificationResult.js";
|
|
3476
|
+
import { HexConverter } from "@unicitylabs/state-transition-sdk/lib/util/HexConverter.js";
|
|
3477
|
+
import { BigintConverter } from "@unicitylabs/state-transition-sdk/lib/util/BigintConverter.js";
|
|
3478
|
+
import { BitString } from "@unicitylabs/state-transition-sdk/lib/util/BitString.js";
|
|
3479
|
+
|
|
3480
|
+
// token-engine/value-envelope.ts
|
|
3481
|
+
function isCoinlessEnvelope(envelope) {
|
|
3482
|
+
return envelope.startsWith("none_");
|
|
3483
|
+
}
|
|
3484
|
+
|
|
3146
3485
|
// modules/payments-v2/receive/Receive.ts
|
|
3147
3486
|
var ACK_BATCH_SIZE = 200;
|
|
3148
3487
|
var REFRESH_INTERVAL_MS = 2500;
|
|
@@ -3283,7 +3622,12 @@ async function screen(deps, engine, entry) {
|
|
|
3283
3622
|
}
|
|
3284
3623
|
return {
|
|
3285
3624
|
kind: "accept",
|
|
3286
|
-
record: {
|
|
3625
|
+
record: {
|
|
3626
|
+
tokenId: keys.tokenId,
|
|
3627
|
+
stateHash: keys.stateHash,
|
|
3628
|
+
assets: toAssetAmounts(token),
|
|
3629
|
+
...isCoinlessEnvelope(token.valueEnvelope) ? { tokenType: token.tokenType } : {}
|
|
3630
|
+
}
|
|
3287
3631
|
};
|
|
3288
3632
|
}
|
|
3289
3633
|
async function announce(deps, entry, record) {
|
|
@@ -3294,6 +3638,7 @@ async function announce(deps, entry, record) {
|
|
|
3294
3638
|
tokenId: record.tokenId,
|
|
3295
3639
|
stateHash: record.stateHash,
|
|
3296
3640
|
assets: record.assets,
|
|
3641
|
+
...record.tokenType !== void 0 ? { tokenType: record.tokenType } : {},
|
|
3297
3642
|
...entry.senderPubkey !== void 0 ? { senderPubkey: entry.senderPubkey } : {},
|
|
3298
3643
|
...entry.senderNametag !== void 0 ? { senderNametag: entry.senderNametag } : {},
|
|
3299
3644
|
...entry.memo !== void 0 ? { memo: entry.memo } : {},
|
|
@@ -3307,6 +3652,19 @@ async function announce(deps, entry, record) {
|
|
|
3307
3652
|
senderPubkey: entry.senderPubkey ?? "",
|
|
3308
3653
|
...entry.senderNametag !== void 0 ? { senderNametag: entry.senderNametag } : {},
|
|
3309
3654
|
tokens: record.assets.map((asset) => toUiToken(record.tokenId, asset, deps.registry, receivedAt)),
|
|
3655
|
+
// #777: named here rather than mapped from assets, which announced an EMPTY list.
|
|
3656
|
+
...record.tokenType !== void 0 ? {
|
|
3657
|
+
coinless: [
|
|
3658
|
+
{
|
|
3659
|
+
tokenId: record.tokenId,
|
|
3660
|
+
tokenType: record.tokenType,
|
|
3661
|
+
stateHash: record.stateHash,
|
|
3662
|
+
transferring: false,
|
|
3663
|
+
createdAt: receivedAt,
|
|
3664
|
+
updatedAt: receivedAt
|
|
3665
|
+
}
|
|
3666
|
+
]
|
|
3667
|
+
} : {},
|
|
3310
3668
|
...entry.memo !== void 0 ? { memo: entry.memo } : {},
|
|
3311
3669
|
receivedAt
|
|
3312
3670
|
};
|
|
@@ -3972,6 +4330,34 @@ var SpendQueue = class {
|
|
|
3972
4330
|
}
|
|
3973
4331
|
return { kind: "queued", settled: this.enqueue(reservationId, request.coinId, amount) };
|
|
3974
4332
|
}
|
|
4333
|
+
/**
|
|
4334
|
+
* #777: reserve a NAMED coinless source. Never queues — there is nothing to wait
|
|
4335
|
+
* for. A coin plan waits because some OTHER combination may free up; a named
|
|
4336
|
+
* token is either free now or held by a reservation that only its own transfer
|
|
4337
|
+
* can release, so queueing would block until a timeout on a spend that cannot
|
|
4338
|
+
* become possible.
|
|
4339
|
+
*/
|
|
4340
|
+
planWhole(reservationId, tokenId) {
|
|
4341
|
+
if (this.destroyed) {
|
|
4342
|
+
throw new SphereError("Module has been destroyed", "MODULE_DESTROYED");
|
|
4343
|
+
}
|
|
4344
|
+
const unproven = this.deps.ledger.unprovenReason();
|
|
4345
|
+
if (unproven !== null) {
|
|
4346
|
+
throw new SphereError(`Cannot spend yet: ${unproven}`, "SEND_SYNC_PENDING");
|
|
4347
|
+
}
|
|
4348
|
+
if (this.deps.spendableToken?.(tokenId) !== true) {
|
|
4349
|
+
throw new SphereError(`Token ${tokenId} is not a spendable holding`, "VALIDATION_ERROR");
|
|
4350
|
+
}
|
|
4351
|
+
const holder = this.deps.ledger.holderOf(tokenId);
|
|
4352
|
+
if (holder !== void 0) {
|
|
4353
|
+
throw new SphereError(
|
|
4354
|
+
`Token ${tokenId} is already reserved by transfer ${holder}`,
|
|
4355
|
+
"VALIDATION_ERROR"
|
|
4356
|
+
);
|
|
4357
|
+
}
|
|
4358
|
+
this.deps.ledger.reserve(reservationId, [{ tokenId }]);
|
|
4359
|
+
return { reservationId, plan: { direct: [tokenId] } };
|
|
4360
|
+
}
|
|
3975
4361
|
/**
|
|
3976
4362
|
* The sources `plan()` WOULD pick right now, reserving nothing and queueing
|
|
3977
4363
|
* nothing — a read-only probe used to warm blobs while the user is still
|
|
@@ -4161,6 +4547,7 @@ function composeFacadeParts(deps, hooks) {
|
|
|
4161
4547
|
const queue = new SpendQueue({
|
|
4162
4548
|
ledger,
|
|
4163
4549
|
getPool: (coinId) => view.pool(coinId),
|
|
4550
|
+
spendableToken: (tokenId) => view.spendableToken(tokenId),
|
|
4164
4551
|
...deps.workBudget !== void 0 ? { workBudget: deps.workBudget } : {}
|
|
4165
4552
|
});
|
|
4166
4553
|
const historyStore = new History({
|
|
@@ -4241,13 +4628,12 @@ function buildMachineDeps(deps, hooks, historyStore) {
|
|
|
4241
4628
|
...deps.ownNametag !== void 0 ? { ownNametag: deps.ownNametag } : {},
|
|
4242
4629
|
emit: deps.emit,
|
|
4243
4630
|
now: deps.now ?? Date.now,
|
|
4244
|
-
recordHistory: async ({ transferId, payload, committedAmount }) => {
|
|
4631
|
+
recordHistory: async ({ transferId, payload, committedAmount, committedAssets }) => {
|
|
4245
4632
|
await historyStore.recordSent({
|
|
4246
4633
|
transferId,
|
|
4247
|
-
|
|
4248
|
-
//
|
|
4249
|
-
|
|
4250
|
-
amount: committedAmount,
|
|
4634
|
+
// §5.9: the SETTLED amount, never payload.amount — the plan. A token
|
|
4635
|
+
// spend moved no coin: `assets: []` + tokenId (wallet-api#151 / §10).
|
|
4636
|
+
...isWholeIntent(payload) ? { assets: committedAssets ?? [], tokenId: payload.direct[0] } : { assets: [{ coinId: payload.coinId, amount: committedAmount }] },
|
|
4251
4637
|
recipientPubkey: payload.recipient,
|
|
4252
4638
|
...payload.memo !== void 0 ? { memo: payload.memo } : {}
|
|
4253
4639
|
});
|
|
@@ -4282,12 +4668,10 @@ function buildReceive(deps, hooks, historyStore, heldStates, refreshView) {
|
|
|
4282
4668
|
});
|
|
4283
4669
|
}
|
|
4284
4670
|
async function recordReceived(historyStore, record) {
|
|
4285
|
-
const first = record.assets[0];
|
|
4286
4671
|
await historyStore.recordReceived({
|
|
4287
4672
|
tokenId: record.tokenId,
|
|
4288
4673
|
stateHash: record.stateHash,
|
|
4289
|
-
|
|
4290
|
-
amount: first?.amount ?? "0",
|
|
4674
|
+
assets: record.assets,
|
|
4291
4675
|
...record.senderPubkey !== void 0 ? { senderPubkey: record.senderPubkey } : {},
|
|
4292
4676
|
...record.senderNametag !== void 0 ? { senderNametag: record.senderNametag } : {},
|
|
4293
4677
|
...record.memo !== void 0 ? { memo: record.memo } : {},
|
|
@@ -4435,6 +4819,13 @@ var PaymentsFacade = class {
|
|
|
4435
4819
|
tokens(filter) {
|
|
4436
4820
|
return this.view.tokens(this.deps.registry, filter);
|
|
4437
4821
|
}
|
|
4822
|
+
coinless() {
|
|
4823
|
+
return this.view.coinless(this.deps.registry);
|
|
4824
|
+
}
|
|
4825
|
+
tokenData(tokenId) {
|
|
4826
|
+
const deps = { engine: this.engine(), view: this.view, storagePort: this.deps.storagePort };
|
|
4827
|
+
return readTokenData(deps, tokenId);
|
|
4828
|
+
}
|
|
4438
4829
|
history(page) {
|
|
4439
4830
|
return this.historyStore.page(page ?? {});
|
|
4440
4831
|
}
|
|
@@ -4457,7 +4848,14 @@ var PaymentsFacade = class {
|
|
|
4457
4848
|
return { queue: this.queue, view: this.view, storagePort: this.deps.storagePort, cache: this.prewarmed };
|
|
4458
4849
|
}
|
|
4459
4850
|
send(request) {
|
|
4460
|
-
return this.track(this.
|
|
4851
|
+
return this.track(this.runJob({ kind: "coin", request }, request.amount));
|
|
4852
|
+
}
|
|
4853
|
+
sendWholeToken(request) {
|
|
4854
|
+
return this.track(this.runJob({ kind: "whole", request, requireCoinless: false }, "0"));
|
|
4855
|
+
}
|
|
4856
|
+
/** NFT-scoped twin: refuses a valued source. */
|
|
4857
|
+
sendCoinless(request) {
|
|
4858
|
+
return this.track(this.runJob({ kind: "whole", request, requireCoinless: true }, "0"));
|
|
4461
4859
|
}
|
|
4462
4860
|
async receive() {
|
|
4463
4861
|
const transfers = await this.track(this.receiveLoop.drainOnce());
|
|
@@ -4489,12 +4887,25 @@ var PaymentsFacade = class {
|
|
|
4489
4887
|
nowMs() {
|
|
4490
4888
|
return (this.deps.now ?? Date.now)();
|
|
4491
4889
|
}
|
|
4890
|
+
mintDeps() {
|
|
4891
|
+
return {
|
|
4892
|
+
engine: this.engine(),
|
|
4893
|
+
mintJournal: this.machineStores.mintJournal,
|
|
4894
|
+
storagePort: this.deps.storagePort,
|
|
4895
|
+
recordMint: (input) => this.historyStore.recordMint(input),
|
|
4896
|
+
armHeartbeat: () => this.heartbeat.arm(),
|
|
4897
|
+
noteHeldState: (tokenId, stateHash) => void this.heldStates.set(tokenId, stateHash),
|
|
4898
|
+
refreshView: () => this.trackTail(this.view.delta()),
|
|
4899
|
+
ownPubkeyBytes: this.ownPubkeyBytes,
|
|
4900
|
+
now: () => this.nowMs()
|
|
4901
|
+
};
|
|
4902
|
+
}
|
|
4492
4903
|
// ── send policy (§5.5 + old-loop parity; the machine stays policy-free) ────
|
|
4493
4904
|
/** The ONE place a send() outcome is shaped: success emits in finishSend, a
|
|
4494
4905
|
* CLEAN rejection emits `transfer:updated{status:'failed'}` here (§4). */
|
|
4495
|
-
async
|
|
4906
|
+
async runJob(job, amount) {
|
|
4496
4907
|
const run = {
|
|
4497
|
-
amount
|
|
4908
|
+
amount,
|
|
4498
4909
|
delivered: [],
|
|
4499
4910
|
sentTokens: [],
|
|
4500
4911
|
tokenTransfers: [],
|
|
@@ -4504,7 +4915,7 @@ var PaymentsFacade = class {
|
|
|
4504
4915
|
lastTransferId: ""
|
|
4505
4916
|
};
|
|
4506
4917
|
try {
|
|
4507
|
-
return await this.sendWithPolicy(
|
|
4918
|
+
return await this.sendWithPolicy(job, run);
|
|
4508
4919
|
} catch (err) {
|
|
4509
4920
|
this.emitCleanFailure(err, run.lastTransferId);
|
|
4510
4921
|
throw err;
|
|
@@ -4524,19 +4935,19 @@ var PaymentsFacade = class {
|
|
|
4524
4935
|
};
|
|
4525
4936
|
this.deps.emit("transfer:updated", failed);
|
|
4526
4937
|
}
|
|
4527
|
-
async sendWithPolicy(
|
|
4528
|
-
const recipient = await requireSameNetworkRecipient(this.deps, request.recipient);
|
|
4938
|
+
async sendWithPolicy(job, run) {
|
|
4939
|
+
const recipient = await requireSameNetworkRecipient(this.deps, job.request.recipient);
|
|
4529
4940
|
for (let attempt = 0; ; attempt++) {
|
|
4530
4941
|
let ctx;
|
|
4531
4942
|
try {
|
|
4532
|
-
ctx = await this.planAndMaterialize(recipient.chainPubkey,
|
|
4943
|
+
ctx = await this.planAndMaterialize(recipient.chainPubkey, job, run);
|
|
4533
4944
|
} catch (err) {
|
|
4534
|
-
throw
|
|
4945
|
+
throw partialize(err, run);
|
|
4535
4946
|
}
|
|
4536
4947
|
const disposition = await this.runAttempt(ctx);
|
|
4537
|
-
if (disposition.kind === "rethrow") throw
|
|
4948
|
+
if (disposition.kind === "rethrow") throw partialize(disposition.error, run);
|
|
4538
4949
|
if (disposition.kind === "retry-full") {
|
|
4539
|
-
if (attempt >= MAX_RESELECT) throw
|
|
4950
|
+
if (job.kind === "whole" || attempt >= MAX_RESELECT) throw partialize(disposition.error, run);
|
|
4540
4951
|
continue;
|
|
4541
4952
|
}
|
|
4542
4953
|
if (disposition.kind === "success") {
|
|
@@ -4592,7 +5003,7 @@ var PaymentsFacade = class {
|
|
|
4592
5003
|
async disposeFailedAttempt(ctx, err) {
|
|
4593
5004
|
if (isPossiblyCommittedSendOutcome(err)) {
|
|
4594
5005
|
this.settleKeepOpen(ctx);
|
|
4595
|
-
|
|
5006
|
+
stampTransferId(err, ctx.transferId);
|
|
4596
5007
|
return { kind: "rethrow", error: err };
|
|
4597
5008
|
}
|
|
4598
5009
|
const backstop = await this.machineStores.backstop.getByKey(ctx.transferId);
|
|
@@ -4626,7 +5037,7 @@ var PaymentsFacade = class {
|
|
|
4626
5037
|
async disposeConvergeFailure(ctx, err) {
|
|
4627
5038
|
if (isPossiblyCommittedSendOutcome(err)) {
|
|
4628
5039
|
this.settleKeepOpen(ctx);
|
|
4629
|
-
|
|
5040
|
+
stampTransferId(err, ctx.transferId);
|
|
4630
5041
|
return { kind: "rethrow", error: err };
|
|
4631
5042
|
}
|
|
4632
5043
|
if (classifyError(err) === "conflict") {
|
|
@@ -4637,9 +5048,29 @@ var PaymentsFacade = class {
|
|
|
4637
5048
|
}
|
|
4638
5049
|
return { kind: "rethrow", error: err };
|
|
4639
5050
|
}
|
|
4640
|
-
async planAndMaterialize(recipientPubkey,
|
|
5051
|
+
async planAndMaterialize(recipientPubkey, job, run) {
|
|
4641
5052
|
const transferId = this.newId();
|
|
4642
5053
|
run.lastTransferId = transferId;
|
|
5054
|
+
if (job.kind === "whole") {
|
|
5055
|
+
const spend2 = this.queue.planWhole(transferId, job.request.tokenId);
|
|
5056
|
+
const sourceIds2 = this.markPlanned(transferId, "", spend2);
|
|
5057
|
+
try {
|
|
5058
|
+
return await materializeWholeSpend(
|
|
5059
|
+
{ engine: this.engine(), storagePort: this.deps.storagePort, registry: this.deps.registry, now: this.nowMs() },
|
|
5060
|
+
{
|
|
5061
|
+
transferId,
|
|
5062
|
+
recipientPubkey,
|
|
5063
|
+
request: job.request,
|
|
5064
|
+
sourceIds: sourceIds2,
|
|
5065
|
+
requireCoinless: job.requireCoinless
|
|
5066
|
+
}
|
|
5067
|
+
);
|
|
5068
|
+
} catch (err) {
|
|
5069
|
+
this.settleFailure(transferId, "", sourceIds2);
|
|
5070
|
+
throw err;
|
|
5071
|
+
}
|
|
5072
|
+
}
|
|
5073
|
+
const request = { ...job.request, amount: run.amount };
|
|
4643
5074
|
const planned = this.queue.plan(transferId, { coinId: request.coinId, amount: request.amount });
|
|
4644
5075
|
const spend = planned.kind === "planned" ? planned.spend : await planned.settled;
|
|
4645
5076
|
const sourceIds = this.markPlanned(transferId, request.coinId, spend);
|
|
@@ -4773,22 +5204,6 @@ var PaymentsFacade = class {
|
|
|
4773
5204
|
return result;
|
|
4774
5205
|
}
|
|
4775
5206
|
/** Nothing delivered → UNWRAPPED; after ≥1 delivered leg every failure surfaces as PartialSendConflictError over the settled set. */
|
|
4776
|
-
partialize(err, run) {
|
|
4777
|
-
if (run.delivered.length === 0) return err;
|
|
4778
|
-
return new PartialSendConflictError(
|
|
4779
|
-
"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.",
|
|
4780
|
-
run.firstPartialId ?? "",
|
|
4781
|
-
run.delivered,
|
|
4782
|
-
run.amount,
|
|
4783
|
-
err
|
|
4784
|
-
);
|
|
4785
|
-
}
|
|
4786
|
-
/** #441: possibly-committed errors must carry the transferId for the settling journal. */
|
|
4787
|
-
stampTransferId(err, transferId) {
|
|
4788
|
-
if (err instanceof SphereError && isPossiblyCommittedSendOutcome(err) && err.transferId === void 0) {
|
|
4789
|
-
err.transferId = transferId;
|
|
4790
|
-
}
|
|
4791
|
-
}
|
|
4792
5207
|
async softAbort(transferId) {
|
|
4793
5208
|
try {
|
|
4794
5209
|
await this.deps.client.abortIntent(transferId);
|
|
@@ -4804,57 +5219,11 @@ var PaymentsFacade = class {
|
|
|
4804
5219
|
const mintId = this.newId();
|
|
4805
5220
|
this.activeMoneyOps.add(mintId);
|
|
4806
5221
|
try {
|
|
4807
|
-
return await this.
|
|
5222
|
+
return await runMintUnderJournal(this.mintDeps(), { mintId, coinId, amount });
|
|
4808
5223
|
} finally {
|
|
4809
5224
|
this.activeMoneyOps.delete(mintId);
|
|
4810
5225
|
}
|
|
4811
5226
|
}
|
|
4812
|
-
async mintUnderJournal(mintId, coinId, amount) {
|
|
4813
|
-
const engine = this.engine();
|
|
4814
|
-
const entry = {
|
|
4815
|
-
mintId,
|
|
4816
|
-
coinId,
|
|
4817
|
-
amount: amount.toString(),
|
|
4818
|
-
tokenId: "",
|
|
4819
|
-
createdAt: this.nowMs()
|
|
4820
|
-
};
|
|
4821
|
-
await this.machineStores.mintJournal.upsert(entry);
|
|
4822
|
-
let token;
|
|
4823
|
-
try {
|
|
4824
|
-
token = await engine.mint(mintParams(this.ownPubkeyBytes, coinId, amount), { transferId: mintId });
|
|
4825
|
-
} catch (err) {
|
|
4826
|
-
this.heartbeat.arm();
|
|
4827
|
-
return { success: false, error: messageOf(err) };
|
|
4828
|
-
}
|
|
4829
|
-
if (token.blob.tokenId !== entry.tokenId) {
|
|
4830
|
-
await this.machineStores.mintJournal.upsert({ ...entry, tokenId: token.blob.tokenId });
|
|
4831
|
-
}
|
|
4832
|
-
try {
|
|
4833
|
-
await this.finalizeMint(engine, mintId, token, coinId, amount.toString());
|
|
4834
|
-
} catch (err) {
|
|
4835
|
-
this.heartbeat.arm();
|
|
4836
|
-
return { success: false, tokenId: token.blob.tokenId, error: messageOf(err) };
|
|
4837
|
-
}
|
|
4838
|
-
return { success: true, tokenId: token.blob.tokenId };
|
|
4839
|
-
}
|
|
4840
|
-
async finalizeMint(engine, mintId, token, coinId, amount) {
|
|
4841
|
-
const bytes = token.blob.token;
|
|
4842
|
-
const digest = bytesToHex(sha256(bytes));
|
|
4843
|
-
const keys = await this.deps.storagePort.uploadBlobs([{ sha256: digest, bytes }]);
|
|
4844
|
-
const key = keys.get(digest);
|
|
4845
|
-
if (key === void 0) {
|
|
4846
|
-
throw new SphereError(`no upload key returned for mint blob ${digest}`, "STORAGE_ERROR");
|
|
4847
|
-
}
|
|
4848
|
-
await this.deps.storagePort.applyDelta({
|
|
4849
|
-
transferId: mintId,
|
|
4850
|
-
spent: [],
|
|
4851
|
-
added: [{ tokenId: token.blob.tokenId, key }]
|
|
4852
|
-
});
|
|
4853
|
-
await this.historyStore.recordMint({ tokenId: token.blob.tokenId, coinId, amount });
|
|
4854
|
-
await this.machineStores.mintJournal.removeByKey(mintId);
|
|
4855
|
-
this.heldStates.set(token.blob.tokenId, (await engine.deliveryKeys(bytes)).stateHash);
|
|
4856
|
-
this.trackTail(this.view.delta());
|
|
4857
|
-
}
|
|
4858
5227
|
/** @returns how many journal entries were RESOLVED (cleared) — heartbeat progress. */
|
|
4859
5228
|
async replayMints() {
|
|
4860
5229
|
let resolved = 0;
|
|
@@ -4886,7 +5255,7 @@ var PaymentsFacade = class {
|
|
|
4886
5255
|
if (token.blob.tokenId !== entry.tokenId) {
|
|
4887
5256
|
await this.machineStores.mintJournal.upsert({ ...entry, tokenId: token.blob.tokenId });
|
|
4888
5257
|
}
|
|
4889
|
-
await this.
|
|
5258
|
+
await finalizeMint(this.mintDeps(), entry.mintId, token, entry.coinId, entry.amount);
|
|
4890
5259
|
return true;
|
|
4891
5260
|
}
|
|
4892
5261
|
async tokenInServerInventory(tokenId) {
|