2026-03-02 12:14:09 -08:00
#!/usr/bin/env bash
# Check deployer balances on all configured networks, estimate gas costs via gas API,
# ensure sufficient network token, then optionally deploy (Chain 138 phased core).
# Uses PRIVATE_KEY from .env (smom-dbis-138).
#
# Balance check: On EVM chains only the NATIVE token can be used for gas. We check that
# token for each network (cast balance = native currency). ERC-20 tokens (USDT, LINK, etc.)
# cannot be used for gas unless a meta-tx relayer is in use.
#
# Gas token per network (only these can be used for gas):
# Chain 138, Ethereum, Base, Optimism, Sepolia, Base Sepolia, Optimism Sepolia → ETH
# Polygon, Polygon Amoy → MATIC
# BSC → BNB
# Avalanche → AVAX
# Cronos → CRO
# Gnosis → xDAI
# Arbitrum → ETH
#
# Usage:
# ./scripts/deployment/check-balances-gas-and-deploy.sh # report only
# ./scripts/deployment/check-balances-gas-and-deploy.sh --deploy # deploy on Chain 138 (requires PRIVATE_KEY)
#
# Uses .env for RPCs and PRIVATE_KEY; --deploy is a tag (not .env).
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_deploy_tag " $@ "
DO_DEPLOY = " ${ DO_DEPLOY :- 0 } "
# Infura: use Basic Auth URL when INFURA_PROJECT_SECRET is set (fixes "error sending request" from Infura)
SCRIPT_LIB = " $SCRIPT_DIR /../lib "
[ -f " ${ SCRIPT_LIB } /infura.sh " ] && source " ${ SCRIPT_LIB } /infura.sh "
# Deployer address: use DEPLOYER_ADDRESS if set (matches MetaMask Portfolio), else derive from PRIVATE_KEY
if [ -n " ${ DEPLOYER_ADDRESS :- } " ] ; then
DEPLOYER = " ${ DEPLOYER_ADDRESS } "
[ [ " $DEPLOYER " != 0x* ] ] && DEPLOYER = " 0x $DEPLOYER "
else
if [ -z " ${ PRIVATE_KEY :- } " ] ; then
echo "ERROR: Set PRIVATE_KEY in .env or set DEPLOYER_ADDRESS=0x4A666F96fC8764181194447A7dFdb7d471b301C8 for read-only balance check"
exit 1
fi
DEPLOYER = $( cast wallet address " $PRIVATE_KEY " 2>/dev/null || true )
if [ -z " $DEPLOYER " ] ; then
echo "ERROR: Could not derive deployer address from PRIVATE_KEY (is 'cast' available?)"
exit 1
fi
fi
# Gas estimate for "full" deploy per chain (conservative)
GAS_FULL_DEPLOY_138 = " ${ GAS_FULL_DEPLOY_138 :- 5000000 } "
GAS_FULL_DEPLOY_MAINNET = " ${ GAS_FULL_DEPLOY_MAINNET :- 5000000 } "
BUFFER_BPS = 120 # 20% buffer
RED = '\033[0;31m'
GREEN = '\033[0;32m'
YELLOW = '\033[1;33m'
BLUE = '\033[0;34m'
NC = '\033[0m'
# Short form for matching MetaMask Portfolio (0x4A66...b301C8)
DEPLOYER_SHORT = " ${ DEPLOYER : 0 : 6 } ... ${ DEPLOYER : -6 } "
echo -e " ${ BLUE } ============================================ ${ NC } "
echo -e " ${ BLUE } Deployer balance & gas check (all networks) ${ NC } "
echo -e " ${ BLUE } ============================================ ${ NC } "
echo " Deployer: $DEPLOYER_SHORT (full: $DEPLOYER ) "
echo " (Matches MetaMask Portfolio when RPCs are reachable; use DEPLOYER_ADDRESS=0x... to check a specific wallet)"
echo ""
# Format wei to ether with 8 decimal places (avoids excessive decimals from cast)
format_wei_to_ether( ) {
local wei = " ${ 1 :- 0 } "
local eth
eth = $( echo " scale=10; $wei / 1000000000000000000 " | bc 2>/dev/null) || eth = "0"
printf "%.8f" " ${ eth :- 0 } "
}
# Infura Gas API: supported chain IDs (EIP-1559 style; API returns suggestedMaxFeePerGas in gwei)
INFURA_GAS_API_CHAINS = "1 137 10 8453 42161 11155111 84532 11155420 80002 43114 56 100 324 534352"
# Get gas price in wei from Infura Gas API for a given chain_id (optional).
# Returns wei or empty; caller should fallback to cast gas-price.
get_gas_price_wei_infura_api( ) {
local chain_id = " ${ 1 :- 1 } "
local key = ""
if [ -n " ${ INFURA_GAS_API :- } " ] ; then
local url = " $INFURA_GAS_API "
if [ [ " $url " = = *"/v3/" * ] ] ; then
key = " ${ url #*v3/ } "
key = " ${ key %%/* } "
else
key = " $url "
fi
elif [ -n " ${ INFURA_PROJECT_ID :- } " ] ; then
key = " ${ INFURA_PROJECT_ID } "
fi
if [ -z " $key " ] || ! command -v curl >/dev/null 2>& 1 || ! command -v jq >/dev/null 2>& 1; then
return 1
fi
local api_url = " https://gas.api.infura.io/v3/ ${ key } /networks/ ${ chain_id } /suggestedGasFees "
local res
res = $( curl -sL --connect-timeout 5 " $api_url " 2>/dev/null || true )
if [ -z " $res " ] || echo " $res " | grep -qi "error\|required\|private key\|not found" ; then
return 1
fi
local gwei
gwei = $( echo " $res " | jq -r '.medium.suggestedMaxFeePerGas // .standard.suggestedMaxFeePerGas // .standard.maxFeePerGas // .low.suggestedMaxFeePerGas // empty' 2>/dev/null)
if [ -z " $gwei " ] || [ " $gwei " = "null" ] ; then
return 1
fi
local wei
wei = $( echo " scale=0; ( $gwei ) * 1000000000 / 1 " | bc 2>/dev/null)
[ -n " $wei " ] && [ " $wei " != "0" ] && echo " $wei "
}
# Get mainnet gas price from Infura Gas API (preferred) or RPC fallback.
get_mainnet_gas_price_wei( ) {
local wei
wei = $( get_gas_price_wei_infura_api 1)
if [ -z " $wei " ] || [ " $wei " = "0" ] ; then
[ -n " ${ ETHEREUM_MAINNET_RPC :- } " ] && command -v cast >/dev/null 2>& 1 && wei = $( cast gas-price --rpc-url " $ETHEREUM_MAINNET_RPC " 2>/dev/null || true )
fi
[ -n " $wei " ] && [ " $wei " != "0" ] && echo " $wei " || echo "30000000000"
}
# Resolve gas price for a chain: try Infura Gas API first (if chain supported), else RPC, else default_wei.
# Usage: get_gas_price_for_chain chain_id rpc_url default_wei
# Output: wei (and sets GAS_SOURCE="Infura Gas API" or "RPC" or "default")
get_gas_price_for_chain( ) {
local chain_id = " $1 "
local rpc_url = " $2 "
local default_wei = " ${ 3 :- 30000000000 } "
local wei = ""
[ -n " $rpc_url " ] && type ensure_infura_rpc_url & >/dev/null && rpc_url = $( ensure_infura_rpc_url " $rpc_url " )
if [ [ " $INFURA_GAS_API_CHAINS " = = *" $chain_id " * ] ] ; then
wei = $( get_gas_price_wei_infura_api " $chain_id " )
[ -n " $wei " ] && GAS_SOURCE = "Infura Gas API" && echo " $wei " && return
fi
if [ -n " $rpc_url " ] && command -v cast >/dev/null 2>& 1; then
wei = $( cast gas-price --rpc-url " $rpc_url " 2>/dev/null || true )
[ -n " $wei " ] && [ " $wei " != "0" ] && GAS_SOURCE = "RPC" && echo " $wei " && return
fi
GAS_SOURCE = "default"
echo " $default_wei "
}
mainnet_gas_wei = $( get_mainnet_gas_price_wei)
mainnet_gas_gwei = $( printf "%.2f" $( echo " scale=4; $mainnet_gas_wei / 1000000000 " | bc 2>/dev/null || echo "30" ) )
echo "Gas prices (verified via Infura Gas API where supported, else RPC or default):"
echo " Ethereum Mainnet (1): ${ mainnet_gas_gwei } gwei (Infura Gas API) "
echo ""
# Check native (gas) token balance for one network. Only this token can be used for gas on EVM.
check_network( ) {
local name = " $1 "
local rpc = " $2 "
local chain_id = " ${ 3 :- } "
local gas_token = " ${ 4 :- ETH } "
local gas_limit = " ${ 5 :- $GAS_FULL_DEPLOY_MAINNET } "
local gas_price_wei = " ${ 6 :- $mainnet_gas_wei } "
if [ -z " $rpc " ] ; then
echo -e " ${ YELLOW } $name (chain $chain_id ): no RPC — gas token = $gas_token (not checked) ${ NC } "
return 0
fi
type ensure_infura_rpc_url & >/dev/null && rpc = $( ensure_infura_rpc_url " $rpc " )
local balance_wei
balance_wei = $( cast balance " $DEPLOYER " --rpc-url " $rpc " 2>/dev/null) || balance_wei = ""
local rpc_ok = 1
if [ -z " $balance_wei " ] || ! [ [ " $balance_wei " = ~ ^[ 0-9] +$ ] ] ; then
rpc_ok = 0
balance_wei = "0"
fi
local balance_display
if [ " $rpc_ok " = "0" ] ; then
balance_display = "(unable to fetch — RPC unreachable?)"
else
balance_display = $( format_wei_to_ether " $balance_wei " )
fi
local cost_wei
cost_wei = $( echo " $gas_limit * $gas_price_wei " | bc 2>/dev/null || echo "0" )
local cost_with_buffer
cost_with_buffer = $( echo " $cost_wei * $BUFFER_BPS / 100 " | bc 2>/dev/null || echo " $cost_wei " )
local cost_display
cost_display = $( format_wei_to_ether " $cost_with_buffer " )
local ok = 0
[ " $rpc_ok " = "1" ] && [ " $( echo " $balance_wei >= $cost_with_buffer " | bc 2>/dev/null) " = "1" ] && ok = 1
if [ " $ok " = "1" ] ; then
echo -e " ${ GREEN } $name (chain $chain_id ): gas token $gas_token — balance ${ balance_display } , required ~ ${ cost_display } — OK for deploy ${ NC } "
elif [ " $rpc_ok " = "0" ] ; then
echo -e " ${ YELLOW } $name (chain $chain_id ): gas token $gas_token — balance ${ balance_display } , required ~ ${ cost_display } — check RPC / run from network with access ${ NC } "
else
echo -e " ${ RED } $name (chain $chain_id ): gas token $gas_token — balance ${ balance_display } , required ~ ${ cost_display } — INSUFFICIENT for deploy ${ NC } "
fi
return $(( 1 - ok))
}
# All networks: order matches MetaMask Portfolio (Ethereum, BSC, Arbitrum, Optimism, Polygon, Cronos, …)
# Gas needed from Infura Gas API or RPC. Native gas token only (ETH, BNB, POL, CRO, etc.).
echo "Checking gas token balance (same order as MetaMask Portfolio where applicable):"
echo ""
any_insufficient = 0
# --- Portfolio main networks (Ethereum, BSC, Arbitrum, Optimism, Polygon, Cronos) ---
check_network "Ethereum Mainnet" " ${ ETHEREUM_MAINNET_RPC :- } " "1" "ETH" " $GAS_FULL_DEPLOY_MAINNET " " $mainnet_gas_wei " || any_insufficient = 1
check_network "BSC" " ${ BSC_RPC_URL :- ${ BSC_MAINNET_RPC :- https : //bsc-dataseed.binance.org } } " "56" "BNB" "5000000" " $( get_gas_price_for_chain 56 " ${ BSC_RPC_URL :- ${ BSC_MAINNET_RPC :- } } " "5000000000" ) " || true
check_network "Arbitrum" " ${ ARBITRUM_MAINNET_RPC :- https : //arbitrum-one.publicnode.com } " "42161" "ETH" "5000000" " $( get_gas_price_for_chain 42161 " ${ ARBITRUM_MAINNET_RPC :- } " "100000000" ) " || true
check_network "Optimism" " ${ OPTIMISM_MAINNET_RPC :- } " "10" "ETH" "5000000" " $( get_gas_price_for_chain 10 " ${ OPTIMISM_MAINNET_RPC :- } " "1000000" ) " || true
check_network "Polygon" " ${ POLYGON_MAINNET_RPC :- } " "137" "POL" "5000000" " $( get_gas_price_for_chain 137 " ${ POLYGON_MAINNET_RPC :- } " "50000000000" ) " || any_insufficient = 1
check_network "Cronos" " ${ CRONOS_RPC_URL :- https : //evm.cronos.org } " "25" "CRO" "5000000" " $( get_gas_price_for_chain 25 " ${ CRONOS_RPC_URL :- } " "1000000000" ) " || true
# --- Chain 138 (project chain) ---
check_network "Chain 138" " ${ RPC_URL_138 :- } " "138" "ETH" " $GAS_FULL_DEPLOY_138 " " $( cast gas-price --rpc-url " ${ RPC_URL_138 :- http : //192.168.11.211 : 8545 } " 2>/dev/null || echo "1000000000" ) " || any_insufficient = 1
# --- Other mainnets ---
check_network "Base" " ${ BASE_MAINNET_RPC :- } " "8453" "ETH" "5000000" " $( get_gas_price_for_chain 8453 " ${ BASE_MAINNET_RPC :- } " "100000000" ) " || true
check_network "Avalanche" " ${ AVALANCHE_RPC_URL :- ${ AVALANCHE_MAINNET_RPC :- https : //avalanche-c-chain.publicnode.com } } " "43114" "AVAX" "5000000" " $( get_gas_price_for_chain 43114 " ${ AVALANCHE_RPC_URL :- ${ AVALANCHE_MAINNET_RPC :- } } " "25000000000" ) " || true
check_network "Gnosis" " ${ GNOSIS_MAINNET_RPC :- ${ GNOSIS_RPC :- https : //rpc.gnosischain.com } } " "100" "xDAI" "5000000" " $( get_gas_price_for_chain 100 " ${ GNOSIS_MAINNET_RPC :- ${ GNOSIS_RPC :- } } " "1000000000" ) " || true
# --- Testnets ---
check_network "Ethereum Sepolia" " ${ ETHEREUM_SEPOLIA_RPC :- } " "11155111" "ETH" "5000000" " $( get_gas_price_for_chain 11155111 " ${ ETHEREUM_SEPOLIA_RPC :- } " "20000000000" ) " || true
check_network "Polygon Amoy" " ${ POLYGON_AMOY_RPC :- } " "80002" "MATIC" "5000000" " $( get_gas_price_for_chain 80002 " ${ POLYGON_AMOY_RPC :- } " "30000000000" ) " || true
check_network "Base Sepolia" " ${ BASE_SEPOLIA_RPC :- } " "84532" "ETH" "5000000" " $( get_gas_price_for_chain 84532 " ${ BASE_SEPOLIA_RPC :- } " "100000000" ) " || true
check_network "Optimism Sepolia" " ${ OPTIMISM_SEPOLIA_RPC :- } " "11155420" "ETH" "5000000" " $( get_gas_price_for_chain 11155420 " ${ OPTIMISM_SEPOLIA_RPC :- } " "1000000" ) " || true
2026-06-13 12:52:49 -07:00
check_network "Monad Mainnet" " ${ MONAD_MAINNET_RPC :- https : //rpc.monad.xyz } " "143" "MON" "5000000" " $( get_gas_price_for_chain 143 " ${ MONAD_MAINNET_RPC :- https : //rpc.monad.xyz } " "102000000000" ) " || true
2026-03-02 12:14:09 -08:00
echo ""
echo "Gas token summary: each chain uses only its native token for gas. ERC-20 (USDT, LINK, etc.) cannot pay gas."
echo "Gas needed above was computed from: Infura Gas API (chains 1, 10, 56, 100, 137, 42161, 43114, 8453, 84532, 80002, 11155111, 11155420) or RPC eth_gasPrice when API unavailable."
echo ""
if [ " ${ DO_DEPLOY :- 0 } " = "1" ] ; then
if [ -n " ${ DEPLOYER_ADDRESS :- } " ] ; then
echo -e " ${ RED } ERROR: --deploy requires PRIVATE_KEY in .env (unset DEPLOYER_ADDRESS to use key-derived deployer). ${ NC } "
exit 1
fi
if [ -z " ${ PRIVATE_KEY :- } " ] ; then
echo -e " ${ RED } ERROR: PRIVATE_KEY required for --deploy. ${ NC } "
exit 1
fi
echo -e " ${ BLUE } Deploy phase (Chain 138 only; other chains need CCIP_* env) ${ NC } "
RPC_138 = " ${ RPC_URL_138 :- http : //192.168.11.211 : 8545 } "
balance_138 = $( cast balance " $DEPLOYER " --rpc-url " $RPC_138 " 2>/dev/null || echo "0" )
need_138 = $( echo " 5000000 * 1000000000 * $BUFFER_BPS / 100 " | bc 2>/dev/null || echo "6000000000000000000" )
if [ -n " $balance_138 " ] && [ " $( echo " $balance_138 >= $need_138 " | bc 2>/dev/null) " = "1" ] ; then
echo "Deploying phased core (01_DeployCore, 02_DeployBridges) on Chain 138..."
export PRIVATE_KEY
out_01 = $( mktemp)
trap " rm -f $out_01 " EXIT
# Chain 138 often requires minimum gas price (e.g. 1 gwei)
GAS_PRICE_138 = " ${ GAS_PRICE_138 :- 1000000000 } "
if forge script script/deploy/01_DeployCore.s.sol:DeployCore \
--rpc-url " $RPC_138 " \
--private-key " $PRIVATE_KEY " \
--broadcast \
--with-gas-price " $GAS_PRICE_138 " \
-vvv 2>& 1 | tee " $out_01 " ; then
reg = $( grep "UNIVERSAL_ASSET_REGISTRY" " $out_01 " | tail -1 | grep -oE "0x[a-fA-F0-9]{40}" | head -1)
if [ -n " $reg " ] && [ -n " ${ CCIP_ROUTER :- } " ] ; then
export UNIVERSAL_ASSET_REGISTRY = " $reg "
forge script script/deploy/02_DeployBridges.s.sol:DeployBridges \
--rpc-url " $RPC_138 " \
--private-key " $PRIVATE_KEY " \
--broadcast \
--with-gas-price " $GAS_PRICE_138 " \
-vvv 2>& 1 || echo "02_DeployBridges failed or skipped."
fi
else
echo "01_DeployCore failed or skipped (may already be deployed)."
fi
echo -e " ${ GREEN } Chain 138 deploy phase finished. ${ NC } "
else
echo -e " ${ RED } Chain 138 balance insufficient for deploy (need more native ETH on 138). Fund deployer and re-run. ${ NC } "
exit 1
fi
else
echo "Run with --deploy to run deployment on Chain 138 (phased core)."
echo "Other chains: fund deployer with that chain's native token (MATIC, BNB, etc.) and set CCIP_* in .env for DeployAll."
fi