@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/core/index.js
CHANGED
|
@@ -8127,6 +8127,8 @@ var FETCH_TIMEOUT_MS = 1e4;
|
|
|
8127
8127
|
var TokenRegistry = class _TokenRegistry {
|
|
8128
8128
|
static instance = null;
|
|
8129
8129
|
definitionsById;
|
|
8130
|
+
/** Non-fungible definitions keyed by TOKEN TYPE — a separate namespace (#147). */
|
|
8131
|
+
definitionsByType;
|
|
8130
8132
|
definitionsBySymbol;
|
|
8131
8133
|
definitionsByName;
|
|
8132
8134
|
// Remote refresh state
|
|
@@ -8145,6 +8147,7 @@ var TokenRegistry = class _TokenRegistry {
|
|
|
8145
8147
|
inFlight = null;
|
|
8146
8148
|
constructor() {
|
|
8147
8149
|
this.definitionsById = /* @__PURE__ */ new Map();
|
|
8150
|
+
this.definitionsByType = /* @__PURE__ */ new Map();
|
|
8148
8151
|
this.definitionsBySymbol = /* @__PURE__ */ new Map();
|
|
8149
8152
|
this.definitionsByName = /* @__PURE__ */ new Map();
|
|
8150
8153
|
}
|
|
@@ -8372,9 +8375,11 @@ var TokenRegistry = class _TokenRegistry {
|
|
|
8372
8375
|
this.definitionsById.clear();
|
|
8373
8376
|
this.definitionsBySymbol.clear();
|
|
8374
8377
|
this.definitionsByName.clear();
|
|
8378
|
+
this.definitionsByType.clear();
|
|
8375
8379
|
for (const def of definitions) {
|
|
8376
8380
|
const idLower = def.id.toLowerCase();
|
|
8377
8381
|
this.definitionsById.set(idLower, def);
|
|
8382
|
+
if (def.assetKind === "non-fungible") this.definitionsByType.set(idLower, def);
|
|
8378
8383
|
if (def.symbol) {
|
|
8379
8384
|
this.definitionsBySymbol.set(def.symbol.toUpperCase(), def);
|
|
8380
8385
|
}
|
|
@@ -8588,6 +8593,20 @@ var TokenRegistry = class _TokenRegistry {
|
|
|
8588
8593
|
getAllDefinitions() {
|
|
8589
8594
|
return Array.from(this.definitionsById.values());
|
|
8590
8595
|
}
|
|
8596
|
+
/**
|
|
8597
|
+
* Definition for a coinless token's genesis TOKEN TYPE (#777). Never falls back
|
|
8598
|
+
* to `getDefinition`: a type and a coin id live in different namespaces, so a
|
|
8599
|
+
* type that collided with a coin id would otherwise render as that coin.
|
|
8600
|
+
*/
|
|
8601
|
+
getTypeMeta(tokenType) {
|
|
8602
|
+
const def = this.getTypeDefinition(tokenType);
|
|
8603
|
+
if (!def) return null;
|
|
8604
|
+
return { name: def.name, iconUrl: def.icons?.[0]?.url ?? null };
|
|
8605
|
+
}
|
|
8606
|
+
getTypeDefinition(tokenType) {
|
|
8607
|
+
if (!tokenType) return void 0;
|
|
8608
|
+
return this.definitionsByType.get(tokenType.toLowerCase());
|
|
8609
|
+
}
|
|
8591
8610
|
/**
|
|
8592
8611
|
* Get all fungible token definitions
|
|
8593
8612
|
* @returns Array of fungible token definitions
|
|
@@ -9423,6 +9442,8 @@ import { DataHasherFactory } from "@unicitylabs/state-transition-sdk/lib/crypto/
|
|
|
9423
9442
|
import { CborSerializer } from "@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborSerializer.js";
|
|
9424
9443
|
import { CborDeserializer } from "@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborDeserializer.js";
|
|
9425
9444
|
import { CborError } from "@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborError.js";
|
|
9445
|
+
import { CborReader } from "@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborReader.js";
|
|
9446
|
+
import { MajorType } from "@unicitylabs/state-transition-sdk/lib/serialization/cbor/MajorType.js";
|
|
9426
9447
|
import { Asset } from "@unicitylabs/state-transition-sdk/lib/payment/asset/Asset.js";
|
|
9427
9448
|
import { AssetId } from "@unicitylabs/state-transition-sdk/lib/payment/asset/AssetId.js";
|
|
9428
9449
|
import { PaymentAssetCollection } from "@unicitylabs/state-transition-sdk/lib/payment/asset/PaymentAssetCollection.js";
|
|
@@ -9835,6 +9856,126 @@ async function burntTokenFromCheckpoint(deps, storedBytes, reDerivedBurnTx, sour
|
|
|
9835
9856
|
return burntToken;
|
|
9836
9857
|
}
|
|
9837
9858
|
|
|
9859
|
+
// token-engine/value-envelope.ts
|
|
9860
|
+
function isCoinlessEnvelope(envelope) {
|
|
9861
|
+
return envelope.startsWith("none_");
|
|
9862
|
+
}
|
|
9863
|
+
var SELF_DESCRIBED_CBOR_TAG = 55799n;
|
|
9864
|
+
var CBOR_MAJOR_TYPE_MASK = 224;
|
|
9865
|
+
var describe = (error) => error instanceof Error ? error.message : String(error);
|
|
9866
|
+
var invalid = (message) => new SphereError(message, "VALIDATION_ERROR");
|
|
9867
|
+
function majorTypeOf(bytes) {
|
|
9868
|
+
const first = bytes.at(0);
|
|
9869
|
+
return first === void 0 ? null : first & CBOR_MAJOR_TYPE_MASK;
|
|
9870
|
+
}
|
|
9871
|
+
function headTagNumber(bytes) {
|
|
9872
|
+
try {
|
|
9873
|
+
return new CborReader(bytes).readLength(MajorType.TAG);
|
|
9874
|
+
} catch {
|
|
9875
|
+
return null;
|
|
9876
|
+
}
|
|
9877
|
+
}
|
|
9878
|
+
function isAssetShaped(item) {
|
|
9879
|
+
try {
|
|
9880
|
+
return CborDeserializer.decodeArray(item, 2).every(
|
|
9881
|
+
(field) => majorTypeOf(field) === MajorType.BYTE_STRING
|
|
9882
|
+
);
|
|
9883
|
+
} catch {
|
|
9884
|
+
return false;
|
|
9885
|
+
}
|
|
9886
|
+
}
|
|
9887
|
+
function arrayItemsOrNull(bytes) {
|
|
9888
|
+
try {
|
|
9889
|
+
return CborDeserializer.decodeArray(bytes);
|
|
9890
|
+
} catch {
|
|
9891
|
+
return null;
|
|
9892
|
+
}
|
|
9893
|
+
}
|
|
9894
|
+
function decodeSphere(genesisData) {
|
|
9895
|
+
try {
|
|
9896
|
+
return SpherePaymentData.fromCBOR(genesisData).toValue();
|
|
9897
|
+
} catch (error) {
|
|
9898
|
+
throw invalid(`Failed to decode token payment data: ${describe(error)}`);
|
|
9899
|
+
}
|
|
9900
|
+
}
|
|
9901
|
+
function taggedItemBody(genesisData) {
|
|
9902
|
+
try {
|
|
9903
|
+
return CborDeserializer.decodeTag(genesisData).data;
|
|
9904
|
+
} catch (error) {
|
|
9905
|
+
throw invalid(
|
|
9906
|
+
`Token value payload is a CBOR tag whose item is malformed or carries trailing bytes: ${describe(error)}`
|
|
9907
|
+
);
|
|
9908
|
+
}
|
|
9909
|
+
}
|
|
9910
|
+
function selfDescribedBodyCarriesValue(body) {
|
|
9911
|
+
const major = majorTypeOf(body);
|
|
9912
|
+
if (major === MajorType.TAG) {
|
|
9913
|
+
const inner = headTagNumber(body);
|
|
9914
|
+
return inner === SpherePaymentData.CBOR_TAG || inner === SELF_DESCRIBED_CBOR_TAG;
|
|
9915
|
+
}
|
|
9916
|
+
if (major === MajorType.ARRAY) {
|
|
9917
|
+
const items = arrayItemsOrNull(body);
|
|
9918
|
+
return items !== null && items.some(isAssetShaped);
|
|
9919
|
+
}
|
|
9920
|
+
return false;
|
|
9921
|
+
}
|
|
9922
|
+
function classifyTagged(genesisData) {
|
|
9923
|
+
const tag = headTagNumber(genesisData);
|
|
9924
|
+
if (tag === null) {
|
|
9925
|
+
throw invalid(
|
|
9926
|
+
"Token value payload has an unreadable or non-canonically encoded CBOR tag head \u2014 it could be a malformed SpherePaymentData envelope"
|
|
9927
|
+
);
|
|
9928
|
+
}
|
|
9929
|
+
if (tag === SpherePaymentData.CBOR_TAG) {
|
|
9930
|
+
return { envelope: "sphere", value: decodeSphere(genesisData) };
|
|
9931
|
+
}
|
|
9932
|
+
const body = taggedItemBody(genesisData);
|
|
9933
|
+
if (tag === SELF_DESCRIBED_CBOR_TAG && selfDescribedBodyCarriesValue(body)) {
|
|
9934
|
+
throw invalid(
|
|
9935
|
+
"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"
|
|
9936
|
+
);
|
|
9937
|
+
}
|
|
9938
|
+
return { envelope: "none_tag", value: null };
|
|
9939
|
+
}
|
|
9940
|
+
function classifyArray(genesisData) {
|
|
9941
|
+
const items = arrayItemsOrNull(genesisData);
|
|
9942
|
+
if (items === null) {
|
|
9943
|
+
throw invalid("Token value payload is not PaymentAssetCollection: malformed CBOR array");
|
|
9944
|
+
}
|
|
9945
|
+
return items.some(isAssetShaped) ? { envelope: "bare_collection", value: null } : { envelope: "none_other", value: null };
|
|
9946
|
+
}
|
|
9947
|
+
function classifyValueEnvelope(genesisData) {
|
|
9948
|
+
if (genesisData === null) return { envelope: "none_absent", value: null };
|
|
9949
|
+
const major = majorTypeOf(genesisData);
|
|
9950
|
+
if (major === null) return { envelope: "none_absent", value: null };
|
|
9951
|
+
if (major === MajorType.TAG) return classifyTagged(genesisData);
|
|
9952
|
+
if (major === MajorType.ARRAY) return classifyArray(genesisData);
|
|
9953
|
+
return { envelope: "none_other", value: null };
|
|
9954
|
+
}
|
|
9955
|
+
function wrapToken(sdkToken) {
|
|
9956
|
+
const { envelope, value } = classifyValueEnvelope(sdkToken.genesis.data);
|
|
9957
|
+
const blob = {
|
|
9958
|
+
tokenId: HexConverter.encode(sdkToken.id.bytes),
|
|
9959
|
+
token: sdkToken.toCBOR()
|
|
9960
|
+
};
|
|
9961
|
+
return {
|
|
9962
|
+
sdkToken,
|
|
9963
|
+
blob,
|
|
9964
|
+
value,
|
|
9965
|
+
valueEnvelope: envelope,
|
|
9966
|
+
tokenType: HexConverter.encode(sdkToken.type.bytes)
|
|
9967
|
+
};
|
|
9968
|
+
}
|
|
9969
|
+
function assertMintableData(data) {
|
|
9970
|
+
try {
|
|
9971
|
+
classifyValueEnvelope(data);
|
|
9972
|
+
} catch (error) {
|
|
9973
|
+
throw invalid(
|
|
9974
|
+
`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.`
|
|
9975
|
+
);
|
|
9976
|
+
}
|
|
9977
|
+
}
|
|
9978
|
+
|
|
9838
9979
|
// token-engine/SphereTokenEngine.ts
|
|
9839
9980
|
var DEFAULT_PROOF_POLL_INTERVAL_MS = 300;
|
|
9840
9981
|
var MAX_MINT_CONCURRENCY = 8;
|
|
@@ -9891,7 +10032,7 @@ var SphereTokenEngine = class {
|
|
|
9891
10032
|
return sdkToken.latestTransaction.data;
|
|
9892
10033
|
}
|
|
9893
10034
|
const data = sdkToken.genesis.data;
|
|
9894
|
-
if (data &&
|
|
10035
|
+
if (data && classifyValueEnvelope(data).envelope === "sphere") {
|
|
9895
10036
|
return SpherePaymentData.fromCBOR(data).memo;
|
|
9896
10037
|
}
|
|
9897
10038
|
return null;
|
|
@@ -9921,9 +10062,10 @@ var SphereTokenEngine = class {
|
|
|
9921
10062
|
);
|
|
9922
10063
|
const certified = await mintTx.toCertifiedTransaction(this.deps.trustBase, this.deps.predicateVerifier, this.deps.unicityCertificateVerifier, proof);
|
|
9923
10064
|
const token = await Token.mint(certified, this.deps.verificationContext);
|
|
9924
|
-
return
|
|
10065
|
+
return wrapToken(token);
|
|
9925
10066
|
}
|
|
9926
10067
|
async mintDataToken(params, options) {
|
|
10068
|
+
assertMintableData(params.data);
|
|
9927
10069
|
const recipient = SignaturePredicate.create(params.recipientPubkey);
|
|
9928
10070
|
const tokenType = params.tokenType ? new TokenType(params.tokenType) : TokenType.generate();
|
|
9929
10071
|
const salt = params.salt ? TokenSalt.fromBytes(params.salt) : TokenSalt.generate();
|
|
@@ -9938,7 +10080,7 @@ var SphereTokenEngine = class {
|
|
|
9938
10080
|
);
|
|
9939
10081
|
const certified = await mintTx.toCertifiedTransaction(this.deps.trustBase, this.deps.predicateVerifier, this.deps.unicityCertificateVerifier, proof);
|
|
9940
10082
|
const token = await Token.mint(certified, this.deps.verificationContext);
|
|
9941
|
-
return
|
|
10083
|
+
return wrapToken(token);
|
|
9942
10084
|
}
|
|
9943
10085
|
async transfer(params, options) {
|
|
9944
10086
|
this.assertOwned(params.token);
|
|
@@ -9961,13 +10103,19 @@ var SphereTokenEngine = class {
|
|
|
9961
10103
|
);
|
|
9962
10104
|
const certified = await transferTx.toCertifiedTransaction(this.deps.trustBase, this.deps.predicateVerifier, this.deps.unicityCertificateVerifier, proof);
|
|
9963
10105
|
const transferred = await params.token.sdkToken.transfer(certified, this.deps.verificationContext);
|
|
9964
|
-
return
|
|
10106
|
+
return wrapToken(transferred);
|
|
9965
10107
|
}
|
|
9966
10108
|
async split(params, options) {
|
|
9967
10109
|
this.assertOwned(params.token);
|
|
9968
10110
|
if (params.outputs.length === 0) {
|
|
9969
10111
|
throw new SphereError("Split requires at least one output", "VALIDATION_ERROR");
|
|
9970
10112
|
}
|
|
10113
|
+
if (params.token.value === null) {
|
|
10114
|
+
throw new SphereError(
|
|
10115
|
+
`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.`,
|
|
10116
|
+
"VALIDATION_ERROR"
|
|
10117
|
+
);
|
|
10118
|
+
}
|
|
9971
10119
|
const transferId = this.resolveTransferId(options);
|
|
9972
10120
|
const requests = params.outputs.map(
|
|
9973
10121
|
(o, i) => SplitTokenRequest.create(
|
|
@@ -10089,7 +10237,7 @@ var SphereTokenEngine = class {
|
|
|
10089
10237
|
const proof = await this.submitSplitMintLeg(certData, mintTx, options);
|
|
10090
10238
|
const certified = await mintTx.toCertifiedTransaction(this.deps.trustBase, this.deps.predicateVerifier, this.deps.unicityCertificateVerifier, proof);
|
|
10091
10239
|
const token = await Token.mint(certified, this.deps.verificationContext);
|
|
10092
|
-
return
|
|
10240
|
+
return wrapToken(token);
|
|
10093
10241
|
}
|
|
10094
10242
|
/**
|
|
10095
10243
|
* Submit one split-mint leg. A `TRANSACTION_HASH_MISMATCH` on a mint leg is NOT a foreign spend
|
|
@@ -10147,7 +10295,7 @@ var SphereTokenEngine = class {
|
|
|
10147
10295
|
"VALIDATION_ERROR"
|
|
10148
10296
|
);
|
|
10149
10297
|
}
|
|
10150
|
-
return
|
|
10298
|
+
return wrapToken(sdkToken);
|
|
10151
10299
|
}
|
|
10152
10300
|
// ── internals ────────────────────────────────────────────────────────────────
|
|
10153
10301
|
/**
|
|
@@ -10259,34 +10407,6 @@ var SphereTokenEngine = class {
|
|
|
10259
10407
|
throw new SphereError("Cannot transfer a token not owned by this engine identity", "VALIDATION_ERROR");
|
|
10260
10408
|
}
|
|
10261
10409
|
}
|
|
10262
|
-
/** Wrap an SDK token into a SphereToken: cache its blob (incl. stable tokenId) + decoded value. */
|
|
10263
|
-
wrapToken(sdkToken) {
|
|
10264
|
-
const data = sdkToken.genesis.data;
|
|
10265
|
-
let value = null;
|
|
10266
|
-
if (data && this.isSpherePaymentData(data)) {
|
|
10267
|
-
try {
|
|
10268
|
-
value = SpherePaymentData.fromCBOR(data).toValue();
|
|
10269
|
-
} catch (err) {
|
|
10270
|
-
throw new SphereError(
|
|
10271
|
-
`Failed to decode token payment data: ${err instanceof Error ? err.message : String(err)}`,
|
|
10272
|
-
"VALIDATION_ERROR"
|
|
10273
|
-
);
|
|
10274
|
-
}
|
|
10275
|
-
}
|
|
10276
|
-
const blob = {
|
|
10277
|
-
tokenId: HexConverter.encode(sdkToken.id.bytes),
|
|
10278
|
-
token: sdkToken.toCBOR()
|
|
10279
|
-
};
|
|
10280
|
-
return { sdkToken, blob, value };
|
|
10281
|
-
}
|
|
10282
|
-
/** True if the bytes are a SpherePaymentData envelope (value token) vs a raw data token. */
|
|
10283
|
-
isSpherePaymentData(data) {
|
|
10284
|
-
try {
|
|
10285
|
-
return CborDeserializer.decodeTag(data).tag === SpherePaymentData.CBOR_TAG;
|
|
10286
|
-
} catch {
|
|
10287
|
-
return false;
|
|
10288
|
-
}
|
|
10289
|
-
}
|
|
10290
10410
|
/**
|
|
10291
10411
|
* Terminate the verification worker pool, if this engine was configured with
|
|
10292
10412
|
* one. Idempotent — the pool's dispose only touches workers it spawned.
|
|
@@ -11682,6 +11802,14 @@ function createMachineStores(kv) {
|
|
|
11682
11802
|
};
|
|
11683
11803
|
}
|
|
11684
11804
|
|
|
11805
|
+
// modules/payments-v2/machine/payload-view.ts
|
|
11806
|
+
function splitOf(p) {
|
|
11807
|
+
return p.kind === "coin" ? p.split : void 0;
|
|
11808
|
+
}
|
|
11809
|
+
function coinIdOf(p) {
|
|
11810
|
+
return p.kind === "coin" ? p.coinId : void 0;
|
|
11811
|
+
}
|
|
11812
|
+
|
|
11685
11813
|
// modules/payments-v2/machine/TransferMachine.ts
|
|
11686
11814
|
var CERTIFY_WIDTH = 20;
|
|
11687
11815
|
var KEEP_OPEN_CODES = /* @__PURE__ */ new Set([
|
|
@@ -11884,13 +12012,14 @@ var TransferMachine = class {
|
|
|
11884
12012
|
const finished = await engine.transfer({ token: source, recipientPubkey }, opts);
|
|
11885
12013
|
return { recipientBlob: finished.blob.token };
|
|
11886
12014
|
}
|
|
11887
|
-
const split = ctx.payload
|
|
12015
|
+
const split = splitOf(ctx.payload);
|
|
12016
|
+
const splitCoinId = coinIdOf(ctx.payload);
|
|
11888
12017
|
const { outputs } = await engine.split(
|
|
11889
12018
|
{
|
|
11890
12019
|
token: source,
|
|
11891
12020
|
outputs: [
|
|
11892
|
-
{ recipientPubkey, coinId:
|
|
11893
|
-
{ recipientPubkey: this.deps.ownPubkey, coinId:
|
|
12021
|
+
{ recipientPubkey, coinId: splitCoinId, amount: BigInt(split.splitAmount) },
|
|
12022
|
+
{ recipientPubkey: this.deps.ownPubkey, coinId: splitCoinId, amount: BigInt(split.remainderAmount) }
|
|
11894
12023
|
]
|
|
11895
12024
|
},
|
|
11896
12025
|
opts
|
|
@@ -12012,7 +12141,9 @@ var TransferMachine = class {
|
|
|
12012
12141
|
const entry = {
|
|
12013
12142
|
transferId: r.transferId,
|
|
12014
12143
|
remainingAmount: undelivered.toString(),
|
|
12015
|
-
|
|
12144
|
+
// '' only on a token-addressed intent, which is one leg: it either lands or
|
|
12145
|
+
// conflicts, so a shortfall row is unreachable there (settlePartial needs >1).
|
|
12146
|
+
coinId: coinIdOf(r.payload) ?? "",
|
|
12016
12147
|
recipient: r.payload.recipient,
|
|
12017
12148
|
committedTokenIds: committed.map((o) => o.op.sourceTokenId),
|
|
12018
12149
|
createdAt: this.deps.now()
|
|
@@ -12038,7 +12169,8 @@ var TransferMachine = class {
|
|
|
12038
12169
|
}
|
|
12039
12170
|
async finishBookkeeping(engine, transferId, payload, phase, committed) {
|
|
12040
12171
|
try {
|
|
12041
|
-
const
|
|
12172
|
+
const coinId = coinIdOf(payload);
|
|
12173
|
+
const committedAmount = coinId === void 0 ? "0" : await this.settledAmount(engine, coinId, committed);
|
|
12042
12174
|
await this.deps.recordHistory?.({ transferId, payload, phase, committedAmount });
|
|
12043
12175
|
} catch {
|
|
12044
12176
|
}
|
|
@@ -12081,8 +12213,11 @@ var TransferMachine = class {
|
|
|
12081
12213
|
}
|
|
12082
12214
|
};
|
|
12083
12215
|
function conflictAmount(engine, payload, op, source) {
|
|
12084
|
-
|
|
12085
|
-
|
|
12216
|
+
const split = splitOf(payload);
|
|
12217
|
+
if (op.kind === "split") return BigInt(split.splitAmount);
|
|
12218
|
+
const coinId = coinIdOf(payload);
|
|
12219
|
+
if (coinId === void 0) return 0n;
|
|
12220
|
+
return source !== void 0 ? engine.balanceOf(source, coinId) : 0n;
|
|
12086
12221
|
}
|
|
12087
12222
|
|
|
12088
12223
|
// modules/payments-v2/machine/resume.ts
|
|
@@ -12189,6 +12324,14 @@ async function runOne(ctx, job, report) {
|
|
|
12189
12324
|
report.failed.push(job.transferId);
|
|
12190
12325
|
return;
|
|
12191
12326
|
}
|
|
12327
|
+
if (job.payload.kind === "coinless" && !isCoinlessEnvelope(token.valueEnvelope)) {
|
|
12328
|
+
logger.warn(
|
|
12329
|
+
"PaymentsV2",
|
|
12330
|
+
`resume: token intent ${job.transferId} names a source carrying coin value \u2014 refusing`
|
|
12331
|
+
);
|
|
12332
|
+
report.failed.push(job.transferId);
|
|
12333
|
+
return;
|
|
12334
|
+
}
|
|
12192
12335
|
mine.set(op.sourceTokenId, token);
|
|
12193
12336
|
}
|
|
12194
12337
|
try {
|
|
@@ -12219,6 +12362,28 @@ async function classifyResumeFailure(deps, stores, transferId, err, report) {
|
|
|
12219
12362
|
}
|
|
12220
12363
|
report.failed.push(transferId);
|
|
12221
12364
|
}
|
|
12365
|
+
function validateCoinPayload(c) {
|
|
12366
|
+
if (typeof c.coinId !== "string" || typeof c.amount !== "string") {
|
|
12367
|
+
throw new SphereError("intent payload is missing coinId/amount", "VALIDATION_ERROR");
|
|
12368
|
+
}
|
|
12369
|
+
const s = c.split;
|
|
12370
|
+
if (s !== void 0 && (typeof s.tokenId !== "string" || typeof s.splitAmount !== "string" || typeof s.remainderAmount !== "string")) {
|
|
12371
|
+
throw new SphereError("intent payload split spec is malformed", "VALIDATION_ERROR");
|
|
12372
|
+
}
|
|
12373
|
+
return { ...c, kind: "coin" };
|
|
12374
|
+
}
|
|
12375
|
+
function validateCoinlessPayload(p) {
|
|
12376
|
+
if (p.direct?.length !== 1 || typeof p.direct[0] !== "string" || p.direct[0] === "") {
|
|
12377
|
+
throw new SphereError(
|
|
12378
|
+
"token intent payload must name exactly one source token",
|
|
12379
|
+
"VALIDATION_ERROR"
|
|
12380
|
+
);
|
|
12381
|
+
}
|
|
12382
|
+
if (p.split !== void 0) {
|
|
12383
|
+
throw new SphereError("token intent payload cannot carry a split", "VALIDATION_ERROR");
|
|
12384
|
+
}
|
|
12385
|
+
return { ...p, kind: "coinless", direct: [p.direct[0]] };
|
|
12386
|
+
}
|
|
12222
12387
|
function validatePayload(raw) {
|
|
12223
12388
|
const p = raw !== null && typeof raw === "object" ? raw : null;
|
|
12224
12389
|
if (p === null || p.v !== 2 || !Array.isArray(p.direct) || p.direct.some((t) => typeof t !== "string")) {
|
|
@@ -12227,14 +12392,17 @@ function validatePayload(raw) {
|
|
|
12227
12392
|
"VALIDATION_ERROR"
|
|
12228
12393
|
);
|
|
12229
12394
|
}
|
|
12230
|
-
if (typeof p.recipient !== "string"
|
|
12231
|
-
throw new SphereError("intent payload is missing recipient
|
|
12395
|
+
if (typeof p.recipient !== "string") {
|
|
12396
|
+
throw new SphereError("intent payload is missing recipient", "VALIDATION_ERROR");
|
|
12232
12397
|
}
|
|
12233
|
-
const
|
|
12234
|
-
if (
|
|
12235
|
-
throw new SphereError(
|
|
12398
|
+
const kind = p.kind ?? "coin";
|
|
12399
|
+
if (kind !== "coin" && kind !== "coinless") {
|
|
12400
|
+
throw new SphereError(
|
|
12401
|
+
`unsupported intent kind '${String(kind)}' \u2014 not resumable by this client`,
|
|
12402
|
+
"VALIDATION_ERROR"
|
|
12403
|
+
);
|
|
12236
12404
|
}
|
|
12237
|
-
return p;
|
|
12405
|
+
return kind === "coinless" ? validateCoinlessPayload(p) : validateCoinPayload(p);
|
|
12238
12406
|
}
|
|
12239
12407
|
|
|
12240
12408
|
// modules/payments-v2/convergence.ts
|
|
@@ -12440,13 +12608,23 @@ async function openRow(entry, journal, decryptPayload) {
|
|
|
12440
12608
|
transferId: entry.transferId,
|
|
12441
12609
|
kind: "open",
|
|
12442
12610
|
recipient: typeof payload?.recipient === "string" ? payload.recipient : "",
|
|
12443
|
-
|
|
12444
|
-
amount: typeof payload?.amount === "string" ? payload.amount : "",
|
|
12611
|
+
...subject(payload),
|
|
12445
12612
|
legs: { certified, total: Math.max(total, certified) },
|
|
12446
12613
|
deliveryPending: journal.length > 0,
|
|
12447
12614
|
createdAt: entry.createdAt
|
|
12448
12615
|
};
|
|
12449
12616
|
}
|
|
12617
|
+
function subject(payload) {
|
|
12618
|
+
if (payload?.kind === "coinless") {
|
|
12619
|
+
const tokenId = payload.direct?.[0];
|
|
12620
|
+
return { coinId: "", amount: "", ...typeof tokenId === "string" ? { tokenId } : {} };
|
|
12621
|
+
}
|
|
12622
|
+
const coin = payload;
|
|
12623
|
+
return {
|
|
12624
|
+
coinId: typeof coin?.coinId === "string" ? coin.coinId : "",
|
|
12625
|
+
amount: typeof coin?.amount === "string" ? coin.amount : ""
|
|
12626
|
+
};
|
|
12627
|
+
}
|
|
12450
12628
|
function journalOnlyRow(transferId, entries) {
|
|
12451
12629
|
const certified = distinctOpCount(entries);
|
|
12452
12630
|
return {
|
|
@@ -12476,6 +12654,160 @@ function distinctOpCount(entries) {
|
|
|
12476
12654
|
return new Set(entries.map((entry) => entry.opIndex)).size;
|
|
12477
12655
|
}
|
|
12478
12656
|
|
|
12657
|
+
// modules/payments-v2/inventory/token-data.ts
|
|
12658
|
+
async function readTokenData(deps, tokenId) {
|
|
12659
|
+
if (deps.view.stateHashOf(tokenId) === void 0) {
|
|
12660
|
+
throw new SphereError(`Token ${tokenId} is not in inventory`, "VALIDATION_ERROR");
|
|
12661
|
+
}
|
|
12662
|
+
const bytes = (await deps.storagePort.getBlobs([tokenId])).get(tokenId);
|
|
12663
|
+
if (bytes === void 0) {
|
|
12664
|
+
throw new SphereError(`Token ${tokenId} has no blob in storage`, "STORAGE_ERROR");
|
|
12665
|
+
}
|
|
12666
|
+
return deps.engine.readTokenData(await deps.engine.decodeToken({ tokenId, token: bytes }));
|
|
12667
|
+
}
|
|
12668
|
+
|
|
12669
|
+
// modules/payments-v2/machine/payload.ts
|
|
12670
|
+
function buildPayload(recipientPubkey, request, spend, spentStates) {
|
|
12671
|
+
return {
|
|
12672
|
+
v: 2,
|
|
12673
|
+
kind: "coin",
|
|
12674
|
+
recipient: recipientPubkey,
|
|
12675
|
+
coinId: request.coinId,
|
|
12676
|
+
amount: request.amount,
|
|
12677
|
+
...request.memo !== void 0 ? { memo: request.memo } : {},
|
|
12678
|
+
direct: [...spend.plan.direct],
|
|
12679
|
+
...spend.plan.split !== void 0 ? {
|
|
12680
|
+
split: {
|
|
12681
|
+
tokenId: spend.plan.split.tokenId,
|
|
12682
|
+
splitAmount: spend.plan.split.splitAmount.toString(),
|
|
12683
|
+
remainderAmount: spend.plan.split.remainderAmount.toString()
|
|
12684
|
+
}
|
|
12685
|
+
} : {},
|
|
12686
|
+
spentStates
|
|
12687
|
+
};
|
|
12688
|
+
}
|
|
12689
|
+
function messageOf(err) {
|
|
12690
|
+
return err instanceof Error ? err.message : String(err);
|
|
12691
|
+
}
|
|
12692
|
+
function buildCoinlessPayload(recipientPubkey, request, spentStates) {
|
|
12693
|
+
return {
|
|
12694
|
+
v: 2,
|
|
12695
|
+
kind: "coinless",
|
|
12696
|
+
recipient: recipientPubkey,
|
|
12697
|
+
...request.memo !== void 0 ? { memo: request.memo } : {},
|
|
12698
|
+
direct: [request.tokenId],
|
|
12699
|
+
spentStates
|
|
12700
|
+
};
|
|
12701
|
+
}
|
|
12702
|
+
|
|
12703
|
+
// modules/payments-v2/mint-params.ts
|
|
12704
|
+
function mintParams(recipientPubkey, coinId, amount) {
|
|
12705
|
+
return { recipientPubkey, value: { assets: [{ coinId, amount }] } };
|
|
12706
|
+
}
|
|
12707
|
+
|
|
12708
|
+
// modules/payments-v2/mint.ts
|
|
12709
|
+
function runMintUnderJournal(deps, input) {
|
|
12710
|
+
return mintUnderJournal(deps, input);
|
|
12711
|
+
}
|
|
12712
|
+
async function mintUnderJournal(deps, input) {
|
|
12713
|
+
const { mintId, coinId, amount } = input;
|
|
12714
|
+
const engine = deps.engine;
|
|
12715
|
+
const entry = {
|
|
12716
|
+
mintId,
|
|
12717
|
+
coinId,
|
|
12718
|
+
amount: amount.toString(),
|
|
12719
|
+
tokenId: "",
|
|
12720
|
+
createdAt: deps.now()
|
|
12721
|
+
};
|
|
12722
|
+
await deps.mintJournal.upsert(entry);
|
|
12723
|
+
let token;
|
|
12724
|
+
try {
|
|
12725
|
+
token = await engine.mint(mintParams(deps.ownPubkeyBytes, coinId, amount), { transferId: mintId });
|
|
12726
|
+
} catch (err) {
|
|
12727
|
+
deps.armHeartbeat();
|
|
12728
|
+
return { success: false, error: messageOf(err) };
|
|
12729
|
+
}
|
|
12730
|
+
if (token.blob.tokenId !== entry.tokenId) {
|
|
12731
|
+
await deps.mintJournal.upsert({ ...entry, tokenId: token.blob.tokenId });
|
|
12732
|
+
}
|
|
12733
|
+
try {
|
|
12734
|
+
await finalizeMint(deps, mintId, token, coinId, amount.toString());
|
|
12735
|
+
} catch (err) {
|
|
12736
|
+
deps.armHeartbeat();
|
|
12737
|
+
return { success: false, tokenId: token.blob.tokenId, error: messageOf(err) };
|
|
12738
|
+
}
|
|
12739
|
+
return { success: true, tokenId: token.blob.tokenId };
|
|
12740
|
+
}
|
|
12741
|
+
async function finalizeMint(deps, mintId, token, coinId, amount) {
|
|
12742
|
+
const engine = deps.engine;
|
|
12743
|
+
const bytes = token.blob.token;
|
|
12744
|
+
const digest = bytesToHex3(sha256(bytes));
|
|
12745
|
+
const keys = await deps.storagePort.uploadBlobs([{ sha256: digest, bytes }]);
|
|
12746
|
+
const key = keys.get(digest);
|
|
12747
|
+
if (key === void 0) {
|
|
12748
|
+
throw new SphereError(`no upload key returned for mint blob ${digest}`, "STORAGE_ERROR");
|
|
12749
|
+
}
|
|
12750
|
+
await deps.storagePort.applyDelta({
|
|
12751
|
+
transferId: mintId,
|
|
12752
|
+
spent: [],
|
|
12753
|
+
added: [{ tokenId: token.blob.tokenId, key }]
|
|
12754
|
+
});
|
|
12755
|
+
await deps.recordMint({
|
|
12756
|
+
tokenId: token.blob.tokenId,
|
|
12757
|
+
assets: [{ coinId, amount }]
|
|
12758
|
+
});
|
|
12759
|
+
await deps.mintJournal.removeByKey(mintId);
|
|
12760
|
+
deps.noteHeldState(token.blob.tokenId, (await engine.deliveryKeys(bytes)).stateHash);
|
|
12761
|
+
deps.refreshView();
|
|
12762
|
+
}
|
|
12763
|
+
|
|
12764
|
+
// modules/payments-v2/send-coinless.ts
|
|
12765
|
+
async function materializeCoinlessSpend(deps, input) {
|
|
12766
|
+
const { engine } = deps;
|
|
12767
|
+
const tokenId = input.request.tokenId;
|
|
12768
|
+
const bytes = (await deps.storagePort.getBlobs([tokenId])).get(tokenId);
|
|
12769
|
+
if (bytes === void 0) {
|
|
12770
|
+
throw new SphereError(`Selected source ${tokenId} has no blob in storage`, "STORAGE_ERROR");
|
|
12771
|
+
}
|
|
12772
|
+
const token = await engine.decodeToken({ tokenId, token: bytes });
|
|
12773
|
+
if (!isCoinlessEnvelope(token.valueEnvelope)) {
|
|
12774
|
+
throw new SphereError(
|
|
12775
|
+
`Token ${tokenId} carries coin value and cannot be sent with sendCoinless \u2014 use send()`,
|
|
12776
|
+
"VALIDATION_ERROR"
|
|
12777
|
+
);
|
|
12778
|
+
}
|
|
12779
|
+
const keys = await engine.deliveryKeys(bytes);
|
|
12780
|
+
const payload = buildCoinlessPayload(input.recipientPubkey, input.request, {
|
|
12781
|
+
[tokenId]: { local: keys.stateHash, protocol: keys.stateHash }
|
|
12782
|
+
});
|
|
12783
|
+
const plan = {
|
|
12784
|
+
transferId: input.transferId,
|
|
12785
|
+
recipientPubkey: input.recipientPubkey,
|
|
12786
|
+
payload,
|
|
12787
|
+
ops: buildOps(payload),
|
|
12788
|
+
sources: /* @__PURE__ */ new Map([[tokenId, token]]),
|
|
12789
|
+
...input.request.memo !== void 0 ? { memo: input.request.memo } : {}
|
|
12790
|
+
};
|
|
12791
|
+
return { transferId: input.transferId, coinId: "", plan, sourceIds: input.sourceIds, sourceTokens: [] };
|
|
12792
|
+
}
|
|
12793
|
+
|
|
12794
|
+
// modules/payments-v2/send-errors.ts
|
|
12795
|
+
function partialize(err, run) {
|
|
12796
|
+
if (run.delivered.length === 0) return err;
|
|
12797
|
+
return new PartialSendConflictError(
|
|
12798
|
+
"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.",
|
|
12799
|
+
run.firstPartialId ?? "",
|
|
12800
|
+
[...run.delivered],
|
|
12801
|
+
run.amount,
|
|
12802
|
+
err
|
|
12803
|
+
);
|
|
12804
|
+
}
|
|
12805
|
+
function stampTransferId(err, transferId) {
|
|
12806
|
+
if (err instanceof SphereError && isPossiblyCommittedSendOutcome(err) && err.transferId === void 0) {
|
|
12807
|
+
err.transferId = transferId;
|
|
12808
|
+
}
|
|
12809
|
+
}
|
|
12810
|
+
|
|
12479
12811
|
// modules/payments-v2/recipient.ts
|
|
12480
12812
|
var ATTENTION_RECIPIENT_NETWORK_UNVERIFIED = "recipient:network-unverified";
|
|
12481
12813
|
async function requireSameNetworkRecipient(deps, identifier) {
|
|
@@ -12524,11 +12856,6 @@ async function splitOpIndex(deps, envelope) {
|
|
|
12524
12856
|
return Array.isArray(payload.direct) ? payload.direct.length : 0;
|
|
12525
12857
|
}
|
|
12526
12858
|
|
|
12527
|
-
// modules/payments-v2/mint-params.ts
|
|
12528
|
-
function mintParams(recipientPubkey, coinId, amount) {
|
|
12529
|
-
return { recipientPubkey, value: { assets: [{ coinId, amount }] } };
|
|
12530
|
-
}
|
|
12531
|
-
|
|
12532
12859
|
// modules/payments-v2/prewarm-cache.ts
|
|
12533
12860
|
var PrewarmCache = class _PrewarmCache {
|
|
12534
12861
|
entries = /* @__PURE__ */ new Map();
|
|
@@ -12702,30 +13029,15 @@ function transferringToken(tokenId, coinId, amount, registry, now) {
|
|
|
12702
13029
|
});
|
|
12703
13030
|
}
|
|
12704
13031
|
|
|
12705
|
-
// modules/payments-v2/
|
|
12706
|
-
function
|
|
12707
|
-
return {
|
|
12708
|
-
v: 2,
|
|
12709
|
-
recipient: recipientPubkey,
|
|
12710
|
-
coinId: request.coinId,
|
|
12711
|
-
amount: request.amount,
|
|
12712
|
-
...request.memo !== void 0 ? { memo: request.memo } : {},
|
|
12713
|
-
direct: [...spend.plan.direct],
|
|
12714
|
-
...spend.plan.split !== void 0 ? {
|
|
12715
|
-
split: {
|
|
12716
|
-
tokenId: spend.plan.split.tokenId,
|
|
12717
|
-
splitAmount: spend.plan.split.splitAmount.toString(),
|
|
12718
|
-
remainderAmount: spend.plan.split.remainderAmount.toString()
|
|
12719
|
-
}
|
|
12720
|
-
} : {},
|
|
12721
|
-
spentStates
|
|
12722
|
-
};
|
|
13032
|
+
// modules/payments-v2/history/History.ts
|
|
13033
|
+
function wireAssets(assets) {
|
|
13034
|
+
return assets.map((a) => ({ coinId: a.coinId, amount: a.amount }));
|
|
12723
13035
|
}
|
|
12724
|
-
function
|
|
12725
|
-
|
|
13036
|
+
function isPermanentReject(err) {
|
|
13037
|
+
const status = err?.status;
|
|
13038
|
+
if (typeof status !== "number") return false;
|
|
13039
|
+
return status >= 400 && status < 500 && status !== 408 && status !== 429;
|
|
12726
13040
|
}
|
|
12727
|
-
|
|
12728
|
-
// modules/payments-v2/history/History.ts
|
|
12729
13041
|
var WIRE_TOKEN_ID = /^(?:[0-9a-f]{2}){1,64}$/;
|
|
12730
13042
|
var WIRE_PUBKEY = /^0[23][0-9a-f]{64}$/;
|
|
12731
13043
|
var History = class {
|
|
@@ -12750,7 +13062,7 @@ var History = class {
|
|
|
12750
13062
|
dedupKey: input.transferId,
|
|
12751
13063
|
id: (this.deps.newId ?? randomUUID)(),
|
|
12752
13064
|
type: "SENT",
|
|
12753
|
-
assets:
|
|
13065
|
+
assets: wireAssets(input.assets),
|
|
12754
13066
|
ts: this.ts(input.timestamp),
|
|
12755
13067
|
transferId: input.transferId,
|
|
12756
13068
|
...this.wireTokenId(input.tokenId),
|
|
@@ -12763,7 +13075,7 @@ var History = class {
|
|
|
12763
13075
|
dedupKey: `RECEIVED:${input.tokenId.toLowerCase()}:${input.stateHash.toLowerCase()}`,
|
|
12764
13076
|
id: (this.deps.newId ?? randomUUID)(),
|
|
12765
13077
|
type: "RECEIVED",
|
|
12766
|
-
assets:
|
|
13078
|
+
assets: wireAssets(input.assets),
|
|
12767
13079
|
ts: this.ts(input.timestamp),
|
|
12768
13080
|
...input.transferId !== void 0 ? { transferId: input.transferId } : {},
|
|
12769
13081
|
...this.wireTokenId(input.tokenId),
|
|
@@ -12776,7 +13088,7 @@ var History = class {
|
|
|
12776
13088
|
dedupKey: `MINT:${input.tokenId.toLowerCase()}`,
|
|
12777
13089
|
id: (this.deps.newId ?? randomUUID)(),
|
|
12778
13090
|
type: "MINT",
|
|
12779
|
-
assets:
|
|
13091
|
+
assets: wireAssets(input.assets),
|
|
12780
13092
|
ts: this.ts(input.timestamp),
|
|
12781
13093
|
...this.wireTokenId(input.tokenId)
|
|
12782
13094
|
}));
|
|
@@ -12787,7 +13099,10 @@ var History = class {
|
|
|
12787
13099
|
try {
|
|
12788
13100
|
await this.deps.client.postHistory([record]);
|
|
12789
13101
|
} catch (err) {
|
|
12790
|
-
this.deps.log?.(
|
|
13102
|
+
this.deps.log?.(
|
|
13103
|
+
isPermanentReject(err) ? "history POST REJECTED \u2014 the record shape is refused; no retry can fix it" : "history POST failed (dedupKey makes retry safe)",
|
|
13104
|
+
err
|
|
13105
|
+
);
|
|
12791
13106
|
return;
|
|
12792
13107
|
}
|
|
12793
13108
|
this.deps.emit("history:updated", this.toEntry(record));
|
|
@@ -12854,6 +13169,13 @@ var CURSOR_KEY = STORE_KEYS.streamCursor("inventory");
|
|
|
12854
13169
|
function stateKey(tokenId, stateHash) {
|
|
12855
13170
|
return `${tokenId}:${stateHash}`;
|
|
12856
13171
|
}
|
|
13172
|
+
function unchanged(prev, item, tokenType) {
|
|
13173
|
+
return prev.seq === item.seq && prev.status === item.status && prev.stateHash === item.stateHash && tokenType === prev.tokenType;
|
|
13174
|
+
}
|
|
13175
|
+
function isCoinless(item, prev) {
|
|
13176
|
+
if (item.status !== "active") return prev?.coinless ?? false;
|
|
13177
|
+
return (item.assets?.length ?? 0) === 0;
|
|
13178
|
+
}
|
|
12857
13179
|
var InventoryView = class {
|
|
12858
13180
|
constructor(deps) {
|
|
12859
13181
|
this.deps = deps;
|
|
@@ -12947,14 +13269,26 @@ var InventoryView = class {
|
|
|
12947
13269
|
pool(coinId) {
|
|
12948
13270
|
const out = [];
|
|
12949
13271
|
for (const [tokenId, entry] of this.mirror) {
|
|
12950
|
-
if (
|
|
12951
|
-
if (this.inFlight.has(tokenId) && !this.pinned(tokenId)) continue;
|
|
12952
|
-
if (this.suspected.has(stateKey(tokenId, entry.stateHash))) continue;
|
|
13272
|
+
if (!this.isSpendable(tokenId, entry)) continue;
|
|
12953
13273
|
const asset = entry.assets.find((a) => a.coinId === coinId);
|
|
12954
13274
|
if (asset) out.push({ tokenId, amount: BigInt(asset.amount) });
|
|
12955
13275
|
}
|
|
12956
13276
|
return out;
|
|
12957
13277
|
}
|
|
13278
|
+
/**
|
|
13279
|
+
* The eligibility gates every spend shares, coin or token — ONE definition, so a
|
|
13280
|
+
* single probe covers both verbs and neither can drift from the other.
|
|
13281
|
+
*/
|
|
13282
|
+
isSpendable(tokenId, entry) {
|
|
13283
|
+
if (entry.status !== "active") return false;
|
|
13284
|
+
if (this.inFlight.has(tokenId) && !this.pinned(tokenId)) return false;
|
|
13285
|
+
return !this.suspected.has(stateKey(tokenId, entry.stateHash));
|
|
13286
|
+
}
|
|
13287
|
+
/** #777: is this NAMED token a spendable COINLESS holding? Never a coin source. */
|
|
13288
|
+
spendableCoinless(tokenId) {
|
|
13289
|
+
const entry = this.mirror.get(tokenId);
|
|
13290
|
+
return entry !== void 0 && entry.coinless && this.isSpendable(tokenId, entry);
|
|
13291
|
+
}
|
|
12958
13292
|
/**
|
|
12959
13293
|
* Current mirrored state of an ACTIVE token, or undefined when it is unknown
|
|
12960
13294
|
* or tombstoned. Exists so a cached blob can be state-scoped (F6): a blob is
|
|
@@ -12980,6 +13314,29 @@ var InventoryView = class {
|
|
|
12980
13314
|
}
|
|
12981
13315
|
return out;
|
|
12982
13316
|
}
|
|
13317
|
+
/**
|
|
13318
|
+
* Coinless holdings (#777) — DISJOINT from tokens(): an entry is coinless
|
|
13319
|
+
* exactly when it is not, so no mirror row can appear in both reads.
|
|
13320
|
+
*/
|
|
13321
|
+
coinless(registry) {
|
|
13322
|
+
const out = [];
|
|
13323
|
+
for (const [tokenId, entry] of this.mirror) {
|
|
13324
|
+
if (entry.status !== "active" || !entry.coinless) continue;
|
|
13325
|
+
const meta = entry.tokenType !== void 0 ? registry.getTypeMeta?.(entry.tokenType) : null;
|
|
13326
|
+
out.push({
|
|
13327
|
+
tokenId,
|
|
13328
|
+
...entry.tokenType !== void 0 ? { tokenType: entry.tokenType } : {},
|
|
13329
|
+
...meta ? { name: meta.name } : {},
|
|
13330
|
+
...meta?.iconUrl != null ? { iconUrl: meta.iconUrl } : {},
|
|
13331
|
+
stateHash: entry.stateHash,
|
|
13332
|
+
transferring: this.held(tokenId),
|
|
13333
|
+
...this.suspected.has(stateKey(tokenId, entry.stateHash)) ? { suspectedSpent: true } : {},
|
|
13334
|
+
createdAt: entry.createdAt,
|
|
13335
|
+
updatedAt: entry.updatedAt
|
|
13336
|
+
});
|
|
13337
|
+
}
|
|
13338
|
+
return out;
|
|
13339
|
+
}
|
|
12983
13340
|
async assets(registry, price) {
|
|
12984
13341
|
const raw = aggregateAssets(this.activeEntries(), (tokenId) => this.held(tokenId), registry);
|
|
12985
13342
|
if (!price || raw.length === 0) return raw;
|
|
@@ -13051,14 +13408,19 @@ var InventoryView = class {
|
|
|
13051
13408
|
applyOne(item, now) {
|
|
13052
13409
|
const prev = this.mirror.get(item.tokenId);
|
|
13053
13410
|
if (prev && item.seq < prev.seq) return false;
|
|
13054
|
-
|
|
13055
|
-
|
|
13056
|
-
}
|
|
13411
|
+
const tokenType = item.tokenType ?? prev?.tokenType;
|
|
13412
|
+
if (prev && unchanged(prev, item, tokenType)) return false;
|
|
13057
13413
|
this.mirror.set(item.tokenId, {
|
|
13058
13414
|
stateHash: item.stateHash,
|
|
13059
13415
|
seq: item.seq,
|
|
13060
13416
|
status: item.status,
|
|
13061
|
-
|
|
13417
|
+
// ONLY a tombstone inherits: it omits assets for an unrelated reason and
|
|
13418
|
+
// recoverRemoved must know the amount it restores. An ACTIVE row that omits
|
|
13419
|
+
// them is stating coinlessness, and inheriting there would keep stale assets
|
|
13420
|
+
// in tokens() while `coinless` is true — the row in BOTH reads (§16).
|
|
13421
|
+
assets: item.assets ?? (item.status === "removed" ? prev?.assets ?? [] : []),
|
|
13422
|
+
coinless: isCoinless(item, prev),
|
|
13423
|
+
...tokenType !== void 0 ? { tokenType } : {},
|
|
13062
13424
|
createdAt: prev?.createdAt ?? now,
|
|
13063
13425
|
updatedAt: now
|
|
13064
13426
|
});
|
|
@@ -13294,7 +13656,12 @@ async function screen(deps, engine, entry) {
|
|
|
13294
13656
|
}
|
|
13295
13657
|
return {
|
|
13296
13658
|
kind: "accept",
|
|
13297
|
-
record: {
|
|
13659
|
+
record: {
|
|
13660
|
+
tokenId: keys.tokenId,
|
|
13661
|
+
stateHash: keys.stateHash,
|
|
13662
|
+
assets: toAssetAmounts(token),
|
|
13663
|
+
...isCoinlessEnvelope2(token.valueEnvelope) ? { tokenType: token.tokenType } : {}
|
|
13664
|
+
}
|
|
13298
13665
|
};
|
|
13299
13666
|
}
|
|
13300
13667
|
async function announce(deps, entry, record) {
|
|
@@ -13305,6 +13672,7 @@ async function announce(deps, entry, record) {
|
|
|
13305
13672
|
tokenId: record.tokenId,
|
|
13306
13673
|
stateHash: record.stateHash,
|
|
13307
13674
|
assets: record.assets,
|
|
13675
|
+
...record.tokenType !== void 0 ? { tokenType: record.tokenType } : {},
|
|
13308
13676
|
...entry.senderPubkey !== void 0 ? { senderPubkey: entry.senderPubkey } : {},
|
|
13309
13677
|
...entry.senderNametag !== void 0 ? { senderNametag: entry.senderNametag } : {},
|
|
13310
13678
|
...entry.memo !== void 0 ? { memo: entry.memo } : {},
|
|
@@ -13318,6 +13686,19 @@ async function announce(deps, entry, record) {
|
|
|
13318
13686
|
senderPubkey: entry.senderPubkey ?? "",
|
|
13319
13687
|
...entry.senderNametag !== void 0 ? { senderNametag: entry.senderNametag } : {},
|
|
13320
13688
|
tokens: record.assets.map((asset) => toUiToken(record.tokenId, asset, deps.registry, receivedAt)),
|
|
13689
|
+
// #777: named here rather than mapped from assets, which announced an EMPTY list.
|
|
13690
|
+
...record.tokenType !== void 0 ? {
|
|
13691
|
+
coinless: [
|
|
13692
|
+
{
|
|
13693
|
+
tokenId: record.tokenId,
|
|
13694
|
+
tokenType: record.tokenType,
|
|
13695
|
+
stateHash: record.stateHash,
|
|
13696
|
+
transferring: false,
|
|
13697
|
+
createdAt: receivedAt,
|
|
13698
|
+
updatedAt: receivedAt
|
|
13699
|
+
}
|
|
13700
|
+
]
|
|
13701
|
+
} : {},
|
|
13321
13702
|
...entry.memo !== void 0 ? { memo: entry.memo } : {},
|
|
13322
13703
|
receivedAt
|
|
13323
13704
|
};
|
|
@@ -13416,6 +13797,9 @@ function claimAck(entry) {
|
|
|
13416
13797
|
function rejectAck(entry, reason) {
|
|
13417
13798
|
return { deliveryId: entry.deliveryId, disposition: "rejected", reason, cursor: entry.cursor };
|
|
13418
13799
|
}
|
|
13800
|
+
function isCoinlessEnvelope2(envelope) {
|
|
13801
|
+
return envelope.startsWith("none_");
|
|
13802
|
+
}
|
|
13419
13803
|
function toAssetAmounts(token) {
|
|
13420
13804
|
return (token.value?.assets ?? []).map((asset) => ({
|
|
13421
13805
|
coinId: asset.coinId,
|
|
@@ -13983,6 +14367,37 @@ var SpendQueue = class {
|
|
|
13983
14367
|
}
|
|
13984
14368
|
return { kind: "queued", settled: this.enqueue(reservationId, request.coinId, amount) };
|
|
13985
14369
|
}
|
|
14370
|
+
/**
|
|
14371
|
+
* #777: reserve a NAMED coinless source. Never queues — there is nothing to wait
|
|
14372
|
+
* for. A coin plan waits because some OTHER combination may free up; a named
|
|
14373
|
+
* token is either free now or held by a reservation that only its own transfer
|
|
14374
|
+
* can release, so queueing would block until a timeout on a spend that cannot
|
|
14375
|
+
* become possible.
|
|
14376
|
+
*/
|
|
14377
|
+
planCoinless(reservationId, tokenId) {
|
|
14378
|
+
if (this.destroyed) {
|
|
14379
|
+
throw new SphereError("Module has been destroyed", "MODULE_DESTROYED");
|
|
14380
|
+
}
|
|
14381
|
+
const unproven = this.deps.ledger.unprovenReason();
|
|
14382
|
+
if (unproven !== null) {
|
|
14383
|
+
throw new SphereError(`Cannot spend yet: ${unproven}`, "SEND_SYNC_PENDING");
|
|
14384
|
+
}
|
|
14385
|
+
if (this.deps.spendableCoinless?.(tokenId) !== true) {
|
|
14386
|
+
throw new SphereError(
|
|
14387
|
+
`Token ${tokenId} is not a spendable coinless holding`,
|
|
14388
|
+
"VALIDATION_ERROR"
|
|
14389
|
+
);
|
|
14390
|
+
}
|
|
14391
|
+
const holder = this.deps.ledger.holderOf(tokenId);
|
|
14392
|
+
if (holder !== void 0) {
|
|
14393
|
+
throw new SphereError(
|
|
14394
|
+
`Token ${tokenId} is already reserved by transfer ${holder}`,
|
|
14395
|
+
"VALIDATION_ERROR"
|
|
14396
|
+
);
|
|
14397
|
+
}
|
|
14398
|
+
this.deps.ledger.reserve(reservationId, [{ tokenId }]);
|
|
14399
|
+
return { reservationId, plan: { direct: [tokenId] } };
|
|
14400
|
+
}
|
|
13986
14401
|
/**
|
|
13987
14402
|
* The sources `plan()` WOULD pick right now, reserving nothing and queueing
|
|
13988
14403
|
* nothing — a read-only probe used to warm blobs while the user is still
|
|
@@ -14172,6 +14587,7 @@ function composeFacadeParts(deps, hooks) {
|
|
|
14172
14587
|
const queue = new SpendQueue({
|
|
14173
14588
|
ledger,
|
|
14174
14589
|
getPool: (coinId) => view.pool(coinId),
|
|
14590
|
+
spendableCoinless: (tokenId) => view.spendableCoinless(tokenId),
|
|
14175
14591
|
...deps.workBudget !== void 0 ? { workBudget: deps.workBudget } : {}
|
|
14176
14592
|
});
|
|
14177
14593
|
const historyStore = new History({
|
|
@@ -14255,10 +14671,9 @@ function buildMachineDeps(deps, hooks, historyStore) {
|
|
|
14255
14671
|
recordHistory: async ({ transferId, payload, committedAmount }) => {
|
|
14256
14672
|
await historyStore.recordSent({
|
|
14257
14673
|
transferId,
|
|
14258
|
-
|
|
14259
|
-
//
|
|
14260
|
-
|
|
14261
|
-
amount: committedAmount,
|
|
14674
|
+
// §5.9: the SETTLED amount, never payload.amount — the plan. A token
|
|
14675
|
+
// spend moved no coin: `assets: []` + tokenId (wallet-api#151 / §10).
|
|
14676
|
+
...payload.kind === "coinless" ? { assets: [], tokenId: payload.direct[0] } : { assets: [{ coinId: payload.coinId, amount: committedAmount }] },
|
|
14262
14677
|
recipientPubkey: payload.recipient,
|
|
14263
14678
|
...payload.memo !== void 0 ? { memo: payload.memo } : {}
|
|
14264
14679
|
});
|
|
@@ -14293,12 +14708,10 @@ function buildReceive(deps, hooks, historyStore, heldStates, refreshView) {
|
|
|
14293
14708
|
});
|
|
14294
14709
|
}
|
|
14295
14710
|
async function recordReceived(historyStore, record) {
|
|
14296
|
-
const first = record.assets[0];
|
|
14297
14711
|
await historyStore.recordReceived({
|
|
14298
14712
|
tokenId: record.tokenId,
|
|
14299
14713
|
stateHash: record.stateHash,
|
|
14300
|
-
|
|
14301
|
-
amount: first?.amount ?? "0",
|
|
14714
|
+
assets: record.assets,
|
|
14302
14715
|
...record.senderPubkey !== void 0 ? { senderPubkey: record.senderPubkey } : {},
|
|
14303
14716
|
...record.senderNametag !== void 0 ? { senderNametag: record.senderNametag } : {},
|
|
14304
14717
|
...record.memo !== void 0 ? { memo: record.memo } : {},
|
|
@@ -14446,6 +14859,13 @@ var PaymentsFacade = class {
|
|
|
14446
14859
|
tokens(filter) {
|
|
14447
14860
|
return this.view.tokens(this.deps.registry, filter);
|
|
14448
14861
|
}
|
|
14862
|
+
coinless() {
|
|
14863
|
+
return this.view.coinless(this.deps.registry);
|
|
14864
|
+
}
|
|
14865
|
+
tokenData(tokenId) {
|
|
14866
|
+
const deps = { engine: this.engine(), view: this.view, storagePort: this.deps.storagePort };
|
|
14867
|
+
return readTokenData(deps, tokenId);
|
|
14868
|
+
}
|
|
14449
14869
|
history(page) {
|
|
14450
14870
|
return this.historyStore.page(page ?? {});
|
|
14451
14871
|
}
|
|
@@ -14470,6 +14890,9 @@ var PaymentsFacade = class {
|
|
|
14470
14890
|
send(request) {
|
|
14471
14891
|
return this.track(this.sendOutcome(request));
|
|
14472
14892
|
}
|
|
14893
|
+
sendCoinless(request) {
|
|
14894
|
+
return this.track(this.sendCoinlessOutcome(request));
|
|
14895
|
+
}
|
|
14473
14896
|
async receive() {
|
|
14474
14897
|
const transfers = await this.track(this.receiveLoop.drainOnce());
|
|
14475
14898
|
return { transfers };
|
|
@@ -14500,12 +14923,32 @@ var PaymentsFacade = class {
|
|
|
14500
14923
|
nowMs() {
|
|
14501
14924
|
return (this.deps.now ?? Date.now)();
|
|
14502
14925
|
}
|
|
14926
|
+
mintDeps() {
|
|
14927
|
+
return {
|
|
14928
|
+
engine: this.engine(),
|
|
14929
|
+
mintJournal: this.machineStores.mintJournal,
|
|
14930
|
+
storagePort: this.deps.storagePort,
|
|
14931
|
+
recordMint: (input) => this.historyStore.recordMint(input),
|
|
14932
|
+
armHeartbeat: () => this.heartbeat.arm(),
|
|
14933
|
+
noteHeldState: (tokenId, stateHash) => void this.heldStates.set(tokenId, stateHash),
|
|
14934
|
+
refreshView: () => this.trackTail(this.view.delta()),
|
|
14935
|
+
ownPubkeyBytes: this.ownPubkeyBytes,
|
|
14936
|
+
now: () => this.nowMs()
|
|
14937
|
+
};
|
|
14938
|
+
}
|
|
14503
14939
|
// ── send policy (§5.5 + old-loop parity; the machine stays policy-free) ────
|
|
14504
14940
|
/** The ONE place a send() outcome is shaped: success emits in finishSend, a
|
|
14505
14941
|
* CLEAN rejection emits `transfer:updated{status:'failed'}` here (§4). */
|
|
14506
|
-
|
|
14942
|
+
sendOutcome(request) {
|
|
14943
|
+
return this.runJob({ kind: "coin", request }, request.amount);
|
|
14944
|
+
}
|
|
14945
|
+
/** A token spend has no amount; '0' keeps the shortfall arithmetic total-free. */
|
|
14946
|
+
sendCoinlessOutcome(request) {
|
|
14947
|
+
return this.runJob({ kind: "coinless", request }, "0");
|
|
14948
|
+
}
|
|
14949
|
+
async runJob(job, amount) {
|
|
14507
14950
|
const run = {
|
|
14508
|
-
amount
|
|
14951
|
+
amount,
|
|
14509
14952
|
delivered: [],
|
|
14510
14953
|
sentTokens: [],
|
|
14511
14954
|
tokenTransfers: [],
|
|
@@ -14515,7 +14958,7 @@ var PaymentsFacade = class {
|
|
|
14515
14958
|
lastTransferId: ""
|
|
14516
14959
|
};
|
|
14517
14960
|
try {
|
|
14518
|
-
return await this.sendWithPolicy(
|
|
14961
|
+
return await this.sendWithPolicy(job, run);
|
|
14519
14962
|
} catch (err) {
|
|
14520
14963
|
this.emitCleanFailure(err, run.lastTransferId);
|
|
14521
14964
|
throw err;
|
|
@@ -14535,19 +14978,19 @@ var PaymentsFacade = class {
|
|
|
14535
14978
|
};
|
|
14536
14979
|
this.deps.emit("transfer:updated", failed);
|
|
14537
14980
|
}
|
|
14538
|
-
async sendWithPolicy(
|
|
14539
|
-
const recipient = await requireSameNetworkRecipient(this.deps, request.recipient);
|
|
14981
|
+
async sendWithPolicy(job, run) {
|
|
14982
|
+
const recipient = await requireSameNetworkRecipient(this.deps, job.request.recipient);
|
|
14540
14983
|
for (let attempt = 0; ; attempt++) {
|
|
14541
14984
|
let ctx;
|
|
14542
14985
|
try {
|
|
14543
|
-
ctx = await this.planAndMaterialize(recipient.chainPubkey,
|
|
14986
|
+
ctx = await this.planAndMaterialize(recipient.chainPubkey, job, run);
|
|
14544
14987
|
} catch (err) {
|
|
14545
|
-
throw
|
|
14988
|
+
throw partialize(err, run);
|
|
14546
14989
|
}
|
|
14547
14990
|
const disposition = await this.runAttempt(ctx);
|
|
14548
|
-
if (disposition.kind === "rethrow") throw
|
|
14991
|
+
if (disposition.kind === "rethrow") throw partialize(disposition.error, run);
|
|
14549
14992
|
if (disposition.kind === "retry-full") {
|
|
14550
|
-
if (attempt >= MAX_RESELECT) throw
|
|
14993
|
+
if (job.kind === "coinless" || attempt >= MAX_RESELECT) throw partialize(disposition.error, run);
|
|
14551
14994
|
continue;
|
|
14552
14995
|
}
|
|
14553
14996
|
if (disposition.kind === "success") {
|
|
@@ -14603,7 +15046,7 @@ var PaymentsFacade = class {
|
|
|
14603
15046
|
async disposeFailedAttempt(ctx, err) {
|
|
14604
15047
|
if (isPossiblyCommittedSendOutcome(err)) {
|
|
14605
15048
|
this.settleKeepOpen(ctx);
|
|
14606
|
-
|
|
15049
|
+
stampTransferId(err, ctx.transferId);
|
|
14607
15050
|
return { kind: "rethrow", error: err };
|
|
14608
15051
|
}
|
|
14609
15052
|
const backstop = await this.machineStores.backstop.getByKey(ctx.transferId);
|
|
@@ -14637,7 +15080,7 @@ var PaymentsFacade = class {
|
|
|
14637
15080
|
async disposeConvergeFailure(ctx, err) {
|
|
14638
15081
|
if (isPossiblyCommittedSendOutcome(err)) {
|
|
14639
15082
|
this.settleKeepOpen(ctx);
|
|
14640
|
-
|
|
15083
|
+
stampTransferId(err, ctx.transferId);
|
|
14641
15084
|
return { kind: "rethrow", error: err };
|
|
14642
15085
|
}
|
|
14643
15086
|
if (classifyError(err) === "conflict") {
|
|
@@ -14648,9 +15091,23 @@ var PaymentsFacade = class {
|
|
|
14648
15091
|
}
|
|
14649
15092
|
return { kind: "rethrow", error: err };
|
|
14650
15093
|
}
|
|
14651
|
-
async planAndMaterialize(recipientPubkey,
|
|
15094
|
+
async planAndMaterialize(recipientPubkey, job, run) {
|
|
14652
15095
|
const transferId = this.newId();
|
|
14653
15096
|
run.lastTransferId = transferId;
|
|
15097
|
+
if (job.kind === "coinless") {
|
|
15098
|
+
const spend2 = this.queue.planCoinless(transferId, job.request.tokenId);
|
|
15099
|
+
const sourceIds2 = this.markPlanned(transferId, "", spend2);
|
|
15100
|
+
try {
|
|
15101
|
+
return await materializeCoinlessSpend(
|
|
15102
|
+
{ engine: this.engine(), storagePort: this.deps.storagePort },
|
|
15103
|
+
{ transferId, recipientPubkey, request: job.request, sourceIds: sourceIds2 }
|
|
15104
|
+
);
|
|
15105
|
+
} catch (err) {
|
|
15106
|
+
this.settleFailure(transferId, "", sourceIds2);
|
|
15107
|
+
throw err;
|
|
15108
|
+
}
|
|
15109
|
+
}
|
|
15110
|
+
const request = { ...job.request, amount: run.amount };
|
|
14654
15111
|
const planned = this.queue.plan(transferId, { coinId: request.coinId, amount: request.amount });
|
|
14655
15112
|
const spend = planned.kind === "planned" ? planned.spend : await planned.settled;
|
|
14656
15113
|
const sourceIds = this.markPlanned(transferId, request.coinId, spend);
|
|
@@ -14784,22 +15241,6 @@ var PaymentsFacade = class {
|
|
|
14784
15241
|
return result;
|
|
14785
15242
|
}
|
|
14786
15243
|
/** Nothing delivered → UNWRAPPED; after ≥1 delivered leg every failure surfaces as PartialSendConflictError over the settled set. */
|
|
14787
|
-
partialize(err, run) {
|
|
14788
|
-
if (run.delivered.length === 0) return err;
|
|
14789
|
-
return new PartialSendConflictError(
|
|
14790
|
-
"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.",
|
|
14791
|
-
run.firstPartialId ?? "",
|
|
14792
|
-
run.delivered,
|
|
14793
|
-
run.amount,
|
|
14794
|
-
err
|
|
14795
|
-
);
|
|
14796
|
-
}
|
|
14797
|
-
/** #441: possibly-committed errors must carry the transferId for the settling journal. */
|
|
14798
|
-
stampTransferId(err, transferId) {
|
|
14799
|
-
if (err instanceof SphereError && isPossiblyCommittedSendOutcome(err) && err.transferId === void 0) {
|
|
14800
|
-
err.transferId = transferId;
|
|
14801
|
-
}
|
|
14802
|
-
}
|
|
14803
15244
|
async softAbort(transferId) {
|
|
14804
15245
|
try {
|
|
14805
15246
|
await this.deps.client.abortIntent(transferId);
|
|
@@ -14815,57 +15256,11 @@ var PaymentsFacade = class {
|
|
|
14815
15256
|
const mintId = this.newId();
|
|
14816
15257
|
this.activeMoneyOps.add(mintId);
|
|
14817
15258
|
try {
|
|
14818
|
-
return await this.
|
|
15259
|
+
return await runMintUnderJournal(this.mintDeps(), { mintId, coinId, amount });
|
|
14819
15260
|
} finally {
|
|
14820
15261
|
this.activeMoneyOps.delete(mintId);
|
|
14821
15262
|
}
|
|
14822
15263
|
}
|
|
14823
|
-
async mintUnderJournal(mintId, coinId, amount) {
|
|
14824
|
-
const engine = this.engine();
|
|
14825
|
-
const entry = {
|
|
14826
|
-
mintId,
|
|
14827
|
-
coinId,
|
|
14828
|
-
amount: amount.toString(),
|
|
14829
|
-
tokenId: "",
|
|
14830
|
-
createdAt: this.nowMs()
|
|
14831
|
-
};
|
|
14832
|
-
await this.machineStores.mintJournal.upsert(entry);
|
|
14833
|
-
let token;
|
|
14834
|
-
try {
|
|
14835
|
-
token = await engine.mint(mintParams(this.ownPubkeyBytes, coinId, amount), { transferId: mintId });
|
|
14836
|
-
} catch (err) {
|
|
14837
|
-
this.heartbeat.arm();
|
|
14838
|
-
return { success: false, error: messageOf(err) };
|
|
14839
|
-
}
|
|
14840
|
-
if (token.blob.tokenId !== entry.tokenId) {
|
|
14841
|
-
await this.machineStores.mintJournal.upsert({ ...entry, tokenId: token.blob.tokenId });
|
|
14842
|
-
}
|
|
14843
|
-
try {
|
|
14844
|
-
await this.finalizeMint(engine, mintId, token, coinId, amount.toString());
|
|
14845
|
-
} catch (err) {
|
|
14846
|
-
this.heartbeat.arm();
|
|
14847
|
-
return { success: false, tokenId: token.blob.tokenId, error: messageOf(err) };
|
|
14848
|
-
}
|
|
14849
|
-
return { success: true, tokenId: token.blob.tokenId };
|
|
14850
|
-
}
|
|
14851
|
-
async finalizeMint(engine, mintId, token, coinId, amount) {
|
|
14852
|
-
const bytes = token.blob.token;
|
|
14853
|
-
const digest = bytesToHex3(sha256(bytes));
|
|
14854
|
-
const keys = await this.deps.storagePort.uploadBlobs([{ sha256: digest, bytes }]);
|
|
14855
|
-
const key = keys.get(digest);
|
|
14856
|
-
if (key === void 0) {
|
|
14857
|
-
throw new SphereError(`no upload key returned for mint blob ${digest}`, "STORAGE_ERROR");
|
|
14858
|
-
}
|
|
14859
|
-
await this.deps.storagePort.applyDelta({
|
|
14860
|
-
transferId: mintId,
|
|
14861
|
-
spent: [],
|
|
14862
|
-
added: [{ tokenId: token.blob.tokenId, key }]
|
|
14863
|
-
});
|
|
14864
|
-
await this.historyStore.recordMint({ tokenId: token.blob.tokenId, coinId, amount });
|
|
14865
|
-
await this.machineStores.mintJournal.removeByKey(mintId);
|
|
14866
|
-
this.heldStates.set(token.blob.tokenId, (await engine.deliveryKeys(bytes)).stateHash);
|
|
14867
|
-
this.trackTail(this.view.delta());
|
|
14868
|
-
}
|
|
14869
15264
|
/** @returns how many journal entries were RESOLVED (cleared) — heartbeat progress. */
|
|
14870
15265
|
async replayMints() {
|
|
14871
15266
|
let resolved = 0;
|
|
@@ -14897,7 +15292,7 @@ var PaymentsFacade = class {
|
|
|
14897
15292
|
if (token.blob.tokenId !== entry.tokenId) {
|
|
14898
15293
|
await this.machineStores.mintJournal.upsert({ ...entry, tokenId: token.blob.tokenId });
|
|
14899
15294
|
}
|
|
14900
|
-
await this.
|
|
15295
|
+
await finalizeMint(this.mintDeps(), entry.mintId, token, entry.coinId, entry.amount);
|
|
14901
15296
|
return true;
|
|
14902
15297
|
}
|
|
14903
15298
|
async tokenInServerInventory(tokenId) {
|
|
@@ -16672,7 +17067,8 @@ var Sphere = class _Sphere {
|
|
|
16672
17067
|
}
|
|
16673
17068
|
const progress = options.onProgress;
|
|
16674
17069
|
logger.debug("Sphere", "Starting import...");
|
|
16675
|
-
const
|
|
17070
|
+
const liveHere = _Sphere.liveOn(options.storage).some((s) => s._storage === options.storage);
|
|
17071
|
+
const needsClear = liveHere || await _Sphere.exists(options.storage);
|
|
16676
17072
|
if (needsClear) {
|
|
16677
17073
|
progress?.({ step: "clearing", message: "Clearing previous wallet data..." });
|
|
16678
17074
|
logger.debug("Sphere", "Clearing existing wallet data...");
|