#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" SMOM_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" ENV_FILE="$SMOM_ROOT/.env" CONFIG_JSON="${POOL_CONFIG_JSON:-$SMOM_ROOT/config/chain138-pmm-pools.json}" ORIG_RPC_URL_138="${RPC_URL_138-}" ORIG_RPC_URL="${RPC_URL-}" ORIG_DODO_PMM_INTEGRATION_ADDRESS="${DODO_PMM_INTEGRATION_ADDRESS-}" ORIG_DODO_PMM_INTEGRATION="${DODO_PMM_INTEGRATION-}" ORIG_DODO_PMM_PROVIDER_ADDRESS="${DODO_PMM_PROVIDER_ADDRESS-}" ORIG_DODO_PMM_PROVIDER="${DODO_PMM_PROVIDER-}" ORIG_PRIVATE_KEY="${PRIVATE_KEY-}" ORIG_DRY_RUN="${DRY_RUN-}" ORIG_CHAIN_GAS_PRICE="${CHAIN_GAS_PRICE-}" 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 "$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" [[ -n "$ORIG_RPC_URL" ]] && RPC_URL="$ORIG_RPC_URL" [[ -n "$ORIG_DODO_PMM_INTEGRATION_ADDRESS" ]] && DODO_PMM_INTEGRATION_ADDRESS="$ORIG_DODO_PMM_INTEGRATION_ADDRESS" [[ -n "$ORIG_DODO_PMM_INTEGRATION" ]] && DODO_PMM_INTEGRATION="$ORIG_DODO_PMM_INTEGRATION" [[ -n "$ORIG_DODO_PMM_PROVIDER_ADDRESS" ]] && DODO_PMM_PROVIDER_ADDRESS="$ORIG_DODO_PMM_PROVIDER_ADDRESS" [[ -n "$ORIG_DODO_PMM_PROVIDER" ]] && DODO_PMM_PROVIDER="$ORIG_DODO_PMM_PROVIDER" [[ -n "$ORIG_PRIVATE_KEY" ]] && PRIVATE_KEY="$ORIG_PRIVATE_KEY" [[ -n "$ORIG_DRY_RUN" ]] && DRY_RUN="$ORIG_DRY_RUN" [[ -n "$ORIG_CHAIN_GAS_PRICE" ]] && CHAIN_GAS_PRICE="$ORIG_CHAIN_GAS_PRICE" [[ -n "$ORIG_TX_TIMEOUT_SECONDS" ]] && TX_TIMEOUT_SECONDS="$ORIG_TX_TIMEOUT_SECONDS" [[ -n "$ORIG_POST_CREATE_POLL_SECONDS" ]] && POST_CREATE_POLL_SECONDS="$ORIG_POST_CREATE_POLL_SECONDS" [[ -n "$ORIG_POST_CREATE_POLL_INTERVAL" ]] && POST_CREATE_POLL_INTERVAL="$ORIG_POST_CREATE_POLL_INTERVAL" RPC_URL_138="${RPC_URL_138:-${RPC_URL:-http://192.168.11.211:8545}}" DODO_PMM_INTEGRATION_ADDRESS="${DODO_PMM_INTEGRATION_ADDRESS:-${DODO_PMM_INTEGRATION:-}}" DODO_PMM_PROVIDER_ADDRESS="${DODO_PMM_PROVIDER_ADDRESS:-${DODO_PMM_PROVIDER:-}}" DRY_RUN="${DRY_RUN:-0}" [[ -f "$CONFIG_JSON" ]] || { echo "POOL_CONFIG_JSON not found: $CONFIG_JSON" >&2; exit 1; } command -v jq >/dev/null 2>&1 || { echo "jq is required" >&2; exit 1; } command -v cast >/dev/null 2>&1 || { echo "cast is required" >&2; exit 1; } [[ -n "$DODO_PMM_INTEGRATION_ADDRESS" ]] || { echo "DODO_PMM_INTEGRATION_ADDRESS not set" >&2; exit 1; } [[ -n "$DODO_PMM_PROVIDER_ADDRESS" ]] || { echo "DODO_PMM_PROVIDER_ADDRESS not set" >&2; exit 1; } [[ "$DRY_RUN" == "1" || -n "${PRIVATE_KEY:-}" ]] || { echo "PRIVATE_KEY not set (required unless DRY_RUN=1)" >&2; exit 1; } CHAIN_GAS_PRICE="${CHAIN_GAS_PRICE:-1000000000}" TX_TIMEOUT_SECONDS="${TX_TIMEOUT_SECONDS:-120}" POST_CREATE_POLL_SECONDS="${POST_CREATE_POLL_SECONDS:-20}" POST_CREATE_POLL_INTERVAL="${POST_CREATE_POLL_INTERVAL:-1}" LP_FEE="${LP_FEE_RATE:-$(jq -r '.defaults.lpFeeRate' "$CONFIG_JSON")}" INITIAL_PRICE="${INITIAL_PRICE:-$(jq -r '.defaults.initialPrice' "$CONFIG_JSON")}" K_FACTOR="${K_FACTOR:-$(jq -r '.defaults.kFactor' "$CONFIG_JSON")}" ENABLE_TWAP="${ENABLE_TWAP:-$(jq -r '.defaults.enableTwap' "$CONFIG_JSON")}" declare -A TOKENS=() while IFS=$'\t' read -r sym addr; do TOKENS["$sym"]="$addr" done < <(jq -r '.tokens | to_entries[] | [.key, .value] | @tsv' "$CONFIG_JSON") mapfile -t C_STARS < <(jq -r '.groups.cStarSymbols[]' "$CONFIG_JSON") mapfile -t OFFICIALS < <(jq -r '.groups.officialStableSymbols[]' "$CONFIG_JSON") WETH_SYMBOL="$(jq -r '.groups.wethSymbol' "$CONFIG_JSON")" DEPLOY_CSTAR_CSTAR="$(jq -r '.groups.deploy.cStarVsCStar' "$CONFIG_JSON")" DEPLOY_CSTAR_OFFICIAL="$(jq -r '.groups.deploy.cStarVsOfficial' "$CONFIG_JSON")" DEPLOY_CSTAR_WETH="$(jq -r '.groups.deploy.cStarVsWeth' "$CONFIG_JSON")" DEPLOY_OFFICIAL_WETH="$(jq -r '.groups.deploy.officialVsWeth' "$CONFIG_JSON")" declare -A SEEN=() DESIRED_PAIRS=() add_pair() { local base_sym="$1" quote_sym="$2" local base="${TOKENS[$base_sym]:-}" local quote="${TOKENS[$quote_sym]:-}" local key="${base_sym}|${quote_sym}" [[ -n "$base" && -n "$quote" ]] || return 0 [[ "$base" != "0x0000000000000000000000000000000000000000" ]] || return 0 [[ "$quote" != "0x0000000000000000000000000000000000000000" ]] || return 0 [[ -z "${SEEN[$key]:-}" ]] || return 0 SEEN["$key"]=1 DESIRED_PAIRS+=("${base_sym}|${base}|${quote_sym}|${quote}") } if [[ "$DEPLOY_CSTAR_CSTAR" == "true" ]]; then for ((i=0; i<${#C_STARS[@]}; i++)); do for ((j=i+1; j<${#C_STARS[@]}; j++)); do add_pair "${C_STARS[$i]}" "${C_STARS[$j]}" done done fi if [[ "$DEPLOY_CSTAR_OFFICIAL" == "true" ]]; then for cstar in "${C_STARS[@]}"; do for official in "${OFFICIALS[@]}"; do add_pair "$cstar" "$official" done done fi if [[ "$DEPLOY_CSTAR_WETH" == "true" ]]; then for cstar in "${C_STARS[@]}"; do add_pair "$cstar" "$WETH_SYMBOL" done fi if [[ "$DEPLOY_OFFICIAL_WETH" == "true" ]]; then for official in "${OFFICIALS[@]}"; do add_pair "$official" "$WETH_SYMBOL" done fi while IFS=$'\t' read -r base_sym quote_sym; do add_pair "$base_sym" "$quote_sym" done < <(jq -r '.explicitPairs[]? | [.baseSymbol, .quoteSymbol] | @tsv' "$CONFIG_JSON") zero_addr='0x0000000000000000000000000000000000000000' created=0 registered=0 skipped_existing=0 failed=0 CURRENT_NONCE="" if [[ "$DRY_RUN" != "1" ]]; then DEPLOYER_ADDRESS="$(cast wallet address --private-key "$PRIVATE_KEY" 2>/dev/null || true)" [[ -n "$DEPLOYER_ADDRESS" ]] || { echo "Failed to derive deployer address from PRIVATE_KEY" >&2; exit 1; } fi rpc_call() { local to="$1" data="$2" local payload payload=$(printf '{"jsonrpc":"2.0","method":"eth_call","params":[{"to":"%s","data":"%s"},"latest"],"id":1}' "$to" "$data") curl -s -X POST "$RPC_URL_138" -H 'Content-Type: application/json' --data "$payload" } json_result() { sed -n 's/.*"result":"\([^"]*\)".*/\1/p' } hex_to_addr() { local value="${1#0x}" if [[ ${#value} -lt 40 ]]; then echo "$zero_addr" return 0 fi printf '0x%s\n' "${value: -40}" } pool_for() { local calldata result calldata="$(cast calldata "pools(address,address)" "$1" "$2" 2>/dev/null || true)" [[ -n "$calldata" ]] || { echo "$zero_addr"; return 0; } result="$(rpc_call "$DODO_PMM_INTEGRATION_ADDRESS" "$calldata" | json_result)" [[ -n "$result" ]] || { echo "$zero_addr"; return 0; } hex_to_addr "$result" } provider_pool_for() { local calldata result calldata="$(cast calldata "pools(address,address)" "$1" "$2" 2>/dev/null || true)" [[ -n "$calldata" ]] || { echo "$zero_addr"; return 0; } result="$(rpc_call "$DODO_PMM_PROVIDER_ADDRESS" "$calldata" | json_result)" [[ -n "$result" ]] || { echo "$zero_addr"; return 0; } hex_to_addr "$result" } send_tx() { local to="$1" shift local attempts nonce output rc for attempts in 1 2 3; do nonce="$(cast nonce "$DEPLOYER_ADDRESS" --block pending --rpc-url "$RPC_URL_138" 2>/dev/null || true)" [[ -n "$nonce" ]] || { echo "Failed to fetch pending nonce for deployer" >&2 return 1 } set +e output="$(cast send "$to" "$@" \ --rpc-url "$RPC_URL_138" \ --private-key "$PRIVATE_KEY" \ --legacy \ --gas-price "$CHAIN_GAS_PRICE" \ --nonce "$nonce" \ --confirmations 1 \ --timeout "$TX_TIMEOUT_SECONDS" \ -q 2>&1)" rc=$? set -e if (( rc == 0 )); then return 0 fi if [[ "$output" == *"Nonce too low"* || "$output" == *"Replacement transaction underpriced"* ]]; then sleep 1 continue fi printf '%s\n' "$output" >&2 return "$rc" done printf '%s\n' "$output" >&2 return 1 } wait_for_pool() { local base="$1" quote="$2" local max_polls poll current max_polls=$((POST_CREATE_POLL_SECONDS / POST_CREATE_POLL_INTERVAL)) (( max_polls < 1 )) && max_polls=1 for ((poll=0; poll zero pool address" ((failed++)) || true return 1 fi if [[ "$DRY_RUN" == "1" ]]; then echo " [DRY] would ensure provider registration for $label -> $pool (both directions)" ((registered++)) || true return 0 fi if send_tx "$DODO_PMM_PROVIDER_ADDRESS" "registerPool(address,address,address)" "$base" "$quote" "$pool" --gas-limit 200000 \ && send_tx "$DODO_PMM_PROVIDER_ADDRESS" "registerPool(address,address,address)" "$quote" "$base" "$pool" --gas-limit 200000; then echo " OK register $label -> $pool" ((registered++)) || true return 0 fi echo " FAIL register $label -> $pool" ((failed++)) || true return 1 } echo "=== Chain 138 PMM Desired-State Sync ===" echo "Config: $CONFIG_JSON" echo "Integration: $DODO_PMM_INTEGRATION_ADDRESS" echo "Provider: $DODO_PMM_PROVIDER_ADDRESS" echo "RPC: $RPC_URL_138" echo "Dry run: $DRY_RUN" echo "Desired pairs:${#DESIRED_PAIRS[@]}" echo "" for pair in "${DESIRED_PAIRS[@]}"; do IFS='|' read -r base_sym base quote_sym quote <<< "$pair" label="${base_sym}/${quote_sym}" existing_pool="$(pool_for "$base" "$quote")" provider_pool="$(provider_pool_for "$base" "$quote")" if [[ -n "$existing_pool" && "$existing_pool" != "$zero_addr" ]]; then echo "SKIP create $label (exists: $existing_pool)" ((skipped_existing++)) || true register_pair "$base" "$quote" "$existing_pool" "$label" continue fi if [[ -n "$provider_pool" && "$provider_pool" != "$zero_addr" ]]; then echo "BLOCKER create $label (provider already points to existing pool $provider_pool, but integration has no pool record)" echo " Repair requires importing the existing pool into DODOPMMIntegration with importExistingPool(...)." ((failed++)) || true continue fi if [[ "$DRY_RUN" == "1" ]]; then echo "[DRY] would create $label" ((created++)) || true continue fi if send_tx "$DODO_PMM_INTEGRATION_ADDRESS" "createPool(address,address,uint256,uint256,uint256,bool)" \ "$base" "$quote" "$LP_FEE" "$INITIAL_PRICE" "$K_FACTOR" "$ENABLE_TWAP" --gas-limit 500000; then new_pool="$(wait_for_pool "$base" "$quote")" if [[ -z "$new_pool" || "$new_pool" == "$zero_addr" ]]; then echo "FAIL create $label (tx confirmed but integration still reports zero pool address)" ((failed++)) || true continue fi echo "OK create $label -> $new_pool" ((created++)) || true register_pair "$base" "$quote" "$new_pool" "$label" else echo "FAIL create $label" ((failed++)) || true fi done echo "" echo "Summary:" echo " Created: $created" echo " Registered: $registered" echo " Existing: $skipped_existing" echo " Failed: $failed"