Skip to content
Logo

Deploying Contracts

This guide covers the full deployment workflow: local testing, testnet deployment, mainnet deployment, contract verification, and deterministic addresses with CREATE2.

Local deployment with Anvil

Start a local node

$ anvil

Anvil provides 10 pre-funded accounts for testing. The first account's private key is 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80.

Deploy your contract

$ forge script script/Deploy.s.sol --broadcast --rpc-url http://localhost:8545 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80

Testnet deployment

Create an encrypted keystore

Never use plaintext private keys. Import your key into an encrypted keystore:

$ cast wallet import deployer --interactive

Deploy to a testnet

$ forge script script/Deploy.s.sol --account deployer --broadcast --rpc-url https://sepolia.drpc.org

Verify the contract

$ forge verify-contract $CONTRACT_ADDRESS src/Counter.sol:Counter --chain sepolia

Mainnet deployment

For mainnet, use a hardware wallet:

$ forge script script/Deploy.s.sol --ledger --broadcast --rpc-url https://ethereum.reth.rs/rpc

Or with an encrypted keystore:

$ forge script script/Deploy.s.sol --account deployer --broadcast --rpc-url https://ethereum.reth.rs/rpc

Contract verification

Forge can verify contracts on Etherscan and compatible explorers.

Automatic verification during deployment

$ forge script script/Deploy.s.sol --broadcast --verify --rpc-url https://ethereum.reth.rs/rpc

Manual verification

$ forge verify-contract $CONTRACT_ADDRESS src/Token.sol:Token \
    --chain mainnet \
    --constructor-args $(cast abi-encode "constructor(string,string)" "MyToken" "MTK")

Verification with constructor arguments

If your contract has constructor arguments, encode them:

# For a constructor: constructor(address admin, uint256 supply)
$ forge verify-contract $CONTRACT_ADDRESS src/Token.sol:Token \
    --chain mainnet \
    --constructor-args $(cast abi-encode "constructor(address,uint256)" 0x1234...5678 1000000000000000000000000)

Deterministic addresses with CREATE2

CREATE2 deploys contracts to predictable addresses based on the deployer, salt, and bytecode. See the Deterministic Deployments guide for comprehensive coverage including foundry.toml configuration requirements.

Quick example using CreateX:

script/DeployCreate2.s.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
 
import {Script, console} from "forge-std/Script.sol";
import {Counter} from "../src/Counter.sol";
 
interface ICreateX {
    function deployCreate2(bytes32 salt, bytes memory initCode) external payable returns (address);
}
 
contract DeployCreate2 is Script {
    ICreateX constant CREATEX = ICreateX(0xba5Ed099633D3B313e4D5F7bdc1305d3c28ba5Ed);
 
    function run() public {
        bytes32 salt = keccak256("my-unique-salt-v1");
        bytes memory initCode = type(Counter).creationCode;
 
        vm.startBroadcast();
        address deployed = CREATEX.deployCreate2(salt, initCode);
        vm.stopBroadcast();
 
        console.log("Deployed at:", deployed);
    }
}

Deployment script patterns

Basic deployment script

script/Deploy.s.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
 
import {Script, console} from "forge-std/Script.sol";
import {Counter} from "../src/Counter.sol";
 
contract DeployScript is Script {
    function run() public {
        vm.startBroadcast(); 
        
        Counter counter = new Counter();
        console.log("Counter deployed at:", address(counter));
        
        vm.stopBroadcast(); 
    }
}

Deployment with configuration

script/Deploy.s.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
 
import {Script, console} from "forge-std/Script.sol";
import {Token} from "../src/Token.sol";
 
contract DeployScript is Script {
    function run() public {
        address admin = vm.envAddress("ADMIN_ADDRESS"); 
        uint256 initialSupply = vm.envOr("INITIAL_SUPPLY", uint256(1_000_000 ether)); 
 
        vm.startBroadcast();
        
        Token token = new Token(admin, initialSupply);
        console.log("Token deployed at:", address(token));
        console.log("Admin:", admin);
        console.log("Initial supply:", initialSupply);
        
        vm.stopBroadcast();
    }
}

Verifying deployment

After deployment, verify the contract state:

# Check the deployed bytecode
$ cast code $CONTRACT_ADDRESS --rpc-url https://ethereum.reth.rs/rpc
 
# Call a view function
$ cast call $CONTRACT_ADDRESS "owner()" --rpc-url https://ethereum.reth.rs/rpc
 
# Check contract balance
$ cast balance $CONTRACT_ADDRESS --rpc-url https://ethereum.reth.rs/rpc