neozip-cli 0.75.1-beta → 0.80.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/DOCUMENTATION.md +9 -1
  3. package/README.md +22 -10
  4. package/dist/src/commands/mintTimestampProof.js +335 -0
  5. package/dist/src/commands/verifyEmail.js +146 -0
  6. package/dist/src/config/ConfigSetup.js +50 -20
  7. package/dist/src/config/ConfigStore.js +36 -3
  8. package/dist/src/index.js +1 -1
  9. package/dist/src/neolist.js +18 -10
  10. package/dist/src/neounzip.js +305 -60
  11. package/dist/src/neozip/blockchain.js +5 -5
  12. package/dist/src/neozip/createZip.js +207 -41
  13. package/dist/src/neozip/upgradeZip.js +182 -0
  14. package/dist/src/neozip.js +143 -8
  15. package/env.example +10 -0
  16. package/package.json +91 -85
  17. package/dist/neozipkit-bundles/blockchain.js +0 -13725
  18. package/dist/neozipkit-bundles/browser.js +0 -6186
  19. package/dist/neozipkit-bundles/core.js +0 -3839
  20. package/dist/neozipkit-bundles/node.js +0 -17730
  21. package/dist/neozipkit-wrappers/blockchain/core/contracts.js +0 -16
  22. package/dist/neozipkit-wrappers/blockchain/index.js +0 -2
  23. package/dist/neozipkit-wrappers/core/ZipDecompress.js +0 -2
  24. package/dist/neozipkit-wrappers/core/components/HashCalculator.js +0 -2
  25. package/dist/neozipkit-wrappers/core/components/Logger.js +0 -2
  26. package/dist/neozipkit-wrappers/core/constants/Errors.js +0 -2
  27. package/dist/neozipkit-wrappers/core/constants/Headers.js +0 -2
  28. package/dist/neozipkit-wrappers/core/encryption/ZipCrypto.js +0 -7
  29. package/dist/neozipkit-wrappers/core/index.js +0 -3
  30. package/dist/neozipkit-wrappers/index.js +0 -13
  31. package/dist/neozipkit-wrappers/node/index.js +0 -2
package/CHANGELOG.md CHANGED
@@ -5,6 +5,36 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.80.0] - 2026-03-04
9
+
10
+ ### Added
11
+
12
+ - **Zipstamp (blockchain timestamping)**
13
+ - Display Zipstamp server URL when submitting timestamp proof (configurable via `ZIPSTAMP_SERVER_URL`)
14
+ - `neozip verify-email` command and npm script for Zipstamp email configuration
15
+ - Post-mint summary after `neozip mint`: status, token ID, network, contract, transaction, block, and block timestamp (minted) in the same style as Zipstamp verification
16
+ - **Scripts**
17
+ - `scripts/remove-env-from-history.sh` to remove `.env.local` and `.env.local.rem` from git history (see `scripts/README.md`)
18
+ - **Configuration**
19
+ - `neozip config` and `neozip init` work when run via ts-node (fixed dynamic import resolution for `.ts` vs `.js`)
20
+
21
+ ### Changed
22
+
23
+ - User-facing copy now uses "blockchain" instead of "Ethereum blockchain" for Zipstamp and mint flows
24
+ - Zipstamp verification in `neounzip` shows "Zipstamp (blockchain timestamp)" instead of "Ethereum timestamp"
25
+ - Help text: `-ts, --timestamp` described as "Enable Zipstamp timestamp (blockchain)"
26
+
27
+ ### Fixed
28
+
29
+ - `neozip config` / `neozip init` no longer fail with "Cannot find module './config/ConfigSetup.js'" when run via `yarn neozip-config` or ts-node
30
+
31
+ ### Security
32
+
33
+ - `.env.*` (including `.env.local`, `.env.local.rem`) added to `.gitignore` to avoid committing secrets
34
+ - Documented how to purge env files from git history if they were ever committed
35
+
36
+ ---
37
+
8
38
  ## [0.70.0-alpha] - 2025-11-21
9
39
 
10
40
  ### ⚠️ Alpha Release Notice
package/DOCUMENTATION.md CHANGED
@@ -17,7 +17,7 @@ npm install -g neozip-cli@alpha
17
17
  neozip archive.nzip file1.txt file2.txt folder/
