neozip-blockchain 0.5.1

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 (127) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +189 -0
  3. package/dist/browser/TokenVerifierBrowser.d.ts +63 -0
  4. package/dist/browser/TokenVerifierBrowser.d.ts.map +1 -0
  5. package/dist/browser/TokenVerifierBrowser.js +358 -0
  6. package/dist/browser/TokenVerifierBrowser.js.map +1 -0
  7. package/dist/browser/WalletManagerBrowser.d.ts +99 -0
  8. package/dist/browser/WalletManagerBrowser.d.ts.map +1 -0
  9. package/dist/browser/WalletManagerBrowser.js +434 -0
  10. package/dist/browser/WalletManagerBrowser.js.map +1 -0
  11. package/dist/browser/ZipkitMinterBrowser.d.ts +59 -0
  12. package/dist/browser/ZipkitMinterBrowser.d.ts.map +1 -0
  13. package/dist/browser/ZipkitMinterBrowser.js +337 -0
  14. package/dist/browser/ZipkitMinterBrowser.js.map +1 -0
  15. package/dist/browser/index.d.ts +12 -0
  16. package/dist/browser/index.d.ts.map +1 -0
  17. package/dist/browser/index.js +19 -0
  18. package/dist/browser/index.js.map +1 -0
  19. package/dist/constants/index.d.ts +5 -0
  20. package/dist/constants/index.d.ts.map +1 -0
  21. package/dist/constants/index.js +21 -0
  22. package/dist/constants/index.js.map +1 -0
  23. package/dist/constants/metadata.d.ts +18 -0
  24. package/dist/constants/metadata.d.ts.map +1 -0
  25. package/dist/constants/metadata.js +33 -0
  26. package/dist/constants/metadata.js.map +1 -0
  27. package/dist/constants/servers.d.ts +38 -0
  28. package/dist/constants/servers.d.ts.map +1 -0
  29. package/dist/constants/servers.js +54 -0
  30. package/dist/constants/servers.js.map +1 -0
  31. package/dist/core/ContractVersionRegistry.d.ts +63 -0
  32. package/dist/core/ContractVersionRegistry.d.ts.map +1 -0
  33. package/dist/core/ContractVersionRegistry.js +128 -0
  34. package/dist/core/ContractVersionRegistry.js.map +1 -0
  35. package/dist/core/WalletManager.d.ts +123 -0
  36. package/dist/core/WalletManager.d.ts.map +1 -0
  37. package/dist/core/WalletManager.js +360 -0
  38. package/dist/core/WalletManager.js.map +1 -0
  39. package/dist/core/ZipkitMinter.d.ts +155 -0
  40. package/dist/core/ZipkitMinter.d.ts.map +1 -0
  41. package/dist/core/ZipkitMinter.js +625 -0
  42. package/dist/core/ZipkitMinter.js.map +1 -0
  43. package/dist/core/ZipkitVerifier.d.ts +252 -0
  44. package/dist/core/ZipkitVerifier.d.ts.map +1 -0
  45. package/dist/core/ZipkitVerifier.js +1370 -0
  46. package/dist/core/ZipkitVerifier.js.map +1 -0
  47. package/dist/core/adapters/AdapterFactory.d.ts +22 -0
  48. package/dist/core/adapters/AdapterFactory.d.ts.map +1 -0
  49. package/dist/core/adapters/AdapterFactory.js +62 -0
  50. package/dist/core/adapters/AdapterFactory.js.map +1 -0
  51. package/dist/core/adapters/ContractVersionAdapter.d.ts +96 -0
  52. package/dist/core/adapters/ContractVersionAdapter.d.ts.map +1 -0
  53. package/dist/core/adapters/ContractVersionAdapter.js +9 -0
  54. package/dist/core/adapters/ContractVersionAdapter.js.map +1 -0
  55. package/dist/core/adapters/V2_10Adapter.d.ts +24 -0
  56. package/dist/core/adapters/V2_10Adapter.d.ts.map +1 -0
  57. package/dist/core/adapters/V2_10Adapter.js +87 -0
  58. package/dist/core/adapters/V2_10Adapter.js.map +1 -0
  59. package/dist/core/adapters/V2_11Adapter.d.ts +24 -0
  60. package/dist/core/adapters/V2_11Adapter.d.ts.map +1 -0
  61. package/dist/core/adapters/V2_11Adapter.js +87 -0
  62. package/dist/core/adapters/V2_11Adapter.js.map +1 -0
  63. package/dist/core/adapters/V2_50Adapter.d.ts +105 -0
  64. package/dist/core/adapters/V2_50Adapter.d.ts.map +1 -0
  65. package/dist/core/adapters/V2_50Adapter.js +263 -0
  66. package/dist/core/adapters/V2_50Adapter.js.map +1 -0
  67. package/dist/core/contracts.d.ts +169 -0
  68. package/dist/core/contracts.d.ts.map +1 -0
  69. package/dist/core/contracts.js +731 -0
  70. package/dist/core/contracts.js.map +1 -0
  71. package/dist/core/index.d.ts +24 -0
  72. package/dist/core/index.d.ts.map +1 -0
  73. package/dist/core/index.js +82 -0
  74. package/dist/core/index.js.map +1 -0
  75. package/dist/index.d.ts +15 -0
  76. package/dist/index.d.ts.map +1 -0
  77. package/dist/index.js +40 -0
  78. package/dist/index.js.map +1 -0
  79. package/dist/node/WalletManagerNode.d.ts +47 -0
  80. package/dist/node/WalletManagerNode.d.ts.map +1 -0
  81. package/dist/node/WalletManagerNode.js +203 -0
  82. package/dist/node/WalletManagerNode.js.map +1 -0
  83. package/dist/node/index.d.ts +12 -0
  84. package/dist/node/index.d.ts.map +1 -0
  85. package/dist/node/index.js +21 -0
  86. package/dist/node/index.js.map +1 -0
  87. package/dist/ots/ZipkitOTS.d.ts +152 -0
  88. package/dist/ots/ZipkitOTS.d.ts.map +1 -0
  89. package/dist/ots/ZipkitOTS.js +544 -0
  90. package/dist/ots/ZipkitOTS.js.map +1 -0
  91. package/dist/ots/index.d.ts +9 -0
  92. package/dist/ots/index.d.ts.map +1 -0
  93. package/dist/ots/index.js +25 -0
  94. package/dist/ots/index.js.map +1 -0
  95. package/dist/types/index.d.ts +84 -0
  96. package/dist/types/index.d.ts.map +1 -0
  97. package/dist/types/index.js +37 -0
  98. package/dist/types/index.js.map +1 -0
  99. package/dist/utils/Logger.d.ts +73 -0
  100. package/dist/utils/Logger.d.ts.map +1 -0
  101. package/dist/utils/Logger.js +156 -0
  102. package/dist/utils/Logger.js.map +1 -0
  103. package/dist/utils/crc32.d.ts +6 -0
  104. package/dist/utils/crc32.d.ts.map +1 -0
  105. package/dist/utils/crc32.js +23 -0
  106. package/dist/utils/crc32.js.map +1 -0
  107. package/dist/zipstamp-server/CalendarManager.d.ts +238 -0
  108. package/dist/zipstamp-server/CalendarManager.d.ts.map +1 -0
  109. package/dist/zipstamp-server/CalendarManager.js +364 -0
  110. package/dist/zipstamp-server/CalendarManager.js.map +1 -0
  111. package/dist/zipstamp-server/ZipstampServerAPI.d.ts +427 -0
  112. package/dist/zipstamp-server/ZipstampServerAPI.d.ts.map +1 -0
  113. package/dist/zipstamp-server/ZipstampServerAPI.js +696 -0
  114. package/dist/zipstamp-server/ZipstampServerAPI.js.map +1 -0
  115. package/dist/zipstamp-server/ZipstampServerClient.d.ts +590 -0
  116. package/dist/zipstamp-server/ZipstampServerClient.d.ts.map +1 -0
  117. package/dist/zipstamp-server/ZipstampServerClient.js +433 -0
  118. package/dist/zipstamp-server/ZipstampServerClient.js.map +1 -0
  119. package/dist/zipstamp-server/ZipstampServerHelpers.d.ts +430 -0
  120. package/dist/zipstamp-server/ZipstampServerHelpers.d.ts.map +1 -0
  121. package/dist/zipstamp-server/ZipstampServerHelpers.js +503 -0
  122. package/dist/zipstamp-server/ZipstampServerHelpers.js.map +1 -0
  123. package/dist/zipstamp-server/index.d.ts +24 -0
  124. package/dist/zipstamp-server/index.d.ts.map +1 -0
  125. package/dist/zipstamp-server/index.js +67 -0
  126. package/dist/zipstamp-server/index.js.map +1 -0
  127. package/package.json +117 -0
