@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.
- package/dist/connect/index.cjs +7 -4
- package/dist/connect/index.cjs.map +1 -1
- package/dist/connect/index.d.cts +4 -0
- package/dist/connect/index.d.ts +4 -0
- package/dist/connect/index.js +7 -4
- package/dist/connect/index.js.map +1 -1
- package/dist/core/index.cjs +69 -22
- package/dist/core/index.cjs.map +1 -1
- package/dist/core/index.d.cts +2 -0
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +69 -22
- package/dist/core/index.js.map +1 -1
- package/dist/impl/browser/connect/index.cjs +1 -1
- package/dist/impl/browser/connect/index.cjs.map +1 -1
- package/dist/impl/browser/connect/index.js +1 -1
- package/dist/impl/browser/connect/index.js.map +1 -1
- package/dist/impl/wallet-api-v2/index.cjs +3 -0
- package/dist/impl/wallet-api-v2/index.cjs.map +1 -1
- package/dist/impl/wallet-api-v2/index.js +3 -0
- package/dist/impl/wallet-api-v2/index.js.map +1 -1
- package/dist/index.cjs +88 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +22 -1
- package/dist/index.d.ts +22 -1
- package/dist/index.js +85 -22
- package/dist/index.js.map +1 -1
- package/dist/modules/payments-v2/index.cjs +40 -6
- package/dist/modules/payments-v2/index.cjs.map +1 -1
- package/dist/modules/payments-v2/index.d.cts +22 -1
- package/dist/modules/payments-v2/index.d.ts +22 -1
- package/dist/modules/payments-v2/index.js +37 -6
- package/dist/modules/payments-v2/index.js.map +1 -1
- package/dist/token-engine/index.cjs +67 -21
- package/dist/token-engine/index.cjs.map +1 -1
- package/dist/token-engine/index.d.cts +28 -4
- package/dist/token-engine/index.d.ts +28 -4
- package/dist/token-engine/index.js +64 -21
- package/dist/token-engine/index.js.map +1 -1
- package/package.json +1 -1
package/dist/core/index.d.cts
CHANGED
|
@@ -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';
|
package/dist/core/index.d.ts
CHANGED
|
@@ -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';
|
package/dist/core/index.js
CHANGED
|
@@ -4539,6 +4539,26 @@ var GroupVisibility = {
|
|
|
4539
4539
|
PRIVATE: "PRIVATE"
|
|
4540
4540
|
};
|
|
4541
4541
|
|
|
4542
|
+
// modules/groupchat/group-id.ts
|
|
4543
|
+
var MAX_SLUG_LENGTH = 24;
|
|
4544
|
+
var RANDOM_BYTES = 8;
|
|
4545
|
+
function groupIdSlug(name) {
|
|
4546
|
+
const slug = name.normalize("NFKD").replace(/\p{M}/gu, "").toLowerCase().replace(/['\u2019]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
4547
|
+
if (slug.length <= MAX_SLUG_LENGTH) return slug;
|
|
4548
|
+
const head = slug.slice(0, MAX_SLUG_LENGTH + 1);
|
|
4549
|
+
const hyphen = head.lastIndexOf("-");
|
|
4550
|
+
return hyphen > 0 ? head.slice(0, hyphen) : slug.slice(0, MAX_SLUG_LENGTH);
|
|
4551
|
+
}
|
|
4552
|
+
function newGroupId(name) {
|
|
4553
|
+
const crypto2 = globalThis.crypto;
|
|
4554
|
+
if (typeof crypto2?.getRandomValues !== "function") {
|
|
4555
|
+
throw new SphereError("Creating a group requires crypto.getRandomValues", "INVALID_CONFIG");
|
|
4556
|
+
}
|
|
4557
|
+
const bytes = crypto2.getRandomValues(new Uint8Array(RANDOM_BYTES));
|
|
4558
|
+
const random = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
4559
|
+
return `${groupIdSlug(name) || "group"}-${random}`;
|
|
4560
|
+
}
|
|
4561
|
+
|
|
4542
4562
|
// modules/groupchat/GroupChatModule.ts
|
|
4543
4563
|
function createNip29Filter(data) {
|
|
4544
4564
|
return new Filter3(data);
|
|
@@ -5150,9 +5170,9 @@ var GroupChatModule = class {
|
|
|
5150
5170
|
if (!this.client) return null;
|
|
5151
5171
|
const creatorPubkey = this.getMyPublicKey();
|
|
5152
5172
|
if (!creatorPubkey) return null;
|
|
5153
|
-
const proposedGroupId = options.name.toLowerCase().replace(/[^a-z0-9]/g, "").slice(0, 20) || this.randomId();
|
|
5154
5173
|
try {
|
|
5155
5174
|
const isPrivate = options.visibility === GroupVisibility.PRIVATE;
|
|
5175
|
+
const proposedGroupId = newGroupId(options.name);
|
|
5156
5176
|
const eventId = await this.client.createAndPublishEvent({
|
|
5157
5177
|
kind: NIP29_KINDS.CREATE_GROUP,
|
|
5158
5178
|
tags: [["h", proposedGroupId]],
|
|
@@ -9784,7 +9804,8 @@ var NFT_MEDIA_TAG = 39053n;
|
|
|
9784
9804
|
var NFT_LINK_TAG = 39054n;
|
|
9785
9805
|
var NFT_SIGNED_TAG = 39055n;
|
|
9786
9806
|
var NFT_FORMAT_VERSION = 1n;
|
|
9787
|
-
var
|
|
9807
|
+
var NFT_DOCUMENT_MEDIA_TYPE = "application/vnd.unicity.nft+cbor";
|
|
9808
|
+
var METADATA_ARITY = 9;
|
|
9788
9809
|
var MEDIA_ARITY = 3;
|
|
9789
9810
|
var LINK_ARITY = 4;
|
|
9790
9811
|
var SIGNED_ARITY = 4;
|
|
@@ -9792,12 +9813,15 @@ var CREATOR_BYTES = 33;
|
|
|
9792
9813
|
var SIGNATURE_BYTES = 65;
|
|
9793
9814
|
var MAX_URI_LENGTH = 2048;
|
|
9794
9815
|
var LINK_SCHEMES = ["https://", "ipfs://", "ar://"];
|
|
9795
|
-
var SIGNED_DOMAIN = "
|
|
9816
|
+
var SIGNED_DOMAIN = new TextEncoder().encode("UNICITY_NFT_SIGNED");
|
|
9796
9817
|
var MEDIA_TYPE_PATTERN = /^[a-z0-9][a-z0-9!#$&^_.+-]{0,126}\/[a-z0-9][a-z0-9!#$&^_.+-]{0,126}$/;
|
|
9797
9818
|
var SHA256_HEX_PATTERN = /^[0-9a-f]{64}$/i;
|
|
9819
|
+
var COLLECTION_ID_PATTERN = /^(?:[0-9a-f]{2}){1,64}$/i;
|
|
9798
9820
|
var LONE_SURROGATE = /\p{Cs}/u;
|
|
9799
9821
|
var CBOR_MAJOR_TYPE_MASK = 224;
|
|
9800
9822
|
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";
|
|
9823
|
+
var COLLECTION_ID_RULE = "must be null or 1\u201364 bytes as even-length hex (2\u2013128 characters)";
|
|
9824
|
+
var DOCUMENT_AS_MEDIA_RULE = `must not be ${NFT_DOCUMENT_MEDIA_TYPE}: a document link is a token's content, never media`;
|
|
9801
9825
|
var UTF8 = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
|
|
9802
9826
|
function refuse(field, rule, cause) {
|
|
9803
9827
|
return new SphereError(`Invalid NFT ${field}: ${rule}`, "VALIDATION_ERROR", cause);
|
|
@@ -9809,6 +9833,7 @@ var fieldName = (prefix, name) => prefix === "" ? name : `${prefix}.${name}`;
|
|
|
9809
9833
|
var isText = (value) => typeof value === "string" && value.length > 0 && !LONE_SURROGATE.test(value);
|
|
9810
9834
|
var isBytes3 = (value, length) => value instanceof Uint8Array && (length === void 0 ? value.length > 0 : value.length === length);
|
|
9811
9835
|
var isMediaType = (value) => typeof value === "string" && MEDIA_TYPE_PATTERN.test(value);
|
|
9836
|
+
var isCollectionId = (value) => typeof value === "string" && COLLECTION_ID_PATTERN.test(value);
|
|
9812
9837
|
var isAttributeValue = (value) => typeof value === "number" ? Number.isSafeInteger(value) : isText(value);
|
|
9813
9838
|
function hasSpaceOrControl(value) {
|
|
9814
9839
|
for (let i = 0; i < value.length; i++) {
|
|
@@ -9851,8 +9876,9 @@ function checkLink(prefix, link) {
|
|
|
9851
9876
|
function checkMediaRef(field, ref) {
|
|
9852
9877
|
if (ref === null) return;
|
|
9853
9878
|
if (ref?.kind === "media") return checkMedia(field, ref);
|
|
9854
|
-
if (ref?.kind
|
|
9855
|
-
|
|
9879
|
+
if (ref?.kind !== "link") throw refuse(field, "must be an NftMedia, an NftLink or null");
|
|
9880
|
+
checkLink(field, ref);
|
|
9881
|
+
ensure(!isNftDocumentLink(ref), fieldName(field, "media_type"), DOCUMENT_AS_MEDIA_RULE);
|
|
9856
9882
|
}
|
|
9857
9883
|
function checkAttributes(attributes) {
|
|
9858
9884
|
ensure(Array.isArray(attributes), "attributes", "must be an array \u2014 [] when there are none");
|
|
@@ -9869,6 +9895,7 @@ function checkMetadata(metadata) {
|
|
|
9869
9895
|
checkOptionalText("external_url", metadata.external_url);
|
|
9870
9896
|
checkAttributes(metadata.attributes);
|
|
9871
9897
|
checkOptionalText("collection", metadata.collection);
|
|
9898
|
+
ensure(metadata.collection_id === null || isCollectionId(metadata.collection_id), "collection_id", COLLECTION_ID_RULE);
|
|
9872
9899
|
}
|
|
9873
9900
|
function checkContent(content) {
|
|
9874
9901
|
switch (content?.kind) {
|
|
@@ -9926,6 +9953,7 @@ function arrayCbor(items) {
|
|
|
9926
9953
|
}
|
|
9927
9954
|
return out;
|
|
9928
9955
|
}
|
|
9956
|
+
var optionalHexBytes = (value) => CborSerializer.encodeNullable(value, (hex) => CborSerializer.encodeByteString(HexConverter.decode(hex.toLowerCase())));
|
|
9929
9957
|
function metadataCbor(metadata) {
|
|
9930
9958
|
return versioned(
|
|
9931
9959
|
NFT_METADATA_TAG,
|
|
@@ -9935,7 +9963,8 @@ function metadataCbor(metadata) {
|
|
|
9935
9963
|
mediaRefCbor(metadata.animation_url),
|
|
9936
9964
|
optionalText(metadata.external_url),
|
|
9937
9965
|
arrayCbor(metadata.attributes.map(attributeCbor)),
|
|
9938
|
-
optionalText(metadata.collection)
|
|
9966
|
+
optionalText(metadata.collection),
|
|
9967
|
+
optionalHexBytes(metadata.collection_id)
|
|
9939
9968
|
);
|
|
9940
9969
|
}
|
|
9941
9970
|
function encodeNftContent(content) {
|
|
@@ -9993,13 +10022,14 @@ function decodeMedia(body) {
|
|
|
9993
10022
|
const [, mediaType, bytes] = versionedFields(body, MEDIA_ARITY);
|
|
9994
10023
|
return { kind: "media", media_type: decodeText(mediaType), bytes: CborDeserializer.decodeByteString(bytes) };
|
|
9995
10024
|
}
|
|
10025
|
+
var decodeHex = (item) => HexConverter.encode(CborDeserializer.decodeByteString(item));
|
|
9996
10026
|
function decodeLink(body) {
|
|
9997
10027
|
const [, mediaType, uri, digest] = versionedFields(body, LINK_ARITY);
|
|
9998
10028
|
return {
|
|
9999
10029
|
kind: "link",
|
|
10000
10030
|
media_type: decodeText(mediaType),
|
|
10001
10031
|
uri: decodeText(uri),
|
|
10002
|
-
sha256:
|
|
10032
|
+
sha256: decodeHex(digest)
|
|
10003
10033
|
};
|
|
10004
10034
|
}
|
|
10005
10035
|
function decodeMetadata(body) {
|
|
@@ -10012,7 +10042,8 @@ function decodeMetadata(body) {
|
|
|
10012
10042
|
animation_url: decodeMediaRef("animation_url", fields[4]),
|
|
10013
10043
|
external_url: decodeOptionalText(fields[5]),
|
|
10014
10044
|
attributes: decodeAttributes(fields[6]),
|
|
10015
|
-
collection: decodeOptionalText(fields[7])
|
|
10045
|
+
collection: decodeOptionalText(fields[7]),
|
|
10046
|
+
collection_id: CborDeserializer.decodeNullable(fields[8], decodeHex)
|
|
10016
10047
|
};
|
|
10017
10048
|
}
|
|
10018
10049
|
var MEDIA_REF_DECODERS = /* @__PURE__ */ new Map([
|
|
@@ -10058,20 +10089,26 @@ function parseNftPayload(data) {
|
|
|
10058
10089
|
return null;
|
|
10059
10090
|
}
|
|
10060
10091
|
}
|
|
10061
|
-
function
|
|
10092
|
+
function isNftDocumentLink(content) {
|
|
10093
|
+
return content?.kind === "link" && content.media_type === NFT_DOCUMENT_MEDIA_TYPE;
|
|
10094
|
+
}
|
|
10095
|
+
function nftSignedDigest(context, payload) {
|
|
10062
10096
|
const preimage = CborSerializer.encodeArray(
|
|
10063
|
-
CborSerializer.
|
|
10064
|
-
|
|
10065
|
-
|
|
10097
|
+
CborSerializer.encodeByteString(SIGNED_DOMAIN),
|
|
10098
|
+
CborSerializer.encodeUnsignedInteger(NFT_FORMAT_VERSION),
|
|
10099
|
+
CborSerializer.encodeUnsignedInteger(NetworkId.fromId(context.networkId).id),
|
|
10100
|
+
CborSerializer.encodeByteString(context.recipientPredicate),
|
|
10101
|
+
CborSerializer.encodeByteString(context.tokenId),
|
|
10102
|
+
CborSerializer.encodeByteString(context.tokenType),
|
|
10066
10103
|
CborSerializer.encodeByteString(payload)
|
|
10067
10104
|
);
|
|
10068
10105
|
return new DataHasher(HashAlgorithm.SHA256).update(preimage).digest();
|
|
10069
10106
|
}
|
|
10070
|
-
async function verifyNftSignature(signed,
|
|
10107
|
+
async function verifyNftSignature(signed, context) {
|
|
10071
10108
|
try {
|
|
10072
10109
|
if (!SigningService.isPublicKeyValid(signed.creator)) return "invalid";
|
|
10073
10110
|
const signature = Signature.decode(signed.signature);
|
|
10074
|
-
const digest = await nftSignedDigest(
|
|
10111
|
+
const digest = await nftSignedDigest(context, signed.payload);
|
|
10075
10112
|
return await SigningService.verifyWithPublicKey(digest, signature, signed.creator) ? "valid" : "invalid";
|
|
10076
10113
|
} catch {
|
|
10077
10114
|
return "invalid";
|
|
@@ -10202,8 +10239,8 @@ function assertMintableData(data) {
|
|
|
10202
10239
|
function nftRecipientCbor(pubkey) {
|
|
10203
10240
|
return EncodedPredicate.fromPredicate(SignaturePredicate.create(pubkey)).toCBOR();
|
|
10204
10241
|
}
|
|
10205
|
-
async function signPayload(deps, payload,
|
|
10206
|
-
const digest = await nftSignedDigest(
|
|
10242
|
+
async function signPayload(deps, payload, context) {
|
|
10243
|
+
const digest = await nftSignedDigest(context, payload);
|
|
10207
10244
|
const signature = await deps.signingService.sign(digest);
|
|
10208
10245
|
return encodeNftSigned(deps.signingService.publicKey, payload, signature.encode());
|
|
10209
10246
|
}
|
|
@@ -10212,31 +10249,41 @@ async function planNftMint(deps, params) {
|
|
|
10212
10249
|
const tokenType = new TokenType(params.tokenType).bytes;
|
|
10213
10250
|
const salt = TokenSalt.generate();
|
|
10214
10251
|
const tokenId = await TokenId.fromSalt(deps.networkId, salt);
|
|
10215
|
-
const data = params.sign ? await signPayload(deps, payload,
|
|
10252
|
+
const data = params.sign ? await signPayload(deps, payload, {
|
|
10253
|
+
networkId: deps.networkId.id,
|
|
10254
|
+
recipientPredicate: nftRecipientCbor(params.recipientPubkey),
|
|
10255
|
+
tokenId: tokenId.bytes,
|
|
10256
|
+
tokenType
|
|
10257
|
+
}) : payload;
|
|
10216
10258
|
assertMintableData(data);
|
|
10217
10259
|
return { data, salt: salt.toBytes(), tokenType, tokenId: HexConverter.encode(tokenId.bytes) };
|
|
10218
10260
|
}
|
|
10219
|
-
async function verifyAgainst(signed,
|
|
10261
|
+
async function verifyAgainst(signed, genesisContext) {
|
|
10220
10262
|
try {
|
|
10221
|
-
return await verifyNftSignature(signed,
|
|
10263
|
+
return await verifyNftSignature(signed, genesisContext());
|
|
10222
10264
|
} catch {
|
|
10223
10265
|
return "invalid";
|
|
10224
10266
|
}
|
|
10225
10267
|
}
|
|
10226
|
-
async function readNftData(data,
|
|
10268
|
+
async function readNftData(data, genesisContext) {
|
|
10227
10269
|
const parsed = parseNftPayload(data);
|
|
10228
10270
|
if (parsed === null) return null;
|
|
10229
10271
|
if (parsed.signed === null) return { content: parsed.content, creator: null, signature: "unsigned" };
|
|
10230
10272
|
return {
|
|
10231
10273
|
content: parsed.content,
|
|
10232
10274
|
creator: HexConverter.encode(parsed.signed.creator),
|
|
10233
|
-
signature: await verifyAgainst(parsed.signed,
|
|
10275
|
+
signature: await verifyAgainst(parsed.signed, genesisContext)
|
|
10234
10276
|
};
|
|
10235
10277
|
}
|
|
10236
10278
|
async function readTokenNft(token) {
|
|
10237
10279
|
try {
|
|
10238
10280
|
const { genesis } = token.sdkToken;
|
|
10239
|
-
return await readNftData(genesis.data,
|
|
10281
|
+
return await readNftData(genesis.data, () => ({
|
|
10282
|
+
networkId: genesis.networkId.id,
|
|
10283
|
+
recipientPredicate: genesis.recipient.toCBOR(),
|
|
10284
|
+
tokenId: genesis.tokenId.bytes,
|
|
10285
|
+
tokenType: genesis.tokenType.bytes
|
|
10286
|
+
}));
|
|
10240
10287
|
} catch {
|
|
10241
10288
|
return null;
|
|
10242
10289
|
}
|