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

Step 1 - Verify the RPCs

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>

Step 2 - Verify the Withdrawal and Output Root

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