@ethernity-cloud/sdk-js 1.7.1 → 1.7.7

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/pynithy/ipfs.mjs CHANGED
@@ -223,12 +223,13 @@ const retryOnFailure = async (fn, label) => {
223
223
  return null;
224
224
  };
225
225
 
226
- // Upload a single file: POST it to /api/v0/add and return its hash.
227
- const uploadFileToIPFSOnce = async (filePath) => {
226
+ // Upload a single file: POST it to /api/v0/add and return its hash. With
227
+ // onlyHash the node computes the same hash and stores nothing.
228
+ const uploadFileToIPFSOnce = async (filePath, onlyHash = false) => {
228
229
  const files = [filePath];
229
230
  const dir = path.dirname(filePath);
230
231
  const { body, boundary, length } = buildMultipartBody(files, dir);
231
- const { statusCode, text } = await postAdd('', body, boundary, length, null);
232
+ const { statusCode, text } = await postAdd(onlyHash ? '?only-hash=true' : '', body, boundary, length, null);
232
233
  if (statusCode !== 200) {
233
234
  console.log(`Failed to upload to IPFS. Status code: ${statusCode}`);
234
235
  return null;
@@ -260,13 +261,15 @@ const uploadFileToIPFS = async (filePath) => {
260
261
  };
261
262
 
262
263
  // Upload a whole directory with wrap-with-directory=true and return the root
263
- // directory hash -- the exact behaviour of Python's upload_dir.
264
- const uploadDirOnce = async (dirPath) => {
264
+ // directory hash -- the exact behaviour of Python's upload_dir. With onlyHash
265
+ // the node computes the same root hash and stores nothing.
266
+ const uploadDirOnce = async (dirPath, onlyHash = false) => {
265
267
  const abs = path.resolve(dirPath);
266
268
  const files = gatherFiles(abs);
267
269
  const totalSize = files.reduce((acc, f) => acc + fs.statSync(f).size, 0);
268
270
  const totalMb = Math.floor(totalSize / (1024 * 1024));
269
271
  const { body, boundary, length } = buildMultipartBody(files, abs);
272
+ const step = onlyHash ? 'Hashing enclave for IPFS' : 'Uploading and pinning enclave to IPFS';
270
273
 
271
274
  let lastShownMb = -1;
272
275
  let frame = 0;
@@ -276,27 +279,28 @@ const uploadDirOnce = async (dirPath) => {
276
279
  frame = (frame + 1) % SPINNER_FRAMES.length;
277
280
  lastShownMb = mb;
278
281
  process.stdout.write(
279
- `\r\t${SPINNER_FRAMES[frame]} Uploading and pinning enclave to IPFS... ${mb}MB/${totalMb}MB`
282
+ `\r\t${SPINNER_FRAMES[frame]} ${step}... ${mb}MB/${totalMb}MB`
280
283
  );
281
284
  }
282
285
  };
283
286
 
284
287
  const query =
285
- '?quieter=true&stream-channels=true&wrap-with-directory=true&progress=false&timeout=5m';
288
+ '?quieter=true&stream-channels=true&wrap-with-directory=true&progress=false&timeout=5m' +
289
+ (onlyHash ? '&only-hash=true' : '');
286
290
  const { statusCode, text } = await postAdd(query, body, boundary, length, onProgress);
287
291
 
288
292
  if (statusCode !== 200) {
289
- process.stdout.write(`\r\t${FAIL}Uploading and pinning enclave to IPFS\n`);
293
+ process.stdout.write(`\r\t${FAIL}${step}\n`);
290
294
  console.log(`Failed to upload to IPFS. Status code: ${statusCode}`);
291
295
  return false;
292
296
  }
293
297
  const rootHash = parseRootHash(text);
294
298
  if (!rootHash) {
295
- process.stdout.write(`\r\t${FAIL}Uploading and pinning enclave to IPFS\n`);
299
+ process.stdout.write(`\r\t${FAIL}${step}\n`);
296
300
  console.log('Failed to upload to IPFS. Could not determine root hash.');
297
301
  return false;
298
302
  }
299
- process.stdout.write(`\r\t${CHECK}Uploading and pinning enclave to IPFS\n`);
303
+ process.stdout.write(`\r\t${CHECK}${step}\n`);
300
304
  return rootHash;
301
305
  };
302
306
 
@@ -322,6 +326,16 @@ const upload = async (p) => {
322
326
  return null;
323
327
  };
324
328
 
329
+ // The hash `upload(p)` gives, from the same add call with only-hash: the IPFS
330
+ // node neither stores nor announces anything.
331
+ const hashForIPFS = async (p) => {
332
+ const stat = fs.statSync(p);
333
+ if (stat.isFile()) return retryOnFailure(() => uploadFileToIPFSOnce(p, true), 'hash_file');
334
+ if (stat.isDirectory()) return retryOnFailure(() => uploadDirOnce(p, true), 'hash_dir');
335
+ console.log(`Path ${p} is neither a file nor a directory.`);
336
+ return null;
337
+ };
338
+
325
339
  // ---------------------------------------------------------------------------
326
340
  // Download helpers (kubo-rpc-client based; unchanged behaviour)
327
341
  // ---------------------------------------------------------------------------
@@ -397,6 +411,7 @@ const getContentFromIPFS = async (hash, maxRetries = process.env.REACT_APP_IPFS_
397
411
  export {
398
412
  initialize,
399
413
  upload,
414
+ hashForIPFS,
400
415
  uploadFileToIPFS,
401
416
  uploadFolderToIPFS,
402
417
  getFromIPFS,
@@ -405,7 +420,7 @@ export {
405
420
  };
406
421
 
407
422
  // ---------------------------------------------------------------------------
408
- // CLI (unchanged interface: --host --action upload/download --filePath/--folderPath)
423
+ // CLI: --host --action upload/hash/download --filePath/--folderPath
409
424
  // ---------------------------------------------------------------------------
410
425
 
411
426
  program
@@ -413,7 +428,7 @@ program
413
428
  .option('--hhash <hhash>', 'IPFS hash')
414
429
  .option('--filePath <path>', 'Path to the file')
415
430
  .option('--folderPath <path>', 'Path to the folder')
416
- .option('--action <action>', 'Action to perform (upload, download)')
431
+ .option('--action <action>', 'Action to perform (upload, hash, download)')
417
432
  .option('--token <token>', 'IPFS authorization token')
418
433
  .option('--output <path>', 'Output path for download');
419
434
 
@@ -445,6 +460,15 @@ if (isMain) {
445
460
  console.error('Please provide a filePath or folderPath for upload.');
446
461
  process.exit(1);
447
462
  }
463
+ } else if (options.action === 'hash') {
464
+ const target = options.filePath || options.folderPath;
465
+ if (!target) {
466
+ console.error('Please provide a filePath or folderPath to hash.');
467
+ process.exit(1);
468
+ }
469
+ const hhash = await hashForIPFS(target);
470
+ console.log(`${hhash}`);
471
+ process.exit(hhash ? 0 : 1);
448
472
  } else if (options.action === 'download') {
449
473
  if (options.filePath) {
450
474
  await getFromIPFS(options.hhash, options.filePath);
@@ -456,7 +480,7 @@ if (isMain) {
456
480
  console.error('Please provide a filePath or folderPath for download.');
457
481
  }
458
482
  } else {
459
- console.error('Please provide a valid action (upload, download).');
483
+ console.error('Please provide a valid action (upload, hash, download).');
460
484
  process.exit(1);
461
485
  }
462
486
  };
@@ -1 +1 @@
1
- [{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"wallet","type":"address"}],"name":"AllowedWalletAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"wallet","type":"address"}],"name":"AllowedWalletRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"ipfsHash","type":"string"},{"indexed":false,"internalType":"string","name":"publicKey","type":"string"},{"indexed":false,"internalType":"string","name":"version","type":"string"}],"name":"ImageAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"ipfsHash","type":"string"}],"name":"ImagePublished","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"ipfsHash","type":"string"}],"name":"ImageRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"ipfsHash","type":"string"}],"name":"ImageValidated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnerChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"ipfsHash","type":"string"}],"name":"TrustedZoneImageAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"ipfsHash","type":"string"}],"name":"TrustedZoneImageRemoved","type":"event"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"}],"name":"addAllowedWallet","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"ipfsHash","type":"string"},{"internalType":"string","name":"certPublicKey","type":"string"},{"internalType":"string","name":"version","type":"string"},{"internalType":"string","name":"imageName","type":"string"},{"internalType":"string","name":"dockerComposeHash","type":"string"},{"internalType":"string","name":"session","type":"string"},{"internalType":"uint8","name":"fee","type":"uint8"}],"name":"addImage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"ipfsHash","type":"string"},{"internalType":"string","name":"certPublicKey","type":"string"},{"internalType":"string","name":"version","type":"string"},{"internalType":"string","name":"imageName","type":"string"},{"internalType":"string","name":"dockerComposeHash","type":"string"},{"internalType":"string","name":"session","type":"string"},{"internalType":"uint8","name":"fee","type":"uint8"}],"name":"addTrustedZoneImage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"ipfsHash","type":"string"},{"internalType":"address","name":"newRewardAddress","type":"address"}],"name":"changeImageRewardAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"changeOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"ipfsHash","type":"string"},{"internalType":"address","name":"newRewardAddress","type":"address"}],"name":"changeTrustedZoneImageRewardAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"ipfsHash","type":"string"}],"name":"publishImage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"}],"name":"removeAllowedWallet","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"ipfsHash","type":"string"}],"name":"validateImage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"allowedWallets","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"ipfsHash","type":"string"}],"name":"getImageCertPublicKey","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"ipfsHash","type":"string"}],"name":"getImageSession","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"imageName","type":"string"},{"internalType":"string","name":"version","type":"string"}],"name":"getLatestImageVersionPublicKey","outputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"imageName","type":"string"},{"internalType":"string","name":"version","type":"string"}],"name":"getLatestTrustedZoneImageCertPublicKey","outputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"ipfsHash","type":"string"}],"name":"getRewardAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"ipfsHash","type":"string"}],"name":"getTrustedZoneImageCertPublicKey","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"ipfsHash","type":"string"}],"name":"getTrustedZoneImageSession","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"ipfsHash","type":"string"}],"name":"getTrustedZoneRewardAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"}],"name":"imageDetails","outputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"string","name":"ipfsHash","type":"string"},{"internalType":"string","name":"version","type":"string"},{"internalType":"string","name":"session","type":"string"},{"internalType":"uint8","name":"fee","type":"uint8"},{"internalType":"address","name":"rewardAddress","type":"address"},{"internalType":"bool","name":"validated","type":"bool"},{"internalType":"bool","name":"published","type":"bool"},{"internalType":"string","name":"certPublicKey","type":"string"},{"internalType":"string","name":"dockerComposeHash","type":"string"},{"internalType":"string","name":"name","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"images","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"imageVersions","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"imageVersionsHash","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"}],"name":"trustedZoneImageDetails","outputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"string","name":"ipfsHash","type":"string"},{"internalType":"string","name":"version","type":"string"},{"internalType":"string","name":"session","type":"string"},{"internalType":"uint8","name":"fee","type":"uint8"},{"internalType":"address","name":"rewardAddress","type":"address"},{"internalType":"bool","name":"validated","type":"bool"},{"internalType":"bool","name":"published","type":"bool"},{"internalType":"string","name":"certPublicKey","type":"string"},{"internalType":"string","name":"dockerComposeHash","type":"string"},{"internalType":"string","name":"name","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"trustedZoneImageVersions","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"trustedZoneVersionsHash","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"}]
1
+ [{"inputs":[{"internalType":"string","name":"imageName","type":"string"}],"name":"imageNameOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"wallet","type":"address"}],"name":"AllowedWalletAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"wallet","type":"address"}],"name":"AllowedWalletRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"ipfsHash","type":"string"},{"indexed":false,"internalType":"string","name":"publicKey","type":"string"},{"indexed":false,"internalType":"string","name":"version","type":"string"}],"name":"ImageAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"ipfsHash","type":"string"}],"name":"ImagePublished","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"ipfsHash","type":"string"}],"name":"ImageRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"ipfsHash","type":"string"}],"name":"ImageValidated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnerChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"ipfsHash","type":"string"}],"name":"TrustedZoneImageAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"ipfsHash","type":"string"}],"name":"TrustedZoneImageRemoved","type":"event"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"}],"name":"addAllowedWallet","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"ipfsHash","type":"string"},{"internalType":"string","name":"certPublicKey","type":"string"},{"internalType":"string","name":"version","type":"string"},{"internalType":"string","name":"imageName","type":"string"},{"internalType":"string","name":"dockerComposeHash","type":"string"},{"internalType":"string","name":"session","type":"string"},{"internalType":"uint8","name":"fee","type":"uint8"}],"name":"addImage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"ipfsHash","type":"string"},{"internalType":"string","name":"certPublicKey","type":"string"},{"internalType":"string","name":"version","type":"string"},{"internalType":"string","name":"imageName","type":"string"},{"internalType":"string","name":"dockerComposeHash","type":"string"},{"internalType":"string","name":"session","type":"string"},{"internalType":"uint8","name":"fee","type":"uint8"}],"name":"addTrustedZoneImage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"ipfsHash","type":"string"},{"internalType":"address","name":"newRewardAddress","type":"address"}],"name":"changeImageRewardAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"changeOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"ipfsHash","type":"string"},{"internalType":"address","name":"newRewardAddress","type":"address"}],"name":"changeTrustedZoneImageRewardAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"ipfsHash","type":"string"}],"name":"publishImage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"}],"name":"removeAllowedWallet","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"ipfsHash","type":"string"}],"name":"validateImage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"allowedWallets","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"ipfsHash","type":"string"}],"name":"getImageCertPublicKey","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"ipfsHash","type":"string"}],"name":"getImageSession","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"imageName","type":"string"},{"internalType":"string","name":"version","type":"string"}],"name":"getLatestImageVersionPublicKey","outputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"imageName","type":"string"},{"internalType":"string","name":"version","type":"string"}],"name":"getLatestTrustedZoneImageCertPublicKey","outputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"ipfsHash","type":"string"}],"name":"getRewardAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"ipfsHash","type":"string"}],"name":"getTrustedZoneImageCertPublicKey","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"ipfsHash","type":"string"}],"name":"getTrustedZoneImageSession","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"ipfsHash","type":"string"}],"name":"getTrustedZoneRewardAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"}],"name":"imageDetails","outputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"string","name":"ipfsHash","type":"string"},{"internalType":"string","name":"version","type":"string"},{"internalType":"string","name":"session","type":"string"},{"internalType":"uint8","name":"fee","type":"uint8"},{"internalType":"address","name":"rewardAddress","type":"address"},{"internalType":"bool","name":"validated","type":"bool"},{"internalType":"bool","name":"published","type":"bool"},{"internalType":"string","name":"certPublicKey","type":"string"},{"internalType":"string","name":"dockerComposeHash","type":"string"},{"internalType":"string","name":"name","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"images","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"imageVersions","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"imageVersionsHash","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"}],"name":"trustedZoneImageDetails","outputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"string","name":"ipfsHash","type":"string"},{"internalType":"string","name":"version","type":"string"},{"internalType":"string","name":"session","type":"string"},{"internalType":"uint8","name":"fee","type":"uint8"},{"internalType":"address","name":"rewardAddress","type":"address"},{"internalType":"bool","name":"validated","type":"bool"},{"internalType":"bool","name":"published","type":"bool"},{"internalType":"string","name":"certPublicKey","type":"string"},{"internalType":"string","name":"dockerComposeHash","type":"string"},{"internalType":"string","name":"name","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"trustedZoneImageVersions","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"","type":"string"},{"internalType":"string","name":"","type":"string"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"trustedZoneVersionsHash","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"ipfsHash","type":"string"},{"indexed":false,"internalType":"string","name":"imageName","type":"string"},{"indexed":false,"internalType":"string","name":"version","type":"string"}],"name":"ImageCertSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"ipfsHash","type":"string"},{"indexed":false,"internalType":"string","name":"imageName","type":"string"},{"indexed":false,"internalType":"string","name":"version","type":"string"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"string","name":"ipfsPeer","type":"string"}],"name":"ImageRegistered","type":"event"},{"inputs":[],"name":"pendingImages","outputs":[{"internalType":"string[]","name":"","type":"string[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"ipfsHash","type":"string"},{"internalType":"string","name":"version","type":"string"},{"internalType":"string","name":"imageName","type":"string"},{"internalType":"string","name":"dockerComposeHash","type":"string"},{"internalType":"string","name":"session","type":"string"},{"internalType":"uint8","name":"fee","type":"uint8"},{"internalType":"string","name":"ipfsPeer","type":"string"}],"name":"registerImage","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"ipfsHash","type":"string"}],"name":"registrationPeer","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"ipfsHash","type":"string"},{"internalType":"string","name":"certPublicKey","type":"string"}],"name":"setImageCert","outputs":[],"stateMutability":"nonpayable","type":"function"}]
@@ -1,25 +1,27 @@
1
1
  const Web3 = require('web3');
2
2
  const { ethers } = require('ethers');
3
- const { Account } = require('eth-lib/lib/account');
4
3
  const { config } = require('dotenv');
5
4
  const fs = require('fs');
6
5
  const path = require('path');
6
+ const { isRevert, revertReason } = require('../../revert.js');
7
7
 
8
8
  config();
9
9
 
10
+ // The publishing wallet's key comes from the environment only: a command line
11
+ // is visible to other processes and is printed when the command fails.
10
12
  const PRIVATE_KEY = process.env.PRIVATE_KEY || "";
11
13
  let BLOCKCHAIN_NETWORK = process.env.BLOCKCHAIN_NETWORK || "Bloxberg_Testnet";
12
14
  let NETWORK_RPC = "https://bloxberg.ethernity.cloud";
13
- let IMAGE_REGISTRY_ADDRESS = "0x99A84C624C028bdf0a855A1E9E3f2fcf7275B3D8"; // bloxberg testnet (ECImageRegistryV2)
15
+ let IMAGE_REGISTRY_ADDRESS = "0xa372a6e1Eb7Fcf343AF6b91E809b900C55001CD1"; // bloxberg testnet (ECImageRegistryV3)
14
16
  let CHAIN_ID = 8995;
15
17
  let GAS = 9000000;
16
18
  let GAS_PRICE = 1;
17
19
 
18
20
  function setVars(network = "") {
19
21
  if (BLOCKCHAIN_NETWORK.includes("Bloxberg")) {
20
- // The bloxberg testnet reads ECImageRegistryV2; mainnet the original registry.
22
+ // The bloxberg testnet reads ECImageRegistryV3; mainnet the original registry.
21
23
  IMAGE_REGISTRY_ADDRESS = BLOCKCHAIN_NETWORK.includes("Testnet")
22
- ? "0x99A84C624C028bdf0a855A1E9E3f2fcf7275B3D8"
24
+ ? "0xa372a6e1Eb7Fcf343AF6b91E809b900C55001CD1"
23
25
  : "0x15D73a742529C3fb11f3FA32EF7f0CC3870ACA31";
24
26
  } else if (BLOCKCHAIN_NETWORK.includes("Polygon")) {
25
27
  if (BLOCKCHAIN_NETWORK.includes("Mainnet")) {
@@ -58,6 +60,26 @@ function isStringPrivateKey(privateKey) {
58
60
  }
59
61
  }
60
62
 
63
+ // The address of a private key given with or without its 0x prefix.
64
+ function walletAddress(privateKey) {
65
+ return new ethers.Wallet(privateKey.startsWith("0x") ? privateKey : `0x${privateKey}`).address;
66
+ }
67
+
68
+ // REWARD_ADDRESS from the environment: where the image's developer fee is
69
+ // paid when not the publishing wallet, or "" when unset. ECImageRegistryV3
70
+ // refuses the zero address, so it is refused here before any transaction.
71
+ function rewardAddressFromEnv() {
72
+ const rewardAddress = (process.env.REWARD_ADDRESS || "").trim();
73
+ if (!rewardAddress) return "";
74
+ if (!ethers.utils.isAddress(rewardAddress)) {
75
+ throw new Error(`REWARD_ADDRESS ${rewardAddress} is not an address`);
76
+ }
77
+ if (rewardAddress.toLowerCase() === ethers.constants.AddressZero) {
78
+ throw new Error("REWARD_ADDRESS is the zero address; leave it empty to be paid at the publishing wallet");
79
+ }
80
+ return rewardAddress;
81
+ }
82
+
61
83
  async function checkAccountBalance() {
62
84
  try {
63
85
  const web3 = new ethers.providers.JsonRpcProvider(NETWORK_RPC);
@@ -124,61 +146,12 @@ class ImageRegistry {
124
146
  const receipt = await this.provider.sendTransaction(signedTxn.rawTransaction);
125
147
  }
126
148
 
149
+ // Record the securelock with its certificate on a V1 registry, in one
150
+ // addImage call. A failure, including the registry's refusal, throws.
127
151
  async addSecureLockImageCert(certContent, ipfsHash, imageName, version, dockerComposeHash, enclaveNameSecureLock, fee) {
128
- try {
129
- console.log("Adding secure lock image cert to image registry");
130
- // Fetch current nonce
131
- if (BLOCKCHAIN_NETWORK.includes("Polygon")) {
132
- console.log("Polygon Mainnet");
133
- let nonce = await this.provider.getTransactionCount(this.acct.address, 'pending');
134
- // Fetch current gas price and increase it
135
- let gasPrice = await this.provider.getGasPrice();
136
- gasPrice = gasPrice.mul(ethers.BigNumber.from(110)).div(ethers.BigNumber.from(100));
137
- const unicornTxn = await this.imageRegistryContract.addImage(
138
- ipfsHash, certContent, version, imageName, dockerComposeHash, enclaveNameSecureLock, fee,
139
- {
140
- nonce: nonce,
141
- gasPrice: gasPrice,
142
- });
143
- // console.log("Transaction: ", unicornTxn);
144
- const receipt = await this.imageRegistryContract.provider.waitForTransaction(unicornTxn.hash);
145
-
146
- // console.log("transaction status: ", receipt.status);
147
- console.log("transaction receipt: ", unicornTxn.hash);
148
- if (receipt.status === 1) {
149
- console.log("Adding secure lock image cert transaction was successful!");
150
- } else {
151
- // console.log("receipt.status", receipt.status)
152
- console.log("Image certificates already exist for this image!");
153
- }
154
- } else {
155
- const unicornTxn = await this.imageRegistryContract.addImage(
156
- ipfsHash, certContent, version, imageName, dockerComposeHash, enclaveNameSecureLock, fee);
157
- // console.log("Transaction: ", unicornTxn);
158
- const receipt = await this.imageRegistryContract.provider.waitForTransaction(unicornTxn.hash);
159
-
160
- // console.log("transaction status: ", receipt.status);
161
- console.log("transaction receipt: ", unicornTxn.hash);
162
- if (receipt.status === 1) {
163
- console.log("Adding secure lock image cert transaction was successful!");
164
- } else {
165
- // console.log("receipt.status", receipt.status)
166
- console.log("Image certificates already exist for this image!");
167
- }
168
- }
169
-
170
- // console.log("this.acct.address: ", this.acct.address);
171
- // console.log("private key", PRIVATE_KEY);
172
- // console.log('Getting nonce');
173
- // const nonce = await this.imageRegistryContract.provider.getTransactionCount(this.acct.address);
174
-
175
-
176
- // const signedTxn = await this.acct.signTransaction(unicornTxn);
177
- // const receipt = await this.provider.sendTransaction(signedTxn.rawTransaction);
178
- } catch (e) {
179
- // console.error(e);
180
- console.log("Image certificates already exist for this image!");
181
- }
152
+ console.log("Adding secure lock image cert to image registry");
153
+ await this.write('addImage', ipfsHash, certContent, version, imageName, dockerComposeHash, enclaveNameSecureLock, fee);
154
+ console.log("Adding secure lock image cert transaction was successful!");
182
155
  }
183
156
 
184
157
  async getImagePublicKeyCert(ipfsHash) {
@@ -253,6 +226,18 @@ class ImageRegistry {
253
226
  const receipt = await this.provider.sendTransaction(signedTxn.rawTransaction);
254
227
  }
255
228
 
229
+ // Where the image's developer fee is paid. The registry records the
230
+ // publishing wallet at registration; only the image's owner changes it.
231
+ async setRewardAddress(ipfsHash, rewardAddress) {
232
+ const current = await this.imageRegistryContract.getRewardAddress(ipfsHash);
233
+ if (current.toLowerCase() === rewardAddress.toLowerCase()) {
234
+ console.log(`Reward address is already ${rewardAddress}`);
235
+ return;
236
+ }
237
+ console.log(`Setting the reward address to ${rewardAddress}`);
238
+ await this.write('changeImageRewardAddress', ipfsHash, rewardAddress);
239
+ }
240
+
256
241
  async getTrustedZoneCert(ipfsHash) {
257
242
  const cert = await this.imageRegistryContract.getTrustedZoneCert(ipfsHash);
258
243
  return cert;
@@ -283,14 +268,142 @@ class ImageRegistry {
283
268
  return ['', '', ''];
284
269
  }
285
270
  }
271
+
272
+ // Whether the registry is an ECImageRegistryV2 or later, which records an
273
+ // image before its certificate exists (registerImage, then setImageCert).
274
+ // A V1 registry has no pendingImages() and reverts; a node that does not
275
+ // answer is an error, not a V1 registry.
276
+ async isV2() {
277
+ try {
278
+ await this.imageRegistryContract.pendingImages();
279
+ return true;
280
+ } catch (e) {
281
+ if (isRevert(e)) return false;
282
+ throw e;
283
+ }
284
+ }
285
+
286
+ // The wallet that owns the securelock name on an ECImageRegistryV3 or
287
+ // later (the zero address while nobody does), or null on a registry
288
+ // without name owners. Only the owner publishes under the name, and a
289
+ // name and its -unsafe twin have one owner.
290
+ async imageNameOwner(imageName) {
291
+ try {
292
+ return await this.imageRegistryContract.imageNameOwner(imageName);
293
+ } catch (e) {
294
+ if (isRevert(e)) return null;
295
+ throw e;
296
+ }
297
+ }
298
+
299
+ // Send `method(...args)` from the publishing wallet. It is simulated
300
+ // first, so a call the registry refuses stops here with the registry's
301
+ // reason, then sent with its gas estimate plus 30%: what a registry write
302
+ // costs depends on the pending list, which other publishers change
303
+ // between the estimate and the block.
304
+ async write(method, ...args) {
305
+ try {
306
+ await this.imageRegistryContract.callStatic[method](...args);
307
+ } catch (e) {
308
+ throw new Error(`the image registry refuses ${method}: ${revertReason(e, this.imageRegistryContract.interface)}`);
309
+ }
310
+ const gas = await this.imageRegistryContract.estimateGas[method](...args);
311
+ const overrides = { gasLimit: gas.mul(13).div(10) };
312
+ if (BLOCKCHAIN_NETWORK.includes("Polygon")) {
313
+ overrides.nonce = await this.provider.getTransactionCount(this.acct.address, 'pending');
314
+ overrides.gasPrice = (await this.provider.getGasPrice()).mul(110).div(100);
315
+ }
316
+ const tx = await this.imageRegistryContract[method](...args, overrides);
317
+ const receipt = await this.imageRegistryContract.provider.waitForTransaction(tx.hash);
318
+ console.log("transaction receipt: ", tx.hash);
319
+ if (receipt.status !== 1) {
320
+ throw new Error(`${method} reverted (${tx.hash})`);
321
+ }
322
+ }
323
+
324
+ // Record the securelock on a V2 registry before its certificate exists:
325
+ // name, protocol version v3, compose, session, the publisher's fee and the
326
+ // IPFS node that holds the image (a multiaddr, or ""). A hash this wallet
327
+ // already registered with the same name, compose and session is left as
328
+ // it is; a hash another wallet registered is refused, and so is a name
329
+ // another wallet owns. The record cannot be changed, so a hash registered
330
+ // with another compose or session is refused too: the nodes would fetch
331
+ // the compose the record names.
332
+ async registerImage(ipfsHash, imageName, dockerComposeHash, session, fee, ipfsPeer) {
333
+ const details = await this.imageRegistryContract.imageDetails(ipfsHash);
334
+ if (details.owner !== ethers.constants.AddressZero) {
335
+ if (details.owner.toLowerCase() !== this.acct.address.toLowerCase()) {
336
+ throw new Error(`${ipfsHash} is registered by ${details.owner}, not by this wallet`);
337
+ }
338
+ const differing = [
339
+ ['name', details.name, imageName],
340
+ ['compose', details.dockerComposeHash, dockerComposeHash],
341
+ ['session', details.session, session],
342
+ ].filter(([, recorded, wanted]) => recorded !== wanted);
343
+ if (differing.length) {
344
+ const recorded = differing.map(([field, value]) => `${field} ${value}`).join(', ');
345
+ const wanted = differing.map(([field, , value]) => `${field} ${value}`).join(', ');
346
+ throw new Error(`${ipfsHash} is registered with ${recorded}, and this publish has ${wanted}; run ecld-build to publish a new version`);
347
+ }
348
+ console.log(`${ipfsHash} is already registered`);
349
+ return false;
350
+ }
351
+ const nameOwner = await this.imageNameOwner(imageName);
352
+ if (nameOwner && nameOwner !== ethers.constants.AddressZero
353
+ && nameOwner.toLowerCase() !== this.acct.address.toLowerCase()) {
354
+ throw new Error(`the image name ${imageName} belongs to ${nameOwner}; publish under another PROJECT_NAME`);
355
+ }
356
+ console.log(`Registering ${imageName} v3 as ${ipfsHash} (compose ${dockerComposeHash}, peer ${ipfsPeer || 'none'})`);
357
+ await this.write('registerImage',
358
+ ipfsHash, "v3", imageName, dockerComposeHash, session, Number(fee), ipfsPeer || "");
359
+ return true;
360
+ }
361
+
362
+ // Write the certificate of a registered image, once, from the wallet that
363
+ // registered it. A certificate already there is left as it is. The RPC
364
+ // endpoint balances requests over nodes that can lag the chain by several
365
+ // blocks, and a read can fail outright, so an image this publish
366
+ // registered minutes earlier can read as missing, or be refused as "Image
367
+ // not found" by the simulation: both are tried again every 15 seconds for
368
+ // `waitMs` before the image is called missing.
369
+ async setImageCert(ipfsHash, cert, waitMs = 600000) {
370
+ const deadline = Date.now() + waitMs;
371
+ for (;;) {
372
+ let details = null;
373
+ try {
374
+ details = await this.imageRegistryContract.imageDetails(ipfsHash);
375
+ } catch (e) {
376
+ if (Date.now() >= deadline) throw e;
377
+ }
378
+ if (details && details.owner !== ethers.constants.AddressZero) {
379
+ if (details.certPublicKey) {
380
+ if (details.certPublicKey.trim() === cert.trim()) {
381
+ console.log(`The certificate of ${ipfsHash} is already registered`);
382
+ return false;
383
+ }
384
+ throw new Error(`${ipfsHash} already has a certificate, and it differs from the extracted one`);
385
+ }
386
+ try {
387
+ console.log(`Registering the certificate of ${ipfsHash}`);
388
+ await this.write('setImageCert', ipfsHash, cert);
389
+ return true;
390
+ } catch (e) {
391
+ if (!(e.message || '').includes('Image not found') || Date.now() >= deadline) throw e;
392
+ }
393
+ } else if (details && Date.now() >= deadline) {
394
+ throw new Error(`${ipfsHash} is not registered`);
395
+ }
396
+ await new Promise((resolve) => setTimeout(resolve, 15000));
397
+ }
398
+ }
286
399
  }
287
400
 
288
401
  (async () => {
289
402
  try {
290
- const [networkName, projectName, version, privateKey, action] = process.argv.slice(2);
403
+ const [networkName, projectName, version, action] = process.argv.slice(2);
291
404
  if (action === "validateAddress") {
292
- if (privateKey) {
293
- console.log(isStringPrivateKey(privateKey));
405
+ if (PRIVATE_KEY) {
406
+ console.log(isStringPrivateKey(PRIVATE_KEY));
294
407
  }
295
408
  process.exit(0);
296
409
  }
@@ -304,8 +417,8 @@ class ImageRegistry {
304
417
  process.exit(0);
305
418
  }
306
419
  // The certificate of the trustedzone's own record. getLatestImageVersionPublicKey
307
- // reads the securelock records instead: ECImageRegistryV2 reverts it for
308
- // a trustedzone, and the bloxberg mainnet and LitVM registries return a
420
+ // reads the securelock records instead: ECImageRegistryV2 and V3 revert it
421
+ // for a trustedzone, and the bloxberg mainnet and LitVM registries return a
309
422
  // different certificate for the same name.
310
423
  if (action === 'getTrustedZoneCert') {
311
424
  const imageRegistry = new ImageRegistry();
@@ -328,6 +441,47 @@ class ImageRegistry {
328
441
  }
329
442
 
330
443
  const imageRegistry = new ImageRegistry();
444
+ // Whether the registry records images before their certificate.
445
+ if (action === 'isV2') {
446
+ try {
447
+ console.log((await imageRegistry.isV2()) ? 'true' : 'false');
448
+ } catch (e) {
449
+ console.error(`Could not read the image registry ${IMAGE_REGISTRY_ADDRESS}: ${e.reason || e.message}`);
450
+ process.exit(1);
451
+ }
452
+ process.exit(0);
453
+ }
454
+ // Record the image before its certificate (V2): IPFS_HASH,
455
+ // IPFS_DOCKER_COMPOSE_HASH, PROJECT_NAME (the name registered),
456
+ // ENCLAVE_NAME_SECURELOCK (the session), DEVELOPER_FEE and IPFS_PEER
457
+ // (the publish's node) come from the environment.
458
+ if (action === 'registerImage') {
459
+ try {
460
+ const rewardAddress = rewardAddressFromEnv();
461
+ await imageRegistry.registerImage(
462
+ process.env.IPFS_HASH || "", process.env.PROJECT_NAME || "",
463
+ process.env.IPFS_DOCKER_COMPOSE_HASH || "", process.env.ENCLAVE_NAME_SECURELOCK || "",
464
+ process.env.DEVELOPER_FEE || "0", process.env.IPFS_PEER || "");
465
+ if (rewardAddress) {
466
+ await imageRegistry.setRewardAddress(process.env.IPFS_HASH || "", rewardAddress);
467
+ }
468
+ } catch (e) {
469
+ console.error(`Could not register the image: ${e.reason || e.message}`);
470
+ process.exit(1);
471
+ }
472
+ process.exit(0);
473
+ }
474
+ // Write the extracted certificate of a registered image (V2).
475
+ if (action === 'setImageCert') {
476
+ try {
477
+ const cert = fs.readFileSync("./registry/certificate.securelock.crt", 'utf8');
478
+ await imageRegistry.setImageCert(process.env.IPFS_HASH || "", cert);
479
+ } catch (e) {
480
+ console.error(`Could not register the certificate: ${e.reason || e.message}`);
481
+ process.exit(1);
482
+ }
483
+ process.exit(0);
484
+ }
331
485
  if (action === 'registerSecureLockImage') {
332
486
  const secureLock = fs.readFileSync("./registry/certificate.securelock.crt", 'utf8');
333
487
  // console.log("SECURELOCK:", secureLock);
@@ -357,7 +511,28 @@ class ImageRegistry {
357
511
  if (enclaveVersion && enclaveVersion !== "v3") versionKeys.push(enclaveVersion);
358
512
  for (const versionKey of versionKeys) {
359
513
  console.log(`Registering securelock under version '${versionKey}'`);
360
- await imageRegistry.addSecureLockImageCert(secureLock, ipfsHash, imageName, versionKey, ipfsDockerComposeHash, enclaveNameSecureLock, fee);
514
+ try {
515
+ await imageRegistry.addSecureLockImageCert(secureLock, ipfsHash, imageName, versionKey, ipfsDockerComposeHash, enclaveNameSecureLock, fee);
516
+ } catch (e) {
517
+ // The registry refuses a hash the (name, version) channel already holds.
518
+ if ((e.message || '').includes('Image hash is already in registry')) {
519
+ console.log(`The securelock is already registered under version '${versionKey}'`);
520
+ continue;
521
+ }
522
+ console.error(`Could not register the securelock under version '${versionKey}': ${e.reason || e.message}`);
523
+ process.exit(1);
524
+ }
525
+ }
526
+ // REWARD_ADDRESS: where the image's developer fee is paid, when not
527
+ // the publishing wallet.
528
+ try {
529
+ const rewardAddress = rewardAddressFromEnv();
530
+ if (rewardAddress) {
531
+ await imageRegistry.setRewardAddress(ipfsHash, rewardAddress);
532
+ }
533
+ } catch (e) {
534
+ console.error(`Could not set the reward address: ${e.reason || e.message}`);
535
+ process.exit(1);
361
536
  }
362
537
  process.exit(0);
363
538
  }
@@ -367,6 +542,22 @@ class ImageRegistry {
367
542
  process.exit(0);
368
543
  }
369
544
  console.log(`Checking image: '${projectName}' on the ${networkName} blockchain...`);
545
+ // On a registry with name owners the name's owner answers, whether or
546
+ // not an image is certified under the name yet.
547
+ const nameOwner = await imageRegistry.imageNameOwner(projectName);
548
+ if (nameOwner !== null) {
549
+ if (nameOwner === ethers.constants.AddressZero) {
550
+ console.log(`Image: '${projectName}' is available on the ${networkName} blockchain.`);
551
+ process.exit(0);
552
+ }
553
+ if (PRIVATE_KEY && isStringPrivateKey(PRIVATE_KEY) === "OK"
554
+ && walletAddress(PRIVATE_KEY).toLowerCase() !== nameOwner.toLowerCase()) {
555
+ console.log(`!!! Image: '${projectName}' is owned by '${nameOwner}'.\nYou are not the account holder of the image.\nPlease change the project name and try again.\n`);
556
+ process.exit(1);
557
+ }
558
+ console.log(`Image: '${projectName}' is owned by '${nameOwner}'.\nIf you are not the account holder, you will not be able to publish your project with the current name. Please change the project name and try again.\n`);
559
+ process.exit(0);
560
+ }
370
561
  const imageHash = (await imageRegistry._getLatestImageVersionPublicKey(projectName, version))[0];
371
562
  console.log(`Image hash: ${imageHash}`);
372
563
  if (!imageHash) {
@@ -375,10 +566,9 @@ class ImageRegistry {
375
566
  }
376
567
  const imageOwner = (await imageRegistry.getImageDetails(imageHash))[0];
377
568
 
378
- if (privateKey) {
379
- if (isStringPrivateKey(privateKey) === "OK") {
380
- const account = Account.fromPrivate(privateKey);
381
- if (imageOwner !== account.address) {
569
+ if (PRIVATE_KEY) {
570
+ if (isStringPrivateKey(PRIVATE_KEY) === "OK") {
571
+ if (imageOwner.toLowerCase() !== walletAddress(PRIVATE_KEY).toLowerCase()) {
382
572
  console.log(`!!! Image: '${projectName}' is owned by '${imageOwner}'.\nYou are not the account holder of the image.\nPlease change the project name and try again.\n`);
383
573
  process.exit(1);
384
574
  }