feat: bridges, PMM, flash workflow, token-aggregation, and deployment docs

- CCIP/trustless bridge contracts, GRU tokens, DEX/PMM tests, reserve vault.
- Token-aggregation service routes, planner, chain config, relay env templates.
- Config snapshots and multi-chain deployment markdown updates.
- gitignore services/btc-intake/dist/ (tsc output); do not track dist.

Run forge build && forge test before deploy (large solc graph).

Made-with: Cursor
This commit is contained in:
defiQUG
2026-04-07 23:40:52 -07:00
parent 0fb7bba07b
commit 76aa419320
289 changed files with 28367 additions and 824 deletions

View File

@@ -0,0 +1,321 @@
#!/usr/bin/env bash
# Audit live cross-network funding routes for Chain 138 WETH9.
#
# This script distinguishes:
# - configured destination mappings on Chain 138
# - practical first-hop routes that actually work with the current relay model
# - mainnet-hub destinations that become useful only after mainnet is funded
#
# Important:
# - Chain 138 uses a custom router that emits MessageSent events but does not
# natively deliver into public-chain bridges.
# - A native-mapped destination on Chain 138 is therefore configuration signal,
# not proof of a live direct route.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
cd "$PROJECT_ROOT"
if [[ -f "$SCRIPT_DIR/../lib/deployment/dotenv.sh" ]]; then
# shellcheck disable=SC1090
source "$SCRIPT_DIR/../lib/deployment/dotenv.sh"
load_deployment_env --repo-root "$PROJECT_ROOT"
elif [[ -f "$PROJECT_ROOT/.env" ]]; then
set -a
# shellcheck disable=SC1090
source "$PROJECT_ROOT/.env"
set +a
fi
need_var() {
local name="$1"
[[ -n "${!name:-}" ]] || {
echo "Error: $name is required" >&2
exit 1
}
}
need_var PRIVATE_KEY
need_var RPC_URL_138
need_var ETHEREUM_MAINNET_RPC
need_var CCIPWETH9_BRIDGE_CHAIN138
need_var MAINNET_CCIP_WETH9_BRIDGE
need_var ETH_MAINNET_SELECTOR
DEPLOYER="$(cast wallet address "$PRIVATE_KEY" 2>/dev/null || true)"
[[ -n "$DEPLOYER" ]] || {
echo "Error: could not derive deployer from PRIVATE_KEY" >&2
exit 1
}
CHAIN138_SELECTOR_VALUE="${CHAIN138_SELECTOR:-138}"
CURRENT_138_WETH9="${CCIPWETH9_BRIDGE_CHAIN138,,}"
LEGACY_138_WETH9="${CCIPWETH9_BRIDGE_DIRECT_LEGACY:-0x971cD9D156f193df8051E48043C476e53ECd4693}"
LEGACY_138_WETH9="${LEGACY_138_WETH9,,}"
MAINNET_WETH9="$(cast call "$MAINNET_CCIP_WETH9_BRIDGE" 'weth9()(address)' --rpc-url "$ETHEREUM_MAINNET_RPC" | tail -n1)"
MAINNET_FEE_TOKEN="$(cast call "$MAINNET_CCIP_WETH9_BRIDGE" 'feeToken()(address)' --rpc-url "$ETHEREUM_MAINNET_RPC" | tail -n1)"
fmt_ether() {
local raw="${1:-0}"
raw="$(echo "$raw" | awk '{print $1}')"
cast from-wei "$raw" ether 2>/dev/null || echo "$raw"
}
contains_true() {
[[ "$1" == *", true)"* ]]
}
extract_addr() {
echo "$1" | grep -oE '0x[a-fA-F0-9]{40}' | head -n1 | tr '[:upper:]' '[:lower:]'
}
extract_enabled_addr() {
local raw="$1"
local addr
addr="$(extract_addr "$raw")"
if contains_true "$raw" && [[ -n "$addr" ]]; then
echo "$addr"
else
echo ""
fi
}
query_dest() {
local rpc="$1"
local bridge="$2"
local selector="$3"
if [[ -z "$rpc" || -z "$bridge" || -z "$selector" ]]; then
echo "UNSET"
return 0
fi
cast call "$bridge" 'destinations(uint64)((uint64,address,bool))' "$selector" --rpc-url "$rpc" 2>/dev/null || echo "ERROR"
}
quote_fee_state() {
local rpc="$1"
local bridge="$2"
local selector="$3"
if [[ -z "$rpc" || -z "$bridge" || -z "$selector" ]]; then
echo "n/a"
return 0
fi
if cast call "$bridge" 'calculateFee(uint64,uint256)(uint256)' "$selector" 4000000000000000 --rpc-url "$rpc" >/dev/null 2>&1; then
echo "quoted"
else
echo "blocked"
fi
}
balance_of() {
local rpc="$1"
local token="$2"
local wallet="$3"
if [[ -z "$rpc" || -z "$token" || -z "$wallet" ]]; then
echo "0"
return 0
fi
cast call "$token" 'balanceOf(address)(uint256)' "$wallet" --rpc-url "$rpc" 2>/dev/null | awk '{print $1}' || echo "0"
}
native_balance() {
local rpc="$1"
if [[ -z "$rpc" ]]; then
echo "0"
return 0
fi
cast balance "$DEPLOYER" --rpc-url "$rpc" 2>/dev/null || echo "0"
}
configured_class() {
local source_addr="$1"
local expected_bridge="$2"
if [[ -z "${expected_bridge,,}" ]]; then
echo "deploy-first"
elif [[ "$source_addr" == "${expected_bridge,,}" ]]; then
echo "native-mapped"
elif [[ -n "$source_addr" ]]; then
echo "relay-or-other"
else
echo "disabled"
fi
}
practical_route() {
local name="$1"
local hub_quote="$2"
case "$name" in
mainnet|bsc|avalanche) echo "relay-backed-first-hop" ;;
wemix) echo "deploy-first" ;;
*)
if [[ "$hub_quote" == "blocked" ]]; then
echo "mainnet-hub-blocked"
else
echo "via-mainnet-hub"
fi
;;
esac
}
relay_inventory_state() {
local balance="$1"
if [[ "$(echo "$balance > 0" | bc)" == "1" ]]; then
echo "present"
else
echo "empty"
fi
}
detail_line() {
local name="$1"
local configured="$2"
local mainnet_to_remote="$3"
local mainnet_quote="$4"
local remote_to_138="$5"
local relay_balance="$6"
local source_addr="$7"
local parts=()
case "$configured" in
native-mapped) parts+=("138 mapping matches native bridge") ;;
relay-or-other) parts+=("138 mapping points at non-native receiver $source_addr") ;;
deploy-first) parts+=("bridge not deployed in env") ;;
*) parts+=("138 mapping missing or disabled") ;;
esac
if contains_true "$mainnet_to_remote"; then
if [[ "$mainnet_quote" == "blocked" ]]; then
parts+=("mainnet hub mapping enabled but source fee quote reverts")
else
parts+=("mainnet hub mapping enabled")
fi
else
parts+=("mainnet hub mapping missing or disabled")
fi
local back_addr
back_addr="$(extract_enabled_addr "$remote_to_138")"
if [[ -n "$back_addr" ]]; then
if [[ "$back_addr" == "$CURRENT_138_WETH9" ]]; then
parts+=("return path points at current 138 bridge")
elif [[ "$back_addr" == "$LEGACY_138_WETH9" ]]; then
parts+=("return path still points at legacy 138 bridge")
else
parts+=("return path points at unexpected 138 bridge $back_addr")
fi
fi
case "$(practical_route "$name" "$mainnet_quote")" in
relay-backed-first-hop)
parts+=("practical first hop")
if [[ -n "$relay_balance" ]]; then
parts+=("relay inventory $(relay_inventory_state "$relay_balance")")
fi
;;
via-mainnet-hub)
parts+=("use after mainnet bootstrap")
;;
mainnet-hub-blocked)
parts+=("mainnet WETH9 public fan-out currently blocked at source bridge/router")
;;
deploy-first)
parts+=("deploy and seed before use")
;;
esac
local IFS='; '
echo "${parts[*]}"
}
echo "=== Chain 138 Funding Route Audit (WETH9 rail) ==="
echo "Date: $(date -u '+%Y-%m-%d %H:%M:%S UTC')"
echo "Deployer: $DEPLOYER"
echo "Current Chain 138 WETH9 bridge: $CURRENT_138_WETH9"
echo "Legacy Chain 138 WETH9 bridge: $LEGACY_138_WETH9"
echo "Mainnet WETH9 bridge: ${MAINNET_CCIP_WETH9_BRIDGE,,}"
echo ""
printf '%-10s | %-14s | %-14s | %-14s | %-16s | %-16s | %-22s | %-14s\n' \
"Chain" "Native" "Wrapped" "LINK" "138 mapping" "Mainnet hub" "Practical route" "Relay inv."
printf '%s\n' "--------------------------------------------------------------------------------------------------------------------------------------------"
CHAINS=(
"mainnet|$ETHEREUM_MAINNET_RPC|$ETH_MAINNET_SELECTOR|$MAINNET_CCIP_WETH9_BRIDGE|$MAINNET_WETH9|$MAINNET_FEE_TOKEN|${CCIP_RELAY_BRIDGE_MAINNET:-}"
"bsc|$BSC_MAINNET_RPC|$BSC_SELECTOR|$CCIPWETH9_BRIDGE_BSC|$WETH9_BSC|$LINK_TOKEN_BSC|0x886C6A4ABC064dbf74E7caEc460b7eeC31F1b78C"
"avalanche|$AVALANCHE_MAINNET_RPC|$AVALANCHE_SELECTOR|$CCIPWETH9_BRIDGE_AVALANCHE|$WETH9_AVALANCHE|$LINK_TOKEN_AVALANCHE|0x3f8C409C6072a2B6a4Ff17071927bA70F80c725F"
"gnosis|$GNOSIS_RPC|$GNOSIS_SELECTOR|$CCIPWETH9_BRIDGE_GNOSIS|$WETH9_GNOSIS|$LINK_TOKEN_GNOSIS|"
"cronos|$CRONOS_RPC|$CRONOS_SELECTOR|$CCIPWETH9_BRIDGE_CRONOS|$WETH9_CRONOS|$LINK_TOKEN_CRONOS|"
"celo|$CELO_RPC|${CELO_SELECTOR:-1346049177634351622}|$CCIPWETH9_BRIDGE_CELO|$WETH9_CELO|$LINK_TOKEN_CELO|"
"polygon|$POLYGON_MAINNET_RPC|$POLYGON_SELECTOR|$CCIPWETH9_BRIDGE_POLYGON|$WETH9_POLYGON|$LINK_TOKEN_POLYGON|"
"arbitrum|$ARBITRUM_MAINNET_RPC|$ARBITRUM_SELECTOR|$CCIPWETH9_BRIDGE_ARBITRUM|$WETH9_ARBITRUM|$LINK_TOKEN_ARBITRUM|"
"optimism|$OPTIMISM_MAINNET_RPC|$OPTIMISM_SELECTOR|$CCIPWETH9_BRIDGE_OPTIMISM|$WETH9_OPTIMISM|$LINK_TOKEN_OPTIMISM|"
"base|$BASE_MAINNET_RPC|$BASE_SELECTOR|$CCIPWETH9_BRIDGE_BASE|$WETH9_BASE|$LINK_TOKEN_BASE|"
"wemix|$WEMIX_RPC|${WEMIX_SELECTOR:-5142893604156789321}|$CCIPWETH9_BRIDGE_WEMIX|$WETH9_WEMIX|$LINK_TOKEN_WEMIX|"
)
for entry in "${CHAINS[@]}"; do
IFS='|' read -r name rpc selector expected_bridge wrapped_token link_token relay_bridge <<< "$entry"
native_raw="$(native_balance "$rpc")"
wrapped_raw="$(balance_of "$rpc" "$wrapped_token" "$DEPLOYER")"
link_raw="$(balance_of "$rpc" "$link_token" "$DEPLOYER")"
source_dest="$(query_dest "$RPC_URL_138" "$CCIPWETH9_BRIDGE_CHAIN138" "$selector")"
source_addr="$(extract_enabled_addr "$source_dest")"
mainnet_to_remote="$(query_dest "$ETHEREUM_MAINNET_RPC" "$MAINNET_CCIP_WETH9_BRIDGE" "$selector")"
mainnet_quote="n/a"
if [[ "$name" != "mainnet" ]]; then
mainnet_quote="$(quote_fee_state "$ETHEREUM_MAINNET_RPC" "$MAINNET_CCIP_WETH9_BRIDGE" "$selector")"
fi
mainnet_hub_flag="disabled"
if [[ "$name" == "mainnet" ]]; then
mainnet_hub_flag="n/a"
elif contains_true "$mainnet_to_remote"; then
mainnet_hub_flag="enabled/$mainnet_quote"
fi
relay_inv="n/a"
if [[ -n "$relay_bridge" && -n "$wrapped_token" ]]; then
relay_inv="$(fmt_ether "$(balance_of "$rpc" "$wrapped_token" "$relay_bridge")")"
fi
printf '%-10s | %-14s | %-14s | %-14s | %-16s | %-16s | %-22s | %-14s\n' \
"$name" \
"$(fmt_ether "$native_raw")" \
"$(fmt_ether "$wrapped_raw")" \
"$(fmt_ether "$link_raw")" \
"$(configured_class "$source_addr" "$expected_bridge")" \
"$mainnet_hub_flag" \
"$(practical_route "$name" "$mainnet_quote")" \
"$relay_inv"
done
echo ""
echo "Detail Notes"
echo "------------"
for entry in "${CHAINS[@]}"; do
IFS='|' read -r name rpc selector expected_bridge wrapped_token _link_token relay_bridge <<< "$entry"
source_dest="$(query_dest "$RPC_URL_138" "$CCIPWETH9_BRIDGE_CHAIN138" "$selector")"
source_addr="$(extract_enabled_addr "$source_dest")"
mainnet_to_remote="$(query_dest "$ETHEREUM_MAINNET_RPC" "$MAINNET_CCIP_WETH9_BRIDGE" "$selector")"
mainnet_quote="n/a"
if [[ "$name" != "mainnet" ]]; then
mainnet_quote="$(quote_fee_state "$ETHEREUM_MAINNET_RPC" "$MAINNET_CCIP_WETH9_BRIDGE" "$selector")"
fi
remote_to_138="$(query_dest "$rpc" "$expected_bridge" "$CHAIN138_SELECTOR_VALUE")"
relay_balance=""
if [[ -n "$relay_bridge" && -n "$wrapped_token" ]]; then
relay_balance="$(balance_of "$rpc" "$wrapped_token" "$relay_bridge")"
fi
echo "- $name: $(detail_line "$name" "$(configured_class "$source_addr" "$expected_bridge")" "$mainnet_to_remote" "$mainnet_quote" "$remote_to_138" "$relay_balance" "${source_addr:-disabled}")"
done
echo ""
echo "Important"
echo "---------"
echo "- This audit intentionally uses the WETH9 rail. Live WETH10 destination mappings are currently drifted from env and the mainnet WETH10 bridge is not wired to the public-chain destinations."
echo "- 'native-mapped' means the live Chain 138 destination address matches the expected native bridge address."
echo "- Because Chain 138 uses a custom event-emitting router, 'native-mapped' is configuration signal only, not proof of a direct live first-hop route."
echo "- Practical first hops today are the relay-backed lanes. The other public chains are best treated as mainnet-hub destinations unless a relay is added for them."

