@unicitylabs/sphere-sdk 0.17.2-dev.1 → 0.17.2-dev.3

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 (47) hide show
  1. package/dist/connect/index.cjs +202 -8
  2. package/dist/connect/index.cjs.map +1 -1
  3. package/dist/connect/index.d.cts +81 -4
  4. package/dist/connect/index.d.ts +81 -4
  5. package/dist/connect/index.js +202 -8
  6. package/dist/connect/index.js.map +1 -1
  7. package/dist/core/index.cjs +48 -21
  8. package/dist/core/index.cjs.map +1 -1
  9. package/dist/core/index.d.cts +2 -0
  10. package/dist/core/index.d.ts +2 -0
  11. package/dist/core/index.js +48 -21
  12. package/dist/core/index.js.map +1 -1
  13. package/dist/impl/browser/connect/index.cjs +17 -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 +17 -5
  18. package/dist/impl/browser/connect/index.js.map +1 -1
  19. package/dist/impl/nodejs/connect/index.cjs +16 -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 +16 -4
  24. package/dist/impl/nodejs/connect/index.js.map +1 -1
  25. package/dist/impl/wallet-api-v2/index.cjs +3 -0
  26. package/dist/impl/wallet-api-v2/index.cjs.map +1 -1
  27. package/dist/impl/wallet-api-v2/index.js +3 -0
  28. package/dist/impl/wallet-api-v2/index.js.map +1 -1
  29. package/dist/index.cjs +67 -21
  30. package/dist/index.cjs.map +1 -1
  31. package/dist/index.d.cts +22 -1
  32. package/dist/index.d.ts +22 -1
  33. package/dist/index.js +64 -21
  34. package/dist/index.js.map +1 -1
  35. package/dist/modules/payments-v2/index.cjs +40 -6
  36. package/dist/modules/payments-v2/index.cjs.map +1 -1
  37. package/dist/modules/payments-v2/index.d.cts +22 -1
  38. package/dist/modules/payments-v2/index.d.ts +22 -1
  39. package/dist/modules/payments-v2/index.js +37 -6
  40. package/dist/modules/payments-v2/index.js.map +1 -1
  41. package/dist/token-engine/index.cjs +67 -21
  42. package/dist/token-engine/index.cjs.map +1 -1
  43. package/dist/token-engine/index.d.cts +28 -4
  44. package/dist/token-engine/index.d.ts +28 -4
  45. package/dist/token-engine/index.js +64 -21
  46. package/dist/token-engine/index.js.map +1 -1
  47. package/package.json +1 -1
