Hardhat Multi-Deploy Smart Contract Auto-Deployment Setup

We design and develop full-cycle blockchain solutions: from smart contract architecture to launching DeFi protocols, NFT marketplaces and crypto exchanges. Security audits, tokenomics, integration with existing infrastructure.
Showing 1 of 1 servicesAll 1306 services
Hardhat Multi-Deploy Smart Contract Auto-Deployment Setup
Simple
from 1 business day to 3 business days
FAQ
Blockchain Development Services
Blockchain Development Stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1218
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    853
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1047
  • image_logo-advance_0.png
    B2B Advance company logo design
    561
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    823

Setting Up Automatic Smart Contract Deployment via Hardhat Multi-Deploy

Deploying to one network — a few commands. Deploying to 8 networks simultaneously while preserving addresses, verifying sources, and updating frontend configs — that's an infrastructure task. The hardhat-deploy plugin solves it declaratively.

Why Standard Hardhat Deploy Isn't Enough

The basic npx hardhat run scripts/deploy.ts approach is an imperative script without state. There's no tracking of what's already deployed. No idempotency. Running it again deploys again, and you get a second instance of the contract. Addresses aren't saved automatically anywhere.

hardhat-deploy adds:

  • Deployment tracking — JSON files in deployments/<network>/ with address, ABI, bytecode, transaction hash
  • Idempotency — rerunning doesn't deploy if the contract exists and hasn't changed
  • Named accountsnamedAccounts in config for readability
  • Test fixturesdeployments available in tests via getNamedAccounts

Multi-Chain Configuration

// hardhat.config.ts
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
import "hardhat-deploy";

const config: HardhatUserConfig = {
  solidity: {
    version: "0.8.24",
    settings: {
      optimizer: { enabled: true, runs: 200 },
      viaIR: false, // enable only if needed
    },
  },
  
  namedAccounts: {
    deployer: {
      default: 0,         // first account
      mainnet: "0x...",   // specific address for mainnet
    },
    treasury: {
      default: 1,
      mainnet: "0x...",
    },
  },
  
  networks: {
    mainnet:  { url: process.env.MAINNET_RPC,  accounts: [process.env.DEPLOYER_KEY!], chainId: 1 },
    polygon:  { url: process.env.POLYGON_RPC,  accounts: [process.env.DEPLOYER_KEY!], chainId: 137 },
    arbitrum: { url: process.env.ARBITRUM_RPC, accounts: [process.env.DEPLOYER_KEY!], chainId: 42161 },
    optimism: { url: process.env.OPTIMISM_RPC, accounts: [process.env.DEPLOYER_KEY!], chainId: 10 },
    base:     { url: process.env.BASE_RPC,     accounts: [process.env.DEPLOYER_KEY!], chainId: 8453 },
  },
  
  etherscan: {
    apiKey: {
      mainnet:        process.env.ETHERSCAN_KEY!,
      polygon:        process.env.POLYGONSCAN_KEY!,
      arbitrumOne:    process.env.ARBISCAN_KEY!,
      optimisticEthereum: process.env.OPTIMISM_KEY!,
      base:           process.env.BASESCAN_KEY!,
    },
  },
};

Deploy Scripts with hardhat-deploy

// deploy/001_deploy_token.ts
import { HardhatRuntimeEnvironment } from "hardhat/types";
import { DeployFunction } from "hardhat-deploy/types";

const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
  const { deployments, getNamedAccounts, network } = hre;
  const { deploy } = deployments;
  const { deployer, treasury } = await getNamedAccounts();

  const token = await deploy("MyToken", {
    from: deployer,
    args: [treasury, "1000000000000000000000000"], // 1M tokens
    log: true,          // logs address and tx hash
    autoMine: true,     // automatic mining on local network
    waitConfirmations: network.name === "mainnet" ? 5 : 1,
  });

  // Verification right after deployment
  if (network.name !== "hardhat" && network.name !== "localhost") {
    await hre.run("verify:verify", {
      address: token.address,
      constructorArguments: [treasury, "1000000000000000000000000"],
    });
  }
};

func.tags = ["Token", "all"];
func.dependencies = []; // this script has no dependencies
export default func;
// deploy/002_deploy_staking.ts
const func: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
  const { deployments, getNamedAccounts } = hre;
  const { deploy, get } = deployments;
  const { deployer } = await getNamedAccounts();

  const token = await get("MyToken"); // get address of already deployed token

  await deploy("StakingContract", {
    from: deployer,
    args: [token.address],
    log: true,
  });
};

func.tags = ["Staking", "all"];
func.dependencies = ["Token"]; // deploys only after Token
export default func;

Execution order is managed through tags and dependencies. Hardhat-deploy builds a dependency graph and deploys in the correct order.

Deploying to Multiple Networks

# One network
npx hardhat deploy --network polygon

# Multiple networks through script
for network in mainnet polygon arbitrum optimism base; do
  npx hardhat deploy --network $network --tags all
done

For parallel deployment to multiple networks simultaneously — a small bash script:

#!/bin/bash
networks=("polygon" "arbitrum" "optimism" "base")
pids=()

for network in "${networks[@]}"; do
  npx hardhat deploy --network $network --tags all &
  pids+=($!)
done

for pid in "${pids[@]}"; do
  wait $pid || exit 1
done

echo "All deployments complete"

Mainnet deployment is done separately, manually, after testing all testnets.

Address Storage and Export

After deployment hardhat-deploy creates files in deployments/polygon/MyToken.json with address and ABI. For frontend — export into a single config:

// scripts/export-addresses.ts
import { deployments } from "hardhat";

const networks = ["mainnet", "polygon", "arbitrum", "optimism", "base"];
const contracts = ["MyToken", "StakingContract"];

const config: Record<string, Record<string, string>> = {};

for (const network of networks) {
  config[network] = {};
  for (const contract of contracts) {
    try {
      const deployment = await deployments.get(contract);
      config[network][contract] = deployment.address;
    } catch {
      // contract not deployed to this network
    }
  }
}

fs.writeFileSync("src/contracts/addresses.json", JSON.stringify(config, null, 2));

CI/CD Integration

GitHub Actions for automatic deployment when merging to main:

# .github/workflows/deploy.yml
name: Deploy Contracts

on:
  push:
    branches: [main]
    paths: ["contracts/**", "deploy/**"]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - uses: actions/setup-node@v4
        with:
          node-version: "20"
          cache: "npm"
      
      - run: npm ci
      
      - name: Deploy to testnets
        env:
          DEPLOYER_KEY: ${{ secrets.DEPLOYER_KEY }}
          POLYGON_RPC: ${{ secrets.POLYGON_MUMBAI_RPC }}
        run: npx hardhat deploy --network polygonMumbai --tags all
      
      - name: Commit updated deployments
        run: |
          git config user.name "GitHub Actions"
          git config user.email "[email protected]"
          git add deployments/
          git commit -m "chore: update deployment artifacts" || echo "No changes"
          git push

Deployment artifacts are committed back to the repository — addresses are always up-to-date and versioned.

Time to set up a full multi-chain deployment pipeline — 1-3 days depending on number of networks and CI/CD requirements.