@aboutcircles/sdk-utils 0.1.0

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/abi.d.ts ADDED
@@ -0,0 +1,31 @@
1
+ import type { Abi } from 'abitype';
2
+ import type { Hex } from '@aboutcircles/sdk-types';
3
+ /**
4
+ * Convert an Ethereum address to EIP-55 checksummed format
5
+ * @param address - The address to checksum (with or without 0x prefix)
6
+ * @returns The checksummed address with 0x prefix
7
+ */
8
+ export declare function checksumAddress(address: string): string;
9
+ export declare function encodeFunctionData(config: {
10
+ abi: Abi;
11
+ functionName: string;
12
+ args?: readonly unknown[];
13
+ }): Hex;
14
+ export declare function decodeFunctionResult(config: {
15
+ abi: Abi;
16
+ functionName: string;
17
+ data: string;
18
+ }): unknown;
19
+ /**
20
+ * Decode error data from a contract revert
21
+ * @param config - Configuration with ABI and error data
22
+ * @returns Decoded error with name and arguments
23
+ */
24
+ export declare function decodeErrorResult(config: {
25
+ abi: Abi;
26
+ data: string;
27
+ }): {
28
+ errorName: string;
29
+ args?: any[];
30
+ } | null;
31
+ //# sourceMappingURL=abi.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"abi.d.ts","sourceRoot":"","sources":["../src/abi.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,GAAG,EAAe,MAAM,SAAS,CAAC;AAChD,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,yBAAyB,CAAC;AAwCnD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CASvD;AAoXD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE;IACzC,GAAG,EAAE,GAAG,CAAC;IACT,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,SAAS,OAAO,EAAE,CAAC;CAC3B,GAAG,GAAG,CA+DN;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE;IAC3C,GAAG,EAAE,GAAG,CAAC;IACT,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;CACd,GAAG,OAAO,CA0CV;AAmBD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE;IACxC,GAAG,EAAE,GAAG,CAAC;IACT,IAAI,EAAE,MAAM,CAAC;CACd,GAAG;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,GAAG,EAAE,CAAA;CAAE,GAAG,IAAI,CA+C7C"}
package/dist/abi.js ADDED
@@ -0,0 +1,448 @@
1
+ import { keccak_256 } from '@noble/hashes/sha3.js';
2
+ import { bytesToHex } from './bytes';
3
+ // ============================================================================
4
+ // CONSTANTS & UTILITIES
5
+ // ============================================================================
6
+ const HEX_CHARS = 64; // 32 bytes = 64 hex characters
7
+ const BYTES_PER_WORD = 32;
8
+ /** Remove 0x prefix efficiently */
9
+ const strip0x = (hex) => hex.startsWith('0x') ? hex.slice(2) : hex;
10
+ /** Pad hex string to 64 characters (32 bytes) */
11
+ const pad64 = (hex) => hex.padStart(HEX_CHARS, '0');
12
+ /** Convert number to padded hex */
13
+ const toHex64 = (num) => num.toString(16).padStart(HEX_CHARS, '0');
14
+ // ============================================================================
15
+ // ADDRESS CHECKSUMMING (EIP-55)
16
+ // ============================================================================
17
+ /**
18
+ * Convert an Ethereum address to EIP-55 checksummed format
19
+ * @param address - The address to checksum (with or without 0x prefix)
20
+ * @returns The checksummed address with 0x prefix
21
+ */
22
+ export function checksumAddress(address) {
23
+ const addr = address.toLowerCase().replace('0x', '');
24
+ const hash = bytesToHex(keccak_256(new TextEncoder().encode(addr))).slice(2);
25
+ let result = '0x';
26
+ for (let i = 0; i < addr.length; i++) {
27
+ result += parseInt(hash[i], 16) >= 8 ? addr[i].toUpperCase() : addr[i];
28
+ }
29
+ return result;
30
+ }
31
+ // ============================================================================
32
+ // FUNCTION SIGNATURE & SELECTOR
33
+ // ============================================================================
34
+ function getTypeString(type, components) {
35
+ if (type === 'tuple' && components) {
36
+ const types = components.map(c => getTypeString(c.type, c.components));
37
+ return `(${types.join(',')})`;
38
+ }
39
+ // Handle tuple arrays: tuple[] or tuple[N]
40
+ const tupleArrayMatch = type.match(/^tuple(\[\d*\])$/);
41
+ if (tupleArrayMatch && components) {
42
+ const baseType = getTypeString('tuple', components);
43
+ return `${baseType}${tupleArrayMatch[1]}`;
44
+ }
45
+ return type;
46
+ }
47
+ function getFunctionSignature(abiFunction) {
48
+ const inputs = abiFunction.inputs || [];
49
+ const types = inputs.map(input => getTypeString(input.type, input.components));
50
+ return `${abiFunction.name}(${types.join(',')})`;
51
+ }
52
+ function getFunctionSelector(abiFunction) {
53
+ const signature = getFunctionSignature(abiFunction);
54
+ const hash = keccak_256(new TextEncoder().encode(signature));
55
+ return bytesToHex(hash.slice(0, 4));
56
+ }
57
+ // ============================================================================
58
+ // DYNAMIC TYPE DETECTION
59
+ // ============================================================================
60
+ function isDynamicType(type, components) {
61
+ // String and bytes are always dynamic
62
+ if (type === 'string' || type === 'bytes')
63
+ return true;
64
+ // Dynamic arrays (including fixed arrays of dynamic elements)
65
+ if (type.includes('[')) {
66
+ const baseType = type.slice(0, type.indexOf('['));
67
+ // Dynamic length array is always dynamic
68
+ if (type.endsWith('[]'))
69
+ return true;
70
+ // Fixed array is dynamic if its elements are dynamic
71
+ if (baseType === 'tuple') {
72
+ return isTupleDynamic(components);
73
+ }
74
+ return isDynamicType(baseType);
75
+ }
76
+ // Tuple is dynamic if any component is dynamic
77
+ if (type === 'tuple') {
78
+ return isTupleDynamic(components);
79
+ }
80
+ return false;
81
+ }
82
+ function isTupleDynamic(components) {
83
+ return components?.some(c => isDynamicType(c.type, c.components)) ?? false;
84
+ }
85
+ // ============================================================================
86
+ // ENCODING
87
+ // ============================================================================
88
+ function encodeParam(type, value, components) {
89
+ // Handle tuples
90
+ if (type === 'tuple' && components) {
91
+ return encodeTuple(components, value);
92
+ }
93
+ // Handle arrays
94
+ if (type.includes('[')) {
95
+ return encodeArray(type, value, components);
96
+ }
97
+ // Handle primitives
98
+ return encodePrimitive(type, value);
99
+ }
100
+ function encodeArray(type, arr, components) {
101
+ const baseType = type.slice(0, type.indexOf('['));
102
+ const isDynamic = type.endsWith('[]');
103
+ const elementsAreDynamic = isDynamicType(baseType, components);
104
+ let encoded;
105
+ if (elementsAreDynamic) {
106
+ // Encode with offsets for dynamic elements
107
+ const encodings = arr.map(v => encodeParam(baseType, v, components));
108
+ let offset = arr.length * BYTES_PER_WORD;
109
+ const offsets = encodings.map(enc => {
110
+ const currentOffset = offset;
111
+ offset += enc.length / 2;
112
+ return toHex64(currentOffset);
113
+ });
114
+ encoded = offsets.join('') + encodings.join('');
115
+ }
116
+ else {
117
+ // Encode inline for static elements
118
+ encoded = arr.map(v => encodeParam(baseType, v, components)).join('');
119
+ }
120
+ // Prepend length for dynamic arrays
121
+ return isDynamic ? toHex64(arr.length) + encoded : encoded;
122
+ }
123
+ function encodeTuple(components, value) {
124
+ const isArray = Array.isArray(value);
125
+ const encodings = [];
126
+ const dynamicData = [];
127
+ const isDynamic = [];
128
+ // First pass: encode all components
129
+ for (let i = 0; i < components.length; i++) {
130
+ const component = components[i];
131
+ const val = isArray ? value[i] : value[component.name || ''];
132
+ const dynamic = isDynamicType(component.type, component.components);
133
+ isDynamic.push(dynamic);
134
+ if (dynamic) {
135
+ encodings.push(''); // Placeholder for offset
136
+ dynamicData.push(encodeParam(component.type, val, component.components));
137
+ }
138
+ else {
139
+ encodings.push(encodeParam(component.type, val, component.components));
140
+ }
141
+ }
142
+ // Second pass: calculate offsets and build result
143
+ if (dynamicData.length > 0) {
144
+ let offset = encodings.reduce((sum, enc, i) => sum + (isDynamic[i] ? BYTES_PER_WORD : enc.length / 2), 0);
145
+ let result = '';
146
+ let dynamicIndex = 0;
147
+ for (let i = 0; i < components.length; i++) {
148
+ if (isDynamic[i]) {
149
+ result += toHex64(offset);
150
+ offset += dynamicData[dynamicIndex].length / 2;
151
+ dynamicIndex++;
152
+ }
153
+ else {
154
+ result += encodings[i];
155
+ }
156
+ }
157
+ return result + dynamicData.join('');
158
+ }
159
+ return encodings.join('');
160
+ }
161
+ function encodePrimitive(type, value) {
162
+ // Address
163
+ if (type === 'address') {
164
+ return strip0x(value).toLowerCase().padStart(HEX_CHARS, '0');
165
+ }
166
+ // Boolean
167
+ if (type === 'bool') {
168
+ return toHex64(value ? 1 : 0);
169
+ }
170
+ // Unsigned integers
171
+ if (type.startsWith('uint')) {
172
+ const num = typeof value === 'bigint' ? value : BigInt(value);
173
+ return toHex64(num);
174
+ }
175
+ // Signed integers
176
+ if (type.startsWith('int')) {
177
+ let num = typeof value === 'bigint' ? value : BigInt(value);
178
+ if (num < 0n) {
179
+ const bits = type === 'int' ? 256 : parseInt(type.slice(3));
180
+ num = (1n << BigInt(bits)) + num; // Two's complement
181
+ }
182
+ return toHex64(num);
183
+ }
184
+ // Fixed-length bytes (bytes1-bytes32)
185
+ if (type.startsWith('bytes') && type !== 'bytes') {
186
+ return strip0x(value).padEnd(HEX_CHARS, '0');
187
+ }
188
+ // Dynamic bytes
189
+ if (type === 'bytes') {
190
+ const bytes = strip0x(value);
191
+ const length = toHex64(bytes.length / 2);
192
+ const padded = bytes.padEnd(Math.ceil(bytes.length / HEX_CHARS) * HEX_CHARS, '0');
193
+ return length + padded;
194
+ }
195
+ // String
196
+ if (type === 'string') {
197
+ const bytes = Array.from(new TextEncoder().encode(value))
198
+ .map(b => b.toString(16).padStart(2, '0'))
199
+ .join('');
200
+ const length = toHex64(bytes.length / 2);
201
+ const padded = bytes.padEnd(Math.ceil(bytes.length / HEX_CHARS) * HEX_CHARS, '0');
202
+ return length + padded;
203
+ }
204
+ throw new Error(`Unsupported type: ${type}`);
205
+ }
206
+ function decodeParam(type, data, offset = 0, components) {
207
+ // Handle tuples
208
+ if (type === 'tuple' && components) {
209
+ return decodeTuple(components, data, offset);
210
+ }
211
+ // Handle arrays
212
+ if (type.includes('[')) {
213
+ return decodeArray(type, data, offset, components);
214
+ }
215
+ // Handle primitives
216
+ return decodePrimitive(type, data, offset);
217
+ }
218
+ function decodeArray(type, data, offset, components) {
219
+ const baseType = type.slice(0, type.indexOf('['));
220
+ const chunk = data.slice(offset, offset + HEX_CHARS);
221
+ // Dynamic array
222
+ if (type.endsWith('[]')) {
223
+ const dataOffset = parseInt(chunk, 16) * 2;
224
+ const length = parseInt(data.slice(dataOffset, dataOffset + HEX_CHARS), 16);
225
+ const values = [];
226
+ let currentOffset = dataOffset + HEX_CHARS;
227
+ for (let i = 0; i < length; i++) {
228
+ const result = decodeParam(baseType, data, currentOffset, components);
229
+ values.push(result.value);
230
+ currentOffset += result.consumed;
231
+ }
232
+ return { value: values, consumed: HEX_CHARS };
233
+ }
234
+ // Fixed array
235
+ const match = type.match(/\[(\d+)\]$/);
236
+ if (match) {
237
+ const length = parseInt(match[1]);
238
+ const values = [];
239
+ let consumed = 0;
240
+ for (let i = 0; i < length; i++) {
241
+ const result = decodeParam(baseType, data, offset + consumed, components);
242
+ values.push(result.value);
243
+ consumed += result.consumed;
244
+ }
245
+ return { value: values, consumed };
246
+ }
247
+ throw new Error(`Invalid array type: ${type}`);
248
+ }
249
+ function decodeTuple(components, data, offset) {
250
+ const values = [];
251
+ let currentOffset = offset;
252
+ for (const component of components) {
253
+ const result = decodeParam(component.type, data, currentOffset, component.components);
254
+ values.push(result.value);
255
+ currentOffset += result.consumed;
256
+ }
257
+ return { value: values, consumed: currentOffset - offset };
258
+ }
259
+ function decodePrimitive(type, data, offset) {
260
+ const chunk = data.slice(offset, offset + HEX_CHARS);
261
+ // Address
262
+ if (type === 'address') {
263
+ return {
264
+ value: checksumAddress('0x' + chunk.slice(24)),
265
+ consumed: HEX_CHARS
266
+ };
267
+ }
268
+ // Boolean
269
+ if (type === 'bool') {
270
+ return {
271
+ value: parseInt(chunk, 16) !== 0,
272
+ consumed: HEX_CHARS
273
+ };
274
+ }
275
+ // Unsigned integers
276
+ if (type.startsWith('uint')) {
277
+ return {
278
+ value: BigInt('0x' + chunk),
279
+ consumed: HEX_CHARS
280
+ };
281
+ }
282
+ // Signed integers
283
+ if (type.startsWith('int')) {
284
+ const value = BigInt('0x' + chunk);
285
+ const bits = type === 'int' ? 256 : parseInt(type.slice(3));
286
+ const signBit = 1n << BigInt(bits - 1);
287
+ return {
288
+ value: value >= signBit ? value - (1n << BigInt(bits)) : value,
289
+ consumed: HEX_CHARS
290
+ };
291
+ }
292
+ // Fixed-length bytes
293
+ if (type.startsWith('bytes') && type !== 'bytes') {
294
+ const size = parseInt(type.match(/^bytes(\d+)$/)[1]);
295
+ return {
296
+ value: '0x' + chunk.slice(0, size * 2),
297
+ consumed: HEX_CHARS
298
+ };
299
+ }
300
+ // Dynamic bytes
301
+ if (type === 'bytes') {
302
+ const dataOffset = parseInt(chunk, 16) * 2;
303
+ const length = parseInt(data.slice(dataOffset, dataOffset + HEX_CHARS), 16) * 2;
304
+ return {
305
+ value: '0x' + data.slice(dataOffset + HEX_CHARS, dataOffset + HEX_CHARS + length),
306
+ consumed: HEX_CHARS
307
+ };
308
+ }
309
+ // String
310
+ if (type === 'string') {
311
+ const dataOffset = parseInt(chunk, 16) * 2;
312
+ const length = parseInt(data.slice(dataOffset, dataOffset + HEX_CHARS), 16) * 2;
313
+ const bytes = data.slice(dataOffset + HEX_CHARS, dataOffset + HEX_CHARS + length);
314
+ const byteArray = new Uint8Array(bytes.match(/.{2}/g)?.map(b => parseInt(b, 16)) || []);
315
+ return {
316
+ value: new TextDecoder().decode(byteArray),
317
+ consumed: HEX_CHARS
318
+ };
319
+ }
320
+ throw new Error(`Unsupported type: ${type}`);
321
+ }
322
+ // ============================================================================
323
+ // PUBLIC API
324
+ // ============================================================================
325
+ export function encodeFunctionData(config) {
326
+ const { abi, functionName, args = [] } = config;
327
+ const abiFunction = abi.find(item => item.type === 'function' && item.name === functionName);
328
+ if (!abiFunction) {
329
+ throw new Error(`Function "${functionName}" not found in ABI`);
330
+ }
331
+ const selector = getFunctionSelector(abiFunction);
332
+ const inputs = abiFunction.inputs || [];
333
+ if (inputs.length === 0)
334
+ return selector;
335
+ if (args.length !== inputs.length) {
336
+ throw new Error(`Expected ${inputs.length} arguments, got ${args.length}`);
337
+ }
338
+ // Encode parameters
339
+ const encodings = [];
340
+ const dynamicData = [];
341
+ const isDynamic = [];
342
+ for (let i = 0; i < inputs.length; i++) {
343
+ const input = inputs[i];
344
+ const components = input.components;
345
+ const dynamic = isDynamicType(input.type, components);
346
+ isDynamic.push(dynamic);
347
+ if (dynamic) {
348
+ encodings.push('');
349
+ dynamicData.push(encodeParam(input.type, args[i], components));
350
+ }
351
+ else {
352
+ encodings.push(encodeParam(input.type, args[i], components));
353
+ }
354
+ }
355
+ // Calculate offsets and build result
356
+ if (dynamicData.length === 0) {
357
+ return (selector + encodings.join(''));
358
+ }
359
+ let offset = encodings.reduce((sum, enc, i) => sum + (isDynamic[i] ? BYTES_PER_WORD : enc.length / 2), 0);
360
+ let result = '';
361
+ let dynamicIndex = 0;
362
+ for (let i = 0; i < inputs.length; i++) {
363
+ if (isDynamic[i]) {
364
+ result += toHex64(offset);
365
+ offset += dynamicData[dynamicIndex].length / 2;
366
+ dynamicIndex++;
367
+ }
368
+ else {
369
+ result += encodings[i];
370
+ }
371
+ }
372
+ return (selector + result + dynamicData.join(''));
373
+ }
374
+ export function decodeFunctionResult(config) {
375
+ const { abi, functionName, data } = config;
376
+ const abiFunction = abi.find(item => item.type === 'function' && item.name === functionName);
377
+ if (!abiFunction) {
378
+ throw new Error(`Function "${functionName}" not found in ABI`);
379
+ }
380
+ const outputs = abiFunction.outputs || [];
381
+ if (outputs.length === 0)
382
+ return undefined;
383
+ const cleanData = strip0x(data);
384
+ // Single output
385
+ if (outputs.length === 1) {
386
+ return decodeParam(outputs[0].type, cleanData, 0, outputs[0].components).value;
387
+ }
388
+ // Multiple outputs
389
+ const results = [];
390
+ let offset = 0;
391
+ for (const output of outputs) {
392
+ const result = decodeParam(output.type, cleanData, offset, output.components);
393
+ results.push(result.value);
394
+ offset += result.consumed;
395
+ }
396
+ return results;
397
+ }
398
+ // ============================================================================
399
+ // ERROR DECODING
400
+ // ============================================================================
401
+ /**
402
+ * Get error selector (first 4 bytes) from error ABI item
403
+ */
404
+ function getErrorSelector(errorItem) {
405
+ const inputs = errorItem.inputs || [];
406
+ const types = inputs.map((input) => getTypeString(input.type, input.components));
407
+ const signature = `${errorItem.name}(${types.join(',')})`;
408
+ const hash = keccak_256(new TextEncoder().encode(signature));
409
+ return bytesToHex(hash.slice(0, 4));
410
+ }
411
+ /**
412
+ * Decode error data from a contract revert
413
+ * @param config - Configuration with ABI and error data
414
+ * @returns Decoded error with name and arguments
415
+ */
416
+ export function decodeErrorResult(config) {
417
+ const { abi, data } = config;
418
+ if (!data || data.length < 10) {
419
+ return null; // Not enough data for a selector
420
+ }
421
+ // Extract the error selector (first 4 bytes)
422
+ const selector = data.slice(0, 10).toLowerCase();
423
+ const cleanData = strip0x(data.slice(10));
424
+ // Find matching error in ABI
425
+ const errors = abi.filter(item => item.type === 'error');
426
+ for (const errorItem of errors) {
427
+ const errorSelector = getErrorSelector(errorItem);
428
+ if (errorSelector.toLowerCase() === selector) {
429
+ const inputs = errorItem.inputs || [];
430
+ if (inputs.length === 0) {
431
+ return { errorName: errorItem.name };
432
+ }
433
+ // Decode error parameters
434
+ const decodedArgs = [];
435
+ let offset = 0;
436
+ for (const input of inputs) {
437
+ const result = decodeParam(input.type, cleanData, offset, input.components);
438
+ decodedArgs.push(result.value);
439
+ offset += result.consumed;
440
+ }
441
+ return {
442
+ errorName: errorItem.name,
443
+ args: decodedArgs
444
+ };
445
+ }
446
+ }
447
+ return null; // No matching error found
448
+ }
@@ -0,0 +1,10 @@
1
+ import type { Address } from '@aboutcircles/sdk-types';
2
+ /**
3
+ * Converts a uint256 value to an Ethereum address
4
+ * Takes the last 20 bytes of the uint256
5
+ *
6
+ * @param uint256 - The uint256 value as a bigint
7
+ * @returns The address as a checksummed hex string
8
+ */
9
+ export declare function uint256ToAddress(uint256: bigint): Address;
10
+ //# sourceMappingURL=address.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"address.d.ts","sourceRoot":"","sources":["../src/address.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAEvD;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAYzD"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Converts a uint256 value to an Ethereum address
3
+ * Takes the last 20 bytes of the uint256
4
+ *
5
+ * @param uint256 - The uint256 value as a bigint
6
+ * @returns The address as a checksummed hex string
7
+ */
8
+ export function uint256ToAddress(uint256) {
9
+ // Convert bigint to hex string
10
+ const hex = uint256.toString(16);
11
+ // Pad to 64 characters (256 bits / 4 bits per hex char)
12
+ const paddedHex = hex.padStart(64, '0');
13
+ // Take the last 40 characters (20 bytes = 160 bits)
14
+ const addressHex = paddedHex.slice(-40);
15
+ // Add 0x prefix and return as lowercase (checksumming happens at consumer level if needed)
16
+ return `0x${addressHex}`;
17
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Convert a Uint8Array to a hex string with 0x prefix
3
+ */
4
+ export declare function bytesToHex(bytes: Uint8Array): string;
5
+ //# sourceMappingURL=bytes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bytes.d.ts","sourceRoot":"","sources":["../src/bytes.ts"],"names":[],"mappings":"AAMA;;GAEG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,UAAU,GAAG,MAAM,CAMpD"}
package/dist/bytes.js ADDED
@@ -0,0 +1,15 @@
1
+ // Pre-computed lookup table for byte-to-hex conversion
2
+ const byteToHex = [];
3
+ for (let i = 0; i < 256; i++) {
4
+ byteToHex[i] = i.toString(16).padStart(2, '0');
5
+ }
6
+ /**
7
+ * Convert a Uint8Array to a hex string with 0x prefix
8
+ */
9
+ export function bytesToHex(bytes) {
10
+ let hex = '0x';
11
+ for (let i = 0; i < bytes.length; i++) {
12
+ hex += byteToHex[bytes[i]];
13
+ }
14
+ return hex;
15
+ }
package/dist/cid.d.ts ADDED
@@ -0,0 +1,39 @@
1
+ import type { Hex } from '@aboutcircles/sdk-types';
2
+ /**
3
+ * Convert a CIDv0 string to a bytes32 hex string for on-chain storage
4
+ *
5
+ * CIDv0 format:
6
+ * - Base58 encoded multihash
7
+ * - Always uses SHA-256 hash algorithm
8
+ * - Contains 32-byte hash digest
9
+ *
10
+ * This function is browser-compatible using the modern `multiformats` library.
11
+ *
12
+ * @param cidV0 - The CIDv0 string (e.g., "QmXxxx...")
13
+ * @returns The 32-byte hash digest as a hex string
14
+ * @throws Error if the CID is invalid or uses unsupported hash algorithm
15
+ *
16
+ * @example
17
+ * ```typescript
18
+ * const hex = cidV0ToHex('QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG');
19
+ * // Returns: '0x...' (32 bytes / 64 hex characters)
20
+ * ```
21
+ */
22
+ export declare function cidV0ToHex(cidV0: string): Hex;
23
+ /**
24
+ * Convert a CIDv0 string to a Uint8Array of the hash digest
25
+ *
26
+ * This function is browser-compatible using the modern `multiformats` library.
27
+ *
28
+ * @param cidV0 - The CIDv0 string (e.g., "QmXxxx...")
29
+ * @returns The 32-byte hash digest as a Uint8Array
30
+ * @throws Error if the CID is invalid or uses unsupported hash algorithm
31
+ *
32
+ * @example
33
+ * ```typescript
34
+ * const bytes = cidV0ToUint8Array('QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG');
35
+ * // Returns: Uint8Array(32) [...]
36
+ * ```
37
+ */
38
+ export declare function cidV0ToUint8Array(cidV0: string): Uint8Array;
39
+ //# sourceMappingURL=cid.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cid.d.ts","sourceRoot":"","sources":["../src/cid.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,yBAAyB,CAAC;AAEnD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAmB7C;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAgB3D"}
package/dist/cid.js ADDED
@@ -0,0 +1,67 @@
1
+ import { CID } from 'multiformats/cid';
2
+ import { base58btc } from 'multiformats/bases/base58';
3
+ /**
4
+ * Convert a CIDv0 string to a bytes32 hex string for on-chain storage
5
+ *
6
+ * CIDv0 format:
7
+ * - Base58 encoded multihash
8
+ * - Always uses SHA-256 hash algorithm
9
+ * - Contains 32-byte hash digest
10
+ *
11
+ * This function is browser-compatible using the modern `multiformats` library.
12
+ *
13
+ * @param cidV0 - The CIDv0 string (e.g., "QmXxxx...")
14
+ * @returns The 32-byte hash digest as a hex string
15
+ * @throws Error if the CID is invalid or uses unsupported hash algorithm
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * const hex = cidV0ToHex('QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG');
20
+ * // Returns: '0x...' (32 bytes / 64 hex characters)
21
+ * ```
22
+ */
23
+ export function cidV0ToHex(cidV0) {
24
+ // Parse the CIDv0 string using base58btc decoder
25
+ const cid = CID.parse(cidV0, base58btc);
26
+ // Verify this is CIDv0
27
+ if (cid.version !== 0) {
28
+ throw new Error(`Expected CIDv0, got CIDv${cid.version}`);
29
+ }
30
+ // Verify the multihash uses SHA-256 (code: 0x12)
31
+ if (cid.multihash.code !== 0x12) {
32
+ throw new Error('Unsupported hash algorithm. Only SHA-256 is supported for CIDv0.');
33
+ }
34
+ // Extract the 32-byte hash digest and convert to hex
35
+ const digest = cid.multihash.digest;
36
+ const hexString = `0x${Array.from(digest).map(b => b.toString(16).padStart(2, '0')).join('')}`;
37
+ return hexString;
38
+ }
39
+ /**
40
+ * Convert a CIDv0 string to a Uint8Array of the hash digest
41
+ *
42
+ * This function is browser-compatible using the modern `multiformats` library.
43
+ *
44
+ * @param cidV0 - The CIDv0 string (e.g., "QmXxxx...")
45
+ * @returns The 32-byte hash digest as a Uint8Array
46
+ * @throws Error if the CID is invalid or uses unsupported hash algorithm
47
+ *
48
+ * @example
49
+ * ```typescript
50
+ * const bytes = cidV0ToUint8Array('QmYwAPJzv5CZsnA625s3Xf2nemtYgPpHdWEz79ojWnPbdG');
51
+ * // Returns: Uint8Array(32) [...]
52
+ * ```
53
+ */
54
+ export function cidV0ToUint8Array(cidV0) {
55
+ // Parse the CIDv0 string using base58btc decoder
56
+ const cid = CID.parse(cidV0, base58btc);
57
+ // Verify this is CIDv0
58
+ if (cid.version !== 0) {
59
+ throw new Error(`Expected CIDv0, got CIDv${cid.version}`);
60
+ }
61
+ // Verify the multihash uses SHA-256 (code: 0x12)
62
+ if (cid.multihash.code !== 0x12) {
63
+ throw new Error('Unsupported hash algorithm. Only SHA-256 is supported for CIDv0.');
64
+ }
65
+ // Extract and return the 32-byte hash digest
66
+ return cid.multihash.digest;
67
+ }