View File

@@ -50,10 +50,28 @@ for k in PRIVATE_KEY RPC_URL RPC_URL_138; do
check "$k" && echo " OK $k" || echo " MISS $k"
done
# PRIVATE_KEY format: 64 hex chars (no value printed)
# PRIVATE_KEY format: 64 hex chars (no value printed). Use last assignment (dotenv override). Full value after first '='.
if check "PRIVATE_KEY"; then
len=$(awk -F= '/^PRIVATE_KEY=/ { v=$2; gsub(/^0x/,"",v); print length(v) }' "$ENV_FILE" 2>/dev/null || echo "0")
[ "$len" = "64" ] && echo " PRIVATE_KEY format: 64-char hex" || echo " PRIVATE_KEY format: WARN (length=$len, expected 64)"
v="$(awk '
/^PRIVATE_KEY=/ { v=$0; sub(/^PRIVATE_KEY=/, "", v); last=v }
/^export[ \t]+PRIVATE_KEY=/ { v=$0; sub(/^export[ \t]+PRIVATE_KEY=/, "", v); last=v }
END { print last }
' "$ENV_FILE" 2>/dev/null || true)"
v="${v//$'\r'/}"
v="${v#\"}"
v="${v%\"}"
v="${v#\'}"
v="${v%\'}"
v="$(printf '%s' "$v" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
v="${v#0x}"
len=${#v}
if printf '%s' "$v" | grep -q '\${'; then
echo " PRIVATE_KEY format: SKIP (value references another var in file; length=$len — check resolved key after source)"
elif [ "$len" = "64" ] && printf '%s' "$v" | grep -qE '^[0-9a-fA-F]{64}$'; then
echo " PRIVATE_KEY format: 64-char hex"
else
echo " PRIVATE_KEY format: WARN (length=$len, expected 64 hex chars after optional 0x)"
fi
fi
echo ""

View File

@@ -0,0 +1,937 @@
#!/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}"

View File

@@ -0,0 +1,274 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SMOM_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
WITH_CUTOVER=false
DRY_RUN=false
for arg in "$@"; do
case "$arg" in
--with-cutover) WITH_CUTOVER=true ;;
--dry-run) DRY_RUN=true ;;
*)
echo "Unknown option: $arg" >&2
echo "Usage: $0 [--dry-run] [--with-cutover]" >&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; }
ENV_FILE="$SMOM_ROOT/.env"
DEPLOYER_ADDRESS="$(cast wallet address "$PRIVATE_KEY")"
RPC="$RPC_URL_138"
CW_VAULT="${CW_STABLECOIN_RESERVE_VAULT:-}"
TARGET_USDT_BACKING="${CW_VAULT_SEED_USDT_AMOUNT:-}"
TARGET_USDC_BACKING="${CW_VAULT_SEED_USDC_AMOUNT:-}"
log() {
printf '[cw-vault] %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_command() {
if $DRY_RUN; then
redact_secrets "$(format_command "$@")"
else
"$@"
fi
}
has_code() {
local address="$1"
[[ -n "$address" ]] || return 1
local code
code="$(cast code "$address" --rpc-url "$RPC" 2>/dev/null || true)"
[[ -n "$code" && "$code" != "0x" ]]
}
send_cast() {
local to="$1"
local signature="$2"
shift 2
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 extra_env="$3"
local broadcast_path="$SMOM_ROOT/broadcast/${script_file}/138/run-latest.json"
if $DRY_RUN; then
redact_secrets "$extra_env forge script script/reserve/${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/reserve/${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
}
call_uint() {
cast call "$1" "$2" "${@:3}" --rpc-url "$RPC"
}
call_addr() {
cast call "$1" "$2" --rpc-url "$RPC"
}
normalize_uint() {
printf '%s\n' "$1" | awk '{print $1}'
}
ensure_vault() {
if has_code "$CW_VAULT"; then
log "Using existing stablecoin reserve vault $CW_VAULT"
return
fi
log "Deploying stablecoin reserve vault"
if $DRY_RUN; then
deploy_from_broadcast "DeployStablecoinReserveVault.s.sol" "DeployStablecoinReserveVault" ""
CW_VAULT="0x0000000000000000000000000000000000000140"
return
fi
CW_VAULT="$(
deploy_from_broadcast "DeployStablecoinReserveVault.s.sol" "DeployStablecoinReserveVault" ""
)"
set_env_value "$ENV_FILE" "CW_STABLECOIN_RESERVE_VAULT" "$CW_VAULT"
}
ensure_official_balance() {
local token="$1"
local amount_needed="$2"
local label="$3"
local owner balance shortfall
owner="$(cast call "$token" "owner()(address)" --rpc-url "$RPC")"
balance="$(normalize_uint "$(cast call "$token" "balanceOf(address)(uint256)" "$DEPLOYER_ADDRESS" --rpc-url "$RPC")")"
if [[ "$balance" =~ ^[0-9]+$ ]] && (( balance >= amount_needed )); then
log "$label deployer balance already sufficient: $balance"
return
fi
shortfall=$((amount_needed - balance))
if [[ "${owner,,}" != "${DEPLOYER_ADDRESS,,}" ]]; then
echo "Deployer is not owner of $label token $token; cannot mint shortfall $shortfall" >&2
exit 1
fi
if (( shortfall > 0 )); then
send_cast "$token" "mint(address,uint256)" "$DEPLOYER_ADDRESS" "$shortfall"
fi
}
ensure_seeded_backing() {
local official_token="$1"
local reserve_getter="$2"
local seed_signature="$3"
local target_amount="$4"
local label="$5"
local current_reserve shortfall
if $DRY_RUN; then
current_reserve=0
else
current_reserve="$(normalize_uint "$(cast call "$CW_VAULT" "$reserve_getter" --rpc-url "$RPC")")"
fi
if [[ -z "$target_amount" ]]; then
target_amount="$(normalize_uint "$(cast call "$official_token" "totalSupply()(uint256)" --rpc-url "$RPC")")"
else
target_amount="$(normalize_uint "$target_amount")"
fi
if (( current_reserve >= target_amount )); then
log "$label reserve already seeded: $current_reserve"
return
fi
shortfall=$((target_amount - current_reserve))
ensure_official_balance "$official_token" "$shortfall" "$label"
send_cast "$official_token" "approve(address,uint256)" "$CW_VAULT" "$shortfall"
send_cast "$CW_VAULT" "$seed_signature" "$shortfall"
}
ensure_compliant_owner() {
local token="$1"
local label="$2"
local owner
if $DRY_RUN; then
owner="$DEPLOYER_ADDRESS"
else
owner="$(cast call "$token" "owner()(address)" --rpc-url "$RPC")"
fi
if [[ "${owner,,}" == "${CW_VAULT,,}" ]]; then
log "$label ownership already transferred to vault"
return
fi
if [[ "${owner,,}" != "${DEPLOYER_ADDRESS,,}" ]]; then
echo "$label owner is $owner, expected deployer or vault" >&2
exit 1
fi
send_cast "$token" "transferOwnership(address)" "$CW_VAULT"
}
verify_vault_state() {
local usdt_owner usdc_owner usdt_ratio usdc_ratio
usdt_owner="$(cast call "$COMPLIANT_USDT_ADDRESS" "owner()(address)" --rpc-url "$RPC")"
usdc_owner="$(cast call "$COMPLIANT_USDC_ADDRESS" "owner()(address)" --rpc-url "$RPC")"
usdt_ratio="$(cast call "$CW_VAULT" "getBackingRatio(address)(uint256,uint256,uint256)" "$COMPLIANT_USDT_ADDRESS" --rpc-url "$RPC" | tr '\n' ' ' | xargs)"
usdc_ratio="$(cast call "$CW_VAULT" "getBackingRatio(address)(uint256,uint256,uint256)" "$COMPLIANT_USDC_ADDRESS" --rpc-url "$RPC" | tr '\n' ' ' | xargs)"
log "cUSDT owner: $usdt_owner"
log "cUSDC owner: $usdc_owner"
log "cUSDT backing tuple: $usdt_ratio"
log "cUSDC backing tuple: $usdc_ratio"
}
log "Deployer: $DEPLOYER_ADDRESS"
log "Dry run: $DRY_RUN"
if [[ -z "$TARGET_USDT_BACKING" ]]; then
TARGET_USDT_BACKING="$(normalize_uint "$(cast call "$COMPLIANT_USDT_ADDRESS" "totalSupply()(uint256)" --rpc-url "$RPC")")"
fi
if [[ -z "$TARGET_USDC_BACKING" ]]; then
TARGET_USDC_BACKING="$(normalize_uint "$(cast call "$COMPLIANT_USDC_ADDRESS" "totalSupply()(uint256)" --rpc-url "$RPC")")"
fi
ensure_vault
if ! $DRY_RUN; then
set_env_value "$ENV_FILE" "CW_STABLECOIN_RESERVE_VAULT" "$CW_VAULT"
set_env_value "$ENV_FILE" "CW_REQUIRE_VAULT_BACKING" "1"
set_env_value "$ENV_FILE" "CW_REQUIRE_TOKEN_OWNER_MATCH_VAULT" "1"
set_env_value "$ENV_FILE" "CW_REQUIRE_RESERVE_SYSTEM_BALANCE" "0"
fi
ensure_seeded_backing "$OFFICIAL_USDT_ADDRESS" "usdtReserveBalance()(uint256)" "seedUSDTReserve(uint256)" "$TARGET_USDT_BACKING" "USDT"
ensure_seeded_backing "$OFFICIAL_USDC_ADDRESS" "usdcReserveBalance()(uint256)" "seedUSDCReserve(uint256)" "$TARGET_USDC_BACKING" "USDC"
ensure_compliant_owner "$COMPLIANT_USDT_ADDRESS" "cUSDT"
ensure_compliant_owner "$COMPLIANT_USDC_ADDRESS" "cUSDC"
if ! $DRY_RUN; then
verify_vault_state
fi
if $WITH_CUTOVER; then
if $DRY_RUN; then
echo "(cd \"$SMOM_ROOT\" && ./scripts/deployment/complete-nonprefunded-avax-cutover.sh --dry-run)"
else
(cd "$SMOM_ROOT" && ./scripts/deployment/complete-nonprefunded-avax-cutover.sh)
fi
fi

