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.
Files changed (135) hide show
  1. package/README.md +400 -0
  2. package/contracts/Lock.sol +34 -0
  3. package/contracts/bytecode.txt +1 -0
  4. package/contracts/compile-contract.js +168 -0
  5. package/contracts/deploy/deploy-both-contracts.js +286 -0
  6. package/contracts/deploy/deploy-enhanced-contract.js +197 -0
  7. package/contracts/deploy/deploy-private-contract.js +190 -0
  8. package/contracts/deploy/deploy-public-clean.js +1063 -0
  9. package/contracts/deploy/deploy-public-contract.js +1107 -0
  10. package/contracts/deployment.config.example.js +25 -0
  11. package/contracts/remix-compile.js +222 -0
  12. package/contracts/setup-deployment.js +51 -0
  13. package/contracts/src/NZIP-NFT-Private.sol +350 -0
  14. package/contracts/src/NZIP-NFT.sol +265 -0
  15. package/contracts/src/ZipFileNFT-DEPRECATED-6field.sol +260 -0
  16. package/contracts/src/ZipFileNFT-DEPRECATED-Enhanced.sol +289 -0
  17. package/dist/browser/CertificateGeneratorBrowser.d.ts +75 -0
  18. package/dist/browser/CertificateGeneratorBrowser.js +433 -0
  19. package/dist/browser/TokenVerifierBrowser.d.ts +76 -0
  20. package/dist/browser/TokenVerifierBrowser.js +299 -0
  21. package/dist/browser/ZipKitBrowser.d.ts +18 -0
  22. package/dist/browser/ZipKitBrowser.js +250 -0
  23. package/dist/browser/ZipKitStampBrowser.d.ts +2 -0
  24. package/dist/browser/ZipKitStampBrowser.js +12 -0
  25. package/dist/browser-index.d.ts +21 -0
  26. package/dist/browser-index.js +58 -0
  27. package/dist/browser.d.ts +21 -0
  28. package/dist/browser.js +58 -0
  29. package/dist/index.d.ts +29 -0
  30. package/dist/index.js +76 -0
  31. package/dist/server/blockchain/CertificateToken.d.ts +48 -0
  32. package/dist/server/blockchain/CertificateToken.js +386 -0
  33. package/dist/server/blockchain/NFTMinter.d.ts +41 -0
  34. package/dist/server/blockchain/NFTMinter.js +323 -0
  35. package/dist/server/blockchain/TokenQuerier.d.ts +31 -0
  36. package/dist/server/blockchain/TokenQuerier.js +259 -0
  37. package/dist/server/blockchain/TokenVerifier.d.ts +20 -0
  38. package/dist/server/blockchain/TokenVerifier.js +395 -0
  39. package/dist/server/blockchain/WalletAnalyzer.d.ts +81 -0
  40. package/dist/server/blockchain/WalletAnalyzer.js +356 -0
  41. package/dist/server/blockchain/WalletFormatter.d.ts +39 -0
  42. package/dist/server/blockchain/WalletFormatter.js +196 -0
  43. package/dist/server/blockchain/WalletManager.d.ts +46 -0
  44. package/dist/server/blockchain/WalletManager.js +294 -0
  45. package/dist/server/blockchain/ZipMetadata.d.ts +27 -0
  46. package/dist/server/blockchain/ZipMetadata.js +131 -0
  47. package/dist/server/blockchain/ZipTokenProcessor.d.ts +44 -0
  48. package/dist/server/blockchain/ZipTokenProcessor.js +204 -0
  49. package/dist/server/blockchain/constants.d.ts +102 -0
  50. package/dist/server/blockchain/constants.js +968 -0
  51. package/dist/server/blockchain/index.d.ts +9 -0
  52. package/dist/server/blockchain/index.js +44 -0
  53. package/dist/server/blockchain/test-certificate.d.ts +8 -0
  54. package/dist/server/blockchain/test-certificate.js +100 -0
  55. package/dist/server/blockchain/types.d.ts +69 -0
  56. package/dist/server/blockchain/types.js +51 -0
  57. package/dist/server/platform/server/ZipKitServer.d.ts +105 -0
  58. package/dist/server/platform/server/ZipKitServer.js +437 -0
  59. package/dist/server/platform/servercomponents/ZipKitOTS.d.ts +91 -0
  60. package/dist/server/platform/servercomponents/ZipKitOTS.js +271 -0
  61. package/dist/shared/ZipEntry.d.ts +131 -0
  62. package/dist/shared/ZipEntry.js +686 -0
  63. package/dist/shared/ZipKit.d.ts +249 -0
  64. package/dist/shared/ZipKit.js +789 -0
  65. package/dist/shared/components/CertificateOTS.d.ts +15 -0
  66. package/dist/shared/components/CertificateOTS.js +116 -0
  67. package/dist/shared/components/FileSys.d.ts +2 -0
  68. package/dist/shared/components/FileSys.js +113 -0
  69. package/dist/shared/components/HashAccumulator.d.ts +55 -0
  70. package/dist/shared/components/HashAccumulator.js +100 -0
  71. package/dist/shared/components/Support.d.ts +63 -0
  72. package/dist/shared/components/Support.js +70 -0
  73. package/dist/shared/components/Util.d.ts +21 -0
  74. package/dist/shared/components/Util.js +115 -0
  75. package/dist/shared/components/ZipCrypto.d.ts +3 -0
  76. package/dist/shared/components/ZipCrypto.js +78 -0
  77. package/dist/shared/components/merkleTree.d.ts +66 -0
  78. package/dist/shared/components/merkleTree.js +136 -0
  79. package/dist/shared/constants/Errors.d.ts +53 -0
  80. package/dist/shared/constants/Errors.js +69 -0
  81. package/dist/shared/constants/Headers.d.ts +167 -0
  82. package/dist/shared/constants/Headers.js +191 -0
  83. package/dist/shared/types/index.d.ts +21 -0
  84. package/dist/shared/types/index.js +3 -0
  85. package/dist/shared/types.d.ts +33 -0
  86. package/dist/shared/types.js +8 -0
  87. package/dist/shared/version.d.ts +4 -0
  88. package/dist/shared/version.js +7 -0
  89. package/dist/types/index.d.ts +21 -0
  90. package/dist/types/index.js +3 -0
  91. package/examples/unzip-token.ts +488 -0
  92. package/examples/zip-token.ts +604 -0
  93. package/package.json +117 -0
  94. package/src/browser/CertificateGeneratorBrowser.ts +498 -0
  95. package/src/browser/TokenVerifierBrowser.ts +344 -0
  96. package/src/browser/ZipKitBrowser.ts +242 -0
  97. package/src/browser/ZipKitStampBrowser.ts +9 -0
  98. package/src/browser-index.ts +36 -0
  99. package/src/index.ts +48 -0
  100. package/src/server/blockchain/CertificateToken.ts +444 -0
  101. package/src/server/blockchain/NFTMinter.ts +357 -0
  102. package/src/server/blockchain/TokenQuerier.ts +287 -0
  103. package/src/server/blockchain/TokenVerifier.ts +453 -0
  104. package/src/server/blockchain/WalletAnalyzer.ts +397 -0
  105. package/src/server/blockchain/WalletFormatter.ts +206 -0
  106. package/src/server/blockchain/WalletManager.ts +294 -0
  107. package/src/server/blockchain/ZipMetadata.ts +117 -0
  108. package/src/server/blockchain/ZipTokenProcessor.ts +234 -0
  109. package/src/server/blockchain/constants.ts +969 -0
  110. package/src/server/blockchain/index.ts +42 -0
  111. package/src/server/blockchain/test-certificate.ts +137 -0
  112. package/src/server/blockchain/types.ts +134 -0
  113. package/src/server/platform/server/ZipKitServer.ts +459 -0
  114. package/src/server/platform/servercomponents/ZipKitOTS.ts +313 -0
  115. package/src/server/platform/servercomponents/opentimestamps.d.ts +23 -0
  116. package/src/shared/ZipEntry.ts +767 -0
  117. package/src/shared/ZipKit.ts +899 -0
  118. package/src/shared/components/CertificateOTS.ts +132 -0
  119. package/src/shared/components/FileSys.ts +87 -0
  120. package/src/shared/components/HashAccumulator.ts +113 -0
  121. package/src/shared/components/Support.ts +77 -0
  122. package/src/shared/components/Util.ts +113 -0
  123. package/src/shared/components/ZipCrypto.ts +91 -0
  124. package/src/shared/components/merkleTree.ts +162 -0
  125. package/src/shared/constants/Errors.ts +82 -0
  126. package/src/shared/constants/Headers.ts +203 -0
  127. package/src/shared/types/index.ts +25 -0
  128. package/src/shared/types/modules.d.ts +22 -0
  129. package/src/shared/types/opentimestamps.d.ts +23 -0
  130. package/src/shared/types.d.ts +8 -0
  131. package/src/shared/types.ts +43 -0
  132. package/src/shared/version.ts +4 -0
  133. package/src/types/index.ts +25 -0
  134. package/src/types/modules.d.ts +22 -0
  135. 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
+ }