@beclab/olaresid 0.1.13 → 0.2.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.
Files changed (181) hide show
  1. package/CLI-TREE.md +107 -0
  2. package/CLI.md +122 -1340
  3. package/README.md +30 -12
  4. package/SDK-TREE.md +151 -0
  5. package/TAG.md +95 -41
  6. package/config.json +6 -4
  7. package/dist/abi/TerminusDIDQueryABI.d.ts +397 -0
  8. package/dist/abi/TerminusDIDQueryABI.d.ts.map +1 -0
  9. package/dist/abi/TerminusDIDQueryABI.js +519 -0
  10. package/dist/abi/TerminusDIDQueryABI.js.map +1 -0
  11. package/dist/business/index.d.ts.map +1 -1
  12. package/dist/business/index.js +9 -23
  13. package/dist/business/index.js.map +1 -1
  14. package/dist/business/tag-context.d.ts +1 -0
  15. package/dist/business/tag-context.d.ts.map +1 -1
  16. package/dist/business/tag-context.js +13 -7
  17. package/dist/business/tag-context.js.map +1 -1
  18. package/dist/cli.js +177 -76
  19. package/dist/cli.js.map +1 -1
  20. package/dist/config/index.d.ts +16 -4
  21. package/dist/config/index.d.ts.map +1 -1
  22. package/dist/config/index.js +28 -14
  23. package/dist/config/index.js.map +1 -1
  24. package/dist/domain/core.d.ts +65 -0
  25. package/dist/domain/core.d.ts.map +1 -0
  26. package/dist/domain/core.js +317 -0
  27. package/dist/domain/core.js.map +1 -0
  28. package/dist/domain/index.d.ts +104 -57
  29. package/dist/domain/index.d.ts.map +1 -1
  30. package/dist/domain/index.js +188 -428
  31. package/dist/domain/index.js.map +1 -1
  32. package/dist/domain/types.d.ts +56 -0
  33. package/dist/domain/types.d.ts.map +1 -0
  34. package/dist/domain/types.js +3 -0
  35. package/dist/domain/types.js.map +1 -0
  36. package/dist/index.d.ts +80 -23
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +152 -143
  39. package/dist/index.js.map +1 -1
  40. package/dist/utils/crypto-utils.d.ts +110 -0
  41. package/dist/utils/crypto-utils.d.ts.map +1 -1
  42. package/dist/utils/crypto-utils.js +127 -8
  43. package/dist/utils/crypto-utils.js.map +1 -1
  44. package/dist/utils/error-parser.d.ts.map +1 -1
  45. package/dist/utils/error-parser.js +2 -1
  46. package/dist/utils/error-parser.js.map +1 -1
  47. package/dist/utils/event-parser.d.ts +161 -0
  48. package/dist/utils/event-parser.d.ts.map +1 -0
  49. package/dist/utils/event-parser.js +140 -0
  50. package/dist/utils/event-parser.js.map +1 -0
  51. package/dist/utils/tag-type-builder.d.ts +43 -0
  52. package/dist/utils/tag-type-builder.d.ts.map +1 -1
  53. package/dist/utils/tag-type-builder.js +122 -0
  54. package/dist/utils/tag-type-builder.js.map +1 -1
  55. package/dist/utils/tag-type-parser.d.ts +70 -0
  56. package/dist/utils/tag-type-parser.d.ts.map +1 -0
  57. package/dist/utils/tag-type-parser.js +190 -0
  58. package/dist/utils/tag-type-parser.js.map +1 -0
  59. package/examples/create-with-rpc-demo.ts +142 -0
  60. package/examples/fetch-all-flat-demo.ts +159 -0
  61. package/examples/fetch-by-indices-demo.ts +235 -0
  62. package/examples/fetch-domain-demo.ts +137 -0
  63. package/examples/fetch-domains-demo.ts +221 -0
  64. package/examples/frontend-demo/index.html +2 -2
  65. package/examples/frontend-demo/package-lock.json +4 -1
  66. package/examples/index.ts +3 -5
  67. package/jest.config.js +25 -0
  68. package/package.json +6 -2
  69. package/src/abi/TerminusDIDQueryABI.ts +516 -0
  70. package/src/business/index.ts +9 -33
  71. package/src/business/tag-context.ts +35 -7
  72. package/src/cli.ts +253 -90
  73. package/src/config/index.ts +34 -19
  74. package/src/domain/core.ts +382 -0
  75. package/src/domain/index.ts +271 -641
  76. package/src/domain/types.ts +59 -0
  77. package/src/index.ts +221 -207
  78. package/src/utils/crypto-utils.ts +205 -2
  79. package/src/utils/error-parser.ts +2 -1
  80. package/src/utils/event-parser.ts +353 -0
  81. package/src/utils/tag-type-builder.ts +138 -0
  82. package/src/utils/tag-type-parser.ts +246 -0
  83. package/tests/unit/crypto-utils.test.ts +338 -0
  84. package/tests/unit/ed25519-jwk.test.ts +201 -0
  85. package/tests/unit/event-parser.test.ts +690 -0
  86. package/tests/unit/generate-mnemonic.test.ts +268 -0
  87. package/tests/unit/olares-id-format.test.ts +321 -0
  88. package/tests/unit/tag-type-parser.test.ts +802 -0
  89. package/tests/unit/tag-types.test.ts +821 -0
  90. package/tsconfig.json +3 -2
  91. package/dist/abi/ABITypeABI.d.ts +0 -88
  92. package/dist/abi/ABITypeABI.d.ts.map +0 -1
  93. package/dist/abi/ABITypeABI.js +0 -382
  94. package/dist/abi/ABITypeABI.js.map +0 -1
  95. package/dist/abi/RegistryABI.d.ts +0 -77
  96. package/dist/abi/RegistryABI.d.ts.map +0 -1
  97. package/dist/abi/RegistryABI.js +0 -462
  98. package/dist/abi/RegistryABI.js.map +0 -1
  99. package/dist/tag/address.d.ts +0 -11
  100. package/dist/tag/address.d.ts.map +0 -1
  101. package/dist/tag/address.js +0 -44
  102. package/dist/tag/address.js.map +0 -1
  103. package/dist/tag/array.d.ts +0 -14
  104. package/dist/tag/array.d.ts.map +0 -1
  105. package/dist/tag/array.js +0 -72
  106. package/dist/tag/array.js.map +0 -1
  107. package/dist/tag/bool.d.ts +0 -11
  108. package/dist/tag/bool.d.ts.map +0 -1
  109. package/dist/tag/bool.js +0 -43
  110. package/dist/tag/bool.js.map +0 -1
  111. package/dist/tag/bytes.d.ts +0 -11
  112. package/dist/tag/bytes.d.ts.map +0 -1
  113. package/dist/tag/bytes.js +0 -37
  114. package/dist/tag/bytes.js.map +0 -1
  115. package/dist/tag/flarray.d.ts +0 -15
  116. package/dist/tag/flarray.d.ts.map +0 -1
  117. package/dist/tag/flarray.js +0 -81
  118. package/dist/tag/flarray.js.map +0 -1
  119. package/dist/tag/flbytes.d.ts +0 -11
  120. package/dist/tag/flbytes.d.ts.map +0 -1
  121. package/dist/tag/flbytes.js +0 -47
  122. package/dist/tag/flbytes.js.map +0 -1
  123. package/dist/tag/index.d.ts +0 -32
  124. package/dist/tag/index.d.ts.map +0 -1
  125. package/dist/tag/index.js +0 -121
  126. package/dist/tag/index.js.map +0 -1
  127. package/dist/tag/int.d.ts +0 -12
  128. package/dist/tag/int.d.ts.map +0 -1
  129. package/dist/tag/int.js +0 -49
  130. package/dist/tag/int.js.map +0 -1
  131. package/dist/tag/string.d.ts +0 -11
  132. package/dist/tag/string.d.ts.map +0 -1
  133. package/dist/tag/string.js +0 -37
  134. package/dist/tag/string.js.map +0 -1
  135. package/dist/tag/tag.d.ts +0 -67
  136. package/dist/tag/tag.d.ts.map +0 -1
  137. package/dist/tag/tag.js +0 -157
  138. package/dist/tag/tag.js.map +0 -1
  139. package/dist/tag/tuple.d.ts +0 -17
  140. package/dist/tag/tuple.d.ts.map +0 -1
  141. package/dist/tag/tuple.js +0 -162
  142. package/dist/tag/tuple.js.map +0 -1
  143. package/dist/tag/uint.d.ts +0 -12
  144. package/dist/tag/uint.d.ts.map +0 -1
  145. package/dist/tag/uint.js +0 -49
  146. package/dist/tag/uint.js.map +0 -1
  147. package/dist/test/did.d.ts +0 -2
  148. package/dist/test/did.d.ts.map +0 -1
  149. package/dist/test/did.js +0 -177
  150. package/dist/test/did.js.map +0 -1
  151. package/dist/utils/tag-abi-codec.d.ts +0 -69
  152. package/dist/utils/tag-abi-codec.d.ts.map +0 -1
  153. package/dist/utils/tag-abi-codec.js +0 -144
  154. package/dist/utils/tag-abi-codec.js.map +0 -1
  155. package/examples/crypto-utilities.ts +0 -140
  156. package/examples/ed25519-jwk.ts +0 -73
  157. package/examples/generate-mnemonic.ts +0 -149
  158. package/examples/legacy.ts +0 -33
  159. package/examples/olares-id-format.ts +0 -197
  160. package/examples/tag-builder.ts +0 -235
  161. package/examples/tag-nested-tuple.ts +0 -190
  162. package/examples/tag-simple.ts +0 -149
  163. package/examples/tag-tagger.ts +0 -217
  164. package/examples/test-nested-tuple-conversion.ts +0 -143
  165. package/examples/test-type-bytes-parser.ts +0 -70
  166. package/src/abi/ABITypeABI.ts +0 -379
  167. package/src/abi/RegistryABI.ts +0 -459
  168. package/src/tag/address.ts +0 -48
  169. package/src/tag/array.ts +0 -80
  170. package/src/tag/bool.ts +0 -43
  171. package/src/tag/bytes.ts +0 -38
  172. package/src/tag/flarray.ts +0 -99
  173. package/src/tag/flbytes.ts +0 -48
  174. package/src/tag/index.ts +0 -170
  175. package/src/tag/int.ts +0 -51
  176. package/src/tag/string.ts +0 -38
  177. package/src/tag/tag.ts +0 -229
  178. package/src/tag/tuple.ts +0 -193
  179. package/src/tag/uint.ts +0 -51
  180. package/src/test/did.ts +0 -346
  181. package/src/utils/tag-abi-codec.ts +0 -158
