@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.
Files changed (53) hide show
  1. package/dist/connect/index.cjs +11 -5
  2. package/dist/connect/index.cjs.map +1 -1
  3. package/dist/connect/index.d.cts +4 -1
  4. package/dist/connect/index.d.ts +4 -1
  5. package/dist/connect/index.js +11 -5
  6. package/dist/connect/index.js.map +1 -1
  7. package/dist/core/index.cjs +566 -171
  8. package/dist/core/index.cjs.map +1 -1
  9. package/dist/core/index.d.cts +67 -0
  10. package/dist/core/index.d.ts +67 -0
  11. package/dist/core/index.js +566 -171
  12. package/dist/core/index.js.map +1 -1
  13. package/dist/impl/browser/connect/index.cjs +11 -5
  14. package/dist/impl/browser/connect/index.cjs.map +1 -1
  15. package/dist/impl/browser/connect/index.d.cts +3 -1
  16. package/dist/impl/browser/connect/index.d.ts +3 -1
  17. package/dist/impl/browser/connect/index.js +11 -5
  18. package/dist/impl/browser/connect/index.js.map +1 -1
  19. package/dist/impl/nodejs/connect/index.cjs +10 -4
  20. package/dist/impl/nodejs/connect/index.cjs.map +1 -1
  21. package/dist/impl/nodejs/connect/index.d.cts +1 -1
  22. package/dist/impl/nodejs/connect/index.d.ts +1 -1
  23. package/dist/impl/nodejs/connect/index.js +10 -4
  24. package/dist/impl/nodejs/connect/index.js.map +1 -1
  25. package/dist/impl/nodejs/index.d.cts +11 -0
  26. package/dist/impl/nodejs/index.d.ts +11 -0
  27. package/dist/impl/shared/wallet-api/index.d.cts +11 -0
  28. package/dist/impl/shared/wallet-api/index.d.ts +11 -0
  29. package/dist/impl/wallet-api-v2/index.cjs +2 -0
  30. package/dist/impl/wallet-api-v2/index.cjs.map +1 -1
  31. package/dist/impl/wallet-api-v2/index.d.cts +13 -0
  32. package/dist/impl/wallet-api-v2/index.d.ts +13 -0
  33. package/dist/impl/wallet-api-v2/index.js +2 -0
  34. package/dist/impl/wallet-api-v2/index.js.map +1 -1
  35. package/dist/index.cjs +566 -171
  36. package/dist/index.cjs.map +1 -1
  37. package/dist/index.d.cts +61 -1
  38. package/dist/index.d.ts +61 -1
  39. package/dist/index.js +566 -171
  40. package/dist/index.js.map +1 -1
  41. package/dist/modules/payments-v2/index.cjs +609 -269
  42. package/dist/modules/payments-v2/index.cjs.map +1 -1
  43. package/dist/modules/payments-v2/index.d.cts +88 -13
  44. package/dist/modules/payments-v2/index.d.ts +88 -13
  45. package/dist/modules/payments-v2/index.js +609 -269
  46. package/dist/modules/payments-v2/index.js.map +1 -1
  47. package/dist/token-engine/index.cjs +132 -34
  48. package/dist/token-engine/index.cjs.map +1 -1
  49. package/dist/token-engine/index.d.cts +19 -0
  50. package/dist/token-engine/index.d.ts +19 -0
  51. package/dist/token-engine/index.js +132 -34
  52. package/dist/token-engine/index.js.map +1 -1
  53. 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 && this.isSpherePaymentData(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 this.wrapToken(token);
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 this.wrapToken(token);
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 this.wrapToken(transferred);
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 this.wrapToken(token);
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 this.wrapToken(sdkToken);
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.