18
18
 
19
19
  # Extract files from archive
20
- neounzip archive.nzip extracted/
20
+ neounzip archive.nzip tests/extracted/
21
21
 
22
22
  # List archive contents
23
23
  neolist archive.nzip
@@ -116,6 +116,11 @@ neolist --help # Show listing help
116
116
  - Cryptographic proof of archive existence
117
117
  - Timestamp verification
118
118
 
119
+ - **Zipstamp**
120
+ - Ethereum blockchain timestamping via Zipstamp server
121
+ - Pending timestamps (TS-SUBMIT.NZIP) can be upgraded to confirmed (TIMESTAMP.NZIP)
122
+ - Use `neozip upgrade <archive> [output] [--wait]` to upgrade pending timestamps
123
+
119
124
  - **Wallet Integration**
120
125
  - Interactive wallet setup wizard (`neozip init`)
121
126
  - Configuration management (`neozip config`)
@@ -144,6 +149,9 @@ neozip init
144
149
 
145
150
  # View or modify configuration
146
151
  neozip config
152
+
153
+ # Upgrade pending Zipstamp timestamp to confirmed
154
+ neozip upgrade archive.nzip [output.nzip] [--wait]
147
155
  ```
148
156
 
149
157
  Configuration is stored in `~/.neozip/wallet.json` and supports:
package/README.md CHANGED
@@ -49,6 +49,7 @@ NeoZip CLI is a modern, full-featured ZIP utility that provides:
49
49
  ### Blockchain Features
50
50
  - **Tokenization**: Create on-chain tokens for archives
51
51
  - **OpenTimestamp**: Bitcoin blockchain timestamping
52
+ - **Zipstamp**: Ethereum blockchain timestamping (Zipstamp server)
52
53
  - **Integrity Verification**: Verify archive integrity on-chain
53
54
  - **Network Support**: Base Sepolia, Base Mainnet, Arbitrum, and more
54
55
 
@@ -103,7 +104,7 @@ neozip -r archive.nzip ./project/
103
104
  neozip -b tokenized.nzip file1.txt file2.txt
104
105
 
105
106
  # Extract files from archive
106
- neounzip archive.nzip extracted/
107
+ neounzip archive.nzip tests/extracted/
107
108
 
108
109
  # Extract and verify tokenized archive
109
110
  neounzip -t tokenized.nzip
@@ -144,7 +145,7 @@ neozip -e secure.nzip file.txt
144
145
  neozip -e -P "mypassword" secure.nzip file.txt
145
146
 
146
147
  # Extract encrypted archive
147
- neounzip -P "mypassword" secure.nzip extracted/
148
+ neounzip -P "mypassword" secure.nzip tests/extracted/
148
149
  ```
149
150
 
150
151
  ### Blockchain Examples
@@ -153,11 +154,17 @@ neounzip -P "mypassword" secure.nzip extracted/
153
154
  # Tokenize archive on Base Sepolia (default)
154
155
  neozip -b tokenized.nzip file.txt
155
156
 
156
- # Tokenize with OpenTimestamp proof
157
+ # Tokenize with OpenTimestamp proof (Bitcoin)
157
158
  neozip -ots timestamped.nzip file.txt
158
159
 
160
+ # Create Zipstamp timestamp (Ethereum)
161
+ neozip -ts zipstamp.nzip file.txt
162
+
163
+ # Upgrade pending Zipstamp timestamp to confirmed
164
+ neozip upgrade zipstamp.nzip [output.nzip] [--wait]
165
+
159
166
  # Extract and verify blockchain integrity
160
- neounzip tokenized.nzip extracted/
167
+ neounzip tokenized.nzip tests/extracted/
161
168
  ```
162
169
 
163
170
  ## Commands
@@ -179,7 +186,9 @@ neozip [options] <archive> [files...]
179
186
  - `-e, --encrypt` - Encrypt files (will prompt for password)
180
187
  - `-P, --password <pwd>` - Encrypt with password
181
188
  - `-b, --blockchain` - Enable blockchain tokenization
182
- - `-ots, --opentimestamp` - Enable OpenTimestamp proof
189
+ - `-ots, --opentimestamp` - Enable OpenTimestamp proof (Bitcoin)
190
+ - `-ts, --timestamp` - Enable Zipstamp timestamp (Ethereum)
191
+ - `--timestamp-email <email>` - Email for Zipstamp server (some servers require verified email)
183
192
  - `-T, --test-integrity` - Test archive after creation
184
193
  - `-v, --verbose` - Enable verbose output
185
194
  - `-q, --quiet` - Suppress output
@@ -187,7 +196,8 @@ neozip [options] <archive> [files...]
187
196
  **Configuration Commands:**
188
197
  ```bash
