@unicitylabs/sphere-sdk 0.16.0-dev.2 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/connect/index.cjs +11 -5
- package/dist/connect/index.cjs.map +1 -1
- package/dist/connect/index.d.cts +4 -1
- package/dist/connect/index.d.ts +4 -1
- package/dist/connect/index.js +11 -5
- package/dist/connect/index.js.map +1 -1
- package/dist/core/index.cjs +568 -172
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +67 -0
- package/dist/core/index.d.ts +67 -0
- package/dist/core/index.js +568 -172
- package/dist/core/index.js.map +1 -1
- package/dist/impl/browser/connect/index.cjs +11 -5
- package/dist/impl/browser/connect/index.cjs.map +1 -1
- package/dist/impl/browser/connect/index.d.cts +3 -1
- package/dist/impl/browser/connect/index.d.ts +3 -1
- package/dist/impl/browser/connect/index.js +11 -5
- package/dist/impl/browser/connect/index.js.map +1 -1
- package/dist/impl/browser/index.cjs +2 -2
- package/dist/impl/browser/index.cjs.map +1 -1
- package/dist/impl/browser/index.js +2 -2
- package/dist/impl/browser/index.js.map +1 -1
- package/dist/impl/nodejs/connect/index.cjs +10 -4
- package/dist/impl/nodejs/connect/index.cjs.map +1 -1
- package/dist/impl/nodejs/connect/index.d.cts +1 -1
- package/dist/impl/nodejs/connect/index.d.ts +1 -1
- package/dist/impl/nodejs/connect/index.js +10 -4
- package/dist/impl/nodejs/connect/index.js.map +1 -1
- package/dist/impl/nodejs/index.cjs +19 -3
- package/dist/impl/nodejs/index.cjs.map +1 -1
- package/dist/impl/nodejs/index.d.cts +11 -0
- package/dist/impl/nodejs/index.d.ts +11 -0
- package/dist/impl/nodejs/index.js +19 -3
- package/dist/impl/nodejs/index.js.map +1 -1
- package/dist/impl/shared/wallet-api/index.d.cts +11 -0
- package/dist/impl/shared/wallet-api/index.d.ts +11 -0
- package/dist/impl/wallet-api-v2/index.cjs +2 -0
- package/dist/impl/wallet-api-v2/index.cjs.map +1 -1
- package/dist/impl/wallet-api-v2/index.d.cts +13 -0
- package/dist/impl/wallet-api-v2/index.d.ts +13 -0
- package/dist/impl/wallet-api-v2/index.js +2 -0
- package/dist/impl/wallet-api-v2/index.js.map +1 -1
- package/dist/index.cjs +568 -172
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +61 -1
- package/dist/index.d.ts +61 -1
- package/dist/index.js +568 -172
- package/dist/index.js.map +1 -1
- package/dist/modules/payments-v2/index.cjs +609 -269
- package/dist/modules/payments-v2/index.cjs.map +1 -1
- package/dist/modules/payments-v2/index.d.cts +88 -13
- package/dist/modules/payments-v2/index.d.ts +88 -13
- package/dist/modules/payments-v2/index.js +609 -269
- package/dist/modules/payments-v2/index.js.map +1 -1
- package/dist/token-engine/index.cjs +132 -34
- package/dist/token-engine/index.cjs.map +1 -1
- package/dist/token-engine/index.d.cts +19 -0
- package/dist/token-engine/index.d.ts +19 -0
- package/dist/token-engine/index.js +132 -34
- package/dist/token-engine/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -8146,6 +8146,8 @@ var FETCH_TIMEOUT_MS = 1e4;
|
|
|
8146
8146
|
var TokenRegistry = class _TokenRegistry {
|
|
8147
8147
|
static instance = null;
|
|
8148
8148
|
definitionsById;
|
|
8149
|
+
/** Non-fungible definitions keyed by TOKEN TYPE — a separate namespace (#147). */
|
|
8150
|
+
definitionsByType;
|
|
8149
8151
|
definitionsBySymbol;
|
|
8150
8152
|
definitionsByName;
|
|
8151
8153
|
// Remote refresh state
|
|
@@ -8164,6 +8166,7 @@ var TokenRegistry = class _TokenRegistry {
|
|
|
8164
8166
|
inFlight = null;
|
|
8165
8167
|
constructor() {
|
|
8166
8168
|
this.definitionsById = /* @__PURE__ */ new Map();
|
|
8169
|
+
this.definitionsByType = /* @__PURE__ */ new Map();
|
|
8167
8170
|
this.definitionsBySymbol = /* @__PURE__ */ new Map();
|
|
8168
8171
|
this.definitionsByName = /* @__PURE__ */ new Map();
|
|
8169
8172
|
}
|
|
@@ -8391,9 +8394,11 @@ var TokenRegistry = class _TokenRegistry {
|
|
|
8391
8394
|
this.definitionsById.clear();
|
|
8392
8395
|
this.definitionsBySymbol.clear();
|
|
8393
8396
|
this.definitionsByName.clear();
|
|
8397
|
+
this.definitionsByType.clear();
|
|
8394
8398
|
for (const def of definitions) {
|
|
8395
8399
|
const idLower = def.id.toLowerCase();
|
|
8396
8400
|
this.definitionsById.set(idLower, def);
|
|
8401
|
+
if (def.assetKind === "non-fungible") this.definitionsByType.set(idLower, def);
|
|
8397
8402
|
if (def.symbol) {
|
|
8398
8403
|
this.definitionsBySymbol.set(def.symbol.toUpperCase(), def);
|
|
8399
8404
|
}
|
|
@@ -8607,6 +8612,20 @@ var TokenRegistry = class _TokenRegistry {
|
|
|
8607
8612
|
getAllDefinitions() {
|
|
8608
8613
|
return Array.from(this.definitionsById.values());
|
|
8609
8614
|
}
|
|
8615
|
+
/**
|
|
8616
|
+
* Definition for a coinless token's genesis TOKEN TYPE (#777). Never falls back
|
|
8617
|
+
* to `getDefinition`: a type and a coin id live in different namespaces, so a
|
|
8618
|
+
* type that collided with a coin id would otherwise render as that coin.
|
|
8619
|
+
*/
|
|
8620
|
+
getTypeMeta(tokenType) {
|
|
8621
|
+
const def = this.getTypeDefinition(tokenType);
|
|
8622
|
+
if (!def) return null;
|
|
8623
|
+
return { name: def.name, iconUrl: def.icons?.[0]?.url ?? null };
|
|
8624
|
+
}
|
|
8625
|
+
getTypeDefinition(tokenType) {
|
|
8626
|
+
if (!tokenType) return void 0;
|
|
8627
|
+
return this.definitionsByType.get(tokenType.toLowerCase());
|
|
8628
|
+
}
|
|
8610
8629
|
/**
|
|
8611
8630
|
* Get all fungible token definitions
|
|
8612
8631
|
* @returns Array of fungible token definitions
|
|
@@ -9441,6 +9460,8 @@ import { DataHasherFactory } from "@unicitylabs/state-transition-sdk/lib/crypto/
|
|
|
9441
9460
|
import { CborSerializer } from "@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborSerializer.js";
|
|
9442
9461
|
import { CborDeserializer } from "@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborDeserializer.js";
|
|
9443
9462
|
import { CborError } from "@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborError.js";
|
|
9463
|
+
import { CborReader } from "@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborReader.js";
|
|
9464
|
+
import { MajorType } from "@unicitylabs/state-transition-sdk/lib/serialization/cbor/MajorType.js";
|
|
9444
9465
|
import { Asset } from "@unicitylabs/state-transition-sdk/lib/payment/asset/Asset.js";
|
|
9445
9466
|
import { AssetId } from "@unicitylabs/state-transition-sdk/lib/payment/asset/AssetId.js";
|
|
9446
9467
|
import { PaymentAssetCollection } from "@unicitylabs/state-transition-sdk/lib/payment/asset/PaymentAssetCollection.js";
|
|
@@ -9853,6 +9874,126 @@ async function burntTokenFromCheckpoint(deps, storedBytes, reDerivedBurnTx, sour
|
|
|
9853
9874
|
return burntToken;
|
|
9854
9875
|
}
|
|
9855
9876
|
|
|
9877
|
+
// token-engine/value-envelope.ts
|
|
9878
|
+
function isCoinlessEnvelope(envelope) {
|
|
9879
|
+
return envelope.startsWith("none_");
|
|
9880
|
+
}
|
|
9881
|
+
var SELF_DESCRIBED_CBOR_TAG = 55799n;
|
|
9882
|
+
var CBOR_MAJOR_TYPE_MASK = 224;
|
|
9883
|
+
var describe = (error) => error instanceof Error ? error.message : String(error);
|
|
9884
|
+
var invalid = (message) => new SphereError(message, "VALIDATION_ERROR");
|
|
9885
|
+
function majorTypeOf(bytes) {
|
|
9886
|
+
const first = bytes.at(0);
|
|
9887
|
+
return first === void 0 ? null : first & CBOR_MAJOR_TYPE_MASK;
|
|
9888
|
+
}
|
|
9889
|
+
function headTagNumber(bytes) {
|
|
9890
|
+
try {
|
|
9891
|
+
return new CborReader(bytes).readLength(MajorType.TAG);
|
|
9892
|
+
} catch {
|
|
9893
|
+
return null;
|
|
9894
|
+
}
|
|
9895
|
+
}
|
|
9896
|
+
function isAssetShaped(item) {
|
|
9897
|
+
try {
|
|
9898
|
+
return CborDeserializer.decodeArray(item, 2).every(
|
|
9899
|
+
(field) => majorTypeOf(field) === MajorType.BYTE_STRING
|
|
9900
|
+
);
|
|
9901
|
+
} catch {
|
|
9902
|
+
return false;
|
|
9903
|
+
}
|
|
9904
|
+
}
|
|
9905
|
+
function arrayItemsOrNull(bytes) {
|
|
9906
|
+
try {
|
|
9907
|
+
return CborDeserializer.decodeArray(bytes);
|
|
9908
|
+
} catch {
|
|
9909
|
+
return null;
|
|
9910
|
+
}
|
|
9911
|
+
}
|
|
9912
|
+
function decodeSphere(genesisData) {
|
|
9913
|
+
try {
|
|
9914
|
+
return SpherePaymentData.fromCBOR(genesisData).toValue();
|
|
9915
|
+
} catch (error) {
|
|
9916
|
+
throw invalid(`Failed to decode token payment data: ${describe(error)}`);
|
|
9917
|
+
}
|
|
9918
|
+
}
|
|
9919
|
+
function taggedItemBody(genesisData) {
|
|
9920
|
+
try {
|
|
9921
|
+
return CborDeserializer.decodeTag(genesisData).data;
|
|
9922
|
+
} catch (error) {
|
|
9923
|
+
throw invalid(
|
|
9924
|
+
`Token value payload is a CBOR tag whose item is malformed or carries trailing bytes: ${describe(error)}`
|
|
9925
|
+
);
|
|
9926
|
+
}
|
|
9927
|
+
}
|
|
9928
|
+
function selfDescribedBodyCarriesValue(body) {
|
|
9929
|
+
const major = majorTypeOf(body);
|
|
9930
|
+
if (major === MajorType.TAG) {
|
|
9931
|
+
const inner = headTagNumber(body);
|
|
9932
|
+
return inner === SpherePaymentData.CBOR_TAG || inner === SELF_DESCRIBED_CBOR_TAG;
|
|
9933
|
+
}
|
|
9934
|
+
if (major === MajorType.ARRAY) {
|
|
9935
|
+
const items = arrayItemsOrNull(body);
|
|
9936
|
+
return items !== null && items.some(isAssetShaped);
|
|
9937
|
+
}
|
|
9938
|
+
return false;
|
|
9939
|
+
}
|
|
9940
|
+
function classifyTagged(genesisData) {
|
|
9941
|
+
const tag = headTagNumber(genesisData);
|
|
9942
|
+
if (tag === null) {
|
|
9943
|
+
throw invalid(
|
|
9944
|
+
"Token value payload has an unreadable or non-canonically encoded CBOR tag head \u2014 it could be a malformed SpherePaymentData envelope"
|
|
9945
|
+
);
|
|
9946
|
+
}
|
|
9947
|
+
if (tag === SpherePaymentData.CBOR_TAG) {
|
|
9948
|
+
return { envelope: "sphere", value: decodeSphere(genesisData) };
|
|
9949
|
+
}
|
|
9950
|
+
const body = taggedItemBody(genesisData);
|
|
9951
|
+
if (tag === SELF_DESCRIBED_CBOR_TAG && selfDescribedBodyCarriesValue(body)) {
|
|
9952
|
+
throw invalid(
|
|
9953
|
+
"Token value payload is a value envelope wrapped in the self-described CBOR tag 55799 (RFC 8949 \xA73.4.6), which no codec here unwraps \u2014 its coins would be invisible. Emit the envelope without the self-describe prefix"
|
|
9954
|
+
);
|
|
9955
|
+
}
|
|
9956
|
+
return { envelope: "none_tag", value: null };
|
|
9957
|
+
}
|
|
9958
|
+
function classifyArray(genesisData) {
|
|
9959
|
+
const items = arrayItemsOrNull(genesisData);
|
|
9960
|
+
if (items === null) {
|
|
9961
|
+
throw invalid("Token value payload is not PaymentAssetCollection: malformed CBOR array");
|
|
9962
|
+
}
|
|
9963
|
+
return items.some(isAssetShaped) ? { envelope: "bare_collection", value: null } : { envelope: "none_other", value: null };
|
|
9964
|
+
}
|
|
9965
|
+
function classifyValueEnvelope(genesisData) {
|
|
9966
|
+
if (genesisData === null) return { envelope: "none_absent", value: null };
|
|
9967
|
+
const major = majorTypeOf(genesisData);
|
|
9968
|
+
if (major === null) return { envelope: "none_absent", value: null };
|
|
9969
|
+
if (major === MajorType.TAG) return classifyTagged(genesisData);
|
|
9970
|
+
if (major === MajorType.ARRAY) return classifyArray(genesisData);
|
|
9971
|
+
return { envelope: "none_other", value: null };
|
|
9972
|
+
}
|
|
9973
|
+
function wrapToken(sdkToken) {
|
|
9974
|
+
const { envelope, value } = classifyValueEnvelope(sdkToken.genesis.data);
|
|
9975
|
+
const blob = {
|
|
9976
|
+
tokenId: HexConverter.encode(sdkToken.id.bytes),
|
|
9977
|
+
token: sdkToken.toCBOR()
|
|
9978
|
+
};
|
|
9979
|
+
return {
|
|
9980
|
+
sdkToken,
|
|
9981
|
+
blob,
|
|
9982
|
+
value,
|
|
9983
|
+
valueEnvelope: envelope,
|
|
9984
|
+
tokenType: HexConverter.encode(sdkToken.type.bytes)
|
|
9985
|
+
};
|
|
9986
|
+
}
|
|
9987
|
+
function assertMintableData(data) {
|
|
9988
|
+
try {
|
|
9989
|
+
classifyValueEnvelope(data);
|
|
9990
|
+
} catch (error) {
|
|
9991
|
+
throw invalid(
|
|
9992
|
+
`Cannot mint a data token with this payload: ${describe(error)}. Raw bytes starting in the CBOR array (0x80-0x9f) or tag (0xc0-0xdf) range must be well-formed canonical CBOR; wrap them in a CBOR byte string, map, text string, or a tag other than 39050/55799.`
|
|
9993
|
+
);
|
|
9994
|
+
}
|
|
9995
|
+
}
|
|
9996
|
+
|
|
9856
9997
|
// token-engine/SphereTokenEngine.ts
|
|
9857
9998
|
var DEFAULT_PROOF_POLL_INTERVAL_MS = 300;
|
|
9858
9999
|
var MAX_MINT_CONCURRENCY = 8;
|
|
@@ -9909,7 +10050,7 @@ var SphereTokenEngine = class {
|
|
|
9909
10050
|
return sdkToken.latestTransaction.data;
|
|
9910
10051
|
}
|
|
9911
10052
|
const data = sdkToken.genesis.data;
|
|
9912
|
-
if (data &&
|
|
10053
|
+
if (data && classifyValueEnvelope(data).envelope === "sphere") {
|
|
9913
10054
|
return SpherePaymentData.fromCBOR(data).memo;
|
|
9914
10055
|
}
|
|
9915
10056
|
return null;
|
|
@@ -9939,9 +10080,10 @@ var SphereTokenEngine = class {
|
|
|
9939
10080
|
);
|
|
9940
10081
|
const certified = await mintTx.toCertifiedTransaction(this.deps.trustBase, this.deps.predicateVerifier, this.deps.unicityCertificateVerifier, proof);
|
|
9941
10082
|
const token = await Token.mint(certified, this.deps.verificationContext);
|
|
9942
|
-
return
|
|
10083
|
+
return wrapToken(token);
|
|
9943
10084
|
}
|
|
9944
10085
|
async mintDataToken(params, options) {
|
|
10086
|
+
assertMintableData(params.data);
|
|
9945
10087
|
const recipient = SignaturePredicate.create(params.recipientPubkey);
|
|
9946
10088
|
const tokenType = params.tokenType ? new TokenType(params.tokenType) : TokenType.generate();
|
|
9947
10089
|
const salt = params.salt ? TokenSalt.fromBytes(params.salt) : TokenSalt.generate();
|
|
@@ -9956,7 +10098,7 @@ var SphereTokenEngine = class {
|
|
|
9956
10098
|
);
|
|
9957
10099
|
const certified = await mintTx.toCertifiedTransaction(this.deps.trustBase, this.deps.predicateVerifier, this.deps.unicityCertificateVerifier, proof);
|
|
9958
10100
|
const token = await Token.mint(certified, this.deps.verificationContext);
|
|
9959
|
-
return
|
|
10101
|
+
return wrapToken(token);
|
|
9960
10102
|
}
|
|
9961
10103
|
async transfer(params, options) {
|
|
9962
10104
|
this.assertOwned(params.token);
|
|
@@ -9979,13 +10121,19 @@ var SphereTokenEngine = class {
|
|
|
9979
10121
|
);
|
|
9980
10122
|
const certified = await transferTx.toCertifiedTransaction(this.deps.trustBase, this.deps.predicateVerifier, this.deps.unicityCertificateVerifier, proof);
|
|
9981
10123
|
const transferred = await params.token.sdkToken.transfer(certified, this.deps.verificationContext);
|
|
9982
|
-
return
|
|
10124
|
+
return wrapToken(transferred);
|
|
9983
10125
|
}
|
|
9984
10126
|
async split(params, options) {
|
|
9985
10127
|
this.assertOwned(params.token);
|
|
9986
10128
|
if (params.outputs.length === 0) {
|
|
9987
10129
|
throw new SphereError("Split requires at least one output", "VALIDATION_ERROR");
|
|
9988
10130
|
}
|
|
10131
|
+
if (params.token.value === null) {
|
|
10132
|
+
throw new SphereError(
|
|
10133
|
+
`Cannot split token ${params.token.blob.tokenId}: its genesis payload carries no value this SDK can read (envelope: ${params.token.valueEnvelope}). A coinless token can only be transferred whole.`,
|
|
10134
|
+
"VALIDATION_ERROR"
|
|
10135
|
+
);
|
|
10136
|
+
}
|
|
9989
10137
|
const transferId = this.resolveTransferId(options);
|
|
9990
10138
|
const requests = params.outputs.map(
|
|
9991
10139
|
(o, i) => SplitTokenRequest.create(
|
|
@@ -10107,7 +10255,7 @@ var SphereTokenEngine = class {
|
|
|
10107
10255
|
const proof = await this.submitSplitMintLeg(certData, mintTx, options);
|
|
10108
10256
|
const certified = await mintTx.toCertifiedTransaction(this.deps.trustBase, this.deps.predicateVerifier, this.deps.unicityCertificateVerifier, proof);
|
|
10109
10257
|
const token = await Token.mint(certified, this.deps.verificationContext);
|
|
10110
|
-
return
|
|
10258
|
+
return wrapToken(token);
|
|
10111
10259
|
}
|
|
10112
10260
|
/**
|
|
10113
10261
|
* Submit one split-mint leg. A `TRANSACTION_HASH_MISMATCH` on a mint leg is NOT a foreign spend
|
|
@@ -10165,7 +10313,7 @@ var SphereTokenEngine = class {
|
|
|
10165
10313
|
"VALIDATION_ERROR"
|
|
10166
10314
|
);
|
|
10167
10315
|
}
|
|
10168
|
-
return
|
|
10316
|
+
return wrapToken(sdkToken);
|
|
10169
10317
|
}
|
|
10170
10318
|
// ── internals ────────────────────────────────────────────────────────────────
|
|
10171
10319
|
/**
|
|
@@ -10277,34 +10425,6 @@ var SphereTokenEngine = class {
|
|
|
10277
10425
|
throw new SphereError("Cannot transfer a token not owned by this engine identity", "VALIDATION_ERROR");
|
|
10278
10426
|
}
|
|
10279
10427
|
}
|
|
10280
|
-
/** Wrap an SDK token into a SphereToken: cache its blob (incl. stable tokenId) + decoded value. */
|
|
10281
|
-
wrapToken(sdkToken) {
|
|
10282
|
-
const data = sdkToken.genesis.data;
|
|
10283
|
-
let value = null;
|
|
10284
|
-
if (data && this.isSpherePaymentData(data)) {
|
|
10285
|
-
try {
|
|
10286
|
-
value = SpherePaymentData.fromCBOR(data).toValue();
|
|
10287
|
-
} catch (err) {
|
|
10288
|
-
throw new SphereError(
|
|
10289
|
-
`Failed to decode token payment data: ${err instanceof Error ? err.message : String(err)}`,
|
|
10290
|
-
"VALIDATION_ERROR"
|
|
10291
|
-
);
|
|
10292
|
-
}
|
|
10293
|
-
}
|
|
10294
|
-
const blob = {
|
|
10295
|
-
tokenId: HexConverter.encode(sdkToken.id.bytes),
|
|
10296
|
-
token: sdkToken.toCBOR()
|
|
10297
|
-
};
|
|
10298
|
-
return { sdkToken, blob, value };
|
|
10299
|
-
}
|
|
10300
|
-
/** True if the bytes are a SpherePaymentData envelope (value token) vs a raw data token. */
|
|
10301
|
-
isSpherePaymentData(data) {
|
|
10302
|
-
try {
|
|
10303
|
-
return CborDeserializer.decodeTag(data).tag === SpherePaymentData.CBOR_TAG;
|
|
10304
|
-
} catch {
|
|
10305
|
-
return false;
|
|
10306
|
-
}
|
|
10307
|
-
}
|
|
10308
10428
|
/**
|
|
10309
10429
|
* Terminate the verification worker pool, if this engine was configured with
|
|
10310
10430
|
* one. Idempotent — the pool's dispose only touches workers it spawned.
|
|
@@ -11700,6 +11820,14 @@ function createMachineStores(kv) {
|
|
|
11700
11820
|
};
|
|
11701
11821
|
}
|
|
11702
11822
|
|
|
11823
|
+
// modules/payments-v2/machine/payload-view.ts
|
|
11824
|
+
function splitOf(p) {
|
|
11825
|
+
return p.kind === "coin" ? p.split : void 0;
|
|
11826
|
+
}
|
|
11827
|
+
function coinIdOf(p) {
|
|
11828
|
+
return p.kind === "coin" ? p.coinId : void 0;
|
|
11829
|
+
}
|
|
11830
|
+
|
|
11703
11831
|
// modules/payments-v2/machine/TransferMachine.ts
|
|
11704
11832
|
var CERTIFY_WIDTH = 20;
|
|
11705
11833
|
var KEEP_OPEN_CODES = /* @__PURE__ */ new Set([
|
|
@@ -11902,13 +12030,14 @@ var TransferMachine = class {
|
|
|
11902
12030
|
const finished = await engine.transfer({ token: source, recipientPubkey }, opts);
|
|
11903
12031
|
return { recipientBlob: finished.blob.token };
|
|
11904
12032
|
}
|
|
11905
|
-
const split = ctx.payload
|
|
12033
|
+
const split = splitOf(ctx.payload);
|
|
12034
|
+
const splitCoinId = coinIdOf(ctx.payload);
|
|
11906
12035
|
const { outputs } = await engine.split(
|
|
11907
12036
|
{
|
|
11908
12037
|
token: source,
|
|
11909
12038
|
outputs: [
|
|
11910
|
-
{ recipientPubkey, coinId:
|
|
11911
|
-
{ recipientPubkey: this.deps.ownPubkey, coinId:
|
|
12039
|
+
{ recipientPubkey, coinId: splitCoinId, amount: BigInt(split.splitAmount) },
|
|
12040
|
+
{ recipientPubkey: this.deps.ownPubkey, coinId: splitCoinId, amount: BigInt(split.remainderAmount) }
|
|
11912
12041
|
]
|
|
11913
12042
|
},
|
|
11914
12043
|
opts
|
|
@@ -12030,7 +12159,9 @@ var TransferMachine = class {
|
|
|
12030
12159
|
const entry = {
|
|
12031
12160
|
transferId: r.transferId,
|
|
12032
12161
|
remainingAmount: undelivered.toString(),
|
|
12033
|
-
|
|
12162
|
+
// '' only on a token-addressed intent, which is one leg: it either lands or
|
|
12163
|
+
// conflicts, so a shortfall row is unreachable there (settlePartial needs >1).
|
|
12164
|
+
coinId: coinIdOf(r.payload) ?? "",
|
|
12034
12165
|
recipient: r.payload.recipient,
|
|
12035
12166
|
committedTokenIds: committed.map((o) => o.op.sourceTokenId),
|
|
12036
12167
|
createdAt: this.deps.now()
|
|
@@ -12056,7 +12187,8 @@ var TransferMachine = class {
|
|
|
12056
12187
|
}
|
|
12057
12188
|
async finishBookkeeping(engine, transferId, payload, phase, committed) {
|
|
12058
12189
|
try {
|
|
12059
|
-
const
|
|
12190
|
+
const coinId = coinIdOf(payload);
|
|
12191
|
+
const committedAmount = coinId === void 0 ? "0" : await this.settledAmount(engine, coinId, committed);
|
|
12060
12192
|
await this.deps.recordHistory?.({ transferId, payload, phase, committedAmount });
|
|
12061
12193
|
} catch {
|
|
12062
12194
|
}
|
|
@@ -12099,8 +12231,11 @@ var TransferMachine = class {
|
|
|
12099
12231
|
}
|
|
12100
12232
|
};
|
|
12101
12233
|
function conflictAmount(engine, payload, op, source) {
|
|
12102
|
-
|
|
12103
|
-
|
|
12234
|
+
const split = splitOf(payload);
|
|
12235
|
+
if (op.kind === "split") return BigInt(split.splitAmount);
|
|
12236
|
+
const coinId = coinIdOf(payload);
|
|
12237
|
+
if (coinId === void 0) return 0n;
|
|
12238
|
+
return source !== void 0 ? engine.balanceOf(source, coinId) : 0n;
|
|
12104
12239
|
}
|
|
12105
12240
|
|
|
12106
12241
|
// modules/payments-v2/machine/resume.ts
|
|
@@ -12207,6 +12342,14 @@ async function runOne(ctx, job, report) {
|
|
|
12207
12342
|
report.failed.push(job.transferId);
|
|
12208
12343
|
return;
|
|
12209
12344
|
}
|
|
12345
|
+
if (job.payload.kind === "coinless" && !isCoinlessEnvelope(token.valueEnvelope)) {
|
|
12346
|
+
logger.warn(
|
|
12347
|
+
"PaymentsV2",
|
|
12348
|
+
`resume: token intent ${job.transferId} names a source carrying coin value \u2014 refusing`
|
|
12349
|
+
);
|
|
12350
|
+
report.failed.push(job.transferId);
|
|
12351
|
+
return;
|
|
12352
|
+
}
|
|
12210
12353
|
mine.set(op.sourceTokenId, token);
|
|
12211
12354
|
}
|
|
12212
12355
|
try {
|
|
@@ -12237,6 +12380,28 @@ async function classifyResumeFailure(deps, stores, transferId, err, report) {
|
|
|
12237
12380
|
}
|
|
12238
12381
|
report.failed.push(transferId);
|
|
12239
12382
|
}
|
|
12383
|
+
function validateCoinPayload(c) {
|
|
12384
|
+
if (typeof c.coinId !== "string" || typeof c.amount !== "string") {
|
|
12385
|
+
throw new SphereError("intent payload is missing coinId/amount", "VALIDATION_ERROR");
|
|
12386
|
+
}
|
|
12387
|
+
const s = c.split;
|
|
12388
|
+
if (s !== void 0 && (typeof s.tokenId !== "string" || typeof s.splitAmount !== "string" || typeof s.remainderAmount !== "string")) {
|
|
12389
|
+
throw new SphereError("intent payload split spec is malformed", "VALIDATION_ERROR");
|
|
12390
|
+
}
|
|
12391
|
+
return { ...c, kind: "coin" };
|
|
12392
|
+
}
|
|
12393
|
+
function validateCoinlessPayload(p) {
|
|
12394
|
+
if (p.direct?.length !== 1 || typeof p.direct[0] !== "string" || p.direct[0] === "") {
|
|
12395
|
+
throw new SphereError(
|
|
12396
|
+
"token intent payload must name exactly one source token",
|
|
12397
|
+
"VALIDATION_ERROR"
|
|
12398
|
+
);
|
|
12399
|
+
}
|
|
12400
|
+
if (p.split !== void 0) {
|
|
12401
|
+
throw new SphereError("token intent payload cannot carry a split", "VALIDATION_ERROR");
|
|
12402
|
+
}
|
|
12403
|
+
return { ...p, kind: "coinless", direct: [p.direct[0]] };
|
|
12404
|
+
}
|
|
12240
12405
|
function validatePayload(raw) {
|
|
12241
12406
|
const p = raw !== null && typeof raw === "object" ? raw : null;
|
|
12242
12407
|
if (p === null || p.v !== 2 || !Array.isArray(p.direct) || p.direct.some((t) => typeof t !== "string")) {
|
|
@@ -12245,14 +12410,17 @@ function validatePayload(raw) {
|
|
|
12245
12410
|
"VALIDATION_ERROR"
|
|
12246
12411
|
);
|
|
12247
12412
|
}
|
|
12248
|
-
if (typeof p.recipient !== "string"
|
|
12249
|
-
throw new SphereError("intent payload is missing recipient
|
|
12413
|
+
if (typeof p.recipient !== "string") {
|
|
12414
|
+
throw new SphereError("intent payload is missing recipient", "VALIDATION_ERROR");
|
|
12250
12415
|
}
|
|
12251
|
-
const
|
|
12252
|
-
if (
|
|
12253
|
-
throw new SphereError(
|
|
12416
|
+
const kind = p.kind ?? "coin";
|
|
12417
|
+
if (kind !== "coin" && kind !== "coinless") {
|
|
12418
|
+
throw new SphereError(
|
|
12419
|
+
`unsupported intent kind '${String(kind)}' \u2014 not resumable by this client`,
|
|
12420
|
+
"VALIDATION_ERROR"
|
|
12421
|
+
);
|
|
12254
12422
|
}
|
|
12255
|
-
return p;
|
|
12423
|
+
return kind === "coinless" ? validateCoinlessPayload(p) : validateCoinPayload(p);
|
|
12256
12424
|
}
|
|
12257
12425
|
|
|
12258
12426
|
// modules/payments-v2/convergence.ts
|
|
@@ -12458,13 +12626,23 @@ async function openRow(entry, journal, decryptPayload) {
|
|
|
12458
12626
|
transferId: entry.transferId,
|
|
12459
12627
|
kind: "open",
|
|
12460
12628
|
recipient: typeof payload?.recipient === "string" ? payload.recipient : "",
|
|
12461
|
-
|
|
12462
|
-
amount: typeof payload?.amount === "string" ? payload.amount : "",
|
|
12629
|
+
...subject(payload),
|
|
12463
12630
|
legs: { certified, total: Math.max(total, certified) },
|
|
12464
12631
|
deliveryPending: journal.length > 0,
|
|
12465
12632
|
createdAt: entry.createdAt
|
|
12466
12633
|
};
|
|
12467
12634
|
}
|
|
12635
|
+
function subject(payload) {
|
|
12636
|
+
if (payload?.kind === "coinless") {
|
|
12637
|
+
const tokenId = payload.direct?.[0];
|
|
12638
|
+
return { coinId: "", amount: "", ...typeof tokenId === "string" ? { tokenId } : {} };
|
|
12639
|
+
}
|
|
12640
|
+
const coin = payload;
|
|
12641
|
+
return {
|
|
12642
|
+
coinId: typeof coin?.coinId === "string" ? coin.coinId : "",
|
|
12643
|
+
amount: typeof coin?.amount === "string" ? coin.amount : ""
|
|
12644
|
+
};
|
|
12645
|
+
}
|
|
12468
12646
|
function journalOnlyRow(transferId, entries) {
|
|
12469
12647
|
const certified = distinctOpCount(entries);
|
|
12470
12648
|
return {
|
|
@@ -12494,6 +12672,160 @@ function distinctOpCount(entries) {
|
|
|
12494
12672
|
return new Set(entries.map((entry) => entry.opIndex)).size;
|
|
12495
12673
|
}
|
|
12496
12674
|
|
|
12675
|
+
// modules/payments-v2/inventory/token-data.ts
|
|
12676
|
+
async function readTokenData(deps, tokenId) {
|
|
12677
|
+
if (deps.view.stateHashOf(tokenId) === void 0) {
|
|
12678
|
+
throw new SphereError(`Token ${tokenId} is not in inventory`, "VALIDATION_ERROR");
|
|
12679
|
+
}
|
|
12680
|
+
const bytes = (await deps.storagePort.getBlobs([tokenId])).get(tokenId);
|
|
12681
|
+
if (bytes === void 0) {
|
|
12682
|
+
throw new SphereError(`Token ${tokenId} has no blob in storage`, "STORAGE_ERROR");
|
|
12683
|
+
}
|
|
12684
|
+
return deps.engine.readTokenData(await deps.engine.decodeToken({ tokenId, token: bytes }));
|
|
12685
|
+
}
|
|
12686
|
+
|
|
12687
|
+
// modules/payments-v2/machine/payload.ts
|
|
12688
|
+
function buildPayload(recipientPubkey, request, spend, spentStates) {
|
|
12689
|
+
return {
|
|
12690
|
+
v: 2,
|
|
12691
|
+
kind: "coin",
|
|
12692
|
+
recipient: recipientPubkey,
|
|
12693
|
+
coinId: request.coinId,
|
|
12694
|
+
amount: request.amount,
|
|
12695
|
+
...request.memo !== void 0 ? { memo: request.memo } : {},
|
|
12696
|
+
direct: [...spend.plan.direct],
|
|
12697
|
+
...spend.plan.split !== void 0 ? {
|
|
12698
|
+
split: {
|
|
12699
|
+
tokenId: spend.plan.split.tokenId,
|
|
12700
|
+
splitAmount: spend.plan.split.splitAmount.toString(),
|
|
12701
|
+
remainderAmount: spend.plan.split.remainderAmount.toString()
|
|
12702
|
+
}
|
|
12703
|
+
} : {},
|
|
12704
|
+
spentStates
|
|
12705
|
+
};
|
|
12706
|
+
}
|
|
12707
|
+
function messageOf(err) {
|
|
12708
|
+
return err instanceof Error ? err.message : String(err);
|
|
12709
|
+
}
|
|
12710
|
+
function buildCoinlessPayload(recipientPubkey, request, spentStates) {
|
|
12711
|
+
return {
|
|
12712
|
+
v: 2,
|
|
12713
|
+
kind: "coinless",
|
|
12714
|
+
recipient: recipientPubkey,
|
|
12715
|
+
...request.memo !== void 0 ? { memo: request.memo } : {},
|
|
12716
|
+
direct: [request.tokenId],
|
|
12717
|
+
spentStates
|
|
12718
|
+
};
|
|
12719
|
+
}
|
|
12720
|
+
|
|
12721
|
+
// modules/payments-v2/mint-params.ts
|
|
12722
|
+
function mintParams(recipientPubkey, coinId, amount) {
|
|
12723
|
+
return { recipientPubkey, value: { assets: [{ coinId, amount }] } };
|
|
12724
|
+
}
|
|
12725
|
+
|
|
12726
|
+
// modules/payments-v2/mint.ts
|
|
12727
|
+
function runMintUnderJournal(deps, input) {
|
|
12728
|
+
return mintUnderJournal(deps, input);
|
|
12729
|
+
}
|
|
12730
|
+
async function mintUnderJournal(deps, input) {
|
|
12731
|
+
const { mintId, coinId, amount } = input;
|
|
12732
|
+
const engine = deps.engine;
|
|
12733
|
+
const entry = {
|
|
12734
|
+
mintId,
|
|
12735
|
+
coinId,
|
|
12736
|
+
amount: amount.toString(),
|
|
12737
|
+
tokenId: "",
|
|
12738
|
+
createdAt: deps.now()
|
|
12739
|
+
};
|
|
12740
|
+
await deps.mintJournal.upsert(entry);
|
|
12741
|
+
let token;
|
|
12742
|
+
try {
|
|
12743
|
+
token = await engine.mint(mintParams(deps.ownPubkeyBytes, coinId, amount), { transferId: mintId });
|
|
12744
|
+
} catch (err) {
|
|
12745
|
+
deps.armHeartbeat();
|
|
12746
|
+
return { success: false, error: messageOf(err) };
|
|
12747
|
+
}
|
|
12748
|
+
if (token.blob.tokenId !== entry.tokenId) {
|
|
12749
|
+
await deps.mintJournal.upsert({ ...entry, tokenId: token.blob.tokenId });
|
|
12750
|
+
}
|
|
12751
|
+
try {
|
|
12752
|
+
await finalizeMint(deps, mintId, token, coinId, amount.toString());
|
|
12753
|
+
} catch (err) {
|
|
12754
|
+
deps.armHeartbeat();
|
|
12755
|
+
return { success: false, tokenId: token.blob.tokenId, error: messageOf(err) };
|
|
12756
|
+
}
|
|
12757
|
+
return { success: true, tokenId: token.blob.tokenId };
|
|
12758
|
+
}
|
|
12759
|
+
async function finalizeMint(deps, mintId, token, coinId, amount) {
|
|
12760
|
+
const engine = deps.engine;
|
|
12761
|
+
const bytes = token.blob.token;
|
|
12762
|
+
const digest = bytesToHex3(sha256(bytes));
|
|
12763
|
+
const keys = await deps.storagePort.uploadBlobs([{ sha256: digest, bytes }]);
|
|
12764
|
+
const key = keys.get(digest);
|
|
12765
|
+
if (key === void 0) {
|
|
12766
|
+
throw new SphereError(`no upload key returned for mint blob ${digest}`, "STORAGE_ERROR");
|
|
12767
|
+
}
|
|
12768
|
+
await deps.storagePort.applyDelta({
|
|
12769
|
+
transferId: mintId,
|
|
12770
|
+
spent: [],
|
|
12771
|
+
added: [{ tokenId: token.blob.tokenId, key }]
|
|
12772
|
+
});
|
|
12773
|
+
await deps.recordMint({
|
|
12774
|
+
tokenId: token.blob.tokenId,
|
|
12775
|
+
assets: [{ coinId, amount }]
|
|
12776
|
+
});
|
|
12777
|
+
await deps.mintJournal.removeByKey(mintId);
|
|
12778
|
+
deps.noteHeldState(token.blob.tokenId, (await engine.deliveryKeys(bytes)).stateHash);
|
|
12779
|
+
deps.refreshView();
|
|
12780
|
+
}
|
|
12781
|
+
|
|
12782
|
+
// modules/payments-v2/send-coinless.ts
|
|
12783
|
+
async function materializeCoinlessSpend(deps, input) {
|
|
12784
|
+
const { engine } = deps;
|
|
12785
|
+
const tokenId = input.request.tokenId;
|
|
12786
|
+
const bytes = (await deps.storagePort.getBlobs([tokenId])).get(tokenId);
|
|
12787
|
+
if (bytes === void 0) {
|
|
12788
|
+
throw new SphereError(`Selected source ${tokenId} has no blob in storage`, "STORAGE_ERROR");
|
|
12789
|
+
}
|
|
12790
|
+
const token = await engine.decodeToken({ tokenId, token: bytes });
|
|
12791
|
+
if (!isCoinlessEnvelope(token.valueEnvelope)) {
|
|
12792
|
+
throw new SphereError(
|
|
12793
|
+
`Token ${tokenId} carries coin value and cannot be sent with sendCoinless \u2014 use send()`,
|
|
12794
|
+
"VALIDATION_ERROR"
|
|
12795
|
+
);
|
|
12796
|
+
}
|
|
12797
|
+
const keys = await engine.deliveryKeys(bytes);
|
|
12798
|
+
const payload = buildCoinlessPayload(input.recipientPubkey, input.request, {
|
|
12799
|
+
[tokenId]: { local: keys.stateHash, protocol: keys.stateHash }
|
|
12800
|
+
});
|
|
12801
|
+
const plan = {
|
|
12802
|
+
transferId: input.transferId,
|
|
12803
|
+
recipientPubkey: input.recipientPubkey,
|
|
12804
|
+
payload,
|
|
12805
|
+
ops: buildOps(payload),
|
|
12806
|
+
sources: /* @__PURE__ */ new Map([[tokenId, token]]),
|
|
12807
|
+
...input.request.memo !== void 0 ? { memo: input.request.memo } : {}
|
|
12808
|
+
};
|
|
12809
|
+
return { transferId: input.transferId, coinId: "", plan, sourceIds: input.sourceIds, sourceTokens: [] };
|
|
12810
|
+
}
|
|
12811
|
+
|
|
12812
|
+
// modules/payments-v2/send-errors.ts
|
|
12813
|
+
function partialize(err, run) {
|
|
12814
|
+
if (run.delivered.length === 0) return err;
|
|
12815
|
+
return new PartialSendConflictError(
|
|
12816
|
+
"Part of your payment was sent; the remaining amount could not be completed (see cause). The delivered portion is final \u2014 re-plan only the shortfall, never the full amount.",
|
|
12817
|
+
run.firstPartialId ?? "",
|
|
12818
|
+
[...run.delivered],
|
|
12819
|
+
run.amount,
|
|
12820
|
+
err
|
|
12821
|
+
);
|
|
12822
|
+
}
|
|
12823
|
+
function stampTransferId(err, transferId) {
|
|
12824
|
+
if (err instanceof SphereError && isPossiblyCommittedSendOutcome(err) && err.transferId === void 0) {
|
|
12825
|
+
err.transferId = transferId;
|
|
12826
|
+
}
|
|
12827
|
+
}
|
|
12828
|
+
|
|
12497
12829
|
// modules/payments-v2/recipient.ts
|
|
12498
12830
|
var ATTENTION_RECIPIENT_NETWORK_UNVERIFIED = "recipient:network-unverified";
|
|
12499
12831
|
async function requireSameNetworkRecipient(deps, identifier) {
|
|
@@ -12542,11 +12874,6 @@ async function splitOpIndex(deps, envelope) {
|
|
|
12542
12874
|
return Array.isArray(payload.direct) ? payload.direct.length : 0;
|
|
12543
12875
|
}
|
|
12544
12876
|
|
|
12545
|
-
// modules/payments-v2/mint-params.ts
|
|
12546
|
-
function mintParams(recipientPubkey, coinId, amount) {
|
|
12547
|
-
return { recipientPubkey, value: { assets: [{ coinId, amount }] } };
|
|
12548
|
-
}
|
|
12549
|
-
|
|
12550
12877
|
// modules/payments-v2/prewarm-cache.ts
|
|
12551
12878
|
var PrewarmCache = class _PrewarmCache {
|
|
12552
12879
|
entries = /* @__PURE__ */ new Map();
|
|
@@ -12720,30 +13047,15 @@ function transferringToken(tokenId, coinId, amount, registry, now) {
|
|
|
12720
13047
|
});
|
|
12721
13048
|
}
|
|
12722
13049
|
|
|
12723
|
-
// modules/payments-v2/
|
|
12724
|
-
function
|
|
12725
|
-
return {
|
|
12726
|
-
v: 2,
|
|
12727
|
-
recipient: recipientPubkey,
|
|
12728
|
-
coinId: request.coinId,
|
|
12729
|
-
amount: request.amount,
|
|
12730
|
-
...request.memo !== void 0 ? { memo: request.memo } : {},
|
|
12731
|
-
direct: [...spend.plan.direct],
|
|
12732
|
-
...spend.plan.split !== void 0 ? {
|
|
12733
|
-
split: {
|
|
12734
|
-
tokenId: spend.plan.split.tokenId,
|
|
12735
|
-
splitAmount: spend.plan.split.splitAmount.toString(),
|
|
12736
|
-
remainderAmount: spend.plan.split.remainderAmount.toString()
|
|
12737
|
-
}
|
|
12738
|
-
} : {},
|
|
12739
|
-
spentStates
|
|
12740
|
-
};
|
|
13050
|
+
// modules/payments-v2/history/History.ts
|
|
13051
|
+
function wireAssets(assets) {
|
|
13052
|
+
return assets.map((a) => ({ coinId: a.coinId, amount: a.amount }));
|
|
12741
13053
|
}
|
|
12742
|
-
function
|
|
12743
|
-
|
|
13054
|
+
function isPermanentReject(err) {
|
|
13055
|
+
const status = err?.status;
|
|
13056
|
+
if (typeof status !== "number") return false;
|
|
13057
|
+
return status >= 400 && status < 500 && status !== 408 && status !== 429;
|
|
12744
13058
|
}
|
|
12745
|
-
|
|
12746
|
-
// modules/payments-v2/history/History.ts
|
|
12747
13059
|
var WIRE_TOKEN_ID = /^(?:[0-9a-f]{2}){1,64}$/;
|
|
12748
13060
|
var WIRE_PUBKEY = /^0[23][0-9a-f]{64}$/;
|
|
12749
13061
|
var History = class {
|
|
@@ -12768,7 +13080,7 @@ var History = class {
|
|
|
12768
13080
|
dedupKey: input.transferId,
|
|
12769
13081
|
id: (this.deps.newId ?? randomUUID)(),
|
|
12770
13082
|
type: "SENT",
|
|
12771
|
-
assets:
|
|
13083
|
+
assets: wireAssets(input.assets),
|
|
12772
13084
|
ts: this.ts(input.timestamp),
|
|
12773
13085
|
transferId: input.transferId,
|
|
12774
13086
|
...this.wireTokenId(input.tokenId),
|
|
@@ -12781,7 +13093,7 @@ var History = class {
|
|
|
12781
13093
|
dedupKey: `RECEIVED:${input.tokenId.toLowerCase()}:${input.stateHash.toLowerCase()}`,
|
|
12782
13094
|
id: (this.deps.newId ?? randomUUID)(),
|
|
12783
13095
|
type: "RECEIVED",
|
|
12784
|
-
assets:
|
|
13096
|
+
assets: wireAssets(input.assets),
|
|
12785
13097
|
ts: this.ts(input.timestamp),
|
|
12786
13098
|
...input.transferId !== void 0 ? { transferId: input.transferId } : {},
|
|
12787
13099
|
...this.wireTokenId(input.tokenId),
|
|
@@ -12794,7 +13106,7 @@ var History = class {
|
|
|
12794
13106
|
dedupKey: `MINT:${input.tokenId.toLowerCase()}`,
|
|
12795
13107
|
id: (this.deps.newId ?? randomUUID)(),
|
|
12796
13108
|
type: "MINT",
|
|
12797
|
-
assets:
|
|
13109
|
+
assets: wireAssets(input.assets),
|
|
12798
13110
|
ts: this.ts(input.timestamp),
|
|
12799
13111
|
...this.wireTokenId(input.tokenId)
|
|
12800
13112
|
}));
|
|
@@ -12805,7 +13117,10 @@ var History = class {
|
|
|
12805
13117
|
try {
|
|
12806
13118
|
await this.deps.client.postHistory([record]);
|
|
12807
13119
|
} catch (err) {
|
|
12808
|
-
this.deps.log?.(
|
|
13120
|
+
this.deps.log?.(
|
|
13121
|
+
isPermanentReject(err) ? "history POST REJECTED \u2014 the record shape is refused; no retry can fix it" : "history POST failed (dedupKey makes retry safe)",
|
|
13122
|
+
err
|
|
13123
|
+
);
|
|
12809
13124
|
return;
|
|
12810
13125
|
}
|
|
12811
13126
|
this.deps.emit("history:updated", this.toEntry(record));
|
|
@@ -12872,6 +13187,13 @@ var CURSOR_KEY = STORE_KEYS.streamCursor("inventory");
|
|
|
12872
13187
|
function stateKey(tokenId, stateHash) {
|
|
12873
13188
|
return `${tokenId}:${stateHash}`;
|
|
12874
13189
|
}
|
|
13190
|
+
function unchanged(prev, item, tokenType) {
|
|
13191
|
+
return prev.seq === item.seq && prev.status === item.status && prev.stateHash === item.stateHash && tokenType === prev.tokenType;
|
|
13192
|
+
}
|
|
13193
|
+
function isCoinless(item, prev) {
|
|
13194
|
+
if (item.status !== "active") return prev?.coinless ?? false;
|
|
13195
|
+
return (item.assets?.length ?? 0) === 0;
|
|
13196
|
+
}
|
|
12875
13197
|
var InventoryView = class {
|
|
12876
13198
|
constructor(deps) {
|
|
12877
13199
|
this.deps = deps;
|
|
@@ -12965,14 +13287,26 @@ var InventoryView = class {
|
|
|
12965
13287
|
pool(coinId) {
|
|
12966
13288
|
const out = [];
|
|
12967
13289
|
for (const [tokenId, entry] of this.mirror) {
|
|
12968
|
-
if (
|
|
12969
|
-
if (this.inFlight.has(tokenId) && !this.pinned(tokenId)) continue;
|
|
12970
|
-
if (this.suspected.has(stateKey(tokenId, entry.stateHash))) continue;
|
|
13290
|
+
if (!this.isSpendable(tokenId, entry)) continue;
|
|
12971
13291
|
const asset = entry.assets.find((a) => a.coinId === coinId);
|
|
12972
13292
|
if (asset) out.push({ tokenId, amount: BigInt(asset.amount) });
|
|
12973
13293
|
}
|
|
12974
13294
|
return out;
|
|
12975
13295
|
}
|
|
13296
|
+
/**
|
|
13297
|
+
* The eligibility gates every spend shares, coin or token — ONE definition, so a
|
|
13298
|
+
* single probe covers both verbs and neither can drift from the other.
|
|
13299
|
+
*/
|
|
13300
|
+
isSpendable(tokenId, entry) {
|
|
13301
|
+
if (entry.status !== "active") return false;
|
|
13302
|
+
if (this.inFlight.has(tokenId) && !this.pinned(tokenId)) return false;
|
|
13303
|
+
return !this.suspected.has(stateKey(tokenId, entry.stateHash));
|
|
13304
|
+
}
|
|
13305
|
+
/** #777: is this NAMED token a spendable COINLESS holding? Never a coin source. */
|
|
13306
|
+
spendableCoinless(tokenId) {
|
|
13307
|
+
const entry = this.mirror.get(tokenId);
|
|
13308
|
+
return entry !== void 0 && entry.coinless && this.isSpendable(tokenId, entry);
|
|
13309
|
+
}
|
|
12976
13310
|
/**
|
|
12977
13311
|
* Current mirrored state of an ACTIVE token, or undefined when it is unknown
|
|
12978
13312
|
* or tombstoned. Exists so a cached blob can be state-scoped (F6): a blob is
|
|
@@ -12998,6 +13332,29 @@ var InventoryView = class {
|
|
|
12998
13332
|
}
|
|
12999
13333
|
return out;
|
|
13000
13334
|
}
|
|
13335
|
+
/**
|
|
13336
|
+
* Coinless holdings (#777) — DISJOINT from tokens(): an entry is coinless
|
|
13337
|
+
* exactly when it is not, so no mirror row can appear in both reads.
|
|
13338
|
+
*/
|
|
13339
|
+
coinless(registry) {
|
|
13340
|
+
const out = [];
|
|
13341
|
+
for (const [tokenId, entry] of this.mirror) {
|
|
13342
|
+
if (entry.status !== "active" || !entry.coinless) continue;
|
|
13343
|
+
const meta = entry.tokenType !== void 0 ? registry.getTypeMeta?.(entry.tokenType) : null;
|
|
13344
|
+
out.push({
|
|
13345
|
+
tokenId,
|
|
13346
|
+
...entry.tokenType !== void 0 ? { tokenType: entry.tokenType } : {},
|
|
13347
|
+
...meta ? { name: meta.name } : {},
|
|
13348
|
+
...meta?.iconUrl != null ? { iconUrl: meta.iconUrl } : {},
|
|
13349
|
+
stateHash: entry.stateHash,
|
|
13350
|
+
transferring: this.held(tokenId),
|
|
13351
|
+
...this.suspected.has(stateKey(tokenId, entry.stateHash)) ? { suspectedSpent: true } : {},
|
|
13352
|
+
createdAt: entry.createdAt,
|
|
13353
|
+
updatedAt: entry.updatedAt
|
|
13354
|
+
});
|
|
13355
|
+
}
|
|
13356
|
+
return out;
|
|
13357
|
+
}
|
|
13001
13358
|
async assets(registry, price) {
|
|
13002
13359
|
const raw = aggregateAssets(this.activeEntries(), (tokenId) => this.held(tokenId), registry);
|
|
13003
13360
|
if (!price || raw.length === 0) return raw;
|
|
@@ -13069,14 +13426,19 @@ var InventoryView = class {
|
|
|
13069
13426
|
applyOne(item, now) {
|
|
13070
13427
|
const prev = this.mirror.get(item.tokenId);
|
|
13071
13428
|
if (prev && item.seq < prev.seq) return false;
|
|
13072
|
-
|
|
13073
|
-
|
|
13074
|
-
}
|
|
13429
|
+
const tokenType = item.tokenType ?? prev?.tokenType;
|
|
13430
|
+
if (prev && unchanged(prev, item, tokenType)) return false;
|
|
13075
13431
|
this.mirror.set(item.tokenId, {
|
|
13076
13432
|
stateHash: item.stateHash,
|
|
13077
13433
|
seq: item.seq,
|
|
13078
13434
|
status: item.status,
|
|
13079
|
-
|
|
13435
|
+
// ONLY a tombstone inherits: it omits assets for an unrelated reason and
|
|
13436
|
+
// recoverRemoved must know the amount it restores. An ACTIVE row that omits
|
|
13437
|
+
// them is stating coinlessness, and inheriting there would keep stale assets
|
|
13438
|
+
// in tokens() while `coinless` is true — the row in BOTH reads (§16).
|
|
13439
|
+
assets: item.assets ?? (item.status === "removed" ? prev?.assets ?? [] : []),
|
|
13440
|
+
coinless: isCoinless(item, prev),
|
|
13441
|
+
...tokenType !== void 0 ? { tokenType } : {},
|
|
13080
13442
|
createdAt: prev?.createdAt ?? now,
|
|
13081
13443
|
updatedAt: now
|
|
13082
13444
|
});
|
|
@@ -13312,7 +13674,12 @@ async function screen(deps, engine, entry) {
|
|
|
13312
13674
|
}
|
|
13313
13675
|
return {
|
|
13314
13676
|
kind: "accept",
|
|
13315
|
-
record: {
|
|
13677
|
+
record: {
|
|
13678
|
+
tokenId: keys.tokenId,
|
|
13679
|
+
stateHash: keys.stateHash,
|
|
13680
|
+
assets: toAssetAmounts(token),
|
|
13681
|
+
...isCoinlessEnvelope2(token.valueEnvelope) ? { tokenType: token.tokenType } : {}
|
|
13682
|
+
}
|
|
13316
13683
|
};
|
|
13317
13684
|
}
|
|
13318
13685
|
async function announce(deps, entry, record) {
|
|
@@ -13323,6 +13690,7 @@ async function announce(deps, entry, record) {
|
|
|
13323
13690
|
tokenId: record.tokenId,
|
|
13324
13691
|
stateHash: record.stateHash,
|
|
13325
13692
|
assets: record.assets,
|
|
13693
|
+
...record.tokenType !== void 0 ? { tokenType: record.tokenType } : {},
|
|
13326
13694
|
...entry.senderPubkey !== void 0 ? { senderPubkey: entry.senderPubkey } : {},
|
|
13327
13695
|
...entry.senderNametag !== void 0 ? { senderNametag: entry.senderNametag } : {},
|
|
13328
13696
|
...entry.memo !== void 0 ? { memo: entry.memo } : {},
|
|
@@ -13336,6 +13704,19 @@ async function announce(deps, entry, record) {
|
|
|
13336
13704
|
senderPubkey: entry.senderPubkey ?? "",
|
|
13337
13705
|
...entry.senderNametag !== void 0 ? { senderNametag: entry.senderNametag } : {},
|
|
13338
13706
|
tokens: record.assets.map((asset) => toUiToken(record.tokenId, asset, deps.registry, receivedAt)),
|
|
13707
|
+
// #777: named here rather than mapped from assets, which announced an EMPTY list.
|
|
13708
|
+
...record.tokenType !== void 0 ? {
|
|
13709
|
+
coinless: [
|
|
13710
|
+
{
|
|
13711
|
+
tokenId: record.tokenId,
|
|
13712
|
+
tokenType: record.tokenType,
|
|
13713
|
+
stateHash: record.stateHash,
|
|
13714
|
+
transferring: false,
|
|
13715
|
+
createdAt: receivedAt,
|
|
13716
|
+
updatedAt: receivedAt
|
|
13717
|
+
}
|
|
13718
|
+
]
|
|
13719
|
+
} : {},
|
|
13339
13720
|
...entry.memo !== void 0 ? { memo: entry.memo } : {},
|
|
13340
13721
|
receivedAt
|
|
13341
13722
|
};
|
|
@@ -13434,6 +13815,9 @@ function claimAck(entry) {
|
|
|
13434
13815
|
function rejectAck(entry, reason) {
|
|
13435
13816
|
return { deliveryId: entry.deliveryId, disposition: "rejected", reason, cursor: entry.cursor };
|
|
13436
13817
|
}
|
|
13818
|
+
function isCoinlessEnvelope2(envelope) {
|
|
13819
|
+
return envelope.startsWith("none_");
|
|
13820
|
+
}
|
|
13437
13821
|
function toAssetAmounts(token) {
|
|
13438
13822
|
return (token.value?.assets ?? []).map((asset) => ({
|
|
13439
13823
|
coinId: asset.coinId,
|
|
@@ -14001,6 +14385,37 @@ var SpendQueue = class {
|
|
|
14001
14385
|
}
|
|
14002
14386
|
return { kind: "queued", settled: this.enqueue(reservationId, request.coinId, amount) };
|
|
14003
14387
|
}
|
|
14388
|
+
/**
|
|
14389
|
+
* #777: reserve a NAMED coinless source. Never queues — there is nothing to wait
|
|
14390
|
+
* for. A coin plan waits because some OTHER combination may free up; a named
|
|
14391
|
+
* token is either free now or held by a reservation that only its own transfer
|
|
14392
|
+
* can release, so queueing would block until a timeout on a spend that cannot
|
|
14393
|
+
* become possible.
|
|
14394
|
+
*/
|
|
14395
|
+
planCoinless(reservationId, tokenId) {
|
|
14396
|
+
if (this.destroyed) {
|
|
14397
|
+
throw new SphereError("Module has been destroyed", "MODULE_DESTROYED");
|
|
14398
|
+
}
|
|
14399
|
+
const unproven = this.deps.ledger.unprovenReason();
|
|
14400
|
+
if (unproven !== null) {
|
|
14401
|
+
throw new SphereError(`Cannot spend yet: ${unproven}`, "SEND_SYNC_PENDING");
|
|
14402
|
+
}
|
|
14403
|
+
if (this.deps.spendableCoinless?.(tokenId) !== true) {
|
|
14404
|
+
throw new SphereError(
|
|
14405
|
+
`Token ${tokenId} is not a spendable coinless holding`,
|
|
14406
|
+
"VALIDATION_ERROR"
|
|
14407
|
+
);
|
|
14408
|
+
}
|
|
14409
|
+
const holder = this.deps.ledger.holderOf(tokenId);
|
|
14410
|
+
if (holder !== void 0) {
|
|
14411
|
+
throw new SphereError(
|
|
14412
|
+
`Token ${tokenId} is already reserved by transfer ${holder}`,
|
|
14413
|
+
"VALIDATION_ERROR"
|
|
14414
|
+
);
|
|
14415
|
+
}
|
|
14416
|
+
this.deps.ledger.reserve(reservationId, [{ tokenId }]);
|
|
14417
|
+
return { reservationId, plan: { direct: [tokenId] } };
|
|
14418
|
+
}
|
|
14004
14419
|
/**
|
|
14005
14420
|
* The sources `plan()` WOULD pick right now, reserving nothing and queueing
|
|
14006
14421
|
* nothing — a read-only probe used to warm blobs while the user is still
|
|
@@ -14190,6 +14605,7 @@ function composeFacadeParts(deps, hooks) {
|
|
|
14190
14605
|
const queue = new SpendQueue({
|
|
14191
14606
|
ledger,
|
|
14192
14607
|
getPool: (coinId) => view.pool(coinId),
|
|
14608
|
+
spendableCoinless: (tokenId) => view.spendableCoinless(tokenId),
|
|
14193
14609
|
...deps.workBudget !== void 0 ? { workBudget: deps.workBudget } : {}
|
|
14194
14610
|
});
|
|
14195
14611
|
const historyStore = new History({
|
|
@@ -14273,10 +14689,9 @@ function buildMachineDeps(deps, hooks, historyStore) {
|
|
|
14273
14689
|
recordHistory: async ({ transferId, payload, committedAmount }) => {
|
|
14274
14690
|
await historyStore.recordSent({
|
|
14275
14691
|
transferId,
|
|
14276
|
-
|
|
14277
|
-
//
|
|
14278
|
-
|
|
14279
|
-
amount: committedAmount,
|
|
14692
|
+
// §5.9: the SETTLED amount, never payload.amount — the plan. A token
|
|
14693
|
+
// spend moved no coin: `assets: []` + tokenId (wallet-api#151 / §10).
|
|
14694
|
+
...payload.kind === "coinless" ? { assets: [], tokenId: payload.direct[0] } : { assets: [{ coinId: payload.coinId, amount: committedAmount }] },
|
|
14280
14695
|
recipientPubkey: payload.recipient,
|
|
14281
14696
|
...payload.memo !== void 0 ? { memo: payload.memo } : {}
|
|
14282
14697
|
});
|
|
@@ -14311,12 +14726,10 @@ function buildReceive(deps, hooks, historyStore, heldStates, refreshView) {
|
|
|
14311
14726
|
});
|
|
14312
14727
|
}
|
|
14313
14728
|
async function recordReceived(historyStore, record) {
|
|
14314
|
-
const first = record.assets[0];
|
|
14315
14729
|
await historyStore.recordReceived({
|
|
14316
14730
|
tokenId: record.tokenId,
|
|
14317
14731
|
stateHash: record.stateHash,
|
|
14318
|
-
|
|
14319
|
-
amount: first?.amount ?? "0",
|
|
14732
|
+
assets: record.assets,
|
|
14320
14733
|
...record.senderPubkey !== void 0 ? { senderPubkey: record.senderPubkey } : {},
|
|
14321
14734
|
...record.senderNametag !== void 0 ? { senderNametag: record.senderNametag } : {},
|
|
14322
14735
|
...record.memo !== void 0 ? { memo: record.memo } : {},
|
|
@@ -14464,6 +14877,13 @@ var PaymentsFacade = class {
|
|
|
14464
14877
|
tokens(filter) {
|
|
14465
14878
|
return this.view.tokens(this.deps.registry, filter);
|
|
14466
14879
|
}
|
|
14880
|
+
coinless() {
|
|
14881
|
+
return this.view.coinless(this.deps.registry);
|
|
14882
|
+
}
|
|
14883
|
+
tokenData(tokenId) {
|
|
14884
|
+
const deps = { engine: this.engine(), view: this.view, storagePort: this.deps.storagePort };
|
|
14885
|
+
return readTokenData(deps, tokenId);
|
|
14886
|
+
}
|
|
14467
14887
|
history(page) {
|
|
14468
14888
|
return this.historyStore.page(page ?? {});
|
|
14469
14889
|
}
|
|
@@ -14488,6 +14908,9 @@ var PaymentsFacade = class {
|
|
|
14488
14908
|
send(request) {
|
|
14489
14909
|
return this.track(this.sendOutcome(request));
|
|
14490
14910
|
}
|
|
14911
|
+
sendCoinless(request) {
|
|
14912
|
+
return this.track(this.sendCoinlessOutcome(request));
|
|
14913
|
+
}
|
|
14491
14914
|
async receive() {
|
|
14492
14915
|
const transfers = await this.track(this.receiveLoop.drainOnce());
|
|
14493
14916
|
return { transfers };
|
|
@@ -14518,12 +14941,32 @@ var PaymentsFacade = class {
|
|
|
14518
14941
|
nowMs() {
|
|
14519
14942
|
return (this.deps.now ?? Date.now)();
|
|
14520
14943
|
}
|
|
14944
|
+
mintDeps() {
|
|
14945
|
+
return {
|
|
14946
|
+
engine: this.engine(),
|
|
14947
|
+
mintJournal: this.machineStores.mintJournal,
|
|
14948
|
+
storagePort: this.deps.storagePort,
|
|
14949
|
+
recordMint: (input) => this.historyStore.recordMint(input),
|
|
14950
|
+
armHeartbeat: () => this.heartbeat.arm(),
|
|
14951
|
+
noteHeldState: (tokenId, stateHash) => void this.heldStates.set(tokenId, stateHash),
|
|
14952
|
+
refreshView: () => this.trackTail(this.view.delta()),
|
|
14953
|
+
ownPubkeyBytes: this.ownPubkeyBytes,
|
|
14954
|
+
now: () => this.nowMs()
|
|
14955
|
+
};
|
|
14956
|
+
}
|
|
14521
14957
|
// ── send policy (§5.5 + old-loop parity; the machine stays policy-free) ────
|
|
14522
14958
|
/** The ONE place a send() outcome is shaped: success emits in finishSend, a
|
|
14523
14959
|
* CLEAN rejection emits `transfer:updated{status:'failed'}` here (§4). */
|
|
14524
|
-
|
|
14960
|
+
sendOutcome(request) {
|
|
14961
|
+
return this.runJob({ kind: "coin", request }, request.amount);
|
|
14962
|
+
}
|
|
14963
|
+
/** A token spend has no amount; '0' keeps the shortfall arithmetic total-free. */
|
|
14964
|
+
sendCoinlessOutcome(request) {
|
|
14965
|
+
return this.runJob({ kind: "coinless", request }, "0");
|
|
14966
|
+
}
|
|
14967
|
+
async runJob(job, amount) {
|
|
14525
14968
|
const run = {
|
|
14526
|
-
amount
|
|
14969
|
+
amount,
|
|
14527
14970
|
delivered: [],
|
|
14528
14971
|
sentTokens: [],
|
|
14529
14972
|
tokenTransfers: [],
|
|
@@ -14533,7 +14976,7 @@ var PaymentsFacade = class {
|
|
|
14533
14976
|
lastTransferId: ""
|
|
14534
14977
|
};
|
|
14535
14978
|
try {
|
|
14536
|
-
return await this.sendWithPolicy(
|
|
14979
|
+
return await this.sendWithPolicy(job, run);
|
|
14537
14980
|
} catch (err) {
|
|
14538
14981
|
this.emitCleanFailure(err, run.lastTransferId);
|
|
14539
14982
|
throw err;
|
|
@@ -14553,19 +14996,19 @@ var PaymentsFacade = class {
|
|
|
14553
14996
|
};
|
|
14554
14997
|
this.deps.emit("transfer:updated", failed);
|
|
14555
14998
|
}
|
|
14556
|
-
async sendWithPolicy(
|
|
14557
|
-
const recipient = await requireSameNetworkRecipient(this.deps, request.recipient);
|
|
14999
|
+
async sendWithPolicy(job, run) {
|
|
15000
|
+
const recipient = await requireSameNetworkRecipient(this.deps, job.request.recipient);
|
|
14558
15001
|
for (let attempt = 0; ; attempt++) {
|
|
14559
15002
|
let ctx;
|
|
14560
15003
|
try {
|
|
14561
|
-
ctx = await this.planAndMaterialize(recipient.chainPubkey,
|
|
15004
|
+
ctx = await this.planAndMaterialize(recipient.chainPubkey, job, run);
|
|
14562
15005
|
} catch (err) {
|
|
14563
|
-
throw
|
|
15006
|
+
throw partialize(err, run);
|
|
14564
15007
|
}
|
|
14565
15008
|
const disposition = await this.runAttempt(ctx);
|
|
14566
|
-
if (disposition.kind === "rethrow") throw
|
|
15009
|
+
if (disposition.kind === "rethrow") throw partialize(disposition.error, run);
|
|
14567
15010
|
if (disposition.kind === "retry-full") {
|
|
14568
|
-
if (attempt >= MAX_RESELECT) throw
|
|
15011
|
+
if (job.kind === "coinless" || attempt >= MAX_RESELECT) throw partialize(disposition.error, run);
|
|
14569
15012
|
continue;
|
|
14570
15013
|
}
|
|
14571
15014
|
if (disposition.kind === "success") {
|
|
@@ -14621,7 +15064,7 @@ var PaymentsFacade = class {
|
|
|
14621
15064
|
async disposeFailedAttempt(ctx, err) {
|
|
14622
15065
|
if (isPossiblyCommittedSendOutcome(err)) {
|
|
14623
15066
|
this.settleKeepOpen(ctx);
|
|
14624
|
-
|
|
15067
|
+
stampTransferId(err, ctx.transferId);
|
|
14625
15068
|
return { kind: "rethrow", error: err };
|
|
14626
15069
|
}
|
|
14627
15070
|
const backstop = await this.machineStores.backstop.getByKey(ctx.transferId);
|
|
@@ -14655,7 +15098,7 @@ var PaymentsFacade = class {
|
|
|
14655
15098
|
async disposeConvergeFailure(ctx, err) {
|
|
14656
15099
|
if (isPossiblyCommittedSendOutcome(err)) {
|
|
14657
15100
|
this.settleKeepOpen(ctx);
|
|
14658
|
-
|
|
15101
|
+
stampTransferId(err, ctx.transferId);
|
|
14659
15102
|
return { kind: "rethrow", error: err };
|
|
14660
15103
|
}
|
|
14661
15104
|
if (classifyError(err) === "conflict") {
|
|
@@ -14666,9 +15109,23 @@ var PaymentsFacade = class {
|
|
|
14666
15109
|
}
|
|
14667
15110
|
return { kind: "rethrow", error: err };
|
|
14668
15111
|
}
|
|
14669
|
-
async planAndMaterialize(recipientPubkey,
|
|
15112
|
+
async planAndMaterialize(recipientPubkey, job, run) {
|
|
14670
15113
|
const transferId = this.newId();
|
|
14671
15114
|
run.lastTransferId = transferId;
|
|
15115
|
+
if (job.kind === "coinless") {
|
|
15116
|
+
const spend2 = this.queue.planCoinless(transferId, job.request.tokenId);
|
|
15117
|
+
const sourceIds2 = this.markPlanned(transferId, "", spend2);
|
|
15118
|
+
try {
|
|
15119
|
+
return await materializeCoinlessSpend(
|
|
15120
|
+
{ engine: this.engine(), storagePort: this.deps.storagePort },
|
|
15121
|
+
{ transferId, recipientPubkey, request: job.request, sourceIds: sourceIds2 }
|
|
15122
|
+
);
|
|
15123
|
+
} catch (err) {
|
|
15124
|
+
this.settleFailure(transferId, "", sourceIds2);
|
|
15125
|
+
throw err;
|
|
15126
|
+
}
|
|
15127
|
+
}
|
|
15128
|
+
const request = { ...job.request, amount: run.amount };
|
|
14672
15129
|
const planned = this.queue.plan(transferId, { coinId: request.coinId, amount: request.amount });
|
|
14673
15130
|
const spend = planned.kind === "planned" ? planned.spend : await planned.settled;
|
|
14674
15131
|
const sourceIds = this.markPlanned(transferId, request.coinId, spend);
|
|
@@ -14802,22 +15259,6 @@ var PaymentsFacade = class {
|
|
|
14802
15259
|
return result;
|
|
14803
15260
|
}
|
|
14804
15261
|
/** Nothing delivered → UNWRAPPED; after ≥1 delivered leg every failure surfaces as PartialSendConflictError over the settled set. */
|
|
14805
|
-
partialize(err, run) {
|
|
14806
|
-
if (run.delivered.length === 0) return err;
|
|
14807
|
-
return new PartialSendConflictError(
|
|
14808
|
-
"Part of your payment was sent; the remaining amount could not be completed (see cause). The delivered portion is final \u2014 re-plan only the shortfall, never the full amount.",
|
|
14809
|
-
run.firstPartialId ?? "",
|
|
14810
|
-
run.delivered,
|
|
14811
|
-
run.amount,
|
|
14812
|
-
err
|
|
14813
|
-
);
|
|
14814
|
-
}
|
|
14815
|
-
/** #441: possibly-committed errors must carry the transferId for the settling journal. */
|
|
14816
|
-
stampTransferId(err, transferId) {
|
|
14817
|
-
if (err instanceof SphereError && isPossiblyCommittedSendOutcome(err) && err.transferId === void 0) {
|
|
14818
|
-
err.transferId = transferId;
|
|
14819
|
-
}
|
|
14820
|
-
}
|
|
14821
15262
|
async softAbort(transferId) {
|
|
14822
15263
|
try {
|
|
14823
15264
|
await this.deps.client.abortIntent(transferId);
|
|
@@ -14833,57 +15274,11 @@ var PaymentsFacade = class {
|
|
|
14833
15274
|
const mintId = this.newId();
|
|
14834
15275
|
this.activeMoneyOps.add(mintId);
|
|
14835
15276
|
try {
|
|
14836
|
-
return await this.
|
|
15277
|
+
return await runMintUnderJournal(this.mintDeps(), { mintId, coinId, amount });
|
|
14837
15278
|
} finally {
|
|
14838
15279
|
this.activeMoneyOps.delete(mintId);
|
|
14839
15280
|
}
|
|
14840
15281
|
}
|
|
14841
|
-
async mintUnderJournal(mintId, coinId, amount) {
|
|
14842
|
-
const engine = this.engine();
|
|
14843
|
-
const entry = {
|
|
14844
|
-
mintId,
|
|
14845
|
-
coinId,
|
|
14846
|
-
amount: amount.toString(),
|
|
14847
|
-
tokenId: "",
|
|
14848
|
-
createdAt: this.nowMs()
|
|
14849
|
-
};
|
|
14850
|
-
await this.machineStores.mintJournal.upsert(entry);
|
|
14851
|
-
let token;
|
|
14852
|
-
try {
|
|
14853
|
-
token = await engine.mint(mintParams(this.ownPubkeyBytes, coinId, amount), { transferId: mintId });
|
|
14854
|
-
} catch (err) {
|
|
14855
|
-
this.heartbeat.arm();
|
|
14856
|
-
return { success: false, error: messageOf(err) };
|
|
14857
|
-
}
|
|
14858
|
-
if (token.blob.tokenId !== entry.tokenId) {
|
|
14859
|
-
await this.machineStores.mintJournal.upsert({ ...entry, tokenId: token.blob.tokenId });
|
|
14860
|
-
}
|
|
14861
|
-
try {
|
|
14862
|
-
await this.finalizeMint(engine, mintId, token, coinId, amount.toString());
|
|
14863
|
-
} catch (err) {
|
|
14864
|
-
this.heartbeat.arm();
|
|
14865
|
-
return { success: false, tokenId: token.blob.tokenId, error: messageOf(err) };
|
|
14866
|
-
}
|
|
14867
|
-
return { success: true, tokenId: token.blob.tokenId };
|
|
14868
|
-
}
|
|
14869
|
-
async finalizeMint(engine, mintId, token, coinId, amount) {
|
|
14870
|
-
const bytes = token.blob.token;
|
|
14871
|
-
const digest = bytesToHex3(sha256(bytes));
|
|
14872
|
-
const keys = await this.deps.storagePort.uploadBlobs([{ sha256: digest, bytes }]);
|
|
14873
|
-
const key = keys.get(digest);
|
|
14874
|
-
if (key === void 0) {
|
|
14875
|
-
throw new SphereError(`no upload key returned for mint blob ${digest}`, "STORAGE_ERROR");
|
|
14876
|
-
}
|
|
14877
|
-
await this.deps.storagePort.applyDelta({
|
|
14878
|
-
transferId: mintId,
|
|
14879
|
-
spent: [],
|
|
14880
|
-
added: [{ tokenId: token.blob.tokenId, key }]
|
|
14881
|
-
});
|
|
14882
|
-
await this.historyStore.recordMint({ tokenId: token.blob.tokenId, coinId, amount });
|
|
14883
|
-
await this.machineStores.mintJournal.removeByKey(mintId);
|
|
14884
|
-
this.heldStates.set(token.blob.tokenId, (await engine.deliveryKeys(bytes)).stateHash);
|
|
14885
|
-
this.trackTail(this.view.delta());
|
|
14886
|
-
}
|
|
14887
15282
|
/** @returns how many journal entries were RESOLVED (cleared) — heartbeat progress. */
|
|
14888
15283
|
async replayMints() {
|
|
14889
15284
|
let resolved = 0;
|
|
@@ -14915,7 +15310,7 @@ var PaymentsFacade = class {
|
|
|
14915
15310
|
if (token.blob.tokenId !== entry.tokenId) {
|
|
14916
15311
|
await this.machineStores.mintJournal.upsert({ ...entry, tokenId: token.blob.tokenId });
|
|
14917
15312
|
}
|
|
14918
|
-
await this.
|
|
15313
|
+
await finalizeMint(this.mintDeps(), entry.mintId, token, entry.coinId, entry.amount);
|
|
14919
15314
|
return true;
|
|
14920
15315
|
}
|
|
14921
15316
|
async tokenInServerInventory(tokenId) {
|
|
@@ -16690,7 +17085,8 @@ var Sphere = class _Sphere {
|
|
|
16690
17085
|
}
|
|
16691
17086
|
const progress = options.onProgress;
|
|
16692
17087
|
logger.debug("Sphere", "Starting import...");
|
|
16693
|
-
const
|
|
17088
|
+
const liveHere = _Sphere.liveOn(options.storage).some((s) => s._storage === options.storage);
|
|
17089
|
+
const needsClear = liveHere || await _Sphere.exists(options.storage);
|
|
16694
17090
|
if (needsClear) {
|
|
16695
17091
|
progress?.({ step: "clearing", message: "Clearing previous wallet data..." });
|
|
16696
17092
|
logger.debug("Sphere", "Clearing existing wallet data...");
|