Force Inclusion — Foundry

Force-include a transaction on MegaETH using Foundry (cast or forge) — step-by-step guide for scripted and automated submissions.

Force inclusion submits a transaction directly to the L1 OptimismPortal, bypassing the sequencer. This page covers the scripted path using Foundryarrow-up-right. For the Etherscan UI path, see Force-include a transaction.

Contract addresses

Contract
Chain
Address

OptimismPortal

Ethereum (chain 1)

0x7f82f57F0Dd546519324392e408b01fcC7D709e8

Step-by-step

Your L1 wallet address is preserved as the sender on MegaETH — so the same private key that holds tokens on MegaETH signs the L1 transaction.

1

Set environment variables

export PRIVATE_KEY=0x...
export TOKEN=...        # ERC-20 contract address on MegaETH
export RECIPIENT=...    # address to receive the tokens
export AMOUNT=...       # amount in wei

# Testnet
export PORTAL=0xF68D900e1Cdec64a8f5Dc0Ee873A9E2879256b10
export L1_RPC=https://ethereum-sepolia-rpc.publicnode.com
export L2_RPC=https://carrot.megaeth.com/rpc  # see Connect page for all RPC options

# Mainnet
# export PORTAL=0x7f82f57F0Dd546519324392e408b01fcC7D709e8
# export L1_RPC=<your Ethereum mainnet RPC>
# export L2_RPC=https://mainnet.megaeth.com/rpc
2

Check your L2 token balance

SENDER=$(cast wallet address --private-key $PRIVATE_KEY)
cast call $TOKEN "balanceOf(address)(uint256)" $SENDER --rpc-url $L2_RPC
3

Estimate L2 gas

Always query the MegaETH L2 RPC — MegaETH's dual gas model (compute + storage) means standard Ethereum tooling underestimates. Add 20% headroom to account for storage gas variance.

SENDER=$(cast wallet address --private-key $PRIVATE_KEY)

GAS_EST=$(cast estimate $TOKEN \
  "transfer(address,uint256)" $RECIPIENT $AMOUNT \
  --from $SENDER \
  --rpc-url $L2_RPC)

export GAS_LIMIT=$(( GAS_EST * 120 / 100 ))
echo "estimate: $GAS_EST   limit: $GAS_LIMIT"

For a standard ERC-20 transfer() on MegaETH the estimate is approximately 54,000 gas; a limit of 65,000 is sufficient.

circle-exclamation
4

Encode the calldata

The _data field passed to depositTransaction is the function call you want to execute on L2, serialized into bytes (ABI-encoded). Use cast calldata to produce it:

CALLDATA=$(cast calldata "transfer(address,uint256)" $RECIPIENT $AMOUNT)
echo $CALLDATA

Example output:

0xa9059cbb000000000000000000000000<recipient>0000000000000000000000000000000000000000000000000de0b6b3a7640000

The first 4 bytes (0xa9059cbb) are the transfer function selector. The remaining 64 bytes are the ABI-encoded recipient address and amount.

For a plain ETH transfer with no contract call, set _data to 0x and skip this step.

5

Submit depositTransaction on L1

cast send $PORTAL \
  "depositTransaction(address,uint256,uint64,bool,bytes)" \
  $TOKEN \
  0 \
  $GAS_LIMIT \
  false \
  $CALLDATA \
  --value 0 \
  --rpc-url $L1_RPC \
  --private-key $PRIVATE_KEY

The call emits a TransactionDeposited event on L1. Save the transaction hash to track the deposit.

6

Verify on L2

After 5–20 minutes, confirm the recipient's balance increased.

cast call $TOKEN "balanceOf(address)(uint256)" $RECIPIENT --rpc-url $L2_RPC

How it works

  1. depositTransaction emits TransactionDeposited(address indexed from, address indexed to, uint256 indexed version, bytes opaqueData) on L1.

  2. MegaETH watches for these events and converts each one into a deposited transaction (type 0x7E) to execute on L2.

  3. The deposited transaction runs on L2 with your L1 wallet address as the sender (no address transformation is applied for regular wallets).

  4. _gasLimit caps L2 execution gas. If unused, it is not refunded, but no ETH is charged on L1 for it — only the standard Ethereum gas fee for calling depositTransaction applies.

  5. Setting _value forwards ETH to _to; it must equal msg.value.

For the formal deposit specification, see Depositsarrow-up-right in the OP Stack spec.

Last updated