189
198
  neozip init # Interactive wallet setup wizard
190
- neozip config # Show current configuration
199
+ neozip config # Show current configuration
200
+ neozip upgrade <archive> [output] [--wait] # Upgrade pending Zipstamp timestamp to confirmed
191
201
  ```
192
202
 
193
203
  **Examples:**
@@ -235,16 +245,16 @@ neounzip [options] <archive> [output]
235
245
  neounzip archive.nzip
236
246
 
237
247
  # Extract to specific directory
238
- neounzip archive.nzip ./extracted/
248
+ neounzip archive.nzip tests/extracted/
239
249
 
240
250
  # Test archive integrity
241
251
  neounzip -t archive.nzip
242
252
 
243
253
  # Extract with exclusions
244
- neounzip -x "*.log" archive.nzip extracted/
254
+ neounzip -x "*.log" archive.nzip tests/extracted/
245
255
 
246
256
  # Extract encrypted archive
247
- neounzip --password "mypassword" secure.nzip extracted/
257
+ neounzip --password "mypassword" secure.nzip tests/extracted/
248
258
  ```
249
259
 
250
260
  ### `neolist` - List Archive Contents
@@ -303,6 +313,8 @@ You can also configure via environment variables:
303
313
  ```bash
304
314
  export NEOZIP_WALLET_PASSKEY="your-wallet-key"
305
315
  export NEOZIP_NETWORK="base-sepolia" # or base-mainnet, arbitrum-sepolia, etc.
316
+ export ZIPSTAMP_SERVER_URL="https://zipstamp-dev.neozip.io" # Zipstamp server (optional)
317
+ export NEOZIP_TIMESTAMP_EMAIL="your@email.com" # Email for Zipstamp (some servers require verified email)
306
318
  ```
307
319
 
308
320
  ### Configuration File
@@ -329,7 +341,7 @@ neozip project.nzip src/ docs/ README.md
329
341
  neolist project.nzip
330
342
 
331
343
  # Extract files
332
- neounzip project.nzip extracted/
344
+ neounzip project.nzip tests/extracted/
333
345
 
334
346
  # Test integrity
335
347
  neounzip -t project.nzip
