#!/usr/bin/env bash # Send WETH cross-chain via CCIP (Chain 138 → destination chain). # Usage: ./scripts/bridge/run-send-cross-chain.sh [recipient] [--dry-run] # Env: CCIP_DEST_CHAIN_SELECTOR, GAS_PRICE, GAS_LIMIT, CONFIRM_ABOVE_ETH (prompt above this amount) # Version: 2026-03-30 set -euo pipefail [[ "${DEBUG:-0}" = "1" ]] && set -x SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" had_nounset=0 if [[ $- == *u* ]]; then had_nounset=1 set +u fi source "${SCRIPT_DIR}/../lib/load-project-env.sh" (( had_nounset )) && set -u [[ -z "${PRIVATE_KEY:-}" ]] && { echo "PRIVATE_KEY required"; exit 1; } [[ -z "${CCIPWETH9_BRIDGE_CHAIN138:-}" ]] && { echo "CCIPWETH9_BRIDGE_CHAIN138 required"; exit 1; } command -v cast &>/dev/null || { echo "ERROR: cast not found (install Foundry)"; exit 1; } DRY_RUN=false ARGS=() for a in "$@"; do [[ "$a" = "--dry-run" ]] && DRY_RUN=true || ARGS+=("$a") done AMOUNT_ETH="${ARGS[0]:?Usage: $0 amount_eth [recipient] [--dry-run]}" RECIPIENT="${ARGS[1]:-$(cast wallet address "$PRIVATE_KEY" 2>/dev/null)}" SENDER_ADDR="$(cast wallet address "$PRIVATE_KEY" 2>/dev/null)" DEST_SELECTOR="${CCIP_DEST_CHAIN_SELECTOR:-5009297550715157269}" GAS_PRICE="${GAS_PRICE:-1000000000}" GAS_LIMIT="${GAS_LIMIT:-}" RPC="${RPC_URL_138:-$CHAIN138_RPC}" [[ -z "$RPC" ]] && { echo "ERROR: RPC_URL_138 or CHAIN138_RPC required"; exit 1; } BRIDGE="${CCIPWETH9_BRIDGE_CHAIN138}" extract_first_address() { echo "$1" | grep -oE '0x[a-fA-F0-9]{40}' | sed -n '1p' } raw_uint() { echo "$1" | awk '{print $1}' } lower() { echo "$1" | tr '[:upper:]' '[:lower:]' } MAINNET_SELECTOR_VALUE="${MAINNET_SELECTOR:-5009297550715157269}" BSC_SELECTOR_VALUE="${BSC_SELECTOR:-11344663589394136015}" AVALANCHE_SELECTOR_VALUE="${AVALANCHE_SELECTOR:-6433500567565415381}" assert_supported_direct_first_hop() { case "$DEST_SELECTOR" in "$MAINNET_SELECTOR_VALUE"|"${BSC_SELECTOR_VALUE}"|"${AVALANCHE_SELECTOR_VALUE}") return 0 ;; esac if [[ "${ALLOW_UNSUPPORTED_DIRECT_FIRST_HOP:-0}" = "1" ]]; then echo "WARNING: proceeding with unsupported direct first hop for selector $DEST_SELECTOR because ALLOW_UNSUPPORTED_DIRECT_FIRST_HOP=1" return 0 fi cat < Mainnet 2. then bridge Mainnet -> destination Set ALLOW_UNSUPPORTED_DIRECT_FIRST_HOP=1 only if you are intentionally testing an unsupported path and accept that the source send may succeed without destination delivery. EOF exit 1 } DEST_RAW="$(cast call "$BRIDGE" 'destinations(uint64)((uint64,address,bool))' "$DEST_SELECTOR" --rpc-url "$RPC" 2>/dev/null || echo "")" DEST_ADDR="$(extract_first_address "$DEST_RAW")" assert_supported_direct_first_hop if [[ "$DEST_SELECTOR" == "$AVALANCHE_SELECTOR_VALUE" ]]; then AVALANCHE_NATIVE_BRIDGE="${CCIPWETH9_BRIDGE_AVALANCHE:-}" if [[ -n "$AVALANCHE_NATIVE_BRIDGE" ]] && [[ "$(lower "$DEST_ADDR")" == "$(lower "$AVALANCHE_NATIVE_BRIDGE")" ]] && [[ "${ALLOW_UNSUPPORTED_AVAX_NATIVE:-0}" != "1" ]]; then cat <=b+0)}' 2>/dev/null; then read -p "Send $AMOUNT_ETH ETH to $RECIPIENT? [y/N] " r [[ "${r,,}" != "y" ]] && [[ "${r,,}" != "yes" ]] && exit 0 fi AMOUNT_WEI=$(cast --to-wei "$AMOUNT_ETH" ether) ALLOWANCE_WETH_RAW="$(cast call "$WETH9" "allowance(address,address)(uint256)" "$SENDER_ADDR" "$BRIDGE" --rpc-url "$RPC" 2>/dev/null || echo "0")" ALLOWANCE_WETH="$(raw_uint "$ALLOWANCE_WETH_RAW")" if [[ "$ALLOWANCE_WETH" =~ ^[0-9]+$ ]] && (( ALLOWANCE_WETH < AMOUNT_WEI )); then cat </dev/null | cast --to-dec || echo "0") FEE_TOKEN=$(cast call "$BRIDGE" "feeToken()(address)" --rpc-url "$RPC" 2>/dev/null || echo "0x0") if [[ "$FEE_TOKEN" != "0x0000000000000000000000000000000000000000" ]] && [[ -n "$FEE_WEI" ]] && [[ "$FEE_WEI" != "0" ]]; then ALLOWANCE_FEE_RAW="$(cast call "$FEE_TOKEN" "allowance(address,address)(uint256)" "$SENDER_ADDR" "$BRIDGE" --rpc-url "$RPC" 2>/dev/null || echo "0")" ALLOWANCE_FEE="$(raw_uint "$ALLOWANCE_FEE_RAW")" if [[ "$ALLOWANCE_FEE" =~ ^[0-9]+$ ]] && (( ALLOWANCE_FEE < FEE_WEI )); then cat </dev/null || echo "0")" DEST_BALANCE="$(raw_uint "$DEST_BALANCE_RAW")" if [[ "$DEST_BALANCE" =~ ^[0-9]+$ ]] && (( DEST_BALANCE < AMOUNT_WEI )); then SHORTFALL=$(( AMOUNT_WEI - DEST_BALANCE )) cat </dev/null && echo "Simulation: OK" || echo "Simulation: (check params)" exit 0 fi # Real execution: broadcasts sendCrossChain transaction (no --dry-run) cast send "$BRIDGE" "sendCrossChain(uint64,address,uint256)" "$DEST_SELECTOR" "$RECIPIENT" "$AMOUNT_WEI" --rpc-url "$RPC" --private-key "$PRIVATE_KEY" --gas-price "$GAS_PRICE" --legacy $V $GAS_OPT