@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.cjs
CHANGED
|
@@ -8212,6 +8212,8 @@ var FETCH_TIMEOUT_MS = 1e4;
|
|
|
8212
8212
|
var TokenRegistry = class _TokenRegistry {
|
|
8213
8213
|
static instance = null;
|
|
8214
8214
|
definitionsById;
|
|
8215
|
+
/** Non-fungible definitions keyed by TOKEN TYPE — a separate namespace (#147). */
|
|
8216
|
+
definitionsByType;
|
|
8215
8217
|
definitionsBySymbol;
|
|
8216
8218
|
definitionsByName;
|
|
8217
8219
|
// Remote refresh state
|
|
@@ -8230,6 +8232,7 @@ var TokenRegistry = class _TokenRegistry {
|
|
|
8230
8232
|
inFlight = null;
|
|
8231
8233
|
constructor() {
|
|
8232
8234
|
this.definitionsById = /* @__PURE__ */ new Map();
|
|
8235
|
+
this.definitionsByType = /* @__PURE__ */ new Map();
|
|
8233
8236
|
this.definitionsBySymbol = /* @__PURE__ */ new Map();
|
|
8234
8237
|
this.definitionsByName = /* @__PURE__ */ new Map();
|
|
8235
8238
|
}
|
|
@@ -8457,9 +8460,11 @@ var TokenRegistry = class _TokenRegistry {
|
|
|
8457
8460
|
this.definitionsById.clear();
|
|
8458
8461
|
this.definitionsBySymbol.clear();
|
|
8459
8462
|
this.definitionsByName.clear();
|
|
8463
|
+
this.definitionsByType.clear();
|
|
8460
8464
|
for (const def of definitions) {
|
|
8461
8465
|
const idLower = def.id.toLowerCase();
|
|
8462
8466
|
this.definitionsById.set(idLower, def);
|
|
8467
|
+
if (def.assetKind === "non-fungible") this.definitionsByType.set(idLower, def);
|
|
8463
8468
|
if (def.symbol) {
|
|
8464
8469
|
this.definitionsBySymbol.set(def.symbol.toUpperCase(), def);
|
|
8465
8470
|
}
|
|
@@ -8673,6 +8678,20 @@ var TokenRegistry = class _TokenRegistry {
|
|
|
8673
8678
|
getAllDefinitions() {
|
|
8674
8679
|
return Array.from(this.definitionsById.values());
|
|
8675
8680
|
}
|
|
8681
|
+
/**
|
|
8682
|
+
* Definition for a coinless token's genesis TOKEN TYPE (#777). Never falls back
|
|
8683
|
+
* to `getDefinition`: a type and a coin id live in different namespaces, so a
|
|
8684
|
+
* type that collided with a coin id would otherwise render as that coin.
|
|
8685
|
+
*/
|
|
8686
|
+
getTypeMeta(tokenType) {
|
|
8687
|
+
const def = this.getTypeDefinition(tokenType);
|
|
8688
|
+
if (!def) return null;
|
|
8689
|
+
return { name: def.name, iconUrl: def.icons?.[0]?.url ?? null };
|
|
8690
|
+
}
|
|
8691
|
+
getTypeDefinition(tokenType) {
|
|
8692
|
+
if (!tokenType) return void 0;
|
|
8693
|
+
return this.definitionsByType.get(tokenType.toLowerCase());
|
|
8694
|
+
}
|
|
8676
8695
|
/**
|
|
8677
8696
|
* Get all fungible token definitions
|
|
8678
8697
|
* @returns Array of fungible token definitions
|
|
@@ -9508,6 +9527,8 @@ var import_DataHasherFactory = require("@unicitylabs/state-transition-sdk/lib/cr
|
|
|
9508
9527
|
var import_CborSerializer = require("@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborSerializer.js");
|
|
9509
9528
|
var import_CborDeserializer = require("@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborDeserializer.js");
|
|
9510
9529
|
var import_CborError = require("@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborError.js");
|
|
9530
|
+
var import_CborReader = require("@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborReader.js");
|
|
9531
|
+
var import_MajorType = require("@unicitylabs/state-transition-sdk/lib/serialization/cbor/MajorType.js");
|
|
9511
9532
|
var import_Asset = require("@unicitylabs/state-transition-sdk/lib/payment/asset/Asset.js");
|
|
9512
9533
|
var import_AssetId = require("@unicitylabs/state-transition-sdk/lib/payment/asset/AssetId.js");
|
|
9513
9534
|
var import_PaymentAssetCollection = require("@unicitylabs/state-transition-sdk/lib/payment/asset/PaymentAssetCollection.js");
|
|
@@ -9920,6 +9941,126 @@ async function burntTokenFromCheckpoint(deps, storedBytes, reDerivedBurnTx, sour
|
|
|
9920
9941
|
return burntToken;
|
|
9921
9942
|
}
|
|
9922
9943
|
|
|
9944
|
+
// token-engine/value-envelope.ts
|
|
9945
|
+
function isCoinlessEnvelope(envelope) {
|
|
9946
|
+
return envelope.startsWith("none_");
|
|
9947
|
+
}
|
|
9948
|
+
var SELF_DESCRIBED_CBOR_TAG = 55799n;
|
|
9949
|
+
var CBOR_MAJOR_TYPE_MASK = 224;
|
|
9950
|
+
var describe = (error) => error instanceof Error ? error.message : String(error);
|
|
9951
|
+
var invalid = (message) => new SphereError(message, "VALIDATION_ERROR");
|
|
9952
|
+
function majorTypeOf(bytes) {
|
|
9953
|
+
const first = bytes.at(0);
|
|
9954
|
+
return first === void 0 ? null : first & CBOR_MAJOR_TYPE_MASK;
|
|
9955
|
+
}
|
|
9956
|
+
function headTagNumber(bytes) {
|
|
9957
|
+
try {
|
|
9958
|
+
return new import_CborReader.CborReader(bytes).readLength(import_MajorType.MajorType.TAG);
|
|
9959
|
+
} catch {
|
|
9960
|
+
return null;
|
|
9961
|
+
}
|
|
9962
|
+
}
|
|
9963
|
+
function isAssetShaped(item) {
|
|
9964
|
+
try {
|
|
9965
|
+
return import_CborDeserializer.CborDeserializer.decodeArray(item, 2).every(
|
|
9966
|
+
(field) => majorTypeOf(field) === import_MajorType.MajorType.BYTE_STRING
|
|
9967
|
+
);
|
|
9968
|
+
} catch {
|
|
9969
|
+
return false;
|
|
9970
|
+
}
|
|
9971
|
+
}
|
|
9972
|
+
function arrayItemsOrNull(bytes) {
|
|
9973
|
+
try {
|
|
9974
|
+
return import_CborDeserializer.CborDeserializer.decodeArray(bytes);
|
|
9975
|
+
} catch {
|
|
9976
|
+
return null;
|
|
9977
|
+
}
|
|
9978
|
+
}
|
|
9979
|
+
function decodeSphere(genesisData) {
|
|
9980
|
+
try {
|
|
9981
|
+
return SpherePaymentData.fromCBOR(genesisData).toValue();
|
|
9982
|
+
} catch (error) {
|
|
9983
|
+
throw invalid(`Failed to decode token payment data: ${describe(error)}`);
|
|
9984
|
+
}
|
|
9985
|
+
}
|
|
9986
|
+
function taggedItemBody(genesisData) {
|
|
9987
|
+
try {
|
|
9988
|
+
return import_CborDeserializer.CborDeserializer.decodeTag(genesisData).data;
|
|
9989
|
+
} catch (error) {
|
|
9990
|
+
throw invalid(
|
|
9991
|
+
`Token value payload is a CBOR tag whose item is malformed or carries trailing bytes: ${describe(error)}`
|
|
9992
|
+
);
|
|
9993
|
+
}
|
|
9994
|
+
}
|
|
9995
|
+
function selfDescribedBodyCarriesValue(body) {
|
|
9996
|
+
const major = majorTypeOf(body);
|
|
9997
|
+
if (major === import_MajorType.MajorType.TAG) {
|
|
9998
|
+
const inner = headTagNumber(body);
|
|
9999
|
+
return inner === SpherePaymentData.CBOR_TAG || inner === SELF_DESCRIBED_CBOR_TAG;
|
|
10000
|
+
}
|
|
10001
|
+
if (major === import_MajorType.MajorType.ARRAY) {
|
|
10002
|
+
const items = arrayItemsOrNull(body);
|
|
10003
|
+
return items !== null && items.some(isAssetShaped);
|
|
10004
|
+
}
|
|
10005
|
+
return false;
|
|
10006
|
+
}
|
|
10007
|
+
function classifyTagged(genesisData) {
|
|
10008
|
+
const tag = headTagNumber(genesisData);
|
|
10009
|
+
if (tag === null) {
|
|
10010
|
+
throw invalid(
|
|
10011
|
+
"Token value payload has an unreadable or non-canonically encoded CBOR tag head \u2014 it could be a malformed SpherePaymentData envelope"
|
|
10012
|
+
);
|
|
10013
|
+
}
|
|
10014
|
+
if (tag === SpherePaymentData.CBOR_TAG) {
|
|
10015
|
+
return { envelope: "sphere", value: decodeSphere(genesisData) };
|
|
10016
|
+
}
|
|
10017
|
+
const body = taggedItemBody(genesisData);
|
|
10018
|
+
if (tag === SELF_DESCRIBED_CBOR_TAG && selfDescribedBodyCarriesValue(body)) {
|
|
10019
|
+
throw invalid(
|
|
10020
|
+
"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"
|
|
10021
|
+
);
|
|
10022
|
+
}
|
|
10023
|
+
return { envelope: "none_tag", value: null };
|
|
10024
|
+
}
|
|
10025
|
+
function classifyArray(genesisData) {
|
|
10026
|
+
const items = arrayItemsOrNull(genesisData);
|
|
10027
|
+
if (items === null) {
|
|
10028
|
+
throw invalid("Token value payload is not PaymentAssetCollection: malformed CBOR array");
|
|
10029
|
+
}
|
|
10030
|
+
return items.some(isAssetShaped) ? { envelope: "bare_collection", value: null } : { envelope: "none_other", value: null };
|
|
10031
|
+
}
|
|
10032
|
+
function classifyValueEnvelope(genesisData) {
|
|
10033
|
+
if (genesisData === null) return { envelope: "none_absent", value: null };
|
|
10034
|
+
const major = majorTypeOf(genesisData);
|
|
10035
|
+
if (major === null) return { envelope: "none_absent", value: null };
|
|
10036
|
+
if (major === import_MajorType.MajorType.TAG) return classifyTagged(genesisData);
|
|
10037
|
+
if (major === import_MajorType.MajorType.ARRAY) return classifyArray(genesisData);
|
|
10038
|
+
return { envelope: "none_other", value: null };
|
|
10039
|
+
}
|
|
10040
|
+
function wrapToken(sdkToken) {
|
|
10041
|
+
const { envelope, value } = classifyValueEnvelope(sdkToken.genesis.data);
|
|
10042
|
+
const blob = {
|
|
10043
|
+
tokenId: import_HexConverter.HexConverter.encode(sdkToken.id.bytes),
|
|
10044
|
+
token: sdkToken.toCBOR()
|
|
10045
|
+
};
|
|
10046
|
+
return {
|
|
10047
|
+
sdkToken,
|
|
10048
|
+
blob,
|
|
10049
|
+
value,
|
|
10050
|
+
valueEnvelope: envelope,
|
|
10051
|
+
tokenType: import_HexConverter.HexConverter.encode(sdkToken.type.bytes)
|
|
10052
|
+
};
|
|
10053
|
+
}
|
|
10054
|
+
function assertMintableData(data) {
|
|
10055
|
+
try {
|
|
10056
|
+
classifyValueEnvelope(data);
|
|
10057
|
+
} catch (error) {
|
|
10058
|
+
throw invalid(
|
|
10059
|
+
`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.`
|
|
10060
|
+
);
|
|
10061
|
+
}
|
|
10062
|
+
}
|
|
10063
|
+
|
|
9923
10064
|
// token-engine/SphereTokenEngine.ts
|
|
9924
10065
|
var DEFAULT_PROOF_POLL_INTERVAL_MS = 300;
|
|
9925
10066
|
var MAX_MINT_CONCURRENCY = 8;
|
|
@@ -9976,7 +10117,7 @@ var SphereTokenEngine = class {
|
|
|
9976
10117
|
return sdkToken.latestTransaction.data;
|
|
9977
10118
|
}
|
|
9978
10119
|
const data = sdkToken.genesis.data;
|
|
9979
|
-
if (data &&
|
|
10120
|
+
if (data && classifyValueEnvelope(data).envelope === "sphere") {
|
|
9980
10121
|
return SpherePaymentData.fromCBOR(data).memo;
|
|
9981
10122
|
}
|
|
9982
10123
|
return null;
|
|
@@ -10006,9 +10147,10 @@ var SphereTokenEngine = class {
|
|
|
10006
10147
|
);
|
|
10007
10148
|
const certified = await mintTx.toCertifiedTransaction(this.deps.trustBase, this.deps.predicateVerifier, this.deps.unicityCertificateVerifier, proof);
|
|
10008
10149
|
const token = await import_Token.Token.mint(certified, this.deps.verificationContext);
|
|
10009
|
-
return
|
|
10150
|
+
return wrapToken(token);
|
|
10010
10151
|
}
|
|
10011
10152
|
async mintDataToken(params, options) {
|
|
10153
|
+
assertMintableData(params.data);
|
|
10012
10154
|
const recipient = import_SignaturePredicate.SignaturePredicate.create(params.recipientPubkey);
|
|
10013
10155
|
const tokenType = params.tokenType ? new import_TokenType.TokenType(params.tokenType) : import_TokenType.TokenType.generate();
|
|
10014
10156
|
const salt = params.salt ? import_TokenSalt.TokenSalt.fromBytes(params.salt) : import_TokenSalt.TokenSalt.generate();
|
|
@@ -10023,7 +10165,7 @@ var SphereTokenEngine = class {
|
|
|
10023
10165
|
);
|
|
10024
10166
|
const certified = await mintTx.toCertifiedTransaction(this.deps.trustBase, this.deps.predicateVerifier, this.deps.unicityCertificateVerifier, proof);
|
|
10025
10167
|
const token = await import_Token.Token.mint(certified, this.deps.verificationContext);
|
|
10026
|
-
return
|
|
10168
|
+
return wrapToken(token);
|
|
10027
10169
|
}
|
|
10028
10170
|
async transfer(params, options) {
|
|
10029
10171
|
this.assertOwned(params.token);
|
|
@@ -10046,13 +10188,19 @@ var SphereTokenEngine = class {
|
|
|
10046
10188
|
);
|
|
10047
10189
|
const certified = await transferTx.toCertifiedTransaction(this.deps.trustBase, this.deps.predicateVerifier, this.deps.unicityCertificateVerifier, proof);
|
|
10048
10190
|
const transferred = await params.token.sdkToken.transfer(certified, this.deps.verificationContext);
|
|
10049
|
-
return
|
|
10191
|
+
return wrapToken(transferred);
|
|
10050
10192
|
}
|
|
10051
10193
|
async split(params, options) {
|
|
10052
10194
|
this.assertOwned(params.token);
|
|
10053
10195
|
if (params.outputs.length === 0) {
|
|
10054
10196
|
throw new SphereError("Split requires at least one output", "VALIDATION_ERROR");
|
|
10055
10197
|
}
|
|
10198
|
+
if (params.token.value === null) {
|
|
10199
|
+
throw new SphereError(
|
|
10200
|
+
`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.`,
|
|
10201
|
+
"VALIDATION_ERROR"
|
|
10202
|
+
);
|
|
10203
|
+
}
|
|
10056
10204
|
const transferId = this.resolveTransferId(options);
|
|
10057
10205
|
const requests = params.outputs.map(
|
|
10058
10206
|
(o, i) => import_SplitTokenRequest.SplitTokenRequest.create(
|
|
@@ -10174,7 +10322,7 @@ var SphereTokenEngine = class {
|
|
|
10174
10322
|
const proof = await this.submitSplitMintLeg(certData, mintTx, options);
|
|
10175
10323
|
const certified = await mintTx.toCertifiedTransaction(this.deps.trustBase, this.deps.predicateVerifier, this.deps.unicityCertificateVerifier, proof);
|
|
10176
10324
|
const token = await import_Token.Token.mint(certified, this.deps.verificationContext);
|
|
10177
|
-
return
|
|
10325
|
+
return wrapToken(token);
|
|
10178
10326
|
}
|
|
10179
10327
|
/**
|
|
10180
10328
|
* Submit one split-mint leg. A `TRANSACTION_HASH_MISMATCH` on a mint leg is NOT a foreign spend
|
|
@@ -10232,7 +10380,7 @@ var SphereTokenEngine = class {
|
|
|
10232
10380
|
"VALIDATION_ERROR"
|
|
10233
10381
|
);
|
|
10234
10382
|
}
|
|
10235
|
-
return
|
|
10383
|
+
return wrapToken(sdkToken);
|
|
10236
10384
|
}
|
|
10237
10385
|
// ── internals ────────────────────────────────────────────────────────────────
|
|
10238
10386
|
/**
|
|
@@ -10344,34 +10492,6 @@ var SphereTokenEngine = class {
|
|
|
10344
10492
|
throw new SphereError("Cannot transfer a token not owned by this engine identity", "VALIDATION_ERROR");
|
|
10345
10493
|
}
|
|
10346
10494
|
}
|
|
10347
|
-
/** Wrap an SDK token into a SphereToken: cache its blob (incl. stable tokenId) + decoded value. */
|
|
10348
|
-
wrapToken(sdkToken) {
|
|
10349
|
-
const data = sdkToken.genesis.data;
|
|
10350
|
-
let value = null;
|
|
10351
|
-
if (data && this.isSpherePaymentData(data)) {
|
|
10352
|
-
try {
|
|
10353
|
-
value = SpherePaymentData.fromCBOR(data).toValue();
|
|
10354
|
-
} catch (err) {
|
|
10355
|
-
throw new SphereError(
|
|
10356
|
-
`Failed to decode token payment data: ${err instanceof Error ? err.message : String(err)}`,
|
|
10357
|
-
"VALIDATION_ERROR"
|
|
10358
|
-
);
|
|
10359
|
-
}
|
|
10360
|
-
}
|
|
10361
|
-
const blob = {
|
|
10362
|
-
tokenId: import_HexConverter.HexConverter.encode(sdkToken.id.bytes),
|
|
10363
|
-
token: sdkToken.toCBOR()
|
|
10364
|
-
};
|
|
10365
|
-
return { sdkToken, blob, value };
|
|
10366
|
-
}
|
|
10367
|
-
/** True if the bytes are a SpherePaymentData envelope (value token) vs a raw data token. */
|
|
10368
|
-
isSpherePaymentData(data) {
|
|
10369
|
-
try {
|
|
10370
|
-
return import_CborDeserializer.CborDeserializer.decodeTag(data).tag === SpherePaymentData.CBOR_TAG;
|
|
10371
|
-
} catch {
|
|
10372
|
-
return false;
|
|
10373
|
-
}
|
|
10374
|
-
}
|
|
10375
10495
|
/**
|
|
10376
10496
|
* Terminate the verification worker pool, if this engine was configured with
|
|
10377
10497
|
* one. Idempotent — the pool's dispose only touches workers it spawned.
|
|
@@ -11767,6 +11887,14 @@ function createMachineStores(kv) {
|
|
|
11767
11887
|
};
|
|
11768
11888
|
}
|
|
11769
11889
|
|
|
11890
|
+
// modules/payments-v2/machine/payload-view.ts
|
|
11891
|
+
function splitOf(p) {
|
|
11892
|
+
return p.kind === "coin" ? p.split : void 0;
|
|
11893
|
+
}
|
|
11894
|
+
function coinIdOf(p) {
|
|
11895
|
+
return p.kind === "coin" ? p.coinId : void 0;
|
|
11896
|
+
}
|
|
11897
|
+
|
|
11770
11898
|
// modules/payments-v2/machine/TransferMachine.ts
|
|
11771
11899
|
var CERTIFY_WIDTH = 20;
|
|
11772
11900
|
var KEEP_OPEN_CODES = /* @__PURE__ */ new Set([
|
|
@@ -11969,13 +12097,14 @@ var TransferMachine = class {
|
|
|
11969
12097
|
const finished = await engine.transfer({ token: source, recipientPubkey }, opts);
|
|
11970
12098
|
return { recipientBlob: finished.blob.token };
|
|
11971
12099
|
}
|
|
11972
|
-
const split = ctx.payload
|
|
12100
|
+
const split = splitOf(ctx.payload);
|
|
12101
|
+
const splitCoinId = coinIdOf(ctx.payload);
|
|
11973
12102
|
const { outputs } = await engine.split(
|
|
11974
12103
|
{
|
|
11975
12104
|
token: source,
|
|
11976
12105
|
outputs: [
|
|
11977
|
-
{ recipientPubkey, coinId:
|
|
11978
|
-
{ recipientPubkey: this.deps.ownPubkey, coinId:
|
|
12106
|
+
{ recipientPubkey, coinId: splitCoinId, amount: BigInt(split.splitAmount) },
|
|
12107
|
+
{ recipientPubkey: this.deps.ownPubkey, coinId: splitCoinId, amount: BigInt(split.remainderAmount) }
|
|
11979
12108
|
]
|
|
11980
12109
|
},
|
|
11981
12110
|
opts
|
|
@@ -12097,7 +12226,9 @@ var TransferMachine = class {
|
|
|
12097
12226
|
const entry = {
|
|
12098
12227
|
transferId: r.transferId,
|
|
12099
12228
|
remainingAmount: undelivered.toString(),
|
|
12100
|
-
|
|
12229
|
+
// '' only on a token-addressed intent, which is one leg: it either lands or
|
|
12230
|
+
// conflicts, so a shortfall row is unreachable there (settlePartial needs >1).
|
|
12231
|
+
coinId: coinIdOf(r.payload) ?? "",
|
|
12101
12232
|
recipient: r.payload.recipient,
|
|
12102
12233
|
committedTokenIds: committed.map((o) => o.op.sourceTokenId),
|
|
12103
12234
|
createdAt: this.deps.now()
|
|
@@ -12123,7 +12254,8 @@ var TransferMachine = class {
|
|
|
12123
12254
|
}
|
|
12124
12255
|
async finishBookkeeping(engine, transferId, payload, phase, committed) {
|
|
12125
12256
|
try {
|
|
12126
|
-
const
|
|
12257
|
+
const coinId = coinIdOf(payload);
|
|
12258
|
+
const committedAmount = coinId === void 0 ? "0" : await this.settledAmount(engine, coinId, committed);
|
|
12127
12259
|
await this.deps.recordHistory?.({ transferId, payload, phase, committedAmount });
|
|
12128
12260
|
} catch {
|
|
12129
12261
|
}
|
|
@@ -12166,8 +12298,11 @@ var TransferMachine = class {
|
|
|
12166
12298
|
}
|
|
12167
12299
|
};
|
|
12168
12300
|
function conflictAmount(engine, payload, op, source) {
|
|
12169
|
-
|
|
12170
|
-
|
|
12301
|
+
const split = splitOf(payload);
|
|
12302
|
+
if (op.kind === "split") return BigInt(split.splitAmount);
|
|
12303
|
+
const coinId = coinIdOf(payload);
|
|
12304
|
+
if (coinId === void 0) return 0n;
|
|
12305
|
+
return source !== void 0 ? engine.balanceOf(source, coinId) : 0n;
|
|
12171
12306
|
}
|
|
12172
12307
|
|
|
12173
12308
|
// modules/payments-v2/machine/resume.ts
|
|
@@ -12274,6 +12409,14 @@ async function runOne(ctx, job, report) {
|
|
|
12274
12409
|
report.failed.push(job.transferId);
|
|
12275
12410
|
return;
|
|
12276
12411
|
}
|
|
12412
|
+
if (job.payload.kind === "coinless" && !isCoinlessEnvelope(token.valueEnvelope)) {
|
|
12413
|
+
logger.warn(
|
|
12414
|
+
"PaymentsV2",
|
|
12415
|
+
`resume: token intent ${job.transferId} names a source carrying coin value \u2014 refusing`
|
|
12416
|
+
);
|
|
12417
|
+
report.failed.push(job.transferId);
|
|
12418
|
+
return;
|
|
12419
|
+
}
|
|
12277
12420
|
mine.set(op.sourceTokenId, token);
|
|
12278
12421
|
}
|
|
12279
12422
|
try {
|
|
@@ -12304,6 +12447,28 @@ async function classifyResumeFailure(deps, stores, transferId, err, report) {
|
|
|
12304
12447
|
}
|
|
12305
12448
|
report.failed.push(transferId);
|
|
12306
12449
|
}
|
|
12450
|
+
function validateCoinPayload(c) {
|
|
12451
|
+
if (typeof c.coinId !== "string" || typeof c.amount !== "string") {
|
|
12452
|
+
throw new SphereError("intent payload is missing coinId/amount", "VALIDATION_ERROR");
|
|
12453
|
+
}
|
|
12454
|
+
const s = c.split;
|
|
12455
|
+
if (s !== void 0 && (typeof s.tokenId !== "string" || typeof s.splitAmount !== "string" || typeof s.remainderAmount !== "string")) {
|
|
12456
|
+
throw new SphereError("intent payload split spec is malformed", "VALIDATION_ERROR");
|
|
12457
|
+
}
|
|
12458
|
+
return { ...c, kind: "coin" };
|
|
12459
|
+
}
|
|
12460
|
+
function validateCoinlessPayload(p) {
|
|
12461
|
+
if (p.direct?.length !== 1 || typeof p.direct[0] !== "string" || p.direct[0] === "") {
|
|
12462
|
+
throw new SphereError(
|
|
12463
|
+
"token intent payload must name exactly one source token",
|
|
12464
|
+
"VALIDATION_ERROR"
|
|
12465
|
+
);
|
|
12466
|
+
}
|
|
12467
|
+
if (p.split !== void 0) {
|
|
12468
|
+
throw new SphereError("token intent payload cannot carry a split", "VALIDATION_ERROR");
|
|
12469
|
+
}
|
|
12470
|
+
return { ...p, kind: "coinless", direct: [p.direct[0]] };
|
|
12471
|
+
}
|
|
12307
12472
|
function validatePayload(raw) {
|
|
12308
12473
|
const p = raw !== null && typeof raw === "object" ? raw : null;
|
|
12309
12474
|
if (p === null || p.v !== 2 || !Array.isArray(p.direct) || p.direct.some((t) => typeof t !== "string")) {
|
|
@@ -12312,14 +12477,17 @@ function validatePayload(raw) {
|
|
|
12312
12477
|
"VALIDATION_ERROR"
|
|
12313
12478
|
);
|
|
12314
12479
|
}
|
|
12315
|
-
if (typeof p.recipient !== "string"
|
|
12316
|
-
throw new SphereError("intent payload is missing recipient
|
|
12480
|
+
if (typeof p.recipient !== "string") {
|
|
12481
|
+
throw new SphereError("intent payload is missing recipient", "VALIDATION_ERROR");
|
|
12317
12482
|
}
|
|
12318
|
-
const
|
|
12319
|
-
if (
|
|
12320
|
-
throw new SphereError(
|
|
12483
|
+
const kind = p.kind ?? "coin";
|
|
12484
|
+
if (kind !== "coin" && kind !== "coinless") {
|
|
12485
|
+
throw new SphereError(
|
|
12486
|
+
`unsupported intent kind '${String(kind)}' \u2014 not resumable by this client`,
|
|
12487
|
+
"VALIDATION_ERROR"
|
|
12488
|
+
);
|
|
12321
12489
|
}
|
|
12322
|
-
return p;
|
|
12490
|
+
return kind === "coinless" ? validateCoinlessPayload(p) : validateCoinPayload(p);
|
|
12323
12491
|
}
|
|
12324
12492
|
|
|
12325
12493
|
// modules/payments-v2/convergence.ts
|
|
@@ -12525,13 +12693,23 @@ async function openRow(entry, journal, decryptPayload) {
|
|
|
12525
12693
|
transferId: entry.transferId,
|
|
12526
12694
|
kind: "open",
|
|
12527
12695
|
recipient: typeof payload?.recipient === "string" ? payload.recipient : "",
|
|
12528
|
-
|
|
12529
|
-
amount: typeof payload?.amount === "string" ? payload.amount : "",
|
|
12696
|
+
...subject(payload),
|
|
12530
12697
|
legs: { certified, total: Math.max(total, certified) },
|
|
12531
12698
|
deliveryPending: journal.length > 0,
|
|
12532
12699
|
createdAt: entry.createdAt
|
|
12533
12700
|
};
|
|
12534
12701
|
}
|
|
12702
|
+
function subject(payload) {
|
|
12703
|
+
if (payload?.kind === "coinless") {
|
|
12704
|
+
const tokenId = payload.direct?.[0];
|
|
12705
|
+
return { coinId: "", amount: "", ...typeof tokenId === "string" ? { tokenId } : {} };
|
|
12706
|
+
}
|
|
12707
|
+
const coin = payload;
|
|
12708
|
+
return {
|
|
12709
|
+
coinId: typeof coin?.coinId === "string" ? coin.coinId : "",
|
|
12710
|
+
amount: typeof coin?.amount === "string" ? coin.amount : ""
|
|
12711
|
+
};
|
|
12712
|
+
}
|
|
12535
12713
|
function journalOnlyRow(transferId, entries) {
|
|
12536
12714
|
const certified = distinctOpCount(entries);
|
|
12537
12715
|
return {
|
|
@@ -12561,6 +12739,160 @@ function distinctOpCount(entries) {
|
|
|
12561
12739
|
return new Set(entries.map((entry) => entry.opIndex)).size;
|
|
12562
12740
|
}
|
|
12563
12741
|
|
|
12742
|
+
// modules/payments-v2/inventory/token-data.ts
|
|
12743
|
+
async function readTokenData(deps, tokenId) {
|
|
12744
|
+
if (deps.view.stateHashOf(tokenId) === void 0) {
|
|
12745
|
+
throw new SphereError(`Token ${tokenId} is not in inventory`, "VALIDATION_ERROR");
|
|
12746
|
+
}
|
|
12747
|
+
const bytes = (await deps.storagePort.getBlobs([tokenId])).get(tokenId);
|
|
12748
|
+
if (bytes === void 0) {
|
|
12749
|
+
throw new SphereError(`Token ${tokenId} has no blob in storage`, "STORAGE_ERROR");
|
|
12750
|
+
}
|
|
12751
|
+
return deps.engine.readTokenData(await deps.engine.decodeToken({ tokenId, token: bytes }));
|
|
12752
|
+
}
|
|
12753
|
+
|
|
12754
|
+
// modules/payments-v2/machine/payload.ts
|
|
12755
|
+
function buildPayload(recipientPubkey, request, spend, spentStates) {
|
|
12756
|
+
return {
|
|
12757
|
+
v: 2,
|
|
12758
|
+
kind: "coin",
|
|
12759
|
+
recipient: recipientPubkey,
|
|
12760
|
+
coinId: request.coinId,
|
|
12761
|
+
amount: request.amount,
|
|
12762
|
+
...request.memo !== void 0 ? { memo: request.memo } : {},
|
|
12763
|
+
direct: [...spend.plan.direct],
|
|
12764
|
+
...spend.plan.split !== void 0 ? {
|
|
12765
|
+
split: {
|
|
12766
|
+
tokenId: spend.plan.split.tokenId,
|
|
12767
|
+
splitAmount: spend.plan.split.splitAmount.toString(),
|
|
12768
|
+
remainderAmount: spend.plan.split.remainderAmount.toString()
|
|
12769
|
+
}
|
|
12770
|
+
} : {},
|
|
12771
|
+
spentStates
|
|
12772
|
+
};
|
|
12773
|
+
}
|
|
12774
|
+
function messageOf(err) {
|
|
12775
|
+
return err instanceof Error ? err.message : String(err);
|
|
12776
|
+
}
|
|
12777
|
+
function buildCoinlessPayload(recipientPubkey, request, spentStates) {
|
|
12778
|
+
return {
|
|
12779
|
+
v: 2,
|
|
12780
|
+
kind: "coinless",
|
|
12781
|
+
recipient: recipientPubkey,
|
|
12782
|
+
...request.memo !== void 0 ? { memo: request.memo } : {},
|
|
12783
|
+
direct: [request.tokenId],
|
|
12784
|
+
spentStates
|
|
12785
|
+
};
|
|
12786
|
+
}
|
|
12787
|
+
|
|
12788
|
+
// modules/payments-v2/mint-params.ts
|
|
12789
|
+
function mintParams(recipientPubkey, coinId, amount) {
|
|
12790
|
+
return { recipientPubkey, value: { assets: [{ coinId, amount }] } };
|
|
12791
|
+
}
|
|
12792
|
+
|
|
12793
|
+
// modules/payments-v2/mint.ts
|
|
12794
|
+
function runMintUnderJournal(deps, input) {
|
|
12795
|
+
return mintUnderJournal(deps, input);
|
|
12796
|
+
}
|
|
12797
|
+
async function mintUnderJournal(deps, input) {
|
|
12798
|
+
const { mintId, coinId, amount } = input;
|
|
12799
|
+
const engine = deps.engine;
|
|
12800
|
+
const entry = {
|
|
12801
|
+
mintId,
|
|
12802
|
+
coinId,
|
|
12803
|
+
amount: amount.toString(),
|
|
12804
|
+
tokenId: "",
|
|
12805
|
+
createdAt: deps.now()
|
|
12806
|
+
};
|
|
12807
|
+
await deps.mintJournal.upsert(entry);
|
|
12808
|
+
let token;
|
|
12809
|
+
try {
|
|
12810
|
+
token = await engine.mint(mintParams(deps.ownPubkeyBytes, coinId, amount), { transferId: mintId });
|
|
12811
|
+
} catch (err) {
|
|
12812
|
+
deps.armHeartbeat();
|
|
12813
|
+
return { success: false, error: messageOf(err) };
|
|
12814
|
+
}
|
|
12815
|
+
if (token.blob.tokenId !== entry.tokenId) {
|
|
12816
|
+
await deps.mintJournal.upsert({ ...entry, tokenId: token.blob.tokenId });
|
|
12817
|
+
}
|
|
12818
|
+
try {
|
|
12819
|
+
await finalizeMint(deps, mintId, token, coinId, amount.toString());
|
|
12820
|
+
} catch (err) {
|
|
12821
|
+
deps.armHeartbeat();
|
|
12822
|
+
return { success: false, tokenId: token.blob.tokenId, error: messageOf(err) };
|
|
12823
|
+
}
|
|
12824
|
+
return { success: true, tokenId: token.blob.tokenId };
|
|
12825
|
+
}
|
|
12826
|
+
async function finalizeMint(deps, mintId, token, coinId, amount) {
|
|
12827
|
+
const engine = deps.engine;
|
|
12828
|
+
const bytes = token.blob.token;
|
|
12829
|
+
const digest = bytesToHex3(sha256(bytes));
|
|
12830
|
+
const keys = await deps.storagePort.uploadBlobs([{ sha256: digest, bytes }]);
|
|
12831
|
+
const key = keys.get(digest);
|
|
12832
|
+
if (key === void 0) {
|
|
12833
|
+
throw new SphereError(`no upload key returned for mint blob ${digest}`, "STORAGE_ERROR");
|
|
12834
|
+
}
|
|
12835
|
+
await deps.storagePort.applyDelta({
|
|
12836
|
+
transferId: mintId,
|
|
12837
|
+
spent: [],
|
|
12838
|
+
added: [{ tokenId: token.blob.tokenId, key }]
|
|
12839
|
+
});
|
|
12840
|
+
await deps.recordMint({
|
|
12841
|
+
tokenId: token.blob.tokenId,
|
|
12842
|
+
assets: [{ coinId, amount }]
|
|
12843
|
+
});
|
|
12844
|
+
await deps.mintJournal.removeByKey(mintId);
|
|
12845
|
+
deps.noteHeldState(token.blob.tokenId, (await engine.deliveryKeys(bytes)).stateHash);
|
|
12846
|
+
deps.refreshView();
|
|
12847
|
+
}
|
|
12848
|
+
|
|
12849
|
+
// modules/payments-v2/send-coinless.ts
|
|
12850
|
+
async function materializeCoinlessSpend(deps, input) {
|
|
12851
|
+
const { engine } = deps;
|
|
12852
|
+
const tokenId = input.request.tokenId;
|
|
12853
|
+
const bytes = (await deps.storagePort.getBlobs([tokenId])).get(tokenId);
|
|
12854
|
+
if (bytes === void 0) {
|
|
12855
|
+
throw new SphereError(`Selected source ${tokenId} has no blob in storage`, "STORAGE_ERROR");
|
|
12856
|
+
}
|
|
12857
|
+
const token = await engine.decodeToken({ tokenId, token: bytes });
|
|
12858
|
+
if (!isCoinlessEnvelope(token.valueEnvelope)) {
|
|
12859
|
+
throw new SphereError(
|
|
12860
|
+
`Token ${tokenId} carries coin value and cannot be sent with sendCoinless \u2014 use send()`,
|
|
12861
|
+
"VALIDATION_ERROR"
|
|
12862
|
+
);
|
|
12863
|
+
}
|
|
12864
|
+
const keys = await engine.deliveryKeys(bytes);
|
|
12865
|
+
const payload = buildCoinlessPayload(input.recipientPubkey, input.request, {
|
|
12866
|
+
[tokenId]: { local: keys.stateHash, protocol: keys.stateHash }
|
|
12867
|
+
});
|
|
12868
|
+
const plan = {
|
|
12869
|
+
transferId: input.transferId,
|
|
12870
|
+
recipientPubkey: input.recipientPubkey,
|
|
12871
|
+
payload,
|
|
12872
|
+
ops: buildOps(payload),
|
|
12873
|
+
sources: /* @__PURE__ */ new Map([[tokenId, token]]),
|
|
12874
|
+
...input.request.memo !== void 0 ? { memo: input.request.memo } : {}
|
|
12875
|
+
};
|
|
12876
|
+
return { transferId: input.transferId, coinId: "", plan, sourceIds: input.sourceIds, sourceTokens: [] };
|
|
12877
|
+
}
|
|
12878
|
+
|
|
12879
|
+
// modules/payments-v2/send-errors.ts
|
|
12880
|
+
function partialize(err, run) {
|
|
12881
|
+
if (run.delivered.length === 0) return err;
|
|
12882
|
+
return new PartialSendConflictError(
|
|
12883
|
+
"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.",
|
|
12884
|
+
run.firstPartialId ?? "",
|
|
12885
|
+
[...run.delivered],
|
|
12886
|
+
run.amount,
|
|
12887
|
+
err
|
|
12888
|
+
);
|
|
12889
|
+
}
|
|
12890
|
+
function stampTransferId(err, transferId) {
|
|
12891
|
+
if (err instanceof SphereError && isPossiblyCommittedSendOutcome(err) && err.transferId === void 0) {
|
|
12892
|
+
err.transferId = transferId;
|
|
12893
|
+
}
|
|
12894
|
+
}
|
|
12895
|
+
|
|
12564
12896
|
// modules/payments-v2/recipient.ts
|
|
12565
12897
|
var ATTENTION_RECIPIENT_NETWORK_UNVERIFIED = "recipient:network-unverified";
|
|
12566
12898
|
async function requireSameNetworkRecipient(deps, identifier) {
|
|
@@ -12609,11 +12941,6 @@ async function splitOpIndex(deps, envelope) {
|
|
|
12609
12941
|
return Array.isArray(payload.direct) ? payload.direct.length : 0;
|
|
12610
12942
|
}
|
|
12611
12943
|
|
|
12612
|
-
// modules/payments-v2/mint-params.ts
|
|
12613
|
-
function mintParams(recipientPubkey, coinId, amount) {
|
|
12614
|
-
return { recipientPubkey, value: { assets: [{ coinId, amount }] } };
|
|
12615
|
-
}
|
|
12616
|
-
|
|
12617
12944
|
// modules/payments-v2/prewarm-cache.ts
|
|
12618
12945
|
var PrewarmCache = class _PrewarmCache {
|
|
12619
12946
|
entries = /* @__PURE__ */ new Map();
|
|
@@ -12787,30 +13114,15 @@ function transferringToken(tokenId, coinId, amount, registry, now) {
|
|
|
12787
13114
|
});
|
|
12788
13115
|
}
|
|
12789
13116
|
|
|
12790
|
-
// modules/payments-v2/
|
|
12791
|
-
function
|
|
12792
|
-
return {
|
|
12793
|
-
v: 2,
|
|
12794
|
-
recipient: recipientPubkey,
|
|
12795
|
-
coinId: request.coinId,
|
|
12796
|
-
amount: request.amount,
|
|
12797
|
-
...request.memo !== void 0 ? { memo: request.memo } : {},
|
|
12798
|
-
direct: [...spend.plan.direct],
|
|
12799
|
-
...spend.plan.split !== void 0 ? {
|
|
12800
|
-
split: {
|
|
12801
|
-
tokenId: spend.plan.split.tokenId,
|
|
12802
|
-
splitAmount: spend.plan.split.splitAmount.toString(),
|
|
12803
|
-
remainderAmount: spend.plan.split.remainderAmount.toString()
|
|
12804
|
-
}
|
|
12805
|
-
} : {},
|
|
12806
|
-
spentStates
|
|
12807
|
-
};
|
|
13117
|
+
// modules/payments-v2/history/History.ts
|
|
13118
|
+
function wireAssets(assets) {
|
|
13119
|
+
return assets.map((a) => ({ coinId: a.coinId, amount: a.amount }));
|
|
12808
13120
|
}
|
|
12809
|
-
function
|
|
12810
|
-
|
|
13121
|
+
function isPermanentReject(err) {
|
|
13122
|
+
const status = err?.status;
|
|
13123
|
+
if (typeof status !== "number") return false;
|
|
13124
|
+
return status >= 400 && status < 500 && status !== 408 && status !== 429;
|
|
12811
13125
|
}
|
|
12812
|
-
|
|
12813
|
-
// modules/payments-v2/history/History.ts
|
|
12814
13126
|
var WIRE_TOKEN_ID = /^(?:[0-9a-f]{2}){1,64}$/;
|
|
12815
13127
|
var WIRE_PUBKEY = /^0[23][0-9a-f]{64}$/;
|
|
12816
13128
|
var History = class {
|
|
@@ -12835,7 +13147,7 @@ var History = class {
|
|
|
12835
13147
|
dedupKey: input.transferId,
|
|
12836
13148
|
id: (this.deps.newId ?? randomUUID)(),
|
|
12837
13149
|
type: "SENT",
|
|
12838
|
-
assets:
|
|
13150
|
+
assets: wireAssets(input.assets),
|
|
12839
13151
|
ts: this.ts(input.timestamp),
|
|
12840
13152
|
transferId: input.transferId,
|
|
12841
13153
|
...this.wireTokenId(input.tokenId),
|
|
@@ -12848,7 +13160,7 @@ var History = class {
|
|
|
12848
13160
|
dedupKey: `RECEIVED:${input.tokenId.toLowerCase()}:${input.stateHash.toLowerCase()}`,
|
|
12849
13161
|
id: (this.deps.newId ?? randomUUID)(),
|
|
12850
13162
|
type: "RECEIVED",
|
|
12851
|
-
assets:
|
|
13163
|
+
assets: wireAssets(input.assets),
|
|
12852
13164
|
ts: this.ts(input.timestamp),
|
|
12853
13165
|
...input.transferId !== void 0 ? { transferId: input.transferId } : {},
|
|
12854
13166
|
...this.wireTokenId(input.tokenId),
|
|
@@ -12861,7 +13173,7 @@ var History = class {
|
|
|
12861
13173
|
dedupKey: `MINT:${input.tokenId.toLowerCase()}`,
|
|
12862
13174
|
id: (this.deps.newId ?? randomUUID)(),
|
|
12863
13175
|
type: "MINT",
|
|
12864
|
-
assets:
|
|
13176
|
+
assets: wireAssets(input.assets),
|
|
12865
13177
|
ts: this.ts(input.timestamp),
|
|
12866
13178
|
...this.wireTokenId(input.tokenId)
|
|
12867
13179
|
}));
|
|
@@ -12872,7 +13184,10 @@ var History = class {
|
|
|
12872
13184
|
try {
|
|
12873
13185
|
await this.deps.client.postHistory([record]);
|
|
12874
13186
|
} catch (err) {
|
|
12875
|
-
this.deps.log?.(
|
|
13187
|
+
this.deps.log?.(
|
|
13188
|
+
isPermanentReject(err) ? "history POST REJECTED \u2014 the record shape is refused; no retry can fix it" : "history POST failed (dedupKey makes retry safe)",
|
|
13189
|
+
err
|
|
13190
|
+
);
|
|
12876
13191
|
return;
|
|
12877
13192
|
}
|
|
12878
13193
|
this.deps.emit("history:updated", this.toEntry(record));
|
|
@@ -12939,6 +13254,13 @@ var CURSOR_KEY = STORE_KEYS.streamCursor("inventory");
|
|
|
12939
13254
|
function stateKey(tokenId, stateHash) {
|
|
12940
13255
|
return `${tokenId}:${stateHash}`;
|
|
12941
13256
|
}
|
|
13257
|
+
function unchanged(prev, item, tokenType) {
|
|
13258
|
+
return prev.seq === item.seq && prev.status === item.status && prev.stateHash === item.stateHash && tokenType === prev.tokenType;
|
|
13259
|
+
}
|
|
13260
|
+
function isCoinless(item, prev) {
|
|
13261
|
+
if (item.status !== "active") return prev?.coinless ?? false;
|
|
13262
|
+
return (item.assets?.length ?? 0) === 0;
|
|
13263
|
+
}
|
|
12942
13264
|
var InventoryView = class {
|
|
12943
13265
|
constructor(deps) {
|
|
12944
13266
|
this.deps = deps;
|
|
@@ -13032,14 +13354,26 @@ var InventoryView = class {
|
|
|
13032
13354
|
pool(coinId) {
|
|
13033
13355
|
const out = [];
|
|
13034
13356
|
for (const [tokenId, entry] of this.mirror) {
|
|
13035
|
-
if (
|
|
13036
|
-
if (this.inFlight.has(tokenId) && !this.pinned(tokenId)) continue;
|
|
13037
|
-
if (this.suspected.has(stateKey(tokenId, entry.stateHash))) continue;
|
|
13357
|
+
if (!this.isSpendable(tokenId, entry)) continue;
|
|
13038
13358
|
const asset = entry.assets.find((a) => a.coinId === coinId);
|
|
13039
13359
|
if (asset) out.push({ tokenId, amount: BigInt(asset.amount) });
|
|
13040
13360
|
}
|
|
13041
13361
|
return out;
|
|
13042
13362
|
}
|
|
13363
|
+
/**
|
|
13364
|
+
* The eligibility gates every spend shares, coin or token — ONE definition, so a
|
|
13365
|
+
* single probe covers both verbs and neither can drift from the other.
|
|
13366
|
+
*/
|
|
13367
|
+
isSpendable(tokenId, entry) {
|
|
13368
|
+
if (entry.status !== "active") return false;
|
|
13369
|
+
if (this.inFlight.has(tokenId) && !this.pinned(tokenId)) return false;
|
|
13370
|
+
return !this.suspected.has(stateKey(tokenId, entry.stateHash));
|
|
13371
|
+
}
|
|
13372
|
+
/** #777: is this NAMED token a spendable COINLESS holding? Never a coin source. */
|
|
13373
|
+
spendableCoinless(tokenId) {
|
|
13374
|
+
const entry = this.mirror.get(tokenId);
|
|
13375
|
+
return entry !== void 0 && entry.coinless && this.isSpendable(tokenId, entry);
|
|
13376
|
+
}
|
|
13043
13377
|
/**
|
|
13044
13378
|
* Current mirrored state of an ACTIVE token, or undefined when it is unknown
|
|
13045
13379
|
* or tombstoned. Exists so a cached blob can be state-scoped (F6): a blob is
|
|
@@ -13065,6 +13399,29 @@ var InventoryView = class {
|
|
|
13065
13399
|
}
|
|
13066
13400
|
return out;
|
|
13067
13401
|
}
|
|
13402
|
+
/**
|
|
13403
|
+
* Coinless holdings (#777) — DISJOINT from tokens(): an entry is coinless
|
|
13404
|
+
* exactly when it is not, so no mirror row can appear in both reads.
|
|
13405
|
+
*/
|
|
13406
|
+
coinless(registry) {
|
|
13407
|
+
const out = [];
|
|
13408
|
+
for (const [tokenId, entry] of this.mirror) {
|
|
13409
|
+
if (entry.status !== "active" || !entry.coinless) continue;
|
|
13410
|
+
const meta = entry.tokenType !== void 0 ? registry.getTypeMeta?.(entry.tokenType) : null;
|
|
13411
|
+
out.push({
|
|
13412
|
+
tokenId,
|
|
13413
|
+
...entry.tokenType !== void 0 ? { tokenType: entry.tokenType } : {},
|
|
13414
|
+
...meta ? { name: meta.name } : {},
|
|
13415
|
+
...meta?.iconUrl != null ? { iconUrl: meta.iconUrl } : {},
|
|
13416
|
+
stateHash: entry.stateHash,
|
|
13417
|
+
transferring: this.held(tokenId),
|
|
13418
|
+
...this.suspected.has(stateKey(tokenId, entry.stateHash)) ? { suspectedSpent: true } : {},
|
|
13419
|
+
createdAt: entry.createdAt,
|
|
13420
|
+
updatedAt: entry.updatedAt
|
|
13421
|
+
});
|
|
13422
|
+
}
|
|
13423
|
+
return out;
|
|
13424
|
+
}
|
|
13068
13425
|
async assets(registry, price) {
|
|
13069
13426
|
const raw = aggregateAssets(this.activeEntries(), (tokenId) => this.held(tokenId), registry);
|
|
13070
13427
|
if (!price || raw.length === 0) return raw;
|
|
@@ -13136,14 +13493,19 @@ var InventoryView = class {
|
|
|
13136
13493
|
applyOne(item, now) {
|
|
13137
13494
|
const prev = this.mirror.get(item.tokenId);
|
|
13138
13495
|
if (prev && item.seq < prev.seq) return false;
|
|
13139
|
-
|
|
13140
|
-
|
|
13141
|
-
}
|
|
13496
|
+
const tokenType = item.tokenType ?? prev?.tokenType;
|
|
13497
|
+
if (prev && unchanged(prev, item, tokenType)) return false;
|
|
13142
13498
|
this.mirror.set(item.tokenId, {
|
|
13143
13499
|
stateHash: item.stateHash,
|
|
13144
13500
|
seq: item.seq,
|
|
13145
13501
|
status: item.status,
|
|
13146
|
-
|
|
13502
|
+
// ONLY a tombstone inherits: it omits assets for an unrelated reason and
|
|
13503
|
+
// recoverRemoved must know the amount it restores. An ACTIVE row that omits
|
|
13504
|
+
// them is stating coinlessness, and inheriting there would keep stale assets
|
|
13505
|
+
// in tokens() while `coinless` is true — the row in BOTH reads (§16).
|
|
13506
|
+
assets: item.assets ?? (item.status === "removed" ? prev?.assets ?? [] : []),
|
|
13507
|
+
coinless: isCoinless(item, prev),
|
|
13508
|
+
...tokenType !== void 0 ? { tokenType } : {},
|
|
13147
13509
|
createdAt: prev?.createdAt ?? now,
|
|
13148
13510
|
updatedAt: now
|
|
13149
13511
|
});
|
|
@@ -13379,7 +13741,12 @@ async function screen(deps, engine, entry) {
|
|
|
13379
13741
|
}
|
|
13380
13742
|
return {
|
|
13381
13743
|
kind: "accept",
|
|
13382
|
-
record: {
|
|
13744
|
+
record: {
|
|
13745
|
+
tokenId: keys.tokenId,
|
|
13746
|
+
stateHash: keys.stateHash,
|
|
13747
|
+
assets: toAssetAmounts(token),
|
|
13748
|
+
...isCoinlessEnvelope2(token.valueEnvelope) ? { tokenType: token.tokenType } : {}
|
|
13749
|
+
}
|
|
13383
13750
|
};
|
|
13384
13751
|
}
|
|
13385
13752
|
async function announce(deps, entry, record) {
|
|
@@ -13390,6 +13757,7 @@ async function announce(deps, entry, record) {
|
|
|
13390
13757
|
tokenId: record.tokenId,
|
|
13391
13758
|
stateHash: record.stateHash,
|
|
13392
13759
|
assets: record.assets,
|
|
13760
|
+
...record.tokenType !== void 0 ? { tokenType: record.tokenType } : {},
|
|
13393
13761
|
...entry.senderPubkey !== void 0 ? { senderPubkey: entry.senderPubkey } : {},
|
|
13394
13762
|
...entry.senderNametag !== void 0 ? { senderNametag: entry.senderNametag } : {},
|
|
13395
13763
|
...entry.memo !== void 0 ? { memo: entry.memo } : {},
|
|
@@ -13403,6 +13771,19 @@ async function announce(deps, entry, record) {
|
|
|
13403
13771
|
senderPubkey: entry.senderPubkey ?? "",
|
|
13404
13772
|
...entry.senderNametag !== void 0 ? { senderNametag: entry.senderNametag } : {},
|
|
13405
13773
|
tokens: record.assets.map((asset) => toUiToken(record.tokenId, asset, deps.registry, receivedAt)),
|
|
13774
|
+
// #777: named here rather than mapped from assets, which announced an EMPTY list.
|
|
13775
|
+
...record.tokenType !== void 0 ? {
|
|
13776
|
+
coinless: [
|
|
13777
|
+
{
|
|
13778
|
+
tokenId: record.tokenId,
|
|
13779
|
+
tokenType: record.tokenType,
|
|
13780
|
+
stateHash: record.stateHash,
|
|
13781
|
+
transferring: false,
|
|
13782
|
+
createdAt: receivedAt,
|
|
13783
|
+
updatedAt: receivedAt
|
|
13784
|
+
}
|
|
13785
|
+
]
|
|
13786
|
+
} : {},
|
|
13406
13787
|
...entry.memo !== void 0 ? { memo: entry.memo } : {},
|
|
13407
13788
|
receivedAt
|
|
13408
13789
|
};
|
|
@@ -13501,6 +13882,9 @@ function claimAck(entry) {
|
|
|
13501
13882
|
function rejectAck(entry, reason) {
|
|
13502
13883
|
return { deliveryId: entry.deliveryId, disposition: "rejected", reason, cursor: entry.cursor };
|
|
13503
13884
|
}
|
|
13885
|
+
function isCoinlessEnvelope2(envelope) {
|
|
13886
|
+
return envelope.startsWith("none_");
|
|
13887
|
+
}
|
|
13504
13888
|
function toAssetAmounts(token) {
|
|
13505
13889
|
return (token.value?.assets ?? []).map((asset) => ({
|
|
13506
13890
|
coinId: asset.coinId,
|
|
@@ -14068,6 +14452,37 @@ var SpendQueue = class {
|
|
|
14068
14452
|
}
|
|
14069
14453
|
return { kind: "queued", settled: this.enqueue(reservationId, request.coinId, amount) };
|
|
14070
14454
|
}
|
|
14455
|
+
/**
|
|
14456
|
+
* #777: reserve a NAMED coinless source. Never queues — there is nothing to wait
|
|
14457
|
+
* for. A coin plan waits because some OTHER combination may free up; a named
|
|
14458
|
+
* token is either free now or held by a reservation that only its own transfer
|
|
14459
|
+
* can release, so queueing would block until a timeout on a spend that cannot
|
|
14460
|
+
* become possible.
|
|
14461
|
+
*/
|
|
14462
|
+
planCoinless(reservationId, tokenId) {
|
|
14463
|
+
if (this.destroyed) {
|
|
14464
|
+
throw new SphereError("Module has been destroyed", "MODULE_DESTROYED");
|
|
14465
|
+
}
|
|
14466
|
+
const unproven = this.deps.ledger.unprovenReason();
|
|
14467
|
+
if (unproven !== null) {
|
|
14468
|
+
throw new SphereError(`Cannot spend yet: ${unproven}`, "SEND_SYNC_PENDING");
|
|
14469
|
+
}
|
|
14470
|
+
if (this.deps.spendableCoinless?.(tokenId) !== true) {
|
|
14471
|
+
throw new SphereError(
|
|
14472
|
+
`Token ${tokenId} is not a spendable coinless holding`,
|
|
14473
|
+
"VALIDATION_ERROR"
|
|
14474
|
+
);
|
|
14475
|
+
}
|
|
14476
|
+
const holder = this.deps.ledger.holderOf(tokenId);
|
|
14477
|
+
if (holder !== void 0) {
|
|
14478
|
+
throw new SphereError(
|
|
14479
|
+
`Token ${tokenId} is already reserved by transfer ${holder}`,
|
|
14480
|
+
"VALIDATION_ERROR"
|
|
14481
|
+
);
|
|
14482
|
+
}
|
|
14483
|
+
this.deps.ledger.reserve(reservationId, [{ tokenId }]);
|
|
14484
|
+
return { reservationId, plan: { direct: [tokenId] } };
|
|
14485
|
+
}
|
|
14071
14486
|
/**
|
|
14072
14487
|
* The sources `plan()` WOULD pick right now, reserving nothing and queueing
|
|
14073
14488
|
* nothing — a read-only probe used to warm blobs while the user is still
|
|
@@ -14257,6 +14672,7 @@ function composeFacadeParts(deps, hooks) {
|
|
|
14257
14672
|
const queue = new SpendQueue({
|
|
14258
14673
|
ledger,
|
|
14259
14674
|
getPool: (coinId) => view.pool(coinId),
|
|
14675
|
+
spendableCoinless: (tokenId) => view.spendableCoinless(tokenId),
|
|
14260
14676
|
...deps.workBudget !== void 0 ? { workBudget: deps.workBudget } : {}
|
|
14261
14677
|
});
|
|
14262
14678
|
const historyStore = new History({
|
|
@@ -14340,10 +14756,9 @@ function buildMachineDeps(deps, hooks, historyStore) {
|
|
|
14340
14756
|
recordHistory: async ({ transferId, payload, committedAmount }) => {
|
|
14341
14757
|
await historyStore.recordSent({
|
|
14342
14758
|
transferId,
|
|
14343
|
-
|
|
14344
|
-
//
|
|
14345
|
-
|
|
14346
|
-
amount: committedAmount,
|
|
14759
|
+
// §5.9: the SETTLED amount, never payload.amount — the plan. A token
|
|
14760
|
+
// spend moved no coin: `assets: []` + tokenId (wallet-api#151 / §10).
|
|
14761
|
+
...payload.kind === "coinless" ? { assets: [], tokenId: payload.direct[0] } : { assets: [{ coinId: payload.coinId, amount: committedAmount }] },
|
|
14347
14762
|
recipientPubkey: payload.recipient,
|
|
14348
14763
|
...payload.memo !== void 0 ? { memo: payload.memo } : {}
|
|
14349
14764
|
});
|
|
@@ -14378,12 +14793,10 @@ function buildReceive(deps, hooks, historyStore, heldStates, refreshView) {
|
|
|
14378
14793
|
});
|
|
14379
14794
|
}
|
|
14380
14795
|
async function recordReceived(historyStore, record) {
|
|
14381
|
-
const first = record.assets[0];
|
|
14382
14796
|
await historyStore.recordReceived({
|
|
14383
14797
|
tokenId: record.tokenId,
|
|
14384
14798
|
stateHash: record.stateHash,
|
|
14385
|
-
|
|
14386
|
-
amount: first?.amount ?? "0",
|
|
14799
|
+
assets: record.assets,
|
|
14387
14800
|
...record.senderPubkey !== void 0 ? { senderPubkey: record.senderPubkey } : {},
|
|
14388
14801
|
...record.senderNametag !== void 0 ? { senderNametag: record.senderNametag } : {},
|
|
14389
14802
|
...record.memo !== void 0 ? { memo: record.memo } : {},
|
|
@@ -14531,6 +14944,13 @@ var PaymentsFacade = class {
|
|
|
14531
14944
|
tokens(filter) {
|
|
14532
14945
|
return this.view.tokens(this.deps.registry, filter);
|
|
14533
14946
|
}
|
|
14947
|
+
coinless() {
|
|
14948
|
+
return this.view.coinless(this.deps.registry);
|
|
14949
|
+
}
|
|
14950
|
+
tokenData(tokenId) {
|
|
14951
|
+
const deps = { engine: this.engine(), view: this.view, storagePort: this.deps.storagePort };
|
|
14952
|
+
return readTokenData(deps, tokenId);
|
|
14953
|
+
}
|
|
14534
14954
|
history(page) {
|
|
14535
14955
|
return this.historyStore.page(page ?? {});
|
|
14536
14956
|
}
|
|
@@ -14555,6 +14975,9 @@ var PaymentsFacade = class {
|
|
|
14555
14975
|
send(request) {
|
|
14556
14976
|
return this.track(this.sendOutcome(request));
|
|
14557
14977
|
}
|
|
14978
|
+
sendCoinless(request) {
|
|
14979
|
+
return this.track(this.sendCoinlessOutcome(request));
|
|
14980
|
+
}
|
|
14558
14981
|
async receive() {
|
|
14559
14982
|
const transfers = await this.track(this.receiveLoop.drainOnce());
|
|
14560
14983
|
return { transfers };
|
|
@@ -14585,12 +15008,32 @@ var PaymentsFacade = class {
|
|
|
14585
15008
|
nowMs() {
|
|
14586
15009
|
return (this.deps.now ?? Date.now)();
|
|
14587
15010
|
}
|
|
15011
|
+
mintDeps() {
|
|
15012
|
+
return {
|
|
15013
|
+
engine: this.engine(),
|
|
15014
|
+
mintJournal: this.machineStores.mintJournal,
|
|
15015
|
+
storagePort: this.deps.storagePort,
|
|
15016
|
+
recordMint: (input) => this.historyStore.recordMint(input),
|
|
15017
|
+
armHeartbeat: () => this.heartbeat.arm(),
|
|
15018
|
+
noteHeldState: (tokenId, stateHash) => void this.heldStates.set(tokenId, stateHash),
|
|
15019
|
+
refreshView: () => this.trackTail(this.view.delta()),
|
|
15020
|
+
ownPubkeyBytes: this.ownPubkeyBytes,
|
|
15021
|
+
now: () => this.nowMs()
|
|
15022
|
+
};
|
|
15023
|
+
}
|
|
14588
15024
|
// ── send policy (§5.5 + old-loop parity; the machine stays policy-free) ────
|
|
14589
15025
|
/** The ONE place a send() outcome is shaped: success emits in finishSend, a
|
|
14590
15026
|
* CLEAN rejection emits `transfer:updated{status:'failed'}` here (§4). */
|
|
14591
|
-
|
|
15027
|
+
sendOutcome(request) {
|
|
15028
|
+
return this.runJob({ kind: "coin", request }, request.amount);
|
|
15029
|
+
}
|
|
15030
|
+
/** A token spend has no amount; '0' keeps the shortfall arithmetic total-free. */
|
|
15031
|
+
sendCoinlessOutcome(request) {
|
|
15032
|
+
return this.runJob({ kind: "coinless", request }, "0");
|
|
15033
|
+
}
|
|
15034
|
+
async runJob(job, amount) {
|
|
14592
15035
|
const run = {
|
|
14593
|
-
amount
|
|
15036
|
+
amount,
|
|
14594
15037
|
delivered: [],
|
|
14595
15038
|
sentTokens: [],
|
|
14596
15039
|
tokenTransfers: [],
|
|
@@ -14600,7 +15043,7 @@ var PaymentsFacade = class {
|
|
|
14600
15043
|
lastTransferId: ""
|
|
14601
15044
|
};
|
|
14602
15045
|
try {
|
|
14603
|
-
return await this.sendWithPolicy(
|
|
15046
|
+
return await this.sendWithPolicy(job, run);
|
|
14604
15047
|
} catch (err) {
|
|
14605
15048
|
this.emitCleanFailure(err, run.lastTransferId);
|
|
14606
15049
|
throw err;
|
|
@@ -14620,19 +15063,19 @@ var PaymentsFacade = class {
|
|
|
14620
15063
|
};
|
|
14621
15064
|
this.deps.emit("transfer:updated", failed);
|
|
14622
15065
|
}
|
|
14623
|
-
async sendWithPolicy(
|
|
14624
|
-
const recipient = await requireSameNetworkRecipient(this.deps, request.recipient);
|
|
15066
|
+
async sendWithPolicy(job, run) {
|
|
15067
|
+
const recipient = await requireSameNetworkRecipient(this.deps, job.request.recipient);
|
|
14625
15068
|
for (let attempt = 0; ; attempt++) {
|
|
14626
15069
|
let ctx;
|
|
14627
15070
|
try {
|
|
14628
|
-
ctx = await this.planAndMaterialize(recipient.chainPubkey,
|
|
15071
|
+
ctx = await this.planAndMaterialize(recipient.chainPubkey, job, run);
|
|
14629
15072
|
} catch (err) {
|
|
14630
|
-
throw
|
|
15073
|
+
throw partialize(err, run);
|
|
14631
15074
|
}
|
|
14632
15075
|
const disposition = await this.runAttempt(ctx);
|
|
14633
|
-
if (disposition.kind === "rethrow") throw
|
|
15076
|
+
if (disposition.kind === "rethrow") throw partialize(disposition.error, run);
|
|
14634
15077
|
if (disposition.kind === "retry-full") {
|
|
14635
|
-
if (attempt >= MAX_RESELECT) throw
|
|
15078
|
+
if (job.kind === "coinless" || attempt >= MAX_RESELECT) throw partialize(disposition.error, run);
|
|
14636
15079
|
continue;
|
|
14637
15080
|
}
|
|
14638
15081
|
if (disposition.kind === "success") {
|
|
@@ -14688,7 +15131,7 @@ var PaymentsFacade = class {
|
|
|
14688
15131
|
async disposeFailedAttempt(ctx, err) {
|
|
14689
15132
|
if (isPossiblyCommittedSendOutcome(err)) {
|
|
14690
15133
|
this.settleKeepOpen(ctx);
|
|
14691
|
-
|
|
15134
|
+
stampTransferId(err, ctx.transferId);
|
|
14692
15135
|
return { kind: "rethrow", error: err };
|
|
14693
15136
|
}
|
|
14694
15137
|
const backstop = await this.machineStores.backstop.getByKey(ctx.transferId);
|
|
@@ -14722,7 +15165,7 @@ var PaymentsFacade = class {
|
|
|
14722
15165
|
async disposeConvergeFailure(ctx, err) {
|
|
14723
15166
|
if (isPossiblyCommittedSendOutcome(err)) {
|
|
14724
15167
|
this.settleKeepOpen(ctx);
|
|
14725
|
-
|
|
15168
|
+
stampTransferId(err, ctx.transferId);
|
|
14726
15169
|
return { kind: "rethrow", error: err };
|
|
14727
15170
|
}
|
|
14728
15171
|
if (classifyError(err) === "conflict") {
|
|
@@ -14733,9 +15176,23 @@ var PaymentsFacade = class {
|
|
|
14733
15176
|
}
|
|
14734
15177
|
return { kind: "rethrow", error: err };
|
|
14735
15178
|
}
|
|
14736
|
-
async planAndMaterialize(recipientPubkey,
|
|
15179
|
+
async planAndMaterialize(recipientPubkey, job, run) {
|
|
14737
15180
|
const transferId = this.newId();
|
|
14738
15181
|
run.lastTransferId = transferId;
|
|
15182
|
+
if (job.kind === "coinless") {
|
|
15183
|
+
const spend2 = this.queue.planCoinless(transferId, job.request.tokenId);
|
|
15184
|
+
const sourceIds2 = this.markPlanned(transferId, "", spend2);
|
|
15185
|
+
try {
|
|
15186
|
+
return await materializeCoinlessSpend(
|
|
15187
|
+
{ engine: this.engine(), storagePort: this.deps.storagePort },
|
|
15188
|
+
{ transferId, recipientPubkey, request: job.request, sourceIds: sourceIds2 }
|
|
15189
|
+
);
|
|
15190
|
+
} catch (err) {
|
|
15191
|
+
this.settleFailure(transferId, "", sourceIds2);
|
|
15192
|
+
throw err;
|
|
15193
|
+
}
|
|
15194
|
+
}
|
|
15195
|
+
const request = { ...job.request, amount: run.amount };
|
|
14739
15196
|
const planned = this.queue.plan(transferId, { coinId: request.coinId, amount: request.amount });
|
|
14740
15197
|
const spend = planned.kind === "planned" ? planned.spend : await planned.settled;
|
|
14741
15198
|
const sourceIds = this.markPlanned(transferId, request.coinId, spend);
|
|
@@ -14869,22 +15326,6 @@ var PaymentsFacade = class {
|
|
|
14869
15326
|
return result;
|
|
14870
15327
|
}
|
|
14871
15328
|
/** Nothing delivered → UNWRAPPED; after ≥1 delivered leg every failure surfaces as PartialSendConflictError over the settled set. */
|
|
14872
|
-
partialize(err, run) {
|
|
14873
|
-
if (run.delivered.length === 0) return err;
|
|
14874
|
-
return new PartialSendConflictError(
|
|
14875
|
-
"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.",
|
|
14876
|
-
run.firstPartialId ?? "",
|
|
14877
|
-
run.delivered,
|
|
14878
|
-
run.amount,
|
|
14879
|
-
err
|
|
14880
|
-
);
|
|
14881
|
-
}
|
|
14882
|
-
/** #441: possibly-committed errors must carry the transferId for the settling journal. */
|
|
14883
|
-
stampTransferId(err, transferId) {
|
|
14884
|
-
if (err instanceof SphereError && isPossiblyCommittedSendOutcome(err) && err.transferId === void 0) {
|
|
14885
|
-
err.transferId = transferId;
|
|
14886
|
-
}
|
|
14887
|
-
}
|
|
14888
15329
|
async softAbort(transferId) {
|
|
14889
15330
|
try {
|
|
14890
15331
|
await this.deps.client.abortIntent(transferId);
|
|
@@ -14900,57 +15341,11 @@ var PaymentsFacade = class {
|
|
|
14900
15341
|
const mintId = this.newId();
|
|
14901
15342
|
this.activeMoneyOps.add(mintId);
|
|
14902
15343
|
try {
|
|
14903
|
-
return await this.
|
|
15344
|
+
return await runMintUnderJournal(this.mintDeps(), { mintId, coinId, amount });
|
|
14904
15345
|
} finally {
|
|
14905
15346
|
this.activeMoneyOps.delete(mintId);
|
|
14906
15347
|
}
|
|
14907
15348
|
}
|
|
14908
|
-
async mintUnderJournal(mintId, coinId, amount) {
|
|
14909
|
-
const engine = this.engine();
|
|
14910
|
-
const entry = {
|
|
14911
|
-
mintId,
|
|
14912
|
-
coinId,
|
|
14913
|
-
amount: amount.toString(),
|
|
14914
|
-
tokenId: "",
|
|
14915
|
-
createdAt: this.nowMs()
|
|
14916
|
-
};
|
|
14917
|
-
await this.machineStores.mintJournal.upsert(entry);
|
|
14918
|
-
let token;
|
|
14919
|
-
try {
|
|
14920
|
-
token = await engine.mint(mintParams(this.ownPubkeyBytes, coinId, amount), { transferId: mintId });
|
|
14921
|
-
} catch (err) {
|
|
14922
|
-
this.heartbeat.arm();
|
|
14923
|
-
return { success: false, error: messageOf(err) };
|
|
14924
|
-
}
|
|
14925
|
-
if (token.blob.tokenId !== entry.tokenId) {
|
|
14926
|
-
await this.machineStores.mintJournal.upsert({ ...entry, tokenId: token.blob.tokenId });
|
|
14927
|
-
}
|
|
14928
|
-
try {
|
|
14929
|
-
await this.finalizeMint(engine, mintId, token, coinId, amount.toString());
|
|
14930
|
-
} catch (err) {
|
|
14931
|
-
this.heartbeat.arm();
|
|
14932
|
-
return { success: false, tokenId: token.blob.tokenId, error: messageOf(err) };
|
|
14933
|
-
}
|
|
14934
|
-
return { success: true, tokenId: token.blob.tokenId };
|
|
14935
|
-
}
|
|
14936
|
-
async finalizeMint(engine, mintId, token, coinId, amount) {
|
|
14937
|
-
const bytes = token.blob.token;
|
|
14938
|
-
const digest = bytesToHex3(sha256(bytes));
|
|
14939
|
-
const keys = await this.deps.storagePort.uploadBlobs([{ sha256: digest, bytes }]);
|
|
14940
|
-
const key = keys.get(digest);
|
|
14941
|
-
if (key === void 0) {
|
|
14942
|
-
throw new SphereError(`no upload key returned for mint blob ${digest}`, "STORAGE_ERROR");
|
|
14943
|
-
}
|
|
14944
|
-
await this.deps.storagePort.applyDelta({
|
|
14945
|
-
transferId: mintId,
|
|
14946
|
-
spent: [],
|
|
14947
|
-
added: [{ tokenId: token.blob.tokenId, key }]
|
|
14948
|
-
});
|
|
14949
|
-
await this.historyStore.recordMint({ tokenId: token.blob.tokenId, coinId, amount });
|
|
14950
|
-
await this.machineStores.mintJournal.removeByKey(mintId);
|
|
14951
|
-
this.heldStates.set(token.blob.tokenId, (await engine.deliveryKeys(bytes)).stateHash);
|
|
14952
|
-
this.trackTail(this.view.delta());
|
|
14953
|
-
}
|
|
14954
15349
|
/** @returns how many journal entries were RESOLVED (cleared) — heartbeat progress. */
|
|
14955
15350
|
async replayMints() {
|
|
14956
15351
|
let resolved = 0;
|
|
@@ -14982,7 +15377,7 @@ var PaymentsFacade = class {
|
|
|
14982
15377
|
if (token.blob.tokenId !== entry.tokenId) {
|
|
14983
15378
|
await this.machineStores.mintJournal.upsert({ ...entry, tokenId: token.blob.tokenId });
|
|
14984
15379
|
}
|
|
14985
|
-
await this.
|
|
15380
|
+
await finalizeMint(this.mintDeps(), entry.mintId, token, entry.coinId, entry.amount);
|
|
14986
15381
|
return true;
|
|
14987
15382
|
}
|
|
14988
15383
|
async tokenInServerInventory(tokenId) {
|
|
@@ -16757,7 +17152,8 @@ var Sphere = class _Sphere {
|
|
|
16757
17152
|
}
|
|
16758
17153
|
const progress = options.onProgress;
|
|
16759
17154
|
logger.debug("Sphere", "Starting import...");
|
|
16760
|
-
const
|
|
17155
|
+
const liveHere = _Sphere.liveOn(options.storage).some((s) => s._storage === options.storage);
|
|
17156
|
+
const needsClear = liveHere || await _Sphere.exists(options.storage);
|
|
16761
17157
|
if (needsClear) {
|
|
16762
17158
|
progress?.({ step: "clearing", message: "Clearing previous wallet data..." });
|
|
16763
17159
|
logger.debug("Sphere", "Clearing existing wallet data...");
|