@@ -0,0 +1,1370 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ /**
4
+ * ZipkitVerifier - Handles NFT token verification for ZIP files
5
+ * Verifier API for NZIP-NFT smart contract.
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.ZipkitVerifier = void 0;
9
+ const ethers_1 = require("ethers");
10
+ const contracts_1 = require("./contracts");
11
+ const AdapterFactory_1 = require("./adapters/AdapterFactory");
12
+ const ContractVersionRegistry_1 = require("./ContractVersionRegistry");
13
+ /**
14
+ * Blockchain Token Verifier for ZIP files
15
+ */
16
+ class ZipkitVerifier {
17
+ constructor(options = {}) {
18
+ this.debug = options.debug || false;
19
+ this.maxRetries = options.maxRetries || 3;
20
+ this.retryDelay = options.retryDelay || 1000;
21
+ this.batchDelay = options.batchDelay || 500;
22
+ if (this.debug) {
23
+ console.log(`[DEBUG] ZipkitVerifier initialized`);
24
+ }
25
+ }
26
+ /**
27
+ * Extracts and normalizes token metadata from a tokenized ZIP archive.
28
+ *
29
+ * Handles field name variations across different token versions:
30
+ * - chainId vs networkChainId
31
+ * - timestamp vs mintedAt
32
+ * - Validates required fields
33
+ *
34
+ * @param zipkit - ZipkitNode instance with loaded archive
35
+ * @returns Normalized TokenMetadata or null if not tokenized
36
+ * @throws Error if TOKEN file exists but is invalid
37
+ *
38
+ * @example
39
+ * ```typescript
40
+ * import { ZipkitNode } from 'neozipkit/node';
41
+ * import { ZipkitVerifier } from 'neozipkit/blockchain';
42
+ *
43
+ * const zipkit = new ZipkitNode();
44
+ * await zipkit.loadZipFile('archive.nzip');
45
+ * const metadata = await ZipkitVerifier.extractTokenMetadata(zipkit);
46
+ * if (metadata) {
47
+ * console.log(`Token ID: ${metadata.tokenId}`);
48
+ * console.log(`Network: ${metadata.network}`);
49
+ * }
50
+ * ```
51
+ */
52
+ static async extractTokenMetadata(zipkit) {
53
+ try {
54
+ // Check for META-INF/TOKEN.NZIP entry (new standard)
55
+ let tokenEntry = zipkit.getZipEntry(contracts_1.TOKENIZED_METADATA);
56
+ // Fallback to legacy META-INF/NZIP.TOKEN for backward compatibility
57
+ if (!tokenEntry) {
58
+ tokenEntry = zipkit.getZipEntry(contracts_1.TOKENIZED_METADATA_LEGACY);
59
+ if (tokenEntry) {
60
+ console.warn('[ZipkitVerifier] Reading legacy NZIP.TOKEN. Write new files as TOKEN.NZIP.');
61
+ }
62
+ }
63
+ if (!tokenEntry) {
64
+ return null;
65
+ }
66
+ // Extract token data to buffer
67
+ const tokenDataBuffer = await zipkit.extractToBuffer(tokenEntry, {
68
+ skipHashCheck: false
69
+ });
70
+ const tokenData = tokenDataBuffer.toString('utf-8');
71
+ const rawMetadata = JSON.parse(tokenData);
72
+ // Normalize field names for compatibility across versions
73
+ const chainId = rawMetadata.chainId ?? rawMetadata.networkChainId;
74
+ const timestamp = rawMetadata.timestamp ?? rawMetadata.mintedAt;
75
+ // SECURITY: networkChainId is REQUIRED - cannot infer from network name (ambiguous)
76
+ // Network names like "sepolia" could match multiple networks (Base Sepolia, Ethereum Sepolia, Arbitrum Sepolia)
77
+ let networkChainId = chainId;
78
+ let contractVersion = rawMetadata.contractVersion;
79
+ // Validate required fields - chainId is mandatory for security
80
+ if (!networkChainId) {
81
+ throw new Error('Invalid token metadata: missing required field "networkChainId". Network names are ambiguous and cannot be used for verification. Please ensure token metadata includes networkChainId.');
82
+ }
83
+ // contractVersion: Do NOT infer from network config because:
84
+ // 1. Old v2.10 files never had contractVersion in TOKEN file
85
+ // 2. Network configs may have been updated to newer contract versions
86
+ // 3. Verification will try all versions automatically if version is 'unknown'
87
+ if (!contractVersion) {
88
+ console.warn(`[WARNING] TOKEN file missing contractVersion (common for v2.10 files) - verification will try all versions automatically`);
89
+ contractVersion = 'unknown';
90
+ }
91
+ // Create normalized metadata object
92
+ const metadata = {
93
+ tokenId: rawMetadata.tokenId,
94
+ contractAddress: rawMetadata.contractAddress,
95
+ network: rawMetadata.network,
96
+ networkChainId: networkChainId, // Required - now guaranteed to be set
97
+ contractVersion: contractVersion, // Required - now guaranteed to be set
98
+ merkleRoot: rawMetadata.merkleRoot,
99
+ creationTimestamp: rawMetadata.blockNumber || rawMetadata.creationTimestamp,
100
+ transactionHash: rawMetadata.transactionHash,
101
+ owner: rawMetadata.owner || rawMetadata.ownerAddress,
102
+ encryptedHash: rawMetadata.encryptedHash,
103
+ mintedAt: rawMetadata.mintedAt || rawMetadata.mintDate,
104
+ mintDate: rawMetadata.mintDate || rawMetadata.mintedAt,
105
+ ipfsHash: rawMetadata.ipfsHash,
106
+ blockNumber: rawMetadata.blockNumber
107
+ };
108
+ // Validate other required fields exist
109
+ if (!metadata.tokenId || !metadata.contractAddress || !metadata.merkleRoot) {
110
+ throw new Error('Invalid token metadata: missing required fields (tokenId, contractAddress, or merkleRoot)');
111
+ }
112
+ // SECURITY: Validate input formats to prevent injection attacks and invalid data processing
113
+ // Validate token ID format
114
+ const tokenIdValidation = (0, contracts_1.validateTokenId)(metadata.tokenId);
115
+ if (!tokenIdValidation.success) {
116
+ throw new Error(`Invalid token ID: ${tokenIdValidation.error}`);
117
+ }
118
+ // Validate contract address format
119
+ const addressValidation = (0, contracts_1.validateEthereumAddress)(metadata.contractAddress);
120
+ if (!addressValidation.success) {
121
+ throw new Error(`Invalid contract address: ${addressValidation.error}`);
122
+ }
123
+ // Use normalized address (checksum format)
124
+ if (addressValidation.normalizedAddress) {
125
+ metadata.contractAddress = addressValidation.normalizedAddress;
126
+ }
127
+ // Sanitize network name
128
+ const networkValidation = (0, contracts_1.sanitizeNetworkName)(metadata.network);
129
+ if (!networkValidation.success) {
130
+ throw new Error(`Invalid network name: ${networkValidation.error}`);
131
+ }
132
+ if (networkValidation.sanitized) {
133
+ metadata.network = networkValidation.sanitized;
134
+ }
135
+ // Validate merkle root format
136
+ const merkleRootValidation = (0, contracts_1.validateMerkleRootFormat)(metadata.merkleRoot);
137
+ if (!merkleRootValidation.success) {
138
+ throw new Error(`Invalid merkle root format: ${merkleRootValidation.error}`);
139
+ }
140
+ // Use normalized merkle root (consistent format)
141
+ if (merkleRootValidation.normalized) {
142
+ metadata.merkleRoot = merkleRootValidation.normalized;
143
+ }
144
+ return metadata;
145
+ }
146
+ catch (error) {
147
+ if (error instanceof Error && error.message.includes('Invalid token metadata')) {
148
+ throw error;
149
+ }
150
+ // Return null for non-tokenized archives or extraction errors
151
+ return null;
152
+ }
153
+ }
154
+ /**
155
+ * Instance method for convenience - extracts token metadata from loaded archive
156
+ * @param zipkit - ZipkitNode instance with loaded archive
157
+ * @returns Normalized TokenMetadata or null if not tokenized
158
+ */
159
+ async getTokenMetadata(zipkit) {
160
+ return ZipkitVerifier.extractTokenMetadata(zipkit);
161
+ }
162
+ /**
163
+ * Query contract version from blockchain
164
+ * @param contractAddress - Contract address
165
+ * @param networkConfig - Network configuration with RPC URLs
166
+ * @param rpcUrlIndex - Optional RPC URL index (default: 0)
167
+ * @returns Contract version string or null if query fails
168
+ */
169
+ async queryContractVersion(contractAddress, networkConfig, rpcUrlIndex = 0) {
170
+ const rpcUrls = networkConfig.rpcUrls.length > 0 ? networkConfig.rpcUrls : [];
171
+ if (rpcUrls.length === 0 || rpcUrlIndex >= rpcUrls.length) {
172
+ return null;
173
+ }
174
+ const rpcUrl = rpcUrls[rpcUrlIndex];
175
+ let provider = null;
176
+ try {
177
+ provider = new ethers_1.ethers.JsonRpcProvider(rpcUrl);
178
+ const contract = new ethers_1.ethers.Contract(contractAddress, contracts_1.NZIP_CONTRACT_ABI, provider);
179
+ // Query getVersion() function with timeout
180
+ let timeoutId = null;
181
+ const version = await Promise.race([
182
+ contract.getVersion(),
183
+ new Promise((_, reject) => {
184
+ timeoutId = setTimeout(() => reject(new Error('getVersion timeout after 10 seconds')), 10000);
185
+ })
186
+ ]).finally(() => {
187
+ // Clear the timeout if it's still pending
188
+ if (timeoutId !== null) {
189
+ clearTimeout(timeoutId);
190
+ }
191
+ });
192
+ if (this.debug) {
193
+ console.log(`[DEBUG] Contract version queried from blockchain: ${version}`);
194
+ }
195
+ return version;
196
+ }
197
+ catch (error) {
198
+ if (this.debug) {
199
+ console.log(`[DEBUG] Failed to query contract version: ${error.message || String(error)}`);
200
+ }
201
+ return null;
202
+ }
203
+ finally {
204
+ try {
205
+ if (provider) {
206
+ provider.destroy();
207
+ }
208
+ }
209
+ catch (cleanupError) {
210
+ // Ignore cleanup errors
211
+ }
212
+ }
213
+ }
214
+ /**
215
+ * Extract and validate token metadata from ZIP
216
+ */
217
+ async extractTokenMetadata(tokenBuffer) {
218
+ try {
219
+ if (this.debug) {
220
+ console.log(`[DEBUG] Extracting token metadata from buffer (${tokenBuffer.length} bytes)`);
221
+ }
222
+ const tokenData = JSON.parse(tokenBuffer.toString('utf8'));
223
+ // Validate basic required fields exist
224
+ const requiredFields = ['tokenId', 'contractAddress', 'network', 'merkleRoot'];
225
+ for (const field of requiredFields) {
226
+ if (!tokenData[field]) {
227
+ if (this.debug) {
228
+ console.log(`[DEBUG] Missing required field: ${field}`);
229
+ }
230
+ return {
231
+ success: false,
232
+ error: `Missing required field: ${field}`
233
+ };
234
+ }
235
+ }
236
+ // SECURITY: Validate input formats to prevent injection attacks and invalid data processing
237
+ // Validate token ID format
238
+ const tokenIdValidation = (0, contracts_1.validateTokenId)(tokenData.tokenId);
239
+ if (!tokenIdValidation.success) {
240
+ if (this.debug) {
241
+ console.log(`[DEBUG] Token ID validation failed: ${tokenIdValidation.error}`);
242
+ }
243
+ return {
244
+ success: false,
245
+ error: `Invalid token ID format: ${tokenIdValidation.error}`
246
+ };
247
+ }
248
+ // Validate contract address format
249
+ const addressValidation = (0, contracts_1.validateEthereumAddress)(tokenData.contractAddress);
250
+ if (!addressValidation.success) {
251
+ if (this.debug) {
252
+ console.log(`[DEBUG] Contract address validation failed: ${addressValidation.error}`);
253
+ }
254
+ return {
255
+ success: false,
256
+ error: `Invalid contract address format: ${addressValidation.error}`
257
+ };
258
+ }
259
+ // Use normalized address (checksum format)
260
+ if (addressValidation.normalizedAddress) {
261
+ tokenData.contractAddress = addressValidation.normalizedAddress;
262
+ }
263
+ // Sanitize network name
264
+ const networkValidation = (0, contracts_1.sanitizeNetworkName)(tokenData.network);
265
+ if (!networkValidation.success) {
266
+ if (this.debug) {
267
+ console.log(`[DEBUG] Network name validation failed: ${networkValidation.error}`);
268
+ }
269
+ return {
270
+ success: false,
271
+ error: `Invalid network name: ${networkValidation.error}`
272
+ };
273
+ }
274
+ if (networkValidation.sanitized) {
275
+ tokenData.network = networkValidation.sanitized;
276
+ }
277
+ // Validate merkle root format
278
+ const merkleRootValidation = (0, contracts_1.validateMerkleRootFormat)(tokenData.merkleRoot);
279
+ if (!merkleRootValidation.success) {
280
+ if (this.debug) {
281
+ console.log(`[DEBUG] Merkle root validation failed: ${merkleRootValidation.error}`);
282
+ }
283
+ return {
284
+ success: false,
285
+ error: `Invalid merkle root format: ${merkleRootValidation.error}`
286
+ };
287
+ }
288
+ // Use normalized merkle root (consistent format)
289
+ if (merkleRootValidation.normalized) {
290
+ tokenData.merkleRoot = merkleRootValidation.normalized;
291
+ }
292
+ // SECURITY: networkChainId is REQUIRED - cannot infer from network name (ambiguous)
293
+ // Network names like "sepolia" could match multiple networks (Base Sepolia, Ethereum Sepolia, Arbitrum Sepolia)
294
+ let networkChainId = tokenData.networkChainId ?? tokenData.chainId;
295
+ let contractVersion = tokenData.contractVersion;
296
+ // Validate networkChainId (required for blockchain queries)
297
+ if (!networkChainId) {
298
+ return {
299
+ success: false,
300
+ error: 'Missing required field: networkChainId. Network names are ambiguous and cannot be used for verification. Please ensure token metadata includes networkChainId.'
301
+ };
302
+ }
303
+ // contractVersion: Do NOT infer from network config because:
304
+ // 1. Old v2.10 files never had contractVersion in TOKEN file
305
+ // 2. Network configs may have been updated to newer contract versions
306
+ // 3. Query from blockchain first, then let verification try all versions if needed
307
+ // If contractVersion is missing, try to query from blockchain
308
+ if (!contractVersion && networkChainId && tokenData.contractAddress) {
309
+ const networkConfig = (0, contracts_1.getContractConfig)(networkChainId);
310
+ if (networkConfig) {
311
+ if (this.debug) {
312
+ console.log(`[DEBUG] TOKEN file missing contractVersion - attempting to query from blockchain...`);
313
+ }
314
+ try {
315
+ const blockchainVersion = await this.queryContractVersion(tokenData.contractAddress, networkConfig, 0);
316
+ if (blockchainVersion) {
317
+ const normalized = (0, ContractVersionRegistry_1.normalizeVersion)(blockchainVersion);
318
+ if (normalized && (0, ContractVersionRegistry_1.isVersionSupported)(normalized)) {
319
+ contractVersion = normalized;
320
+ if (this.debug) {
321
+ console.log(`[DEBUG] Successfully queried contractVersion from blockchain: ${contractVersion}`);
322
+ }
323
+ }
324
+ else {
325
+ if (this.debug) {
326
+ console.log(`[DEBUG] Queried version ${blockchainVersion} (normalized: ${normalized}) is not supported`);
327
+ }
328
+ }
329
+ }
330
+ }
331
+ catch (queryError) {
332
+ if (this.debug) {
333
+ console.log(`[DEBUG] Failed to query contract version from blockchain: ${queryError.message || String(queryError)}`);
334
+ }
335
+ // Continue - we'll try multiple versions during verification
336
+ }
337
+ }
338
+ }
339
+ // Final validation - contractVersion is preferred but not strictly required
340
+ // (we can try multiple versions during verification)
341
+ if (!contractVersion) {
342
+ if (this.debug) {
343
+ console.log(`[DEBUG] ⚠️ Contract version not found in TOKEN file or blockchain. Will try all versions during verification.`);
344
+ }
345
+ // Don't fail here - let verification try multiple versions
346
+ // But we still need a version for the metadata object, so use a placeholder
347
+ contractVersion = 'unknown';
348
+ }
349
+ // Create normalized metadata object
350
+ const metadata = {
351
+ tokenId: tokenData.tokenId,
352
+ contractAddress: tokenData.contractAddress,
353
+ network: tokenData.network,
354
+ networkChainId: networkChainId, // Required - now guaranteed to be set
355
+ contractVersion: contractVersion, // Required - now guaranteed to be set
356
+ merkleRoot: tokenData.merkleRoot,
357
+ transactionHash: tokenData.transactionHash,
358
+ blockNumber: tokenData.blockNumber,
359
+ owner: tokenData.owner || tokenData.ownerAddress,
360
+ encryptedHash: tokenData.encryptedHash,
361
+ mintedAt: tokenData.mintedAt || tokenData.mintDate,
362
+ mintDate: tokenData.mintDate || tokenData.mintedAt,
363
+ creationTimestamp: tokenData.creationTimestamp || tokenData.blockNumber,
364
+ ipfsHash: tokenData.ipfsHash
365
+ };
366
+ if (this.debug) {
367
+ console.log(`[DEBUG] Token metadata extracted successfully`);
368
+ console.log(`[DEBUG] Token ID: ${metadata.tokenId}, Network: ${metadata.network}, ChainId: ${metadata.networkChainId}, Version: ${metadata.contractVersion}`);
369
+ }
370
+ return {
371
+ success: true,
372
+ metadata
373
+ };
374
+ }
375
+ catch (error) {
376
+ const errorMessage = error instanceof Error ? error.message : 'Invalid JSON format';
377
+ if (this.debug) {
378
+ console.log(`[DEBUG] Failed to parse token metadata: ${errorMessage}`);
379
+ }
380
+ return {
381
+ success: false,
382
+ error: `Invalid token metadata format: ${errorMessage}`
383
+ };
384
+ }
385
+ }
386
+ /**
387
+ * Map network name to RPC configuration using contracts.ts (single source of truth)
388
+ * REQUIRES chainId for security - network names are ambiguous and can match wrong networks
389
+ *
390
+ * @param networkName Network name (for logging/display purposes only)
391
+ * @param chainId Chain ID (REQUIRED - must be provided)
392
+ * @returns Network configuration or error if chainId is missing or invalid
393
+ */
394
+ getNetworkConfig(networkName, chainId) {
395
+ if (this.debug) {
396
+ if (chainId) {
397
+ console.log(`[DEBUG] Looking up network config by chainId: ${chainId} (network: ${networkName})`);
398
+ }
399
+ else {
400
+ console.log(`[DEBUG] SECURITY WARNING: chainId not provided for network: ${networkName}`);
401
+ }
402
+ }
403
+ // SECURITY: chainId is REQUIRED - network names are ambiguous and can match wrong networks
404
+ // For example, "sepolia" could match Base Sepolia, Ethereum Sepolia, or Arbitrum Sepolia
405
+ if (!chainId) {
406
+ return {
407
+ success: false,
408
+ error: `chainId is required for network identification. Network name "${networkName}" is ambiguous and cannot be used for verification. Please ensure token metadata includes networkChainId.`
409
+ };
410
+ }
411
+ // Use chainId directly (most reliable and secure)
412
+ const config = (0, contracts_1.getContractConfig)(chainId);
413
+ if (config && config.chainId === chainId) {
414
+ if (this.debug) {
415
+ console.log(`[DEBUG] Found config by chainId: ${config.network} (${config.chainId})`);
416
+ }
417
+ return { success: true, config };
418
+ }
419
+ // ChainId not found in contract configs
420
+ if (this.debug) {
421
+ console.log(`[DEBUG] ChainId ${chainId} not found in contract configs`);
422
+ }
423
+ return {
424
+ success: false,
425
+ error: `Unsupported chainId: ${chainId}. Network "${networkName}" with chainId ${chainId} is not configured.`
426
+ };
427
+ }
428
+ /**
429
+ * Perform merkle root comparison
430
+ * Uses direct string comparison - merkle roots are hex strings and must match exactly
431
+ * Case-sensitive comparison ensures no encoding issues or false positives
432
+ */
433
+ compareMerkleRoots(calculated, declared) {
434
+ if (this.debug) {
435
+ console.log(`[DEBUG] Comparing merkle roots (case-sensitive)`);
436
+ console.log(`[DEBUG] Calculated: ${calculated || 'null'}`);
437
+ console.log(`[DEBUG] Declared: ${declared || 'null'}`);
438
+ }
439
+ if (!calculated) {
440
+ return {
441
+ success: false,
442
+ match: false,
443
+ error: 'Cannot calculate merkle root from archive contents'
444
+ };
445
+ }
446
+ if (!declared) {
447
+ return {
448
+ success: false,
449
+ match: false,
450
+ calculated,
451
+ error: 'No merkle root found in token metadata'
452
+ };
453
+ }
454
+ // Direct string comparison - merkle roots are hex strings and must match exactly
455
+ // Case-sensitive comparison prevents encoding issues and false positives
456
+ const match = calculated === declared;
457
+ if (this.debug) {
458
+ console.log(`[DEBUG] Merkle root match: ${match}`);
459
+ if (!match) {
460
+ console.log(`[DEBUG] Mismatch details:`);
461
+ console.log(`[DEBUG] Calculated length: ${calculated.length}, Declared length: ${declared.length}`);
462
+ console.log(`[DEBUG] Calculated: ${calculated}`);
463
+ console.log(`[DEBUG] Declared: ${declared}`);
464
+ }
465
+ }
466
+ return {
467
+ success: true,
468
+ match,
469
+ calculated,
470
+ declared
471
+ };
472
+ }
473
+ /**
474
+ * Query multiple RPC endpoints with consensus mechanism
475
+ * Returns data only if majority of endpoints agree
476
+ */
477
+ async queryMultipleRPCsWithConsensus(tokenId, contractAddress, networkConfig, merkleRoot, minConsensus = 2, metadataContractVersion // Optional: version from metadata for validation
478
+ ) {
479
+ const rpcUrls = networkConfig.rpcUrls.length > 0 ? networkConfig.rpcUrls : [];
480
+ if (rpcUrls.length < minConsensus) {
481
+ return {
482
+ success: false,
483
+ error: `Not enough RPC endpoints (${rpcUrls.length}) for consensus (minimum: ${minConsensus})`
484
+ };
485
+ }
486
+ // Query up to 3 RPC endpoints (or all if less than 3)
487
+ const endpointsToQuery = rpcUrls.slice(0, Math.min(3, rpcUrls.length));
488
+ const results = [];
489
+ if (this.debug) {
490
+ console.log(`[DEBUG] Querying ${endpointsToQuery.length} RPC endpoints for consensus...`);
491
+ }
492
+ // Query all endpoints in parallel
493
+ const queryPromises = endpointsToQuery.map(async (rpcUrl) => {
494
+ try {
495
+ // First validate chainId from RPC
496
+ const provider = new ethers_1.ethers.JsonRpcProvider(rpcUrl);
497
+ const actualChainId = await Promise.race([
498
+ provider.getNetwork().then(n => Number(n.chainId)),
499
+ new Promise((_, reject) => setTimeout(() => reject(new Error('ChainId query timeout')), 5000))
500
+ ]);
501
+ if (actualChainId !== networkConfig.chainId) {
502
+ if (this.debug) {
503
+ console.log(`[DEBUG] RPC ${rpcUrl} chainId mismatch: expected ${networkConfig.chainId}, got ${actualChainId}`);
504
+ }
505
+ return {
506
+ success: false,
507
+ error: `ChainId mismatch: expected ${networkConfig.chainId}, got ${actualChainId}`,
508
+ rpcUrl
509
+ };
510
+ }
511
+ // Query token info
512
+ const contract = new ethers_1.ethers.Contract(contractAddress, contracts_1.NZIP_CONTRACT_ABI, provider);
513
+ const adapter = (0, AdapterFactory_1.getAdapter)(networkConfig.version || contracts_1.DEFAULT_CONTRACT_VERSION);
514
+ const zipFileInfo = await Promise.race([
515
+ adapter.getZipFileInfo(contract, BigInt(tokenId)),
516
+ new Promise((_, reject) => setTimeout(() => reject(new Error('getZipFileInfo timeout')), 10000))
517
+ ]);
518
+ provider.destroy();
519
+ return {
520
+ success: true,
521
+ onChainMerkleRoot: zipFileInfo.merkleRootHash,
522
+ onChainTokenizationTime: Number(zipFileInfo.tokenizationTime),
523
+ onChainCreator: zipFileInfo.creator,
524
+ onChainBlockNumber: Number(zipFileInfo.blockNumber),
525
+ chainId: actualChainId,
526
+ rpcUrl
527
+ };
528
+ }
529
+ catch (error) {
530
+ const errorMsg = error?.message || String(error);
531
+ if (this.debug) {
532
+ console.log(`[DEBUG] RPC ${rpcUrl} query failed: ${errorMsg}`);
533
+ }
534
+ return {
535
+ success: false,
536
+ error: errorMsg,
537
+ rpcUrl
538
+ };
539
+ }
540
+ });
541
+ const queryResults = await Promise.all(queryPromises);
542
+ results.push(...queryResults);
543
+ // Filter successful results
544
+ const successfulResults = results.filter(r => r.success);
545
+ if (successfulResults.length < minConsensus) {
546
+ return {
547
+ success: false,
548
+ error: `Consensus failed: only ${successfulResults.length} of ${endpointsToQuery.length} endpoints succeeded (minimum: ${minConsensus})`,
549
+ consensusCount: successfulResults.length,
550
+ totalQueried: endpointsToQuery.length
551
+ };
552
+ }
553
+ // Check if all successful results agree on the merkle root
554
+ // Use direct comparison - merkle roots are hex strings and must match exactly
555
+ const merkleRoots = successfulResults.map(r => r.onChainMerkleRoot).filter(Boolean);
556
+ const uniqueRoots = new Set(merkleRoots);
557
+ if (uniqueRoots.size > 1) {
558
+ if (this.debug) {
559
+ console.log(`[DEBUG] RPC consensus mismatch: different merkle roots returned`);
560
+ successfulResults.forEach((r, i) => {
561
+ console.log(`[DEBUG] RPC ${i + 1}: ${r.onChainMerkleRoot}`);
562
+ });
563
+ }
564
+ return {
565
+ success: false,
566
+ error: `RPC consensus failed: endpoints returned different merkle roots`,
567
+ consensusCount: successfulResults.length,
568
+ totalQueried: endpointsToQuery.length
569
+ };
570
+ }
571
+ // Use the first successful result (all should be the same)
572
+ const consensusResult = successfulResults[0];
573
+ if (this.debug) {
574
+ console.log(`[DEBUG] RPC consensus achieved: ${successfulResults.length} of ${endpointsToQuery.length} endpoints agreed`);
575
+ }
576
+ return {
577
+ success: true,
578
+ onChainMerkleRoot: consensusResult.onChainMerkleRoot,
579
+ onChainTokenizationTime: consensusResult.onChainTokenizationTime,
580
+ onChainCreator: consensusResult.onChainCreator,
581
+ onChainBlockNumber: consensusResult.onChainBlockNumber,
582
+ consensusCount: successfulResults.length,
583
+ totalQueried: endpointsToQuery.length
584
+ };
585
+ }
586
+ /**
587
+ * Verify token against blockchain
588
+ * @param rpcUrlIndex Optional index of RPC URL to use (default: 0)
589
+ * @param options Optional verification options including RPC consensus settings
590
+ */
591
+ async verifyOnChain(tokenId, contractAddress, networkConfig, merkleRoot, rpcUrlIndex = 0, options = {}, metadataContractVersion // Optional: version from metadata for validation
592
+ ) {
593
+ // If RPC consensus is enabled, use consensus mechanism
594
+ if (options.rpcConsensus) {
595
+ const minConsensus = options.minRpcConsensus ?? 2;
596
+ const consensusResult = await this.queryMultipleRPCsWithConsensus(tokenId, contractAddress, networkConfig, merkleRoot, minConsensus, metadataContractVersion // Pass metadata version for validation
597
+ );
598
+ if (!consensusResult.success) {
599
+ return {
600
+ success: false,
601
+ error: consensusResult.error || 'RPC consensus failed'
602
+ };
603
+ }
604
+ // SECURITY: Validate version matches on-chain version (if enabled)
605
+ if (options.validateContractVersion !== false) {
606
+ let onChainVersion = null;
607
+ try {
608
+ const rpcUrl = networkConfig.rpcUrls[0];
609
+ const provider = new ethers_1.ethers.JsonRpcProvider(rpcUrl);
610
+ const contract = new ethers_1.ethers.Contract(contractAddress, contracts_1.NZIP_CONTRACT_ABI, provider);
611
+ onChainVersion = await Promise.race([
612
+ contract.getVersion(),
613
+ new Promise((_, reject) => setTimeout(() => reject(new Error('getVersion timeout')), 5000))
614
+ ]);
615
+ provider.destroy();
616
+ }
617
+ catch (versionError) {
618
+ if (this.debug) {
619
+ console.log(`[DEBUG] Could not query on-chain version in consensus mode: ${versionError.message}`);
620
+ }
621
+ }
622
+ // Validate version if we have both metadata version and on-chain version
623
+ if (metadataContractVersion && metadataContractVersion !== 'unknown' && onChainVersion) {
624
+ const normalizedMetadata = (0, ContractVersionRegistry_1.normalizeVersion)(metadataContractVersion);
625
+ const normalizedOnChain = (0, ContractVersionRegistry_1.normalizeVersion)(onChainVersion);
626
+ if (normalizedMetadata && normalizedOnChain && normalizedMetadata !== normalizedOnChain) {
627
+ return {
628
+ success: false,
629
+ error: `Contract version mismatch: metadata version "${metadataContractVersion}" does not match on-chain version "${onChainVersion}"`
630
+ };
631
+ }
632
+ if (this.debug && normalizedMetadata === normalizedOnChain) {
633
+ console.log(`[DEBUG] ✅ Version validation passed (consensus mode): metadata version "${metadataContractVersion}" matches on-chain version "${onChainVersion}"`);
634
+ }
635
+ }
636
+ }
637
+ else {
638
+ if (this.debug) {
639
+ console.log(`[DEBUG] Version validation disabled (consensus mode) - skipping on-chain version check`);
640
+ }
641
+ }
642
+ // Verify merkle root match - direct comparison (case-sensitive)
643
+ // Merkle roots are hex strings and must match exactly
644
+ const merkleRootMatch = consensusResult.onChainMerkleRoot &&
645
+ consensusResult.onChainMerkleRoot === merkleRoot;
646
+ // Also call verifyZipFile for additional validation
647
+ let verifyResult = false;
648
+ try {
649
+ // Use first available RPC for verifyZipFile call
650
+ const rpcUrl = networkConfig.rpcUrls[0];
651
+ const provider = new ethers_1.ethers.JsonRpcProvider(rpcUrl);
652
+ const contract = new ethers_1.ethers.Contract(contractAddress, contracts_1.NZIP_CONTRACT_ABI, provider);
653
+ verifyResult = await Promise.race([
654
+ contract.verifyZipFile(tokenId, merkleRoot),
655
+ new Promise((_, reject) => setTimeout(() => reject(new Error('Contract call timeout')), 30000))
656
+ ]);
657
+ provider.destroy();
658
+ }
659
+ catch (verifyError) {
660
+ if (this.debug) {
661
+ console.log(`[DEBUG] verifyZipFile call failed: ${verifyError.message}`);
662
+ }
663
+ }
664
+ const isValid = merkleRootMatch === true;
665
+ return {
666
+ success: true,
667
+ isValid,
668
+ onChainMerkleRoot: consensusResult.onChainMerkleRoot,
669
+ onChainTokenizationTime: consensusResult.onChainTokenizationTime,
670
+ onChainCreator: consensusResult.onChainCreator,
671
+ onChainBlockNumber: consensusResult.onChainBlockNumber
672
+ };
673
+ }
674
+ // Standard single RPC query (quick validation - default behavior)
675
+ const rpcUrls = networkConfig.rpcUrls.length > 0 ? networkConfig.rpcUrls : [];
676
+ if (rpcUrls.length === 0) {
677
+ return {
678
+ success: false,
679
+ error: 'No RPC URLs configured for this network'
680
+ };
681
+ }
682
+ if (rpcUrlIndex >= rpcUrls.length) {
683
+ return {
684
+ success: false,
685
+ error: `RPC URL index ${rpcUrlIndex} is out of range (${rpcUrls.length} RPCs available)`
686
+ };
687
+ }
688
+ const rpcUrl = rpcUrls[rpcUrlIndex];
689
+ let provider = null;
690
+ try {
691
+ if (this.debug) {
692
+ console.log(`[DEBUG] Starting on-chain verification (quick mode - single RPC)`);
693
+ console.log(`[DEBUG] Token ID: ${tokenId}, Contract: ${contractAddress}`);
694
+ console.log(`[DEBUG] Network: ${networkConfig.network}, Chain ID: ${networkConfig.chainId}`);
695
+ console.log(`[DEBUG] Using RPC: ${rpcUrl}`);
696
+ }
697
+ provider = new ethers_1.ethers.JsonRpcProvider(rpcUrl);
698
+ // Validate chainId from RPC if option is enabled (default: true)
699
+ if (options.validateRpcChainId !== false) {
700
+ try {
701
+ const actualChainId = await Promise.race([
702
+ provider.getNetwork().then(n => Number(n.chainId)),
703
+ new Promise((_, reject) => setTimeout(() => reject(new Error('ChainId validation timeout')), 5000))
704
+ ]);
705
+ if (actualChainId !== networkConfig.chainId) {
706
+ provider.destroy();
707
+ return {
708
+ success: false,
709
+ error: `RPC chainId validation failed: expected ${networkConfig.chainId}, got ${actualChainId}. RPC may be pointing to wrong network.`,
710
+ rpcUrl
711
+ };
712
+ }
713
+ if (this.debug) {
714
+ console.log(`[DEBUG] RPC chainId validated: ${actualChainId}`);
715
+ }
716
+ }
717
+ catch (chainIdError) {
718
+ if (this.debug) {
719
+ console.log(`[DEBUG] ChainId validation failed: ${chainIdError.message}`);
720
+ }
721
+ // Continue with verification even if chainId validation fails (non-critical)
722
+ }
723
+ }
724
+ const contract = new ethers_1.ethers.Contract(contractAddress, contracts_1.NZIP_CONTRACT_ABI, provider);
725
+ if (this.debug) {
726
+ console.log(`[DEBUG] Calling contract.verifyZipFile(${tokenId}, ${merkleRoot.substring(0, 10)}...)`);
727
+ }
728
+ // If contractVersion is missing/unknown, use DEFAULT_CONTRACT_VERSION (v2.50) first, then older versions for legacy files
729
+ // No need to query blockchain - just use the version from networkConfig or default order
730
+ // Determine which version to try
731
+ const versionsToTry = [];
732
+ if (networkConfig.version && networkConfig.version !== 'unknown') {
733
+ // Use the version from network config if available
734
+ versionsToTry.push(networkConfig.version);
735
+ }
736
+ else {
737
+ // If version is missing/unknown, try default (v2.50) first, then 2.11, 2.10 for legacy files
738
+ versionsToTry.push(contracts_1.DEFAULT_CONTRACT_VERSION, '2.11', '2.10');
739
+ }
740
+ if (versionsToTry.length === 0) {
741
+ return {
742
+ success: false,
743
+ error: 'No contract versions available to try. No versions implemented or detected.'
744
+ };
745
+ }
746
+ // First check if token exists and get ALL actual on-chain data (not from metadata)
747
+ // Use adapter to handle version-specific differences
748
+ let onChainMerkleRoot;
749
+ let onChainTokenizationTime;
750
+ let onChainCreator;
751
+ let onChainBlockNumber;
752
+ let onChainEncryptedHash;
753
+ let lastError;
754
+ let successfulVersion;
755
+ // Try each version until one works
756
+ for (const version of versionsToTry) {
757
+ try {
758
+ if (this.debug) {
759
+ console.log(`[DEBUG] Trying contract version: ${version}`);
760
+ }
761
+ const adapter = (0, AdapterFactory_1.getAdapter)(version);
762
+ // Get token info using adapter (handles version-specific differences)
763
+ // CRITICAL: We trust ONLY blockchain data, not metadata file which can be tampered with
764
+ const zipFileInfo = await Promise.race([
765
+ adapter.getZipFileInfo(contract, BigInt(tokenId)),
766
+ new Promise((_, reject) => setTimeout(() => reject(new Error('getZipFileInfo timeout after 10 seconds')), 10000))
767
+ ]);
768
+ // Extract all on-chain data (adapter returns full structure for this version)
769
+ onChainMerkleRoot = zipFileInfo.merkleRootHash;
770
+ onChainTokenizationTime = Number(zipFileInfo.tokenizationTime);
771
+ onChainCreator = zipFileInfo.creator;
772
+ onChainBlockNumber = Number(zipFileInfo.blockNumber);
773
+ // encryptedHash is available if contract version supports it (v2.11+)
774
+ onChainEncryptedHash = zipFileInfo.encryptedHash || undefined;
775
+ successfulVersion = version;
776
+ if (this.debug) {
777
+ console.log(`[DEBUG] Successfully queried token info using version ${version}`);
778
+ console.log(`[DEBUG] Token ${tokenId} exists`);
779
+ console.log(`[DEBUG] On-chain merkle root: ${onChainMerkleRoot}`);
780
+ console.log(`[DEBUG] On-chain tokenization time: ${onChainTokenizationTime} (${new Date(onChainTokenizationTime * 1000).toLocaleString()})`);
781
+ console.log(`[DEBUG] On-chain creator: ${onChainCreator}`);
782
+ console.log(`[DEBUG] On-chain block number: ${onChainBlockNumber}`);
783
+ console.log(`[DEBUG] Calculated merkle root: ${merkleRoot}`);
784
+ }
785
+ // Success! Break out of version loop
786
+ break;
787
+ }
788
+ catch (infoError) {
789
+ const errorMsg = infoError.message || String(infoError);
790
+ lastError = errorMsg;
791
+ if (this.debug) {
792
+ console.log(`[DEBUG] Version ${version} failed: ${errorMsg}`);
793
+ }
794
+ // If token doesn't exist, don't try other versions
795
+ if (errorMsg.includes('nonexistent token') || errorMsg.includes('ERC721: invalid token ID')) {
796
+ return {
797
+ success: false,
798
+ error: `Token ${tokenId} does not exist on contract ${contractAddress}`
799
+ };
800
+ }
801
+ // If it's an "out of result range" error, try next version
802
+ if (errorMsg.includes('out of result range') || errorMsg.includes('data out-of-bounds')) {
803
+ if (this.debug) {
804
+ console.log(`[DEBUG] Version ${version} incompatible (wrong return structure), trying next version...`);
805
+ }
806
+ continue; // Try next version
807
+ }
808
+ // For other errors, continue to next version
809
+ continue;
810
+ }
811
+ }
812
+ // If we tried all versions and none worked
813
+ if (!onChainMerkleRoot) {
814
+ const triedVersions = versionsToTry.join(', ');
815
+ return {
816
+ success: false,
817
+ error: `Failed to get token info after trying all versions (${triedVersions}). Last error: ${lastError || 'Unknown error'}`
818
+ };
819
+ }
820
+ // SECURITY: Validate version matches on-chain version
821
+ // Query actual contract version from blockchain and compare
822
+ let onChainVersion = null;
823
+ try {
824
+ onChainVersion = await this.queryContractVersion(contractAddress, networkConfig, rpcUrlIndex);
825
+ }
826
+ catch (versionError) {
827
+ if (this.debug) {
828
+ console.log(`[DEBUG] Could not query on-chain version: ${versionError.message}`);
829
+ }
830
+ // Continue - version validation is important but not critical if query fails
831
+ }
832
+ if (onChainVersion) {
833
+ const normalizedOnChain = (0, ContractVersionRegistry_1.normalizeVersion)(onChainVersion);
834
+ const normalizedSuccessful = (0, ContractVersionRegistry_1.normalizeVersion)(successfulVersion || '');
835
+ // SECURITY: Validate version
836
+ // 1. If metadata has explicit version, it MUST match on-chain version (strict validation)
837
+ // 2. If metadata version is missing (legacy), validate that the version we used matches on-chain
838
+ if (metadataContractVersion && metadataContractVersion !== 'unknown') {
839
+ // New file with explicit version - strict validation
840
+ const normalizedMetadata = (0, ContractVersionRegistry_1.normalizeVersion)(metadataContractVersion);
841
+ if (normalizedMetadata && normalizedOnChain && normalizedMetadata !== normalizedOnChain) {
842
+ const errorMsg = `Contract version mismatch: metadata version "${metadataContractVersion}" does not match on-chain version "${onChainVersion}" (normalized: ${normalizedMetadata} vs ${normalizedOnChain})`;
843
+ if (this.debug) {
844
+ console.log(`[DEBUG] ❌ ${errorMsg}`);
845
+ }
846
+ return {
847
+ success: false,
848
+ error: errorMsg,
849
+ rpcUrl
850
+ };
851
+ }
852
+ if (this.debug && normalizedMetadata === normalizedOnChain) {
853
+ console.log(`[DEBUG] ✅ Version validation passed: metadata version "${metadataContractVersion}" matches on-chain version "${onChainVersion}"`);
854
+ }
855
+ }
856
+ else {
857
+ // Legacy file - validate that the version we used matches on-chain
858
+ if (normalizedOnChain && normalizedSuccessful && normalizedOnChain !== normalizedSuccessful) {
859
+ const errorMsg = `Contract version mismatch: used version "${successfulVersion}" does not match on-chain version "${onChainVersion}" (normalized: ${normalizedSuccessful} vs ${normalizedOnChain})`;
860
+ if (this.debug) {
861
+ console.log(`[DEBUG] ⚠️ ${errorMsg}`);
862
+ }
863
+ return {
864
+ success: false,
865
+ error: errorMsg,
866
+ rpcUrl
867
+ };
868
+ }
869
+ if (this.debug && normalizedOnChain === normalizedSuccessful) {
870
+ console.log(`[DEBUG] ✅ Version validation passed (legacy file): on-chain version "${onChainVersion}" matches used version "${successfulVersion}"`);
871
+ }
872
+ }
873
+ }
874
+ else {
875
+ if (this.debug) {
876
+ console.log(`[DEBUG] ⚠️ Could not query on-chain version for validation - continuing with used version "${successfulVersion}"`);
877
+ }
878
+ }
879
+ if (this.debug && successfulVersion && successfulVersion !== networkConfig.version) {
880
+ console.log(`[DEBUG] ⚠️ Contract version mismatch: network config says "${networkConfig.version || 'unknown'}", but "${successfulVersion}" worked. Consider updating network config.`);
881
+ }
882
+ // Compare the calculated merkle root with the on-chain merkle root
883
+ // This is the critical security check - we trust the blockchain, not the metadata file
884
+ // Direct string comparison (case-sensitive) - merkle roots are hex strings and must match exactly
885
+ const merkleRootMatch = onChainMerkleRoot && onChainMerkleRoot === merkleRoot;
886
+ if (this.debug) {
887
+ console.log(`[DEBUG] Merkle root match: ${merkleRootMatch}`);
888
+ }
889
+ // Also call verifyZipFile for additional validation (though we already have the root)
890
+ let verifyResult = false;
891
+ try {
892
+ verifyResult = await Promise.race([
893
+ contract.verifyZipFile(tokenId, merkleRoot),
894
+ new Promise((_, reject) => setTimeout(() => reject(new Error('Contract call timeout after 30 seconds')), 30000))
895
+ ]);
896
+ }
897
+ catch (verifyError) {
898
+ // If verifyZipFile fails but we have the root, we can still validate
899
+ if (this.debug) {
900
+ console.log(`[DEBUG] verifyZipFile call failed: ${verifyError.message}`);
901
+ }
902
+ }
903
+ // The merkle root comparison is the primary check - if roots match, verification passes
904
+ // verifyZipFile is a secondary check for additional validation
905
+ const isValid = merkleRootMatch === true;
906
+ if (this.debug) {
907
+ console.log(`[DEBUG] On-chain verification result: ${isValid} (merkle root match: ${merkleRootMatch}, verifyZipFile: ${verifyResult})`);
908
+ }
909
+ // Clean up provider to allow process to exit
910
+ try {
911
+ if (provider) {
912
+ provider.destroy();
913
+ }
914
+ }
915
+ catch (cleanupError) {
916
+ // Ignore cleanup errors
917
+ if (this.debug) {
918
+ console.log(`[DEBUG] Error destroying provider: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`);
919
+ }
920
+ }
921
+ return {
922
+ success: true,
923
+ isValid,
924
+ onChainMerkleRoot,
925
+ onChainTokenizationTime,
926
+ onChainCreator,
927
+ onChainBlockNumber
928
+ };
929
+ }
930
+ catch (error) {
931
+ const errorMessage = error instanceof Error ? error.message : 'Network error';
932
+ if (this.debug) {
933
+ console.log(`[DEBUG] On-chain verification failed: ${errorMessage}`);
934
+ }
935
+ // Clean up provider on error
936
+ try {
937
+ if (provider) {
938
+ provider.destroy();
939
+ }
940
+ }
941
+ catch (cleanupError) {
942
+ // Ignore cleanup errors
943
+ }
944
+ return {
945
+ success: false,
946
+ error: errorMessage,
947
+ rpcUrl: rpcUrl
948
+ };
949
+ }
950
+ }
951
+ /**
952
+ * Complete verification process
953
+ * @param rpcUrlIndex Optional index of RPC URL to use (default: 0)
954
+ */
955
+ async verifyToken(tokenMetadata, calculatedMerkleRoot, options = {}, rpcUrlIndex = 0) {
956
+ try {
957
+ if (this.debug) {
958
+ console.log(`[DEBUG] Starting complete token verification`);
959
+ }
960
+ // SECURITY: Validate input formats before processing
961
+ const tokenIdValidation = (0, contracts_1.validateTokenId)(tokenMetadata.tokenId);
962
+ if (!tokenIdValidation.success) {
963
+ return {
964
+ success: false,
965
+ message: `Invalid token ID: ${tokenIdValidation.error}`,
966
+ errorDetails: {
967
+ errorType: 'METADATA_ERROR',
968
+ tokenId: tokenMetadata.tokenId
969
+ }
970
+ };
971
+ }
972
+ const addressValidation = (0, contracts_1.validateEthereumAddress)(tokenMetadata.contractAddress);
973
+ if (!addressValidation.success) {
974
+ return {
975
+ success: false,
976
+ message: `Invalid contract address: ${addressValidation.error}`,
977
+ errorDetails: {
978
+ errorType: 'METADATA_ERROR',
979
+ contractAddress: tokenMetadata.contractAddress
980
+ }
981
+ };
982
+ }
983
+ const networkValidation = (0, contracts_1.sanitizeNetworkName)(tokenMetadata.network);
984
+ if (!networkValidation.success) {
985
+ return {
986
+ success: false,
987
+ message: `Invalid network name: ${networkValidation.error}`,
988
+ errorDetails: {
989
+ errorType: 'NETWORK_ERROR',
990
+ networkName: tokenMetadata.network
991
+ }
992
+ };
993
+ }
994
+ if (calculatedMerkleRoot) {
995
+ const merkleRootValidation = (0, contracts_1.validateMerkleRootFormat)(calculatedMerkleRoot);
996
+ if (!merkleRootValidation.success) {
997
+ return {
998
+ success: false,
999
+ message: `Invalid calculated merkle root format: ${merkleRootValidation.error}`,
1000
+ errorDetails: {
1001
+ errorType: 'MERKLE_ERROR',
1002
+ calculatedMerkleRoot
1003
+ }
1004
+ };
1005
+ }
1006
+ }
1007
+ // NOTE: tokenMetadata (tokenId, contractAddress, network) is used ONLY to locate the token on blockchain
1008
+ // All verification data (merkle root, tokenization time, creator, block number) comes from blockchain contract
1009
+ // If metadata is tampered with, the blockchain query will fail or return mismatched data, causing verification to fail
1010
+ // Get network configuration - use chainId if available (most reliable)
1011
+ const networkResult = this.getNetworkConfig(tokenMetadata.network, tokenMetadata.networkChainId);
1012
+ if (!networkResult.success || !networkResult.config) {
1013
+ return {
1014
+ success: false,
1015
+ message: networkResult.error || 'Unknown network error',
1016
+ errorDetails: {
1017
+ errorType: 'NETWORK_ERROR',
1018
+ networkName: tokenMetadata.network
1019
+ }
1020
+ };
1021
+ }
1022
+ const networkConfig = networkResult.config;
1023
+ // CRITICAL SECURITY CHECK: Validate contract address matches expected contract for network
1024
+ // This prevents attackers from creating fake metadata pointing to malicious contracts
1025
+ const contractValidation = (0, contracts_1.validateContractAddress)(tokenMetadata.contractAddress, networkConfig.chainId, this.debug);
1026
+ if (!contractValidation.success) {
1027
+ return {
1028
+ success: false,
1029
+ message: `Contract address validation failed: ${contractValidation.error}`,
1030
+ errorDetails: {
1031
+ errorType: 'CONTRACT_ERROR',
1032
+ networkName: tokenMetadata.network,
1033
+ contractAddress: tokenMetadata.contractAddress,
1034
+ tokenId: tokenMetadata.tokenId
1035
+ }
1036
+ };
1037
+ }
1038
+ // CRITICAL: We must calculate the merkle root from the actual files and compare with on-chain value
1039
+ // We do NOT trust the merkle root stored in the metadata file as it could be tampered with
1040
+ if (!calculatedMerkleRoot) {
1041
+ return {
1042
+ success: false,
1043
+ message: 'No merkle root calculated from archive contents - cannot verify',
1044
+ errorDetails: {
1045
+ errorType: 'MERKLE_ERROR'
1046
+ }
1047
+ };
1048
+ }
1049
+ // SECURITY: Version validation with backward compatibility for legacy files
1050
+ // Strategy:
1051
+ // 1. If contractVersion is provided in metadata: Validate it matches on-chain version (strict)
1052
+ // 2. If contractVersion is missing (legacy files): Query on-chain version and validate it matches what we use
1053
+ const configToUse = { ...networkConfig };
1054
+ const isLegacyFile = !tokenMetadata.contractVersion || tokenMetadata.contractVersion === 'unknown';
1055
+ if (isLegacyFile) {
1056
+ if (this.debug) {
1057
+ console.log(`[DEBUG] Legacy file detected (missing contractVersion) - will query on-chain version for validation`);
1058
+ }
1059
+ configToUse.version = 'unknown'; // This will trigger fallback logic in verifyOnChain
1060
+ }
1061
+ else {
1062
+ // New file with explicit version - validate it matches on-chain version
1063
+ if (this.debug) {
1064
+ console.log(`[DEBUG] File has explicit contractVersion: ${tokenMetadata.contractVersion} - will validate against on-chain version`);
1065
+ }
1066
+ // Keep the version from metadata for validation
1067
+ }
1068
+ // Perform on-chain verification - this will fetch the actual merkle root from the blockchain
1069
+ // This also validates version matches on-chain version
1070
+ const onChainResult = await this.verifyOnChain(tokenMetadata.tokenId, tokenMetadata.contractAddress, configToUse, calculatedMerkleRoot, rpcUrlIndex, options, // Pass options for RPC consensus settings
1071
+ tokenMetadata.contractVersion // Pass metadata version for validation
1072
+ );
1073
+ if (!onChainResult.success) {
1074
+ return {
1075
+ success: false,
1076
+ message: `Blockchain verification failed: ${onChainResult.error}`,
1077
+ errorDetails: {
1078
+ errorType: 'CONTRACT_ERROR',
1079
+ networkName: tokenMetadata.network,
1080
+ rpcUrl: onChainResult.rpcUrl || networkConfig.rpcUrls[rpcUrlIndex] || 'unknown',
1081
+ contractAddress: tokenMetadata.contractAddress,
1082
+ tokenId: tokenMetadata.tokenId
1083
+ }
1084
+ };
1085
+ }
1086
+ // Compare calculated merkle root with on-chain merkle root
1087
+ if (!onChainResult.onChainMerkleRoot) {
1088
+ return {
1089
+ success: false,
1090
+ message: 'Blockchain verification failed - could not retrieve on-chain merkle root',
1091
+ errorDetails: {
1092
+ errorType: 'CONTRACT_ERROR',
1093
+ tokenId: tokenMetadata.tokenId,
1094
+ contractAddress: tokenMetadata.contractAddress
1095
+ }
1096
+ };
1097
+ }
1098
+ // This is the critical security check: calculated root must match on-chain root
1099
+ // Direct string comparison (case-sensitive) - merkle roots are hex strings and must match exactly
1100
+ const onChainRoot = onChainResult.onChainMerkleRoot;
1101
+ const calculatedRoot = calculatedMerkleRoot;
1102
+ if (onChainRoot !== calculatedRoot) {
1103
+ return {
1104
+ success: false,
1105
+ message: 'Archive integrity verification FAILED - merkle root mismatch',
1106
+ errorDetails: {
1107
+ errorType: 'MERKLE_ERROR',
1108
+ calculatedMerkleRoot: calculatedMerkleRoot,
1109
+ onChainMerkleRoot: onChainResult.onChainMerkleRoot,
1110
+ merkleRoot: `calculated: ${calculatedMerkleRoot}, on-chain: ${onChainResult.onChainMerkleRoot}` // Keep for backward compatibility
1111
+ }
1112
+ };
1113
+ }
1114
+ if (!onChainResult.isValid) {
1115
+ return {
1116
+ success: false,
1117
+ message: 'Blockchain verification FAILED - contract verification returned false',
1118
+ errorDetails: {
1119
+ errorType: 'CONTRACT_ERROR',
1120
+ tokenId: tokenMetadata.tokenId,
1121
+ contractAddress: tokenMetadata.contractAddress,
1122
+ merkleRoot: calculatedMerkleRoot
1123
+ }
1124
+ };
1125
+ }
1126
+ // Success - create verification details
1127
+ // CRITICAL: Use ONLY blockchain data for display, not metadata which can be tampered with
1128
+ const mintDate = onChainResult.onChainTokenizationTime
1129
+ ? new Date(onChainResult.onChainTokenizationTime * 1000).toLocaleString()
1130
+ : (tokenMetadata.mintDate ||
1131
+ (tokenMetadata.creationTimestamp ? new Date(tokenMetadata.creationTimestamp * 1000).toLocaleString() : '') ||
1132
+ 'Unknown'); // Use on-chain tokenization time as source of truth
1133
+ return {
1134
+ success: true,
1135
+ message: 'Archive integrity VERIFIED against blockchain records',
1136
+ tokenMetadata,
1137
+ verificationDetails: {
1138
+ // These identifiers come from metadata (needed to locate token), but are validated by blockchain query
1139
+ tokenId: tokenMetadata.tokenId,
1140
+ network: tokenMetadata.network,
1141
+ contractAddress: tokenMetadata.contractAddress,
1142
+ // CRITICAL: All verification data comes from blockchain, not metadata
1143
+ merkleRoot: onChainResult.onChainMerkleRoot || calculatedMerkleRoot, // On-chain value (source of truth)
1144
+ mintDate, // On-chain tokenizationTime converted to date string (source of truth)
1145
+ calculatedMerkleRoot: calculatedMerkleRoot || undefined,
1146
+ declaredMerkleRoot: onChainResult.onChainMerkleRoot || undefined, // On-chain value, not metadata
1147
+ merkleRootMatch: true, // Verified: calculated root matches on-chain root
1148
+ onChainValid: true
1149
+ }
1150
+ };
1151
+ }
1152
+ catch (error) {
1153
+ const errorMessage = error instanceof Error ? error.message : 'Unknown verification error';
1154
+ if (this.debug) {
1155
+ console.log(`[DEBUG] Verification process failed: ${errorMessage}`);
1156
+ }
1157
+ return {
1158
+ success: false,
1159
+ message: `Tokenization verification failed: ${errorMessage}`,
1160
+ errorDetails: {
1161
+ errorType: 'METADATA_ERROR'
1162
+ }
1163
+ };
1164
+ }
1165
+ }
1166
+ /**
1167
+ * Verify token with retry logic and exponential backoff
1168
+ * @param tokenMetadata Token metadata to verify
1169
+ * @param calculatedMerkleRoot Calculated merkle root from archive
1170
+ * @param maxRetries Maximum number of retry attempts (default: uses instance maxRetries)
1171
+ * @returns Enhanced verification result with retry information
1172
+ */
1173
+ async verifyTokenWithRetry(tokenMetadata, calculatedMerkleRoot, maxRetries) {
1174
+ const startTime = Date.now();
1175
+ const retries = maxRetries || this.maxRetries;
1176
+ let lastError = null;
1177
+ for (let attempt = 1; attempt <= retries; attempt++) {
1178
+ try {
1179
+ console.log(`🔄 Verification attempt ${attempt}/${retries} for token ${tokenMetadata.tokenId}`);
1180
+ const result = await this.verifyToken(tokenMetadata, calculatedMerkleRoot);
1181
+ if (result.success) {
1182
+ console.log(`✅ Verification successful on attempt ${attempt} for token ${tokenMetadata.tokenId}`);
1183
+ return {
1184
+ ...result,
1185
+ retryAttempts: attempt,
1186
+ processingTime: Date.now() - startTime,
1187
+ timestamp: Date.now()
1188
+ };
1189
+ }
1190
+ lastError = new Error(result.message || 'Verification failed');
1191
+ }
1192
+ catch (error) {
1193
+ lastError = error instanceof Error ? error : new Error('Unknown verification error');
1194
+ console.warn(`❌ Verification attempt ${attempt} failed for token ${tokenMetadata.tokenId}:`, lastError.message);
1195
+ if (attempt < retries) {
1196
+ const delay = this.calculateRetryDelay(attempt);
1197
+ console.log(`⏳ Waiting ${delay}ms before retry...`);
1198
+ await this.delay(delay);
1199
+ }
1200
+ }
1201
+ }
1202
+ return {
1203
+ success: false,
1204
+ message: `Verification failed after ${retries} attempts: ${lastError?.message || 'Unknown error'}`,
1205
+ retryAttempts: retries,
1206
+ processingTime: Date.now() - startTime,
1207
+ timestamp: Date.now()
1208
+ };
1209
+ }
1210
+ /**
1211
+ * Batch verification for multiple tokens with rate limiting
1212
+ * @param tokens Array of token metadata and merkle roots to verify
1213
+ * @param options Batch processing options
1214
+ * @returns Batch verification result
1215
+ */
1216
+ async verifyBatch(tokens, options = {}) {
1217
+ const startTime = Date.now();
1218
+ const { parallel = false, maxConcurrent = 3 } = options;
1219
+ console.log(`🔄 Starting batch verification for ${tokens.length} tokens (parallel: ${parallel})`);
1220
+ let results = [];
1221
+ if (parallel) {
1222
+ // Parallel processing with concurrency limit
1223
+ results = await this.verifyBatchParallel(tokens, maxConcurrent);
1224
+ }
1225
+ else {
1226
+ // Sequential processing with rate limiting
1227
+ results = await this.verifyBatchSequential(tokens);
1228
+ }
1229
+ const successfulVerifications = results.filter(r => r.success).length;
1230
+ const failedVerifications = results.length - successfulVerifications;
1231
+ console.log(`✅ Batch verification completed: ${successfulVerifications}/${tokens.length} successful`);
1232
+ return {
1233
+ success: failedVerifications === 0,
1234
+ totalTokens: tokens.length,
1235
+ successfulVerifications,
1236
+ failedVerifications,
1237
+ results,
1238
+ processingTime: Date.now() - startTime
1239
+ };
1240
+ }
1241
+ /**
1242
+ * Verify tokens in parallel with concurrency control
1243
+ */
1244
+ async verifyBatchParallel(tokens, maxConcurrent) {
1245
+ const results = [];
1246
+ const chunks = [];
1247
+ // Split tokens into chunks for parallel processing
1248
+ for (let i = 0; i < tokens.length; i += maxConcurrent) {
1249
+ chunks.push(tokens.slice(i, i + maxConcurrent));
1250
+ }
1251
+ // Process each chunk in parallel
1252
+ for (const chunk of chunks) {
1253
+ const chunkPromises = chunk.map(token => this.verifyTokenWithRetry(token.tokenMetadata, token.calculatedMerkleRoot));
1254
+ const chunkResults = await Promise.all(chunkPromises);
1255
+ results.push(...chunkResults);
1256
+ // Add delay between chunks to avoid overwhelming the network
1257
+ if (chunks.indexOf(chunk) < chunks.length - 1) {
1258
+ await this.delay(this.batchDelay);
1259
+ }
1260
+ }
1261
+ return results;
1262
+ }
1263
+ /**
1264
+ * Verify tokens sequentially with rate limiting
1265
+ */
1266
+ async verifyBatchSequential(tokens) {
1267
+ const results = [];
1268
+ for (const token of tokens) {
1269
+ const result = await this.verifyTokenWithRetry(token.tokenMetadata, token.calculatedMerkleRoot);
1270
+ results.push(result);
1271
+ // Add delay between verifications to avoid rate limiting
1272
+ if (tokens.indexOf(token) < tokens.length - 1) {
1273
+ await this.delay(this.batchDelay);
1274
+ }
1275
+ }
1276
+ return results;
1277
+ }
1278
+ /**
1279
+ * Process verification jobs with priority queue
1280
+ * @param jobs Array of verification jobs
1281
+ * @param options Processing options
1282
+ * @returns Batch verification result
1283
+ */
1284
+ async processVerificationJobs(jobs, options = {}) {
1285
+ const { maxConcurrent = 3, priorityOrder = true, getTokenMetadata, getCalculatedMerkleRoot } = options;
1286
+ if (!getTokenMetadata || !getCalculatedMerkleRoot) {
1287
+ return {
1288
+ success: false,
1289
+ totalTokens: jobs.length,
1290
+ successfulVerifications: 0,
1291
+ failedVerifications: jobs.length,
1292
+ results: [],
1293
+ processingTime: 0
1294
+ };
1295
+ }
1296
+ // Sort jobs by priority if requested
1297
+ const sortedJobs = priorityOrder
1298
+ ? this.sortJobsByPriority(jobs)
1299
+ : jobs;
1300
+ // Convert jobs to token format
1301
+ const tokens = [];
1302
+ for (const job of sortedJobs) {
1303
+ const tokenMetadata = await getTokenMetadata(job.tokenId);
1304
+ const calculatedMerkleRoot = await getCalculatedMerkleRoot(job.tokenId);
1305
+ if (tokenMetadata) {
1306
+ tokens.push({
1307
+ tokenMetadata,
1308
+ calculatedMerkleRoot: calculatedMerkleRoot || null
1309
+ });
1310
+ }
1311
+ }
1312
+ return this.verifyBatch(tokens, { parallel: true, maxConcurrent });
1313
+ }
1314
+ /**
1315
+ * Sort verification jobs by priority
1316
+ */
1317
+ sortJobsByPriority(jobs) {
1318
+ const priorityOrder = { high: 0, normal: 1, low: 2 };
1319
+ return jobs.sort((a, b) => {
1320
+ const aPriority = priorityOrder[a.priority || 'normal'];
1321
+ const bPriority = priorityOrder[b.priority || 'normal'];
1322
+ if (aPriority !== bPriority) {
1323
+ return aPriority - bPriority;
1324
+ }
1325
+ // If same priority, sort by retry count (fewer retries first)
1326
+ return (a.retryCount || 0) - (b.retryCount || 0);
1327
+ });
1328
+ }
1329
+ /**
1330
+ * Calculate retry delay with exponential backoff
1331
+ */
1332
+ calculateRetryDelay(attempt) {
1333
+ return Math.min(this.retryDelay * Math.pow(2, attempt - 1), 30000); // Max 30 seconds
1334
+ }
1335
+ /**
1336
+ * Utility method for delays
1337
+ */
1338
+ async delay(ms) {
1339
+ return new Promise(resolve => setTimeout(resolve, ms));
1340
+ }
1341
+ /**
1342
+ * Get verification statistics
1343
+ */
1344
+ getVerificationStats() {
1345
+ return {
1346
+ maxRetries: this.maxRetries,
1347
+ retryDelay: this.retryDelay,
1348
+ batchDelay: this.batchDelay
1349
+ };
1350
+ }
1351
+ /**
1352
+ * Update verification configuration
1353
+ */
1354
+ updateConfig(config) {
1355
+ if (config.maxRetries !== undefined) {
1356
+ this.maxRetries = config.maxRetries;
1357
+ }
1358
+ if (config.retryDelay !== undefined) {
1359
+ this.retryDelay = config.retryDelay;
1360
+ }
1361
+ if (config.batchDelay !== undefined) {
1362
+ this.batchDelay = config.batchDelay;
1363
+ }
1364
+ if (this.debug) {
1365
+ console.log('🔧 ZipkitVerifier configuration updated:', this.getVerificationStats());
1366
+ }
1367
+ }
1368
+ }
1369
+ exports.ZipkitVerifier = ZipkitVerifier;
1370
+ //# sourceMappingURL=ZipkitVerifier.js.map