View File

@@ -2,10 +2,89 @@
# Deploy official DODO DVM (DVMFactory + deps) to Chain 138 via DODOEX/contractV2 Truffle,
# then deploy DVMFactoryAdapter (createDVM -> createDODOVendingMachine) via Forge and update .env.
# Requires: smom-dbis-138/.env with PRIVATE_KEY, RPC_URL_138
#
# Notes:
# - The vendored DODO V2 tree contains both Solidity 0.6.9 and 0.8.x sources.
# Truffle uses a single configured compiler, so this script temporarily hides the
# unrelated 0.8.x contracts during compile/migrate, then restores them on exit.
# - Use --compile-only to verify the official DVM stack compiles cleanly before broadcast.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
DODO_DIR="$PROJECT_ROOT/lib/dodo-contractV2"
COMPILE_ONLY=false
TRUFFLE_RESET=false
HIDDEN_ROOT="$DODO_DIR/.chain138-dvm-solc-excludes"
declare -a HIDDEN_SOL_FILES=()
usage() {
cat <<'EOF'
Usage: deploy-official-dvm-chain138.sh [--compile-only] [--reset]
Options:
--compile-only Compile the official DODO V2 DVM stack in DVM-only mode, then exit.
--reset Pass --reset to truffle migrate.
EOF
}
while [[ $# -gt 0 ]]; do
case "$1" in
--compile-only)
COMPILE_ONLY=true
shift
;;
--reset)
TRUFFLE_RESET=true
shift
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unknown argument: $1" >&2
usage >&2
exit 1
;;
esac
done
prepare_dvm_only_sources() {
local rel_path
rm -rf "$HIDDEN_ROOT"
mkdir -p "$HIDDEN_ROOT"
while IFS= read -r rel_path; do
[[ -z "$rel_path" ]] && continue
mkdir -p "$HIDDEN_ROOT/$(dirname "$rel_path")"
mv "$DODO_DIR/$rel_path" "$HIDDEN_ROOT/$rel_path"
HIDDEN_SOL_FILES+=("$rel_path")
done < <(
cd "$DODO_DIR" &&
find contracts -type f -name '*.sol' -print0 |
xargs -0 grep -El 'pragma solidity (\^?0\.8|>=0\.8)' |
sort
)
if [[ ${#HIDDEN_SOL_FILES[@]} -gt 0 ]]; then
echo "Temporarily hiding ${#HIDDEN_SOL_FILES[@]} Solidity 0.8.x sources for Chain 138 DVM-only compile..."
fi
}
restore_hidden_sources() {
local rel_path
if [[ -d "$HIDDEN_ROOT" ]]; then
while IFS= read -r rel_path; do
[[ -z "$rel_path" ]] && continue
mkdir -p "$DODO_DIR/$(dirname "$rel_path")"
mv "$HIDDEN_ROOT/$rel_path" "$DODO_DIR/$rel_path"
done < <(cd "$HIDDEN_ROOT" && find contracts -type f -name '*.sol' | sort)
rm -rf "$HIDDEN_ROOT"
fi
}
trap restore_hidden_sources EXIT
cd "$PROJECT_ROOT"
# Load .env via dotenv (RPC CR/LF trim). Fallback: raw source.
if [[ -f "$SCRIPT_DIR/../lib/deployment/dotenv.sh" ]]; then
@@ -52,12 +131,27 @@ export privKey="${PRIVATE_KEY#0x}"
export RPC_URL_138
export GAS_PRICE_138="${GAS_PRICE_138:-1000000000}"
prepare_dvm_only_sources
echo "=== Compiling official DODO V2 DVM-only subset for Chain 138 ==="
(cd "$DODO_DIR" && rm -rf build/contracts && npx truffle compile)
if [[ "$COMPILE_ONLY" == "true" ]]; then
echo "DODO V2 DVM-only compile completed successfully."
exit 0
fi
TRUFFLE_RESET_ARGS=()
if [[ "$TRUFFLE_RESET" == "true" ]]; then
TRUFFLE_RESET_ARGS+=(--reset)
fi
# Deploy Migrations (required for Truffle), then DVM stack only
echo "=== Running Truffle migration 1 (Migrations) on Chain 138 ==="
(cd "$DODO_DIR" && npx truffle migrate -f 1 --to 1 --network chain138) || true
(cd "$DODO_DIR" && npx truffle migrate -f 1 --to 1 --network chain138 "${TRUFFLE_RESET_ARGS[@]}") || true
echo "=== Running Truffle migration 9 (DVM stack) on Chain 138 ==="
(cd "$DODO_DIR" && npx truffle migrate -f 9 --to 9 --network chain138)
(cd "$DODO_DIR" && npx truffle migrate -f 9 --to 9 --network chain138 "${TRUFFLE_RESET_ARGS[@]}")
# Parse DVMFactory address from Truffle build (network id 138 as string or number)
DVM_FACTORY_ADDRESS=$(cd "$DODO_DIR" && node -e "
@@ -84,7 +178,18 @@ forge script script/dex/DeployDVMFactoryAdapter.s.sol:DeployDVMFactoryAdapter \
--rpc-url "$RPC_URL_138" --broadcast --private-key "$PRIVATE_KEY" --legacy
# Extract adapter address from broadcast (or script output)
ADAPTER_ADDRESS=$(grep -o '"contractAddress":"0x[^"]*"' "$PROJECT_ROOT/broadcast/DeployDVMFactoryAdapter.s.sol/138/"*run-latest.json 2>/dev/null | tail -1 | sed 's/.*"0x/0x/;s/".*//') || true
ADAPTER_BROADCAST="$PROJECT_ROOT/broadcast/DeployDVMFactoryAdapter.s.sol/138/run-latest.json"
ADAPTER_ADDRESS=$(
node -e "
const fs = require('fs');
const path = process.argv[1];
if (!fs.existsSync(path)) process.exit(1);
const j = JSON.parse(fs.readFileSync(path, 'utf8'));
const tx = (j.transactions || []).find((entry) => entry.contractName === 'DVMFactoryAdapter' && entry.contractAddress);
if (!tx) process.exit(1);
console.log(tx.contractAddress);
" "$ADAPTER_BROADCAST" 2>/dev/null
) || true
if [[ -z "$ADAPTER_ADDRESS" ]]; then
echo "Set DODO_VENDING_MACHINE_ADDRESS to the DVMFactoryAdapter address printed above."
exit 0

View File

@@ -11,6 +11,17 @@ source "$SCRIPT_DIR/../lib/deployment/dotenv.sh"
source "$SCRIPT_DIR/../lib/deployment/prompts.sh"
load_deployment_env
first_set_env() {
local key
for key in "$@"; do
if [[ -n "${!key:-}" ]]; then
printf '%s' "${!key}"
return 0
fi
done
return 1
}
parse_chain_filter "$@"
if [[ ${#CHAIN_FILTER[@]} -eq 0 && -n "${DEPLOY_PMM_L2S_FILTER:-}" ]]; then
CHAIN_FILTER=()
@@ -42,22 +53,35 @@ for entry in "${CHAINS[@]}"; do
dvm_var="${name}_DODO_VENDING_MACHINE_ADDRESS"
usdt_var="${name}_OFFICIAL_USDT_ADDRESS"
usdc_var="${name}_OFFICIAL_USDC_ADDRESS"
usdt_var_alt="OFFICIAL_USDT_${name}"
usdc_var_alt="OFFICIAL_USDC_${name}"
cusdt_var="${name}_COMPLIANT_USDT_ADDRESS"
cusdc_var="${name}_COMPLIANT_USDC_ADDRESS"
cusdt_var_alt="COMPLIANT_USDT_${name}"
cusdc_var_alt="COMPLIANT_USDC_${name}"
# Per-chain cUSDT/cUSDC (optional): CUSDT_ADDRESS_<chainId> / CUSDC_ADDRESS_<chainId> or POLYGON_COMPLIANT_USDT_ADDRESS etc.
cusdt_chain="CUSDT_ADDRESS_${chain_id}"
cusdc_chain="CUSDC_ADDRESS_${chain_id}"
dvm="${!dvm_var:-$DODO_VENDING_MACHINE_ADDRESS}"
usdt="${!usdt_var:-$OFFICIAL_USDT_ADDRESS}"
usdc="${!usdc_var:-$OFFICIAL_USDC_ADDRESS}"
compliant_usdt="${!cusdt_var:-${!cusdt_chain:-$usdt}}"
compliant_usdc="${!cusdc_var:-${!cusdc_chain:-$usdc}}"
dvm="$(first_set_env "$dvm_var" "DODO_VENDING_MACHINE_ADDRESS" || true)"
usdt="$(first_set_env "$usdt_var" "$usdt_var_alt" "OFFICIAL_USDT_ADDRESS" || true)"
usdc="$(first_set_env "$usdc_var" "$usdc_var_alt" "OFFICIAL_USDC_ADDRESS" || true)"
compliant_usdt="$(first_set_env "$cusdt_var" "$cusdt_var_alt" "$cusdt_chain" || true)"
compliant_usdc="$(first_set_env "$cusdc_var" "$cusdc_var_alt" "$cusdc_chain" || true)"
compliant_usdt="${compliant_usdt:-$usdt}"
compliant_usdc="${compliant_usdc:-$usdc}"
if [[ -z "$dvm" ]] || [[ -z "$usdt" ]] || [[ -z "$usdc" ]]; then
echo "Skip $name: set ${dvm_var} (or DODO_VENDING_MACHINE_ADDRESS), ${usdt_var}, ${usdc_var} (or OFFICIAL_USDT/USDC_ADDRESS)"
echo "Skip $name: set ${dvm_var} (or DODO_VENDING_MACHINE_ADDRESS), ${usdt_var}/${usdt_var_alt}, ${usdc_var}/${usdc_var_alt} (or OFFICIAL_USDT/USDC_ADDRESS)"
continue
fi
if [[ -z "${!usdt_var:-}" && -z "${!usdt_var_alt:-}" ]]; then
echo "WARN $name: using global OFFICIAL_USDT_ADDRESS fallback; set ${usdt_var} or ${usdt_var_alt} for chain-specific safety"
fi
if [[ -z "${!usdc_var:-}" && -z "${!usdc_var_alt:-}" ]]; then
echo "WARN $name: using global OFFICIAL_USDC_ADDRESS fallback; set ${usdc_var} or ${usdc_var_alt} for chain-specific safety"
fi
echo "=== Deploying DODOPMMIntegration on $name (chain $chain_id) ==="
DODO_VENDING_MACHINE_ADDRESS="$dvm" \
OFFICIAL_USDT_ADDRESS="$usdt" \

View File

@@ -77,7 +77,9 @@ done
# Chain ID : Name : RPC env var (primary)
# DeployAll.s.sol supports: 1, 25, 56, 137, 100, 43114, 8453, 42161, 10. 651940 = env validation only.
ALL_CHAINS="1:Mainnet:ETH_MAINNET_RPC_URL 25:Cronos:CRONOS_RPC_URL 56:BSC:BSC_RPC_URL 137:Polygon:POLYGON_RPC_URL 100:Gnosis:GNOSIS_RPC_URL 43114:Avalanche:AVALANCHE_RPC_URL 8453:Base:BASE_RPC_URL 42161:Arbitrum:ARBITRUM_RPC_URL 10:Optimism:OPTIMISM_RPC_URL 651940:ALL:CHAIN_651940_RPC"
# Celo / Wemix are included here for c* / cW* deployment even though their WETH/bridge deploy
# flow is handled separately via deploy-bridges-config-ready-chains.sh.
ALL_CHAINS="1:Mainnet:ETH_MAINNET_RPC_URL 25:Cronos:CRONOS_RPC_URL 56:BSC:BSC_RPC_URL 137:Polygon:POLYGON_RPC_URL 100:Gnosis:GNOSIS_RPC_URL 43114:Avalanche:AVALANCHE_RPC_URL 8453:Base:BASE_RPC_URL 42161:Arbitrum:ARBITRUM_RPC_URL 10:Optimism:OPTIMISM_RPC_URL 42220:Celo:CELO_RPC 1111:Wemix:WEMIX_RPC 651940:ALL:CHAIN_651940_RPC"
# Fallback RPC env names (some scripts use different names)
fallback_rpc() {
@@ -95,6 +97,8 @@ fallback_rpc() {
8453) rpc="${BASE_MAINNET_RPC:-}";;
42161) rpc="${ARBITRUM_MAINNET_RPC:-}";;
10) rpc="${OPTIMISM_MAINNET_RPC:-}";;
42220) rpc="${CELO_RPC:-${CELO_MAINNET_RPC:-}}";;
1111) rpc="${WEMIX_RPC:-${WEMIX_MAINNET_RPC:-}}";;
651940) rpc="${CHAIN_651940_RPC:-${ALL_MAINNET_RPC:-}}";;
esac
fi
@@ -130,6 +134,10 @@ run_deploy_all() {
echo " Skip $name (chain 651940): DeployAll not supported; env validation only."
return 0
fi
if [[ "$chain_id" == "42220" || "$chain_id" == "1111" ]]; then
echo " Skip $name (chain $chain_id): WETH/bridge deploy handled by deploy-bridges-config-ready-chains.sh; c*/cW* phases may still run."
return 0
fi
local rpc
rpc=$(fallback_rpc "$chain_id" "$rpc_var")
if [[ -z "$rpc" ]]; then
@@ -230,9 +238,13 @@ run_deploy_cw() {
rpc=$(fallback_rpc "$chain_id" "$rpc_var")
if [[ -z "$rpc" ]]; then return 0; fi
local bridge_var="CW_BRIDGE_${name^^}"
local ccip_bridge_var="CCIPWETH9_BRIDGE_${name^^}"
local bridge="${!bridge_var:-${CW_BRIDGE_ADDRESS:-}}"
if [[ -z "$bridge" || "$bridge" == "0x0000000000000000000000000000000000000000" ]]; then
bridge="${!ccip_bridge_var:-}"
fi
if [[ -z "$bridge" || "$bridge" == "0x"*"0000000000000000000000000000000000000000" ]]; then
echo " Skip cW* on $name (chain $chain_id): set CW_BRIDGE_ADDRESS or CW_BRIDGE_${name^^} in .env."
echo " Skip cW* on $name (chain $chain_id): set CW_BRIDGE_ADDRESS / CW_BRIDGE_${name^^} or deploy/configure CCIPWETH9_BRIDGE_${name^^}."
return 0
fi
local cw_var="CWUSDT_${name^^}"

View File

@@ -3,7 +3,10 @@ set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SMOM_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
PROJECT_ROOT="$(cd "$SMOM_ROOT/.." && pwd)"
PROJECT_ENV_LOADER="$PROJECT_ROOT/scripts/lib/load-project-env.sh"
ENV_FILE="$SMOM_ROOT/.env"
ENV_SOURCE="<none>"
RUN_FORGE_DRY_RUN=0
RUN_TIMEOUT_SECONDS="${RUN_TIMEOUT_SECONDS:-90}"
VERBOSITY="${VERBOSITY:--vv}"
@@ -35,11 +38,17 @@ CHAIN138_USDT_DEFAULT="0x004b63A7B5b0E06f6bB6adb4a5F9f590BF3182D1"
CHAIN138_USDC_DEFAULT="0x71D6687F38b93CCad569Fa6352c876eea967201b"
DAI_PLACEHOLDER_DEFAULT="0x6B175474E89094C44Da98b954EedeAC495271d0F"
if [[ -f "$ENV_FILE" ]]; then
if [[ -f "$PROJECT_ENV_LOADER" ]]; then
export PROJECT_ROOT
# shellcheck disable=SC1090
source "$PROJECT_ENV_LOADER"
ENV_SOURCE="$PROJECT_ENV_LOADER"
elif [[ -f "$ENV_FILE" ]]; then
set -a
# shellcheck disable=SC1090
source "$ENV_FILE"
set +a
ENV_SOURCE="$ENV_FILE"
fi
RPC_URL_138="${RPC_URL_138:-http://192.168.11.211:8545}"
@@ -97,22 +106,25 @@ run_forge_dry_run() {
)
local exit_code=0
echo "Running targeted build warm-up"
forge build contracts/bridge/trustless/EnhancedSwapRouter.sol script/bridge/trustless/DeployEnhancedSwapRouter.s.sol
echo ""
echo "Running sourced non-broadcast forge script"
echo "Timeout: ${RUN_TIMEOUT_SECONDS}s"
echo "Verbosity: $VERBOSITY"
echo ""
set +e
if command -v timeout >/dev/null 2>&1; then
timeout "${RUN_TIMEOUT_SECONDS}s" "${forge_cmd[@]}"
exit_code=$?
else
"${forge_cmd[@]}"
exit_code=$?
fi
(
cd "$SMOM_ROOT" || exit 1
echo "Running targeted build warm-up"
forge build contracts/bridge/trustless/EnhancedSwapRouter.sol script/bridge/trustless/DeployEnhancedSwapRouter.s.sol
echo ""
echo "Running sourced non-broadcast forge script"
echo "Working directory: $SMOM_ROOT"
echo "Timeout: ${RUN_TIMEOUT_SECONDS}s"
echo "Verbosity: $VERBOSITY"
echo ""
if command -v timeout >/dev/null 2>&1; then
timeout "${RUN_TIMEOUT_SECONDS}s" "${forge_cmd[@]}"
else
"${forge_cmd[@]}"
fi
)
exit_code=$?
set -e
if (( exit_code == 124 )); then
@@ -136,7 +148,10 @@ run_forge_dry_run() {
echo "=== EnhancedSwapRouter Chain 138 Dry Run ==="
echo "Project root: $SMOM_ROOT"
echo "Repository root: $PROJECT_ROOT"
echo "Project env loader: $PROJECT_ENV_LOADER"
echo "Env file: $ENV_FILE"
echo "Env source: $ENV_SOURCE"
echo ""
if (( ${#missing[@]} > 0 )); then
@@ -145,7 +160,7 @@ if (( ${#missing[@]} > 0 )); then
echo " - $item"
done
echo ""
echo "Set them in $ENV_FILE or export them in your shell, then rerun."
echo "Set them in $ENV_FILE, the repo root env loaded by $PROJECT_ENV_LOADER, or export them in your shell, then rerun."
exit 1
fi
@@ -215,7 +230,11 @@ echo " - cEURT <-> cXAUC"
echo ""
echo "Exact dry-run command"
echo "cd \"$SMOM_ROOT\" && source .env && forge script script/bridge/trustless/DeployEnhancedSwapRouter.s.sol:DeployEnhancedSwapRouter --rpc-url \"$RPC_URL_138\" --private-key \"\$PRIVATE_KEY\""
if [[ -f "$PROJECT_ENV_LOADER" ]]; then
echo "cd \"$PROJECT_ROOT\" && source scripts/lib/load-project-env.sh && cd smom-dbis-138 && forge script script/bridge/trustless/DeployEnhancedSwapRouter.s.sol:DeployEnhancedSwapRouter --rpc-url \"$RPC_URL_138\" --private-key \"\$PRIVATE_KEY\""
else
echo "cd \"$SMOM_ROOT\" && source .env && forge script script/bridge/trustless/DeployEnhancedSwapRouter.s.sol:DeployEnhancedSwapRouter --rpc-url \"$RPC_URL_138\" --private-key \"\$PRIVATE_KEY\""
fi
echo ""
echo "Example minimal exports before dry-run"
cat <<EOF

View File

@@ -0,0 +1,196 @@
#!/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)"
PROJECT_ENV_LOADER="$PROJECT_ROOT/scripts/lib/load-project-env.sh"
ENV_FILE="$SMOM_ROOT/.env"
ENV_SOURCE="<none>"
RUN_FORGE_DRY_RUN=0
RUN_TIMEOUT_SECONDS="${RUN_TIMEOUT_SECONDS:-120}"
VERBOSITY="${VERBOSITY:--vv}"
while [[ $# -gt 0 ]]; do
case "$1" in
--run)
RUN_FORGE_DRY_RUN=1
shift
;;
--timeout-seconds)
RUN_TIMEOUT_SECONDS="${2:?missing value for --timeout-seconds}"
shift 2
;;
--verbosity)
VERBOSITY="${2:?missing value for --verbosity}"
shift 2
;;
*)
echo "Unknown argument: $1" >&2
echo "Usage: $0 [--run] [--timeout-seconds <seconds>] [--verbosity <-v|-vv|-vvv|...>]" >&2
exit 1
;;
esac
done
if [[ -f "$PROJECT_ENV_LOADER" ]]; then
export PROJECT_ROOT
# shellcheck disable=SC1090
source "$PROJECT_ENV_LOADER"
ENV_SOURCE="$PROJECT_ENV_LOADER"
elif [[ -f "$ENV_FILE" ]]; then
set -a
# shellcheck disable=SC1090
source "$ENV_FILE"
set +a
ENV_SOURCE="$ENV_FILE"
fi
RPC_URL_138="${RPC_URL_138:-http://192.168.11.211:8545}"
WETH="${WETH:-0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2}"
OFFICIAL_USDT_ADDRESS="${OFFICIAL_USDT_ADDRESS:-0x004b63A7B5b0E06f6bB6adb4a5F9f590BF3182D1}"
OFFICIAL_USDC_ADDRESS="${OFFICIAL_USDC_ADDRESS:-0x71D6687F38b93CCad569Fa6352c876eea967201b}"
DODO_PMM_PROVIDER_ADDRESS="${DODO_PMM_PROVIDER_ADDRESS:-${DODO_PMM_PROVIDER:-}}"
show_var() {
local name="$1" value="$2" note="${3:-}"
printf ' %-28s %s' "$name" "$value"
[[ -n "$note" ]] && printf ' (%s)' "$note"
printf '\n'
}
show_secret_var() {
local name="$1" value="${2:-}" note="${3:-}"
local display="<unset>"
if [[ -n "$value" ]]; then
display="<set>"
fi
printf ' %-28s %s' "$name" "$display"
[[ -n "$note" ]] && printf ' (%s)' "$note"
printf '\n'
}
probe_support() {
local token_in="$1" token_out="$2" provider="$3" label="$4"
local result
result="$(cast call "$provider" "supportsTokenPair(address,address)(bool)" "$token_in" "$token_out" --rpc-url "$RPC_URL_138" 2>/dev/null || true)"
if [[ "$result" == "true" ]]; then
echo " OK: $label"
return 0
fi
echo " MISSING: $label"
return 1
}
probe_quote() {
local token_in="$1" token_out="$2" provider="$3" amount="$4" label="$5"
local result amount_out
result="$(cast call "$provider" "getQuote(address,address,uint256)(uint256,uint256)" "$token_in" "$token_out" "$amount" --rpc-url "$RPC_URL_138" 2>/dev/null || true)"
amount_out="$(awk 'NR==1 {print $1}' <<<"$result")"
if [[ -n "$amount_out" && "$amount_out" != "0" ]]; then
echo " OK: $label => $amount_out"
return 0
fi
echo " ZERO-QUOTE: $label"
return 1
}
run_forge_dry_run() {
local forge_cmd=(
forge script script/bridge/trustless/DeployEnhancedSwapRouterV2.s.sol:DeployEnhancedSwapRouterV2
--skip test
--non-interactive
--rpc-url "$RPC_URL_138"
--private-key "$PRIVATE_KEY"
"$VERBOSITY"
)
(
cd "$SMOM_ROOT"
forge build contracts/bridge/trustless/EnhancedSwapRouterV2.sol script/bridge/trustless/DeployEnhancedSwapRouterV2.s.sol
if command -v timeout >/dev/null 2>&1; then
timeout "${RUN_TIMEOUT_SECONDS}s" "${forge_cmd[@]}"
else
"${forge_cmd[@]}"
fi
)
}
echo "=== EnhancedSwapRouterV2 Chain 138 Dry Run ==="
echo "Project root: $SMOM_ROOT"
echo "Repository root: $PROJECT_ROOT"
echo "Env source: $ENV_SOURCE"
echo ""
show_var "RPC_URL_138" "$RPC_URL_138" "Core RPC only"
show_secret_var "PRIVATE_KEY" "${PRIVATE_KEY:-}" "not printed when present"
show_var "DODO_PMM_PROVIDER_ADDRESS" "${DODO_PMM_PROVIDER_ADDRESS:-<unset>}" "required"
show_var "CHAIN138_POOL_WETH_USDT" "${CHAIN138_POOL_WETH_USDT:-<unset>}" "required for swapToStablecoin readiness"
show_var "CHAIN138_POOL_WETH_USDC" "${CHAIN138_POOL_WETH_USDC:-<unset>}" "required for swapToStablecoin readiness"
show_var "CHAIN138_D3_PROXY_ADDRESS" "${CHAIN138_D3_PROXY_ADDRESS:-0xc9a11abB7C63d88546Be24D58a6d95e3762cB843}" "optional DODO v3 execution"
show_var "CHAIN138_D3_MM_ADDRESS" "${CHAIN138_D3_MM_ADDRESS:-0x6550A3a59070061a262a893A1D6F3F490afFDBDA}" "optional DODO v3 execution"
show_var "UNISWAP_V3_ROUTER" "${UNISWAP_V3_ROUTER:-<unset>}" "optional"
show_var "UNISWAP_QUOTER_ADDRESS" "${UNISWAP_QUOTER_ADDRESS:-<unset>}" "optional"
show_var "BALANCER_VAULT" "${BALANCER_VAULT:-<unset>}" "optional"
show_var "CURVE_3POOL" "${CURVE_3POOL:-<unset>}" "optional"
echo ""
if [[ -z "${PRIVATE_KEY:-}" ]]; then
echo "PRIVATE_KEY is required"
exit 1
fi
if [[ -z "$DODO_PMM_PROVIDER_ADDRESS" ]]; then
echo "DODO_PMM_PROVIDER_ADDRESS is required"
exit 1
fi
readiness_fail=0
echo "Provider route probe"
if [[ -n "${CHAIN138_POOL_WETH_USDT:-}" ]]; then
if ! probe_support "$WETH" "$OFFICIAL_USDT_ADDRESS" "$DODO_PMM_PROVIDER_ADDRESS" "WETH -> USDT"; then
readiness_fail=1
fi
else
echo " SKIP: WETH -> USDT (CHAIN138_POOL_WETH_USDT unset)"
fi
if [[ -n "${CHAIN138_POOL_WETH_USDC:-}" ]]; then
if ! probe_support "$WETH" "$OFFICIAL_USDC_ADDRESS" "$DODO_PMM_PROVIDER_ADDRESS" "WETH -> USDC"; then
readiness_fail=1
fi
else
echo " SKIP: WETH -> USDC (CHAIN138_POOL_WETH_USDC unset)"
fi
echo ""
echo "Quote readiness probe"
for amount in "1000000000000000000:1" "5000000000000000000:5" "25000000000000000000:25"; do
raw_amount="${amount%%:*}"
human_amount="${amount##*:}"
if [[ -n "${CHAIN138_POOL_WETH_USDT:-}" ]]; then
if ! probe_quote "$WETH" "$OFFICIAL_USDT_ADDRESS" "$DODO_PMM_PROVIDER_ADDRESS" "$raw_amount" "WETH -> USDT @ ${human_amount} WETH"; then
readiness_fail=1
fi
fi
if [[ -n "${CHAIN138_POOL_WETH_USDC:-}" ]]; then
if ! probe_quote "$WETH" "$OFFICIAL_USDC_ADDRESS" "$DODO_PMM_PROVIDER_ADDRESS" "$raw_amount" "WETH -> USDC @ ${human_amount} WETH"; then
readiness_fail=1
fi
fi
done
echo ""
if [[ -z "${CHAIN138_POOL_WETH_USDT:-}" || -z "${CHAIN138_POOL_WETH_USDC:-}" ]]; then
echo "Readiness note: canonical WETH -> stable DODO PMM routes are still incomplete, so swapToStablecoin readiness remains partial."
echo "The router-v2 deployment can still proceed for explicit executeRoute calldata, including DODO v3 pilot execution."
elif (( readiness_fail == 1 )); then
echo "Readiness failed: Chain 138 still lacks at least one required WETH -> stable DODO route or non-zero quote."
echo "Set CHAIN138_POOL_WETH_USDT / CHAIN138_POOL_WETH_USDC after creating and registering the canonical pools, then rerun."
exit 1
else
echo "Readiness passed: required WETH -> stable DODO routes are present with non-zero quotes for 1/5/25 WETH."
fi
if (( RUN_FORGE_DRY_RUN == 1 )); then
echo ""
run_forge_dry_run
fi

View File

@@ -73,10 +73,10 @@ cat > "$CONFIG_DIR/SUPPORTED_CHAINS.md" << 'EOF'
| Framework | Type | Status | Adapter | Nodes |
|-----------|------|--------|---------|-------|
| Firefly | Orchestration | ✅ Deployed | FireflyAdapter | VMID 6202, 6203 |
| Cacti | Interoperability | ✅ Deployed | CactiAdapter | VMID 5201 |
| Fabric | Permissioned | 🔨 Plan | FabricAdapter | TBD |
| Indy | Identity | 🔨 Plan | IndyVerifier | TBD |
| FireFly | Orchestration | ⚠️ Adapter/service client only | FireflyAdapter | `6200` live primary; `6201` retired / standby; `6202` / `6203` not deployed |
| Cacti | Interoperability | ⚠️ Active surfaces, adapter still pending | CactiAdapter | `5200` live primary; `5201` / `5202` live public Cacti surfaces with local `:4000` APIs |
| Fabric | Permissioned | ⚠️ Primary sample network only | FabricAdapter | `6000` live sample network; `6001` / `6002` placeholders |
| Indy | Identity | ⚠️ Primary validator pool only | IndyVerifier | `6400` live validator pool; `6401` / `6402` placeholders |
## Legend
- ✅ Live: Fully deployed and operational

View File

@@ -0,0 +1,572 @@
#!/usr/bin/env bash
# Print the live unload routes from Chain 138 to reachable public chains.
#
# This helper does not send transactions. It reviews the current WETH9 bridge
# topology, classifies each public-chain lane, and prints exact cast command
# packs for the current amount/recipient.
#
# Usage:
# ./scripts/deployment/print-chain138-public-chain-unload-routes.sh
#
# Optional env:
# TARGET_CHAIN=gnosis
# UNLOAD_AMOUNT_WEI=10000000000000000
# RECIPIENT=0x...
# OPTIONAL_UNWRAP_WEI=2000000000000000
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
cd "$PROJECT_ROOT"
if [[ -f "$SCRIPT_DIR/../lib/deployment/dotenv.sh" ]]; then
# shellcheck disable=SC1090
source "$SCRIPT_DIR/../lib/deployment/dotenv.sh"
load_deployment_env --repo-root "$PROJECT_ROOT"
elif [[ -f "$PROJECT_ROOT/.env" ]]; then
set -a
# shellcheck disable=SC1090
source "$PROJECT_ROOT/.env"
set +a
fi
need_var() {
local name="$1"
[[ -n "${!name:-}" ]] || {
echo "Error: $name is required" >&2
exit 1
}
}
trim_word() {
echo "$1" | awk '{print $1}'
}
fmt_ether() {
local raw="${1:-0}"
raw="$(trim_word "$raw")"
cast from-wei "$raw" ether 2>/dev/null || echo "$raw"
}
contains_true() {
[[ "$1" == *", true)"* ]]
}
extract_addr() {
echo "$1" | grep -oE '0x[a-fA-F0-9]{40}' | head -n1 | tr '[:upper:]' '[:lower:]'
}
extract_enabled_addr() {
local raw="$1"
local addr
addr="$(extract_addr "$raw")"
if contains_true "$raw" && [[ -n "$addr" ]]; then
echo "$addr"
else
echo ""
fi
}
query_dest() {
local rpc="$1"
local bridge="$2"
local selector="$3"
if [[ -z "$rpc" || -z "$bridge" || -z "$selector" ]]; then
echo "UNSET"
return 0
fi
cast call "$bridge" 'destinations(uint64)((uint64,address,bool))' "$selector" --rpc-url "$rpc" 2>/dev/null || echo "ERROR"
}
quote_fee_state() {
local rpc="$1"
local bridge="$2"
local selector="$3"
if [[ -z "$rpc" || -z "$bridge" || -z "$selector" ]]; then
echo "n/a"
return 0
fi
if cast call "$bridge" 'calculateFee(uint64,uint256)(uint256)' "$selector" 4000000000000000 --rpc-url "$rpc" >/dev/null 2>&1; then
echo "quoted"
else
echo "blocked"
fi
}
balance_of() {
local rpc="$1"
local token="$2"
local wallet="$3"
if [[ -z "$rpc" || -z "$token" || -z "$wallet" ]]; then
echo "0"
return 0
fi
cast call "$token" 'balanceOf(address)(uint256)' "$wallet" --rpc-url "$rpc" 2>/dev/null | awk '{print $1}' || echo "0"
}
native_balance() {
local rpc="$1"
if [[ -z "$rpc" ]]; then
echo "0"
return 0
fi
cast balance "$DEPLOYER" --rpc-url "$rpc" 2>/dev/null || echo "0"
}
env_value_from_file() {
local file="$1"
local key="$2"
[[ -f "$file" ]] || {
echo ""
return 0
}
grep -E "^${key}=" "$file" | head -n1 | cut -d'=' -f2- | tr -d ' "\r\n' || true
}
relay_inventory_note() {
local inventory_raw="$1"
if [[ "$(echo "$inventory_raw >= $UNLOAD_AMOUNT_WEI" | bc)" == "1" ]]; then
echo "enough-for-request"
elif [[ "$(echo "$inventory_raw > 0" | bc)" == "1" ]]; then
echo "small-only"
else
echo "empty"
fi
}
practical_route_label() {
local name="$1"
local mainnet_quote_state="$2"
case "$name" in
mainnet|bsc|avalanche) echo "relay-backed-direct" ;;
wemix) echo "blocked" ;;
*)
if [[ "$mainnet_quote_state" == "blocked" ]]; then
echo "mainnet-hub-blocked"
else
echo "via-mainnet-hub"
fi
;;
esac
}
prereq_text() {
local name="$1"
local practical_route="$2"
local inventory_state="$3"
local mainnet_dest_enabled="$4"
case "$practical_route" in
relay-backed-direct)
case "$inventory_state" in
enough-for-request) echo "inventory ready" ;;
small-only) echo "inventory only covers tiny unloads" ;;
empty)
if [[ "$name" == "mainnet" ]]; then
echo "seed mainnet relay bridge WETH"
else
echo "seed relay bridge WETH"
fi
;;
esac
;;
via-mainnet-hub)
if [[ "$mainnet_dest_enabled" == "enabled" ]]; then
echo "bootstrap mainnet first"
else
echo "fix mainnet destination mapping first"
fi
;;
mainnet-hub-blocked)
echo "repair Mainnet WETH9 source bridge quote/send path first"
;;
*)
echo "deploy bridge and seed gas"
;;
esac
}
best_use_text() {
local name="$1"
local practical_route="$2"
local inventory_state="$3"
local mainnet_dest_enabled="$4"
case "$practical_route" in
relay-backed-direct)
case "$name" in
mainnet)
if [[ "$inventory_state" == "enough-for-request" ]]; then
echo "direct 138 -> mainnet unload"
else
echo "bootstrap mainnet through the proven BSC recovery path, then keep relay inventory topped up"
fi
;;
bsc)
if [[ "$inventory_state" == "enough-for-request" ]]; then
echo "direct 138 -> BSC unload"
elif [[ "$inventory_state" == "small-only" ]]; then
echo "tiny direct 138 -> BSC unloads; use mainnet hub for larger funding"
else
echo "seed BSC relay inventory or fund BSC from mainnet hub"
fi
;;
avalanche)
if [[ "$inventory_state" == "enough-for-request" ]]; then
echo "direct 138 -> Avalanche unload"
else
echo "seed Avalanche relay inventory or fund Avalanche from mainnet hub"
fi
;;
esac
;;
via-mainnet-hub)
if [[ "$mainnet_dest_enabled" == "enabled" ]]; then
echo "bootstrap mainnet, then send mainnet -> $name"
else
echo "repair mainnet -> $name mapping before using the hub"
fi
;;
mainnet-hub-blocked)
echo "blocked until the Mainnet WETH9 source bridge/router quote path is repaired"
;;
*)
echo "deploy and wire WEMIX first"
;;
esac
}
print_source_send_commands() {
local name="$1"
local selector="$2"
local upper
upper="$(echo "$name" | tr '[:lower:]-' '[:upper:]_')"
cat <<EOF
export FEE_${upper}_WEI=\$(cast call "$CCIPWETH9_BRIDGE_CHAIN138" "calculateFee(uint64,uint256)(uint256)" \\
"$selector" "$UNLOAD_AMOUNT_WEI" \\
--rpc-url "\$RPC_URL_138" | awk '{print \$1}')
echo "FEE_${upper}_WEI=\$FEE_${upper}_WEI"
cast send "$WETH9_138" "approve(address,uint256)" \\
"$CCIPWETH9_BRIDGE_CHAIN138" "$UNLOAD_AMOUNT_WEI" \\
--rpc-url "\$RPC_URL_138" \\
--private-key "\$PRIVATE_KEY" \\
--legacy --gas-price 2000000000
cast send "$LINK_138" "approve(address,uint256)" \\
"$CCIPWETH9_BRIDGE_CHAIN138" "\$FEE_${upper}_WEI" \\
--rpc-url "\$RPC_URL_138" \\
--private-key "\$PRIVATE_KEY" \\
--legacy --gas-price 2000000000
cast send "$CCIPWETH9_BRIDGE_CHAIN138" "sendCrossChain(uint64,address,uint256)" \\
"$selector" "\$RECIPIENT" "$UNLOAD_AMOUNT_WEI" \\
--rpc-url "\$RPC_URL_138" \\
--private-key "\$PRIVATE_KEY" \\
--legacy --gas-limit 900000 --gas-price 2000000000
EOF
}
print_unwrap_commands() {
local wrapped_token="$1"
local rpc="$2"
[[ -n "$wrapped_token" && -n "$rpc" ]] || return 0
cat <<EOF
# Optional: unwrap a little of the received wrapped-native token into gas.
cast send "$wrapped_token" "withdraw(uint256)" \\
"$OPTIONAL_UNWRAP_WEI" \\
--rpc-url "$rpc" \\
--private-key "\$PRIVATE_KEY" \\
--legacy
EOF
}
print_mainnet_send_commands() {
local name="$1"
local selector="$2"
local wrapped_token="$3"
local rpc="$4"
local upper
upper="$(echo "$name" | tr '[:lower:]-' '[:upper:]_')"
cat <<EOF
# After mainnet is funded, fan out from the mainnet WETH9 bridge:
export FEE_MAINNET_TO_${upper}_WEI=\$(cast call "$MAINNET_CCIP_WETH9_BRIDGE" "calculateFee(uint64,uint256)(uint256)" \\
"$selector" "$UNLOAD_AMOUNT_WEI" \\
--rpc-url "\$ETHEREUM_MAINNET_RPC" | awk '{print \$1}')
echo "FEE_MAINNET_TO_${upper}_WEI=\$FEE_MAINNET_TO_${upper}_WEI"
cast send "$MAINNET_WETH9" "approve(address,uint256)" \\
"$MAINNET_CCIP_WETH9_BRIDGE" "$UNLOAD_AMOUNT_WEI" \\
--rpc-url "\$ETHEREUM_MAINNET_RPC" \\
--private-key "\$PRIVATE_KEY" \\
--legacy
EOF
if [[ "${MAINNET_FEE_TOKEN,,}" != "${ZERO_ADDRESS,,}" ]]; then
cat <<EOF
cast send "$MAINNET_FEE_TOKEN" "approve(address,uint256)" \\
"$MAINNET_CCIP_WETH9_BRIDGE" "\$FEE_MAINNET_TO_${upper}_WEI" \\
--rpc-url "\$ETHEREUM_MAINNET_RPC" \\
--private-key "\$PRIVATE_KEY" \\
--legacy
cast send "$MAINNET_CCIP_WETH9_BRIDGE" "sendCrossChain(uint64,address,uint256)" \\
"$selector" "\$RECIPIENT" "$UNLOAD_AMOUNT_WEI" \\
--rpc-url "\$ETHEREUM_MAINNET_RPC" \\
--private-key "\$PRIVATE_KEY" \\
--legacy --gas-limit 900000
EOF
else
cat <<EOF
cast send "$MAINNET_CCIP_WETH9_BRIDGE" "sendCrossChain(uint64,address,uint256)" \\
"$selector" "\$RECIPIENT" "$UNLOAD_AMOUNT_WEI" \\
--rpc-url "\$ETHEREUM_MAINNET_RPC" \\
--private-key "\$PRIVATE_KEY" \\
--legacy --gas-limit 900000 --value "\$FEE_MAINNET_TO_${upper}_WEI"
EOF
fi
if [[ -n "$wrapped_token" && -n "$rpc" ]]; then
echo ""
print_unwrap_commands "$wrapped_token" "$rpc"
fi
}
need_var PRIVATE_KEY
need_var RPC_URL_138
need_var CCIPWETH9_BRIDGE_CHAIN138
need_var ETH_MAINNET_SELECTOR
need_var MAINNET_CCIP_WETH9_BRIDGE
need_var ETHEREUM_MAINNET_RPC
DEPLOYER="$(cast wallet address "$PRIVATE_KEY" 2>/dev/null || true)"
[[ -n "$DEPLOYER" ]] || {
echo "Error: could not derive deployer from PRIVATE_KEY" >&2
exit 1
}
WETH9_138="$(cast call "$CCIPWETH9_BRIDGE_CHAIN138" 'weth9()(address)' --rpc-url "$RPC_URL_138" | tail -n1)"
LINK_138="$(cast call "$CCIPWETH9_BRIDGE_CHAIN138" 'feeToken()(address)' --rpc-url "$RPC_URL_138" | tail -n1)"
MAINNET_WETH9="$(cast call "$MAINNET_CCIP_WETH9_BRIDGE" 'weth9()(address)' --rpc-url "$ETHEREUM_MAINNET_RPC" | tail -n1)"
MAINNET_FEE_TOKEN="$(cast call "$MAINNET_CCIP_WETH9_BRIDGE" 'feeToken()(address)' --rpc-url "$ETHEREUM_MAINNET_RPC" | tail -n1)"
ZERO_ADDRESS="0x0000000000000000000000000000000000000000"
CHAIN138_SELECTOR_VALUE="${CHAIN138_SELECTOR:-138}"
CURRENT_138_WETH9="${CCIPWETH9_BRIDGE_CHAIN138,,}"
LEGACY_138_WETH9="${CCIPWETH9_BRIDGE_DIRECT_LEGACY:-0x971cD9D156f193df8051E48043C476e53ECd4693}"
LEGACY_138_WETH9="${LEGACY_138_WETH9,,}"
TARGET_CHAIN="${TARGET_CHAIN:-all}"
UNLOAD_AMOUNT_WEI="${UNLOAD_AMOUNT_WEI:-10000000000000000}"
OPTIONAL_UNWRAP_WEI="${OPTIONAL_UNWRAP_WEI:-2000000000000000}"
RECIPIENT="${RECIPIENT:-$DEPLOYER}"
RELAY_DIR="$PROJECT_ROOT/services/relay"
BSC_RELAY_PROFILE="$RELAY_DIR/.env.bsc"
AVAX_RELAY_PROFILE="$RELAY_DIR/.env.avax"
CHAINS=(
"mainnet|${ETH_MAINNET_SELECTOR:-}|${ETHEREUM_MAINNET_RPC:-}|${MAINNET_CCIP_WETH9_BRIDGE:-}|$MAINNET_WETH9|${CCIP_RELAY_BRIDGE_MAINNET:-}|$RELAY_DIR/.env"
"bsc|${BSC_SELECTOR:-}|${BSC_MAINNET_RPC:-}|${CCIPWETH9_BRIDGE_BSC:-}|${WETH9_BSC:-}|0x886C6A4ABC064dbf74E7caEc460b7eeC31F1b78C|$BSC_RELAY_PROFILE"
"avalanche|${AVALANCHE_SELECTOR:-}|${AVALANCHE_MAINNET_RPC:-}|${CCIPWETH9_BRIDGE_AVALANCHE:-}|${WETH9_AVALANCHE:-}|0x3f8C409C6072a2B6a4Ff17071927bA70F80c725F|$AVAX_RELAY_PROFILE"
"gnosis|${GNOSIS_SELECTOR:-}|${GNOSIS_RPC:-}|${CCIPWETH9_BRIDGE_GNOSIS:-}|${WETH9_GNOSIS:-}||"
"cronos|${CRONOS_SELECTOR:-}|${CRONOS_RPC:-}|${CCIPWETH9_BRIDGE_CRONOS:-}|${WETH9_CRONOS:-}||"
"celo|${CELO_SELECTOR:-1346049177634351622}|${CELO_RPC:-}|${CCIPWETH9_BRIDGE_CELO:-}|${WETH9_CELO:-}||"
"polygon|${POLYGON_SELECTOR:-}|${POLYGON_MAINNET_RPC:-}|${CCIPWETH9_BRIDGE_POLYGON:-}|${WETH9_POLYGON:-}||"
"arbitrum|${ARBITRUM_SELECTOR:-}|${ARBITRUM_MAINNET_RPC:-}|${CCIPWETH9_BRIDGE_ARBITRUM:-}|${WETH9_ARBITRUM:-}||"
"optimism|${OPTIMISM_SELECTOR:-}|${OPTIMISM_MAINNET_RPC:-}|${CCIPWETH9_BRIDGE_OPTIMISM:-}|${WETH9_OPTIMISM:-}||"
"base|${BASE_SELECTOR:-}|${BASE_MAINNET_RPC:-}|${CCIPWETH9_BRIDGE_BASE:-}|${WETH9_BASE:-}||"
"wemix|${WEMIX_SELECTOR:-5142893604156789321}|${WEMIX_RPC:-}|${CCIPWETH9_BRIDGE_WEMIX:-}|${WETH9_WEMIX:-}||"
)
echo "# Chain 138 Public-Chain Unload Routes"
echo "# Generated: $(date -u '+%Y-%m-%d %H:%M:%S UTC')"
echo "# Deployer: $DEPLOYER"
echo "# Recipient: $RECIPIENT"
echo "# Unload amount: $UNLOAD_AMOUNT_WEI ($(fmt_ether "$UNLOAD_AMOUNT_WEI"))"
echo "# Optional unwrap amount: $OPTIONAL_UNWRAP_WEI ($(fmt_ether "$OPTIONAL_UNWRAP_WEI"))"
echo "# WETH9 rail only. WETH10 remains drifted and is intentionally excluded."
echo ""
printf '%-10s | %-22s | %-28s | %-34s\n' "Chain" "Practical Route" "Live Prerequisite" "Best Use"
printf '%s\n' "------------------------------------------------------------------------------------------------------------------------"
for entry in "${CHAINS[@]}"; do
IFS='|' read -r name selector rpc native_bridge wrapped_token relay_bridge relay_profile <<< "$entry"
if [[ "$TARGET_CHAIN" != "all" && "$TARGET_CHAIN" != "$name" ]]; then
continue
fi
source_dest="$(query_dest "$RPC_URL_138" "$CCIPWETH9_BRIDGE_CHAIN138" "$selector")"
source_addr="$(extract_enabled_addr "$source_dest")"
route_type="disabled"
if [[ -n "$source_addr" && -n "$native_bridge" && "$source_addr" == "${native_bridge,,}" ]]; then
route_type="direct-native"
elif [[ -n "$source_addr" && -n "$relay_bridge" && "$source_addr" == "${relay_bridge,,}" ]]; then
route_type="relay-backed"
elif [[ -n "$source_addr" ]]; then
route_type="other:$source_addr"
elif [[ -z "$native_bridge" ]]; then
route_type="deploy-first"
fi
inventory_raw="0"
if [[ "$route_type" == "relay-backed" && -n "$wrapped_token" && -n "$relay_bridge" && -n "$rpc" ]]; then
inventory_raw="$(balance_of "$rpc" "$wrapped_token" "$relay_bridge")"
fi
inventory_state="$(relay_inventory_note "$inventory_raw")"
mainnet_route_to_target="$(query_dest "$ETHEREUM_MAINNET_RPC" "$MAINNET_CCIP_WETH9_BRIDGE" "$selector")"
mainnet_dest_enabled="disabled"
mainnet_quote_state="n/a"
if [[ "$name" != "mainnet" ]]; then
mainnet_quote_state="$(quote_fee_state "$ETHEREUM_MAINNET_RPC" "$MAINNET_CCIP_WETH9_BRIDGE" "$selector")"
fi
if contains_true "$mainnet_route_to_target"; then
mainnet_dest_enabled="enabled"
fi
practical_route="$(practical_route_label "$name" "$mainnet_quote_state")"
prereq="$(prereq_text "$name" "$practical_route" "$inventory_state" "$mainnet_dest_enabled")"
best_use="$(best_use_text "$name" "$practical_route" "$inventory_state" "$mainnet_dest_enabled")"
printf '%-10s | %-22s | %-28s | %-34s\n' "$name" "$practical_route" "$prereq" "$best_use"
done
echo ""
for entry in "${CHAINS[@]}"; do
IFS='|' read -r name selector rpc native_bridge wrapped_token relay_bridge relay_profile <<< "$entry"
if [[ "$TARGET_CHAIN" != "all" && "$TARGET_CHAIN" != "$name" ]]; then
continue
fi
source_dest="$(query_dest "$RPC_URL_138" "$CCIPWETH9_BRIDGE_CHAIN138" "$selector")"
source_addr="$(extract_enabled_addr "$source_dest")"
route_type="disabled"
if [[ -n "$source_addr" && -n "$native_bridge" && "$source_addr" == "${native_bridge,,}" ]]; then
route_type="direct-native"
elif [[ -n "$source_addr" && -n "$relay_bridge" && "$source_addr" == "${relay_bridge,,}" ]]; then
route_type="relay-backed"
elif [[ -n "$source_addr" ]]; then
route_type="other:$source_addr"
elif [[ -z "$native_bridge" ]]; then
route_type="deploy-first"
fi
inventory_raw="0"
inventory_state="empty"
if [[ "$route_type" == "relay-backed" && -n "$wrapped_token" && -n "$relay_bridge" && -n "$rpc" ]]; then
inventory_raw="$(balance_of "$rpc" "$wrapped_token" "$relay_bridge")"
inventory_state="$(relay_inventory_note "$inventory_raw")"
fi
mainnet_route_to_target="$(query_dest "$ETHEREUM_MAINNET_RPC" "$MAINNET_CCIP_WETH9_BRIDGE" "$selector")"
mainnet_dest_enabled="disabled"
mainnet_dest_addr="$(extract_enabled_addr "$mainnet_route_to_target")"
mainnet_quote_state="n/a"
if [[ "$name" != "mainnet" ]]; then
mainnet_quote_state="$(quote_fee_state "$ETHEREUM_MAINNET_RPC" "$MAINNET_CCIP_WETH9_BRIDGE" "$selector")"
fi
if [[ "$name" == "mainnet" ]]; then
mainnet_dest_enabled="n/a"
mainnet_dest_addr="n/a"
elif contains_true "$mainnet_route_to_target"; then
mainnet_dest_enabled="enabled"
fi
practical_route="$(practical_route_label "$name" "$mainnet_quote_state")"
remote_to_mainnet="$(query_dest "$rpc" "$native_bridge" "$ETH_MAINNET_SELECTOR")"
remote_to_138="$(query_dest "$rpc" "$native_bridge" "$CHAIN138_SELECTOR_VALUE")"
mainnet_enabled="disabled"
back_addr="$(extract_enabled_addr "$remote_to_138")"
return_state="missing"
if contains_true "$remote_to_mainnet"; then
mainnet_enabled="enabled"
fi
if [[ -n "$back_addr" ]]; then
if [[ "$back_addr" == "$CURRENT_138_WETH9" ]]; then
return_state="current"
elif [[ "$back_addr" == "$LEGACY_138_WETH9" ]]; then
return_state="legacy"
else
return_state="$back_addr"
fi
fi
echo "## $name"
echo "- Chain 138 mapping: $route_type"
echo "- Chain 138 destination: ${source_addr:-disabled}"
echo "- Practical route today: $practical_route"
if [[ -n "$wrapped_token" ]]; then
echo "- Destination wrapped asset: $wrapped_token"
fi
if [[ -n "$rpc" ]]; then
echo "- Deployer native gas on destination: $(fmt_ether "$(native_balance "$rpc")")"
fi
if [[ "$route_type" == "relay-backed" ]]; then
echo "- Relay bridge inventory: $(fmt_ether "$inventory_raw")"
echo "- Request coverage at current amount: $(relay_inventory_note "$inventory_raw")"
fi
if [[ "$mainnet_enabled" == "enabled" ]]; then
echo "- Mainnet fan-out from this chain: enabled"
elif [[ "$name" != "mainnet" && -n "$native_bridge" ]]; then
echo "- Mainnet fan-out from this chain: disabled"
fi
if [[ "$name" != "mainnet" && -n "$native_bridge" ]]; then
echo "- Return path to Chain 138: $return_state"
fi
echo "- Mainnet hub destination: ${mainnet_dest_addr:-disabled} ($mainnet_dest_enabled)"
if [[ "$name" != "mainnet" ]]; then
echo "- Mainnet hub quote preflight: $mainnet_quote_state"
fi
echo "- Best use today: $(best_use_text "$name" "$practical_route" "$inventory_state" "$mainnet_dest_enabled")"
echo ""
if [[ "$practical_route" == "relay-backed-direct" ]]; then
echo "Exact Chain 138 send commands:"
print_source_send_commands "$name" "$selector"
echo ""
if [[ -n "$relay_profile" && -f "$relay_profile" ]]; then
relay_name="$(basename "$relay_profile")"
if [[ "$name" == "mainnet" ]]; then
cat <<EOF
# Relay worker for this lane:
cd "$RELAY_DIR"
./start-relay.sh
EOF
elif [[ "$name" == "bsc" || "$name" == "avalanche" ]]; then
profile_name="${name/avalanche/avax}"
cat <<EOF
# Relay worker for this lane:
cd "$RELAY_DIR"
./start-relay.sh "$profile_name"
EOF
fi
echo "# Relay profile file: $relay_name"
fi
else
if [[ "$name" == "wemix" ]]; then
echo "No unload commands printed because the WEMIX bridge is not deployed in env."
else
echo "The native Chain 138 mapping is not the practical route of truth here."
echo "Why: Chain 138 emits through a custom router that only creates events; it does not natively deliver into these public CCIP bridges."
echo "Proof point: the earlier 138 -> Gnosis native-bridge attempt failed for exactly this reason."
echo ""
if [[ "$mainnet_dest_enabled" == "enabled" && "$mainnet_quote_state" == "quoted" ]]; then
echo "Exact mainnet hub commands:"
echo "# First bootstrap mainnet. If the mainnet relay bridge is empty, reuse the proven BSC relay plus external bridge recovery flow."
print_mainnet_send_commands "$name" "$selector" "$wrapped_token" "$rpc"
elif [[ "$mainnet_dest_enabled" == "enabled" ]]; then
echo "Mainnet exposes an enabled destination for $name, but the current Mainnet WETH9 source bridge quote path is blocked."
echo "Do not broadcast mainnet fan-out sends until the source bridge/router path is repaired or replaced."
else
echo "Mainnet does not currently expose an enabled destination for $name, so the hub route must be repaired first."
fi
fi
fi
echo ""
done

