@unicitylabs/sphere-sdk 0.16.0 → 0.17.0
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 +566 -171
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +67 -0
- package/dist/core/index.d.ts +67 -0
- package/dist/core/index.js +566 -171
- 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 +566 -171
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +61 -1
- package/dist/index.d.ts +61 -1
- package/dist/index.js +566 -171
- package/dist/index.js.map +1 -1
- package/dist/modules/payments-v2/index.cjs +609 -269
- package/dist/modules/payments-v2/index.cjs.map +1 -1
- package/dist/modules/payments-v2/index.d.cts +88 -13
- package/dist/modules/payments-v2/index.d.ts +88 -13
- package/dist/modules/payments-v2/index.js +609 -269
- 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,73 @@ function createMachineStores(kv) {
|
|
|
834
832
|
};
|
|
835
833
|
}
|
|
836
834
|
|
|
835
|
+
// token-engine/sdk.ts
|
|
836
|
+
var import_StateTransitionClient = require("@unicitylabs/state-transition-sdk/lib/StateTransitionClient.js");
|
|
837
|
+
var import_AggregatorClient = require("@unicitylabs/state-transition-sdk/lib/api/AggregatorClient.js");
|
|
838
|
+
var import_NetworkId = require("@unicitylabs/state-transition-sdk/lib/api/NetworkId.js");
|
|
839
|
+
var import_CertificationData = require("@unicitylabs/state-transition-sdk/lib/api/CertificationData.js");
|
|
840
|
+
var import_CertificationResponse = require("@unicitylabs/state-transition-sdk/lib/api/CertificationResponse.js");
|
|
841
|
+
var import_StateId = require("@unicitylabs/state-transition-sdk/lib/api/StateId.js");
|
|
842
|
+
var import_InclusionProof = require("@unicitylabs/state-transition-sdk/lib/api/InclusionProof.js");
|
|
843
|
+
var import_InclusionProofResponse = require("@unicitylabs/state-transition-sdk/lib/api/InclusionProofResponse.js");
|
|
844
|
+
var import_JsonRpcNetworkError = require("@unicitylabs/state-transition-sdk/lib/api/json-rpc/JsonRpcNetworkError.js");
|
|
845
|
+
var import_RootTrustBase = require("@unicitylabs/state-transition-sdk/lib/api/bft/RootTrustBase.js");
|
|
846
|
+
var import_InclusionProofUtils = require("@unicitylabs/state-transition-sdk/lib/util/InclusionProofUtils.js");
|
|
847
|
+
var import_InclusionProofVerificationRule = require("@unicitylabs/state-transition-sdk/lib/transaction/verification/rule/InclusionProofVerificationRule.js");
|
|
848
|
+
var import_Token = require("@unicitylabs/state-transition-sdk/lib/transaction/Token.js");
|
|
849
|
+
var import_MintTransaction = require("@unicitylabs/state-transition-sdk/lib/transaction/MintTransaction.js");
|
|
850
|
+
var import_TransferTransaction = require("@unicitylabs/state-transition-sdk/lib/transaction/TransferTransaction.js");
|
|
851
|
+
var import_CertifiedMintTransaction = require("@unicitylabs/state-transition-sdk/lib/transaction/CertifiedMintTransaction.js");
|
|
852
|
+
var import_CertifiedTransferTransaction = require("@unicitylabs/state-transition-sdk/lib/transaction/CertifiedTransferTransaction.js");
|
|
853
|
+
var import_TokenId = require("@unicitylabs/state-transition-sdk/lib/transaction/TokenId.js");
|
|
854
|
+
var import_TokenType = require("@unicitylabs/state-transition-sdk/lib/transaction/TokenType.js");
|
|
855
|
+
var import_TokenSalt = require("@unicitylabs/state-transition-sdk/lib/transaction/TokenSalt.js");
|
|
856
|
+
var import_StateMask = require("@unicitylabs/state-transition-sdk/lib/transaction/StateMask.js");
|
|
857
|
+
var import_MintJustificationVerifierService = require("@unicitylabs/state-transition-sdk/lib/transaction/verification/MintJustificationVerifierService.js");
|
|
858
|
+
var import_TokenIssuanceVerifierService = require("@unicitylabs/state-transition-sdk/lib/transaction/verification/TokenIssuanceVerifierService.js");
|
|
859
|
+
var import_VerificationContext = require("@unicitylabs/state-transition-sdk/lib/transaction/verification/VerificationContext.js");
|
|
860
|
+
var import_UnicityCertificateVerifier = require("@unicitylabs/state-transition-sdk/lib/api/bft/verification/UnicityCertificateVerifier.js");
|
|
861
|
+
var import_UnicitySealQuorumSignaturesVerificationRule = require("@unicitylabs/state-transition-sdk/lib/api/bft/verification/rule/UnicitySealQuorumSignaturesVerificationRule.js");
|
|
862
|
+
var import_VerifiedSealCache = require("@unicitylabs/state-transition-sdk/lib/api/bft/verification/VerifiedSealCache.js");
|
|
863
|
+
var import_Secp256k1SignatureVerifier = require("@unicitylabs/state-transition-sdk/lib/crypto/secp256k1/Secp256k1SignatureVerifier.js");
|
|
864
|
+
var import_WorkerTokenVerifier = require("@unicitylabs/state-transition-sdk/lib/transaction/verification/worker/WorkerTokenVerifier.js");
|
|
865
|
+
var import_EncodedPredicate = require("@unicitylabs/state-transition-sdk/lib/predicate/EncodedPredicate.js");
|
|
866
|
+
var import_SignaturePredicate = require("@unicitylabs/state-transition-sdk/lib/predicate/builtin/SignaturePredicate.js");
|
|
867
|
+
var import_SignaturePredicateUnlockScript = require("@unicitylabs/state-transition-sdk/lib/predicate/builtin/SignaturePredicateUnlockScript.js");
|
|
868
|
+
var import_BurnPredicate = require("@unicitylabs/state-transition-sdk/lib/predicate/builtin/BurnPredicate.js");
|
|
869
|
+
var import_PredicateVerifierService = require("@unicitylabs/state-transition-sdk/lib/predicate/verification/PredicateVerifierService.js");
|
|
870
|
+
var import_SigningService = require("@unicitylabs/state-transition-sdk/lib/crypto/secp256k1/SigningService.js");
|
|
871
|
+
var import_Signature = require("@unicitylabs/state-transition-sdk/lib/crypto/secp256k1/Signature.js");
|
|
872
|
+
var import_MintSigningService = require("@unicitylabs/state-transition-sdk/lib/crypto/MintSigningService.js");
|
|
873
|
+
var import_HashAlgorithm = require("@unicitylabs/state-transition-sdk/lib/crypto/hash/HashAlgorithm.js");
|
|
874
|
+
var import_DataHash = require("@unicitylabs/state-transition-sdk/lib/crypto/hash/DataHash.js");
|
|
875
|
+
var import_DataHasher = require("@unicitylabs/state-transition-sdk/lib/crypto/hash/DataHasher.js");
|
|
876
|
+
var import_DataHasherFactory = require("@unicitylabs/state-transition-sdk/lib/crypto/hash/DataHasherFactory.js");
|
|
877
|
+
var import_CborSerializer = require("@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborSerializer.js");
|
|
878
|
+
var import_CborDeserializer = require("@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborDeserializer.js");
|
|
879
|
+
var import_CborError = require("@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborError.js");
|
|
880
|
+
var import_CborReader = require("@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborReader.js");
|
|
881
|
+
var import_MajorType = require("@unicitylabs/state-transition-sdk/lib/serialization/cbor/MajorType.js");
|
|
882
|
+
var import_Asset = require("@unicitylabs/state-transition-sdk/lib/payment/asset/Asset.js");
|
|
883
|
+
var import_AssetId = require("@unicitylabs/state-transition-sdk/lib/payment/asset/AssetId.js");
|
|
884
|
+
var import_PaymentAssetCollection = require("@unicitylabs/state-transition-sdk/lib/payment/asset/PaymentAssetCollection.js");
|
|
885
|
+
var import_TokenSplit = require("@unicitylabs/state-transition-sdk/lib/payment/TokenSplit.js");
|
|
886
|
+
var import_SplitTokenRequest = require("@unicitylabs/state-transition-sdk/lib/payment/SplitTokenRequest.js");
|
|
887
|
+
var import_SplitToken = require("@unicitylabs/state-transition-sdk/lib/payment/SplitToken.js");
|
|
888
|
+
var import_SplitAllocationProof = require("@unicitylabs/state-transition-sdk/lib/payment/SplitAllocationProof.js");
|
|
889
|
+
var import_SplitMintJustification = require("@unicitylabs/state-transition-sdk/lib/payment/SplitMintJustification.js");
|
|
890
|
+
var import_SplitMintJustificationVerifier = require("@unicitylabs/state-transition-sdk/lib/payment/SplitMintJustificationVerifier.js");
|
|
891
|
+
var import_VerificationStatus = require("@unicitylabs/state-transition-sdk/lib/verification/VerificationStatus.js");
|
|
892
|
+
var import_VerificationResult = require("@unicitylabs/state-transition-sdk/lib/verification/VerificationResult.js");
|
|
893
|
+
var import_HexConverter = require("@unicitylabs/state-transition-sdk/lib/util/HexConverter.js");
|
|
894
|
+
var import_BigintConverter = require("@unicitylabs/state-transition-sdk/lib/util/BigintConverter.js");
|
|
895
|
+
var import_BitString = require("@unicitylabs/state-transition-sdk/lib/util/BitString.js");
|
|
896
|
+
|
|
897
|
+
// token-engine/value-envelope.ts
|
|
898
|
+
function isCoinlessEnvelope(envelope) {
|
|
899
|
+
return envelope.startsWith("none_");
|
|
900
|
+
}
|
|
901
|
+
|
|
837
902
|
// token-engine/errors.ts
|
|
838
903
|
var TransferConflictError = class extends SphereError {
|
|
839
904
|
/**
|
|
@@ -873,6 +938,14 @@ var CheckpointTrustbaseMismatchError = class extends SphereError {
|
|
|
873
938
|
}
|
|
874
939
|
};
|
|
875
940
|
|
|
941
|
+
// modules/payments-v2/machine/payload-view.ts
|
|
942
|
+
function splitOf(p) {
|
|
943
|
+
return p.kind === "coin" ? p.split : void 0;
|
|
944
|
+
}
|
|
945
|
+
function coinIdOf(p) {
|
|
946
|
+
return p.kind === "coin" ? p.coinId : void 0;
|
|
947
|
+
}
|
|
948
|
+
|
|
876
949
|
// modules/payments-v2/machine/TransferMachine.ts
|
|
877
950
|
var CERTIFY_WIDTH = 20;
|
|
878
951
|
var KEEP_OPEN_CODES = /* @__PURE__ */ new Set([
|
|
@@ -1075,13 +1148,14 @@ var TransferMachine = class {
|
|
|
1075
1148
|
const finished = await engine.transfer({ token: source, recipientPubkey }, opts);
|
|
1076
1149
|
return { recipientBlob: finished.blob.token };
|
|
1077
1150
|
}
|
|
1078
|
-
const split = ctx.payload
|
|
1151
|
+
const split = splitOf(ctx.payload);
|
|
1152
|
+
const splitCoinId = coinIdOf(ctx.payload);
|
|
1079
1153
|
const { outputs } = await engine.split(
|
|
1080
1154
|
{
|
|
1081
1155
|
token: source,
|
|
1082
1156
|
outputs: [
|
|
1083
|
-
{ recipientPubkey, coinId:
|
|
1084
|
-
{ recipientPubkey: this.deps.ownPubkey, coinId:
|
|
1157
|
+
{ recipientPubkey, coinId: splitCoinId, amount: BigInt(split.splitAmount) },
|
|
1158
|
+
{ recipientPubkey: this.deps.ownPubkey, coinId: splitCoinId, amount: BigInt(split.remainderAmount) }
|
|
1085
1159
|
]
|
|
1086
1160
|
},
|
|
1087
1161
|
opts
|
|
@@ -1203,7 +1277,9 @@ var TransferMachine = class {
|
|
|
1203
1277
|
const entry = {
|
|
1204
1278
|
transferId: r.transferId,
|
|
1205
1279
|
remainingAmount: undelivered.toString(),
|
|
1206
|
-
|
|
1280
|
+
// '' only on a token-addressed intent, which is one leg: it either lands or
|
|
1281
|
+
// conflicts, so a shortfall row is unreachable there (settlePartial needs >1).
|
|
1282
|
+
coinId: coinIdOf(r.payload) ?? "",
|
|
1207
1283
|
recipient: r.payload.recipient,
|
|
1208
1284
|
committedTokenIds: committed.map((o) => o.op.sourceTokenId),
|
|
1209
1285
|
createdAt: this.deps.now()
|
|
@@ -1229,7 +1305,8 @@ var TransferMachine = class {
|
|
|
1229
1305
|
}
|
|
1230
1306
|
async finishBookkeeping(engine, transferId, payload, phase, committed) {
|
|
1231
1307
|
try {
|
|
1232
|
-
const
|
|
1308
|
+
const coinId = coinIdOf(payload);
|
|
1309
|
+
const committedAmount = coinId === void 0 ? "0" : await this.settledAmount(engine, coinId, committed);
|
|
1233
1310
|
await this.deps.recordHistory?.({ transferId, payload, phase, committedAmount });
|
|
1234
1311
|
} catch {
|
|
1235
1312
|
}
|
|
@@ -1272,8 +1349,11 @@ var TransferMachine = class {
|
|
|
1272
1349
|
}
|
|
1273
1350
|
};
|
|
1274
1351
|
function conflictAmount(engine, payload, op, source) {
|
|
1275
|
-
|
|
1276
|
-
|
|
1352
|
+
const split = splitOf(payload);
|
|
1353
|
+
if (op.kind === "split") return BigInt(split.splitAmount);
|
|
1354
|
+
const coinId = coinIdOf(payload);
|
|
1355
|
+
if (coinId === void 0) return 0n;
|
|
1356
|
+
return source !== void 0 ? engine.balanceOf(source, coinId) : 0n;
|
|
1277
1357
|
}
|
|
1278
1358
|
|
|
1279
1359
|
// modules/payments-v2/machine/resume.ts
|
|
@@ -1380,6 +1460,14 @@ async function runOne(ctx, job, report) {
|
|
|
1380
1460
|
report.failed.push(job.transferId);
|
|
1381
1461
|
return;
|
|
1382
1462
|
}
|
|
1463
|
+
if (job.payload.kind === "coinless" && !isCoinlessEnvelope(token.valueEnvelope)) {
|
|
1464
|
+
logger.warn(
|
|
1465
|
+
"PaymentsV2",
|
|
1466
|
+
`resume: token intent ${job.transferId} names a source carrying coin value \u2014 refusing`
|
|
1467
|
+
);
|
|
1468
|
+
report.failed.push(job.transferId);
|
|
1469
|
+
return;
|
|
1470
|
+
}
|
|
1383
1471
|
mine.set(op.sourceTokenId, token);
|
|
1384
1472
|
}
|
|
1385
1473
|
try {
|
|
@@ -1410,6 +1498,28 @@ async function classifyResumeFailure(deps, stores, transferId, err, report) {
|
|
|
1410
1498
|
}
|
|
1411
1499
|
report.failed.push(transferId);
|
|
1412
1500
|
}
|
|
1501
|
+
function validateCoinPayload(c) {
|
|
1502
|
+
if (typeof c.coinId !== "string" || typeof c.amount !== "string") {
|
|
1503
|
+
throw new SphereError("intent payload is missing coinId/amount", "VALIDATION_ERROR");
|
|
1504
|
+
}
|
|
1505
|
+
const s = c.split;
|
|
1506
|
+
if (s !== void 0 && (typeof s.tokenId !== "string" || typeof s.splitAmount !== "string" || typeof s.remainderAmount !== "string")) {
|
|
1507
|
+
throw new SphereError("intent payload split spec is malformed", "VALIDATION_ERROR");
|
|
1508
|
+
}
|
|
1509
|
+
return { ...c, kind: "coin" };
|
|
1510
|
+
}
|
|
1511
|
+
function validateCoinlessPayload(p) {
|
|
1512
|
+
if (p.direct?.length !== 1 || typeof p.direct[0] !== "string" || p.direct[0] === "") {
|
|
1513
|
+
throw new SphereError(
|
|
1514
|
+
"token intent payload must name exactly one source token",
|
|
1515
|
+
"VALIDATION_ERROR"
|
|
1516
|
+
);
|
|
1517
|
+
}
|
|
1518
|
+
if (p.split !== void 0) {
|
|
1519
|
+
throw new SphereError("token intent payload cannot carry a split", "VALIDATION_ERROR");
|
|
1520
|
+
}
|
|
1521
|
+
return { ...p, kind: "coinless", direct: [p.direct[0]] };
|
|
1522
|
+
}
|
|
1413
1523
|
function validatePayload(raw) {
|
|
1414
1524
|
const p = raw !== null && typeof raw === "object" ? raw : null;
|
|
1415
1525
|
if (p === null || p.v !== 2 || !Array.isArray(p.direct) || p.direct.some((t) => typeof t !== "string")) {
|
|
@@ -1418,14 +1528,17 @@ function validatePayload(raw) {
|
|
|
1418
1528
|
"VALIDATION_ERROR"
|
|
1419
1529
|
);
|
|
1420
1530
|
}
|
|
1421
|
-
if (typeof p.recipient !== "string"
|
|
1422
|
-
throw new SphereError("intent payload is missing recipient
|
|
1531
|
+
if (typeof p.recipient !== "string") {
|
|
1532
|
+
throw new SphereError("intent payload is missing recipient", "VALIDATION_ERROR");
|
|
1423
1533
|
}
|
|
1424
|
-
const
|
|
1425
|
-
if (
|
|
1426
|
-
throw new SphereError(
|
|
1534
|
+
const kind = p.kind ?? "coin";
|
|
1535
|
+
if (kind !== "coin" && kind !== "coinless") {
|
|
1536
|
+
throw new SphereError(
|
|
1537
|
+
`unsupported intent kind '${String(kind)}' \u2014 not resumable by this client`,
|
|
1538
|
+
"VALIDATION_ERROR"
|
|
1539
|
+
);
|
|
1427
1540
|
}
|
|
1428
|
-
return p;
|
|
1541
|
+
return kind === "coinless" ? validateCoinlessPayload(p) : validateCoinPayload(p);
|
|
1429
1542
|
}
|
|
1430
1543
|
|
|
1431
1544
|
// modules/payments-v2/convergence.ts
|
|
@@ -1631,13 +1744,23 @@ async function openRow(entry, journal, decryptPayload) {
|
|
|
1631
1744
|
transferId: entry.transferId,
|
|
1632
1745
|
kind: "open",
|
|
1633
1746
|
recipient: typeof payload?.recipient === "string" ? payload.recipient : "",
|
|
1634
|
-
|
|
1635
|
-
amount: typeof payload?.amount === "string" ? payload.amount : "",
|
|
1747
|
+
...subject(payload),
|
|
1636
1748
|
legs: { certified, total: Math.max(total, certified) },
|
|
1637
1749
|
deliveryPending: journal.length > 0,
|
|
1638
1750
|
createdAt: entry.createdAt
|
|
1639
1751
|
};
|
|
1640
1752
|
}
|
|
1753
|
+
function subject(payload) {
|
|
1754
|
+
if (payload?.kind === "coinless") {
|
|
1755
|
+
const tokenId = payload.direct?.[0];
|
|
1756
|
+
return { coinId: "", amount: "", ...typeof tokenId === "string" ? { tokenId } : {} };
|
|
1757
|
+
}
|
|
1758
|
+
const coin = payload;
|
|
1759
|
+
return {
|
|
1760
|
+
coinId: typeof coin?.coinId === "string" ? coin.coinId : "",
|
|
1761
|
+
amount: typeof coin?.amount === "string" ? coin.amount : ""
|
|
1762
|
+
};
|
|
1763
|
+
}
|
|
1641
1764
|
function journalOnlyRow(transferId, entries) {
|
|
1642
1765
|
const certified = distinctOpCount(entries);
|
|
1643
1766
|
return {
|
|
@@ -1667,6 +1790,160 @@ function distinctOpCount(entries) {
|
|
|
1667
1790
|
return new Set(entries.map((entry) => entry.opIndex)).size;
|
|
1668
1791
|
}
|
|
1669
1792
|
|
|
1793
|
+
// modules/payments-v2/inventory/token-data.ts
|
|
1794
|
+
async function readTokenData(deps, tokenId) {
|
|
1795
|
+
if (deps.view.stateHashOf(tokenId) === void 0) {
|
|
1796
|
+
throw new SphereError(`Token ${tokenId} is not in inventory`, "VALIDATION_ERROR");
|
|
1797
|
+
}
|
|
1798
|
+
const bytes = (await deps.storagePort.getBlobs([tokenId])).get(tokenId);
|
|
1799
|
+
if (bytes === void 0) {
|
|
1800
|
+
throw new SphereError(`Token ${tokenId} has no blob in storage`, "STORAGE_ERROR");
|
|
1801
|
+
}
|
|
1802
|
+
return deps.engine.readTokenData(await deps.engine.decodeToken({ tokenId, token: bytes }));
|
|
1803
|
+
}
|
|
1804
|
+
|
|
1805
|
+
// modules/payments-v2/machine/payload.ts
|
|
1806
|
+
function buildPayload(recipientPubkey, request, spend, spentStates) {
|
|
1807
|
+
return {
|
|
1808
|
+
v: 2,
|
|
1809
|
+
kind: "coin",
|
|
1810
|
+
recipient: recipientPubkey,
|
|
1811
|
+
coinId: request.coinId,
|
|
1812
|
+
amount: request.amount,
|
|
1813
|
+
...request.memo !== void 0 ? { memo: request.memo } : {},
|
|
1814
|
+
direct: [...spend.plan.direct],
|
|
1815
|
+
...spend.plan.split !== void 0 ? {
|
|
1816
|
+
split: {
|
|
1817
|
+
tokenId: spend.plan.split.tokenId,
|
|
1818
|
+
splitAmount: spend.plan.split.splitAmount.toString(),
|
|
1819
|
+
remainderAmount: spend.plan.split.remainderAmount.toString()
|
|
1820
|
+
}
|
|
1821
|
+
} : {},
|
|
1822
|
+
spentStates
|
|
1823
|
+
};
|
|
1824
|
+
}
|
|
1825
|
+
function messageOf(err) {
|
|
1826
|
+
return err instanceof Error ? err.message : String(err);
|
|
1827
|
+
}
|
|
1828
|
+
function buildCoinlessPayload(recipientPubkey, request, spentStates) {
|
|
1829
|
+
return {
|
|
1830
|
+
v: 2,
|
|
1831
|
+
kind: "coinless",
|
|
1832
|
+
recipient: recipientPubkey,
|
|
1833
|
+
...request.memo !== void 0 ? { memo: request.memo } : {},
|
|
1834
|
+
direct: [request.tokenId],
|
|
1835
|
+
spentStates
|
|
1836
|
+
};
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
// modules/payments-v2/mint-params.ts
|
|
1840
|
+
function mintParams(recipientPubkey, coinId, amount) {
|
|
1841
|
+
return { recipientPubkey, value: { assets: [{ coinId, amount }] } };
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
// modules/payments-v2/mint.ts
|
|
1845
|
+
function runMintUnderJournal(deps, input) {
|
|
1846
|
+
return mintUnderJournal(deps, input);
|
|
1847
|
+
}
|
|
1848
|
+
async function mintUnderJournal(deps, input) {
|
|
1849
|
+
const { mintId, coinId, amount } = input;
|
|
1850
|
+
const engine = deps.engine;
|
|
1851
|
+
const entry = {
|
|
1852
|
+
mintId,
|
|
1853
|
+
coinId,
|
|
1854
|
+
amount: amount.toString(),
|
|
1855
|
+
tokenId: "",
|
|
1856
|
+
createdAt: deps.now()
|
|
1857
|
+
};
|
|
1858
|
+
await deps.mintJournal.upsert(entry);
|
|
1859
|
+
let token;
|
|
1860
|
+
try {
|
|
1861
|
+
token = await engine.mint(mintParams(deps.ownPubkeyBytes, coinId, amount), { transferId: mintId });
|
|
1862
|
+
} catch (err) {
|
|
1863
|
+
deps.armHeartbeat();
|
|
1864
|
+
return { success: false, error: messageOf(err) };
|
|
1865
|
+
}
|
|
1866
|
+
if (token.blob.tokenId !== entry.tokenId) {
|
|
1867
|
+
await deps.mintJournal.upsert({ ...entry, tokenId: token.blob.tokenId });
|
|
1868
|
+
}
|
|
1869
|
+
try {
|
|
1870
|
+
await finalizeMint(deps, mintId, token, coinId, amount.toString());
|
|
1871
|
+
} catch (err) {
|
|
1872
|
+
deps.armHeartbeat();
|
|
1873
|
+
return { success: false, tokenId: token.blob.tokenId, error: messageOf(err) };
|
|
1874
|
+
}
|
|
1875
|
+
return { success: true, tokenId: token.blob.tokenId };
|
|
1876
|
+
}
|
|
1877
|
+
async function finalizeMint(deps, mintId, token, coinId, amount) {
|
|
1878
|
+
const engine = deps.engine;
|
|
1879
|
+
const bytes = token.blob.token;
|
|
1880
|
+
const digest = bytesToHex(sha256(bytes));
|
|
1881
|
+
const keys = await deps.storagePort.uploadBlobs([{ sha256: digest, bytes }]);
|
|
1882
|
+
const key = keys.get(digest);
|
|
1883
|
+
if (key === void 0) {
|
|
1884
|
+
throw new SphereError(`no upload key returned for mint blob ${digest}`, "STORAGE_ERROR");
|
|
1885
|
+
}
|
|
1886
|
+
await deps.storagePort.applyDelta({
|
|
1887
|
+
transferId: mintId,
|
|
1888
|
+
spent: [],
|
|
1889
|
+
added: [{ tokenId: token.blob.tokenId, key }]
|
|
1890
|
+
});
|
|
1891
|
+
await deps.recordMint({
|
|
1892
|
+
tokenId: token.blob.tokenId,
|
|
1893
|
+
assets: [{ coinId, amount }]
|
|
1894
|
+
});
|
|
1895
|
+
await deps.mintJournal.removeByKey(mintId);
|
|
1896
|
+
deps.noteHeldState(token.blob.tokenId, (await engine.deliveryKeys(bytes)).stateHash);
|
|
1897
|
+
deps.refreshView();
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1900
|
+
// modules/payments-v2/send-coinless.ts
|
|
1901
|
+
async function materializeCoinlessSpend(deps, input) {
|
|
1902
|
+
const { engine } = deps;
|
|
1903
|
+
const tokenId = input.request.tokenId;
|
|
1904
|
+
const bytes = (await deps.storagePort.getBlobs([tokenId])).get(tokenId);
|
|
1905
|
+
if (bytes === void 0) {
|
|
1906
|
+
throw new SphereError(`Selected source ${tokenId} has no blob in storage`, "STORAGE_ERROR");
|
|
1907
|
+
}
|
|
1908
|
+
const token = await engine.decodeToken({ tokenId, token: bytes });
|
|
1909
|
+
if (!isCoinlessEnvelope(token.valueEnvelope)) {
|
|
1910
|
+
throw new SphereError(
|
|
1911
|
+
`Token ${tokenId} carries coin value and cannot be sent with sendCoinless \u2014 use send()`,
|
|
1912
|
+
"VALIDATION_ERROR"
|
|
1913
|
+
);
|
|
1914
|
+
}
|
|
1915
|
+
const keys = await engine.deliveryKeys(bytes);
|
|
1916
|
+
const payload = buildCoinlessPayload(input.recipientPubkey, input.request, {
|
|
1917
|
+
[tokenId]: { local: keys.stateHash, protocol: keys.stateHash }
|
|
1918
|
+
});
|
|
1919
|
+
const plan = {
|
|
1920
|
+
transferId: input.transferId,
|
|
1921
|
+
recipientPubkey: input.recipientPubkey,
|
|
1922
|
+
payload,
|
|
1923
|
+
ops: buildOps(payload),
|
|
1924
|
+
sources: /* @__PURE__ */ new Map([[tokenId, token]]),
|
|
1925
|
+
...input.request.memo !== void 0 ? { memo: input.request.memo } : {}
|
|
1926
|
+
};
|
|
1927
|
+
return { transferId: input.transferId, coinId: "", plan, sourceIds: input.sourceIds, sourceTokens: [] };
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
// modules/payments-v2/send-errors.ts
|
|
1931
|
+
function partialize(err, run) {
|
|
1932
|
+
if (run.delivered.length === 0) return err;
|
|
1933
|
+
return new PartialSendConflictError(
|
|
1934
|
+
"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.",
|
|
1935
|
+
run.firstPartialId ?? "",
|
|
1936
|
+
[...run.delivered],
|
|
1937
|
+
run.amount,
|
|
1938
|
+
err
|
|
1939
|
+
);
|
|
1940
|
+
}
|
|
1941
|
+
function stampTransferId(err, transferId) {
|
|
1942
|
+
if (err instanceof SphereError && isPossiblyCommittedSendOutcome(err) && err.transferId === void 0) {
|
|
1943
|
+
err.transferId = transferId;
|
|
1944
|
+
}
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1670
1947
|
// modules/payments-v2/recipient.ts
|
|
1671
1948
|
var ATTENTION_RECIPIENT_NETWORK_UNVERIFIED = "recipient:network-unverified";
|
|
1672
1949
|
async function requireSameNetworkRecipient(deps, identifier) {
|
|
@@ -1715,11 +1992,6 @@ async function splitOpIndex(deps, envelope) {
|
|
|
1715
1992
|
return Array.isArray(payload.direct) ? payload.direct.length : 0;
|
|
1716
1993
|
}
|
|
1717
1994
|
|
|
1718
|
-
// modules/payments-v2/mint-params.ts
|
|
1719
|
-
function mintParams(recipientPubkey, coinId, amount) {
|
|
1720
|
-
return { recipientPubkey, value: { assets: [{ coinId, amount }] } };
|
|
1721
|
-
}
|
|
1722
|
-
|
|
1723
1995
|
// modules/payments-v2/prewarm-cache.ts
|
|
1724
1996
|
var PrewarmCache = class _PrewarmCache {
|
|
1725
1997
|
entries = /* @__PURE__ */ new Map();
|
|
@@ -1893,29 +2165,6 @@ function transferringToken(tokenId, coinId, amount, registry, now) {
|
|
|
1893
2165
|
});
|
|
1894
2166
|
}
|
|
1895
2167
|
|
|
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
2168
|
// node_modules/@noble/ciphers/utils.js
|
|
1920
2169
|
function isBytes2(a) {
|
|
1921
2170
|
return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array" && "BYTES_PER_ELEMENT" in a && a.BYTES_PER_ELEMENT === 1;
|
|
@@ -2767,6 +3016,14 @@ function decryptField(key, envelope) {
|
|
|
2767
3016
|
}
|
|
2768
3017
|
|
|
2769
3018
|
// modules/payments-v2/history/History.ts
|
|
3019
|
+
function wireAssets(assets) {
|
|
3020
|
+
return assets.map((a) => ({ coinId: a.coinId, amount: a.amount }));
|
|
3021
|
+
}
|
|
3022
|
+
function isPermanentReject(err) {
|
|
3023
|
+
const status = err?.status;
|
|
3024
|
+
if (typeof status !== "number") return false;
|
|
3025
|
+
return status >= 400 && status < 500 && status !== 408 && status !== 429;
|
|
3026
|
+
}
|
|
2770
3027
|
var WIRE_TOKEN_ID = /^(?:[0-9a-f]{2}){1,64}$/;
|
|
2771
3028
|
var WIRE_PUBKEY = /^0[23][0-9a-f]{64}$/;
|
|
2772
3029
|
var History = class {
|
|
@@ -2791,7 +3048,7 @@ var History = class {
|
|
|
2791
3048
|
dedupKey: input.transferId,
|
|
2792
3049
|
id: (this.deps.newId ?? randomUUID)(),
|
|
2793
3050
|
type: "SENT",
|
|
2794
|
-
assets:
|
|
3051
|
+
assets: wireAssets(input.assets),
|
|
2795
3052
|
ts: this.ts(input.timestamp),
|
|
2796
3053
|
transferId: input.transferId,
|
|
2797
3054
|
...this.wireTokenId(input.tokenId),
|
|
@@ -2804,7 +3061,7 @@ var History = class {
|
|
|
2804
3061
|
dedupKey: `RECEIVED:${input.tokenId.toLowerCase()}:${input.stateHash.toLowerCase()}`,
|
|
2805
3062
|
id: (this.deps.newId ?? randomUUID)(),
|
|
2806
3063
|
type: "RECEIVED",
|
|
2807
|
-
assets:
|
|
3064
|
+
assets: wireAssets(input.assets),
|
|
2808
3065
|
ts: this.ts(input.timestamp),
|
|
2809
3066
|
...input.transferId !== void 0 ? { transferId: input.transferId } : {},
|
|
2810
3067
|
...this.wireTokenId(input.tokenId),
|
|
@@ -2817,7 +3074,7 @@ var History = class {
|
|
|
2817
3074
|
dedupKey: `MINT:${input.tokenId.toLowerCase()}`,
|
|
2818
3075
|
id: (this.deps.newId ?? randomUUID)(),
|
|
2819
3076
|
type: "MINT",
|
|
2820
|
-
assets:
|
|
3077
|
+
assets: wireAssets(input.assets),
|
|
2821
3078
|
ts: this.ts(input.timestamp),
|
|
2822
3079
|
...this.wireTokenId(input.tokenId)
|
|
2823
3080
|
}));
|
|
@@ -2828,7 +3085,10 @@ var History = class {
|
|
|
2828
3085
|
try {
|
|
2829
3086
|
await this.deps.client.postHistory([record]);
|
|
2830
3087
|
} catch (err) {
|
|
2831
|
-
this.deps.log?.(
|
|
3088
|
+
this.deps.log?.(
|
|
3089
|
+
isPermanentReject(err) ? "history POST REJECTED \u2014 the record shape is refused; no retry can fix it" : "history POST failed (dedupKey makes retry safe)",
|
|
3090
|
+
err
|
|
3091
|
+
);
|
|
2832
3092
|
return;
|
|
2833
3093
|
}
|
|
2834
3094
|
this.deps.emit("history:updated", this.toEntry(record));
|
|
@@ -2895,6 +3155,13 @@ var CURSOR_KEY = STORE_KEYS.streamCursor("inventory");
|
|
|
2895
3155
|
function stateKey(tokenId, stateHash) {
|
|
2896
3156
|
return `${tokenId}:${stateHash}`;
|
|
2897
3157
|
}
|
|
3158
|
+
function unchanged(prev, item, tokenType) {
|
|
3159
|
+
return prev.seq === item.seq && prev.status === item.status && prev.stateHash === item.stateHash && tokenType === prev.tokenType;
|
|
3160
|
+
}
|
|
3161
|
+
function isCoinless(item, prev) {
|
|
3162
|
+
if (item.status !== "active") return prev?.coinless ?? false;
|
|
3163
|
+
return (item.assets?.length ?? 0) === 0;
|
|
3164
|
+
}
|
|
2898
3165
|
var InventoryView = class {
|
|
2899
3166
|
constructor(deps) {
|
|
2900
3167
|
this.deps = deps;
|
|
@@ -2988,14 +3255,26 @@ var InventoryView = class {
|
|
|
2988
3255
|
pool(coinId) {
|
|
2989
3256
|
const out = [];
|
|
2990
3257
|
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;
|
|
3258
|
+
if (!this.isSpendable(tokenId, entry)) continue;
|
|
2994
3259
|
const asset = entry.assets.find((a) => a.coinId === coinId);
|
|
2995
3260
|
if (asset) out.push({ tokenId, amount: BigInt(asset.amount) });
|
|
2996
3261
|
}
|
|
2997
3262
|
return out;
|
|
2998
3263
|
}
|
|
3264
|
+
/**
|
|
3265
|
+
* The eligibility gates every spend shares, coin or token — ONE definition, so a
|
|
3266
|
+
* single probe covers both verbs and neither can drift from the other.
|
|
3267
|
+
*/
|
|
3268
|
+
isSpendable(tokenId, entry) {
|
|
3269
|
+
if (entry.status !== "active") return false;
|
|
3270
|
+
if (this.inFlight.has(tokenId) && !this.pinned(tokenId)) return false;
|
|
3271
|
+
return !this.suspected.has(stateKey(tokenId, entry.stateHash));
|
|
3272
|
+
}
|
|
3273
|
+
/** #777: is this NAMED token a spendable COINLESS holding? Never a coin source. */
|
|
3274
|
+
spendableCoinless(tokenId) {
|
|
3275
|
+
const entry = this.mirror.get(tokenId);
|
|
3276
|
+
return entry !== void 0 && entry.coinless && this.isSpendable(tokenId, entry);
|
|
3277
|
+
}
|
|
2999
3278
|
/**
|
|
3000
3279
|
* Current mirrored state of an ACTIVE token, or undefined when it is unknown
|
|
3001
3280
|
* or tombstoned. Exists so a cached blob can be state-scoped (F6): a blob is
|
|
@@ -3021,6 +3300,29 @@ var InventoryView = class {
|
|
|
3021
3300
|
}
|
|
3022
3301
|
return out;
|
|
3023
3302
|
}
|
|
3303
|
+
/**
|
|
3304
|
+
* Coinless holdings (#777) — DISJOINT from tokens(): an entry is coinless
|
|
3305
|
+
* exactly when it is not, so no mirror row can appear in both reads.
|
|
3306
|
+
*/
|
|
3307
|
+
coinless(registry) {
|
|
3308
|
+
const out = [];
|
|
3309
|
+
for (const [tokenId, entry] of this.mirror) {
|
|
3310
|
+
if (entry.status !== "active" || !entry.coinless) continue;
|
|
3311
|
+
const meta = entry.tokenType !== void 0 ? registry.getTypeMeta?.(entry.tokenType) : null;
|
|
3312
|
+
out.push({
|
|
3313
|
+
tokenId,
|
|
3314
|
+
...entry.tokenType !== void 0 ? { tokenType: entry.tokenType } : {},
|
|
3315
|
+
...meta ? { name: meta.name } : {},
|
|
3316
|
+
...meta?.iconUrl != null ? { iconUrl: meta.iconUrl } : {},
|
|
3317
|
+
stateHash: entry.stateHash,
|
|
3318
|
+
transferring: this.held(tokenId),
|
|
3319
|
+
...this.suspected.has(stateKey(tokenId, entry.stateHash)) ? { suspectedSpent: true } : {},
|
|
3320
|
+
createdAt: entry.createdAt,
|
|
3321
|
+
updatedAt: entry.updatedAt
|
|
3322
|
+
});
|
|
3323
|
+
}
|
|
3324
|
+
return out;
|
|
3325
|
+
}
|
|
3024
3326
|
async assets(registry, price) {
|
|
3025
3327
|
const raw = aggregateAssets(this.activeEntries(), (tokenId) => this.held(tokenId), registry);
|
|
3026
3328
|
if (!price || raw.length === 0) return raw;
|
|
@@ -3092,14 +3394,19 @@ var InventoryView = class {
|
|
|
3092
3394
|
applyOne(item, now) {
|
|
3093
3395
|
const prev = this.mirror.get(item.tokenId);
|
|
3094
3396
|
if (prev && item.seq < prev.seq) return false;
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
}
|
|
3397
|
+
const tokenType = item.tokenType ?? prev?.tokenType;
|
|
3398
|
+
if (prev && unchanged(prev, item, tokenType)) return false;
|
|
3098
3399
|
this.mirror.set(item.tokenId, {
|
|
3099
3400
|
stateHash: item.stateHash,
|
|
3100
3401
|
seq: item.seq,
|
|
3101
3402
|
status: item.status,
|
|
3102
|
-
|
|
3403
|
+
// ONLY a tombstone inherits: it omits assets for an unrelated reason and
|
|
3404
|
+
// recoverRemoved must know the amount it restores. An ACTIVE row that omits
|
|
3405
|
+
// them is stating coinlessness, and inheriting there would keep stale assets
|
|
3406
|
+
// in tokens() while `coinless` is true — the row in BOTH reads (§16).
|
|
3407
|
+
assets: item.assets ?? (item.status === "removed" ? prev?.assets ?? [] : []),
|
|
3408
|
+
coinless: isCoinless(item, prev),
|
|
3409
|
+
...tokenType !== void 0 ? { tokenType } : {},
|
|
3103
3410
|
createdAt: prev?.createdAt ?? now,
|
|
3104
3411
|
updatedAt: now
|
|
3105
3412
|
});
|
|
@@ -3330,7 +3637,12 @@ async function screen(deps, engine, entry) {
|
|
|
3330
3637
|
}
|
|
3331
3638
|
return {
|
|
3332
3639
|
kind: "accept",
|
|
3333
|
-
record: {
|
|
3640
|
+
record: {
|
|
3641
|
+
tokenId: keys.tokenId,
|
|
3642
|
+
stateHash: keys.stateHash,
|
|
3643
|
+
assets: toAssetAmounts(token),
|
|
3644
|
+
...isCoinlessEnvelope2(token.valueEnvelope) ? { tokenType: token.tokenType } : {}
|
|
3645
|
+
}
|
|
3334
3646
|
};
|
|
3335
3647
|
}
|
|
3336
3648
|
async function announce(deps, entry, record) {
|
|
@@ -3341,6 +3653,7 @@ async function announce(deps, entry, record) {
|
|
|
3341
3653
|
tokenId: record.tokenId,
|
|
3342
3654
|
stateHash: record.stateHash,
|
|
3343
3655
|
assets: record.assets,
|
|
3656
|
+
...record.tokenType !== void 0 ? { tokenType: record.tokenType } : {},
|
|
3344
3657
|
...entry.senderPubkey !== void 0 ? { senderPubkey: entry.senderPubkey } : {},
|
|
3345
3658
|
...entry.senderNametag !== void 0 ? { senderNametag: entry.senderNametag } : {},
|
|
3346
3659
|
...entry.memo !== void 0 ? { memo: entry.memo } : {},
|
|
@@ -3354,6 +3667,19 @@ async function announce(deps, entry, record) {
|
|
|
3354
3667
|
senderPubkey: entry.senderPubkey ?? "",
|
|
3355
3668
|
...entry.senderNametag !== void 0 ? { senderNametag: entry.senderNametag } : {},
|
|
3356
3669
|
tokens: record.assets.map((asset) => toUiToken(record.tokenId, asset, deps.registry, receivedAt)),
|
|
3670
|
+
// #777: named here rather than mapped from assets, which announced an EMPTY list.
|
|
3671
|
+
...record.tokenType !== void 0 ? {
|
|
3672
|
+
coinless: [
|
|
3673
|
+
{
|
|
3674
|
+
tokenId: record.tokenId,
|
|
3675
|
+
tokenType: record.tokenType,
|
|
3676
|
+
stateHash: record.stateHash,
|
|
3677
|
+
transferring: false,
|
|
3678
|
+
createdAt: receivedAt,
|
|
3679
|
+
updatedAt: receivedAt
|
|
3680
|
+
}
|
|
3681
|
+
]
|
|
3682
|
+
} : {},
|
|
3357
3683
|
...entry.memo !== void 0 ? { memo: entry.memo } : {},
|
|
3358
3684
|
receivedAt
|
|
3359
3685
|
};
|
|
@@ -3452,6 +3778,9 @@ function claimAck(entry) {
|
|
|
3452
3778
|
function rejectAck(entry, reason) {
|
|
3453
3779
|
return { deliveryId: entry.deliveryId, disposition: "rejected", reason, cursor: entry.cursor };
|
|
3454
3780
|
}
|
|
3781
|
+
function isCoinlessEnvelope2(envelope) {
|
|
3782
|
+
return envelope.startsWith("none_");
|
|
3783
|
+
}
|
|
3455
3784
|
function toAssetAmounts(token) {
|
|
3456
3785
|
return (token.value?.assets ?? []).map((asset) => ({
|
|
3457
3786
|
coinId: asset.coinId,
|
|
@@ -4019,6 +4348,37 @@ var SpendQueue = class {
|
|
|
4019
4348
|
}
|
|
4020
4349
|
return { kind: "queued", settled: this.enqueue(reservationId, request.coinId, amount) };
|
|
4021
4350
|
}
|
|
4351
|
+
/**
|
|
4352
|
+
* #777: reserve a NAMED coinless source. Never queues — there is nothing to wait
|
|
4353
|
+
* for. A coin plan waits because some OTHER combination may free up; a named
|
|
4354
|
+
* token is either free now or held by a reservation that only its own transfer
|
|
4355
|
+
* can release, so queueing would block until a timeout on a spend that cannot
|
|
4356
|
+
* become possible.
|
|
4357
|
+
*/
|
|
4358
|
+
planCoinless(reservationId, tokenId) {
|
|
4359
|
+
if (this.destroyed) {
|
|
4360
|
+
throw new SphereError("Module has been destroyed", "MODULE_DESTROYED");
|
|
4361
|
+
}
|
|
4362
|
+
const unproven = this.deps.ledger.unprovenReason();
|
|
4363
|
+
if (unproven !== null) {
|
|
4364
|
+
throw new SphereError(`Cannot spend yet: ${unproven}`, "SEND_SYNC_PENDING");
|
|
4365
|
+
}
|
|
4366
|
+
if (this.deps.spendableCoinless?.(tokenId) !== true) {
|
|
4367
|
+
throw new SphereError(
|
|
4368
|
+
`Token ${tokenId} is not a spendable coinless holding`,
|
|
4369
|
+
"VALIDATION_ERROR"
|
|
4370
|
+
);
|
|
4371
|
+
}
|
|
4372
|
+
const holder = this.deps.ledger.holderOf(tokenId);
|
|
4373
|
+
if (holder !== void 0) {
|
|
4374
|
+
throw new SphereError(
|
|
4375
|
+
`Token ${tokenId} is already reserved by transfer ${holder}`,
|
|
4376
|
+
"VALIDATION_ERROR"
|
|
4377
|
+
);
|
|
4378
|
+
}
|
|
4379
|
+
this.deps.ledger.reserve(reservationId, [{ tokenId }]);
|
|
4380
|
+
return { reservationId, plan: { direct: [tokenId] } };
|
|
4381
|
+
}
|
|
4022
4382
|
/**
|
|
4023
4383
|
* The sources `plan()` WOULD pick right now, reserving nothing and queueing
|
|
4024
4384
|
* nothing — a read-only probe used to warm blobs while the user is still
|
|
@@ -4208,6 +4568,7 @@ function composeFacadeParts(deps, hooks) {
|
|
|
4208
4568
|
const queue = new SpendQueue({
|
|
4209
4569
|
ledger,
|
|
4210
4570
|
getPool: (coinId) => view.pool(coinId),
|
|
4571
|
+
spendableCoinless: (tokenId) => view.spendableCoinless(tokenId),
|
|
4211
4572
|
...deps.workBudget !== void 0 ? { workBudget: deps.workBudget } : {}
|
|
4212
4573
|
});
|
|
4213
4574
|
const historyStore = new History({
|
|
@@ -4291,10 +4652,9 @@ function buildMachineDeps(deps, hooks, historyStore) {
|
|
|
4291
4652
|
recordHistory: async ({ transferId, payload, committedAmount }) => {
|
|
4292
4653
|
await historyStore.recordSent({
|
|
4293
4654
|
transferId,
|
|
4294
|
-
|
|
4295
|
-
//
|
|
4296
|
-
|
|
4297
|
-
amount: committedAmount,
|
|
4655
|
+
// §5.9: the SETTLED amount, never payload.amount — the plan. A token
|
|
4656
|
+
// spend moved no coin: `assets: []` + tokenId (wallet-api#151 / §10).
|
|
4657
|
+
...payload.kind === "coinless" ? { assets: [], tokenId: payload.direct[0] } : { assets: [{ coinId: payload.coinId, amount: committedAmount }] },
|
|
4298
4658
|
recipientPubkey: payload.recipient,
|
|
4299
4659
|
...payload.memo !== void 0 ? { memo: payload.memo } : {}
|
|
4300
4660
|
});
|
|
@@ -4329,12 +4689,10 @@ function buildReceive(deps, hooks, historyStore, heldStates, refreshView) {
|
|
|
4329
4689
|
});
|
|
4330
4690
|
}
|
|
4331
4691
|
async function recordReceived(historyStore, record) {
|
|
4332
|
-
const first = record.assets[0];
|
|
4333
4692
|
await historyStore.recordReceived({
|
|
4334
4693
|
tokenId: record.tokenId,
|
|
4335
4694
|
stateHash: record.stateHash,
|
|
4336
|
-
|
|
4337
|
-
amount: first?.amount ?? "0",
|
|
4695
|
+
assets: record.assets,
|
|
4338
4696
|
...record.senderPubkey !== void 0 ? { senderPubkey: record.senderPubkey } : {},
|
|
4339
4697
|
...record.senderNametag !== void 0 ? { senderNametag: record.senderNametag } : {},
|
|
4340
4698
|
...record.memo !== void 0 ? { memo: record.memo } : {},
|
|
@@ -4482,6 +4840,13 @@ var PaymentsFacade = class {
|
|
|
4482
4840
|
tokens(filter) {
|
|
4483
4841
|
return this.view.tokens(this.deps.registry, filter);
|
|
4484
4842
|
}
|
|
4843
|
+
coinless() {
|
|
4844
|
+
return this.view.coinless(this.deps.registry);
|
|
4845
|
+
}
|
|
4846
|
+
tokenData(tokenId) {
|
|
4847
|
+
const deps = { engine: this.engine(), view: this.view, storagePort: this.deps.storagePort };
|
|
4848
|
+
return readTokenData(deps, tokenId);
|
|
4849
|
+
}
|
|
4485
4850
|
history(page) {
|
|
4486
4851
|
return this.historyStore.page(page ?? {});
|
|
4487
4852
|
}
|
|
@@ -4506,6 +4871,9 @@ var PaymentsFacade = class {
|
|
|
4506
4871
|
send(request) {
|
|
4507
4872
|
return this.track(this.sendOutcome(request));
|
|
4508
4873
|
}
|
|
4874
|
+
sendCoinless(request) {
|
|
4875
|
+
return this.track(this.sendCoinlessOutcome(request));
|
|
4876
|
+
}
|
|
4509
4877
|
async receive() {
|
|
4510
4878
|
const transfers = await this.track(this.receiveLoop.drainOnce());
|
|
4511
4879
|
return { transfers };
|
|
@@ -4536,12 +4904,32 @@ var PaymentsFacade = class {
|
|
|
4536
4904
|
nowMs() {
|
|
4537
4905
|
return (this.deps.now ?? Date.now)();
|
|
4538
4906
|
}
|
|
4907
|
+
mintDeps() {
|
|
4908
|
+
return {
|
|
4909
|
+
engine: this.engine(),
|
|
4910
|
+
mintJournal: this.machineStores.mintJournal,
|
|
4911
|
+
storagePort: this.deps.storagePort,
|
|
4912
|
+
recordMint: (input) => this.historyStore.recordMint(input),
|
|
4913
|
+
armHeartbeat: () => this.heartbeat.arm(),
|
|
4914
|
+
noteHeldState: (tokenId, stateHash) => void this.heldStates.set(tokenId, stateHash),
|
|
4915
|
+
refreshView: () => this.trackTail(this.view.delta()),
|
|
4916
|
+
ownPubkeyBytes: this.ownPubkeyBytes,
|
|
4917
|
+
now: () => this.nowMs()
|
|
4918
|
+
};
|
|
4919
|
+
}
|
|
4539
4920
|
// ── send policy (§5.5 + old-loop parity; the machine stays policy-free) ────
|
|
4540
4921
|
/** The ONE place a send() outcome is shaped: success emits in finishSend, a
|
|
4541
4922
|
* CLEAN rejection emits `transfer:updated{status:'failed'}` here (§4). */
|
|
4542
|
-
|
|
4923
|
+
sendOutcome(request) {
|
|
4924
|
+
return this.runJob({ kind: "coin", request }, request.amount);
|
|
4925
|
+
}
|
|
4926
|
+
/** A token spend has no amount; '0' keeps the shortfall arithmetic total-free. */
|
|
4927
|
+
sendCoinlessOutcome(request) {
|
|
4928
|
+
return this.runJob({ kind: "coinless", request }, "0");
|
|
4929
|
+
}
|
|
4930
|
+
async runJob(job, amount) {
|
|
4543
4931
|
const run = {
|
|
4544
|
-
amount
|
|
4932
|
+
amount,
|
|
4545
4933
|
delivered: [],
|
|
4546
4934
|
sentTokens: [],
|
|
4547
4935
|
tokenTransfers: [],
|
|
@@ -4551,7 +4939,7 @@ var PaymentsFacade = class {
|
|
|
4551
4939
|
lastTransferId: ""
|
|
4552
4940
|
};
|
|
4553
4941
|
try {
|
|
4554
|
-
return await this.sendWithPolicy(
|
|
4942
|
+
return await this.sendWithPolicy(job, run);
|
|
4555
4943
|
} catch (err) {
|
|
4556
4944
|
this.emitCleanFailure(err, run.lastTransferId);
|
|
4557
4945
|
throw err;
|
|
@@ -4571,19 +4959,19 @@ var PaymentsFacade = class {
|
|
|
4571
4959
|
};
|
|
4572
4960
|
this.deps.emit("transfer:updated", failed);
|
|
4573
4961
|
}
|
|
4574
|
-
async sendWithPolicy(
|
|
4575
|
-
const recipient = await requireSameNetworkRecipient(this.deps, request.recipient);
|
|
4962
|
+
async sendWithPolicy(job, run) {
|
|
4963
|
+
const recipient = await requireSameNetworkRecipient(this.deps, job.request.recipient);
|
|
4576
4964
|
for (let attempt = 0; ; attempt++) {
|
|
4577
4965
|
let ctx;
|
|
4578
4966
|
try {
|
|
4579
|
-
ctx = await this.planAndMaterialize(recipient.chainPubkey,
|
|
4967
|
+
ctx = await this.planAndMaterialize(recipient.chainPubkey, job, run);
|
|
4580
4968
|
} catch (err) {
|
|
4581
|
-
throw
|
|
4969
|
+
throw partialize(err, run);
|
|
4582
4970
|
}
|
|
4583
4971
|
const disposition = await this.runAttempt(ctx);
|
|
4584
|
-
if (disposition.kind === "rethrow") throw
|
|
4972
|
+
if (disposition.kind === "rethrow") throw partialize(disposition.error, run);
|
|
4585
4973
|
if (disposition.kind === "retry-full") {
|
|
4586
|
-
if (attempt >= MAX_RESELECT) throw
|
|
4974
|
+
if (job.kind === "coinless" || attempt >= MAX_RESELECT) throw partialize(disposition.error, run);
|
|
4587
4975
|
continue;
|
|
4588
4976
|
}
|
|
4589
4977
|
if (disposition.kind === "success") {
|
|
@@ -4639,7 +5027,7 @@ var PaymentsFacade = class {
|
|
|
4639
5027
|
async disposeFailedAttempt(ctx, err) {
|
|
4640
5028
|
if (isPossiblyCommittedSendOutcome(err)) {
|
|
4641
5029
|
this.settleKeepOpen(ctx);
|
|
4642
|
-
|
|
5030
|
+
stampTransferId(err, ctx.transferId);
|
|
4643
5031
|
return { kind: "rethrow", error: err };
|
|
4644
5032
|
}
|
|
4645
5033
|
const backstop = await this.machineStores.backstop.getByKey(ctx.transferId);
|
|
@@ -4673,7 +5061,7 @@ var PaymentsFacade = class {
|
|
|
4673
5061
|
async disposeConvergeFailure(ctx, err) {
|
|
4674
5062
|
if (isPossiblyCommittedSendOutcome(err)) {
|
|
4675
5063
|
this.settleKeepOpen(ctx);
|
|
4676
|
-
|
|
5064
|
+
stampTransferId(err, ctx.transferId);
|
|
4677
5065
|
return { kind: "rethrow", error: err };
|
|
4678
5066
|
}
|
|
4679
5067
|
if (classifyError(err) === "conflict") {
|
|
@@ -4684,9 +5072,23 @@ var PaymentsFacade = class {
|
|
|
4684
5072
|
}
|
|
4685
5073
|
return { kind: "rethrow", error: err };
|
|
4686
5074
|
}
|
|
4687
|
-
async planAndMaterialize(recipientPubkey,
|
|
5075
|
+
async planAndMaterialize(recipientPubkey, job, run) {
|
|
4688
5076
|
const transferId = this.newId();
|
|
4689
5077
|
run.lastTransferId = transferId;
|
|
5078
|
+
if (job.kind === "coinless") {
|
|
5079
|
+
const spend2 = this.queue.planCoinless(transferId, job.request.tokenId);
|
|
5080
|
+
const sourceIds2 = this.markPlanned(transferId, "", spend2);
|
|
5081
|
+
try {
|
|
5082
|
+
return await materializeCoinlessSpend(
|
|
5083
|
+
{ engine: this.engine(), storagePort: this.deps.storagePort },
|
|
5084
|
+
{ transferId, recipientPubkey, request: job.request, sourceIds: sourceIds2 }
|
|
5085
|
+
);
|
|
5086
|
+
} catch (err) {
|
|
5087
|
+
this.settleFailure(transferId, "", sourceIds2);
|
|
5088
|
+
throw err;
|
|
5089
|
+
}
|
|
5090
|
+
}
|
|
5091
|
+
const request = { ...job.request, amount: run.amount };
|
|
4690
5092
|
const planned = this.queue.plan(transferId, { coinId: request.coinId, amount: request.amount });
|
|
4691
5093
|
const spend = planned.kind === "planned" ? planned.spend : await planned.settled;
|
|
4692
5094
|
const sourceIds = this.markPlanned(transferId, request.coinId, spend);
|
|
@@ -4820,22 +5222,6 @@ var PaymentsFacade = class {
|
|
|
4820
5222
|
return result;
|
|
4821
5223
|
}
|
|
4822
5224
|
/** 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
5225
|
async softAbort(transferId) {
|
|
4840
5226
|
try {
|
|
4841
5227
|
await this.deps.client.abortIntent(transferId);
|
|
@@ -4851,57 +5237,11 @@ var PaymentsFacade = class {
|
|
|
4851
5237
|
const mintId = this.newId();
|
|
4852
5238
|
this.activeMoneyOps.add(mintId);
|
|
4853
5239
|
try {
|
|
4854
|
-
return await this.
|
|
5240
|
+
return await runMintUnderJournal(this.mintDeps(), { mintId, coinId, amount });
|
|
4855
5241
|
} finally {
|
|
4856
5242
|
this.activeMoneyOps.delete(mintId);
|
|
4857
5243
|
}
|
|
4858
5244
|
}
|
|
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
5245
|
/** @returns how many journal entries were RESOLVED (cleared) — heartbeat progress. */
|
|
4906
5246
|
async replayMints() {
|
|
4907
5247
|
let resolved = 0;
|
|
@@ -4933,7 +5273,7 @@ var PaymentsFacade = class {
|
|
|
4933
5273
|
if (token.blob.tokenId !== entry.tokenId) {
|
|
4934
5274
|
await this.machineStores.mintJournal.upsert({ ...entry, tokenId: token.blob.tokenId });
|
|
4935
5275
|
}
|
|
4936
|
-
await this.
|
|
5276
|
+
await finalizeMint(this.mintDeps(), entry.mintId, token, entry.coinId, entry.amount);
|
|
4937
5277
|
return true;
|
|
4938
5278
|
}
|
|
4939
5279
|
async tokenInServerInventory(tokenId) {
|