- CI/CD Integration: Run tests against forked networks and set up staging environments
- Multi-Network Testing: Provision multiple networks in a single step
- Automated Deployments: Deploy and verify contracts with complete deployment logs
- Artifact Collection: Track deployments across networks with structured logs
- Local setup: Set up Foundry to create and test out the workflow file
- GitHub setup: Set up a GitHub Action, configure GitHub secrets and variables, and test the build
Foundry CI/CD setup

Stage 1: Local Setup
First, create a workflow file with the necessary settings.tenderly/vnet-github-action to provision Virtual Environments based on a given network_id (Mainnet and Base), with custom chain_idupdate_foundry_config_and_build to modify foundry.toml with the correct chain ID (especially important if you’re using a custom chain_id), and passes verification information.DEPLOYER_WALLET_ADDRESS EOA with 100 ETH, using set_wallet_balance$BUILD_OUTPUT_FILE_1 and $BUILD_OUTPUT_FILE_8453, respectively, using foundry’s --json optionThe
mode argument for Tenderly/vnet-github-action 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: Foundry CI/CD
on:
push:
pull_request:
env:
TENDERLY_ACCESS_KEY: ${{ secrets.TENDERLY_ACCESS_KEY }}
DEPLOYER_WALLET_ADDRESS: ${{ vars.DEPLOYER_WALLET_ADDRESS }}
DEBUG: tenderly/vnet-github-action@v1.0.14
jobs:
deploy:
runs-on: ubuntu-latest
defaults:
run:
working-directory: ./examples/foundry
permissions:
contents: write
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- 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: 7357
public_explorer: true
verification_visibility: 'src'
push_on_complete: true
- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
- name: Fund Deployer Account on Mainnet
run: |
source ../../fixtures/load-fixtures.sh
update_foundry_config_and_build $TENDERLY_ACCESS_KEY $TENDERLY_FOUNDRY_VERIFICATION_URL_1 $TENDERLY_CHAIN_ID_1
set_wallet_balance $TENDERLY_ADMIN_RPC_URL_1 ${{ vars.DEPLOYER_WALLET_ADDRESS }} $HUNDRED_ETH
- name: Deploy Contracts Mainnet
run: |
forge build --sizes
forge script script/Counter.s.sol \
--private-key ${{ secrets.DEPLOYER_PRIVATE_KEY }} \
--rpc-url ${{ env.TENDERLY_PUBLIC_RPC_URL_1 }} \
--verifier-url ${{ env.TENDERLY_FOUNDRY_VERIFICATION_URL_1 }} \
--etherscan-api-key $TENDERLY_ACCESS_KEY \
--slow \
--broadcast \
--verify \
--json > $BUILD_OUTPUT_FILE_1
- name: Fund Deployer Account on Base
run: |
source ../../fixtures/load-fixtures.sh
update_foundry_config_and_build $TENDERLY_ACCESS_KEY $TENDERLY_FOUNDRY_VERIFICATION_URL_8453 $TENDERLY_CHAIN_ID_8453
set_wallet_balance $TENDERLY_ADMIN_RPC_URL_8453 ${{ vars.DEPLOYER_WALLET_ADDRESS }} $HUNDRED_ETH
- name: Deploy Contracts Base
working-directory: ./examples/foundry
run: |
forge script script/Counter.s.sol \
--private-key ${{ secrets.DEPLOYER_PRIVATE_KEY }} \
--rpc-url ${{ env.TENDERLY_PUBLIC_RPC_URL_8453 }} \
--verifier-url ${{ env.TENDERLY_FOUNDRY_VERIFICATION_URL_8453 }} \
--etherscan-api-key $TENDERLY_ACCESS_KEY \
--slow \
--broadcast \
--verify \
--json > $BUILD_OUTPUT_FILE_8453
Create or update your
foundry.toml configuration file. To use automatic contract verification, you must have the unknown_chain entry with key, chain, and url.When using Virtual Environments from your local machine, replace the `chain = 0` with the chain ID of the Virtual Environment you connected to.
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
cbor_metadata = true
# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options
[etherscan]
## placeholders must be replaced with key ("string"), chain_id (number, non-quoted), and tenderly_foundry_verification_url ("string")
unknown_chain = { key = "$TENDERLY_ACCESS_KEY", chain = 0, url = "$TENDERLY_FOUNDRY_VERIFICATION_URL" }
# Access parameters
TENDERLY_ACCESS_KEY=...
TENDERLY_PROJECT_NAME=...
TENDERLY_ACCOUNT_NAME=...
# Deployment parameters
DEPLOYER_PRIVATE_KEY=...
DEPLOYER_WALLET_ADDRESS=...
TENDERLY_ACCESS_KEY: Your Tenderly access keyTENDERLY_ACCOUNT_NAME and TENDERLY_PROJECT_NAME: Your account and project namesDEPLOYER_PRIVATE_KEY: Private key for the deployment walletDEPLOYER_WALLET_ADDRESS: Address corresponding to the private keyAdd the following
fixtures.sh to your repository to be able to fund accounts and adapt foundry.toml easily.set_wallet_balance function to top up the balance (RPC_URL WALLET_ADDRESS BALANCE_HEX)update_foundry_config_and_build function to replace the placeholders in foundry.tomlHUNDRED_ETH the wei value corresponding to 100 ETH#!/bin/bash
set_wallet_balance() {
if [ "$#" -ne 3 ]; then
echo "Usage: set_wallet_balance RPC_URL WALLET_ADDRESS BALANCE_HEX"
return 1
fi
local rpc_url="$1"
local wallet_address="$2"
local balance_hex="$3"
curl --location "$rpc_url" \
--header 'Content-Type: application/json' \
--data "{
\"jsonrpc\": \"2.0\",
\"method\": \"tenderly_setBalance\",
\"params\": [\"$wallet_address\", \"$balance_hex\"],
\"id\": \"1\"
}"
}
update_foundry_config_and_build() {
if [ "$#" -ne 3 ]; then
echo "Usage: update_foundry_config_and_build ACCESS_KEY VERIFICATION_URL CHAIN_ID"
return 1
fi
local access_key="$1"
local verification_url="$2"
local chain_id="$3"
# Create temporary file and update foundry.toml
sed -e "s|\${TENDERLY_ACCESS_KEY}|$access_key|g" \
-e "s|\${TENDERLY_FOUNDRY_VERIFICATION_URL}|$verification_url|g" \
-e "s/\(unknown_chain[[:space:]]*=[[:space:]]*{[^}]*chain[[:space:]]*=[[:space:]]*\)[0-9][0-9]*/\1$chain_id/g" \
foundry.toml > foundry.toml.tmp && mv foundry.toml.tmp foundry.toml
}
# Export constant
HUNDRED_ETH="0xDE0B6B3A7640000"
export HUNDRED_ETH
TENDERLY_TESTNET_ID_{network_id}TENDERLY_ADMIN_RPC_URL_{network_id}TENDERLY_PUBLIC_RPC_URL_{network_id}TENDERLY_CHAIN_ID_{network_id}TENDERLY_TESTNET_SLUG_{network_id}TENDERLY_FOUNDRY_VERIFICATION_URL_{network_id}BUILD_OUTPUT_FILE_{network_id}You can test your GitHub Action locally using Act without pushing to GitHub.
Stage 2: Github Setup
After confirming your local setup works, configure your GitHub repository using the following steps.TENDERLY_ACCESS_KEY: the Tenderly Access KeyDEPLOYER_PRIVATE_KEY: the private key for the deployer EOATENDERLY_PROJECT_NAME: the Tenderly project nameTENDERLY_ACCOUNT_NAME: the project’s owner’s nameDEPLOYER_WALLET_ADDRESS: the deployer EOAYou can achieve this via the GitHub UI or using the
gh command line:# Load environment variables
source .env
# Set variables
gh variable set TENDERLY_PROJECT_NAME --body ${TENDERLY_PROJECT_NAME}
gh variable set TENDERLY_ACCOUNT_NAME --body ${TENDERLY_ACCOUNT_NAME}
gh variable set DEPLOYER_WALLET_ADDRESS --body ${DEPLOYER_WALLET_ADDRESS}
# Set secrets
gh secret set TENDERLY_ACCESS_KEY --body ${TENDERLY_ACCESS_KEY}
gh secret set DEPLOYER_PRIVATE_KEY --body ${DEPLOYER_PRIVATE_KEY}
Go go your Github repository and check Actions tab. Congrats, you ran your CI/CD workflow with Virtual Environments and Foundry. After
Next steps
- Explore the Foundry example on Github.
- Explore the Hardhat example on Github.
- Use the balance fixtures.