We need to collect the following information from the grafana dashboard for the offending withdrawal (as specified in the runbooks for this page).
event_tx_hash=0xe276fb2dea106d7cfeb9ed980101d7c64f5c7f75dafd557bcb6c4c322de280df
withdrawal_hash=0xadea6fbd204cce41eb72e0962a8bd9fc1bd02ffd04fc873e14353a12a78bbda7
proof_submitter=0xb47520aEA870C3fB9715C15fDF255C435F08c6c9
Set l1 and l2 below. Examples are l1=”sepolia” l2=”ink”
#!/bin/bash
echo "INITIAL SETUP"
l1="mainnet"
l2="base"
if [ -z "$l1" ] || [ -z "$l2" ]; then
echo "ERROR: Variables l1 and/or l2 are not set. Please specify the L1 and L2 chains."
else
echo "Using L1: $l1, L2: $l2"
l2_rpc_url="<https://$l2-opn-geth-a-rpc-0-op-geth.primary.$l1.prod.oplabs.cloud>"
l2_rpc_url_node="<https://$l2-opn-geth-a-rpc-0-op-node.primary.$l1.prod.oplabs.cloud>"
l1_rpc_url="<https://proxyd-l1-consensus.primary.$l1.prod.oplabs.cloud>"
superchain_file_url="<https://raw.githubusercontent.com/ethereum-optimism/superchain-registry/refs/heads/main/superchain/configs/$l1/$l2.toml>"
superchain_file_content=$(curl -s -L $superchain_file_url)
OPTIMISM_PORTAL_PROXY=$(echo "$superchain_file_content" | sed -n 's/.*OptimismPortalProxy = "\\([^"]*\\)".*/\\1/p')
L2ToL1MessagePasserAddress="0x4200000000000000000000000000000000000016"
echo "!!!! PASTE THIS IN YOUR TERMINAL BEFORE EXECUTING THE NEXT SCRIPT !!!!"
echo "export l2_rpc_url=\\"$l2_rpc_url\\""
echo "export l2_rpc_url_node=\\"$l2_rpc_url_node\\""
echo "export l1_rpc_url=\\"$l1_rpc_url\\""
echo "export OPTIMISM_PORTAL_PROXY=\\"$OPTIMISM_PORTAL_PROXY\\""
echo "export L2ToL1MessagePasserAddress=\\"$L2ToL1MessagePasserAddress\\""
echo "===================================================================="
check_rpc() {
url=$1
response=$(curl -s -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","method":"eth_chainId","params":[],"id":1}' "$url")
if echo "$response" | grep -q '"result"'; then
echo "✅ $url is reachable and responded with chainId."
else
echo "❌ $url is NOT reachable or didn't return a valid response."
fi
check_rpc_node() {
url=$1
response=$(curl -s -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","method":"optimism_syncStatus","params":[],"id":1}' "$url")
if echo "$response" | grep -q '"result"'; then
echo "✅ $url is reachable and responded with syncStatus."
else
echo "❌ $url is NOT reachable or didn't return a valid response."
fi
}
}
echo "Checking RPC endpoints..."
check_rpc "$l2_rpc_url"
check_rpc_node "$l2_rpc_url_node"
check_rpc "$l1_rpc_url"
fi
<aside> ⚠️
It may happen the the l2_rpc_url or l2_rpc_url_node generated do not work, because for example they do not follow the name convention. In that case you will need to find the nodes url manually. If for example the name is not correct you may need to look directly into the code. For example for base-mainnet you could look at https://github.com/ethereum-optimism/k8s/blob/master/kustomize/automated-security-monitoring/oplabs-tools-security/faultproof-withdrawals/overlays/base-mainnet/faultproof-withdrawals.env
and you would see that the nodes used are:
l2_rpc_url_node=https://base-opn-geth-a-rpc-2-op-node.quaternary.mainnet.prod.oplabs.cloud l2_rpc_url=https://base-opn-geth-a-rpc-2-op-geth.quaternary.mainnet.prod.oplabs.cloud
so set those instead manually in the shall
The RPCs are private so you will need to connect through the tailscale-oplabs-tools-tunnel
tailscale exit node and installed the internal domain root certificates.
</aside>
Get the dispute game, setting the withdrawal_hash below
#!/bin/bash
event_signature="WithdrawalProvenExtension1(bytes32,address)"
event_topic=$(cast keccak "$event_signature")
receipt=$(cast receipt $event_tx_hash --rpc-url=$l1_rpc_url --json)
logs=$(echo "$receipt" | jq --arg topic "$event_topic" '.logs[] | select(.topics[0] == $topic)')
if [ -z "$logs" ]; then
echo "Error: No WithdrawalProvenExtension1 events found"
else
withdrawal_hash_from_topic=$(echo "$logs" | jq -r '.topics[1]')
proof_submitter_from_topic=$(echo "$logs" | jq -r '.topics[2]')
output=$(cast call $OPTIMISM_PORTAL_PROXY "provenWithdrawals(bytes32,address)(address,uint64)" $withdrawal_hash $proof_submitter --rpc-url $l1_rpc_url)
read game_proxy_address timestamp <<< "$output"
echo "Withdrawal Hash: $withdrawal_hash_from_topic"
echo "Proof Submitter: $proof_submitter_from_topic"
echo "Game Proxy Address: $game_proxy_address"
l2_block_number=$(cast call $game_proxy_address "l2BlockNumber()(uint256)" --rpc-url $l1_rpc_url | cut -d' ' -f1)
l2_block_number_hex=$(printf "0x%X" "$l2_block_number")
echo "L2 Block Number: $l2_block_number"
echo "L2 Block Number (Hex): $l2_block_number_hex"
root_claim=$(cast call $game_proxy_address "rootClaim()(bytes32)" --rpc-url $l1_rpc_url)
echo "Root Claim: $root_claim"
sent_message=$(cast call $L2ToL1MessagePasserAddress "sentMessages(bytes32)(bytes32)" $withdrawal_hash_from_topic --rpc-url $l2_rpc_url)
if [[ "$sent_message" == "0x0000000000000000000000000000000000000000000000000000000000000001" ]]; then
echo "Withdrawal is verified on L2 $l2_rpc_url ✅"
else
echo "Withdrawal is NOT verified on L2 $l2_rpc_url ❌"
fi
l2_output_root=$(cast rpc optimism_outputAtBlock "$l2_block_number_hex" --rpc-url $l2_rpc_url_node | jq -r .outputRoot)
if [[ "$l2_output_root" == "$root_claim" ]]; then
echo "$l2_output_root == $root_claim Game Claim is good ✅"
else
echo "$l2_output_root != $root_claim Game Claim is NOT good ❌"
fi
fi