@unicitylabs/sphere-sdk 0.18.0-dev.2 → 0.18.0-dev.3
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/chunks/{chunk-OAAY2JEA.js → chunk-7BZL7HE2.js} +3 -3
- package/dist/connect/chunks/{chunk-MQQ3TMHM.js → chunk-CJ4D6M6W.js} +2 -2
- package/dist/connect/chunks/{chunk-FGQ6CKKK.js → chunk-LDEOQNG2.js} +2 -2
- package/dist/connect/chunks/{chunk-FGQ6CKKK.js.map → chunk-LDEOQNG2.js.map} +1 -1
- package/dist/connect/chunks/{chunk-3CYK5SIU.js → chunk-WCKTUB4V.js} +1 -1
- package/dist/connect/chunks/chunk-WCKTUB4V.js.map +1 -0
- package/dist/connect/chunks/{chunk-JYBI36GK.js → chunk-WL6VYP7G.js} +2 -2
- package/dist/connect/index.cjs +1 -1
- package/dist/connect/index.cjs.map +1 -1
- package/dist/connect/index.js +5 -5
- package/dist/connect/internal/host.js +3 -3
- package/dist/connect/internal/nft-wire.js +2 -2
- package/dist/core/index.cjs +831 -343
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +30 -1
- package/dist/core/index.d.ts +30 -1
- package/dist/core/index.js +831 -343
- package/dist/core/index.js.map +1 -1
- package/dist/impl/browser/connect/index.cjs +1 -1
- package/dist/impl/browser/connect/index.cjs.map +1 -1
- package/dist/impl/browser/connect/index.js +2 -2
- package/dist/impl/browser/index.cjs.map +1 -1
- package/dist/impl/browser/index.js.map +1 -1
- package/dist/impl/nodejs/index.cjs.map +1 -1
- package/dist/impl/nodejs/index.d.cts +1 -0
- package/dist/impl/nodejs/index.d.ts +1 -0
- package/dist/impl/nodejs/index.js.map +1 -1
- package/dist/impl/shared/wallet-api/index.d.cts +1 -0
- package/dist/impl/shared/wallet-api/index.d.ts +1 -0
- package/dist/impl/wallet-api-v2/index.cjs +2 -1
- package/dist/impl/wallet-api-v2/index.cjs.map +1 -1
- package/dist/impl/wallet-api-v2/index.d.cts +3 -0
- package/dist/impl/wallet-api-v2/index.d.ts +3 -0
- package/dist/impl/wallet-api-v2/index.js +2 -1
- package/dist/impl/wallet-api-v2/index.js.map +1 -1
- package/dist/index.cjs +833 -343
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +32 -2
- package/dist/index.d.ts +32 -2
- package/dist/index.js +832 -343
- package/dist/index.js.map +1 -1
- package/dist/modules/payments-v2/index.cjs +658 -259
- package/dist/modules/payments-v2/index.cjs.map +1 -1
- package/dist/modules/payments-v2/index.d.cts +65 -2
- package/dist/modules/payments-v2/index.d.ts +65 -2
- package/dist/modules/payments-v2/index.js +656 -259
- package/dist/modules/payments-v2/index.js.map +1 -1
- package/dist/token-engine/index.cjs +185 -65
- package/dist/token-engine/index.cjs.map +1 -1
- package/dist/token-engine/index.d.cts +20 -3
- package/dist/token-engine/index.d.ts +20 -3
- package/dist/token-engine/index.js +184 -65
- package/dist/token-engine/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/connect/chunks/chunk-3CYK5SIU.js.map +0 -1
- /package/dist/connect/chunks/{chunk-OAAY2JEA.js.map → chunk-7BZL7HE2.js.map} +0 -0
- /package/dist/connect/chunks/{chunk-MQQ3TMHM.js.map → chunk-CJ4D6M6W.js.map} +0 -0
- /package/dist/connect/chunks/{chunk-JYBI36GK.js.map → chunk-WL6VYP7G.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -9529,6 +9529,150 @@ async function deriveDirectAddress(publicKey) {
|
|
|
9529
9529
|
return `DIRECT://${HexConverter.encode(imprint)}${HexConverter.encode(checksum)}`;
|
|
9530
9530
|
}
|
|
9531
9531
|
|
|
9532
|
+
// token-engine/claims.ts
|
|
9533
|
+
var CoinClaims = class _CoinClaims {
|
|
9534
|
+
issuers = /* @__PURE__ */ new Map();
|
|
9535
|
+
policies = [];
|
|
9536
|
+
static fromPlugins(plugins) {
|
|
9537
|
+
const claims = new _CoinClaims();
|
|
9538
|
+
for (const plugin of plugins ?? []) {
|
|
9539
|
+
for (const policy of plugin.tokenIssuancePolicies ?? []) claims.add(policy);
|
|
9540
|
+
}
|
|
9541
|
+
return claims;
|
|
9542
|
+
}
|
|
9543
|
+
add(policy) {
|
|
9544
|
+
const tokenType = HexConverter.encode(policy.tokenType.bytes);
|
|
9545
|
+
const coinIds = policy.coinIds.map((coinId) => coinId.toLowerCase());
|
|
9546
|
+
const taken = coinIds.find((coinId) => this.issuers.has(coinId));
|
|
9547
|
+
if (taken !== void 0) throw new Error(`Coin ${taken} is already issued by token type ${this.issuers.get(taken)}.`);
|
|
9548
|
+
for (const coinId of coinIds) this.issuers.set(coinId, tokenType);
|
|
9549
|
+
this.policies.push(JSON.stringify([tokenType, [...coinIds].sort(), policy.revision ?? ""]));
|
|
9550
|
+
}
|
|
9551
|
+
fingerprint() {
|
|
9552
|
+
return [...this.policies].sort().join("\n");
|
|
9553
|
+
}
|
|
9554
|
+
issuerOf(coinId) {
|
|
9555
|
+
return this.issuers.get(coinId.toLowerCase()) ?? null;
|
|
9556
|
+
}
|
|
9557
|
+
};
|
|
9558
|
+
|
|
9559
|
+
// token-engine/errors.ts
|
|
9560
|
+
var TransferConflictError = class extends SphereError {
|
|
9561
|
+
/**
|
|
9562
|
+
* #625: the source token id (the caller's id, e.g. `v2_<genesis>`) whose state was already spent.
|
|
9563
|
+
* Set by PaymentsModule at the engine call site so the self-healing retry can demote it and re-plan.
|
|
9564
|
+
*/
|
|
9565
|
+
conflictedSourceId;
|
|
9566
|
+
constructor(message, cause) {
|
|
9567
|
+
super(message, "TRANSFER_CONFLICT", cause);
|
|
9568
|
+
this.name = "TransferConflictError";
|
|
9569
|
+
}
|
|
9570
|
+
};
|
|
9571
|
+
var MintReasonUnverifiableError = class extends SphereError {
|
|
9572
|
+
constructor(message, cause) {
|
|
9573
|
+
super(message, "MINT_REASON_UNVERIFIABLE", cause);
|
|
9574
|
+
this.name = "MintReasonUnverifiableError";
|
|
9575
|
+
}
|
|
9576
|
+
};
|
|
9577
|
+
var ProofUnconfirmedError = class extends SphereError {
|
|
9578
|
+
mayHaveCertified = true;
|
|
9579
|
+
constructor(message, cause) {
|
|
9580
|
+
super(message, "CERTIFICATION_UNCONFIRMED", cause);
|
|
9581
|
+
this.name = "ProofUnconfirmedError";
|
|
9582
|
+
}
|
|
9583
|
+
};
|
|
9584
|
+
var CheckpointPersistFailedError = class extends SphereError {
|
|
9585
|
+
mayHaveCertified = true;
|
|
9586
|
+
constructor(message, cause) {
|
|
9587
|
+
super(message, "CHECKPOINT_PERSIST_FAILED", cause);
|
|
9588
|
+
this.name = "CheckpointPersistFailedError";
|
|
9589
|
+
}
|
|
9590
|
+
};
|
|
9591
|
+
var SplitCheckpointLostError = class extends SphereError {
|
|
9592
|
+
constructor(message, cause) {
|
|
9593
|
+
super(message, "SPLIT_CHECKPOINT_LOST", cause);
|
|
9594
|
+
this.name = "SplitCheckpointLostError";
|
|
9595
|
+
}
|
|
9596
|
+
};
|
|
9597
|
+
var CheckpointTrustbaseMismatchError = class extends SphereError {
|
|
9598
|
+
constructor(message, cause) {
|
|
9599
|
+
super(message, "CHECKPOINT_TRUSTBASE_MISMATCH", cause);
|
|
9600
|
+
this.name = "CheckpointTrustbaseMismatchError";
|
|
9601
|
+
}
|
|
9602
|
+
};
|
|
9603
|
+
function isKeepOpenSplitError(err) {
|
|
9604
|
+
return err instanceof ProofUnconfirmedError || err instanceof CheckpointPersistFailedError || err instanceof SplitCheckpointLostError || err instanceof CheckpointTrustbaseMismatchError;
|
|
9605
|
+
}
|
|
9606
|
+
|
|
9607
|
+
// token-engine/mint-reasons.ts
|
|
9608
|
+
var RULE = "MintJustificationVerification";
|
|
9609
|
+
var MintReasonRegistry = class _MintReasonRegistry extends MintJustificationVerifierService {
|
|
9610
|
+
byTag = /* @__PURE__ */ new Map();
|
|
9611
|
+
pluginTags = /* @__PURE__ */ new Set();
|
|
9612
|
+
register(verifier) {
|
|
9613
|
+
super.register(verifier);
|
|
9614
|
+
this.byTag.set(verifier.tag, verifier);
|
|
9615
|
+
return this;
|
|
9616
|
+
}
|
|
9617
|
+
registerPlugin(verifier) {
|
|
9618
|
+
this.register(verifier);
|
|
9619
|
+
this.pluginTags.add(verifier.tag);
|
|
9620
|
+
return this;
|
|
9621
|
+
}
|
|
9622
|
+
async verify(transaction, nestedTokenCollector) {
|
|
9623
|
+
const tag = transaction.justification ? reasonTag(transaction.justification) : null;
|
|
9624
|
+
if (tag === null) return super.verify(transaction, nestedTokenCollector);
|
|
9625
|
+
const verifier = this.byTag.get(tag);
|
|
9626
|
+
if (verifier === void 0) throw new MintReasonUnverifiableError(`No verifier is registered for mint-reason tag ${tag}`);
|
|
9627
|
+
if (!this.pluginTags.has(tag)) return super.verify(transaction, nestedTokenCollector);
|
|
9628
|
+
return verifyPlugin(verifier, transaction, nestedTokenCollector);
|
|
9629
|
+
}
|
|
9630
|
+
knows(tag) {
|
|
9631
|
+
return this.byTag.has(tag);
|
|
9632
|
+
}
|
|
9633
|
+
/** `verifiers` replace the registered ones of their tags; a tag this registry lacks is refused. */
|
|
9634
|
+
overlaid(verifiers) {
|
|
9635
|
+
for (const verifier of verifiers) assertKnown(this, verifier.tag);
|
|
9636
|
+
const replaced = new Map(verifiers.map((verifier) => [verifier.tag, verifier]));
|
|
9637
|
+
const overlay = new _MintReasonRegistry();
|
|
9638
|
+
for (const [tag, verifier] of this.byTag) {
|
|
9639
|
+
const perCall = replaced.get(tag);
|
|
9640
|
+
if (perCall !== void 0 || this.pluginTags.has(tag)) overlay.registerPlugin(perCall ?? verifier);
|
|
9641
|
+
else overlay.register(verifier);
|
|
9642
|
+
}
|
|
9643
|
+
return overlay;
|
|
9644
|
+
}
|
|
9645
|
+
assertVerifiable(justification) {
|
|
9646
|
+
const tag = reasonTag(justification);
|
|
9647
|
+
if (tag === null) throw new SphereError("The mint reason is not tagged CBOR", "VALIDATION_ERROR");
|
|
9648
|
+
assertKnown(this, tag);
|
|
9649
|
+
}
|
|
9650
|
+
};
|
|
9651
|
+
function reasonTag(justification) {
|
|
9652
|
+
try {
|
|
9653
|
+
return BigInt(CborDeserializer.decodeTag(justification).tag);
|
|
9654
|
+
} catch {
|
|
9655
|
+
return null;
|
|
9656
|
+
}
|
|
9657
|
+
}
|
|
9658
|
+
async function verifyPlugin(verifier, transaction, nestedTokenCollector) {
|
|
9659
|
+
let result;
|
|
9660
|
+
try {
|
|
9661
|
+
result = await verifier.verify(transaction, nestedTokenCollector);
|
|
9662
|
+
} catch (err) {
|
|
9663
|
+
const why = err instanceof Error ? err.message : String(err);
|
|
9664
|
+
throw new MintReasonUnverifiableError(`Mint-reason tag ${verifier.tag} cannot be verified yet: ${why}`, err);
|
|
9665
|
+
}
|
|
9666
|
+
return result.status === VerificationStatus.OK ? new VerificationResult(RULE, VerificationStatus.OK, "", [result]) : new VerificationResult(RULE, VerificationStatus.FAIL, `Verification failed for tag ${verifier.tag}.`, [result]);
|
|
9667
|
+
}
|
|
9668
|
+
function assertKnown(registry, tag) {
|
|
9669
|
+
if (registry.knows(tag)) return;
|
|
9670
|
+
throw new SphereError(
|
|
9671
|
+
`No verifier is registered on the engine for mint-reason tag ${tag}, so a replay of this mint could never verify`,
|
|
9672
|
+
"VALIDATION_ERROR"
|
|
9673
|
+
);
|
|
9674
|
+
}
|
|
9675
|
+
|
|
9532
9676
|
// token-engine/SpherePaymentData.ts
|
|
9533
9677
|
var COIN_ID_PATTERN = /^([0-9a-f]{2})+$/;
|
|
9534
9678
|
function assertAsset(coinId, amount) {
|
|
@@ -9705,45 +9849,6 @@ async function awaitProofBounded(deps, transaction, signal) {
|
|
|
9705
9849
|
return value;
|
|
9706
9850
|
}
|
|
9707
9851
|
|
|
9708
|
-
// token-engine/errors.ts
|
|
9709
|
-
var TransferConflictError = class extends SphereError {
|
|
9710
|
-
/**
|
|
9711
|
-
* #625: the source token id (the caller's id, e.g. `v2_<genesis>`) whose state was already spent.
|
|
9712
|
-
* Set by PaymentsModule at the engine call site so the self-healing retry can demote it and re-plan.
|
|
9713
|
-
*/
|
|
9714
|
-
conflictedSourceId;
|
|
9715
|
-
constructor(message, cause) {
|
|
9716
|
-
super(message, "TRANSFER_CONFLICT", cause);
|
|
9717
|
-
this.name = "TransferConflictError";
|
|
9718
|
-
}
|
|
9719
|
-
};
|
|
9720
|
-
var ProofUnconfirmedError = class extends SphereError {
|
|
9721
|
-
mayHaveCertified = true;
|
|
9722
|
-
constructor(message, cause) {
|
|
9723
|
-
super(message, "CERTIFICATION_UNCONFIRMED", cause);
|
|
9724
|
-
this.name = "ProofUnconfirmedError";
|
|
9725
|
-
}
|
|
9726
|
-
};
|
|
9727
|
-
var CheckpointPersistFailedError = class extends SphereError {
|
|
9728
|
-
mayHaveCertified = true;
|
|
9729
|
-
constructor(message, cause) {
|
|
9730
|
-
super(message, "CHECKPOINT_PERSIST_FAILED", cause);
|
|
9731
|
-
this.name = "CheckpointPersistFailedError";
|
|
9732
|
-
}
|
|
9733
|
-
};
|
|
9734
|
-
var SplitCheckpointLostError = class extends SphereError {
|
|
9735
|
-
constructor(message, cause) {
|
|
9736
|
-
super(message, "SPLIT_CHECKPOINT_LOST", cause);
|
|
9737
|
-
this.name = "SplitCheckpointLostError";
|
|
9738
|
-
}
|
|
9739
|
-
};
|
|
9740
|
-
var CheckpointTrustbaseMismatchError = class extends SphereError {
|
|
9741
|
-
constructor(message, cause) {
|
|
9742
|
-
super(message, "CHECKPOINT_TRUSTBASE_MISMATCH", cause);
|
|
9743
|
-
this.name = "CheckpointTrustbaseMismatchError";
|
|
9744
|
-
}
|
|
9745
|
-
};
|
|
9746
|
-
|
|
9747
9852
|
// token-engine/certification-outcome.ts
|
|
9748
9853
|
function certificationFailure(err, failLabel, submitError, submitRejectedCleanly) {
|
|
9749
9854
|
if (err instanceof Error && err.message === `Invalid inclusion proof status: ${InclusionProofVerificationStatus.TRANSACTION_HASH_MISMATCH}`) {
|
|
@@ -10322,15 +10427,17 @@ async function readTokenNft(token) {
|
|
|
10322
10427
|
}
|
|
10323
10428
|
|
|
10324
10429
|
// token-engine/plugin-ops.ts
|
|
10325
|
-
function
|
|
10326
|
-
|
|
10327
|
-
|
|
10430
|
+
function mintContext(deps, params) {
|
|
10431
|
+
assertMintableData(params.data);
|
|
10432
|
+
if (params.justification !== void 0) deps.mintJustificationVerifier.assertVerifiable(params.justification);
|
|
10433
|
+
const verifiers = params.mintJustificationVerifiers;
|
|
10434
|
+
if (verifiers === void 0) return deps.verificationContext;
|
|
10328
10435
|
return new VerificationContext(
|
|
10329
|
-
|
|
10330
|
-
|
|
10331
|
-
|
|
10332
|
-
|
|
10333
|
-
|
|
10436
|
+
deps.trustBase,
|
|
10437
|
+
deps.predicateVerifier,
|
|
10438
|
+
deps.unicityCertificateVerifier,
|
|
10439
|
+
deps.mintJustificationVerifier.overlaid(verifiers),
|
|
10440
|
+
deps.verificationContext.tokenIssuanceVerifier
|
|
10334
10441
|
);
|
|
10335
10442
|
}
|
|
10336
10443
|
|
|
@@ -10418,9 +10525,6 @@ async function burntTokenFromCheckpoint(deps, storedBytes, reDerivedBurnTx, sour
|
|
|
10418
10525
|
// token-engine/SphereTokenEngine.ts
|
|
10419
10526
|
var DEFAULT_PROOF_POLL_INTERVAL_MS = 300;
|
|
10420
10527
|
var MAX_MINT_CONCURRENCY = 8;
|
|
10421
|
-
function isKeepOpenSplitError(err) {
|
|
10422
|
-
return err instanceof ProofUnconfirmedError || err instanceof CheckpointPersistFailedError || err instanceof SplitCheckpointLostError || err instanceof CheckpointTrustbaseMismatchError;
|
|
10423
|
-
}
|
|
10424
10528
|
var TRANSFER_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
10425
10529
|
var CLEAN_REJECT_STATUSES = /* @__PURE__ */ new Set([
|
|
10426
10530
|
CertificationStatus.STATE_ID_MISMATCH,
|
|
@@ -10510,8 +10614,9 @@ var SphereTokenEngine = class {
|
|
|
10510
10614
|
const token = await Token.mint(certified, this.deps.verificationContext);
|
|
10511
10615
|
return wrapToken(token);
|
|
10512
10616
|
}
|
|
10617
|
+
assertMintable = (params) => void mintContext(this.deps, params);
|
|
10513
10618
|
async mintDataToken(params, options) {
|
|
10514
|
-
|
|
10619
|
+
const context = mintContext(this.deps, params);
|
|
10515
10620
|
const recipient = SignaturePredicate.create(params.recipientPubkey);
|
|
10516
10621
|
const tokenType = params.tokenType ? new TokenType(params.tokenType) : TokenType.generate();
|
|
10517
10622
|
const salt = params.salt ? TokenSalt.fromBytes(params.salt) : TokenSalt.generate();
|
|
@@ -10525,8 +10630,7 @@ var SphereTokenEngine = class {
|
|
|
10525
10630
|
options
|
|
10526
10631
|
);
|
|
10527
10632
|
const certified = await mintTx.toCertifiedTransaction(this.deps.trustBase, this.deps.predicateVerifier, this.deps.unicityCertificateVerifier, proof);
|
|
10528
|
-
|
|
10529
|
-
return wrapToken(await Token.mint(certified, verifiers ? contextWithMintVerifiers(this.deps, verifiers) : this.deps.verificationContext));
|
|
10633
|
+
return wrapToken(await Token.mint(certified, context));
|
|
10530
10634
|
}
|
|
10531
10635
|
buildNftMint(params) {
|
|
10532
10636
|
return planNftMint(this.deps, params);
|
|
@@ -10863,20 +10967,30 @@ var SphereTokenEngine = class {
|
|
|
10863
10967
|
|
|
10864
10968
|
// token-engine/factory.ts
|
|
10865
10969
|
var DEFAULT_VERIFICATION_POOL_SIZE = 4;
|
|
10866
|
-
function
|
|
10970
|
+
function registerPlugins(registries, plugins) {
|
|
10867
10971
|
for (const plugin of plugins ?? []) {
|
|
10868
10972
|
for (const verifier of plugin.mintJustificationVerifiers ?? []) {
|
|
10869
|
-
|
|
10870
|
-
|
|
10871
|
-
|
|
10872
|
-
|
|
10873
|
-
|
|
10874
|
-
|
|
10875
|
-
);
|
|
10876
|
-
}
|
|
10973
|
+
claim(plugin, `mint-reason tag ${verifier.tag}`, () => registries.reasons.registerPlugin(verifier));
|
|
10974
|
+
}
|
|
10975
|
+
for (const policy of plugin.tokenIssuancePolicies ?? []) {
|
|
10976
|
+
const tokenType = HexConverter.encode(policy.tokenType.bytes);
|
|
10977
|
+
claim(plugin, `an issuance policy for token type ${tokenType}`, () => {
|
|
10978
|
+
registries.claims.add(policy);
|
|
10979
|
+
registries.policies.register(policy);
|
|
10980
|
+
});
|
|
10877
10981
|
}
|
|
10878
10982
|
}
|
|
10879
10983
|
}
|
|
10984
|
+
function claim(plugin, what, register) {
|
|
10985
|
+
try {
|
|
10986
|
+
register();
|
|
10987
|
+
} catch (err) {
|
|
10988
|
+
throw new SphereError(
|
|
10989
|
+
`Token plugin '${plugin.id}' registers ${what} twice or over another plugin's: ` + (err instanceof Error ? err.message : String(err)),
|
|
10990
|
+
"INVALID_CONFIG"
|
|
10991
|
+
);
|
|
10992
|
+
}
|
|
10993
|
+
}
|
|
10880
10994
|
function disposedError() {
|
|
10881
10995
|
return new SphereError(
|
|
10882
10996
|
"Verification worker pool disposed \u2014 the in-flight verification was cancelled",
|
|
@@ -10954,11 +11068,15 @@ async function createSphereTokenEngine(config) {
|
|
|
10954
11068
|
const unicityCertificateVerifier = new UnicityCertificateVerifier(
|
|
10955
11069
|
new UnicitySealQuorumSignaturesVerificationRule(new Secp256k1SignatureVerifier(), new VerifiedSealCache(256))
|
|
10956
11070
|
);
|
|
10957
|
-
const mintJustificationVerifier = new
|
|
11071
|
+
const mintJustificationVerifier = new MintReasonRegistry();
|
|
10958
11072
|
mintJustificationVerifier.register(
|
|
10959
11073
|
new SplitMintJustificationVerifier(decodeSpherePaymentData)
|
|
10960
11074
|
);
|
|
10961
|
-
|
|
11075
|
+
const tokenIssuanceVerifier = new TokenIssuanceVerifierService(false);
|
|
11076
|
+
registerPlugins(
|
|
11077
|
+
{ reasons: mintJustificationVerifier, policies: tokenIssuanceVerifier, claims: new CoinClaims() },
|
|
11078
|
+
config.plugins
|
|
11079
|
+
);
|
|
10962
11080
|
const deps = {
|
|
10963
11081
|
client: new StateTransitionClient(new AggregatorClient(config.aggregatorUrl, config.apiKey ?? null)),
|
|
10964
11082
|
trustBase,
|
|
@@ -10970,7 +11088,7 @@ async function createSphereTokenEngine(config) {
|
|
|
10970
11088
|
predicateVerifier,
|
|
10971
11089
|
unicityCertificateVerifier,
|
|
10972
11090
|
mintJustificationVerifier,
|
|
10973
|
-
|
|
11091
|
+
tokenIssuanceVerifier
|
|
10974
11092
|
),
|
|
10975
11093
|
signingService: new SigningService(config.privateKey),
|
|
10976
11094
|
// Also the HKDF ikm for deterministic realization (Part E.1) — the
|
|
@@ -12070,6 +12188,9 @@ function createScopedKV(storage, network, chainPubkey) {
|
|
|
12070
12188
|
}
|
|
12071
12189
|
};
|
|
12072
12190
|
}
|
|
12191
|
+
function isLiveBurn(entry) {
|
|
12192
|
+
return !entry.settled && entry.failure === void 0;
|
|
12193
|
+
}
|
|
12073
12194
|
var STORE_KEYS = {
|
|
12074
12195
|
intentBackstop: "intents",
|
|
12075
12196
|
checkpointCache: "checkpoints",
|
|
@@ -12084,7 +12205,8 @@ var STORE_KEYS = {
|
|
|
12084
12205
|
epochLatch: "epoch-latch",
|
|
12085
12206
|
// §5.2 InventoryView durable overlays (#625/#679).
|
|
12086
12207
|
suspectedSpent: "suspected-spent",
|
|
12087
|
-
knownSpends: "known-spends"
|
|
12208
|
+
knownSpends: "known-spends",
|
|
12209
|
+
verifiedTokens: "verified-tokens"
|
|
12088
12210
|
};
|
|
12089
12211
|
|
|
12090
12212
|
// modules/payments-v2/machine/journal.ts
|
|
@@ -12601,6 +12723,7 @@ var TransferMachine = class {
|
|
|
12601
12723
|
const key = keys.get(digest);
|
|
12602
12724
|
if (key === void 0) throw new SphereError(`no upload key returned for ${digest}`, "STORAGE_ERROR");
|
|
12603
12725
|
const { tokenId } = await engine.deliveryKeys(o.changeBlob);
|
|
12726
|
+
await this.deps.recordSplit?.(o.op.sourceTokenId, tokenId);
|
|
12604
12727
|
added.push({ tokenId, key });
|
|
12605
12728
|
}
|
|
12606
12729
|
return added;
|
|
@@ -12996,7 +13119,7 @@ var Converger = class {
|
|
|
12996
13119
|
this.deps.stores.burnJournal.list()
|
|
12997
13120
|
]);
|
|
12998
13121
|
const mintsOpen = mints.length > 0 || nftMints.length > 0 || customMints.length > 0;
|
|
12999
|
-
const burnsOpen = burns.some(
|
|
13122
|
+
const burnsOpen = burns.some(isLiveBurn);
|
|
13000
13123
|
return backstop.length > 0 || journal.some((e) => e.undeliverable !== true) || mintsOpen || burnsOpen;
|
|
13001
13124
|
}
|
|
13002
13125
|
async resumePass() {
|
|
@@ -13083,6 +13206,9 @@ async function deriveOpenIntentHolds(stores, decryptPayload) {
|
|
|
13083
13206
|
const split = typeof payload?.split?.tokenId === "string" ? [payload.split.tokenId] : [];
|
|
13084
13207
|
holds.set(entry.transferId, [...direct, ...split]);
|
|
13085
13208
|
}
|
|
13209
|
+
for (const burn of await stores.burnJournal.list()) {
|
|
13210
|
+
if (isLiveBurn(burn)) holds.set(burn.burnId, [burn.tokenId]);
|
|
13211
|
+
}
|
|
13086
13212
|
return { open: holds, complete };
|
|
13087
13213
|
}
|
|
13088
13214
|
function groupJournal(journal) {
|
|
@@ -13393,18 +13519,21 @@ function toToken(tokenId, snapshot, asset, registry, flags) {
|
|
|
13393
13519
|
createdAt: snapshot.createdAt,
|
|
13394
13520
|
updatedAt: snapshot.updatedAt,
|
|
13395
13521
|
lazy: true,
|
|
13396
|
-
...flags.suspectedSpent ? { suspectedSpent: true } : {}
|
|
13522
|
+
...flags.suspectedSpent ? { suspectedSpent: true } : {},
|
|
13523
|
+
...flags.standing !== "trusted" ? { unverified: flags.standing } : {}
|
|
13397
13524
|
};
|
|
13398
13525
|
}
|
|
13399
|
-
function aggregateAssets(entries,
|
|
13526
|
+
function aggregateAssets(entries, reading, registry) {
|
|
13400
13527
|
const groups = /* @__PURE__ */ new Map();
|
|
13401
13528
|
for (const [tokenId, entry] of entries) {
|
|
13402
|
-
const moving = isTransferring(tokenId);
|
|
13529
|
+
const moving = reading.isTransferring(tokenId);
|
|
13403
13530
|
for (const asset of entry.assets) {
|
|
13404
|
-
|
|
13531
|
+
const standing = reading.standing(tokenId, asset.coinId);
|
|
13532
|
+
const key = standing === "trusted" ? asset.coinId : `${asset.coinId}:${standing}`;
|
|
13533
|
+
let group = groups.get(key);
|
|
13405
13534
|
if (!group) {
|
|
13406
|
-
group = { total: 0n, count: 0, transferring: 0n, transferringCount: 0 };
|
|
13407
|
-
groups.set(
|
|
13535
|
+
group = { coinId: asset.coinId, standing, total: 0n, count: 0, transferring: 0n, transferringCount: 0 };
|
|
13536
|
+
groups.set(key, group);
|
|
13408
13537
|
}
|
|
13409
13538
|
if (moving) {
|
|
13410
13539
|
group.transferring += BigInt(asset.amount);
|
|
@@ -13415,9 +13544,10 @@ function aggregateAssets(entries, isTransferring, registry) {
|
|
|
13415
13544
|
}
|
|
13416
13545
|
}
|
|
13417
13546
|
}
|
|
13418
|
-
return [...groups].map((
|
|
13547
|
+
return [...groups.values()].map((totals) => toAsset(totals, registry));
|
|
13419
13548
|
}
|
|
13420
|
-
function toAsset(
|
|
13549
|
+
function toAsset(totals, registry) {
|
|
13550
|
+
const coinId = totals.coinId;
|
|
13421
13551
|
const iconUrl = registry.getIconUrl(coinId);
|
|
13422
13552
|
return {
|
|
13423
13553
|
coinId,
|
|
@@ -13437,7 +13567,8 @@ function toAsset(coinId, totals, registry) {
|
|
|
13437
13567
|
priceEur: null,
|
|
13438
13568
|
change24h: null,
|
|
13439
13569
|
fiatValueUsd: null,
|
|
13440
|
-
fiatValueEur: null
|
|
13570
|
+
fiatValueEur: null,
|
|
13571
|
+
...totals.standing !== "trusted" ? { unverified: totals.standing } : {}
|
|
13441
13572
|
};
|
|
13442
13573
|
}
|
|
13443
13574
|
function priceIdOf(registry, coinId) {
|
|
@@ -13445,9 +13576,9 @@ function priceIdOf(registry, coinId) {
|
|
|
13445
13576
|
}
|
|
13446
13577
|
async function withPrices(raw, registry, price) {
|
|
13447
13578
|
try {
|
|
13448
|
-
const ids = [...new Set(raw.map((a) => priceIdOf(registry, a.coinId)))];
|
|
13579
|
+
const ids = [...new Set(raw.filter((a) => !a.unverified).map((a) => priceIdOf(registry, a.coinId)))];
|
|
13449
13580
|
const quotes = await price.getPrices(ids);
|
|
13450
|
-
return raw.map((a) => priceOne(a, quotes.get(priceIdOf(registry, a.coinId))));
|
|
13581
|
+
return raw.map((a) => a.unverified ? a : priceOne(a, quotes.get(priceIdOf(registry, a.coinId))));
|
|
13451
13582
|
} catch {
|
|
13452
13583
|
return raw;
|
|
13453
13584
|
}
|
|
@@ -13468,11 +13599,13 @@ function transferringToken(tokenId, coinId, amount, registry, now) {
|
|
|
13468
13599
|
const snapshot = { assets: [], createdAt: now, updatedAt: now };
|
|
13469
13600
|
return toToken(tokenId, snapshot, { coinId, amount: amount.toString() }, registry, {
|
|
13470
13601
|
transferring: true,
|
|
13471
|
-
suspectedSpent: false
|
|
13602
|
+
suspectedSpent: false,
|
|
13603
|
+
standing: "trusted"
|
|
13472
13604
|
});
|
|
13473
13605
|
}
|
|
13474
13606
|
|
|
13475
13607
|
// modules/payments-v2/inventory/InventoryView.ts
|
|
13608
|
+
var TRUST_ALL = { isClaimed: () => false, standing: () => "trusted" };
|
|
13476
13609
|
var SUSPECTED_KEY = STORE_KEYS.suspectedSpent;
|
|
13477
13610
|
var KNOWN_SPENDS_KEY = STORE_KEYS.knownSpends;
|
|
13478
13611
|
var CURSOR_KEY = STORE_KEYS.streamCursor("inventory");
|
|
@@ -13566,6 +13699,12 @@ var InventoryView = class {
|
|
|
13566
13699
|
for (const tokenId of tokenIds) if (this.inFlight.delete(tokenId)) changed = true;
|
|
13567
13700
|
if (changed) this.deps.emit("inventory:updated");
|
|
13568
13701
|
}
|
|
13702
|
+
get trust() {
|
|
13703
|
+
return this.deps.trust ?? TRUST_ALL;
|
|
13704
|
+
}
|
|
13705
|
+
trusts(tokenId, coinId) {
|
|
13706
|
+
return this.trust.standing(tokenId, coinId) === "trusted";
|
|
13707
|
+
}
|
|
13569
13708
|
pinned(tokenId) {
|
|
13570
13709
|
return this.deps.isPinned?.(tokenId) ?? false;
|
|
13571
13710
|
}
|
|
@@ -13592,6 +13731,7 @@ var InventoryView = class {
|
|
|
13592
13731
|
isSpendable(tokenId, entry) {
|
|
13593
13732
|
if (entry.status !== "active") return false;
|
|
13594
13733
|
if (this.inFlight.has(tokenId) && !this.pinned(tokenId)) return false;
|
|
13734
|
+
if (!entry.assets.every((asset) => this.trusts(tokenId, asset.coinId))) return false;
|
|
13595
13735
|
return !this.suspected.has(stateKey(tokenId, entry.stateHash));
|
|
13596
13736
|
}
|
|
13597
13737
|
/** Is this NAMED token spendable whole — coinless or valued (#777)? */
|
|
@@ -13618,7 +13758,8 @@ var InventoryView = class {
|
|
|
13618
13758
|
out.push(
|
|
13619
13759
|
toToken(tokenId, entry, asset, registry, {
|
|
13620
13760
|
transferring: this.held(tokenId),
|
|
13621
|
-
suspectedSpent: this.suspected.has(stateKey(tokenId, entry.stateHash))
|
|
13761
|
+
suspectedSpent: this.suspected.has(stateKey(tokenId, entry.stateHash)),
|
|
13762
|
+
standing: this.trust.standing(tokenId, asset.coinId)
|
|
13622
13763
|
})
|
|
13623
13764
|
);
|
|
13624
13765
|
}
|
|
@@ -13648,10 +13789,21 @@ var InventoryView = class {
|
|
|
13648
13789
|
return out;
|
|
13649
13790
|
}
|
|
13650
13791
|
async assets(registry, price) {
|
|
13651
|
-
const
|
|
13792
|
+
const reading = {
|
|
13793
|
+
isTransferring: (tokenId) => this.held(tokenId),
|
|
13794
|
+
standing: (tokenId, coinId) => this.trust.standing(tokenId, coinId)
|
|
13795
|
+
};
|
|
13796
|
+
const raw = aggregateAssets(this.activeEntries(), reading, registry);
|
|
13652
13797
|
if (!price || raw.length === 0) return raw;
|
|
13653
13798
|
return withPrices(raw, registry, price);
|
|
13654
13799
|
}
|
|
13800
|
+
claimHolders() {
|
|
13801
|
+
const out = [];
|
|
13802
|
+
for (const [tokenId, entry] of this.activeEntries()) {
|
|
13803
|
+
if (entry.assets.some((asset) => this.trust.isClaimed(asset.coinId))) out.push(tokenId);
|
|
13804
|
+
}
|
|
13805
|
+
return out;
|
|
13806
|
+
}
|
|
13655
13807
|
*activeEntries() {
|
|
13656
13808
|
for (const [tokenId, entry] of this.mirror) {
|
|
13657
13809
|
if (entry.status === "active") yield [tokenId, entry];
|
|
@@ -13821,6 +13973,171 @@ var InventoryView = class {
|
|
|
13821
13973
|
}
|
|
13822
13974
|
};
|
|
13823
13975
|
|
|
13976
|
+
// modules/payments-v2/inventory/verdicts.ts
|
|
13977
|
+
var VERDICT_RETRY_MS = 3e4;
|
|
13978
|
+
var VERDICT_RETRY_MAX_MS = 60 * 60 * 1e3;
|
|
13979
|
+
function honoursClaims(claims, token) {
|
|
13980
|
+
const issuers = (token.value?.assets ?? []).map((asset) => claims.issuerOf(asset.coinId));
|
|
13981
|
+
return issuers.some((issuer) => issuer !== null) && issuers.every((issuer) => issuer === null || issuer === token.tokenType);
|
|
13982
|
+
}
|
|
13983
|
+
var TokenVerdicts = class {
|
|
13984
|
+
constructor(deps) {
|
|
13985
|
+
this.deps = deps;
|
|
13986
|
+
}
|
|
13987
|
+
verified = /* @__PURE__ */ new Set();
|
|
13988
|
+
refused = /* @__PURE__ */ new Set();
|
|
13989
|
+
retries = /* @__PURE__ */ new Map();
|
|
13990
|
+
writes = new SerialChain();
|
|
13991
|
+
hydration = null;
|
|
13992
|
+
queued = null;
|
|
13993
|
+
running = null;
|
|
13994
|
+
held = [];
|
|
13995
|
+
timer = null;
|
|
13996
|
+
live = false;
|
|
13997
|
+
isClaimed(coinId) {
|
|
13998
|
+
return this.deps.claims.issuerOf(coinId) !== null;
|
|
13999
|
+
}
|
|
14000
|
+
standing(tokenId, coinId) {
|
|
14001
|
+
if (!this.isClaimed(coinId) || this.verified.has(tokenId)) return "trusted";
|
|
14002
|
+
return this.refused.has(tokenId) ? "refused" : "pending";
|
|
14003
|
+
}
|
|
14004
|
+
trusts(tokenId, coinId) {
|
|
14005
|
+
return this.standing(tokenId, coinId) === "trusted";
|
|
14006
|
+
}
|
|
14007
|
+
hydrate() {
|
|
14008
|
+
this.hydration ??= this.load().catch((error) => {
|
|
14009
|
+
this.hydration = null;
|
|
14010
|
+
throw error;
|
|
14011
|
+
});
|
|
14012
|
+
return this.hydration;
|
|
14013
|
+
}
|
|
14014
|
+
start() {
|
|
14015
|
+
this.live = true;
|
|
14016
|
+
return this.hydrate();
|
|
14017
|
+
}
|
|
14018
|
+
stop() {
|
|
14019
|
+
this.live = false;
|
|
14020
|
+
if (this.timer !== null) clearTimeout(this.timer);
|
|
14021
|
+
this.timer = null;
|
|
14022
|
+
}
|
|
14023
|
+
async accept(token) {
|
|
14024
|
+
if (honoursClaims(this.deps.claims, token)) await this.remember(token.blob.tokenId);
|
|
14025
|
+
}
|
|
14026
|
+
async recordSplit(sourceTokenId, outputTokenId) {
|
|
14027
|
+
await this.hydrate();
|
|
14028
|
+
if (this.verified.has(sourceTokenId)) await this.remember(outputTokenId);
|
|
14029
|
+
}
|
|
14030
|
+
review(holders) {
|
|
14031
|
+
this.queued = holders;
|
|
14032
|
+
this.running ??= this.drain();
|
|
14033
|
+
return this.running;
|
|
14034
|
+
}
|
|
14035
|
+
async drain() {
|
|
14036
|
+
try {
|
|
14037
|
+
while (this.queued !== null) {
|
|
14038
|
+
const holders = this.queued;
|
|
14039
|
+
this.queued = null;
|
|
14040
|
+
await this.reviewOnce(holders);
|
|
14041
|
+
}
|
|
14042
|
+
} finally {
|
|
14043
|
+
this.running = null;
|
|
14044
|
+
}
|
|
14045
|
+
}
|
|
14046
|
+
async reviewOnce(holders) {
|
|
14047
|
+
await this.hydrate();
|
|
14048
|
+
this.held = holders;
|
|
14049
|
+
const due = holders.filter((tokenId) => this.isDue(tokenId));
|
|
14050
|
+
const verdicts = due.length === 0 ? /* @__PURE__ */ new Map() : await this.check(due);
|
|
14051
|
+
for (const tokenId of due) this.settle(tokenId, verdicts.get(tokenId) ?? "retry");
|
|
14052
|
+
if (due.some((tokenId) => verdicts.get(tokenId) === "verified")) {
|
|
14053
|
+
await this.persist();
|
|
14054
|
+
this.deps.changed();
|
|
14055
|
+
}
|
|
14056
|
+
this.scheduleRetry();
|
|
14057
|
+
}
|
|
14058
|
+
isDue(tokenId) {
|
|
14059
|
+
if (this.verified.has(tokenId) || this.refused.has(tokenId)) return false;
|
|
14060
|
+
return (this.retries.get(tokenId)?.atMs ?? 0) <= this.now();
|
|
14061
|
+
}
|
|
14062
|
+
async check(due) {
|
|
14063
|
+
const verdicts = /* @__PURE__ */ new Map();
|
|
14064
|
+
let blobs;
|
|
14065
|
+
try {
|
|
14066
|
+
blobs = await this.deps.getBlobs([...due]);
|
|
14067
|
+
} catch {
|
|
14068
|
+
return verdicts;
|
|
14069
|
+
}
|
|
14070
|
+
for (const tokenId of due) {
|
|
14071
|
+
const bytes = blobs.get(tokenId);
|
|
14072
|
+
if (bytes !== void 0) verdicts.set(tokenId, await this.verdictOf(tokenId, bytes));
|
|
14073
|
+
}
|
|
14074
|
+
return verdicts;
|
|
14075
|
+
}
|
|
14076
|
+
async verdictOf(tokenId, bytes) {
|
|
14077
|
+
let engine;
|
|
14078
|
+
try {
|
|
14079
|
+
engine = this.deps.engine();
|
|
14080
|
+
} catch {
|
|
14081
|
+
return "retry";
|
|
14082
|
+
}
|
|
14083
|
+
let token;
|
|
14084
|
+
try {
|
|
14085
|
+
token = await engine.decodeToken({ tokenId, token: bytes });
|
|
14086
|
+
} catch {
|
|
14087
|
+
return "refused";
|
|
14088
|
+
}
|
|
14089
|
+
if (token.blob.tokenId !== tokenId || !honoursClaims(this.deps.claims, token)) return "refused";
|
|
14090
|
+
try {
|
|
14091
|
+
return (await engine.verify(token)).ok ? "verified" : "refused";
|
|
14092
|
+
} catch {
|
|
14093
|
+
return "retry";
|
|
14094
|
+
}
|
|
14095
|
+
}
|
|
14096
|
+
settle(tokenId, verdict) {
|
|
14097
|
+
if (verdict === "retry") {
|
|
14098
|
+
this.retries.set(tokenId, this.nextRetry(this.retries.get(tokenId)));
|
|
14099
|
+
return;
|
|
14100
|
+
}
|
|
14101
|
+
this.retries.delete(tokenId);
|
|
14102
|
+
(verdict === "verified" ? this.verified : this.refused).add(tokenId);
|
|
14103
|
+
}
|
|
14104
|
+
nextRetry(previous) {
|
|
14105
|
+
const attempts = (previous?.attempts ?? 0) + 1;
|
|
14106
|
+
const delay = Math.min(VERDICT_RETRY_MS * 2 ** (attempts - 1), VERDICT_RETRY_MAX_MS);
|
|
14107
|
+
return { attempts, atMs: this.now() + delay };
|
|
14108
|
+
}
|
|
14109
|
+
scheduleRetry() {
|
|
14110
|
+
if (!this.live || this.timer !== null) return;
|
|
14111
|
+
const due = this.held.flatMap((tokenId) => this.retries.get(tokenId)?.atMs ?? []);
|
|
14112
|
+
if (due.length === 0) return;
|
|
14113
|
+
const delay = Math.max(0, due.reduce((a, b) => Math.min(a, b)) - this.now());
|
|
14114
|
+
this.timer = setTimeout(() => {
|
|
14115
|
+
this.timer = null;
|
|
14116
|
+
void this.review(this.held).catch(() => void 0);
|
|
14117
|
+
}, delay);
|
|
14118
|
+
}
|
|
14119
|
+
async remember(tokenId) {
|
|
14120
|
+
await this.hydrate();
|
|
14121
|
+
if (this.verified.has(tokenId)) return;
|
|
14122
|
+
this.verified.add(tokenId);
|
|
14123
|
+
this.refused.delete(tokenId);
|
|
14124
|
+
this.retries.delete(tokenId);
|
|
14125
|
+
await this.persist();
|
|
14126
|
+
}
|
|
14127
|
+
persist() {
|
|
14128
|
+
const remembered = { fingerprint: this.deps.claims.fingerprint(), tokenIds: [...this.verified] };
|
|
14129
|
+
return this.writes.enqueue(() => this.deps.kv.set(STORE_KEYS.verifiedTokens, remembered));
|
|
14130
|
+
}
|
|
14131
|
+
async load() {
|
|
14132
|
+
const remembered = await this.deps.kv.get(STORE_KEYS.verifiedTokens);
|
|
14133
|
+
if (remembered?.fingerprint !== this.deps.claims.fingerprint()) return;
|
|
14134
|
+
for (const tokenId of remembered.tokenIds) this.verified.add(tokenId);
|
|
14135
|
+
}
|
|
14136
|
+
now() {
|
|
14137
|
+
return (this.deps.now ?? Date.now)();
|
|
14138
|
+
}
|
|
14139
|
+
};
|
|
14140
|
+
|
|
13824
14141
|
// modules/payments-v2/ports.ts
|
|
13825
14142
|
function isRetryableAckError(err) {
|
|
13826
14143
|
return err !== null && typeof err === "object" && err.retryable === true;
|
|
@@ -13830,6 +14147,8 @@ function isRetryableAckError(err) {
|
|
|
13830
14147
|
var ACK_BATCH_SIZE = 200;
|
|
13831
14148
|
var REFRESH_INTERVAL_MS = 2500;
|
|
13832
14149
|
var POLL_INTERVAL_MS = 3e4;
|
|
14150
|
+
var RECHECK_BASE_MS = POLL_INTERVAL_MS;
|
|
14151
|
+
var RECHECK_MAX_MS = 60 * 60 * 1e3;
|
|
13833
14152
|
var ATTENTION_CLAIM_CONFLICT = "claim:conflict";
|
|
13834
14153
|
function receivedDedupKey(tokenId, stateHash) {
|
|
13835
14154
|
return `RECEIVED:${tokenId.toLowerCase()}:${stateHash.toLowerCase()}`;
|
|
@@ -13846,6 +14165,7 @@ var Receive = class {
|
|
|
13846
14165
|
pollTimer = null;
|
|
13847
14166
|
unsubscribeWake = null;
|
|
13848
14167
|
lastRefreshAt = 0;
|
|
14168
|
+
rechecks = /* @__PURE__ */ new Map();
|
|
13849
14169
|
drainOnce() {
|
|
13850
14170
|
return this.drainFlight.run(() => this.doDrain());
|
|
13851
14171
|
}
|
|
@@ -13904,6 +14224,7 @@ var Receive = class {
|
|
|
13904
14224
|
const pending = [];
|
|
13905
14225
|
const pageEpoch = () => deps.delivery.incomingEpoch() ?? deps.syncEpoch();
|
|
13906
14226
|
const claimable = { n: 0 };
|
|
14227
|
+
const deferred = { any: false };
|
|
13907
14228
|
const finish = () => {
|
|
13908
14229
|
if (claimable.n > 0) deps.refreshView?.();
|
|
13909
14230
|
return stored;
|
|
@@ -13912,7 +14233,7 @@ var Receive = class {
|
|
|
13912
14233
|
const record = await deps.kv.get(STORE_KEYS.streamCursor("mailbox"));
|
|
13913
14234
|
let basis = record !== null && record.syncEpoch === deps.syncEpoch() ? record : null;
|
|
13914
14235
|
for (let pass = 0; pass < 2; pass++) {
|
|
13915
|
-
await this.drainPages({ deps, engine, pending, stored, pageEpoch, claimable }, basis);
|
|
14236
|
+
await this.drainPages({ deps, engine, pending, stored, pageEpoch, claimable, deferred }, basis);
|
|
13916
14237
|
const served = deps.delivery.incomingEpoch();
|
|
13917
14238
|
if (basis === null || served === null || served === basis.syncEpoch) break;
|
|
13918
14239
|
await deps.kv.remove(STORE_KEYS.streamCursor("mailbox"));
|
|
@@ -13929,20 +14250,41 @@ var Receive = class {
|
|
|
13929
14250
|
return finish();
|
|
13930
14251
|
}
|
|
13931
14252
|
async processEntry(ctx, entry) {
|
|
13932
|
-
const { deps, engine,
|
|
13933
|
-
const screened = await screen(deps, engine, entry);
|
|
14253
|
+
const { deps, engine, stored, claimable } = ctx;
|
|
14254
|
+
const screened = this.recheckDue(entry.deliveryId) ? await screen(deps, engine, entry) : { kind: "defer" };
|
|
14255
|
+
this.scheduleRecheck(entry.deliveryId, screened.kind === "defer");
|
|
14256
|
+
if (screened.kind === "defer") {
|
|
14257
|
+
ctx.deferred.any = true;
|
|
14258
|
+
return;
|
|
14259
|
+
}
|
|
13934
14260
|
if (screened.kind === "ack") {
|
|
13935
|
-
|
|
14261
|
+
queueAck(ctx, screened.ack);
|
|
13936
14262
|
if (screened.ack.disposition === "claimed") claimable.n += 1;
|
|
13937
14263
|
return;
|
|
13938
14264
|
}
|
|
14265
|
+
await deps.accepted?.(screened.token);
|
|
13939
14266
|
await deps.view.store(screened.record);
|
|
13940
|
-
|
|
14267
|
+
queueAck(ctx, claimAck(entry));
|
|
13941
14268
|
claimable.n += 1;
|
|
13942
14269
|
const transfer = await announce(deps, entry, screened.record);
|
|
13943
14270
|
stored.push(transfer);
|
|
13944
14271
|
deps.emit("transfer:incoming", transfer);
|
|
13945
14272
|
}
|
|
14273
|
+
recheckDue(deliveryId) {
|
|
14274
|
+
const recheck = this.rechecks.get(deliveryId);
|
|
14275
|
+
return recheck === void 0 || (this.deps.now?.() ?? Date.now()) >= recheck.atMs;
|
|
14276
|
+
}
|
|
14277
|
+
scheduleRecheck(deliveryId, deferred) {
|
|
14278
|
+
if (!deferred) {
|
|
14279
|
+
this.rechecks.delete(deliveryId);
|
|
14280
|
+
return;
|
|
14281
|
+
}
|
|
14282
|
+
const previous = this.rechecks.get(deliveryId);
|
|
14283
|
+
if (previous !== void 0 && (this.deps.now?.() ?? Date.now()) < previous.atMs) return;
|
|
14284
|
+
const attempts = (previous?.attempts ?? 0) + 1;
|
|
14285
|
+
const delay = Math.min(RECHECK_BASE_MS * 2 ** (attempts - 1), RECHECK_MAX_MS);
|
|
14286
|
+
this.rechecks.set(deliveryId, { attempts, atMs: (this.deps.now?.() ?? Date.now()) + delay });
|
|
14287
|
+
}
|
|
13946
14288
|
};
|
|
13947
14289
|
async function screen(deps, engine, entry) {
|
|
13948
14290
|
const blobBytes = await entry.fetchBlob();
|
|
@@ -13953,7 +14295,8 @@ async function screen(deps, engine, entry) {
|
|
|
13953
14295
|
logger.warn("PaymentsV2", `receive: undecodable blob rejected \u2014 ${String(err)}`);
|
|
13954
14296
|
return { kind: "ack", ack: rejectAck(entry, "invalid") };
|
|
13955
14297
|
}
|
|
13956
|
-
const verdict = await engine
|
|
14298
|
+
const verdict = await verdictOf(engine, token);
|
|
14299
|
+
if (verdict === null) return { kind: "defer" };
|
|
13957
14300
|
if (!verdict.ok) return { kind: "ack", ack: rejectAck(entry, "invalid") };
|
|
13958
14301
|
if (!engine.isOwnedBy(token, engine.getIdentity().chainPubkey)) {
|
|
13959
14302
|
return { kind: "ack", ack: rejectAck(entry, "not-owned") };
|
|
@@ -13964,38 +14307,43 @@ async function screen(deps, engine, entry) {
|
|
|
13964
14307
|
if (held !== null && await engine.isSpent(token)) {
|
|
13965
14308
|
return { kind: "ack", ack: rejectAck(entry, "invalid") };
|
|
13966
14309
|
}
|
|
14310
|
+
const unverifiedCoinIds = unverifiedCoinsOf(deps, token);
|
|
13967
14311
|
return {
|
|
13968
14312
|
kind: "accept",
|
|
14313
|
+
token,
|
|
13969
14314
|
record: {
|
|
13970
14315
|
tokenId: keys.tokenId,
|
|
13971
14316
|
stateHash: keys.stateHash,
|
|
13972
14317
|
assets: toAssetAmounts(token),
|
|
13973
|
-
...isCoinlessEnvelope(token.valueEnvelope) ? { tokenType: token.tokenType } : {}
|
|
14318
|
+
...isCoinlessEnvelope(token.valueEnvelope) ? { tokenType: token.tokenType } : {},
|
|
14319
|
+
...unverifiedCoinIds.length > 0 ? { unverifiedCoinIds } : {}
|
|
13974
14320
|
}
|
|
13975
14321
|
};
|
|
13976
14322
|
}
|
|
13977
|
-
|
|
13978
|
-
const
|
|
14323
|
+
function unverifiedCoinsOf(deps, token) {
|
|
14324
|
+
const claims = deps.claims;
|
|
14325
|
+
if (claims === void 0 || honoursClaims(claims, token)) return [];
|
|
14326
|
+
return (token.value?.assets ?? []).map((asset) => asset.coinId).filter((coinId) => claims.issuerOf(coinId) !== null);
|
|
14327
|
+
}
|
|
14328
|
+
async function verdictOf(engine, token) {
|
|
13979
14329
|
try {
|
|
13980
|
-
await
|
|
13981
|
-
dedupKey: receivedDedupKey(record.tokenId, record.stateHash),
|
|
13982
|
-
tokenId: record.tokenId,
|
|
13983
|
-
stateHash: record.stateHash,
|
|
13984
|
-
assets: record.assets,
|
|
13985
|
-
...record.tokenType !== void 0 ? { tokenType: record.tokenType } : {},
|
|
13986
|
-
...entry.senderPubkey !== void 0 ? { senderPubkey: entry.senderPubkey } : {},
|
|
13987
|
-
...entry.senderNametag !== void 0 ? { senderNametag: entry.senderNametag } : {},
|
|
13988
|
-
...entry.memo !== void 0 ? { memo: entry.memo } : {},
|
|
13989
|
-
receivedAt
|
|
13990
|
-
});
|
|
14330
|
+
return await engine.verify(token);
|
|
13991
14331
|
} catch (err) {
|
|
13992
|
-
|
|
14332
|
+
if (isSphereError(err) && err.code === "MINT_REASON_UNVERIFIABLE") return null;
|
|
14333
|
+
throw err;
|
|
13993
14334
|
}
|
|
14335
|
+
}
|
|
14336
|
+
async function announce(deps, entry, record) {
|
|
14337
|
+
const receivedAt = (deps.now ?? Date.now)();
|
|
14338
|
+
await recordArrival(deps, entry, record, receivedAt);
|
|
14339
|
+
const refused2 = record.assets.filter((asset) => record.unverifiedCoinIds?.includes(asset.coinId));
|
|
14340
|
+
const counted = record.assets.filter((asset) => !refused2.includes(asset));
|
|
13994
14341
|
return {
|
|
13995
14342
|
id: record.tokenId,
|
|
13996
14343
|
senderPubkey: entry.senderPubkey ?? "",
|
|
13997
14344
|
...entry.senderNametag !== void 0 ? { senderNametag: entry.senderNametag } : {},
|
|
13998
|
-
tokens:
|
|
14345
|
+
tokens: counted.map((asset) => toUiToken(record.tokenId, asset, deps.registry, receivedAt)),
|
|
14346
|
+
...refused2.length > 0 ? { unverifiedTokens: refused2.map((asset) => ({ ...toUiToken(record.tokenId, asset, deps.registry, receivedAt), unverified: "refused" })) } : {},
|
|
13999
14347
|
// #777: named here rather than mapped from assets, which announced an EMPTY list.
|
|
14000
14348
|
...record.tokenType !== void 0 ? {
|
|
14001
14349
|
coinless: [
|
|
@@ -14013,6 +14361,25 @@ async function announce(deps, entry, record) {
|
|
|
14013
14361
|
receivedAt
|
|
14014
14362
|
};
|
|
14015
14363
|
}
|
|
14364
|
+
async function recordArrival(deps, entry, record, receivedAt) {
|
|
14365
|
+
const assets = record.assets.filter((asset) => !record.unverifiedCoinIds?.includes(asset.coinId));
|
|
14366
|
+
if (assets.length === 0 && record.unverifiedCoinIds !== void 0) return;
|
|
14367
|
+
try {
|
|
14368
|
+
await deps.recordReceived({
|
|
14369
|
+
dedupKey: receivedDedupKey(record.tokenId, record.stateHash),
|
|
14370
|
+
tokenId: record.tokenId,
|
|
14371
|
+
stateHash: record.stateHash,
|
|
14372
|
+
assets,
|
|
14373
|
+
...record.tokenType !== void 0 ? { tokenType: record.tokenType } : {},
|
|
14374
|
+
...entry.senderPubkey !== void 0 ? { senderPubkey: entry.senderPubkey } : {},
|
|
14375
|
+
...entry.senderNametag !== void 0 ? { senderNametag: entry.senderNametag } : {},
|
|
14376
|
+
...entry.memo !== void 0 ? { memo: entry.memo } : {},
|
|
14377
|
+
receivedAt
|
|
14378
|
+
});
|
|
14379
|
+
} catch (err) {
|
|
14380
|
+
logger.debug("PaymentsV2", "RECEIVED history hook failed (money path unaffected):", err);
|
|
14381
|
+
}
|
|
14382
|
+
}
|
|
14016
14383
|
async function flushAcks(deps, pending, epochOf) {
|
|
14017
14384
|
if (pending.length === 0) return { progressed: true };
|
|
14018
14385
|
const before = pending.length;
|
|
@@ -14020,7 +14387,7 @@ async function flushAcks(deps, pending, epochOf) {
|
|
|
14020
14387
|
try {
|
|
14021
14388
|
const settled = await settleAcks(deps, pending);
|
|
14022
14389
|
let i = 0;
|
|
14023
|
-
while (i < pending.length && settled.has(pending[i].deliveryId)) {
|
|
14390
|
+
while (i < pending.length && settled.has(pending[i].deliveryId) && pending[i].holdsCursor !== true) {
|
|
14024
14391
|
lastAcked = pending[i].cursor;
|
|
14025
14392
|
i += 1;
|
|
14026
14393
|
}
|
|
@@ -14096,6 +14463,9 @@ async function ackOne(deps, ack) {
|
|
|
14096
14463
|
await rejectStaleClaim(deps, ack.deliveryId, ack.transferId);
|
|
14097
14464
|
}
|
|
14098
14465
|
}
|
|
14466
|
+
function queueAck(ctx, ack) {
|
|
14467
|
+
ctx.pending.push(ctx.deferred.any ? { ...ack, holdsCursor: true } : ack);
|
|
14468
|
+
}
|
|
14099
14469
|
function claimAck(entry) {
|
|
14100
14470
|
return {
|
|
14101
14471
|
deliveryId: entry.deliveryId,
|
|
@@ -14804,72 +15174,276 @@ var SpendQueue = class {
|
|
|
14804
15174
|
queue = [];
|
|
14805
15175
|
this.queues.set(coinId, queue);
|
|
14806
15176
|
}
|
|
14807
|
-
queue.push(entry);
|
|
14808
|
-
return settled;
|
|
15177
|
+
queue.push(entry);
|
|
15178
|
+
return settled;
|
|
15179
|
+
}
|
|
15180
|
+
expire(id, coinId) {
|
|
15181
|
+
const entries = this.queues.get(coinId);
|
|
15182
|
+
if (!entries) return;
|
|
15183
|
+
const index = entries.findIndex((e) => e.id === id);
|
|
15184
|
+
if (index === -1) return;
|
|
15185
|
+
const [entry] = entries.splice(index, 1);
|
|
15186
|
+
entry.reject(new SphereError("Send queue timeout", "SEND_QUEUE_TIMEOUT"));
|
|
15187
|
+
if (entries.length === 0) this.queues.delete(coinId);
|
|
15188
|
+
}
|
|
15189
|
+
// Whole-token semantics: only fully-free tokens enter the selector pool. What
|
|
15190
|
+
// the reservations hold back is counted too — the refusal has to name it (#737).
|
|
15191
|
+
freeView(coinId) {
|
|
15192
|
+
const freeView = [];
|
|
15193
|
+
let freeTotal = 0n;
|
|
15194
|
+
let pinnedTotal = 0n;
|
|
15195
|
+
const holders = /* @__PURE__ */ new Set();
|
|
15196
|
+
const unproven = this.deps.ledger.unprovenReason();
|
|
15197
|
+
for (const entry of this.deps.getPool(coinId)) {
|
|
15198
|
+
if (entry.amount <= 0n) continue;
|
|
15199
|
+
const holder = unproven === null ? this.deps.ledger.holderOf(entry.tokenId) : entry.tokenId;
|
|
15200
|
+
if (holder === void 0) {
|
|
15201
|
+
freeView.push(entry);
|
|
15202
|
+
freeTotal += entry.amount;
|
|
15203
|
+
} else {
|
|
15204
|
+
pinnedTotal += entry.amount;
|
|
15205
|
+
holders.add(holder);
|
|
15206
|
+
}
|
|
15207
|
+
}
|
|
15208
|
+
return { freeView, freeTotal, pinnedTotal, pinnedBy: holders.size, unproven };
|
|
15209
|
+
}
|
|
15210
|
+
expectedChangeTotal(coinId) {
|
|
15211
|
+
let total = 0n;
|
|
15212
|
+
for (const change of this.expectedChange.values()) {
|
|
15213
|
+
if (change.coinId === coinId) total += change.amount;
|
|
15214
|
+
}
|
|
15215
|
+
return total;
|
|
15216
|
+
}
|
|
15217
|
+
reservePlan(reservationId, plan, freeView) {
|
|
15218
|
+
const pool = new Set(freeView.map((e) => e.tokenId));
|
|
15219
|
+
const tokenIds = [...plan.direct, ...plan.split ? [plan.split.tokenId] : []];
|
|
15220
|
+
for (const tokenId of tokenIds) {
|
|
15221
|
+
if (!pool.has(tokenId)) {
|
|
15222
|
+
throw new SphereError("Planned token missing from pool", "VALIDATION_ERROR");
|
|
15223
|
+
}
|
|
15224
|
+
}
|
|
15225
|
+
this.deps.ledger.reserve(reservationId, tokenIds.map((tokenId) => ({ tokenId })));
|
|
15226
|
+
}
|
|
15227
|
+
totalQueued() {
|
|
15228
|
+
let total = 0;
|
|
15229
|
+
for (const entries of this.queues.values()) total += entries.length;
|
|
15230
|
+
return total;
|
|
15231
|
+
}
|
|
15232
|
+
};
|
|
15233
|
+
function parseAmount(raw) {
|
|
15234
|
+
if (!/^\d+$/.test(raw)) {
|
|
15235
|
+
throw new SphereError("Invalid send amount", "VALIDATION_ERROR");
|
|
15236
|
+
}
|
|
15237
|
+
const amount = BigInt(raw);
|
|
15238
|
+
if (amount <= 0n) {
|
|
15239
|
+
throw new SphereError("Send amount must be positive", "VALIDATION_ERROR");
|
|
15240
|
+
}
|
|
15241
|
+
return amount;
|
|
15242
|
+
}
|
|
15243
|
+
|
|
15244
|
+
// modules/payments-v2/machine/payload.ts
|
|
15245
|
+
function buildPayload(recipientPubkey, request, spend, spentStates) {
|
|
15246
|
+
return {
|
|
15247
|
+
v: 2,
|
|
15248
|
+
kind: "coin",
|
|
15249
|
+
recipient: recipientPubkey,
|
|
15250
|
+
coinId: request.coinId,
|
|
15251
|
+
amount: request.amount,
|
|
15252
|
+
...request.memo !== void 0 ? { memo: request.memo } : {},
|
|
15253
|
+
direct: [...spend.plan.direct],
|
|
15254
|
+
...spend.plan.split !== void 0 ? {
|
|
15255
|
+
split: {
|
|
15256
|
+
tokenId: spend.plan.split.tokenId,
|
|
15257
|
+
splitAmount: spend.plan.split.splitAmount.toString(),
|
|
15258
|
+
remainderAmount: spend.plan.split.remainderAmount.toString()
|
|
15259
|
+
}
|
|
15260
|
+
} : {},
|
|
15261
|
+
spentStates
|
|
15262
|
+
};
|
|
15263
|
+
}
|
|
15264
|
+
function messageOf(err) {
|
|
15265
|
+
return err instanceof Error ? err.message : String(err);
|
|
15266
|
+
}
|
|
15267
|
+
function buildWholePayload(recipientPubkey, request, spentStates) {
|
|
15268
|
+
return {
|
|
15269
|
+
v: 2,
|
|
15270
|
+
kind: "whole",
|
|
15271
|
+
recipient: recipientPubkey,
|
|
15272
|
+
...request.memo !== void 0 ? { memo: request.memo } : {},
|
|
15273
|
+
direct: [request.tokenId],
|
|
15274
|
+
spentStates
|
|
15275
|
+
};
|
|
15276
|
+
}
|
|
15277
|
+
|
|
15278
|
+
// modules/payments-v2/burn.ts
|
|
15279
|
+
var HEX32 = /^[0-9a-f]{64}$/;
|
|
15280
|
+
async function heldToken2(deps, tokenId) {
|
|
15281
|
+
const bytes = (await deps.storagePort.getBlobs([tokenId])).get(tokenId);
|
|
15282
|
+
if (bytes === void 0) {
|
|
15283
|
+
throw new SphereError(`Token ${tokenId} has no blob in storage; it is not held here`, "STORAGE_ERROR");
|
|
15284
|
+
}
|
|
15285
|
+
return deps.engine.decodeToken({ tokenId, token: bytes });
|
|
15286
|
+
}
|
|
15287
|
+
function assertBurnable(engine, token, tokenId) {
|
|
15288
|
+
if (!engine.isOwnedBy(token, engine.getIdentity().chainPubkey)) {
|
|
15289
|
+
throw new SphereError(`Token ${tokenId} is not owned by this wallet`, "VALIDATION_ERROR");
|
|
15290
|
+
}
|
|
15291
|
+
if (token.valueEnvelope === "bare_collection") {
|
|
15292
|
+
throw new SphereError(
|
|
15293
|
+
`Token ${tokenId} carries a value envelope this SDK cannot read, so its coins cannot be accounted for; it cannot be burned`,
|
|
15294
|
+
"VALIDATION_ERROR"
|
|
15295
|
+
);
|
|
15296
|
+
}
|
|
15297
|
+
}
|
|
15298
|
+
function assetsOf(token) {
|
|
15299
|
+
return (token.value?.assets ?? []).map((a) => ({ coinId: a.coinId, amount: a.amount.toString() }));
|
|
15300
|
+
}
|
|
15301
|
+
async function runBurnUnderJournal(deps, input) {
|
|
15302
|
+
const { burnId } = input;
|
|
15303
|
+
const { tokenId, reasonBytes } = input.request;
|
|
15304
|
+
if (!HEX32.test(tokenId) || reasonBytes.length === 0) {
|
|
15305
|
+
return { success: false, burnId, tokenId, error: "burn: tokenId must be 64 hex and reasonBytes non-empty", errorCode: "VALIDATION_ERROR" };
|
|
15306
|
+
}
|
|
15307
|
+
let token;
|
|
15308
|
+
try {
|
|
15309
|
+
token = await heldToken2(deps, tokenId);
|
|
15310
|
+
assertBurnable(deps.engine, token, tokenId);
|
|
15311
|
+
} catch (err) {
|
|
15312
|
+
return refused(burnId, tokenId, err);
|
|
15313
|
+
}
|
|
15314
|
+
const entry = {
|
|
15315
|
+
burnId,
|
|
15316
|
+
tokenId,
|
|
15317
|
+
reasonHex: bytesToHex3(reasonBytes),
|
|
15318
|
+
burnedTokenHex: null,
|
|
15319
|
+
settled: false,
|
|
15320
|
+
assets: assetsOf(token),
|
|
15321
|
+
createdAt: deps.now()
|
|
15322
|
+
};
|
|
15323
|
+
await deps.burnJournal.upsert(entry);
|
|
15324
|
+
try {
|
|
15325
|
+
const settled = await burnJournaled(deps, entry, token);
|
|
15326
|
+
return { success: true, burnId, tokenId, burnedToken: hexToBytes4(settled.burnedTokenHex ?? "") };
|
|
15327
|
+
} catch (err) {
|
|
15328
|
+
const failure = failureOf(err);
|
|
15329
|
+
if (isTerminal(err)) await deps.burnJournal.upsert({ ...entry, failure });
|
|
15330
|
+
else deps.armHeartbeat();
|
|
15331
|
+
return { success: false, burnId, tokenId, error: failure.message, errorCode: failure.code };
|
|
15332
|
+
}
|
|
15333
|
+
}
|
|
15334
|
+
async function replayBurns(deps) {
|
|
15335
|
+
let progressed = 0;
|
|
15336
|
+
for (const entry of await deps.burnJournal.list()) {
|
|
15337
|
+
if (!isLiveBurn(entry) || deps.isActiveOp(entry.burnId)) continue;
|
|
15338
|
+
try {
|
|
15339
|
+
const token = entry.burnedTokenHex === null ? await heldToken2(deps, entry.tokenId) : null;
|
|
15340
|
+
await burnJournaled(deps, entry, token);
|
|
15341
|
+
progressed += 1;
|
|
15342
|
+
} catch (err) {
|
|
15343
|
+
if (!isTerminal(err)) continue;
|
|
15344
|
+
await deps.burnJournal.upsert({ ...entry, failure: failureOf(err) });
|
|
15345
|
+
progressed += 1;
|
|
15346
|
+
}
|
|
15347
|
+
}
|
|
15348
|
+
return progressed;
|
|
15349
|
+
}
|
|
15350
|
+
async function pendingBurns(deps) {
|
|
15351
|
+
return (await deps.burnJournal.list()).map((entry) => ({
|
|
15352
|
+
burnId: entry.burnId,
|
|
15353
|
+
tokenId: entry.tokenId,
|
|
15354
|
+
reasonBytes: hexToBytes4(entry.reasonHex),
|
|
15355
|
+
burnedToken: entry.burnedTokenHex === null ? null : hexToBytes4(entry.burnedTokenHex),
|
|
15356
|
+
settled: entry.settled,
|
|
15357
|
+
createdAt: entry.createdAt,
|
|
15358
|
+
...entry.failure !== void 0 ? { failure: entry.failure } : {}
|
|
15359
|
+
}));
|
|
15360
|
+
}
|
|
15361
|
+
async function acknowledgeBurn(deps, burnId) {
|
|
15362
|
+
const entry = await deps.burnJournal.getByKey(burnId);
|
|
15363
|
+
if (entry === void 0) return;
|
|
15364
|
+
if (isLiveBurn(entry)) {
|
|
15365
|
+
throw new SphereError(`Burn ${burnId} is not settled yet; it cannot be acknowledged`, "VALIDATION_ERROR");
|
|
15366
|
+
}
|
|
15367
|
+
await deps.burnJournal.removeByKey(burnId);
|
|
15368
|
+
}
|
|
15369
|
+
function refused(burnId, tokenId, err) {
|
|
15370
|
+
const failure = failureOf(err);
|
|
15371
|
+
return { success: false, burnId, tokenId, error: failure.message, errorCode: failure.code };
|
|
15372
|
+
}
|
|
15373
|
+
function isTerminal(err) {
|
|
15374
|
+
return classifyError(err) === "conflict";
|
|
15375
|
+
}
|
|
15376
|
+
function failureOf(err) {
|
|
15377
|
+
const code = err !== null && typeof err === "object" ? err.code : void 0;
|
|
15378
|
+
return { code: typeof code === "string" ? code : "UNKNOWN", message: messageOf(err) };
|
|
15379
|
+
}
|
|
15380
|
+
async function burnJournaled(deps, entry, token) {
|
|
15381
|
+
let current = entry;
|
|
15382
|
+
if (current.burnedTokenHex === null) {
|
|
15383
|
+
if (token === null) throw new SphereError("burn replay needs the held token to certify", "VALIDATION_ERROR");
|
|
15384
|
+
const burned = await deps.engine.burn(
|
|
15385
|
+
{ token, reasonBytes: hexToBytes4(current.reasonHex) },
|
|
15386
|
+
{ transferId: current.burnId }
|
|
15387
|
+
);
|
|
15388
|
+
if (burned.blob.tokenId !== current.tokenId) {
|
|
15389
|
+
throw new SphereError(`burned token ${burned.blob.tokenId}, but the journal names ${current.tokenId}`, "VALIDATION_ERROR");
|
|
15390
|
+
}
|
|
15391
|
+
current = { ...current, burnedTokenHex: bytesToHex3(burned.blob.token) };
|
|
15392
|
+
await deps.burnJournal.upsert(current);
|
|
14809
15393
|
}
|
|
14810
|
-
|
|
14811
|
-
|
|
14812
|
-
|
|
14813
|
-
|
|
14814
|
-
|
|
14815
|
-
|
|
14816
|
-
|
|
14817
|
-
|
|
15394
|
+
await deps.storagePort.applyDelta({ transferId: current.burnId, spent: [current.tokenId], added: [], externalDelivery: true });
|
|
15395
|
+
await deps.recordSent({ transferId: current.burnId, assets: current.assets, tokenId: current.tokenId });
|
|
15396
|
+
current = { ...current, settled: true };
|
|
15397
|
+
await deps.burnJournal.upsert(current);
|
|
15398
|
+
deps.refreshView();
|
|
15399
|
+
return current;
|
|
15400
|
+
}
|
|
15401
|
+
|
|
15402
|
+
// modules/payments-v2/burn-hold.ts
|
|
15403
|
+
var BurnHold = class {
|
|
15404
|
+
constructor(deps) {
|
|
15405
|
+
this.deps = deps;
|
|
14818
15406
|
}
|
|
14819
|
-
|
|
14820
|
-
|
|
14821
|
-
|
|
14822
|
-
|
|
14823
|
-
|
|
14824
|
-
let pinnedTotal = 0n;
|
|
14825
|
-
const holders = /* @__PURE__ */ new Set();
|
|
14826
|
-
const unproven = this.deps.ledger.unprovenReason();
|
|
14827
|
-
for (const entry of this.deps.getPool(coinId)) {
|
|
14828
|
-
if (entry.amount <= 0n) continue;
|
|
14829
|
-
const holder = unproven === null ? this.deps.ledger.holderOf(entry.tokenId) : entry.tokenId;
|
|
14830
|
-
if (holder === void 0) {
|
|
14831
|
-
freeView.push(entry);
|
|
14832
|
-
freeTotal += entry.amount;
|
|
14833
|
-
} else {
|
|
14834
|
-
pinnedTotal += entry.amount;
|
|
14835
|
-
holders.add(holder);
|
|
14836
|
-
}
|
|
15407
|
+
async run(burnId, tokenId, burn) {
|
|
15408
|
+
try {
|
|
15409
|
+
this.deps.queue.planWhole(burnId, tokenId);
|
|
15410
|
+
} catch (err) {
|
|
15411
|
+
return refused(burnId, tokenId, err);
|
|
14837
15412
|
}
|
|
14838
|
-
|
|
14839
|
-
|
|
14840
|
-
|
|
14841
|
-
|
|
14842
|
-
|
|
14843
|
-
|
|
15413
|
+
this.deps.view.markInFlightMany([tokenId]);
|
|
15414
|
+
let burned = false;
|
|
15415
|
+
try {
|
|
15416
|
+
const result = await burn();
|
|
15417
|
+
burned = result.success;
|
|
15418
|
+
return result;
|
|
15419
|
+
} finally {
|
|
15420
|
+
await this.settle(burnId, tokenId, burned);
|
|
14844
15421
|
}
|
|
14845
|
-
return total;
|
|
14846
15422
|
}
|
|
14847
|
-
|
|
14848
|
-
|
|
14849
|
-
|
|
14850
|
-
|
|
14851
|
-
|
|
14852
|
-
throw new SphereError("Planned token missing from pool", "VALIDATION_ERROR");
|
|
14853
|
-
}
|
|
15423
|
+
async settle(burnId, tokenId, burned) {
|
|
15424
|
+
if (burned) {
|
|
15425
|
+
this.deps.ledger.commit(burnId);
|
|
15426
|
+
this.deps.track(this.refreshThenRelease(tokenId));
|
|
15427
|
+
return;
|
|
14854
15428
|
}
|
|
14855
|
-
this.deps.
|
|
15429
|
+
const entry = await this.deps.burnJournal.getByKey(burnId).catch(() => null);
|
|
15430
|
+
if (entry === void 0 || entry !== null && !isLiveBurn(entry)) {
|
|
15431
|
+
this.deps.ledger.cancel(burnId);
|
|
15432
|
+
this.deps.view.releaseMany([tokenId]);
|
|
15433
|
+
this.deps.queue.notifyChange("");
|
|
15434
|
+
return;
|
|
15435
|
+
}
|
|
15436
|
+
this.deps.pins.adopt(burnId);
|
|
14856
15437
|
}
|
|
14857
|
-
|
|
14858
|
-
|
|
14859
|
-
|
|
14860
|
-
|
|
15438
|
+
async refreshThenRelease(tokenId) {
|
|
15439
|
+
try {
|
|
15440
|
+
await this.deps.view.delta();
|
|
15441
|
+
this.deps.view.releaseMany([tokenId]);
|
|
15442
|
+
} finally {
|
|
15443
|
+
this.deps.queue.notifyChange("");
|
|
15444
|
+
}
|
|
14861
15445
|
}
|
|
14862
15446
|
};
|
|
14863
|
-
function parseAmount(raw) {
|
|
14864
|
-
if (!/^\d+$/.test(raw)) {
|
|
14865
|
-
throw new SphereError("Invalid send amount", "VALIDATION_ERROR");
|
|
14866
|
-
}
|
|
14867
|
-
const amount = BigInt(raw);
|
|
14868
|
-
if (amount <= 0n) {
|
|
14869
|
-
throw new SphereError("Send amount must be positive", "VALIDATION_ERROR");
|
|
14870
|
-
}
|
|
14871
|
-
return amount;
|
|
14872
|
-
}
|
|
14873
15447
|
|
|
14874
15448
|
// modules/payments-v2/compose.ts
|
|
14875
15449
|
function supportsDeterministicMint(engine) {
|
|
@@ -14878,16 +15452,8 @@ function supportsDeterministicMint(engine) {
|
|
|
14878
15452
|
function composeFacadeParts(deps, hooks) {
|
|
14879
15453
|
const ownPubkeyBytes = hexToBytes4(deps.ownPubkey);
|
|
14880
15454
|
const ledger = new ReservationLedger();
|
|
14881
|
-
const
|
|
14882
|
-
|
|
14883
|
-
kv: deps.kv,
|
|
14884
|
-
emit: (event) => deps.emit(event, {}),
|
|
14885
|
-
// #737: a reserved token is unselectable, so it is never confirmed balance.
|
|
14886
|
-
// #738: while the held-set is unproven, EVERY token reads pinned — the report
|
|
14887
|
-
// must not call a token spendable that the queue is about to refuse to spend.
|
|
14888
|
-
isPinned: (tokenId) => ledger.unprovenReason() !== null || ledger.holderOf(tokenId) !== void 0,
|
|
14889
|
-
...deps.now !== void 0 ? { now: deps.now } : {}
|
|
14890
|
-
});
|
|
15455
|
+
const verdicts = buildVerdicts(deps, hooks);
|
|
15456
|
+
const view = buildView(deps, hooks, ledger, verdicts);
|
|
14891
15457
|
const queue = new SpendQueue({
|
|
14892
15458
|
ledger,
|
|
14893
15459
|
getPool: (coinId) => view.pool(coinId),
|
|
@@ -14902,27 +15468,57 @@ function composeFacadeParts(deps, hooks) {
|
|
|
14902
15468
|
...deps.now !== void 0 ? { now: deps.now } : {},
|
|
14903
15469
|
...deps.newId !== void 0 ? { newId: deps.newId } : {}
|
|
14904
15470
|
});
|
|
14905
|
-
const machineDeps = buildMachineDeps(deps, hooks, historyStore);
|
|
15471
|
+
const machineDeps = buildMachineDeps(deps, hooks, historyStore, verdicts);
|
|
14906
15472
|
const machineStores = createMachineStores(deps.kv);
|
|
14907
15473
|
const heldStates = /* @__PURE__ */ new Map();
|
|
14908
15474
|
const refreshView = coalesced(() => view.delta(), hooks.track);
|
|
15475
|
+
const pins = buildPins(deps, hooks, { ledger, view, machineStores, machineDeps });
|
|
14909
15476
|
return {
|
|
14910
15477
|
ownPubkeyBytes,
|
|
14911
15478
|
view,
|
|
15479
|
+
verdicts,
|
|
14912
15480
|
refreshView,
|
|
14913
15481
|
ledger,
|
|
14914
|
-
pins
|
|
15482
|
+
pins,
|
|
14915
15483
|
queue,
|
|
15484
|
+
burnHold: new BurnHold({ queue, ledger, view, pins, burnJournal: machineStores.burnJournal, track: hooks.track }),
|
|
14916
15485
|
historyStore,
|
|
14917
15486
|
machineStores,
|
|
14918
15487
|
machineDeps,
|
|
14919
15488
|
machine: new TransferMachine(machineDeps),
|
|
14920
15489
|
heldStates,
|
|
14921
|
-
receiveLoop: buildReceive(deps, hooks, historyStore, heldStates, refreshView),
|
|
15490
|
+
receiveLoop: buildReceive(deps, hooks, { historyStore, heldStates, verdicts }, refreshView),
|
|
14922
15491
|
requests: buildRequests(deps, hooks),
|
|
14923
15492
|
restoreDeps: buildRestoreDeps(deps, machineDeps, machineStores, view)
|
|
14924
15493
|
};
|
|
14925
15494
|
}
|
|
15495
|
+
function buildView(deps, hooks, ledger, verdicts) {
|
|
15496
|
+
const view = new InventoryView({
|
|
15497
|
+
port: deps.storagePort,
|
|
15498
|
+
kv: deps.kv,
|
|
15499
|
+
trust: verdicts,
|
|
15500
|
+
emit: (event) => {
|
|
15501
|
+
deps.emit(event, {});
|
|
15502
|
+
hooks.track(verdicts.review(view.claimHolders()));
|
|
15503
|
+
},
|
|
15504
|
+
// #737: a reserved token is unselectable, so it is never confirmed balance.
|
|
15505
|
+
// #738: while the held-set is unproven, EVERY token reads pinned — the report
|
|
15506
|
+
// must not call a token spendable that the queue is about to refuse to spend.
|
|
15507
|
+
isPinned: (tokenId) => ledger.unprovenReason() !== null || ledger.holderOf(tokenId) !== void 0,
|
|
15508
|
+
...deps.now !== void 0 ? { now: deps.now } : {}
|
|
15509
|
+
});
|
|
15510
|
+
return view;
|
|
15511
|
+
}
|
|
15512
|
+
function buildVerdicts(deps, hooks) {
|
|
15513
|
+
return new TokenVerdicts({
|
|
15514
|
+
kv: deps.kv,
|
|
15515
|
+
claims: deps.claims ?? new CoinClaims(),
|
|
15516
|
+
engine: () => hooks.engine(),
|
|
15517
|
+
getBlobs: (tokenIds) => deps.storagePort.getBlobs(tokenIds),
|
|
15518
|
+
changed: () => deps.emit("inventory:updated", {}),
|
|
15519
|
+
...deps.now !== void 0 ? { now: deps.now } : {}
|
|
15520
|
+
});
|
|
15521
|
+
}
|
|
14926
15522
|
function buildPins(deps, hooks, parts) {
|
|
14927
15523
|
return new IntentPins({
|
|
14928
15524
|
ledger: parts.ledger,
|
|
@@ -14950,8 +15546,9 @@ function buildRestoreDeps(deps, machineDeps, machineStores, view) {
|
|
|
14950
15546
|
}
|
|
14951
15547
|
};
|
|
14952
15548
|
}
|
|
14953
|
-
function buildMachineDeps(deps, hooks, historyStore) {
|
|
15549
|
+
function buildMachineDeps(deps, hooks, historyStore, verdicts) {
|
|
14954
15550
|
return {
|
|
15551
|
+
recordSplit: (sourceTokenId, outputTokenId) => verdicts.recordSplit(sourceTokenId, outputTokenId),
|
|
14955
15552
|
engine: () => hooks.engine(),
|
|
14956
15553
|
storage: deps.storagePort,
|
|
14957
15554
|
delivery: deps.deliveryPort,
|
|
@@ -14984,10 +15581,13 @@ function buildMachineDeps(deps, hooks, historyStore) {
|
|
|
14984
15581
|
}
|
|
14985
15582
|
};
|
|
14986
15583
|
}
|
|
14987
|
-
function buildReceive(deps, hooks,
|
|
15584
|
+
function buildReceive(deps, hooks, parts, refreshView) {
|
|
15585
|
+
const { historyStore, heldStates, verdicts } = parts;
|
|
14988
15586
|
return new Receive({
|
|
14989
15587
|
delivery: deps.deliveryPort,
|
|
14990
15588
|
engine: () => hooks.engine(),
|
|
15589
|
+
claims: deps.claims ?? new CoinClaims(),
|
|
15590
|
+
accepted: (token) => verdicts.accept(token),
|
|
14991
15591
|
view: {
|
|
14992
15592
|
heldState: (tokenId) => heldStates.get(tokenId) ?? null,
|
|
14993
15593
|
store: async (entry) => {
|
|
@@ -15037,40 +15637,6 @@ function buildRequests(deps, hooks) {
|
|
|
15037
15637
|
});
|
|
15038
15638
|
}
|
|
15039
15639
|
|
|
15040
|
-
// modules/payments-v2/machine/payload.ts
|
|
15041
|
-
function buildPayload(recipientPubkey, request, spend, spentStates) {
|
|
15042
|
-
return {
|
|
15043
|
-
v: 2,
|
|
15044
|
-
kind: "coin",
|
|
15045
|
-
recipient: recipientPubkey,
|
|
15046
|
-
coinId: request.coinId,
|
|
15047
|
-
amount: request.amount,
|
|
15048
|
-
...request.memo !== void 0 ? { memo: request.memo } : {},
|
|
15049
|
-
direct: [...spend.plan.direct],
|
|
15050
|
-
...spend.plan.split !== void 0 ? {
|
|
15051
|
-
split: {
|
|
15052
|
-
tokenId: spend.plan.split.tokenId,
|
|
15053
|
-
splitAmount: spend.plan.split.splitAmount.toString(),
|
|
15054
|
-
remainderAmount: spend.plan.split.remainderAmount.toString()
|
|
15055
|
-
}
|
|
15056
|
-
} : {},
|
|
15057
|
-
spentStates
|
|
15058
|
-
};
|
|
15059
|
-
}
|
|
15060
|
-
function messageOf(err) {
|
|
15061
|
-
return err instanceof Error ? err.message : String(err);
|
|
15062
|
-
}
|
|
15063
|
-
function buildWholePayload(recipientPubkey, request, spentStates) {
|
|
15064
|
-
return {
|
|
15065
|
-
v: 2,
|
|
15066
|
-
kind: "whole",
|
|
15067
|
-
recipient: recipientPubkey,
|
|
15068
|
-
...request.memo !== void 0 ? { memo: request.memo } : {},
|
|
15069
|
-
direct: [request.tokenId],
|
|
15070
|
-
spentStates
|
|
15071
|
-
};
|
|
15072
|
-
}
|
|
15073
|
-
|
|
15074
15640
|
// modules/payments-v2/mint-params.ts
|
|
15075
15641
|
function mintParams(recipientPubkey, coinId, amount) {
|
|
15076
15642
|
return { recipientPubkey, value: { assets: [{ coinId, amount }] } };
|
|
@@ -15140,6 +15706,16 @@ async function tokenInServerInventory(storagePort, tokenId) {
|
|
|
15140
15706
|
}
|
|
15141
15707
|
return false;
|
|
15142
15708
|
}
|
|
15709
|
+
async function seedHeldStates(storagePort, heldStates) {
|
|
15710
|
+
let page = await storagePort.listInventory();
|
|
15711
|
+
for (let i = 0; i < INVENTORY_SCAN_PAGE_LIMIT; i++) {
|
|
15712
|
+
for (const item of page.items) {
|
|
15713
|
+
if (item.status === "active") heldStates.set(item.tokenId, item.stateHash);
|
|
15714
|
+
}
|
|
15715
|
+
if (!page.more) return;
|
|
15716
|
+
page = await storagePort.listInventory(page.cursor);
|
|
15717
|
+
}
|
|
15718
|
+
}
|
|
15143
15719
|
async function replayCoinMints(deps) {
|
|
15144
15720
|
let resolved = 0;
|
|
15145
15721
|
for (const entry of await deps.mintJournal.list()) {
|
|
@@ -15244,7 +15820,8 @@ async function mintJournaled(deps, entry) {
|
|
|
15244
15820
|
}
|
|
15245
15821
|
|
|
15246
15822
|
// modules/payments-v2/mint-custom.ts
|
|
15247
|
-
var
|
|
15823
|
+
var HEX322 = /^[0-9a-f]{64}$/;
|
|
15824
|
+
var CUSTOM_MINT_MAX_PAYLOAD_BYTES = 1024 * 1024;
|
|
15248
15825
|
function assertRequest(request) {
|
|
15249
15826
|
if (request.tokenType.length !== 32) {
|
|
15250
15827
|
throw new SphereError("mintCustom: tokenType must be 32 bytes", "VALIDATION_ERROR");
|
|
@@ -15255,19 +15832,27 @@ function assertRequest(request) {
|
|
|
15255
15832
|
if (request.data.length === 0) {
|
|
15256
15833
|
throw new SphereError("mintCustom: data must not be empty", "VALIDATION_ERROR");
|
|
15257
15834
|
}
|
|
15835
|
+
if (request.justification !== void 0 && request.justification.length === 0) {
|
|
15836
|
+
throw new SphereError("mintCustom: justification must be omitted, not empty", "VALIDATION_ERROR");
|
|
15837
|
+
}
|
|
15838
|
+
if (request.data.length + (request.justification?.length ?? 0) > CUSTOM_MINT_MAX_PAYLOAD_BYTES) {
|
|
15839
|
+
throw new SphereError(`mintCustom: data and justification exceed ${CUSTOM_MINT_MAX_PAYLOAD_BYTES} bytes`, "VALIDATION_ERROR");
|
|
15840
|
+
}
|
|
15258
15841
|
for (const asset of request.assets) {
|
|
15259
|
-
if (!
|
|
15842
|
+
if (!HEX322.test(asset.coinId) || asset.amount <= 0n) {
|
|
15260
15843
|
throw new SphereError("mintCustom: assets need a 64-hex coinId and a positive amount", "VALIDATION_ERROR");
|
|
15261
15844
|
}
|
|
15262
15845
|
}
|
|
15263
15846
|
}
|
|
15264
15847
|
async function runCustomMintUnderJournal(deps, input) {
|
|
15848
|
+
let entry;
|
|
15265
15849
|
try {
|
|
15266
15850
|
assertRequest(input.request);
|
|
15851
|
+
entry = journalEntryOf2(input.mintId, input.request, deps.now());
|
|
15852
|
+
deps.engine.assertMintable(mintParamsOf(deps, entry, input.request.mintJustificationVerifiers));
|
|
15267
15853
|
} catch (err) {
|
|
15268
15854
|
return { success: false, error: messageOf(err) };
|
|
15269
15855
|
}
|
|
15270
|
-
const entry = journalEntryOf2(input.mintId, input.request, deps.now());
|
|
15271
15856
|
await deps.customMintJournal.upsert(entry);
|
|
15272
15857
|
let tokenId;
|
|
15273
15858
|
try {
|
|
@@ -15307,24 +15892,25 @@ function journalEntryOf2(mintId, request, createdAt) {
|
|
|
15307
15892
|
createdAt
|
|
15308
15893
|
};
|
|
15309
15894
|
}
|
|
15895
|
+
function mintParamsOf(deps, entry, mintJustificationVerifiers) {
|
|
15896
|
+
return {
|
|
15897
|
+
recipientPubkey: deps.ownPubkeyBytes,
|
|
15898
|
+
data: hexToBytes4(entry.dataHex),
|
|
15899
|
+
tokenType: hexToBytes4(entry.tokenTypeHex),
|
|
15900
|
+
salt: hexToBytes4(entry.saltHex),
|
|
15901
|
+
...entry.justificationHex !== null ? { justification: hexToBytes4(entry.justificationHex) } : {},
|
|
15902
|
+
...mintJustificationVerifiers ? { mintJustificationVerifiers } : {}
|
|
15903
|
+
};
|
|
15904
|
+
}
|
|
15310
15905
|
async function mintJournaled2(deps, entry, mintJustificationVerifiers) {
|
|
15311
|
-
const token = await deps.engine.mintDataToken(
|
|
15312
|
-
{
|
|
15313
|
-
recipientPubkey: deps.ownPubkeyBytes,
|
|
15314
|
-
data: hexToBytes4(entry.dataHex),
|
|
15315
|
-
tokenType: hexToBytes4(entry.tokenTypeHex),
|
|
15316
|
-
salt: hexToBytes4(entry.saltHex),
|
|
15317
|
-
...entry.justificationHex !== null ? { justification: hexToBytes4(entry.justificationHex) } : {},
|
|
15318
|
-
...mintJustificationVerifiers ? { mintJustificationVerifiers } : {}
|
|
15319
|
-
},
|
|
15320
|
-
{ transferId: entry.mintId }
|
|
15321
|
-
);
|
|
15906
|
+
const token = await deps.engine.mintDataToken(mintParamsOf(deps, entry, mintJustificationVerifiers), { transferId: entry.mintId });
|
|
15322
15907
|
if (entry.tokenId !== "" && token.blob.tokenId !== entry.tokenId) {
|
|
15323
15908
|
throw new SphereError(`minted token ${token.blob.tokenId}, but the journal names ${entry.tokenId}`, "VALIDATION_ERROR");
|
|
15324
15909
|
}
|
|
15325
15910
|
if (entry.tokenId === "") {
|
|
15326
15911
|
await deps.customMintJournal.upsert({ ...entry, tokenId: token.blob.tokenId });
|
|
15327
15912
|
}
|
|
15913
|
+
if (mintJustificationVerifiers === void 0) await deps.accepted?.(token);
|
|
15328
15914
|
await finalizeMint(deps, {
|
|
15329
15915
|
mintId: entry.mintId,
|
|
15330
15916
|
token,
|
|
@@ -15334,108 +15920,6 @@ async function mintJournaled2(deps, entry, mintJustificationVerifiers) {
|
|
|
15334
15920
|
return token.blob.tokenId;
|
|
15335
15921
|
}
|
|
15336
15922
|
|
|
15337
|
-
// modules/payments-v2/burn.ts
|
|
15338
|
-
var HEX322 = /^[0-9a-f]{64}$/;
|
|
15339
|
-
async function heldToken2(deps, tokenId) {
|
|
15340
|
-
const bytes = (await deps.storagePort.getBlobs([tokenId])).get(tokenId);
|
|
15341
|
-
if (bytes === void 0) {
|
|
15342
|
-
throw new SphereError(`Token ${tokenId} has no blob in storage; it is not held here`, "STORAGE_ERROR");
|
|
15343
|
-
}
|
|
15344
|
-
return deps.engine.decodeToken({ tokenId, token: bytes });
|
|
15345
|
-
}
|
|
15346
|
-
function assetsOf(token) {
|
|
15347
|
-
return (token.value?.assets ?? []).map((a) => ({ coinId: a.coinId, amount: a.amount.toString() }));
|
|
15348
|
-
}
|
|
15349
|
-
async function runBurnUnderJournal(deps, input) {
|
|
15350
|
-
const { burnId } = input;
|
|
15351
|
-
const { tokenId, reasonBytes } = input.request;
|
|
15352
|
-
if (!HEX322.test(tokenId) || reasonBytes.length === 0) {
|
|
15353
|
-
return { success: false, burnId, tokenId, error: "burn: tokenId must be 64 hex and reasonBytes non-empty" };
|
|
15354
|
-
}
|
|
15355
|
-
let token;
|
|
15356
|
-
try {
|
|
15357
|
-
token = await heldToken2(deps, tokenId);
|
|
15358
|
-
if (!deps.engine.isOwnedBy(token, deps.engine.getIdentity().chainPubkey)) {
|
|
15359
|
-
throw new SphereError(`Token ${tokenId} is not owned by this wallet`, "VALIDATION_ERROR");
|
|
15360
|
-
}
|
|
15361
|
-
} catch (err) {
|
|
15362
|
-
return { success: false, burnId, tokenId, error: messageOf(err) };
|
|
15363
|
-
}
|
|
15364
|
-
const entry = {
|
|
15365
|
-
burnId,
|
|
15366
|
-
tokenId,
|
|
15367
|
-
reasonHex: bytesToHex3(reasonBytes),
|
|
15368
|
-
burnedTokenHex: null,
|
|
15369
|
-
settled: false,
|
|
15370
|
-
assets: assetsOf(token),
|
|
15371
|
-
createdAt: deps.now()
|
|
15372
|
-
};
|
|
15373
|
-
await deps.burnJournal.upsert(entry);
|
|
15374
|
-
try {
|
|
15375
|
-
const settled = await burnJournaled(deps, entry, token, { applied: false });
|
|
15376
|
-
return { success: true, burnId, tokenId, burnedToken: hexToBytes4(settled.burnedTokenHex ?? "") };
|
|
15377
|
-
} catch (err) {
|
|
15378
|
-
deps.armHeartbeat();
|
|
15379
|
-
return { success: false, burnId, tokenId, error: messageOf(err) };
|
|
15380
|
-
}
|
|
15381
|
-
}
|
|
15382
|
-
async function replayBurns(deps) {
|
|
15383
|
-
let progressed = 0;
|
|
15384
|
-
for (const entry of await deps.burnJournal.list()) {
|
|
15385
|
-
if (entry.settled || deps.isActiveOp(entry.burnId)) continue;
|
|
15386
|
-
try {
|
|
15387
|
-
const token = entry.burnedTokenHex === null ? await heldToken2(deps, entry.tokenId) : null;
|
|
15388
|
-
const applied = entry.burnedTokenHex !== null && !await deps.tokenInServerInventory(entry.tokenId);
|
|
15389
|
-
await burnJournaled(deps, entry, token, { applied });
|
|
15390
|
-
progressed += 1;
|
|
15391
|
-
} catch {
|
|
15392
|
-
continue;
|
|
15393
|
-
}
|
|
15394
|
-
}
|
|
15395
|
-
return progressed;
|
|
15396
|
-
}
|
|
15397
|
-
async function pendingBurns(deps) {
|
|
15398
|
-
return (await deps.burnJournal.list()).map((entry) => ({
|
|
15399
|
-
burnId: entry.burnId,
|
|
15400
|
-
tokenId: entry.tokenId,
|
|
15401
|
-
reasonBytes: hexToBytes4(entry.reasonHex),
|
|
15402
|
-
burnedToken: entry.burnedTokenHex === null ? null : hexToBytes4(entry.burnedTokenHex),
|
|
15403
|
-
settled: entry.settled,
|
|
15404
|
-
createdAt: entry.createdAt
|
|
15405
|
-
}));
|
|
15406
|
-
}
|
|
15407
|
-
async function acknowledgeBurn(deps, burnId) {
|
|
15408
|
-
const entry = await deps.burnJournal.getByKey(burnId);
|
|
15409
|
-
if (entry === void 0) return;
|
|
15410
|
-
if (!entry.settled) {
|
|
15411
|
-
throw new SphereError(`Burn ${burnId} is not settled yet; it cannot be acknowledged`, "VALIDATION_ERROR");
|
|
15412
|
-
}
|
|
15413
|
-
await deps.burnJournal.removeByKey(burnId);
|
|
15414
|
-
}
|
|
15415
|
-
async function burnJournaled(deps, entry, token, state) {
|
|
15416
|
-
let current = entry;
|
|
15417
|
-
if (current.burnedTokenHex === null) {
|
|
15418
|
-
if (token === null) throw new SphereError("burn replay needs the held token to certify", "VALIDATION_ERROR");
|
|
15419
|
-
const burned = await deps.engine.burn(
|
|
15420
|
-
{ token, reasonBytes: hexToBytes4(current.reasonHex) },
|
|
15421
|
-
{ transferId: current.burnId }
|
|
15422
|
-
);
|
|
15423
|
-
if (burned.blob.tokenId !== current.tokenId) {
|
|
15424
|
-
throw new SphereError(`burned token ${burned.blob.tokenId}, but the journal names ${current.tokenId}`, "VALIDATION_ERROR");
|
|
15425
|
-
}
|
|
15426
|
-
current = { ...current, burnedTokenHex: bytesToHex3(burned.blob.token) };
|
|
15427
|
-
await deps.burnJournal.upsert(current);
|
|
15428
|
-
}
|
|
15429
|
-
if (!state.applied) {
|
|
15430
|
-
await deps.storagePort.applyDelta({ transferId: current.burnId, spent: [current.tokenId], added: [] });
|
|
15431
|
-
}
|
|
15432
|
-
await deps.recordSent({ transferId: current.burnId, assets: current.assets, tokenId: current.tokenId });
|
|
15433
|
-
current = { ...current, settled: true };
|
|
15434
|
-
await deps.burnJournal.upsert(current);
|
|
15435
|
-
deps.refreshView();
|
|
15436
|
-
return current;
|
|
15437
|
-
}
|
|
15438
|
-
|
|
15439
15923
|
// modules/payments-v2/send-whole.ts
|
|
15440
15924
|
async function materializeWholeSpend(deps, input) {
|
|
15441
15925
|
const { engine } = deps;
|
|
@@ -15631,6 +16115,7 @@ var PaymentsFacade = class {
|
|
|
15631
16115
|
track: (op) => this.trackTail(op)
|
|
15632
16116
|
});
|
|
15633
16117
|
this.view = parts.view;
|
|
16118
|
+
this.verdicts = parts.verdicts;
|
|
15634
16119
|
this.wakeRefresh = parts.refreshView;
|
|
15635
16120
|
this.ledger = parts.ledger;
|
|
15636
16121
|
this.queue = parts.queue;
|
|
@@ -15644,6 +16129,7 @@ var PaymentsFacade = class {
|
|
|
15644
16129
|
this.restoreDeps = parts.restoreDeps;
|
|
15645
16130
|
this.requests = parts.requests;
|
|
15646
16131
|
this.pins = parts.pins;
|
|
16132
|
+
this.burnHold = parts.burnHold;
|
|
15647
16133
|
deps.deliveryPort.bindDeliveryKeys((blob) => this.engine().deliveryKeys(blob));
|
|
15648
16134
|
this.heartbeat = new ConvergenceHeartbeat({
|
|
15649
16135
|
now: () => this.nowMs(),
|
|
@@ -15667,6 +16153,7 @@ var PaymentsFacade = class {
|
|
|
15667
16153
|
wakeRefresh;
|
|
15668
16154
|
prewarmed = new PrewarmCache();
|
|
15669
16155
|
view;
|
|
16156
|
+
verdicts;
|
|
15670
16157
|
ledger;
|
|
15671
16158
|
queue;
|
|
15672
16159
|
machine;
|
|
@@ -15693,6 +16180,7 @@ var PaymentsFacade = class {
|
|
|
15693
16180
|
activeMoneyOps = /* @__PURE__ */ new Set();
|
|
15694
16181
|
/** #737: the ledger holds exactly the sources of the still-open intents. */
|
|
15695
16182
|
pins;
|
|
16183
|
+
burnHold;
|
|
15696
16184
|
/** Per facade, so per address: an NFT reading is never served to another wallet. */
|
|
15697
16185
|
nftCache = new NftCache();
|
|
15698
16186
|
// ── lifecycle ──────────────────────────────────────────────────────────────
|
|
@@ -15717,7 +16205,8 @@ var PaymentsFacade = class {
|
|
|
15717
16205
|
await this.deps.session.start();
|
|
15718
16206
|
await this.pins.sync().catch(() => void 0);
|
|
15719
16207
|
this.trackTail(this.runConvergencePass());
|
|
15720
|
-
this.trackTail(this.
|
|
16208
|
+
this.trackTail(seedHeldStates(this.deps.storagePort, this.heldStates));
|
|
16209
|
+
await this.verdicts.start().catch(() => void 0);
|
|
15721
16210
|
await this.view.fullPull().catch(() => void 0);
|
|
15722
16211
|
this.receiveLoop.start(this.deps.receivePollMs);
|
|
15723
16212
|
}
|
|
@@ -15726,6 +16215,7 @@ var PaymentsFacade = class {
|
|
|
15726
16215
|
this.started = false;
|
|
15727
16216
|
this.heartbeat.stop();
|
|
15728
16217
|
this.receiveLoop.stop();
|
|
16218
|
+
this.verdicts.stop();
|
|
15729
16219
|
for (const unsubscribe of this.unsubscribers) unsubscribe();
|
|
15730
16220
|
this.unsubscribers = [];
|
|
15731
16221
|
await this.deps.session.stop();
|
|
@@ -15742,12 +16232,17 @@ var PaymentsFacade = class {
|
|
|
15742
16232
|
}
|
|
15743
16233
|
// ── reads ──────────────────────────────────────────────────────────────────
|
|
15744
16234
|
async assets(coinId) {
|
|
16235
|
+
return (await this.heldAssets(coinId)).filter((asset) => asset.unverified === void 0);
|
|
16236
|
+
}
|
|
16237
|
+
unverifiedAssets = async (coinId) => (await this.heldAssets(coinId)).filter((a) => a.unverified !== void 0);
|
|
16238
|
+
async heldAssets(coinId) {
|
|
15745
16239
|
const all = await this.view.assets(this.deps.registry, this.deps.price);
|
|
15746
16240
|
return coinId === void 0 ? all : all.filter((asset) => asset.coinId === coinId);
|
|
15747
16241
|
}
|
|
15748
16242
|
tokens(filter) {
|
|
15749
|
-
return this.view.tokens(this.deps.registry, filter);
|
|
16243
|
+
return this.view.tokens(this.deps.registry, filter).filter((token) => token.unverified === void 0);
|
|
15750
16244
|
}
|
|
16245
|
+
unverifiedTokens = (filter) => this.view.tokens(this.deps.registry, filter).filter((t) => t.unverified !== void 0);
|
|
15751
16246
|
coinless() {
|
|
15752
16247
|
return this.view.coinless(this.deps.registry);
|
|
15753
16248
|
}
|
|
@@ -15808,9 +16303,9 @@ var PaymentsFacade = class {
|
|
|
15808
16303
|
return this.track(this.ownedOp((mintId) => runNftMintUnderJournal(this.mintDeps(), { mintId, request })));
|
|
15809
16304
|
}
|
|
15810
16305
|
mintCustom = (request) => this.track(this.ownedOp((mintId) => runCustomMintUnderJournal(this.mintDeps(), { mintId, request })));
|
|
15811
|
-
burn = (request) => this.track(this.ownedOp((burnId) => runBurnUnderJournal(this.mintDeps(), { burnId, request })));
|
|
15812
|
-
pendingBurns = () => this.track(pendingBurns(this.
|
|
15813
|
-
acknowledgeBurn = (burnId) => this.track(acknowledgeBurn(this.
|
|
16306
|
+
burn = (request) => this.track(this.ownedOp((burnId) => this.burnHold.run(burnId, request.tokenId, () => runBurnUnderJournal(this.mintDeps(), { burnId, request }))));
|
|
16307
|
+
pendingBurns = () => this.track(pendingBurns(this.machineStores));
|
|
16308
|
+
acknowledgeBurn = (burnId) => this.track(acknowledgeBurn(this.machineStores, burnId));
|
|
15814
16309
|
// §4 retry verb: coalesces onto the running pass — NEVER re-issue send() (#631/#676).
|
|
15815
16310
|
async resumeNow() {
|
|
15816
16311
|
if (!this.started) return;
|
|
@@ -15853,7 +16348,8 @@ var PaymentsFacade = class {
|
|
|
15853
16348
|
isActiveOp: (mintId) => this.activeMoneyOps.has(mintId),
|
|
15854
16349
|
tokenInServerInventory: (tokenId) => tokenInServerInventory(this.deps.storagePort, tokenId),
|
|
15855
16350
|
emit: this.deps.emit,
|
|
15856
|
-
now: () => this.nowMs()
|
|
16351
|
+
now: () => this.nowMs(),
|
|
16352
|
+
accepted: (token) => this.verdicts.accept(token)
|
|
15857
16353
|
};
|
|
15858
16354
|
}
|
|
15859
16355
|
// ── send policy (§5.5 + old-loop parity; the machine stays policy-free) ────
|
|
@@ -16191,16 +16687,6 @@ var PaymentsFacade = class {
|
|
|
16191
16687
|
newId() {
|
|
16192
16688
|
return (this.deps.newId ?? randomUUID)();
|
|
16193
16689
|
}
|
|
16194
|
-
async seedHeldStates() {
|
|
16195
|
-
let page = await this.deps.storagePort.listInventory();
|
|
16196
|
-
for (let i = 0; i < INVENTORY_SCAN_PAGE_LIMIT; i++) {
|
|
16197
|
-
for (const item of page.items) {
|
|
16198
|
-
if (item.status === "active") this.heldStates.set(item.tokenId, item.stateHash);
|
|
16199
|
-
}
|
|
16200
|
-
if (!page.more) return;
|
|
16201
|
-
page = await this.deps.storagePort.listInventory(page.cursor);
|
|
16202
|
-
}
|
|
16203
|
-
}
|
|
16204
16690
|
track(op) {
|
|
16205
16691
|
const settled = op.then(() => void 0, () => void 0);
|
|
16206
16692
|
this.pendingOps.add(settled);
|
|
@@ -17444,6 +17930,7 @@ function composePaymentsV2(spec) {
|
|
|
17444
17930
|
engineRef: spec.engineRef,
|
|
17445
17931
|
kv,
|
|
17446
17932
|
registry: host.registry,
|
|
17933
|
+
claims: CoinClaims.fromPlugins(spec.plugins),
|
|
17447
17934
|
...host.price !== null ? { price: host.price } : {},
|
|
17448
17935
|
emit: host.emit,
|
|
17449
17936
|
resolveRecipient: (identifier) => resolveRecipientInfo(identifier, network, host.resolvePeer),
|
|
@@ -20492,7 +20979,8 @@ var Sphere = class _Sphere {
|
|
|
20492
20979
|
// deliver the NEXT address's Unicity ID — a name the sending key does
|
|
20493
20980
|
// not own (sphere#487 review).
|
|
20494
20981
|
nametag: () => this.getNametagForAddress(facadeAddressId)
|
|
20495
|
-
}
|
|
20982
|
+
},
|
|
20983
|
+
...this._plugins ? { plugins: this._plugins } : {}
|
|
20496
20984
|
});
|
|
20497
20985
|
this._paymentsV2Active = { index, facade };
|
|
20498
20986
|
await facade.start();
|
|
@@ -21127,6 +21615,7 @@ function addressesMatch(a, b) {
|
|
|
21127
21615
|
export {
|
|
21128
21616
|
AUTH_CHALLENGE_PREFIX,
|
|
21129
21617
|
COIN_TYPES,
|
|
21618
|
+
CUSTOM_MINT_MAX_PAYLOAD_BYTES,
|
|
21130
21619
|
ChallengeTemplateError,
|
|
21131
21620
|
CoinGeckoPriceProvider,
|
|
21132
21621
|
CommunicationsModule,
|