@beclab/olaresid 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/CLI.md +1300 -0
  2. package/README.md +40 -31
  3. package/TAG.md +589 -0
  4. package/dist/abi/RootResolver2ABI.d.ts +54 -0
  5. package/dist/abi/RootResolver2ABI.d.ts.map +1 -0
  6. package/dist/abi/RootResolver2ABI.js +240 -0
  7. package/dist/abi/RootResolver2ABI.js.map +1 -0
  8. package/dist/business/index.d.ts +302 -0
  9. package/dist/business/index.d.ts.map +1 -0
  10. package/dist/business/index.js +1211 -0
  11. package/dist/business/index.js.map +1 -0
  12. package/dist/business/tag-context.d.ts +219 -0
  13. package/dist/business/tag-context.d.ts.map +1 -0
  14. package/dist/business/tag-context.js +560 -0
  15. package/dist/business/tag-context.js.map +1 -0
  16. package/dist/cli.js +2102 -39
  17. package/dist/cli.js.map +1 -1
  18. package/dist/debug.d.ts.map +1 -1
  19. package/dist/debug.js +14 -2
  20. package/dist/debug.js.map +1 -1
  21. package/dist/index.d.ts +51 -2
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +241 -12
  24. package/dist/index.js.map +1 -1
  25. package/dist/utils/crypto-utils.d.ts +130 -0
  26. package/dist/utils/crypto-utils.d.ts.map +1 -0
  27. package/dist/utils/crypto-utils.js +402 -0
  28. package/dist/utils/crypto-utils.js.map +1 -0
  29. package/dist/utils/error-parser.d.ts +35 -0
  30. package/dist/utils/error-parser.d.ts.map +1 -0
  31. package/dist/utils/error-parser.js +202 -0
  32. package/dist/utils/error-parser.js.map +1 -0
  33. package/dist/utils/olares-id.d.ts +36 -0
  34. package/dist/utils/olares-id.d.ts.map +1 -0
  35. package/dist/utils/olares-id.js +52 -0
  36. package/dist/utils/olares-id.js.map +1 -0
  37. package/dist/utils/tag-abi-codec.d.ts +69 -0
  38. package/dist/utils/tag-abi-codec.d.ts.map +1 -0
  39. package/dist/utils/tag-abi-codec.js +144 -0
  40. package/dist/utils/tag-abi-codec.js.map +1 -0
  41. package/dist/utils/tag-type-builder.d.ts +158 -0
  42. package/dist/utils/tag-type-builder.d.ts.map +1 -0
  43. package/dist/utils/tag-type-builder.js +410 -0
  44. package/dist/utils/tag-type-builder.js.map +1 -0
  45. package/examples/crypto-utilities.ts +140 -0
  46. package/examples/domain-context.ts +80 -0
  47. package/examples/generate-mnemonic.ts +149 -0
  48. package/examples/index.ts +1 -1
  49. package/examples/ip.ts +171 -0
  50. package/examples/legacy.ts +10 -10
  51. package/examples/list-wallets.ts +81 -0
  52. package/examples/olares-id-format.ts +197 -0
  53. package/examples/quasar-demo/.eslintrc.js +23 -0
  54. package/examples/quasar-demo/.quasar/app.js +43 -0
  55. package/examples/quasar-demo/.quasar/client-entry.js +38 -0
  56. package/examples/quasar-demo/.quasar/client-prefetch.js +130 -0
  57. package/examples/quasar-demo/.quasar/quasar-user-options.js +16 -0
  58. package/examples/quasar-demo/README.md +49 -0
  59. package/examples/quasar-demo/index.html +11 -0
  60. package/examples/quasar-demo/package-lock.json +6407 -0
  61. package/examples/quasar-demo/package.json +36 -0
  62. package/examples/quasar-demo/quasar.config.js +73 -0
  63. package/examples/quasar-demo/src/App.vue +13 -0
  64. package/examples/quasar-demo/src/css/app.scss +1 -0
  65. package/examples/quasar-demo/src/layouts/MainLayout.vue +21 -0
  66. package/examples/quasar-demo/src/pages/IndexPage.vue +905 -0
  67. package/examples/quasar-demo/src/router/index.ts +25 -0
  68. package/examples/quasar-demo/src/router/routes.ts +11 -0
  69. package/examples/quasar-demo/tsconfig.json +28 -0
  70. package/examples/register-subdomain.ts +152 -0
  71. package/examples/rsa-keypair.ts +148 -0
  72. package/examples/tag-builder.ts +235 -0
  73. package/examples/tag-management.ts +534 -0
  74. package/examples/tag-nested-tuple.ts +190 -0
  75. package/examples/tag-simple.ts +149 -0
  76. package/examples/tag-tagger.ts +217 -0
  77. package/examples/test-nested-tuple-conversion.ts +143 -0
  78. package/examples/test-type-bytes-parser.ts +70 -0
  79. package/examples/transfer-domain.ts +197 -0
  80. package/examples/wallet-management.ts +196 -0
  81. package/package.json +24 -15
  82. package/src/abi/RootResolver2ABI.ts +237 -0
  83. package/src/business/index.ts +1492 -0
  84. package/src/business/tag-context.ts +747 -0
  85. package/src/cli.ts +2772 -39
  86. package/src/debug.ts +17 -2
  87. package/src/index.ts +313 -17
  88. package/src/utils/crypto-utils.ts +459 -0
  89. package/src/utils/error-parser.ts +225 -0
  90. package/src/utils/olares-id.ts +49 -0
  91. package/src/utils/tag-abi-codec.ts +158 -0
  92. package/src/utils/tag-type-builder.ts +469 -0
  93. package/tsconfig.json +1 -1
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Olares ID Utilities
3
+ *
4
+ * Olares ID is a new format that looks like email addresses: user@domain.com
5
+ * It's essentially a domain name where the first dot (.) is replaced with an at sign (@)
6
+ *
7
+ * Examples:
8
+ * - alice.example.com → alice@example.com (Olares ID)
9
+ * - bob.sub.example.com → bob@sub.example.com (Olares ID)
10
+ */
11
+
12
+ /**
13
+ * Convert Olares ID format to standard domain format
14
+ * Replaces the first @ with a dot (.)
15
+ *
16
+ * @param input The input string (can be either Olares ID or domain format)
17
+ * @returns The normalized domain format
18
+ *
19
+ * @example
20
+ * normalizeToDomain('alice@example.com') // returns 'alice.example.com'
21
+ * normalizeToDomain('alice.example.com') // returns 'alice.example.com' (no change)
22
+ * normalizeToDomain('bob@sub.example.com') // returns 'bob.sub.example.com'
23
+ */
24
+ export function normalizeToDomain(input: string): string {
25
+ if (!input) {
26
+ return input;
27
+ }
28
+ // Replace the first @ with a dot
29
+ return input.replace('@', '.');
30
+ }
31
+
32
+ /**
33
+ * Convert standard domain format to Olares ID format
34
+ * Replaces the first dot (.) with an at sign (@)
35
+ *
36
+ * @param domain The domain name
37
+ * @returns The Olares ID format
38
+ *
39
+ * @example
40
+ * normalizeToOlaresId('alice.example.com') // returns 'alice@example.com'
41
+ * normalizeToOlaresId('bob.sub.example.com') // returns 'bob@sub.example.com'
42
+ */
43
+ export function normalizeToOlaresId(domain: string): string {
44
+ if (!domain) {
45
+ return domain;
46
+ }
47
+ // Replace the first dot with @
48
+ return domain.replace('.', '@');
49
+ }
@@ -0,0 +1,158 @@
1
+ import { AbiCoder } from 'ethers';
2
+
3
+ /**
4
+ * Tag ABI Codec
5
+ * Unified encoding/decoding utility for Tag values
6
+ *
7
+ * Key Principle: ALL values use abi.encode()
8
+ * - Simple values: abi.encode("value")
9
+ * - Arrays: abi.encode(["a", "b", "c"])
10
+ * - Tuples: abi.encode(struct)
11
+ * - Elements for push: abi.encode(singleElement)
12
+ */
13
+ export class TagAbiCodec {
14
+ private static abiCoder = new AbiCoder();
15
+
16
+ /**
17
+ * Parse tuple field types from ABI type string
18
+ * @param abiType e.g., "tuple(string,uint8,tuple(bool,address))"
19
+ * @returns Array of field types: ["string", "uint8", "tuple(bool,address)"]
20
+ */
21
+ private static parseTupleFields(abiType: string): string[] {
22
+ if (!abiType.startsWith('tuple(')) {
23
+ return [];
24
+ }
25
+
26
+ const innerTypes = abiType.slice(6, -1); // Remove "tuple(" and ")"
27
+ const fields: string[] = [];
28
+ let depth = 0;
29
+ let currentField = '';
30
+
31
+ for (let i = 0; i < innerTypes.length; i++) {
32
+ const char = innerTypes[i];
33
+
34
+ if (char === '(') {
35
+ depth++;
36
+ currentField += char;
37
+ } else if (char === ')') {
38
+ depth--;
39
+ currentField += char;
40
+ } else if (char === ',' && depth === 0) {
41
+ fields.push(currentField.trim());
42
+ currentField = '';
43
+ } else {
44
+ currentField += char;
45
+ }
46
+ }
47
+
48
+ if (currentField) {
49
+ fields.push(currentField.trim());
50
+ }
51
+
52
+ return fields;
53
+ }
54
+
55
+ /**
56
+ * Convert object to array based on ABI type structure
57
+ * Supports nested tuples recursively
58
+ *
59
+ * @param abiType ABI type string
60
+ * @param value Value (can be object or array)
61
+ * @returns Array value
62
+ */
63
+ private static convertObjectToArray(abiType: string, value: any): any {
64
+ // If already an array or not an object, return as-is
65
+ if (
66
+ Array.isArray(value) ||
67
+ typeof value !== 'object' ||
68
+ value === null
69
+ ) {
70
+ return value;
71
+ }
72
+
73
+ // If not a tuple type, return as-is
74
+ if (!abiType.startsWith('tuple(')) {
75
+ return value;
76
+ }
77
+
78
+ // Parse tuple field types
79
+ const fieldTypes = this.parseTupleFields(abiType);
80
+ const fieldNames = Object.keys(value);
81
+
82
+ if (fieldNames.length !== fieldTypes.length) {
83
+ throw new Error(
84
+ `Tuple field count mismatch: expected ${
85
+ fieldTypes.length
86
+ } fields, got ${fieldNames.length} (${fieldNames.join(', ')})`
87
+ );
88
+ }
89
+
90
+ // Convert object to array, recursively handling nested tuples
91
+ const result: any[] = [];
92
+ for (let i = 0; i < fieldNames.length; i++) {
93
+ const fieldName = fieldNames[i];
94
+ const fieldType = fieldTypes[i];
95
+ const fieldValue = value[fieldName];
96
+
97
+ // Recursively convert nested tuples
98
+ if (fieldType.startsWith('tuple(')) {
99
+ result.push(this.convertObjectToArray(fieldType, fieldValue));
100
+ } else {
101
+ result.push(fieldValue);
102
+ }
103
+ }
104
+
105
+ return result;
106
+ }
107
+
108
+ /**
109
+ * Encode a value with given ABI type
110
+ *
111
+ * @param abiType ABI type string (e.g., "string", "uint256", "tuple(string,uint8)")
112
+ * @param value Value to encode (objects are automatically converted to arrays)
113
+ * @returns Encoded bytes (with 0x prefix)
114
+ *
115
+ * @example
116
+ * // Simple value
117
+ * TagAbiCodec.encode("string", "hello");
118
+ *
119
+ * // Array value
120
+ * TagAbiCodec.encode("string[]", ["a", "b", "c"]);
121
+ *
122
+ * // Tuple with object input
123
+ * TagAbiCodec.encode("tuple(string,uint8)", {name: "Alice", age: 30});
124
+ *
125
+ * // Nested tuple with object input
126
+ * TagAbiCodec.encode(
127
+ * "tuple(string,tuple(uint8,bool))",
128
+ * {name: "Alice", profile: {age: 30, verified: true}}
129
+ * );
130
+ *
131
+ * // Tuple with array input (still supported)
132
+ * TagAbiCodec.encode("tuple(string,uint8)", ["Alice", 30]);
133
+ */
134
+ static encode(abiType: string, value: any): string {
135
+ // Convert object to array if needed (handles nested tuples recursively)
136
+ const convertedValue = this.convertObjectToArray(abiType, value);
137
+
138
+ // Use ethers AbiCoder to encode
139
+ return this.abiCoder.encode([abiType], [convertedValue]);
140
+ }
141
+
142
+ /**
143
+ * Decode bytes with given ABI type
144
+ *
145
+ * @param abiType ABI type string
146
+ * @param encodedData Encoded bytes (with or without 0x prefix)
147
+ * @returns Decoded value
148
+ *
149
+ * @example
150
+ * TagAbiCodec.decode("string", encodedBytes); // → "hello"
151
+ * TagAbiCodec.decode("uint256", encodedBytes); // → 123n (BigInt)
152
+ * TagAbiCodec.decode("tuple(string,uint8)", encodedBytes); // → ["Alice", 30n]
153
+ */
154
+ static decode(abiType: string, encodedData: string): any {
155
+ const decoded = this.abiCoder.decode([abiType], encodedData);
156
+ return decoded[0];
157
+ }
158
+ }
@@ -0,0 +1,469 @@
1
+ /**
2
+ * Tag Type Builder
3
+ * Calculate ABI type bytes offline without contract calls
4
+ * Does NOT handle encoding/decoding - use TagAbiCodec for that
5
+ *
6
+ * Encoding Rules:
7
+ * - uint: 0x01 + size (size = bits/8)
8
+ * - int: 0x00 + size
9
+ * - bool: 0x02
10
+ * - string: 0x03
11
+ * - array: 0x04 + elementType
12
+ * - fixedArray: 0x05 + length (2 bytes) + elementType
13
+ * - tuple: 0x06 + fieldCount (2 bytes) + fieldTypes...
14
+ * - address: 0x07
15
+ * - fixedBytes: 0x08 + size
16
+ * - bytes: 0x09
17
+ */
18
+ export class TagTypeBuilder {
19
+ private typeBytes: string = '';
20
+ private abiTypeString: string = '';
21
+
22
+ protected constructor(typeBytes: string, abiTypeString: string = '') {
23
+ this.typeBytes = typeBytes;
24
+ this.abiTypeString = abiTypeString;
25
+ }
26
+
27
+ /**
28
+ * Get ABI type bytes (without 0x prefix)
29
+ */
30
+ getTypeBytes(): string {
31
+ return this.typeBytes;
32
+ }
33
+
34
+ /**
35
+ * Get ABI type string (e.g., "string", "uint256", "tuple(string,uint8)")
36
+ */
37
+ getAbiTypeString(): string {
38
+ return this.abiTypeString;
39
+ }
40
+
41
+ /**
42
+ * Parse type bytes to ABI type string
43
+ * This is the reverse operation of building type bytes
44
+ * @param typeBytes Hex string of type bytes (with or without 0x prefix)
45
+ * @returns ABI type string
46
+ */
47
+ static parseTypeBytesToAbiString(typeBytes: string): string {
48
+ // Remove 0x prefix if present
49
+ const bytes = typeBytes.startsWith('0x')
50
+ ? typeBytes.slice(2)
51
+ : typeBytes;
52
+ if (bytes.length === 0) {
53
+ throw new Error('Empty type bytes');
54
+ }
55
+
56
+ const typeFlag = parseInt(bytes.slice(0, 2), 16);
57
+
58
+ switch (typeFlag) {
59
+ case 0x00: {
60
+ // int
61
+ const size = parseInt(bytes.slice(2, 4), 16);
62
+ return `int${size * 8}`;
63
+ }
64
+ case 0x01: {
65
+ // uint
66
+ const size = parseInt(bytes.slice(2, 4), 16);
67
+ return `uint${size * 8}`;
68
+ }
69
+ case 0x02: {
70
+ // bool
71
+ return 'bool';
72
+ }
73
+ case 0x03: {
74
+ // string
75
+ return 'string';
76
+ }
77
+ case 0x04: {
78
+ // array
79
+ const elementTypeBytes = '0x' + bytes.slice(2);
80
+ const elementType =
81
+ this.parseTypeBytesToAbiString(elementTypeBytes);
82
+ return `${elementType}[]`;
83
+ }
84
+ case 0x05: {
85
+ // fixedArray
86
+ const length = parseInt(bytes.slice(2, 6), 16);
87
+ const elementTypeBytes = '0x' + bytes.slice(6);
88
+ const elementType =
89
+ this.parseTypeBytesToAbiString(elementTypeBytes);
90
+ return `${elementType}[${length}]`;
91
+ }
92
+ case 0x06: {
93
+ // tuple
94
+ const fieldCount = parseInt(bytes.slice(2, 6), 16);
95
+ let offset = 6;
96
+ const fieldTypes: string[] = [];
97
+
98
+ for (let i = 0; i < fieldCount; i++) {
99
+ // Parse each field's type recursively
100
+ const fieldTypeBytes = '0x' + bytes.slice(offset);
101
+ const fieldType =
102
+ this.parseTypeBytesToAbiString(fieldTypeBytes);
103
+ fieldTypes.push(fieldType);
104
+
105
+ // Calculate the length of this field's type bytes
106
+ const fieldTypeBytesLength =
107
+ this.getTypeBytesLength(fieldTypeBytes);
108
+ offset += fieldTypeBytesLength;
109
+ }
110
+
111
+ return `tuple(${fieldTypes.join(',')})`;
112
+ }
113
+ case 0x07: {
114
+ // address
115
+ return 'address';
116
+ }
117
+ case 0x08: {
118
+ // fixedBytes
119
+ const size = parseInt(bytes.slice(2, 4), 16);
120
+ return `bytes${size}`;
121
+ }
122
+ case 0x09: {
123
+ // bytes
124
+ return 'bytes';
125
+ }
126
+ default:
127
+ throw new Error(
128
+ `Unknown type flag: 0x${typeFlag.toString(16)}`
129
+ );
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Calculate the byte length of a type bytes string (without 0x prefix)
135
+ * @private
136
+ */
137
+ private static getTypeBytesLength(typeBytes: string): number {
138
+ const bytes = typeBytes.startsWith('0x')
139
+ ? typeBytes.slice(2)
140
+ : typeBytes;
141
+ const typeFlag = parseInt(bytes.slice(0, 2), 16);
142
+
143
+ switch (typeFlag) {
144
+ case 0x00: // int
145
+ case 0x01: // uint
146
+ case 0x08: // fixedBytes
147
+ return 4; // 1 byte flag + 1 byte size
148
+ case 0x02: // bool
149
+ case 0x03: // string
150
+ case 0x07: // address
151
+ case 0x09: // bytes
152
+ return 2; // 1 byte flag only
153
+ case 0x04: {
154
+ // array
155
+ const elementTypeBytes = '0x' + bytes.slice(2);
156
+ return 2 + this.getTypeBytesLength(elementTypeBytes);
157
+ }
158
+ case 0x05: {
159
+ // fixedArray
160
+ const elementTypeBytes = '0x' + bytes.slice(6);
161
+ return 6 + this.getTypeBytesLength(elementTypeBytes); // flag + length (2 bytes) + element type
162
+ }
163
+ case 0x06: {
164
+ // tuple
165
+ const fieldCount = parseInt(bytes.slice(2, 6), 16);
166
+ let length = 6; // flag + field count (2 bytes)
167
+ let offset = 6;
168
+
169
+ for (let i = 0; i < fieldCount; i++) {
170
+ const fieldTypeBytes = '0x' + bytes.slice(offset);
171
+ const fieldLength = this.getTypeBytesLength(fieldTypeBytes);
172
+ length += fieldLength;
173
+ offset += fieldLength;
174
+ }
175
+
176
+ return length;
177
+ }
178
+ default:
179
+ throw new Error(
180
+ `Unknown type flag: 0x${typeFlag.toString(16)}`
181
+ );
182
+ }
183
+ }
184
+
185
+ /**
186
+ * Get field names (for Tuple types)
187
+ * Returns format: string[][], each element is a tuple's field name array
188
+ */
189
+ getFieldNames(): string[][] {
190
+ // Default returns empty array, Tuple type will override this method
191
+ return [];
192
+ }
193
+
194
+ // ========================================
195
+ // Basic Types
196
+ // ========================================
197
+
198
+ /**
199
+ * uint type
200
+ * @param bits Bit count: 8, 16, 24, ..., 256 (must be divisible by 8)
201
+ * @example
202
+ * TagTypeBuilder.uint(8) → "0101"
203
+ * TagTypeBuilder.uint(256) → "0120"
204
+ */
205
+ static uint(bits: number = 256): TagTypeBuilder {
206
+ if (bits < 8 || bits > 256 || bits % 8 !== 0) {
207
+ throw new Error('uint bits must be 8-256 and divisible by 8');
208
+ }
209
+ const size = bits / 8;
210
+ const sizeHex = size.toString(16).padStart(2, '0');
211
+ return new TagTypeBuilder(`01${sizeHex}`, `uint${bits}`);
212
+ }
213
+
214
+ /**
215
+ * int type
216
+ * @param bits Bit count: 8, 16, 24, ..., 256 (must be divisible by 8)
217
+ * @example
218
+ * TagTypeBuilder.int(8) → "0001"
219
+ * TagTypeBuilder.int(256) → "0020"
220
+ */
221
+ static int(bits: number = 256): TagTypeBuilder {
222
+ if (bits < 8 || bits > 256 || bits % 8 !== 0) {
223
+ throw new Error('int bits must be 8-256 and divisible by 8');
224
+ }
225
+ const size = bits / 8;
226
+ const sizeHex = size.toString(16).padStart(2, '0');
227
+ return new TagTypeBuilder(`00${sizeHex}`, `int${bits}`);
228
+ }
229
+
230
+ /**
231
+ * bool type
232
+ * @example
233
+ * TagTypeBuilder.bool() → "02"
234
+ */
235
+ static bool(): TagTypeBuilder {
236
+ return new TagTypeBuilder('02', 'bool');
237
+ }
238
+
239
+ /**
240
+ * string type
241
+ * @example
242
+ * TagTypeBuilder.string() → "03"
243
+ */
244
+ static string(): TagTypeBuilder {
245
+ return new TagTypeBuilder('03', 'string');
246
+ }
247
+
248
+ /**
249
+ * address type
250
+ * @example
251
+ * TagTypeBuilder.address() → "07"
252
+ */
253
+ static address(): TagTypeBuilder {
254
+ return new TagTypeBuilder('07', 'address');
255
+ }
256
+
257
+ /**
258
+ * bytes type (dynamic length)
259
+ * @example
260
+ * TagTypeBuilder.bytes() → "09"
261
+ */
262
+ static bytes(): TagTypeBuilder {
263
+ return new TagTypeBuilder('09', 'bytes');
264
+ }
265
+
266
+ /**
267
+ * bytesN type (fixed length)
268
+ * @param size Byte count: 1-32
269
+ * @example
270
+ * TagTypeBuilder.fixedBytes(32) → "0820"
271
+ */
272
+ static fixedBytes(size: number): TagTypeBuilder {
273
+ if (size < 1 || size > 32) {
274
+ throw new Error('Fixed bytes size must be between 1 and 32');
275
+ }
276
+ const sizeHex = size.toString(16).padStart(2, '0');
277
+ return new TagTypeBuilder(`08${sizeHex}`, `bytes${size}`);
278
+ }
279
+
280
+ // ========================================
281
+ // Array Types
282
+ // ========================================
283
+
284
+ /**
285
+ * Dynamic array
286
+ * @param elementType Element type
287
+ * @example
288
+ * TagTypeBuilder.array(TagTypeBuilder.address()) → "0407"
289
+ */
290
+ static array(elementType: TagTypeBuilder): TagTypeBuilder {
291
+ const typeBytes = `04${elementType.getTypeBytes()}`;
292
+ const abiTypeString = `${elementType.getAbiTypeString()}[]`;
293
+ const fieldNames = elementType.getFieldNames();
294
+
295
+ // If element type has field names (e.g., tuple), propagate them
296
+ if (fieldNames.length > 0) {
297
+ return new TupleTypeBuilder(typeBytes, fieldNames, abiTypeString);
298
+ }
299
+ return new TagTypeBuilder(typeBytes, abiTypeString);
300
+ }
301
+
302
+ /**
303
+ * Fixed-length array
304
+ * @param elementType Element type
305
+ * @param length Array length (1-65535)
306
+ * @example
307
+ * TagTypeBuilder.fixedArray(TagTypeBuilder.address(), 5) → "05000507"
308
+ */
309
+ static fixedArray(
310
+ elementType: TagTypeBuilder,
311
+ length: number
312
+ ): TagTypeBuilder {
313
+ if (length < 1 || length > 65535) {
314
+ throw new Error('Fixed array length must be between 1 and 65535');
315
+ }
316
+ const lengthHex = length.toString(16).padStart(4, '0');
317
+ const typeBytes = `05${lengthHex}${elementType.getTypeBytes()}`;
318
+ const abiTypeString = `${elementType.getAbiTypeString()}[${length}]`;
319
+ const fieldNames = elementType.getFieldNames();
320
+
321
+ // If element type has field names (e.g., tuple), propagate them
322
+ if (fieldNames.length > 0) {
323
+ return new TupleTypeBuilder(typeBytes, fieldNames, abiTypeString);
324
+ }
325
+ return new TagTypeBuilder(typeBytes, abiTypeString);
326
+ }
327
+
328
+ // ========================================
329
+ // Tuple (Struct)
330
+ // ========================================
331
+
332
+ /**
333
+ * tuple type
334
+ * @param fields Field definitions, format: { fieldName: fieldType }
335
+ * @example
336
+ * TagTypeBuilder.tuple({
337
+ * name: TagTypeBuilder.string(),
338
+ * age: TagTypeBuilder.uint8()
339
+ * }) → "060002" + "03" + "0101"
340
+ *
341
+ * Field names are collected in pre-order traversal (DFS pre-order) for nested tuples.
342
+ * Pre-order: Visit root first, then recursively visit children left-to-right.
343
+ *
344
+ * Example tree structure: A{B{D{E, F}}, C}
345
+ * fieldNames = [
346
+ * ["i", "j"], // A's fields (root)
347
+ * ["i"], // B's fields (A's 1st child)
348
+ * ["i", "j"], // D's fields (B's child)
349
+ * ["i", "j"], // E's fields (D's 1st child)
350
+ * ["i", "j"], // F's fields (D's 2nd child)
351
+ * ["i"] // C's fields (A's 2nd child)
352
+ * ]
353
+ */
354
+ static tuple(fields: Record<string, TagTypeBuilder>): TupleTypeBuilder {
355
+ const fieldNames: string[] = [];
356
+ const fieldTypes: string[] = [];
357
+ const fieldAbiTypes: string[] = [];
358
+ const allFieldNames: string[][] = [];
359
+
360
+ // First, add current tuple's field names
361
+ for (const name of Object.keys(fields)) {
362
+ fieldNames.push(name);
363
+ }
364
+ allFieldNames.push(fieldNames);
365
+
366
+ // Then, recursively collect nested tuple field names in depth-first order
367
+ for (const [_name, type] of Object.entries(fields)) {
368
+ fieldTypes.push(type.getTypeBytes());
369
+ fieldAbiTypes.push(type.getAbiTypeString());
370
+
371
+ // Recursively collect nested tuple field names
372
+ const nested = type.getFieldNames();
373
+ if (nested.length > 0) {
374
+ allFieldNames.push(...nested);
375
+ }
376
+ }
377
+
378
+ const fieldCount = fieldTypes.length;
379
+ const fieldCountHex = fieldCount.toString(16).padStart(4, '0');
380
+ const typeBytes = `06${fieldCountHex}${fieldTypes.join('')}`;
381
+ const abiTypeString = `tuple(${fieldAbiTypes.join(',')})`;
382
+
383
+ return new TupleTypeBuilder(
384
+ typeBytes,
385
+ allFieldNames,
386
+ abiTypeString,
387
+ fields
388
+ );
389
+ }
390
+
391
+ // ========================================
392
+ // Common Type Shortcuts
393
+ // ========================================
394
+
395
+ static uint8() {
396
+ return this.uint(8);
397
+ }
398
+ static uint16() {
399
+ return this.uint(16);
400
+ }
401
+ static uint32() {
402
+ return this.uint(32);
403
+ }
404
+ static uint64() {
405
+ return this.uint(64);
406
+ }
407
+ static uint128() {
408
+ return this.uint(128);
409
+ }
410
+ static uint256() {
411
+ return this.uint(256);
412
+ }
413
+
414
+ static int8() {
415
+ return this.int(8);
416
+ }
417
+ static int16() {
418
+ return this.int(16);
419
+ }
420
+ static int32() {
421
+ return this.int(32);
422
+ }
423
+ static int64() {
424
+ return this.int(64);
425
+ }
426
+ static int128() {
427
+ return this.int(128);
428
+ }
429
+ static int256() {
430
+ return this.int(256);
431
+ }
432
+
433
+ static bytes32() {
434
+ return this.fixedBytes(32);
435
+ }
436
+
437
+ static addressArray() {
438
+ return this.array(this.address());
439
+ }
440
+ static stringArray() {
441
+ return this.array(this.string());
442
+ }
443
+ static uint256Array() {
444
+ return this.array(this.uint256());
445
+ }
446
+ }
447
+
448
+ /**
449
+ * Tuple Type Builder (supports field names and object encoding)
450
+ */
451
+ export class TupleTypeBuilder extends TagTypeBuilder {
452
+ private fieldNames: string[][];
453
+ private fieldTypes?: Record<string, TagTypeBuilder>;
454
+
455
+ constructor(
456
+ typeBytes: string,
457
+ fieldNames: string[][],
458
+ abiTypeString: string = '',
459
+ fieldTypes?: Record<string, TagTypeBuilder>
460
+ ) {
461
+ super(typeBytes, abiTypeString);
462
+ this.fieldNames = fieldNames;
463
+ this.fieldTypes = fieldTypes;
464
+ }
465
+
466
+ getFieldNames(): string[][] {
467
+ return this.fieldNames;
468
+ }
469
+ }
package/tsconfig.json CHANGED
@@ -5,5 +5,5 @@
5
5
  "rootDir": "./src"
6
6
  },
7
7
  "include": ["src/**/*"],
8
- "exclude": ["node_modules", "dist"]
8
+ "exclude": ["node_modules", "dist", "examples"]
9
9
  }