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.
- package/README.md +400 -0
- package/contracts/Lock.sol +34 -0
- package/contracts/bytecode.txt +1 -0
- package/contracts/compile-contract.js +168 -0
- package/contracts/deploy/deploy-both-contracts.js +286 -0
- package/contracts/deploy/deploy-enhanced-contract.js +197 -0
- package/contracts/deploy/deploy-private-contract.js +190 -0
- package/contracts/deploy/deploy-public-clean.js +1063 -0
- package/contracts/deploy/deploy-public-contract.js +1107 -0
- package/contracts/deployment.config.example.js +25 -0
- package/contracts/remix-compile.js +222 -0
- package/contracts/setup-deployment.js +51 -0
- package/contracts/src/NZIP-NFT-Private.sol +350 -0
- package/contracts/src/NZIP-NFT.sol +265 -0
- package/contracts/src/ZipFileNFT-DEPRECATED-6field.sol +260 -0
- package/contracts/src/ZipFileNFT-DEPRECATED-Enhanced.sol +289 -0
- package/dist/browser/CertificateGeneratorBrowser.d.ts +75 -0
- package/dist/browser/CertificateGeneratorBrowser.js +433 -0
- package/dist/browser/TokenVerifierBrowser.d.ts +76 -0
- package/dist/browser/TokenVerifierBrowser.js +299 -0
- package/dist/browser/ZipKitBrowser.d.ts +18 -0
- package/dist/browser/ZipKitBrowser.js +250 -0
- package/dist/browser/ZipKitStampBrowser.d.ts +2 -0
- package/dist/browser/ZipKitStampBrowser.js +12 -0
- package/dist/browser-index.d.ts +21 -0
- package/dist/browser-index.js +58 -0
- package/dist/browser.d.ts +21 -0
- package/dist/browser.js +58 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +76 -0
- package/dist/server/blockchain/CertificateToken.d.ts +48 -0
- package/dist/server/blockchain/CertificateToken.js +386 -0
- package/dist/server/blockchain/NFTMinter.d.ts +41 -0
- package/dist/server/blockchain/NFTMinter.js +323 -0
- package/dist/server/blockchain/TokenQuerier.d.ts +31 -0
- package/dist/server/blockchain/TokenQuerier.js +259 -0
- package/dist/server/blockchain/TokenVerifier.d.ts +20 -0
- package/dist/server/blockchain/TokenVerifier.js +395 -0
- package/dist/server/blockchain/WalletAnalyzer.d.ts +81 -0
- package/dist/server/blockchain/WalletAnalyzer.js +356 -0
- package/dist/server/blockchain/WalletFormatter.d.ts +39 -0
- package/dist/server/blockchain/WalletFormatter.js +196 -0
- package/dist/server/blockchain/WalletManager.d.ts +46 -0
- package/dist/server/blockchain/WalletManager.js +294 -0
- package/dist/server/blockchain/ZipMetadata.d.ts +27 -0
- package/dist/server/blockchain/ZipMetadata.js +131 -0
- package/dist/server/blockchain/ZipTokenProcessor.d.ts +44 -0
- package/dist/server/blockchain/ZipTokenProcessor.js +204 -0
- package/dist/server/blockchain/constants.d.ts +102 -0
- package/dist/server/blockchain/constants.js +968 -0
- package/dist/server/blockchain/index.d.ts +9 -0
- package/dist/server/blockchain/index.js +44 -0
- package/dist/server/blockchain/test-certificate.d.ts +8 -0
- package/dist/server/blockchain/test-certificate.js +100 -0
- package/dist/server/blockchain/types.d.ts +69 -0
- package/dist/server/blockchain/types.js +51 -0
- package/dist/server/platform/server/ZipKitServer.d.ts +105 -0
- package/dist/server/platform/server/ZipKitServer.js +437 -0
- package/dist/server/platform/servercomponents/ZipKitOTS.d.ts +91 -0
- package/dist/server/platform/servercomponents/ZipKitOTS.js +271 -0
- package/dist/shared/ZipEntry.d.ts +131 -0
- package/dist/shared/ZipEntry.js +686 -0
- package/dist/shared/ZipKit.d.ts +249 -0
- package/dist/shared/ZipKit.js +789 -0
- package/dist/shared/components/CertificateOTS.d.ts +15 -0
- package/dist/shared/components/CertificateOTS.js +116 -0
- package/dist/shared/components/FileSys.d.ts +2 -0
- package/dist/shared/components/FileSys.js +113 -0
- package/dist/shared/components/HashAccumulator.d.ts +55 -0
- package/dist/shared/components/HashAccumulator.js +100 -0
- package/dist/shared/components/Support.d.ts +63 -0
- package/dist/shared/components/Support.js +70 -0
- package/dist/shared/components/Util.d.ts +21 -0
- package/dist/shared/components/Util.js +115 -0
- package/dist/shared/components/ZipCrypto.d.ts +3 -0
- package/dist/shared/components/ZipCrypto.js +78 -0
- package/dist/shared/components/merkleTree.d.ts +66 -0
- package/dist/shared/components/merkleTree.js +136 -0
- package/dist/shared/constants/Errors.d.ts +53 -0
- package/dist/shared/constants/Errors.js +69 -0
- package/dist/shared/constants/Headers.d.ts +167 -0
- package/dist/shared/constants/Headers.js +191 -0
- package/dist/shared/types/index.d.ts +21 -0
- package/dist/shared/types/index.js +3 -0
- package/dist/shared/types.d.ts +33 -0
- package/dist/shared/types.js +8 -0
- package/dist/shared/version.d.ts +4 -0
- package/dist/shared/version.js +7 -0
- package/dist/types/index.d.ts +21 -0
- package/dist/types/index.js +3 -0
- package/examples/unzip-token.ts +488 -0
- package/examples/zip-token.ts +604 -0
- package/package.json +117 -0
- package/src/browser/CertificateGeneratorBrowser.ts +498 -0
- package/src/browser/TokenVerifierBrowser.ts +344 -0
- package/src/browser/ZipKitBrowser.ts +242 -0
- package/src/browser/ZipKitStampBrowser.ts +9 -0
- package/src/browser-index.ts +36 -0
- package/src/index.ts +48 -0
- package/src/server/blockchain/CertificateToken.ts +444 -0
- package/src/server/blockchain/NFTMinter.ts +357 -0
- package/src/server/blockchain/TokenQuerier.ts +287 -0
- package/src/server/blockchain/TokenVerifier.ts +453 -0
- package/src/server/blockchain/WalletAnalyzer.ts +397 -0
- package/src/server/blockchain/WalletFormatter.ts +206 -0
- package/src/server/blockchain/WalletManager.ts +294 -0
- package/src/server/blockchain/ZipMetadata.ts +117 -0
- package/src/server/blockchain/ZipTokenProcessor.ts +234 -0
- package/src/server/blockchain/constants.ts +969 -0
- package/src/server/blockchain/index.ts +42 -0
- package/src/server/blockchain/test-certificate.ts +137 -0
- package/src/server/blockchain/types.ts +134 -0
- package/src/server/platform/server/ZipKitServer.ts +459 -0
- package/src/server/platform/servercomponents/ZipKitOTS.ts +313 -0
- package/src/server/platform/servercomponents/opentimestamps.d.ts +23 -0
- package/src/shared/ZipEntry.ts +767 -0
- package/src/shared/ZipKit.ts +899 -0
- package/src/shared/components/CertificateOTS.ts +132 -0
- package/src/shared/components/FileSys.ts +87 -0
- package/src/shared/components/HashAccumulator.ts +113 -0
- package/src/shared/components/Support.ts +77 -0
- package/src/shared/components/Util.ts +113 -0
- package/src/shared/components/ZipCrypto.ts +91 -0
- package/src/shared/components/merkleTree.ts +162 -0
- package/src/shared/constants/Errors.ts +82 -0
- package/src/shared/constants/Headers.ts +203 -0
- package/src/shared/types/index.ts +25 -0
- package/src/shared/types/modules.d.ts +22 -0
- package/src/shared/types/opentimestamps.d.ts +23 -0
- package/src/shared/types.d.ts +8 -0
- package/src/shared/types.ts +43 -0
- package/src/shared/version.ts +4 -0
- package/src/types/index.ts +25 -0
- package/src/types/modules.d.ts +22 -0
- 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
|
+
}
|