- 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.
CI/CD setup with hardhat-ignition
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.test job to run hardhat tests using a single network for testingdeploy job to deploy contracts to multiple networks (Mainnet and Base) after successful testingThe
mode argument takes values CI and CD.- The
CDmode keeps the Virtual Environment active and you can work with deployed contracts. - The
CImode pauses the Virtual Environment 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 Environment
uses: tenderly/vnet-github-action@v1.0.14
with:
mode: CI # pauses the Virtual Environment 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 Environment
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
## 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
TENDERLY_ACCESS_KEY: Learn how to get your access keyTENDERLY_ACCOUNT_NAME and TENDERLY_PROJECT_NAME: Follow steps to get your account and project name
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`,
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`,
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;
{
"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 on mainnet fork
npx hardhat test:1
# Deploy to mainnet fork
npx hardhat deploy:1
# Deploy to Base fork
npx hardhat deploy:8453
To test your action locally, you can use Act:
Stage 2: Github Setup
After successful local setup, proceed by configuring Github with environment variables and necessary secrets.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}
Environment Variables Reference
The GitHub Action exposes the following network-specific variables:| Variable | Description |
|---|---|
TENDERLY_TESTNET_ID_{network_id} | Virtual Environment 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 Virtual Environment slug |
BUILD_SLUG | Unique identifier for the current build |