@unicitylabs/sphere-sdk 0.16.0 → 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 +566 -171
- 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 +566 -171
- 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/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.d.cts +11 -0
- package/dist/impl/nodejs/index.d.ts +11 -0
- 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 +566 -171
- 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 +566 -171
- 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
|
@@ -3,6 +3,9 @@ import { IPaymentData } from '@unicitylabs/state-transition-sdk/lib/payment/IPay
|
|
|
3
3
|
import { Asset } from '@unicitylabs/state-transition-sdk/lib/payment/asset/Asset.js';
|
|
4
4
|
import { PaymentAssetCollection } from '@unicitylabs/state-transition-sdk/lib/payment/asset/PaymentAssetCollection.js';
|
|
5
5
|
|
|
6
|
+
/** `none_*` = coinless; `bare_collection` = a dialect this SDK cannot read. */
|
|
7
|
+
type ValueEnvelope = 'sphere' | 'bare_collection' | 'none_tag' | 'none_other' | 'none_absent';
|
|
8
|
+
|
|
6
9
|
/**
|
|
7
10
|
* token-engine/types.ts — the FROZEN, sphere-domain contract surface.
|
|
8
11
|
*
|
|
@@ -65,6 +68,22 @@ interface SphereToken {
|
|
|
65
68
|
readonly blob: TokenBlob;
|
|
66
69
|
/** Decoded value (cached); null when the token carries no sphere payment data. */
|
|
67
70
|
readonly value: SphereValue | null;
|
|
71
|
+
/**
|
|
72
|
+
* Which value envelope the genesis payload carried (#778). Distinguishes the
|
|
73
|
+
* reasons `value` is null, which the old boolean predicate collapsed:
|
|
74
|
+
* `'none_*'` means the token genuinely names no coin — a COINLESS token — while
|
|
75
|
+
* `'bare_collection'` means it carries coins in the bridged dialect this SDK
|
|
76
|
+
* does not decode, so a zero here is "cannot read", not "has none". A corrupt
|
|
77
|
+
* envelope never reaches this field: it throws during classification.
|
|
78
|
+
*/
|
|
79
|
+
readonly valueEnvelope: ValueEnvelope;
|
|
80
|
+
/**
|
|
81
|
+
* Genesis `TokenType`, lowercase hex. The token's CLASS, never its instance —
|
|
82
|
+
* `blob.tokenId` is the instance key (wallet-api#147). Only as meaningful as its
|
|
83
|
+
* minter made it: `mint()` and split outputs derive one per operation, so for
|
|
84
|
+
* value tokens it is per-mint noise. Never a spend gate.
|
|
85
|
+
*/
|
|
86
|
+
readonly tokenType: string;
|
|
68
87
|
}
|
|
69
88
|
interface MintParams {
|
|
70
89
|
/** Recipient's 33-byte compressed chain pubkey; engine derives the predicate. */
|
|
@@ -3,6 +3,9 @@ import { IPaymentData } from '@unicitylabs/state-transition-sdk/lib/payment/IPay
|
|
|
3
3
|
import { Asset } from '@unicitylabs/state-transition-sdk/lib/payment/asset/Asset.js';
|
|
4
4
|
import { PaymentAssetCollection } from '@unicitylabs/state-transition-sdk/lib/payment/asset/PaymentAssetCollection.js';
|
|
5
5
|
|
|
6
|
+
/** `none_*` = coinless; `bare_collection` = a dialect this SDK cannot read. */
|
|
7
|
+
type ValueEnvelope = 'sphere' | 'bare_collection' | 'none_tag' | 'none_other' | 'none_absent';
|
|
8
|
+
|
|
6
9
|
/**
|
|
7
10
|
* token-engine/types.ts — the FROZEN, sphere-domain contract surface.
|
|
8
11
|
*
|
|
@@ -65,6 +68,22 @@ interface SphereToken {
|
|
|
65
68
|
readonly blob: TokenBlob;
|
|
66
69
|
/** Decoded value (cached); null when the token carries no sphere payment data. */
|
|
67
70
|
readonly value: SphereValue | null;
|
|
71
|
+
/**
|
|
72
|
+
* Which value envelope the genesis payload carried (#778). Distinguishes the
|
|
73
|
+
* reasons `value` is null, which the old boolean predicate collapsed:
|
|
74
|
+
* `'none_*'` means the token genuinely names no coin — a COINLESS token — while
|
|
75
|
+
* `'bare_collection'` means it carries coins in the bridged dialect this SDK
|
|
76
|
+
* does not decode, so a zero here is "cannot read", not "has none". A corrupt
|
|
77
|
+
* envelope never reaches this field: it throws during classification.
|
|
78
|
+
*/
|
|
79
|
+
readonly valueEnvelope: ValueEnvelope;
|
|
80
|
+
/**
|
|
81
|
+
* Genesis `TokenType`, lowercase hex. The token's CLASS, never its instance —
|
|
82
|
+
* `blob.tokenId` is the instance key (wallet-api#147). Only as meaningful as its
|
|
83
|
+
* minter made it: `mint()` and split outputs derive one per operation, so for
|
|
84
|
+
* value tokens it is per-mint noise. Never a spend gate.
|
|
85
|
+
*/
|
|
86
|
+
readonly tokenType: string;
|
|
68
87
|
}
|
|
69
88
|
interface MintParams {
|
|
70
89
|
/** Recipient's 33-byte compressed chain pubkey; engine derives the predicate. */
|
|
@@ -43,6 +43,8 @@ import { DataHasherFactory } from "@unicitylabs/state-transition-sdk/lib/crypto/
|
|
|
43
43
|
import { CborSerializer } from "@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborSerializer.js";
|
|
44
44
|
import { CborDeserializer } from "@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborDeserializer.js";
|
|
45
45
|
import { CborError } from "@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborError.js";
|
|
46
|
+
import { CborReader } from "@unicitylabs/state-transition-sdk/lib/serialization/cbor/CborReader.js";
|
|
47
|
+
import { MajorType } from "@unicitylabs/state-transition-sdk/lib/serialization/cbor/MajorType.js";
|
|
46
48
|
import { Asset } from "@unicitylabs/state-transition-sdk/lib/payment/asset/Asset.js";
|
|
47
49
|
import { AssetId } from "@unicitylabs/state-transition-sdk/lib/payment/asset/AssetId.js";
|
|
48
50
|
import { PaymentAssetCollection } from "@unicitylabs/state-transition-sdk/lib/payment/asset/PaymentAssetCollection.js";
|
|
@@ -1000,6 +1002,123 @@ async function burntTokenFromCheckpoint(deps, storedBytes, reDerivedBurnTx, sour
|
|
|
1000
1002
|
return burntToken;
|
|
1001
1003
|
}
|
|
1002
1004
|
|
|
1005
|
+
// token-engine/value-envelope.ts
|
|
1006
|
+
var SELF_DESCRIBED_CBOR_TAG = 55799n;
|
|
1007
|
+
var CBOR_MAJOR_TYPE_MASK = 224;
|
|
1008
|
+
var describe = (error) => error instanceof Error ? error.message : String(error);
|
|
1009
|
+
var invalid = (message) => new SphereError(message, "VALIDATION_ERROR");
|
|
1010
|
+
function majorTypeOf(bytes) {
|
|
1011
|
+
const first = bytes.at(0);
|
|
1012
|
+
return first === void 0 ? null : first & CBOR_MAJOR_TYPE_MASK;
|
|
1013
|
+
}
|
|
1014
|
+
function headTagNumber(bytes) {
|
|
1015
|
+
try {
|
|
1016
|
+
return new CborReader(bytes).readLength(MajorType.TAG);
|
|
1017
|
+
} catch {
|
|
1018
|
+
return null;
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
function isAssetShaped(item) {
|
|
1022
|
+
try {
|
|
1023
|
+
return CborDeserializer.decodeArray(item, 2).every(
|
|
1024
|
+
(field) => majorTypeOf(field) === MajorType.BYTE_STRING
|
|
1025
|
+
);
|
|
1026
|
+
} catch {
|
|
1027
|
+
return false;
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
function arrayItemsOrNull(bytes) {
|
|
1031
|
+
try {
|
|
1032
|
+
return CborDeserializer.decodeArray(bytes);
|
|
1033
|
+
} catch {
|
|
1034
|
+
return null;
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
function decodeSphere(genesisData) {
|
|
1038
|
+
try {
|
|
1039
|
+
return SpherePaymentData.fromCBOR(genesisData).toValue();
|
|
1040
|
+
} catch (error) {
|
|
1041
|
+
throw invalid(`Failed to decode token payment data: ${describe(error)}`);
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
function taggedItemBody(genesisData) {
|
|
1045
|
+
try {
|
|
1046
|
+
return CborDeserializer.decodeTag(genesisData).data;
|
|
1047
|
+
} catch (error) {
|
|
1048
|
+
throw invalid(
|
|
1049
|
+
`Token value payload is a CBOR tag whose item is malformed or carries trailing bytes: ${describe(error)}`
|
|
1050
|
+
);
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
function selfDescribedBodyCarriesValue(body) {
|
|
1054
|
+
const major = majorTypeOf(body);
|
|
1055
|
+
if (major === MajorType.TAG) {
|
|
1056
|
+
const inner = headTagNumber(body);
|
|
1057
|
+
return inner === SpherePaymentData.CBOR_TAG || inner === SELF_DESCRIBED_CBOR_TAG;
|
|
1058
|
+
}
|
|
1059
|
+
if (major === MajorType.ARRAY) {
|
|
1060
|
+
const items = arrayItemsOrNull(body);
|
|
1061
|
+
return items !== null && items.some(isAssetShaped);
|
|
1062
|
+
}
|
|
1063
|
+
return false;
|
|
1064
|
+
}
|
|
1065
|
+
function classifyTagged(genesisData) {
|
|
1066
|
+
const tag = headTagNumber(genesisData);
|
|
1067
|
+
if (tag === null) {
|
|
1068
|
+
throw invalid(
|
|
1069
|
+
"Token value payload has an unreadable or non-canonically encoded CBOR tag head \u2014 it could be a malformed SpherePaymentData envelope"
|
|
1070
|
+
);
|
|
1071
|
+
}
|
|
1072
|
+
if (tag === SpherePaymentData.CBOR_TAG) {
|
|
1073
|
+
return { envelope: "sphere", value: decodeSphere(genesisData) };
|
|
1074
|
+
}
|
|
1075
|
+
const body = taggedItemBody(genesisData);
|
|
1076
|
+
if (tag === SELF_DESCRIBED_CBOR_TAG && selfDescribedBodyCarriesValue(body)) {
|
|
1077
|
+
throw invalid(
|
|
1078
|
+
"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"
|
|
1079
|
+
);
|
|
1080
|
+
}
|
|
1081
|
+
return { envelope: "none_tag", value: null };
|
|
1082
|
+
}
|
|
1083
|
+
function classifyArray(genesisData) {
|
|
1084
|
+
const items = arrayItemsOrNull(genesisData);
|
|
1085
|
+
if (items === null) {
|
|
1086
|
+
throw invalid("Token value payload is not PaymentAssetCollection: malformed CBOR array");
|
|
1087
|
+
}
|
|
1088
|
+
return items.some(isAssetShaped) ? { envelope: "bare_collection", value: null } : { envelope: "none_other", value: null };
|
|
1089
|
+
}
|
|
1090
|
+
function classifyValueEnvelope(genesisData) {
|
|
1091
|
+
if (genesisData === null) return { envelope: "none_absent", value: null };
|
|
1092
|
+
const major = majorTypeOf(genesisData);
|
|
1093
|
+
if (major === null) return { envelope: "none_absent", value: null };
|
|
1094
|
+
if (major === MajorType.TAG) return classifyTagged(genesisData);
|
|
1095
|
+
if (major === MajorType.ARRAY) return classifyArray(genesisData);
|
|
1096
|
+
return { envelope: "none_other", value: null };
|
|
1097
|
+
}
|
|
1098
|
+
function wrapToken(sdkToken) {
|
|
1099
|
+
const { envelope, value } = classifyValueEnvelope(sdkToken.genesis.data);
|
|
1100
|
+
const blob = {
|
|
1101
|
+
tokenId: HexConverter.encode(sdkToken.id.bytes),
|
|
1102
|
+
token: sdkToken.toCBOR()
|
|
1103
|
+
};
|
|
1104
|
+
return {
|
|
1105
|
+
sdkToken,
|
|
1106
|
+
blob,
|
|
1107
|
+
value,
|
|
1108
|
+
valueEnvelope: envelope,
|
|
1109
|
+
tokenType: HexConverter.encode(sdkToken.type.bytes)
|
|
1110
|
+
};
|
|
1111
|
+
}
|
|
1112
|
+
function assertMintableData(data) {
|
|
1113
|
+
try {
|
|
1114
|
+
classifyValueEnvelope(data);
|
|
1115
|
+
} catch (error) {
|
|
1116
|
+
throw invalid(
|
|
1117
|
+
`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.`
|
|
1118
|
+
);
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1003
1122
|
// token-engine/SphereTokenEngine.ts
|
|
1004
1123
|
var DEFAULT_PROOF_POLL_INTERVAL_MS = 300;
|
|
1005
1124
|
var MAX_MINT_CONCURRENCY = 8;
|
|
@@ -1056,7 +1175,7 @@ var SphereTokenEngine = class {
|
|
|
1056
1175
|
return sdkToken.latestTransaction.data;
|
|
1057
1176
|
}
|
|
1058
1177
|
const data = sdkToken.genesis.data;
|
|
1059
|
-
if (data &&
|
|
1178
|
+
if (data && classifyValueEnvelope(data).envelope === "sphere") {
|
|
1060
1179
|
return SpherePaymentData.fromCBOR(data).memo;
|
|
1061
1180
|
}
|
|
1062
1181
|
return null;
|
|
@@ -1086,9 +1205,10 @@ var SphereTokenEngine = class {
|
|
|
1086
1205
|
);
|
|
1087
1206
|
const certified = await mintTx.toCertifiedTransaction(this.deps.trustBase, this.deps.predicateVerifier, this.deps.unicityCertificateVerifier, proof);
|
|
1088
1207
|
const token = await Token.mint(certified, this.deps.verificationContext);
|
|
1089
|
-
return
|
|
1208
|
+
return wrapToken(token);
|
|
1090
1209
|
}
|
|
1091
1210
|
async mintDataToken(params, options) {
|
|
1211
|
+
assertMintableData(params.data);
|
|
1092
1212
|
const recipient = SignaturePredicate.create(params.recipientPubkey);
|
|
1093
1213
|
const tokenType = params.tokenType ? new TokenType(params.tokenType) : TokenType.generate();
|
|
1094
1214
|
const salt = params.salt ? TokenSalt.fromBytes(params.salt) : TokenSalt.generate();
|
|
@@ -1103,7 +1223,7 @@ var SphereTokenEngine = class {
|
|
|
1103
1223
|
);
|
|
1104
1224
|
const certified = await mintTx.toCertifiedTransaction(this.deps.trustBase, this.deps.predicateVerifier, this.deps.unicityCertificateVerifier, proof);
|
|
1105
1225
|
const token = await Token.mint(certified, this.deps.verificationContext);
|
|
1106
|
-
return
|
|
1226
|
+
return wrapToken(token);
|
|
1107
1227
|
}
|
|
1108
1228
|
async transfer(params, options) {
|
|
1109
1229
|
this.assertOwned(params.token);
|
|
@@ -1126,13 +1246,19 @@ var SphereTokenEngine = class {
|
|
|
1126
1246
|
);
|
|
1127
1247
|
const certified = await transferTx.toCertifiedTransaction(this.deps.trustBase, this.deps.predicateVerifier, this.deps.unicityCertificateVerifier, proof);
|
|
1128
1248
|
const transferred = await params.token.sdkToken.transfer(certified, this.deps.verificationContext);
|
|
1129
|
-
return
|
|
1249
|
+
return wrapToken(transferred);
|
|
1130
1250
|
}
|
|
1131
1251
|
async split(params, options) {
|
|
1132
1252
|
this.assertOwned(params.token);
|
|
1133
1253
|
if (params.outputs.length === 0) {
|
|
1134
1254
|
throw new SphereError("Split requires at least one output", "VALIDATION_ERROR");
|
|
1135
1255
|
}
|
|
1256
|
+
if (params.token.value === null) {
|
|
1257
|
+
throw new SphereError(
|
|
1258
|
+
`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.`,
|
|
1259
|
+
"VALIDATION_ERROR"
|
|
1260
|
+
);
|
|
1261
|
+
}
|
|
1136
1262
|
const transferId = this.resolveTransferId(options);
|
|
1137
1263
|
const requests = params.outputs.map(
|
|
1138
1264
|
(o, i) => SplitTokenRequest.create(
|
|
@@ -1254,7 +1380,7 @@ var SphereTokenEngine = class {
|
|
|
1254
1380
|
const proof = await this.submitSplitMintLeg(certData, mintTx, options);
|
|
1255
1381
|
const certified = await mintTx.toCertifiedTransaction(this.deps.trustBase, this.deps.predicateVerifier, this.deps.unicityCertificateVerifier, proof);
|
|
1256
1382
|
const token = await Token.mint(certified, this.deps.verificationContext);
|
|
1257
|
-
return
|
|
1383
|
+
return wrapToken(token);
|
|
1258
1384
|
}
|
|
1259
1385
|
/**
|
|
1260
1386
|
* Submit one split-mint leg. A `TRANSACTION_HASH_MISMATCH` on a mint leg is NOT a foreign spend
|
|
@@ -1312,7 +1438,7 @@ var SphereTokenEngine = class {
|
|
|
1312
1438
|
"VALIDATION_ERROR"
|
|
1313
1439
|
);
|
|
1314
1440
|
}
|
|
1315
|
-
return
|
|
1441
|
+
return wrapToken(sdkToken);
|
|
1316
1442
|
}
|
|
1317
1443
|
// ── internals ────────────────────────────────────────────────────────────────
|
|
1318
1444
|
/**
|
|
@@ -1424,34 +1550,6 @@ var SphereTokenEngine = class {
|
|
|
1424
1550
|
throw new SphereError("Cannot transfer a token not owned by this engine identity", "VALIDATION_ERROR");
|
|
1425
1551
|
}
|
|
1426
1552
|
}
|
|
1427
|
-
/** Wrap an SDK token into a SphereToken: cache its blob (incl. stable tokenId) + decoded value. */
|
|
1428
|
-
wrapToken(sdkToken) {
|
|
1429
|
-
const data = sdkToken.genesis.data;
|
|
1430
|
-
let value = null;
|
|
1431
|
-
if (data && this.isSpherePaymentData(data)) {
|
|
1432
|
-
try {
|
|
1433
|
-
value = SpherePaymentData.fromCBOR(data).toValue();
|
|
1434
|
-
} catch (err) {
|
|
1435
|
-
throw new SphereError(
|
|
1436
|
-
`Failed to decode token payment data: ${err instanceof Error ? err.message : String(err)}`,
|
|
1437
|
-
"VALIDATION_ERROR"
|
|
1438
|
-
);
|
|
1439
|
-
}
|
|
1440
|
-
}
|
|
1441
|
-
const blob = {
|
|
1442
|
-
tokenId: HexConverter.encode(sdkToken.id.bytes),
|
|
1443
|
-
token: sdkToken.toCBOR()
|
|
1444
|
-
};
|
|
1445
|
-
return { sdkToken, blob, value };
|
|
1446
|
-
}
|
|
1447
|
-
/** True if the bytes are a SpherePaymentData envelope (value token) vs a raw data token. */
|
|
1448
|
-
isSpherePaymentData(data) {
|
|
1449
|
-
try {
|
|
1450
|
-
return CborDeserializer.decodeTag(data).tag === SpherePaymentData.CBOR_TAG;
|
|
1451
|
-
} catch {
|
|
1452
|
-
return false;
|
|
1453
|
-
}
|
|
1454
|
-
}
|
|
1455
1553
|
/**
|
|
1456
1554
|
* Terminate the verification worker pool, if this engine was configured with
|
|
1457
1555
|
* one. Idempotent — the pool's dispose only touches workers it spawned.
|