Skip to content

Hardhat

  • Test and Deploy with Hardhat


    Master Solidity smart contract development with Hardhat. Learn testing, deployment, and network interaction in one comprehensive tutorial.


    Get Started

Overview

Hardhat is a robust development environment for Ethereum-compatible chains that makes smart contract development more efficient. This guide walks you through the essentials of using Hardhat to create, compile, test, and deploy smart contracts on Polkadot Hub.

Prerequisites

Before getting started, ensure you have:

  • Node.js (v16.0.0 or later) and npm installed
  • Basic understanding of Solidity programming
  • Some WND test tokens to cover transaction fees (easily obtainable from the Polkadot faucet). To learn how to get test tokens, check out the Test Tokens section

Set Up Hardhat

  1. Create a new directory for your project and navigate into it:

    mkdir hardhat-example
    cd hardhat-example
    
  2. Initialize a new npm project:

    npm init -y
    
  3. To interact with Polkadot, Hardhat requires the following plugin to compile contracts to PolkaVM bytecode and to spawn a local node compatible with PolkaVM:

    npm install --save-dev @parity/hardhat-polkadot
    
  4. Create a Hardhat project:

    npx hardhat-polkadot init
    

    Select Create a JavaScript project when prompted and follow the instructions. After that, your project will be created with three main folders:

    • contracts - where your Solidity smart contracts live
    • test - contains your test files that validate contract functionality
    • ignition - deployment modules for safely deploying your contracts to various networks
  5. Finish the setup by installing all the dependencies:

    npm install
    

    Note

    This last step is needed to set up the hardhat-polkadot plugin. It will install the @parity/hardhat-polkadot package and all its dependencies. In the future, the plugin will handle this automatically.

Compiling Your Contract

The plugin will compile your Solidity contracts for Solidity versions 0.8.0 and higher to be PolkaVM compatible. When compiling your contract, there are two ways to configure your compilation process:

  • npm compiler - uses library @parity/resolc for simplicity and ease of use
  • Binary compiler - uses your local resolc binary directly for more control and configuration options

