@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.cjs
CHANGED
|
@@ -32,6 +32,7 @@ var index_exports = {};
|
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
AUTH_CHALLENGE_PREFIX: () => AUTH_CHALLENGE_PREFIX,
|
|
34
34
|
COIN_TYPES: () => COIN_TYPES,
|
|
35
|
+
CUSTOM_MINT_MAX_PAYLOAD_BYTES: () => CUSTOM_MINT_MAX_PAYLOAD_BYTES,
|
|
35
36
|
ChallengeTemplateError: () => ChallengeTemplateError,
|
|
36
37
|
CoinGeckoPriceProvider: () => CoinGeckoPriceProvider,
|
|
37
38
|
CommunicationsModule: () => CommunicationsModule,
|
|
@@ -9668,6 +9669,150 @@ async function deriveDirectAddress(publicKey) {
|
|
|
9668
9669
|
return `DIRECT://${import_HexConverter.HexConverter.encode(imprint)}${import_HexConverter.HexConverter.encode(checksum)}`;
|
|
9669
9670
|
}
|
|
9670
9671
|
|
|
9672
|
+
// token-engine/claims.ts
|
|
9673
|
+
var CoinClaims = class _CoinClaims {
|
|
9674
|
+
issuers = /* @__PURE__ */ new Map();
|
|
9675
|
+
policies = [];
|
|
9676
|
+
static fromPlugins(plugins) {
|
|
9677
|
+
const claims = new _CoinClaims();
|
|
9678
|
+
for (const plugin of plugins ?? []) {
|
|
9679
|
+
for (const policy of plugin.tokenIssuancePolicies ?? []) claims.add(policy);
|
|
9680
|
+
}
|
|
9681
|
+
return claims;
|
|
9682
|
+
}
|
|
9683
|
+
add(policy) {
|
|
9684
|
+
const tokenType = import_HexConverter.HexConverter.encode(policy.tokenType.bytes);
|
|
9685
|
+
const coinIds = policy.coinIds.map((coinId) => coinId.toLowerCase());
|
|
9686
|
+
const taken = coinIds.find((coinId) => this.issuers.has(coinId));
|
|
9687
|
+
if (taken !== void 0) throw new Error(`Coin ${taken} is already issued by token type ${this.issuers.get(taken)}.`);
|
|
9688
|
+
for (const coinId of coinIds) this.issuers.set(coinId, tokenType);
|
|
9689
|
+
this.policies.push(JSON.stringify([tokenType, [...coinIds].sort(), policy.revision ?? ""]));
|
|
9690
|
+
}
|
|
9691
|
+
fingerprint() {
|
|
9692
|
+
return [...this.policies].sort().join("\n");
|
|
9693
|
+
}
|
|
9694
|
+
issuerOf(coinId) {
|
|
9695
|
+
return this.issuers.get(coinId.toLowerCase()) ?? null;
|
|
9696
|
+
}
|
|
9697
|
+
};
|
|
9698
|
+
|
|
9699
|
+
// token-engine/errors.ts
|
|
9700
|
+
var TransferConflictError = class extends SphereError {
|
|
9701
|
+
/**
|
|
9702
|
+
* #625: the source token id (the caller's id, e.g. `v2_<genesis>`) whose state was already spent.
|
|
9703
|
+
* Set by PaymentsModule at the engine call site so the self-healing retry can demote it and re-plan.
|
|
9704
|
+
*/
|
|
9705
|
+
conflictedSourceId;
|
|
9706
|
+
constructor(message, cause) {
|
|
9707
|
+
super(message, "TRANSFER_CONFLICT", cause);
|
|
9708
|
+
this.name = "TransferConflictError";
|
|
9709
|
+
}
|
|
9710
|
+
};
|
|
9711
|
+
var MintReasonUnverifiableError = class extends SphereError {
|
|
9712
|
+
constructor(message, cause) {
|
|
9713
|
+
super(message, "MINT_REASON_UNVERIFIABLE", cause);
|
|
9714
|
+
this.name = "MintReasonUnverifiableError";
|
|
9715
|
+
}
|
|
9716
|
+
};
|
|
9717
|
+
var ProofUnconfirmedError = class extends SphereError {
|
|
9718
|
+
mayHaveCertified = true;
|
|
9719
|
+
constructor(message, cause) {
|
|
9720
|
+
super(message, "CERTIFICATION_UNCONFIRMED", cause);
|
|
9721
|
+
this.name = "ProofUnconfirmedError";
|
|
9722
|
+
}
|
|
9723
|
+
};
|
|
9724
|
+
var CheckpointPersistFailedError = class extends SphereError {
|
|
9725
|
+
mayHaveCertified = true;
|
|
9726
|
+
constructor(message, cause) {
|
|
9727
|
+
super(message, "CHECKPOINT_PERSIST_FAILED", cause);
|
|
9728
|
+
this.name = "CheckpointPersistFailedError";
|
|
9729
|
+
}
|
|
9730
|
+
};
|
|
9731
|
+
var SplitCheckpointLostError = class extends SphereError {
|
|
9732
|
+
constructor(message, cause) {
|
|
9733
|
+
super(message, "SPLIT_CHECKPOINT_LOST", cause);
|
|
9734
|
+
this.name = "SplitCheckpointLostError";
|
|
9735
|
+
}
|
|
9736
|
+
};
|
|
9737
|
+
var CheckpointTrustbaseMismatchError = class extends SphereError {
|
|
9738
|
+
constructor(message, cause) {
|
|
9739
|
+
super(message, "CHECKPOINT_TRUSTBASE_MISMATCH", cause);
|
|
9740
|
+
this.name = "CheckpointTrustbaseMismatchError";
|
|
9741
|
+
}
|
|
9742
|
+
};
|
|
9743
|
+
function isKeepOpenSplitError(err) {
|
|
9744
|
+
return err instanceof ProofUnconfirmedError || err instanceof CheckpointPersistFailedError || err instanceof SplitCheckpointLostError || err instanceof CheckpointTrustbaseMismatchError;
|
|
9745
|
+
}
|
|
9746
|
+
|
|
9747
|
+
// token-engine/mint-reasons.ts
|
|
9748
|
+
var RULE = "MintJustificationVerification";
|
|
9749
|
+
var MintReasonRegistry = class _MintReasonRegistry extends import_MintJustificationVerifierService.MintJustificationVerifierService {
|
|
9750
|
+
byTag = /* @__PURE__ */ new Map();
|
|
9751
|
+
pluginTags = /* @__PURE__ */ new Set();
|
|
9752
|
+
register(verifier) {
|
|
9753
|
+
super.register(verifier);
|
|
9754
|
+
this.byTag.set(verifier.tag, verifier);
|
|
9755
|
+
return this;
|
|
9756
|
+
}
|
|
9757
|
+
registerPlugin(verifier) {
|
|
9758
|
+
this.register(verifier);
|
|
9759
|
+
this.pluginTags.add(verifier.tag);
|
|
9760
|
+
return this;
|
|
9761
|
+
}
|
|
9762
|
+
async verify(transaction, nestedTokenCollector) {
|
|
9763
|
+
const tag = transaction.justification ? reasonTag(transaction.justification) : null;
|
|
9764
|
+
if (tag === null) return super.verify(transaction, nestedTokenCollector);
|
|
9765
|
+
const verifier = this.byTag.get(tag);
|
|
9766
|
+
if (verifier === void 0) throw new MintReasonUnverifiableError(`No verifier is registered for mint-reason tag ${tag}`);
|
|
9767
|
+
if (!this.pluginTags.has(tag)) return super.verify(transaction, nestedTokenCollector);
|
|
9768
|
+
return verifyPlugin(verifier, transaction, nestedTokenCollector);
|
|
9769
|
+
}
|
|
9770
|
+
knows(tag) {
|
|
9771
|
+
return this.byTag.has(tag);
|
|
9772
|
+
}
|
|
9773
|
+
/** `verifiers` replace the registered ones of their tags; a tag this registry lacks is refused. */
|
|
9774
|
+
overlaid(verifiers) {
|
|
9775
|
+
for (const verifier of verifiers) assertKnown(this, verifier.tag);
|
|
9776
|
+
const replaced = new Map(verifiers.map((verifier) => [verifier.tag, verifier]));
|
|
9777
|
+
const overlay = new _MintReasonRegistry();
|
|
9778
|
+
for (const [tag, verifier] of this.byTag) {
|
|
9779
|
+
const perCall = replaced.get(tag);
|
|
9780
|
+
if (perCall !== void 0 || this.pluginTags.has(tag)) overlay.registerPlugin(perCall ?? verifier);
|
|
9781
|
+
else overlay.register(verifier);
|
|
9782
|
+
}
|
|
9783
|
+
return overlay;
|
|
9784
|
+
}
|
|
9785
|
+
assertVerifiable(justification) {
|
|
9786
|
+
const tag = reasonTag(justification);
|
|
9787
|
+
if (tag === null) throw new SphereError("The mint reason is not tagged CBOR", "VALIDATION_ERROR");
|
|
9788
|
+
assertKnown(this, tag);
|
|
9789
|
+
}
|
|
9790
|
+
};
|
|
9791
|
+
function reasonTag(justification) {
|
|
9792
|
+
try {
|
|
9793
|
+
return BigInt(import_CborDeserializer.CborDeserializer.decodeTag(justification).tag);
|
|
9794
|
+
} catch {
|
|
9795
|
+
return null;
|
|
9796
|
+
}
|
|
9797
|
+
}
|
|
9798
|
+
async function verifyPlugin(verifier, transaction, nestedTokenCollector) {
|
|
9799
|
+
let result;
|
|
9800
|
+
try {
|
|
9801
|
+
result = await verifier.verify(transaction, nestedTokenCollector);
|
|
9802
|
+
} catch (err) {
|
|
9803
|
+
const why = err instanceof Error ? err.message : String(err);
|
|
9804
|
+
throw new MintReasonUnverifiableError(`Mint-reason tag ${verifier.tag} cannot be verified yet: ${why}`, err);
|
|
9805
|
+
}
|
|
9806
|
+
return result.status === import_VerificationStatus.VerificationStatus.OK ? new import_VerificationResult.VerificationResult(RULE, import_VerificationStatus.VerificationStatus.OK, "", [result]) : new import_VerificationResult.VerificationResult(RULE, import_VerificationStatus.VerificationStatus.FAIL, `Verification failed for tag ${verifier.tag}.`, [result]);
|
|
9807
|
+
}
|
|
9808
|
+
function assertKnown(registry, tag) {
|
|
9809
|
+
if (registry.knows(tag)) return;
|
|
9810
|
+
throw new SphereError(
|
|
9811
|
+
`No verifier is registered on the engine for mint-reason tag ${tag}, so a replay of this mint could never verify`,
|
|
9812
|
+
"VALIDATION_ERROR"
|
|
9813
|
+
);
|
|
9814
|
+
}
|
|
9815
|
+
|
|
9671
9816
|
// token-engine/SpherePaymentData.ts
|
|
9672
9817
|
var COIN_ID_PATTERN = /^([0-9a-f]{2})+$/;
|
|
9673
9818
|
function assertAsset(coinId, amount) {
|
|
@@ -9844,45 +9989,6 @@ async function awaitProofBounded(deps, transaction, signal) {
|
|
|
9844
9989
|
return value;
|
|
9845
9990
|
}
|
|
9846
9991
|
|
|
9847
|
-
// token-engine/errors.ts
|
|
9848
|
-
var TransferConflictError = class extends SphereError {
|
|
9849
|
-
/**
|
|
9850
|
-
* #625: the source token id (the caller's id, e.g. `v2_<genesis>`) whose state was already spent.
|
|
9851
|
-
* Set by PaymentsModule at the engine call site so the self-healing retry can demote it and re-plan.
|
|
9852
|
-
*/
|
|
9853
|
-
conflictedSourceId;
|
|
9854
|
-
constructor(message, cause) {
|
|
9855
|
-
super(message, "TRANSFER_CONFLICT", cause);
|
|
9856
|
-
this.name = "TransferConflictError";
|
|
9857
|
-
}
|
|
9858
|
-
};
|
|
9859
|
-
var ProofUnconfirmedError = class extends SphereError {
|
|
9860
|
-
mayHaveCertified = true;
|
|
9861
|
-
constructor(message, cause) {
|
|
9862
|
-
super(message, "CERTIFICATION_UNCONFIRMED", cause);
|
|
9863
|
-
this.name = "ProofUnconfirmedError";
|
|
9864
|
-
}
|
|
9865
|
-
};
|
|
9866
|
-
var CheckpointPersistFailedError = class extends SphereError {
|
|
9867
|
-
mayHaveCertified = true;
|
|
9868
|
-
constructor(message, cause) {
|
|
9869
|
-
super(message, "CHECKPOINT_PERSIST_FAILED", cause);
|
|
9870
|
-
this.name = "CheckpointPersistFailedError";
|
|
9871
|
-
}
|
|
9872
|
-
};
|
|
9873
|
-
var SplitCheckpointLostError = class extends SphereError {
|
|
9874
|
-
constructor(message, cause) {
|
|
9875
|
-
super(message, "SPLIT_CHECKPOINT_LOST", cause);
|
|
9876
|
-
this.name = "SplitCheckpointLostError";
|
|
9877
|
-
}
|
|
9878
|
-
};
|
|
9879
|
-
var CheckpointTrustbaseMismatchError = class extends SphereError {
|
|
9880
|
-
constructor(message, cause) {
|
|
9881
|
-
super(message, "CHECKPOINT_TRUSTBASE_MISMATCH", cause);
|
|
9882
|
-
this.name = "CheckpointTrustbaseMismatchError";
|
|
9883
|
-
}
|
|
9884
|
-
};
|
|
9885
|
-
|
|
9886
9992
|
// token-engine/certification-outcome.ts
|
|
9887
9993
|
function certificationFailure(err, failLabel, submitError, submitRejectedCleanly) {
|
|
9888
9994
|
if (err instanceof Error && err.message === `Invalid inclusion proof status: ${import_InclusionProofVerificationRule.InclusionProofVerificationStatus.TRANSACTION_HASH_MISMATCH}`) {
|
|
@@ -10461,15 +10567,17 @@ async function readTokenNft(token) {
|
|
|
10461
10567
|
}
|
|
10462
10568
|
|
|
10463
10569
|
// token-engine/plugin-ops.ts
|
|
10464
|
-
function
|
|
10465
|
-
|
|
10466
|
-
|
|
10570
|
+
function mintContext(deps, params) {
|
|
10571
|
+
assertMintableData(params.data);
|
|
10572
|
+
if (params.justification !== void 0) deps.mintJustificationVerifier.assertVerifiable(params.justification);
|
|
10573
|
+
const verifiers = params.mintJustificationVerifiers;
|
|
10574
|
+
if (verifiers === void 0) return deps.verificationContext;
|
|
10467
10575
|
return new import_VerificationContext.VerificationContext(
|
|
10468
|
-
|
|
10469
|
-
|
|
10470
|
-
|
|
10471
|
-
|
|
10472
|
-
|
|
10576
|
+
deps.trustBase,
|
|
10577
|
+
deps.predicateVerifier,
|
|
10578
|
+
deps.unicityCertificateVerifier,
|
|
10579
|
+
deps.mintJustificationVerifier.overlaid(verifiers),
|
|
10580
|
+
deps.verificationContext.tokenIssuanceVerifier
|
|
10473
10581
|
);
|
|
10474
10582
|
}
|
|
10475
10583
|
|
|
@@ -10557,9 +10665,6 @@ async function burntTokenFromCheckpoint(deps, storedBytes, reDerivedBurnTx, sour
|
|
|
10557
10665
|
// token-engine/SphereTokenEngine.ts
|
|
10558
10666
|
var DEFAULT_PROOF_POLL_INTERVAL_MS = 300;
|
|
10559
10667
|
var MAX_MINT_CONCURRENCY = 8;
|
|
10560
|
-
function isKeepOpenSplitError(err) {
|
|
10561
|
-
return err instanceof ProofUnconfirmedError || err instanceof CheckpointPersistFailedError || err instanceof SplitCheckpointLostError || err instanceof CheckpointTrustbaseMismatchError;
|
|
10562
|
-
}
|
|
10563
10668
|
var TRANSFER_ID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
10564
10669
|
var CLEAN_REJECT_STATUSES = /* @__PURE__ */ new Set([
|
|
10565
10670
|
import_CertificationResponse.CertificationStatus.STATE_ID_MISMATCH,
|
|
@@ -10649,8 +10754,9 @@ var SphereTokenEngine = class {
|
|
|
10649
10754
|
const token = await import_Token.Token.mint(certified, this.deps.verificationContext);
|
|
10650
10755
|
return wrapToken(token);
|
|
10651
10756
|
}
|
|
10757
|
+
assertMintable = (params) => void mintContext(this.deps, params);
|
|
10652
10758
|
async mintDataToken(params, options) {
|
|
10653
|
-
|
|
10759
|
+
const context = mintContext(this.deps, params);
|
|
10654
10760
|
const recipient = import_SignaturePredicate.SignaturePredicate.create(params.recipientPubkey);
|
|
10655
10761
|
const tokenType = params.tokenType ? new import_TokenType.TokenType(params.tokenType) : import_TokenType.TokenType.generate();
|
|
10656
10762
|
const salt = params.salt ? import_TokenSalt.TokenSalt.fromBytes(params.salt) : import_TokenSalt.TokenSalt.generate();
|
|
@@ -10664,8 +10770,7 @@ var SphereTokenEngine = class {
|
|
|
10664
10770
|
options
|
|
10665
10771
|
);
|
|
10666
10772
|
const certified = await mintTx.toCertifiedTransaction(this.deps.trustBase, this.deps.predicateVerifier, this.deps.unicityCertificateVerifier, proof);
|
|
10667
|
-
|
|
10668
|
-
return wrapToken(await import_Token.Token.mint(certified, verifiers ? contextWithMintVerifiers(this.deps, verifiers) : this.deps.verificationContext));
|
|
10773
|
+
return wrapToken(await import_Token.Token.mint(certified, context));
|
|
10669
10774
|
}
|
|
10670
10775
|
buildNftMint(params) {
|
|
10671
10776
|
return planNftMint(this.deps, params);
|
|
@@ -11002,20 +11107,30 @@ var SphereTokenEngine = class {
|
|
|
11002
11107
|
|
|
11003
11108
|
// token-engine/factory.ts
|
|
11004
11109
|
var DEFAULT_VERIFICATION_POOL_SIZE = 4;
|
|
11005
|
-
function
|
|
11110
|
+
function registerPlugins(registries, plugins) {
|
|
11006
11111
|
for (const plugin of plugins ?? []) {
|
|
11007
11112
|
for (const verifier of plugin.mintJustificationVerifiers ?? []) {
|
|
11008
|
-
|
|
11009
|
-
|
|
11010
|
-
|
|
11011
|
-
|
|
11012
|
-
|
|
11013
|
-
|
|
11014
|
-
);
|
|
11015
|
-
}
|
|
11113
|
+
claim(plugin, `mint-reason tag ${verifier.tag}`, () => registries.reasons.registerPlugin(verifier));
|
|
11114
|
+
}
|
|
11115
|
+
for (const policy of plugin.tokenIssuancePolicies ?? []) {
|
|
11116
|
+
const tokenType = import_HexConverter.HexConverter.encode(policy.tokenType.bytes);
|
|
11117
|
+
claim(plugin, `an issuance policy for token type ${tokenType}`, () => {
|
|
11118
|
+
registries.claims.add(policy);
|
|
11119
|
+
registries.policies.register(policy);
|
|
11120
|
+
});
|
|
11016
11121
|
}
|
|
11017
11122
|
}
|
|
11018
11123
|
}
|
|
11124
|
+
function claim(plugin, what, register) {
|
|
11125
|
+
try {
|
|
11126
|
+
register();
|
|
11127
|
+
} catch (err) {
|
|
11128
|
+
throw new SphereError(
|
|
11129
|
+
`Token plugin '${plugin.id}' registers ${what} twice or over another plugin's: ` + (err instanceof Error ? err.message : String(err)),
|
|
11130
|
+
"INVALID_CONFIG"
|
|
11131
|
+
);
|
|
11132
|
+
}
|
|
11133
|
+
}
|
|
11019
11134
|
function disposedError() {
|
|
11020
11135
|
return new SphereError(
|
|
11021
11136
|
"Verification worker pool disposed \u2014 the in-flight verification was cancelled",
|
|
@@ -11093,11 +11208,15 @@ async function createSphereTokenEngine(config) {
|
|
|
11093
11208
|
const unicityCertificateVerifier = new import_UnicityCertificateVerifier.UnicityCertificateVerifier(
|
|
11094
11209
|
new import_UnicitySealQuorumSignaturesVerificationRule.UnicitySealQuorumSignaturesVerificationRule(new import_Secp256k1SignatureVerifier.Secp256k1SignatureVerifier(), new import_VerifiedSealCache.VerifiedSealCache(256))
|
|
11095
11210
|
);
|
|
11096
|
-
const mintJustificationVerifier = new
|
|
11211
|
+
const mintJustificationVerifier = new MintReasonRegistry();
|
|
11097
11212
|
mintJustificationVerifier.register(
|
|
11098
11213
|
new import_SplitMintJustificationVerifier.SplitMintJustificationVerifier(decodeSpherePaymentData)
|
|
11099
11214
|
);
|
|
11100
|
-
|
|
11215
|
+
const tokenIssuanceVerifier = new import_TokenIssuanceVerifierService.TokenIssuanceVerifierService(false);
|
|
11216
|
+
registerPlugins(
|
|
11217
|
+
{ reasons: mintJustificationVerifier, policies: tokenIssuanceVerifier, claims: new CoinClaims() },
|
|
11218
|
+
config.plugins
|
|
11219
|
+
);
|
|
11101
11220
|
const deps = {
|
|
11102
11221
|
client: new import_StateTransitionClient.StateTransitionClient(new import_AggregatorClient.AggregatorClient(config.aggregatorUrl, config.apiKey ?? null)),
|
|
11103
11222
|
trustBase,
|
|
@@ -11109,7 +11228,7 @@ async function createSphereTokenEngine(config) {
|
|
|
11109
11228
|
predicateVerifier,
|
|
11110
11229
|
unicityCertificateVerifier,
|
|
11111
11230
|
mintJustificationVerifier,
|
|
11112
|
-
|
|
11231
|
+
tokenIssuanceVerifier
|
|
11113
11232
|
),
|
|
11114
11233
|
signingService: new import_SigningService.SigningService(config.privateKey),
|
|
11115
11234
|
// Also the HKDF ikm for deterministic realization (Part E.1) — the
|
|
@@ -12209,6 +12328,9 @@ function createScopedKV(storage, network, chainPubkey) {
|
|
|
12209
12328
|
}
|
|
12210
12329
|
};
|
|
12211
12330
|
}
|
|
12331
|
+
function isLiveBurn(entry) {
|
|
12332
|
+
return !entry.settled && entry.failure === void 0;
|
|
12333
|
+
}
|
|
12212
12334
|
var STORE_KEYS = {
|
|
12213
12335
|
intentBackstop: "intents",
|
|
12214
12336
|
checkpointCache: "checkpoints",
|
|
@@ -12223,7 +12345,8 @@ var STORE_KEYS = {
|
|
|
12223
12345
|
epochLatch: "epoch-latch",
|
|
12224
12346
|
// §5.2 InventoryView durable overlays (#625/#679).
|
|
12225
12347
|
suspectedSpent: "suspected-spent",
|
|
12226
|
-
knownSpends: "known-spends"
|
|
12348
|
+
knownSpends: "known-spends",
|
|
12349
|
+
verifiedTokens: "verified-tokens"
|
|
12227
12350
|
};
|
|
12228
12351
|
|
|
12229
12352
|
// modules/payments-v2/machine/journal.ts
|
|
@@ -12740,6 +12863,7 @@ var TransferMachine = class {
|
|
|
12740
12863
|
const key = keys.get(digest);
|
|
12741
12864
|
if (key === void 0) throw new SphereError(`no upload key returned for ${digest}`, "STORAGE_ERROR");
|
|
12742
12865
|
const { tokenId } = await engine.deliveryKeys(o.changeBlob);
|
|
12866
|
+
await this.deps.recordSplit?.(o.op.sourceTokenId, tokenId);
|
|
12743
12867
|
added.push({ tokenId, key });
|
|
12744
12868
|
}
|
|
12745
12869
|
return added;
|
|
@@ -13135,7 +13259,7 @@ var Converger = class {
|
|
|
13135
13259
|
this.deps.stores.burnJournal.list()
|
|
13136
13260
|
]);
|
|
13137
13261
|
const mintsOpen = mints.length > 0 || nftMints.length > 0 || customMints.length > 0;
|
|
13138
|
-
const burnsOpen = burns.some(
|
|
13262
|
+
const burnsOpen = burns.some(isLiveBurn);
|
|
13139
13263
|
return backstop.length > 0 || journal.some((e) => e.undeliverable !== true) || mintsOpen || burnsOpen;
|
|
13140
13264
|
}
|
|
13141
13265
|
async resumePass() {
|
|
@@ -13222,6 +13346,9 @@ async function deriveOpenIntentHolds(stores, decryptPayload) {
|
|
|
13222
13346
|
const split = typeof payload?.split?.tokenId === "string" ? [payload.split.tokenId] : [];
|
|
13223
13347
|
holds.set(entry.transferId, [...direct, ...split]);
|
|
13224
13348
|
}
|
|
13349
|
+
for (const burn of await stores.burnJournal.list()) {
|
|
13350
|
+
if (isLiveBurn(burn)) holds.set(burn.burnId, [burn.tokenId]);
|
|
13351
|
+
}
|
|
13225
13352
|
return { open: holds, complete };
|
|
13226
13353
|
}
|
|
13227
13354
|
function groupJournal(journal) {
|
|
@@ -13532,18 +13659,21 @@ function toToken(tokenId, snapshot, asset, registry, flags) {
|
|
|
13532
13659
|
createdAt: snapshot.createdAt,
|
|
13533
13660
|
updatedAt: snapshot.updatedAt,
|
|
13534
13661
|
lazy: true,
|
|
13535
|
-
...flags.suspectedSpent ? { suspectedSpent: true } : {}
|
|
13662
|
+
...flags.suspectedSpent ? { suspectedSpent: true } : {},
|
|
13663
|
+
...flags.standing !== "trusted" ? { unverified: flags.standing } : {}
|
|
13536
13664
|
};
|
|
13537
13665
|
}
|
|
13538
|
-
function aggregateAssets(entries,
|
|
13666
|
+
function aggregateAssets(entries, reading, registry) {
|
|
13539
13667
|
const groups = /* @__PURE__ */ new Map();
|
|
13540
13668
|
for (const [tokenId, entry] of entries) {
|
|
13541
|
-
const moving = isTransferring(tokenId);
|
|
13669
|
+
const moving = reading.isTransferring(tokenId);
|
|
13542
13670
|
for (const asset of entry.assets) {
|
|
13543
|
-
|
|
13671
|
+
const standing = reading.standing(tokenId, asset.coinId);
|
|
13672
|
+
const key = standing === "trusted" ? asset.coinId : `${asset.coinId}:${standing}`;
|
|
13673
|
+
let group = groups.get(key);
|
|
13544
13674
|
if (!group) {
|
|
13545
|
-
group = { total: 0n, count: 0, transferring: 0n, transferringCount: 0 };
|
|
13546
|
-
groups.set(
|
|
13675
|
+
group = { coinId: asset.coinId, standing, total: 0n, count: 0, transferring: 0n, transferringCount: 0 };
|
|
13676
|
+
groups.set(key, group);
|
|
13547
13677
|
}
|
|
13548
13678
|
if (moving) {
|
|
13549
13679
|
group.transferring += BigInt(asset.amount);
|
|
@@ -13554,9 +13684,10 @@ function aggregateAssets(entries, isTransferring, registry) {
|
|
|
13554
13684
|
}
|
|
13555
13685
|
}
|
|
13556
13686
|
}
|
|
13557
|
-
return [...groups].map((
|
|
13687
|
+
return [...groups.values()].map((totals) => toAsset(totals, registry));
|
|
13558
13688
|
}
|
|
13559
|
-
function toAsset(
|
|
13689
|
+
function toAsset(totals, registry) {
|
|
13690
|
+
const coinId = totals.coinId;
|
|
13560
13691
|
const iconUrl = registry.getIconUrl(coinId);
|
|
13561
13692
|
return {
|
|
13562
13693
|
coinId,
|
|
@@ -13576,7 +13707,8 @@ function toAsset(coinId, totals, registry) {
|
|
|
13576
13707
|
priceEur: null,
|
|
13577
13708
|
change24h: null,
|
|
13578
13709
|
fiatValueUsd: null,
|
|
13579
|
-
fiatValueEur: null
|
|
13710
|
+
fiatValueEur: null,
|
|
13711
|
+
...totals.standing !== "trusted" ? { unverified: totals.standing } : {}
|
|
13580
13712
|
};
|
|
13581
13713
|
}
|
|
13582
13714
|
function priceIdOf(registry, coinId) {
|
|
@@ -13584,9 +13716,9 @@ function priceIdOf(registry, coinId) {
|
|
|
13584
13716
|
}
|
|
13585
13717
|
async function withPrices(raw, registry, price) {
|
|
13586
13718
|
try {
|
|
13587
|
-
const ids = [...new Set(raw.map((a) => priceIdOf(registry, a.coinId)))];
|
|
13719
|
+
const ids = [...new Set(raw.filter((a) => !a.unverified).map((a) => priceIdOf(registry, a.coinId)))];
|
|
13588
13720
|
const quotes = await price.getPrices(ids);
|
|
13589
|
-
return raw.map((a) => priceOne(a, quotes.get(priceIdOf(registry, a.coinId))));
|
|
13721
|
+
return raw.map((a) => a.unverified ? a : priceOne(a, quotes.get(priceIdOf(registry, a.coinId))));
|
|
13590
13722
|
} catch {
|
|
13591
13723
|
return raw;
|
|
13592
13724
|
}
|
|
@@ -13607,11 +13739,13 @@ function transferringToken(tokenId, coinId, amount, registry, now) {
|
|
|
13607
13739
|
const snapshot = { assets: [], createdAt: now, updatedAt: now };
|
|
13608
13740
|
return toToken(tokenId, snapshot, { coinId, amount: amount.toString() }, registry, {
|
|
13609
13741
|
transferring: true,
|
|
13610
|
-
suspectedSpent: false
|
|
13742
|
+
suspectedSpent: false,
|
|
13743
|
+
standing: "trusted"
|
|
13611
13744
|
});
|
|
13612
13745
|
}
|
|
13613
13746
|
|
|
13614
13747
|
// modules/payments-v2/inventory/InventoryView.ts
|
|
13748
|
+
var TRUST_ALL = { isClaimed: () => false, standing: () => "trusted" };
|
|
13615
13749
|
var SUSPECTED_KEY = STORE_KEYS.suspectedSpent;
|
|
13616
13750
|
var KNOWN_SPENDS_KEY = STORE_KEYS.knownSpends;
|
|
13617
13751
|
var CURSOR_KEY = STORE_KEYS.streamCursor("inventory");
|
|
@@ -13705,6 +13839,12 @@ var InventoryView = class {
|
|
|
13705
13839
|
for (const tokenId of tokenIds) if (this.inFlight.delete(tokenId)) changed = true;
|
|
13706
13840
|
if (changed) this.deps.emit("inventory:updated");
|
|
13707
13841
|
}
|
|
13842
|
+
get trust() {
|
|
13843
|
+
return this.deps.trust ?? TRUST_ALL;
|
|
13844
|
+
}
|
|
13845
|
+
trusts(tokenId, coinId) {
|
|
13846
|
+
return this.trust.standing(tokenId, coinId) === "trusted";
|
|
13847
|
+
}
|
|
13708
13848
|
pinned(tokenId) {
|
|
13709
13849
|
return this.deps.isPinned?.(tokenId) ?? false;
|
|
13710
13850
|
}
|
|
@@ -13731,6 +13871,7 @@ var InventoryView = class {
|
|
|
13731
13871
|
isSpendable(tokenId, entry) {
|
|
13732
13872
|
if (entry.status !== "active") return false;
|
|
13733
13873
|
if (this.inFlight.has(tokenId) && !this.pinned(tokenId)) return false;
|
|
13874
|
+
if (!entry.assets.every((asset) => this.trusts(tokenId, asset.coinId))) return false;
|
|
13734
13875
|
return !this.suspected.has(stateKey(tokenId, entry.stateHash));
|
|
13735
13876
|
}
|
|
13736
13877
|
/** Is this NAMED token spendable whole — coinless or valued (#777)? */
|
|
@@ -13757,7 +13898,8 @@ var InventoryView = class {
|
|
|
13757
13898
|
out.push(
|
|
13758
13899
|
toToken(tokenId, entry, asset, registry, {
|
|
13759
13900
|
transferring: this.held(tokenId),
|
|
13760
|
-
suspectedSpent: this.suspected.has(stateKey(tokenId, entry.stateHash))
|
|
13901
|
+
suspectedSpent: this.suspected.has(stateKey(tokenId, entry.stateHash)),
|
|
13902
|
+
standing: this.trust.standing(tokenId, asset.coinId)
|
|
13761
13903
|
})
|
|
13762
13904
|
);
|
|
13763
13905
|
}
|
|
@@ -13787,10 +13929,21 @@ var InventoryView = class {
|
|
|
13787
13929
|
return out;
|
|
13788
13930
|
}
|
|
13789
13931
|
async assets(registry, price) {
|
|
13790
|
-
const
|
|
13932
|
+
const reading = {
|
|
13933
|
+
isTransferring: (tokenId) => this.held(tokenId),
|
|
13934
|
+
standing: (tokenId, coinId) => this.trust.standing(tokenId, coinId)
|
|
13935
|
+
};
|
|
13936
|
+
const raw = aggregateAssets(this.activeEntries(), reading, registry);
|
|
13791
13937
|
if (!price || raw.length === 0) return raw;
|
|
13792
13938
|
return withPrices(raw, registry, price);
|
|
13793
13939
|
}
|
|
13940
|
+
claimHolders() {
|
|
13941
|
+
const out = [];
|
|
13942
|
+
for (const [tokenId, entry] of this.activeEntries()) {
|
|
13943
|
+
if (entry.assets.some((asset) => this.trust.isClaimed(asset.coinId))) out.push(tokenId);
|
|
13944
|
+
}
|
|
13945
|
+
return out;
|
|
13946
|
+
}
|
|
13794
13947
|
*activeEntries() {
|
|
13795
13948
|
for (const [tokenId, entry] of this.mirror) {
|
|
13796
13949
|
if (entry.status === "active") yield [tokenId, entry];
|
|
@@ -13960,6 +14113,171 @@ var InventoryView = class {
|
|
|
13960
14113
|
}
|
|
13961
14114
|
};
|
|
13962
14115
|
|
|
14116
|
+
// modules/payments-v2/inventory/verdicts.ts
|
|
14117
|
+
var VERDICT_RETRY_MS = 3e4;
|
|
14118
|
+
var VERDICT_RETRY_MAX_MS = 60 * 60 * 1e3;
|
|
14119
|
+
function honoursClaims(claims, token) {
|
|
14120
|
+
const issuers = (token.value?.assets ?? []).map((asset) => claims.issuerOf(asset.coinId));
|
|
14121
|
+
return issuers.some((issuer) => issuer !== null) && issuers.every((issuer) => issuer === null || issuer === token.tokenType);
|
|
14122
|
+
}
|
|
14123
|
+
var TokenVerdicts = class {
|
|
14124
|
+
constructor(deps) {
|
|
14125
|
+
this.deps = deps;
|
|
14126
|
+
}
|
|
14127
|
+
verified = /* @__PURE__ */ new Set();
|
|
14128
|
+
refused = /* @__PURE__ */ new Set();
|
|
14129
|
+
retries = /* @__PURE__ */ new Map();
|
|
14130
|
+
writes = new SerialChain();
|
|
14131
|
+
hydration = null;
|
|
14132
|
+
queued = null;
|
|
14133
|
+
running = null;
|
|
14134
|
+
held = [];
|
|
14135
|
+
timer = null;
|
|
14136
|
+
live = false;
|
|
14137
|
+
isClaimed(coinId) {
|
|
14138
|
+
return this.deps.claims.issuerOf(coinId) !== null;
|
|
14139
|
+
}
|
|
14140
|
+
standing(tokenId, coinId) {
|
|
14141
|
+
if (!this.isClaimed(coinId) || this.verified.has(tokenId)) return "trusted";
|
|
14142
|
+
return this.refused.has(tokenId) ? "refused" : "pending";
|
|
14143
|
+
}
|
|
14144
|
+
trusts(tokenId, coinId) {
|
|
14145
|
+
return this.standing(tokenId, coinId) === "trusted";
|
|
14146
|
+
}
|
|
14147
|
+
hydrate() {
|
|
14148
|
+
this.hydration ??= this.load().catch((error) => {
|
|
14149
|
+
this.hydration = null;
|
|
14150
|
+
throw error;
|
|
14151
|
+
});
|
|
14152
|
+
return this.hydration;
|
|
14153
|
+
}
|
|
14154
|
+
start() {
|
|
14155
|
+
this.live = true;
|
|
14156
|
+
return this.hydrate();
|
|
14157
|
+
}
|
|
14158
|
+
stop() {
|
|
14159
|
+
this.live = false;
|
|
14160
|
+
if (this.timer !== null) clearTimeout(this.timer);
|
|
14161
|
+
this.timer = null;
|
|
14162
|
+
}
|
|
14163
|
+
async accept(token) {
|
|
14164
|
+
if (honoursClaims(this.deps.claims, token)) await this.remember(token.blob.tokenId);
|
|
14165
|
+
}
|
|
14166
|
+
async recordSplit(sourceTokenId, outputTokenId) {
|
|
14167
|
+
await this.hydrate();
|
|
14168
|
+
if (this.verified.has(sourceTokenId)) await this.remember(outputTokenId);
|
|
14169
|
+
}
|
|
14170
|
+
review(holders) {
|
|
14171
|
+
this.queued = holders;
|
|
14172
|
+
this.running ??= this.drain();
|
|
14173
|
+
return this.running;
|
|
14174
|
+
}
|
|
14175
|
+
async drain() {
|
|
14176
|
+
try {
|
|
14177
|
+
while (this.queued !== null) {
|
|
14178
|
+
const holders = this.queued;
|
|
14179
|
+
this.queued = null;
|
|
14180
|
+
await this.reviewOnce(holders);
|
|
14181
|
+
}
|
|
14182
|
+
} finally {
|
|
14183
|
+
this.running = null;
|
|
14184
|
+
}
|
|
14185
|
+
}
|
|
14186
|
+
async reviewOnce(holders) {
|
|
14187
|
+
await this.hydrate();
|
|
14188
|
+
this.held = holders;
|
|
14189
|
+
const due = holders.filter((tokenId) => this.isDue(tokenId));
|
|
14190
|
+
const verdicts = due.length === 0 ? /* @__PURE__ */ new Map() : await this.check(due);
|
|
14191
|
+
for (const tokenId of due) this.settle(tokenId, verdicts.get(tokenId) ?? "retry");
|
|
14192
|
+
if (due.some((tokenId) => verdicts.get(tokenId) === "verified")) {
|
|
14193
|
+
await this.persist();
|
|
14194
|
+
this.deps.changed();
|
|
14195
|
+
}
|
|
14196
|
+
this.scheduleRetry();
|
|
14197
|
+
}
|
|
14198
|
+
isDue(tokenId) {
|
|
14199
|
+
if (this.verified.has(tokenId) || this.refused.has(tokenId)) return false;
|
|
14200
|
+
return (this.retries.get(tokenId)?.atMs ?? 0) <= this.now();
|
|
14201
|
+
}
|
|
14202
|
+
async check(due) {
|
|
14203
|
+
const verdicts = /* @__PURE__ */ new Map();
|
|
14204
|
+
let blobs;
|
|
14205
|
+
try {
|
|
14206
|
+
blobs = await this.deps.getBlobs([...due]);
|
|
14207
|
+
} catch {
|
|
14208
|
+
return verdicts;
|
|
14209
|
+
}
|
|
14210
|
+
for (const tokenId of due) {
|
|
14211
|
+
const bytes = blobs.get(tokenId);
|
|
14212
|
+
if (bytes !== void 0) verdicts.set(tokenId, await this.verdictOf(tokenId, bytes));
|
|
14213
|
+
}
|
|
14214
|
+
return verdicts;
|
|
14215
|
+
}
|
|
14216
|
+
async verdictOf(tokenId, bytes) {
|
|
14217
|
+
let engine;
|
|
14218
|
+
try {
|
|
14219
|
+
engine = this.deps.engine();
|
|
14220
|
+
} catch {
|
|
14221
|
+
return "retry";
|
|
14222
|
+
}
|
|
14223
|
+
let token;
|
|
14224
|
+
try {
|
|
14225
|
+
token = await engine.decodeToken({ tokenId, token: bytes });
|
|
14226
|
+
} catch {
|
|
14227
|
+
return "refused";
|
|
14228
|
+
}
|
|
14229
|
+
if (token.blob.tokenId !== tokenId || !honoursClaims(this.deps.claims, token)) return "refused";
|
|
14230
|
+
try {
|
|
14231
|
+
return (await engine.verify(token)).ok ? "verified" : "refused";
|
|
14232
|
+
} catch {
|
|
14233
|
+
return "retry";
|
|
14234
|
+
}
|
|
14235
|
+
}
|
|
14236
|
+
settle(tokenId, verdict) {
|
|
14237
|
+
if (verdict === "retry") {
|
|
14238
|
+
this.retries.set(tokenId, this.nextRetry(this.retries.get(tokenId)));
|
|
14239
|
+
return;
|
|
14240
|
+
}
|
|
14241
|
+
this.retries.delete(tokenId);
|
|
14242
|
+
(verdict === "verified" ? this.verified : this.refused).add(tokenId);
|
|
14243
|
+
}
|
|
14244
|
+
nextRetry(previous) {
|
|
14245
|
+
const attempts = (previous?.attempts ?? 0) + 1;
|
|
14246
|
+
const delay = Math.min(VERDICT_RETRY_MS * 2 ** (attempts - 1), VERDICT_RETRY_MAX_MS);
|
|
14247
|
+
return { attempts, atMs: this.now() + delay };
|
|
14248
|
+
}
|
|
14249
|
+
scheduleRetry() {
|
|
14250
|
+
if (!this.live || this.timer !== null) return;
|
|
14251
|
+
const due = this.held.flatMap((tokenId) => this.retries.get(tokenId)?.atMs ?? []);
|
|
14252
|
+
if (due.length === 0) return;
|
|
14253
|
+
const delay = Math.max(0, due.reduce((a, b) => Math.min(a, b)) - this.now());
|
|
14254
|
+
this.timer = setTimeout(() => {
|
|
14255
|
+
this.timer = null;
|
|
14256
|
+
void this.review(this.held).catch(() => void 0);
|
|
14257
|
+
}, delay);
|
|
14258
|
+
}
|
|
14259
|
+
async remember(tokenId) {
|
|
14260
|
+
await this.hydrate();
|
|
14261
|
+
if (this.verified.has(tokenId)) return;
|
|
14262
|
+
this.verified.add(tokenId);
|
|
14263
|
+
this.refused.delete(tokenId);
|
|
14264
|
+
this.retries.delete(tokenId);
|
|
14265
|
+
await this.persist();
|
|
14266
|
+
}
|
|
14267
|
+
persist() {
|
|
14268
|
+
const remembered = { fingerprint: this.deps.claims.fingerprint(), tokenIds: [...this.verified] };
|
|
14269
|
+
return this.writes.enqueue(() => this.deps.kv.set(STORE_KEYS.verifiedTokens, remembered));
|
|
14270
|
+
}
|
|
14271
|
+
async load() {
|
|
14272
|
+
const remembered = await this.deps.kv.get(STORE_KEYS.verifiedTokens);
|
|
14273
|
+
if (remembered?.fingerprint !== this.deps.claims.fingerprint()) return;
|
|
14274
|
+
for (const tokenId of remembered.tokenIds) this.verified.add(tokenId);
|
|
14275
|
+
}
|
|
14276
|
+
now() {
|
|
14277
|
+
return (this.deps.now ?? Date.now)();
|
|
14278
|
+
}
|
|
14279
|
+
};
|
|
14280
|
+
|
|
13963
14281
|
// modules/payments-v2/ports.ts
|
|
13964
14282
|
function isRetryableAckError(err) {
|
|
13965
14283
|
return err !== null && typeof err === "object" && err.retryable === true;
|
|
@@ -13969,6 +14287,8 @@ function isRetryableAckError(err) {
|
|
|
13969
14287
|
var ACK_BATCH_SIZE = 200;
|
|
13970
14288
|
var REFRESH_INTERVAL_MS = 2500;
|
|
13971
14289
|
var POLL_INTERVAL_MS = 3e4;
|
|
14290
|
+
var RECHECK_BASE_MS = POLL_INTERVAL_MS;
|
|
14291
|
+
var RECHECK_MAX_MS = 60 * 60 * 1e3;
|
|
13972
14292
|
var ATTENTION_CLAIM_CONFLICT = "claim:conflict";
|
|
13973
14293
|
function receivedDedupKey(tokenId, stateHash) {
|
|
13974
14294
|
return `RECEIVED:${tokenId.toLowerCase()}:${stateHash.toLowerCase()}`;
|
|
@@ -13985,6 +14305,7 @@ var Receive = class {
|
|
|
13985
14305
|
pollTimer = null;
|
|
13986
14306
|
unsubscribeWake = null;
|
|
13987
14307
|
lastRefreshAt = 0;
|
|
14308
|
+
rechecks = /* @__PURE__ */ new Map();
|
|
13988
14309
|
drainOnce() {
|
|
13989
14310
|
return this.drainFlight.run(() => this.doDrain());
|
|
13990
14311
|
}
|
|
@@ -14043,6 +14364,7 @@ var Receive = class {
|
|
|
14043
14364
|
const pending = [];
|
|
14044
14365
|
const pageEpoch = () => deps.delivery.incomingEpoch() ?? deps.syncEpoch();
|
|
14045
14366
|
const claimable = { n: 0 };
|
|
14367
|
+
const deferred = { any: false };
|
|
14046
14368
|
const finish = () => {
|
|
14047
14369
|
if (claimable.n > 0) deps.refreshView?.();
|
|
14048
14370
|
return stored;
|
|
@@ -14051,7 +14373,7 @@ var Receive = class {
|
|
|
14051
14373
|
const record = await deps.kv.get(STORE_KEYS.streamCursor("mailbox"));
|
|
14052
14374
|
let basis = record !== null && record.syncEpoch === deps.syncEpoch() ? record : null;
|
|
14053
14375
|
for (let pass = 0; pass < 2; pass++) {
|
|
14054
|
-
await this.drainPages({ deps, engine, pending, stored, pageEpoch, claimable }, basis);
|
|
14376
|
+
await this.drainPages({ deps, engine, pending, stored, pageEpoch, claimable, deferred }, basis);
|
|
14055
14377
|
const served = deps.delivery.incomingEpoch();
|
|
14056
14378
|
if (basis === null || served === null || served === basis.syncEpoch) break;
|
|
14057
14379
|
await deps.kv.remove(STORE_KEYS.streamCursor("mailbox"));
|
|
@@ -14068,20 +14390,41 @@ var Receive = class {
|
|
|
14068
14390
|
return finish();
|
|
14069
14391
|
}
|
|
14070
14392
|
async processEntry(ctx, entry) {
|
|
14071
|
-
const { deps, engine,
|
|
14072
|
-
const screened = await screen(deps, engine, entry);
|
|
14393
|
+
const { deps, engine, stored, claimable } = ctx;
|
|
14394
|
+
const screened = this.recheckDue(entry.deliveryId) ? await screen(deps, engine, entry) : { kind: "defer" };
|
|
14395
|
+
this.scheduleRecheck(entry.deliveryId, screened.kind === "defer");
|
|
14396
|
+
if (screened.kind === "defer") {
|
|
14397
|
+
ctx.deferred.any = true;
|
|
14398
|
+
return;
|
|
14399
|
+
}
|
|
14073
14400
|
if (screened.kind === "ack") {
|
|
14074
|
-
|
|
14401
|
+
queueAck(ctx, screened.ack);
|
|
14075
14402
|
if (screened.ack.disposition === "claimed") claimable.n += 1;
|
|
14076
14403
|
return;
|
|
14077
14404
|
}
|
|
14405
|
+
await deps.accepted?.(screened.token);
|
|
14078
14406
|
await deps.view.store(screened.record);
|
|
14079
|
-
|
|
14407
|
+
queueAck(ctx, claimAck(entry));
|
|
14080
14408
|
claimable.n += 1;
|
|
14081
14409
|
const transfer = await announce(deps, entry, screened.record);
|
|
14082
14410
|
stored.push(transfer);
|
|
14083
14411
|
deps.emit("transfer:incoming", transfer);
|
|
14084
14412
|
}
|
|
14413
|
+
recheckDue(deliveryId) {
|
|
14414
|
+
const recheck = this.rechecks.get(deliveryId);
|
|
14415
|
+
return recheck === void 0 || (this.deps.now?.() ?? Date.now()) >= recheck.atMs;
|
|
14416
|
+
}
|
|
14417
|
+
scheduleRecheck(deliveryId, deferred) {
|
|
14418
|
+
if (!deferred) {
|
|
14419
|
+
this.rechecks.delete(deliveryId);
|
|
14420
|
+
return;
|
|
14421
|
+
}
|
|
14422
|
+
const previous = this.rechecks.get(deliveryId);
|
|
14423
|
+
if (previous !== void 0 && (this.deps.now?.() ?? Date.now()) < previous.atMs) return;
|
|
14424
|
+
const attempts = (previous?.attempts ?? 0) + 1;
|
|
14425
|
+
const delay = Math.min(RECHECK_BASE_MS * 2 ** (attempts - 1), RECHECK_MAX_MS);
|
|
14426
|
+
this.rechecks.set(deliveryId, { attempts, atMs: (this.deps.now?.() ?? Date.now()) + delay });
|
|
14427
|
+
}
|
|
14085
14428
|
};
|
|
14086
14429
|
async function screen(deps, engine, entry) {
|
|
14087
14430
|
const blobBytes = await entry.fetchBlob();
|
|
@@ -14092,7 +14435,8 @@ async function screen(deps, engine, entry) {
|
|
|
14092
14435
|
logger.warn("PaymentsV2", `receive: undecodable blob rejected \u2014 ${String(err)}`);
|
|
14093
14436
|
return { kind: "ack", ack: rejectAck(entry, "invalid") };
|
|
14094
14437
|
}
|
|
14095
|
-
const verdict = await engine
|
|
14438
|
+
const verdict = await verdictOf(engine, token);
|
|
14439
|
+
if (verdict === null) return { kind: "defer" };
|
|
14096
14440
|
if (!verdict.ok) return { kind: "ack", ack: rejectAck(entry, "invalid") };
|
|
14097
14441
|
if (!engine.isOwnedBy(token, engine.getIdentity().chainPubkey)) {
|
|
14098
14442
|
return { kind: "ack", ack: rejectAck(entry, "not-owned") };
|
|
@@ -14103,38 +14447,43 @@ async function screen(deps, engine, entry) {
|
|
|
14103
14447
|
if (held !== null && await engine.isSpent(token)) {
|
|
14104
14448
|
return { kind: "ack", ack: rejectAck(entry, "invalid") };
|
|
14105
14449
|
}
|
|
14450
|
+
const unverifiedCoinIds = unverifiedCoinsOf(deps, token);
|
|
14106
14451
|
return {
|
|
14107
14452
|
kind: "accept",
|
|
14453
|
+
token,
|
|
14108
14454
|
record: {
|
|
14109
14455
|
tokenId: keys.tokenId,
|
|
14110
14456
|
stateHash: keys.stateHash,
|
|
14111
14457
|
assets: toAssetAmounts(token),
|
|
14112
|
-
...isCoinlessEnvelope(token.valueEnvelope) ? { tokenType: token.tokenType } : {}
|
|
14458
|
+
...isCoinlessEnvelope(token.valueEnvelope) ? { tokenType: token.tokenType } : {},
|
|
14459
|
+
...unverifiedCoinIds.length > 0 ? { unverifiedCoinIds } : {}
|
|
14113
14460
|
}
|
|
14114
14461
|
};
|
|
14115
14462
|
}
|
|
14116
|
-
|
|
14117
|
-
const
|
|
14463
|
+
function unverifiedCoinsOf(deps, token) {
|
|
14464
|
+
const claims = deps.claims;
|
|
14465
|
+
if (claims === void 0 || honoursClaims(claims, token)) return [];
|
|
14466
|
+
return (token.value?.assets ?? []).map((asset) => asset.coinId).filter((coinId) => claims.issuerOf(coinId) !== null);
|
|
14467
|
+
}
|
|
14468
|
+
async function verdictOf(engine, token) {
|
|
14118
14469
|
try {
|
|
14119
|
-
await
|
|
14120
|
-
dedupKey: receivedDedupKey(record.tokenId, record.stateHash),
|
|
14121
|
-
tokenId: record.tokenId,
|
|
14122
|
-
stateHash: record.stateHash,
|
|
14123
|
-
assets: record.assets,
|
|
14124
|
-
...record.tokenType !== void 0 ? { tokenType: record.tokenType } : {},
|
|
14125
|
-
...entry.senderPubkey !== void 0 ? { senderPubkey: entry.senderPubkey } : {},
|
|
14126
|
-
...entry.senderNametag !== void 0 ? { senderNametag: entry.senderNametag } : {},
|
|
14127
|
-
...entry.memo !== void 0 ? { memo: entry.memo } : {},
|
|
14128
|
-
receivedAt
|
|
14129
|
-
});
|
|
14470
|
+
return await engine.verify(token);
|
|
14130
14471
|
} catch (err) {
|
|
14131
|
-
|
|
14472
|
+
if (isSphereError(err) && err.code === "MINT_REASON_UNVERIFIABLE") return null;
|
|
14473
|
+
throw err;
|
|
14132
14474
|
}
|
|
14475
|
+
}
|
|
14476
|
+
async function announce(deps, entry, record) {
|
|
14477
|
+
const receivedAt = (deps.now ?? Date.now)();
|
|
14478
|
+
await recordArrival(deps, entry, record, receivedAt);
|
|
14479
|
+
const refused2 = record.assets.filter((asset) => record.unverifiedCoinIds?.includes(asset.coinId));
|
|
14480
|
+
const counted = record.assets.filter((asset) => !refused2.includes(asset));
|
|
14133
14481
|
return {
|
|
14134
14482
|
id: record.tokenId,
|
|
14135
14483
|
senderPubkey: entry.senderPubkey ?? "",
|
|
14136
14484
|
...entry.senderNametag !== void 0 ? { senderNametag: entry.senderNametag } : {},
|
|
14137
|
-
tokens:
|
|
14485
|
+
tokens: counted.map((asset) => toUiToken(record.tokenId, asset, deps.registry, receivedAt)),
|
|
14486
|
+
...refused2.length > 0 ? { unverifiedTokens: refused2.map((asset) => ({ ...toUiToken(record.tokenId, asset, deps.registry, receivedAt), unverified: "refused" })) } : {},
|
|
14138
14487
|
// #777: named here rather than mapped from assets, which announced an EMPTY list.
|
|
14139
14488
|
...record.tokenType !== void 0 ? {
|
|
14140
14489
|
coinless: [
|
|
@@ -14152,6 +14501,25 @@ async function announce(deps, entry, record) {
|
|
|
14152
14501
|
receivedAt
|
|
14153
14502
|
};
|
|
14154
14503
|
}
|
|
14504
|
+
async function recordArrival(deps, entry, record, receivedAt) {
|
|
14505
|
+
const assets = record.assets.filter((asset) => !record.unverifiedCoinIds?.includes(asset.coinId));
|
|
14506
|
+
if (assets.length === 0 && record.unverifiedCoinIds !== void 0) return;
|
|
14507
|
+
try {
|
|
14508
|
+
await deps.recordReceived({
|
|
14509
|
+
dedupKey: receivedDedupKey(record.tokenId, record.stateHash),
|
|
14510
|
+
tokenId: record.tokenId,
|
|
14511
|
+
stateHash: record.stateHash,
|
|
14512
|
+
assets,
|
|
14513
|
+
...record.tokenType !== void 0 ? { tokenType: record.tokenType } : {},
|
|
14514
|
+
...entry.senderPubkey !== void 0 ? { senderPubkey: entry.senderPubkey } : {},
|
|
14515
|
+
...entry.senderNametag !== void 0 ? { senderNametag: entry.senderNametag } : {},
|
|
14516
|
+
...entry.memo !== void 0 ? { memo: entry.memo } : {},
|
|
14517
|
+
receivedAt
|
|
14518
|
+
});
|
|
14519
|
+
} catch (err) {
|
|
14520
|
+
logger.debug("PaymentsV2", "RECEIVED history hook failed (money path unaffected):", err);
|
|
14521
|
+
}
|
|
14522
|
+
}
|
|
14155
14523
|
async function flushAcks(deps, pending, epochOf) {
|
|
14156
14524
|
if (pending.length === 0) return { progressed: true };
|
|
14157
14525
|
const before = pending.length;
|
|
@@ -14159,7 +14527,7 @@ async function flushAcks(deps, pending, epochOf) {
|
|
|
14159
14527
|
try {
|
|
14160
14528
|
const settled = await settleAcks(deps, pending);
|
|
14161
14529
|
let i = 0;
|
|
14162
|
-
while (i < pending.length && settled.has(pending[i].deliveryId)) {
|
|
14530
|
+
while (i < pending.length && settled.has(pending[i].deliveryId) && pending[i].holdsCursor !== true) {
|
|
14163
14531
|
lastAcked = pending[i].cursor;
|
|
14164
14532
|
i += 1;
|
|
14165
14533
|
}
|
|
@@ -14235,6 +14603,9 @@ async function ackOne(deps, ack) {
|
|
|
14235
14603
|
await rejectStaleClaim(deps, ack.deliveryId, ack.transferId);
|
|
14236
14604
|
}
|
|
14237
14605
|
}
|
|
14606
|
+
function queueAck(ctx, ack) {
|
|
14607
|
+
ctx.pending.push(ctx.deferred.any ? { ...ack, holdsCursor: true } : ack);
|
|
14608
|
+
}
|
|
14238
14609
|
function claimAck(entry) {
|
|
14239
14610
|
return {
|
|
14240
14611
|
deliveryId: entry.deliveryId,
|
|
@@ -14943,72 +15314,276 @@ var SpendQueue = class {
|
|
|
14943
15314
|
queue = [];
|
|
14944
15315
|
this.queues.set(coinId, queue);
|
|
14945
15316
|
}
|
|
14946
|
-
queue.push(entry);
|
|
14947
|
-
return settled;
|
|
15317
|
+
queue.push(entry);
|
|
15318
|
+
return settled;
|
|
15319
|
+
}
|
|
15320
|
+
expire(id, coinId) {
|
|
15321
|
+
const entries = this.queues.get(coinId);
|
|
15322
|
+
if (!entries) return;
|
|
15323
|
+
const index = entries.findIndex((e) => e.id === id);
|
|
15324
|
+
if (index === -1) return;
|
|
15325
|
+
const [entry] = entries.splice(index, 1);
|
|
15326
|
+
entry.reject(new SphereError("Send queue timeout", "SEND_QUEUE_TIMEOUT"));
|
|
15327
|
+
if (entries.length === 0) this.queues.delete(coinId);
|
|
15328
|
+
}
|
|
15329
|
+
// Whole-token semantics: only fully-free tokens enter the selector pool. What
|
|
15330
|
+
// the reservations hold back is counted too — the refusal has to name it (#737).
|
|
15331
|
+
freeView(coinId) {
|
|
15332
|
+
const freeView = [];
|
|
15333
|
+
let freeTotal = 0n;
|
|
15334
|
+
let pinnedTotal = 0n;
|
|
15335
|
+
const holders = /* @__PURE__ */ new Set();
|
|
15336
|
+
const unproven = this.deps.ledger.unprovenReason();
|
|
15337
|
+
for (const entry of this.deps.getPool(coinId)) {
|
|
15338
|
+
if (entry.amount <= 0n) continue;
|
|
15339
|
+
const holder = unproven === null ? this.deps.ledger.holderOf(entry.tokenId) : entry.tokenId;
|
|
15340
|
+
if (holder === void 0) {
|
|
15341
|
+
freeView.push(entry);
|
|
15342
|
+
freeTotal += entry.amount;
|
|
15343
|
+
} else {
|
|
15344
|
+
pinnedTotal += entry.amount;
|
|
15345
|
+
holders.add(holder);
|
|
15346
|
+
}
|
|
15347
|
+
}
|
|
15348
|
+
return { freeView, freeTotal, pinnedTotal, pinnedBy: holders.size, unproven };
|
|
15349
|
+
}
|
|
15350
|
+
expectedChangeTotal(coinId) {
|
|
15351
|
+
let total = 0n;
|
|
15352
|
+
for (const change of this.expectedChange.values()) {
|
|
15353
|
+
if (change.coinId === coinId) total += change.amount;
|
|
15354
|
+
}
|
|
15355
|
+
return total;
|
|
15356
|
+
}
|
|
15357
|
+
reservePlan(reservationId, plan, freeView) {
|
|
15358
|
+
const pool = new Set(freeView.map((e) => e.tokenId));
|
|
15359
|
+
const tokenIds = [...plan.direct, ...plan.split ? [plan.split.tokenId] : []];
|
|
15360
|
+
for (const tokenId of tokenIds) {
|
|
15361
|
+
if (!pool.has(tokenId)) {
|
|
15362
|
+
throw new SphereError("Planned token missing from pool", "VALIDATION_ERROR");
|
|
15363
|
+
}
|
|
15364
|
+
}
|
|
15365
|
+
this.deps.ledger.reserve(reservationId, tokenIds.map((tokenId) => ({ tokenId })));
|
|
15366
|
+
}
|
|
15367
|
+
totalQueued() {
|
|
15368
|
+
let total = 0;
|
|
15369
|
+
for (const entries of this.queues.values()) total += entries.length;
|
|
15370
|
+
return total;
|
|
15371
|
+
}
|
|
15372
|
+
};
|
|
15373
|
+
function parseAmount(raw) {
|
|
15374
|
+
if (!/^\d+$/.test(raw)) {
|
|
15375
|
+
throw new SphereError("Invalid send amount", "VALIDATION_ERROR");
|
|
15376
|
+
}
|
|
15377
|
+
const amount = BigInt(raw);
|
|
15378
|
+
if (amount <= 0n) {
|
|
15379
|
+
throw new SphereError("Send amount must be positive", "VALIDATION_ERROR");
|
|
15380
|
+
}
|
|
15381
|
+
return amount;
|
|
15382
|
+
}
|
|
15383
|
+
|
|
15384
|
+
// modules/payments-v2/machine/payload.ts
|
|
15385
|
+
function buildPayload(recipientPubkey, request, spend, spentStates) {
|
|
15386
|
+
return {
|
|
15387
|
+
v: 2,
|
|
15388
|
+
kind: "coin",
|
|
15389
|
+
recipient: recipientPubkey,
|
|
15390
|
+
coinId: request.coinId,
|
|
15391
|
+
amount: request.amount,
|
|
15392
|
+
...request.memo !== void 0 ? { memo: request.memo } : {},
|
|
15393
|
+
direct: [...spend.plan.direct],
|
|
15394
|
+
...spend.plan.split !== void 0 ? {
|
|
15395
|
+
split: {
|
|
15396
|
+
tokenId: spend.plan.split.tokenId,
|
|
15397
|
+
splitAmount: spend.plan.split.splitAmount.toString(),
|
|
15398
|
+
remainderAmount: spend.plan.split.remainderAmount.toString()
|
|
15399
|
+
}
|
|
15400
|
+
} : {},
|
|
15401
|
+
spentStates
|
|
15402
|
+
};
|
|
15403
|
+
}
|
|
15404
|
+
function messageOf(err) {
|
|
15405
|
+
return err instanceof Error ? err.message : String(err);
|
|
15406
|
+
}
|
|
15407
|
+
function buildWholePayload(recipientPubkey, request, spentStates) {
|
|
15408
|
+
return {
|
|
15409
|
+
v: 2,
|
|
15410
|
+
kind: "whole",
|
|
15411
|
+
recipient: recipientPubkey,
|
|
15412
|
+
...request.memo !== void 0 ? { memo: request.memo } : {},
|
|
15413
|
+
direct: [request.tokenId],
|
|
15414
|
+
spentStates
|
|
15415
|
+
};
|
|
15416
|
+
}
|
|
15417
|
+
|
|
15418
|
+
// modules/payments-v2/burn.ts
|
|
15419
|
+
var HEX32 = /^[0-9a-f]{64}$/;
|
|
15420
|
+
async function heldToken2(deps, tokenId) {
|
|
15421
|
+
const bytes = (await deps.storagePort.getBlobs([tokenId])).get(tokenId);
|
|
15422
|
+
if (bytes === void 0) {
|
|
15423
|
+
throw new SphereError(`Token ${tokenId} has no blob in storage; it is not held here`, "STORAGE_ERROR");
|
|
15424
|
+
}
|
|
15425
|
+
return deps.engine.decodeToken({ tokenId, token: bytes });
|
|
15426
|
+
}
|
|
15427
|
+
function assertBurnable(engine, token, tokenId) {
|
|
15428
|
+
if (!engine.isOwnedBy(token, engine.getIdentity().chainPubkey)) {
|
|
15429
|
+
throw new SphereError(`Token ${tokenId} is not owned by this wallet`, "VALIDATION_ERROR");
|
|
15430
|
+
}
|
|
15431
|
+
if (token.valueEnvelope === "bare_collection") {
|
|
15432
|
+
throw new SphereError(
|
|
15433
|
+
`Token ${tokenId} carries a value envelope this SDK cannot read, so its coins cannot be accounted for; it cannot be burned`,
|
|
15434
|
+
"VALIDATION_ERROR"
|
|
15435
|
+
);
|
|
15436
|
+
}
|
|
15437
|
+
}
|
|
15438
|
+
function assetsOf(token) {
|
|
15439
|
+
return (token.value?.assets ?? []).map((a) => ({ coinId: a.coinId, amount: a.amount.toString() }));
|
|
15440
|
+
}
|
|
15441
|
+
async function runBurnUnderJournal(deps, input) {
|
|
15442
|
+
const { burnId } = input;
|
|
15443
|
+
const { tokenId, reasonBytes } = input.request;
|
|
15444
|
+
if (!HEX32.test(tokenId) || reasonBytes.length === 0) {
|
|
15445
|
+
return { success: false, burnId, tokenId, error: "burn: tokenId must be 64 hex and reasonBytes non-empty", errorCode: "VALIDATION_ERROR" };
|
|
15446
|
+
}
|
|
15447
|
+
let token;
|
|
15448
|
+
try {
|
|
15449
|
+
token = await heldToken2(deps, tokenId);
|
|
15450
|
+
assertBurnable(deps.engine, token, tokenId);
|
|
15451
|
+
} catch (err) {
|
|
15452
|
+
return refused(burnId, tokenId, err);
|
|
15453
|
+
}
|
|
15454
|
+
const entry = {
|
|
15455
|
+
burnId,
|
|
15456
|
+
tokenId,
|
|
15457
|
+
reasonHex: bytesToHex3(reasonBytes),
|
|
15458
|
+
burnedTokenHex: null,
|
|
15459
|
+
settled: false,
|
|
15460
|
+
assets: assetsOf(token),
|
|
15461
|
+
createdAt: deps.now()
|
|
15462
|
+
};
|
|
15463
|
+
await deps.burnJournal.upsert(entry);
|
|
15464
|
+
try {
|
|
15465
|
+
const settled = await burnJournaled(deps, entry, token);
|
|
15466
|
+
return { success: true, burnId, tokenId, burnedToken: hexToBytes4(settled.burnedTokenHex ?? "") };
|
|
15467
|
+
} catch (err) {
|
|
15468
|
+
const failure = failureOf(err);
|
|
15469
|
+
if (isTerminal(err)) await deps.burnJournal.upsert({ ...entry, failure });
|
|
15470
|
+
else deps.armHeartbeat();
|
|
15471
|
+
return { success: false, burnId, tokenId, error: failure.message, errorCode: failure.code };
|
|
15472
|
+
}
|
|
15473
|
+
}
|
|
15474
|
+
async function replayBurns(deps) {
|
|
15475
|
+
let progressed = 0;
|
|
15476
|
+
for (const entry of await deps.burnJournal.list()) {
|
|
15477
|
+
if (!isLiveBurn(entry) || deps.isActiveOp(entry.burnId)) continue;
|
|
15478
|
+
try {
|
|
15479
|
+
const token = entry.burnedTokenHex === null ? await heldToken2(deps, entry.tokenId) : null;
|
|
15480
|
+
await burnJournaled(deps, entry, token);
|
|
15481
|
+
progressed += 1;
|
|
15482
|
+
} catch (err) {
|
|
15483
|
+
if (!isTerminal(err)) continue;
|
|
15484
|
+
await deps.burnJournal.upsert({ ...entry, failure: failureOf(err) });
|
|
15485
|
+
progressed += 1;
|
|
15486
|
+
}
|
|
15487
|
+
}
|
|
15488
|
+
return progressed;
|
|
15489
|
+
}
|
|
15490
|
+
async function pendingBurns(deps) {
|
|
15491
|
+
return (await deps.burnJournal.list()).map((entry) => ({
|
|
15492
|
+
burnId: entry.burnId,
|
|
15493
|
+
tokenId: entry.tokenId,
|
|
15494
|
+
reasonBytes: hexToBytes4(entry.reasonHex),
|
|
15495
|
+
burnedToken: entry.burnedTokenHex === null ? null : hexToBytes4(entry.burnedTokenHex),
|
|
15496
|
+
settled: entry.settled,
|
|
15497
|
+
createdAt: entry.createdAt,
|
|
15498
|
+
...entry.failure !== void 0 ? { failure: entry.failure } : {}
|
|
15499
|
+
}));
|
|
15500
|
+
}
|
|
15501
|
+
async function acknowledgeBurn(deps, burnId) {
|
|
15502
|
+
const entry = await deps.burnJournal.getByKey(burnId);
|
|
15503
|
+
if (entry === void 0) return;
|
|
15504
|
+
if (isLiveBurn(entry)) {
|
|
15505
|
+
throw new SphereError(`Burn ${burnId} is not settled yet; it cannot be acknowledged`, "VALIDATION_ERROR");
|
|
15506
|
+
}
|
|
15507
|
+
await deps.burnJournal.removeByKey(burnId);
|
|
15508
|
+
}
|
|
15509
|
+
function refused(burnId, tokenId, err) {
|
|
15510
|
+
const failure = failureOf(err);
|
|
15511
|
+
return { success: false, burnId, tokenId, error: failure.message, errorCode: failure.code };
|
|
15512
|
+
}
|
|
15513
|
+
function isTerminal(err) {
|
|
15514
|
+
return classifyError(err) === "conflict";
|
|
15515
|
+
}
|
|
15516
|
+
function failureOf(err) {
|
|
15517
|
+
const code = err !== null && typeof err === "object" ? err.code : void 0;
|
|
15518
|
+
return { code: typeof code === "string" ? code : "UNKNOWN", message: messageOf(err) };
|
|
15519
|
+
}
|
|
15520
|
+
async function burnJournaled(deps, entry, token) {
|
|
15521
|
+
let current = entry;
|
|
15522
|
+
if (current.burnedTokenHex === null) {
|
|
15523
|
+
if (token === null) throw new SphereError("burn replay needs the held token to certify", "VALIDATION_ERROR");
|
|
15524
|
+
const burned = await deps.engine.burn(
|
|
15525
|
+
{ token, reasonBytes: hexToBytes4(current.reasonHex) },
|
|
15526
|
+
{ transferId: current.burnId }
|
|
15527
|
+
);
|
|
15528
|
+
if (burned.blob.tokenId !== current.tokenId) {
|
|
15529
|
+
throw new SphereError(`burned token ${burned.blob.tokenId}, but the journal names ${current.tokenId}`, "VALIDATION_ERROR");
|
|
15530
|
+
}
|
|
15531
|
+
current = { ...current, burnedTokenHex: bytesToHex3(burned.blob.token) };
|
|
15532
|
+
await deps.burnJournal.upsert(current);
|
|
14948
15533
|
}
|
|
14949
|
-
|
|
14950
|
-
|
|
14951
|
-
|
|
14952
|
-
|
|
14953
|
-
|
|
14954
|
-
|
|
14955
|
-
|
|
14956
|
-
|
|
15534
|
+
await deps.storagePort.applyDelta({ transferId: current.burnId, spent: [current.tokenId], added: [], externalDelivery: true });
|
|
15535
|
+
await deps.recordSent({ transferId: current.burnId, assets: current.assets, tokenId: current.tokenId });
|
|
15536
|
+
current = { ...current, settled: true };
|
|
15537
|
+
await deps.burnJournal.upsert(current);
|
|
15538
|
+
deps.refreshView();
|
|
15539
|
+
return current;
|
|
15540
|
+
}
|
|
15541
|
+
|
|
15542
|
+
// modules/payments-v2/burn-hold.ts
|
|
15543
|
+
var BurnHold = class {
|
|
15544
|
+
constructor(deps) {
|
|
15545
|
+
this.deps = deps;
|
|
14957
15546
|
}
|
|
14958
|
-
|
|
14959
|
-
|
|
14960
|
-
|
|
14961
|
-
|
|
14962
|
-
|
|
14963
|
-
let pinnedTotal = 0n;
|
|
14964
|
-
const holders = /* @__PURE__ */ new Set();
|
|
14965
|
-
const unproven = this.deps.ledger.unprovenReason();
|
|
14966
|
-
for (const entry of this.deps.getPool(coinId)) {
|
|
14967
|
-
if (entry.amount <= 0n) continue;
|
|
14968
|
-
const holder = unproven === null ? this.deps.ledger.holderOf(entry.tokenId) : entry.tokenId;
|
|
14969
|
-
if (holder === void 0) {
|
|
14970
|
-
freeView.push(entry);
|
|
14971
|
-
freeTotal += entry.amount;
|
|
14972
|
-
} else {
|
|
14973
|
-
pinnedTotal += entry.amount;
|
|
14974
|
-
holders.add(holder);
|
|
14975
|
-
}
|
|
15547
|
+
async run(burnId, tokenId, burn) {
|
|
15548
|
+
try {
|
|
15549
|
+
this.deps.queue.planWhole(burnId, tokenId);
|
|
15550
|
+
} catch (err) {
|
|
15551
|
+
return refused(burnId, tokenId, err);
|
|
14976
15552
|
}
|
|
14977
|
-
|
|
14978
|
-
|
|
14979
|
-
|
|
14980
|
-
|
|
14981
|
-
|
|
14982
|
-
|
|
15553
|
+
this.deps.view.markInFlightMany([tokenId]);
|
|
15554
|
+
let burned = false;
|
|
15555
|
+
try {
|
|
15556
|
+
const result = await burn();
|
|
15557
|
+
burned = result.success;
|
|
15558
|
+
return result;
|
|
15559
|
+
} finally {
|
|
15560
|
+
await this.settle(burnId, tokenId, burned);
|
|
14983
15561
|
}
|
|
14984
|
-
return total;
|
|
14985
15562
|
}
|
|
14986
|
-
|
|
14987
|
-
|
|
14988
|
-
|
|
14989
|
-
|
|
14990
|
-
|
|
14991
|
-
throw new SphereError("Planned token missing from pool", "VALIDATION_ERROR");
|
|
14992
|
-
}
|
|
15563
|
+
async settle(burnId, tokenId, burned) {
|
|
15564
|
+
if (burned) {
|
|
15565
|
+
this.deps.ledger.commit(burnId);
|
|
15566
|
+
this.deps.track(this.refreshThenRelease(tokenId));
|
|
15567
|
+
return;
|
|
14993
15568
|
}
|
|
14994
|
-
this.deps.
|
|
15569
|
+
const entry = await this.deps.burnJournal.getByKey(burnId).catch(() => null);
|
|
15570
|
+
if (entry === void 0 || entry !== null && !isLiveBurn(entry)) {
|
|
15571
|
+
this.deps.ledger.cancel(burnId);
|
|
15572
|
+
this.deps.view.releaseMany([tokenId]);
|
|
15573
|
+
this.deps.queue.notifyChange("");
|
|
15574
|
+
return;
|
|
15575
|
+
}
|
|
15576
|
+
this.deps.pins.adopt(burnId);
|
|
14995
15577
|
}
|
|
14996
|
-
|
|
14997
|
-
|
|
14998
|
-
|
|
14999
|
-
|
|
15578
|
+
async refreshThenRelease(tokenId) {
|
|
15579
|
+
try {
|
|
15580
|
+
await this.deps.view.delta();
|
|
15581
|
+
this.deps.view.releaseMany([tokenId]);
|
|
15582
|
+
} finally {
|
|
15583
|
+
this.deps.queue.notifyChange("");
|
|
15584
|
+
}
|
|
15000
15585
|
}
|
|
15001
15586
|
};
|
|
15002
|
-
function parseAmount(raw) {
|
|
15003
|
-
if (!/^\d+$/.test(raw)) {
|
|
15004
|
-
throw new SphereError("Invalid send amount", "VALIDATION_ERROR");
|
|
15005
|
-
}
|
|
15006
|
-
const amount = BigInt(raw);
|
|
15007
|
-
if (amount <= 0n) {
|
|
15008
|
-
throw new SphereError("Send amount must be positive", "VALIDATION_ERROR");
|
|
15009
|
-
}
|
|
15010
|
-
return amount;
|
|
15011
|
-
}
|
|
15012
15587
|
|
|
15013
15588
|
// modules/payments-v2/compose.ts
|
|
15014
15589
|
function supportsDeterministicMint(engine) {
|
|
@@ -15017,16 +15592,8 @@ function supportsDeterministicMint(engine) {
|
|
|
15017
15592
|
function composeFacadeParts(deps, hooks) {
|
|
15018
15593
|
const ownPubkeyBytes = hexToBytes4(deps.ownPubkey);
|
|
15019
15594
|
const ledger = new ReservationLedger();
|
|
15020
|
-
const
|
|
15021
|
-
|
|
15022
|
-
kv: deps.kv,
|
|
15023
|
-
emit: (event) => deps.emit(event, {}),
|
|
15024
|
-
// #737: a reserved token is unselectable, so it is never confirmed balance.
|
|
15025
|
-
// #738: while the held-set is unproven, EVERY token reads pinned — the report
|
|
15026
|
-
// must not call a token spendable that the queue is about to refuse to spend.
|
|
15027
|
-
isPinned: (tokenId) => ledger.unprovenReason() !== null || ledger.holderOf(tokenId) !== void 0,
|
|
15028
|
-
...deps.now !== void 0 ? { now: deps.now } : {}
|
|
15029
|
-
});
|
|
15595
|
+
const verdicts = buildVerdicts(deps, hooks);
|
|
15596
|
+
const view = buildView(deps, hooks, ledger, verdicts);
|
|
15030
15597
|
const queue = new SpendQueue({
|
|
15031
15598
|
ledger,
|
|
15032
15599
|
getPool: (coinId) => view.pool(coinId),
|
|
@@ -15041,27 +15608,57 @@ function composeFacadeParts(deps, hooks) {
|
|
|
15041
15608
|
...deps.now !== void 0 ? { now: deps.now } : {},
|
|
15042
15609
|
...deps.newId !== void 0 ? { newId: deps.newId } : {}
|
|
15043
15610
|
});
|
|
15044
|
-
const machineDeps = buildMachineDeps(deps, hooks, historyStore);
|
|
15611
|
+
const machineDeps = buildMachineDeps(deps, hooks, historyStore, verdicts);
|
|
15045
15612
|
const machineStores = createMachineStores(deps.kv);
|
|
15046
15613
|
const heldStates = /* @__PURE__ */ new Map();
|
|
15047
15614
|
const refreshView = coalesced(() => view.delta(), hooks.track);
|
|
15615
|
+
const pins = buildPins(deps, hooks, { ledger, view, machineStores, machineDeps });
|
|
15048
15616
|
return {
|
|
15049
15617
|
ownPubkeyBytes,
|
|
15050
15618
|
view,
|
|
15619
|
+
verdicts,
|
|
15051
15620
|
refreshView,
|
|
15052
15621
|
ledger,
|
|
15053
|
-
pins
|
|
15622
|
+
pins,
|
|
15054
15623
|
queue,
|
|
15624
|
+
burnHold: new BurnHold({ queue, ledger, view, pins, burnJournal: machineStores.burnJournal, track: hooks.track }),
|
|
15055
15625
|
historyStore,
|
|
15056
15626
|
machineStores,
|
|
15057
15627
|
machineDeps,
|
|
15058
15628
|
machine: new TransferMachine(machineDeps),
|
|
15059
15629
|
heldStates,
|
|
15060
|
-
receiveLoop: buildReceive(deps, hooks, historyStore, heldStates, refreshView),
|
|
15630
|
+
receiveLoop: buildReceive(deps, hooks, { historyStore, heldStates, verdicts }, refreshView),
|
|
15061
15631
|
requests: buildRequests(deps, hooks),
|
|
15062
15632
|
restoreDeps: buildRestoreDeps(deps, machineDeps, machineStores, view)
|
|
15063
15633
|
};
|
|
15064
15634
|
}
|
|
15635
|
+
function buildView(deps, hooks, ledger, verdicts) {
|
|
15636
|
+
const view = new InventoryView({
|
|
15637
|
+
port: deps.storagePort,
|
|
15638
|
+
kv: deps.kv,
|
|
15639
|
+
trust: verdicts,
|
|
15640
|
+
emit: (event) => {
|
|
15641
|
+
deps.emit(event, {});
|
|
15642
|
+
hooks.track(verdicts.review(view.claimHolders()));
|
|
15643
|
+
},
|
|
15644
|
+
// #737: a reserved token is unselectable, so it is never confirmed balance.
|
|
15645
|
+
// #738: while the held-set is unproven, EVERY token reads pinned — the report
|
|
15646
|
+
// must not call a token spendable that the queue is about to refuse to spend.
|
|
15647
|
+
isPinned: (tokenId) => ledger.unprovenReason() !== null || ledger.holderOf(tokenId) !== void 0,
|
|
15648
|
+
...deps.now !== void 0 ? { now: deps.now } : {}
|
|
15649
|
+
});
|
|
15650
|
+
return view;
|
|
15651
|
+
}
|
|
15652
|
+
function buildVerdicts(deps, hooks) {
|
|
15653
|
+
return new TokenVerdicts({
|
|
15654
|
+
kv: deps.kv,
|
|
15655
|
+
claims: deps.claims ?? new CoinClaims(),
|
|
15656
|
+
engine: () => hooks.engine(),
|
|
15657
|
+
getBlobs: (tokenIds) => deps.storagePort.getBlobs(tokenIds),
|
|
15658
|
+
changed: () => deps.emit("inventory:updated", {}),
|
|
15659
|
+
...deps.now !== void 0 ? { now: deps.now } : {}
|
|
15660
|
+
});
|
|
15661
|
+
}
|
|
15065
15662
|
function buildPins(deps, hooks, parts) {
|
|
15066
15663
|
return new IntentPins({
|
|
15067
15664
|
ledger: parts.ledger,
|
|
@@ -15089,8 +15686,9 @@ function buildRestoreDeps(deps, machineDeps, machineStores, view) {
|
|
|
15089
15686
|
}
|
|
15090
15687
|
};
|
|
15091
15688
|
}
|
|
15092
|
-
function buildMachineDeps(deps, hooks, historyStore) {
|
|
15689
|
+
function buildMachineDeps(deps, hooks, historyStore, verdicts) {
|
|
15093
15690
|
return {
|
|
15691
|
+
recordSplit: (sourceTokenId, outputTokenId) => verdicts.recordSplit(sourceTokenId, outputTokenId),
|
|
15094
15692
|
engine: () => hooks.engine(),
|
|
15095
15693
|
storage: deps.storagePort,
|
|
15096
15694
|
delivery: deps.deliveryPort,
|
|
@@ -15123,10 +15721,13 @@ function buildMachineDeps(deps, hooks, historyStore) {
|
|
|
15123
15721
|
}
|
|
15124
15722
|
};
|
|
15125
15723
|
}
|
|
15126
|
-
function buildReceive(deps, hooks,
|
|
15724
|
+
function buildReceive(deps, hooks, parts, refreshView) {
|
|
15725
|
+
const { historyStore, heldStates, verdicts } = parts;
|
|
15127
15726
|
return new Receive({
|
|
15128
15727
|
delivery: deps.deliveryPort,
|
|
15129
15728
|
engine: () => hooks.engine(),
|
|
15729
|
+
claims: deps.claims ?? new CoinClaims(),
|
|
15730
|
+
accepted: (token) => verdicts.accept(token),
|
|
15130
15731
|
view: {
|
|
15131
15732
|
heldState: (tokenId) => heldStates.get(tokenId) ?? null,
|
|
15132
15733
|
store: async (entry) => {
|
|
@@ -15176,40 +15777,6 @@ function buildRequests(deps, hooks) {
|
|
|
15176
15777
|
});
|
|
15177
15778
|
}
|
|
15178
15779
|
|
|
15179
|
-
// modules/payments-v2/machine/payload.ts
|
|
15180
|
-
function buildPayload(recipientPubkey, request, spend, spentStates) {
|
|
15181
|
-
return {
|
|
15182
|
-
v: 2,
|
|
15183
|
-
kind: "coin",
|
|
15184
|
-
recipient: recipientPubkey,
|
|
15185
|
-
coinId: request.coinId,
|
|
15186
|
-
amount: request.amount,
|
|
15187
|
-
...request.memo !== void 0 ? { memo: request.memo } : {},
|
|
15188
|
-
direct: [...spend.plan.direct],
|
|
15189
|
-
...spend.plan.split !== void 0 ? {
|
|
15190
|
-
split: {
|
|
15191
|
-
tokenId: spend.plan.split.tokenId,
|
|
15192
|
-
splitAmount: spend.plan.split.splitAmount.toString(),
|
|
15193
|
-
remainderAmount: spend.plan.split.remainderAmount.toString()
|
|
15194
|
-
}
|
|
15195
|
-
} : {},
|
|
15196
|
-
spentStates
|
|
15197
|
-
};
|
|
15198
|
-
}
|
|
15199
|
-
function messageOf(err) {
|
|
15200
|
-
return err instanceof Error ? err.message : String(err);
|
|
15201
|
-
}
|
|
15202
|
-
function buildWholePayload(recipientPubkey, request, spentStates) {
|
|
15203
|
-
return {
|
|
15204
|
-
v: 2,
|
|
15205
|
-
kind: "whole",
|
|
15206
|
-
recipient: recipientPubkey,
|
|
15207
|
-
...request.memo !== void 0 ? { memo: request.memo } : {},
|
|
15208
|
-
direct: [request.tokenId],
|
|
15209
|
-
spentStates
|
|
15210
|
-
};
|
|
15211
|
-
}
|
|
15212
|
-
|
|
15213
15780
|
// modules/payments-v2/mint-params.ts
|
|
15214
15781
|
function mintParams(recipientPubkey, coinId, amount) {
|
|
15215
15782
|
return { recipientPubkey, value: { assets: [{ coinId, amount }] } };
|
|
@@ -15279,6 +15846,16 @@ async function tokenInServerInventory(storagePort, tokenId) {
|
|
|
15279
15846
|
}
|
|
15280
15847
|
return false;
|
|
15281
15848
|
}
|
|
15849
|
+
async function seedHeldStates(storagePort, heldStates) {
|
|
15850
|
+
let page = await storagePort.listInventory();
|
|
15851
|
+
for (let i = 0; i < INVENTORY_SCAN_PAGE_LIMIT; i++) {
|
|
15852
|
+
for (const item of page.items) {
|
|
15853
|
+
if (item.status === "active") heldStates.set(item.tokenId, item.stateHash);
|
|
15854
|
+
}
|
|
15855
|
+
if (!page.more) return;
|
|
15856
|
+
page = await storagePort.listInventory(page.cursor);
|
|
15857
|
+
}
|
|
15858
|
+
}
|
|
15282
15859
|
async function replayCoinMints(deps) {
|
|
15283
15860
|
let resolved = 0;
|
|
15284
15861
|
for (const entry of await deps.mintJournal.list()) {
|
|
@@ -15383,7 +15960,8 @@ async function mintJournaled(deps, entry) {
|
|
|
15383
15960
|
}
|
|
15384
15961
|
|
|
15385
15962
|
// modules/payments-v2/mint-custom.ts
|
|
15386
|
-
var
|
|
15963
|
+
var HEX322 = /^[0-9a-f]{64}$/;
|
|
15964
|
+
var CUSTOM_MINT_MAX_PAYLOAD_BYTES = 1024 * 1024;
|
|
15387
15965
|
function assertRequest(request) {
|
|
15388
15966
|
if (request.tokenType.length !== 32) {
|
|
15389
15967
|
throw new SphereError("mintCustom: tokenType must be 32 bytes", "VALIDATION_ERROR");
|
|
@@ -15394,19 +15972,27 @@ function assertRequest(request) {
|
|
|
15394
15972
|
if (request.data.length === 0) {
|
|
15395
15973
|
throw new SphereError("mintCustom: data must not be empty", "VALIDATION_ERROR");
|
|
15396
15974
|
}
|
|
15975
|
+
if (request.justification !== void 0 && request.justification.length === 0) {
|
|
15976
|
+
throw new SphereError("mintCustom: justification must be omitted, not empty", "VALIDATION_ERROR");
|
|
15977
|
+
}
|
|
15978
|
+
if (request.data.length + (request.justification?.length ?? 0) > CUSTOM_MINT_MAX_PAYLOAD_BYTES) {
|
|
15979
|
+
throw new SphereError(`mintCustom: data and justification exceed ${CUSTOM_MINT_MAX_PAYLOAD_BYTES} bytes`, "VALIDATION_ERROR");
|
|
15980
|
+
}
|
|
15397
15981
|
for (const asset of request.assets) {
|
|
15398
|
-
if (!
|
|
15982
|
+
if (!HEX322.test(asset.coinId) || asset.amount <= 0n) {
|
|
15399
15983
|
throw new SphereError("mintCustom: assets need a 64-hex coinId and a positive amount", "VALIDATION_ERROR");
|
|
15400
15984
|
}
|
|
15401
15985
|
}
|
|
15402
15986
|
}
|
|
15403
15987
|
async function runCustomMintUnderJournal(deps, input) {
|
|
15988
|
+
let entry;
|
|
15404
15989
|
try {
|
|
15405
15990
|
assertRequest(input.request);
|
|
15991
|
+
entry = journalEntryOf2(input.mintId, input.request, deps.now());
|
|
15992
|
+
deps.engine.assertMintable(mintParamsOf(deps, entry, input.request.mintJustificationVerifiers));
|
|
15406
15993
|
} catch (err) {
|
|
15407
15994
|
return { success: false, error: messageOf(err) };
|
|
15408
15995
|
}
|
|
15409
|
-
const entry = journalEntryOf2(input.mintId, input.request, deps.now());
|
|
15410
15996
|
await deps.customMintJournal.upsert(entry);
|
|
15411
15997
|
let tokenId;
|
|
15412
15998
|
try {
|
|
@@ -15446,24 +16032,25 @@ function journalEntryOf2(mintId, request, createdAt) {
|
|
|
15446
16032
|
createdAt
|
|
15447
16033
|
};
|
|
15448
16034
|
}
|
|
16035
|
+
function mintParamsOf(deps, entry, mintJustificationVerifiers) {
|
|
16036
|
+
return {
|
|
16037
|
+
recipientPubkey: deps.ownPubkeyBytes,
|
|
16038
|
+
data: hexToBytes4(entry.dataHex),
|
|
16039
|
+
tokenType: hexToBytes4(entry.tokenTypeHex),
|
|
16040
|
+
salt: hexToBytes4(entry.saltHex),
|
|
16041
|
+
...entry.justificationHex !== null ? { justification: hexToBytes4(entry.justificationHex) } : {},
|
|
16042
|
+
...mintJustificationVerifiers ? { mintJustificationVerifiers } : {}
|
|
16043
|
+
};
|
|
16044
|
+
}
|
|
15449
16045
|
async function mintJournaled2(deps, entry, mintJustificationVerifiers) {
|
|
15450
|
-
const token = await deps.engine.mintDataToken(
|
|
15451
|
-
{
|
|
15452
|
-
recipientPubkey: deps.ownPubkeyBytes,
|
|
15453
|
-
data: hexToBytes4(entry.dataHex),
|
|
15454
|
-
tokenType: hexToBytes4(entry.tokenTypeHex),
|
|
15455
|
-
salt: hexToBytes4(entry.saltHex),
|
|
15456
|
-
...entry.justificationHex !== null ? { justification: hexToBytes4(entry.justificationHex) } : {},
|
|
15457
|
-
...mintJustificationVerifiers ? { mintJustificationVerifiers } : {}
|
|
15458
|
-
},
|
|
15459
|
-
{ transferId: entry.mintId }
|
|
15460
|
-
);
|
|
16046
|
+
const token = await deps.engine.mintDataToken(mintParamsOf(deps, entry, mintJustificationVerifiers), { transferId: entry.mintId });
|
|
15461
16047
|
if (entry.tokenId !== "" && token.blob.tokenId !== entry.tokenId) {
|
|
15462
16048
|
throw new SphereError(`minted token ${token.blob.tokenId}, but the journal names ${entry.tokenId}`, "VALIDATION_ERROR");
|
|
15463
16049
|
}
|
|
15464
16050
|
if (entry.tokenId === "") {
|
|
15465
16051
|
await deps.customMintJournal.upsert({ ...entry, tokenId: token.blob.tokenId });
|
|
15466
16052
|
}
|
|
16053
|
+
if (mintJustificationVerifiers === void 0) await deps.accepted?.(token);
|
|
15467
16054
|
await finalizeMint(deps, {
|
|
15468
16055
|
mintId: entry.mintId,
|
|
15469
16056
|
token,
|
|
@@ -15473,108 +16060,6 @@ async function mintJournaled2(deps, entry, mintJustificationVerifiers) {
|
|
|
15473
16060
|
return token.blob.tokenId;
|
|
15474
16061
|
}
|
|
15475
16062
|
|
|
15476
|
-
// modules/payments-v2/burn.ts
|
|
15477
|
-
var HEX322 = /^[0-9a-f]{64}$/;
|
|
15478
|
-
async function heldToken2(deps, tokenId) {
|
|
15479
|
-
const bytes = (await deps.storagePort.getBlobs([tokenId])).get(tokenId);
|
|
15480
|
-
if (bytes === void 0) {
|
|
15481
|
-
throw new SphereError(`Token ${tokenId} has no blob in storage; it is not held here`, "STORAGE_ERROR");
|
|
15482
|
-
}
|
|
15483
|
-
return deps.engine.decodeToken({ tokenId, token: bytes });
|
|
15484
|
-
}
|
|
15485
|
-
function assetsOf(token) {
|
|
15486
|
-
return (token.value?.assets ?? []).map((a) => ({ coinId: a.coinId, amount: a.amount.toString() }));
|
|
15487
|
-
}
|
|
15488
|
-
async function runBurnUnderJournal(deps, input) {
|
|
15489
|
-
const { burnId } = input;
|
|
15490
|
-
const { tokenId, reasonBytes } = input.request;
|
|
15491
|
-
if (!HEX322.test(tokenId) || reasonBytes.length === 0) {
|
|
15492
|
-
return { success: false, burnId, tokenId, error: "burn: tokenId must be 64 hex and reasonBytes non-empty" };
|
|
15493
|
-
}
|
|
15494
|
-
let token;
|
|
15495
|
-
try {
|
|
15496
|
-
token = await heldToken2(deps, tokenId);
|
|
15497
|
-
if (!deps.engine.isOwnedBy(token, deps.engine.getIdentity().chainPubkey)) {
|
|
15498
|
-
throw new SphereError(`Token ${tokenId} is not owned by this wallet`, "VALIDATION_ERROR");
|
|
15499
|
-
}
|
|
15500
|
-
} catch (err) {
|
|
15501
|
-
return { success: false, burnId, tokenId, error: messageOf(err) };
|
|
15502
|
-
}
|
|
15503
|
-
const entry = {
|
|
15504
|
-
burnId,
|
|
15505
|
-
tokenId,
|
|
15506
|
-
reasonHex: bytesToHex3(reasonBytes),
|
|
15507
|
-
burnedTokenHex: null,
|
|
15508
|
-
settled: false,
|
|
15509
|
-
assets: assetsOf(token),
|
|
15510
|
-
createdAt: deps.now()
|
|
15511
|
-
};
|
|
15512
|
-
await deps.burnJournal.upsert(entry);
|
|
15513
|
-
try {
|
|
15514
|
-
const settled = await burnJournaled(deps, entry, token, { applied: false });
|
|
15515
|
-
return { success: true, burnId, tokenId, burnedToken: hexToBytes4(settled.burnedTokenHex ?? "") };
|
|
15516
|
-
} catch (err) {
|
|
15517
|
-
deps.armHeartbeat();
|
|
15518
|
-
return { success: false, burnId, tokenId, error: messageOf(err) };
|
|
15519
|
-
}
|
|
15520
|
-
}
|
|
15521
|
-
async function replayBurns(deps) {
|
|
15522
|
-
let progressed = 0;
|
|
15523
|
-
for (const entry of await deps.burnJournal.list()) {
|
|
15524
|
-
if (entry.settled || deps.isActiveOp(entry.burnId)) continue;
|
|
15525
|
-
try {
|
|
15526
|
-
const token = entry.burnedTokenHex === null ? await heldToken2(deps, entry.tokenId) : null;
|
|
15527
|
-
const applied = entry.burnedTokenHex !== null && !await deps.tokenInServerInventory(entry.tokenId);
|
|
15528
|
-
await burnJournaled(deps, entry, token, { applied });
|
|
15529
|
-
progressed += 1;
|
|
15530
|
-
} catch {
|
|
15531
|
-
continue;
|
|
15532
|
-
}
|
|
15533
|
-
}
|
|
15534
|
-
return progressed;
|
|
15535
|
-
}
|
|
15536
|
-
async function pendingBurns(deps) {
|
|
15537
|
-
return (await deps.burnJournal.list()).map((entry) => ({
|
|
15538
|
-
burnId: entry.burnId,
|
|
15539
|
-
tokenId: entry.tokenId,
|
|
15540
|
-
reasonBytes: hexToBytes4(entry.reasonHex),
|
|
15541
|
-
burnedToken: entry.burnedTokenHex === null ? null : hexToBytes4(entry.burnedTokenHex),
|
|
15542
|
-
settled: entry.settled,
|
|
15543
|
-
createdAt: entry.createdAt
|
|
15544
|
-
}));
|
|
15545
|
-
}
|
|
15546
|
-
async function acknowledgeBurn(deps, burnId) {
|
|
15547
|
-
const entry = await deps.burnJournal.getByKey(burnId);
|
|
15548
|
-
if (entry === void 0) return;
|
|
15549
|
-
if (!entry.settled) {
|
|
15550
|
-
throw new SphereError(`Burn ${burnId} is not settled yet; it cannot be acknowledged`, "VALIDATION_ERROR");
|
|
15551
|
-
}
|
|
15552
|
-
await deps.burnJournal.removeByKey(burnId);
|
|
15553
|
-
}
|
|
15554
|
-
async function burnJournaled(deps, entry, token, state) {
|
|
15555
|
-
let current = entry;
|
|
15556
|
-
if (current.burnedTokenHex === null) {
|
|
15557
|
-
if (token === null) throw new SphereError("burn replay needs the held token to certify", "VALIDATION_ERROR");
|
|
15558
|
-
const burned = await deps.engine.burn(
|
|
15559
|
-
{ token, reasonBytes: hexToBytes4(current.reasonHex) },
|
|
15560
|
-
{ transferId: current.burnId }
|
|
15561
|
-
);
|
|
15562
|
-
if (burned.blob.tokenId !== current.tokenId) {
|
|
15563
|
-
throw new SphereError(`burned token ${burned.blob.tokenId}, but the journal names ${current.tokenId}`, "VALIDATION_ERROR");
|
|
15564
|
-
}
|
|
15565
|
-
current = { ...current, burnedTokenHex: bytesToHex3(burned.blob.token) };
|
|
15566
|
-
await deps.burnJournal.upsert(current);
|
|
15567
|
-
}
|
|
15568
|
-
if (!state.applied) {
|
|
15569
|
-
await deps.storagePort.applyDelta({ transferId: current.burnId, spent: [current.tokenId], added: [] });
|
|
15570
|
-
}
|
|
15571
|
-
await deps.recordSent({ transferId: current.burnId, assets: current.assets, tokenId: current.tokenId });
|
|
15572
|
-
current = { ...current, settled: true };
|
|
15573
|
-
await deps.burnJournal.upsert(current);
|
|
15574
|
-
deps.refreshView();
|
|
15575
|
-
return current;
|
|
15576
|
-
}
|
|
15577
|
-
|
|
15578
16063
|
// modules/payments-v2/send-whole.ts
|
|
15579
16064
|
async function materializeWholeSpend(deps, input) {
|
|
15580
16065
|
const { engine } = deps;
|
|
@@ -15770,6 +16255,7 @@ var PaymentsFacade = class {
|
|
|
15770
16255
|
track: (op) => this.trackTail(op)
|
|
15771
16256
|
});
|
|
15772
16257
|
this.view = parts.view;
|
|
16258
|
+
this.verdicts = parts.verdicts;
|
|
15773
16259
|
this.wakeRefresh = parts.refreshView;
|
|
15774
16260
|
this.ledger = parts.ledger;
|
|
15775
16261
|
this.queue = parts.queue;
|
|
@@ -15783,6 +16269,7 @@ var PaymentsFacade = class {
|
|
|
15783
16269
|
this.restoreDeps = parts.restoreDeps;
|
|
15784
16270
|
this.requests = parts.requests;
|
|
15785
16271
|
this.pins = parts.pins;
|
|
16272
|
+
this.burnHold = parts.burnHold;
|
|
15786
16273
|
deps.deliveryPort.bindDeliveryKeys((blob) => this.engine().deliveryKeys(blob));
|
|
15787
16274
|
this.heartbeat = new ConvergenceHeartbeat({
|
|
15788
16275
|
now: () => this.nowMs(),
|
|
@@ -15806,6 +16293,7 @@ var PaymentsFacade = class {
|
|
|
15806
16293
|
wakeRefresh;
|
|
15807
16294
|
prewarmed = new PrewarmCache();
|
|
15808
16295
|
view;
|
|
16296
|
+
verdicts;
|
|
15809
16297
|
ledger;
|
|
15810
16298
|
queue;
|
|
15811
16299
|
machine;
|
|
@@ -15832,6 +16320,7 @@ var PaymentsFacade = class {
|
|
|
15832
16320
|
activeMoneyOps = /* @__PURE__ */ new Set();
|
|
15833
16321
|
/** #737: the ledger holds exactly the sources of the still-open intents. */
|
|
15834
16322
|
pins;
|
|
16323
|
+
burnHold;
|
|
15835
16324
|
/** Per facade, so per address: an NFT reading is never served to another wallet. */
|
|
15836
16325
|
nftCache = new NftCache();
|
|
15837
16326
|
// ── lifecycle ──────────────────────────────────────────────────────────────
|
|
@@ -15856,7 +16345,8 @@ var PaymentsFacade = class {
|
|
|
15856
16345
|
await this.deps.session.start();
|
|
15857
16346
|
await this.pins.sync().catch(() => void 0);
|
|
15858
16347
|
this.trackTail(this.runConvergencePass());
|
|
15859
|
-
this.trackTail(this.
|
|
16348
|
+
this.trackTail(seedHeldStates(this.deps.storagePort, this.heldStates));
|
|
16349
|
+
await this.verdicts.start().catch(() => void 0);
|
|
15860
16350
|
await this.view.fullPull().catch(() => void 0);
|
|
15861
16351
|
this.receiveLoop.start(this.deps.receivePollMs);
|
|
15862
16352
|
}
|
|
@@ -15865,6 +16355,7 @@ var PaymentsFacade = class {
|
|
|
15865
16355
|
this.started = false;
|
|
15866
16356
|
this.heartbeat.stop();
|
|
15867
16357
|
this.receiveLoop.stop();
|
|
16358
|
+
this.verdicts.stop();
|
|
15868
16359
|
for (const unsubscribe of this.unsubscribers) unsubscribe();
|
|
15869
16360
|
this.unsubscribers = [];
|
|
15870
16361
|
await this.deps.session.stop();
|
|
@@ -15881,12 +16372,17 @@ var PaymentsFacade = class {
|
|
|
15881
16372
|
}
|
|
15882
16373
|
// ── reads ──────────────────────────────────────────────────────────────────
|
|
15883
16374
|
async assets(coinId) {
|
|
16375
|
+
return (await this.heldAssets(coinId)).filter((asset) => asset.unverified === void 0);
|
|
16376
|
+
}
|
|
16377
|
+
unverifiedAssets = async (coinId) => (await this.heldAssets(coinId)).filter((a) => a.unverified !== void 0);
|
|
16378
|
+
async heldAssets(coinId) {
|
|
15884
16379
|
const all = await this.view.assets(this.deps.registry, this.deps.price);
|
|
15885
16380
|
return coinId === void 0 ? all : all.filter((asset) => asset.coinId === coinId);
|
|
15886
16381
|
}
|
|
15887
16382
|
tokens(filter) {
|
|
15888
|
-
return this.view.tokens(this.deps.registry, filter);
|
|
16383
|
+
return this.view.tokens(this.deps.registry, filter).filter((token) => token.unverified === void 0);
|
|
15889
16384
|
}
|
|
16385
|
+
unverifiedTokens = (filter) => this.view.tokens(this.deps.registry, filter).filter((t) => t.unverified !== void 0);
|
|
15890
16386
|
coinless() {
|
|
15891
16387
|
return this.view.coinless(this.deps.registry);
|
|
15892
16388
|
}
|
|
@@ -15947,9 +16443,9 @@ var PaymentsFacade = class {
|
|
|
15947
16443
|
return this.track(this.ownedOp((mintId) => runNftMintUnderJournal(this.mintDeps(), { mintId, request })));
|
|
15948
16444
|
}
|
|
15949
16445
|
mintCustom = (request) => this.track(this.ownedOp((mintId) => runCustomMintUnderJournal(this.mintDeps(), { mintId, request })));
|
|
15950
|
-
burn = (request) => this.track(this.ownedOp((burnId) => runBurnUnderJournal(this.mintDeps(), { burnId, request })));
|
|
15951
|
-
pendingBurns = () => this.track(pendingBurns(this.
|
|
15952
|
-
acknowledgeBurn = (burnId) => this.track(acknowledgeBurn(this.
|
|
16446
|
+
burn = (request) => this.track(this.ownedOp((burnId) => this.burnHold.run(burnId, request.tokenId, () => runBurnUnderJournal(this.mintDeps(), { burnId, request }))));
|
|
16447
|
+
pendingBurns = () => this.track(pendingBurns(this.machineStores));
|
|
16448
|
+
acknowledgeBurn = (burnId) => this.track(acknowledgeBurn(this.machineStores, burnId));
|
|
15953
16449
|
// §4 retry verb: coalesces onto the running pass — NEVER re-issue send() (#631/#676).
|
|
15954
16450
|
async resumeNow() {
|
|
15955
16451
|
if (!this.started) return;
|
|
@@ -15992,7 +16488,8 @@ var PaymentsFacade = class {
|
|
|
15992
16488
|
isActiveOp: (mintId) => this.activeMoneyOps.has(mintId),
|
|
15993
16489
|
tokenInServerInventory: (tokenId) => tokenInServerInventory(this.deps.storagePort, tokenId),
|
|
15994
16490
|
emit: this.deps.emit,
|
|
15995
|
-
now: () => this.nowMs()
|
|
16491
|
+
now: () => this.nowMs(),
|
|
16492
|
+
accepted: (token) => this.verdicts.accept(token)
|
|
15996
16493
|
};
|
|
15997
16494
|
}
|
|
15998
16495
|
// ── send policy (§5.5 + old-loop parity; the machine stays policy-free) ────
|
|
@@ -16330,16 +16827,6 @@ var PaymentsFacade = class {
|
|
|
16330
16827
|
newId() {
|
|
16331
16828
|
return (this.deps.newId ?? randomUUID)();
|
|
16332
16829
|
}
|
|
16333
|
-
async seedHeldStates() {
|
|
16334
|
-
let page = await this.deps.storagePort.listInventory();
|
|
16335
|
-
for (let i = 0; i < INVENTORY_SCAN_PAGE_LIMIT; i++) {
|
|
16336
|
-
for (const item of page.items) {
|
|
16337
|
-
if (item.status === "active") this.heldStates.set(item.tokenId, item.stateHash);
|
|
16338
|
-
}
|
|
16339
|
-
if (!page.more) return;
|
|
16340
|
-
page = await this.deps.storagePort.listInventory(page.cursor);
|
|
16341
|
-
}
|
|
16342
|
-
}
|
|
16343
16830
|
track(op) {
|
|
16344
16831
|
const settled = op.then(() => void 0, () => void 0);
|
|
16345
16832
|
this.pendingOps.add(settled);
|
|
@@ -17583,6 +18070,7 @@ function composePaymentsV2(spec) {
|
|
|
17583
18070
|
engineRef: spec.engineRef,
|
|
17584
18071
|
kv,
|
|
17585
18072
|
registry: host.registry,
|
|
18073
|
+
claims: CoinClaims.fromPlugins(spec.plugins),
|
|
17586
18074
|
...host.price !== null ? { price: host.price } : {},
|
|
17587
18075
|
emit: host.emit,
|
|
17588
18076
|
resolveRecipient: (identifier) => resolveRecipientInfo(identifier, network, host.resolvePeer),
|
|
@@ -20631,7 +21119,8 @@ var Sphere = class _Sphere {
|
|
|
20631
21119
|
// deliver the NEXT address's Unicity ID — a name the sending key does
|
|
20632
21120
|
// not own (sphere#487 review).
|
|
20633
21121
|
nametag: () => this.getNametagForAddress(facadeAddressId)
|
|
20634
|
-
}
|
|
21122
|
+
},
|
|
21123
|
+
...this._plugins ? { plugins: this._plugins } : {}
|
|
20635
21124
|
});
|
|
20636
21125
|
this._paymentsV2Active = { index, facade };
|
|
20637
21126
|
await facade.start();
|
|
@@ -21259,6 +21748,7 @@ function addressesMatch(a, b) {
|
|
|
21259
21748
|
0 && (module.exports = {
|
|
21260
21749
|
AUTH_CHALLENGE_PREFIX,
|
|
21261
21750
|
COIN_TYPES,
|
|
21751
|
+
CUSTOM_MINT_MAX_PAYLOAD_BYTES,
|
|
21262
21752
|
ChallengeTemplateError,
|
|
21263
21753
|
CoinGeckoPriceProvider,
|
|
21264
21754
|
CommunicationsModule,
|