deploy_contract_with_forge

Documentation for eth_defi.foundry.forge.deploy_contract_with_forge function.

deploy_contract_with_forge(web3, project_folder, contract_file, contract_name, deployer, constructor_args=None, etherscan_api_key=None, verifier=None, verifier_url=None, register_for_tracing=True, timeout=240, wait_for_block_confirmations=0, verify_delay=20, verify_retries=9, verbose=False, contract_file_out=None, forge_libraries=None, cache_dir=None, deploy_retries=1)

Deploy and verify smart contract with Forge.

  • The smart contracts must be developed with Foundry tool chain and its forge command

  • Uses Forge to verify the contract on Etherscan

  • For normal use deploy_contract() is much easier

Example:

guard, tx_hash = deploy_contract_with_forge(
    web3,
    CONTRACTS_ROOT / "guard",  # Foundry projec path
    "GuardV0.sol",  # src/GuardV0.sol
    f"GuardV0",  # GuardV0 is the smart contract name
    deployer,  # Local account with a private key we use for the deployment
    etherscan_api_key=etherscan_api_key,  # Etherscan API key we use for the verification
)
logger.info("GuardV0 is %s deployed at %s", guard.address, tx_hash.hex())

# Test the deployed contract
assert guard.functions.getInternalVersion().call() == 1

Assumes standard Foundry project layout with foundry.toml, src and out.

Known forge issues with “contract was not deployed”

Forge’s forge create has a known issue where all PendingTransactionError variants (receipt polling timeout, transaction dropped from mempool, RPC failure) are silently converted into a single generic "contract was not deployed" message in crates/forge/src/cmd/create.rs:

impl From<PendingTransactionError> for ContractDeploymentError {
    fn from(_err: PendingTransactionError) -> Self {
        Self::ContractNotDeployed  // original error discarded
    }
}

On load-balanced RPCs (e.g. drpc.live), this is frequently a false positive: the transaction was mined but forge polled a different backend node that didn’t have the receipt yet. Foundry #1362 reported ~80% failure rate on testnets. Forge does not print the transaction hash on failure, so the deployer address + nonce are needed to look up the tx on a block explorer.

This function handles the issue with two mechanisms:

  1. False-positive recovery: after a failure, checks the on-chain nonce and scans recent blocks to detect if the contract was actually deployed (see _try_recover_deployment()).

  2. Retry with nonce re-sync: if recovery fails and deploy_retries > 1, re-syncs the nonce from the chain and retries the deployment.

Other relevant Foundry issues:

  • #877 — “forge create sometimes takes two invocations”

  • #13352 — open issue to improve UX for dropped transactions

  • #1803--gas-estimate-multiplier not available for forge create (only forge script)

RPC URL selection and L2 sequencers

forge create requires a full RPC that supports both reads (eth_chainId, eth_gasPrice, eth_getTransactionReceipt) and writes (eth_sendRawTransaction). This rules out L2 sequencer endpoints:

  • Arbitrum sequencers (e.g. arb1-sequencer.arbitrum.io/rpc) are write-only — they only accept eth_sendRawTransaction. Forge will fail immediately because it calls eth_chainId first.

  • OP Stack sequencers (Base, Optimism) run a full op-geth but may return 403 Forbidden on read calls under load.

For forge deployments, prefer the chain’s official single-endpoint public RPC over a load-balanced aggregator like drpc.live. These route to one backend, avoiding the receipt-polling inconsistency that triggers foundry#1362:

  • Arbitrum Sepolia: https://sepolia-rollup.arbitrum.io/rpc

  • Base Sepolia: https://sepolia.base.org

  • Arbitrum One: https://arb1.arbitrum.io/rpc

  • Base: https://mainnet.base.org

See eth_defi.chain.SEQUENCERS for the full mapping of chain IDs to sequencer and public RPC URLs.

When using MultiProviderWeb3, this function automatically selects the call provider URL (not the mev+ broadcast endpoint) via web3.provider.call_endpoint_uri.

See

Parameters
  • web3 (web3.main.Web3) – Web3 instance

  • deployer (Union[eth_defi.hotwallet.HotWallet, eth_account.signers.local.LocalAccount]) –

    Deployer tracked as a hot wallet.

    We need to be able to manually track the nonce across multiple contract deployments.

  • project_folder (pathlib.Path) – Foundry project with foundry.toml in the root.

  • contract_file (Union[pathlib.Path, str]) –

    Contract path relative to the project folder.

    E.g. TermsOfService.sol.

  • contract_name (str) –

    The smart contract name within the file.

    E.g. TermsOfService.

  • constructor_args (Optional[list[str]]) –

    Other arguments to pass to the contract’s constructor.

    Need to be able to stringify these for forge.

  • etherscan_api_key (Optional[str]) –

    API key for Etherscan-compatible verification services.

    Required when using verifier="etherscan" or verifier="oklink".

    Not needed for Blockscout or Sourcify.

    E.g. 3F3H8…..

  • verifier (Optional[Literal['etherscan', 'blockscout', 'sourcify', 'oklink']]) –

    The contract verification provider to use.

    Supported values:

    • "etherscan": Etherscan and compatible explorers (requires API key)

    • "blockscout": Blockscout explorers (requires verifier_url)

    • "sourcify": Sourcify verification (no API key required)

    • "oklink": OKLink explorer (requires API key)

    If None but etherscan_api_key is provided, defaults to "etherscan" for backward compatibility.

  • verifier_url (Optional[str]) –

    Custom verifier URL for Blockscout or other custom verification endpoints.

    Required when verifier="blockscout".

    Example: "https://base.blockscout.com/api/"

  • register_for_tracing

    Make the symbolic contract information available on web3 instance.

    See get_contract_registry()

  • wait_for_block_confirmations – Currently not used.

  • verbose – Try to be extra verbose with Forge output to pin point errors

  • forge_libraries (Optional[dict[str, str]]) –

    Pre-deployed library addresses for --libraries flag.

    Maps "source_path:LibraryName" to deployed address. E.g. {"src/lib/CowSwapLib.sol:CowSwapLib": "0x000..."}.

    Use eth_defi.deploy.build_guard_forge_libraries() to build this mapping for guard contracts.

  • cache_dir (Optional[pathlib.Path]) – Isolated directory for forge’s --cache-path and --out flags. When set, forge writes compilation cache and ABI artifacts here instead of in project_folder. This allows multiple concurrent forge processes to share the same source tree without lock contention.

  • deploy_retries (int) –

    Number of attempts when forge reports "contract was not deployed".

    On unreliable testnet RPCs (e.g. drpc.live load-balanced Base Sepolia), forge may fail to confirm the deploy transaction. Setting this > 1 will re-sync the nonce and retry. Default: 1 (no retries).

    Safety: values > 1 are only allowed on testnets (Anvil or chain IDs listed in eth_defi.chain.TESTNET_CHAIN_IDS). A ValueError is raised if retries are requested on mainnet.

  • contract_file_out (Optional[Union[pathlib.Path, str]]) –

Raises

ForgeFailed

In the case we could not deploy the contract.

  • Running forge failed

  • Transaction could not be confirmed

Returns

Contract and deployment tx hash.

Return type

Tuple[web3.contract.contract.Contract, hexbytes.main.HexBytes]