@@ -0,0 +1,335 @@
1
+ "use strict";
2
+ /**
3
+ * Mint TimestampProofNFT from a confirmed timestamped ZIP (TIMESTAMP.NZIP)
4
+ *
5
+ * Creates TOKEN.NZIP metadata and outputs a new ZIP with NFT proof.
6
+ */
7
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
+ if (k2 === undefined) k2 = k;
9
+ var desc = Object.getOwnPropertyDescriptor(m, k);
10
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
11
+ desc = { enumerable: true, get: function() { return m[k]; } };
12
+ }
13
+ Object.defineProperty(o, k2, desc);
14
+ }) : (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ o[k2] = m[k];
17
+ }));
18
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
19
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
20
+ }) : function(o, v) {
21
+ o["default"] = v;
22
+ });
23
+ var __importStar = (this && this.__importStar) || (function () {
24
+ var ownKeys = function(o) {
25
+ ownKeys = Object.getOwnPropertyNames || function (o) {
26
+ var ar = [];
27
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
28
+ return ar;
29
+ };
30
+ return ownKeys(o);
31
+ };
32
+ return function (mod) {
33
+ if (mod && mod.__esModule) return mod;
34
+ var result = {};
35
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
36
+ __setModuleDefault(result, mod);
37
+ return result;
38
+ };
39
+ })();
40
+ var __importDefault = (this && this.__importDefault) || function (mod) {
41
+ return (mod && mod.__esModule) ? mod : { "default": mod };
42
+ };
43
+ Object.defineProperty(exports, "__esModule", { value: true });
44
+ exports.runMintTimestampProof = runMintTimestampProof;
45
+ const fs = __importStar(require("fs"));
46
+ const path = __importStar(require("path"));
47
+ const os = __importStar(require("os"));
48
+ const ethers_1 = require("ethers");
49
+ const node_1 = __importDefault(require("neozipkit/node"));
50
+ const node_2 = require("neozipkit/node");
51
+ const zipstamp_server_1 = require("neozip-blockchain/zipstamp-server");
52
+ const neozip_blockchain_1 = require("neozip-blockchain");
53
+ const blockchain_1 = require("../neozip/blockchain");
54
+ const RPC_BY_CHAIN = {
55
+ 84532: 'https://sepolia.base.org',
56
+ 421614: 'https://sepolia-rollup.arbitrum.io/rpc',
57
+ 11155111: 'https://ethereum-sepolia-rpc.publicnode.com',
58
+ 8453: 'https://mainnet.base.org',
59
+ 42161: 'https://arb1.arbitrum.io/rpc',
60
+ 1: 'https://eth.llamarpc.com',
61
+ };
62
+ /** Get a single string from an error so we can reliably detect "already minted" (ethers v6 puts reason in error.reason). */
63
+ function getErrorMessage(error) {
64
+ if (error == null)
65
+ return '';
66
+ const e = error;
67
+ const parts = [];
68
+ if (typeof e.reason === 'string')
69
+ parts.push(e.reason);
70
+ if (typeof e.message === 'string')
71
+ parts.push(e.message);
72
+ if (parts.length === 0)
73
+ parts.push(String(error));
74
+ return parts.join(' ');
75
+ }
76
+ /**
77
+ * Run the mint flow: load ZIP, extract TIMESTAMP.NZIP, prepare mint, mint NFT, create output ZIP with TOKEN.NZIP
78
+ */
79
+ async function runMintTimestampProof(options) {
80
+ const { inputPath, outputPath, walletKey: cliWalletKey, debug = false } = options;
81
+ if (!fs.existsSync(inputPath)) {
82
+ console.error(`Error: ZIP file not found: ${inputPath}`);
83
+ return false;
84
+ }
85
+ const walletKey = (0, blockchain_1.getWalletPasskey)(cliWalletKey, true);
86
+ if (!walletKey) {
87
+ return false;
88
+ }
89
+ const zip = new node_1.default();
90
+ await zip.loadZipFile(inputPath);
91
+ const entries = zip.getDirectory() || [];
92
+ const metadataResult = (0, zipstamp_server_1.findMetadataEntry)(entries);
93
+ if (!metadataResult) {
94
+ console.error('No Zipstamp timestamp found in ZIP file');
95
+ return false;
96
+ }
97
+ if (metadataResult.type !== 'confirmed') {
98
+ console.error('Archive must have confirmed timestamp (TIMESTAMP.NZIP). Run "neozip upgrade <archive>" first.');
99
+ return false;
100
+ }
101
+ let timestampMetadata = await (0, zipstamp_server_1.extractTimestampData)(zip, metadataResult.entry);
102
+ if (!timestampMetadata || !timestampMetadata.digest) {
103
+ // Fallback for file-based ZIPs where zip.extract() may not work
104
+ try {
105
+ const tmpPath = path.join(os.tmpdir(), `neozip-mint-meta-${Date.now()}.json`);
106
+ await zip.extractToFile(metadataResult.entry, tmpPath, { skipHashCheck: true });
107
+ try {
108
+ const buf = fs.readFileSync(tmpPath);
109
+ timestampMetadata = JSON.parse(buf.toString('utf-8'));
110
+ }
111
+ finally {
112
+ if (fs.existsSync(tmpPath))
113
+ fs.unlinkSync(tmpPath);
114
+ }
115
+ }
116
+ catch {
117
+ /* ignore */
118
+ }
119
+ }
120
+ if (!timestampMetadata || !timestampMetadata.digest) {
121
+ console.error('Failed to extract timestamp metadata');
122
+ return false;
123
+ }
124
+ const chainId = timestampMetadata.chainId ?? 84532;
125
+ const batchId = timestampMetadata.batchId ?? undefined;
126
+ console.log('Checking NFT status...');
127
+ const nftStatus = await (0, zipstamp_server_1.checkNFTStatus)(timestampMetadata.digest, chainId, { debug });
128
+ if (nftStatus.isMinted && nftStatus.tokenId) {
129
+ console.log(`Digest already minted as token #${nftStatus.tokenId}`);
130
+ console.log('Proceeding to create TOKEN.NZIP with existing token data...');
131
+ }
132
+ console.log('Preparing mint data...');
133
+ const prepareResult = await (0, zipstamp_server_1.prepareMint)(timestampMetadata.digest, chainId, batchId, { debug });
134
+ if (!prepareResult.success || !prepareResult.mintData) {
135
+ console.error(`Error: ${prepareResult.error || 'Failed to prepare mint data'}`);
136
+ return false;
137
+ }
138
+ const mintData = prepareResult.mintData;
139
+ let tokenId;
140
+ let mintTransactionHash;
141
+ let mintBlockNumber;
142
+ let ownerAddress;
143
+ if (nftStatus.isMinted && nftStatus.tokenId) {
144
+ tokenId = nftStatus.tokenId;
145
+ ownerAddress = nftStatus.owner || 'unknown';
146
+ mintTransactionHash = 'already-minted';
147
+ mintBlockNumber = 0;
148
+ }
149
+ else {
150
+ const rpcUrl = RPC_BY_CHAIN[chainId] ?? (0, neozip_blockchain_1.getContractConfig)(chainId)?.rpcUrls?.[0];
151
+ if (!rpcUrl) {
152
+ console.error(`Error: No RPC URL for chain ID ${chainId}`);
153
+ return false;
154
+ }
155
+ const provider = new ethers_1.ethers.JsonRpcProvider(rpcUrl, chainId);
156
+ const wallet = new ethers_1.ethers.Wallet(walletKey.startsWith('0x') ? walletKey : `0x${walletKey}`, provider);
157
+ ownerAddress = wallet.address;
158
+ const balance = await provider.getBalance(wallet.address);
159
+ const mintFeeWei = BigInt(mintData.mintFeeWei);
160
+ if (balance < mintFeeWei) {
161
+ console.error(`Error: Insufficient balance. Required: ${mintData.mintFee} ETH`);
162
+ return false;
163
+ }
164
+ const nftContract = new ethers_1.ethers.Contract(mintData.nftContractAddress, neozip_blockchain_1.NZIP_CONTRACT_ABI_V250, wallet);
165
+ console.log('Minting NFT...');
166
+ try {
167
+ const tx = await nftContract.mintWithTimestampProof(mintData.digest, mintData.merkleProof, mintData.batchMerkleRoot, { value: mintFeeWei });
168
+ const receipt = await tx.wait();
169
+ if (!receipt || receipt.status !== 1) {
170
+ throw new Error('Transaction failed');
171
+ }
172
+ mintTransactionHash = receipt.hash;
173
+ mintBlockNumber = receipt.blockNumber;
174
+ tokenId = '0';
175
+ for (const eventLog of receipt.logs) {
176
+ try {
177
+ const parsed = nftContract.interface.parseLog({
178
+ topics: eventLog.topics,
179
+ data: eventLog.data,
180
+ });
181
+ if (parsed?.name === 'TimestampProofMinted') {
182
+ tokenId = parsed.args.tokenId.toString();
183
+ break;
184
+ }
185
+ }
186
+ catch {
187
+ /* skip */
188
+ }
189
+ }
190
+ console.log(`Token minted: #${tokenId}`);
191
+ }
192
+ catch (error) {
193
+ const msg = getErrorMessage(error);
194
+ const isAlreadyMinted = /already minted/i.test(msg);
195
+ if (isAlreadyMinted) {
196
+ console.log('Contract reports digest already minted; resolving existing token...');
197
+ let existingTokenId;
198
+ let existingOwner;
199
+ let existingBlockNumber;
200
+ const existingStatus = await (0, zipstamp_server_1.checkNFTStatus)(timestampMetadata.digest, chainId, { debug });
201
+ if (existingStatus.isMinted && existingStatus.tokenId) {
202
+ existingTokenId = existingStatus.tokenId;
203
+ existingOwner = existingStatus.owner;
204
+ existingBlockNumber = existingStatus.proofData?.batchBlockNumber;
205
+ }
206
+ if (!existingTokenId) {
207
+ const verifyRes = await (0, zipstamp_server_1.verifyDigest)(timestampMetadata.digest, chainId, batchId ?? undefined, undefined, { debug });
208
+ if (verifyRes.verified && verifyRes.tokenId) {
209
+ existingTokenId = verifyRes.tokenId;
210
+ existingOwner = verifyRes.owner;
211
+ existingBlockNumber = verifyRes.blockNumber;
212
+ }
213
+ }
214
+ if (existingTokenId) {
215
+ tokenId = existingTokenId;
216
+ ownerAddress = existingOwner ?? 'unknown';
217
+ mintTransactionHash = 'already-minted';
218
+ mintBlockNumber = existingBlockNumber ?? 0;
219
+ console.log(`Using existing token #${tokenId}. Creating TOKEN.NZIP...`);
220
+ }
221
+ else {
222
+ console.error('Mint failed: digest is already minted on-chain but token details could not be retrieved. Try the Zipstamp server or block explorer for this digest.');
223
+ console.error(`Details: ${msg}`);
224
+ return false;
225
+ }
226
+ }
227
+ else {
228
+ console.error(`Mint failed: ${msg}`);
229
+ return false;
230
+ }
231
+ }
232
+ }
233
+ const nftMetadata = {
234
+ tokenId,
235
+ contractAddress: mintData.nftContractAddress,
236
+ network: mintData.network,
237
+ merkleRoot: mintData.digest,
238
+ networkChainId: chainId,
239
+ contractVersion: mintData.contractVersion,
240
+ transactionHash: mintTransactionHash,
241
+ blockNumber: mintBlockNumber,
242
+ owner: ownerAddress,
243
+ mintedAt: new Date().toISOString(),
244
+ timestampProof: {
245
+ digest: mintData.digest,
246
+ merkleProof: mintData.merkleProof,
247
+ batchMerkleRoot: mintData.batchMerkleRoot,
248
+ batchNumber: mintData.batchNumber,
249
+ batchTransactionHash: mintData.batchTransactionHash,
250
+ batchBlockNumber: mintData.batchBlockNumber,
251
+ batchTimestamp: mintData.batchTimestamp,
252
+ registryAddress: mintData.registryAddress,
253
+ nftContractAddress: mintData.nftContractAddress,
254
+ serverUrl: (0, zipstamp_server_1.getZipStampServerUrl)(),
255
+ },
256
+ };
257
+ const metadataFiles = (0, zipstamp_server_1.getMetadataFileNames)();
258
+ const outPath = outputPath ?? inputPath.replace(/(\.nzip|\.zip)$/i, '-nft$1');
259
+ const tempDir = path.dirname(outPath) || '.';
260
+ const tempPath = path.join(tempDir, `.neozip-mint-${Date.now()}.tmp`);
261
+ try {
262
+ const zipCopy = new node_2.ZipCopyNode(new node_1.default());
263
+ const { destPath, dataEndOffset, copiedEntries } = await zipCopy.copyZipEntriesOnly(inputPath, tempPath, {
264
+ entryFilter: (entry) => !metadataFiles.includes(entry.filename || ''),
265
+ });
266
+ const tokenContent = JSON.stringify(nftMetadata, null, 2);
267
+ const tokenBuffer = Buffer.from(tokenContent, 'utf-8');
268
+ const { crc32 } = await import('neozipkit');
269
+ const tokenEntry = zip.createZipEntry('META-INF/TOKEN.NZIP');
270
+ tokenEntry.crc = crc32(tokenBuffer) >>> 0;
271
+ tokenEntry.compressedSize = tokenBuffer.length;
272
+ tokenEntry.uncompressedSize = tokenBuffer.length;
273
+ tokenEntry.cmpMethod = 0;
274
+ tokenEntry.localHdrOffset = dataEndOffset;
275
+ tokenEntry.fileBuffer = tokenBuffer;
276
+ const localHdr = tokenEntry.createLocalHdr();
277
+ const fd = fs.openSync(destPath, 'r+');
278
+ try {
279
+ fs.writeSync(fd, localHdr, 0, localHdr.length, dataEndOffset);
280
+ fs.writeSync(fd, tokenBuffer, 0, tokenBuffer.length, dataEndOffset + localHdr.length);
281
+ }
282
+ finally {
283
+ fs.closeSync(fd);
284
+ }
285
+ const allEntries = [...copiedEntries, tokenEntry];
286
+ zipCopy.writeCentralDirectoryAndEOCD(destPath, allEntries, { zipComment: '' });
287
+ fs.renameSync(destPath, outPath);
288
+ console.log(`Output: ${outPath}`);
289
+ // Summary matching Zipstamp verification style
290
+ console.log('\n🔗 Mint complete:');
291
+ console.log(' - Status: ✅ MINTED');
292
+ console.log(' - Stamp type: NZIP-NFT (tokenized archive)');
293
+ console.log(` - Token ID: #${tokenId}`);
294
+ console.log(` - Network: ${mintData.network}`);
295
+ console.log(` - Contract: ${mintData.nftContractAddress}`);
296
+ if (mintTransactionHash && mintTransactionHash !== 'already-minted') {
297
+ console.log(` - Transaction: ${mintTransactionHash}`);
298
+ }
299
+ if (mintBlockNumber > 0) {
300
+ console.log(` - Block: ${mintBlockNumber}`);
301
+ }
302
+ const batchTs = mintData.batchTimestamp;
303
+ if (batchTs != null && mintBlockNumber > 0) {
304
+ const ts = typeof batchTs === 'number' ? (batchTs < 1e12 ? batchTs * 1000 : batchTs) : Date.parse(String(batchTs));
305
+ if (!isNaN(ts)) {
306
+ const blockDate = new Date(ts);
307
+ const mintedStr = blockDate.toLocaleString(undefined, {
308
+ month: '2-digit',
309
+ day: '2-digit',
310
+ year: 'numeric',
311
+ hour: '2-digit',
312
+ minute: '2-digit',
313
+ second: '2-digit',
314
+ hour12: true,
315
+ });
316
+ console.log(` - Block timestamp (minted): ${mintedStr}`);
317
+ }
318
+ }
319
+ return true;
320
+ }
321
+ catch (error) {
322
+ if (fs.existsSync(tempPath)) {
323
+ try {
324
+ fs.unlinkSync(tempPath);
325
+ }
326
+ catch {
327
+ /* ignore */
328
+ }
329
+ }
330
+ const msg = error instanceof Error ? error.message : String(error);
331
+ console.error(`Error creating output: ${msg}`);
332
+ return false;
333
+ }
334
+ }
335
+ //# sourceMappingURL=mintTimestampProof.js.map
@@ -0,0 +1,146 @@
1
+ "use strict";
2
+ /**
3
+ * Verify email for Zipstamp timestamping
4
+ *
5
+ * Sets a default email for Zipstamp timestamps. Checks with the server whether
6
+ * the email is already verified; if not, sends a magic link. Always saves as default.
7
+ */
8
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
+ if (k2 === undefined) k2 = k;
10
+ var desc = Object.getOwnPropertyDescriptor(m, k);
11
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
12
+ desc = { enumerable: true, get: function() { return m[k]; } };
13
+ }
14
+ Object.defineProperty(o, k2, desc);
15
+ }) : (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ o[k2] = m[k];
18
+ }));
19
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
20
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
21
+ }) : function(o, v) {
22
+ o["default"] = v;
23
+ });
24
+ var __importStar = (this && this.__importStar) || (function () {
25
+ var ownKeys = function(o) {
26
+ ownKeys = Object.getOwnPropertyNames || function (o) {
27
+ var ar = [];
28
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
29
+ return ar;
30
+ };
31
+ return ownKeys(o);
32
+ };
33
+ return function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ })();
41
+ Object.defineProperty(exports, "__esModule", { value: true });
42
+ exports.runVerifyEmail = runVerifyEmail;
43
+ const readline = __importStar(require("readline"));
44
+ const zipstamp_server_1 = require("neozip-blockchain/zipstamp-server");
45
+ const ConfigStore_1 = require("../config/ConfigStore");
46
+ const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
47
+ function isValidEmail(email) {
48
+ return EMAIL_REGEX.test(email.trim());
49
+ }
50
+ function isAlreadyVerifiedMessage(msg) {
51
+ if (!msg)
52
+ return false;
53
+ const lower = msg.toLowerCase();
54
+ return ((lower.includes('already') && (lower.includes('verified') || lower.includes('registered'))) ||
55
+ lower.includes('already verified') ||
56
+ lower.includes('email already'));
57
+ }
58
+ /**
59
+ * Run the verify-email flow.
60
+ * Calls the server to check verification status; if already verified, sets as default.
61
+ * If not, sends magic link and sets as default. Always saves email to config.
62
+ */
63
+ async function runVerifyEmail(options) {
64
+ const rl = readline.createInterface({
65
+ input: process.stdin,
66
+ output: process.stdout,
67
+ });
68
+ const prompt = (question, defaultValue) => {
69
+ return new Promise((resolve) => {
70
+ const display = defaultValue ? `${question} (${defaultValue}): ` : `${question}: `;
71
+ rl.question(display, (answer) => {
72
+ resolve((answer.trim() || defaultValue || '').trim());
73
+ });
74
+ });
75
+ };
76
+ try {
77
+ console.log('\n📧 Zipstamp Email Configuration\n');
78
+ console.log('Zipstamp requires a verified email to create timestamped ZIP files.');
79
+ console.log('');
80
+ const config = ConfigStore_1.ConfigStore.getConfig();
81
+ const defaultEmail = options?.email || config.timestampEmail || '';
82
+ const email = await prompt('Email address', defaultEmail || undefined);
83
+ if (!email) {
84
+ console.error('❌ Email is required');
85
+ rl.close();
86
+ return false;
87
+ }
88
+ if (!isValidEmail(email)) {
89
+ console.error('❌ Invalid email format');
90
+ rl.close();
91
+ return false;
92
+ }
93
+ // --set-default: skip server check, just save
94
+ if (options?.setDefaultOnly) {
95
+ const saved = ConfigStore_1.ConfigStore.set('zipstamp.timestampEmail', email);
96
+ if (!saved) {
97
+ console.error('❌ Failed to save email to configuration');
98
+ rl.close();
99
+ return false;
100
+ }
101
+ console.log(`\n✅ ${email} set as default for Zipstamp timestamps.`);
102
+ console.log('\n💡 You can now create timestamped ZIPs: neozip -ts <archive> <files>\n');
103
+ rl.close();
104
+ return true;
105
+ }
106
+ // Check with server: register returns status (already verified vs magic link sent)
107
+ console.log(`\n📤 Checking verification status for ${email}...`);
108
+ const registerResult = await (0, zipstamp_server_1.registerEmail)(email, {
109
+ debug: options?.debug,
110
+ });
111
+ if (!registerResult.success) {
112
+ console.error('❌ Failed to register email:', registerResult.error || 'Unknown error');
113
+ rl.close();
114
+ return false;
115
+ }
116
+ const res = registerResult;
117
+ const msg = res.message || '';
118
+ const alreadyVerified = res.verified === true ||
119
+ res.alreadyVerified === true ||
120
+ isAlreadyVerifiedMessage(msg);
121
+ const saved = ConfigStore_1.ConfigStore.set('zipstamp.timestampEmail', email);
122
+ if (!saved) {
123
+ console.error('❌ Failed to save email to configuration');
124
+ rl.close();
125
+ return false;
126
+ }
127
+ if (alreadyVerified) {
128
+ console.log('\n✅ Email is already verified. Set as default.');
129
+ }
130
+ else {
131
+ console.log('\n✅ Verification link sent. Check your inbox (and spam folder).');
132
+ console.log(' Click the magic link in the email to complete verification.');
133
+ console.log(` ${email} has been set as default and will work once you've clicked the link.`);
134
+ }
135
+ console.log('\n💡 You can now create timestamped ZIPs: neozip -ts <archive> <files>\n');
136
+ rl.close();
137
+ return true;
138
+ }
139
+ catch (error) {
140
+ const msg = error instanceof Error ? error.message : String(error);
141
+ console.error('❌ Error:', msg);
142
+ rl.close();
143
+ return false;
144
+ }
145
+ }
146
+ //# sourceMappingURL=verifyEmail.js.map