The Tenderly/vnet-github-action
automates provisioning of Virtual TestNets for smart contract CI/CD pipelines. Virtual TestNets are testing and staging infrastructure with built Mainnet State Sync, Unlimited Faucet, Debugger, and Public Block Explorer
This action creates a new Virtual TestNet from your configuration and exposes its RPC URLs through environment variables, enabling automated testing and staging environments for your protocols.
This action enables:
- Continuous Integration with Hardhat: Run your Hardhat tests against a forked network and use Tenderly debugger and Simulator to fix issues
- Protocol Staging with Hardhat and Foundry: Deploy and stage your protocols in an isolated, mainnet-like environment
- Contract Staging with Hardhat and Foundry: Use dedicated Virtual TestNets as staging environments for contract development
This example shows how to fork Ethereum mainnet with a custom chain ID, enable the Public Block Explorer, and keep the testnet state in sync with mainnet.
After the step Setup Virtual TestNet
you can access the RPC URL from TENDERLY_TESTNET_ID
, TENDERLY_ADMIN_RPC_URL
, and TENDERLY_FOUNDRY_VERIFICATION_URL
.
- Get your Tenderly access key and place it in Github Action Secrets under
TENDERLY_ACCESS_KEY
. - Place the project name and account name in Github Action environment variables under
TENDERLY_PROJECT_NAME
andTENDERLY_ACCOUNT_NAME
respectively. - Create the file
.github/workflows/ci.yaml
and use the starter action configuration below. - Commit, push, and check your action execution.
name: Smart Contract CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Virtual TestNet
uses: Tenderly/[email protected]
with:
mode: CI
access_key: ${{ secrets.TENDERLY_ACCESS_KEY }}
project_name: ${{ vars.TENDERLY_PROJECT_NAME }}
account_name: ${{ vars.TENDERLY_ACCOUNT_NAME }}
testnet_name: 'CI Test Network'
network_id: 1
chain_id: 73571 # Recommended: prefix with 7357 for unique identification to avoid transaction replay attacks
public_explorer: true
verification_visibility: 'src' # Options: 'abi', 'src', 'bytecode'
state_sync: true
Name | Required | Default | Description |
---|---|---|---|
access_key |
Yes | - | Tenderly API Access Key |
project_name |
Yes | - | Tenderly Project Name |
account_name |
Yes | - | Tenderly Account Name |
testnet_name |
No | 'CI Virtual TestNet' | Display name for the Virtual TestNet |
network_id |
Yes | 1 | Network ID to fork (e.g., 1 for Ethereum mainnet) - integer |
chain_id |
No | - | Custom chain ID for Virtual TestNet (Recommended: prefix with 7357) - integer |
block_number |
No | 'latest' | Block number to fork from (must be a hex string, e.g., '0x1234567') |
public_explorer |
No | false | Enable block explorer for the Virtual TestNet |
verification_visibility |
No | 'bytecode' | Contract verification type ('abi', 'src', or 'bytecode') |
state_sync |
No | false | Enable state synchronization with forked network |
mode |
No | 'CI' | Action mode ('CI' or 'CD') - CI cleans up after completion, CD preserves the Virtual TestNet |
The action exports several environment variables:
Variable | Description |
---|---|
TENDERLY_TESTNET_ID |
The ID of the created Virtual TestNet |
TENDERLY_ADMIN_RPC_URL |
Admin RPC endpoint URL |
TENDERLY_PUBLIC_RPC_URL |
Public RPC endpoint URL |
TENDERLY_FOUNDRY_VERIFICATION_URL |
URL for Foundry contract verification |
TENDERLY_EXPLORER_URL |
Block explorer URL for the Virtual TestNet |
The following examples demonstrate how to integrate Tenderly Virtual TestNet into your CI/CD pipeline using popular development frameworks. Each example includes both testing and staging deployment stages, with tests running on ephemeral environments and deployments targeting a persistent staging testnet.
This sample configuration will:
test
contracts by deploying them and sending test transactions to an ephemeral Virtual TestNetdeploy
contracts to the staging Virtual TestNet
- Set up your github action using the following starter yaml:
name: Hardhat Pipeline
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '18'
cache: 'npm'
- name: Setup Virtual TestNet
uses: Tenderly/[email protected]
with:
access_key: ${{ secrets.TENDERLY_ACCESS_KEY }}
project_name: ${{ vars.TENDERLY_PROJECT_NAME }}
account_name: ${{ vars.TENDERLY_ACCOUNT_NAME }}
network_id: 1
chain_id: 73571
public_explorer: true
- name: Install dependencies
run: npm install
- name: Run Tests
run: npx hardhat test --network tenderly_ci
deploy:
needs: test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Virtual TestNet
uses: Tenderly/[email protected]
with:
access_key: ${{ secrets.TENDERLY_ACCESS_KEY }}
project_name: ${{ vars.TENDERLY_PROJECT_NAME }}
account_name: ${{ vars.TENDERLY_ACCOUNT_NAME }}
network_id: 1
chain_id: 73571
public_explorer: true
- name: Deploy Contracts
run: npx hardhat run scripts/deploy.js --network tenderly_ci
- Extend your
hardhat.config.js
by adding the following configuration, using theTENDERLY_ADMIN_RPC_URL
andTENDERLY_CHAIN_ID
:
{
networks: {
tenderly_ci: {
url: process.env.TENDERLY_ADMIN_RPC_URL,
chainId: parseInt(process.env.TENDERLY_CHAIN_ID)
}
},
tenderly: {
project: process.env.TENDERLY_PROJECT_NAME,
username: process.env.TENDERLY_ACCOUNT_NAME,
accessKey: process.env.TENDERLY_ACCESS_KEY
}
}
This pipeline runs tests and deploys verified contracts to a Virtual TestNet. Virtual TestNets provide a persistent environment, making them ideal for staging and integration testing.
name: Foundry Pipeline
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
- name: Build and Test
run: |
forge build
forge test -vvv
deploy:
needs: test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Virtual TestNet
uses: Tenderly/[email protected]
with:
access_key: ${{ secrets.TENDERLY_ACCESS_KEY }}
project_name: ${{ vars.TENDERLY_PROJECT_NAME }}
account_name: ${{ vars.TENDERLY_ACCOUNT_NAME }}
network_id: 1
chain_id: 73571
public_explorer: true
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
- name: Foundry Build
run: |
forge --version
forge build --sizes
- name: Deploy Contracts
env:
FOUNDRY_ETH_RPC_URL: ${{ env.TENDERLY_PUBLIC_RPC_URL }}
FOUNDRY_VERIFIER_URL: ${{ env.TENDERLY_FOUNDRY_VERIFICATION_URL }}
run: |
forge script script/Deploy.s.sol \
--rpc-url $FOUNDRY_ETH_RPC_URL \
--verifier-url $FOUNDRY_VERIFIER_URL \
--slow \
--broadcast \
--verify
If you'd like to use specific address for the deployment and verification of contracts on Virtual TestNets you can do so by following these steps:
- Add your private key to GitHub Secrets (through UI or CLI)
- Update your workflow to include wallet funding and deployment:
- name: Fund Wallet
run: |
curl --location ${{ env.TENDERLY_ADMIN_RPC_URL }} \
--header 'Content-Type: application/json' \
--data '{
"jsonrpc": "2.0",
"method": "tenderly_setBalance",
"params": ["${{ secrets.WALLET_ADDRESS }}", "0xDE0B6B3A7640000"],
"id": "1234"
}'
- name: Deploy Contracts
env:
FOUNDRY_ETH_RPC_URL: ${{ env.TENDERLY_PUBLIC_RPC_URL }}
FOUNDRY_VERIFIER_URL: ${{ env.TENDERLY_FOUNDRY_VERIFICATION_URL }}
PRIVATE_KEY: ${{ secrets.PRIVATE_KEY }}
TENDERLY_ACCESS_TOKEN: ${{ secrets.TENDERLY_ACCESS_KEY }}
run: |
forge script script/Deploy.s.sol \
--private-key $PRIVATE_KEY \
--rpc-url $FOUNDRY_ETH_RPC_URL \
--verifier-url $FOUNDRY_VERIFIER_URL \
--etherscan-api-key $TENDERLY_ACCESS_TOKEN \
--slow \
--broadcast \
--verify
Advanced configuration examples demonstrate how to extend the basic Virtual TestNet setup for more complex scenarios, such as multi-network testing and custom chain configurations.
Use matrix strategy to test your contracts across multiple networks in parallel. This is particularly useful for protocols that deploy across multiple chains and need to ensure consistent behavior.
jobs:
test:
strategy:
matrix:
network: ['1', '137', '42161'] # Ethereum, Polygon, Arbitrum
steps:
- uses: Tenderly/[email protected]
with:
network_id: ${{ matrix.network }}
chain_id: ${{ format('7357{0}', matrix.network) }}
Enable debug logs by setting:
env:
DEBUG: '@tenderly/github-action'
- Virtual TestNets are automatically cleaned up after the workflow completes
- Use matrix builds for testing across multiple networks
- Contract verification works automatically. Follow the guides for verification with Hardhat and Foundry.
- Use unique chain IDs when possible (e.g. by prefixing with
7357
) to avoid transaction replay attacks. - If you don't want to use Github UI to add required variables, you can do the following with utilizing Github CLI. Commands that enable you do it are:
TENDERLY_PROJECT_NAME=...
TENDERLY_ACCOUNT_NAME=...
TENDERLY_ACCESS_KEY=...
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}