neozipkit 0.1.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 (135) hide show
  1. package/README.md +400 -0
  2. package/contracts/Lock.sol +34 -0
  3. package/contracts/bytecode.txt +1 -0
  4. package/contracts/compile-contract.js +168 -0
  5. package/contracts/deploy/deploy-both-contracts.js +286 -0
  6. package/contracts/deploy/deploy-enhanced-contract.js +197 -0
  7. package/contracts/deploy/deploy-private-contract.js +190 -0
  8. package/contracts/deploy/deploy-public-clean.js +1063 -0
  9. package/contracts/deploy/deploy-public-contract.js +1107 -0
  10. package/contracts/deployment.config.example.js +25 -0
  11. package/contracts/remix-compile.js +222 -0
  12. package/contracts/setup-deployment.js +51 -0
  13. package/contracts/src/NZIP-NFT-Private.sol +350 -0
  14. package/contracts/src/NZIP-NFT.sol +265 -0
  15. package/contracts/src/ZipFileNFT-DEPRECATED-6field.sol +260 -0
  16. package/contracts/src/ZipFileNFT-DEPRECATED-Enhanced.sol +289 -0
  17. package/dist/browser/CertificateGeneratorBrowser.d.ts +75 -0
  18. package/dist/browser/CertificateGeneratorBrowser.js +433 -0
  19. package/dist/browser/TokenVerifierBrowser.d.ts +76 -0
  20. package/dist/browser/TokenVerifierBrowser.js +299 -0
  21. package/dist/browser/ZipKitBrowser.d.ts +18 -0
  22. package/dist/browser/ZipKitBrowser.js +250 -0
  23. package/dist/browser/ZipKitStampBrowser.d.ts +2 -0
  24. package/dist/browser/ZipKitStampBrowser.js +12 -0
  25. package/dist/browser-index.d.ts +21 -0
  26. package/dist/browser-index.js +58 -0
  27. package/dist/browser.d.ts +21 -0
  28. package/dist/browser.js +58 -0
  29. package/dist/index.d.ts +29 -0
  30. package/dist/index.js +76 -0
  31. package/dist/server/blockchain/CertificateToken.d.ts +48 -0
  32. package/dist/server/blockchain/CertificateToken.js +386 -0
  33. package/dist/server/blockchain/NFTMinter.d.ts +41 -0
  34. package/dist/server/blockchain/NFTMinter.js +323 -0
  35. package/dist/server/blockchain/TokenQuerier.d.ts +31 -0
  36. package/dist/server/blockchain/TokenQuerier.js +259 -0
  37. package/dist/server/blockchain/TokenVerifier.d.ts +20 -0
  38. package/dist/server/blockchain/TokenVerifier.js +395 -0
  39. package/dist/server/blockchain/WalletAnalyzer.d.ts +81 -0
  40. package/dist/server/blockchain/WalletAnalyzer.js +356 -0
  41. package/dist/server/blockchain/WalletFormatter.d.ts +39 -0
  42. package/dist/server/blockchain/WalletFormatter.js +196 -0
  43. package/dist/server/blockchain/WalletManager.d.ts +46 -0
  44. package/dist/server/blockchain/WalletManager.js +294 -0
  45. package/dist/server/blockchain/ZipMetadata.d.ts +27 -0
  46. package/dist/server/blockchain/ZipMetadata.js +131 -0
  47. package/dist/server/blockchain/ZipTokenProcessor.d.ts +44 -0
  48. package/dist/server/blockchain/ZipTokenProcessor.js +204 -0
  49. package/dist/server/blockchain/constants.d.ts +102 -0
  50. package/dist/server/blockchain/constants.js +968 -0
  51. package/dist/server/blockchain/index.d.ts +9 -0
  52. package/dist/server/blockchain/index.js +44 -0
  53. package/dist/server/blockchain/test-certificate.d.ts +8 -0
  54. package/dist/server/blockchain/test-certificate.js +100 -0
  55. package/dist/server/blockchain/types.d.ts +69 -0
  56. package/dist/server/blockchain/types.js +51 -0
  57. package/dist/server/platform/server/ZipKitServer.d.ts +105 -0
  58. package/dist/server/platform/server/ZipKitServer.js +437 -0
  59. package/dist/server/platform/servercomponents/ZipKitOTS.d.ts +91 -0
  60. package/dist/server/platform/servercomponents/ZipKitOTS.js +271 -0
  61. package/dist/shared/ZipEntry.d.ts +131 -0
  62. package/dist/shared/ZipEntry.js +686 -0
  63. package/dist/shared/ZipKit.d.ts +249 -0
  64. package/dist/shared/ZipKit.js +789 -0
  65. package/dist/shared/components/CertificateOTS.d.ts +15 -0
  66. package/dist/shared/components/CertificateOTS.js +116 -0
  67. package/dist/shared/components/FileSys.d.ts +2 -0
  68. package/dist/shared/components/FileSys.js +113 -0
  69. package/dist/shared/components/HashAccumulator.d.ts +55 -0
  70. package/dist/shared/components/HashAccumulator.js +100 -0
  71. package/dist/shared/components/Support.d.ts +63 -0
  72. package/dist/shared/components/Support.js +70 -0
  73. package/dist/shared/components/Util.d.ts +21 -0
  74. package/dist/shared/components/Util.js +115 -0
  75. package/dist/shared/components/ZipCrypto.d.ts +3 -0
  76. package/dist/shared/components/ZipCrypto.js +78 -0
  77. package/dist/shared/components/merkleTree.d.ts +66 -0
  78. package/dist/shared/components/merkleTree.js +136 -0
  79. package/dist/shared/constants/Errors.d.ts +53 -0
  80. package/dist/shared/constants/Errors.js +69 -0
  81. package/dist/shared/constants/Headers.d.ts +167 -0
  82. package/dist/shared/constants/Headers.js +191 -0
  83. package/dist/shared/types/index.d.ts +21 -0
  84. package/dist/shared/types/index.js +3 -0
  85. package/dist/shared/types.d.ts +33 -0
  86. package/dist/shared/types.js +8 -0
  87. package/dist/shared/version.d.ts +4 -0
  88. package/dist/shared/version.js +7 -0
  89. package/dist/types/index.d.ts +21 -0
  90. package/dist/types/index.js +3 -0
  91. package/examples/unzip-token.ts +488 -0
  92. package/examples/zip-token.ts +604 -0
  93. package/package.json +117 -0
  94. package/src/browser/CertificateGeneratorBrowser.ts +498 -0
  95. package/src/browser/TokenVerifierBrowser.ts +344 -0
  96. package/src/browser/ZipKitBrowser.ts +242 -0
  97. package/src/browser/ZipKitStampBrowser.ts +9 -0
  98. package/src/browser-index.ts +36 -0
  99. package/src/index.ts +48 -0
  100. package/src/server/blockchain/CertificateToken.ts +444 -0
  101. package/src/server/blockchain/NFTMinter.ts +357 -0
  102. package/src/server/blockchain/TokenQuerier.ts +287 -0
  103. package/src/server/blockchain/TokenVerifier.ts +453 -0
  104. package/src/server/blockchain/WalletAnalyzer.ts +397 -0
  105. package/src/server/blockchain/WalletFormatter.ts +206 -0
  106. package/src/server/blockchain/WalletManager.ts +294 -0
  107. package/src/server/blockchain/ZipMetadata.ts +117 -0
  108. package/src/server/blockchain/ZipTokenProcessor.ts +234 -0
  109. package/src/server/blockchain/constants.ts +969 -0
  110. package/src/server/blockchain/index.ts +42 -0
  111. package/src/server/blockchain/test-certificate.ts +137 -0
  112. package/src/server/blockchain/types.ts +134 -0
  113. package/src/server/platform/server/ZipKitServer.ts +459 -0
  114. package/src/server/platform/servercomponents/ZipKitOTS.ts +313 -0
  115. package/src/server/platform/servercomponents/opentimestamps.d.ts +23 -0
  116. package/src/shared/ZipEntry.ts +767 -0
  117. package/src/shared/ZipKit.ts +899 -0
  118. package/src/shared/components/CertificateOTS.ts +132 -0
  119. package/src/shared/components/FileSys.ts +87 -0
  120. package/src/shared/components/HashAccumulator.ts +113 -0
  121. package/src/shared/components/Support.ts +77 -0
  122. package/src/shared/components/Util.ts +113 -0
  123. package/src/shared/components/ZipCrypto.ts +91 -0
  124. package/src/shared/components/merkleTree.ts +162 -0
  125. package/src/shared/constants/Errors.ts +82 -0
  126. package/src/shared/constants/Headers.ts +203 -0
  127. package/src/shared/types/index.ts +25 -0
  128. package/src/shared/types/modules.d.ts +22 -0
  129. package/src/shared/types/opentimestamps.d.ts +23 -0
  130. package/src/shared/types.d.ts +8 -0
  131. package/src/shared/types.ts +43 -0
  132. package/src/shared/version.ts +4 -0
  133. package/src/types/index.ts +25 -0
  134. package/src/types/modules.d.ts +22 -0
  135. package/src/types/opentimestamps.d.ts +23 -0