View File

@@ -0,0 +1,38 @@
#!/usr/bin/env bash
# Deprecated helper kept as a guardrail for historical references.
#
# Why deprecated:
# - Chain 138 emits through a custom router that only creates MessageSent events.
# - Native destination mappings like 138 -> Gnosis do not by themselves create
# a live delivery path into public-chain CCIP bridges.
# - A real 138 -> Gnosis test already failed for this reason.
#
# Use instead:
# ./scripts/deployment/print-chain138-public-chain-unload-routes.sh
set -euo pipefail
cat <<'EOF'
print-gnosis-bootstrap-cast-sequence.sh is deprecated.
Reason:
- `138 -> Gnosis` is not a practical first-hop route with the current live architecture.
- Chain 138 uses a custom event-emitting router, so a native Gnosis bridge mapping is not enough to deliver the message.
- The earlier live `138 -> Gnosis` attempt failed for exactly that reason.
Use this instead:
cd /home/intlc/projects/proxmox/smom-dbis-138
./scripts/deployment/print-chain138-public-chain-unload-routes.sh
Useful examples:
TARGET_CHAIN=mainnet ./scripts/deployment/print-chain138-public-chain-unload-routes.sh
TARGET_CHAIN=bsc ./scripts/deployment/print-chain138-public-chain-unload-routes.sh
TARGET_CHAIN=gnosis ./scripts/deployment/print-chain138-public-chain-unload-routes.sh
Current practical model:
- first-hop relay-backed lanes: Mainnet, BSC, Avalanche
- hub-distribution lanes after mainnet bootstrap: Gnosis, Cronos, Celo, Polygon, Arbitrum, Optimism, Base
- WEMIX: deploy and wire first
EOF

