@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
@@ -112,6 +112,8 @@ interface NftMetadata {
112
112
  readonly external_url: string | null;
113
113
  readonly attributes: readonly NftAttribute[];
114
114
  readonly collection: string | null;
115
+ /** The collection the item claims: 1–64 bytes as even-length hex (decoded lowercase). Never verified here. */
116
+ readonly collection_id: string | null;
115
117
  }
116
118
  type NftContent = NftMetadata | NftMedia | NftLink;
117
119
  type NftSignatureStatus = 'unsigned' | 'valid' | 'invalid';
@@ -112,6 +112,8 @@ interface NftMetadata {
112
112
  readonly external_url: string | null;
113
113
  readonly attributes: readonly NftAttribute[];
114
114
  readonly collection: string | null;
115
+ /** The collection the item claims: 1–64 bytes as even-length hex (decoded lowercase). Never verified here. */
116
+ readonly collection_id: string | null;
115
117
  }
116
118
  type NftContent = NftMetadata | NftMedia | NftLink;
117
119
  type NftSignatureStatus = 'unsigned' | 'valid' | 'invalid';
@@ -9784,7 +9784,8 @@ var NFT_MEDIA_TAG = 39053n;
9784
9784
  var NFT_LINK_TAG = 39054n;
9785
9785
  var NFT_SIGNED_TAG = 39055n;
9786
9786
  var NFT_FORMAT_VERSION = 1n;
9787
- var METADATA_ARITY = 8;
9787
+ var NFT_DOCUMENT_MEDIA_TYPE = "application/vnd.unicity.nft+cbor";
9788
+ var METADATA_ARITY = 9;
9788
9789
  var MEDIA_ARITY = 3;
9789
9790
  var LINK_ARITY = 4;
9790
9791
  var SIGNED_ARITY = 4;
@@ -9792,12 +9793,15 @@ var CREATOR_BYTES = 33;
9792
9793
  var SIGNATURE_BYTES = 65;
9793
9794
  var MAX_URI_LENGTH = 2048;
9794
9795
  var LINK_SCHEMES = ["https://", "ipfs://", "ar://"];