@@ -1,13 +1,15 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
+ import configData from '../../config.json';
3
4
 
4
5
  export interface NetworkConfig {
5
6
  rpc: string;
6
7
  contractDid: string;
7
8
  contractRootResolver: string;
8
9
  contractAbiType: string;
9
- contractRootResolver2: string;
10
- supportSvcUrl: string;
10
+ contractRootResolver2?: string;
11
+ supportSvcUrl?: string;
12
+ queryContract?: string;
11
13
  }
12
14
 
13
15
  export interface ConfigData {
@@ -16,34 +18,31 @@ export interface ConfigData {
16
18
  };
17
19
  }
18
20
 
19
- // Configuration file path - in the project directory
20
- const CONFIG_FILE = path.join(__dirname, '../../config.json');
21
+ export interface ContractConfig {
22
+ contractDid: string;
23
+ contractRootResolver: string;
24
+ contractAbiType: string;
25
+ contractRootResolver2?: string;
26
+ }
21
27
 
22
28
  /**
23
- * Load configuration from file
29
+ * Load configuration from imported JSON
30
+ * Works in both Node.js and browser environments
24
31
  */
25
32
  export function loadConfig(): ConfigData {
26
- try {
27
- const data = fs.readFileSync(CONFIG_FILE, 'utf-8');
28
- return JSON.parse(data);
29
- } catch (error) {
30
- throw new Error(
31
- `Failed to load config from ${CONFIG_FILE}: ${
32
- error instanceof Error ? error.message : String(error)
33
- }`
34
- );
35
- }
33
+ return configData;
36
34
  }