@@ -0,0 +1,357 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { ethers } from 'ethers';
4
+ import {
5
+ MintingResult,
6
+ ContractInfo,
7
+ ZipTokenData,
8
+ NFTMetadata,
9
+ NFT_METADATA_TEMPLATE
10
+ } from './types';
11
+
12
+ export interface ExistingTokenInfo {
13
+ tokenId: string;
14
+ fileName: string;
15
+ merkleRootHash: string;
16
+ creationTimestamp: number;
17
+ tokenizationTime: number;
18
+ creator: string;
19
+ isOwnedByWallet: boolean;
20
+ }
21
+
22
+ export class NFTMinter {
23
+ private wallet: ethers.Wallet | null;
24
+ private provider: ethers.JsonRpcProvider | null;
25
+
26
+ constructor(wallet: ethers.Wallet | null, provider: ethers.JsonRpcProvider | null) {
27
+ this.wallet = wallet;
28
+ this.provider = provider;
29
+ }
30
+
31
+ /**
32
+ * Creates NFT metadata for the ZIP archive
33
+ */
34
+ createNFTMetadata(zipTokenData: ZipTokenData): NFTMetadata {
35
+ const metadata = { ...NFT_METADATA_TEMPLATE };
36
+ metadata.name = `${zipTokenData.fileName} Archive`;
37
+ metadata.description = `Verified archive created on ${zipTokenData.timestamp}`;
38
+ metadata.attributes[0].value = zipTokenData.merkleRoot;
39
+ metadata.attributes[1].value = zipTokenData.timestamp;
40
+
41
+ return metadata;
42
+ }
43
+
44
+ /**
45
+ * Loads contract deployment information
46
+ */
47
+ private loadContractInfo(): ContractInfo | null {
48
+ try {
49
+ const contractInfoPath = path.join(process.cwd(), 'zipfile-nft-deployment.json');
50
+
51
+ if (!fs.existsSync(contractInfoPath)) {
52
+ return null;
53
+ }
54
+
55
+ const contractInfo = JSON.parse(fs.readFileSync(contractInfoPath, 'utf8'));
56
+ return contractInfo as ContractInfo;
57
+ } catch (error) {
58
+ console.error('Error loading contract info:', error);
59
+ return null;
60
+ }
61
+ }
62
+
63
+ /**
64
+ * Check for existing tokens with the same merkle root
65
+ * This is efficient because it uses the contract's getTokensByMerkleRoot function
66
+ */
67
+ async findExistingTokensByMerkleRoot(merkleRoot: string): Promise<ExistingTokenInfo[]> {
68
+ if (!this.provider || !this.wallet) {
69
+ return [];
70
+ }
71
+
72
+ try {
73
+ const contractInfo = this.loadContractInfo();
74
+ if (!contractInfo) {
75
+ return [];
76
+ }
77
+
78
+ console.log(`🔍 Checking for existing tokens with merkle root: ${merkleRoot}`);
79
+ const contract = new ethers.Contract(contractInfo.address, contractInfo.abi, this.provider);
80
+
81
+ // Use the efficient contract function to get tokens by merkle root
82
+ const tokenIds = await contract.getTokensByMerkleRoot(merkleRoot);
83
+
84
+ if (tokenIds.length === 0) {
85
+ console.log(` ✅ No existing tokens found for this content`);
86
+ return [];
87
+ }
88
+
89
+ console.log(` 📋 Found ${tokenIds.length} existing token(s) for this content:`);
90
+
91
+ const existingTokens: ExistingTokenInfo[] = [];
92
+
93
+ for (const tokenId of tokenIds) {
94
+ try {
95
+ // Get token info
96
+ const zipInfo = await contract.getZipFileInfo(tokenId);
97
+ const owner = await contract.ownerOf(tokenId);
98
+
99
+ const tokenInfo: ExistingTokenInfo = {
100
+ tokenId: tokenId.toString(),
101
+ fileName: zipInfo.fileName,
102
+ merkleRootHash: zipInfo.merkleRootHash,
103
+ creationTimestamp: Number(zipInfo.creationTimestamp),
104
+ tokenizationTime: Number(zipInfo.tokenizationTime),
105
+ creator: zipInfo.creator,
106
+ isOwnedByWallet: owner.toLowerCase() === this.wallet!.address.toLowerCase()
107
+ };
108
+
109
+ existingTokens.push(tokenInfo);
110
+
111
+ const ownershipStatus = tokenInfo.isOwnedByWallet ? "👤 (owned by you)" : `👥 (owned by ${owner})`;
112
+ const createdDate = new Date(tokenInfo.creationTimestamp * 1000).toISOString();
113
+ const mintedDate = new Date(tokenInfo.tokenizationTime * 1000).toISOString();
114
+
115
+ console.log(` 📄 Token ID ${tokenInfo.tokenId}: ${tokenInfo.fileName}`);
116
+ console.log(` Created: ${createdDate}`);
117
+ console.log(` Minted: ${mintedDate}`);
118
+ console.log(` Owner: ${ownershipStatus}`);
119
+
120
+ } catch (error) {
121
+ console.warn(` ⚠️ Could not retrieve info for token ${tokenId}: ${error}`);
122
+ }
123
+ }
124
+
125
+ return existingTokens;
126
+
127
+ } catch (error) {
128
+ console.warn(`Error checking for existing tokens: ${error}`);
129
+ return [];
130
+ }
131
+ }
132
+
133
+ /**
134
+ * Prompts user to choose between reusing an existing timestamp or creating a new token
135
+ */
136
+ async promptUserChoice(existingTokens: ExistingTokenInfo[], fileName: string): Promise<{
137
+ useExisting: boolean;
138
+ selectedTimestamp?: number;
139
+ selectedTokenId?: string;
140
+ }> {
141
+ return new Promise((resolve) => {
142
+ const readline = require('readline');
143
+ const rl = readline.createInterface({
144
+ input: process.stdin,
145
+ output: process.stdout
146
+ });
147
+
148
+ console.log('\n🤔 What would you like to do?');
149
+ console.log('═'.repeat(60));
150
+ console.log('1. Create a new token with current timestamp (recommended)');
151
+
152
+ // Show existing tokens as options
153
+ existingTokens.forEach((token, index) => {
154
+ const createdDate = new Date(token.creationTimestamp * 1000).toLocaleString();
155
+ const ownershipNote = token.isOwnedByWallet ? ' (you own this)' : ` (owned by ${token.creator.slice(0, 8)}...)`;
156
+ console.log(`${index + 2}. Reuse timestamp from Token ${token.tokenId}: ${createdDate}${ownershipNote}`);
157
+ });
158
+
159
+ console.log('═'.repeat(60));
160
+
161
+ rl.question('Enter your choice (1-' + (existingTokens.length + 1) + '): ', (answer: string) => {
162
+ rl.close();
163
+
164
+ const choice = parseInt(answer.trim());
165
+
166
+ if (choice === 1) {
167
+ resolve({ useExisting: false });
168
+ } else if (choice >= 2 && choice <= existingTokens.length + 1) {
169
+ const selectedToken = existingTokens[choice - 2];
170
+ resolve({
171
+ useExisting: true,
172
+ selectedTimestamp: selectedToken.creationTimestamp,
173
+ selectedTokenId: selectedToken.tokenId
174
+ });
175
+ } else {
176
+ console.log('❌ Invalid choice. Creating new token with current timestamp.');
177
+ resolve({ useExisting: false });
178
+ }
179
+ });
180
+ });
181
+ }
182
+
183
+ /**
184
+ * Mints an NFT token for the ZIP archive using the timestamped contract
185
+ */
186
+ async mintZipFileNFT(zipTokenData: ZipTokenData): Promise<MintingResult> {
187
+ if (!this.wallet) {
188
+ return {
189
+ success: false,
190
+ message: 'Wallet not set up. Cannot mint token.'
191
+ };
192
+ }
193
+
194
+ if (!this.provider) {
195
+ return {
196
+ success: false,
197
+ message: 'Network connection failed. Cannot mint token without blockchain connectivity.'
198
+ };
199
+ }
200
+
201
+ try {
202
+ console.log(`\nArchive Merkle root: ${zipTokenData.merkleRoot}`);
203
+
204
+ // Check for existing tokens with the same merkle root
205
+ const existingTokens = await this.findExistingTokensByMerkleRoot(zipTokenData.merkleRoot);
206
+
207
+ let creationTimestamp: number;
208
+ let reusingExistingToken = false;
209
+ let selectedTokenId: string | undefined;
210
+
211
+ if (existingTokens.length > 0) {
212
+ console.log(`\n💡 Found ${existingTokens.length} existing token(s) with the same content.`);
213
+ console.log(' You can create a new token or reuse an existing timestamp.');
214
+
215
+ const userChoice = await this.promptUserChoice(existingTokens, zipTokenData.fileName);
216
+
217
+ if (userChoice.useExisting && userChoice.selectedTimestamp) {
218
+ creationTimestamp = userChoice.selectedTimestamp;
219
+ selectedTokenId = userChoice.selectedTokenId;
220
+ reusingExistingToken = true;
221
+ console.log(`\n✅ Reusing timestamp ${creationTimestamp} from Token ID ${selectedTokenId}`);
222
+
223
+ // Check if this exact combination already exists
224
+ const contractInfo = this.loadContractInfo();
225
+ if (contractInfo) {
226
+ const contract = new ethers.Contract(contractInfo.address, contractInfo.abi, this.provider);
227
+ const [exists, existingTokenId] = await contract.isZipFileTokenized(zipTokenData.merkleRoot, creationTimestamp);
228
+
229
+ if (exists) {
230
+ return {
231
+ success: false,
232
+ message: `A token with this exact merkle root and timestamp already exists (Token ID: ${existingTokenId}). Cannot create duplicate.`
233
+ };
234
+ }
235
+ }
236
+ } else {
237
+ creationTimestamp = Math.floor(Date.now() / 1000);
238
+ console.log(`\n✅ Creating new token with current timestamp: ${creationTimestamp}`);
239
+ }
240
+ } else {
241
+ // No existing tokens, use current timestamp
242
+ creationTimestamp = Math.floor(Date.now() / 1000);
243
+ console.log('Creating new NFT token for this ZIP archive...');
244
+ }
245
+
246
+ // Create token metadata
247
+ const metadata = this.createNFTMetadata(zipTokenData);
248
+
249
+ console.log('Token metadata created:');
250
+ console.log(JSON.stringify(metadata, null, 2));
251
+
252
+ // Load contract information
253
+ const contractInfo = this.loadContractInfo();
254
+
255
+ if (!contractInfo) {
256
+ return {
257
+ success: false,
258
+ message: 'No deployed contract found. Deploy the contract using: npm run deploy-nft'
259
+ };
260
+ }
261
+
262
+ console.log(`Found deployed contract at ${contractInfo.address}`);
263
+
264
+ try {
265
+ // Create contract instance
266
+ const contract = new ethers.Contract(contractInfo.address, contractInfo.abi, this.wallet);
267
+
268
+ console.log(`\nMinting NFT token...`);
269
+ console.log(`Merkle Root: ${zipTokenData.merkleRoot}`);
270
+ console.log(`Creation Timestamp: ${creationTimestamp}${reusingExistingToken ? ' (reused)' : ' (new)'}`);
271
+
272
+ // Use the timestamped contract (5 parameters)
273
+ console.log(`Using ZipFileNFT contract`);
274
+ console.log(`contract.publicMintZipFile(`);
275
+ console.log(` fileName: ${zipTokenData.fileName},`);
276
+ console.log(` merkleRootHash: ${zipTokenData.merkleRoot},`);
277
+ console.log(` creationTimestamp: ${creationTimestamp},`);
278
+ console.log(` ipfsHash: "",`);
279
+ console.log(` tokenURI: ""`);
280
+ console.log(`)`);
281
+
282
+ const tx = await contract.publicMintZipFile(
283
+ zipTokenData.fileName,
284
+ zipTokenData.merkleRoot, // Use original merkle root
285
+ creationTimestamp, // Use selected timestamp (new or reused)
286
+ "", // Empty ipfsHash - not implemented yet
287
+ "" // Empty tokenURI - not implemented yet
288
+ );
289
+
290
+ console.log(`Transaction submitted: ${tx.hash}`);
291
+ console.log('Waiting for confirmation...');
292
+
293
+ const receipt = await tx.wait();
294
+ console.log(`Transaction confirmed in block: ${receipt.blockNumber}`);
295
+
296
+ // Extract token ID from the transfer event
297
+ let tokenId: string | undefined;
298
+ for (const log of receipt.logs) {
299
+ try {
300
+ const parsedLog = contract.interface.parseLog(log);
301
+ if (parsedLog && parsedLog.name === 'Transfer' && parsedLog.args.from === ethers.ZeroAddress) {
302
+ tokenId = parsedLog.args.tokenId.toString();
303
+ break;
304
+ }
305
+ } catch (error) {
306
+ // Skip logs that can't be parsed
307
+ }
308
+ }
309
+
310
+ if (!tokenId) {
311
+ // Try to get token ID from ZipFileTokenized event
312
+ for (const log of receipt.logs) {
313
+ try {
314
+ const parsedLog = contract.interface.parseLog(log);
315
+ if (parsedLog && parsedLog.name === 'ZipFileTokenized') {
316
+ tokenId = parsedLog.args.tokenId.toString();
317
+ break;
318
+ }
319
+ } catch (error) {
320
+ // Skip logs that can't be parsed
321
+ }
322
+ }
323
+ }
324
+
325
+ const successMessage = reusingExistingToken
326
+ ? `NFT minted successfully! Token ID: ${tokenId || 'Unknown'} (reused timestamp from Token ${selectedTokenId})`
327
+ : `NFT minted successfully! Token ID: ${tokenId || 'Unknown'} (new timestamp)`;
328
+
329
+ return {
330
+ success: true,
331
+ txHash: tx.hash,
332
+ tokenId: tokenId,
333
+ message: successMessage,
334
+ creationTimestamp: creationTimestamp
335
+ };
336
+
337
+ } catch (contractError: unknown) {
338
+ const errorMessage = contractError instanceof Error ? contractError.message : String(contractError);
339
+ console.error(`Contract interaction failed: ${errorMessage}`);
340
+
341
+ return {
342
+ success: false,
343
+ message: `Contract interaction failed: ${errorMessage}`
344
+ };
345
+ }
346
+
347
+ } catch (error: unknown) {
348
+ const errorMessage = error instanceof Error ? error.message : String(error);
349
+ console.error(`Error minting NFT: ${errorMessage}`);
350
+
351
+ return {
352
+ success: false,
353
+ message: `Error minting NFT: ${errorMessage}`
354
+ };
355
+ }
356
+ }
357
+ }
@@ -0,0 +1,287 @@
1
+ // ======================================
2
+ // TokenQuerier.ts
3
+ // Blockchain token querying utilities for ZIP archives
4
+ // Copyright (c) 2025 NeoWare, Inc. All rights reserved.
5
+ // ======================================
6
+
7
+ import * as fs from 'fs';
8
+ import * as path from 'path';
9
+ import { CURRENT_DEPLOYMENT } from './constants';
10
+
11
+ // Network configuration for blockchain queries
12
+ const NETWORK_CONFIG: Record<string, any> = {
13
+ 'Base Sepolia': {
14
+ rpcUrls: [
15
+ 'https://sepolia.base.org',
16
+ 'https://base-sepolia.g.alchemy.com/v2/demo',
17
+ 'https://base-sepolia-rpc.publicnode.com',
18
+ 'https://base-sepolia.blockpi.network/v1/rpc/public'
19
+ ],
20
+ contractAddress: CURRENT_DEPLOYMENT.address, // NZIP NFT Contract v2.0
21
+ chainId: 84532
22
+ },
23
+ 'Base Mainnet': {
24
+ rpcUrls: [
25
+ 'https://mainnet.base.org',
26
+ 'https://base-mainnet.g.alchemy.com/v2/demo',
27
+ 'https://base-rpc.publicnode.com',
28
+ 'https://base.blockpi.network/v1/rpc/public'
29
+ ],
30
+ contractAddress: "0x...", // Add mainnet contract address when deployed
31
+ chainId: 8453
32
+ }
33
+ };
34
+
35
+ export interface ExistingTokenInfo {
36
+ version: string;
37
+ tokenId: string;
38
+ contractAddress: string;
39
+ network: string;
40
+ networkChainId: number;
41
+ transactionHash?: string;
42
+ merkleRoot: string;
43
+ fileName: string;
44
+ mintedAt: string;
45
+ }
46
+
47
+ export interface TokenQueryOptions {
48
+ network?: string;
49
+ walletAddress?: string;
50
+ contractAddress?: string;
51
+ }
52
+
53
+ /**
54
+ * Queries the blockchain for an existing token with the given merkle root
55
+ * @param merkleRoot - The merkle root hash to search for
56
+ * @param fileName - The name of the ZIP file
57
+ * @param options - Optional query parameters
58
+ * @returns Promise resolving to token info if found, null otherwise
59
+ */
60
+ export async function queryBlockchainForExistingToken(
61
+ merkleRoot: string,
62
+ fileName: string,
63
+ options: TokenQueryOptions = {}
64
+ ): Promise<ExistingTokenInfo | null> {
65
+ try {
66
+ const { ethers } = await import('ethers');
67
+
68
+ // Use Base Sepolia as default network
69
+ const network = options.network || 'Base Sepolia';
70
+ const networkConfig = NETWORK_CONFIG[network];
71
+
72
+ if (!networkConfig) {
73
+ console.log(`⚠️ Unsupported network: ${network}`);
74
+ return null;
75
+ }
76
+
77
+ // Load the contract ABI from deployment file
78
+ const deploymentPath = path.join(process.cwd(), 'zipfile-nft-deployment.json');
79
+
80
+ if (!fs.existsSync(deploymentPath)) {
81
+ console.log(`⚠️ Deployment file not found: ${deploymentPath}`);
82
+ return null;
83
+ }
84
+
85
+ const deploymentInfo = JSON.parse(fs.readFileSync(deploymentPath, 'utf8'));
86
+ const contractAddress = options.contractAddress || networkConfig.contractAddress;
87
+
88
+ // Default wallet address - this should ideally be passed in from the calling context
89
+ const walletAddress = options.walletAddress || "0x6d4d3075d102376164818471c2f974b9ef211df2";
90
+
91
+ // Try multiple RPC endpoints
92
+ for (const rpcUrl of networkConfig.rpcUrls) {
93
+ try {
94
+ console.log(` Trying RPC: ${rpcUrl}`);
95
+ const provider = new ethers.JsonRpcProvider(rpcUrl);
96
+ const contract = new ethers.Contract(contractAddress, deploymentInfo.abi, provider);
97
+
98
+ // Get balance of current wallet to know how many tokens to check
99
+ const balance = await contract.balanceOf(walletAddress);
100
+ console.log(` Wallet owns ${balance} tokens, checking each for merkle root match...`);
101
+
102
+ // Search through wallet's tokens to find one with our merkle root
103
+ // First try: Query the ZipFileTokenized events for efficiency
104
+ try {
105
+ const filter = contract.filters.ZipFileTokenized();
106
+ const events = await contract.queryFilter(filter, 0, 'latest');
107
+
108
+ for (const event of events) {
109
+ if ('args' in event && event.args) {
110
+ const tokenId = event.args.tokenId.toString();
111
+ const eventMerkleRoot = event.args.merkleRootHash;
112
+ const eventCreator = event.args.creator;
113
+
114
+ // Check if this is our token (same merkle root and created by our wallet)
115
+ if (eventMerkleRoot === merkleRoot && eventCreator.toLowerCase() === walletAddress.toLowerCase()) {
116
+ console.log(` ✅ Found token ${tokenId} with matching merkle root (created by our wallet)`);
117
+
118
+ // Get block timestamp for more accurate mint time
119
+ const block = await provider.getBlock(event.blockNumber);
120
+ const mintTime = block ? new Date(block.timestamp * 1000).toISOString() : new Date().toISOString();
121
+
122
+ // Create token metadata with the information we have
123
+ const tokenInfo: ExistingTokenInfo = {
124
+ version: "1.0",
125
+ tokenId: tokenId,
126
+ contractAddress: contractAddress,
127
+ network: network,
128
+ networkChainId: networkConfig.chainId,
129
+ transactionHash: event.transactionHash,
130
+ merkleRoot: merkleRoot,
131
+ fileName: fileName,
132
+ mintedAt: mintTime
133
+ };
134
+
135
+ return tokenInfo;
136
+ }
137
+ }
138
+ }
139
+ } catch (eventError) {
140
+ console.log(` ⚠️ Event query failed, trying direct token scan...`);
141
+
142
+ // Fallback: try scanning recent token IDs directly
143
+ const totalSupply = await contract.totalSupply();
144
+ console.log(` Scanning ${totalSupply} total tokens...`);
145
+
146
+ // Limit scanning to avoid RPC timeouts
147
+ const maxTokensToScan = Math.min(Number(totalSupply), 1000);
148
+
149
+ for (let tokenId = 0; tokenId < maxTokensToScan; tokenId++) {
150
+ try {
151
+ const owner = await contract.ownerOf(tokenId);
152
+ if (owner.toLowerCase() === walletAddress.toLowerCase()) {
153
+ const zipInfo = await contract.getZipFileInfo(tokenId);
154
+
155
+ if (zipInfo.merkleRootHash === merkleRoot) {
156
+ console.log(` ✅ Found token ${tokenId} with matching merkle root (owned by our wallet)`);
157
+
158
+ const tokenInfo: ExistingTokenInfo = {
159
+ version: "1.0",
160
+ tokenId: tokenId.toString(),
161
+ contractAddress: contractAddress,
162
+ network: network,
163
+ networkChainId: networkConfig.chainId,
164
+ merkleRoot: merkleRoot,
165
+ fileName: fileName,
166
+ mintedAt: new Date(Number(zipInfo.tokenizationTime) * 1000).toISOString()
167
+ };
168
+
169
+ return tokenInfo;
170
+ }
171
+ }
172
+ } catch (tokenError) {
173
+ // Token might not exist or be accessible, continue searching
174
+ continue;
175
+ }
176
+ }
177
+
178
+ if (Number(totalSupply) > maxTokensToScan) {
179
+ console.log(` ⚠️ Only scanned first ${maxTokensToScan} tokens (total: ${totalSupply})`);
180
+ }
181
+ }
182
+
183
+ console.log(' ❌ No token found with matching merkle root');
184
+ return null;
185
+
186
+ } catch (rpcError) {
187
+ const errorMessage = rpcError instanceof Error ? rpcError.message : String(rpcError);
188
+ console.log(` ⚠️ RPC ${rpcUrl} failed: ${errorMessage.substring(0, 80)}...`);
189
+ continue;
190
+ }
191
+ }
192
+
193
+ console.log(' ❌ All RPC endpoints failed');
194
+ return null;
195
+
196
+ } catch (error) {
197
+ const errorMessage = error instanceof Error ? error.message : String(error);
198
+ console.log(' ❌ Blockchain query failed:', errorMessage);
199
+ return null;
200
+ }
201
+ }
202
+
203
+ /**
204
+ * Queries all tokens owned by a specific wallet address
205
+ * @param walletAddress - The wallet address to query
206
+ * @param options - Optional query parameters
207
+ * @returns Promise resolving to array of token info
208
+ */
209
+ export async function queryWalletTokens(
210
+ walletAddress: string,
211
+ options: TokenQueryOptions = {}
212
+ ): Promise<ExistingTokenInfo[]> {
213
+ try {
214
+ const { ethers } = await import('ethers');
215
+
216
+ const network = options.network || 'Base Sepolia';
217
+ const networkConfig = NETWORK_CONFIG[network];
218
+
219
+ if (!networkConfig) {
220
+ console.log(`⚠️ Unsupported network: ${network}`);
221
+ return [];
222
+ }
223
+
224
+ const deploymentPath = path.join(process.cwd(), 'zipfile-nft-deployment.json');
225
+
226
+ if (!fs.existsSync(deploymentPath)) {
227
+ console.log(`⚠️ Deployment file not found: ${deploymentPath}`);
228
+ return [];
229
+ }
230
+
231
+ const deploymentInfo = JSON.parse(fs.readFileSync(deploymentPath, 'utf8'));
232
+ const contractAddress = options.contractAddress || networkConfig.contractAddress;
233
+
234
+ const tokens: ExistingTokenInfo[] = [];
235
+
236
+ // Try the first available RPC endpoint
237
+ const rpcUrl = networkConfig.rpcUrls[0];
238
+ try {
239
+ const provider = new ethers.JsonRpcProvider(rpcUrl);
240
+ const contract = new ethers.Contract(contractAddress, deploymentInfo.abi, provider);
241
+
242
+ const balance = await contract.balanceOf(walletAddress);
243
+ console.log(` Wallet ${walletAddress} owns ${balance} tokens`);
244
+
245
+ // Query events for tokens created by this wallet
246
+ const filter = contract.filters.ZipFileTokenized();
247
+ const events = await contract.queryFilter(filter, 0, 'latest');
248
+
249
+ for (const event of events) {
250
+ if ('args' in event && event.args) {
251
+ const eventCreator = event.args.creator;
252
+
253
+ if (eventCreator.toLowerCase() === walletAddress.toLowerCase()) {
254
+ const tokenId = event.args.tokenId.toString();
255
+ const merkleRoot = event.args.merkleRootHash;
256
+
257
+ const block = await provider.getBlock(event.blockNumber);
258
+ const mintTime = block ? new Date(block.timestamp * 1000).toISOString() : new Date().toISOString();
259
+
260
+ const tokenInfo: ExistingTokenInfo = {
261
+ version: "1.0",
262
+ tokenId: tokenId,
263
+ contractAddress: contractAddress,
264
+ network: network,
265
+ networkChainId: networkConfig.chainId,
266
+ transactionHash: event.transactionHash,
267
+ merkleRoot: merkleRoot,
268
+ fileName: `token-${tokenId}.zip`, // Placeholder filename
269
+ mintedAt: mintTime
270
+ };
271
+
272
+ tokens.push(tokenInfo);
273
+ }
274
+ }
275
+ }
276
+
277
+ } catch (error) {
278
+ console.log(' ❌ Failed to query wallet tokens:', error instanceof Error ? error.message : String(error));
279
+ }
280
+
281
+ return tokens;
282
+
283
+ } catch (error) {
284
+ console.log(' ❌ Wallet query failed:', error instanceof Error ? error.message : String(error));
285
+ return [];
286
+ }
287
+ }