To compile your project, follow these instructions:

  1. Modify your Hardhat configuration file to specify which compilation process you will be using and activate the polkavm flag in the Hardhat network:

    hardhat.config.js
    require('@nomicfoundation/hardhat-toolbox');
    
    require('@parity/hardhat-polkadot');
    
    /** @type import('hardhat/config').HardhatUserConfig */
    module.exports = {
      solidity: '0.8.28',
      // npm Compiler
      resolc: {
        version: '1.5.2',
        compilerSource: 'npm',
        settings: {
          optimizer: {
            enabled: true,
            parameters: 'z',
            fallbackOz: true,
            runs: 200,
          },
        },
      },
      networks: {
        hardhat: {
          polkavm: true,
        },
      },
    };
    
    hardhat.config.js
    require('@nomicfoundation/hardhat-toolbox');
    
    require('@parity/hardhat-polkadot');
    
    /** @type import('hardhat/config').HardhatUserConfig */
    module.exports = {
      solidity: '0.8.28',
      // Binary Compiler
      resolc: {
        compilerSource: 'binary',
        settings: {
          optimizer: {
            enabled: true,
            parameters: 'z',
            fallbackOz: true,
            runs: 200,
          },
          evmVersion: 'istanbul',
          compilerPath: 'INSERT_PATH_TO_RESOLC_COMPILER',
          standardJson: true,
        },
      },
      networks: {
        hardhat: {
          polkavm: true,
        },
      },
    };
    

    For the binary configuration, replace INSERT_PATH_TO_RESOLC_COMPILER with the proper path to the binary. To obtain the binary, check the releases section of the resolc compiler, and download the latest version.

    The optimizer settings use the default values in the examples above. You can change them according to your project needs.

  2. Compile the contract with Hardhat:

    npx hardhat compile
    
  3. After successful compilation, you'll see the artifacts generated in the artifacts-pvm directory:

    ls artifacts-pvm/contracts/*.sol/
    

    You should see JSON files containing the contract ABI and bytecode of the contracts you compiled.

Testing Your Contract

When testing your contract, be aware that @nomicfoundation/hardhat-toolbox/network-helpers is not fully compatible with Polkadot Hub's available RPCs. Specifically, Hardhat-only helpers like time and loadFixture may not work due to missing RPC calls in the node. For more details, refer to the Compatibility section in the hardhat-revive docs. You should avoid using helpers like time and loadFixture when writing tests.

To run your test:

  1. Update the hardhat.config.js file to specify the path of the local node and the ETH-RPC adapter:

    hardhat.config.js
    require('@nomicfoundation/hardhat-toolbox');
    
    require('@parity/hardhat-polkadot');
    
    /** @type import('hardhat/config').HardhatUserConfig */
    module.exports = {
      solidity: '0.8.28',
      // npm Compiler
      resolc: {
        version: '1.5.2',
        compilerSource: 'npm',
        settings: {
          optimizer: {
            enabled: true,
            parameters: 'z',
            fallbackOz: true,
            runs: 200,
          },
        },
      },
      networks: {
        hardhat: {
          polkavm: true,
          nodeConfig: {
            nodeBinaryPath: 'INSERT_PATH_TO_SUBSTRATE_NODE',
            rpcPort: 8000,
            dev: true,
          },
          adapterConfig: {
            adapterBinaryPath: 'INSERT_PATH_TO_ETH_RPC_ADAPTER',
            dev: true,
          },
        },
      },
    };
    
    hardhat.config.js
    require('@nomicfoundation/hardhat-toolbox');
    
    require('@parity/hardhat-polkadot');
    
    /** @type import('hardhat/config').HardhatUserConfig */
    module.exports = {
      solidity: '0.8.28',
      // Binary Compiler
      resolc: {
        compilerSource: 'binary',
        settings: {
          optimizer: {
            enabled: true,
            parameters: 'z',
            fallbackOz: true,
            runs: 200,
          },
          evmVersion: 'istanbul',
          compilerPath: 'INSERT_PATH_TO_RESOLC_COMPILER',
          standardJson: true,
        },
      },
      networks: {
        hardhat: {
          polkavm: true,
          nodeConfig: {
            nodeBinaryPath: 'INSERT_PATH_TO_SUBSTRATE_NODE',
            rpcPort: 8000,
            dev: true,
          },
          adapterConfig: {
            adapterBinaryPath: 'INSERT_PATH_TO_ETH_RPC_ADAPTER',
            dev: true,
          },
        },
      },
    };
    

    Ensure to replace INSERT_PATH_TO_SUBSTRATE_NODE and INSERT_PATH_TO_ETH_RPC_ADAPTER with the proper paths to the compiled binaries. To obtain these binaries, check the Installation section on the Local Development Node page.

    For example, if you cloned the polkadot-sdk repository to your home directory, the paths might look like:

    nodeBinaryPath: '/home/username/polkadot-sdk/target/release/substrate-node',
    adapterBinaryPath: '/home/username/polkadot-sdk/target/release/eth-rpc',
    
  2. Execute the following command:

    npx hardhat test
    

Deploying with a Local Node

Before deploying to a live network, you can deploy your contract to a local node using Ignition modules:

Contract Size Limitation in Testing Environment

When deploying large contracts, you might encounter: Error: the initcode size of this transaction is too large.

This limitation is imposed by Hardhat's client-side checks, not by PolkaVM itself. As a workaround, you can use a direct JsonRpcProvider:

const { JsonRpcProvider } = require('ethers');
const provider = new JsonRpcProvider('http://localhost:8545');
const wallet = new ethers.Wallet('INSERT_PRIVATE_KEY', provider);

// Deploy using direct provider instead of Hardhat's wrapper
const ContractFactory = new ethers.ContractFactory(
  contractABI,
  contractBytecode,
  wallet,
);
const contract = await ContractFactory.deploy(...constructorArgs);

