#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SMOM_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" PROJECT_ROOT="$(cd "$SMOM_ROOT/.." && pwd)" DRY_RUN=false START_RELAYS=false for arg in "$@"; do case "$arg" in --dry-run) DRY_RUN=true ;; --start-relays) START_RELAYS=true ;; *) echo "Unknown option: $arg" >&2 echo "Usage: $0 [--dry-run] [--start-relays]" >&2 exit 1 ;; esac done source "$SMOM_ROOT/scripts/load-env.sh" >/dev/null 2>&1 command -v cast >/dev/null 2>&1 || { echo "cast is required" >&2; exit 1; } command -v forge >/dev/null 2>&1 || { echo "forge is required" >&2; exit 1; } command -v jq >/dev/null 2>&1 || { echo "jq is required" >&2; exit 1; } DEPLOYER_ADDRESS="$(cast wallet address "$PRIVATE_KEY")" CHAIN138_SEND_ROUTER="${CCIP_ROUTER_CHAIN138:-0x42DAb7b888Dd382bD5Adcf9E038dBF1fD03b4817}" CHAIN138_RELAY_ROUTER="${CCIP_RELAY_ROUTER_CHAIN138:-}" AVAX_SEND_ROUTER="${CCIP_AVALANCHE_ROUTER:-}" AVAX_WETH_RELAY_ROUTER="${CCIP_RELAY_ROUTER_AVALANCHE:-0x2a0023Ad5ce1Ac6072B454575996DfFb1BB11b16}" AVAX_RELAY_ROUTER="${CCIP_RELAY_ROUTER_AVALANCHE_CW:-${CCIP_RELAY_ROUTER_AVALANCHE:-}}" AVAX_WETH_RELAY_BRIDGE="0x3f8C409C6072a2B6a4Ff17071927bA70F80c725F" CHAIN138_L1_BRIDGE="${CW_L1_BRIDGE_CHAIN138:-}" AVAX_CW_BRIDGE="${CW_BRIDGE_AVALANCHE:-}" CW_RESERVE_VERIFIER="${CW_RESERVE_VERIFIER_CHAIN138:-}" CW_STABLECOIN_RESERVE_VAULT_ADDR="${CW_STABLECOIN_RESERVE_VAULT:-${STABLECOIN_RESERVE_VAULT:-}}" CW_RESERVE_SYSTEM_ADDR="${CW_RESERVE_SYSTEM:-${RESERVE_SYSTEM:-}}" CW_CANONICAL_USDT_ADDR="${CW_CANONICAL_USDT:-${COMPLIANT_USDT_ADDRESS:-${CUSDT_ADDRESS_138:-}}}" CW_CANONICAL_USDC_ADDR="${CW_CANONICAL_USDC:-${COMPLIANT_USDC_ADDRESS:-${CUSDC_ADDRESS_138:-}}}" CW_USDT_RESERVE_ASSET_ADDR="${CW_USDT_RESERVE_ASSET:-${OFFICIAL_USDT_ADDRESS:-}}" CW_USDC_RESERVE_ASSET_ADDR="${CW_USDC_RESERVE_ASSET:-${OFFICIAL_USDC_ADDRESS:-}}" CW_MAX_OUTSTANDING_USDT_AVALANCHE="${CW_MAX_OUTSTANDING_USDT_AVALANCHE:-}" CW_MAX_OUTSTANDING_USDC_AVALANCHE="${CW_MAX_OUTSTANDING_USDC_AVALANCHE:-}" DRY_RUN_AVAX_SEND_ROUTER="0x0000000000000000000000000000000000004311" DRY_RUN_AVAX_RELAY_ROUTER="0x0000000000000000000000000000000000004313" DRY_RUN_CHAIN138_RELAY_ROUTER="0x0000000000000000000000000000000000000138" DRY_RUN_CHAIN138_L1_BRIDGE="0x0000000000000000000000000000000000001138" DRY_RUN_AVAX_CW_BRIDGE="0x0000000000000000000000000000000000004312" DRY_RUN_CHAIN138_RESERVE_VERIFIER="0x0000000000000000000000000000000000001139" LOCAL_RELAY_ROUTER_BYTECODE="" AVAX_WETH9_ADDRESS="${WETH9_AVALANCHE:-0xa4B9DD039565AeD9641D45b57061f99d9cA6Df08}" ZERO_ADDRESS="0x0000000000000000000000000000000000000000" log() { printf '[avax-cutover] %s\n' "$*" } redact_secrets() { local text="$1" if [[ -n "${PRIVATE_KEY:-}" ]]; then text="${text//${PRIVATE_KEY}/\$PRIVATE_KEY}" fi printf '%s\n' "$text" } format_command() { printf '%q ' "$@" printf '\n' } run_or_echo() { local cmd="$*" if $DRY_RUN; then redact_secrets "$cmd" else eval "$cmd" fi } run_command() { if $DRY_RUN; then redact_secrets "$(format_command "$@")" else "$@" fi } is_truthy() { case "${1:-}" in 1|true|TRUE|yes|YES|on|ON) return 0 ;; *) return 1 ;; esac } resolve_bool() { local value="${1:-}" local fallback="${2:-false}" if [[ -z "$value" ]]; then printf '%s\n' "$fallback" return fi if is_truthy "$value"; then printf 'true\n' else printf 'false\n' fi } bool_to_uint() { if [[ "${1:-false}" == "true" ]]; then printf '1\n' else printf '0\n' fi } DEFAULT_REQUIRE_VAULT_BACKING=false if [[ -n "$CW_STABLECOIN_RESERVE_VAULT_ADDR" ]]; then DEFAULT_REQUIRE_VAULT_BACKING=true fi DEFAULT_REQUIRE_RESERVE_SYSTEM_BALANCE=false if [[ -n "$CW_RESERVE_SYSTEM_ADDR" ]]; then DEFAULT_REQUIRE_RESERVE_SYSTEM_BALANCE=true fi DEFAULT_REQUIRE_TOKEN_OWNER_MATCH_VAULT=false if [[ -n "$CW_STABLECOIN_RESERVE_VAULT_ADDR" ]]; then DEFAULT_REQUIRE_TOKEN_OWNER_MATCH_VAULT=true fi CW_ATTACH_VERIFIER_TO_L1_BOOL="$(resolve_bool "${CW_ATTACH_VERIFIER_TO_L1:-}" "true")" CW_REQUIRE_VAULT_BACKING_BOOL="$(resolve_bool "${CW_REQUIRE_VAULT_BACKING:-}" "$DEFAULT_REQUIRE_VAULT_BACKING")" CW_REQUIRE_RESERVE_SYSTEM_BALANCE_BOOL="$(resolve_bool "${CW_REQUIRE_RESERVE_SYSTEM_BALANCE:-}" "$DEFAULT_REQUIRE_RESERVE_SYSTEM_BALANCE")" CW_REQUIRE_TOKEN_OWNER_MATCH_VAULT_BOOL="$(resolve_bool "${CW_REQUIRE_TOKEN_OWNER_MATCH_VAULT:-}" "$DEFAULT_REQUIRE_TOKEN_OWNER_MATCH_VAULT")" CW_FREEZE_AVAX_L2_CONFIG_BOOL="$(resolve_bool "${CW_FREEZE_AVAX_L2_CONFIG:-}" "false")" has_code() { local address="$1" local rpc="$2" [[ -n "$address" ]] || return 1 local code code="$(cast code "$address" --rpc-url "$rpc" 2>/dev/null || true)" [[ -n "$code" && "$code" != "0x" ]] } contract_call_succeeds() { local address="$1" local rpc="$2" shift 2 cast call "$address" "$@" --rpc-url "$rpc" >/dev/null 2>&1 } is_ccip_router_contract() { local address="$1" local rpc="$2" has_code "$address" "$rpc" || return 1 contract_call_succeeds "$address" "$rpc" "feeToken()(address)" && contract_call_succeeds "$address" "$rpc" "admin()(address)" && contract_call_succeeds "$address" "$rpc" "supportedChains(uint64)(bool)" 138 } is_relay_router_contract() { local address="$1" local rpc="$2" has_code "$address" "$rpc" || return 1 contract_call_succeeds "$address" "$rpc" "RELAYER_ROLE()(bytes32)" && contract_call_succeeds "$address" "$rpc" "authorizedBridges(address)(bool)" "$AVAX_WETH_RELAY_BRIDGE" } get_local_relay_router_bytecode() { if [[ -n "$LOCAL_RELAY_ROUTER_BYTECODE" ]]; then printf '%s\n' "$LOCAL_RELAY_ROUTER_BYTECODE" return fi LOCAL_RELAY_ROUTER_BYTECODE="$( cd "$SMOM_ROOT" && forge inspect contracts/relay/CCIPRelayRouter.sol:CCIPRelayRouter deployedBytecode 2>/dev/null | tr '[:upper:]' '[:lower:]' )" printf '%s\n' "$LOCAL_RELAY_ROUTER_BYTECODE" } is_current_relay_router_contract() { local address="$1" local rpc="$2" is_relay_router_contract "$address" "$rpc" || return 1 local local_code deployed_code local_code="$(get_local_relay_router_bytecode)" deployed_code="$(cast code "$address" --rpc-url "$rpc" 2>/dev/null | tr '[:upper:]' '[:lower:]')" [[ -n "$local_code" && -n "$deployed_code" && "$local_code" == "$deployed_code" ]] } is_cw_l1_bridge() { local address="$1" local rpc="$2" has_code "$address" "$rpc" || return 1 contract_call_succeeds "$address" "$rpc" "sendRouter()(address)" && contract_call_succeeds "$address" "$rpc" "receiveRouter()(address)" && contract_call_succeeds "$address" "$rpc" "feeToken()(address)" && contract_call_succeeds "$address" "$rpc" "admin()(address)" && contract_call_succeeds "$address" "$rpc" "supportedCanonicalToken(address)(bool)" "$COMPLIANT_USDT_ADDRESS" && contract_call_succeeds "$address" "$rpc" "maxOutstanding(address,uint64)(uint256)" "$COMPLIANT_USDT_ADDRESS" "$AVALANCHE_SELECTOR" && contract_call_succeeds "$address" "$rpc" "reserveVerifier()(address)" && contract_call_succeeds "$address" "$rpc" "destinations(address,uint64)((address,bool))" "$COMPLIANT_USDT_ADDRESS" "$AVALANCHE_SELECTOR" && ! contract_call_succeeds "$address" "$rpc" "canonicalToMirrored(address)(address)" "$COMPLIANT_USDT_ADDRESS" } is_cw_l2_bridge() { local address="$1" local rpc="$2" has_code "$address" "$rpc" || return 1 contract_call_succeeds "$address" "$rpc" "sendRouter()(address)" && contract_call_succeeds "$address" "$rpc" "receiveRouter()(address)" && contract_call_succeeds "$address" "$rpc" "feeToken()(address)" && contract_call_succeeds "$address" "$rpc" "admin()(address)" && contract_call_succeeds "$address" "$rpc" "tokenPairFrozen(address)(bool)" "$COMPLIANT_USDT_ADDRESS" && contract_call_succeeds "$address" "$rpc" "destinationFrozen(uint64)(bool)" 138 && contract_call_succeeds "$address" "$rpc" "destinations(uint64)((address,bool))" 138 && contract_call_succeeds "$address" "$rpc" "canonicalToMirrored(address)(address)" "$COMPLIANT_USDT_ADDRESS" } is_reserve_verifier_contract() { local address="$1" local rpc="$2" has_code "$address" "$rpc" || return 1 contract_call_succeeds "$address" "$rpc" "bridge()(address)" && contract_call_succeeds "$address" "$rpc" "OPERATOR_ROLE()(bytes32)" } send_cast() { local rpc="$1" local to="$2" local signature="$3" shift 3 local cmd=(cast send) local gas_price gas_price="$(cast gas-price --rpc-url "$rpc" 2>/dev/null || true)" if [[ "$gas_price" =~ ^[0-9]+$ && "$gas_price" -gt 0 ]]; then gas_price=$((gas_price + gas_price / 5 + 1000000)) cmd+=(--gas-price "$gas_price") fi cmd+=(--rpc-url "$rpc" --private-key "$PRIVATE_KEY" --legacy "$to" "$signature") if [[ "$#" -gt 0 ]]; then cmd+=("$@") fi run_command "${cmd[@]}" } set_env_value() { local file="$1" local key="$2" local value="$3" local escaped_value escaped_value="$(printf '%s' "$value" | sed -e 's/[\\/&]/\\&/g')" if grep -q "^${key}=" "$file"; then sed -i "s/^${key}=.*/${key}=${escaped_value}/" "$file" else printf '%s=%s\n' "$key" "$value" >>"$file" fi } deploy_from_broadcast() { local script_file="$1" local script_contract="$2" local rpc="$3" local chain_id="$4" local extra_env="$5" local broadcast_path="$SMOM_ROOT/broadcast/${script_file}/${chain_id}/run-latest.json" if $DRY_RUN; then redact_secrets "$extra_env forge script script/${script_file}:${script_contract} --rpc-url \"$rpc\" --broadcast --private-key \"$PRIVATE_KEY\" --legacy -vvv" return 0 fi ( cd "$SMOM_ROOT" eval "$extra_env forge script script/${script_file}:${script_contract} --rpc-url \"$rpc\" --broadcast --private-key \"\$PRIVATE_KEY\" --legacy -vvv >/tmp/${script_contract}.log" ) jq -r '.transactions[] | select(.transactionType == "CREATE") | .contractAddress' "$broadcast_path" | tail -1 } ensure_avax_router() { if is_ccip_router_contract "$AVAX_SEND_ROUTER" "$AVALANCHE_RPC_URL"; then log "Using existing AVAX send router $AVAX_SEND_ROUTER" return fi log "Deploying AVAX send router" if $DRY_RUN; then deploy_from_broadcast \ "DeployCCIPRouter.s.sol" \ "DeployCCIPRouter" \ "$AVALANCHE_RPC_URL" \ "43114" \ "CCIP_FEE_TOKEN=$LINK_TOKEN_AVALANCHE CCIP_BASE_FEE=0 CCIP_DATA_FEE_PER_BYTE=0" AVAX_SEND_ROUTER="$DRY_RUN_AVAX_SEND_ROUTER" return fi local addr addr="$( deploy_from_broadcast \ "DeployCCIPRouter.s.sol" \ "DeployCCIPRouter" \ "$AVALANCHE_RPC_URL" \ "43114" \ "CCIP_FEE_TOKEN=$LINK_TOKEN_AVALANCHE CCIP_BASE_FEE=0 CCIP_DATA_FEE_PER_BYTE=0" )" if ! $DRY_RUN; then AVAX_SEND_ROUTER="$addr" set_env_value "$SMOM_ROOT/.env" "CCIP_AVALANCHE_ROUTER" "$AVAX_SEND_ROUTER" fi } ensure_avax_relay_router() { if is_current_relay_router_contract "$AVAX_RELAY_ROUTER" "$AVALANCHE_RPC_URL"; then log "Using existing AVAX cW relay router $AVAX_RELAY_ROUTER" return fi log "Deploying AVAX cW relay router" if $DRY_RUN; then deploy_from_broadcast \ "DeployCCIPRelayRouterOnly.s.sol" \ "DeployCCIPRelayRouterOnly" \ "$AVALANCHE_RPC_URL" \ "43114" \ "RELAYER_ADDRESS=$RELAYER_ADDRESS" AVAX_RELAY_ROUTER="$DRY_RUN_AVAX_RELAY_ROUTER" return fi local addr addr="$( deploy_from_broadcast \ "DeployCCIPRelayRouterOnly.s.sol" \ "DeployCCIPRelayRouterOnly" \ "$AVALANCHE_RPC_URL" \ "43114" \ "RELAYER_ADDRESS=$RELAYER_ADDRESS" )" AVAX_RELAY_ROUTER="$addr" set_env_value "$SMOM_ROOT/.env" "CCIP_RELAY_ROUTER_AVALANCHE_CW" "$AVAX_RELAY_ROUTER" } ensure_chain138_relay_router() { if is_relay_router_contract "$CHAIN138_RELAY_ROUTER" "$RPC_URL_138"; then log "Using existing Chain 138 relay router $CHAIN138_RELAY_ROUTER" return fi log "Deploying Chain 138 relay router" if $DRY_RUN; then deploy_from_broadcast \ "DeployCCIPRelayRouterOnly.s.sol" \ "DeployCCIPRelayRouterOnly" \ "$RPC_URL_138" \ "138" \ "RELAYER_ADDRESS=$RELAYER_ADDRESS" CHAIN138_RELAY_ROUTER="$DRY_RUN_CHAIN138_RELAY_ROUTER" return fi local addr addr="$( deploy_from_broadcast \ "DeployCCIPRelayRouterOnly.s.sol" \ "DeployCCIPRelayRouterOnly" \ "$RPC_URL_138" \ "138" \ "RELAYER_ADDRESS=$RELAYER_ADDRESS" )" if ! $DRY_RUN; then CHAIN138_RELAY_ROUTER="$addr" set_env_value "$SMOM_ROOT/.env" "CCIP_RELAY_ROUTER_CHAIN138" "$CHAIN138_RELAY_ROUTER" fi } ensure_l1_bridge() { if is_cw_l1_bridge "$CHAIN138_L1_BRIDGE" "$RPC_URL_138"; then log "Using existing Chain 138 cW L1 bridge $CHAIN138_L1_BRIDGE" return fi log "Deploying Chain 138 cW L1 bridge" if $DRY_RUN; then deploy_from_broadcast \ "DeployCWMultiTokenBridgeL1.s.sol" \ "DeployCWMultiTokenBridgeL1" \ "$RPC_URL_138" \ "138" \ "CW_SEND_ROUTER=$CHAIN138_SEND_ROUTER CW_RECEIVE_ROUTER=$CHAIN138_RELAY_ROUTER CW_FEE_TOKEN=$LINK_TOKEN_CHAIN138" CHAIN138_L1_BRIDGE="$DRY_RUN_CHAIN138_L1_BRIDGE" return fi local addr addr="$( deploy_from_broadcast \ "DeployCWMultiTokenBridgeL1.s.sol" \ "DeployCWMultiTokenBridgeL1" \ "$RPC_URL_138" \ "138" \ "CW_SEND_ROUTER=$CHAIN138_SEND_ROUTER CW_RECEIVE_ROUTER=$CHAIN138_RELAY_ROUTER CW_FEE_TOKEN=$LINK_TOKEN_CHAIN138" )" if ! $DRY_RUN; then CHAIN138_L1_BRIDGE="$addr" set_env_value "$SMOM_ROOT/.env" "CW_L1_BRIDGE_CHAIN138" "$CHAIN138_L1_BRIDGE" fi } ensure_l2_bridge() { if is_cw_l2_bridge "$AVAX_CW_BRIDGE" "$AVALANCHE_RPC_URL"; then log "Using existing AVAX cW bridge $AVAX_CW_BRIDGE" return fi log "Deploying AVAX cW bridge" if $DRY_RUN; then deploy_from_broadcast \ "DeployCWMultiTokenBridgeL2.s.sol" \ "DeployCWMultiTokenBridgeL2" \ "$AVALANCHE_RPC_URL" \ "43114" \ "CW_SEND_ROUTER=$AVAX_SEND_ROUTER CW_RECEIVE_ROUTER=$AVAX_RELAY_ROUTER CW_FEE_TOKEN=$LINK_TOKEN_AVALANCHE" AVAX_CW_BRIDGE="$DRY_RUN_AVAX_CW_BRIDGE" return fi local addr addr="$( deploy_from_broadcast \ "DeployCWMultiTokenBridgeL2.s.sol" \ "DeployCWMultiTokenBridgeL2" \ "$AVALANCHE_RPC_URL" \ "43114" \ "CW_SEND_ROUTER=$AVAX_SEND_ROUTER CW_RECEIVE_ROUTER=$AVAX_RELAY_ROUTER CW_FEE_TOKEN=$LINK_TOKEN_AVALANCHE" )" if ! $DRY_RUN; then AVAX_CW_BRIDGE="$addr" set_env_value "$SMOM_ROOT/.env" "CW_BRIDGE_AVALANCHE" "$AVAX_CW_BRIDGE" fi } ensure_l2_receive_router() { local current current="$(cast call "$AVAX_CW_BRIDGE" "receiveRouter()(address)" --rpc-url "$AVALANCHE_RPC_URL" 2>/dev/null || true)" if [[ "${current,,}" == "${AVAX_RELAY_ROUTER,,}" ]]; then log "AVAX cW bridge already points at relay router $AVAX_RELAY_ROUTER" return fi send_cast "$AVALANCHE_RPC_URL" "$AVAX_CW_BRIDGE" "setReceiveRouter(address)" "$AVAX_RELAY_ROUTER" } ensure_supported_chain() { local rpc="$1" local router="$2" local selector="$3" local label="$4" local current current="$(cast call "$router" "supportedChains(uint64)(bool)" "$selector" --rpc-url "$rpc" 2>/dev/null || echo "false")" if [[ "$current" == "true" ]]; then log "$label already supports selector $selector" return fi send_cast "$rpc" "$router" "addSupportedChain(uint64)" "$selector" } ensure_authorized_bridge() { local rpc="$1" local router="$2" local bridge="$3" local current current="$(cast call "$router" "authorizedBridges(address)(bool)" "$bridge" --rpc-url "$rpc" 2>/dev/null || echo "false")" if [[ "$current" == "true" ]]; then log "Bridge $bridge already authorized on $router" return fi send_cast "$rpc" "$router" "authorizeBridge(address)" "$bridge" } ensure_relayer_role() { local rpc="$1" local router="$2" local role role="$(cast keccak "RELAYER_ROLE")" local current current="$(cast call "$router" "hasRole(bytes32,address)(bool)" "$role" "$RELAYER_ADDRESS" --rpc-url "$rpc" 2>/dev/null || echo "false")" if [[ "$current" == "true" ]]; then log "Relayer role already granted on $router" return fi send_cast "$rpc" "$router" "grantRelayerRole(address)" "$RELAYER_ADDRESS" } ensure_l2_token_pair() { local canonical="$1" local mirrored="$2" local current current="$(cast call "$AVAX_CW_BRIDGE" "canonicalToMirrored(address)(address)" "$canonical" --rpc-url "$AVALANCHE_RPC_URL" 2>/dev/null || echo "0x0")" if [[ "${current,,}" == "${mirrored,,}" ]]; then log "Token pair $canonical -> $mirrored already configured" return fi send_cast "$AVALANCHE_RPC_URL" "$AVAX_CW_BRIDGE" "configureTokenPair(address,address)" "$canonical" "$mirrored" } ensure_destination_l1() { local token="$1" local encoded encoded="$(cast call "$CHAIN138_L1_BRIDGE" "destinations(address,uint64)((address,bool))" "$token" "$AVALANCHE_SELECTOR" --rpc-url "$RPC_URL_138" 2>/dev/null || true)" encoded="${encoded,,}" if [[ "$encoded" == *"${AVAX_CW_BRIDGE,,}"* && "$encoded" == *"true"* ]]; then log "L1 destination already configured for $token" return fi send_cast "$RPC_URL_138" "$CHAIN138_L1_BRIDGE" "configureDestination(address,uint64,address,bool)" "$token" "$AVALANCHE_SELECTOR" "$AVAX_CW_BRIDGE" true } ensure_destination_l2() { local encoded encoded="$(cast call "$AVAX_CW_BRIDGE" "destinations(uint64)((address,bool))" 138 --rpc-url "$AVALANCHE_RPC_URL" 2>/dev/null || true)" encoded="${encoded,,}" if [[ "$encoded" == *"${CHAIN138_L1_BRIDGE,,}"* && "$encoded" == *"true"* ]]; then log "L2 destination back to Chain 138 already configured" return fi send_cast "$AVALANCHE_RPC_URL" "$AVAX_CW_BRIDGE" "configureDestination(uint64,address,bool)" 138 "$CHAIN138_L1_BRIDGE" true } ensure_supported_canonical_token() { local token="$1" local label="$2" local current current="$(cast call "$CHAIN138_L1_BRIDGE" "supportedCanonicalToken(address)(bool)" "$token" --rpc-url "$RPC_URL_138" 2>/dev/null || echo "false")" if [[ "$current" == "true" ]]; then log "$label already allowlisted on Chain 138 cW bridge" return fi send_cast "$RPC_URL_138" "$CHAIN138_L1_BRIDGE" "configureSupportedCanonicalToken(address,bool)" "$token" true } ensure_max_outstanding() { local token="$1" local amount="$2" local label="$3" if [[ -z "$amount" ]]; then log "Skipping maxOutstanding for $label: CW_MAX_OUTSTANDING not set" return fi local current current="$(cast call "$CHAIN138_L1_BRIDGE" "maxOutstanding(address,uint64)(uint256)" "$token" "$AVALANCHE_SELECTOR" --rpc-url "$RPC_URL_138" 2>/dev/null || true)" if [[ "$current" == "$amount" ]]; then log "maxOutstanding already set for $label: $amount" return fi send_cast "$RPC_URL_138" "$CHAIN138_L1_BRIDGE" "setMaxOutstanding(address,uint64,uint256)" "$token" "$AVALANCHE_SELECTOR" "$amount" } build_verifier_deploy_env() { local -a env_parts=() env_parts+=("CW_L1_BRIDGE=$CHAIN138_L1_BRIDGE") env_parts+=("CW_ATTACH_VERIFIER_TO_L1=$(bool_to_uint "$CW_ATTACH_VERIFIER_TO_L1_BOOL")") env_parts+=("CW_CANONICAL_USDT=$CW_CANONICAL_USDT_ADDR") env_parts+=("CW_CANONICAL_USDC=$CW_CANONICAL_USDC_ADDR") env_parts+=("CW_USDT_RESERVE_ASSET=${CW_USDT_RESERVE_ASSET_ADDR:-$ZERO_ADDRESS}") env_parts+=("CW_USDC_RESERVE_ASSET=${CW_USDC_RESERVE_ASSET_ADDR:-$ZERO_ADDRESS}") env_parts+=("CW_REQUIRE_VAULT_BACKING=$(bool_to_uint "$CW_REQUIRE_VAULT_BACKING_BOOL")") env_parts+=("CW_REQUIRE_RESERVE_SYSTEM_BALANCE=$(bool_to_uint "$CW_REQUIRE_RESERVE_SYSTEM_BALANCE_BOOL")") env_parts+=("CW_REQUIRE_TOKEN_OWNER_MATCH_VAULT=$(bool_to_uint "$CW_REQUIRE_TOKEN_OWNER_MATCH_VAULT_BOOL")") if [[ -n "$CW_STABLECOIN_RESERVE_VAULT_ADDR" ]]; then env_parts+=("CW_STABLECOIN_RESERVE_VAULT=$CW_STABLECOIN_RESERVE_VAULT_ADDR") fi if [[ -n "$CW_RESERVE_SYSTEM_ADDR" ]]; then env_parts+=("CW_RESERVE_SYSTEM=$CW_RESERVE_SYSTEM_ADDR") fi printf '%s ' "${env_parts[@]}" } ensure_verifier_bridge_binding() { local current current="$(cast call "$CW_RESERVE_VERIFIER" "bridge()(address)" --rpc-url "$RPC_URL_138" 2>/dev/null || true)" if [[ "${current,,}" == "${CHAIN138_L1_BRIDGE,,}" ]]; then return fi send_cast "$RPC_URL_138" "$CW_RESERVE_VERIFIER" "setBridge(address)" "$CHAIN138_L1_BRIDGE" } ensure_verifier_stablecoin_reserve_vault() { [[ -n "$CW_STABLECOIN_RESERVE_VAULT_ADDR" ]] || return local current current="$(cast call "$CW_RESERVE_VERIFIER" "stablecoinReserveVault()(address)" --rpc-url "$RPC_URL_138" 2>/dev/null || true)" if [[ "${current,,}" == "${CW_STABLECOIN_RESERVE_VAULT_ADDR,,}" ]]; then return fi send_cast "$RPC_URL_138" "$CW_RESERVE_VERIFIER" "setStablecoinReserveVault(address)" "$CW_STABLECOIN_RESERVE_VAULT_ADDR" } ensure_verifier_reserve_system() { [[ -n "$CW_RESERVE_SYSTEM_ADDR" ]] || return local current current="$(cast call "$CW_RESERVE_VERIFIER" "reserveSystem()(address)" --rpc-url "$RPC_URL_138" 2>/dev/null || true)" if [[ "${current,,}" == "${CW_RESERVE_SYSTEM_ADDR,,}" ]]; then return fi send_cast "$RPC_URL_138" "$CW_RESERVE_VERIFIER" "setReserveSystem(address)" "$CW_RESERVE_SYSTEM_ADDR" } ensure_verifier_token_config() { local canonical="$1" local reserve_asset="${2:-$ZERO_ADDRESS}" local label="$3" [[ -n "$canonical" ]] || { log "Skipping verifier config for $label: canonical token missing" return } if [[ "$CW_REQUIRE_RESERVE_SYSTEM_BALANCE_BOOL" == "true" && "$reserve_asset" == "$ZERO_ADDRESS" ]]; then log "Skipping verifier config for $label: reserve asset missing while reserve-system balance checks are enabled" return fi local current normalized current="$(cast call "$CW_RESERVE_VERIFIER" "tokenConfigs(address)(bool,address,bool,bool,bool)" "$canonical" --rpc-url "$RPC_URL_138" 2>/dev/null || true)" normalized="$(printf '%s' "$current" | tr '\n' ' ' | xargs 2>/dev/null || true)" local enabled current_reserve current_require_vault current_require_reserve current_require_owner read -r enabled current_reserve current_require_vault current_require_reserve current_require_owner <<<"$normalized" if [[ "$enabled" == "true" && "${current_reserve,,}" == "${reserve_asset,,}" && "$current_require_vault" == "$CW_REQUIRE_VAULT_BACKING_BOOL" && "$current_require_reserve" == "$CW_REQUIRE_RESERVE_SYSTEM_BALANCE_BOOL" && "$current_require_owner" == "$CW_REQUIRE_TOKEN_OWNER_MATCH_VAULT_BOOL" ]]; then log "Reserve verifier already configured for $label" return fi send_cast \ "$RPC_URL_138" \ "$CW_RESERVE_VERIFIER" \ "configureToken(address,address,bool,bool,bool)" \ "$canonical" \ "$reserve_asset" \ "$CW_REQUIRE_VAULT_BACKING_BOOL" \ "$CW_REQUIRE_RESERVE_SYSTEM_BALANCE_BOOL" \ "$CW_REQUIRE_TOKEN_OWNER_MATCH_VAULT_BOOL" } ensure_reserve_verifier() { local current current="$(cast call "$CHAIN138_L1_BRIDGE" "reserveVerifier()(address)" --rpc-url "$RPC_URL_138" 2>/dev/null || true)" if is_reserve_verifier_contract "$current" "$RPC_URL_138"; then CW_RESERVE_VERIFIER="$current" log "Using existing Chain 138 reserve verifier $CW_RESERVE_VERIFIER" elif is_reserve_verifier_contract "$CW_RESERVE_VERIFIER" "$RPC_URL_138"; then log "Using configured Chain 138 reserve verifier $CW_RESERVE_VERIFIER" else log "Deploying Chain 138 reserve verifier" if $DRY_RUN; then deploy_from_broadcast \ "DeployCWReserveVerifier.s.sol" \ "DeployCWReserveVerifier" \ "$RPC_URL_138" \ "138" \ "$(build_verifier_deploy_env)" CW_RESERVE_VERIFIER="$DRY_RUN_CHAIN138_RESERVE_VERIFIER" return fi local addr addr="$( deploy_from_broadcast \ "DeployCWReserveVerifier.s.sol" \ "DeployCWReserveVerifier" \ "$RPC_URL_138" \ "138" \ "$(build_verifier_deploy_env)" )" CW_RESERVE_VERIFIER="$addr" fi if ! $DRY_RUN; then set_env_value "$SMOM_ROOT/.env" "CW_RESERVE_VERIFIER_CHAIN138" "$CW_RESERVE_VERIFIER" fi if [[ "$CW_ATTACH_VERIFIER_TO_L1_BOOL" == "true" && "${current,,}" != "${CW_RESERVE_VERIFIER,,}" ]]; then send_cast "$RPC_URL_138" "$CHAIN138_L1_BRIDGE" "setReserveVerifier(address)" "$CW_RESERVE_VERIFIER" fi ensure_verifier_bridge_binding ensure_verifier_stablecoin_reserve_vault ensure_verifier_reserve_system ensure_verifier_token_config "$CW_CANONICAL_USDT_ADDR" "${CW_USDT_RESERVE_ASSET_ADDR:-$ZERO_ADDRESS}" "cUSDT" ensure_verifier_token_config "$CW_CANONICAL_USDC_ADDR" "${CW_USDC_RESERVE_ASSET_ADDR:-$ZERO_ADDRESS}" "cUSDC" } ensure_l2_token_pair_frozen() { local canonical="$1" local label="$2" local current current="$(cast call "$AVAX_CW_BRIDGE" "tokenPairFrozen(address)(bool)" "$canonical" --rpc-url "$AVALANCHE_RPC_URL" 2>/dev/null || echo "false")" if [[ "$current" == "true" ]]; then log "$label token pair already frozen on Avalanche" return fi send_cast "$AVALANCHE_RPC_URL" "$AVAX_CW_BRIDGE" "freezeTokenPair(address)" "$canonical" } ensure_l2_destination_frozen() { local current current="$(cast call "$AVAX_CW_BRIDGE" "destinationFrozen(uint64)(bool)" 138 --rpc-url "$AVALANCHE_RPC_URL" 2>/dev/null || echo "false")" if [[ "$current" == "true" ]]; then log "Avalanche destination back to Chain 138 already frozen" return fi send_cast "$AVALANCHE_RPC_URL" "$AVAX_CW_BRIDGE" "freezeDestination(uint64)" 138 } ensure_role() { local rpc="$1" local token="$2" local role_name="$3" local grantee="$4" local role role="$(cast keccak "$role_name")" local current current="$(cast call "$token" "hasRole(bytes32,address)(bool)" "$role" "$grantee" --rpc-url "$rpc" 2>/dev/null || echo "false")" if [[ "$current" == "true" ]]; then log "$role_name already granted on $token" return fi send_cast "$rpc" "$token" "grantRole(bytes32,address)" "$role" "$grantee" } write_relay_profiles() { local avax_profile="$SMOM_ROOT/services/relay/.env.avax" local avax_cw_profile="$SMOM_ROOT/services/relay/.env.avax-cw" local reverse_profile="$SMOM_ROOT/services/relay/.env.avax-to-138" if $DRY_RUN; then cat <"$avax_cw_profile" <"$reverse_profile" </tmp/relay-avax.log 2>&1 &) (cd "$SMOM_ROOT/services/relay" && nohup env RELAY_SKIP_ENV_LOCAL=1 ./start-relay.sh avax-cw >/tmp/relay-avax-cw.log 2>&1 &) (cd "$SMOM_ROOT/services/relay" && nohup env RELAY_SKIP_ENV_LOCAL=1 ./start-relay.sh avax-to-138 >/tmp/relay-avax-to-138.log 2>&1 &) } log "Deployer: $DEPLOYER_ADDRESS" log "Dry run: $DRY_RUN" ensure_avax_router ensure_avax_relay_router ensure_chain138_relay_router ensure_l1_bridge ensure_l2_bridge if ! $DRY_RUN; then ensure_supported_chain "$AVALANCHE_RPC_URL" "$AVAX_SEND_ROUTER" 138 "AVAX router" fi ensure_l2_receive_router ensure_authorized_bridge "$AVALANCHE_RPC_URL" "$AVAX_RELAY_ROUTER" "$AVAX_CW_BRIDGE" ensure_relayer_role "$AVALANCHE_RPC_URL" "$AVAX_RELAY_ROUTER" ensure_authorized_bridge "$RPC_URL_138" "$CHAIN138_RELAY_ROUTER" "$CHAIN138_L1_BRIDGE" ensure_relayer_role "$RPC_URL_138" "$CHAIN138_RELAY_ROUTER" ensure_l2_token_pair "$COMPLIANT_USDT_ADDRESS" "$CWUSDT_AVALANCHE" ensure_l2_token_pair "$COMPLIANT_USDC_ADDRESS" "$CWUSDC_AVALANCHE" ensure_role "$AVALANCHE_RPC_URL" "$CWUSDT_AVALANCHE" "MINTER_ROLE" "$AVAX_CW_BRIDGE" ensure_role "$AVALANCHE_RPC_URL" "$CWUSDT_AVALANCHE" "BURNER_ROLE" "$AVAX_CW_BRIDGE" ensure_role "$AVALANCHE_RPC_URL" "$CWUSDC_AVALANCHE" "MINTER_ROLE" "$AVAX_CW_BRIDGE" ensure_role "$AVALANCHE_RPC_URL" "$CWUSDC_AVALANCHE" "BURNER_ROLE" "$AVAX_CW_BRIDGE" ensure_supported_canonical_token "$CW_CANONICAL_USDT_ADDR" "cUSDT" ensure_supported_canonical_token "$CW_CANONICAL_USDC_ADDR" "cUSDC" ensure_max_outstanding "$CW_CANONICAL_USDT_ADDR" "$CW_MAX_OUTSTANDING_USDT_AVALANCHE" "cUSDT -> Avalanche" ensure_max_outstanding "$CW_CANONICAL_USDC_ADDR" "$CW_MAX_OUTSTANDING_USDC_AVALANCHE" "cUSDC -> Avalanche" ensure_destination_l1 "$COMPLIANT_USDT_ADDRESS" ensure_destination_l1 "$COMPLIANT_USDC_ADDRESS" ensure_destination_l2 ensure_reserve_verifier if [[ "$CW_FREEZE_AVAX_L2_CONFIG_BOOL" == "true" ]]; then ensure_l2_token_pair_frozen "$CW_CANONICAL_USDT_ADDR" "cUSDT" ensure_l2_token_pair_frozen "$CW_CANONICAL_USDC_ADDR" "cUSDC" ensure_l2_destination_frozen else log "Skipping Avalanche L2 freeze; set CW_FREEZE_AVAX_L2_CONFIG=1 to lock token pairs and destination" fi write_relay_profiles start_relays log "Summary:" log " AVAX send router: ${AVAX_SEND_ROUTER:-}" log " AVAX cW relay router: ${AVAX_RELAY_ROUTER:-}" log " Chain 138 relay router:${CHAIN138_RELAY_ROUTER:-}" log " Chain 138 L1 bridge: ${CHAIN138_L1_BRIDGE:-}" log " AVAX cW bridge: ${AVAX_CW_BRIDGE:-}" log " Chain 138 verifier: ${CW_RESERVE_VERIFIER:-}" log " Freeze AVAX L2 config: ${CW_FREEZE_AVAX_L2_CONFIG_BOOL}"