@@ -22,6 +22,7 @@ var token_engine_exports = {};
22
22
  __export(token_engine_exports, {
23
23
  CheckpointPersistFailedError: () => CheckpointPersistFailedError,
24
24
  CheckpointTrustbaseMismatchError: () => CheckpointTrustbaseMismatchError,
25
+ NFT_DOCUMENT_MEDIA_TYPE: () => NFT_DOCUMENT_MEDIA_TYPE,
25
26
  NFT_FORMAT_VERSION: () => NFT_FORMAT_VERSION,
26
27
  NFT_LINK_TAG: () => NFT_LINK_TAG,
27
28
  NFT_MEDIA_TAG: () => NFT_MEDIA_TAG,
@@ -36,7 +37,9 @@ __export(token_engine_exports, {
36
37
  deriveDirectAddress: () => deriveDirectAddress,
37
38
  encodeNftContent: () => encodeNftContent,
38
39
  encodeNftSigned: () => encodeNftSigned,
40
+ isNftDocumentLink: () => isNftDocumentLink,
39
41
  nftSignedDigest: () => nftSignedDigest,
42
+ parseNftDocument: () => parseNftDocument,
40
43
  parseNftPayload: () => parseNftPayload,
41
44
  sphereAssetToSdk: () => sphereAssetToSdk,
42
45
  verifyNftLinkContent: () => verifyNftLinkContent,
@@ -973,7 +976,8 @@ var NFT_MEDIA_TAG = 39053n;
973
976
  var NFT_LINK_TAG = 39054n;
974
977
  var NFT_SIGNED_TAG = 39055n;
975
978
  var NFT_FORMAT_VERSION = 1n;
976
- var METADATA_ARITY = 8;
979
+ var NFT_DOCUMENT_MEDIA_TYPE = "application/vnd.unicity.nft+cbor";
980
+ var METADATA_ARITY = 9;
977
981
  var MEDIA_ARITY = 3;
978
982
  var LINK_ARITY = 4;
979
983
  var SIGNED_ARITY = 4;
@@ -981,12 +985,15 @@ var CREATOR_BYTES = 33;
981
985
  var SIGNATURE_BYTES = 65;
982
986
  var MAX_URI_LENGTH = 2048;
983
987
  var LINK_SCHEMES = ["https://", "ipfs://", "ar://"];
984
- var SIGNED_DOMAIN = "NftSigned";
988
+ var SIGNED_DOMAIN = new TextEncoder().encode("UNICITY_NFT_SIGNED");
985
989
  var MEDIA_TYPE_PATTERN = /^[a-z0-9][a-z0-9!#$&^_.+-]{0,126}\/[a-z0-9][a-z0-9!#$&^_.+-]{0,126}$/;
986
990
  var SHA256_HEX_PATTERN = /^[0-9a-f]{64}$/i;
991
+ var COLLECTION_ID_PATTERN = /^(?:[0-9a-f]{2}){1,64}$/i;
987
992
  var LONE_SURROGATE = /\p{Cs}/u;
988
993
  var CBOR_MAJOR_TYPE_MASK = 224;
989
994
  var ATTRIBUTE_VALUE_RULE = "must be non-empty text or an integer within \xB1(2^53 \u2212 1) \u2014 write decimals and larger numbers as text";
995
+ var COLLECTION_ID_RULE = "must be null or 1\u201364 bytes as even-length hex (2\u2013128 characters)";
996
+ var DOCUMENT_AS_MEDIA_RULE = `must not be ${NFT_DOCUMENT_MEDIA_TYPE}: a document link is a token's content, never media`;
990
997
  var UTF8 = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
991
998
  function refuse(field, rule, cause) {
992
999
  return new SphereError(`Invalid NFT ${field}: ${rule}`, "VALIDATION_ERROR", cause);
@@ -998,6 +1005,7 @@ var fieldName = (prefix, name) => prefix === "" ? name : `${prefix}.${name}`;
998
1005
  var isText = (value) => typeof value === "string" && value.length > 0 && !LONE_SURROGATE.test(value);
999
1006
  var isBytes2 = (value, length) => value instanceof Uint8Array && (length === void 0 ? value.length > 0 : value.length === length);
1000
1007
  var isMediaType = (value) => typeof value === "string" && MEDIA_TYPE_PATTERN.test(value);
1008
+ var isCollectionId = (value) => typeof value === "string" && COLLECTION_ID_PATTERN.test(value);
1001
1009
  var isAttributeValue = (value) => typeof value === "number" ? Number.isSafeInteger(value) : isText(value);
1002
1010
  function hasSpaceOrControl(value) {
1003
1011
  for (let i = 0; i < value.length; i++) {
@@ -1040,8 +1048,9 @@ function checkLink(prefix, link) {
1040
1048
  function checkMediaRef(field, ref) {
1041
1049
  if (ref === null) return;
1042
1050
  if (ref?.kind === "media") return checkMedia(field, ref);
1043
- if (ref?.kind === "link") return checkLink(field, ref);
1044
- throw refuse(field, "must be an NftMedia, an NftLink or null");
1051
+ if (ref?.kind !== "link") throw refuse(field, "must be an NftMedia, an NftLink or null");
1052
+ checkLink(field, ref);
1053
+ ensure(!isNftDocumentLink(ref), fieldName(field, "media_type"), DOCUMENT_AS_MEDIA_RULE);
1045
1054
  }
1046
1055
  function checkAttributes(attributes) {
1047
1056
  ensure(Array.isArray(attributes), "attributes", "must be an array \u2014 [] when there are none");
@@ -1058,6 +1067,7 @@ function checkMetadata(metadata) {
1058
1067
  checkOptionalText("external_url", metadata.external_url);
1059
1068
  checkAttributes(metadata.attributes);
1060
1069
  checkOptionalText("collection", metadata.collection);
1070
+ ensure(metadata.collection_id === null || isCollectionId(metadata.collection_id), "collection_id", COLLECTION_ID_RULE);
1061
1071
  }
1062
1072
  function checkContent(content) {
1063
1073
  switch (content?.kind) {
@@ -1115,6 +1125,7 @@ function arrayCbor(items) {
1115
1125
  }
1116
1126
  return out;
1117
1127
  }
1128
+ var optionalHexBytes = (value) => import_CborSerializer.CborSerializer.encodeNullable(value, (hex) => import_CborSerializer.CborSerializer.encodeByteString(import_HexConverter.HexConverter.decode(hex.toLowerCase())));
1118
1129
  function metadataCbor(metadata) {
1119
1130
  return versioned(
1120
1131
  NFT_METADATA_TAG,
@@ -1124,7 +1135,8 @@ function metadataCbor(metadata) {
1124
1135
  mediaRefCbor(metadata.animation_url),
1125
1136
  optionalText(metadata.external_url),
1126
1137
  arrayCbor(metadata.attributes.map(attributeCbor)),
1127
- optionalText(metadata.collection)
1138
+ optionalText(metadata.collection),
1139
+ optionalHexBytes(metadata.collection_id)
1128
1140
  );
1129
1141
  }
1130
1142
  function encodeNftContent(content) {
@@ -1182,13 +1194,14 @@ function decodeMedia(body) {
1182
1194
  const [, mediaType, bytes] = versionedFields(body, MEDIA_ARITY);
1183
1195
  return { kind: "media", media_type: decodeText(mediaType), bytes: import_CborDeserializer.CborDeserializer.decodeByteString(bytes) };
1184
1196
  }
1197
+ var decodeHex = (item) => import_HexConverter.HexConverter.encode(import_CborDeserializer.CborDeserializer.decodeByteString(item));
1185
1198
  function decodeLink(body) {
1186
1199
  const [, mediaType, uri, digest] = versionedFields(body, LINK_ARITY);
1187
1200
  return {
1188
1201
  kind: "link",
1189
1202
  media_type: decodeText(mediaType),
1190
1203
  uri: decodeText(uri),
1191
- sha256: import_HexConverter.HexConverter.encode(import_CborDeserializer.CborDeserializer.decodeByteString(digest))
1204
+ sha256: decodeHex(digest)
1192
1205
  };
1193
1206
  }
1194
1207
  function decodeMetadata(body) {
@@ -1201,7 +1214,8 @@ function decodeMetadata(body) {
1201
1214
  animation_url: decodeMediaRef("animation_url", fields[4]),
1202
1215
  external_url: decodeOptionalText(fields[5]),
1203
1216
  attributes: decodeAttributes(fields[6]),
1204
- collection: decodeOptionalText(fields[7])
1217
+ collection: decodeOptionalText(fields[7]),
1218
+ collection_id: import_CborDeserializer.CborDeserializer.decodeNullable(fields[8], decodeHex)
1205
1219
  };
1206
1220
  }
1207
1221
  var MEDIA_REF_DECODERS = /* @__PURE__ */ new Map([
@@ -1213,6 +1227,10 @@ var UNSIGNED_DECODERS = /* @__PURE__ */ new Map([
1213
1227
  [NFT_MEDIA_TAG, decodeMedia],
1214
1228
  [NFT_LINK_TAG, decodeLink]
1215
1229
  ]);
1230
+ var DOCUMENT_DECODERS = /* @__PURE__ */ new Map([
1231
+ [NFT_METADATA_TAG, decodeMetadata],
1232
+ [NFT_MEDIA_TAG, decodeMedia]
1233
+ ]);
1216
1234
  function decodeBody(decoders, field, tag, body) {
1217
1235
  const decode = decoders.get(tag);
1218
1236
  if (decode === void 0) throw refuse(field, `tag ${tag} is not allowed here`);
@@ -1247,20 +1265,35 @@ function parseNftPayload(data) {
1247
1265
  return null;
1248
1266
  }
1249
1267
  }
1250
- function nftSignedDigest(tokenIdCbor, recipientPredicateCbor, payload) {
1268
+ function parseNftDocument(bytes) {
1269
+ try {
1270
+ const document = decodeItem(DOCUMENT_DECODERS, "document", bytes);
1271
+ checkContent(document);
1272
+ return document;
1273
+ } catch {
1274
+ return null;
1275
+ }
1276
+ }
1277
+ function isNftDocumentLink(content) {
1278
+ return content?.kind === "link" && content.media_type === NFT_DOCUMENT_MEDIA_TYPE;
1279
+ }
1280
+ function nftSignedDigest(context, payload) {
1251
1281
  const preimage = import_CborSerializer.CborSerializer.encodeArray(
1252
- import_CborSerializer.CborSerializer.encodeTextString(SIGNED_DOMAIN),
1253
- tokenIdCbor,
1254
- recipientPredicateCbor,
1282
+ import_CborSerializer.CborSerializer.encodeByteString(SIGNED_DOMAIN),
1283
+ import_CborSerializer.CborSerializer.encodeUnsignedInteger(NFT_FORMAT_VERSION),
1284
+ import_CborSerializer.CborSerializer.encodeUnsignedInteger(import_NetworkId.NetworkId.fromId(context.networkId).id),
1285
+ import_CborSerializer.CborSerializer.encodeByteString(context.recipientPredicate),
1286
+ import_CborSerializer.CborSerializer.encodeByteString(context.tokenId),
1287
+ import_CborSerializer.CborSerializer.encodeByteString(context.tokenType),
1255
1288
  import_CborSerializer.CborSerializer.encodeByteString(payload)
1256
1289
  );
1257
1290
  return new import_DataHasher.DataHasher(import_HashAlgorithm.HashAlgorithm.SHA256).update(preimage).digest();
1258
1291
  }
1259
- async function verifyNftSignature(signed, tokenIdCbor, recipientPredicateCbor) {
1292
+ async function verifyNftSignature(signed, context) {
1260
1293
  try {
1261
1294
  if (!import_SigningService.SigningService.isPublicKeyValid(signed.creator)) return "invalid";
1262
1295
  const signature = import_Signature.Signature.decode(signed.signature);
1263
- const digest = await nftSignedDigest(tokenIdCbor, recipientPredicateCbor, signed.payload);
1296
+ const digest = await nftSignedDigest(context, signed.payload);
1264
1297
  return await import_SigningService.SigningService.verifyWithPublicKey(digest, signature, signed.creator) ? "valid" : "invalid";
1265
1298
  } catch {
1266
1299
  return "invalid";
@@ -1391,8 +1424,8 @@ function assertMintableData(data) {
1391
1424
  function nftRecipientCbor(pubkey) {
1392
1425
  return import_EncodedPredicate.EncodedPredicate.fromPredicate(import_SignaturePredicate.SignaturePredicate.create(pubkey)).toCBOR();
1393
1426
  }
1394
- async function signPayload(deps, payload, tokenId, recipientPubkey) {
1395
- const digest = await nftSignedDigest(tokenId.toCBOR(), nftRecipientCbor(recipientPubkey), payload);
1427
+ async function signPayload(deps, payload, context) {
1428
+ const digest = await nftSignedDigest(context, payload);
1396
1429
  const signature = await deps.signingService.sign(digest);
1397
1430
  return encodeNftSigned(deps.signingService.publicKey, payload, signature.encode());
1398
1431
  }
@@ -1401,31 +1434,41 @@ async function planNftMint(deps, params) {
1401
1434
  const tokenType = new import_TokenType.TokenType(params.tokenType).bytes;
1402
1435
  const salt = import_TokenSalt.TokenSalt.generate();
1403
1436
  const tokenId = await import_TokenId.TokenId.fromSalt(deps.networkId, salt);
1404
- const data = params.sign ? await signPayload(deps, payload, tokenId, params.recipientPubkey) : payload;
1437
+ const data = params.sign ? await signPayload(deps, payload, {
1438
+ networkId: deps.networkId.id,
1439
+ recipientPredicate: nftRecipientCbor(params.recipientPubkey),
1440
+ tokenId: tokenId.bytes,
1441
+ tokenType
1442
+ }) : payload;
1405
1443
  assertMintableData(data);
1406
1444
  return { data, salt: salt.toBytes(), tokenType, tokenId: import_HexConverter.HexConverter.encode(tokenId.bytes) };
1407
1445
  }
1408
- async function verifyAgainst(signed, tokenIdCbor, genesisRecipientCbor) {
1446
+ async function verifyAgainst(signed, genesisContext) {
1409
1447
  try {
1410
- return await verifyNftSignature(signed, tokenIdCbor, genesisRecipientCbor());
1448
+ return await verifyNftSignature(signed, genesisContext());
1411
1449
  } catch {
1412
1450
  return "invalid";
1413
1451
  }
1414
1452
  }
1415
- async function readNftData(data, tokenIdCbor, genesisRecipientCbor) {
1453
+ async function readNftData(data, genesisContext) {
1416
1454
  const parsed = parseNftPayload(data);
1417
1455
  if (parsed === null) return null;
1418
1456
  if (parsed.signed === null) return { content: parsed.content, creator: null, signature: "unsigned" };
1419
1457
  return {
1420
1458
  content: parsed.content,
1421
1459
  creator: import_HexConverter.HexConverter.encode(parsed.signed.creator),
1422
- signature: await verifyAgainst(parsed.signed, tokenIdCbor, genesisRecipientCbor)
1460
+ signature: await verifyAgainst(parsed.signed, genesisContext)
1423
1461
  };
1424
1462
  }
1425
1463
  async function readTokenNft(token) {
1426
1464
  try {
1427
1465
  const { genesis } = token.sdkToken;
1428
- return await readNftData(genesis.data, genesis.tokenId.toCBOR(), () => genesis.recipient.toCBOR());
1466
+ return await readNftData(genesis.data, () => ({
1467
+ networkId: genesis.networkId.id,
1468
+ recipientPredicate: genesis.recipient.toCBOR(),
1469
+ tokenId: genesis.tokenId.bytes,
1470
+ tokenType: genesis.tokenType.bytes
1471
+ }));
1429
1472
  } catch {
1430
1473
  return null;
1431
1474
  }
@@ -2074,6 +2117,7 @@ async function createSphereTokenEngine(config) {
2074
2117
  0 && (module.exports = {
2075
2118
  CheckpointPersistFailedError,
2076
2119
  CheckpointTrustbaseMismatchError,
2120
+ NFT_DOCUMENT_MEDIA_TYPE,
2077
2121
  NFT_FORMAT_VERSION,
2078
2122
  NFT_LINK_TAG,
2079
2123
  NFT_MEDIA_TAG,
@@ -2088,7 +2132,9 @@ async function createSphereTokenEngine(config) {
2088
2132
  deriveDirectAddress,
2089
2133
  encodeNftContent,
2090
2134
  encodeNftSigned,
2135
+ isNftDocumentLink,
2091
2136
  nftSignedDigest,
2137
+ parseNftDocument,
2092
2138
  parseNftPayload,
2093
2139
  sphereAssetToSdk,
2094
2140
  verifyNftLinkContent,