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,604 @@
1
+ #!/usr/bin/env node
2
+
3
+ // ======================================
4
+ // NeoZip.ts
5
+ // A command-line utility for creating and managing ZIP archives using NeoZipKit
6
+ // Copyright (c) 2025 NeoWare, Inc. All rights reserved.
7
+ // ======================================
8
+
9
+ import * as readline from 'readline';
10
+ import { exit, off } from 'process';
11
+ import { ZipKitServer, NEOZIPKIT_INFO } from 'neozipkit';
12
+ import type { CompressOptions } from 'neozipkit';
13
+ import yargs from 'yargs';
14
+ import { hideBin } from 'yargs/helpers';
15
+ import { NEOZIP_VERSION, NEOZIP_RELEASE_DATE } from './version';
16
+ import * as path from 'path';
17
+ import * as fs from 'fs';
18
+ import * as crc32 from 'crc-32';
19
+
20
+ // Import blockchain components
21
+ import { ZipTokenProcessor, MintingOptions, queryBlockchainForExistingToken } from './blockchain';
22
+ import { TOKENIZED_METADATA } from '../src/constants/Headers';
23
+
24
+ // Display copyright and version header on startup
25
+ console.log(`NeoZip v${NEOZIP_VERSION} (${NEOZIP_RELEASE_DATE}) Copyright (c) 2025 NeoWare, Inc. All rights reserved`);
26
+ console.log(`Using NeoZipKit v${NEOZIPKIT_INFO.version} (${NEOZIPKIT_INFO.releaseDate})`);
27
+
28
+ // NFT Metadata template
29
+ const NFT_METADATA_TEMPLATE = {
30
+ name: "NeoZip Archive Token",
31
+ description: "This NFT represents ownership of a timestamped and verified ZIP archive on Base",
32
+ image: "", // Will be set to IPFS hash of thumbnail
33
+ attributes: [
34
+ {
35
+ trait_type: "Archive Hash",
36
+ value: "" // Will be set to the Merkle root hash of the archive
37
+ },
38
+ {
39
+ trait_type: "Creation Date",
40
+ value: "" // Will be set to timestamp
41
+ },
42
+ {
43
+ trait_type: "Blockchain",
44
+ value: "Base" // Specify Base as the blockchain
45
+ }
46
+ ]
47
+ };
48
+
49
+ /**
50
+ * Command line argument parser configuration using yargs.
51
+ * Supports the following options:
52
+ * -r/--recurse: Recurse into directories
53
+ * -m/--move: Move files into archive after adding
54
+ * -u/--update: Add new or update existing entries if source is newer
55
+ * -e/--encrypt: Encrypt entries using password protection
56
+ * -f/--freshen: Update only existing entries
57
+ * -i/--include: Include only specified files
58
+ * -x/--exclude: Exclude specified files
59
+ * -z/--zipComment: Add archive comment
60
+ * -L/--license: Show license information
61
+ * -H/--SHA-256: Calculate SHA-256 hash for entries
62
+ * -S/--timestamp: Submit timestamp to Bitcoin blockchain
63
+ * -Z/--zipcomp: Use Zip compatible compression (deflate) instead of Zstandard
64
+ * -0/--store: Store files without compression
65
+ * -1...-9: Set compression level (1=fastest, 9=highest compression, 6=default)
66
+ */
67
+ const options = yargs(hideBin(process.argv))
68
+ .usage("Usage: $0 <zipfile> <files to add...>")
69
+ .demandCommand(1, "Error - The .zip filename is required.")
70
+ .option('r', { alias: 'recurse', describe: 'Recurse into directories', type: 'boolean' })
71
+ .option('m', { alias: 'move', describe: 'Move specified files into the zip archive', type: 'boolean' })
72
+ .option('u', { alias: 'update', describe: 'Add new entries/update existing entries only if source is newer', type: 'boolean' })
73
+ .option('e', { alias: 'encrypt', describe: 'Encrypt entries in a zip archive', type: 'boolean' })
74
+ .option('f', { alias: 'freshen', describe: 'Update existing entries only (no new entries added)', type: 'boolean' })
75
+ .option('x', { alias: 'exclude', describe: 'Exclude the specified files', type: 'array' })
76
+ .option('L', { alias: 'license', describe: 'Display software license', type: 'boolean' })
77
+ .option('H', { alias: 'SHA-256', describe: 'Calculate SHA-256 Hash on entries', type: 'boolean' })
78
+ // .option('S', { alias: 'timestamp', describe: 'Submit Timestamp to Bitcoin blockchain', type: 'boolean' })
79
+ .option('Z', { alias: 'zipcomp', describe: 'Use Zip compatible compression (deflate) instead of Zstandard', type: 'boolean', default: false })
80
+ .option('0', {
81
+ alias: 'store',
82
+ type: 'boolean',
83
+ description: 'Store files without compression',
84
+ default: false
85
+ })
86
+ // Add individual compression level shortcuts
87
+ .option('1', { type: 'boolean', hidden: true })
88
+ .option('2', { type: 'boolean', hidden: true })
89
+ .option('3', { type: 'boolean', hidden: true })
90
+ .option('4', { type: 'boolean', hidden: true })
91
+ .option('5', { type: 'boolean', hidden: true })
92
+ .option('6', { type: 'boolean', hidden: true })
93
+ .option('7', { type: 'boolean', hidden: true })
94
+ .option('8', { type: 'boolean', hidden: true })
95
+ .option('9', { type: 'boolean', hidden: true })
96
+ .option('l', { alias: 'list', describe: 'List archive files (short format)', type: 'boolean' })
97
+ // Add blockchain wallet options
98
+ .option('wallet', { describe: 'Path to wallet key file or mnemonic phrase', type: 'string' })
99
+ .option('network', { describe: 'Base network to connect to', type: 'string', default: 'base-sepolia' })
100
+ .option('skip-blockchain', { describe: 'Skip blockchain operations and create regular ZIP file', type: 'boolean' })
101
+ .option('y', { alias: 'yes', describe: 'Automatically confirm NFT token creation without prompting', type: 'boolean' })
102
+ .help('help', 'Show help').alias('help', 'h')
103
+ .wrap(null)
104
+ .group(['r', 'm', 'd', 'e', 'f', 'i', 'j', 'x', 'z', 'L', 'H', 'S', 'Z', '0'], 'Options:')
105
+ .group(['wallet', 'network', 'skip-blockchain', 'y'], 'Blockchain Options:')
106
+ .epilog('-1...-9: Compression levels: -1 (fastest) to -9 (maximum compression), -6 (default)\n\nCopyright (c) 2025 NeoWare, Inc. - Type "neozip "-L" for software license.')
107
+ .parseSync();
108
+
109
+ const zipKit = new ZipKitServer();
110
+
111
+ const zipFile = options._[0] as string;
112
+ zipKit.loadZipFile(zipFile);
113
+
114
+ if (options.L) {
115
+ console.log('Copyright (c) 2025 by NeoWare, Inc. All rights reserved\n');
116
+ exit(0);
117
+ }
118
+
119
+ const filesAdded = zipKit.addFiles(options._.slice(1) as string[], options.f, options.u);
120
+ if (filesAdded === 0) {
121
+ console.error("Error: No files specified to add to archive.");
122
+ exit(1);
123
+ }
124
+ console.log('filesAdded', filesAdded);
125
+ processZip();
126
+
127
+ /**
128
+ * Determines the compression level from command line options
129
+ * @returns number representing the compression level (0-9)
130
+ */
131
+ function getCompressionLevel(): number {
132
+ if (options.store) return 0;
133
+
134
+ // Check for individual compression level flags (-1 through -9)
135
+ for (let i = 1; i <= 9; i++) {
136
+ if (options[i.toString()]) {
137
+ return i;
138
+ }
139
+ }
140
+
141
+ // Default compression level
142
+ return 6;
143
+ }
144
+
145
+ /**
146
+ * Validates compression options for conflicts
147
+ * @returns boolean indicating if the options are valid
148
+ */
149
+ function validateCompressionOptions(): boolean {
150
+ // Count how many individual compression level options are set
151
+ let levelCount = 0;
152
+ for (let i = 1; i <= 9; i++) {
153
+ if (options[i.toString()]) {
154
+ levelCount++;
155
+ }
156
+ }
157
+
158
+ // Check if more than one individual compression level is specified
159
+ if (levelCount > 1) {
160
+ console.error('Error: Cannot specify more than one compression level (-1 through -9)');
161
+ return false;
162
+ }
163
+
164
+ // Check if both store (0) and compression level options are specified
165
+ if (options.store && levelCount > 0) {
166
+ console.error('Error: Cannot specify both -0/--store and compression level options');
167
+ return false;
168
+ }
169
+
170
+ return true;
171
+ }
172
+
173
+ /**
174
+ * Sets the compression options for the ZIP archive
175
+ * @returns Promise resolving to the compression options
176
+ */
177
+ async function setCompressionOptions(): Promise<CompressOptions> {
178
+ // Validate compression options
179
+ if (!validateCompressionOptions()) {
180
+ exit(1);
181
+ }
182
+
183
+ // Display compression information
184
+ if (options.store) {
185
+ console.log('Using storage mode (no compression)');
186
+ } else {
187
+ const compressionLevel = getCompressionLevel();
188
+ const compressionMethod = options.Z ? 'Deflate (zip-compatible)' : 'ZStandard';
189
+ console.log(`Using ${compressionMethod} compression algorithm with level ${compressionLevel}`);
190
+ }
191
+
192
+ // Input password
193
+ let _password = null;
194
+ if (options.e) {
195
+ _password = await inputPassword('Enter password: ');
196
+ const _verify = await inputPassword('Verify password: ');
197
+ if (_password != _verify) {
198
+ console.log('Zip Error: Password verification failed');
199
+ exit(1);
200
+ }
201
+ }
202
+
203
+ const compressOptions: CompressOptions = {
204
+ level: getCompressionLevel(),
205
+ password: options.e && _password ? _password : undefined,
206
+ useSHA256: options.H,
207
+ useZstd: !options.Z // Use Zstandard by default, disable when -Z is specified
208
+ };
209
+ console.log('compressOptions', compressOptions);
210
+
211
+ return compressOptions;
212
+ }
213
+
214
+ /**
215
+ * Outputs the final ZIP archive file and handles blockchain operations
216
+ */
217
+ async function processZip() {
218
+ // Set compression options
219
+ const compressOptions = await setCompressionOptions();
220
+
221
+ // Use our new createZipToken function that properly processes files first,
222
+ // then calculates merkle root, then handles blockchain operations
223
+ await createZipToken(zipFile, compressOptions);
224
+ }
225
+
226
+ /**
227
+ * Prompts the user for a password with optional verification
228
+ * @param question - The prompt message to display
229
+ * @returns Promise resolving to the entered password
230
+ */
231
+ async function inputPassword(question: string): Promise<string> {
232
+ const rl = readline.createInterface({
233
+ input: process.stdin,
234
+ output: process.stdout,
235
+ terminal: true
236
+ });
237
+
238
+ return new Promise<string>((resolve) => {
239
+ rl.question(question, (input: string) => {
240
+ rl.close();
241
+ resolve(input);
242
+ });
243
+ });
244
+ }
245
+
246
+ /**
247
+ * Creates a ZIP file with NFT token metadata
248
+ *
249
+ * This method creates a ZIP file with NFT token metadata, which includes
250
+ * blockchain token information for the ZIP file's contents.
251
+ *
252
+ * @param zipFile - The path to the ZIP file to create
253
+ * @param compressOptions - The compression options for the ZIP file
254
+ * @returns void - Creates the ZIP file with embedded token metadata
255
+ */
256
+ async function createZipToken(zipFile: string, compressOptions: CompressOptions) {
257
+
258
+ // Ensure the directory structure for the output file exists
259
+ const outputDir = path.dirname(zipFile);
260
+ if (!fs.existsSync(outputDir)) {
261
+ fs.mkdirSync(outputDir, { recursive: true });
262
+ }
263
+
264
+ // Generate a unique temporary file name in the output directory
265
+ const _tempZipFile = path.join(outputDir, `neozip-${Date.now()}.zip`);
266
+
267
+ // Open or create the .zip file for writing
268
+ const fd = fs.openSync(_tempZipFile, 'w');
269
+
270
+ console.log('Processing files and calculating hashes...');
271
+
272
+ // Remove any existing token metadata entries from the ZIP directory BEFORE processing
273
+ console.log('🔧 Scanning ZIP directory for existing token files...');
274
+ const currentZipEntries = zipKit.getDirectory();
275
+ if (currentZipEntries) {
276
+ const existingTokenEntries = currentZipEntries.filter((entry: any) => entry.filename === TOKENIZED_METADATA);
277
+ if (existingTokenEntries.length > 0) {
278
+ console.log(` Found ${existingTokenEntries.length} existing token metadata entries - removing them`);
279
+ // Remove existing token entries
280
+ for (const existingEntry of existingTokenEntries) {
281
+ const index = currentZipEntries.indexOf(existingEntry);
282
+ if (index > -1) {
283
+ currentZipEntries.splice(index, 1);
284
+ console.log(` ❌ Removed: ${existingEntry.filename}`);
285
+ }
286
+ }
287
+ console.log(` ✅ Successfully removed ${existingTokenEntries.length} existing token entries`);
288
+ } else {
289
+ console.log(' ✅ No existing token entries found in ZIP directory');
290
+ }
291
+ }
292
+
293
+ // Process all files and calculate SHA256 hashes - this populates the Central Directory
294
+ let offset = await zipKit.createZipFileData(compressOptions, {
295
+ onReadEntryData: (entry: any) => {
296
+ try {
297
+ return fs.readFileSync(entry.filename);
298
+ } catch (error) {
299
+ throw new Error(`Failed to read file: ${entry.filename}`);
300
+ }
301
+ },
302
+ onOutputBuffer: (buffer: Buffer) => {
303
+ fs.writeSync(fd, buffer);
304
+ },
305
+ onEntryDone: (entry: any, status: string) => {
306
+ console.log(`${status} ${entry.filename} to ${zipFile}`);
307
+ }
308
+ });
309
+
310
+ // NOW we can get the merkle root since files have been processed
311
+ const _merkleRoot = zipKit.getMerkleRoot();
312
+
313
+ // Exit immediately if merkle root is null
314
+ if (!_merkleRoot) {
315
+ fs.closeSync(fd);
316
+ fs.unlinkSync(_tempZipFile);
317
+ console.error('Error: Merkle root is null after processing files.');
318
+ console.log('This typically means insufficient files or missing -H flag for SHA256 calculation.');
319
+ exit(1);
320
+ }
321
+
322
+ console.log('\nMerkle Root: ' + _merkleRoot);
323
+
324
+ // Check for existing token inside the ZIP file and compare merkle roots
325
+ let existingTokenMetadata = null;
326
+ let shouldReuseExistingToken = false;
327
+
328
+ // Check if the ZIP file already exists and contains a token
329
+ if (fs.existsSync(zipFile)) {
330
+ console.log('\n🔍 Checking existing ZIP file for token metadata...');
331
+
332
+ try {
333
+ // Load the existing ZIP file to check for token
334
+ const existingZipKit = new ZipKitServer();
335
+ const existingZipData = fs.readFileSync(zipFile);
336
+ existingZipKit.loadZip(existingZipData);
337
+
338
+ // Look for existing token entry
339
+ const existingEntries = existingZipKit.getDirectory();
340
+ const existingTokenEntry = existingEntries?.find((entry: any) => entry.filename === TOKENIZED_METADATA);
341
+
342
+ if (existingTokenEntry) {
343
+ console.log('📋 Found existing token in ZIP file');
344
+
345
+ // Extract and parse the existing token
346
+ const tokenBuffer = await existingZipKit.extract(existingTokenEntry);
347
+ if (tokenBuffer) {
348
+ const tokenContent = tokenBuffer.toString('utf8');
349
+ existingTokenMetadata = JSON.parse(tokenContent);
350
+
351
+ console.log('\n🔍 Existing token detected:');
352
+ console.log('═'.repeat(60));
353
+ console.log(`Token ID: ${existingTokenMetadata.tokenId}`);
354
+ console.log(`Contract: ${existingTokenMetadata.contractAddress}`);
355
+ console.log(`Network: ${existingTokenMetadata.network}`);
356
+ console.log(`Merkle Root: ${existingTokenMetadata.merkleRoot}`);
357
+ console.log(`File Name: ${existingTokenMetadata.fileName}`);
358
+ console.log(`Minted At: ${existingTokenMetadata.mintedAt}`);
359
+ console.log('═'.repeat(60));
360
+
361
+ // Compare merkle roots
362
+ console.log('\n🔍 Comparing calculated merkle root with existing token...');
363
+ console.log(`Calculated: ${_merkleRoot}`);
364
+ console.log(`Existing: ${existingTokenMetadata.merkleRoot}`);
365
+
366
+ if (_merkleRoot === existingTokenMetadata.merkleRoot) {
367
+ console.log('✅ Merkle roots match! The content hasn\'t changed.');
368
+
369
+ if (!options.skipBlockchain && !options.y) {
370
+ console.log('\n🤔 What would you like to do?');
371
+ console.log('═'.repeat(60));
372
+ console.log('1. Keep existing token (recommended for unchanged content)');
373
+ console.log('2. Create new token with current timestamp');
374
+ console.log('═'.repeat(60));
375
+
376
+ const userChoice = await inputPassword('Enter your choice (1-2): ');
377
+ if (userChoice.trim() === '1') {
378
+ shouldReuseExistingToken = true;
379
+ console.log('✅ Keeping existing token metadata');
380
+ } else {
381
+ console.log('✅ Will create new token with current timestamp');
382
+ }
383
+ } else if (options.y) {
384
+ // Auto-mode: default to keeping existing token for unchanged content
385
+ shouldReuseExistingToken = true;
386
+ console.log('✅ Auto-mode: Keeping existing token for unchanged content');
387
+ }
388
+ } else {
389
+ console.log('⚠️ Merkle roots differ! The content has been modified.');
390
+ console.log(' A new token will be created for the updated content.');
391
+ }
392
+ }
393
+ } else {
394
+ console.log('✅ No existing token found in ZIP file');
395
+ }
396
+
397
+ } catch (error) {
398
+ console.warn(` ⚠️ Could not read existing ZIP file: ${error}`);
399
+ existingTokenMetadata = null;
400
+ }
401
+ } else {
402
+ console.log('✅ Creating new ZIP file (no existing file found)');
403
+ }
404
+
405
+ // Handle blockchain operations
406
+ let tokenMetadataEntry = null;
407
+ let userCancelledMinting = false;
408
+
409
+ if (!options.skipBlockchain) {
410
+ if (shouldReuseExistingToken && existingTokenMetadata) {
411
+ // Reuse existing token - no blockchain operations needed
412
+ console.log('\n♻️ Reusing existing token metadata...');
413
+
414
+ // Create the token content from existing metadata
415
+ const tokenContent = JSON.stringify(existingTokenMetadata, null, 2);
416
+ console.log('\n📄 Existing token metadata content:');
417
+ console.log('═'.repeat(60));
418
+ console.log(tokenContent);
419
+ console.log('═'.repeat(60));
420
+
421
+ // Create a ZipEntry for the token metadata (virtual file in ZIP)
422
+ const tokenEntry = zipKit.createZipEntry('META-INF/NZIP.TOKEN');
423
+ const tokenBuffer = Buffer.from(tokenContent, 'utf8');
424
+
425
+ tokenEntry.timeDateDOS = tokenEntry.setDateTime(new Date());
426
+ tokenEntry.compressedSize = tokenBuffer.length;
427
+ tokenEntry.uncompressedSize = tokenBuffer.length;
428
+ tokenEntry.cmpMethod = 0; // STORED
429
+ tokenEntry.crc = (crc32.buf(tokenBuffer) >>> 0); // Convert to unsigned 32-bit integer
430
+ tokenEntry.fileBuffer = tokenBuffer;
431
+ tokenEntry.platform = process.platform;
432
+
433
+ // Ensure this entry is marked for inclusion
434
+ tokenEntry.isUpdated = true;
435
+
436
+ tokenMetadataEntry = tokenEntry;
437
+ console.log('✅ Existing token metadata prepared for ZIP inclusion!');
438
+
439
+ } else {
440
+ // Need to create new token - ask user for confirmation
441
+ console.log('\n🎉 ZIP archive ready for tokenization!');
442
+ console.log('📋 This will mint an NFT token representing this archive on the Base blockchain.');
443
+ console.log('💰 Blockchain operations may require gas fees (small network transaction costs).');
444
+
445
+ let confirmed = false;
446
+ if (options.y || options.yes) {
447
+ console.log('✅ Auto-confirming blockchain operations (--yes flag)');
448
+ confirmed = true;
449
+ } else {
450
+ const userConfirmed = await inputPassword('Proceed with blockchain operations? (y/n): ');
451
+ confirmed = userConfirmed.toLowerCase().trim() === 'y' || userConfirmed.toLowerCase().trim() === 'yes';
452
+ }
453
+
454
+ if (!confirmed) {
455
+ console.log('❌ Blockchain operations cancelled by user.');
456
+ userCancelledMinting = true;
457
+ } else {
458
+ console.log('✅ Proceeding with NFT token minting...');
459
+
460
+ const mintingOptions: MintingOptions = {
461
+ wallet: options.wallet,
462
+ network: options.network,
463
+ mintToken: true
464
+ };
465
+
466
+ try {
467
+ console.log('\nInitializing blockchain components...');
468
+ const processor = new ZipTokenProcessor(zipFile, _merkleRoot, mintingOptions);
469
+ const state = await processor.processZipToken(mintingOptions);
470
+
471
+ if (state.error) {
472
+ console.error('\nBlockchain processing failed:', state.error);
473
+
474
+ // All blockchain errors should result in cancellation - no token reuse
475
+ userCancelledMinting = true;
476
+ } else {
477
+ console.log('\nBlockchain processing completed successfully!');
478
+
479
+ if (state.walletSetup) {
480
+ console.log(`Wallet: ${state.walletSetup.wallet?.address}`);
481
+ }
482
+
483
+ if (state.mintingResult) {
484
+ console.log(`Token minting: ${state.mintingResult.success ? 'Success' : 'Failed'}`);
485
+ if (!state.mintingResult.success) {
486
+ if (state.mintingResult.message.includes('already tokenized by another wallet')) {
487
+ console.log('\n⚠️ This ZIP content has already been tokenized by someone else!');
488
+ console.log(`ℹ️ ${state.mintingResult.message}`);
489
+ userCancelledMinting = true;
490
+ } else {
491
+ console.error(`Token minting failed: ${state.mintingResult.message}`);
492
+ userCancelledMinting = true;
493
+ }
494
+ } else {
495
+ // New token minted successfully
496
+
497
+ if (state.mintingResult.tokenId) {
498
+ console.log(`Token ID: ${state.mintingResult.tokenId}`);
499
+ console.log(`Transaction: ${state.mintingResult.txHash}`);
500
+ }
501
+ console.log(`Message: ${state.mintingResult.message}`);
502
+
503
+ // Create token metadata entry if we have valid token info
504
+ if (state.tokenInfo && state.mintingResult.tokenId && state.mintingResult.txHash) {
505
+ console.log('\nCreating META-INF/NZIP.TOKEN entry...');
506
+
507
+ // Create the token content
508
+ const tokenContent = JSON.stringify(state.tokenInfo, null, 2);
509
+ console.log('\n📄 Token metadata content:');
510
+ console.log('═'.repeat(60));
511
+ console.log(tokenContent);
512
+ console.log('═'.repeat(60));
513
+
514
+ // Create a ZipEntry for the token metadata (virtual file in ZIP)
515
+ const tokenEntry = zipKit.createZipEntry('META-INF/NZIP.TOKEN');
516
+ const tokenBuffer = Buffer.from(tokenContent, 'utf8');
517
+
518
+ tokenEntry.timeDateDOS = tokenEntry.setDateTime(new Date());
519
+ tokenEntry.compressedSize = tokenBuffer.length;
520
+ tokenEntry.uncompressedSize = tokenBuffer.length;
521
+ tokenEntry.cmpMethod = 0; // STORED
522
+ tokenEntry.crc = (crc32.buf(tokenBuffer) >>> 0); // Convert to unsigned 32-bit integer
523
+ tokenEntry.fileBuffer = tokenBuffer;
524
+ tokenEntry.platform = process.platform;
525
+
526
+ // Ensure this entry is marked for inclusion
527
+ tokenEntry.isUpdated = true;
528
+
529
+ tokenMetadataEntry = tokenEntry;
530
+ console.log('✅ Token metadata entry created for ZIP inclusion!');
531
+ } else {
532
+ console.error('ERROR: Token minting completed but no valid token metadata was created.');
533
+ userCancelledMinting = true;
534
+ }
535
+ }
536
+ }
537
+ }
538
+ } catch (error) {
539
+ console.error('\nError in blockchain processing:', error);
540
+ userCancelledMinting = true;
541
+ }
542
+ }
543
+ }
544
+ }
545
+
546
+
547
+
548
+ // Finish the ZIP file with or without token metadata
549
+ if (tokenMetadataEntry) {
550
+ console.log('\n🔧 Adding new token metadata to ZIP archive...');
551
+ console.log(` Entry: ${tokenMetadataEntry.filename}`);
552
+ console.log(` Size: ${tokenMetadataEntry.uncompressedSize} bytes`);
553
+ }
554
+
555
+ await zipKit.finishZipFileData(
556
+ offset,
557
+ function onOutputBuffer(buffer: Buffer) {
558
+ fs.writeSync(fd, buffer);
559
+ },
560
+ tokenMetadataEntry || undefined
561
+ );
562
+
563
+ // Close the ZIP file
564
+ fs.closeSync(fd);
565
+
566
+ // Delete the existing ZIP file if it exists
567
+ if (fs.existsSync(zipFile)) {
568
+ fs.unlinkSync(zipFile);
569
+ }
570
+
571
+ // Replace with the new ZIP file
572
+ fs.renameSync(_tempZipFile, zipFile);
573
+
574
+ // Report final results
575
+ if (tokenMetadataEntry) {
576
+ console.log('\n🎉 ZIP file created successfully with embedded NFT token metadata!');
577
+ console.log('✅ The archive contains its blockchain identity in META-INF/NZIP.TOKEN');
578
+ console.log('🔒 Full blockchain verification is now possible with this tokenized archive');
579
+ } else if (userCancelledMinting) {
580
+ console.log('\n📦 ZIP file created successfully (non-tokenized).');
581
+ console.log('❌ BLOCKCHAIN TOKEN RETRIEVAL FAILED');
582
+ console.log('💡 Actual error details should be shown above in the blockchain processing section');
583
+ console.log('🔧 Common issues:');
584
+ console.log(' - RPC endpoint connectivity problems');
585
+ console.log(' - Blockchain network congestion');
586
+ console.log(' - Token exists but cannot be queried due to network issues');
587
+ console.log('💡 Only archives with full NFT token metadata support blockchain verification');
588
+ } else if (options.skipBlockchain) {
589
+ console.log('\n📦 ZIP file created successfully (non-tokenized).');
590
+ console.log('ℹ️ Blockchain operations were skipped. Remove --skip-blockchain to mint NFT tokens.');
591
+ } else {
592
+ console.log('\n📦 ZIP file created successfully (non-tokenized).');
593
+ console.log('⚠️ Token operations failed - check error messages above');
594
+ }
595
+
596
+ // Show final archive contents
597
+ console.log('\nFinal archive contents:');
598
+ const zipEntries = zipKit.getDirectory();
599
+ if (zipEntries) {
600
+ zipEntries.forEach((entry: any) => {
601
+ console.log(` ${entry.filename}`);
602
+ });
603
+ }
604
+ }