37
35
 
38
36
  /**
39
- * Save configuration to file
37
+ * Save configuration to file (Node.js only)
40
38
  */
41
39
  export function saveConfig(config: ConfigData): void {
42
40
  try {
43
- fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
41
+ const configPath = path.join(__dirname, '../../config.json');
42
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 4));
44
43
  } catch (error) {
45
44
  throw new Error(
46
- `Failed to save config to ${CONFIG_FILE}: ${
45
+ `Failed to save config: ${
47
46
  error instanceof Error ? error.message : String(error)
48
47
  }`
49
48
  );
@@ -70,5 +69,21 @@ export function getAvailableNetworks(): string[] {
70
69
  * Get configuration file path
71
70
  */
72
71
  export function getConfigFilePath(): string {
73
- return CONFIG_FILE;
72
+ return path.join(__dirname, '../../config.json');
73
+ }
74
+
75
+ /**
76
+ * Get contract configuration for a specific network
77
+ */
78
+ export function getContractConfig(network: string): ContractConfig | undefined {
79
+ const networkConfig = getNetworkConfig(network);
80
+ if (!networkConfig) {
81
+ return undefined;
82
+ }
83
+ return {
84
+ contractDid: networkConfig.contractDid,
85
+ contractRootResolver: networkConfig.contractRootResolver,
86
+ contractAbiType: networkConfig.contractAbiType,
87
+ contractRootResolver2: networkConfig.contractRootResolver2
88
+ };
74
89
  }
@@ -0,0 +1,382 @@
1
+ import { ethers, AbiCoder } from 'ethers';
2
+ import QUERY_CONTRACT_ABI from '../abi/TerminusDIDQueryABI';
3
+ import { RawDomainData, RawTagData, FlatDomainData, Tag } from './types';
4
+ import { TagTypeParser } from '../utils/tag-type-parser';
5
+ import { parseContractError } from '../utils/error-parser';
6
+ import { getFullyQualifiedVerificationMethodID } from '../utils/crypto-utils';
7
+
8
+ // ============================================================================
9
+ // Query Contract Helper
10
+ // ============================================================================
11
+
12
+ /**
13
+ * Helper class for interacting with TerminusDIDQuery contract
14
+ */
15
+ export class QueryContractHelper {
16
+ private contract: ethers.Contract;
17
+
18
+ constructor(queryContractAddress: string, provider: ethers.Provider) {
19
+ this.contract = new ethers.Contract(
20
+ queryContractAddress,
21
+ QUERY_CONTRACT_ABI,
22
+ provider
23
+ );
24
+ }
25
+
26
+ /**
27
+ * Get total supply of domains
28
+ */
29
+ async getTotalSupply(): Promise<number> {
30
+ try {
31
+ const supply = await this.contract.totalSupply();
32
+ return Number(supply);
33
+ } catch (error) {
34
+ const parsedError = parseContractError(error);
35
+ console.error('[core] getTotalSupply failed:', parsedError.message);
36
+ throw new Error(parsedError.message);
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Get token ID by index
42
+ */
43
+ async getTokenByIndex(index: number): Promise<bigint> {
44
+ try {
45
+ return await this.contract.tokenByIndex(index);
46
+ } catch (error) {
47
+ const parsedError = parseContractError(error);
48
+ console.error(
49
+ '[core] getTokenByIndex failed:',
50
+ parsedError.message
51
+ );
52
+ throw new Error(parsedError.message);
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Get single domain data by token ID or domain name
58
+ * Automatically detects if input is tokenId (starts with 0x or is bigint) or domain name
59
+ */
60
+ async getDomain(
61
+ nameOrTokenId: string | bigint
62
+ ): Promise<RawDomainData | null> {
63
+ try {
64
+ let data: any;
65
+
66
+ // Check if it's a tokenId (hex string or bigint)
67
+ if (
68
+ typeof nameOrTokenId === 'bigint' ||
69
+ (typeof nameOrTokenId === 'string' &&
70
+ nameOrTokenId.startsWith('0x'))
71
+ ) {
72
+ data = await this.contract.getDomain(nameOrTokenId);
73
+ } else {
74
+ // It's a domain name
75
+ data = await this.contract.getDomainByName(nameOrTokenId);
76
+ }
77
+
78
+ return this._convertDomainData(data);
79
+ } catch (error) {
80
+ const parsedError = parseContractError(error);
81
+ console.error('[core] getDomain failed:', parsedError.message);
82
+ throw new Error(parsedError.message);
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Get multiple domains data by token IDs
88
+ */
89
+ async getDomains(tokenIds: (string | bigint)[]): Promise<RawDomainData[]> {
90
+ try {
91
+ const data = await this.contract.getDomains(tokenIds);
92
+ return data.map((d: any) => this._convertDomainData(d));
93
+ } catch (error) {
94
+ const parsedError = parseContractError(error);
95
+ console.error('[core] getDomains failed:', parsedError.message);
96
+ throw new Error(parsedError.message);
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Get all domains with pagination
102
+ */
103
+ async getAllDomains(
104
+ offset: number,
105
+ limit: number
106
+ ): Promise<RawDomainData[]> {
107
+ try {
108
+ const data = await this.contract.getAllDomains(offset, limit);
109
+ return data.map((d: any) => this._convertDomainData(d));
110
+ } catch (error) {
111
+ const parsedError = parseContractError(error);
112
+ console.error('[core] getAllDomains failed:', parsedError.message);
113
+ throw new Error(parsedError.message);
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Get domains by specific indices (single RPC call)
119
+ */
120
+ async getDomainsByIndices(indices: number[]): Promise<RawDomainData[]> {
121
+ try {
122
+ const data = await this.contract.getDomainsByIndices(indices);
123
+ return data.map((d: any) => this._convertDomainData(d));
124
+ } catch (error) {
125
+ const parsedError = parseContractError(error);
126
+ console.error(
127
+ '[core] getDomainsByIndices failed:',
128
+ parsedError.message
129
+ );
130
+ throw new Error(parsedError.message);
131
+ }
132
+ }
133
+
134
+ /**
135
+ * Convert contract response to RawDomainData
136
+ */
137
+ private _convertDomainData(data: any): RawDomainData {
138
+ return {
139
+ tokenId: data.tokenId,
140
+ domain: data.domain,
141
+ did: data.did,
142
+ notes: data.notes,
143
+ allowSubdomain: data.allowSubdomain,
144
+ owner: data.owner,
145
+ tags: data.tags.map((tag: any) => ({
146
+ from: tag.from,
147
+ name: tag.name,
148
+ // ethers v6 returns bytes fields as Uint8Array; normalize to hex string
149
+ abiType: ethers.hexlify(tag.abiType ?? '0x'),
150
+ value: ethers.hexlify(tag.value ?? '0x'),
151
+ fieldNamesHash: tag.fieldNamesHash
152
+ }))
153
+ };
154
+ }
155
+ }
156
+
157
+ // ============================================================================
158
+ // Field Names Fetching
159
+ // ============================================================================
160
+
161
+ /**
162
+ * Collect all unique field name hashes from raw domains
163
+ */
164
+ export function collectFieldNameHashes(rawDomains: RawDomainData[]): string[] {
165
+ const hashSet = new Set<string>();
166
+
167
+ for (const domain of rawDomains) {
168
+ for (const tag of domain.tags) {
169
+ for (const hash of tag.fieldNamesHash) {
170
+ if (
171
+ hash &&
172
+ hash !==
173
+ '0x0000000000000000000000000000000000000000000000000000000000000000'
174
+ ) {
175
+ hashSet.add(hash);
176
+ }
177
+ }
178
+ }
179
+ }
180
+
181
+ return Array.from(hashSet);
182
+ }
183
+
184
+ /**
185
+ * Batch fetch field names from blockchain events
186
+ * Groups queries by block number to minimize RPC calls
187
+ */
188
+ export async function batchFetchFieldNames(
189
+ contract: ethers.Contract,
190
+ hashes: string[]
191
+ ): Promise<Map<string, string[]>> {
192
+ const fieldNamesMap = new Map<string, string[]>();
193
+
194
+ if (hashes.length === 0) {
195
+ return fieldNamesMap;
196
+ }
197
+
198
+ // Step 1: Batch get block numbers for all hashes
199
+ const hashToBlock = new Map<string, number>();
200
+ for (const hash of hashes) {
201
+ try {
202
+ const blockNum = await contract.getFieldNamesEventBlock(hash);
203
+ hashToBlock.set(hash, Number(blockNum));
204
+ } catch (error) {
205
+ console.error(
206
+ `[core] failed to get block number for field name hash ${hash}:`,
207
+ error instanceof Error ? error.message : String(error)
208
+ );
209
+ throw error;
210
+ }
211
+ }
212
+
213
+ // Step 2: Group hashes by block number
214
+ const blockToHashes = new Map<number, string[]>();
215
+ for (const [hash, blockNum] of hashToBlock.entries()) {
216
+ if (!blockToHashes.has(blockNum)) {
217
+ blockToHashes.set(blockNum, []);
218
+ }
219
+ blockToHashes.get(blockNum)!.push(hash);
220
+ }
221
+
222
+ // Step 3: Batch query events by block
223
+ for (const [blockNum, hashesInBlock] of blockToHashes.entries()) {
224
+ try {
225
+ const events = await contract.queryFilter(
226
+ 'OffchainStringArray',
227
+ blockNum,
228
+ blockNum
229
+ );
230
+
231
+ for (const event of events) {
232
+ const eventHash = (event as any).args.hash;
233
+ if (hashesInBlock.includes(eventHash)) {
234
+ fieldNamesMap.set(eventHash, (event as any).args.value);
235
+ }
236
+ }
237
+ } catch (error) {
238
+ console.error(
239
+ `[core] failed to query events at block ${blockNum} for ${hashesInBlock.length} hashes:`,
240
+ error instanceof Error ? error.message : String(error)
241
+ );
242
+ throw error;
243
+ }
244
+ }
245
+
246
+ return fieldNamesMap;
247
+ }
248
+
249
+ // ============================================================================
250
+ // Domain Parsing
251
+ // ============================================================================
252
+
253
+ /**
254
+ * Parse a single raw domain into flat domain data
255
+ */
256
+ export function parseDomain(
257
+ rawDomain: RawDomainData,
258
+ fieldNamesMap: Map<string, string[]>
259
+ ): FlatDomainData {
260
+ const abiCoder = new AbiCoder();
261
+
262
+ const domain: FlatDomainData = {
263
+ id: ethers.toBeHex(rawDomain.tokenId),
264
+ name: rawDomain.domain,
265
+ did: rawDomain.did,
266
+ note: rawDomain.notes,
267
+ allowSubdomain: rawDomain.allowSubdomain,
268
+ owner: ethers.getAddress(rawDomain.owner),
269
+ level: calculateLevel(rawDomain.domain),
270
+ tags: []
271
+ };
272
+
273
+ // Parse tags
274
+ for (const rawTag of rawDomain.tags) {
275
+ try {
276
+ const tag = TagTypeParser.parseTag(rawTag, fieldNamesMap, abiCoder);
277
+ domain.tags.push(tag);
278
+ } catch (error) {
279
+ console.error(`[core] failed to parse tag ${rawTag.name}:`, error);
280
+ throw error;
281
+ }
282
+ }
283
+
284
+ applyLatestDID(domain);
285
+
286
+ return domain;
287
+ }
288
+
289
+ /**
290
+ * Parse all raw domains into flat domain data
291
+ * Also calculates parentId for each domain
292
+ */
293
+ export function parseAllDomains(
294
+ rawDomains: RawDomainData[],
295
+ fieldNamesMap: Map<string, string[]>
296
+ ): FlatDomainData[] {
297
+ const abiCoder = new AbiCoder();
298
+
299
+ // Step 1: Create all domain objects
300
+ const domainMap = new Map<string, FlatDomainData>();
301
+
302
+ for (const rawDomain of rawDomains) {
303
+ const domain: FlatDomainData = {
304
+ id: ethers.toBeHex(rawDomain.tokenId),
305
+ name: rawDomain.domain,
306
+ did: rawDomain.did,
307
+ note: rawDomain.notes,
308
+ allowSubdomain: rawDomain.allowSubdomain,
309
+ owner: ethers.getAddress(rawDomain.owner),
310
+ level: calculateLevel(rawDomain.domain),
311
+ tags: []
312
+ };
313
+
314
+ // Parse tags
315
+ for (const rawTag of rawDomain.tags) {
316
+ try {
317
+ const tag = TagTypeParser.parseTag(
318
+ rawTag,
319
+ fieldNamesMap,
320
+ abiCoder
321
+ );
322
+ domain.tags.push(tag);
323
+ } catch (error) {
324
+ console.error(
325
+ `[core] failed to parse tag ${rawTag.name}:`,
326
+ error
327
+ );
328
+ throw error;
329
+ }
330
+ }
331
+
332
+ applyLatestDID(domain);
333
+
334
+ domainMap.set(domain.name, domain);
335
+ }
336
+
337
+ // Step 2: Calculate parentId
338
+ const flatDomains: FlatDomainData[] = [];
339
+ for (const domain of domainMap.values()) {
340
+ if (domain.level > 1) {
341
+ const parentName = getParentDomainName(domain.name);
342
+ const parent = domainMap.get(parentName);
343
+ if (parent) {
344
+ domain.parentId = parent.id;
345
+ }
346
+ }
347
+ flatDomains.push(domain);
348
+ }
349
+
350
+ return flatDomains;
351
+ }
352
+
353
+ // ============================================================================
354
+ // Utility Functions
355
+ // ============================================================================
356
+
357
+ /**
358
+ * If a domain has a latestDID tag with a non-empty valueFormatted, override did with it
359
+ */
360
+ function applyLatestDID(domain: FlatDomainData): void {
361
+ const latestDIDTag = domain.tags.find((t) => t.name === 'latestDID');
362
+ if (latestDIDTag?.valueFormatted) {
363
+ domain.did = getFullyQualifiedVerificationMethodID(
364
+ latestDIDTag.valueFormatted
365
+ );
366
+ }
367
+ }
368
+
369
+ /**
370
+ * Calculate domain level (number of dots + 1)
371
+ */
372
+ export function calculateLevel(domainName: string): number {
373
+ return domainName.split('.').length;
374
+ }
375
+
376
+ /**
377
+ * Get parent domain name
378
+ */
379
+ export function getParentDomainName(domainName: string): string {
380
+ const parts = domainName.split('.');
381
+ return parts.slice(1).join('.');
382
+ }