@unicitylabs/sphere-sdk 0.17.2-dev.2 → 0.17.2

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 (39) hide show
  1. package/dist/connect/index.cjs +7 -4
  2. package/dist/connect/index.cjs.map +1 -1
  3. package/dist/connect/index.d.cts +4 -0
  4. package/dist/connect/index.d.ts +4 -0
  5. package/dist/connect/index.js +7 -4
  6. package/dist/connect/index.js.map +1 -1
  7. package/dist/core/index.cjs +69 -22
  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 +69 -22
  12. package/dist/core/index.js.map +1 -1
  13. package/dist/impl/browser/connect/index.cjs +1 -1
  14. package/dist/impl/browser/connect/index.cjs.map +1 -1
  15. package/dist/impl/browser/connect/index.js +1 -1
  16. package/dist/impl/browser/connect/index.js.map +1 -1
  17. package/dist/impl/wallet-api-v2/index.cjs +3 -0
  18. package/dist/impl/wallet-api-v2/index.cjs.map +1 -1
  19. package/dist/impl/wallet-api-v2/index.js +3 -0
  20. package/dist/impl/wallet-api-v2/index.js.map +1 -1
  21. package/dist/index.cjs +88 -22
  22. package/dist/index.cjs.map +1 -1
  23. package/dist/index.d.cts +22 -1
  24. package/dist/index.d.ts +22 -1
  25. package/dist/index.js +85 -22
  26. package/dist/index.js.map +1 -1
  27. package/dist/modules/payments-v2/index.cjs +40 -6
  28. package/dist/modules/payments-v2/index.cjs.map +1 -1
  29. package/dist/modules/payments-v2/index.d.cts +22 -1
  30. package/dist/modules/payments-v2/index.d.ts +22 -1
  31. package/dist/modules/payments-v2/index.js +37 -6
  32. package/dist/modules/payments-v2/index.js.map +1 -1
  33. package/dist/token-engine/index.cjs +67 -21
  34. package/dist/token-engine/index.cjs.map +1 -1
  35. package/dist/token-engine/index.d.cts +28 -4
  36. package/dist/token-engine/index.d.ts +28 -4
  37. package/dist/token-engine/index.js +64 -21
  38. package/dist/token-engine/index.js.map +1 -1
  39. package/package.json +1 -1
@@ -4624,6 +4624,26 @@ var GroupVisibility = {
4624
4624
  PRIVATE: "PRIVATE"
4625
4625
  };
4626
4626
 
