🤔 Got questions? Schedule an office hours session.
Virtual TestNets
CI/CD
Foundry & Github Actions

Setting up Github Actions with Foundry

Learn how to configure a GitHub Action to set up a Continuous Integration and Continuous Deployment pipeline (CI/CD) with Foundry on Tenderly Virtual TestNets.

Use the Tenderly Virtual TestNet Setup action to enable automated builds that test your contracts. After successful testing, you can stage them for the rest of your team by deploying them to the provisioned Virtual TestNet.

In this guide, you need to complete two stages:

  1. Local setup: Set up Foundry and create and test out the workflow file
  2. Github setup: Set up a GitHub Action, configure GitHub secrets and variables, and test the build

For reference, use this example project:

Tenderly Docs
Continuous integration (CI) and continous deployment (CD) with Virtual TestNets

Local Setup

First, we’ll create a workflow file and necessary settings.

Create the Workflow File

In your foundry project, create a new workflows directory and ci-cd.yaml:

mkdir -p .github/workflows
touch .github/workflows/ci-cd.yaml

Next, paste the following configuration to ci-cd.yaml that sets up two jobs:

  • test job to run Foundry tests
  • deploy job to deploy contracts to a staging Virtual TestNet

This sample workflow file does several things:

  • Uses Tenderly/vnet-github-action to provision a Virtual TestNet based on given network, with custom chain_id.
  • Updates foundry.toml with correct chain ID (especially important if you’re using a custom chain_id), and passing verification information.
  • Funds the DEPLOYER_WALLET_ADDRESS EOA with 100 ETH.
  • Deploys the contracts using the Virt
name: Foundry CI/CD
 
on:
  push:
  pull_request:
  workflow_dispatch:
 
env:
  TENDERLY_ACCESS_KEY: ${{ secrets.TENDERLY_ACCESS_KEY }}
  DEBUG: '@tenderly/github-action'
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
        working-directory: ./examples/foundry
        run: |
          forge build
          forge test -vvv
 
  deploy:
    needs: test
    if: github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          submodules: recursive
 
      - name: Setup Virtual TestNet
        uses: Tenderly/vnet-github-action@V1.0.11
        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
          chain_id: 73571 # custom chain ID
          public_explorer: true
          verification_visibility: 'src'
 
      - name: Install Foundry
        uses: foundry-rs/foundry-toolchain@v1
 
      - name: Foundry Build
        working-directory: ./examples/foundry
        run: |
          sed -e "s|\${TENDERLY_ACCESS_KEY}|$TENDERLY_ACCESS_KEY|g" \
              -e "s|\${TENDERLY_FOUNDRY_VERIFICATION_URL}|$TENDERLY_FOUNDRY_VERIFICATION_URL|g" \
              -e "s/\(unknown_chain[[:space:]]*=[[:space:]]*{[^}]*chain[[:space:]]*=[[:space:]]*\)[0-9][0-9]*/\1$TENDERLY_CHAIN_ID/g" \
              foundry.toml > foundry.toml.tmp && mv foundry.toml.tmp foundry.toml
          forge --version
          forge build --sizes
 
      - name: Fund Wallet
        run: |
          curl --location "${{ env.TENDERLY_ADMIN_RPC_URL }}" \
          --header 'Content-Type: application/json' \
          --data @- << EOF
          {
            "jsonrpc": "2.0",
            "method": "tenderly_setBalance",
            "params": ["${{ secrets.DEPLOYER_WALLET_ADDRESS }}", "0xDE0B6B3A7640000"],
            "id": "1"
          }
          EOF
 
      - name: Deploy Contracts
        working-directory: ./examples/foundry
        run: |
          forge script script/Counter.s.sol \
            --private-key ${{ secrets.DEPLOYER_PRIVATE_KEY }} \
            --rpc-url ${{ env.TENDERLY_PUBLIC_RPC_URL }} \
            --verifier-url ${{ env.TENDERLY_FOUNDRY_VERIFICATION_URL }} \
            --etherscan-api-key $TENDERLY_ACCESS_KEY \
            --slow \
            --broadcast \
            --verify

Configure Foundry

Create or update your foundry.toml configuration file. To benefit from automatic contract verification you must have the unknown_chain entry with key, chain, url.

When using Virtual TestNets from your local machine, replace the chain = 0 with the chain ID of the virtual testnet you connected to.

foundry.toml
[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" }

Prepare Environment Variables

For local testing, set up an .env file with the following variables:

# Access parameters
TENDERLY_ACCESS_KEY=...
TENDERLY_PROJECT_NAME=...
TENDERLY_ACCOUNT_NAME=...

# Deployment parameters
DEPLOYER_PRIVATE_KEY=...
DEPLOYER_WALLET_ADDRESS=...

Required variables:

  • TENDERLY_ACCESS_KEY: Your Tenderly access key
  • TENDERLY_ACCOUNT_NAME and TENDERLY_PROJECT_NAME: Your account and project names
  • DEPLOYER_PRIVATE_KEY: Private key for the deployment wallet
  • DEPLOYER_WALLET_ADDRESS: Address corresponding to the private key

Test Locally

To test your setup locally:

  1. Build and test the contracts:
source .env
forge build
forge test
  1. Fund the deployer wallet and deploy the contracts
curl  --location "${{ env.TENDERLY_ADMIN_RPC_URL }}" \
      --header 'Content-Type: application/json' \
      --data @- << EOF
      {
        "jsonrpc": "2.0",
        "method": "tenderly_setBalance",
        "params": ["${{ secrets.DEPLOYER_WALLET_ADDRESS }}", "0xDE0B6B3A7640000"],
        "id": "1"
      }
      EOF
 
forge script script/Counter.s.sol \
      --private-key ${{ secrets.DEPLOYER_PRIVATE_KEY }} \
      --rpc-url ${{ env.TENDERLY_PUBLIC_RPC_URL }} \
      --verifier-url ${{ env.TENDERLY_FOUNDRY_VERIFICATION_URL }} \
      --etherscan-api-key $TENDERLY_ACCESS_KEY \
      --slow \
      --broadcast \
      --verify

Test the Github Action Locally

You can test your GitHub Action locally using Act without pushing to Github.

Use the .env file to populate Github secrets and environment variables as follows:

act --secret-file .env --var-file .env

Github Setup

After confirming your local setup works, configure your GitHub repository.

Configure Secrets and Variables

Set up the following GitHub secrets and variables:

Secrets:

  • TENDERLY_ACCESS_KEY: the Tenderly Access Key
  • DEPLOYER_PRIVATE_KEY: the private key for the deployer EOA

Variables:

  • TENDERLY_PROJECT_NAME: the Tenderly project name
  • TENDERLY_ACCOUNT_NAME: the project’s owner’s name
  • DEPLOYER_WALLET_ADDRESS: the deployer EOA

You 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}

Push the Updates

Finally, commit and push your changes:

git add .
git commit -m "Add CI/CD workflow for Foundry"
git push