diff --git a/scripts/deploy.js b/scripts/deploy.js index 4ad7562..1b3d498 100644 --- a/scripts/deploy.js +++ b/scripts/deploy.js @@ -1,74 +1,162 @@ -// scripts/deploy.js - +const { ethers, run } = require("hardhat"); require("dotenv").config(); -const { ethers } = require("hardhat"); +const fs = require("fs").promises; +const path = require("path"); + +// Deployment configuration +const config = { + contractName: process.env.CONTRACT_NAME || "SimpleNFT", + name: process.env.NFT_NAME || "Nexus NFT Collection", + symbol: process.env.NFT_SYMBOL || "NNFT", + baseUri: process.env.NEXT_PUBLIC_API_URL + ? `${process.env.NEXT_PUBLIC_API_URL.replace(/\/$/, "")}/api/metadata/` + : undefined, + initialOwner: undefined, // Set dynamically + mint: { + count: parseInt(process.env.MINT_COUNT) || 1, + recipient: process.env.MINT_RECIPIENT, // Optional: defaults to deployer + }, + allowedNetworks: process.env.ALLOWED_NETWORKS + ? process.env.ALLOWED_NETWORKS.split(",") + : ["sepolia", "localhost", "hardhat"], + gasOptions: { + gasLimit: process.env.GAS_LIMIT ? parseInt(process.env.GAS_LIMIT) : undefined, + gasPrice: process.env.GAS_PRICE ? parseInt(process.env.GAS_PRICE) : undefined, + }, + autoVerify: process.env.AUTO_VERIFY === "true", + dryRun: process.env.DRY_RUN === "true", +}; async function main() { try { console.log("Starting SimpleNFT deployment..."); + // Get deployer account const [deployer] = await ethers.getSigners(); - console.log("Deploying contracts with account:", deployer.address); + console.log("Deployer address:", deployer.address); + config.initialOwner = config.initialOwner || deployer.address; - // Verify environment variable NEXT_PUBLIC_API_URL is set - const apiUrl = process.env.NEXT_PUBLIC_URL; - if (!apiUrl) { - throw new Error("NEXT_PUBLIC_URL environment variable is not set"); + // Validate configuration + if (!config.baseUri) { + throw new Error("NEXT_PUBLIC_API_URL environment variable is not set"); + } + if (!config.baseUri.match(/^https?:\/\//)) { + throw new Error(`Invalid NEXT_PUBLIC_API_URL: ${config.baseUri} (must be a valid URL)`); } - // Ensure base URI ends with a slash + metadata path - const baseUri = apiUrl.replace(/\/$/, "") + "/api/metadata/"; - console.log("Using metadata base URI:", baseUri); + // Validate network + const network = await ethers.provider.getNetwork(); + const networkName = network.name === "unknown" ? "localhost" : network.name; + if (!config.allowedNetworks.includes(networkName)) { + throw new Error( + `Deployment not allowed on network: ${networkName}. Allowed networks: ${config.allowedNetworks.join(", ")}` + ); + } + console.log("Network:", networkName); - // Get the contract factory for SimpleNFT (name must match your contract) - const SimpleNFT = await ethers.getContractFactory("SimpleNFT"); - console.log("Contract factory initialized"); + // Get contract factory + const SimpleNFT = await ethers.getContractFactory(config.contractName); + console.log("Contract factory initialized:", config.contractName); - // Deploy the contract with name, symbol, baseURI, and initialOwner - const nft = await SimpleNFT.deploy( - "Nexus NFT Collection", // name - "NNFT", // symbol - baseUri, // baseTokenURI - deployer.address // initial owner (passed to Ownable) + // Estimate gas for deployment + const deploymentArgs = [ + config.name, + config.symbol, + config.baseUri, + config.initialOwner, + ]; + const estimatedGas = await SimpleNFT.signer.estimateGas( + SimpleNFT.getDeployTransaction(...deploymentArgs) ); + console.log(`Estimated gas for deployment: ${estimatedGas.toString()}`); + + // Dry run mode + if (config.dryRun) { + console.log("Dry run mode: Simulating deployment only"); + console.log("Deployment arguments:", deploymentArgs); + console.log("Deployment would succeed with estimated gas:", estimatedGas.toString()); + return; + } - // Wait for deployment to be mined + // Deploy contract + console.log("Deploying contract with arguments:", deploymentArgs); + const nft = await SimpleNFT.deploy(...deploymentArgs, config.gasOptions); await nft.waitForDeployment(); + const contractAddress = nft.target; + console.log(`${config.contractName} deployed to:`, contractAddress); + console.log("Transaction hash:", nft.deploymentTransaction()?.hash || "N/A"); - console.log("SimpleNFT deployed to:", nft.target); - console.log("Transaction hash:", nft.deploymentTransaction()?.hash || "Transaction hash not available"); - // Log deployment summary - console.log({ - contractAddress: nft.target, + // Save deployment artifacts + const artifacts = { + contractName: config.contractName, + contractAddress, deployer: deployer.address, - network: (await ethers.provider.getNetwork()).name, + network: networkName, blockNumber: await ethers.provider.getBlockNumber(), - }); + timestamp: new Date().toISOString(), + args: deploymentArgs, + }; + const artifactsPath = path.join(__dirname, `../artifacts/deployment-${networkName}.json`); + await fs.writeFile(artifactsPath, JSON.stringify(artifacts, null, 2)); + console.log("Deployment artifacts saved to:", artifactsPath); - // Print verification command for ease of use - console.log("\nTo verify on block explorer:"); - console.log( - `npx hardhat verify --network nexus ${nft.address} "Nexus NFT Collection" "NNFT" "${baseUri}" "${deployer.address}"` - ); + // Automatic verification + if (config.autoVerify && networkName !== "localhost" && networkName !== "hardhat") { + console.log("Verifying contract on block explorer..."); + try { + await run("verify:verify", { + address: contractAddress, + constructorArguments: deploymentArgs, + contract: `contracts/${config.contractName}.sol:${config.contractName}`, + }); + console.log("Contract verified successfully"); + } catch (error) { + console.error("Verification failed:", error.message); + } + } else { + console.log("\nTo verify on block explorer:"); + console.log( + `npx hardhat verify --network ${networkName} ${contractAddress} "${config.name}" "${config.symbol}" "${config.baseUri}" "${config.initialOwner}"` + ); + } - // Mint the first NFT token to deployer address - const mintTx = await nft.safeMint(deployer.address); - await mintTx.wait(); + // Batch mint NFTs + const mintRecipient = config.mint.recipient || deployer.address; + console.log(`Minting ${config.mint.count} NFT(s) to:`, mintRecipient); + for (let i = 0; i < config.mint.count; i++) { + const mintTx = await nft.safeMint(mintRecipient); + const receipt = await mintTx.wait(); + const tokenId = receipt.logs + .filter((log) => log.topics[0] === ethers.id("Transfer(address,address,uint256)")) + .map((log) => ethers.AbiCoder.defaultAbiCoder().decode(["uint256"], log.topics[3])[0])[0]; + console.log(`Minted NFT with tokenId ${tokenId} (Metadata URL: ${config.baseUri}${tokenId})`); + } - console.log("\nFirst NFT minted to deployer"); - console.log("Metadata URL:", `${baseUri}0`); + // Verify Transfer event + const filter = nft.filters.Transfer(null, mintRecipient, null); + const events = await nft.queryFilter(filter, artifacts.blockNumber); + console.log(`Verified ${events.length} Transfer events emitted`); - console.log("Deployment completed successfully"); + console.log("\nDeployment and minting completed successfully"); + console.log("Summary:", artifacts); } catch (error) { - console.error("Deployment failed:", error); + console.error("Deployment failed:", { + message: error.message, + stack: error.stack, + code: error.code, + }); process.exitCode = 1; } } -// Run the deployment script +// Run the script main() .then(() => process.exit(0)) .catch((error) => { - console.error(error); + console.error("Script execution failed:", { + message: error.message, + stack: error.stack, + code: error.code, + }); process.exit(1); - }); \ No newline at end of file + });