neozip-cli 0.75.2-beta → 0.90.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 +78 -0
  2. package/DOCUMENTATION.md +20 -9
  3. package/README.md +55 -31
  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 +25 -11
  10. package/dist/src/neounzip.js +324 -66
  11. package/dist/src/neozip/blockchain.js +5 -5
  12. package/dist/src/neozip/createZip.js +211 -44
  13. package/dist/src/neozip/upgradeZip.js +182 -0
  14. package/dist/src/neozip.js +160 -24
  15. package/env.example +10 -0
  16. package/package.json +97 -82
  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
@@ -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
@@ -41,7 +41,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
41
41
  exports.ConfigSetup = void 0;
42
42
  const readline = __importStar(require("readline"));
43
43
  const ConfigStore_1 = require("./ConfigStore");
44
- const blockchain_1 = require("neozipkit/blockchain");
44
+ const neozip_blockchain_1 = require("neozip-blockchain");
45
+ function resolveScriptPath(jsPath) {
46
+ if (typeof __filename !== 'undefined' && __filename.endsWith('.ts')) {
47
+ return jsPath.replace(/\.js$/, '.ts');
48
+ }
49
+ return jsPath;
50
+ }
45
51
  class ConfigSetup {
46
52
  constructor() {
47
53
  this.rl = readline.createInterface({
@@ -148,7 +154,7 @@ class ConfigSetup {
148
154
  // Dynamically build network options from CONTRACT_CONFIGS
149
155
  // CONTRACT_CONFIGS is already ordered correctly: Base (testnet/mainnet), Arbitrum (testnet/mainnet), Ethereum
150
156
  const networkOptions = [];
151
- const networkConfigs = Object.values(blockchain_1.CONTRACT_CONFIGS);
157
+ const networkConfigs = Object.values(neozip_blockchain_1.CONTRACT_CONFIGS);
152
158
  // Verify we're loading networks
153
159
  if (networkConfigs.length === 0) {
154
160
  console.error('❌ Error: No networks found in CONTRACT_CONFIGS');
@@ -160,7 +166,7 @@ class ConfigSetup {
160
166
  // Use the primary alias (first nameAlias) or network name as value
161
167
  const primaryAlias = config.nameAliases && config.nameAliases.length > 0
162
168
  ? config.nameAliases[0]
163
- : (0, blockchain_1.normalizeNetworkName)(config.network);
169
+ : (0, neozip_blockchain_1.normalizeNetworkName)(config.network);
164
170
  // Determine description based on network type
165
171
  const isTestnet = config.network.toLowerCase().includes('testnet') ||
166
172
  config.network.toLowerCase().includes('sepolia');
@@ -188,7 +194,7 @@ class ConfigSetup {
188
194
  }
189
195
  if (useCustomRpc === 'y') {
190
196
  // Get the selected network config to show default RPC
191
- const selectedNetworkConfig = (0, blockchain_1.getNetworkByName)(network);
197
+ const selectedNetworkConfig = (0, neozip_blockchain_1.getNetworkByName)(network);
192
198
  // Prompt for RPC URL for the selected network
193
199
  if (selectedNetworkConfig) {
194
200
  const defaultRpc = selectedNetworkConfig.rpcUrls && selectedNetworkConfig.rpcUrls.length > 0
@@ -197,12 +203,12 @@ class ConfigSetup {
197
203
  // Use primary alias as RPC key for consistency (or normalized network name as fallback)
198
204
  const rpcKey = (selectedNetworkConfig.nameAliases && selectedNetworkConfig.nameAliases.length > 0)
199
205
  ? selectedNetworkConfig.nameAliases[0]
200
- : (0, blockchain_1.normalizeNetworkName)(selectedNetworkConfig.network);
206
+ : (0, neozip_blockchain_1.normalizeNetworkName)(selectedNetworkConfig.network);
201
207
  // Check both new format and legacy format for backward compatibility
202
208
  const existingRpc = rpcConfig[rpcKey]
203
- || rpcConfig[(0, blockchain_1.normalizeNetworkName)(selectedNetworkConfig.network).replace(/\s+/g, '')]
209
+ || rpcConfig[(0, neozip_blockchain_1.normalizeNetworkName)(selectedNetworkConfig.network).replace(/\s+/g, '')]
204
210
  || existingConfig?.rpc?.[rpcKey]
205
- || existingConfig?.rpc?.[(0, blockchain_1.normalizeNetworkName)(selectedNetworkConfig.network).replace(/\s+/g, '')];
211
+ || existingConfig?.rpc?.[(0, neozip_blockchain_1.normalizeNetworkName)(selectedNetworkConfig.network).replace(/\s+/g, '')];
206
212
  const customRpc = await this.prompt(` ${selectedNetworkConfig.network} RPC URL`, existingRpc || defaultRpc);
207
213
  if (customRpc) {
208
214
  rpcConfig[rpcKey] = customRpc;
@@ -218,21 +224,21 @@ class ConfigSetup {
218
224
  // Skip the already configured network
219
225
  const configAlias = config.nameAliases && config.nameAliases.length > 0
220
226
  ? config.nameAliases[0]
221
- : (0, blockchain_1.normalizeNetworkName)(config.network);
227
+ : (0, neozip_blockchain_1.normalizeNetworkName)(config.network);
222
228
  if (configAlias === network)
223
229
  continue;
224
230
  // Use primary alias as RPC key for consistency (or normalized network name as fallback)
225
231
  const rpcKey = (config.nameAliases && config.nameAliases.length > 0)
226
232
  ? config.nameAliases[0]
227
- : (0, blockchain_1.normalizeNetworkName)(config.network);
233
+ : (0, neozip_blockchain_1.normalizeNetworkName)(config.network);
228
234
  const defaultRpc = config.rpcUrls && config.rpcUrls.length > 0
229
235
  ? config.rpcUrls[0]
230
236
  : '';
231
237
  // Check both new format and legacy format for backward compatibility
232
238
  const existingRpc = rpcConfig[rpcKey]
233
- || rpcConfig[(0, blockchain_1.normalizeNetworkName)(config.network).replace(/\s+/g, '')]
239
+ || rpcConfig[(0, neozip_blockchain_1.normalizeNetworkName)(config.network).replace(/\s+/g, '')]
234
240
  || existingConfig?.rpc?.[rpcKey]
235
- || existingConfig?.rpc?.[(0, blockchain_1.normalizeNetworkName)(config.network).replace(/\s+/g, '')];
241
+ || existingConfig?.rpc?.[(0, neozip_blockchain_1.normalizeNetworkName)(config.network).replace(/\s+/g, '')];
236
242
  const customRpc = await this.prompt(` ${config.network} RPC URL`, existingRpc || defaultRpc);
237
243
  if (customRpc) {
238
244
  rpcConfig[rpcKey] = customRpc;
@@ -274,12 +280,29 @@ class ConfigSetup {
274
280
  verbose = verboseStr === 'y';
275
281
  debugEnabled = debugStr === 'y';
276
282
  }
277
- // Build configuration object
283
+ // 6. Zipstamp Email (optional - for timestamped ZIPs)
284
+ console.log('\n6️⃣ Zipstamp Email (optional)');
285
+ const configureZipstamp = await this.promptSelection('Configure email for Zipstamp timestamping?', [
286
+ { value: 'n', label: 'No', description: 'Skip - run neozip verify-email later if needed' },
287
+ { value: 'y', label: 'Yes', description: 'Set email (must verify with neozip verify-email)' }
288
+ ], 'n');
289
+ let zipstampEmail;
290
+ if (configureZipstamp === 'y') {
291
+ zipstampEmail = await this.prompt(' Zipstamp timestamp email', existingConfig?.zipstamp?.timestampEmail || '');
292
+ if (zipstampEmail) {
293
+ console.log(' 💡 Run "neozip verify-email" to verify this email before creating timestamped ZIPs.');
294
+ }
295
+ }
296
+ // Build configuration object (preserve existing zipstamp if not configuring)
297
+ const zipstampConfig = zipstampEmail !== undefined
298
+ ? (zipstampEmail ? { timestampEmail: zipstampEmail } : undefined)
299
+ : existingConfig?.zipstamp;
278
300
  const config = {
279
301
  wallet: {
280
302
  privateKey,
281
303
  network,
282
304
  },
305
+ zipstamp: zipstampConfig,
283
306
  rpc: Object.keys(rpcConfig).length > 0 ? rpcConfig : undefined,
284
307
  gas: gasMultiplier || maxGasPrice ? {
285
308
  multiplier: gasMultiplier,
@@ -343,16 +366,17 @@ class ConfigSetup {
343
366
  const hasPrivateKey = config.walletKey !== null;
344
367
  console.log('What would you like to do?');
345
368
  console.log(' 1. Modify configuration (update settings)');
369
+ console.log(' 2. Verify Zipstamp email (for timestamped ZIPs)');
346
370
  if (hasPrivateKey) {
347
- console.log(' 2. Delete private key (remove wallet key from configuration)');
348
- console.log(' 3. Reset to defaults (delete entire configuration)');
349
- console.log(' 4. Exit');
371
+ console.log(' 3. Delete private key (remove wallet key from configuration)');
372
+ console.log(' 4. Reset to defaults (delete entire configuration)');
373
+ console.log(' 5. Exit');
350
374
  }
351
375
  else {
352
- console.log(' 2. Reset to defaults (delete entire configuration)');
353
- console.log(' 3. Exit');
376
+ console.log(' 3. Reset to defaults (delete entire configuration)');
377
+ console.log(' 4. Exit');
354
378
  }
355
- const maxChoice = hasPrivateKey ? 4 : 3;
379
+ const maxChoice = hasPrivateKey ? 5 : 4;
356
380
  rl.question(`\nEnter choice (1-${maxChoice}): `, async (answer) => {
357
381
  const choice = parseInt(answer);
358
382
  switch (choice) {
@@ -363,6 +387,12 @@ class ConfigSetup {
363
387
  process.exit(success ? 0 : 1);
364
388
  break;
365
389
  case 2:
390
+ rl.close();
391
+ const { runVerifyEmail } = await import(resolveScriptPath('../commands/verifyEmail.js'));
392
+ const verifySuccess = await runVerifyEmail();
393
+ process.exit(verifySuccess ? 0 : 1);
394
+ break;
395
+ case 3:
366
396
  if (hasPrivateKey) {
367
397
  // Delete private key only
368
398
  rl.question('\n⚠️ Are you sure you want to delete your private key? (y/n): ', (confirm) => {
@@ -403,7 +433,7 @@ class ConfigSetup {
403
433
  });
404
434
  }
405
435
  break;
406
- case 3:
436
+ case 4:
407
437
  if (hasPrivateKey) {
408
438
  // Reset entire config
409
439
  rl.question('\n⚠️ Are you sure you want to delete your wallet configuration? (y/n): ', (confirm) => {
@@ -429,7 +459,7 @@ class ConfigSetup {
429
459
  process.exit(0);
430
460
  }
431
461
  break;
432
- case 4:
462
+ case 5:
433
463
  if (hasPrivateKey) {
434
464
  // Exit
435
465
  console.log('');