9795
- var SIGNED_DOMAIN = "NftSigned";
9796
+ var SIGNED_DOMAIN = new TextEncoder().encode("UNICITY_NFT_SIGNED");
9796
9797
  var MEDIA_TYPE_PATTERN = /^[a-z0-9][a-z0-9!#$&^_.+-]{0,126}\/[a-z0-9][a-z0-9!#$&^_.+-]{0,126}$/;
9797
9798
  var SHA256_HEX_PATTERN = /^[0-9a-f]{64}$/i;
9799
+ var COLLECTION_ID_PATTERN = /^(?:[0-9a-f]{2}){1,64}$/i;
9798
9800
  var LONE_SURROGATE = /\p{Cs}/u;
9799
9801
  var CBOR_MAJOR_TYPE_MASK = 224;
9800
9802
  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";
9803
+ var COLLECTION_ID_RULE = "must be null or 1\u201364 bytes as even-length hex (2\u2013128 characters)";
9804
+ var DOCUMENT_AS_MEDIA_RULE = `must not be ${NFT_DOCUMENT_MEDIA_TYPE}: a document link is a token's content, never media`;
9801
9805
  var UTF8 = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
9802
9806
  function refuse(field, rule, cause) {
9803
9807
  return new SphereError(`Invalid NFT ${field}: ${rule}`, "VALIDATION_ERROR", cause);
@@ -9809,6 +9813,7 @@ var fieldName = (prefix, name) => prefix === "" ? name : `${prefix}.${name}`;
9809
9813
  var isText = (value) => typeof value === "string" && value.length > 0 && !LONE_SURROGATE.test(value);
9810
9814
  var isBytes3 = (value, length) => value instanceof Uint8Array && (length === void 0 ? value.length > 0 : value.length === length);
9811
9815
  var isMediaType = (value) => typeof value === "string" && MEDIA_TYPE_PATTERN.test(value);
9816
+ var isCollectionId = (value) => typeof value === "string" && COLLECTION_ID_PATTERN.test(value);
9812
9817
  var isAttributeValue = (value) => typeof value === "number" ? Number.isSafeInteger(value) : isText(value);
9813
9818
  function hasSpaceOrControl(value) {
9814
9819
  for (let i = 0; i < value.length; i++) {
@@ -9851,8 +9856,9 @@ function checkLink(prefix, link) {
9851
9856
  function checkMediaRef(field, ref) {
9852
9857
  if (ref === null) return;
9853
9858
  if (ref?.kind === "media") return checkMedia(field, ref);
9854
- if (ref?.kind === "link") return checkLink(field, ref);
9855
- throw refuse(field, "must be an NftMedia, an NftLink or null");
9859
+ if (ref?.kind !== "link") throw refuse(field, "must be an NftMedia, an NftLink or null");
9860
+ checkLink(field, ref);
9861
+ ensure(!isNftDocumentLink(ref), fieldName(field, "media_type"), DOCUMENT_AS_MEDIA_RULE);
9856
9862
  }
9857
9863
  function checkAttributes(attributes) {
9858
9864
  ensure(Array.isArray(attributes), "attributes", "must be an array \u2014 [] when there are none");
@@ -9869,6 +9875,7 @@ function checkMetadata(metadata) {
9869
9875
  checkOptionalText("external_url", metadata.external_url);
9870
9876
  checkAttributes(metadata.attributes);
9871
9877
  checkOptionalText("collection", metadata.collection);
9878
+ ensure(metadata.collection_id === null || isCollectionId(metadata.collection_id), "collection_id", COLLECTION_ID_RULE);
9872
9879
  }
9873
9880
  function checkContent(content) {
9874
9881
  switch (content?.kind) {
@@ -9926,6 +9933,7 @@ function arrayCbor(items) {
9926
9933
  }
9927
9934
  return out;
9928
9935
  }
9936
+ var optionalHexBytes = (value) => CborSerializer.encodeNullable(value, (hex) => CborSerializer.encodeByteString(HexConverter.decode(hex.toLowerCase())));
9929
9937
  function metadataCbor(metadata) {
9930
9938
  return versioned(
9931
9939
  NFT_METADATA_TAG,
@@ -9935,7 +9943,8 @@ function metadataCbor(metadata) {
9935
9943
  mediaRefCbor(metadata.animation_url),
9936
9944
  optionalText(metadata.external_url),
9937
9945
  arrayCbor(metadata.attributes.map(attributeCbor)),
9938
- optionalText(metadata.collection)
9946
+ optionalText(metadata.collection),
9947
+ optionalHexBytes(metadata.collection_id)
9939
9948
  );
9940
9949
  }
9941
9950
  function encodeNftContent(content) {
@@ -9993,13 +10002,14 @@ function decodeMedia(body) {
9993
10002
  const [, mediaType, bytes] = versionedFields(body, MEDIA_ARITY);
9994
10003
  return { kind: "media", media_type: decodeText(mediaType), bytes: CborDeserializer.decodeByteString(bytes) };
9995
10004
  }
10005
+ var decodeHex = (item) => HexConverter.encode(CborDeserializer.decodeByteString(item));
9996
10006
  function decodeLink(body) {
9997
10007
  const [, mediaType, uri, digest] = versionedFields(body, LINK_ARITY);
9998
10008
  return {
9999
10009
  kind: "link",
10000
10010
  media_type: decodeText(mediaType),
10001
10011
  uri: decodeText(uri),
10002
- sha256: HexConverter.encode(CborDeserializer.decodeByteString(digest))
10012
+ sha256: decodeHex(digest)
10003
10013
  };
10004
10014
  }
10005
10015
  function decodeMetadata(body) {
@@ -10012,7 +10022,8 @@ function decodeMetadata(body) {
10012
10022
  animation_url: decodeMediaRef("animation_url", fields[4]),
10013
10023
  external_url: decodeOptionalText(fields[5]),
10014
10024
  attributes: decodeAttributes(fields[6]),
10015
- collection: decodeOptionalText(fields[7])
10025
+ collection: decodeOptionalText(fields[7]),
10026
+ collection_id: CborDeserializer.decodeNullable(fields[8], decodeHex)
10016
10027
  };
10017
10028
  }
10018
10029
  var MEDIA_REF_DECODERS = /* @__PURE__ */ new Map([
@@ -10058,20 +10069,26 @@ function parseNftPayload(data) {
10058
10069
  return null;
10059
10070
  }
10060
10071
  }
10061
- function nftSignedDigest(tokenIdCbor, recipientPredicateCbor, payload) {
10072
+ function isNftDocumentLink(content) {
10073
+ return content?.kind === "link" && content.media_type === NFT_DOCUMENT_MEDIA_TYPE;
10074
+ }
10075
+ function nftSignedDigest(context, payload) {
10062
10076
  const preimage = CborSerializer.encodeArray(
10063
- CborSerializer.encodeTextString(SIGNED_DOMAIN),
10064
- tokenIdCbor,
10065
- recipientPredicateCbor,
10077
+ CborSerializer.encodeByteString(SIGNED_DOMAIN),
10078
+ CborSerializer.encodeUnsignedInteger(NFT_FORMAT_VERSION),
10079
+ CborSerializer.encodeUnsignedInteger(NetworkId.fromId(context.networkId).id),
10080
+ CborSerializer.encodeByteString(context.recipientPredicate),
10081
+ CborSerializer.encodeByteString(context.tokenId),
10082
+ CborSerializer.encodeByteString(context.tokenType),
10066
10083
  CborSerializer.encodeByteString(payload)
10067
10084
  );
10068
10085
  return new DataHasher(HashAlgorithm.SHA256).update(preimage).digest();
10069
10086
  }
10070
- async function verifyNftSignature(signed, tokenIdCbor, recipientPredicateCbor) {
10087
+ async function verifyNftSignature(signed, context) {
10071
10088
  try {
10072
10089
  if (!SigningService.isPublicKeyValid(signed.creator)) return "invalid";
10073
10090
  const signature = Signature.decode(signed.signature);
10074
- const digest = await nftSignedDigest(tokenIdCbor, recipientPredicateCbor, signed.payload);
10091
+ const digest = await nftSignedDigest(context, signed.payload);
10075
10092
  return await SigningService.verifyWithPublicKey(digest, signature, signed.creator) ? "valid" : "invalid";
10076
10093
  } catch {
10077
10094
  return "invalid";
@@ -10202,8 +10219,8 @@ function assertMintableData(data) {
10202
10219
  function nftRecipientCbor(pubkey) {
10203
10220
  return EncodedPredicate.fromPredicate(SignaturePredicate.create(pubkey)).toCBOR();
10204
10221
  }
10205
- async function signPayload(deps, payload, tokenId, recipientPubkey) {
10206
- const digest = await nftSignedDigest(tokenId.toCBOR(), nftRecipientCbor(recipientPubkey), payload);
10222
+ async function signPayload(deps, payload, context) {
10223
+ const digest = await nftSignedDigest(context, payload);
10207
10224
  const signature = await deps.signingService.sign(digest);
10208
10225
  return encodeNftSigned(deps.signingService.publicKey, payload, signature.encode());
10209
10226
  }
@@ -10212,31 +10229,41 @@ async function planNftMint(deps, params) {
10212
10229
  const tokenType = new TokenType(params.tokenType).bytes;
10213
10230
  const salt = TokenSalt.generate();
10214
10231
  const tokenId = await TokenId.fromSalt(deps.networkId, salt);
10215
- const data = params.sign ? await signPayload(deps, payload, tokenId, params.recipientPubkey) : payload;
10232
+ const data = params.sign ? await signPayload(deps, payload, {
10233
+ networkId: deps.networkId.id,
10234
+ recipientPredicate: nftRecipientCbor(params.recipientPubkey),
10235
+ tokenId: tokenId.bytes,
10236
+ tokenType
10237
+ }) : payload;
10216
10238
  assertMintableData(data);
10217
10239
  return { data, salt: salt.toBytes(), tokenType, tokenId: HexConverter.encode(tokenId.bytes) };
10218
10240
  }
10219
- async function verifyAgainst(signed, tokenIdCbor, genesisRecipientCbor) {
10241
+ async function verifyAgainst(signed, genesisContext) {
10220
10242
  try {
10221
- return await verifyNftSignature(signed, tokenIdCbor, genesisRecipientCbor());
10243
+ return await verifyNftSignature(signed, genesisContext());
10222
10244
  } catch {
10223
10245
  return "invalid";
10224
10246
  }
10225
10247
  }
10226
- async function readNftData(data, tokenIdCbor, genesisRecipientCbor) {
10248
+ async function readNftData(data, genesisContext) {
10227
10249
  const parsed = parseNftPayload(data);
10228
10250
  if (parsed === null) return null;
10229
10251
  if (parsed.signed === null) return { content: parsed.content, creator: null, signature: "unsigned" };
10230
10252
  return {
10231
10253
  content: parsed.content,
10232
10254
  creator: HexConverter.encode(parsed.signed.creator),
10233
- signature: await verifyAgainst(parsed.signed, tokenIdCbor, genesisRecipientCbor)
10255
+ signature: await verifyAgainst(parsed.signed, genesisContext)
10234
10256
  };
10235
10257
  }
10236
10258
  async function readTokenNft(token) {
10237
10259
  try {
10238
10260
  const { genesis } = token.sdkToken;
10239
- return await readNftData(genesis.data, genesis.tokenId.toCBOR(), () => genesis.recipient.toCBOR());
10261
+ return await readNftData(genesis.data, () => ({
10262
+ networkId: genesis.networkId.id,
10263
+ recipientPredicate: genesis.recipient.toCBOR(),
10264
+ tokenId: genesis.tokenId.bytes,
10265
+ tokenType: genesis.tokenType.bytes
10266
+ }));
10240
10267
  } catch {
10241
10268
  return null;
10242
10269
  }