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.
- package/CHANGELOG.md +30 -0
- package/DOCUMENTATION.md +9 -1
- package/README.md +22 -10
- package/dist/src/commands/mintTimestampProof.js +335 -0
- package/dist/src/commands/verifyEmail.js +146 -0
- package/dist/src/config/ConfigSetup.js +50 -20
- package/dist/src/config/ConfigStore.js +36 -3
- package/dist/src/index.js +1 -1
- package/dist/src/neolist.js +18 -10
- package/dist/src/neounzip.js +305 -60
- package/dist/src/neozip/blockchain.js +5 -5
- package/dist/src/neozip/createZip.js +207 -41
- package/dist/src/neozip/upgradeZip.js +182 -0
- package/dist/src/neozip.js +143 -8
- package/env.example +10 -0
- package/package.json +91 -85
- package/dist/neozipkit-bundles/blockchain.js +0 -13725
- package/dist/neozipkit-bundles/browser.js +0 -6186
- package/dist/neozipkit-bundles/core.js +0 -3839
- package/dist/neozipkit-bundles/node.js +0 -17730
- package/dist/neozipkit-wrappers/blockchain/core/contracts.js +0 -16
- package/dist/neozipkit-wrappers/blockchain/index.js +0 -2
- package/dist/neozipkit-wrappers/core/ZipDecompress.js +0 -2
- package/dist/neozipkit-wrappers/core/components/HashCalculator.js +0 -2
- package/dist/neozipkit-wrappers/core/components/Logger.js +0 -2
- package/dist/neozipkit-wrappers/core/constants/Errors.js +0 -2
- package/dist/neozipkit-wrappers/core/constants/Headers.js +0 -2
- package/dist/neozipkit-wrappers/core/encryption/ZipCrypto.js +0 -7
- package/dist/neozipkit-wrappers/core/index.js +0 -3
- package/dist/neozipkit-wrappers/index.js +0 -13
- 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
|
|
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
|
|
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
|