@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
|
@@ -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,73 @@ function createMachineStores(kv) {
|
|
|
787
785
|
};
|
|
788
786
|
}
|
|
789
787
|
|
|
788
|
+
// token-engine/sdk.ts
|
|
789
|
+
import { StateTransitionClient } from "@unicitylabs/state-transition-sdk/lib/StateTransitionClient.js";
|
|
790
|
+
import { AggregatorClient } from "@unicitylabs/state-transition-sdk/lib/api/AggregatorClient.js";
|
|
791
|
+
import { NetworkId } from "@unicitylabs/state-transition-sdk/lib/api/NetworkId.js";
|
|
792
|
+
import { CertificationData } from "@unicitylabs/state-transition-sdk/lib/api/CertificationData.js";
|
|
793
|
+
import { CertificationResponse, CertificationStatus } from "@unicitylabs/state-transition-sdk/lib/api/CertificationResponse.js";
|
|
794
|
+
import { StateId } from "@unicitylabs/state-transition-sdk/lib/api/StateId.js";
|
|
795
|
+
import { InclusionProof } from "@unicitylabs/state-transition-sdk/lib/api/InclusionProof.js";
|
|
796
|
+
import { InclusionProofResponse } from "@unicitylabs/state-transition-sdk/lib/api/InclusionProofResponse.js";
|
|
797
|
+
import { JsonRpcNetworkError } from "@unicitylabs/state-transition-sdk/lib/api/json-rpc/JsonRpcNetworkError.js";
|
|
798
|
+
import { RootTrustBase } from "@unicitylabs/state-transition-sdk/lib/api/bft/RootTrustBase.js";
|
|
799
|
+
import { waitInclusionProof } from "@unicitylabs/state-transition-sdk/lib/util/InclusionProofUtils.js";
|
|
800
|
+
import { InclusionProofVerificationStatus } from "@unicitylabs/state-transition-sdk/lib/transaction/verification/rule/InclusionProofVerificationRule.js";
|
|
801
|
+
import { Token } from "@unicitylabs/state-transition-sdk/lib/transaction/Token.js";
|
|
802
|
+
import { MintTransaction } from "@unicitylabs/state-transition-sdk/lib/transaction/MintTransaction.js";
|
|
803
|
+
import { TransferTransaction } from "@unicitylabs/state-transition-sdk/lib/transaction/TransferTransaction.js";
|
|
804
|
+
import { CertifiedMintTransaction } from "@unicitylabs/state-transition-sdk/lib/transaction/CertifiedMintTransaction.js";
|
|
805
|
+
import { CertifiedTransferTransaction } from "@unicitylabs/state-transition-sdk/lib/transaction/CertifiedTransferTransaction.js";
|
|
806
|
+
import { TokenId } from "@unicitylabs/state-transition-sdk/lib/transaction/TokenId.js";
|
|
807
|
+
import { TokenType } from "@unicitylabs/state-transition-sdk/lib/transaction/TokenType.js";
|
|
808
|
+
import { TokenSalt } from "@unicitylabs/state-transition-sdk/lib/transaction/TokenSalt.js";
|
|
809
|
+
import { StateMask } from "@unicitylabs/state-transition-sdk/lib/transaction/StateMask.js";
|
|
810
|
+
import { MintJustificationVerifierService } from "@unicitylabs/state-transition-sdk/lib/transaction/verification/MintJustificationVerifierService.js";
|
|
811
|
+
import { TokenIssuanceVerifierService } from "@unicitylabs/state-transition-sdk/lib/transaction/verification/TokenIssuanceVerifierService.js";
|
|
812
|
+
import { VerificationContext } from "@unicitylabs/state-transition-sdk/lib/transaction/verification/VerificationContext.js";
|
|
813
|
+
import { UnicityCertificateVerifier } from "@unicitylabs/state-transition-sdk/lib/api/bft/verification/UnicityCertificateVerifier.js";
|
|
814
|
+
import { UnicitySealQuorumSignaturesVerificationRule } from "@unicitylabs/state-transition-sdk/lib/api/bft/verification/rule/UnicitySealQuorumSignaturesVerificationRule.js";
|
|
815
|
+
import { VerifiedSealCache } from "@unicitylabs/state-transition-sdk/lib/api/bft/verification/VerifiedSealCache.js";
|
|
816
|
+
import { Secp256k1SignatureVerifier } from "@unicitylabs/state-transition-sdk/lib/crypto/secp256k1/Secp256k1SignatureVerifier.js";
|
|
817
|
+
import { WorkerTokenVerifier } from "@unicitylabs/state-transition-sdk/lib/transaction/verification/worker/WorkerTokenVerifier.js";
|
|
818
|
+
import { EncodedPredicate } from "@unicitylabs/state-transition-sdk/lib/predicate/EncodedPredicate.js";
|
|
819
|
+
import { SignaturePredicate } from "@unicitylabs/state-transition-sdk/lib/predicate/builtin/SignaturePredicate.js";
|
|
820
|
+
import { SignaturePredicateUnlockScript } from "@unicitylabs/state-transition-sdk/lib/predicate/builtin/SignaturePredicateUnlockScript.js";
|
|
821
|
+
import { BurnPredicate } from "@unicitylabs/state-transition-sdk/lib/predicate/builtin/BurnPredicate.js";
|
|
822
|
+
import { PredicateVerifierService } from "@unicitylabs/state-transition-sdk/lib/predicate/verification/PredicateVerifierService.js";
|
|
823
|
+
import { SigningService } from "@unicitylabs/state-transition-sdk/lib/crypto/secp256k1/SigningService.js";
|
|
824
|
+
import { Signature } from "@unicitylabs/state-transition-sdk/lib/crypto/secp256k1/Signature.js";
|
|
825
|
+
import { MintSigningService } from "@unicitylabs/state-transition-sdk/lib/crypto/MintSigningService.js";
|
|
826
|
+
import { HashAlgorithm } from "@unicitylabs/state-transition-sdk/lib/crypto/hash/HashAlgorithm.js";
|
|
827
|
+
import { DataHash } from "@unicitylabs/state-transition-sdk/lib/crypto/hash/DataHash.js";
|
|
828
|
+
import { DataHasher } from "@unicitylabs/state-transition-sdk/lib/crypto/hash/DataHasher.js";
|
|
829
|
+
import { DataHasherFactory } from "@unicitylabs/state-transition-sdk/lib/crypto/hash/DataHasherFactory.js";
|
|
830
|
+
import { CborSerializer } from "@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborSerializer.js";
|
|
831
|
+
import { CborDeserializer } from "@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborDeserializer.js";
|
|
832
|
+
import { CborError } from "@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborError.js";
|
|
833
|
+
import { CborReader } from "@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborReader.js";
|
|
834
|
+
import { MajorType } from "@unicitylabs/state-transition-sdk/lib/serialization/cbor/MajorType.js";
|
|
835
|
+
import { Asset } from "@unicitylabs/state-transition-sdk/lib/payment/asset/Asset.js";
|
|
836
|
+
import { AssetId } from "@unicitylabs/state-transition-sdk/lib/payment/asset/AssetId.js";
|
|
837
|
+
import { PaymentAssetCollection } from "@unicitylabs/state-transition-sdk/lib/payment/asset/PaymentAssetCollection.js";
|
|
838
|
+
import { TokenSplit } from "@unicitylabs/state-transition-sdk/lib/payment/TokenSplit.js";
|
|
839
|
+
import { SplitTokenRequest } from "@unicitylabs/state-transition-sdk/lib/payment/SplitTokenRequest.js";
|
|
840
|
+
import { SplitToken } from "@unicitylabs/state-transition-sdk/lib/payment/SplitToken.js";
|
|
841
|
+
import { SplitAllocationProof } from "@unicitylabs/state-transition-sdk/lib/payment/SplitAllocationProof.js";
|
|
842
|
+
import { SplitMintJustification } from "@unicitylabs/state-transition-sdk/lib/payment/SplitMintJustification.js";
|
|
843
|
+
import { SplitMintJustificationVerifier } from "@unicitylabs/state-transition-sdk/lib/payment/SplitMintJustificationVerifier.js";
|
|
844
|
+
import { VerificationStatus } from "@unicitylabs/state-transition-sdk/lib/verification/VerificationStatus.js";
|
|
845
|
+
import { VerificationResult } from "@unicitylabs/state-transition-sdk/lib/verification/VerificationResult.js";
|
|
846
|
+
import { HexConverter } from "@unicitylabs/state-transition-sdk/lib/util/HexConverter.js";
|
|
847
|
+
import { BigintConverter } from "@unicitylabs/state-transition-sdk/lib/util/BigintConverter.js";
|
|
848
|
+
import { BitString } from "@unicitylabs/state-transition-sdk/lib/util/BitString.js";
|
|
849
|
+
|
|
850
|
+
// token-engine/value-envelope.ts
|
|
851
|
+
function isCoinlessEnvelope(envelope) {
|
|
852
|
+
return envelope.startsWith("none_");
|
|
853
|
+
}
|
|
854
|
+
|
|
790
855
|
// token-engine/errors.ts
|
|
791
856
|
var TransferConflictError = class extends SphereError {
|
|
792
857
|
/**
|
|
@@ -826,6 +891,14 @@ var CheckpointTrustbaseMismatchError = class extends SphereError {
|
|
|
826
891
|
}
|
|
827
892
|
};
|
|
828
893
|
|
|
894
|
+
// modules/payments-v2/machine/payload-view.ts
|
|
895
|
+
function splitOf(p) {
|
|
896
|
+
return p.kind === "coin" ? p.split : void 0;
|
|
897
|
+
}
|
|
898
|
+
function coinIdOf(p) {
|
|
899
|
+
return p.kind === "coin" ? p.coinId : void 0;
|
|
900
|
+
}
|
|
901
|
+
|
|
829
902
|
// modules/payments-v2/machine/TransferMachine.ts
|
|
830
903
|
var CERTIFY_WIDTH = 20;
|
|
831
904
|
var KEEP_OPEN_CODES = /* @__PURE__ */ new Set([
|
|
@@ -1028,13 +1101,14 @@ var TransferMachine = class {
|
|
|
1028
1101
|
const finished = await engine.transfer({ token: source, recipientPubkey }, opts);
|
|
1029
1102
|
return { recipientBlob: finished.blob.token };
|
|
1030
1103
|
}
|
|
1031
|
-
const split = ctx.payload
|
|
1104
|
+
const split = splitOf(ctx.payload);
|
|
1105
|
+
const splitCoinId = coinIdOf(ctx.payload);
|
|
1032
1106
|
const { outputs } = await engine.split(
|
|
1033
1107
|
{
|
|
1034
1108
|
token: source,
|
|
1035
1109
|
outputs: [
|
|
1036
|
-
{ recipientPubkey, coinId:
|
|
1037
|
-
{ recipientPubkey: this.deps.ownPubkey, coinId:
|
|
1110
|
+
{ recipientPubkey, coinId: splitCoinId, amount: BigInt(split.splitAmount) },
|
|
1111
|
+
{ recipientPubkey: this.deps.ownPubkey, coinId: splitCoinId, amount: BigInt(split.remainderAmount) }
|
|
1038
1112
|
]
|
|
1039
1113
|
},
|
|
1040
1114
|
opts
|
|
@@ -1156,7 +1230,9 @@ var TransferMachine = class {
|
|
|
1156
1230
|
const entry = {
|
|
1157
1231
|
transferId: r.transferId,
|
|
1158
1232
|
remainingAmount: undelivered.toString(),
|
|
1159
|
-
|
|
1233
|
+
// '' only on a token-addressed intent, which is one leg: it either lands or
|
|
1234
|
+
// conflicts, so a shortfall row is unreachable there (settlePartial needs >1).
|
|
1235
|
+
coinId: coinIdOf(r.payload) ?? "",
|
|
1160
1236
|
recipient: r.payload.recipient,
|
|
1161
1237
|
committedTokenIds: committed.map((o) => o.op.sourceTokenId),
|
|
1162
1238
|
createdAt: this.deps.now()
|
|
@@ -1182,7 +1258,8 @@ var TransferMachine = class {
|
|
|
1182
1258
|
}
|
|
1183
1259
|
async finishBookkeeping(engine, transferId, payload, phase, committed) {
|
|
1184
1260
|
try {
|
|
1185
|
-
const
|
|
1261
|
+
const coinId = coinIdOf(payload);
|
|
1262
|
+
const committedAmount = coinId === void 0 ? "0" : await this.settledAmount(engine, coinId, committed);
|
|
1186
1263
|
await this.deps.recordHistory?.({ transferId, payload, phase, committedAmount });
|
|
1187
1264
|
} catch {
|
|
1188
1265
|
}
|
|
@@ -1225,8 +1302,11 @@ var TransferMachine = class {
|
|
|
1225
1302
|
}
|
|
1226
1303
|
};
|
|
1227
1304
|
function conflictAmount(engine, payload, op, source) {
|
|
1228
|
-
|
|
1229
|
-
|
|
1305
|
+
const split = splitOf(payload);
|
|
1306
|
+
if (op.kind === "split") return BigInt(split.splitAmount);
|
|
1307
|
+
const coinId = coinIdOf(payload);
|
|
1308
|
+
if (coinId === void 0) return 0n;
|
|
1309
|
+
return source !== void 0 ? engine.balanceOf(source, coinId) : 0n;
|
|
1230
1310
|
}
|
|
1231
1311
|
|
|
1232
1312
|
// modules/payments-v2/machine/resume.ts
|
|
@@ -1333,6 +1413,14 @@ async function runOne(ctx, job, report) {
|
|
|
1333
1413
|
report.failed.push(job.transferId);
|
|
1334
1414
|
return;
|
|
1335
1415
|
}
|
|
1416
|
+
if (job.payload.kind === "coinless" && !isCoinlessEnvelope(token.valueEnvelope)) {
|
|
1417
|
+
logger.warn(
|
|
1418
|
+
"PaymentsV2",
|
|
1419
|
+
`resume: token intent ${job.transferId} names a source carrying coin value \u2014 refusing`
|
|
1420
|
+
);
|
|
1421
|
+
report.failed.push(job.transferId);
|
|
1422
|
+
return;
|
|
1423
|
+
}
|
|
1336
1424
|
mine.set(op.sourceTokenId, token);
|
|
1337
1425
|
}
|
|
1338
1426
|
try {
|
|
@@ -1363,6 +1451,28 @@ async function classifyResumeFailure(deps, stores, transferId, err, report) {
|
|
|
1363
1451
|
}
|
|
1364
1452
|
report.failed.push(transferId);
|
|
1365
1453
|
}
|
|
1454
|
+
function validateCoinPayload(c) {
|
|
1455
|
+
if (typeof c.coinId !== "string" || typeof c.amount !== "string") {
|
|
1456
|
+
throw new SphereError("intent payload is missing coinId/amount", "VALIDATION_ERROR");
|
|
1457
|
+
}
|
|
1458
|
+
const s = c.split;
|
|
1459
|
+
if (s !== void 0 && (typeof s.tokenId !== "string" || typeof s.splitAmount !== "string" || typeof s.remainderAmount !== "string")) {
|
|
1460
|
+
throw new SphereError("intent payload split spec is malformed", "VALIDATION_ERROR");
|
|
1461
|
+
}
|
|
1462
|
+
return { ...c, kind: "coin" };
|
|
1463
|
+
}
|
|
1464
|
+
function validateCoinlessPayload(p) {
|
|
1465
|
+
if (p.direct?.length !== 1 || typeof p.direct[0] !== "string" || p.direct[0] === "") {
|
|
1466
|
+
throw new SphereError(
|
|
1467
|
+
"token intent payload must name exactly one source token",
|
|
1468
|
+
"VALIDATION_ERROR"
|
|
1469
|
+
);
|
|
1470
|
+
}
|
|
1471
|
+
if (p.split !== void 0) {
|
|
1472
|
+
throw new SphereError("token intent payload cannot carry a split", "VALIDATION_ERROR");
|
|
1473
|
+
}
|
|
1474
|
+
return { ...p, kind: "coinless", direct: [p.direct[0]] };
|
|
1475
|
+
}
|
|
1366
1476
|
function validatePayload(raw) {
|
|
1367
1477
|
const p = raw !== null && typeof raw === "object" ? raw : null;
|
|
1368
1478
|
if (p === null || p.v !== 2 || !Array.isArray(p.direct) || p.direct.some((t) => typeof t !== "string")) {
|
|
@@ -1371,14 +1481,17 @@ function validatePayload(raw) {
|
|
|
1371
1481
|
"VALIDATION_ERROR"
|
|
1372
1482
|
);
|
|
1373
1483
|
}
|
|
1374
|
-
if (typeof p.recipient !== "string"
|
|
1375
|
-
throw new SphereError("intent payload is missing recipient
|
|
1484
|
+
if (typeof p.recipient !== "string") {
|
|
1485
|
+
throw new SphereError("intent payload is missing recipient", "VALIDATION_ERROR");
|
|
1376
1486
|
}
|
|
1377
|
-
const
|
|
1378
|
-
if (
|
|
1379
|
-
throw new SphereError(
|
|
1487
|
+
const kind = p.kind ?? "coin";
|
|
1488
|
+
if (kind !== "coin" && kind !== "coinless") {
|
|
1489
|
+
throw new SphereError(
|
|
1490
|
+
`unsupported intent kind '${String(kind)}' \u2014 not resumable by this client`,
|
|
1491
|
+
"VALIDATION_ERROR"
|
|
1492
|
+
);
|
|
1380
1493
|
}
|
|
1381
|
-
return p;
|
|
1494
|
+
return kind === "coinless" ? validateCoinlessPayload(p) : validateCoinPayload(p);
|
|
1382
1495
|
}
|
|
1383
1496
|
|
|
1384
1497
|
// modules/payments-v2/convergence.ts
|
|
@@ -1584,13 +1697,23 @@ async function openRow(entry, journal, decryptPayload) {
|
|
|
1584
1697
|
transferId: entry.transferId,
|
|
1585
1698
|
kind: "open",
|
|
1586
1699
|
recipient: typeof payload?.recipient === "string" ? payload.recipient : "",
|
|
1587
|
-
|
|
1588
|
-
amount: typeof payload?.amount === "string" ? payload.amount : "",
|
|
1700
|
+
...subject(payload),
|
|
1589
1701
|
legs: { certified, total: Math.max(total, certified) },
|
|
1590
1702
|
deliveryPending: journal.length > 0,
|
|
1591
1703
|
createdAt: entry.createdAt
|
|
1592
1704
|
};
|
|
1593
1705
|
}
|
|
1706
|
+
function subject(payload) {
|
|
1707
|
+
if (payload?.kind === "coinless") {
|
|
1708
|
+
const tokenId = payload.direct?.[0];
|
|
1709
|
+
return { coinId: "", amount: "", ...typeof tokenId === "string" ? { tokenId } : {} };
|
|
1710
|
+
}
|
|
1711
|
+
const coin = payload;
|
|
1712
|
+
return {
|
|
1713
|
+
coinId: typeof coin?.coinId === "string" ? coin.coinId : "",
|
|
1714
|
+
amount: typeof coin?.amount === "string" ? coin.amount : ""
|
|
1715
|
+
};
|
|
1716
|
+
}
|
|
1594
1717
|
function journalOnlyRow(transferId, entries) {
|
|
1595
1718
|
const certified = distinctOpCount(entries);
|
|
1596
1719
|
return {
|
|
@@ -1620,6 +1743,160 @@ function distinctOpCount(entries) {
|
|
|
1620
1743
|
return new Set(entries.map((entry) => entry.opIndex)).size;
|
|
1621
1744
|
}
|
|
1622
1745
|
|
|
1746
|
+
// modules/payments-v2/inventory/token-data.ts
|
|
1747
|
+
async function readTokenData(deps, tokenId) {
|
|
1748
|
+
if (deps.view.stateHashOf(tokenId) === void 0) {
|
|
1749
|
+
throw new SphereError(`Token ${tokenId} is not in inventory`, "VALIDATION_ERROR");
|
|
1750
|
+
}
|
|
1751
|
+
const bytes = (await deps.storagePort.getBlobs([tokenId])).get(tokenId);
|
|
1752
|
+
if (bytes === void 0) {
|
|
1753
|
+
throw new SphereError(`Token ${tokenId} has no blob in storage`, "STORAGE_ERROR");
|
|
1754
|
+
}
|
|
1755
|
+
return deps.engine.readTokenData(await deps.engine.decodeToken({ tokenId, token: bytes }));
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
// modules/payments-v2/machine/payload.ts
|
|
1759
|
+
function buildPayload(recipientPubkey, request, spend, spentStates) {
|
|
1760
|
+
return {
|
|
1761
|
+
v: 2,
|
|
1762
|
+
kind: "coin",
|
|
1763
|
+
recipient: recipientPubkey,
|
|
1764
|
+
coinId: request.coinId,
|
|
1765
|
+
amount: request.amount,
|
|
1766
|
+
...request.memo !== void 0 ? { memo: request.memo } : {},
|
|
1767
|
+
direct: [...spend.plan.direct],
|
|
1768
|
+
...spend.plan.split !== void 0 ? {
|
|
1769
|
+
split: {
|
|
1770
|
+
tokenId: spend.plan.split.tokenId,
|
|
1771
|
+
splitAmount: spend.plan.split.splitAmount.toString(),
|
|
1772
|
+
remainderAmount: spend.plan.split.remainderAmount.toString()
|
|
1773
|
+
}
|
|
1774
|
+
} : {},
|
|
1775
|
+
spentStates
|
|
1776
|
+
};
|
|
1777
|
+
}
|
|
1778
|
+
function messageOf(err) {
|
|
1779
|
+
return err instanceof Error ? err.message : String(err);
|
|
1780
|
+
}
|
|
1781
|
+
function buildCoinlessPayload(recipientPubkey, request, spentStates) {
|
|
1782
|
+
return {
|
|
1783
|
+
v: 2,
|
|
1784
|
+
kind: "coinless",
|
|
1785
|
+
recipient: recipientPubkey,
|
|
1786
|
+
...request.memo !== void 0 ? { memo: request.memo } : {},
|
|
1787
|
+
direct: [request.tokenId],
|
|
1788
|
+
spentStates
|
|
1789
|
+
};
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1792
|
+
// modules/payments-v2/mint-params.ts
|
|
1793
|
+
function mintParams(recipientPubkey, coinId, amount) {
|
|
1794
|
+
return { recipientPubkey, value: { assets: [{ coinId, amount }] } };
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
// modules/payments-v2/mint.ts
|
|
1798
|
+
function runMintUnderJournal(deps, input) {
|
|
1799
|
+
return mintUnderJournal(deps, input);
|
|
1800
|
+
}
|
|
1801
|
+
async function mintUnderJournal(deps, input) {
|
|
1802
|
+
const { mintId, coinId, amount } = input;
|
|
1803
|
+
const engine = deps.engine;
|
|
1804
|
+
const entry = {
|
|
1805
|
+
mintId,
|
|
1806
|
+
coinId,
|
|
1807
|
+
amount: amount.toString(),
|
|
1808
|
+
tokenId: "",
|
|
1809
|
+
createdAt: deps.now()
|
|
1810
|
+
};
|
|
1811
|
+
await deps.mintJournal.upsert(entry);
|
|
1812
|
+
let token;
|
|
1813
|
+
try {
|
|
1814
|
+
token = await engine.mint(mintParams(deps.ownPubkeyBytes, coinId, amount), { transferId: mintId });
|
|
1815
|
+
} catch (err) {
|
|
1816
|
+
deps.armHeartbeat();
|
|
1817
|
+
return { success: false, error: messageOf(err) };
|
|
1818
|
+
}
|
|
1819
|
+
if (token.blob.tokenId !== entry.tokenId) {
|
|
1820
|
+
await deps.mintJournal.upsert({ ...entry, tokenId: token.blob.tokenId });
|
|
1821
|
+
}
|
|
1822
|
+
try {
|
|
1823
|
+
await finalizeMint(deps, mintId, token, coinId, amount.toString());
|
|
1824
|
+
} catch (err) {
|
|
1825
|
+
deps.armHeartbeat();
|
|
1826
|
+
return { success: false, tokenId: token.blob.tokenId, error: messageOf(err) };
|
|
1827
|
+
}
|
|
1828
|
+
return { success: true, tokenId: token.blob.tokenId };
|
|
1829
|
+
}
|
|
1830
|
+
async function finalizeMint(deps, mintId, token, coinId, amount) {
|
|
1831
|
+
const engine = deps.engine;
|
|
1832
|
+
const bytes = token.blob.token;
|
|
1833
|
+
const digest = bytesToHex(sha256(bytes));
|
|
1834
|
+
const keys = await deps.storagePort.uploadBlobs([{ sha256: digest, bytes }]);
|
|
1835
|
+
const key = keys.get(digest);
|
|
1836
|
+
if (key === void 0) {
|
|
1837
|
+
throw new SphereError(`no upload key returned for mint blob ${digest}`, "STORAGE_ERROR");
|
|
1838
|
+
}
|
|
1839
|
+
await deps.storagePort.applyDelta({
|
|
1840
|
+
transferId: mintId,
|
|
1841
|
+
spent: [],
|
|
1842
|
+
added: [{ tokenId: token.blob.tokenId, key }]
|
|
1843
|
+
});
|
|
1844
|
+
await deps.recordMint({
|
|
1845
|
+
tokenId: token.blob.tokenId,
|
|
1846
|
+
assets: [{ coinId, amount }]
|
|
1847
|
+
});
|
|
1848
|
+
await deps.mintJournal.removeByKey(mintId);
|
|
1849
|
+
deps.noteHeldState(token.blob.tokenId, (await engine.deliveryKeys(bytes)).stateHash);
|
|
1850
|
+
deps.refreshView();
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
// modules/payments-v2/send-coinless.ts
|
|
1854
|
+
async function materializeCoinlessSpend(deps, input) {
|
|
1855
|
+
const { engine } = deps;
|
|
1856
|
+
const tokenId = input.request.tokenId;
|
|
1857
|
+
const bytes = (await deps.storagePort.getBlobs([tokenId])).get(tokenId);
|
|
1858
|
+
if (bytes === void 0) {
|
|
1859
|
+
throw new SphereError(`Selected source ${tokenId} has no blob in storage`, "STORAGE_ERROR");
|
|
1860
|
+
}
|
|
1861
|
+
const token = await engine.decodeToken({ tokenId, token: bytes });
|
|
1862
|
+
if (!isCoinlessEnvelope(token.valueEnvelope)) {
|
|
1863
|
+
throw new SphereError(
|
|
1864
|
+
`Token ${tokenId} carries coin value and cannot be sent with sendCoinless \u2014 use send()`,
|
|
1865
|
+
"VALIDATION_ERROR"
|
|
1866
|
+
);
|
|
1867
|
+
}
|
|
1868
|
+
const keys = await engine.deliveryKeys(bytes);
|
|
1869
|
+
const payload = buildCoinlessPayload(input.recipientPubkey, input.request, {
|
|
1870
|
+
[tokenId]: { local: keys.stateHash, protocol: keys.stateHash }
|
|
1871
|
+
});
|
|
1872
|
+
const plan = {
|
|
1873
|
+
transferId: input.transferId,
|
|
1874
|
+
recipientPubkey: input.recipientPubkey,
|
|
1875
|
+
payload,
|
|
1876
|
+
ops: buildOps(payload),
|
|
1877
|
+
sources: /* @__PURE__ */ new Map([[tokenId, token]]),
|
|
1878
|
+
...input.request.memo !== void 0 ? { memo: input.request.memo } : {}
|
|
1879
|
+
};
|
|
1880
|
+
return { transferId: input.transferId, coinId: "", plan, sourceIds: input.sourceIds, sourceTokens: [] };
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
// modules/payments-v2/send-errors.ts
|
|
1884
|
+
function partialize(err, run) {
|
|
1885
|
+
if (run.delivered.length === 0) return err;
|
|
1886
|
+
return new PartialSendConflictError(
|
|
1887
|
+
"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.",
|
|
1888
|
+
run.firstPartialId ?? "",
|
|
1889
|
+
[...run.delivered],
|
|
1890
|
+
run.amount,
|
|
1891
|
+
err
|
|
1892
|
+
);
|
|
1893
|
+
}
|
|
1894
|
+
function stampTransferId(err, transferId) {
|
|
1895
|
+
if (err instanceof SphereError && isPossiblyCommittedSendOutcome(err) && err.transferId === void 0) {
|
|
1896
|
+
err.transferId = transferId;
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1623
1900
|
// modules/payments-v2/recipient.ts
|
|
1624
1901
|
var ATTENTION_RECIPIENT_NETWORK_UNVERIFIED = "recipient:network-unverified";
|
|
1625
1902
|
async function requireSameNetworkRecipient(deps, identifier) {
|
|
@@ -1668,11 +1945,6 @@ async function splitOpIndex(deps, envelope) {
|
|
|
1668
1945
|
return Array.isArray(payload.direct) ? payload.direct.length : 0;
|
|
1669
1946
|
}
|
|
1670
1947
|
|
|
1671
|
-
// modules/payments-v2/mint-params.ts
|
|
1672
|
-
function mintParams(recipientPubkey, coinId, amount) {
|
|
1673
|
-
return { recipientPubkey, value: { assets: [{ coinId, amount }] } };
|
|
1674
|
-
}
|
|
1675
|
-
|
|
1676
1948
|
// modules/payments-v2/prewarm-cache.ts
|
|
1677
1949
|
var PrewarmCache = class _PrewarmCache {
|
|
1678
1950
|
entries = /* @__PURE__ */ new Map();
|
|
@@ -1846,29 +2118,6 @@ function transferringToken(tokenId, coinId, amount, registry, now) {
|
|
|
1846
2118
|
});
|
|
1847
2119
|
}
|
|
1848
2120
|
|
|
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
2121
|
// node_modules/@noble/ciphers/utils.js
|
|
1873
2122
|
function isBytes2(a) {
|
|
1874
2123
|
return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array" && "BYTES_PER_ELEMENT" in a && a.BYTES_PER_ELEMENT === 1;
|
|
@@ -2720,6 +2969,14 @@ function decryptField(key, envelope) {
|
|
|
2720
2969
|
}
|
|
2721
2970
|
|
|
2722
2971
|
// modules/payments-v2/history/History.ts
|
|
2972
|
+
function wireAssets(assets) {
|
|
2973
|
+
return assets.map((a) => ({ coinId: a.coinId, amount: a.amount }));
|
|
2974
|
+
}
|
|
2975
|
+
function isPermanentReject(err) {
|
|
2976
|
+
const status = err?.status;
|
|
2977
|
+
if (typeof status !== "number") return false;
|
|
2978
|
+
return status >= 400 && status < 500 && status !== 408 && status !== 429;
|
|
2979
|
+
}
|
|
2723
2980
|
var WIRE_TOKEN_ID = /^(?:[0-9a-f]{2}){1,64}$/;
|
|
2724
2981
|
var WIRE_PUBKEY = /^0[23][0-9a-f]{64}$/;
|
|
2725
2982
|
var History = class {
|
|
@@ -2744,7 +3001,7 @@ var History = class {
|
|
|
2744
3001
|
dedupKey: input.transferId,
|
|
2745
3002
|
id: (this.deps.newId ?? randomUUID)(),
|
|
2746
3003
|
type: "SENT",
|
|
2747
|
-
assets:
|
|
3004
|
+
assets: wireAssets(input.assets),
|
|
2748
3005
|
ts: this.ts(input.timestamp),
|
|
2749
3006
|
transferId: input.transferId,
|
|
2750
3007
|
...this.wireTokenId(input.tokenId),
|
|
@@ -2757,7 +3014,7 @@ var History = class {
|
|
|
2757
3014
|
dedupKey: `RECEIVED:${input.tokenId.toLowerCase()}:${input.stateHash.toLowerCase()}`,
|
|
2758
3015
|
id: (this.deps.newId ?? randomUUID)(),
|
|
2759
3016
|
type: "RECEIVED",
|
|
2760
|
-
assets:
|
|
3017
|
+
assets: wireAssets(input.assets),
|
|
2761
3018
|
ts: this.ts(input.timestamp),
|
|
2762
3019
|
...input.transferId !== void 0 ? { transferId: input.transferId } : {},
|
|
2763
3020
|
...this.wireTokenId(input.tokenId),
|
|
@@ -2770,7 +3027,7 @@ var History = class {
|
|
|
2770
3027
|
dedupKey: `MINT:${input.tokenId.toLowerCase()}`,
|
|
2771
3028
|
id: (this.deps.newId ?? randomUUID)(),
|
|
2772
3029
|
type: "MINT",
|
|
2773
|
-
assets:
|
|
3030
|
+
assets: wireAssets(input.assets),
|
|
2774
3031
|
ts: this.ts(input.timestamp),
|
|
2775
3032
|
...this.wireTokenId(input.tokenId)
|
|
2776
3033
|
}));
|
|
@@ -2781,7 +3038,10 @@ var History = class {
|
|
|
2781
3038
|
try {
|
|
2782
3039
|
await this.deps.client.postHistory([record]);
|
|
2783
3040
|
} catch (err) {
|
|
2784
|
-
this.deps.log?.(
|
|
3041
|
+
this.deps.log?.(
|
|
3042
|
+
isPermanentReject(err) ? "history POST REJECTED \u2014 the record shape is refused; no retry can fix it" : "history POST failed (dedupKey makes retry safe)",
|
|
3043
|
+
err
|
|
3044
|
+
);
|
|
2785
3045
|
return;
|
|
2786
3046
|
}
|
|
2787
3047
|
this.deps.emit("history:updated", this.toEntry(record));
|
|
@@ -2848,6 +3108,13 @@ var CURSOR_KEY = STORE_KEYS.streamCursor("inventory");
|
|
|
2848
3108
|
function stateKey(tokenId, stateHash) {
|
|
2849
3109
|
return `${tokenId}:${stateHash}`;
|
|
2850
3110
|
}
|
|
3111
|
+
function unchanged(prev, item, tokenType) {
|
|
3112
|
+
return prev.seq === item.seq && prev.status === item.status && prev.stateHash === item.stateHash && tokenType === prev.tokenType;
|
|
3113
|
+
}
|
|
3114
|
+
function isCoinless(item, prev) {
|
|
3115
|
+
if (item.status !== "active") return prev?.coinless ?? false;
|
|
3116
|
+
return (item.assets?.length ?? 0) === 0;
|
|
3117
|
+
}
|
|
2851
3118
|
var InventoryView = class {
|
|
2852
3119
|
constructor(deps) {
|
|
2853
3120
|
this.deps = deps;
|
|
@@ -2941,14 +3208,26 @@ var InventoryView = class {
|
|
|
2941
3208
|
pool(coinId) {
|
|
2942
3209
|
const out = [];
|
|
2943
3210
|
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;
|
|
3211
|
+
if (!this.isSpendable(tokenId, entry)) continue;
|
|
2947
3212
|
const asset = entry.assets.find((a) => a.coinId === coinId);
|
|
2948
3213
|
if (asset) out.push({ tokenId, amount: BigInt(asset.amount) });
|
|
2949
3214
|
}
|
|
2950
3215
|
return out;
|
|
2951
3216
|
}
|
|
3217
|
+
/**
|
|
3218
|
+
* The eligibility gates every spend shares, coin or token — ONE definition, so a
|
|
3219
|
+
* single probe covers both verbs and neither can drift from the other.
|
|
3220
|
+
*/
|
|
3221
|
+
isSpendable(tokenId, entry) {
|
|
3222
|
+
if (entry.status !== "active") return false;
|
|
3223
|
+
if (this.inFlight.has(tokenId) && !this.pinned(tokenId)) return false;
|
|
3224
|
+
return !this.suspected.has(stateKey(tokenId, entry.stateHash));
|
|
3225
|
+
}
|
|
3226
|
+
/** #777: is this NAMED token a spendable COINLESS holding? Never a coin source. */
|
|
3227
|
+
spendableCoinless(tokenId) {
|
|
3228
|
+
const entry = this.mirror.get(tokenId);
|
|
3229
|
+
return entry !== void 0 && entry.coinless && this.isSpendable(tokenId, entry);
|
|
3230
|
+
}
|
|
2952
3231
|
/**
|
|
2953
3232
|
* Current mirrored state of an ACTIVE token, or undefined when it is unknown
|
|
2954
3233
|
* or tombstoned. Exists so a cached blob can be state-scoped (F6): a blob is
|
|
@@ -2974,6 +3253,29 @@ var InventoryView = class {
|
|
|
2974
3253
|
}
|
|
2975
3254
|
return out;
|
|
2976
3255
|
}
|
|
3256
|
+
/**
|
|
3257
|
+
* Coinless holdings (#777) — DISJOINT from tokens(): an entry is coinless
|
|
3258
|
+
* exactly when it is not, so no mirror row can appear in both reads.
|
|
3259
|
+
*/
|
|
3260
|
+
coinless(registry) {
|
|
3261
|
+
const out = [];
|
|
3262
|
+
for (const [tokenId, entry] of this.mirror) {
|
|
3263
|
+
if (entry.status !== "active" || !entry.coinless) continue;
|
|
3264
|
+
const meta = entry.tokenType !== void 0 ? registry.getTypeMeta?.(entry.tokenType) : null;
|
|
3265
|
+
out.push({
|
|
3266
|
+
tokenId,
|
|
3267
|
+
...entry.tokenType !== void 0 ? { tokenType: entry.tokenType } : {},
|
|
3268
|
+
...meta ? { name: meta.name } : {},
|
|
3269
|
+
...meta?.iconUrl != null ? { iconUrl: meta.iconUrl } : {},
|
|
3270
|
+
stateHash: entry.stateHash,
|
|
3271
|
+
transferring: this.held(tokenId),
|
|
3272
|
+
...this.suspected.has(stateKey(tokenId, entry.stateHash)) ? { suspectedSpent: true } : {},
|
|
3273
|
+
createdAt: entry.createdAt,
|
|
3274
|
+
updatedAt: entry.updatedAt
|
|
3275
|
+
});
|
|
3276
|
+
}
|
|
3277
|
+
return out;
|
|
3278
|
+
}
|
|
2977
3279
|
async assets(registry, price) {
|
|
2978
3280
|
const raw = aggregateAssets(this.activeEntries(), (tokenId) => this.held(tokenId), registry);
|
|
2979
3281
|
if (!price || raw.length === 0) return raw;
|
|
@@ -3045,14 +3347,19 @@ var InventoryView = class {
|
|
|
3045
3347
|
applyOne(item, now) {
|
|
3046
3348
|
const prev = this.mirror.get(item.tokenId);
|
|
3047
3349
|
if (prev && item.seq < prev.seq) return false;
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
}
|
|
3350
|
+
const tokenType = item.tokenType ?? prev?.tokenType;
|
|
3351
|
+
if (prev && unchanged(prev, item, tokenType)) return false;
|
|
3051
3352
|
this.mirror.set(item.tokenId, {
|
|
3052
3353
|
stateHash: item.stateHash,
|
|
3053
3354
|
seq: item.seq,
|
|
3054
3355
|
status: item.status,
|
|
3055
|
-
|
|
3356
|
+
// ONLY a tombstone inherits: it omits assets for an unrelated reason and
|
|
3357
|
+
// recoverRemoved must know the amount it restores. An ACTIVE row that omits
|
|
3358
|
+
// them is stating coinlessness, and inheriting there would keep stale assets
|
|
3359
|
+
// in tokens() while `coinless` is true — the row in BOTH reads (§16).
|
|
3360
|
+
assets: item.assets ?? (item.status === "removed" ? prev?.assets ?? [] : []),
|
|
3361
|
+
coinless: isCoinless(item, prev),
|
|
3362
|
+
...tokenType !== void 0 ? { tokenType } : {},
|
|
3056
3363
|
createdAt: prev?.createdAt ?? now,
|
|
3057
3364
|
updatedAt: now
|
|
3058
3365
|
});
|
|
@@ -3283,7 +3590,12 @@ async function screen(deps, engine, entry) {
|
|
|
3283
3590
|
}
|
|
3284
3591
|
return {
|
|
3285
3592
|
kind: "accept",
|
|
3286
|
-
record: {
|
|
3593
|
+
record: {
|
|
3594
|
+
tokenId: keys.tokenId,
|
|
3595
|
+
stateHash: keys.stateHash,
|
|
3596
|
+
assets: toAssetAmounts(token),
|
|
3597
|
+
...isCoinlessEnvelope2(token.valueEnvelope) ? { tokenType: token.tokenType } : {}
|
|
3598
|
+
}
|
|
3287
3599
|
};
|
|
3288
3600
|
}
|
|
3289
3601
|
async function announce(deps, entry, record) {
|
|
@@ -3294,6 +3606,7 @@ async function announce(deps, entry, record) {
|
|
|
3294
3606
|
tokenId: record.tokenId,
|
|
3295
3607
|
stateHash: record.stateHash,
|
|
3296
3608
|
assets: record.assets,
|
|
3609
|
+
...record.tokenType !== void 0 ? { tokenType: record.tokenType } : {},
|
|
3297
3610
|
...entry.senderPubkey !== void 0 ? { senderPubkey: entry.senderPubkey } : {},
|
|
3298
3611
|
...entry.senderNametag !== void 0 ? { senderNametag: entry.senderNametag } : {},
|
|
3299
3612
|
...entry.memo !== void 0 ? { memo: entry.memo } : {},
|
|
@@ -3307,6 +3620,19 @@ async function announce(deps, entry, record) {
|
|
|
3307
3620
|
senderPubkey: entry.senderPubkey ?? "",
|
|
3308
3621
|
...entry.senderNametag !== void 0 ? { senderNametag: entry.senderNametag } : {},
|
|
3309
3622
|
tokens: record.assets.map((asset) => toUiToken(record.tokenId, asset, deps.registry, receivedAt)),
|
|
3623
|
+
// #777: named here rather than mapped from assets, which announced an EMPTY list.
|
|
3624
|
+
...record.tokenType !== void 0 ? {
|
|
3625
|
+
coinless: [
|
|
3626
|
+
{
|
|
3627
|
+
tokenId: record.tokenId,
|
|
3628
|
+
tokenType: record.tokenType,
|
|
3629
|
+
stateHash: record.stateHash,
|
|
3630
|
+
transferring: false,
|
|
3631
|
+
createdAt: receivedAt,
|
|
3632
|
+
updatedAt: receivedAt
|
|
3633
|
+
}
|
|
3634
|
+
]
|
|
3635
|
+
} : {},
|
|
3310
3636
|
...entry.memo !== void 0 ? { memo: entry.memo } : {},
|
|
3311
3637
|
receivedAt
|
|
3312
3638
|
};
|
|
@@ -3405,6 +3731,9 @@ function claimAck(entry) {
|
|
|
3405
3731
|
function rejectAck(entry, reason) {
|
|
3406
3732
|
return { deliveryId: entry.deliveryId, disposition: "rejected", reason, cursor: entry.cursor };
|
|
3407
3733
|
}
|
|
3734
|
+
function isCoinlessEnvelope2(envelope) {
|
|
3735
|
+
return envelope.startsWith("none_");
|
|
3736
|
+
}
|
|
3408
3737
|
function toAssetAmounts(token) {
|
|
3409
3738
|
return (token.value?.assets ?? []).map((asset) => ({
|
|
3410
3739
|
coinId: asset.coinId,
|
|
@@ -3972,6 +4301,37 @@ var SpendQueue = class {
|
|
|
3972
4301
|
}
|
|
3973
4302
|
return { kind: "queued", settled: this.enqueue(reservationId, request.coinId, amount) };
|
|
3974
4303
|
}
|
|
4304
|
+
/**
|
|
4305
|
+
* #777: reserve a NAMED coinless source. Never queues — there is nothing to wait
|
|
4306
|
+
* for. A coin plan waits because some OTHER combination may free up; a named
|
|
4307
|
+
* token is either free now or held by a reservation that only its own transfer
|
|
4308
|
+
* can release, so queueing would block until a timeout on a spend that cannot
|
|
4309
|
+
* become possible.
|
|
4310
|
+
*/
|
|
4311
|
+
planCoinless(reservationId, tokenId) {
|
|
4312
|
+
if (this.destroyed) {
|
|
4313
|
+
throw new SphereError("Module has been destroyed", "MODULE_DESTROYED");
|
|
4314
|
+
}
|
|
4315
|
+
const unproven = this.deps.ledger.unprovenReason();
|
|
4316
|
+
if (unproven !== null) {
|
|
4317
|
+
throw new SphereError(`Cannot spend yet: ${unproven}`, "SEND_SYNC_PENDING");
|
|
4318
|
+
}
|
|
4319
|
+
if (this.deps.spendableCoinless?.(tokenId) !== true) {
|
|
4320
|
+
throw new SphereError(
|
|
4321
|
+
`Token ${tokenId} is not a spendable coinless holding`,
|
|
4322
|
+
"VALIDATION_ERROR"
|
|
4323
|
+
);
|
|
4324
|
+
}
|
|
4325
|
+
const holder = this.deps.ledger.holderOf(tokenId);
|
|
4326
|
+
if (holder !== void 0) {
|
|
4327
|
+
throw new SphereError(
|
|
4328
|
+
`Token ${tokenId} is already reserved by transfer ${holder}`,
|
|
4329
|
+
"VALIDATION_ERROR"
|
|
4330
|
+
);
|
|
4331
|
+
}
|
|
4332
|
+
this.deps.ledger.reserve(reservationId, [{ tokenId }]);
|
|
4333
|
+
return { reservationId, plan: { direct: [tokenId] } };
|
|
4334
|
+
}
|
|
3975
4335
|
/**
|
|
3976
4336
|
* The sources `plan()` WOULD pick right now, reserving nothing and queueing
|
|
3977
4337
|
* nothing — a read-only probe used to warm blobs while the user is still
|
|
@@ -4161,6 +4521,7 @@ function composeFacadeParts(deps, hooks) {
|
|
|
4161
4521
|
const queue = new SpendQueue({
|
|
4162
4522
|
ledger,
|
|
4163
4523
|
getPool: (coinId) => view.pool(coinId),
|
|
4524
|
+
spendableCoinless: (tokenId) => view.spendableCoinless(tokenId),
|
|
4164
4525
|
...deps.workBudget !== void 0 ? { workBudget: deps.workBudget } : {}
|
|
4165
4526
|
});
|
|
4166
4527
|
const historyStore = new History({
|
|
@@ -4244,10 +4605,9 @@ function buildMachineDeps(deps, hooks, historyStore) {
|
|
|
4244
4605
|
recordHistory: async ({ transferId, payload, committedAmount }) => {
|
|
4245
4606
|
await historyStore.recordSent({
|
|
4246
4607
|
transferId,
|
|
4247
|
-
|
|
4248
|
-
//
|
|
4249
|
-
|
|
4250
|
-
amount: committedAmount,
|
|
4608
|
+
// §5.9: the SETTLED amount, never payload.amount — the plan. A token
|
|
4609
|
+
// spend moved no coin: `assets: []` + tokenId (wallet-api#151 / §10).
|
|
4610
|
+
...payload.kind === "coinless" ? { assets: [], tokenId: payload.direct[0] } : { assets: [{ coinId: payload.coinId, amount: committedAmount }] },
|
|
4251
4611
|
recipientPubkey: payload.recipient,
|
|
4252
4612
|
...payload.memo !== void 0 ? { memo: payload.memo } : {}
|
|
4253
4613
|
});
|
|
@@ -4282,12 +4642,10 @@ function buildReceive(deps, hooks, historyStore, heldStates, refreshView) {
|
|
|
4282
4642
|
});
|
|
4283
4643
|
}
|
|
4284
4644
|
async function recordReceived(historyStore, record) {
|
|
4285
|
-
const first = record.assets[0];
|
|
4286
4645
|
await historyStore.recordReceived({
|
|
4287
4646
|
tokenId: record.tokenId,
|
|
4288
4647
|
stateHash: record.stateHash,
|
|
4289
|
-
|
|
4290
|
-
amount: first?.amount ?? "0",
|
|
4648
|
+
assets: record.assets,
|
|
4291
4649
|
...record.senderPubkey !== void 0 ? { senderPubkey: record.senderPubkey } : {},
|
|
4292
4650
|
...record.senderNametag !== void 0 ? { senderNametag: record.senderNametag } : {},
|
|
4293
4651
|
...record.memo !== void 0 ? { memo: record.memo } : {},
|
|
@@ -4435,6 +4793,13 @@ var PaymentsFacade = class {
|
|
|
4435
4793
|
tokens(filter) {
|
|
4436
4794
|
return this.view.tokens(this.deps.registry, filter);
|
|
4437
4795
|
}
|
|
4796
|
+
coinless() {
|
|
4797
|
+
return this.view.coinless(this.deps.registry);
|
|
4798
|
+
}
|
|
4799
|
+
tokenData(tokenId) {
|
|
4800
|
+
const deps = { engine: this.engine(), view: this.view, storagePort: this.deps.storagePort };
|
|
4801
|
+
return readTokenData(deps, tokenId);
|
|
4802
|
+
}
|
|
4438
4803
|
history(page) {
|
|
4439
4804
|
return this.historyStore.page(page ?? {});
|
|
4440
4805
|
}
|
|
@@ -4459,6 +4824,9 @@ var PaymentsFacade = class {
|
|
|
4459
4824
|
send(request) {
|
|
4460
4825
|
return this.track(this.sendOutcome(request));
|
|
4461
4826
|
}
|
|
4827
|
+
sendCoinless(request) {
|
|
4828
|
+
return this.track(this.sendCoinlessOutcome(request));
|
|
4829
|
+
}
|
|
4462
4830
|
async receive() {
|
|
4463
4831
|
const transfers = await this.track(this.receiveLoop.drainOnce());
|
|
4464
4832
|
return { transfers };
|
|
@@ -4489,12 +4857,32 @@ var PaymentsFacade = class {
|
|
|
4489
4857
|
nowMs() {
|
|
4490
4858
|
return (this.deps.now ?? Date.now)();
|
|
4491
4859
|
}
|
|
4860
|
+
mintDeps() {
|
|
4861
|
+
return {
|
|
4862
|
+
engine: this.engine(),
|
|
4863
|
+
mintJournal: this.machineStores.mintJournal,
|
|
4864
|
+
storagePort: this.deps.storagePort,
|
|
4865
|
+
recordMint: (input) => this.historyStore.recordMint(input),
|
|
4866
|
+
armHeartbeat: () => this.heartbeat.arm(),
|
|
4867
|
+
noteHeldState: (tokenId, stateHash) => void this.heldStates.set(tokenId, stateHash),
|
|
4868
|
+
refreshView: () => this.trackTail(this.view.delta()),
|
|
4869
|
+
ownPubkeyBytes: this.ownPubkeyBytes,
|
|
4870
|
+
now: () => this.nowMs()
|
|
4871
|
+
};
|
|
4872
|
+
}
|
|
4492
4873
|
// ── send policy (§5.5 + old-loop parity; the machine stays policy-free) ────
|
|
4493
4874
|
/** The ONE place a send() outcome is shaped: success emits in finishSend, a
|
|
4494
4875
|
* CLEAN rejection emits `transfer:updated{status:'failed'}` here (§4). */
|
|
4495
|
-
|
|
4876
|
+
sendOutcome(request) {
|
|
4877
|
+
return this.runJob({ kind: "coin", request }, request.amount);
|
|
4878
|
+
}
|
|
4879
|
+
/** A token spend has no amount; '0' keeps the shortfall arithmetic total-free. */
|
|
4880
|
+
sendCoinlessOutcome(request) {
|
|
4881
|
+
return this.runJob({ kind: "coinless", request }, "0");
|
|
4882
|
+
}
|
|
4883
|
+
async runJob(job, amount) {
|
|
4496
4884
|
const run = {
|
|
4497
|
-
amount
|
|
4885
|
+
amount,
|
|
4498
4886
|
delivered: [],
|
|
4499
4887
|
sentTokens: [],
|
|
4500
4888
|
tokenTransfers: [],
|
|
@@ -4504,7 +4892,7 @@ var PaymentsFacade = class {
|
|
|
4504
4892
|
lastTransferId: ""
|
|
4505
4893
|
};
|
|
4506
4894
|
try {
|
|
4507
|
-
return await this.sendWithPolicy(
|
|
4895
|
+
return await this.sendWithPolicy(job, run);
|
|
4508
4896
|
} catch (err) {
|
|
4509
4897
|
this.emitCleanFailure(err, run.lastTransferId);
|
|
4510
4898
|
throw err;
|
|
@@ -4524,19 +4912,19 @@ var PaymentsFacade = class {
|
|
|
4524
4912
|
};
|
|
4525
4913
|
this.deps.emit("transfer:updated", failed);
|
|
4526
4914
|
}
|
|
4527
|
-
async sendWithPolicy(
|
|
4528
|
-
const recipient = await requireSameNetworkRecipient(this.deps, request.recipient);
|
|
4915
|
+
async sendWithPolicy(job, run) {
|
|
4916
|
+
const recipient = await requireSameNetworkRecipient(this.deps, job.request.recipient);
|
|
4529
4917
|
for (let attempt = 0; ; attempt++) {
|
|
4530
4918
|
let ctx;
|
|
4531
4919
|
try {
|
|
4532
|
-
ctx = await this.planAndMaterialize(recipient.chainPubkey,
|
|
4920
|
+
ctx = await this.planAndMaterialize(recipient.chainPubkey, job, run);
|
|
4533
4921
|
} catch (err) {
|
|
4534
|
-
throw
|
|
4922
|
+
throw partialize(err, run);
|
|
4535
4923
|
}
|
|
4536
4924
|
const disposition = await this.runAttempt(ctx);
|
|
4537
|
-
if (disposition.kind === "rethrow") throw
|
|
4925
|
+
if (disposition.kind === "rethrow") throw partialize(disposition.error, run);
|
|
4538
4926
|
if (disposition.kind === "retry-full") {
|
|
4539
|
-
if (attempt >= MAX_RESELECT) throw
|
|
4927
|
+
if (job.kind === "coinless" || attempt >= MAX_RESELECT) throw partialize(disposition.error, run);
|
|
4540
4928
|
continue;
|
|
4541
4929
|
}
|
|
4542
4930
|
if (disposition.kind === "success") {
|
|
@@ -4592,7 +4980,7 @@ var PaymentsFacade = class {
|
|
|
4592
4980
|
async disposeFailedAttempt(ctx, err) {
|
|
4593
4981
|
if (isPossiblyCommittedSendOutcome(err)) {
|
|
4594
4982
|
this.settleKeepOpen(ctx);
|
|
4595
|
-
|
|
4983
|
+
stampTransferId(err, ctx.transferId);
|
|
4596
4984
|
return { kind: "rethrow", error: err };
|
|
4597
4985
|
}
|
|
4598
4986
|
const backstop = await this.machineStores.backstop.getByKey(ctx.transferId);
|
|
@@ -4626,7 +5014,7 @@ var PaymentsFacade = class {
|
|
|
4626
5014
|
async disposeConvergeFailure(ctx, err) {
|
|
4627
5015
|
if (isPossiblyCommittedSendOutcome(err)) {
|
|
4628
5016
|
this.settleKeepOpen(ctx);
|
|
4629
|
-
|
|
5017
|
+
stampTransferId(err, ctx.transferId);
|
|
4630
5018
|
return { kind: "rethrow", error: err };
|
|
4631
5019
|
}
|
|
4632
5020
|
if (classifyError(err) === "conflict") {
|
|
@@ -4637,9 +5025,23 @@ var PaymentsFacade = class {
|
|
|
4637
5025
|
}
|
|
4638
5026
|
return { kind: "rethrow", error: err };
|
|
4639
5027
|
}
|
|
4640
|
-
async planAndMaterialize(recipientPubkey,
|
|
5028
|
+
async planAndMaterialize(recipientPubkey, job, run) {
|
|
4641
5029
|
const transferId = this.newId();
|
|
4642
5030
|
run.lastTransferId = transferId;
|
|
5031
|
+
if (job.kind === "coinless") {
|
|
5032
|
+
const spend2 = this.queue.planCoinless(transferId, job.request.tokenId);
|
|
5033
|
+
const sourceIds2 = this.markPlanned(transferId, "", spend2);
|
|
5034
|
+
try {
|
|
5035
|
+
return await materializeCoinlessSpend(
|
|
5036
|
+
{ engine: this.engine(), storagePort: this.deps.storagePort },
|
|
5037
|
+
{ transferId, recipientPubkey, request: job.request, sourceIds: sourceIds2 }
|
|
5038
|
+
);
|
|
5039
|
+
} catch (err) {
|
|
5040
|
+
this.settleFailure(transferId, "", sourceIds2);
|
|
5041
|
+
throw err;
|
|
5042
|
+
}
|
|
5043
|
+
}
|
|
5044
|
+
const request = { ...job.request, amount: run.amount };
|
|
4643
5045
|
const planned = this.queue.plan(transferId, { coinId: request.coinId, amount: request.amount });
|
|
4644
5046
|
const spend = planned.kind === "planned" ? planned.spend : await planned.settled;
|
|
4645
5047
|
const sourceIds = this.markPlanned(transferId, request.coinId, spend);
|
|
@@ -4773,22 +5175,6 @@ var PaymentsFacade = class {
|
|
|
4773
5175
|
return result;
|
|
4774
5176
|
}
|
|
4775
5177
|
/** 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
5178
|
async softAbort(transferId) {
|
|
4793
5179
|
try {
|
|
4794
5180
|
await this.deps.client.abortIntent(transferId);
|
|
@@ -4804,57 +5190,11 @@ var PaymentsFacade = class {
|
|
|
4804
5190
|
const mintId = this.newId();
|
|
4805
5191
|
this.activeMoneyOps.add(mintId);
|
|
4806
5192
|
try {
|
|
4807
|
-
return await this.
|
|
5193
|
+
return await runMintUnderJournal(this.mintDeps(), { mintId, coinId, amount });
|
|
4808
5194
|
} finally {
|
|
4809
5195
|
this.activeMoneyOps.delete(mintId);
|
|
4810
5196
|
}
|
|
4811
5197
|
}
|
|
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
5198
|
/** @returns how many journal entries were RESOLVED (cleared) — heartbeat progress. */
|
|
4859
5199
|
async replayMints() {
|
|
4860
5200
|
let resolved = 0;
|
|
@@ -4886,7 +5226,7 @@ var PaymentsFacade = class {
|
|
|
4886
5226
|
if (token.blob.tokenId !== entry.tokenId) {
|
|
4887
5227
|
await this.machineStores.mintJournal.upsert({ ...entry, tokenId: token.blob.tokenId });
|
|
4888
5228
|
}
|
|
4889
|
-
await this.
|
|
5229
|
+
await finalizeMint(this.mintDeps(), entry.mintId, token, entry.coinId, entry.amount);
|
|
4890
5230
|
return true;
|
|
4891
5231
|
}
|
|
4892
5232
|
async tokenInServerInventory(tokenId) {
|