Skip to main content

Overview

Foundry is a blazing fast, portable, and modular toolkit for Ethereum application development written in Rust. While Tenderly CLI doesn’t have a dedicated Foundry provider like Hardhat or Truffle, you can still verify and monitor your Foundry contracts on Tenderly using the CLI’s verification commands.
For the most up-to-date Foundry verification workflows, refer to the Foundry verification guide in the official Tenderly documentation.

Prerequisites

Before integrating Tenderly with your Foundry project:
  • Have Foundry installed (forge, cast, anvil)
  • Install and authenticate Tenderly CLI
  • Have an active Tenderly account
  • Contracts deployed to a supported network

Installation

1

Install Foundry

If you haven’t installed Foundry yet:
curl -L https://foundry.paradigm.xyz | bash
foundryup
Verify the installation:
forge --version
cast --version
anvil --version
2

Install Tenderly CLI

Install the Tenderly CLI for your operating system:
Using Homebrew:
brew tap tenderly/tenderly
brew install tenderly
Or using cURL:
curl https://raw.githubusercontent.com/Tenderly/tenderly-cli/master/scripts/install-macos.sh | sh
3

Authenticate with Tenderly

Log in to your Tenderly account:
tenderly login
You can also authenticate using an access key:
tenderly login --authentication-method access-key --access-key YOUR_KEY
4

Initialize Tenderly Project

Navigate to your Foundry project and initialize Tenderly:
cd /path/to/your/foundry/project
tenderly init
This creates a tenderly.yaml configuration file.

Foundry Project Structure

A typical Foundry project structure:
my-foundry-project/
├── src/                     # Smart contract source files
│   └── MyContract.sol
├── test/                    # Test files
│   └── MyContract.t.sol
├── script/                  # Deployment scripts
│   └── Deploy.s.sol
├── lib/                     # Dependencies
├── out/                     # Build artifacts (generated by forge)
│   └── MyContract.sol/
│       └── MyContract.json
├── foundry.toml            # Foundry configuration
└── tenderly.yaml           # Tenderly configuration

Foundry Configuration

Configure your foundry.toml with compiler settings:
foundry.toml
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
solc = "0.8.19"
optimizer = true
optimizer_runs = 200

[rpc_endpoints]
mainnet = "${MAINNET_RPC_URL}"
sepolia = "${SEPOLIA_RPC_URL}"

[etherscan]
mainnet = { key = "${ETHERSCAN_API_KEY}" }
sepolia = { key = "${ETHERSCAN_API_KEY}" }

Contract Development Workflow

1

Write Your Smart Contracts

Create your contracts in the src/ directory:
src/MyToken.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract MyToken is ERC20 {
    constructor(uint256 initialSupply) ERC20("MyToken", "MTK") {
        _mint(msg.sender, initialSupply);
    }

    function mint(address to, uint256 amount) external {
        _mint(to, amount);
    }
}
2

Build Your Contracts

Compile your contracts using Forge:
forge build
This generates build artifacts in the out/ directory.
3

Write Deployment Scripts

Create deployment scripts in the script/ directory:
script/Deploy.s.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "forge-std/Script.sol";
import "../src/MyToken.sol";

contract DeployScript is Script {
    function run() external {
        uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
        vm.startBroadcast(deployerPrivateKey);

        // Deploy with 1 million tokens
        MyToken token = new MyToken(1_000_000 * 10**18);

        console.log("MyToken deployed to:", address(token));

        vm.stopBroadcast();
    }
}
4

Deploy Your Contracts

Deploy using Forge script:
# Start local node in separate terminal
anvil

# Deploy to local node
forge script script/Deploy.s.sol:DeployScript \
  --rpc-url http://localhost:8545 \
  --broadcast

Verifying Contracts on Tenderly

Since Foundry doesn’t have direct CLI integration like Hardhat, verification requires manual setup:

Method 1: Using Tenderly Dashboard

1

Get Contract Information

After deployment, note your contract address and network:
# Contract address from deployment output
export CONTRACT_ADDRESS="0x..."
export NETWORK_ID="1"  # 1 for mainnet, 11155111 for sepolia
2

Prepare Contract Source

Flatten your contract to include all dependencies:
forge flatten src/MyToken.sol > MyToken_flat.sol
3

Verify via Dashboard

  1. Go to Tenderly Dashboard
  2. Navigate to your project
  3. Click “Contracts” → “Add Contract”
  4. Enter contract address and network
  5. Upload the flattened source code
  6. Provide compiler version and settings from foundry.toml

Method 2: Using Tenderly API

For automated verification, use the Tenderly API directly:
script/verify-tenderly.sh
#!/bin/bash

CONTRACT_ADDRESS="0x..."
NETWORK_ID="1"
TENDERLY_ACCESS_KEY="your-access-key"
TENDERLY_ACCOUNT="your-account"
TENDERLY_PROJECT="your-project"

# Get contract source
SOURCE=$(forge flatten src/MyToken.sol)

# Get compiler version
SOLC_VERSION=$(grep "solc =" foundry.toml | cut -d'"' -f2)

