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