Hello! Welcome back to your course on Ethereum development.
Introduction
In our last lesson, we ventured into the low-level world of the EVM, learning how to use Yul (inline assembly) to write highly optimized functions. We saw that by manually managing memory and using clever arithmetic, we can achieve significant gas savings, a crucial skill for performance-critical applications.
Today, we move from optimizing the code inside a contract to managing the lifecycle of the contract itself. This lesson addresses the learning outcome: "Author a reusable deployment script (e.g., using Hardhat-deploy) for multiple networks."
Writing code is only half the battle; deploying it reliably and consistently across different environments—from your local machine to testnets and ultimately to mainnet—is a critical engineering discipline. Your extensive experience as a front-end developer, managing build processes and environment configurations, will be directly applicable here.
By the end of this lesson, you will be able to:
- Securely manage sensitive data like private keys and API keys using environment variables.
- Configure a Hardhat project to connect to multiple blockchain networks.
- Write a simple, reusable deployment script using
ethers.js. - Execute deployments to different networks using a single command.
Let's get started.
The Challenge: From Localhost to Mainnet
A smart contract's life typically involves several stages of deployment:
- Local Development: On a Hardhat Network or Anvil node for rapid testing.
- Testnet: On a public test network like Sepolia (for Ethereum) or Mumbai (for Polygon) to interact with other contracts and test in a more realistic environment.
- Mainnet: The final production deployment.
Each of these networks has a unique RPC URL and requires an account (with a private key) funded with that specific network's native currency for gas fees. Hardcoding these values into your scripts is insecure, error-prone, and not scalable. The professional approach is to abstract this configuration away from the deployment logic.
Step 1: Securely Managing Secrets with .env
The first rule of deployment is to never commit secrets (private keys, API keys) to version control. The standard practice in the JavaScript ecosystem is to use a .env file to store these secrets locally. The dotenv library makes it easy to load these variables into your project's environment.
The following video provides a complete walkthrough of setting up a project for deployment. We'll focus on specific parts of it. To begin, let's see how to set up the environment.
How to deploy a Polygon (MATIC) Smart Contract with Hardhat + Ethers.js
This segment from Alchemy's tutorial demonstrates how to install dotenv and create a .env file to hold your Alchemy API key and your MetaMask private key. This is the foundational step for any secure deployment setup.
Watch the section 'Configuring Hardhat for Deployment' from 00:22:41 to 00:25:20. Focus on the creation of the .env file and the variables being stored (API_URL and PRIVATE_KEY).
As shown in the video, your .env file will look something like this:
API_URL="https://polygon-mumbai.g.alchemy.com/v2/YOUR_ALCHEMY_API_KEY"
PRIVATE_KEY="YOUR_METAMASK_PRIVATE_KEY"
And crucially, your .gitignore file must contain an entry for .env to prevent it from ever being committed.
Step 2: Configuring Multiple Networks in Hardhat
With our secrets properly stored, we can now configure Hardhat to use them. The hardhat.config.js file is the control center for your project, and its networks object is where you define the different chains you want to connect to.
How to deploy a Polygon (MATIC) Smart Contract with Hardhat + Ethers.js
Continuing with the same video, let's see how to modify hardhat.config.js to read the variables from our .env file and define a network configuration for the Mumbai testnet.
Watch from 00:25:20 to 00:29:10. Pay close attention to how require('dotenv').config() is called at the top of the file, and how process.env.API_URL and process.env.PRIVATE_KEY are used to configure the mumbai network.
The pattern demonstrated is the key to this lesson. You create an entry in the networks object for each chain. The structure is simple:
require("@nomicfoundation/hardhat-toolbox");
require("dotenv").config();
const { API_URL, PRIVATE_KEY } = process.env;
module.exports = {
solidity: "0.8.20",
networks: {
mumbai: {
url: API_URL,
accounts: [`0x${PRIVATE_KEY}`] // Note: some wallets export keys without the 0x prefix
}
}
};
To deploy to more networks, you simply add more entries. This text resource provides a clear, concise example of what a config file for multiple networks looks like.
Deploy Smart Contracts on EVM Chains with Hardhat
This blog post, 'Deploy Smart Contracts on EVM Chains with Hardhat', shows a clear example of a hardhat.config.js file configured for three different networks.
Read the section 'Configuring Networks'. Notice the pattern: one entry in the networks object for each chain (ethereum, polygon, bsc), each with its own RPC URL and accounts sourced from environment variables.
By following this pattern, you can support any number of EVM-compatible chains from a single, clean configuration file.
Step 3: Writing a Network-Agnostic Deployment Script
Now for the script itself. A reusable deployment script should contain only the logic for deploying the contract, without any hardcoded addresses, URLs, or keys. Hardhat and ethers.js handle the network connection behind the scenes based on your configuration and command-line arguments.
The default Hardhat project comes with a simple script (scripts/deploy.js or scripts/sample-script.js) that serves as an excellent template.
async function main() {
// 1. Get the contract factory
const Greeter = await hre.ethers.getContractFactory("Greeter");
// 2. Start the deployment, passing constructor arguments
console.log("Deploying Greeter...");
const greeter = await Greeter.deploy("Hello, Hardhat!");
// 3. Wait for the deployment to be confirmed on the network
await greeter.waitForDeployment();
const address = await greeter.getAddress();
console.log(`Greeter deployed to: ${address}`);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Notice that this script is completely generic. It doesn't know or care if it's deploying to a local node or to the Polygon mainnet. That decision is made when we run the script.
Step 4: Executing the Deployment
With the configuration and script in place, deploying is a one-line command. You use the --network flag to tell Hardhat which network configuration from hardhat.config.js to use.
First, let's watch the deployment to the Mumbai testnet.
How to deploy a Polygon (MATIC) Smart Contract with Hardhat + Ethers.js
This section of the video shows the final step: compiling the contract and running the deployment script while targeting the mumbai network we configured earlier.
Watch 'Compiling and Deploying the Smart Contract' from 00:29:10 to 00:32:04. The key command is npx hardhat run scripts/sample-script.js --network mumbai.
Now, how would you deploy the exact same contract to the Polygon mainnet? The process is simple:
- Add a new network configuration for the mainnet in
hardhat.config.js. - Update your
.envfile with the RPC URL for the mainnet and ensure thePRIVATE_KEYcorresponds to an account with real MATIC. - Run the same command, but change the network flag.
The video explains this exact process conceptually.
How to deploy a Polygon (MATIC) Smart Contract with Hardhat + Ethers.js
This final clip explains how to adapt the configuration to deploy to mainnet. It brings together all the concepts we've discussed.
Watch 'Deploying to Mainnet (Conceptual)' from 00:43:40 to 00:45:50. The core idea is simply adding a new network object (e.g., matic) to the config and then running the script with --network matic.
This demonstrates the power of this approach: your deployment logic remains unchanged, and only the configuration and the command-line flag vary.
A Note on hardhat-deploy
The learning outcome mentions hardhat-deploy as an example. While the method we've just covered is the standard, hardhat-deploy is a popular and powerful plugin that takes deployment management a step further.
For complex projects, hardhat-deploy offers several advantages over simple scripts:
- Stateful Deployments: It saves the addresses of deployed contracts in JSON files, allowing scripts to reference previous deployments.
- Idempotency: It can skip deploying a contract if it hasn't changed, saving time and gas.
- Named Accounts: You can assign names to addresses in your config (e.g.,
deployer,admin) for more readable scripts. - Dependency Management: Deployment scripts can specify dependencies on other scripts, ensuring contracts are deployed in the correct order.
A hardhat-deploy script looks slightly different. They are placed in a deploy folder and export a function:
// deploy/01-deploy-greeter.js
module.exports = async ({ getNamedAccounts, deployments }) => {
const { deploy } = deployments;
const { deployer } = await getNamedAccounts();
await deploy('Greeter', {
from: deployer,
args: ["Hello, hardhat-deploy!"],
log: true,
});
};
module.exports.tags = ['Greeter'];
You don't need to master hardhat-deploy for now, but it's important to know it exists as the go-to solution for managing large, multi-contract deployments.
Conclusion
You have now learned the fundamental principles of professional smart contract deployment. By separating configuration from logic, you can create a robust, reusable, and secure workflow for deploying your contracts to any EVM network.
Key Takeaways:
- Always use a
.envfile for secrets like private keys and API keys, and add it to your.gitignore. - The
networksobject inhardhat.config.jsis where you define connection details for each target blockchain. - Deployment scripts should be network-agnostic, reading network details from the Hardhat environment.
- The
npx hardhat run <script> --network <network-name>command is used to execute a deployment on a specific chain. - For more advanced deployment management, the
hardhat-deployplugin provides features like stateful deployments and dependency tracking.
Next Lesson Preview:
Now that you can deploy contracts, the next logical step is to manage their evolution. Contracts on Ethereum are immutable by default, but what if you need to fix a bug or add a feature? In our next lesson, we will learn how to "Integrate a UUPS proxy pattern into a deployment script to manage contract upgrades." The deployment skills you've learned today are a direct prerequisite for handling the complexities of deploying and managing upgradeable contracts.
Can't find a good explanation? Sign up and we'll make it for you
Sign up