For more details, see this GitHub issue.

  1. Update the Hardhat configuration file to add the local network as a target for local deployment:

    hardhat.config.js
    require('@nomicfoundation/hardhat-toolbox');
    
    require('@parity/hardhat-polkadot');
    
    /** @type import('hardhat/config').HardhatUserConfig */
    module.exports = {
      solidity: '0.8.28',
      // npm Compiler
      resolc: {
        version: '1.5.2',
        compilerSource: 'npm',
        settings: {
          optimizer: {
            enabled: true,
            parameters: 'z',
            fallbackOz: true,
            runs: 200,
          },
        },
      },
      networks: {
        hardhat: {
          polkavm: true,
          nodeConfig: {
            nodeBinaryPath: 'INSERT_PATH_TO_SUBSTRATE_NODE',
            rpcPort: 8000,
            dev: true,
          },
          adapterConfig: {
            adapterBinaryPath: 'INSERT_PATH_TO_ETH_RPC_ADAPTER',
            dev: true,
          },
        },
        localNode: {
          polkavm: true,
          url: `http://127.0.0.1:8545`,
        },
      },
    };
    
    hardhat.config.js
    require('@nomicfoundation/hardhat-toolbox');
    
    require('@parity/hardhat-polkadot');
    
    /** @type import('hardhat/config').HardhatUserConfig */
    module.exports = {
      solidity: '0.8.28',
      // Binary Compiler
      resolc: {
        compilerSource: 'binary',
        settings: {
          optimizer: {
            enabled: true,
            parameters: 'z',
            fallbackOz: true,
            runs: 200,
          },
          evmVersion: 'istanbul',
          compilerPath: 'INSERT_PATH_TO_RESOLC_COMPILER',
          standardJson: true,
        },
      },
      networks: {
        hardhat: {
          polkavm: true,
          nodeConfig: {
            nodeBinaryPath: 'INSERT_PATH_TO_SUBSTRATE_NODE',
            rpcPort: 8000,
            dev: true,
          },
          adapterConfig: {
            adapterBinaryPath: 'INSERT_PATH_TO_ETH_RPC_ADAPTER',
            dev: true,
          },
        },
        localNode: {
          polkavm: true,
          url: `http://127.0.0.1:8545`,
        },
      },
    };
    
  2. Start a local node:

    npx hardhat node
    

    This command will spawn a local substrate node along with the ETH-RPC adapter.

  3. In a new terminal window, deploy the contract using Ignition:

    npx hardhat ignition deploy ./ignition/modules/MyToken.js --network localNode
    

Deploying to a Live Network

