@beclab/olaresid 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/CLI.md +1300 -0
  2. package/README.md +37 -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 +1209 -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 +537 -0
  15. package/dist/business/tag-context.js.map +1 -0
  16. package/dist/cli.js +2085 -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 +50 -2
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +229 -9
  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/tag-abi-codec.d.ts +69 -0
  34. package/dist/utils/tag-abi-codec.d.ts.map +1 -0
  35. package/dist/utils/tag-abi-codec.js +144 -0
  36. package/dist/utils/tag-abi-codec.js.map +1 -0
  37. package/dist/utils/tag-type-builder.d.ts +158 -0
  38. package/dist/utils/tag-type-builder.d.ts.map +1 -0
  39. package/dist/utils/tag-type-builder.js +410 -0
  40. package/dist/utils/tag-type-builder.js.map +1 -0
  41. package/examples/crypto-utilities.ts +140 -0
  42. package/examples/domain-context.ts +80 -0
  43. package/examples/generate-mnemonic.ts +149 -0
  44. package/examples/index.ts +1 -1
  45. package/examples/ip.ts +171 -0
  46. package/examples/legacy.ts +10 -10
  47. package/examples/list-wallets.ts +81 -0
  48. package/examples/quasar-demo/.eslintrc.js +23 -0
  49. package/examples/quasar-demo/.quasar/app.js +43 -0
  50. package/examples/quasar-demo/.quasar/client-entry.js +38 -0
  51. package/examples/quasar-demo/.quasar/client-prefetch.js +130 -0
  52. package/examples/quasar-demo/.quasar/quasar-user-options.js +16 -0
  53. package/examples/quasar-demo/README.md +49 -0
  54. package/examples/quasar-demo/index.html +11 -0
  55. package/examples/quasar-demo/package-lock.json +6407 -0
  56. package/examples/quasar-demo/package.json +36 -0
  57. package/examples/quasar-demo/quasar.config.js +73 -0
  58. package/examples/quasar-demo/src/App.vue +13 -0
  59. package/examples/quasar-demo/src/css/app.scss +1 -0
  60. package/examples/quasar-demo/src/layouts/MainLayout.vue +21 -0
  61. package/examples/quasar-demo/src/pages/IndexPage.vue +905 -0
  62. package/examples/quasar-demo/src/router/index.ts +25 -0
  63. package/examples/quasar-demo/src/router/routes.ts +11 -0
  64. package/examples/quasar-demo/tsconfig.json +28 -0
  65. package/examples/register-subdomain.ts +152 -0
  66. package/examples/rsa-keypair.ts +148 -0
  67. package/examples/tag-builder.ts +235 -0
  68. package/examples/tag-management.ts +534 -0
  69. package/examples/tag-nested-tuple.ts +190 -0
  70. package/examples/tag-simple.ts +149 -0
  71. package/examples/tag-tagger.ts +217 -0
  72. package/examples/test-nested-tuple-conversion.ts +143 -0
  73. package/examples/test-type-bytes-parser.ts +70 -0
  74. package/examples/transfer-domain.ts +197 -0
  75. package/examples/wallet-management.ts +196 -0
  76. package/package.json +24 -15
  77. package/src/abi/RootResolver2ABI.ts +237 -0
  78. package/src/business/index.ts +1490 -0
  79. package/src/business/tag-context.ts +713 -0
  80. package/src/cli.ts +2755 -39
  81. package/src/debug.ts +17 -2
  82. package/src/index.ts +300 -14
  83. package/src/utils/crypto-utils.ts +459 -0
  84. package/src/utils/error-parser.ts +225 -0
  85. package/src/utils/tag-abi-codec.ts +158 -0
  86. package/src/utils/tag-type-builder.ts +469 -0
  87. package/tsconfig.json +1 -1
