Setting up Github Actions with Hardhat
Learn how to configure a GitHub Action to set up a Continuous Integration and Continuous Deployment pipeline (CI/CD) with Hardhat on Tenderly Virtual TestNets.
Use the Tenderly Virtual TestNet Setup to enable automated builds that test and deploy your contracts across multiple networks. After successful testing, you can stage them for the rest of your team by deploying them to provisioned Virtual TestNets.
In this guide, you need to complete two stages:
- Local setup: Set up Hardhat and create and test the workflow file.
- Github setup: Set up a GitHub Action, configure GitHub secrets and variables, and test the build.
For reference, use this example project:
This guide demonstrates a CI setup that relies on Hardhat-ignition. For hardhat-verify
setup, the process and configuration are similar.
Stage 1: Local setup
First, we’ll install necessary dependencies and create a workflow file.
Install dependencies
Make sure you have the following packages installed:
npm install --save-dev @tenderly/hardhat-tenderly @nomicfoundation/hardhat-ignition
Create the workflow file
To set up the GitHub Action, create a new workflow:
mkdir -p .github/workflows
touch ci-cd.yaml
Next, paste the following yaml file that configures two jobs:
test
job to run hardhat tests using a single network for testingdeploy
job to deploy contracts to multiple networks (Mainnet and Base) after successful testing
The mode
argument takes values CI
and CD
.
- The
CD
mode keeps the Virtual TestNet active and you can work with deployed contracts. - The
CI
mode pauses the Virtual TestNet after the step completes. You’ll be able to inspect transactions but won’t be able to send further RPC requests.
name: Hardhat CI/CD Multichain
on: [push, pull_request]
env:
## Needed available as env variables for hardhat.config.js
TENDERLY_PROJECT_NAME: ${{ vars.TENDERLY_PROJECT_NAME }}
TENDERLY_ACCOUNT_NAME: ${{ vars.TENDERLY_ACCOUNT_NAME }}
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Setup Virtual TestNet
uses: tenderly/vnet-github-action@v1.0.14
with:
mode: CI # pauses testnet after deployment
access_key: ${{ secrets.TENDERLY_ACCESS_KEY }}
project_name: ${{ vars.TENDERLY_PROJECT_NAME }}
account_name: ${{ vars.TENDERLY_ACCOUNT_NAME }}
testnet_name: "Testing"
network_id: 1
chain_id_prefix: 7357
public_explorer: true
verification_visibility: 'src'
push_on_complete: true
- name: Install dependencies
run: npm install
working-directory: examples/hardhat-ignition
- name: Run Tests
run: npm run test:1
working-directory: examples/hardhat-ignition
deploy:
needs: test
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Setup Virtual TestNet
uses: tenderly/vnet-github-action@v1.0.14
with:
mode: CD
access_key: ${{ secrets.TENDERLY_ACCESS_KEY }}
project_name: ${{ vars.TENDERLY_PROJECT_NAME }}
account_name: ${{ vars.TENDERLY_ACCOUNT_NAME }}
testnet_name: "Staging"
network_id: |
1
8453
chain_id_prefix: ""
public_explorer: true
verification_visibility: 'src'
push_on_complete: true
- name: Install dependencies
run: npm install
working-directory: examples/hardhat-ignition
- name: Deploy Contracts Mainnet
run: npm run deploy:1 -- --deployment-id deploy-1-${BUILD_SLUG}
working-directory: examples/hardhat-ignition
- name: Deploy Contracts Base
run: npm run deploy:8453 -- --deployment-id deploy-8453-${BUILD_SLUG}
working-directory: examples/hardhat-ignition
Prepare environment variables
For local testing, set up an .env
file using this template:
## Access parameters
TENDERLY_ACCESS_KEY=...
TENDERLY_PROJECT_NAME=...
TENDERLY_ACCOUNT_NAME=...
## Network-specific parameters (populated by the action in CI/CD)
TENDERLY_ADMIN_RPC_URL_1=... # Mainnet RPC
TENDERLY_ADMIN_RPC_URL_8453=... # Base RPC
TENDERLY_CHAIN_ID_1=... # Mainnet chain ID
TENDERLY_CHAIN_ID_8453=... # Base chain ID
Required variables:
TENDERLY_ACCESS_KEY
: Learn how to get your access keyTENDERLY_ACCOUNT_NAME
andTENDERLY_PROJECT_NAME
: Follow steps to get your account and project name
Configure Hardhat
Add the Virtual TestNet configuration to hardhat.config.ts
:
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
import "@tenderly/hardhat-tenderly";
import "@nomicfoundation/hardhat-ignition";
import * as dotenv from 'dotenv';
dotenv.config();
const config: HardhatUserConfig = {
solidity: "0.8.27",
networks: {
mainnet: {
url: process.env.TENDERLY_ADMIN_RPC_URL_1,
chainId: parseInt(process.env.TENDERLY_CHAIN_ID_1 || "1")
},
base: {
url: process.env.TENDERLY_ADMIN_RPC_URL_8453,
chainId: parseInt(process.env.TENDERLY_CHAIN_ID_8453 || "8453")
}
},
etherscan: {
apiKey: {
mainnet: process.env.TENDERLY_ACCESS_KEY!,
base: process.env.TENDERLY_ACCESS_KEY!
},
customChains: [
{
network: "mainnet",
chainId: parseInt(process.env.TENDERLY_CHAIN_ID_1!),
urls: {
apiURL: `${process.env.TENDERLY_ADMIN_RPC_URL_1}/verify/etherscan`,
browserURL: process.env.TENDERLY_ADMIN_RPC_URL_1!
}
},
{
network: "base",
chainId: parseInt(process.env.TENDERLY_CHAIN_ID_8453!),
urls: {
apiURL: `${process.env.TENDERLY_ADMIN_RPC_URL_8453}/verify/etherscan`,
browserURL: process.env.TENDERLY_ADMIN_RPC_URL_8453!
}
}
]
},
tenderly: {
project: process.env.TENDERLY_PROJECT_NAME!,
username: process.env.TENDERLY_ACCOUNT_NAME!,
accessKey: process.env.TENDERLY_ACCESS_KEY!
},
sourcify: {
enabled: false
}
};
export default config;
Add test and deploy scripts
Add the following scripts to your package.json
for multi-network testing and deployment:
{
"scripts": {
"deploy:1": "npx hardhat ignition deploy ./ignition/modules/Counter.js --network mainnet",
"deploy:8453": "npx hardhat ignition deploy ./ignition/modules/Counter.js --network base",
"test:1": "npx hardhat test --network mainnet"
}
}
The deploy commands use the BUILD_SLUG environment variable (provided by the action) to create unique deployment identifiers.
Test the build locally
To test the build locally, run the following commands:
# Test on mainnet fork
npx hardhat test:1
# Deploy to mainnet fork
npx hardhat deploy:1
# Deploy to Base fork
npx hardhat deploy:8453
Test the Github Action locally
To test your action locally, you can use Act:
act --secret-file .env --var-file .env
Stage 2: Github Setup
After successful local setup, proceed by configuring Github with environment variables and necessary secrets.
Configure secrets and environment variables in GitHub
You must configure the following Github variables and secret:
Variables:
TENDERLY_PROJECT_NAME
TENDERLY_ACCOUNT_NAME
Secrets:
TENDERLY_ACCESS_KEY
You can configure these via the GitHub UI or using the gh
command line:
source .env
gh variable set TENDERLY_PROJECT_NAME --body ${TENDERLY_PROJECT_NAME}
gh variable set TENDERLY_ACCOUNT_NAME --body ${TENDERLY_ACCOUNT_NAME}
gh secret set TENDERLY_ACCESS_KEY --body ${TENDERLY_ACCESS_KEY}
Push the updates
To trigger the GitHub Action, push your changes:
git add .
git commit -m "Add CI/CD workflow for multichain deployment"
git push
Check your action
Go to your Github repository and check the Actions tab. You should see:
- Test job running against the mainnet fork
- Deploy job creating two Virtual TestNets (mainnet and Base)
- Deployment artifacts being pushed to the repository
Get the RPC links
To get the RPC links for your deployed contracts:
- Go to Tenderly dashboard > Virtual TestNets
- Find your CD Virtual TestNets (one for mainnet fork, one for Base fork)
- Click on each to get their respective RPC URLs
Environment Variables Reference
The GitHub Action exposes the following network-specific variables:
Variable | Description |
---|---|
TENDERLY_TESTNET_ID_{network_id} | Virtual TestNet UUID |
TENDERLY_ADMIN_RPC_URL_{network_id} | Admin RPC endpoint with cheatcode methods |
TENDERLY_PUBLIC_RPC_URL_{network_id} | Public RPC endpoint |
TENDERLY_CHAIN_ID_{network_id} | Chain ID |
TENDERLY_TESTNET_SLUG_{network_id} | Unique TestNet slug |
BUILD_SLUG | Unique identifier for the current build |
Next steps
Explore other examples: