@chillwhales/lsp2 0.1.1
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/LICENSE +21 -0
- package/README.md +45 -0
- package/dist/index.d.mts +423 -0
- package/dist/index.d.ts +423 -0
- package/dist/index.mjs +188 -0
- package/package.json +58 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Chillwhales contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# @chillwhales/lsp2
|
|
2
|
+
|
|
3
|
+
[](./LICENSE)
|
|
4
|
+
|
|
5
|
+
LSP2 ERC725Y JSON Schema — shared primitives, VerifiableURI encoding/decoding, and image utilities for LUKSO dApps.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add @chillwhales/lsp2
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
> **Peer dependency:** This package requires [`viem`](https://viem.sh) ^2.0.0
|
|
14
|
+
>
|
|
15
|
+
> ```bash
|
|
16
|
+
> pnpm add viem
|
|
17
|
+
> ```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
import { encodeVerifiableUri, parseVerifiableUri } from "@chillwhales/lsp2";
|
|
23
|
+
|
|
24
|
+
// Encode metadata as a VerifiableURI for on-chain storage
|
|
25
|
+
const metadata = {
|
|
26
|
+
LSP4Metadata: { name: "My Token", description: "A LUKSO token" },
|
|
27
|
+
};
|
|
28
|
+
const encoded = encodeVerifiableUri(metadata, "ipfs://QmYwAPJz...");
|
|
29
|
+
// encoded is a hex string ready for setData()
|
|
30
|
+
|
|
31
|
+
// Later, read back from on-chain and parse the components
|
|
32
|
+
const { verificationMethod, verificationData, url } =
|
|
33
|
+
parseVerifiableUri(encoded);
|
|
34
|
+
console.log(url); // "ipfs://QmYwAPJz..."
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
> **Spec:** [LSP-2 ERC725Y JSON Schema](https://github.com/lukso-network/LIPs/blob/main/LSPs/LSP-2-ERC725YJSONSchema.md)
|
|
38
|
+
|
|
39
|
+
## API
|
|
40
|
+
|
|
41
|
+
Types are exported and available in your editor via TypeScript IntelliSense.
|
|
42
|
+
|
|
43
|
+
## License
|
|
44
|
+
|
|
45
|
+
[MIT](./LICENSE)
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { Hex } from 'viem';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* LSP2 ERC725Y JSON Schema Constants
|
|
6
|
+
*
|
|
7
|
+
* @see https://github.com/lukso-network/LIPs/blob/main/LSPs/LSP-2-ERC725YJSONSchema.md
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* LSP2 VerifiableURI verification methods
|
|
11
|
+
*/
|
|
12
|
+
declare enum VERIFICATION_METHODS {
|
|
13
|
+
HASH_KECCAK256_UTF8 = "keccak256(utf8)",
|
|
14
|
+
HASH_KECCAK256_BYTES = "keccak256(bytes)",
|
|
15
|
+
ECDSA = "ecdsa"
|
|
16
|
+
}
|
|
17
|
+
/** Separator used in LSP2 mapping keys */
|
|
18
|
+
declare const MAPPING_SEPARATOR = "0x0000";
|
|
19
|
+
/**
|
|
20
|
+
* Verification method ID for keccak256(bytes)
|
|
21
|
+
* Computed as: bytes4(keccak256('keccak256(bytes)')) = 0x8019f9b1
|
|
22
|
+
*/
|
|
23
|
+
declare const KECCAK256_BYTES_METHOD_ID: "0x8019f9b1";
|
|
24
|
+
/**
|
|
25
|
+
* Reserved bytes prefix (2 bytes of zeros) for VerifiableURI
|
|
26
|
+
*/
|
|
27
|
+
declare const RESERVED_PREFIX: "0x0000";
|
|
28
|
+
/**
|
|
29
|
+
* Length of a keccak256 hash in bytes (32 bytes = 0x0020)
|
|
30
|
+
*/
|
|
31
|
+
declare const HASH_LENGTH_PREFIX: "0x0020";
|
|
32
|
+
/**
|
|
33
|
+
* Minimum length of a valid VerifiableURI value in bytes
|
|
34
|
+
* 2 (reserved) + 4 (method ID) + 2 (hash length) + 32 (hash) = 40 bytes = 80 hex chars + '0x'
|
|
35
|
+
*/
|
|
36
|
+
declare const MIN_VERIFIABLE_URI_LENGTH = 82;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* LSP2 Shared Primitive Schemas
|
|
40
|
+
*
|
|
41
|
+
* Reusable Zod schemas for LSP2/LSP3/LSP4 metadata validation.
|
|
42
|
+
* These are the foundation schemas that downstream packages depend on.
|
|
43
|
+
*
|
|
44
|
+
* @see https://github.com/lukso-network/LIPs/blob/main/LSPs/LSP-2-ERC725YJSONSchema.md
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Validates an Ethereum address (0x... format)
|
|
49
|
+
*/
|
|
50
|
+
declare const addressSchema: z.ZodString;
|
|
51
|
+
/**
|
|
52
|
+
* Validates a 32-byte hex string (0x + 64 chars)
|
|
53
|
+
*/
|
|
54
|
+
declare const bytes32Schema: z.ZodString;
|
|
55
|
+
/**
|
|
56
|
+
* Validates any hex string (0x...)
|
|
57
|
+
*/
|
|
58
|
+
declare const bytesSchema: z.ZodString;
|
|
59
|
+
/**
|
|
60
|
+
* Verification data schema for LSP2 ERC725YJSONSchema
|
|
61
|
+
*/
|
|
62
|
+
declare const verificationSchema: z.ZodDiscriminatedUnion<"method", [z.ZodObject<{
|
|
63
|
+
data: z.ZodString;
|
|
64
|
+
method: z.ZodEnum<[VERIFICATION_METHODS.HASH_KECCAK256_BYTES, VERIFICATION_METHODS.HASH_KECCAK256_UTF8]>;
|
|
65
|
+
}, "strip", z.ZodTypeAny, {
|
|
66
|
+
method: VERIFICATION_METHODS.HASH_KECCAK256_UTF8 | VERIFICATION_METHODS.HASH_KECCAK256_BYTES;
|
|
67
|
+
data: string;
|
|
68
|
+
}, {
|
|
69
|
+
method: VERIFICATION_METHODS.HASH_KECCAK256_UTF8 | VERIFICATION_METHODS.HASH_KECCAK256_BYTES;
|
|
70
|
+
data: string;
|
|
71
|
+
}>, z.ZodObject<{
|
|
72
|
+
method: z.ZodEnum<[VERIFICATION_METHODS.ECDSA]>;
|
|
73
|
+
/** Signer address */
|
|
74
|
+
data: z.ZodString;
|
|
75
|
+
/** URL where the signature can be retrieved */
|
|
76
|
+
source: z.ZodString;
|
|
77
|
+
}, "strip", z.ZodTypeAny, {
|
|
78
|
+
method: VERIFICATION_METHODS.ECDSA;
|
|
79
|
+
data: string;
|
|
80
|
+
source: string;
|
|
81
|
+
}, {
|
|
82
|
+
method: VERIFICATION_METHODS.ECDSA;
|
|
83
|
+
data: string;
|
|
84
|
+
source: string;
|
|
85
|
+
}>]>;
|
|
86
|
+
/**
|
|
87
|
+
* Image metadata schema (LSP3/LSP4)
|
|
88
|
+
*/
|
|
89
|
+
declare const imageSchema: z.ZodObject<{
|
|
90
|
+
url: z.ZodString;
|
|
91
|
+
width: z.ZodNumber;
|
|
92
|
+
height: z.ZodNumber;
|
|
93
|
+
verification: z.ZodDiscriminatedUnion<"method", [z.ZodObject<{
|
|
94
|
+
data: z.ZodString;
|
|
95
|
+
method: z.ZodEnum<[VERIFICATION_METHODS.HASH_KECCAK256_BYTES, VERIFICATION_METHODS.HASH_KECCAK256_UTF8]>;
|
|
96
|
+
}, "strip", z.ZodTypeAny, {
|
|
97
|
+
method: VERIFICATION_METHODS.HASH_KECCAK256_UTF8 | VERIFICATION_METHODS.HASH_KECCAK256_BYTES;
|
|
98
|
+
data: string;
|
|
99
|
+
}, {
|
|
100
|
+
method: VERIFICATION_METHODS.HASH_KECCAK256_UTF8 | VERIFICATION_METHODS.HASH_KECCAK256_BYTES;
|
|
101
|
+
data: string;
|
|
102
|
+
}>, z.ZodObject<{
|
|
103
|
+
method: z.ZodEnum<[VERIFICATION_METHODS.ECDSA]>;
|
|
104
|
+
/** Signer address */
|
|
105
|
+
data: z.ZodString;
|
|
106
|
+
/** URL where the signature can be retrieved */
|
|
107
|
+
source: z.ZodString;
|
|
108
|
+
}, "strip", z.ZodTypeAny, {
|
|
109
|
+
method: VERIFICATION_METHODS.ECDSA;
|
|
110
|
+
data: string;
|
|
111
|
+
source: string;
|
|
112
|
+
}, {
|
|
113
|
+
method: VERIFICATION_METHODS.ECDSA;
|
|
114
|
+
data: string;
|
|
115
|
+
source: string;
|
|
116
|
+
}>]>;
|
|
117
|
+
}, "strip", z.ZodTypeAny, {
|
|
118
|
+
url: string;
|
|
119
|
+
width: number;
|
|
120
|
+
height: number;
|
|
121
|
+
verification: {
|
|
122
|
+
method: VERIFICATION_METHODS.HASH_KECCAK256_UTF8 | VERIFICATION_METHODS.HASH_KECCAK256_BYTES;
|
|
123
|
+
data: string;
|
|
124
|
+
} | {
|
|
125
|
+
method: VERIFICATION_METHODS.ECDSA;
|
|
126
|
+
data: string;
|
|
127
|
+
source: string;
|
|
128
|
+
};
|
|
129
|
+
}, {
|
|
130
|
+
url: string;
|
|
131
|
+
width: number;
|
|
132
|
+
height: number;
|
|
133
|
+
verification: {
|
|
134
|
+
method: VERIFICATION_METHODS.HASH_KECCAK256_UTF8 | VERIFICATION_METHODS.HASH_KECCAK256_BYTES;
|
|
135
|
+
data: string;
|
|
136
|
+
} | {
|
|
137
|
+
method: VERIFICATION_METHODS.ECDSA;
|
|
138
|
+
data: string;
|
|
139
|
+
source: string;
|
|
140
|
+
};
|
|
141
|
+
}>;
|
|
142
|
+
/**
|
|
143
|
+
* Asset metadata schema (LSP3/LSP4)
|
|
144
|
+
*/
|
|
145
|
+
declare const assetSchema: z.ZodObject<{
|
|
146
|
+
url: z.ZodString;
|
|
147
|
+
fileType: z.ZodString;
|
|
148
|
+
verification: z.ZodDiscriminatedUnion<"method", [z.ZodObject<{
|
|
149
|
+
data: z.ZodString;
|
|
150
|
+
method: z.ZodEnum<[VERIFICATION_METHODS.HASH_KECCAK256_BYTES, VERIFICATION_METHODS.HASH_KECCAK256_UTF8]>;
|
|
151
|
+
}, "strip", z.ZodTypeAny, {
|
|
152
|
+
method: VERIFICATION_METHODS.HASH_KECCAK256_UTF8 | VERIFICATION_METHODS.HASH_KECCAK256_BYTES;
|
|
153
|
+
data: string;
|
|
154
|
+
}, {
|
|
155
|
+
method: VERIFICATION_METHODS.HASH_KECCAK256_UTF8 | VERIFICATION_METHODS.HASH_KECCAK256_BYTES;
|
|
156
|
+
data: string;
|
|
157
|
+
}>, z.ZodObject<{
|
|
158
|
+
method: z.ZodEnum<[VERIFICATION_METHODS.ECDSA]>;
|
|
159
|
+
/** Signer address */
|
|
160
|
+
data: z.ZodString;
|
|
161
|
+
/** URL where the signature can be retrieved */
|
|
162
|
+
source: z.ZodString;
|
|
163
|
+
}, "strip", z.ZodTypeAny, {
|
|
164
|
+
method: VERIFICATION_METHODS.ECDSA;
|
|
165
|
+
data: string;
|
|
166
|
+
source: string;
|
|
167
|
+
}, {
|
|
168
|
+
method: VERIFICATION_METHODS.ECDSA;
|
|
169
|
+
data: string;
|
|
170
|
+
source: string;
|
|
171
|
+
}>]>;
|
|
172
|
+
}, "strip", z.ZodTypeAny, {
|
|
173
|
+
url: string;
|
|
174
|
+
verification: {
|
|
175
|
+
method: VERIFICATION_METHODS.HASH_KECCAK256_UTF8 | VERIFICATION_METHODS.HASH_KECCAK256_BYTES;
|
|
176
|
+
data: string;
|
|
177
|
+
} | {
|
|
178
|
+
method: VERIFICATION_METHODS.ECDSA;
|
|
179
|
+
data: string;
|
|
180
|
+
source: string;
|
|
181
|
+
};
|
|
182
|
+
fileType: string;
|
|
183
|
+
}, {
|
|
184
|
+
url: string;
|
|
185
|
+
verification: {
|
|
186
|
+
method: VERIFICATION_METHODS.HASH_KECCAK256_UTF8 | VERIFICATION_METHODS.HASH_KECCAK256_BYTES;
|
|
187
|
+
data: string;
|
|
188
|
+
} | {
|
|
189
|
+
method: VERIFICATION_METHODS.ECDSA;
|
|
190
|
+
data: string;
|
|
191
|
+
source: string;
|
|
192
|
+
};
|
|
193
|
+
fileType: string;
|
|
194
|
+
}>;
|
|
195
|
+
/**
|
|
196
|
+
* Link schema (LSP3/LSP4)
|
|
197
|
+
*/
|
|
198
|
+
declare const linkSchema: z.ZodObject<{
|
|
199
|
+
title: z.ZodString;
|
|
200
|
+
url: z.ZodString;
|
|
201
|
+
}, "strip", z.ZodTypeAny, {
|
|
202
|
+
url: string;
|
|
203
|
+
title: string;
|
|
204
|
+
}, {
|
|
205
|
+
url: string;
|
|
206
|
+
title: string;
|
|
207
|
+
}>;
|
|
208
|
+
/**
|
|
209
|
+
* Tag schema (LSP3/LSP4)
|
|
210
|
+
*/
|
|
211
|
+
declare const tagSchema: z.ZodString;
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* LSP2 Type Guards
|
|
215
|
+
*
|
|
216
|
+
* Runtime type guards using Zod schema validation.
|
|
217
|
+
*/
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Type guard for image schema
|
|
221
|
+
*/
|
|
222
|
+
declare function isImageSchema(obj: unknown): obj is z.infer<typeof imageSchema>;
|
|
223
|
+
/**
|
|
224
|
+
* Type guard for link schema
|
|
225
|
+
*/
|
|
226
|
+
declare function isLinkSchema(obj: unknown): obj is z.infer<typeof linkSchema>;
|
|
227
|
+
/**
|
|
228
|
+
* Type guard for tag schema
|
|
229
|
+
*/
|
|
230
|
+
declare function isTagSchema(obj: unknown): obj is z.infer<typeof tagSchema>;
|
|
231
|
+
/**
|
|
232
|
+
* Type guard for asset schema
|
|
233
|
+
*/
|
|
234
|
+
declare function isAssetSchema(obj: unknown): obj is z.infer<typeof assetSchema>;
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* LSP2 Inferred Types
|
|
238
|
+
*
|
|
239
|
+
* TypeScript types inferred from LSP2 Zod schemas.
|
|
240
|
+
*/
|
|
241
|
+
|
|
242
|
+
type Verification = z.infer<typeof verificationSchema>;
|
|
243
|
+
type Image = z.infer<typeof imageSchema>;
|
|
244
|
+
type ImagesArray = Image[];
|
|
245
|
+
type ImagesMatrix = ImagesArray[];
|
|
246
|
+
type Asset = z.infer<typeof assetSchema>;
|
|
247
|
+
type Link = z.infer<typeof linkSchema>;
|
|
248
|
+
type Tag = z.infer<typeof tagSchema>;
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* LSP2 Image Utilities
|
|
252
|
+
*
|
|
253
|
+
* Functions for finding optimal images from LSP metadata arrays.
|
|
254
|
+
* Used by downstream LSP3/LSP4 packages for profile and asset images.
|
|
255
|
+
*/
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Image size in pixels
|
|
259
|
+
*/
|
|
260
|
+
interface ImageSize {
|
|
261
|
+
/** Width in pixels */
|
|
262
|
+
width: number;
|
|
263
|
+
/** Height in pixels */
|
|
264
|
+
height: number;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Find the best matching image from an array based on target dimensions.
|
|
268
|
+
* If no dimensions provided, returns the first image.
|
|
269
|
+
*
|
|
270
|
+
* @param images - Array of images with url, width, height
|
|
271
|
+
* @param options - Optional target dimensions
|
|
272
|
+
* @returns The best matching image or undefined
|
|
273
|
+
*/
|
|
274
|
+
declare function findBestImage(images: Image[] | undefined, options?: Partial<ImageSize>): Image | undefined;
|
|
275
|
+
/**
|
|
276
|
+
* Finds the image closest to the target resolution
|
|
277
|
+
*
|
|
278
|
+
* Uses Euclidean distance in resolution space.
|
|
279
|
+
*
|
|
280
|
+
* @param images - Array of image objects
|
|
281
|
+
* @param targetWidth - Target width
|
|
282
|
+
* @param targetHeight - Target height
|
|
283
|
+
* @returns The closest image object or null if no images provided
|
|
284
|
+
*/
|
|
285
|
+
declare function findClosestImage(images: Image[], targetWidth: number, targetHeight: number): Image | null;
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* LSP2 VerifiableURI Encoding/Decoding Utilities
|
|
289
|
+
*
|
|
290
|
+
* Pure functions for encoding and decoding VerifiableURI values according to
|
|
291
|
+
* the LSP2 ERC725Y JSON Schema specification.
|
|
292
|
+
*
|
|
293
|
+
* @see https://github.com/lukso-network/LIPs/blob/main/LSPs/LSP-2-ERC725YJSONSchema.md
|
|
294
|
+
*/
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Parsed components of a VerifiableURI
|
|
298
|
+
*/
|
|
299
|
+
interface ParsedVerifiableUri {
|
|
300
|
+
/** Verification method ID (e.g., 0x6f357c6a for keccak256(bytes)) */
|
|
301
|
+
verificationMethod: Hex;
|
|
302
|
+
/** Verification hash (32 bytes) */
|
|
303
|
+
verificationData: Hex;
|
|
304
|
+
/** The URL pointing to the content (e.g., ipfs://Qm...) */
|
|
305
|
+
url: string;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Result of decoding a VerifiableURI
|
|
309
|
+
*/
|
|
310
|
+
interface DecodedVerifiableUri<T> {
|
|
311
|
+
/** The parsed and validated data */
|
|
312
|
+
data: T;
|
|
313
|
+
/** The URL extracted from the VerifiableURI */
|
|
314
|
+
url: string;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Encodes data as a VerifiableURI value
|
|
318
|
+
*
|
|
319
|
+
* Creates an ERC725Y-compatible VerifiableURI encoding with:
|
|
320
|
+
* - 2 bytes reserved (0x0000)
|
|
321
|
+
* - 4 bytes verification method ID (keccak256(bytes)) = 0x8019f9b1
|
|
322
|
+
* - 2 bytes verification data length (0x0020 = 32)
|
|
323
|
+
* - 32 bytes verification hash
|
|
324
|
+
* - UTF-8 encoded URL
|
|
325
|
+
*
|
|
326
|
+
* @param data - Any JSON-serializable object to encode
|
|
327
|
+
* @param ipfsUrl - IPFS URL where the JSON will be stored (e.g., "ipfs://Qm...")
|
|
328
|
+
* @returns Hex-encoded VerifiableURI value
|
|
329
|
+
*
|
|
330
|
+
* @example
|
|
331
|
+
* ```typescript
|
|
332
|
+
* import { encodeVerifiableUri } from '@chillwhales/lsp2';
|
|
333
|
+
*
|
|
334
|
+
* const metadata = { LSP4Metadata: { name: 'My Token', description: 'A token' } };
|
|
335
|
+
* const value = encodeVerifiableUri(metadata, 'ipfs://QmXyz...');
|
|
336
|
+
* // Use with setData operation
|
|
337
|
+
* ```
|
|
338
|
+
*/
|
|
339
|
+
declare function encodeVerifiableUri<T>(data: T, ipfsUrl: string): Hex;
|
|
340
|
+
/**
|
|
341
|
+
* Parses a VerifiableURI hex value into its components
|
|
342
|
+
*
|
|
343
|
+
* Format: 0x + reserved (2 bytes) + method (4 bytes) + length (2 bytes) + hash (N bytes) + url
|
|
344
|
+
*
|
|
345
|
+
* @param value - The raw hex value from ERC725Y getData
|
|
346
|
+
* @returns Parsed components (method, hash, url)
|
|
347
|
+
* @throws Error if the value is malformed
|
|
348
|
+
*
|
|
349
|
+
* @example
|
|
350
|
+
* ```typescript
|
|
351
|
+
* import { parseVerifiableUri } from '@chillwhales/lsp2';
|
|
352
|
+
*
|
|
353
|
+
* const { verificationMethod, verificationData, url } = parseVerifiableUri(rawValue);
|
|
354
|
+
* console.log(url); // 'ipfs://Qm...'
|
|
355
|
+
* ```
|
|
356
|
+
*/
|
|
357
|
+
declare function parseVerifiableUri(value: Hex): ParsedVerifiableUri;
|
|
358
|
+
/**
|
|
359
|
+
* Decodes a VerifiableURI value and validates the content
|
|
360
|
+
*
|
|
361
|
+
* @param verifiableUriValue - The raw hex value from ERC725Y getData
|
|
362
|
+
* @param jsonContent - The JSON content fetched from the URL
|
|
363
|
+
* @param schema - Optional Zod schema for type validation
|
|
364
|
+
* @returns Decoded data and URL
|
|
365
|
+
* @throws Error if hash doesn't match or schema validation fails
|
|
366
|
+
*
|
|
367
|
+
* @example
|
|
368
|
+
* ```typescript
|
|
369
|
+
* import { decodeVerifiableUri } from '@chillwhales/lsp2';
|
|
370
|
+
*
|
|
371
|
+
* // Fetch JSON from IPFS first
|
|
372
|
+
* const jsonContent = await fetchFromIpfs(url);
|
|
373
|
+
*
|
|
374
|
+
* // Decode with schema validation
|
|
375
|
+
* const { data, url } = decodeVerifiableUri(
|
|
376
|
+
* rawValue,
|
|
377
|
+
* jsonContent,
|
|
378
|
+
* mySchema
|
|
379
|
+
* );
|
|
380
|
+
*
|
|
381
|
+
* // Decode without schema (caller handles typing)
|
|
382
|
+
* const { data: rawData } = decodeVerifiableUri<MyType>(rawValue, jsonContent);
|
|
383
|
+
* ```
|
|
384
|
+
*/
|
|
385
|
+
declare function decodeVerifiableUri<T>(verifiableUriValue: Hex, jsonContent: string, schema?: z.ZodSchema<T>): DecodedVerifiableUri<T>;
|
|
386
|
+
/**
|
|
387
|
+
* Computes the verification hash for JSON data
|
|
388
|
+
*
|
|
389
|
+
* Useful when you need to compute the hash without encoding the full VerifiableURI.
|
|
390
|
+
*
|
|
391
|
+
* @param data - Any JSON-serializable object
|
|
392
|
+
* @returns The keccak256 hash as a Hex string
|
|
393
|
+
*
|
|
394
|
+
* @example
|
|
395
|
+
* ```typescript
|
|
396
|
+
* import { computeVerificationHash } from '@chillwhales/lsp2';
|
|
397
|
+
*
|
|
398
|
+
* const hash = computeVerificationHash({ name: 'My Profile' });
|
|
399
|
+
* // Use for verification or comparison
|
|
400
|
+
* ```
|
|
401
|
+
*/
|
|
402
|
+
declare function computeVerificationHash<T>(data: T): Hex;
|
|
403
|
+
/**
|
|
404
|
+
* Checks if a hex value appears to be a valid VerifiableURI format
|
|
405
|
+
*
|
|
406
|
+
* This is a quick check based on structure, not content validation.
|
|
407
|
+
*
|
|
408
|
+
* @param value - Hex value to check
|
|
409
|
+
* @returns true if the value has valid VerifiableURI structure
|
|
410
|
+
*
|
|
411
|
+
* @example
|
|
412
|
+
* ```typescript
|
|
413
|
+
* import { isVerifiableUri } from '@chillwhales/lsp2';
|
|
414
|
+
*
|
|
415
|
+
* if (isVerifiableUri(rawValue)) {
|
|
416
|
+
* const { url } = parseVerifiableUri(rawValue);
|
|
417
|
+
* }
|
|
418
|
+
* ```
|
|
419
|
+
*/
|
|
420
|
+
declare function isVerifiableUri(value: Hex): boolean;
|
|
421
|
+
|
|
422
|
+
export { HASH_LENGTH_PREFIX, KECCAK256_BYTES_METHOD_ID, MAPPING_SEPARATOR, MIN_VERIFIABLE_URI_LENGTH, RESERVED_PREFIX, VERIFICATION_METHODS, addressSchema, assetSchema, bytes32Schema, bytesSchema, computeVerificationHash, decodeVerifiableUri, encodeVerifiableUri, findBestImage, findClosestImage, imageSchema, isAssetSchema, isImageSchema, isLinkSchema, isTagSchema, isVerifiableUri, linkSchema, parseVerifiableUri, tagSchema, verificationSchema };
|
|
423
|
+
export type { Asset, DecodedVerifiableUri, Image, ImageSize, ImagesArray, ImagesMatrix, Link, ParsedVerifiableUri, Tag, Verification };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { Hex } from 'viem';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* LSP2 ERC725Y JSON Schema Constants
|
|
6
|
+
*
|
|
7
|
+
* @see https://github.com/lukso-network/LIPs/blob/main/LSPs/LSP-2-ERC725YJSONSchema.md
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* LSP2 VerifiableURI verification methods
|
|
11
|
+
*/
|
|
12
|
+
declare enum VERIFICATION_METHODS {
|
|
13
|
+
HASH_KECCAK256_UTF8 = "keccak256(utf8)",
|
|
14
|
+
HASH_KECCAK256_BYTES = "keccak256(bytes)",
|
|
15
|
+
ECDSA = "ecdsa"
|
|
16
|
+
}
|
|
17
|
+
/** Separator used in LSP2 mapping keys */
|
|
18
|
+
declare const MAPPING_SEPARATOR = "0x0000";
|
|
19
|
+
/**
|
|
20
|
+
* Verification method ID for keccak256(bytes)
|
|
21
|
+
* Computed as: bytes4(keccak256('keccak256(bytes)')) = 0x8019f9b1
|
|
22
|
+
*/
|
|
23
|
+
declare const KECCAK256_BYTES_METHOD_ID: "0x8019f9b1";
|
|
24
|
+
/**
|
|
25
|
+
* Reserved bytes prefix (2 bytes of zeros) for VerifiableURI
|
|
26
|
+
*/
|
|
27
|
+
declare const RESERVED_PREFIX: "0x0000";
|
|
28
|
+
/**
|
|
29
|
+
* Length of a keccak256 hash in bytes (32 bytes = 0x0020)
|
|
30
|
+
*/
|
|
31
|
+
declare const HASH_LENGTH_PREFIX: "0x0020";
|
|
32
|
+
/**
|
|
33
|
+
* Minimum length of a valid VerifiableURI value in bytes
|
|
34
|
+
* 2 (reserved) + 4 (method ID) + 2 (hash length) + 32 (hash) = 40 bytes = 80 hex chars + '0x'
|
|
35
|
+
*/
|
|
36
|
+
declare const MIN_VERIFIABLE_URI_LENGTH = 82;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* LSP2 Shared Primitive Schemas
|
|
40
|
+
*
|
|
41
|
+
* Reusable Zod schemas for LSP2/LSP3/LSP4 metadata validation.
|
|
42
|
+
* These are the foundation schemas that downstream packages depend on.
|
|
43
|
+
*
|
|
44
|
+
* @see https://github.com/lukso-network/LIPs/blob/main/LSPs/LSP-2-ERC725YJSONSchema.md
|
|
45
|
+
*/
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Validates an Ethereum address (0x... format)
|
|
49
|
+
*/
|
|
50
|
+
declare const addressSchema: z.ZodString;
|
|
51
|
+
/**
|
|
52
|
+
* Validates a 32-byte hex string (0x + 64 chars)
|
|
53
|
+
*/
|
|
54
|
+
declare const bytes32Schema: z.ZodString;
|
|
55
|
+
/**
|
|
56
|
+
* Validates any hex string (0x...)
|
|
57
|
+
*/
|
|
58
|
+
declare const bytesSchema: z.ZodString;
|
|
59
|
+
/**
|
|
60
|
+
* Verification data schema for LSP2 ERC725YJSONSchema
|
|
61
|
+
*/
|
|
62
|
+
declare const verificationSchema: z.ZodDiscriminatedUnion<"method", [z.ZodObject<{
|
|
63
|
+
data: z.ZodString;
|
|
64
|
+
method: z.ZodEnum<[VERIFICATION_METHODS.HASH_KECCAK256_BYTES, VERIFICATION_METHODS.HASH_KECCAK256_UTF8]>;
|
|
65
|
+
}, "strip", z.ZodTypeAny, {
|
|
66
|
+
method: VERIFICATION_METHODS.HASH_KECCAK256_UTF8 | VERIFICATION_METHODS.HASH_KECCAK256_BYTES;
|
|
67
|
+
data: string;
|
|
68
|
+
}, {
|
|
69
|
+
method: VERIFICATION_METHODS.HASH_KECCAK256_UTF8 | VERIFICATION_METHODS.HASH_KECCAK256_BYTES;
|
|
70
|
+
data: string;
|
|
71
|
+
}>, z.ZodObject<{
|
|
72
|
+
method: z.ZodEnum<[VERIFICATION_METHODS.ECDSA]>;
|
|
73
|
+
/** Signer address */
|
|
74
|
+
data: z.ZodString;
|
|
75
|
+
/** URL where the signature can be retrieved */
|
|
76
|
+
source: z.ZodString;
|
|
77
|
+
}, "strip", z.ZodTypeAny, {
|
|
78
|
+
method: VERIFICATION_METHODS.ECDSA;
|
|
79
|
+
data: string;
|
|
80
|
+
source: string;
|
|
81
|
+
}, {
|
|
82
|
+
method: VERIFICATION_METHODS.ECDSA;
|
|
83
|
+
data: string;
|
|
84
|
+
source: string;
|
|
85
|
+
}>]>;
|
|
86
|
+
/**
|
|
87
|
+
* Image metadata schema (LSP3/LSP4)
|
|
88
|
+
*/
|
|
89
|
+
declare const imageSchema: z.ZodObject<{
|
|
90
|
+
url: z.ZodString;
|
|
91
|
+
width: z.ZodNumber;
|
|
92
|
+
height: z.ZodNumber;
|
|
93
|
+
verification: z.ZodDiscriminatedUnion<"method", [z.ZodObject<{
|
|
94
|
+
data: z.ZodString;
|
|
95
|
+
method: z.ZodEnum<[VERIFICATION_METHODS.HASH_KECCAK256_BYTES, VERIFICATION_METHODS.HASH_KECCAK256_UTF8]>;
|
|
96
|
+
}, "strip", z.ZodTypeAny, {
|
|
97
|
+
method: VERIFICATION_METHODS.HASH_KECCAK256_UTF8 | VERIFICATION_METHODS.HASH_KECCAK256_BYTES;
|
|
98
|
+
data: string;
|
|
99
|
+
}, {
|
|
100
|
+
method: VERIFICATION_METHODS.HASH_KECCAK256_UTF8 | VERIFICATION_METHODS.HASH_KECCAK256_BYTES;
|
|
101
|
+
data: string;
|
|
102
|
+
}>, z.ZodObject<{
|
|
103
|
+
method: z.ZodEnum<[VERIFICATION_METHODS.ECDSA]>;
|
|
104
|
+
/** Signer address */
|
|
105
|
+
data: z.ZodString;
|
|
106
|
+
/** URL where the signature can be retrieved */
|
|
107
|
+
source: z.ZodString;
|
|
108
|
+
}, "strip", z.ZodTypeAny, {
|
|
109
|
+
method: VERIFICATION_METHODS.ECDSA;
|
|
110
|
+
data: string;
|
|
111
|
+
source: string;
|
|
112
|
+
}, {
|
|
113
|
+
method: VERIFICATION_METHODS.ECDSA;
|
|
114
|
+
data: string;
|
|
115
|
+
source: string;
|
|
116
|
+
}>]>;
|
|
117
|
+
}, "strip", z.ZodTypeAny, {
|
|
118
|
+
url: string;
|
|
119
|
+
width: number;
|
|
120
|
+
height: number;
|
|
121
|
+
verification: {
|
|
122
|
+
method: VERIFICATION_METHODS.HASH_KECCAK256_UTF8 | VERIFICATION_METHODS.HASH_KECCAK256_BYTES;
|
|
123
|
+
data: string;
|
|
124
|
+
} | {
|
|
125
|
+
method: VERIFICATION_METHODS.ECDSA;
|
|
126
|
+
data: string;
|
|
127
|
+
source: string;
|
|
128
|
+
};
|
|
129
|
+
}, {
|
|
130
|
+
url: string;
|
|
131
|
+
width: number;
|
|
132
|
+
height: number;
|
|
133
|
+
verification: {
|
|
134
|
+
method: VERIFICATION_METHODS.HASH_KECCAK256_UTF8 | VERIFICATION_METHODS.HASH_KECCAK256_BYTES;
|
|
135
|
+
data: string;
|
|
136
|
+
} | {
|
|
137
|
+
method: VERIFICATION_METHODS.ECDSA;
|
|
138
|
+
data: string;
|
|
139
|
+
source: string;
|
|
140
|
+
};
|
|
141
|
+
}>;
|
|
142
|
+
/**
|
|
143
|
+
* Asset metadata schema (LSP3/LSP4)
|
|
144
|
+
*/
|
|
145
|
+
declare const assetSchema: z.ZodObject<{
|
|
146
|
+
url: z.ZodString;
|
|
147
|
+
fileType: z.ZodString;
|
|
148
|
+
verification: z.ZodDiscriminatedUnion<"method", [z.ZodObject<{
|
|
149
|
+
data: z.ZodString;
|
|
150
|
+
method: z.ZodEnum<[VERIFICATION_METHODS.HASH_KECCAK256_BYTES, VERIFICATION_METHODS.HASH_KECCAK256_UTF8]>;
|
|
151
|
+
}, "strip", z.ZodTypeAny, {
|
|
152
|
+
method: VERIFICATION_METHODS.HASH_KECCAK256_UTF8 | VERIFICATION_METHODS.HASH_KECCAK256_BYTES;
|
|
153
|
+
data: string;
|
|
154
|
+
}, {
|
|
155
|
+
method: VERIFICATION_METHODS.HASH_KECCAK256_UTF8 | VERIFICATION_METHODS.HASH_KECCAK256_BYTES;
|
|
156
|
+
data: string;
|
|
157
|
+
}>, z.ZodObject<{
|
|
158
|
+
method: z.ZodEnum<[VERIFICATION_METHODS.ECDSA]>;
|
|
159
|
+
/** Signer address */
|
|
160
|
+
data: z.ZodString;
|
|
161
|
+
/** URL where the signature can be retrieved */
|
|
162
|
+
source: z.ZodString;
|
|
163
|
+
}, "strip", z.ZodTypeAny, {
|
|
164
|
+
method: VERIFICATION_METHODS.ECDSA;
|
|
165
|
+
data: string;
|
|
166
|
+
source: string;
|
|
167
|
+
}, {
|
|
168
|
+
method: VERIFICATION_METHODS.ECDSA;
|
|
169
|
+
data: string;
|
|
170
|
+
source: string;
|
|
171
|
+
}>]>;
|
|
172
|
+
}, "strip", z.ZodTypeAny, {
|
|
173
|
+
url: string;
|
|
174
|
+
verification: {
|
|
175
|
+
method: VERIFICATION_METHODS.HASH_KECCAK256_UTF8 | VERIFICATION_METHODS.HASH_KECCAK256_BYTES;
|
|
176
|
+
data: string;
|
|
177
|
+
} | {
|
|
178
|
+
method: VERIFICATION_METHODS.ECDSA;
|
|
179
|
+
data: string;
|
|
180
|
+
source: string;
|
|
181
|
+
};
|
|
182
|
+
fileType: string;
|
|
183
|
+
}, {
|
|
184
|
+
url: string;
|
|
185
|
+
verification: {
|
|
186
|
+
method: VERIFICATION_METHODS.HASH_KECCAK256_UTF8 | VERIFICATION_METHODS.HASH_KECCAK256_BYTES;
|
|
187
|
+
data: string;
|
|
188
|
+
} | {
|
|
189
|
+
method: VERIFICATION_METHODS.ECDSA;
|
|
190
|
+
data: string;
|
|
191
|
+
source: string;
|
|
192
|
+
};
|
|
193
|
+
fileType: string;
|
|
194
|
+
}>;
|
|
195
|
+
/**
|
|
196
|
+
* Link schema (LSP3/LSP4)
|
|
197
|
+
*/
|
|
198
|
+
declare const linkSchema: z.ZodObject<{
|
|
199
|
+
title: z.ZodString;
|
|
200
|
+
url: z.ZodString;
|
|
201
|
+
}, "strip", z.ZodTypeAny, {
|
|
202
|
+
url: string;
|
|
203
|
+
title: string;
|
|
204
|
+
}, {
|
|
205
|
+
url: string;
|
|
206
|
+
title: string;
|
|
207
|
+
}>;
|
|
208
|
+
/**
|
|
209
|
+
* Tag schema (LSP3/LSP4)
|
|
210
|
+
*/
|
|
211
|
+
declare const tagSchema: z.ZodString;
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* LSP2 Type Guards
|
|
215
|
+
*
|
|
216
|
+
* Runtime type guards using Zod schema validation.
|
|
217
|
+
*/
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Type guard for image schema
|
|
221
|
+
*/
|
|
222
|
+
declare function isImageSchema(obj: unknown): obj is z.infer<typeof imageSchema>;
|
|
223
|
+
/**
|
|
224
|
+
* Type guard for link schema
|
|
225
|
+
*/
|
|
226
|
+
declare function isLinkSchema(obj: unknown): obj is z.infer<typeof linkSchema>;
|
|
227
|
+
/**
|
|
228
|
+
* Type guard for tag schema
|
|
229
|
+
*/
|
|
230
|
+
declare function isTagSchema(obj: unknown): obj is z.infer<typeof tagSchema>;
|
|
231
|
+
/**
|
|
232
|
+
* Type guard for asset schema
|
|
233
|
+
*/
|
|
234
|
+
declare function isAssetSchema(obj: unknown): obj is z.infer<typeof assetSchema>;
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* LSP2 Inferred Types
|
|
238
|
+
*
|
|
239
|
+
* TypeScript types inferred from LSP2 Zod schemas.
|
|
240
|
+
*/
|
|
241
|
+
|
|
242
|
+
type Verification = z.infer<typeof verificationSchema>;
|
|
243
|
+
type Image = z.infer<typeof imageSchema>;
|
|
244
|
+
type ImagesArray = Image[];
|
|
245
|
+
type ImagesMatrix = ImagesArray[];
|
|
246
|
+
type Asset = z.infer<typeof assetSchema>;
|
|
247
|
+
type Link = z.infer<typeof linkSchema>;
|
|
248
|
+
type Tag = z.infer<typeof tagSchema>;
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* LSP2 Image Utilities
|
|
252
|
+
*
|
|
253
|
+
* Functions for finding optimal images from LSP metadata arrays.
|
|
254
|
+
* Used by downstream LSP3/LSP4 packages for profile and asset images.
|
|
255
|
+
*/
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Image size in pixels
|
|
259
|
+
*/
|
|
260
|
+
interface ImageSize {
|
|
261
|
+
/** Width in pixels */
|
|
262
|
+
width: number;
|
|
263
|
+
/** Height in pixels */
|
|
264
|
+
height: number;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Find the best matching image from an array based on target dimensions.
|
|
268
|
+
* If no dimensions provided, returns the first image.
|
|
269
|
+
*
|
|
270
|
+
* @param images - Array of images with url, width, height
|
|
271
|
+
* @param options - Optional target dimensions
|
|
272
|
+
* @returns The best matching image or undefined
|
|
273
|
+
*/
|
|
274
|
+
declare function findBestImage(images: Image[] | undefined, options?: Partial<ImageSize>): Image | undefined;
|
|
275
|
+
/**
|
|
276
|
+
* Finds the image closest to the target resolution
|
|
277
|
+
*
|
|
278
|
+
* Uses Euclidean distance in resolution space.
|
|
279
|
+
*
|
|
280
|
+
* @param images - Array of image objects
|
|
281
|
+
* @param targetWidth - Target width
|
|
282
|
+
* @param targetHeight - Target height
|
|
283
|
+
* @returns The closest image object or null if no images provided
|
|
284
|
+
*/
|
|
285
|
+
declare function findClosestImage(images: Image[], targetWidth: number, targetHeight: number): Image | null;
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* LSP2 VerifiableURI Encoding/Decoding Utilities
|
|
289
|
+
*
|
|
290
|
+
* Pure functions for encoding and decoding VerifiableURI values according to
|
|
291
|
+
* the LSP2 ERC725Y JSON Schema specification.
|
|
292
|
+
*
|
|
293
|
+
* @see https://github.com/lukso-network/LIPs/blob/main/LSPs/LSP-2-ERC725YJSONSchema.md
|
|
294
|
+
*/
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Parsed components of a VerifiableURI
|
|
298
|
+
*/
|
|
299
|
+
interface ParsedVerifiableUri {
|
|
300
|
+
/** Verification method ID (e.g., 0x6f357c6a for keccak256(bytes)) */
|
|
301
|
+
verificationMethod: Hex;
|
|
302
|
+
/** Verification hash (32 bytes) */
|
|
303
|
+
verificationData: Hex;
|
|
304
|
+
/** The URL pointing to the content (e.g., ipfs://Qm...) */
|
|
305
|
+
url: string;
|
|
306
|
+
}
|
|
307
|
+
/**
|
|
308
|
+
* Result of decoding a VerifiableURI
|
|
309
|
+
*/
|
|
310
|
+
interface DecodedVerifiableUri<T> {
|
|
311
|
+
/** The parsed and validated data */
|
|
312
|
+
data: T;
|
|
313
|
+
/** The URL extracted from the VerifiableURI */
|
|
314
|
+
url: string;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Encodes data as a VerifiableURI value
|
|
318
|
+
*
|
|
319
|
+
* Creates an ERC725Y-compatible VerifiableURI encoding with:
|
|
320
|
+
* - 2 bytes reserved (0x0000)
|
|
321
|
+
* - 4 bytes verification method ID (keccak256(bytes)) = 0x8019f9b1
|
|
322
|
+
* - 2 bytes verification data length (0x0020 = 32)
|
|
323
|
+
* - 32 bytes verification hash
|
|
324
|
+
* - UTF-8 encoded URL
|
|
325
|
+
*
|
|
326
|
+
* @param data - Any JSON-serializable object to encode
|
|
327
|
+
* @param ipfsUrl - IPFS URL where the JSON will be stored (e.g., "ipfs://Qm...")
|
|
328
|
+
* @returns Hex-encoded VerifiableURI value
|
|
329
|
+
*
|
|
330
|
+
* @example
|
|
331
|
+
* ```typescript
|
|
332
|
+
* import { encodeVerifiableUri } from '@chillwhales/lsp2';
|
|
333
|
+
*
|
|
334
|
+
* const metadata = { LSP4Metadata: { name: 'My Token', description: 'A token' } };
|
|
335
|
+
* const value = encodeVerifiableUri(metadata, 'ipfs://QmXyz...');
|
|
336
|
+
* // Use with setData operation
|
|
337
|
+
* ```
|
|
338
|
+
*/
|
|
339
|
+
declare function encodeVerifiableUri<T>(data: T, ipfsUrl: string): Hex;
|
|
340
|
+
/**
|
|
341
|
+
* Parses a VerifiableURI hex value into its components
|
|
342
|
+
*
|
|
343
|
+
* Format: 0x + reserved (2 bytes) + method (4 bytes) + length (2 bytes) + hash (N bytes) + url
|
|
344
|
+
*
|
|
345
|
+
* @param value - The raw hex value from ERC725Y getData
|
|
346
|
+
* @returns Parsed components (method, hash, url)
|
|
347
|
+
* @throws Error if the value is malformed
|
|
348
|
+
*
|
|
349
|
+
* @example
|
|
350
|
+
* ```typescript
|
|
351
|
+
* import { parseVerifiableUri } from '@chillwhales/lsp2';
|
|
352
|
+
*
|
|
353
|
+
* const { verificationMethod, verificationData, url } = parseVerifiableUri(rawValue);
|
|
354
|
+
* console.log(url); // 'ipfs://Qm...'
|
|
355
|
+
* ```
|
|
356
|
+
*/
|
|
357
|
+
declare function parseVerifiableUri(value: Hex): ParsedVerifiableUri;
|
|
358
|
+
/**
|
|
359
|
+
* Decodes a VerifiableURI value and validates the content
|
|
360
|
+
*
|
|
361
|
+
* @param verifiableUriValue - The raw hex value from ERC725Y getData
|
|
362
|
+
* @param jsonContent - The JSON content fetched from the URL
|
|
363
|
+
* @param schema - Optional Zod schema for type validation
|
|
364
|
+
* @returns Decoded data and URL
|
|
365
|
+
* @throws Error if hash doesn't match or schema validation fails
|
|
366
|
+
*
|
|
367
|
+
* @example
|
|
368
|
+
* ```typescript
|
|
369
|
+
* import { decodeVerifiableUri } from '@chillwhales/lsp2';
|
|
370
|
+
*
|
|
371
|
+
* // Fetch JSON from IPFS first
|
|
372
|
+
* const jsonContent = await fetchFromIpfs(url);
|
|
373
|
+
*
|
|
374
|
+
* // Decode with schema validation
|
|
375
|
+
* const { data, url } = decodeVerifiableUri(
|
|
376
|
+
* rawValue,
|
|
377
|
+
* jsonContent,
|
|
378
|
+
* mySchema
|
|
379
|
+
* );
|
|
380
|
+
*
|
|
381
|
+
* // Decode without schema (caller handles typing)
|
|
382
|
+
* const { data: rawData } = decodeVerifiableUri<MyType>(rawValue, jsonContent);
|
|
383
|
+
* ```
|
|
384
|
+
*/
|
|
385
|
+
declare function decodeVerifiableUri<T>(verifiableUriValue: Hex, jsonContent: string, schema?: z.ZodSchema<T>): DecodedVerifiableUri<T>;
|
|
386
|
+
/**
|
|
387
|
+
* Computes the verification hash for JSON data
|
|
388
|
+
*
|
|
389
|
+
* Useful when you need to compute the hash without encoding the full VerifiableURI.
|
|
390
|
+
*
|
|
391
|
+
* @param data - Any JSON-serializable object
|
|
392
|
+
* @returns The keccak256 hash as a Hex string
|
|
393
|
+
*
|
|
394
|
+
* @example
|
|
395
|
+
* ```typescript
|
|
396
|
+
* import { computeVerificationHash } from '@chillwhales/lsp2';
|
|
397
|
+
*
|
|
398
|
+
* const hash = computeVerificationHash({ name: 'My Profile' });
|
|
399
|
+
* // Use for verification or comparison
|
|
400
|
+
* ```
|
|
401
|
+
*/
|
|
402
|
+
declare function computeVerificationHash<T>(data: T): Hex;
|
|
403
|
+
/**
|
|
404
|
+
* Checks if a hex value appears to be a valid VerifiableURI format
|
|
405
|
+
*
|
|
406
|
+
* This is a quick check based on structure, not content validation.
|
|
407
|
+
*
|
|
408
|
+
* @param value - Hex value to check
|
|
409
|
+
* @returns true if the value has valid VerifiableURI structure
|
|
410
|
+
*
|
|
411
|
+
* @example
|
|
412
|
+
* ```typescript
|
|
413
|
+
* import { isVerifiableUri } from '@chillwhales/lsp2';
|
|
414
|
+
*
|
|
415
|
+
* if (isVerifiableUri(rawValue)) {
|
|
416
|
+
* const { url } = parseVerifiableUri(rawValue);
|
|
417
|
+
* }
|
|
418
|
+
* ```
|
|
419
|
+
*/
|
|
420
|
+
declare function isVerifiableUri(value: Hex): boolean;
|
|
421
|
+
|
|
422
|
+
export { HASH_LENGTH_PREFIX, KECCAK256_BYTES_METHOD_ID, MAPPING_SEPARATOR, MIN_VERIFIABLE_URI_LENGTH, RESERVED_PREFIX, VERIFICATION_METHODS, addressSchema, assetSchema, bytes32Schema, bytesSchema, computeVerificationHash, decodeVerifiableUri, encodeVerifiableUri, findBestImage, findClosestImage, imageSchema, isAssetSchema, isImageSchema, isLinkSchema, isTagSchema, isVerifiableUri, linkSchema, parseVerifiableUri, tagSchema, verificationSchema };
|
|
423
|
+
export type { Asset, DecodedVerifiableUri, Image, ImageSize, ImagesArray, ImagesMatrix, Link, ParsedVerifiableUri, Tag, Verification };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { keccak256, stringToHex, concat, slice, hexToString } from 'viem';
|
|
3
|
+
|
|
4
|
+
var VERIFICATION_METHODS = /* @__PURE__ */ ((VERIFICATION_METHODS2) => {
|
|
5
|
+
VERIFICATION_METHODS2["HASH_KECCAK256_UTF8"] = "keccak256(utf8)";
|
|
6
|
+
VERIFICATION_METHODS2["HASH_KECCAK256_BYTES"] = "keccak256(bytes)";
|
|
7
|
+
VERIFICATION_METHODS2["ECDSA"] = "ecdsa";
|
|
8
|
+
return VERIFICATION_METHODS2;
|
|
9
|
+
})(VERIFICATION_METHODS || {});
|
|
10
|
+
const MAPPING_SEPARATOR = "0x0000";
|
|
11
|
+
const KECCAK256_BYTES_METHOD_ID = "0x8019f9b1";
|
|
12
|
+
const RESERVED_PREFIX = "0x0000";
|
|
13
|
+
const HASH_LENGTH_PREFIX = "0x0020";
|
|
14
|
+
const MIN_VERIFIABLE_URI_LENGTH = 82;
|
|
15
|
+
|
|
16
|
+
const EVM_ADDRESS_REGEX = /^0x[0-9a-fA-F]{40}$/;
|
|
17
|
+
const BYTES32_REGEX = /^0x[0-9a-fA-F]{64}$/;
|
|
18
|
+
const BYTES_REGEX = /^0x[0-9a-fA-F]*$/;
|
|
19
|
+
const addressSchema = z.string({
|
|
20
|
+
invalid_type_error: "Invalid value, not a string"
|
|
21
|
+
}).regex(EVM_ADDRESS_REGEX, "Invalid value, not an Address");
|
|
22
|
+
const bytes32Schema = z.string({
|
|
23
|
+
invalid_type_error: "Invalid value, not a string"
|
|
24
|
+
}).regex(BYTES32_REGEX, "Invalid value, not 32 bytes hex");
|
|
25
|
+
const bytesSchema = z.string({
|
|
26
|
+
invalid_type_error: "Invalid value, not a string"
|
|
27
|
+
}).regex(BYTES_REGEX, "Invalid value, not hex");
|
|
28
|
+
const verificationSchema = z.discriminatedUnion("method", [
|
|
29
|
+
z.object({
|
|
30
|
+
data: bytes32Schema,
|
|
31
|
+
method: z.enum([
|
|
32
|
+
VERIFICATION_METHODS.HASH_KECCAK256_BYTES,
|
|
33
|
+
VERIFICATION_METHODS.HASH_KECCAK256_UTF8
|
|
34
|
+
])
|
|
35
|
+
}),
|
|
36
|
+
z.object({
|
|
37
|
+
method: z.enum([VERIFICATION_METHODS.ECDSA]),
|
|
38
|
+
/** Signer address */
|
|
39
|
+
data: addressSchema,
|
|
40
|
+
/** URL where the signature can be retrieved */
|
|
41
|
+
source: z.string().url("Invalid value, not a URL")
|
|
42
|
+
})
|
|
43
|
+
]);
|
|
44
|
+
const imageSchema = z.object({
|
|
45
|
+
url: z.string({ invalid_type_error: "Invalid value, not a string" }).url("Invalid value, not a URL"),
|
|
46
|
+
width: z.number({ invalid_type_error: "Invalid value, not a number" }),
|
|
47
|
+
height: z.number({ invalid_type_error: "Invalid value, not a number" }),
|
|
48
|
+
verification: verificationSchema
|
|
49
|
+
});
|
|
50
|
+
const assetSchema = z.object({
|
|
51
|
+
url: z.string({ invalid_type_error: "Invalid value, not a string" }).url("Invalid value, not a URL"),
|
|
52
|
+
fileType: z.string({ invalid_type_error: "Invalid value, not a string" }),
|
|
53
|
+
verification: verificationSchema
|
|
54
|
+
});
|
|
55
|
+
const linkSchema = z.object({
|
|
56
|
+
title: z.string({ invalid_type_error: "Invalid value, not a string" }),
|
|
57
|
+
url: z.string({ invalid_type_error: "Invalid value, not a string" }).url("Invalid value, not a URL")
|
|
58
|
+
});
|
|
59
|
+
const tagSchema = z.string({
|
|
60
|
+
invalid_type_error: "Invalid value, not a string"
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
function isImageSchema(obj) {
|
|
64
|
+
const { success } = imageSchema.safeParse(obj);
|
|
65
|
+
return success;
|
|
66
|
+
}
|
|
67
|
+
function isLinkSchema(obj) {
|
|
68
|
+
const { success } = linkSchema.safeParse(obj);
|
|
69
|
+
return success;
|
|
70
|
+
}
|
|
71
|
+
function isTagSchema(obj) {
|
|
72
|
+
const { success } = tagSchema.safeParse(obj);
|
|
73
|
+
return success;
|
|
74
|
+
}
|
|
75
|
+
function isAssetSchema(obj) {
|
|
76
|
+
const { success } = assetSchema.safeParse(obj);
|
|
77
|
+
return success;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function findBestImage(images, options) {
|
|
81
|
+
if (!images || images.length === 0) {
|
|
82
|
+
return void 0;
|
|
83
|
+
}
|
|
84
|
+
if (options?.width != null && options?.height != null) {
|
|
85
|
+
return findClosestImage(images, options.width, options.height) ?? void 0;
|
|
86
|
+
}
|
|
87
|
+
return images[0];
|
|
88
|
+
}
|
|
89
|
+
function findClosestImage(images, targetWidth, targetHeight) {
|
|
90
|
+
if (!images || images.length === 0) {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
const calculateDistance = (width, height) => {
|
|
94
|
+
return Math.sqrt((width - targetWidth) ** 2 + (height - targetHeight) ** 2);
|
|
95
|
+
};
|
|
96
|
+
let closestImage = null;
|
|
97
|
+
let minDistance = Infinity;
|
|
98
|
+
for (const image of images) {
|
|
99
|
+
const distance = calculateDistance(image.width, image.height);
|
|
100
|
+
if (distance < minDistance) {
|
|
101
|
+
minDistance = distance;
|
|
102
|
+
closestImage = image;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return closestImage;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function encodeVerifiableUri(data, ipfsUrl) {
|
|
109
|
+
const jsonString = JSON.stringify(data);
|
|
110
|
+
const verificationHash = keccak256(stringToHex(jsonString));
|
|
111
|
+
const urlHex = stringToHex(ipfsUrl);
|
|
112
|
+
return concat([
|
|
113
|
+
RESERVED_PREFIX,
|
|
114
|
+
KECCAK256_BYTES_METHOD_ID,
|
|
115
|
+
HASH_LENGTH_PREFIX,
|
|
116
|
+
verificationHash,
|
|
117
|
+
urlHex
|
|
118
|
+
]);
|
|
119
|
+
}
|
|
120
|
+
function parseVerifiableUri(value) {
|
|
121
|
+
if (value.length < MIN_VERIFIABLE_URI_LENGTH) {
|
|
122
|
+
throw new Error(
|
|
123
|
+
`Invalid VerifiableURI: value too short (${value.length} chars, minimum ${MIN_VERIFIABLE_URI_LENGTH})`
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
const reservedPrefix = slice(value, 0, 2);
|
|
127
|
+
if (reservedPrefix !== RESERVED_PREFIX) {
|
|
128
|
+
throw new Error(
|
|
129
|
+
`Invalid VerifiableURI: expected reserved prefix ${RESERVED_PREFIX}, got ${reservedPrefix}`
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
const verificationMethod = slice(value, 2, 6);
|
|
133
|
+
const hashLengthHex = slice(value, 6, 8);
|
|
134
|
+
const hashLength = parseInt(hashLengthHex.slice(2), 16);
|
|
135
|
+
const verificationData = slice(value, 8, 8 + hashLength);
|
|
136
|
+
const urlHex = slice(value, 8 + hashLength);
|
|
137
|
+
const url = hexToString(urlHex);
|
|
138
|
+
return {
|
|
139
|
+
verificationMethod,
|
|
140
|
+
verificationData,
|
|
141
|
+
url
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function decodeVerifiableUri(verifiableUriValue, jsonContent, schema) {
|
|
145
|
+
const { verificationMethod, verificationData, url } = parseVerifiableUri(verifiableUriValue);
|
|
146
|
+
if (verificationMethod !== KECCAK256_BYTES_METHOD_ID) {
|
|
147
|
+
throw new Error(
|
|
148
|
+
`Unsupported verification method: ${verificationMethod}. Expected ${KECCAK256_BYTES_METHOD_ID} (keccak256(bytes))`
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
const computedHash = keccak256(stringToHex(jsonContent));
|
|
152
|
+
if (computedHash.toLowerCase() !== verificationData.toLowerCase()) {
|
|
153
|
+
throw new Error(
|
|
154
|
+
`VerifiableURI hash mismatch: content hash ${computedHash} does not match verification data ${verificationData}`
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
let parsed;
|
|
158
|
+
try {
|
|
159
|
+
parsed = JSON.parse(jsonContent);
|
|
160
|
+
} catch {
|
|
161
|
+
throw new Error("Invalid JSON content");
|
|
162
|
+
}
|
|
163
|
+
if (schema) {
|
|
164
|
+
const result = schema.safeParse(parsed);
|
|
165
|
+
if (!result.success) {
|
|
166
|
+
throw new Error(`Schema validation failed: ${result.error.message}`);
|
|
167
|
+
}
|
|
168
|
+
return { data: result.data, url };
|
|
169
|
+
}
|
|
170
|
+
return { data: parsed, url };
|
|
171
|
+
}
|
|
172
|
+
function computeVerificationHash(data) {
|
|
173
|
+
const jsonString = JSON.stringify(data);
|
|
174
|
+
return keccak256(stringToHex(jsonString));
|
|
175
|
+
}
|
|
176
|
+
function isVerifiableUri(value) {
|
|
177
|
+
if (value.length < MIN_VERIFIABLE_URI_LENGTH) {
|
|
178
|
+
return false;
|
|
179
|
+
}
|
|
180
|
+
try {
|
|
181
|
+
const reservedPrefix = slice(value, 0, 2);
|
|
182
|
+
return reservedPrefix === RESERVED_PREFIX;
|
|
183
|
+
} catch {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export { HASH_LENGTH_PREFIX, KECCAK256_BYTES_METHOD_ID, MAPPING_SEPARATOR, MIN_VERIFIABLE_URI_LENGTH, RESERVED_PREFIX, VERIFICATION_METHODS, addressSchema, assetSchema, bytes32Schema, bytesSchema, computeVerificationHash, decodeVerifiableUri, encodeVerifiableUri, findBestImage, findClosestImage, imageSchema, isAssetSchema, isImageSchema, isLinkSchema, isTagSchema, isVerifiableUri, linkSchema, parseVerifiableUri, tagSchema, verificationSchema };
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@chillwhales/lsp2",
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "LSP2 ERC725Y JSON Schema — shared primitives, VerifiableURI encoding/decoding, and image utilities",
|
|
6
|
+
"author": "b00ste",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"types": "./dist/index.d.mts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.mts",
|
|
12
|
+
"default": "./dist/index.mjs"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"LICENSE",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=22"
|
|
22
|
+
},
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://github.com/chillwhales/LSPs.git",
|
|
26
|
+
"directory": "packages/lsp2"
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"chillwhales",
|
|
30
|
+
"lukso",
|
|
31
|
+
"lsp",
|
|
32
|
+
"lsp2",
|
|
33
|
+
"erc725y",
|
|
34
|
+
"verifiable-uri",
|
|
35
|
+
"json-schema"
|
|
36
|
+
],
|
|
37
|
+
"sideEffects": false,
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"zod": "^3.24.1"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"viem": "^2.0.0"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"typescript": "^5.9.3",
|
|
46
|
+
"unbuild": "^3.6.1",
|
|
47
|
+
"viem": "^2.0.0",
|
|
48
|
+
"vitest": "^4.0.17",
|
|
49
|
+
"@chillwhales/config": "0.0.0"
|
|
50
|
+
},
|
|
51
|
+
"scripts": {
|
|
52
|
+
"build": "unbuild",
|
|
53
|
+
"build:watch": "unbuild --watch",
|
|
54
|
+
"clean": "rm -rf dist",
|
|
55
|
+
"test": "vitest run",
|
|
56
|
+
"test:watch": "vitest"
|
|
57
|
+
}
|
|
58
|
+
}
|