@@ -0,0 +1,713 @@
1
+ import { DIDConsole } from '../index';
2
+ import { TransactionResult } from './index';
3
+ import { parseContractError } from '../utils/error-parser';
4
+ import { TagTypeBuilder } from '../utils/tag-type-builder';
5
+ import { TagAbiCodec } from '../utils/tag-abi-codec';
6
+
7
+ /**
8
+ * Tag Operation Context
9
+ * Provides complete Tag management functionality
10
+ */
11
+ export class TagContext {
12
+ constructor(private console: DIDConsole, private fromDomain: string) {}
13
+
14
+ // ========================================
15
+ // 1. Tag Type Definition Management
16
+ // ========================================
17
+
18
+ /**
19
+ * Set the tagger (manager) for a Tag
20
+ * Only the domain owner can set the tagger
21
+ *
22
+ * @param tagName Tag name
23
+ * @param taggerAddress Address of the tagger (manager)
24
+ * @returns Transaction result
25
+ *
26
+ * @example
27
+ * // Set a specific address as the tagger
28
+ * await tagCtx.setTagger('email', '0x1234...');
29
+ *
30
+ * // Set zero address to allow anyone to manage
31
+ * await tagCtx.setTagger('email', '0x0000000000000000000000000000000000000000');
32
+ */
33
+ async setTagger(
34
+ tagName: string,
35
+ taggerAddress: string
36
+ ): Promise<TransactionResult> {
37
+ const contract = this.console.getSignerContractDID();
38
+
39
+ try {
40
+ const tx = await contract.setTagger(
41
+ this.fromDomain,
42
+ tagName,
43
+ taggerAddress
44
+ );
45
+
46
+ const receipt = await tx.wait();
47
+
48
+ return {
49
+ success: true,
50
+ transactionHash: receipt.hash,
51
+ gasUsed: receipt.gasUsed,
52
+ blockNumber: receipt.blockNumber
53
+ };
54
+ } catch (error: any) {
55
+ const errorInfo = parseContractError(error);
56
+ if (errorInfo.isNetworkError) {
57
+ throw new Error(`Network error: ${errorInfo.message}`);
58
+ }
59
+ throw new Error(`Failed to set tagger: ${errorInfo.message}`);
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Get the tagger (manager) address for a Tag
65
+ *
66
+ * @param tagName Tag name
67
+ * @returns Tagger address (returns zero address if no tagger is set)
68
+ *
69
+ * @example
70
+ * const tagger = await tagCtx.getTagger('email');
71
+ * console.log('Tagger address:', tagger);
72
+ *
73
+ * if (tagger === '0x0000000000000000000000000000000000000000') {
74
+ * console.log('No specific tagger, anyone can manage');
75
+ * }
76
+ */
77
+ async getTagger(tagName: string): Promise<string> {
78
+ const contract = this.console.getContractDID();
79
+ return await contract.getTagger(this.fromDomain, tagName);
80
+ }
81
+
82
+ /**
83
+ * Define a new Tag type
84
+ * @param tagName Tag name
85
+ * @param tagType Tag type object (built using TagTypeBuilder)
86
+ * @returns Transaction result
87
+ *
88
+ * @example
89
+ * const emailType = TagTypeBuilder.string();
90
+ * await tagCtx.defineTag('email', emailType);
91
+ */
92
+ async defineTag(
93
+ tagName: string,
94
+ tagType: TagTypeBuilder
95
+ ): Promise<TransactionResult> {
96
+ const contract = this.console.getSignerContractDID();
97
+
98
+ // Get type bytes directly from TagTypeBuilder (no contract call needed)
99
+ const abiTypeBytes = '0x' + tagType.getTypeBytes();
100
+
101
+ // Get field names (for complex types)
102
+ const fieldNames = tagType.getFieldNames();
103
+
104
+ try {
105
+ const tx = await contract.defineTag(
106
+ this.fromDomain,
107
+ tagName,
108
+ abiTypeBytes,
109
+ fieldNames
110
+ );
111
+
112
+ const receipt = await tx.wait();
113
+
114
+ return {
115
+ success: true,
116
+ transactionHash: receipt.hash,
117
+ gasUsed: receipt.gasUsed,
118
+ blockNumber: receipt.blockNumber
119
+ };
120
+ } catch (error: any) {
121
+ const errorInfo = parseContractError(error);
122
+ if (errorInfo.isNetworkError) {
123
+ throw new Error(`Network error: ${errorInfo.message}`);
124
+ }
125
+ throw new Error(`Failed to define tag: ${errorInfo.message}`);
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Get Tag type definition
131
+ * @param tagName Tag name
132
+ * @returns Tag type information, or null if not found
133
+ */
134
+ async getTagType(tagName: string): Promise<{
135
+ abiType: string;
136
+ fieldNamesHash: string[];
137
+ } | null> {
138
+ try {
139
+ const contract = this.console.getContractDID();
140
+ const [abiType, fieldNamesHash] = await contract.getTagType(
141
+ this.fromDomain,
142
+ tagName
143
+ );
144
+ return { abiType, fieldNamesHash };
145
+ } catch (error: any) {
146
+ const errorInfo = parseContractError(error);
147
+ if (errorInfo.errorName === 'UndefinedTag') {
148
+ return null;
149
+ }
150
+ throw error;
151
+ }
152
+ }
153
+
154
+ /**
155
+ * Get all Tag names defined by the domain
156
+ * @returns Array of Tag names
157
+ */
158
+ async getDefinedTagNames(): Promise<string[]> {
159
+ const contract = this.console.getContractDID();
160
+ return await contract.getDefinedTagNames(this.fromDomain);
161
+ }
162
+
163
+ /**
164
+ * Get the count of Tags defined by the domain
165
+ */
166
+ async getDefinedTagCount(): Promise<number> {
167
+ const contract = this.console.getContractDID();
168
+ const count = await contract.getDefinedTagCount(this.fromDomain);
169
+ return Number(count);
170
+ }
171
+
172
+ // ========================================
173
+ // 2. Tag Value Operations
174
+ // ========================================
175
+
176
+ /**
177
+ * Set Tag value
178
+ * Supports simple values, arrays, and tuples (as objects or arrays)
179
+ * Objects are automatically converted to arrays based on ABI type structure
180
+ *
181
+ * @param toDomain Target domain
182
+ * @param tagName Tag name
183
+ * @param value Tag value (objects for tuples, arrays, or simple values)
184
+ * @returns Transaction result
185
+ *
186
+ * @example
187
+ * // Simple value
188
+ * await tagCtx.setTag('example.com', 'email', 'user@example.com');
189
+ *
190
+ * // Array type
191
+ * await tagCtx.setTag('example.com', 'links', ['https://...', 'https://...']);
192
+ *
193
+ * // Tuple type - object notation (recommended)
194
+ * await tagCtx.setTag('example.com', 'userInfo', {
195
+ * name: 'Alice',
196
+ * age: 30,
197
+ * verified: true
198
+ * });
199
+ *
200
+ * // Tuple type - array notation (still supported)
201
+ * await tagCtx.setTag('example.com', 'userInfo', ['Alice', 30, true]);
202
+ *
203
+ * // Nested tuple - object notation
204
+ * await tagCtx.setTag('example.com', 'profile', {
205
+ * name: 'Alice',
206
+ * details: {
207
+ * age: 30,
208
+ * verified: true
209
+ * }
210
+ * });
211
+ */
212
+ async setTag(
213
+ toDomain: string,
214
+ tagName: string,
215
+ value: any
216
+ ): Promise<TransactionResult> {
217
+ const contract = this.console.getSignerContractDID();
218
+
219
+ // Get tag type
220
+ const tagTypeInfo = await this.getTagType(tagName);
221
+ if (!tagTypeInfo) {
222
+ throw new Error(
223
+ `Tag "${tagName}" is not defined in domain "${this.fromDomain}"`
224
+ );
225
+ }
226
+
227
+ try {
228
+ // Parse type bytes to ABI type string
229
+ const abiType = TagTypeBuilder.parseTypeBytesToAbiString(
230
+ tagTypeInfo.abiType
231
+ );
232
+
233
+ // Encode the ENTIRE value using TagAbiCodec
234
+ // Objects are automatically converted to arrays based on ABI type structure
235
+ const encodedValue = TagAbiCodec.encode(abiType, value);
236
+
237
+ // Check if tag already exists
238
+ const hasTag = await contract.hasTag(
239
+ this.fromDomain,
240
+ toDomain,
241
+ tagName
242
+ );
243
+
244
+ let tx;
245
+ if (hasTag) {
246
+ // Update tag value
247
+ tx = await contract.updateTagElem(
248
+ this.fromDomain,
249
+ toDomain,
250
+ tagName,
251
+ [], // Empty path means root element
252
+ encodedValue
253
+ );
254
+ } else {
255
+ // Add new tag
256
+ tx = await contract.addTag(
257
+ this.fromDomain,
258
+ toDomain,
259
+ tagName,
260
+ encodedValue
261
+ );
262
+ }
263
+
264
+ const receipt = await tx.wait();
265
+
266
+ return {
267
+ success: true,
268
+ transactionHash: receipt.hash,
269
+ gasUsed: receipt.gasUsed,
270
+ blockNumber: receipt.blockNumber
271
+ };
272
+ } catch (error: any) {
273
+ const errorInfo = parseContractError(error);
274
+ if (errorInfo.isNetworkError) {
275
+ throw new Error(`Network error: ${errorInfo.message}`);
276
+ }
277
+ throw new Error(`Failed to set tag: ${errorInfo.message}`);
278
+ }
279
+ }
280
+
281
+ /**
282
+ * Get Tag value
283
+ * @param toDomain Target domain
284
+ * @param tagName Tag name
285
+ * @returns Tag value, or null if not found
286
+ */
287
+ async getTag(toDomain: string, tagName: string): Promise<any | null> {
288
+ try {
289
+ const contract = this.console.getContractDID();
290
+
291
+ // Get tag type
292
+ const tagTypeInfo = await this.getTagType(tagName);
293
+ if (!tagTypeInfo) {
294
+ return null;
295
+ }
296
+
297
+ // Get encoded value
298
+ const encodedValue = await contract.getTagElem(
299
+ this.fromDomain,
300
+ toDomain,
301
+ tagName,
302
+ []
303
+ );
304
+
305
+ // Parse type and decode
306
+ const abiType = TagTypeBuilder.parseTypeBytesToAbiString(
307
+ tagTypeInfo.abiType
308
+ );
309
+ return TagAbiCodec.decode(abiType, encodedValue);
310
+ } catch (error: any) {
311
+ const errorInfo = parseContractError(error);
312
+ if (
313
+ errorInfo.errorName === 'TagInvalidOp' ||
314
+ errorInfo.errorName === 'UndefinedTag'
315
+ ) {
316
+ return null;
317
+ }
318
+ if (errorInfo.isNetworkError) {
319
+ throw new Error(`Network error: ${errorInfo.message}`);
320
+ }
321
+ throw error;
322
+ }
323
+ }
324
+
325
+ /**
326
+ * Remove Tag
327
+ * @param toDomain Target domain
328
+ * @param tagName Tag name
329
+ * @returns Transaction result
330
+ */
331
+ async removeTag(
332
+ toDomain: string,
333
+ tagName: string
334
+ ): Promise<TransactionResult> {
335
+ try {
336
+ const contract = this.console.getSignerContractDID();
337
+ const tx = await contract.removeTag(
338
+ this.fromDomain,
339
+ toDomain,
340
+ tagName
341
+ );
342
+ const receipt = await tx.wait();
343
+
344
+ return {
345
+ success: true,
346
+ transactionHash: receipt.hash,
347
+ gasUsed: receipt.gasUsed,
348
+ blockNumber: receipt.blockNumber
349
+ };
350
+ } catch (error: any) {
351
+ const errorInfo = parseContractError(error);
352
+ if (errorInfo.isNetworkError) {
353
+ throw new Error(`Network error: ${errorInfo.message}`);
354
+ }
355
+ throw new Error(`Failed to remove tag: ${errorInfo.message}`);
356
+ }
357
+ }
358
+
359
+ /**
360
+ * Check if Tag exists
361
+ * @param toDomain Target domain
362
+ * @param tagName Tag name
363
+ * @returns Whether the tag exists
364
+ */
365
+ async hasTag(toDomain: string, tagName: string): Promise<boolean> {
366
+ const contract = this.console.getContractDID();
367
+ return await contract.hasTag(this.fromDomain, toDomain, tagName);
368
+ }
369
+
370
+ /**
371
+ * Get all Tag names for the domain
372
+ * @param toDomain Target domain
373
+ * @returns Array of Tag names
374
+ */
375
+ async getTagNames(toDomain: string): Promise<string[]> {
376
+ const contract = this.console.getContractDID();
377
+ const count = await contract.getTagCount(this.fromDomain, toDomain);
378
+ const names: string[] = [];
379
+
380
+ for (let i = 0; i < count; i++) {
381
+ const name = await contract.getTagNameByIndex(
382
+ this.fromDomain,
383
+ toDomain,
384
+ i
385
+ );
386
+ names.push(name);
387
+ }
388
+
389
+ return names;
390
+ }
391
+
392
+ /**
393
+ * Get all Tags and their values for the domain
394
+ * @param toDomain Target domain
395
+ * @returns Array of Tags, each containing name and value
396
+ */
397
+ async getAllTags(
398
+ toDomain: string
399
+ ): Promise<Array<{ name: string; value: any }>> {
400
+ const names = await this.getTagNames(toDomain);
401
+ const result = [];
402
+
403
+ for (const name of names) {
404
+ const value = await this.getTag(toDomain, name);
405
+ result.push({ name, value });
406
+ }
407
+
408
+ return result;
409
+ }
410
+
411
+ // ========================================
412
+ // 3. Array Operations (Advanced Features)
413
+ // ========================================
414
+
415
+ /**
416
+ * Add an element to an array Tag
417
+ * @param toDomain Target domain
418
+ * @param tagName Tag name
419
+ * @param value Element value to add
420
+ * @param elemPath Element path (for nested arrays), defaults to empty array
421
+ * @returns Transaction result
422
+ *
423
+ * @example
424
+ * // Add element to a 1D array
425
+ * await tagCtx.pushElement('example.com', 'socialLinks', 'https://twitter.com/user');
426
+ *
427
+ * // Add element to the first sub-array of a 2D array
428
+ * await tagCtx.pushElement('example.com', 'matrix', 'value', [0]);
429
+ */
430
+ async pushElement(
431
+ toDomain: string,
432
+ tagName: string,
433
+ value: any,
434
+ elemPath: number[] = []
435
+ ): Promise<TransactionResult> {
436
+ const contract = this.console.getSignerContractDID();
437
+
438
+ // Get tag type and encode element
439
+ const tagTypeInfo = await this.getTagType(tagName);
440
+ if (!tagTypeInfo) {
441
+ throw new Error(`Tag "${tagName}" is not defined`);
442
+ }
443
+
444
+ try {
445
+ // Parse type bytes to ABI string
446
+ const rootAbiType = TagTypeBuilder.parseTypeBytesToAbiString(
447
+ tagTypeInfo.abiType
448
+ );
449
+ // Get ELEMENT type (not root type!)
450
+ const elementAbiType = this.getElementAbiType(
451
+ rootAbiType,
452
+ elemPath
453
+ );
454
+ // Encode the SINGLE ELEMENT (not the whole array)
455
+ const encodedValue = TagAbiCodec.encode(elementAbiType, value);
456
+
457
+ const tx = await contract.pushTagElem(
458
+ this.fromDomain,
459
+ toDomain,
460
+ tagName,
461
+ elemPath,
462
+ encodedValue
463
+ );
464
+ const receipt = await tx.wait();
465
+
466
+ return {
467
+ success: true,
468
+ transactionHash: receipt.hash,
469
+ gasUsed: receipt.gasUsed,
470
+ blockNumber: receipt.blockNumber
471
+ };
472
+ } catch (error: any) {
473
+ const errorInfo = parseContractError(error);
474
+ if (errorInfo.isNetworkError) {
475
+ throw new Error(`Network error: ${errorInfo.message}`);
476
+ }
477
+ throw new Error(`Failed to push element: ${errorInfo.message}`);
478
+ }
479
+ }
480
+
481
+ /**
482
+ * Remove the last element from an array Tag
483
+ * @param toDomain Target domain
484
+ * @param tagName Tag name
485
+ * @param elemPath Element path, defaults to empty array
486
+ * @returns Transaction result
487
+ */
488
+ async popElement(
489
+ toDomain: string,
490
+ tagName: string,
491
+ elemPath: number[] = []
492
+ ): Promise<TransactionResult> {
493
+ try {
494
+ const contract = this.console.getSignerContractDID();
495
+ const tx = await contract.popTagElem(
496
+ this.fromDomain,
497
+ toDomain,
498
+ tagName,
499
+ elemPath
500
+ );
501
+ const receipt = await tx.wait();
502
+
503
+ return {
504
+ success: true,
505
+ transactionHash: receipt.hash,
506
+ gasUsed: receipt.gasUsed,
507
+ blockNumber: receipt.blockNumber
508
+ };
509
+ } catch (error: any) {
510
+ const errorInfo = parseContractError(error);
511
+ if (errorInfo.isNetworkError) {
512
+ throw new Error(`Network error: ${errorInfo.message}`);
513
+ }
514
+ throw new Error(`Failed to pop element: ${errorInfo.message}`);
515
+ }
516
+ }
517
+
518
+ /**
519
+ * Update a specific element in an array or tuple
520
+ * @param toDomain Target domain
521
+ * @param tagName Tag name
522
+ * @param elemPath Element path (e.g., [0] for the first element)
523
+ * @param value New value
524
+ * @returns Transaction result
525
+ *
526
+ * @example
527
+ * // Update the first element of an array
528
+ * await tagCtx.updateElement('example.com', 'links', [0], 'https://new-url.com');
529
+ *
530
+ * // Update an element in a 2D array
531
+ * await tagCtx.updateElement('example.com', 'matrix', [0, 1], 'value');
532
+ */
533
+ async updateElement(
534
+ toDomain: string,
535
+ tagName: string,
536
+ elemPath: number[],
537
+ value: any
538
+ ): Promise<TransactionResult> {
539
+ const contract = this.console.getSignerContractDID();
540
+
541
+ const tagTypeInfo = await this.getTagType(tagName);
542
+ if (!tagTypeInfo) {
543
+ throw new Error(`Tag "${tagName}" is not defined`);
544
+ }
545
+
546
+ try {
547
+ // Parse type bytes to ABI string
548
+ const rootAbiType = TagTypeBuilder.parseTypeBytesToAbiString(
549
+ tagTypeInfo.abiType
550
+ );
551
+ // Get element type
552
+ const elementAbiType = this.getElementAbiType(
553
+ rootAbiType,
554
+ elemPath
555
+ );
556
+ // Encode the element value
557
+ const encodedValue = TagAbiCodec.encode(elementAbiType, value);
558
+
559
+ const tx = await contract.updateTagElem(
560
+ this.fromDomain,
561
+ toDomain,
562
+ tagName,
563
+ elemPath,
564
+ encodedValue
565
+ );
566
+ const receipt = await tx.wait();
567
+
568
+ return {
569
+ success: true,
570
+ transactionHash: receipt.hash,
571
+ gasUsed: receipt.gasUsed,
572
+ blockNumber: receipt.blockNumber
573
+ };
574
+ } catch (error: any) {
575
+ const errorInfo = parseContractError(error);
576
+ if (errorInfo.isNetworkError) {
577
+ throw new Error(`Network error: ${errorInfo.message}`);
578
+ }
579
+ throw new Error(`Failed to update element: ${errorInfo.message}`);
580
+ }
581
+ }
582
+
583
+ /**
584
+ * Get the length of an array Tag
585
+ * @param toDomain Target domain
586
+ * @param tagName Tag name
587
+ * @param elemPath Element path, defaults to empty array
588
+ * @returns Array length
589
+ */
590
+ async getArrayLength(
591
+ toDomain: string,
592
+ tagName: string,
593
+ elemPath: number[] = []
594
+ ): Promise<number> {
595
+ const contract = this.console.getContractDID();
596
+ const length = await contract.getTagElemLength(
597
+ this.fromDomain,
598
+ toDomain,
599
+ tagName,
600
+ elemPath
601
+ );
602
+ return Number(length);
603
+ }
604
+
605
+ /**
606
+ * Get a specific element from an array or tuple
607
+ * @param toDomain Target domain
608
+ * @param tagName Tag name
609
+ * @param elemPath Element path
610
+ * @returns Element value, or null if not found
611
+ *
612
+ * @example
613
+ * // Get the first element of an array
614
+ * const first = await tagCtx.getElement('example.com', 'links', [0]);
615
+ *
616
+ * // Get an element from a 2D array
617
+ * const value = await tagCtx.getElement('example.com', 'matrix', [0, 1]);
618
+ */
619
+ async getElement(
620
+ toDomain: string,
621
+ tagName: string,
622
+ elemPath: number[]
623
+ ): Promise<any | null> {
624
+ try {
625
+ const contract = this.console.getContractDID();
626
+
627
+ const encodedValue = await contract.getTagElem(
628
+ this.fromDomain,
629
+ toDomain,
630
+ tagName,
631
+ elemPath
632
+ );
633
+
634
+ // Get tag type and decode element value
635
+ const tagTypeInfo = await this.getTagType(tagName);
636
+ if (!tagTypeInfo) {
637
+ return null;
638
+ }
639
+
640
+ // Parse type bytes to ABI string
641
+ const rootAbiType = TagTypeBuilder.parseTypeBytesToAbiString(
642
+ tagTypeInfo.abiType
643
+ );
644
+ // Get element type
645
+ const elementAbiType = this.getElementAbiType(
646
+ rootAbiType,
647
+ elemPath
648
+ );
649
+ // Decode the element
650
+ return TagAbiCodec.decode(elementAbiType, encodedValue);
651
+ } catch (error: any) {
652
+ const errorInfo = parseContractError(error);
653
+ if (errorInfo.errorName === 'TagInvalidOp') {
654
+ return null;
655
+ }
656
+ if (errorInfo.isNetworkError) {
657
+ throw new Error(`Network error: ${errorInfo.message}`);
658
+ }
659
+ throw error;
660
+ }
661
+ }
662
+
663
+ // ========================================
664
+ // Helper Methods (Private)
665
+ // ========================================
666
+
667
+ /**
668
+ * Get element ABI type based on path
669
+ * Example: string[][] + [0] = string[]
670
+ * string[][] + [0, 0] = string
671
+ *
672
+ * Special case: For pushElement/popElement on array type with empty path,
673
+ * we want the element type:
674
+ * Example: string[] + [] → string (for push operation)
675
+ */
676
+ private getElementAbiType(abiType: string, elemPath: number[]): string {
677
+ let currentType = abiType;
678
+
679
+ // If elemPath is empty and currentType is an array,
680
+ // return the element type (for push/pop operations)
681
+ if (elemPath.length === 0) {
682
+ const arrayMatch = currentType.match(/^(.+?)\[(\d*)\]$/);
683
+ if (arrayMatch) {
684
+ return arrayMatch[1]; // Return element type
685
+ }
686
+ // Not an array, return as-is
687
+ return currentType;
688
+ }
689
+
690
+ // Process each level of the path
691
+ for (const _index of elemPath) {
692
+ // Parse ABI type string
693
+ // Handle array types: type[] or type[N]
694
+ const arrayMatch = currentType.match(/^(.+?)\[(\d*)\]$/);
695
+
696
+ if (arrayMatch) {
697
+ // Array type, element type is the base type
698
+ currentType = arrayMatch[1];
699
+ } else if (currentType.startsWith('tuple(')) {
700
+ // Tuple type - not supported for element access by index
701
+ throw new Error(
702
+ 'Tuple element access by index not supported yet. Use field names instead.'
703
+ );
704
+ } else {
705
+ throw new Error(
706
+ `Cannot access element of non-array type "${currentType}" at path ${elemPath}`
707
+ );
708
+ }
709
+ }
710
+
711
+ return currentType;
712
+ }
713
+ }