@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
package/dist/index.cjs CHANGED
@@ -50,6 +50,7 @@ __export(index_exports, {
50
50
  LIMITS: () => LIMITS,
51
51
  MarketModule: () => MarketModule,
52
52
  NETWORKS: () => NETWORKS,
53
+ NFT_DOCUMENT_MEDIA_TYPE: () => NFT_DOCUMENT_MEDIA_TYPE,
53
54
  NFT_LINK_TAG: () => NFT_LINK_TAG,
54
55
  NFT_MAX_PAYLOAD_BYTES: () => NFT_MAX_PAYLOAD_BYTES,
55
56
  NFT_MEDIA_TAG: () => NFT_MEDIA_TAG,
@@ -128,6 +129,7 @@ __export(index_exports, {
128
129
  identityFromMnemonicSync: () => identityFromMnemonicSync,
129
130
  initSphere: () => initSphere,
130
131
  isKnownToken: () => isKnownToken,
132
+ isNftDocumentLink: () => isNftDocumentLink,
131
133
  isPhoneNumber: () => import_nostr_js_sdk5.isPhoneNumber,
132
134
  isPossiblyCommittedSendOutcome: () => isPossiblyCommittedSendOutcome,
133
135
  isSphereError: () => isSphereError,
@@ -145,6 +147,7 @@ __export(index_exports, {
145
147
  normalizeNametag: () => import_nostr_js_sdk5.normalizeNametag,
146
148
  parseAddress: () => parseAddress,
147
149
  parseAndDecryptWalletText: () => parseAndDecryptWalletText,
150
+ parseNftDocument: () => parseNftDocument,
148
151
  parseNftPayload: () => parseNftPayload,
149
152
  parseTokenAmount: () => parseTokenAmount,
150
153
  parseWalletText: () => parseWalletText,
@@ -4693,6 +4696,26 @@ var GroupVisibility = {
4693
4696
  PRIVATE: "PRIVATE"
4694
4697
  };
4695
4698
 
4699
+ // modules/groupchat/group-id.ts
4700
+ var MAX_SLUG_LENGTH = 24;
4701
+ var RANDOM_BYTES = 8;
4702
+ function groupIdSlug(name) {
4703
+ const slug = name.normalize("NFKD").replace(/\p{M}/gu, "").toLowerCase().replace(/['\u2019]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
4704
+ if (slug.length <= MAX_SLUG_LENGTH) return slug;
4705
+ const head = slug.slice(0, MAX_SLUG_LENGTH + 1);
4706
+ const hyphen = head.lastIndexOf("-");
4707
+ return hyphen > 0 ? head.slice(0, hyphen) : slug.slice(0, MAX_SLUG_LENGTH);
4708
+ }
4709
+ function newGroupId(name) {
4710
+ const crypto2 = globalThis.crypto;
4711
+ if (typeof crypto2?.getRandomValues !== "function") {
4712
+ throw new SphereError("Creating a group requires crypto.getRandomValues", "INVALID_CONFIG");
4713
+ }
4714
+ const bytes = crypto2.getRandomValues(new Uint8Array(RANDOM_BYTES));
4715
+ const random = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
4716
+ return `${groupIdSlug(name) || "group"}-${random}`;
4717
+ }
4718
+
4696
4719
  // modules/groupchat/GroupChatModule.ts
4697
4720
  function createNip29Filter(data) {
4698
4721
  return new import_nostr_js_sdk3.Filter(data);
@@ -5304,9 +5327,9 @@ var GroupChatModule = class {
5304
5327
  if (!this.client) return null;
5305
5328
  const creatorPubkey = this.getMyPublicKey();
5306
5329
  if (!creatorPubkey) return null;
5307
- const proposedGroupId = options.name.toLowerCase().replace(/[^a-z0-9]/g, "").slice(0, 20) || this.randomId();
5308
5330
  try {
5309
5331
  const isPrivate = options.visibility === GroupVisibility.PRIVATE;
5332
+ const proposedGroupId = newGroupId(options.name);
5310
5333
  const eventId = await this.client.createAndPublishEvent({
5311
5334
  kind: NIP29_KINDS.CREATE_GROUP,
5312
5335
  tags: [["h", proposedGroupId]],
@@ -9937,7 +9960,8 @@ var NFT_MEDIA_TAG = 39053n;
9937
9960
  var NFT_LINK_TAG = 39054n;
9938
9961
  var NFT_SIGNED_TAG = 39055n;
9939
9962
  var NFT_FORMAT_VERSION = 1n;
9940
- var METADATA_ARITY = 8;
9963
+ var NFT_DOCUMENT_MEDIA_TYPE = "application/vnd.unicity.nft+cbor";
9964
+ var METADATA_ARITY = 9;
9941
9965
  var MEDIA_ARITY = 3;
9942
9966
  var LINK_ARITY = 4;
9943
9967
  var SIGNED_ARITY = 4;
@@ -9945,12 +9969,15 @@ var CREATOR_BYTES = 33;
9945
9969
  var SIGNATURE_BYTES = 65;
9946
9970
  var MAX_URI_LENGTH = 2048;
9947
9971
  var LINK_SCHEMES = ["https://", "ipfs://", "ar://"];
9948
- var SIGNED_DOMAIN = "NftSigned";
9972
+ var SIGNED_DOMAIN = new TextEncoder().encode("UNICITY_NFT_SIGNED");
9949
9973
  var MEDIA_TYPE_PATTERN = /^[a-z0-9][a-z0-9!#$&^_.+-]{0,126}\/[a-z0-9][a-z0-9!#$&^_.+-]{0,126}$/;
9950
9974
  var SHA256_HEX_PATTERN = /^[0-9a-f]{64}$/i;
9975
+ var COLLECTION_ID_PATTERN = /^(?:[0-9a-f]{2}){1,64}$/i;
9951
9976
  var LONE_SURROGATE = /\p{Cs}/u;
9952
9977
  var CBOR_MAJOR_TYPE_MASK = 224;
9953
9978
  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";
9979
+ var COLLECTION_ID_RULE = "must be null or 1\u201364 bytes as even-length hex (2\u2013128 characters)";
9980
+ var DOCUMENT_AS_MEDIA_RULE = `must not be ${NFT_DOCUMENT_MEDIA_TYPE}: a document link is a token's content, never media`;
9954
9981
  var UTF8 = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
9955
9982
  function refuse(field, rule, cause) {
9956
9983
  return new SphereError(`Invalid NFT ${field}: ${rule}`, "VALIDATION_ERROR", cause);
@@ -9962,6 +9989,7 @@ var fieldName = (prefix, name) => prefix === "" ? name : `${prefix}.${name}`;
9962
9989
  var isText = (value) => typeof value === "string" && value.length > 0 && !LONE_SURROGATE.test(value);
9963
9990
  var isBytes3 = (value, length) => value instanceof Uint8Array && (length === void 0 ? value.length > 0 : value.length === length);
9964
9991
  var isMediaType = (value) => typeof value === "string" && MEDIA_TYPE_PATTERN.test(value);
9992
+ var isCollectionId = (value) => typeof value === "string" && COLLECTION_ID_PATTERN.test(value);
9965
9993
  var isAttributeValue = (value) => typeof value === "number" ? Number.isSafeInteger(value) : isText(value);
9966
9994
  function hasSpaceOrControl(value) {
9967
9995
  for (let i = 0; i < value.length; i++) {
@@ -10004,8 +10032,9 @@ function checkLink(prefix, link) {
10004
10032
  function checkMediaRef(field, ref) {
10005
10033
  if (ref === null) return;
10006
10034
  if (ref?.kind === "media") return checkMedia(field, ref);
10007
- if (ref?.kind === "link") return checkLink(field, ref);
10008
- throw refuse(field, "must be an NftMedia, an NftLink or null");
10035
+ if (ref?.kind !== "link") throw refuse(field, "must be an NftMedia, an NftLink or null");
10036
+ checkLink(field, ref);
10037
+ ensure(!isNftDocumentLink(ref), fieldName(field, "media_type"), DOCUMENT_AS_MEDIA_RULE);
10009
10038
  }
10010
10039
  function checkAttributes(attributes) {
10011
10040
  ensure(Array.isArray(attributes), "attributes", "must be an array \u2014 [] when there are none");
@@ -10022,6 +10051,7 @@ function checkMetadata(metadata) {
10022
10051
  checkOptionalText("external_url", metadata.external_url);
10023
10052
  checkAttributes(metadata.attributes);
10024
10053
  checkOptionalText("collection", metadata.collection);
10054
+ ensure(metadata.collection_id === null || isCollectionId(metadata.collection_id), "collection_id", COLLECTION_ID_RULE);
10025
10055
  }
10026
10056
  function checkContent(content) {
10027
10057
  switch (content?.kind) {
@@ -10079,6 +10109,7 @@ function arrayCbor(items) {
10079
10109
  }
10080
10110
  return out;
10081
10111
  }
10112
+ var optionalHexBytes = (value) => import_CborSerializer.CborSerializer.encodeNullable(value, (hex) => import_CborSerializer.CborSerializer.encodeByteString(import_HexConverter.HexConverter.decode(hex.toLowerCase())));
10082
10113
  function metadataCbor(metadata) {
10083
10114
  return versioned(
10084
10115
  NFT_METADATA_TAG,
@@ -10088,7 +10119,8 @@ function metadataCbor(metadata) {
10088
10119
  mediaRefCbor(metadata.animation_url),
10089
10120
  optionalText(metadata.external_url),
10090
10121
  arrayCbor(metadata.attributes.map(attributeCbor)),
10091
- optionalText(metadata.collection)
10122
+ optionalText(metadata.collection),
10123
+ optionalHexBytes(metadata.collection_id)
10092
10124
  );
10093
10125
  }
10094
10126
  function encodeNftContent(content) {
@@ -10146,13 +10178,14 @@ function decodeMedia(body) {
10146
10178
  const [, mediaType, bytes] = versionedFields(body, MEDIA_ARITY);
10147
10179
  return { kind: "media", media_type: decodeText(mediaType), bytes: import_CborDeserializer.CborDeserializer.decodeByteString(bytes) };
10148
10180
  }
10181
+ var decodeHex = (item) => import_HexConverter.HexConverter.encode(import_CborDeserializer.CborDeserializer.decodeByteString(item));
10149
10182
  function decodeLink(body) {
10150
10183
  const [, mediaType, uri, digest] = versionedFields(body, LINK_ARITY);
10151
10184
  return {
10152
10185
  kind: "link",
10153
10186
  media_type: decodeText(mediaType),
10154
10187
  uri: decodeText(uri),
10155
- sha256: import_HexConverter.HexConverter.encode(import_CborDeserializer.CborDeserializer.decodeByteString(digest))
10188
+ sha256: decodeHex(digest)
10156
10189
  };
10157
10190
  }
10158
10191
  function decodeMetadata(body) {
@@ -10165,7 +10198,8 @@ function decodeMetadata(body) {
10165
10198
  animation_url: decodeMediaRef("animation_url", fields[4]),
10166
10199
  external_url: decodeOptionalText(fields[5]),
10167
10200
  attributes: decodeAttributes(fields[6]),
10168
- collection: decodeOptionalText(fields[7])
10201
+ collection: decodeOptionalText(fields[7]),
10202
+ collection_id: import_CborDeserializer.CborDeserializer.decodeNullable(fields[8], decodeHex)
10169
10203
  };
10170
10204
  }
10171
10205
  var MEDIA_REF_DECODERS = /* @__PURE__ */ new Map([
@@ -10177,6 +10211,10 @@ var UNSIGNED_DECODERS = /* @__PURE__ */ new Map([
10177
10211
  [NFT_MEDIA_TAG, decodeMedia],
10178
10212
  [NFT_LINK_TAG, decodeLink]
10179
10213
  ]);
10214
+ var DOCUMENT_DECODERS = /* @__PURE__ */ new Map([
10215
+ [NFT_METADATA_TAG, decodeMetadata],
10216
+ [NFT_MEDIA_TAG, decodeMedia]
10217
+ ]);
10180
10218
  function decodeBody(decoders, field, tag, body) {
10181
10219
  const decode = decoders.get(tag);
10182
10220
  if (decode === void 0) throw refuse(field, `tag ${tag} is not allowed here`);
@@ -10211,20 +10249,35 @@ function parseNftPayload(data) {
10211
10249
  return null;
10212
10250
  }
10213
10251
  }
10214
- function nftSignedDigest(tokenIdCbor, recipientPredicateCbor, payload) {
10252
+ function parseNftDocument(bytes) {
10253
+ try {
10254
+ const document = decodeItem(DOCUMENT_DECODERS, "document", bytes);
10255
+ checkContent(document);
10256
+ return document;
10257
+ } catch {
10258
+ return null;
10259
+ }
10260
+ }
10261
+ function isNftDocumentLink(content) {
10262
+ return content?.kind === "link" && content.media_type === NFT_DOCUMENT_MEDIA_TYPE;
10263
+ }
10264
+ function nftSignedDigest(context, payload) {
10215
10265
  const preimage = import_CborSerializer.CborSerializer.encodeArray(
10216
- import_CborSerializer.CborSerializer.encodeTextString(SIGNED_DOMAIN),
10217
- tokenIdCbor,
10218
- recipientPredicateCbor,
10266
+ import_CborSerializer.CborSerializer.encodeByteString(SIGNED_DOMAIN),
10267
+ import_CborSerializer.CborSerializer.encodeUnsignedInteger(NFT_FORMAT_VERSION),
10268
+ import_CborSerializer.CborSerializer.encodeUnsignedInteger(import_NetworkId.NetworkId.fromId(context.networkId).id),
10269
+ import_CborSerializer.CborSerializer.encodeByteString(context.recipientPredicate),
10270
+ import_CborSerializer.CborSerializer.encodeByteString(context.tokenId),
10271
+ import_CborSerializer.CborSerializer.encodeByteString(context.tokenType),
10219
10272
  import_CborSerializer.CborSerializer.encodeByteString(payload)
10220
10273
  );
10221
10274
  return new import_DataHasher.DataHasher(import_HashAlgorithm.HashAlgorithm.SHA256).update(preimage).digest();
10222
10275
  }
10223
- async function verifyNftSignature(signed, tokenIdCbor, recipientPredicateCbor) {
10276
+ async function verifyNftSignature(signed, context) {
10224
10277
  try {
10225
10278
  if (!import_SigningService.SigningService.isPublicKeyValid(signed.creator)) return "invalid";
10226
10279
  const signature = import_Signature.Signature.decode(signed.signature);
10227
- const digest = await nftSignedDigest(tokenIdCbor, recipientPredicateCbor, signed.payload);
10280
+ const digest = await nftSignedDigest(context, signed.payload);
10228
10281
  return await import_SigningService.SigningService.verifyWithPublicKey(digest, signature, signed.creator) ? "valid" : "invalid";
10229
10282
  } catch {
10230
10283
  return "invalid";
@@ -10358,8 +10411,8 @@ function assertMintableData(data) {
10358
10411
  function nftRecipientCbor(pubkey) {
10359
10412
  return import_EncodedPredicate.EncodedPredicate.fromPredicate(import_SignaturePredicate.SignaturePredicate.create(pubkey)).toCBOR();
10360
10413
  }
10361
- async function signPayload(deps, payload, tokenId, recipientPubkey) {
10362
- const digest = await nftSignedDigest(tokenId.toCBOR(), nftRecipientCbor(recipientPubkey), payload);
10414
+ async function signPayload(deps, payload, context) {
10415
+ const digest = await nftSignedDigest(context, payload);
10363
10416
  const signature = await deps.signingService.sign(digest);
10364
10417
  return encodeNftSigned(deps.signingService.publicKey, payload, signature.encode());
10365
10418
  }
@@ -10368,31 +10421,41 @@ async function planNftMint(deps, params) {
10368
10421
  const tokenType = new import_TokenType.TokenType(params.tokenType).bytes;
10369
10422
  const salt = import_TokenSalt.TokenSalt.generate();
10370
10423
  const tokenId = await import_TokenId.TokenId.fromSalt(deps.networkId, salt);
10371
- const data = params.sign ? await signPayload(deps, payload, tokenId, params.recipientPubkey) : payload;
10424
+ const data = params.sign ? await signPayload(deps, payload, {
10425
+ networkId: deps.networkId.id,
10426
+ recipientPredicate: nftRecipientCbor(params.recipientPubkey),
10427
+ tokenId: tokenId.bytes,
10428
+ tokenType
10429
+ }) : payload;
10372
10430
  assertMintableData(data);
10373
10431
  return { data, salt: salt.toBytes(), tokenType, tokenId: import_HexConverter.HexConverter.encode(tokenId.bytes) };
10374
10432
  }
10375
- async function verifyAgainst(signed, tokenIdCbor, genesisRecipientCbor) {
10433
+ async function verifyAgainst(signed, genesisContext) {
10376
10434
  try {
10377
- return await verifyNftSignature(signed, tokenIdCbor, genesisRecipientCbor());
10435
+ return await verifyNftSignature(signed, genesisContext());
10378
10436
  } catch {
10379
10437
  return "invalid";
10380
10438
  }
10381
10439
  }
10382
- async function readNftData(data, tokenIdCbor, genesisRecipientCbor) {
10440
+ async function readNftData(data, genesisContext) {
10383
10441
  const parsed = parseNftPayload(data);
10384
10442
  if (parsed === null) return null;
10385
10443
  if (parsed.signed === null) return { content: parsed.content, creator: null, signature: "unsigned" };
10386
10444
  return {
10387
10445
  content: parsed.content,
10388
10446
  creator: import_HexConverter.HexConverter.encode(parsed.signed.creator),
10389
- signature: await verifyAgainst(parsed.signed, tokenIdCbor, genesisRecipientCbor)
10447
+ signature: await verifyAgainst(parsed.signed, genesisContext)
10390
10448
  };
10391
10449
  }
10392
10450
  async function readTokenNft(token) {
10393
10451
  try {
10394
10452
  const { genesis } = token.sdkToken;
10395
- return await readNftData(genesis.data, genesis.tokenId.toCBOR(), () => genesis.recipient.toCBOR());
10453
+ return await readNftData(genesis.data, () => ({
10454
+ networkId: genesis.networkId.id,
10455
+ recipientPredicate: genesis.recipient.toCBOR(),
10456
+ tokenId: genesis.tokenId.bytes,
10457
+ tokenType: genesis.tokenType.bytes
10458
+ }));
10396
10459
  } catch {
10397
10460
  return null;
10398
10461
  }
@@ -20853,6 +20916,7 @@ function addressesMatch(a, b) {
20853
20916
  LIMITS,
20854
20917
  MarketModule,
20855
20918
  NETWORKS,
20919
+ NFT_DOCUMENT_MEDIA_TYPE,
20856
20920
  NFT_LINK_TAG,
20857
20921
  NFT_MAX_PAYLOAD_BYTES,
20858
20922
  NFT_MEDIA_TAG,
@@ -20931,6 +20995,7 @@ function addressesMatch(a, b) {
20931
20995
  identityFromMnemonicSync,
20932
20996
  initSphere,
20933
20997
  isKnownToken,
20998
+ isNftDocumentLink,
20934
20999
  isPhoneNumber,
20935
21000
  isPossiblyCommittedSendOutcome,
20936
21001
  isSphereError,
@@ -20948,6 +21013,7 @@ function addressesMatch(a, b) {
20948
21013
  normalizeNametag,
20949
21014
  parseAddress,
20950
21015
  parseAndDecryptWalletText,
21016
+ parseNftDocument,
20951
21017
  parseNftPayload,
20952
21018
  parseTokenAmount,
20953
21019
  parseWalletText,