4627
+ // modules/groupchat/group-id.ts
4628
+ var MAX_SLUG_LENGTH = 24;
4629
+ var RANDOM_BYTES = 8;
4630
+ function groupIdSlug(name) {
4631
+ const slug = name.normalize("NFKD").replace(/\p{M}/gu, "").toLowerCase().replace(/['\u2019]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
4632
+ if (slug.length <= MAX_SLUG_LENGTH) return slug;
4633
+ const head = slug.slice(0, MAX_SLUG_LENGTH + 1);
4634
+ const hyphen = head.lastIndexOf("-");
4635
+ return hyphen > 0 ? head.slice(0, hyphen) : slug.slice(0, MAX_SLUG_LENGTH);
4636
+ }
4637
+ function newGroupId(name) {
4638
+ const crypto2 = globalThis.crypto;
4639
+ if (typeof crypto2?.getRandomValues !== "function") {
4640
+ throw new SphereError("Creating a group requires crypto.getRandomValues", "INVALID_CONFIG");
4641
+ }
4642
+ const bytes = crypto2.getRandomValues(new Uint8Array(RANDOM_BYTES));
4643
+ const random = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
4644
+ return `${groupIdSlug(name) || "group"}-${random}`;
4645
+ }
4646
+
4627
4647
  // modules/groupchat/GroupChatModule.ts
4628
4648
  function createNip29Filter(data) {
4629
4649
  return new import_nostr_js_sdk3.Filter(data);
@@ -5235,9 +5255,9 @@ var GroupChatModule = class {
5235
5255
  if (!this.client) return null;
5236
5256
  const creatorPubkey = this.getMyPublicKey();
5237
5257
  if (!creatorPubkey) return null;
5238
- const proposedGroupId = options.name.toLowerCase().replace(/[^a-z0-9]/g, "").slice(0, 20) || this.randomId();
5239
5258
  try {
5240
5259
  const isPrivate = options.visibility === GroupVisibility.PRIVATE;
5260
+ const proposedGroupId = newGroupId(options.name);
5241
5261
  const eventId = await this.client.createAndPublishEvent({
5242
5262
  kind: NIP29_KINDS.CREATE_GROUP,
5243
5263
  tags: [["h", proposedGroupId]],
@@ -9869,7 +9889,8 @@ var NFT_MEDIA_TAG = 39053n;
9869
9889
  var NFT_LINK_TAG = 39054n;
9870
9890
  var NFT_SIGNED_TAG = 39055n;
9871
9891
  var NFT_FORMAT_VERSION = 1n;
9872
- var METADATA_ARITY = 8;
9892
+ var NFT_DOCUMENT_MEDIA_TYPE = "application/vnd.unicity.nft+cbor";
9893
+ var METADATA_ARITY = 9;
9873
9894
  var MEDIA_ARITY = 3;
9874
9895
  var LINK_ARITY = 4;
9875
9896
  var SIGNED_ARITY = 4;
@@ -9877,12 +9898,15 @@ var CREATOR_BYTES = 33;
9877
9898
  var SIGNATURE_BYTES = 65;
9878
9899
  var MAX_URI_LENGTH = 2048;
9879
9900
  var LINK_SCHEMES = ["https://", "ipfs://", "ar://"];
9880
- var SIGNED_DOMAIN = "NftSigned";
9901
+ var SIGNED_DOMAIN = new TextEncoder().encode("UNICITY_NFT_SIGNED");
9881
9902
  var MEDIA_TYPE_PATTERN = /^[a-z0-9][a-z0-9!#$&^_.+-]{0,126}\/[a-z0-9][a-z0-9!#$&^_.+-]{0,126}$/;
9882
9903
  var SHA256_HEX_PATTERN = /^[0-9a-f]{64}$/i;
9904
+ var COLLECTION_ID_PATTERN = /^(?:[0-9a-f]{2}){1,64}$/i;
9883
9905
  var LONE_SURROGATE = /\p{Cs}/u;
9884
9906
  var CBOR_MAJOR_TYPE_MASK = 224;
9885
9907
  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";
9908
+ var COLLECTION_ID_RULE = "must be null or 1\u201364 bytes as even-length hex (2\u2013128 characters)";
9909
+ var DOCUMENT_AS_MEDIA_RULE = `must not be ${NFT_DOCUMENT_MEDIA_TYPE}: a document link is a token's content, never media`;
9886
9910
  var UTF8 = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
9887
9911
  function refuse(field, rule, cause) {
9888
9912
  return new SphereError(`Invalid NFT ${field}: ${rule}`, "VALIDATION_ERROR", cause);
@@ -9894,6 +9918,7 @@ var fieldName = (prefix, name) => prefix === "" ? name : `${prefix}.${name}`;
9894
9918
  var isText = (value) => typeof value === "string" && value.length > 0 && !LONE_SURROGATE.test(value);
9895
9919
  var isBytes3 = (value, length) => value instanceof Uint8Array && (length === void 0 ? value.length > 0 : value.length === length);
9896
9920
  var isMediaType = (value) => typeof value === "string" && MEDIA_TYPE_PATTERN.test(value);
9921
+ var isCollectionId = (value) => typeof value === "string" && COLLECTION_ID_PATTERN.test(value);
9897
9922
  var isAttributeValue = (value) => typeof value === "number" ? Number.isSafeInteger(value) : isText(value);
9898
9923
  function hasSpaceOrControl(value) {
9899
9924
  for (let i = 0; i < value.length; i++) {
@@ -9936,8 +9961,9 @@ function checkLink(prefix, link) {
9936
9961
  function checkMediaRef(field, ref) {
9937
9962
  if (ref === null) return;
9938
9963
  if (ref?.kind === "media") return checkMedia(field, ref);
9939
- if (ref?.kind === "link") return checkLink(field, ref);
9940
- throw refuse(field, "must be an NftMedia, an NftLink or null");
9964
+ if (ref?.kind !== "link") throw refuse(field, "must be an NftMedia, an NftLink or null");
9965
+ checkLink(field, ref);
9966
+ ensure(!isNftDocumentLink(ref), fieldName(field, "media_type"), DOCUMENT_AS_MEDIA_RULE);
9941
9967
  }
9942
9968
  function checkAttributes(attributes) {
9943
9969
  ensure(Array.isArray(attributes), "attributes", "must be an array \u2014 [] when there are none");
@@ -9954,6 +9980,7 @@ function checkMetadata(metadata) {
9954
9980
  checkOptionalText("external_url", metadata.external_url);
9955
9981
  checkAttributes(metadata.attributes);
9956
9982
  checkOptionalText("collection", metadata.collection);
9983
+ ensure(metadata.collection_id === null || isCollectionId(metadata.collection_id), "collection_id", COLLECTION_ID_RULE);
9957
9984
  }
9958
9985
  function checkContent(content) {
9959
9986
  switch (content?.kind) {
@@ -10011,6 +10038,7 @@ function arrayCbor(items) {
10011
10038
  }
10012
10039
  return out;
10013
10040
  }
10041
+ var optionalHexBytes = (value) => import_CborSerializer.CborSerializer.encodeNullable(value, (hex) => import_CborSerializer.CborSerializer.encodeByteString(import_HexConverter.HexConverter.decode(hex.toLowerCase())));
10014
10042
  function metadataCbor(metadata) {
10015
10043
  return versioned(
10016
10044
  NFT_METADATA_TAG,
@@ -10020,7 +10048,8 @@ function metadataCbor(metadata) {
10020
10048
  mediaRefCbor(metadata.animation_url),
10021
10049
  optionalText(metadata.external_url),
10022
10050
  arrayCbor(metadata.attributes.map(attributeCbor)),
10023
- optionalText(metadata.collection)
10051
+ optionalText(metadata.collection),
10052
+ optionalHexBytes(metadata.collection_id)
10024
10053
  );
10025
10054
  }
10026
10055
  function encodeNftContent(content) {
@@ -10078,13 +10107,14 @@ function decodeMedia(body) {
10078
10107
  const [, mediaType, bytes] = versionedFields(body, MEDIA_ARITY);
10079
10108
  return { kind: "media", media_type: decodeText(mediaType), bytes: import_CborDeserializer.CborDeserializer.decodeByteString(bytes) };
10080
10109
  }
10110
+ var decodeHex = (item) => import_HexConverter.HexConverter.encode(import_CborDeserializer.CborDeserializer.decodeByteString(item));
10081
10111
  function decodeLink(body) {
10082
10112
  const [, mediaType, uri, digest] = versionedFields(body, LINK_ARITY);
10083
10113
  return {
10084
10114
  kind: "link",
10085
10115
  media_type: decodeText(mediaType),
10086
10116
  uri: decodeText(uri),
10087
- sha256: import_HexConverter.HexConverter.encode(import_CborDeserializer.CborDeserializer.decodeByteString(digest))
10117
+ sha256: decodeHex(digest)
10088
10118
  };
10089
10119
  }
10090
10120
  function decodeMetadata(body) {
@@ -10097,7 +10127,8 @@ function decodeMetadata(body) {
10097
10127
  animation_url: decodeMediaRef("animation_url", fields[4]),
10098
10128
  external_url: decodeOptionalText(fields[5]),
10099
10129
  attributes: decodeAttributes(fields[6]),
10100
- collection: decodeOptionalText(fields[7])
10130
+ collection: decodeOptionalText(fields[7]),
10131
+ collection_id: import_CborDeserializer.CborDeserializer.decodeNullable(fields[8], decodeHex)
10101
10132
  };
10102
10133
  }
10103
10134
  var MEDIA_REF_DECODERS = /* @__PURE__ */ new Map([
@@ -10143,20 +10174,26 @@ function parseNftPayload(data) {
10143
10174
  return null;
10144
10175
  }
10145
10176
  }
10146
- function nftSignedDigest(tokenIdCbor, recipientPredicateCbor, payload) {
10177
+ function isNftDocumentLink(content) {
10178
+ return content?.kind === "link" && content.media_type === NFT_DOCUMENT_MEDIA_TYPE;
10179
+ }
10180
+ function nftSignedDigest(context, payload) {
10147
10181
  const preimage = import_CborSerializer.CborSerializer.encodeArray(
10148
- import_CborSerializer.CborSerializer.encodeTextString(SIGNED_DOMAIN),
10149
- tokenIdCbor,
10150
- recipientPredicateCbor,
10182
+ import_CborSerializer.CborSerializer.encodeByteString(SIGNED_DOMAIN),
10183
+ import_CborSerializer.CborSerializer.encodeUnsignedInteger(NFT_FORMAT_VERSION),
10184
+ import_CborSerializer.CborSerializer.encodeUnsignedInteger(import_NetworkId.NetworkId.fromId(context.networkId).id),
10185
+ import_CborSerializer.CborSerializer.encodeByteString(context.recipientPredicate),
10186
+ import_CborSerializer.CborSerializer.encodeByteString(context.tokenId),
10187
+ import_CborSerializer.CborSerializer.encodeByteString(context.tokenType),
10151
10188
  import_CborSerializer.CborSerializer.encodeByteString(payload)
10152
10189
  );
10153
10190
  return new import_DataHasher.DataHasher(import_HashAlgorithm.HashAlgorithm.SHA256).update(preimage).digest();
10154
10191
  }
10155
- async function verifyNftSignature(signed, tokenIdCbor, recipientPredicateCbor) {
10192
+ async function verifyNftSignature(signed, context) {
10156
10193
  try {
10157
10194
  if (!import_SigningService.SigningService.isPublicKeyValid(signed.creator)) return "invalid";
10158
10195
  const signature = import_Signature.Signature.decode(signed.signature);
10159
- const digest = await nftSignedDigest(tokenIdCbor, recipientPredicateCbor, signed.payload);
10196
+ const digest = await nftSignedDigest(context, signed.payload);
10160
10197
  return await import_SigningService.SigningService.verifyWithPublicKey(digest, signature, signed.creator) ? "valid" : "invalid";
10161
10198
  } catch {
10162
10199
  return "invalid";
@@ -10287,8 +10324,8 @@ function assertMintableData(data) {
10287
10324
  function nftRecipientCbor(pubkey) {
10288
10325
  return import_EncodedPredicate.EncodedPredicate.fromPredicate(import_SignaturePredicate.SignaturePredicate.create(pubkey)).toCBOR();
10289
10326
  }
10290
- async function signPayload(deps, payload, tokenId, recipientPubkey) {
10291
- const digest = await nftSignedDigest(tokenId.toCBOR(), nftRecipientCbor(recipientPubkey), payload);
10327
+ async function signPayload(deps, payload, context) {
10328
+ const digest = await nftSignedDigest(context, payload);
10292
10329
  const signature = await deps.signingService.sign(digest);
10293
10330
  return encodeNftSigned(deps.signingService.publicKey, payload, signature.encode());
10294
10331
  }
@@ -10297,31 +10334,41 @@ async function planNftMint(deps, params) {
10297
10334
  const tokenType = new import_TokenType.TokenType(params.tokenType).bytes;
10298
10335
  const salt = import_TokenSalt.TokenSalt.generate();
10299
10336
  const tokenId = await import_TokenId.TokenId.fromSalt(deps.networkId, salt);
10300
- const data = params.sign ? await signPayload(deps, payload, tokenId, params.recipientPubkey) : payload;
10337
+ const data = params.sign ? await signPayload(deps, payload, {
10338
+ networkId: deps.networkId.id,
10339
+ recipientPredicate: nftRecipientCbor(params.recipientPubkey),
10340
+ tokenId: tokenId.bytes,
10341
+ tokenType
10342
+ }) : payload;
10301
10343
  assertMintableData(data);
10302
10344
  return { data, salt: salt.toBytes(), tokenType, tokenId: import_HexConverter.HexConverter.encode(tokenId.bytes) };
10303
10345
  }
10304
- async function verifyAgainst(signed, tokenIdCbor, genesisRecipientCbor) {
10346
+ async function verifyAgainst(signed, genesisContext) {
10305
10347
  try {
10306
- return await verifyNftSignature(signed, tokenIdCbor, genesisRecipientCbor());
10348
+ return await verifyNftSignature(signed, genesisContext());
10307
10349
  } catch {
10308
10350
  return "invalid";
10309
10351
  }
10310
10352
  }
10311
- async function readNftData(data, tokenIdCbor, genesisRecipientCbor) {
10353
+ async function readNftData(data, genesisContext) {
10312
10354
  const parsed = parseNftPayload(data);
10313
10355
  if (parsed === null) return null;
10314
10356
  if (parsed.signed === null) return { content: parsed.content, creator: null, signature: "unsigned" };
10315
10357
  return {
10316
10358
  content: parsed.content,
10317
10359
  creator: import_HexConverter.HexConverter.encode(parsed.signed.creator),
10318
- signature: await verifyAgainst(parsed.signed, tokenIdCbor, genesisRecipientCbor)
10360
+ signature: await verifyAgainst(parsed.signed, genesisContext)
10319
10361
  };
10320
10362
  }
10321
10363
  async function readTokenNft(token) {
10322
10364
  try {
10323
10365
  const { genesis } = token.sdkToken;
10324
- return await readNftData(genesis.data, genesis.tokenId.toCBOR(), () => genesis.recipient.toCBOR());
10366
+ return await readNftData(genesis.data, () => ({
10367
+ networkId: genesis.networkId.id,
10368
+ recipientPredicate: genesis.recipient.toCBOR(),
10369
+ tokenId: genesis.tokenId.bytes,
10370
+ tokenType: genesis.tokenType.bytes
10371
+ }));
10325
10372
  } catch {
10326
10373
  return null;
10327
10374
  }