#!/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 <<EOF
# Would update $avax_profile:
DEST_RELAY_ROUTER=${AVAX_WETH_RELAY_ROUTER}
DEST_RELAY_BRIDGE_ALLOWLIST=${AVAX_WETH_RELAY_BRIDGE}

# Would write $avax_cw_profile:
RPC_URL_138=${RPC_URL_138}
CCIP_ROUTER_CHAIN138=${CHAIN138_SEND_ROUTER}
CCIPWETH9_BRIDGE_CHAIN138=${CHAIN138_L1_BRIDGE}
SOURCE_CHAIN_SELECTOR=138
DEST_CHAIN_NAME=Avalanche
DEST_CHAIN_ID=43114
DEST_RPC_URL=${AVALANCHE_RPC_URL}
DEST_CHAIN_SELECTOR=${AVALANCHE_SELECTOR}
DEST_RELAY_ROUTER=${AVAX_RELAY_ROUTER}
DEST_RELAY_BRIDGE=${AVAX_CW_BRIDGE}
DEST_WETH9_ADDRESS=${AVAX_WETH9_ADDRESS}
RELAYER_PRIVATE_KEY=\${PRIVATE_KEY}
RELAYER_ADDRESS=${RELAYER_ADDRESS}
START_BLOCK=latest
POLL_INTERVAL=5000
CONFIRMATION_BLOCKS=1
MAX_RETRIES=3
RETRY_DELAY=5000
LOG_LEVEL=info
DEST_RELAY_BRIDGE_ALLOWLIST=${AVAX_CW_BRIDGE}

# Would write $reverse_profile:
SOURCE_CHAIN_NAME=Avalanche
SOURCE_CHAIN_ID=43114
SOURCE_CHAIN_SELECTOR=${AVALANCHE_SELECTOR}
SOURCE_RPC_URL=https://api.avax.network/ext/bc/C/rpc
SOURCE_ROUTER_ADDRESS=${AVAX_SEND_ROUTER}
DEST_CHAIN_NAME=Chain 138
DEST_CHAIN_ID=138
DEST_CHAIN_SELECTOR=138
DEST_RPC_URL=${RPC_URL_138}
DEST_RELAY_ROUTER=${CHAIN138_RELAY_ROUTER}
DEST_RELAY_BRIDGE=${CHAIN138_L1_BRIDGE}
DEST_RELAY_BRIDGE_ALLOWLIST=${CHAIN138_L1_BRIDGE}
RELAYER_PRIVATE_KEY=\${PRIVATE_KEY}
RELAYER_ADDRESS=${RELAYER_ADDRESS}
RELAY_DEST_LEGACY_TX=1
RELAY_DEST_GAS_PRICE_BUMP_PCT=20
RELAY_DEST_GAS_PRICE_BUMP_WEI=1000000
EOF
    return
  fi

  set_env_value "$avax_profile" "DEST_RELAY_ROUTER" "$AVAX_WETH_RELAY_ROUTER"
  set_env_value "$avax_profile" "DEST_RELAY_BRIDGE_ALLOWLIST" "$AVAX_WETH_RELAY_BRIDGE"

  cat >"$avax_cw_profile" <<EOF
# Forward relay profile for non-prefunded AVAX cW minting.
RPC_URL_138=${RPC_URL_138}
CCIP_ROUTER_CHAIN138=${CHAIN138_SEND_ROUTER}
CCIPWETH9_BRIDGE_CHAIN138=${CHAIN138_L1_BRIDGE}
SOURCE_CHAIN_SELECTOR=138

DEST_CHAIN_NAME=Avalanche
DEST_CHAIN_ID=43114
DEST_RPC_URL=${AVALANCHE_RPC_URL}
DEST_CHAIN_SELECTOR=${AVALANCHE_SELECTOR}
DEST_RELAY_ROUTER=${AVAX_RELAY_ROUTER}
DEST_RELAY_BRIDGE=${AVAX_CW_BRIDGE}
DEST_WETH9_ADDRESS=${AVAX_WETH9_ADDRESS}

RELAYER_PRIVATE_KEY=\${PRIVATE_KEY}
RELAYER_ADDRESS=${RELAYER_ADDRESS}

START_BLOCK=latest
POLL_INTERVAL=5000
CONFIRMATION_BLOCKS=1
MAX_RETRIES=3
RETRY_DELAY=5000
LOG_LEVEL=info
DEST_RELAY_BRIDGE_ALLOWLIST=${AVAX_CW_BRIDGE}
EOF

  cat >"$reverse_profile" <<EOF
# Reverse relay profile for AVAX cW burns back to Chain 138.
SOURCE_CHAIN_NAME=Avalanche
SOURCE_CHAIN_ID=43114
SOURCE_CHAIN_SELECTOR=${AVALANCHE_SELECTOR}
SOURCE_RPC_URL=https://api.avax.network/ext/bc/C/rpc
SOURCE_ROUTER_ADDRESS=${AVAX_SEND_ROUTER}

DEST_CHAIN_NAME=Chain 138
DEST_CHAIN_ID=138
DEST_CHAIN_SELECTOR=138
DEST_RPC_URL=${RPC_URL_138}
DEST_RELAY_ROUTER=${CHAIN138_RELAY_ROUTER}
DEST_RELAY_BRIDGE=${CHAIN138_L1_BRIDGE}
DEST_RELAY_BRIDGE_ALLOWLIST=${CHAIN138_L1_BRIDGE}

RELAYER_PRIVATE_KEY=\${PRIVATE_KEY}
RELAYER_ADDRESS=${RELAYER_ADDRESS}

START_BLOCK=latest
POLL_INTERVAL=5000
CONFIRMATION_BLOCKS=1
MAX_RETRIES=3
RETRY_DELAY=5000
LOG_LEVEL=info
RELAY_DEST_LEGACY_TX=1
RELAY_DEST_GAS_PRICE_BUMP_PCT=20
RELAY_DEST_GAS_PRICE_BUMP_WEI=1000000
EOF
}

start_relays() {
  if ! $START_RELAYS; then
    return
  fi
  if $DRY_RUN; then
    echo "(cd \"$SMOM_ROOT/services/relay\" && RELAY_SKIP_ENV_LOCAL=1 ./start-relay.sh avax)"
    echo "(cd \"$SMOM_ROOT/services/relay\" && RELAY_SKIP_ENV_LOCAL=1 ./start-relay.sh avax-cw)"
    echo "(cd \"$SMOM_ROOT/services/relay\" && RELAY_SKIP_ENV_LOCAL=1 ./start-relay.sh avax-to-138)"
    return
  fi

  (cd "$SMOM_ROOT/services/relay" && nohup env RELAY_SKIP_ENV_LOCAL=1 ./start-relay.sh avax >/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:-<dry-run>}"
log "  AVAX cW relay router:  ${AVAX_RELAY_ROUTER:-<dry-run>}"
log "  Chain 138 relay router:${CHAIN138_RELAY_ROUTER:-<dry-run>}"
log "  Chain 138 L1 bridge:   ${CHAIN138_L1_BRIDGE:-<dry-run>}"
log "  AVAX cW bridge:        ${AVAX_CW_BRIDGE:-<dry-run>}"
log "  Chain 138 verifier:    ${CW_RESERVE_VERIFIER:-<not-set>}"
log "  Freeze AVAX L2 config: ${CW_FREEZE_AVAX_L2_CONFIG_BOOL}"
