neozipkit 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +400 -0
- package/contracts/Lock.sol +34 -0
- package/contracts/bytecode.txt +1 -0
- package/contracts/compile-contract.js +168 -0
- package/contracts/deploy/deploy-both-contracts.js +286 -0
- package/contracts/deploy/deploy-enhanced-contract.js +197 -0
- package/contracts/deploy/deploy-private-contract.js +190 -0
- package/contracts/deploy/deploy-public-clean.js +1063 -0
- package/contracts/deploy/deploy-public-contract.js +1107 -0
- package/contracts/deployment.config.example.js +25 -0
- package/contracts/remix-compile.js +222 -0
- package/contracts/setup-deployment.js +51 -0
- package/contracts/src/NZIP-NFT-Private.sol +350 -0
- package/contracts/src/NZIP-NFT.sol +265 -0
- package/contracts/src/ZipFileNFT-DEPRECATED-6field.sol +260 -0
- package/contracts/src/ZipFileNFT-DEPRECATED-Enhanced.sol +289 -0
- package/dist/browser/CertificateGeneratorBrowser.d.ts +75 -0
- package/dist/browser/CertificateGeneratorBrowser.js +433 -0
- package/dist/browser/TokenVerifierBrowser.d.ts +76 -0
- package/dist/browser/TokenVerifierBrowser.js +299 -0
- package/dist/browser/ZipKitBrowser.d.ts +18 -0
- package/dist/browser/ZipKitBrowser.js +250 -0
- package/dist/browser/ZipKitStampBrowser.d.ts +2 -0
- package/dist/browser/ZipKitStampBrowser.js +12 -0
- package/dist/browser-index.d.ts +21 -0
- package/dist/browser-index.js +58 -0
- package/dist/browser.d.ts +21 -0
- package/dist/browser.js +58 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.js +76 -0
- package/dist/server/blockchain/CertificateToken.d.ts +48 -0
- package/dist/server/blockchain/CertificateToken.js +386 -0
- package/dist/server/blockchain/NFTMinter.d.ts +41 -0
- package/dist/server/blockchain/NFTMinter.js +323 -0
- package/dist/server/blockchain/TokenQuerier.d.ts +31 -0
- package/dist/server/blockchain/TokenQuerier.js +259 -0
- package/dist/server/blockchain/TokenVerifier.d.ts +20 -0
- package/dist/server/blockchain/TokenVerifier.js +395 -0
- package/dist/server/blockchain/WalletAnalyzer.d.ts +81 -0
- package/dist/server/blockchain/WalletAnalyzer.js +356 -0
- package/dist/server/blockchain/WalletFormatter.d.ts +39 -0
- package/dist/server/blockchain/WalletFormatter.js +196 -0
- package/dist/server/blockchain/WalletManager.d.ts +46 -0
- package/dist/server/blockchain/WalletManager.js +294 -0
- package/dist/server/blockchain/ZipMetadata.d.ts +27 -0
- package/dist/server/blockchain/ZipMetadata.js +131 -0
- package/dist/server/blockchain/ZipTokenProcessor.d.ts +44 -0
- package/dist/server/blockchain/ZipTokenProcessor.js +204 -0
- package/dist/server/blockchain/constants.d.ts +102 -0
- package/dist/server/blockchain/constants.js +968 -0
- package/dist/server/blockchain/index.d.ts +9 -0
- package/dist/server/blockchain/index.js +44 -0
- package/dist/server/blockchain/test-certificate.d.ts +8 -0
- package/dist/server/blockchain/test-certificate.js +100 -0
- package/dist/server/blockchain/types.d.ts +69 -0
- package/dist/server/blockchain/types.js +51 -0
- package/dist/server/platform/server/ZipKitServer.d.ts +105 -0
- package/dist/server/platform/server/ZipKitServer.js +437 -0
- package/dist/server/platform/servercomponents/ZipKitOTS.d.ts +91 -0
- package/dist/server/platform/servercomponents/ZipKitOTS.js +271 -0
- package/dist/shared/ZipEntry.d.ts +131 -0
- package/dist/shared/ZipEntry.js +686 -0
- package/dist/shared/ZipKit.d.ts +249 -0
- package/dist/shared/ZipKit.js +789 -0
- package/dist/shared/components/CertificateOTS.d.ts +15 -0
- package/dist/shared/components/CertificateOTS.js +116 -0
- package/dist/shared/components/FileSys.d.ts +2 -0
- package/dist/shared/components/FileSys.js +113 -0
- package/dist/shared/components/HashAccumulator.d.ts +55 -0
- package/dist/shared/components/HashAccumulator.js +100 -0
- package/dist/shared/components/Support.d.ts +63 -0
- package/dist/shared/components/Support.js +70 -0
- package/dist/shared/components/Util.d.ts +21 -0
- package/dist/shared/components/Util.js +115 -0
- package/dist/shared/components/ZipCrypto.d.ts +3 -0
- package/dist/shared/components/ZipCrypto.js +78 -0
- package/dist/shared/components/merkleTree.d.ts +66 -0
- package/dist/shared/components/merkleTree.js +136 -0
- package/dist/shared/constants/Errors.d.ts +53 -0
- package/dist/shared/constants/Errors.js +69 -0
- package/dist/shared/constants/Headers.d.ts +167 -0
- package/dist/shared/constants/Headers.js +191 -0
- package/dist/shared/types/index.d.ts +21 -0
- package/dist/shared/types/index.js +3 -0
- package/dist/shared/types.d.ts +33 -0
- package/dist/shared/types.js +8 -0
- package/dist/shared/version.d.ts +4 -0
- package/dist/shared/version.js +7 -0
- package/dist/types/index.d.ts +21 -0
- package/dist/types/index.js +3 -0
- package/examples/unzip-token.ts +488 -0
- package/examples/zip-token.ts +604 -0
- package/package.json +117 -0
- package/src/browser/CertificateGeneratorBrowser.ts +498 -0
- package/src/browser/TokenVerifierBrowser.ts +344 -0
- package/src/browser/ZipKitBrowser.ts +242 -0
- package/src/browser/ZipKitStampBrowser.ts +9 -0
- package/src/browser-index.ts +36 -0
- package/src/index.ts +48 -0
- package/src/server/blockchain/CertificateToken.ts +444 -0
- package/src/server/blockchain/NFTMinter.ts +357 -0
- package/src/server/blockchain/TokenQuerier.ts +287 -0
- package/src/server/blockchain/TokenVerifier.ts +453 -0
- package/src/server/blockchain/WalletAnalyzer.ts +397 -0
- package/src/server/blockchain/WalletFormatter.ts +206 -0
- package/src/server/blockchain/WalletManager.ts +294 -0
- package/src/server/blockchain/ZipMetadata.ts +117 -0
- package/src/server/blockchain/ZipTokenProcessor.ts +234 -0
- package/src/server/blockchain/constants.ts +969 -0
- package/src/server/blockchain/index.ts +42 -0
- package/src/server/blockchain/test-certificate.ts +137 -0
- package/src/server/blockchain/types.ts +134 -0
- package/src/server/platform/server/ZipKitServer.ts +459 -0
- package/src/server/platform/servercomponents/ZipKitOTS.ts +313 -0
- package/src/server/platform/servercomponents/opentimestamps.d.ts +23 -0
- package/src/shared/ZipEntry.ts +767 -0
- package/src/shared/ZipKit.ts +899 -0
- package/src/shared/components/CertificateOTS.ts +132 -0
- package/src/shared/components/FileSys.ts +87 -0
- package/src/shared/components/HashAccumulator.ts +113 -0
- package/src/shared/components/Support.ts +77 -0
- package/src/shared/components/Util.ts +113 -0
- package/src/shared/components/ZipCrypto.ts +91 -0
- package/src/shared/components/merkleTree.ts +162 -0
- package/src/shared/constants/Errors.ts +82 -0
- package/src/shared/constants/Headers.ts +203 -0
- package/src/shared/types/index.ts +25 -0
- package/src/shared/types/modules.d.ts +22 -0
- package/src/shared/types/opentimestamps.d.ts +23 -0
- package/src/shared/types.d.ts +8 -0
- package/src/shared/types.ts +43 -0
- package/src/shared/version.ts +4 -0
- package/src/types/index.ts +25 -0
- package/src/types/modules.d.ts +22 -0
- package/src/types/opentimestamps.d.ts +23 -0
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.19;
|
|
3
|
+
|
|
4
|
+
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
|
|
5
|
+
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
|
|
6
|
+
import "@openzeppelin/contracts/access/Ownable.sol";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @title NZIP NFT Contract v2.0
|
|
10
|
+
* @dev Main NFT contract for tokenizing ZIP files with blockchain metadata
|
|
11
|
+
* Features:
|
|
12
|
+
* - Blockchain metadata (block number, timestamps)
|
|
13
|
+
* - Multiple tokens allowed for same content (differentiated by timestamp)
|
|
14
|
+
* - Enhanced events with blockchain context
|
|
15
|
+
* - Structured data organization for better maintainability
|
|
16
|
+
* - Improved input validation and error handling
|
|
17
|
+
* - Removed unreliable block hash storage
|
|
18
|
+
*
|
|
19
|
+
* Version: 2.0 (New Production Contract - Previous contract deprecated)
|
|
20
|
+
* Previous Contract: 0xb78CB3bcC788fca38c731b1E9D70CF60b04CA015 (DEPRECATED)
|
|
21
|
+
* Network: Base Sepolia (for testing) / Base Mainnet (for production)
|
|
22
|
+
*/
|
|
23
|
+
contract ZipFileNFT is ERC721, ERC721URIStorage, Ownable {
|
|
24
|
+
|
|
25
|
+
// Counter for token IDs
|
|
26
|
+
uint256 private _tokenIdCounter;
|
|
27
|
+
|
|
28
|
+
// Struct to store ZIP file information - Latest Version
|
|
29
|
+
struct ZipFileInfo {
|
|
30
|
+
// File identification
|
|
31
|
+
string fileName; // Original ZIP file name
|
|
32
|
+
string merkleRootHash; // Merkle root hash of the ZIP contents
|
|
33
|
+
string ipfsHash; // IPFS hash where the ZIP file is stored
|
|
34
|
+
address creator; // Address that created/minted this token
|
|
35
|
+
|
|
36
|
+
// Timestamps (grouped together)
|
|
37
|
+
uint256 creationTimestamp; // Timestamp when the ZIP was created (user-provided)
|
|
38
|
+
uint256 tokenizationTime; // Timestamp when the NFT was minted (block.timestamp)
|
|
39
|
+
|
|
40
|
+
// Blockchain metadata
|
|
41
|
+
uint256 blockNumber; // Block number when the token was minted
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Mapping from token ID to ZIP file information
|
|
45
|
+
mapping(uint256 => ZipFileInfo) public zipFileInfo;
|
|
46
|
+
|
|
47
|
+
// Mapping from composite key (merkleRoot + timestamp) to token ID
|
|
48
|
+
// This prevents duplicate tokens with same content AND same timestamp
|
|
49
|
+
mapping(bytes32 => uint256) public compositeKeyToTokenId;
|
|
50
|
+
|
|
51
|
+
// Events - Enhanced with blockchain metadata
|
|
52
|
+
event ZipFileTokenized(
|
|
53
|
+
uint256 indexed tokenId,
|
|
54
|
+
address indexed creator,
|
|
55
|
+
string fileName,
|
|
56
|
+
string merkleRootHash,
|
|
57
|
+
uint256 creationTimestamp,
|
|
58
|
+
string ipfsHash,
|
|
59
|
+
uint256 tokenizationTime,
|
|
60
|
+
uint256 blockNumber
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
constructor() ERC721("NeoZip File NFT v2", "NZIP") Ownable(msg.sender) {}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @dev Get the contract version
|
|
67
|
+
* @return version The version string of this contract
|
|
68
|
+
*/
|
|
69
|
+
function getVersion() public pure returns (string memory) {
|
|
70
|
+
return "2.0.0";
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* @dev Generate composite key from merkle root and timestamp
|
|
75
|
+
* @param merkleRootHash The merkle root hash
|
|
76
|
+
* @param creationTimestamp The creation timestamp
|
|
77
|
+
* @return The composite key
|
|
78
|
+
*/
|
|
79
|
+
function generateCompositeKey(
|
|
80
|
+
string memory merkleRootHash,
|
|
81
|
+
uint256 creationTimestamp
|
|
82
|
+
) public pure returns (bytes32) {
|
|
83
|
+
return keccak256(abi.encodePacked(merkleRootHash, creationTimestamp));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* @dev Public minting function for ZIP files
|
|
88
|
+
* @param fileName Name of the ZIP file
|
|
89
|
+
* @param merkleRootHash Merkle root hash of the ZIP contents
|
|
90
|
+
* @param creationTimestamp Timestamp when the ZIP was created
|
|
91
|
+
* @param ipfsHash IPFS hash where the ZIP is stored
|
|
92
|
+
* @param metadataURI URI for the token metadata
|
|
93
|
+
* @return The newly minted token ID
|
|
94
|
+
*/
|
|
95
|
+
function publicMintZipFile(
|
|
96
|
+
string memory fileName,
|
|
97
|
+
string memory merkleRootHash,
|
|
98
|
+
uint256 creationTimestamp,
|
|
99
|
+
string memory ipfsHash,
|
|
100
|
+
string memory metadataURI
|
|
101
|
+
) public returns (uint256) {
|
|
102
|
+
// Input validation
|
|
103
|
+
require(bytes(fileName).length > 0, "File name cannot be empty");
|
|
104
|
+
require(bytes(merkleRootHash).length > 0, "Merkle root hash cannot be empty");
|
|
105
|
+
require(creationTimestamp > 0, "Creation timestamp must be greater than 0");
|
|
106
|
+
|
|
107
|
+
// Generate composite key to check for duplicates
|
|
108
|
+
bytes32 compositeKey = generateCompositeKey(merkleRootHash, creationTimestamp);
|
|
109
|
+
|
|
110
|
+
// Check if this exact combination already exists
|
|
111
|
+
require(compositeKeyToTokenId[compositeKey] == 0, "ZIP file with this content and timestamp already tokenized");
|
|
112
|
+
|
|
113
|
+
// Increment token counter
|
|
114
|
+
_tokenIdCounter++;
|
|
115
|
+
uint256 tokenId = _tokenIdCounter;
|
|
116
|
+
|
|
117
|
+
// Mint the NFT to the caller
|
|
118
|
+
_safeMint(msg.sender, tokenId);
|
|
119
|
+
|
|
120
|
+
// Set token URI if provided
|
|
121
|
+
if (bytes(metadataURI).length > 0) {
|
|
122
|
+
_setTokenURI(tokenId, metadataURI);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Store the ZIP file information with blockchain metadata
|
|
126
|
+
zipFileInfo[tokenId] = ZipFileInfo({
|
|
127
|
+
fileName: fileName,
|
|
128
|
+
merkleRootHash: merkleRootHash,
|
|
129
|
+
ipfsHash: ipfsHash,
|
|
130
|
+
creator: msg.sender,
|
|
131
|
+
creationTimestamp: creationTimestamp,
|
|
132
|
+
tokenizationTime: block.timestamp,
|
|
133
|
+
blockNumber: block.number
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
// Store the composite key mapping
|
|
137
|
+
compositeKeyToTokenId[compositeKey] = tokenId;
|
|
138
|
+
|
|
139
|
+
// Emit event with blockchain metadata
|
|
140
|
+
emit ZipFileTokenized(
|
|
141
|
+
tokenId,
|
|
142
|
+
msg.sender,
|
|
143
|
+
fileName,
|
|
144
|
+
merkleRootHash,
|
|
145
|
+
creationTimestamp,
|
|
146
|
+
ipfsHash,
|
|
147
|
+
block.timestamp,
|
|
148
|
+
block.number
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
return tokenId;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* @dev Get ZIP file information for a token
|
|
156
|
+
* @param tokenId The token ID to query
|
|
157
|
+
* @return ZipFileInfo struct containing all file and blockchain metadata
|
|
158
|
+
*/
|
|
159
|
+
function getZipFileInfo(uint256 tokenId) external view returns (ZipFileInfo memory) {
|
|
160
|
+
require(_ownerOf(tokenId) != address(0), "Token does not exist");
|
|
161
|
+
return zipFileInfo[tokenId];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* @dev Check if a ZIP file is already tokenized
|
|
166
|
+
* @param merkleRootHash The merkle root hash to check
|
|
167
|
+
* @param creationTimestamp The creation timestamp to check
|
|
168
|
+
* @return exists Whether the combination exists
|
|
169
|
+
* @return tokenId The token ID if it exists (0 if not)
|
|
170
|
+
*/
|
|
171
|
+
function isZipFileTokenized(
|
|
172
|
+
string memory merkleRootHash,
|
|
173
|
+
uint256 creationTimestamp
|
|
174
|
+
) external view returns (bool exists, uint256 tokenId) {
|
|
175
|
+
bytes32 compositeKey = generateCompositeKey(merkleRootHash, creationTimestamp);
|
|
176
|
+
tokenId = compositeKeyToTokenId[compositeKey];
|
|
177
|
+
exists = tokenId != 0;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* @dev Get all token IDs for a specific merkle root (across all timestamps)
|
|
182
|
+
* @param merkleRoot The merkle root to search for
|
|
183
|
+
* @return tokenIds Array of token IDs with this merkle root
|
|
184
|
+
*/
|
|
185
|
+
function getTokensByMerkleRoot(string memory merkleRoot) external view returns (uint256[] memory) {
|
|
186
|
+
// Create dynamic array to collect matching tokens
|
|
187
|
+
uint256[] memory tempTokens = new uint256[](_tokenIdCounter);
|
|
188
|
+
uint256 count = 0;
|
|
189
|
+
|
|
190
|
+
// Search through all tokens
|
|
191
|
+
for (uint256 i = 1; i <= _tokenIdCounter; i++) {
|
|
192
|
+
if (_ownerOf(i) != address(0)) { // Token exists
|
|
193
|
+
if (keccak256(abi.encodePacked(zipFileInfo[i].merkleRootHash)) ==
|
|
194
|
+
keccak256(abi.encodePacked(merkleRoot))) {
|
|
195
|
+
tempTokens[count] = i;
|
|
196
|
+
count++;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Create result array with exact size
|
|
202
|
+
uint256[] memory result = new uint256[](count);
|
|
203
|
+
for (uint256 i = 0; i < count; i++) {
|
|
204
|
+
result[i] = tempTokens[i];
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return result;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* @dev Verify that a token matches the provided merkle root
|
|
212
|
+
* @param tokenId The token ID to verify
|
|
213
|
+
* @param providedMerkleRoot The merkle root to check against
|
|
214
|
+
* @return isValid Whether the token matches the provided merkle root
|
|
215
|
+
*/
|
|
216
|
+
function verifyZipFile(uint256 tokenId, string memory providedMerkleRoot) external view returns (bool isValid) {
|
|
217
|
+
require(_ownerOf(tokenId) != address(0), "Token does not exist");
|
|
218
|
+
|
|
219
|
+
return keccak256(abi.encodePacked(zipFileInfo[tokenId].merkleRootHash)) ==
|
|
220
|
+
keccak256(abi.encodePacked(providedMerkleRoot));
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* @dev Get the current total supply of tokens
|
|
225
|
+
* @return The total number of tokens minted
|
|
226
|
+
*/
|
|
227
|
+
function totalSupply() public view returns (uint256) {
|
|
228
|
+
return _tokenIdCounter;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* @dev Get the balance of tokens for an address
|
|
233
|
+
* @param owner The address to query
|
|
234
|
+
* @return The number of tokens owned by the address
|
|
235
|
+
*/
|
|
236
|
+
function balanceOf(address owner) public view override(ERC721, IERC721) returns (uint256) {
|
|
237
|
+
require(owner != address(0), "ERC721: address zero is not a valid owner");
|
|
238
|
+
return super.balanceOf(owner);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* @dev Get blockchain metadata for a token
|
|
243
|
+
* @param tokenId The token ID to query
|
|
244
|
+
* @return blockNumber Block number when token was minted
|
|
245
|
+
* @return tokenizationTime Exact timestamp when token was minted
|
|
246
|
+
*/
|
|
247
|
+
function getBlockchainMetadata(uint256 tokenId) public view returns (
|
|
248
|
+
uint256 blockNumber,
|
|
249
|
+
uint256 tokenizationTime
|
|
250
|
+
) {
|
|
251
|
+
require(_ownerOf(tokenId) != address(0), "Token does not exist");
|
|
252
|
+
ZipFileInfo memory info = zipFileInfo[tokenId];
|
|
253
|
+
return (info.blockNumber, info.tokenizationTime);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Override required by Solidity
|
|
257
|
+
function tokenURI(uint256 tokenId) public view override(ERC721, ERC721URIStorage) returns (string memory) {
|
|
258
|
+
return super.tokenURI(tokenId);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Override required by Solidity
|
|
262
|
+
function supportsInterface(bytes4 interfaceId) public view override(ERC721, ERC721URIStorage) returns (bool) {
|
|
263
|
+
return super.supportsInterface(interfaceId);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.19;
|
|
3
|
+
|
|
4
|
+
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
|
|
5
|
+
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
|
|
6
|
+
import "@openzeppelin/contracts/access/Ownable.sol";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @title ZipFileNFT
|
|
10
|
+
* @dev Enhanced NFT contract for tokenizing ZIP files with timestamp support
|
|
11
|
+
* Allows multiple tokens for the same content by using timestamp as a differentiator
|
|
12
|
+
*/
|
|
13
|
+
contract ZipFileNFT is ERC721, ERC721URIStorage, Ownable {
|
|
14
|
+
|
|
15
|
+
// Counter for token IDs
|
|
16
|
+
uint256 private _tokenIdCounter;
|
|
17
|
+
|
|
18
|
+
// Struct to store ZIP file information
|
|
19
|
+
struct ZipFileInfo {
|
|
20
|
+
// File identification
|
|
21
|
+
string fileName; // Original ZIP file name
|
|
22
|
+
string merkleRootHash; // Merkle root hash of the ZIP contents
|
|
23
|
+
string ipfsHash; // IPFS hash where the ZIP file is stored
|
|
24
|
+
address creator; // Address that created/minted this token
|
|
25
|
+
|
|
26
|
+
// Timestamps (grouped together)
|
|
27
|
+
uint256 creationTimestamp; // Timestamp when the ZIP was created (user-provided)
|
|
28
|
+
uint256 tokenizationTime; // Timestamp when the NFT was minted (block.timestamp)
|
|
29
|
+
|
|
30
|
+
// Blockchain metadata (grouped with timestamps)
|
|
31
|
+
uint256 blockNumber; // Block number when the token was minted
|
|
32
|
+
bytes32 blockHash; // Block hash when the token was minted
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Mapping from token ID to ZIP file information
|
|
36
|
+
mapping(uint256 => ZipFileInfo) public zipFileInfo;
|
|
37
|
+
|
|
38
|
+
// Mapping from composite key (merkleRoot + timestamp) to token ID
|
|
39
|
+
// This prevents duplicate tokens with same content AND same timestamp
|
|
40
|
+
mapping(bytes32 => uint256) public compositeKeyToTokenId;
|
|
41
|
+
|
|
42
|
+
// Events
|
|
43
|
+
event ZipFileTokenized(
|
|
44
|
+
uint256 indexed tokenId,
|
|
45
|
+
address indexed creator,
|
|
46
|
+
string fileName,
|
|
47
|
+
string merkleRootHash,
|
|
48
|
+
uint256 creationTimestamp,
|
|
49
|
+
string ipfsHash,
|
|
50
|
+
uint256 tokenizationTime,
|
|
51
|
+
uint256 blockNumber,
|
|
52
|
+
bytes32 blockHash
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
constructor() ERC721("NeoZip File NFT", "NZIP") Ownable(msg.sender) {}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* @dev Generate composite key from merkle root and timestamp
|
|
59
|
+
* @param merkleRootHash The merkle root hash
|
|
60
|
+
* @param creationTimestamp The creation timestamp
|
|
61
|
+
* @return The composite key
|
|
62
|
+
*/
|
|
63
|
+
function _generateCompositeKey(
|
|
64
|
+
string memory merkleRootHash,
|
|
65
|
+
uint256 creationTimestamp
|
|
66
|
+
) internal pure returns (bytes32) {
|
|
67
|
+
return keccak256(abi.encodePacked(merkleRootHash, creationTimestamp));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @dev Mint a new NFT representing a ZIP file with timestamp
|
|
72
|
+
* @param to Address to mint the token to
|
|
73
|
+
* @param fileName Original name of the ZIP file
|
|
74
|
+
* @param merkleRootHash Merkle root hash of the ZIP contents
|
|
75
|
+
* @param creationTimestamp Timestamp when the ZIP was created
|
|
76
|
+
* @param ipfsHash IPFS hash where the ZIP file is stored
|
|
77
|
+
* @param tokenURI Metadata URI for the token
|
|
78
|
+
* @return tokenId The ID of the newly minted token
|
|
79
|
+
*/
|
|
80
|
+
function mintZipFileToken(
|
|
81
|
+
address to,
|
|
82
|
+
string memory fileName,
|
|
83
|
+
string memory merkleRootHash,
|
|
84
|
+
uint256 creationTimestamp,
|
|
85
|
+
string memory ipfsHash,
|
|
86
|
+
string memory tokenURI
|
|
87
|
+
) public returns (uint256) {
|
|
88
|
+
require(bytes(merkleRootHash).length > 0, "Merkle root hash cannot be empty");
|
|
89
|
+
require(bytes(fileName).length > 0, "File name cannot be empty");
|
|
90
|
+
require(creationTimestamp > 0, "Creation timestamp must be greater than 0");
|
|
91
|
+
|
|
92
|
+
// Generate composite key
|
|
93
|
+
bytes32 compositeKey = _generateCompositeKey(merkleRootHash, creationTimestamp);
|
|
94
|
+
|
|
95
|
+
// Check that this specific combination hasn't been tokenized before
|
|
96
|
+
require(compositeKeyToTokenId[compositeKey] == 0, "ZIP file with this timestamp already tokenized");
|
|
97
|
+
|
|
98
|
+
uint256 tokenId = _tokenIdCounter;
|
|
99
|
+
_tokenIdCounter++;
|
|
100
|
+
|
|
101
|
+
// Mint the token
|
|
102
|
+
_safeMint(to, tokenId);
|
|
103
|
+
_setTokenURI(tokenId, tokenURI);
|
|
104
|
+
|
|
105
|
+
// Store ZIP file information
|
|
106
|
+
zipFileInfo[tokenId] = ZipFileInfo({
|
|
107
|
+
fileName: fileName,
|
|
108
|
+
merkleRootHash: merkleRootHash,
|
|
109
|
+
ipfsHash: ipfsHash,
|
|
110
|
+
creator: msg.sender,
|
|
111
|
+
creationTimestamp: creationTimestamp,
|
|
112
|
+
tokenizationTime: block.timestamp,
|
|
113
|
+
blockNumber: block.number,
|
|
114
|
+
blockHash: blockhash(block.number)
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// Map composite key to token ID for duplicate prevention
|
|
118
|
+
compositeKeyToTokenId[compositeKey] = tokenId;
|
|
119
|
+
|
|
120
|
+
emit ZipFileTokenized(tokenId, to, fileName, merkleRootHash, creationTimestamp, ipfsHash, block.timestamp, block.number, blockhash(block.number));
|
|
121
|
+
|
|
122
|
+
return tokenId;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* @dev Public minting function with timestamp
|
|
127
|
+
* @param fileName Name of the ZIP file
|
|
128
|
+
* @param merkleRootHash Merkle root hash of the ZIP contents
|
|
129
|
+
* @param creationTimestamp Timestamp when the ZIP was created
|
|
130
|
+
* @param ipfsHash IPFS hash where the ZIP file is stored
|
|
131
|
+
* @param tokenURI Metadata URI for the token
|
|
132
|
+
* @return tokenId The ID of the newly minted token
|
|
133
|
+
*/
|
|
134
|
+
function publicMintZipFile(
|
|
135
|
+
string memory fileName,
|
|
136
|
+
string memory merkleRootHash,
|
|
137
|
+
uint256 creationTimestamp,
|
|
138
|
+
string memory ipfsHash,
|
|
139
|
+
string memory tokenURI
|
|
140
|
+
) public returns (uint256) {
|
|
141
|
+
return mintZipFileToken(
|
|
142
|
+
msg.sender,
|
|
143
|
+
fileName,
|
|
144
|
+
merkleRootHash,
|
|
145
|
+
creationTimestamp,
|
|
146
|
+
ipfsHash,
|
|
147
|
+
tokenURI
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* @dev Verify a ZIP file against its token
|
|
153
|
+
* @param tokenId Token ID to verify
|
|
154
|
+
* @param providedMerkleRoot Merkle root to verify against
|
|
155
|
+
* @return isValid Whether the provided hash matches the stored one
|
|
156
|
+
*/
|
|
157
|
+
function verifyZipFile(
|
|
158
|
+
uint256 tokenId,
|
|
159
|
+
string memory providedMerkleRoot
|
|
160
|
+
) public view returns (bool isValid) {
|
|
161
|
+
require(_ownerOf(tokenId) != address(0), "Token does not exist");
|
|
162
|
+
|
|
163
|
+
ZipFileInfo memory info = zipFileInfo[tokenId];
|
|
164
|
+
|
|
165
|
+
isValid = keccak256(abi.encodePacked(info.merkleRootHash)) == keccak256(abi.encodePacked(providedMerkleRoot));
|
|
166
|
+
|
|
167
|
+
return isValid;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* @dev Get information about a ZIP file token
|
|
172
|
+
* @param tokenId Token ID to query
|
|
173
|
+
* @return info Complete ZipFileInfo struct
|
|
174
|
+
*/
|
|
175
|
+
function getZipFileInfo(uint256 tokenId) public view returns (ZipFileInfo memory info) {
|
|
176
|
+
require(_ownerOf(tokenId) != address(0), "Token does not exist");
|
|
177
|
+
return zipFileInfo[tokenId];
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* @dev Check if a ZIP file with specific timestamp has been tokenized
|
|
182
|
+
* @param merkleRootHash Merkle root to check
|
|
183
|
+
* @param creationTimestamp Creation timestamp to check
|
|
184
|
+
* @return exists Whether a token exists for this combination
|
|
185
|
+
* @return tokenId The token ID if it exists (0 if not)
|
|
186
|
+
*/
|
|
187
|
+
function isZipFileTokenized(
|
|
188
|
+
string memory merkleRootHash,
|
|
189
|
+
uint256 creationTimestamp
|
|
190
|
+
) public view returns (bool exists, uint256 tokenId) {
|
|
191
|
+
bytes32 compositeKey = _generateCompositeKey(merkleRootHash, creationTimestamp);
|
|
192
|
+
tokenId = compositeKeyToTokenId[compositeKey];
|
|
193
|
+
exists = (tokenId != 0);
|
|
194
|
+
return (exists, tokenId);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* @dev Get all tokens for a specific merkle root (regardless of timestamp)
|
|
199
|
+
* @param merkleRootHash Merkle root to search for
|
|
200
|
+
* @return tokenIds Array of token IDs that match this merkle root
|
|
201
|
+
*/
|
|
202
|
+
function getTokensByMerkleRoot(string memory merkleRootHash) public view returns (uint256[] memory tokenIds) {
|
|
203
|
+
uint256 totalSupply = _tokenIdCounter;
|
|
204
|
+
uint256[] memory tempTokenIds = new uint256[](totalSupply);
|
|
205
|
+
uint256 count = 0;
|
|
206
|
+
|
|
207
|
+
for (uint256 i = 0; i < totalSupply; i++) {
|
|
208
|
+
if (_ownerOf(i) != address(0)) {
|
|
209
|
+
ZipFileInfo memory info = zipFileInfo[i];
|
|
210
|
+
if (keccak256(abi.encodePacked(info.merkleRootHash)) == keccak256(abi.encodePacked(merkleRootHash))) {
|
|
211
|
+
tempTokenIds[count] = i;
|
|
212
|
+
count++;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Create properly sized array
|
|
218
|
+
tokenIds = new uint256[](count);
|
|
219
|
+
for (uint256 i = 0; i < count; i++) {
|
|
220
|
+
tokenIds[i] = tempTokenIds[i];
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return tokenIds;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* @dev Get the current token ID counter
|
|
228
|
+
* @return The next token ID that will be minted
|
|
229
|
+
*/
|
|
230
|
+
function getCurrentTokenId() public view returns (uint256) {
|
|
231
|
+
return _tokenIdCounter;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* @dev Get total number of tokens minted
|
|
236
|
+
* @return Total supply of tokens
|
|
237
|
+
*/
|
|
238
|
+
function totalSupply() public view returns (uint256) {
|
|
239
|
+
return _tokenIdCounter;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* @dev Update IPFS hash for a token (only by token owner)
|
|
244
|
+
* @param tokenId Token ID to update
|
|
245
|
+
* @param ipfsHash New IPFS hash
|
|
246
|
+
*/
|
|
247
|
+
function updateIpfsHash(uint256 tokenId, string memory ipfsHash) public {
|
|
248
|
+
require(ownerOf(tokenId) == msg.sender, "Only token owner can update IPFS hash");
|
|
249
|
+
zipFileInfo[tokenId].ipfsHash = ipfsHash;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Override required functions
|
|
253
|
+
function tokenURI(uint256 tokenId) public view override(ERC721, ERC721URIStorage) returns (string memory) {
|
|
254
|
+
return super.tokenURI(tokenId);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function supportsInterface(bytes4 interfaceId) public view override(ERC721, ERC721URIStorage) returns (bool) {
|
|
258
|
+
return super.supportsInterface(interfaceId);
|
|
259
|
+
}
|
|
260
|
+
}
|