Estimated time: 10 minutes read
Guide on how to use Hardhat Ignition to deploy smart contracts on Ethereum-compatible networks. Various tools to automate the process are analyzed.
How to deploy smart contracts with Hardhat?
Ignition is Hardhat’s declarative system for deploying smart contracts on Ethereum-compatible networks.
Using this component involves installing the following packages if they haven’t been installed previously:
yarn add --dev @nomicfoundation/hardhat-ignition-ethers @nomicfoundation/hardhat-ethers
@nomicfoundation/hardhat-ignition @nomicfoundation/ignition-core ethers
Additionally, you need to add the following plugin to the “hardhat-config.js” file:
require("@nomicfoundation/hardhat-ignition-ethers");
Deployments are defined through modules. Each module encapsulates a group of smart contracts and the operations required to enable the global deployment of the entire project.
Modules should be created in the “ignition/modules” folder of the Hardhat project. They are defined in JavaScript files. A typical file would be:
const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules");
module.exports = buildModule("moduleID", (m) => {
const futureName = m.contract("contractName", [args]);
return { futureName };
});
The call to “buildModule” creates the Ignition module. It is recommended to define a single module per JS file, meaning only one call to “buildModule”.
The first argument of the “buildModule” call is the “moduleID”, which usually matches the name of the JavaScript file in which it is defined. The second argument is the “callback function”, which specifies the operations to be executed on the smart contracts during deployment.
The constant “futureName” represents the concept of a ‘Future’ in Hardhat Ignition. It is a necessary execution step in the overall deployment process. It is returned in the callback function to be used by other modules.
What types of operations can be performed on smart contracts during deployment?
The complete details can be found in the documentation on the different types of futures in Hardhat.
Some of the most common operations are illustrated with examples:
Deploying a contract with arguments
// CryptoTaskManager.js
const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules");
module.exports = buildModule("CryptoTaskManager", (m) => {
const cryptoTaskManager = m.contract("CryptoTaskManager", [100]);
return { cryptoTaskManager };
});
The Future “cryptoTaskManager” represents the deployment of a contract that is supplied with a numerical argument in its constructor.
Dependency between modules and a call to a write function
// CryptoTaskFactory.js
const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules");
const CryptoTaskManager = require("./CryptoTaskManager");
module.exports = buildModule("CryptoTaskFactory", (m) => {
const { cryptoTaskManager } = m.useModule(CryptoTaskManager);
const cryptoTaskFactory = m.contract("CryptoTaskFactoryWrapperVTest", [cryptoTaskManager]);
m.call(cryptoTaskManager, "setCryptoTaskFactoryImplementation", [cryptoTaskFactory]);
return {cryptoTaskFactory};
});
The new Future “cryptoTaskFactory” represents the deployment of a contract that is supplied with the address of the “cryptoTaskManager” contract from the previous module as an argument.
To reference Futures exported from other modules, the “useModule” call is used.
Finally, the write call on the “setCryptoTaskFactoryImplementation” method is illustrated, providing the address of the newly deployed contract as an argument.
Deploying libraries
// Libraries.js
const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules");
module.exports = buildModule("Libraries", (m) => {
const tokenManagerLibrary = m.library("TokenManagerLibraryV100");
const cryptoTaskLibrary = m.library("CryptoTaskLibraryV100", {
libraries: {
TokenManagerLibraryV100: tokenManagerLibrary,
},
});
return { cryptoTaskLibrary };
});
Two Futures are created corresponding to two libraries. The first one is also linked to the second one.
More details about the usage of libraries can be found in the Solidity libraries article.
Linking a library in a contract
// CryptoTask.js
const { buildModule } = require("@nomicfoundation/hardhat-ignition/modules");
const CryptoTaskManager = require("./CryptoTaskManager");
const Libraries = require("./Libraries");
module.exports = buildModule("CryptoTask", (m) => {
const { cryptoTaskManager } = m.useModule(CryptoTaskManager);
const { cryptoTaskLibrary } = m.useModule(Libraries);
const cryptoTask = m.contract("CryptoTaskV100", [cryptoTaskManager], {
libraries: {
CryptoTaskLibraryV100: cryptoTaskLibrary,
},
});
m.call(cryptoTaskManager, "setCryptoTaskImplementation", [cryptoTask]);
return { cryptoTask };
});
The Future “cryptoTask” represents the deployment of a contract, supplying the address of another previously deployed contract to its constructor and linking a library that was also deployed in another module.
Module dependencies and step review
Dividing the overall project deployment into different modules is useful for breaking down the process into smaller interconnected blocks using the module dependency mechanism (“useModule”).
The entire project deployment can be executed with a single call to the main module. In the examples provided, this would be the “CryptoTask” module presented last.
Before proceeding with the actual deployment on the blockchain, it is helpful to review which operations will be performed. Continuing with our example:
yarn hardhat ignition visualize .\ignition\modules\CryptoTask.js
As a result, an HTML page opens with the necessary steps to deploy the contracts defined in the module, also deploying all established dependencies:
Deployment of the project on the Hardhat Network
To illustrate the concepts in this section, the contracts from the example modules will be used, and the Hardhat Network will serve as the long-running blockchain process. Therefore, it will be executed beforehand:
yarn hardhat network
The deployment command would be as follows:
yarn hardhat ignition deploy .\ignition\modules\CryptoTask.js --network localhost
The following console flow is displayed, corresponding to the detailed steps reviewed by invoking “ignition visualize”:
At the end, the deployed Futures are shown, identified by their “FutureID” and their network addresses.
As a result of successful execution, a folder corresponding to the current “deploymentID” will have been created in the directory “ignition/deployments/”. If not explicitly defined using the “–deployment-id” modifier, it defaults to “chain-$chainID”. For the Hardhat Network, the network identifier (chainID) is 31337.
Compiled contracts and a “deployed_addresses.json” file containing the addresses of the deployed Futures as displayed in the console are located within that folder.
The deployment status can be reviewed using the command “ignition status”, providing the deploymentID:
yarn hardhat ignition status chain-31337
If an error occurs, it’s possible to resume deployment from the breakpoint once it has been fixed. Simply invoking again the “ignition deploy” command will allow Hardhat Ignition to handle it automatically.
It’s also possible to modify the module definition files to rerun them so that Hardhat Ignition attempts to deploy the differences.
If you want to discard any of the deployed Futures, you need to invoke the “ignition wipe” command. For example, to discard the last deployed contract:
yarn hardhat ignition wipe chain-31337 CryptoTask#CryptoTaskV100
If there is any dependency that needs to be resolved, we will receive an explanatory message:
The error will need to be resolved then:
yarn hardhat ignition wipe chain-31337 CryptoTask#CryptoTaskManager~CryptoTaskManager.setCryptoTaskImplementation
yarn hardhat ignition wipe chain-31337 CryptoTask#CryptoTaskV100
Alternatively, to completely restart the deployment of modules, you can directly delete the folder “ignition/deployments/chain-$chainID”.
Deployment on a real blockchain
The difference from deploying on the Hardhat Network is defining a real blockchain in the command invocation. For example, for the Sepolia testnet:
yarn hardhat ignition deploy .\ignition\modules\CryptoTask.js --network sepolia:
In the “hardhat.config.js” file, you must define the RPC-JSON endpoint URL of the blockchain access provider and the private key of the address to be used for deployment (in the example, a “.env” properties file is used):
sepolia: {
chainId: 11155111,
url: `https://eth-sepolia.g.alchemy.com/v2/${ALCHEMY_SEPOLIA_API_KEY}`,
accounts: [`0x${SEPOLIA_ADDRESS_PRIVATE_KEY}`]
},
When executing transactions on the blockchain, confirmations are monitored until reaching the minimum defined in the configuration. Subsequent transactions are queued until confirmed. If the confirmation threshold is not met within a maximum wait time, the transaction is retried with increased fees. After N retries without success, deployment will abort.
Any write operation during deployment is first simulated using “eth_call”.
All transaction-related configurations during deployment are defined as Ignition configuration properties in the “hardhat.config.js” file.
Conclusion: Highly versatile contract deployment
This article has detailed the process of deploying contracts on the blockchain using the Hardhat Ignition tool.
It’s a component that completely renews the way of interacting with the blockchain compared to the old hardhat-deploy plugin.
Among its advantages:
- Ability to divide deployments among related modules that can be executed independently.
- Automatic recovery from deployment errors by resuming from the breakpoint.
- Management of a deployment repository across different blockchains, with the ability to visualize the status and addresses of deployed contracts.
- Option to verify the deployment sequence for validation before execution.
What tool do you typically use to deploy your contracts on the blockchain? I encourage you to share your experience in the comments.
If you’re interested in deploying a contract on the blockchain, I can help. I specialize in blockchain project development. Feel free to contact me, and let’s discuss how we can collaborate. Thank you very much!