@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 +31 -0
- package/dist/abi.d.ts.map +1 -0
- package/dist/abi.js +448 -0
- package/dist/address.d.ts +10 -0
- package/dist/address.d.ts.map +1 -0
- package/dist/address.js +17 -0
- package/dist/bytes.d.ts +5 -0
- package/dist/bytes.d.ts.map +1 -0
- package/dist/bytes.js +15 -0
- package/dist/cid.d.ts +39 -0
- package/dist/cid.d.ts.map +1 -0
- package/dist/cid.js +67 -0
- package/dist/circlesConverter.d.ts +80 -0
- package/dist/circlesConverter.d.ts.map +1 -0
- package/dist/circlesConverter.js +181 -0
- package/dist/constants.d.ts +9 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +7 -0
- package/dist/contractErrors.d.ts +49 -0
- package/dist/contractErrors.d.ts.map +1 -0
- package/dist/contractErrors.js +198 -0
- package/dist/errors.d.ts +103 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +165 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1901 -0
- package/dist/tests/abi.test.d.ts +2 -0
- package/dist/tests/abi.test.d.ts.map +1 -0
- package/dist/tests/abi.test.js +508 -0
- package/dist/tests/checksumAddress.test.d.ts +2 -0
- package/dist/tests/checksumAddress.test.d.ts.map +1 -0
- package/dist/tests/checksumAddress.test.js +93 -0
- package/package.json +33 -0
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"}
|
package/dist/address.js
ADDED
|
@@ -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
|
+
}
|
package/dist/bytes.d.ts
ADDED
|
@@ -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
|
+
}
|