diff --git a/.env.example b/.env.example index 377cd6b..28dae5b 100644 --- a/.env.example +++ b/.env.example @@ -8,3 +8,5 @@ TREASURY_WALLET=0x0000000000000000000000000000000000000000 MINT_CURRENCY_ADDRESS=0x0000000000000000000000000000000000000000 MINT_AMOUNT_ATOMIC=5000000000000000 +# Optional: write deployment metadata JSON (address, tx hash, params). +# DEPLOY_OUTPUT_PATH=./deploy/membership-deploy.sepolia.json diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..209e3ef --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +20 diff --git a/README.md b/README.md index 2694c6e..e1e0c0e 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,8 @@ Features: ## Local Commands +Use a Hardhat-supported Node runtime (`20.x` recommended). + 1. `npm install` 2. `npm run build` 3. `npm run test` @@ -40,6 +42,7 @@ Copy `.env.example` values into your shell/session before deploy: 3. `TREASURY_WALLET` 4. `MINT_CURRENCY_ADDRESS` (`0x000...000` for ETH) 5. `MINT_AMOUNT_ATOMIC` +6. `DEPLOY_OUTPUT_PATH` (optional) Example (Sepolia): diff --git a/scripts/deploy-membership.cjs b/scripts/deploy-membership.cjs index 8fa2d06..c02cc4f 100644 --- a/scripts/deploy-membership.cjs +++ b/scripts/deploy-membership.cjs @@ -1,4 +1,6 @@ const hre = require("hardhat"); +const fs = require("fs"); +const path = require("path"); function requiredEnv(name) { const value = process.env[name]; @@ -8,10 +10,36 @@ function requiredEnv(name) { return value.trim(); } +function requireAddress(name, value, { allowZero = false } = {}) { + if (!hre.ethers.utils.isAddress(value)) { + throw new Error(`Invalid address for ${name}: ${value}`); + } + if (!allowZero && value === hre.ethers.constants.AddressZero) { + throw new Error(`${name} cannot be zero address`); + } + return value; +} + +function parseUint(name, value) { + try { + const parsed = hre.ethers.BigNumber.from(value); + if (parsed.lt(0)) { + throw new Error("must be >= 0"); + } + return parsed; + } catch (err) { + throw new Error(`Invalid uint for ${name}: ${value} (${err.message})`); + } +} + async function main() { - const treasury = requiredEnv("TREASURY_WALLET"); - const mintCurrency = (process.env.MINT_CURRENCY_ADDRESS || hre.ethers.constants.AddressZero).trim(); - const mintAmountAtomic = requiredEnv("MINT_AMOUNT_ATOMIC"); + const treasury = requireAddress("TREASURY_WALLET", requiredEnv("TREASURY_WALLET")); + const mintCurrency = requireAddress( + "MINT_CURRENCY_ADDRESS", + (process.env.MINT_CURRENCY_ADDRESS || hre.ethers.constants.AddressZero).trim(), + { allowZero: true }, + ); + const mintAmountAtomic = parseUint("MINT_AMOUNT_ATOMIC", requiredEnv("MINT_AMOUNT_ATOMIC")); const [deployer] = await hre.ethers.getSigners(); console.log("deployer:", deployer.address); @@ -25,10 +53,27 @@ async function main() { await contract.deployed(); console.log("membership_contract:", contract.address); + + const output = { + network: hre.network.name, + chainId: hre.network.config.chainId || null, + deployer: deployer.address, + treasury, + mintCurrency, + mintAmountAtomic: mintAmountAtomic.toString(), + membershipContract: contract.address, + txHash: contract.deployTransaction.hash, + }; + const outputPath = (process.env.DEPLOY_OUTPUT_PATH || "").trim(); + if (outputPath) { + const absolute = path.resolve(outputPath); + fs.mkdirSync(path.dirname(absolute), { recursive: true }); + fs.writeFileSync(absolute, JSON.stringify(output, null, 2)); + console.log("deployment_output:", absolute); + } } main().catch((err) => { console.error(err); process.exitCode = 1; }); -