View File

@@ -19,11 +19,14 @@ RUN_PHASE2="${RUN_PHASE2:-1}"
DRY_RUN="${DRY_RUN:-}"
RPC_138="${RPC_URL_138:-${RPC_URL:-http://192.168.11.211:8545}}"
GAS_PRICE="${GAS_PRICE_138:-${GAS_PRICE:-1000000000}}"
INTEGRATION="${DODO_PMM_INTEGRATION_ADDRESS:-${DODO_PMM_INTEGRATION:-0x5BDc62f1ae7D630c37A8B363a1d49845356Ee72d}}"
POOL_CUSDTCUSDC="${POOL_CUSDTCUSDC:-0x9fcB06Aa1FD5215DC0E91Fd098aeff4B62fEa5C8}"
# Keep the default aligned with the live canonical integration/provider mapping.
POOL_CUSDTUSDT="${POOL_CUSDTUSDT:-0x6fc60DEDc92a2047062294488539992710b99D71}"
POOL_CUSDCUSDC="${POOL_CUSDCUSDC:-0x90bd9Bf18Daa26Af3e814ea224032d015db58Ea5}"
INTEGRATION="${DODO_PMM_INTEGRATION_ADDRESS:-${DODO_PMM_INTEGRATION:-0x86ADA6Ef91A3B450F89f2b751e93B1b7A3218895}}"
POOL_CUSDTCUSDC="${POOL_CUSDTCUSDC:-0x9e89bAe009adf128782E19e8341996c596ac40dC}"
POOL_CUSDTUSDT="${POOL_CUSDTUSDT:-0x866Cb44b59303d8dc5f4F9E3E7A8e8b0bf238d66}"
POOL_CUSDCUSDC="${POOL_CUSDCUSDC:-0xc39B7D0F40838cbFb54649d327f49a6DAC964062}"
export DODO_LP_FEE_BPS="${DODO_LP_FEE_BPS:-10}"
export DODO_INITIAL_PRICE_1E18="${DODO_INITIAL_PRICE_1E18:-1000000000000000000}"
export DODO_K_FACTOR_1E18="${DODO_K_FACTOR_1E18:-0}"
export DODO_ENABLE_TWAP="${DODO_ENABLE_TWAP:-false}"
export RPC_URL_138="$RPC_138"
export DODO_PMM_INTEGRATION_ADDRESS="$INTEGRATION"
@@ -46,21 +49,15 @@ phase1() {
return 0
fi
# 1a) Create all 3 pools in parallel (if not already created — scripts will fail if pool exists)
log "Creating PMM pools (parallel)..."
# 1a) Create all 3 pools sequentially (single deployer nonce lane; canonical stable params are i=1e18, k=0)
log "Creating PMM pools (sequential)..."
if [[ -z "$DRY_RUN" ]]; then
( forge script script/dex/CreateCUSDTCUSDCPool.s.sol:CreateCUSDTCUSDCPool \
--rpc-url "$RPC_138" --broadcast --private-key "$PRIVATE_KEY" --with-gas-price "$GAS_PRICE" -vv 2>&1 | tee /tmp/pmm-create-cusdt-cusdc.log ) &
PID1=$!
( forge script script/dex/CreateCUSDTUSDTPool.s.sol:CreateCUSDTUSDTPool \
--rpc-url "$RPC_138" --broadcast --private-key "$PRIVATE_KEY" --with-gas-price "$GAS_PRICE" -vv 2>&1 | tee /tmp/pmm-create-cusdt-usdt.log ) &
PID2=$!
( forge script script/dex/CreateCUSDCUSDCPool.s.sol:CreateCUSDCUSDCPool \
--rpc-url "$RPC_138" --broadcast --private-key "$PRIVATE_KEY" --with-gas-price "$GAS_PRICE" -vv 2>&1 | tee /tmp/pmm-create-cusdc-usdc.log ) &
PID3=$!
wait $PID1 2>/dev/null || true
wait $PID2 2>/dev/null || true
wait $PID3 2>/dev/null || true
forge script script/dex/CreateCUSDTCUSDCPool.s.sol:CreateCUSDTCUSDCPool \
--rpc-url "$RPC_138" --broadcast --private-key "$PRIVATE_KEY" --with-gas-price "$GAS_PRICE" -vv 2>&1 | tee /tmp/pmm-create-cusdt-cusdc.log || true
forge script script/dex/CreateCUSDTUSDTPool.s.sol:CreateCUSDTUSDTPool \
--rpc-url "$RPC_138" --broadcast --private-key "$PRIVATE_KEY" --with-gas-price "$GAS_PRICE" -vv 2>&1 | tee /tmp/pmm-create-cusdt-usdt.log || true
forge script script/dex/CreateCUSDCUSDCPool.s.sol:CreateCUSDCUSDCPool \
--rpc-url "$RPC_138" --broadcast --private-key "$PRIVATE_KEY" --with-gas-price "$GAS_PRICE" -vv 2>&1 | tee /tmp/pmm-create-cusdc-usdc.log || true
else
log "[DRY RUN] forge script CreateCUSDTCUSDCPool ..."
log "[DRY RUN] forge script CreateCUSDTUSDTPool ..."

View File

@@ -18,11 +18,21 @@ ORIG_TX_TIMEOUT_SECONDS="${TX_TIMEOUT_SECONDS-}"
ORIG_POST_CREATE_POLL_SECONDS="${POST_CREATE_POLL_SECONDS-}"
ORIG_POST_CREATE_POLL_INTERVAL="${POST_CREATE_POLL_INTERVAL-}"
if [[ -f "$ENV_FILE" ]]; then
if [[ -f "$SMOM_ROOT/scripts/lib/deployment/dotenv.sh" ]]; then
# shellcheck disable=SC1091
source "$SMOM_ROOT/scripts/lib/deployment/dotenv.sh"
load_deployment_env --repo-root "$SMOM_ROOT"
elif [[ -f "$ENV_FILE" ]]; then
had_nounset=0
if [[ $- == *u* ]]; then
had_nounset=1
set +u
fi
set -a
# shellcheck disable=SC1090
source "$ENV_FILE"
set +a
(( had_nounset )) && set -u
fi
[[ -n "$ORIG_RPC_URL_138" ]] && RPC_URL_138="$ORIG_RPC_URL_138"