Skip to content
Logo

Scripting

Forge scripts are Solidity files that deploy contracts and execute transactions on-chain. They replace deployment scripts traditionally written in JavaScript.

For sender selection, CREATE2, library linking, nonces, simulation, and resume semantics, see How scripting works.

Script structure

Scripts inherit from Script and implement a run() function:

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

Key elements:

  • Inherit from forge-std/Script.sol
  • Script files end with .s.sol
  • Wrap deployment logic in vm.startBroadcast() / vm.stopBroadcast()

Running scripts

Simulate a deployment (no transactions sent):

$ forge script script/Deploy.s.sol

Broadcast transactions to a network:

$ forge script script/Deploy.s.sol --broadcast --rpc-url $RPC_URL

Using RPC aliases

Define aliases in foundry.toml and pass the alias to --rpc-url:

foundry.toml
[rpc_endpoints]
sepolia = "${SEPOLIA_RPC_URL}"
$ forge script script/Deploy.s.sol --broadcast --rpc-url sepolia

Providing a private key

$ forge script script/Deploy.s.sol --broadcast --rpc-url $RPC_URL --account deployer

See Browser Wallet Signing for the local connection flow, network checks, and a simulation-first deployment workflow.

Broadcasting from a specific address

To broadcast from a specific address:

vm.startBroadcast(deployerAddress);

Or derive the sender from a private key read from the environment:

uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(deployerPrivateKey);

The address overload selects a transaction sender but does not supply a signing key. Provide a matching wallet when broadcasting, or use --unlocked with a node that controls that account. The private-key overload derives the address and adds the key to Forge's script wallets.

Starting a broadcast changes the sender of outgoing calls; it does not change msg.sender in the script's current frame. Pass the intended owner or deployer explicitly in transaction arguments. Inside a contract called directly by the broadcast transaction, msg.sender is the transaction sender.

Overriding the sender nonce

By default the sender's starting nonce is fetched from the RPC endpoint, or set to 1 when no endpoint is configured. Pass --sender-nonce to pin it instead:

$ forge script script/Deploy.s.sol --rpc-url $RPC_URL --sender-nonce 7

The override applies to script execution and transaction generation and is kept even when broadcasting switches to a different sender. Because addresses of contracts deployed with CREATE depend on the sender's nonce, this keeps simulated deployment addresses consistent with the nonce you plan to broadcast from. See forge script for the full option reference.

Verifying deployed contracts

Verify on Etherscan during deployment:

$ forge script script/Deploy.s.sol \
    --broadcast \
    --rpc-url $RPC_URL \
    --verify \
    --etherscan-api-key $ETHERSCAN_API_KEY

Resuming failed broadcasts

If a broadcast fails partway through, resume from where it left off:

$ forge script script/Deploy.s.sol --broadcast --rpc-url $RPC_URL --resume

Multi-chain deployments

Deploy to multiple chains by running the script with different RPC URLs:

$ forge script script/Deploy.s.sol --broadcast --rpc-url $MAINNET_RPC
$ forge script script/Deploy.s.sol --broadcast --rpc-url $ARBITRUM_RPC
$ forge script script/Deploy.s.sol --broadcast --rpc-url $OPTIMISM_RPC

Reading deployment artifacts

Scripts write transaction receipts to broadcast/. Access deployed addresses in subsequent scripts:

function run() public {
    string memory json = vm.readFile("broadcast/Deploy.s.sol/1/run-latest.json");
    address counter = vm.parseJsonAddress(json, ".transactions[0].contractAddress");
}

Script cheatcodes

Scripts have access to all cheatcodes. Common ones for scripting:

// Read environment variables
string memory rpcUrl = vm.envString("RPC_URL");
uint256 privateKey = vm.envUint("PRIVATE_KEY");
 
// Read/write files
string memory config = vm.readFile("config.json");
vm.writeFile("output.txt", "deployed");
 
// Parse JSON
address addr = vm.parseJsonAddress(json, ".address");
 
// Console logging
console.log("Deploying to:", block.chainid);

Dry run

Test a script without sending transactions:

$ forge script script/Deploy.s.sol --rpc-url $RPC_URL

This simulates against the live chain state and shows what would happen.