@unicitylabs/sphere-sdk 0.16.0-dev.2 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/connect/index.cjs +11 -5
- package/dist/connect/index.cjs.map +1 -1
- package/dist/connect/index.d.cts +4 -1
- package/dist/connect/index.d.ts +4 -1
- package/dist/connect/index.js +11 -5
- package/dist/connect/index.js.map +1 -1
- package/dist/core/index.cjs +568 -172
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +67 -0
- package/dist/core/index.d.ts +67 -0
- package/dist/core/index.js +568 -172
- package/dist/core/index.js.map +1 -1
- package/dist/impl/browser/connect/index.cjs +11 -5
- package/dist/impl/browser/connect/index.cjs.map +1 -1
- package/dist/impl/browser/connect/index.d.cts +3 -1
- package/dist/impl/browser/connect/index.d.ts +3 -1
- package/dist/impl/browser/connect/index.js +11 -5
- package/dist/impl/browser/connect/index.js.map +1 -1
- package/dist/impl/browser/index.cjs +2 -2
- package/dist/impl/browser/index.cjs.map +1 -1
- package/dist/impl/browser/index.js +2 -2
- package/dist/impl/browser/index.js.map +1 -1
- package/dist/impl/nodejs/connect/index.cjs +10 -4
- package/dist/impl/nodejs/connect/index.cjs.map +1 -1
- package/dist/impl/nodejs/connect/index.d.cts +1 -1
- package/dist/impl/nodejs/connect/index.d.ts +1 -1
- package/dist/impl/nodejs/connect/index.js +10 -4
- package/dist/impl/nodejs/connect/index.js.map +1 -1
- package/dist/impl/nodejs/index.cjs +19 -3
- package/dist/impl/nodejs/index.cjs.map +1 -1
- package/dist/impl/nodejs/index.d.cts +11 -0
- package/dist/impl/nodejs/index.d.ts +11 -0
- package/dist/impl/nodejs/index.js +19 -3
- package/dist/impl/nodejs/index.js.map +1 -1
- package/dist/impl/shared/wallet-api/index.d.cts +11 -0
- package/dist/impl/shared/wallet-api/index.d.ts +11 -0
- package/dist/impl/wallet-api-v2/index.cjs +2 -0
- package/dist/impl/wallet-api-v2/index.cjs.map +1 -1
- package/dist/impl/wallet-api-v2/index.d.cts +13 -0
- package/dist/impl/wallet-api-v2/index.d.ts +13 -0
- package/dist/impl/wallet-api-v2/index.js +2 -0
- package/dist/impl/wallet-api-v2/index.js.map +1 -1
- package/dist/index.cjs +568 -172
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +61 -1
- package/dist/index.d.ts +61 -1
- package/dist/index.js +568 -172
- package/dist/index.js.map +1 -1
- package/dist/modules/payments-v2/index.cjs +609 -269
- package/dist/modules/payments-v2/index.cjs.map +1 -1
- package/dist/modules/payments-v2/index.d.cts +88 -13
- package/dist/modules/payments-v2/index.d.ts +88 -13
- package/dist/modules/payments-v2/index.js +609 -269
- package/dist/modules/payments-v2/index.js.map +1 -1
- package/dist/token-engine/index.cjs +132 -34
- package/dist/token-engine/index.cjs.map +1 -1
- package/dist/token-engine/index.d.cts +19 -0
- package/dist/token-engine/index.d.ts +19 -0
- package/dist/token-engine/index.js +132 -34
- package/dist/token-engine/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -8273,6 +8273,8 @@ var FETCH_TIMEOUT_MS = 1e4;
|
|
|
8273
8273
|
var TokenRegistry = class _TokenRegistry {
|
|
8274
8274
|
static instance = null;
|
|
8275
8275
|
definitionsById;
|
|
8276
|
+
/** Non-fungible definitions keyed by TOKEN TYPE — a separate namespace (#147). */
|
|
8277
|
+
definitionsByType;
|
|
8276
8278
|
definitionsBySymbol;
|
|
8277
8279
|
definitionsByName;
|
|
8278
8280
|
// Remote refresh state
|
|
@@ -8291,6 +8293,7 @@ var TokenRegistry = class _TokenRegistry {
|
|
|
8291
8293
|
inFlight = null;
|
|
8292
8294
|
constructor() {
|
|
8293
8295
|
this.definitionsById = /* @__PURE__ */ new Map();
|
|
8296
|
+
this.definitionsByType = /* @__PURE__ */ new Map();
|
|
8294
8297
|
this.definitionsBySymbol = /* @__PURE__ */ new Map();
|
|
8295
8298
|
this.definitionsByName = /* @__PURE__ */ new Map();
|
|
8296
8299
|
}
|
|
@@ -8518,9 +8521,11 @@ var TokenRegistry = class _TokenRegistry {
|
|
|
8518
8521
|
this.definitionsById.clear();
|
|
8519
8522
|
this.definitionsBySymbol.clear();
|
|
8520
8523
|
this.definitionsByName.clear();
|
|
8524
|
+
this.definitionsByType.clear();
|
|
8521
8525
|
for (const def of definitions) {
|
|
8522
8526
|
const idLower = def.id.toLowerCase();
|
|
8523
8527
|
this.definitionsById.set(idLower, def);
|
|
8528
|
+
if (def.assetKind === "non-fungible") this.definitionsByType.set(idLower, def);
|
|
8524
8529
|
if (def.symbol) {
|
|
8525
8530
|
this.definitionsBySymbol.set(def.symbol.toUpperCase(), def);
|
|
8526
8531
|
}
|
|
@@ -8734,6 +8739,20 @@ var TokenRegistry = class _TokenRegistry {
|
|
|
8734
8739
|
getAllDefinitions() {
|
|
8735
8740
|
return Array.from(this.definitionsById.values());
|
|
8736
8741
|
}
|
|
8742
|
+
/**
|
|
8743
|
+
* Definition for a coinless token's genesis TOKEN TYPE (#777). Never falls back
|
|
8744
|
+
* to `getDefinition`: a type and a coin id live in different namespaces, so a
|
|
8745
|
+
* type that collided with a coin id would otherwise render as that coin.
|
|
8746
|
+
*/
|
|
8747
|
+
getTypeMeta(tokenType) {
|
|
8748
|
+
const def = this.getTypeDefinition(tokenType);
|
|
8749
|
+
if (!def) return null;
|
|
8750
|
+
return { name: def.name, iconUrl: def.icons?.[0]?.url ?? null };
|
|
8751
|
+
}
|
|
8752
|
+
getTypeDefinition(tokenType) {
|
|
8753
|
+
if (!tokenType) return void 0;
|
|
8754
|
+
return this.definitionsByType.get(tokenType.toLowerCase());
|
|
8755
|
+
}
|
|
8737
8756
|
/**
|
|
8738
8757
|
* Get all fungible token definitions
|
|
8739
8758
|
* @returns Array of fungible token definitions
|
|
@@ -9568,6 +9587,8 @@ var import_DataHasherFactory = require("@unicitylabs/state-transition-sdk/lib/cr
|
|
|
9568
9587
|
var import_CborSerializer = require("@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborSerializer.js");
|
|
9569
9588
|
var import_CborDeserializer = require("@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborDeserializer.js");
|
|
9570
9589
|
var import_CborError = require("@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborError.js");
|
|
9590
|
+
var import_CborReader = require("@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborReader.js");
|
|
9591
|
+
var import_MajorType = require("@unicitylabs/state-transition-sdk/lib/serialization/cbor/MajorType.js");
|
|
9571
9592
|
var import_Asset = require("@unicitylabs/state-transition-sdk/lib/payment/asset/Asset.js");
|
|
9572
9593
|
var import_AssetId = require("@unicitylabs/state-transition-sdk/lib/payment/asset/AssetId.js");
|
|
9573
9594
|
var import_PaymentAssetCollection = require("@unicitylabs/state-transition-sdk/lib/payment/asset/PaymentAssetCollection.js");
|
|
@@ -9980,6 +10001,126 @@ async function burntTokenFromCheckpoint(deps, storedBytes, reDerivedBurnTx, sour
|
|
|
9980
10001
|
return burntToken;
|
|
9981
10002
|
}
|
|
9982
10003
|
|
|
10004
|
+
// token-engine/value-envelope.ts
|
|
10005
|
+
function isCoinlessEnvelope(envelope) {
|
|
10006
|
+
return envelope.startsWith("none_");
|
|
10007
|
+
}
|
|
10008
|
+
var SELF_DESCRIBED_CBOR_TAG = 55799n;
|
|
10009
|
+
var CBOR_MAJOR_TYPE_MASK = 224;
|
|
10010
|
+
var describe = (error) => error instanceof Error ? error.message : String(error);
|
|
10011
|
+
var invalid = (message) => new SphereError(message, "VALIDATION_ERROR");
|
|
10012
|
+
function majorTypeOf(bytes) {
|
|
10013
|
+
const first = bytes.at(0);
|
|
10014
|
+
return first === void 0 ? null : first & CBOR_MAJOR_TYPE_MASK;
|
|
10015
|
+
}
|
|
10016
|
+
function headTagNumber(bytes) {
|
|
10017
|
+
try {
|
|
10018
|
+
return new import_CborReader.CborReader(bytes).readLength(import_MajorType.MajorType.TAG);
|
|
10019
|
+
} catch {
|
|
10020
|
+
return null;
|
|
10021
|
+
}
|
|
10022
|
+
}
|
|
10023
|
+
function isAssetShaped(item) {
|
|
10024
|
+
try {
|
|
10025
|
+
return import_CborDeserializer.CborDeserializer.decodeArray(item, 2).every(
|
|
10026
|
+
(field) => majorTypeOf(field) === import_MajorType.MajorType.BYTE_STRING
|
|
10027
|
+
);
|
|
10028
|
+
} catch {
|
|
10029
|
+
return false;
|
|
10030
|
+
}
|
|
10031
|
+
}
|
|
10032
|
+
function arrayItemsOrNull(bytes) {
|
|
10033
|
+
try {
|
|
10034
|
+
return import_CborDeserializer.CborDeserializer.decodeArray(bytes);
|
|
10035
|
+
} catch {
|
|
10036
|
+
return null;
|
|
10037
|
+
}
|
|
10038
|
+
}
|
|
10039
|
+
function decodeSphere(genesisData) {
|
|
10040
|
+
try {
|
|
10041
|
+
return SpherePaymentData.fromCBOR(genesisData).toValue();
|
|
10042
|
+
} catch (error) {
|
|
10043
|
+
throw invalid(`Failed to decode token payment data: ${describe(error)}`);
|
|
10044
|
+
}
|
|
10045
|
+
}
|
|
10046
|
+
function taggedItemBody(genesisData) {
|
|
10047
|
+
try {
|
|
10048
|
+
return import_CborDeserializer.CborDeserializer.decodeTag(genesisData).data;
|
|
10049
|
+
} catch (error) {
|
|
10050
|
+
throw invalid(
|
|
10051
|
+
`Token value payload is a CBOR tag whose item is malformed or carries trailing bytes: ${describe(error)}`
|
|
10052
|
+
);
|
|
10053
|
+
}
|
|
10054
|
+
}
|
|
10055
|
+
function selfDescribedBodyCarriesValue(body) {
|
|
10056
|
+
const major = majorTypeOf(body);
|
|
10057
|
+
if (major === import_MajorType.MajorType.TAG) {
|
|
10058
|
+
const inner = headTagNumber(body);
|
|
10059
|
+
return inner === SpherePaymentData.CBOR_TAG || inner === SELF_DESCRIBED_CBOR_TAG;
|
|
10060
|
+
}
|
|
10061
|
+
if (major === import_MajorType.MajorType.ARRAY) {
|
|
10062
|
+
const items = arrayItemsOrNull(body);
|
|
10063
|
+
return items !== null && items.some(isAssetShaped);
|
|
10064
|
+
}
|
|
10065
|
+
return false;
|
|
10066
|
+
}
|
|
10067
|
+
function classifyTagged(genesisData) {
|
|
10068
|
+
const tag = headTagNumber(genesisData);
|
|
10069
|
+
if (tag === null) {
|
|
10070
|
+
throw invalid(
|
|
10071
|
+
"Token value payload has an unreadable or non-canonically encoded CBOR tag head \u2014 it could be a malformed SpherePaymentData envelope"
|
|
10072
|
+
);
|
|
10073
|
+
}
|
|
10074
|
+
if (tag === SpherePaymentData.CBOR_TAG) {
|
|
10075
|
+
return { envelope: "sphere", value: decodeSphere(genesisData) };
|
|
10076
|
+
}
|
|
10077
|
+
const body = taggedItemBody(genesisData);
|
|
10078
|
+
if (tag === SELF_DESCRIBED_CBOR_TAG && selfDescribedBodyCarriesValue(body)) {
|
|
10079
|
+
throw invalid(
|
|
10080
|
+
"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"
|
|
10081
|
+
);
|
|
10082
|
+
}
|
|
10083
|
+
return { envelope: "none_tag", value: null };
|
|
10084
|
+
}
|
|
10085
|
+
function classifyArray(genesisData) {
|
|
10086
|
+
const items = arrayItemsOrNull(genesisData);
|
|
10087
|
+
if (items === null) {
|
|
10088
|
+
throw invalid("Token value payload is not PaymentAssetCollection: malformed CBOR array");
|
|
10089
|
+
}
|
|
10090
|
+
return items.some(isAssetShaped) ? { envelope: "bare_collection", value: null } : { envelope: "none_other", value: null };
|
|
10091
|
+
}
|
|
10092
|
+
function classifyValueEnvelope(genesisData) {
|
|
10093
|
+
if (genesisData === null) return { envelope: "none_absent", value: null };
|
|
10094
|
+
const major = majorTypeOf(genesisData);
|
|
10095
|
+
if (major === null) return { envelope: "none_absent", value: null };
|
|
10096
|
+
if (major === import_MajorType.MajorType.TAG) return classifyTagged(genesisData);
|
|
10097
|
+
if (major === import_MajorType.MajorType.ARRAY) return classifyArray(genesisData);
|
|
10098
|
+
return { envelope: "none_other", value: null };
|
|
10099
|
+
}
|
|
10100
|
+
function wrapToken(sdkToken) {
|
|
10101
|
+
const { envelope, value } = classifyValueEnvelope(sdkToken.genesis.data);
|
|
10102
|
+
const blob = {
|
|
10103
|
+
tokenId: import_HexConverter.HexConverter.encode(sdkToken.id.bytes),
|
|
10104
|
+
token: sdkToken.toCBOR()
|
|
10105
|
+
};
|
|
10106
|
+
return {
|
|
10107
|
+
sdkToken,
|
|
10108
|
+
blob,
|
|
10109
|
+
value,
|
|
10110
|
+
valueEnvelope: envelope,
|
|
10111
|
+
tokenType: import_HexConverter.HexConverter.encode(sdkToken.type.bytes)
|
|
10112
|
+
};
|
|
10113
|
+
}
|
|
10114
|
+
function assertMintableData(data) {
|
|
10115
|
+
try {
|
|
10116
|
+
classifyValueEnvelope(data);
|
|
10117
|
+
} catch (error) {
|
|
10118
|
+
throw invalid(
|
|
10119
|
+
`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.`
|
|
10120
|
+
);
|
|
10121
|
+
}
|
|
10122
|
+
}
|
|
10123
|
+
|
|
9983
10124
|
// token-engine/SphereTokenEngine.ts
|
|
9984
10125
|
var DEFAULT_PROOF_POLL_INTERVAL_MS = 300;
|
|
9985
10126
|
var MAX_MINT_CONCURRENCY = 8;
|
|
@@ -10036,7 +10177,7 @@ var SphereTokenEngine = class {
|
|
|
10036
10177
|
return sdkToken.latestTransaction.data;
|
|
10037
10178
|
}
|
|
10038
10179
|
const data = sdkToken.genesis.data;
|
|
10039
|
-
if (data &&
|
|
10180
|
+
if (data && classifyValueEnvelope(data).envelope === "sphere") {
|
|
10040
10181
|
return SpherePaymentData.fromCBOR(data).memo;
|
|
10041
10182
|
}
|
|
10042
10183
|
return null;
|
|
@@ -10066,9 +10207,10 @@ var SphereTokenEngine = class {
|
|
|
10066
10207
|
);
|
|
10067
10208
|
const certified = await mintTx.toCertifiedTransaction(this.deps.trustBase, this.deps.predicateVerifier, this.deps.unicityCertificateVerifier, proof);
|
|
10068
10209
|
const token = await import_Token.Token.mint(certified, this.deps.verificationContext);
|
|
10069
|
-
return
|
|
10210
|
+
return wrapToken(token);
|
|
10070
10211
|
}
|
|
10071
10212
|
async mintDataToken(params, options) {
|
|
10213
|
+
assertMintableData(params.data);
|
|
10072
10214
|
const recipient = import_SignaturePredicate.SignaturePredicate.create(params.recipientPubkey);
|
|
10073
10215
|
const tokenType = params.tokenType ? new import_TokenType.TokenType(params.tokenType) : import_TokenType.TokenType.generate();
|
|
10074
10216
|
const salt = params.salt ? import_TokenSalt.TokenSalt.fromBytes(params.salt) : import_TokenSalt.TokenSalt.generate();
|
|
@@ -10083,7 +10225,7 @@ var SphereTokenEngine = class {
|
|
|
10083
10225
|
);
|
|
10084
10226
|
const certified = await mintTx.toCertifiedTransaction(this.deps.trustBase, this.deps.predicateVerifier, this.deps.unicityCertificateVerifier, proof);
|
|
10085
10227
|
const token = await import_Token.Token.mint(certified, this.deps.verificationContext);
|
|
10086
|
-
return
|
|
10228
|
+
return wrapToken(token);
|
|
10087
10229
|
}
|
|
10088
10230
|
async transfer(params, options) {
|
|
10089
10231
|
this.assertOwned(params.token);
|
|
@@ -10106,13 +10248,19 @@ var SphereTokenEngine = class {
|
|
|
10106
10248
|
);
|
|
10107
10249
|
const certified = await transferTx.toCertifiedTransaction(this.deps.trustBase, this.deps.predicateVerifier, this.deps.unicityCertificateVerifier, proof);
|
|
10108
10250
|
const transferred = await params.token.sdkToken.transfer(certified, this.deps.verificationContext);
|
|
10109
|
-
return
|
|
10251
|
+
return wrapToken(transferred);
|
|
10110
10252
|
}
|
|
10111
10253
|
async split(params, options) {
|
|
10112
10254
|
this.assertOwned(params.token);
|
|
10113
10255
|
if (params.outputs.length === 0) {
|
|
10114
10256
|
throw new SphereError("Split requires at least one output", "VALIDATION_ERROR");
|
|
10115
10257
|
}
|
|
10258
|
+
if (params.token.value === null) {
|
|
10259
|
+
throw new SphereError(
|
|
10260
|
+
`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.`,
|
|
10261
|
+
"VALIDATION_ERROR"
|
|
10262
|
+
);
|
|
10263
|
+
}
|
|
10116
10264
|
const transferId = this.resolveTransferId(options);
|
|
10117
10265
|
const requests = params.outputs.map(
|
|
10118
10266
|
(o, i) => import_SplitTokenRequest.SplitTokenRequest.create(
|
|
@@ -10234,7 +10382,7 @@ var SphereTokenEngine = class {
|
|
|
10234
10382
|
const proof = await this.submitSplitMintLeg(certData, mintTx, options);
|
|
10235
10383
|
const certified = await mintTx.toCertifiedTransaction(this.deps.trustBase, this.deps.predicateVerifier, this.deps.unicityCertificateVerifier, proof);
|
|
10236
10384
|
const token = await import_Token.Token.mint(certified, this.deps.verificationContext);
|
|
10237
|
-
return
|
|
10385
|
+
return wrapToken(token);
|
|
10238
10386
|
}
|
|
10239
10387
|
/**
|
|
10240
10388
|
* Submit one split-mint leg. A `TRANSACTION_HASH_MISMATCH` on a mint leg is NOT a foreign spend
|
|
@@ -10292,7 +10440,7 @@ var SphereTokenEngine = class {
|
|
|
10292
10440
|
"VALIDATION_ERROR"
|
|
10293
10441
|
);
|
|
10294
10442
|
}
|
|
10295
|
-
return
|
|
10443
|
+
return wrapToken(sdkToken);
|
|
10296
10444
|
}
|
|
10297
10445
|
// ── internals ────────────────────────────────────────────────────────────────
|
|
10298
10446
|
/**
|
|
@@ -10404,34 +10552,6 @@ var SphereTokenEngine = class {
|
|
|
10404
10552
|
throw new SphereError("Cannot transfer a token not owned by this engine identity", "VALIDATION_ERROR");
|
|
10405
10553
|
}
|
|
10406
10554
|
}
|
|
10407
|
-
/** Wrap an SDK token into a SphereToken: cache its blob (incl. stable tokenId) + decoded value. */
|
|
10408
|
-
wrapToken(sdkToken) {
|
|
10409
|
-
const data = sdkToken.genesis.data;
|
|
10410
|
-
let value = null;
|
|
10411
|
-
if (data && this.isSpherePaymentData(data)) {
|
|
10412
|
-
try {
|
|
10413
|
-
value = SpherePaymentData.fromCBOR(data).toValue();
|
|
10414
|
-
} catch (err) {
|
|
10415
|
-
throw new SphereError(
|
|
10416
|
-
`Failed to decode token payment data: ${err instanceof Error ? err.message : String(err)}`,
|
|
10417
|
-
"VALIDATION_ERROR"
|
|
10418
|
-
);
|
|
10419
|
-
}
|
|
10420
|
-
}
|
|
10421
|
-
const blob = {
|
|
10422
|
-
tokenId: import_HexConverter.HexConverter.encode(sdkToken.id.bytes),
|
|
10423
|
-
token: sdkToken.toCBOR()
|
|
10424
|
-
};
|
|
10425
|
-
return { sdkToken, blob, value };
|
|
10426
|
-
}
|
|
10427
|
-
/** True if the bytes are a SpherePaymentData envelope (value token) vs a raw data token. */
|
|
10428
|
-
isSpherePaymentData(data) {
|
|
10429
|
-
try {
|
|
10430
|
-
return import_CborDeserializer.CborDeserializer.decodeTag(data).tag === SpherePaymentData.CBOR_TAG;
|
|
10431
|
-
} catch {
|
|
10432
|
-
return false;
|
|
10433
|
-
}
|
|
10434
|
-
}
|
|
10435
10555
|
/**
|
|
10436
10556
|
* Terminate the verification worker pool, if this engine was configured with
|
|
10437
10557
|
* one. Idempotent — the pool's dispose only touches workers it spawned.
|
|
@@ -11827,6 +11947,14 @@ function createMachineStores(kv) {
|
|
|
11827
11947
|
};
|
|
11828
11948
|
}
|
|
11829
11949
|
|
|
11950
|
+
// modules/payments-v2/machine/payload-view.ts
|
|
11951
|
+
function splitOf(p) {
|
|
11952
|
+
return p.kind === "coin" ? p.split : void 0;
|
|
11953
|
+
}
|
|
11954
|
+
function coinIdOf(p) {
|
|
11955
|
+
return p.kind === "coin" ? p.coinId : void 0;
|
|
11956
|
+
}
|
|
11957
|
+
|
|
11830
11958
|
// modules/payments-v2/machine/TransferMachine.ts
|
|
11831
11959
|
var CERTIFY_WIDTH = 20;
|
|
11832
11960
|
var KEEP_OPEN_CODES = /* @__PURE__ */ new Set([
|
|
@@ -12029,13 +12157,14 @@ var TransferMachine = class {
|
|
|
12029
12157
|
const finished = await engine.transfer({ token: source, recipientPubkey }, opts);
|
|
12030
12158
|
return { recipientBlob: finished.blob.token };
|
|
12031
12159
|
}
|
|
12032
|
-
const split = ctx.payload
|
|
12160
|
+
const split = splitOf(ctx.payload);
|
|
12161
|
+
const splitCoinId = coinIdOf(ctx.payload);
|
|
12033
12162
|
const { outputs } = await engine.split(
|
|
12034
12163
|
{
|
|
12035
12164
|
token: source,
|
|
12036
12165
|
outputs: [
|
|
12037
|
-
{ recipientPubkey, coinId:
|
|
12038
|
-
{ recipientPubkey: this.deps.ownPubkey, coinId:
|
|
12166
|
+
{ recipientPubkey, coinId: splitCoinId, amount: BigInt(split.splitAmount) },
|
|
12167
|
+
{ recipientPubkey: this.deps.ownPubkey, coinId: splitCoinId, amount: BigInt(split.remainderAmount) }
|
|
12039
12168
|
]
|
|
12040
12169
|
},
|
|
12041
12170
|
opts
|
|
@@ -12157,7 +12286,9 @@ var TransferMachine = class {
|
|
|
12157
12286
|
const entry = {
|
|
12158
12287
|
transferId: r.transferId,
|
|
12159
12288
|
remainingAmount: undelivered.toString(),
|
|
12160
|
-
|
|
12289
|
+
// '' only on a token-addressed intent, which is one leg: it either lands or
|
|
12290
|
+
// conflicts, so a shortfall row is unreachable there (settlePartial needs >1).
|
|
12291
|
+
coinId: coinIdOf(r.payload) ?? "",
|
|
12161
12292
|
recipient: r.payload.recipient,
|
|
12162
12293
|
committedTokenIds: committed.map((o) => o.op.sourceTokenId),
|
|
12163
12294
|
createdAt: this.deps.now()
|
|
@@ -12183,7 +12314,8 @@ var TransferMachine = class {
|
|
|
12183
12314
|
}
|
|
12184
12315
|
async finishBookkeeping(engine, transferId, payload, phase, committed) {
|
|
12185
12316
|
try {
|
|
12186
|
-
const
|
|
12317
|
+
const coinId = coinIdOf(payload);
|
|
12318
|
+
const committedAmount = coinId === void 0 ? "0" : await this.settledAmount(engine, coinId, committed);
|
|
12187
12319
|
await this.deps.recordHistory?.({ transferId, payload, phase, committedAmount });
|
|
12188
12320
|
} catch {
|
|
12189
12321
|
}
|
|
@@ -12226,8 +12358,11 @@ var TransferMachine = class {
|
|
|
12226
12358
|
}
|
|
12227
12359
|
};
|
|
12228
12360
|
function conflictAmount(engine, payload, op, source) {
|
|
12229
|
-
|
|
12230
|
-
|
|
12361
|
+
const split = splitOf(payload);
|
|
12362
|
+
if (op.kind === "split") return BigInt(split.splitAmount);
|
|
12363
|
+
const coinId = coinIdOf(payload);
|
|
12364
|
+
if (coinId === void 0) return 0n;
|
|
12365
|
+
return source !== void 0 ? engine.balanceOf(source, coinId) : 0n;
|
|
12231
12366
|
}
|
|
12232
12367
|
|
|
12233
12368
|
// modules/payments-v2/machine/resume.ts
|
|
@@ -12334,6 +12469,14 @@ async function runOne(ctx, job, report) {
|
|
|
12334
12469
|
report.failed.push(job.transferId);
|
|
12335
12470
|
return;
|
|
12336
12471
|
}
|
|
12472
|
+
if (job.payload.kind === "coinless" && !isCoinlessEnvelope(token.valueEnvelope)) {
|
|
12473
|
+
logger.warn(
|
|
12474
|
+
"PaymentsV2",
|
|
12475
|
+
`resume: token intent ${job.transferId} names a source carrying coin value \u2014 refusing`
|
|
12476
|
+
);
|
|
12477
|
+
report.failed.push(job.transferId);
|
|
12478
|
+
return;
|
|
12479
|
+
}
|
|
12337
12480
|
mine.set(op.sourceTokenId, token);
|
|
12338
12481
|
}
|
|
12339
12482
|
try {
|
|
@@ -12364,6 +12507,28 @@ async function classifyResumeFailure(deps, stores, transferId, err, report) {
|
|
|
12364
12507
|
}
|
|
12365
12508
|
report.failed.push(transferId);
|
|
12366
12509
|
}
|
|
12510
|
+
function validateCoinPayload(c) {
|
|
12511
|
+
if (typeof c.coinId !== "string" || typeof c.amount !== "string") {
|
|
12512
|
+
throw new SphereError("intent payload is missing coinId/amount", "VALIDATION_ERROR");
|
|
12513
|
+
}
|
|
12514
|
+
const s = c.split;
|
|
12515
|
+
if (s !== void 0 && (typeof s.tokenId !== "string" || typeof s.splitAmount !== "string" || typeof s.remainderAmount !== "string")) {
|
|
12516
|
+
throw new SphereError("intent payload split spec is malformed", "VALIDATION_ERROR");
|
|
12517
|
+
}
|
|
12518
|
+
return { ...c, kind: "coin" };
|
|
12519
|
+
}
|
|
12520
|
+
function validateCoinlessPayload(p) {
|
|
12521
|
+
if (p.direct?.length !== 1 || typeof p.direct[0] !== "string" || p.direct[0] === "") {
|
|
12522
|
+
throw new SphereError(
|
|
12523
|
+
"token intent payload must name exactly one source token",
|
|
12524
|
+
"VALIDATION_ERROR"
|
|
12525
|
+
);
|
|
12526
|
+
}
|
|
12527
|
+
if (p.split !== void 0) {
|
|
12528
|
+
throw new SphereError("token intent payload cannot carry a split", "VALIDATION_ERROR");
|
|
12529
|
+
}
|
|
12530
|
+
return { ...p, kind: "coinless", direct: [p.direct[0]] };
|
|
12531
|
+
}
|
|
12367
12532
|
function validatePayload(raw) {
|
|
12368
12533
|
const p = raw !== null && typeof raw === "object" ? raw : null;
|
|
12369
12534
|
if (p === null || p.v !== 2 || !Array.isArray(p.direct) || p.direct.some((t) => typeof t !== "string")) {
|
|
@@ -12372,14 +12537,17 @@ function validatePayload(raw) {
|
|
|
12372
12537
|
"VALIDATION_ERROR"
|
|
12373
12538
|
);
|
|
12374
12539
|
}
|
|
12375
|
-
if (typeof p.recipient !== "string"
|
|
12376
|
-
throw new SphereError("intent payload is missing recipient
|
|
12540
|
+
if (typeof p.recipient !== "string") {
|
|
12541
|
+
throw new SphereError("intent payload is missing recipient", "VALIDATION_ERROR");
|
|
12377
12542
|
}
|
|
12378
|
-
const
|
|
12379
|
-
if (
|
|
12380
|
-
throw new SphereError(
|
|
12543
|
+
const kind = p.kind ?? "coin";
|
|
12544
|
+
if (kind !== "coin" && kind !== "coinless") {
|
|
12545
|
+
throw new SphereError(
|
|
12546
|
+
`unsupported intent kind '${String(kind)}' \u2014 not resumable by this client`,
|
|
12547
|
+
"VALIDATION_ERROR"
|
|
12548
|
+
);
|
|
12381
12549
|
}
|
|
12382
|
-
return p;
|
|
12550
|
+
return kind === "coinless" ? validateCoinlessPayload(p) : validateCoinPayload(p);
|
|
12383
12551
|
}
|
|
12384
12552
|
|
|
12385
12553
|
// modules/payments-v2/convergence.ts
|
|
@@ -12585,13 +12753,23 @@ async function openRow(entry, journal, decryptPayload) {
|
|
|
12585
12753
|
transferId: entry.transferId,
|
|
12586
12754
|
kind: "open",
|
|
12587
12755
|
recipient: typeof payload?.recipient === "string" ? payload.recipient : "",
|
|
12588
|
-
|
|
12589
|
-
amount: typeof payload?.amount === "string" ? payload.amount : "",
|
|
12756
|
+
...subject(payload),
|
|
12590
12757
|
legs: { certified, total: Math.max(total, certified) },
|
|
12591
12758
|
deliveryPending: journal.length > 0,
|
|
12592
12759
|
createdAt: entry.createdAt
|
|
12593
12760
|
};
|
|
12594
12761
|
}
|
|
12762
|
+
function subject(payload) {
|
|
12763
|
+
if (payload?.kind === "coinless") {
|
|
12764
|
+
const tokenId = payload.direct?.[0];
|
|
12765
|
+
return { coinId: "", amount: "", ...typeof tokenId === "string" ? { tokenId } : {} };
|
|
12766
|
+
}
|
|
12767
|
+
const coin = payload;
|
|
12768
|
+
return {
|
|
12769
|
+
coinId: typeof coin?.coinId === "string" ? coin.coinId : "",
|
|
12770
|
+
amount: typeof coin?.amount === "string" ? coin.amount : ""
|
|
12771
|
+
};
|
|
12772
|
+
}
|
|
12595
12773
|
function journalOnlyRow(transferId, entries) {
|
|
12596
12774
|
const certified = distinctOpCount(entries);
|
|
12597
12775
|
return {
|
|
@@ -12621,6 +12799,160 @@ function distinctOpCount(entries) {
|
|
|
12621
12799
|
return new Set(entries.map((entry) => entry.opIndex)).size;
|
|
12622
12800
|
}
|
|
12623
12801
|
|
|
12802
|
+
// modules/payments-v2/inventory/token-data.ts
|
|
12803
|
+
async function readTokenData(deps, tokenId) {
|
|
12804
|
+
if (deps.view.stateHashOf(tokenId) === void 0) {
|
|
12805
|
+
throw new SphereError(`Token ${tokenId} is not in inventory`, "VALIDATION_ERROR");
|
|
12806
|
+
}
|
|
12807
|
+
const bytes = (await deps.storagePort.getBlobs([tokenId])).get(tokenId);
|
|
12808
|
+
if (bytes === void 0) {
|
|
12809
|
+
throw new SphereError(`Token ${tokenId} has no blob in storage`, "STORAGE_ERROR");
|
|
12810
|
+
}
|
|
12811
|
+
return deps.engine.readTokenData(await deps.engine.decodeToken({ tokenId, token: bytes }));
|
|
12812
|
+
}
|
|
12813
|
+
|
|
12814
|
+
// modules/payments-v2/machine/payload.ts
|
|
12815
|
+
function buildPayload(recipientPubkey, request, spend, spentStates) {
|
|
12816
|
+
return {
|
|
12817
|
+
v: 2,
|
|
12818
|
+
kind: "coin",
|
|
12819
|
+
recipient: recipientPubkey,
|
|
12820
|
+
coinId: request.coinId,
|
|
12821
|
+
amount: request.amount,
|
|
12822
|
+
...request.memo !== void 0 ? { memo: request.memo } : {},
|
|
12823
|
+
direct: [...spend.plan.direct],
|
|
12824
|
+
...spend.plan.split !== void 0 ? {
|
|
12825
|
+
split: {
|
|
12826
|
+
tokenId: spend.plan.split.tokenId,
|
|
12827
|
+
splitAmount: spend.plan.split.splitAmount.toString(),
|
|
12828
|
+
remainderAmount: spend.plan.split.remainderAmount.toString()
|
|
12829
|
+
}
|
|
12830
|
+
} : {},
|
|
12831
|
+
spentStates
|
|
12832
|
+
};
|
|
12833
|
+
}
|
|
12834
|
+
function messageOf(err) {
|
|
12835
|
+
return err instanceof Error ? err.message : String(err);
|
|
12836
|
+
}
|
|
12837
|
+
function buildCoinlessPayload(recipientPubkey, request, spentStates) {
|
|
12838
|
+
return {
|
|
12839
|
+
v: 2,
|
|
12840
|
+
kind: "coinless",
|
|
12841
|
+
recipient: recipientPubkey,
|
|
12842
|
+
...request.memo !== void 0 ? { memo: request.memo } : {},
|
|
12843
|
+
direct: [request.tokenId],
|
|
12844
|
+
spentStates
|
|
12845
|
+
};
|
|
12846
|
+
}
|
|
12847
|
+
|
|
12848
|
+
// modules/payments-v2/mint-params.ts
|
|
12849
|
+
function mintParams(recipientPubkey, coinId, amount) {
|
|
12850
|
+
return { recipientPubkey, value: { assets: [{ coinId, amount }] } };
|
|
12851
|
+
}
|
|
12852
|
+
|
|
12853
|
+
// modules/payments-v2/mint.ts
|
|
12854
|
+
function runMintUnderJournal(deps, input) {
|
|
12855
|
+
return mintUnderJournal(deps, input);
|
|
12856
|
+
}
|
|
12857
|
+
async function mintUnderJournal(deps, input) {
|
|
12858
|
+
const { mintId, coinId, amount } = input;
|
|
12859
|
+
const engine = deps.engine;
|
|
12860
|
+
const entry = {
|
|
12861
|
+
mintId,
|
|
12862
|
+
coinId,
|
|
12863
|
+
amount: amount.toString(),
|
|
12864
|
+
tokenId: "",
|
|
12865
|
+
createdAt: deps.now()
|
|
12866
|
+
};
|
|
12867
|
+
await deps.mintJournal.upsert(entry);
|
|
12868
|
+
let token;
|
|
12869
|
+
try {
|
|
12870
|
+
token = await engine.mint(mintParams(deps.ownPubkeyBytes, coinId, amount), { transferId: mintId });
|
|
12871
|
+
} catch (err) {
|
|
12872
|
+
deps.armHeartbeat();
|
|
12873
|
+
return { success: false, error: messageOf(err) };
|
|
12874
|
+
}
|
|
12875
|
+
if (token.blob.tokenId !== entry.tokenId) {
|
|
12876
|
+
await deps.mintJournal.upsert({ ...entry, tokenId: token.blob.tokenId });
|
|
12877
|
+
}
|
|
12878
|
+
try {
|
|
12879
|
+
await finalizeMint(deps, mintId, token, coinId, amount.toString());
|
|
12880
|
+
} catch (err) {
|
|
12881
|
+
deps.armHeartbeat();
|
|
12882
|
+
return { success: false, tokenId: token.blob.tokenId, error: messageOf(err) };
|
|
12883
|
+
}
|
|
12884
|
+
return { success: true, tokenId: token.blob.tokenId };
|
|
12885
|
+
}
|
|
12886
|
+
async function finalizeMint(deps, mintId, token, coinId, amount) {
|
|
12887
|
+
const engine = deps.engine;
|
|
12888
|
+
const bytes = token.blob.token;
|
|
12889
|
+
const digest = bytesToHex3(sha256(bytes));
|
|
12890
|
+
const keys = await deps.storagePort.uploadBlobs([{ sha256: digest, bytes }]);
|
|
12891
|
+
const key = keys.get(digest);
|
|
12892
|
+
if (key === void 0) {
|
|
12893
|
+
throw new SphereError(`no upload key returned for mint blob ${digest}`, "STORAGE_ERROR");
|
|
12894
|
+
}
|
|
12895
|
+
await deps.storagePort.applyDelta({
|
|
12896
|
+
transferId: mintId,
|
|
12897
|
+
spent: [],
|
|
12898
|
+
added: [{ tokenId: token.blob.tokenId, key }]
|
|
12899
|
+
});
|
|
12900
|
+
await deps.recordMint({
|
|
12901
|
+
tokenId: token.blob.tokenId,
|
|
12902
|
+
assets: [{ coinId, amount }]
|
|
12903
|
+
});
|
|
12904
|
+
await deps.mintJournal.removeByKey(mintId);
|
|
12905
|
+
deps.noteHeldState(token.blob.tokenId, (await engine.deliveryKeys(bytes)).stateHash);
|
|
12906
|
+
deps.refreshView();
|
|
12907
|
+
}
|
|
12908
|
+
|
|
12909
|
+
// modules/payments-v2/send-coinless.ts
|
|
12910
|
+
async function materializeCoinlessSpend(deps, input) {
|
|
12911
|
+
const { engine } = deps;
|
|
12912
|
+
const tokenId = input.request.tokenId;
|
|
12913
|
+
const bytes = (await deps.storagePort.getBlobs([tokenId])).get(tokenId);
|
|
12914
|
+
if (bytes === void 0) {
|
|
12915
|
+
throw new SphereError(`Selected source ${tokenId} has no blob in storage`, "STORAGE_ERROR");
|
|
12916
|
+
}
|
|
12917
|
+
const token = await engine.decodeToken({ tokenId, token: bytes });
|
|
12918
|
+
if (!isCoinlessEnvelope(token.valueEnvelope)) {
|
|
12919
|
+
throw new SphereError(
|
|
12920
|
+
`Token ${tokenId} carries coin value and cannot be sent with sendCoinless \u2014 use send()`,
|
|
12921
|
+
"VALIDATION_ERROR"
|
|
12922
|
+
);
|
|
12923
|
+
}
|
|
12924
|
+
const keys = await engine.deliveryKeys(bytes);
|
|
12925
|
+
const payload = buildCoinlessPayload(input.recipientPubkey, input.request, {
|
|
12926
|
+
[tokenId]: { local: keys.stateHash, protocol: keys.stateHash }
|
|
12927
|
+
});
|
|
12928
|
+
const plan = {
|
|
12929
|
+
transferId: input.transferId,
|
|
12930
|
+
recipientPubkey: input.recipientPubkey,
|
|
12931
|
+
payload,
|
|
12932
|
+
ops: buildOps(payload),
|
|
12933
|
+
sources: /* @__PURE__ */ new Map([[tokenId, token]]),
|
|
12934
|
+
...input.request.memo !== void 0 ? { memo: input.request.memo } : {}
|
|
12935
|
+
};
|
|
12936
|
+
return { transferId: input.transferId, coinId: "", plan, sourceIds: input.sourceIds, sourceTokens: [] };
|
|
12937
|
+
}
|
|
12938
|
+
|
|
12939
|
+
// modules/payments-v2/send-errors.ts
|
|
12940
|
+
function partialize(err, run) {
|
|
12941
|
+
if (run.delivered.length === 0) return err;
|
|
12942
|
+
return new PartialSendConflictError(
|
|
12943
|
+
"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.",
|
|
12944
|
+
run.firstPartialId ?? "",
|
|
12945
|
+
[...run.delivered],
|
|
12946
|
+
run.amount,
|
|
12947
|
+
err
|
|
12948
|
+
);
|
|
12949
|
+
}
|
|
12950
|
+
function stampTransferId(err, transferId) {
|
|
12951
|
+
if (err instanceof SphereError && isPossiblyCommittedSendOutcome(err) && err.transferId === void 0) {
|
|
12952
|
+
err.transferId = transferId;
|
|
12953
|
+
}
|
|
12954
|
+
}
|
|
12955
|
+
|
|
12624
12956
|
// modules/payments-v2/recipient.ts
|
|
12625
12957
|
var ATTENTION_RECIPIENT_NETWORK_UNVERIFIED = "recipient:network-unverified";
|
|
12626
12958
|
async function requireSameNetworkRecipient(deps, identifier) {
|
|
@@ -12669,11 +13001,6 @@ async function splitOpIndex(deps, envelope) {
|
|
|
12669
13001
|
return Array.isArray(payload.direct) ? payload.direct.length : 0;
|
|
12670
13002
|
}
|
|
12671
13003
|
|
|
12672
|
-
// modules/payments-v2/mint-params.ts
|
|
12673
|
-
function mintParams(recipientPubkey, coinId, amount) {
|
|
12674
|
-
return { recipientPubkey, value: { assets: [{ coinId, amount }] } };
|
|
12675
|
-
}
|
|
12676
|
-
|
|
12677
13004
|
// modules/payments-v2/prewarm-cache.ts
|
|
12678
13005
|
var PrewarmCache = class _PrewarmCache {
|
|
12679
13006
|
entries = /* @__PURE__ */ new Map();
|
|
@@ -12847,30 +13174,15 @@ function transferringToken(tokenId, coinId, amount, registry, now) {
|
|
|
12847
13174
|
});
|
|
12848
13175
|
}
|
|
12849
13176
|
|
|
12850
|
-
// modules/payments-v2/
|
|
12851
|
-
function
|
|
12852
|
-
return {
|
|
12853
|
-
v: 2,
|
|
12854
|
-
recipient: recipientPubkey,
|
|
12855
|
-
coinId: request.coinId,
|
|
12856
|
-
amount: request.amount,
|
|
12857
|
-
...request.memo !== void 0 ? { memo: request.memo } : {},
|
|
12858
|
-
direct: [...spend.plan.direct],
|
|
12859
|
-
...spend.plan.split !== void 0 ? {
|
|
12860
|
-
split: {
|
|
12861
|
-
tokenId: spend.plan.split.tokenId,
|
|
12862
|
-
splitAmount: spend.plan.split.splitAmount.toString(),
|
|
12863
|
-
remainderAmount: spend.plan.split.remainderAmount.toString()
|
|
12864
|
-
}
|
|
12865
|
-
} : {},
|
|
12866
|
-
spentStates
|
|
12867
|
-
};
|
|
13177
|
+
// modules/payments-v2/history/History.ts
|
|
13178
|
+
function wireAssets(assets) {
|
|
13179
|
+
return assets.map((a) => ({ coinId: a.coinId, amount: a.amount }));
|
|
12868
13180
|
}
|
|
12869
|
-
function
|
|
12870
|
-
|
|
13181
|
+
function isPermanentReject(err) {
|
|
13182
|
+
const status = err?.status;
|
|
13183
|
+
if (typeof status !== "number") return false;
|
|
13184
|
+
return status >= 400 && status < 500 && status !== 408 && status !== 429;
|
|
12871
13185
|
}
|
|
12872
|
-
|
|
12873
|
-
// modules/payments-v2/history/History.ts
|
|
12874
13186
|
var WIRE_TOKEN_ID = /^(?:[0-9a-f]{2}){1,64}$/;
|
|
12875
13187
|
var WIRE_PUBKEY = /^0[23][0-9a-f]{64}$/;
|
|
12876
13188
|
var History = class {
|
|
@@ -12895,7 +13207,7 @@ var History = class {
|
|
|
12895
13207
|
dedupKey: input.transferId,
|
|
12896
13208
|
id: (this.deps.newId ?? randomUUID)(),
|
|
12897
13209
|
type: "SENT",
|
|
12898
|
-
assets:
|
|
13210
|
+
assets: wireAssets(input.assets),
|
|
12899
13211
|
ts: this.ts(input.timestamp),
|
|
12900
13212
|
transferId: input.transferId,
|
|
12901
13213
|
...this.wireTokenId(input.tokenId),
|
|
@@ -12908,7 +13220,7 @@ var History = class {
|
|
|
12908
13220
|
dedupKey: `RECEIVED:${input.tokenId.toLowerCase()}:${input.stateHash.toLowerCase()}`,
|
|
12909
13221
|
id: (this.deps.newId ?? randomUUID)(),
|
|
12910
13222
|
type: "RECEIVED",
|
|
12911
|
-
assets:
|
|
13223
|
+
assets: wireAssets(input.assets),
|
|
12912
13224
|
ts: this.ts(input.timestamp),
|
|
12913
13225
|
...input.transferId !== void 0 ? { transferId: input.transferId } : {},
|
|
12914
13226
|
...this.wireTokenId(input.tokenId),
|
|
@@ -12921,7 +13233,7 @@ var History = class {
|
|
|
12921
13233
|
dedupKey: `MINT:${input.tokenId.toLowerCase()}`,
|
|
12922
13234
|
id: (this.deps.newId ?? randomUUID)(),
|
|
12923
13235
|
type: "MINT",
|
|
12924
|
-
assets:
|
|
13236
|
+
assets: wireAssets(input.assets),
|
|
12925
13237
|
ts: this.ts(input.timestamp),
|
|
12926
13238
|
...this.wireTokenId(input.tokenId)
|
|
12927
13239
|
}));
|
|
@@ -12932,7 +13244,10 @@ var History = class {
|
|
|
12932
13244
|
try {
|
|
12933
13245
|
await this.deps.client.postHistory([record]);
|
|
12934
13246
|
} catch (err) {
|
|
12935
|
-
this.deps.log?.(
|
|
13247
|
+
this.deps.log?.(
|
|
13248
|
+
isPermanentReject(err) ? "history POST REJECTED \u2014 the record shape is refused; no retry can fix it" : "history POST failed (dedupKey makes retry safe)",
|
|
13249
|
+
err
|
|
13250
|
+
);
|
|
12936
13251
|
return;
|
|
12937
13252
|
}
|
|
12938
13253
|
this.deps.emit("history:updated", this.toEntry(record));
|
|
@@ -12999,6 +13314,13 @@ var CURSOR_KEY = STORE_KEYS.streamCursor("inventory");
|
|
|
12999
13314
|
function stateKey(tokenId, stateHash) {
|
|
13000
13315
|
return `${tokenId}:${stateHash}`;
|
|
13001
13316
|
}
|
|
13317
|
+
function unchanged(prev, item, tokenType) {
|
|
13318
|
+
return prev.seq === item.seq && prev.status === item.status && prev.stateHash === item.stateHash && tokenType === prev.tokenType;
|
|
13319
|
+
}
|
|
13320
|
+
function isCoinless(item, prev) {
|
|
13321
|
+
if (item.status !== "active") return prev?.coinless ?? false;
|
|
13322
|
+
return (item.assets?.length ?? 0) === 0;
|
|
13323
|
+
}
|
|
13002
13324
|
var InventoryView = class {
|
|
13003
13325
|
constructor(deps) {
|
|
13004
13326
|
this.deps = deps;
|
|
@@ -13092,14 +13414,26 @@ var InventoryView = class {
|
|
|
13092
13414
|
pool(coinId) {
|
|
13093
13415
|
const out = [];
|
|
13094
13416
|
for (const [tokenId, entry] of this.mirror) {
|
|
13095
|
-
if (
|
|
13096
|
-
if (this.inFlight.has(tokenId) && !this.pinned(tokenId)) continue;
|
|
13097
|
-
if (this.suspected.has(stateKey(tokenId, entry.stateHash))) continue;
|
|
13417
|
+
if (!this.isSpendable(tokenId, entry)) continue;
|
|
13098
13418
|
const asset = entry.assets.find((a) => a.coinId === coinId);
|
|
13099
13419
|
if (asset) out.push({ tokenId, amount: BigInt(asset.amount) });
|
|
13100
13420
|
}
|
|
13101
13421
|
return out;
|
|
13102
13422
|
}
|
|
13423
|
+
/**
|
|
13424
|
+
* The eligibility gates every spend shares, coin or token — ONE definition, so a
|
|
13425
|
+
* single probe covers both verbs and neither can drift from the other.
|
|
13426
|
+
*/
|
|
13427
|
+
isSpendable(tokenId, entry) {
|
|
13428
|
+
if (entry.status !== "active") return false;
|
|
13429
|
+
if (this.inFlight.has(tokenId) && !this.pinned(tokenId)) return false;
|
|
13430
|
+
return !this.suspected.has(stateKey(tokenId, entry.stateHash));
|
|
13431
|
+
}
|
|
13432
|
+
/** #777: is this NAMED token a spendable COINLESS holding? Never a coin source. */
|
|
13433
|
+
spendableCoinless(tokenId) {
|
|
13434
|
+
const entry = this.mirror.get(tokenId);
|
|
13435
|
+
return entry !== void 0 && entry.coinless && this.isSpendable(tokenId, entry);
|
|
13436
|
+
}
|
|
13103
13437
|
/**
|
|
13104
13438
|
* Current mirrored state of an ACTIVE token, or undefined when it is unknown
|
|
13105
13439
|
* or tombstoned. Exists so a cached blob can be state-scoped (F6): a blob is
|
|
@@ -13125,6 +13459,29 @@ var InventoryView = class {
|
|
|
13125
13459
|
}
|
|
13126
13460
|
return out;
|
|
13127
13461
|
}
|
|
13462
|
+
/**
|
|
13463
|
+
* Coinless holdings (#777) — DISJOINT from tokens(): an entry is coinless
|
|
13464
|
+
* exactly when it is not, so no mirror row can appear in both reads.
|
|
13465
|
+
*/
|
|
13466
|
+
coinless(registry) {
|
|
13467
|
+
const out = [];
|
|
13468
|
+
for (const [tokenId, entry] of this.mirror) {
|
|
13469
|
+
if (entry.status !== "active" || !entry.coinless) continue;
|
|
13470
|
+
const meta = entry.tokenType !== void 0 ? registry.getTypeMeta?.(entry.tokenType) : null;
|
|
13471
|
+
out.push({
|
|
13472
|
+
tokenId,
|
|
13473
|
+
...entry.tokenType !== void 0 ? { tokenType: entry.tokenType } : {},
|
|
13474
|
+
...meta ? { name: meta.name } : {},
|
|
13475
|
+
...meta?.iconUrl != null ? { iconUrl: meta.iconUrl } : {},
|
|
13476
|
+
stateHash: entry.stateHash,
|
|
13477
|
+
transferring: this.held(tokenId),
|
|
13478
|
+
...this.suspected.has(stateKey(tokenId, entry.stateHash)) ? { suspectedSpent: true } : {},
|
|
13479
|
+
createdAt: entry.createdAt,
|
|
13480
|
+
updatedAt: entry.updatedAt
|
|
13481
|
+
});
|
|
13482
|
+
}
|
|
13483
|
+
return out;
|
|
13484
|
+
}
|
|
13128
13485
|
async assets(registry, price) {
|
|
13129
13486
|
const raw = aggregateAssets(this.activeEntries(), (tokenId) => this.held(tokenId), registry);
|
|
13130
13487
|
if (!price || raw.length === 0) return raw;
|
|
@@ -13196,14 +13553,19 @@ var InventoryView = class {
|
|
|
13196
13553
|
applyOne(item, now) {
|
|
13197
13554
|
const prev = this.mirror.get(item.tokenId);
|
|
13198
13555
|
if (prev && item.seq < prev.seq) return false;
|
|
13199
|
-
|
|
13200
|
-
|
|
13201
|
-
}
|
|
13556
|
+
const tokenType = item.tokenType ?? prev?.tokenType;
|
|
13557
|
+
if (prev && unchanged(prev, item, tokenType)) return false;
|
|
13202
13558
|
this.mirror.set(item.tokenId, {
|
|
13203
13559
|
stateHash: item.stateHash,
|
|
13204
13560
|
seq: item.seq,
|
|
13205
13561
|
status: item.status,
|
|
13206
|
-
|
|
13562
|
+
// ONLY a tombstone inherits: it omits assets for an unrelated reason and
|
|
13563
|
+
// recoverRemoved must know the amount it restores. An ACTIVE row that omits
|
|
13564
|
+
// them is stating coinlessness, and inheriting there would keep stale assets
|
|
13565
|
+
// in tokens() while `coinless` is true — the row in BOTH reads (§16).
|
|
13566
|
+
assets: item.assets ?? (item.status === "removed" ? prev?.assets ?? [] : []),
|
|
13567
|
+
coinless: isCoinless(item, prev),
|
|
13568
|
+
...tokenType !== void 0 ? { tokenType } : {},
|
|
13207
13569
|
createdAt: prev?.createdAt ?? now,
|
|
13208
13570
|
updatedAt: now
|
|
13209
13571
|
});
|
|
@@ -13439,7 +13801,12 @@ async function screen(deps, engine, entry) {
|
|
|
13439
13801
|
}
|
|
13440
13802
|
return {
|
|
13441
13803
|
kind: "accept",
|
|
13442
|
-
record: {
|
|
13804
|
+
record: {
|
|
13805
|
+
tokenId: keys.tokenId,
|
|
13806
|
+
stateHash: keys.stateHash,
|
|
13807
|
+
assets: toAssetAmounts(token),
|
|
13808
|
+
...isCoinlessEnvelope2(token.valueEnvelope) ? { tokenType: token.tokenType } : {}
|
|
13809
|
+
}
|
|
13443
13810
|
};
|
|
13444
13811
|
}
|
|
13445
13812
|
async function announce(deps, entry, record) {
|
|
@@ -13450,6 +13817,7 @@ async function announce(deps, entry, record) {
|
|
|
13450
13817
|
tokenId: record.tokenId,
|
|
13451
13818
|
stateHash: record.stateHash,
|
|
13452
13819
|
assets: record.assets,
|
|
13820
|
+
...record.tokenType !== void 0 ? { tokenType: record.tokenType } : {},
|
|
13453
13821
|
...entry.senderPubkey !== void 0 ? { senderPubkey: entry.senderPubkey } : {},
|
|
13454
13822
|
...entry.senderNametag !== void 0 ? { senderNametag: entry.senderNametag } : {},
|
|
13455
13823
|
...entry.memo !== void 0 ? { memo: entry.memo } : {},
|
|
@@ -13463,6 +13831,19 @@ async function announce(deps, entry, record) {
|
|
|
13463
13831
|
senderPubkey: entry.senderPubkey ?? "",
|
|
13464
13832
|
...entry.senderNametag !== void 0 ? { senderNametag: entry.senderNametag } : {},
|
|
13465
13833
|
tokens: record.assets.map((asset) => toUiToken(record.tokenId, asset, deps.registry, receivedAt)),
|
|
13834
|
+
// #777: named here rather than mapped from assets, which announced an EMPTY list.
|
|
13835
|
+
...record.tokenType !== void 0 ? {
|
|
13836
|
+
coinless: [
|
|
13837
|
+
{
|
|
13838
|
+
tokenId: record.tokenId,
|
|
13839
|
+
tokenType: record.tokenType,
|
|
13840
|
+
stateHash: record.stateHash,
|
|
13841
|
+
transferring: false,
|
|
13842
|
+
createdAt: receivedAt,
|
|
13843
|
+
updatedAt: receivedAt
|
|
13844
|
+
}
|
|
13845
|
+
]
|
|
13846
|
+
} : {},
|
|
13466
13847
|
...entry.memo !== void 0 ? { memo: entry.memo } : {},
|
|
13467
13848
|
receivedAt
|
|
13468
13849
|
};
|
|
@@ -13561,6 +13942,9 @@ function claimAck(entry) {
|
|
|
13561
13942
|
function rejectAck(entry, reason) {
|
|
13562
13943
|
return { deliveryId: entry.deliveryId, disposition: "rejected", reason, cursor: entry.cursor };
|
|
13563
13944
|
}
|
|
13945
|
+
function isCoinlessEnvelope2(envelope) {
|
|
13946
|
+
return envelope.startsWith("none_");
|
|
13947
|
+
}
|
|
13564
13948
|
function toAssetAmounts(token) {
|
|
13565
13949
|
return (token.value?.assets ?? []).map((asset) => ({
|
|
13566
13950
|
coinId: asset.coinId,
|
|
@@ -14128,6 +14512,37 @@ var SpendQueue = class {
|
|
|
14128
14512
|
}
|
|
14129
14513
|
return { kind: "queued", settled: this.enqueue(reservationId, request.coinId, amount) };
|
|
14130
14514
|
}
|
|
14515
|
+
/**
|
|
14516
|
+
* #777: reserve a NAMED coinless source. Never queues — there is nothing to wait
|
|
14517
|
+
* for. A coin plan waits because some OTHER combination may free up; a named
|
|
14518
|
+
* token is either free now or held by a reservation that only its own transfer
|
|
14519
|
+
* can release, so queueing would block until a timeout on a spend that cannot
|
|
14520
|
+
* become possible.
|
|
14521
|
+
*/
|
|
14522
|
+
planCoinless(reservationId, tokenId) {
|
|
14523
|
+
if (this.destroyed) {
|
|
14524
|
+
throw new SphereError("Module has been destroyed", "MODULE_DESTROYED");
|
|
14525
|
+
}
|
|
14526
|
+
const unproven = this.deps.ledger.unprovenReason();
|
|
14527
|
+
if (unproven !== null) {
|
|
14528
|
+
throw new SphereError(`Cannot spend yet: ${unproven}`, "SEND_SYNC_PENDING");
|
|
14529
|
+
}
|
|
14530
|
+
if (this.deps.spendableCoinless?.(tokenId) !== true) {
|
|
14531
|
+
throw new SphereError(
|
|
14532
|
+
`Token ${tokenId} is not a spendable coinless holding`,
|
|
14533
|
+
"VALIDATION_ERROR"
|
|
14534
|
+
);
|
|
14535
|
+
}
|
|
14536
|
+
const holder = this.deps.ledger.holderOf(tokenId);
|
|
14537
|
+
if (holder !== void 0) {
|
|
14538
|
+
throw new SphereError(
|
|
14539
|
+
`Token ${tokenId} is already reserved by transfer ${holder}`,
|
|
14540
|
+
"VALIDATION_ERROR"
|
|
14541
|
+
);
|
|
14542
|
+
}
|
|
14543
|
+
this.deps.ledger.reserve(reservationId, [{ tokenId }]);
|
|
14544
|
+
return { reservationId, plan: { direct: [tokenId] } };
|
|
14545
|
+
}
|
|
14131
14546
|
/**
|
|
14132
14547
|
* The sources `plan()` WOULD pick right now, reserving nothing and queueing
|
|
14133
14548
|
* nothing — a read-only probe used to warm blobs while the user is still
|
|
@@ -14317,6 +14732,7 @@ function composeFacadeParts(deps, hooks) {
|
|
|
14317
14732
|
const queue = new SpendQueue({
|
|
14318
14733
|
ledger,
|
|
14319
14734
|
getPool: (coinId) => view.pool(coinId),
|
|
14735
|
+
spendableCoinless: (tokenId) => view.spendableCoinless(tokenId),
|
|
14320
14736
|
...deps.workBudget !== void 0 ? { workBudget: deps.workBudget } : {}
|
|
14321
14737
|
});
|
|
14322
14738
|
const historyStore = new History({
|
|
@@ -14400,10 +14816,9 @@ function buildMachineDeps(deps, hooks, historyStore) {
|
|
|
14400
14816
|
recordHistory: async ({ transferId, payload, committedAmount }) => {
|
|
14401
14817
|
await historyStore.recordSent({
|
|
14402
14818
|
transferId,
|
|
14403
|
-
|
|
14404
|
-
//
|
|
14405
|
-
|
|
14406
|
-
amount: committedAmount,
|
|
14819
|
+
// §5.9: the SETTLED amount, never payload.amount — the plan. A token
|
|
14820
|
+
// spend moved no coin: `assets: []` + tokenId (wallet-api#151 / §10).
|
|
14821
|
+
...payload.kind === "coinless" ? { assets: [], tokenId: payload.direct[0] } : { assets: [{ coinId: payload.coinId, amount: committedAmount }] },
|
|
14407
14822
|
recipientPubkey: payload.recipient,
|
|
14408
14823
|
...payload.memo !== void 0 ? { memo: payload.memo } : {}
|
|
14409
14824
|
});
|
|
@@ -14438,12 +14853,10 @@ function buildReceive(deps, hooks, historyStore, heldStates, refreshView) {
|
|
|
14438
14853
|
});
|
|
14439
14854
|
}
|
|
14440
14855
|
async function recordReceived(historyStore, record) {
|
|
14441
|
-
const first = record.assets[0];
|
|
14442
14856
|
await historyStore.recordReceived({
|
|
14443
14857
|
tokenId: record.tokenId,
|
|
14444
14858
|
stateHash: record.stateHash,
|
|
14445
|
-
|
|
14446
|
-
amount: first?.amount ?? "0",
|
|
14859
|
+
assets: record.assets,
|
|
14447
14860
|
...record.senderPubkey !== void 0 ? { senderPubkey: record.senderPubkey } : {},
|
|
14448
14861
|
...record.senderNametag !== void 0 ? { senderNametag: record.senderNametag } : {},
|
|
14449
14862
|
...record.memo !== void 0 ? { memo: record.memo } : {},
|
|
@@ -14591,6 +15004,13 @@ var PaymentsFacade = class {
|
|
|
14591
15004
|
tokens(filter) {
|
|
14592
15005
|
return this.view.tokens(this.deps.registry, filter);
|
|
14593
15006
|
}
|
|
15007
|
+
coinless() {
|
|
15008
|
+
return this.view.coinless(this.deps.registry);
|
|
15009
|
+
}
|
|
15010
|
+
tokenData(tokenId) {
|
|
15011
|
+
const deps = { engine: this.engine(), view: this.view, storagePort: this.deps.storagePort };
|
|
15012
|
+
return readTokenData(deps, tokenId);
|
|
15013
|
+
}
|
|
14594
15014
|
history(page) {
|
|
14595
15015
|
return this.historyStore.page(page ?? {});
|
|
14596
15016
|
}
|
|
@@ -14615,6 +15035,9 @@ var PaymentsFacade = class {
|
|
|
14615
15035
|
send(request) {
|
|
14616
15036
|
return this.track(this.sendOutcome(request));
|
|
14617
15037
|
}
|
|
15038
|
+
sendCoinless(request) {
|
|
15039
|
+
return this.track(this.sendCoinlessOutcome(request));
|
|
15040
|
+
}
|
|
14618
15041
|
async receive() {
|
|
14619
15042
|
const transfers = await this.track(this.receiveLoop.drainOnce());
|
|
14620
15043
|
return { transfers };
|
|
@@ -14645,12 +15068,32 @@ var PaymentsFacade = class {
|
|
|
14645
15068
|
nowMs() {
|
|
14646
15069
|
return (this.deps.now ?? Date.now)();
|
|
14647
15070
|
}
|
|
15071
|
+
mintDeps() {
|
|
15072
|
+
return {
|
|
15073
|
+
engine: this.engine(),
|
|
15074
|
+
mintJournal: this.machineStores.mintJournal,
|
|
15075
|
+
storagePort: this.deps.storagePort,
|
|
15076
|
+
recordMint: (input) => this.historyStore.recordMint(input),
|
|
15077
|
+
armHeartbeat: () => this.heartbeat.arm(),
|
|
15078
|
+
noteHeldState: (tokenId, stateHash) => void this.heldStates.set(tokenId, stateHash),
|
|
15079
|
+
refreshView: () => this.trackTail(this.view.delta()),
|
|
15080
|
+
ownPubkeyBytes: this.ownPubkeyBytes,
|
|
15081
|
+
now: () => this.nowMs()
|
|
15082
|
+
};
|
|
15083
|
+
}
|
|
14648
15084
|
// ── send policy (§5.5 + old-loop parity; the machine stays policy-free) ────
|
|
14649
15085
|
/** The ONE place a send() outcome is shaped: success emits in finishSend, a
|
|
14650
15086
|
* CLEAN rejection emits `transfer:updated{status:'failed'}` here (§4). */
|
|
14651
|
-
|
|
15087
|
+
sendOutcome(request) {
|
|
15088
|
+
return this.runJob({ kind: "coin", request }, request.amount);
|
|
15089
|
+
}
|
|
15090
|
+
/** A token spend has no amount; '0' keeps the shortfall arithmetic total-free. */
|
|
15091
|
+
sendCoinlessOutcome(request) {
|
|
15092
|
+
return this.runJob({ kind: "coinless", request }, "0");
|
|
15093
|
+
}
|
|
15094
|
+
async runJob(job, amount) {
|
|
14652
15095
|
const run = {
|
|
14653
|
-
amount
|
|
15096
|
+
amount,
|
|
14654
15097
|
delivered: [],
|
|
14655
15098
|
sentTokens: [],
|
|
14656
15099
|
tokenTransfers: [],
|
|
@@ -14660,7 +15103,7 @@ var PaymentsFacade = class {
|
|
|
14660
15103
|
lastTransferId: ""
|
|
14661
15104
|
};
|
|
14662
15105
|
try {
|
|
14663
|
-
return await this.sendWithPolicy(
|
|
15106
|
+
return await this.sendWithPolicy(job, run);
|
|
14664
15107
|
} catch (err) {
|
|
14665
15108
|
this.emitCleanFailure(err, run.lastTransferId);
|
|
14666
15109
|
throw err;
|
|
@@ -14680,19 +15123,19 @@ var PaymentsFacade = class {
|
|
|
14680
15123
|
};
|
|
14681
15124
|
this.deps.emit("transfer:updated", failed);
|
|
14682
15125
|
}
|
|
14683
|
-
async sendWithPolicy(
|
|
14684
|
-
const recipient = await requireSameNetworkRecipient(this.deps, request.recipient);
|
|
15126
|
+
async sendWithPolicy(job, run) {
|
|
15127
|
+
const recipient = await requireSameNetworkRecipient(this.deps, job.request.recipient);
|
|
14685
15128
|
for (let attempt = 0; ; attempt++) {
|
|
14686
15129
|
let ctx;
|
|
14687
15130
|
try {
|
|
14688
|
-
ctx = await this.planAndMaterialize(recipient.chainPubkey,
|
|
15131
|
+
ctx = await this.planAndMaterialize(recipient.chainPubkey, job, run);
|
|
14689
15132
|
} catch (err) {
|
|
14690
|
-
throw
|
|
15133
|
+
throw partialize(err, run);
|
|
14691
15134
|
}
|
|
14692
15135
|
const disposition = await this.runAttempt(ctx);
|
|
14693
|
-
if (disposition.kind === "rethrow") throw
|
|
15136
|
+
if (disposition.kind === "rethrow") throw partialize(disposition.error, run);
|
|
14694
15137
|
if (disposition.kind === "retry-full") {
|
|
14695
|
-
if (attempt >= MAX_RESELECT) throw
|
|
15138
|
+
if (job.kind === "coinless" || attempt >= MAX_RESELECT) throw partialize(disposition.error, run);
|
|
14696
15139
|
continue;
|
|
14697
15140
|
}
|
|
14698
15141
|
if (disposition.kind === "success") {
|
|
@@ -14748,7 +15191,7 @@ var PaymentsFacade = class {
|
|
|
14748
15191
|
async disposeFailedAttempt(ctx, err) {
|
|
14749
15192
|
if (isPossiblyCommittedSendOutcome(err)) {
|
|
14750
15193
|
this.settleKeepOpen(ctx);
|
|
14751
|
-
|
|
15194
|
+
stampTransferId(err, ctx.transferId);
|
|
14752
15195
|
return { kind: "rethrow", error: err };
|
|
14753
15196
|
}
|
|
14754
15197
|
const backstop = await this.machineStores.backstop.getByKey(ctx.transferId);
|
|
@@ -14782,7 +15225,7 @@ var PaymentsFacade = class {
|
|
|
14782
15225
|
async disposeConvergeFailure(ctx, err) {
|
|
14783
15226
|
if (isPossiblyCommittedSendOutcome(err)) {
|
|
14784
15227
|
this.settleKeepOpen(ctx);
|
|
14785
|
-
|
|
15228
|
+
stampTransferId(err, ctx.transferId);
|
|
14786
15229
|
return { kind: "rethrow", error: err };
|
|
14787
15230
|
}
|
|
14788
15231
|
if (classifyError(err) === "conflict") {
|
|
@@ -14793,9 +15236,23 @@ var PaymentsFacade = class {
|
|
|
14793
15236
|
}
|
|
14794
15237
|
return { kind: "rethrow", error: err };
|
|
14795
15238
|
}
|
|
14796
|
-
async planAndMaterialize(recipientPubkey,
|
|
15239
|
+
async planAndMaterialize(recipientPubkey, job, run) {
|
|
14797
15240
|
const transferId = this.newId();
|
|
14798
15241
|
run.lastTransferId = transferId;
|
|
15242
|
+
if (job.kind === "coinless") {
|
|
15243
|
+
const spend2 = this.queue.planCoinless(transferId, job.request.tokenId);
|
|
15244
|
+
const sourceIds2 = this.markPlanned(transferId, "", spend2);
|
|
15245
|
+
try {
|
|
15246
|
+
return await materializeCoinlessSpend(
|
|
15247
|
+
{ engine: this.engine(), storagePort: this.deps.storagePort },
|
|
15248
|
+
{ transferId, recipientPubkey, request: job.request, sourceIds: sourceIds2 }
|
|
15249
|
+
);
|
|
15250
|
+
} catch (err) {
|
|
15251
|
+
this.settleFailure(transferId, "", sourceIds2);
|
|
15252
|
+
throw err;
|
|
15253
|
+
}
|
|
15254
|
+
}
|
|
15255
|
+
const request = { ...job.request, amount: run.amount };
|
|
14799
15256
|
const planned = this.queue.plan(transferId, { coinId: request.coinId, amount: request.amount });
|
|
14800
15257
|
const spend = planned.kind === "planned" ? planned.spend : await planned.settled;
|
|
14801
15258
|
const sourceIds = this.markPlanned(transferId, request.coinId, spend);
|
|
@@ -14929,22 +15386,6 @@ var PaymentsFacade = class {
|
|
|
14929
15386
|
return result;
|
|
14930
15387
|
}
|
|
14931
15388
|
/** Nothing delivered → UNWRAPPED; after ≥1 delivered leg every failure surfaces as PartialSendConflictError over the settled set. */
|
|
14932
|
-
partialize(err, run) {
|
|
14933
|
-
if (run.delivered.length === 0) return err;
|
|
14934
|
-
return new PartialSendConflictError(
|
|
14935
|
-
"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.",
|
|
14936
|
-
run.firstPartialId ?? "",
|
|
14937
|
-
run.delivered,
|
|
14938
|
-
run.amount,
|
|
14939
|
-
err
|
|
14940
|
-
);
|
|
14941
|
-
}
|
|
14942
|
-
/** #441: possibly-committed errors must carry the transferId for the settling journal. */
|
|
14943
|
-
stampTransferId(err, transferId) {
|
|
14944
|
-
if (err instanceof SphereError && isPossiblyCommittedSendOutcome(err) && err.transferId === void 0) {
|
|
14945
|
-
err.transferId = transferId;
|
|
14946
|
-
}
|
|
14947
|
-
}
|
|
14948
15389
|
async softAbort(transferId) {
|
|
14949
15390
|
try {
|
|
14950
15391
|
await this.deps.client.abortIntent(transferId);
|
|
@@ -14960,57 +15401,11 @@ var PaymentsFacade = class {
|
|
|
14960
15401
|
const mintId = this.newId();
|
|
14961
15402
|
this.activeMoneyOps.add(mintId);
|
|
14962
15403
|
try {
|
|
14963
|
-
return await this.
|
|
15404
|
+
return await runMintUnderJournal(this.mintDeps(), { mintId, coinId, amount });
|
|
14964
15405
|
} finally {
|
|
14965
15406
|
this.activeMoneyOps.delete(mintId);
|
|
14966
15407
|
}
|
|
14967
15408
|
}
|
|
14968
|
-
async mintUnderJournal(mintId, coinId, amount) {
|
|
14969
|
-
const engine = this.engine();
|
|
14970
|
-
const entry = {
|
|
14971
|
-
mintId,
|
|
14972
|
-
coinId,
|
|
14973
|
-
amount: amount.toString(),
|
|
14974
|
-
tokenId: "",
|
|
14975
|
-
createdAt: this.nowMs()
|
|
14976
|
-
};
|
|
14977
|
-
await this.machineStores.mintJournal.upsert(entry);
|
|
14978
|
-
let token;
|
|
14979
|
-
try {
|
|
14980
|
-
token = await engine.mint(mintParams(this.ownPubkeyBytes, coinId, amount), { transferId: mintId });
|
|
14981
|
-
} catch (err) {
|
|
14982
|
-
this.heartbeat.arm();
|
|
14983
|
-
return { success: false, error: messageOf(err) };
|
|
14984
|
-
}
|
|
14985
|
-
if (token.blob.tokenId !== entry.tokenId) {
|
|
14986
|
-
await this.machineStores.mintJournal.upsert({ ...entry, tokenId: token.blob.tokenId });
|
|
14987
|
-
}
|
|
14988
|
-
try {
|
|
14989
|
-
await this.finalizeMint(engine, mintId, token, coinId, amount.toString());
|
|
14990
|
-
} catch (err) {
|
|
14991
|
-
this.heartbeat.arm();
|
|
14992
|
-
return { success: false, tokenId: token.blob.tokenId, error: messageOf(err) };
|
|
14993
|
-
}
|
|
14994
|
-
return { success: true, tokenId: token.blob.tokenId };
|
|
14995
|
-
}
|
|
14996
|
-
async finalizeMint(engine, mintId, token, coinId, amount) {
|
|
14997
|
-
const bytes = token.blob.token;
|
|
14998
|
-
const digest = bytesToHex3(sha256(bytes));
|
|
14999
|
-
const keys = await this.deps.storagePort.uploadBlobs([{ sha256: digest, bytes }]);
|
|
15000
|
-
const key = keys.get(digest);
|
|
15001
|
-
if (key === void 0) {
|
|
15002
|
-
throw new SphereError(`no upload key returned for mint blob ${digest}`, "STORAGE_ERROR");
|
|
15003
|
-
}
|
|
15004
|
-
await this.deps.storagePort.applyDelta({
|
|
15005
|
-
transferId: mintId,
|
|
15006
|
-
spent: [],
|
|
15007
|
-
added: [{ tokenId: token.blob.tokenId, key }]
|
|
15008
|
-
});
|
|
15009
|
-
await this.historyStore.recordMint({ tokenId: token.blob.tokenId, coinId, amount });
|
|
15010
|
-
await this.machineStores.mintJournal.removeByKey(mintId);
|
|
15011
|
-
this.heldStates.set(token.blob.tokenId, (await engine.deliveryKeys(bytes)).stateHash);
|
|
15012
|
-
this.trackTail(this.view.delta());
|
|
15013
|
-
}
|
|
15014
15409
|
/** @returns how many journal entries were RESOLVED (cleared) — heartbeat progress. */
|
|
15015
15410
|
async replayMints() {
|
|
15016
15411
|
let resolved = 0;
|
|
@@ -15042,7 +15437,7 @@ var PaymentsFacade = class {
|
|
|
15042
15437
|
if (token.blob.tokenId !== entry.tokenId) {
|
|
15043
15438
|
await this.machineStores.mintJournal.upsert({ ...entry, tokenId: token.blob.tokenId });
|
|
15044
15439
|
}
|
|
15045
|
-
await this.
|
|
15440
|
+
await finalizeMint(this.mintDeps(), entry.mintId, token, entry.coinId, entry.amount);
|
|
15046
15441
|
return true;
|
|
15047
15442
|
}
|
|
15048
15443
|
async tokenInServerInventory(tokenId) {
|
|
@@ -16817,7 +17212,8 @@ var Sphere = class _Sphere {
|
|
|
16817
17212
|
}
|
|
16818
17213
|
const progress = options.onProgress;
|
|
16819
17214
|
logger.debug("Sphere", "Starting import...");
|
|
16820
|
-
const
|
|
17215
|
+
const liveHere = _Sphere.liveOn(options.storage).some((s) => s._storage === options.storage);
|
|
17216
|
+
const needsClear = liveHere || await _Sphere.exists(options.storage);
|
|
16821
17217
|
if (needsClear) {
|
|
16822
17218
|
progress?.({ step: "clearing", message: "Clearing previous wallet data..." });
|
|
16823
17219
|
logger.debug("Sphere", "Clearing existing wallet data...");
|