After testing your contract locally, you can deploy it to a live network. This guide will use Westend Hub as the target network. Here's how to configure and deploy:

  1. Fund your deployment account with enough tokens to cover gas fees. In this case, the needed tokens are WND (on Westend). You can use the Polkadot faucet to obtain testing tokens.

  2. Export your private key and save it in a .env file:

    PRIVATE_KEY="INSERT_PRIVATE_KEY"
    

    Replace INSERT_PRIVATE_KEY with your actual private key. For further details on private key exportation, refer to the article How to export an account's private key.

    Warning

    Never reveal your private key. Be sure you add the .env file to your .gitignore file.

  3. Install the dotenv package to load the private key into your Hardhat configuration:

    npm install --save-dev dotenv
    
  4. Update your config to load it:

    hardhat.config.js
    require('@nomicfoundation/hardhat-toolbox');
    
    require('@parity/hardhat-polkadot');
    
    require('dotenv').config();
    
    /** @type import('hardhat/config').HardhatUserConfig */
    module.exports = {
      // The rest remains the same...
    };
    
  5. Update your Hardhat configuration file with network settings for the Polkadot network you want to target:

    hardhat.config.js
    require('@nomicfoundation/hardhat-toolbox');
    
    require('@parity/hardhat-polkadot');
    
    require('dotenv').config();
    
    /** @type import('hardhat/config').HardhatUserConfig */
    module.exports = {
      solidity: '0.8.28',
      // npm Compiler
      resolc: {
        version: '1.5.2',
        compilerSource: 'npm',
        settings: {
          optimizer: {
            enabled: true,
            parameters: 'z',
            fallbackOz: true,
            runs: 200,
          },
        },
      },
    
      networks: {
        hardhat: {
          polkavm: true,
          nodeConfig: {
            nodeBinaryPath: 'INSERT_PATH_TO_SUBSTRATE_NODE',
            rpcPort: 8000,
            dev: true,
          },
          adapterConfig: {
            adapterBinaryPath: 'INSERT_PATH_TO_ETH_RPC_ADAPTER',
            dev: true,
          },
        },
        localNode: {
          polkavm: true,
          url: `http://127.0.0.1:8545`,
        },
        westendHub: {
          polkavm: true,
          url: 'https://westend-asset-hub-eth-rpc.polkadot.io',
          accounts: [process.env.PRIVATE_KEY],
        },
      },
    };
    
    hardhat.config.js
    require('@nomicfoundation/hardhat-toolbox');
    
    require('@parity/hardhat-polkadot');
    
    require('dotenv').config();
    
    require('@parity/hardhat-polkadot');
    
    /** @type import('hardhat/config').HardhatUserConfig */
    module.exports = {
      solidity: '0.8.28',
    
      // Binary Compiler
      resolc: {
        compilerSource: 'binary',
        settings: {
          optimizer: {
            enabled: true,
            parameters: 'z',
            fallbackOz: true,
            runs: 200,
          },
          evmVersion: 'istanbul',
          compilerPath: 'INSERT_PATH_TO_RESOLC_COMPILER',
          standardJson: true,
        },
      },
      networks: {
        hardhat: {
          polkavm: true,
          nodeConfig: {
            nodeBinaryPath: 'INSERT_PATH_TO_SUBSTRATE_NODE',
            rpcPort: 8000,
            dev: true,
          },
          adapterConfig: {
            adapterBinaryPath: 'INSERT_PATH_TO_ETH_RPC_ADAPTER',
            dev: true,
          },
        },
        localNode: {
          polkavm: true,
          url: `http://127.0.0.1:8545`,
        },
        westendHub: {
          polkavm: true,
          url: 'https://westend-asset-hub-eth-rpc.polkadot.io',
          accounts: [process.env.PRIVATE_KEY],
        },
      },
    };
    
  6. Deploy your contract using Ignition:

    npx hardhat ignition deploy ./ignition/modules/MyToken.js --network westendHub
    

Interacting with Your Contract

Once deployed, you can create a script to interact with your contract. To do so, create a file called scripts/interact.js and add some logic to interact with the contract.

For example, for the default MyToken.sol contract, you can use the following file that connects to the contract at its address and retrieves the unlockTime, which represents when funds can be withdrawn. The script converts this timestamp into a readable date and logs it. It then checks the contract's balance and displays it. Finally, it attempts to call the withdrawal function on the contract, but it catches and logs the error message if the withdrawal is not yet allowed (e.g., before unlockTime).

interact.js
const hre = require('hardhat');

async function main() {
  // Get the contract factory
  const MyToken = await hre.ethers.getContractFactory('MyToken');

  // Replace with your deployed contract address
  const contractAddress = 'INSERT_CONTRACT_ADDRESS';

  // Attach to existing contract
  const token = await MyToken.attach(contractAddress);

  // Get signers
  const [deployer] = await hre.ethers.getSigners();

  // Read contract state
  const name = await token.name();
  const symbol = await token.symbol();
  const totalSupply = await token.totalSupply();
  const balance = await token.balanceOf(deployer.address);

  console.log(`Token: ${name} (${symbol})`);
  console.log(
    `Total Supply: ${hre.ethers.formatUnits(totalSupply, 18)} tokens`,
  );
  console.log(
    `Deployer Balance: ${hre.ethers.formatUnits(balance, 18)} tokens`,
  );
}

main().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

Run your interaction script:

npx hardhat run scripts/interact.js --network westendHub

Where to Go Next

Hardhat provides a powerful environment for developing, testing, and deploying smart contracts on Polkadot Hub. Its plugin architecture allows seamless integration with PolkaVM through the hardhat-resolc and hardhat-revive-node plugins.

Explore more about smart contracts through these resources:

  • Guide Smart Contracts on Polkadot


    Dive into advanced smart contract concepts.

    Get Started

  • External Hardhat Documentation


    Learn more about Hardhat's advanced features and best practices.

    Get Started

  • External OpenZeppelin Contracts


    Test your skills by deploying contracts with prebuilt templates.

    Get Started

Last update: May 29, 2025
| Created: May 29, 2025