#!/usr/bin/env bash # Audit live cross-network funding routes for Chain 138 WETH9. # # This script distinguishes: # - configured destination mappings on Chain 138 # - practical first-hop routes that actually work with the current relay model # - mainnet-hub destinations that become useful only after mainnet is funded # # Important: # - Chain 138 uses a custom router that emits MessageSent events but does not # natively deliver into public-chain bridges. # - A native-mapped destination on Chain 138 is therefore configuration signal, # not proof of a live direct route. set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" cd "$PROJECT_ROOT" if [[ -f "$SCRIPT_DIR/../lib/deployment/dotenv.sh" ]]; then # shellcheck disable=SC1090 source "$SCRIPT_DIR/../lib/deployment/dotenv.sh" load_deployment_env --repo-root "$PROJECT_ROOT" elif [[ -f "$PROJECT_ROOT/.env" ]]; then set -a # shellcheck disable=SC1090 source "$PROJECT_ROOT/.env" set +a fi need_var() { local name="$1" [[ -n "${!name:-}" ]] || { echo "Error: $name is required" >&2 exit 1 } } need_var PRIVATE_KEY need_var RPC_URL_138 need_var ETHEREUM_MAINNET_RPC need_var CCIPWETH9_BRIDGE_CHAIN138 need_var MAINNET_CCIP_WETH9_BRIDGE need_var ETH_MAINNET_SELECTOR DEPLOYER="$(cast wallet address "$PRIVATE_KEY" 2>/dev/null || true)" [[ -n "$DEPLOYER" ]] || { echo "Error: could not derive deployer from PRIVATE_KEY" >&2 exit 1 } CHAIN138_SELECTOR_VALUE="${CHAIN138_SELECTOR:-138}" CURRENT_138_WETH9="${CCIPWETH9_BRIDGE_CHAIN138,,}" LEGACY_138_WETH9="${CCIPWETH9_BRIDGE_DIRECT_LEGACY:-0x971cD9D156f193df8051E48043C476e53ECd4693}" LEGACY_138_WETH9="${LEGACY_138_WETH9,,}" MAINNET_WETH9="$(cast call "$MAINNET_CCIP_WETH9_BRIDGE" 'weth9()(address)' --rpc-url "$ETHEREUM_MAINNET_RPC" | tail -n1)" MAINNET_FEE_TOKEN="$(cast call "$MAINNET_CCIP_WETH9_BRIDGE" 'feeToken()(address)' --rpc-url "$ETHEREUM_MAINNET_RPC" | tail -n1)" fmt_ether() { local raw="${1:-0}" raw="$(echo "$raw" | awk '{print $1}')" cast from-wei "$raw" ether 2>/dev/null || echo "$raw" } contains_true() { [[ "$1" == *", true)"* ]] } extract_addr() { echo "$1" | grep -oE '0x[a-fA-F0-9]{40}' | head -n1 | tr '[:upper:]' '[:lower:]' } extract_enabled_addr() { local raw="$1" local addr addr="$(extract_addr "$raw")" if contains_true "$raw" && [[ -n "$addr" ]]; then echo "$addr" else echo "" fi } query_dest() { local rpc="$1" local bridge="$2" local selector="$3" if [[ -z "$rpc" || -z "$bridge" || -z "$selector" ]]; then echo "UNSET" return 0 fi cast call "$bridge" 'destinations(uint64)((uint64,address,bool))' "$selector" --rpc-url "$rpc" 2>/dev/null || echo "ERROR" } quote_fee_state() { local rpc="$1" local bridge="$2" local selector="$3" if [[ -z "$rpc" || -z "$bridge" || -z "$selector" ]]; then echo "n/a" return 0 fi if cast call "$bridge" 'calculateFee(uint64,uint256)(uint256)' "$selector" 4000000000000000 --rpc-url "$rpc" >/dev/null 2>&1; then echo "quoted" else echo "blocked" fi } balance_of() { local rpc="$1" local token="$2" local wallet="$3" if [[ -z "$rpc" || -z "$token" || -z "$wallet" ]]; then echo "0" return 0 fi cast call "$token" 'balanceOf(address)(uint256)' "$wallet" --rpc-url "$rpc" 2>/dev/null | awk '{print $1}' || echo "0" } native_balance() { local rpc="$1" if [[ -z "$rpc" ]]; then echo "0" return 0 fi cast balance "$DEPLOYER" --rpc-url "$rpc" 2>/dev/null || echo "0" } configured_class() { local source_addr="$1" local expected_bridge="$2" if [[ -z "${expected_bridge,,}" ]]; then echo "deploy-first" elif [[ "$source_addr" == "${expected_bridge,,}" ]]; then echo "native-mapped" elif [[ -n "$source_addr" ]]; then echo "relay-or-other" else echo "disabled" fi } practical_route() { local name="$1" local hub_quote="$2" case "$name" in mainnet|bsc|avalanche) echo "relay-backed-first-hop" ;; wemix) echo "deploy-first" ;; *) if [[ "$hub_quote" == "blocked" ]]; then echo "mainnet-hub-blocked" else echo "via-mainnet-hub" fi ;; esac } relay_inventory_state() { local balance="$1" if [[ "$(echo "$balance > 0" | bc)" == "1" ]]; then echo "present" else echo "empty" fi } detail_line() { local name="$1" local configured="$2" local mainnet_to_remote="$3" local mainnet_quote="$4" local remote_to_138="$5" local relay_balance="$6" local source_addr="$7" local parts=() case "$configured" in native-mapped) parts+=("138 mapping matches native bridge") ;; relay-or-other) parts+=("138 mapping points at non-native receiver $source_addr") ;; deploy-first) parts+=("bridge not deployed in env") ;; *) parts+=("138 mapping missing or disabled") ;; esac if contains_true "$mainnet_to_remote"; then if [[ "$mainnet_quote" == "blocked" ]]; then parts+=("mainnet hub mapping enabled but source fee quote reverts") else parts+=("mainnet hub mapping enabled") fi else parts+=("mainnet hub mapping missing or disabled") fi local back_addr back_addr="$(extract_enabled_addr "$remote_to_138")" if [[ -n "$back_addr" ]]; then if [[ "$back_addr" == "$CURRENT_138_WETH9" ]]; then parts+=("return path points at current 138 bridge") elif [[ "$back_addr" == "$LEGACY_138_WETH9" ]]; then parts+=("return path still points at legacy 138 bridge") else parts+=("return path points at unexpected 138 bridge $back_addr") fi fi case "$(practical_route "$name" "$mainnet_quote")" in relay-backed-first-hop) parts+=("practical first hop") if [[ -n "$relay_balance" ]]; then parts+=("relay inventory $(relay_inventory_state "$relay_balance")") fi ;; via-mainnet-hub) parts+=("use after mainnet bootstrap") ;; mainnet-hub-blocked) parts+=("mainnet WETH9 public fan-out currently blocked at source bridge/router") ;; deploy-first) parts+=("deploy and seed before use") ;; esac local IFS='; ' echo "${parts[*]}" } echo "=== Chain 138 Funding Route Audit (WETH9 rail) ===" echo "Date: $(date -u '+%Y-%m-%d %H:%M:%S UTC')" echo "Deployer: $DEPLOYER" echo "Current Chain 138 WETH9 bridge: $CURRENT_138_WETH9" echo "Legacy Chain 138 WETH9 bridge: $LEGACY_138_WETH9" echo "Mainnet WETH9 bridge: ${MAINNET_CCIP_WETH9_BRIDGE,,}" echo "" printf '%-10s | %-14s | %-14s | %-14s | %-16s | %-16s | %-22s | %-14s\n' \ "Chain" "Native" "Wrapped" "LINK" "138 mapping" "Mainnet hub" "Practical route" "Relay inv." printf '%s\n' "--------------------------------------------------------------------------------------------------------------------------------------------" CHAINS=( "mainnet|$ETHEREUM_MAINNET_RPC|$ETH_MAINNET_SELECTOR|$MAINNET_CCIP_WETH9_BRIDGE|$MAINNET_WETH9|$MAINNET_FEE_TOKEN|${CCIP_RELAY_BRIDGE_MAINNET:-}" "bsc|$BSC_MAINNET_RPC|$BSC_SELECTOR|$CCIPWETH9_BRIDGE_BSC|$WETH9_BSC|$LINK_TOKEN_BSC|0x886C6A4ABC064dbf74E7caEc460b7eeC31F1b78C" "avalanche|$AVALANCHE_MAINNET_RPC|$AVALANCHE_SELECTOR|$CCIPWETH9_BRIDGE_AVALANCHE|$WETH9_AVALANCHE|$LINK_TOKEN_AVALANCHE|0x3f8C409C6072a2B6a4Ff17071927bA70F80c725F" "gnosis|$GNOSIS_RPC|$GNOSIS_SELECTOR|$CCIPWETH9_BRIDGE_GNOSIS|$WETH9_GNOSIS|$LINK_TOKEN_GNOSIS|" "cronos|$CRONOS_RPC|$CRONOS_SELECTOR|$CCIPWETH9_BRIDGE_CRONOS|$WETH9_CRONOS|$LINK_TOKEN_CRONOS|" "celo|$CELO_RPC|${CELO_SELECTOR:-1346049177634351622}|$CCIPWETH9_BRIDGE_CELO|$WETH9_CELO|$LINK_TOKEN_CELO|" "polygon|$POLYGON_MAINNET_RPC|$POLYGON_SELECTOR|$CCIPWETH9_BRIDGE_POLYGON|$WETH9_POLYGON|$LINK_TOKEN_POLYGON|" "arbitrum|$ARBITRUM_MAINNET_RPC|$ARBITRUM_SELECTOR|$CCIPWETH9_BRIDGE_ARBITRUM|$WETH9_ARBITRUM|$LINK_TOKEN_ARBITRUM|" "optimism|$OPTIMISM_MAINNET_RPC|$OPTIMISM_SELECTOR|$CCIPWETH9_BRIDGE_OPTIMISM|$WETH9_OPTIMISM|$LINK_TOKEN_OPTIMISM|" "base|$BASE_MAINNET_RPC|$BASE_SELECTOR|$CCIPWETH9_BRIDGE_BASE|$WETH9_BASE|$LINK_TOKEN_BASE|" "wemix|$WEMIX_RPC|${WEMIX_SELECTOR:-5142893604156789321}|$CCIPWETH9_BRIDGE_WEMIX|$WETH9_WEMIX|$LINK_TOKEN_WEMIX|" ) for entry in "${CHAINS[@]}"; do IFS='|' read -r name rpc selector expected_bridge wrapped_token link_token relay_bridge <<< "$entry" native_raw="$(native_balance "$rpc")" wrapped_raw="$(balance_of "$rpc" "$wrapped_token" "$DEPLOYER")" link_raw="$(balance_of "$rpc" "$link_token" "$DEPLOYER")" source_dest="$(query_dest "$RPC_URL_138" "$CCIPWETH9_BRIDGE_CHAIN138" "$selector")" source_addr="$(extract_enabled_addr "$source_dest")" mainnet_to_remote="$(query_dest "$ETHEREUM_MAINNET_RPC" "$MAINNET_CCIP_WETH9_BRIDGE" "$selector")" mainnet_quote="n/a" if [[ "$name" != "mainnet" ]]; then mainnet_quote="$(quote_fee_state "$ETHEREUM_MAINNET_RPC" "$MAINNET_CCIP_WETH9_BRIDGE" "$selector")" fi mainnet_hub_flag="disabled" if [[ "$name" == "mainnet" ]]; then mainnet_hub_flag="n/a" elif contains_true "$mainnet_to_remote"; then mainnet_hub_flag="enabled/$mainnet_quote" fi relay_inv="n/a" if [[ -n "$relay_bridge" && -n "$wrapped_token" ]]; then relay_inv="$(fmt_ether "$(balance_of "$rpc" "$wrapped_token" "$relay_bridge")")" fi printf '%-10s | %-14s | %-14s | %-14s | %-16s | %-16s | %-22s | %-14s\n' \ "$name" \ "$(fmt_ether "$native_raw")" \ "$(fmt_ether "$wrapped_raw")" \ "$(fmt_ether "$link_raw")" \ "$(configured_class "$source_addr" "$expected_bridge")" \ "$mainnet_hub_flag" \ "$(practical_route "$name" "$mainnet_quote")" \ "$relay_inv" done echo "" echo "Detail Notes" echo "------------" for entry in "${CHAINS[@]}"; do IFS='|' read -r name rpc selector expected_bridge wrapped_token _link_token relay_bridge <<< "$entry" source_dest="$(query_dest "$RPC_URL_138" "$CCIPWETH9_BRIDGE_CHAIN138" "$selector")" source_addr="$(extract_enabled_addr "$source_dest")" mainnet_to_remote="$(query_dest "$ETHEREUM_MAINNET_RPC" "$MAINNET_CCIP_WETH9_BRIDGE" "$selector")" mainnet_quote="n/a" if [[ "$name" != "mainnet" ]]; then mainnet_quote="$(quote_fee_state "$ETHEREUM_MAINNET_RPC" "$MAINNET_CCIP_WETH9_BRIDGE" "$selector")" fi remote_to_138="$(query_dest "$rpc" "$expected_bridge" "$CHAIN138_SELECTOR_VALUE")" relay_balance="" if [[ -n "$relay_bridge" && -n "$wrapped_token" ]]; then relay_balance="$(balance_of "$rpc" "$wrapped_token" "$relay_bridge")" fi echo "- $name: $(detail_line "$name" "$(configured_class "$source_addr" "$expected_bridge")" "$mainnet_to_remote" "$mainnet_quote" "$remote_to_138" "$relay_balance" "${source_addr:-disabled}")" done echo "" echo "Important" echo "---------" echo "- This audit intentionally uses the WETH9 rail. Live WETH10 destination mappings are currently drifted from env and the mainnet WETH10 bridge is not wired to the public-chain destinations." echo "- 'native-mapped' means the live Chain 138 destination address matches the expected native bridge address." echo "- Because Chain 138 uses a custom event-emitting router, 'native-mapped' is configuration signal only, not proof of a direct live first-hop route." echo "- Practical first hops today are the relay-backed lanes. The other public chains are best treated as mainnet-hub destinations unless a relay is added for them."