Harden membership deploy script with env validation and output metadata

This commit is contained in:
Joshua 2026-02-18 07:11:08 -08:00
parent 8e6c1ceace
commit d91b9927f8
4 changed files with 55 additions and 4 deletions

View File

@ -8,3 +8,5 @@ TREASURY_WALLET=0x0000000000000000000000000000000000000000
MINT_CURRENCY_ADDRESS=0x0000000000000000000000000000000000000000 MINT_CURRENCY_ADDRESS=0x0000000000000000000000000000000000000000
MINT_AMOUNT_ATOMIC=5000000000000000 MINT_AMOUNT_ATOMIC=5000000000000000
# Optional: write deployment metadata JSON (address, tx hash, params).
# DEPLOY_OUTPUT_PATH=./deploy/membership-deploy.sepolia.json

1
.nvmrc Normal file
View File

@ -0,0 +1 @@
20

View File

@ -23,6 +23,8 @@ Features:
## Local Commands ## Local Commands
Use a Hardhat-supported Node runtime (`20.x` recommended).
1. `npm install` 1. `npm install`
2. `npm run build` 2. `npm run build`
3. `npm run test` 3. `npm run test`
@ -40,6 +42,7 @@ Copy `.env.example` values into your shell/session before deploy:
3. `TREASURY_WALLET` 3. `TREASURY_WALLET`
4. `MINT_CURRENCY_ADDRESS` (`0x000...000` for ETH) 4. `MINT_CURRENCY_ADDRESS` (`0x000...000` for ETH)
5. `MINT_AMOUNT_ATOMIC` 5. `MINT_AMOUNT_ATOMIC`
6. `DEPLOY_OUTPUT_PATH` (optional)
Example (Sepolia): Example (Sepolia):

View File

@ -1,4 +1,6 @@
const hre = require("hardhat"); const hre = require("hardhat");
const fs = require("fs");
const path = require("path");
function requiredEnv(name) { function requiredEnv(name) {
const value = process.env[name]; const value = process.env[name];
@ -8,10 +10,36 @@ function requiredEnv(name) {
return value.trim(); 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() { async function main() {
const treasury = requiredEnv("TREASURY_WALLET"); const treasury = requireAddress("TREASURY_WALLET", requiredEnv("TREASURY_WALLET"));
const mintCurrency = (process.env.MINT_CURRENCY_ADDRESS || hre.ethers.constants.AddressZero).trim(); const mintCurrency = requireAddress(
const mintAmountAtomic = requiredEnv("MINT_AMOUNT_ATOMIC"); "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(); const [deployer] = await hre.ethers.getSigners();
console.log("deployer:", deployer.address); console.log("deployer:", deployer.address);
@ -25,10 +53,27 @@ async function main() {
await contract.deployed(); await contract.deployed();
console.log("membership_contract:", contract.address); 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) => { main().catch((err) => {
console.error(err); console.error(err);
process.exitCode = 1; process.exitCode = 1;
}); });