Files
proxmox/scripts/deployment/run-mainnet-public-dodo-cw-swap.sh
defiQUG dbd517b279 Sync workspace: config, docs, scripts, CI, operator rules, and submodule pointers.
- Update dbis_core, cross-chain-pmm-lps, explorer-monorepo, metamask-integration, pr-workspace/chains
- Omit embedded publish git dirs and empty placeholders from index

Made-with: Cursor
2026-04-12 06:12:20 -07:00

409 lines
15 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
# Repeatable swap helper for the public Mainnet DODO PMM cW bootstrap pools.
# Uses the live Mainnet integration, exact-amount approvals, and a reserve-based
# quote fallback because the direct query/read path on these pools currently
# reverts for hosted reads.
#
# Examples:
# bash scripts/deployment/run-mainnet-public-dodo-cw-swap.sh --pair=cwusdt-usdc --direction=base-to-quote --amount=10000
# bash scripts/deployment/run-mainnet-public-dodo-cw-swap.sh --pair=cwusdt-usdc --direction=quote-to-base --amount=10000
# bash scripts/deployment/run-mainnet-public-dodo-cw-swap.sh --pair=cweurc-usdc --direction=quote-to-base --amount=1000
# bash scripts/deployment/run-mainnet-public-dodo-cw-swap.sh --pair=cwgbpc-usdc --direction=base-to-quote --amount=1000
# bash scripts/deployment/run-mainnet-public-dodo-cw-swap.sh --pair=cwaudc-usdc --direction=base-to-quote --amount=1000
# bash scripts/deployment/run-mainnet-public-dodo-cw-swap.sh --pair=cwcadc-usdc --direction=quote-to-base --amount=1000
# bash scripts/deployment/run-mainnet-public-dodo-cw-swap.sh --pair=cwjpyc-usdc --direction=base-to-quote --amount=1000
# bash scripts/deployment/run-mainnet-public-dodo-cw-swap.sh --pair=cwchfc-usdc --direction=quote-to-base --amount=1000
# bash scripts/deployment/run-mainnet-public-dodo-cw-swap.sh --pair=cwusdc-usdt --direction=base-to-quote --amount=10000 --min-out=9000
# bash scripts/deployment/run-mainnet-public-dodo-cw-swap.sh --pair=cwusdt-usdc --direction=base-to-quote --amount=5000 --dry-run
# bash scripts/deployment/run-mainnet-public-dodo-cw-swap.sh --pair=cwusdt-cwusdc --direction=base-to-quote --amount=1000000 --dry-run
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "${SCRIPT_DIR}/../.." && pwd)"
source "${PROJECT_ROOT}/smom-dbis-138/scripts/load-env.sh" >/dev/null 2>&1
require_cmd() {
command -v "$1" >/dev/null 2>&1 || {
echo "[fail] missing required command: $1" >&2
exit 1
}
}
require_cmd cast
require_cmd python3
PAIR="cwusdt-usdc"
DIRECTION="base-to-quote"
AMOUNT="10000"
MIN_OUT=""
RPC_URL="${ETHEREUM_MAINNET_RPC:-}"
PRIVATE_KEY="${PRIVATE_KEY:-}"
INTEGRATION="${DODO_PMM_INTEGRATION_MAINNET:-}"
SLIPPAGE_BPS="${SLIPPAGE_BPS:-100}"
DRY_RUN=0
FORCE_DIRECT_DVM=0
ALLOW_DIRECT_DVM_FALLBACK=0
LOCK_DIR="/tmp/run-mainnet-public-dodo-cw-swap.${PAIR:-default}.lock"
for arg in "$@"; do
case "$arg" in
--pair=*) PAIR="${arg#*=}" ;;
--direction=*) DIRECTION="${arg#*=}" ;;
--amount=*) AMOUNT="${arg#*=}" ;;
--min-out=*) MIN_OUT="${arg#*=}" ;;
--rpc-url=*) RPC_URL="${arg#*=}" ;;
--integration=*) INTEGRATION="${arg#*=}" ;;
--slippage-bps=*) SLIPPAGE_BPS="${arg#*=}" ;;
--dry-run) DRY_RUN=1 ;;
--force-direct-dvm) FORCE_DIRECT_DVM=1 ;;
--allow-direct-dvm-fallback) ALLOW_DIRECT_DVM_FALLBACK=1 ;;
*)
echo "[fail] unknown arg: $arg" >&2
exit 2
;;
esac
done
if [[ -z "$RPC_URL" || -z "$PRIVATE_KEY" || -z "$INTEGRATION" ]]; then
echo "[fail] ETHEREUM_MAINNET_RPC, PRIVATE_KEY, and DODO_PMM_INTEGRATION_MAINNET are required" >&2
exit 1
fi
DEPLOYER="$(cast wallet address --private-key "$PRIVATE_KEY")"
case "$PAIR" in
cwusdt-usdc)
POOL="${POOL_CWUSDT_USDC_MAINNET:-}"
BASE_TOKEN="${CWUSDT_MAINNET:-0xaF5017d0163ecb99D9B5D94e3b4D7b09Af44D8AE}"
QUOTE_TOKEN="0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
;;
cwusdc-usdc)
POOL="${POOL_CWUSDC_USDC_MAINNET:-}"
BASE_TOKEN="${CWUSDC_MAINNET:-0x2de5F116bFcE3d0f922d9C8351e0c5Fc24b9284a}"
QUOTE_TOKEN="0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
;;
cwusdt-usdt)
POOL="${POOL_CWUSDT_USDT_MAINNET:-}"
BASE_TOKEN="${CWUSDT_MAINNET:-0xaF5017d0163ecb99D9B5D94e3b4D7b09Af44D8AE}"
QUOTE_TOKEN="0xdAC17F958D2ee523a2206206994597C13D831ec7"
;;
cwusdc-usdt)
POOL="${POOL_CWUSDC_USDT_MAINNET:-}"
BASE_TOKEN="${CWUSDC_MAINNET:-0x2de5F116bFcE3d0f922d9C8351e0c5Fc24b9284a}"
QUOTE_TOKEN="0xdAC17F958D2ee523a2206206994597C13D831ec7"
;;
cwusdt-cwusdc)
POOL="${POOL_CWUSDT_CWUSDC_MAINNET:-0xe944b7Cb012A0820c07f54D51e92f0e1C74168DB}"
BASE_TOKEN="${CWUSDT_MAINNET:-0xaF5017d0163ecb99D9B5D94e3b4D7b09Af44D8AE}"
QUOTE_TOKEN="${CWUSDC_MAINNET:-0x2de5F116bFcE3d0f922d9C8351e0c5Fc24b9284a}"
;;
cweurc-usdc)
POOL="${POOL_CWEURC_USDC_MAINNET:-}"
BASE_TOKEN="${CWEURC_MAINNET:-0xD4aEAa8cD3fB41Dc8437FaC7639B6d91B60A5e8d}"
QUOTE_TOKEN="0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
;;
cwgbpc-usdc)
POOL="${POOL_CWGBPC_USDC_MAINNET:-}"
BASE_TOKEN="${CWGBPC_MAINNET:-0xc074007dc0bfb384b1cf6426a56287ed23fe4d52}"
QUOTE_TOKEN="0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
;;
cwaudc-usdc)
POOL="${POOL_CWAUDC_USDC_MAINNET:-}"
BASE_TOKEN="${CWAUDC_MAINNET:-0x5020Db641B3Fc0dAbBc0c688C845bc4E3699f35F}"
QUOTE_TOKEN="0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
;;
cwcadc-usdc)
POOL="${POOL_CWCADC_USDC_MAINNET:-}"
BASE_TOKEN="${CWCADC_MAINNET:-0x209FE32fe7B541751D190ae4e50cd005DcF8EDb4}"
QUOTE_TOKEN="0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
;;
cwjpyc-usdc)
POOL="${POOL_CWJPYC_USDC_MAINNET:-}"
BASE_TOKEN="${CWJPYC_MAINNET:-0x07EEd0D7dD40984e47B9D3a3bdded1c536435582}"
QUOTE_TOKEN="0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
;;
cwchfc-usdc)
POOL="${POOL_CWCHFC_USDC_MAINNET:-}"
BASE_TOKEN="${CWCHFC_MAINNET:-0x0F91C5E6Ddd46403746aAC970D05d70FFe404780}"
QUOTE_TOKEN="0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
;;
*)
echo "[fail] unsupported pair: $PAIR" >&2
exit 2
;;
esac
if [[ -z "$POOL" ]]; then
POOL="$(cast call "$INTEGRATION" 'pools(address,address)(address)' "$BASE_TOKEN" "$QUOTE_TOKEN" --rpc-url "$RPC_URL" | awk '{print $1}' || true)"
else
mapped_pool="$(cast call "$INTEGRATION" 'pools(address,address)(address)' "$BASE_TOKEN" "$QUOTE_TOKEN" --rpc-url "$RPC_URL" 2>/dev/null | awk '{print $1}' || true)"
if [[ -n "$mapped_pool" && "$mapped_pool" != "0x0000000000000000000000000000000000000000" && "${mapped_pool,,}" != "${POOL,,}" ]]; then
POOL="$mapped_pool"
fi
fi
if [[ -z "$POOL" || "$POOL" == "0x0000000000000000000000000000000000000000" ]]; then
echo "[fail] pool missing for pair: $PAIR" >&2
exit 1
fi
case "$DIRECTION" in
base-to-quote)
TOKEN_IN="$BASE_TOKEN"
TOKEN_OUT="$QUOTE_TOKEN"
DIRECT_METHOD="sellBase(address)(uint256)"
;;
quote-to-base)
TOKEN_IN="$QUOTE_TOKEN"
TOKEN_OUT="$BASE_TOKEN"
DIRECT_METHOD="sellQuote(address)(uint256)"
;;
*)
echo "[fail] unsupported direction: $DIRECTION" >&2
exit 2
;;
esac
reserve_info="$(cast call "$POOL" 'getVaultReserve()(uint256,uint256)' --rpc-url "$RPC_URL")"
base_reserve="$(printf '%s\n' "$reserve_info" | sed -n '1p' | awk '{print $1}')"
quote_reserve="$(printf '%s\n' "$reserve_info" | sed -n '2p' | awk '{print $1}')"
fee_rate="$(cast call "$POOL" '_LP_FEE_RATE_()(uint256)' --rpc-url "$RPC_URL" | awk '{print $1}')"
mid_price="$(cast call "$POOL" 'getMidPrice()(uint256)' --rpc-url "$RPC_URL" | awk '{print $1}')"
quote_source="reserve_fallback"
query_amount_out=""
pool_surface="reserve_only"
if [[ "$DIRECTION" == "base-to-quote" ]]; then
if query_output="$(cast call "$POOL" 'querySellBase(address,uint256)(uint256,uint256)' "$DEPLOYER" "$AMOUNT" --rpc-url "$RPC_URL" 2>/dev/null)"; then
query_amount_out="$(printf '%s\n' "$query_output" | sed -n '1p' | awk '{print $1}')"
quote_source="pool_query"
fi
else
if query_output="$(cast call "$POOL" 'querySellQuote(address,uint256)(uint256,uint256)' "$DEPLOYER" "$AMOUNT" --rpc-url "$RPC_URL" 2>/dev/null)"; then
query_amount_out="$(printf '%s\n' "$query_output" | sed -n '1p' | awk '{print $1}')"
quote_source="pool_query"
fi
fi
if [[ "$quote_source" == "pool_query" ]]; then
pool_surface="full_quote_surface"
elif [[ -n "$base_reserve" && -n "$quote_reserve" && -n "$mid_price" ]]; then
# This class of mainnet pool exposes reserve/mid-price getters but reverts on querySell*.
# Treat it as integration-only unless proven otherwise.
pool_surface="partial_dodo_surface_integration_only"
fi
quote_info="$(python3 - "$AMOUNT" "$base_reserve" "$quote_reserve" "$fee_rate" "$DIRECTION" "$SLIPPAGE_BPS" "$quote_source" "$query_amount_out" <<'PY'
import sys
amount_in = int(sys.argv[1])
base_reserve = int(sys.argv[2])
quote_reserve = int(sys.argv[3])
fee_rate = int(sys.argv[4])
direction = sys.argv[5]
slippage_bps = int(sys.argv[6])
quote_source = sys.argv[7]
query_amount_out = int(sys.argv[8] or "0")
if quote_source == "pool_query":
out = query_amount_out
else:
net = amount_in * max(0, 10000 - fee_rate) // 10000
if direction == "base-to-quote":
out = (net * quote_reserve) // (base_reserve + net)
else:
out = (net * base_reserve) // (quote_reserve + net)
min_out = out * max(0, 10000 - slippage_bps) // 10000
print(out)
print(min_out)
PY
)"
estimated_out="$(printf '%s\n' "$quote_info" | sed -n '1p')"
fallback_min_out="$(printf '%s\n' "$quote_info" | sed -n '2p')"
if [[ -z "$MIN_OUT" ]]; then
MIN_OUT="$fallback_min_out"
fi
allowance="$(cast call "$TOKEN_IN" 'allowance(address,address)(uint256)' "$DEPLOYER" "$INTEGRATION" --rpc-url "$RPC_URL" | awk '{print $1}')"
balance_in_before="$(cast call "$TOKEN_IN" 'balanceOf(address)(uint256)' "$DEPLOYER" --rpc-url "$RPC_URL" | awk '{print $1}')"
balance_out_before="$(cast call "$TOKEN_OUT" 'balanceOf(address)(uint256)' "$DEPLOYER" --rpc-url "$RPC_URL" | awk '{print $1}')"
if (( balance_in_before < AMOUNT )); then
echo "[fail] insufficient input-token balance: have=$balance_in_before need=$AMOUNT" >&2
exit 1
fi
if (( DRY_RUN == 1 )); then
echo "pair=$PAIR"
echo "direction=$DIRECTION"
echo "pool=$POOL"
echo "integration=$INTEGRATION"
echo "poolSurface=$pool_surface"
echo "quoteSource=$quote_source"
echo "midPrice=$mid_price"
echo "lpFeeRate=$fee_rate"
echo "baseReserve=$base_reserve"
echo "quoteReserve=$quote_reserve"
echo "amountIn=$AMOUNT"
echo "estimatedOut=$estimated_out"
echo "minOut=$MIN_OUT"
echo "tokenIn=$TOKEN_IN"
echo "tokenOut=$TOKEN_OUT"
echo "tokenInBalanceBefore=$balance_in_before"
echo "tokenOutBalanceBefore=$balance_out_before"
echo "allowanceBefore=$allowance"
echo "approvalRequired=$(( allowance < AMOUNT ? 1 : 0 ))"
echo "dryRun=1"
exit 0
fi
LOCK_DIR="/tmp/run-mainnet-public-dodo-cw-swap.${PAIR}.${DIRECTION}.lock"
if ! mkdir "$LOCK_DIR" 2>/dev/null; then
echo "[fail] another live $PAIR $DIRECTION swap helper run appears to be in flight; rerun serially to avoid nonce collisions" >&2
exit 1
fi
cleanup_lock() {
rm -f "$LOCK_DIR/integration-estimate.err" 2>/dev/null || true
rmdir "$LOCK_DIR" 2>/dev/null || true
}
trap cleanup_lock EXIT
if (( allowance < AMOUNT )); then
cast send "$TOKEN_IN" \
'approve(address,uint256)(bool)' \
"$INTEGRATION" "$AMOUNT" \
--rpc-url "$RPC_URL" \
--private-key "$PRIVATE_KEY" >/dev/null
fi
tx_mode="integration"
tx_output=""
integration_error=""
transfer_tx_output=""
transfer_tx_hash=""
if (( FORCE_DIRECT_DVM == 1 )); then
integration_error="forced direct DVM fallback (--force-direct-dvm)"
else
if ! cast estimate \
--from "$DEPLOYER" \
"$INTEGRATION" \
'swapExactIn(address,address,uint256,uint256)(uint256)' \
"$POOL" "$TOKEN_IN" "$AMOUNT" "$MIN_OUT" \
--rpc-url "$RPC_URL" >/dev/null 2>"$LOCK_DIR/integration-estimate.err"; then
integration_error="$(tr '\n' ' ' <"$LOCK_DIR/integration-estimate.err" | sed 's/[[:space:]]\+/ /g; s/^ //; s/ $//')"
fi
fi
if [[ -z "$integration_error" ]]; then
if ! tx_output="$(
cast send "$INTEGRATION" \
'swapExactIn(address,address,uint256,uint256)(uint256)' \
"$POOL" "$TOKEN_IN" "$AMOUNT" "$MIN_OUT" \
--rpc-url "$RPC_URL" \
--private-key "$PRIVATE_KEY" 2>&1
)"; then
integration_error="$(printf '%s' "$tx_output" | tr '\n' ' ' | sed 's/[[:space:]]\+/ /g; s/^ //; s/ $//')"
tx_output=""
fi
fi
if [[ -n "$integration_error" ]]; then
if [[ "$pool_surface" == "partial_dodo_surface_integration_only" ]]; then
echo "[fail] integration swap failed on a partial DODO surface / integration-only pool; refusing raw direct fallback" >&2
echo "[fail] integration error: $integration_error" >&2
echo "[fail] pool=$POOL base=$BASE_TOKEN quote=$QUOTE_TOKEN tokenIn=$TOKEN_IN direction=$DIRECTION" >&2
exit 1
fi
if (( FORCE_DIRECT_DVM == 0 && ALLOW_DIRECT_DVM_FALLBACK == 0 )); then
echo "[fail] integration swap failed; refusing direct DVM fallback unless --allow-direct-dvm-fallback or --force-direct-dvm is set" >&2
echo "[fail] integration error: $integration_error" >&2
exit 1
fi
echo "[warn] integration swap failed; falling back to direct DVM flow" >&2
echo "[warn] integration error: $integration_error" >&2
transfer_tx_output="$(
cast send "$TOKEN_IN" \
'transfer(address,uint256)(bool)' \
"$POOL" "$AMOUNT" \
--rpc-url "$RPC_URL" \
--private-key "$PRIVATE_KEY"
)"
transfer_tx_hash="$(printf '%s\n' "$transfer_tx_output" | grep -E '^0x[0-9a-fA-F]{64}$' | tail -n1 || true)"
if [[ -z "$transfer_tx_hash" ]]; then
transfer_tx_hash="$(printf '%s\n' "$transfer_tx_output" | grep -E '^transactionHash[[:space:]]+0x[0-9a-fA-F]{64}$' | awk '{print $2}' | tail -n1 || true)"
fi
if [[ -z "$transfer_tx_hash" ]]; then
echo "[fail] could not parse transfer transaction hash from cast output" >&2
printf '%s\n' "$transfer_tx_output" >&2
exit 1
fi
cast receipt "$transfer_tx_hash" --rpc-url "$RPC_URL" >/dev/null
if ! tx_output="$(
cast send "$POOL" \
"$DIRECT_METHOD" \
"$DEPLOYER" \
--rpc-url "$RPC_URL" \
--private-key "$PRIVATE_KEY" 2>&1
)"; then
echo "[fail] direct DVM fallback failed" >&2
printf '%s\n' "$tx_output" >&2
exit 1
fi
tx_mode="direct_dvm"
fi
tx_hash="$(printf '%s\n' "$tx_output" | grep -E '^0x[0-9a-fA-F]{64}$' | tail -n1 || true)"
if [[ -z "$tx_hash" ]]; then
tx_hash="$(printf '%s\n' "$tx_output" | grep -E '^transactionHash[[:space:]]+0x[0-9a-fA-F]{64}$' | awk '{print $2}' | tail -n1 || true)"
fi
if [[ -z "$tx_hash" ]]; then
echo "[fail] could not parse transaction hash from cast output" >&2
printf '%s\n' "$tx_output" >&2
exit 1
fi
balance_in_after="$(cast call "$TOKEN_IN" 'balanceOf(address)(uint256)' "$DEPLOYER" --rpc-url "$RPC_URL" | awk '{print $1}')"
balance_out_after="$(cast call "$TOKEN_OUT" 'balanceOf(address)(uint256)' "$DEPLOYER" --rpc-url "$RPC_URL" | awk '{print $1}')"
amount_out_delta=$((balance_out_after - balance_out_before))
if (( amount_out_delta < MIN_OUT )); then
echo "[fail] observed output below minOut after ${tx_mode}: got=$amount_out_delta minOut=$MIN_OUT" >&2
exit 1
fi
echo "pair=$PAIR"
echo "direction=$DIRECTION"
echo "pool=$POOL"
echo "integration=$INTEGRATION"
echo "txMode=$tx_mode"
echo "poolSurface=$pool_surface"
if [[ -n "$transfer_tx_hash" ]]; then
echo "transferTxHash=$transfer_tx_hash"
fi
echo "quoteSource=$quote_source"
echo "midPrice=$mid_price"
echo "lpFeeRate=$fee_rate"
echo "baseReserve=$base_reserve"
echo "quoteReserve=$quote_reserve"
echo "amountIn=$AMOUNT"
echo "estimatedOut=$estimated_out"
echo "minOut=$MIN_OUT"
echo "tokenIn=$TOKEN_IN"
echo "tokenOut=$TOKEN_OUT"
echo "txHash=$tx_hash"
echo "tokenInBalanceBefore=$balance_in_before"
echo "tokenInBalanceAfter=$balance_in_after"
echo "tokenOutBalanceBefore=$balance_out_before"
echo "tokenOutBalanceAfter=$balance_out_after"
echo "observedAmountOut=$amount_out_delta"