# Verify contract
curl -X POST "https://api.tenderly.co/api/v1/account/${TENDERLY_ACCOUNT}/project/${TENDERLY_PROJECT}/contracts" \
  -H "X-Access-Key: ${TENDERLY_ACCESS_KEY}" \
  -H "Content-Type: application/json" \
  -d '{
    "contracts": [{
      "contractName": "MyToken",
      "source": '"${SOURCE}"',
      "sourcePath": "src/MyToken.sol",
      "networks": {
        "'${NETWORK_ID}'": {
          "address": "'${CONTRACT_ADDRESS}'"
        }
      },
      "compiler": {
        "version": "'${SOLC_VERSION}'"
      }
    }]
  }'

Method 3: Export to Hardhat Format

Convert Foundry artifacts to Hardhat-compatible format:
1

Create Conversion Script

scripts/foundry-to-hardhat.js
const fs = require('fs');
const path = require('path');

// Read Foundry artifact
const foundryArtifact = JSON.parse(
  fs.readFileSync('out/MyToken.sol/MyToken.json', 'utf8')
);

// Convert to Hardhat format
const hardhatArtifact = {
  contractName: foundryArtifact.ast.absolutePath.split('/').pop().replace('.sol', ''),
  abi: foundryArtifact.abi,
  bytecode: foundryArtifact.bytecode.object,
  deployedBytecode: foundryArtifact.deployedBytecode.object,
  linkReferences: foundryArtifact.bytecode.linkReferences || {},
  deployedLinkReferences: foundryArtifact.deployedBytecode.linkReferences || {},
  // Add network deployment info
  networks: {
    '1': {  // Network ID
      address: '0x...',  // Your deployed address
      transactionHash: '0x...'  // Deployment tx hash
    }
  }
};

// Create deployments directory
const deploymentsDir = path.join(__dirname, '..', 'deployments');
if (!fs.existsSync(deploymentsDir)) {
  fs.mkdirSync(deploymentsDir);
}

// Write Hardhat-compatible artifact
fs.writeFileSync(
  path.join(deploymentsDir, 'MyToken.json'),
  JSON.stringify(hardhatArtifact, null, 2)
);

console.log('Artifact converted successfully');
2

Run Conversion and Verify

node scripts/foundry-to-hardhat.js
tenderly contracts verify

Tenderly Configuration

Your tenderly.yaml configuration:
tenderly.yaml
account_id: "your-account-id"
project_slug: "my-foundry-project"

Testing with Tenderly Forks

Use Tenderly forks for testing without deploying:
test/MyToken.t.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "forge-std/Test.sol";
import "../src/MyToken.sol";

contract MyTokenTest is Test {
    MyToken token;
    address user = address(0x1);

    function setUp() public {
        // Deploy contract
        token = new MyToken(1_000_000 * 10**18);
    }

    function testMint() public {
        uint256 amount = 100 * 10**18;
        token.mint(user, amount);
        assertEq(token.balanceOf(user), amount);
    }

    function testInitialSupply() public {
        assertEq(token.totalSupply(), 1_000_000 * 10**18);
    }
}
Run tests:
# Run all tests
forge test

# Run with verbosity
forge test -vvv

# Run specific test
forge test --match-test testMint

Best Practices

Keep Artifacts

Don’t delete the out/ directory - it contains essential build artifacts for verification.

Use Environment Variables

Store sensitive data like private keys and API keys in .env files, not in scripts.

Verify on Multiple Platforms

Verify contracts on both Etherscan and Tenderly for maximum transparency.

Test Thoroughly

Use Forge’s comprehensive testing framework before deploying to mainnet.

Useful Forge Commands

# Build contracts
forge build

# Run tests
forge test

# Check gas usage
forge test --gas-report

# Format code
forge fmt

# Get contract size
forge build --sizes

# Flatten contract
forge flatten src/MyToken.sol

# Deploy with verification
forge create src/MyToken.sol:MyToken \
  --rpc-url $RPC_URL \
  --private-key $PRIVATE_KEY \
  --constructor-args 1000000000000000000000000

# Cast commands for interaction
cast call $CONTRACT_ADDRESS "totalSupply()" --rpc-url $RPC_URL
cast send $CONTRACT_ADDRESS "mint(address,uint256)" $RECIPIENT 1000 \
  --rpc-url $RPC_URL --private-key $PRIVATE_KEY

Troubleshooting

Build Artifacts Not Found

If you encounter issues with missing artifacts:
# Clean and rebuild
forge clean
forge build

RPC Connection Issues

If deployment fails with RPC errors:
  1. Check your RPC URL is correct
  2. Ensure you have sufficient ETH for gas
  3. Verify network connectivity
  4. Try increasing gas limit: --gas-limit 5000000

Verification Fails

If manual verification fails:
  • Ensure compiler version matches exactly
  • Check optimizer settings match foundry.toml
  • Verify contract address is correct
  • Confirm contract is deployed on the specified network

Next Steps

Foundry Book

Complete Foundry documentation and guides.

Tenderly Dashboard

Monitor and debug your contracts visually.

Contract Verification

Learn advanced verification techniques on Tenderly.

Testing Guide

Master Forge testing capabilities.

Build docs developers (and LLMs) love