#!/usr/bin/env bash # Fund all CCIP WETH9/WETH10 bridge contracts with LINK on each chain. # Amount via tag (not .env): --link (default 10 LINK), --dry-run to print commands only. # --cap-to-deployer: per chain, each bridge gets min(--link, deployer_LINK_balance // bridges_on_chain). # --chain gnosis [celo ...]: limit to named chains (138, eth, gnosis, celo, wemix, cronos, ...). # Usage: ./scripts/deployment/fund-ccip-bridges-with-link.sh [--link 10] [--cap-to-deployer] [--chain gnosis] [--dry-run] set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" cd "$PROJECT_ROOT" source "$SCRIPT_DIR/../lib/deployment/dotenv.sh" source "$SCRIPT_DIR/../lib/deployment/prompts.sh" load_deployment_env parse_chain_filter "$@" parse_link_tags "${PARSE_CHAIN_FILTER_REMAINING[@]}" chain_selected() { local want="$1" [[ ${#CHAIN_FILTER[@]} -eq 0 ]] && return 0 local c for c in "${CHAIN_FILTER[@]}"; do [[ "$c" == "$want" ]] && return 0 done return 1 } [[ -f "$SCRIPT_DIR/../lib/infura.sh" ]] && source "$SCRIPT_DIR/../lib/infura.sh" 2>/dev/null || true [[ -n "${PRIVATE_KEY:-}" && ! "$PRIVATE_KEY" =~ ^0x ]] && PRIVATE_KEY="0x$PRIVATE_KEY" report_cast_send_result() { local _out="$1" if echo "$_out" | grep -qE 'status[[:space:]]+0[[:space:]]+\(failed\)'; then echo " On-chain revert (mined, status=0)" >&2 echo " $_out" >&2 elif echo "$_out" | grep -qE 'status[[:space:]]+1[[:space:]]+\(success\)'; then echo " OK (mined)" [[ -n "$_out" ]] && echo " $_out" elif echo "$_out" | grep -qiE 'Error:|revert|Failed|timeout|null response|invalid nonce'; then echo " RPC / client failure (non-fatal)" >&2 echo " $_out" >&2 elif echo "$_out" | grep -q 'transactionHash'; then echo " OK (submitted)" [[ -n "$_out" ]] && echo " $_out" else echo " Ambiguous cast output (non-fatal)" >&2 echo " $_out" >&2 fi } run_or_echo() { if [[ "${DRY_RUN:-0}" = "1" ]]; then echo " [DRY RUN] $*" else local _out if _out=$(eval "$*" 2>&1); then report_cast_send_result "$_out" else echo " Failed (non-fatal)" >&2 echo " $_out" >&2 fi fi } ensure_rpc() { local rpc="$1"; type ensure_infura_rpc_url &>/dev/null && [[ -n "$rpc" ]] && rpc=$(ensure_infura_rpc_url "$rpc"); echo "$rpc"; } get_token_balance() { local token="$1" owner="$2" rpc="$3" cast call "${token,,}" "balanceOf(address)(uint256)" "${owner,,}" --rpc-url "$rpc" 2>/dev/null || echo "0" } has_sufficient_link() { local token="$1" owner="$2" rpc="$3" needed="$4" local bal bal=$(get_token_balance "$token" "$owner" "$rpc") bal="${bal%% *}" [[ "$bal" =~ ^[0-9]+$ ]] || bal="0" python3 - <= needed else 1) PY } print_skip() { local label="$1" token="$2" owner="$3" rpc="$4" needed="$5" local bal bal=$(get_token_balance "$token" "$owner" "$rpc") echo " Skipped (insufficient LINK balance: have ${bal%% *}, need $needed)" } # Integer wei per bridge: min(LINK_AMOUNT_WEI, balance // n_bridges). per_bridge_cap_wei() { local token="$1" owner="$2" rpc="$3" n="$4" local bal bal=$(get_token_balance "$token" "$owner" "$rpc") bal="${bal%% *}" [[ "$bal" =~ ^[0-9]+$ ]] || bal="0" [[ "${n:-1}" -lt 1 ]] && n=1 python3 -c "b=int('$bal'); r=int('${LINK_AMOUNT_WEI}'); n=int('$n'); print(min(r, b//n))" } # Echo wei to transfer for one bridge; 0 means skip. resolve_bridge_transfer_wei() { local token="$1" owner="$2" rpc="$3" n_bridges="$4" if [[ "${CAP_LINK_TO_DEPLOYER_BALANCE:-0}" == "1" ]]; then per_bridge_cap_wei "$token" "$owner" "$rpc" "$n_bridges" return fi if has_sufficient_link "$token" "$owner" "$rpc" "$LINK_AMOUNT_WEI"; then echo "$LINK_AMOUNT_WEI" else echo "0" fi } print_skip_or_cap_zero() { local label="$1" token="$2" owner="$3" rpc="$4" if [[ "${CAP_LINK_TO_DEPLOYER_BALANCE:-0}" == "1" ]]; then local bal bal=$(get_token_balance "$token" "$owner" "$rpc") echo " Skipped ($label: deployer LINK ${bal%% *} wei → 0 per-bridge after split/cap vs target $LINK_AMOUNT_WEI)" else print_skip "$label" "$token" "$owner" "$rpc" "$LINK_AMOUNT_WEI" fi } log_capped_notice() { local label="$1" wei="$2" if [[ "${CAP_LINK_TO_DEPLOYER_BALANCE:-0}" == "1" ]] && [[ "$wei" != "0" ]] && [[ "$wei" -lt "$LINK_AMOUNT_WEI" ]]; then echo " $label: transfer ${wei} wei (capped from target ${LINK_AMOUNT_WEI})" fi } if [[ -z "$PRIVATE_KEY" ]]; then echo "ERROR: PRIVATE_KEY not set" >&2 exit 1 fi DEPLOYER_ADDR=$(cast wallet address --private-key "$PRIVATE_KEY" 2>/dev/null || true) if [[ -z "$DEPLOYER_ADDR" ]]; then echo "ERROR: could not derive deployer address from PRIVATE_KEY" >&2 exit 1 fi echo "Funding CCIP bridges with LINK (target per bridge: $LINK_AMOUNT_WEI wei)" [[ "${CAP_LINK_TO_DEPLOYER_BALANCE:-0}" == "1" ]] && echo "Mode: --cap-to-deployer (each chain uses min(target, balance // bridges_on_chain))" echo "Deployer: $DEPLOYER_ADDR" echo "" # Chain 138 (Besu: gas estimation often fails; use explicit legacy gas like manual cast) if chain_selected CHAIN138 && [[ -n "${RPC_URL_138:-}" && -n "${LINK_TOKEN_CHAIN138:-${LINK_TOKEN:-}}" ]]; then link="${LINK_TOKEN_CHAIN138:-$LINK_TOKEN}" link="${link,,}" rpc=$(ensure_rpc "$RPC_URL_138") echo "Chain 138 (RPC: ${rpc%%\?*}...)" _gas138="--legacy --gas-limit 250000 --gas-price 2000000000 --timeout 120" _n138=0 [[ -n "${CCIPWETH9_BRIDGE_CHAIN138:-}" ]] && _n138=$((_n138 + 1)) [[ -n "${CCIPWETH10_BRIDGE_CHAIN138:-}" ]] && _n138=$((_n138 + 1)) _cap_once=0 if [[ "${CAP_LINK_TO_DEPLOYER_BALANCE:-0}" == "1" ]]; then _wei_chain138=$(per_bridge_cap_wei "$link" "$DEPLOYER_ADDR" "$rpc" "$_n138") _cap_once=1 fi if [[ -n "${CCIPWETH9_BRIDGE_CHAIN138:-}" ]]; then if [[ "$_cap_once" == "1" ]]; then _wei="$_wei_chain138"; else _wei=$(resolve_bridge_transfer_wei "$link" "$DEPLOYER_ADDR" "$rpc" "$_n138"); fi if [[ "$_wei" == "0" ]]; then print_skip_or_cap_zero "CHAIN138/WETH9" "$link" "$DEPLOYER_ADDR" "$rpc" else log_capped_notice "CHAIN138/WETH9" "$_wei" run_or_echo "cast send $link \"transfer(address,uint256)\" ${CCIPWETH9_BRIDGE_CHAIN138,,} $_wei --rpc-url \"$rpc\" --private-key \"\$PRIVATE_KEY\" $_gas138" fi fi if [[ -n "${CCIPWETH10_BRIDGE_CHAIN138:-}" ]]; then if [[ "$_cap_once" == "1" ]]; then _wei="$_wei_chain138"; else _wei=$(resolve_bridge_transfer_wei "$link" "$DEPLOYER_ADDR" "$rpc" "$_n138"); fi if [[ "$_wei" == "0" ]]; then print_skip_or_cap_zero "CHAIN138/WETH10" "$link" "$DEPLOYER_ADDR" "$rpc" else log_capped_notice "CHAIN138/WETH10" "$_wei" run_or_echo "cast send $link \"transfer(address,uint256)\" ${CCIPWETH10_BRIDGE_CHAIN138,,} $_wei --rpc-url \"$rpc\" --private-key \"\$PRIVATE_KEY\" $_gas138" fi fi echo "" fi # Ethereum if chain_selected ETH && [[ -n "${ETHEREUM_MAINNET_RPC:-}" && -n "${MAINNET_LINK_TOKEN:-${CCIP_ETH_LINK_TOKEN:-}}" ]]; then link="${MAINNET_LINK_TOKEN:-$CCIP_ETH_LINK_TOKEN}" rpc=$(ensure_rpc "$ETHEREUM_MAINNET_RPC") echo "Ethereum Mainnet" _pub_timeout="--timeout 300 --rpc-timeout 120" _neth=0 [[ -n "${MAINNET_CCIP_WETH9_BRIDGE:-}" ]] && _neth=$((_neth + 1)) [[ -n "${MAINNET_CCIP_WETH10_BRIDGE:-}" ]] && _neth=$((_neth + 1)) _cap_once_eth=0 if [[ "${CAP_LINK_TO_DEPLOYER_BALANCE:-0}" == "1" ]]; then _wei_chain_eth=$(per_bridge_cap_wei "$link" "$DEPLOYER_ADDR" "$rpc" "$_neth") _cap_once_eth=1 fi if [[ -n "${MAINNET_CCIP_WETH9_BRIDGE:-}" ]]; then if [[ "$_cap_once_eth" == "1" ]]; then _wei="$_wei_chain_eth"; else _wei=$(resolve_bridge_transfer_wei "$link" "$DEPLOYER_ADDR" "$rpc" "$_neth"); fi if [[ "$_wei" == "0" ]]; then print_skip_or_cap_zero "ETH/WETH9" "$link" "$DEPLOYER_ADDR" "$rpc" else log_capped_notice "ETH/WETH9" "$_wei" run_or_echo "cast send $link \"transfer(address,uint256)\" $MAINNET_CCIP_WETH9_BRIDGE $_wei --rpc-url \"$rpc\" --private-key \"\$PRIVATE_KEY\" --legacy $_pub_timeout" fi fi if [[ -n "${MAINNET_CCIP_WETH10_BRIDGE:-}" ]]; then if [[ "$_cap_once_eth" == "1" ]]; then _wei="$_wei_chain_eth"; else _wei=$(resolve_bridge_transfer_wei "$link" "$DEPLOYER_ADDR" "$rpc" "$_neth"); fi if [[ "$_wei" == "0" ]]; then print_skip_or_cap_zero "ETH/WETH10" "$link" "$DEPLOYER_ADDR" "$rpc" else log_capped_notice "ETH/WETH10" "$_wei" run_or_echo "cast send $link \"transfer(address,uint256)\" $MAINNET_CCIP_WETH10_BRIDGE $_wei --rpc-url \"$rpc\" --private-key \"\$PRIVATE_KEY\" --legacy $_pub_timeout" fi fi echo "" fi # BSC, Polygon, Base, Optimism, Arbitrum, Avalanche, Cronos, Gnosis, Celo, Wemix (matches check-link-balance-config-ready-chains.sh) for label in BSC POLYGON BASE OPTIMISM ARBITRUM AVALANCHE CRONOS GNOSIS CELO WEMIX; do chain_selected "$label" || continue case "$label" in BSC) rpc_var="BSC_RPC_URL"; link_var="CCIP_BSC_LINK_TOKEN"; ;; POLYGON) rpc_var="POLYGON_MAINNET_RPC"; link_var="CCIP_POLYGON_LINK_TOKEN"; ;; BASE) rpc_var="BASE_MAINNET_RPC"; link_var="CCIP_BASE_LINK_TOKEN"; ;; OPTIMISM) rpc_var="OPTIMISM_MAINNET_RPC"; link_var="CCIP_OPTIMISM_LINK_TOKEN"; ;; ARBITRUM) rpc_var="ARBITRUM_MAINNET_RPC"; link_var="CCIP_ARBITRUM_LINK_TOKEN"; ;; AVALANCHE) rpc_var="AVALANCHE_RPC_URL"; link_var="CCIP_AVALANCHE_LINK_TOKEN"; ;; CRONOS) rpc_var="CRONOS_RPC_URL"; link_var="CCIP_CRONOS_LINK_TOKEN"; rpc_fb="CRONOS_RPC"; link_fb="LINK_TOKEN_CRONOS"; ;; GNOSIS) rpc_var="GNOSIS_MAINNET_RPC"; link_var="CCIP_GNOSIS_LINK_TOKEN"; rpc_fb="GNOSIS_RPC"; link_fb="LINK_TOKEN_GNOSIS"; ;; CELO) rpc_var="CELO_MAINNET_RPC"; link_var="CCIP_CELO_LINK_TOKEN"; rpc_fb="CELO_RPC"; link_fb="LINK_TOKEN_CELO"; ;; WEMIX) rpc_var="WEMIX_RPC"; link_var="CCIP_WEMIX_LINK_TOKEN"; rpc_fb="WEMIX_MAINNET_RPC"; link_fb="LINK_TOKEN_WEMIX"; ;; esac rpc="${!rpc_var:-${!rpc_fb:-}}" link="${!link_var:-${!link_fb:-}}" rpc=$(ensure_rpc "$rpc") [[ -z "$rpc" || -z "$link" ]] && continue bridge9_var="CCIPWETH9_BRIDGE_${label}" bridge10_var="CCIPWETH10_BRIDGE_${label}" addr9="${!bridge9_var:-}" addr10="${!bridge10_var:-}" [[ -z "$addr9" && -z "$addr10" ]] && continue echo "$label" _pub_timeout_loop="--timeout 300 --rpc-timeout 120" # Arbitrum: default cast fee fields can sit under base fee; bump legacy gas price from RPC. if [[ "$label" == "ARBITRUM" ]]; then _gp_try=$(cast gas-price --rpc-url "$rpc" 2>/dev/null | head -1 | tr -d '\r\n ' || true) if [[ -n "$_gp_try" ]] && [[ "$_gp_try" =~ ^[0-9]+$ ]] && [[ "$_gp_try" -gt 0 ]]; then _gp_arb=$(python3 -c "print(max(int(int('$_gp_try') * 3), 120000000))") else _gp_arb="1200000000" fi _pub_timeout_loop="$_pub_timeout_loop --gas-price $_gp_arb" fi _nl2=0 [[ -n "$addr9" ]] && _nl2=$((_nl2 + 1)) [[ -n "$addr10" ]] && _nl2=$((_nl2 + 1)) _cap_once_l2=0 if [[ "${CAP_LINK_TO_DEPLOYER_BALANCE:-0}" == "1" ]]; then _wei_chain_l2=$(per_bridge_cap_wei "$link" "$DEPLOYER_ADDR" "$rpc" "$_nl2") _cap_once_l2=1 fi if [[ -n "$addr9" ]]; then if [[ "$_cap_once_l2" == "1" ]]; then _wei="$_wei_chain_l2"; else _wei=$(resolve_bridge_transfer_wei "$link" "$DEPLOYER_ADDR" "$rpc" "$_nl2"); fi if [[ "$_wei" == "0" ]]; then print_skip_or_cap_zero "$label/WETH9" "$link" "$DEPLOYER_ADDR" "$rpc" else log_capped_notice "$label/WETH9" "$_wei" run_or_echo "cast send ${link,,} \"transfer(address,uint256)\" ${addr9,,} $_wei --rpc-url \"$rpc\" --private-key \"\$PRIVATE_KEY\" --legacy $_pub_timeout_loop" fi fi if [[ -n "$addr10" ]]; then if [[ "$_cap_once_l2" == "1" ]]; then _wei="$_wei_chain_l2"; else _wei=$(resolve_bridge_transfer_wei "$link" "$DEPLOYER_ADDR" "$rpc" "$_nl2"); fi if [[ "$_wei" == "0" ]]; then print_skip_or_cap_zero "$label/WETH10" "$link" "$DEPLOYER_ADDR" "$rpc" else log_capped_notice "$label/WETH10" "$_wei" run_or_echo "cast send ${link,,} \"transfer(address,uint256)\" ${addr10,,} $_wei --rpc-url \"$rpc\" --private-key \"\$PRIVATE_KEY\" --legacy $_pub_timeout_loop" fi fi echo "" done echo "Done. Use --link , --cap-to-deployer, and --dry-run to adjust."