#!/usr/bin/env bash # Print one number: hosted **gross USDC per cWUSDC** for selling `base_raw` minimal units # on Ethereum mainnet, using either **DODO SmartTrade** or **1inch v6** quote APIs. # Intended for `pmm-flash-push-break-even.mjs --external-exit-price-cmd` (exit becomes "live"). # # Dependencies: curl, python3 (no jq). # # Usage: # source scripts/lib/load-project-env.sh # export ONEINCH_API_KEY=... # or DODO_API_KEY / DODO_SECRET_KEY / DODO_DEVELOPER_API_KEY # bash scripts/verify/print-mainnet-cwusdc-external-exit-quote.sh 1inch 6187975 # bash scripts/verify/print-mainnet-cwusdc-external-exit-quote.sh dodo 6187975 # # Env (optional): # EXIT_QUOTE_ENGINE — default engine if $1 omitted: dodo | 1inch # EXIT_QUOTE_BASE_RAW — default size if $2 omitted (raw 6dp cWUSDC) # CWUSDC_MAINNET, MAINNET_USDC — token overrides # DODO_QUOTE_URL — default https://api.dodoex.io/route-service/developer/swap # DODO_SLIPPAGE — default 0.005 # DODO_USER_ADDRESS / USER_ADDR / DEPLOYER_ADDRESS — DODO userAddr; else derived from PRIVATE_KEY # ONEINCH_API_URL — default https://api.1inch.dev/swap/v6.0 # # Notes: # - Quotes are **not** guaranteed executable at execution time; refresh per tranche. # - DODO and 1inch may not route thin cW*; failures exit non-zero with stderr detail. set -euo pipefail ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" # shellcheck disable=SC1091 source "$ROOT/scripts/lib/load-project-env.sh" 2>/dev/null || true require_cmd() { command -v "$1" >/dev/null 2>&1 || { echo "[fail] missing command: $1" >&2 exit 1 } } require_cmd curl require_cmd python3 ENGINE="${1:-${EXIT_QUOTE_ENGINE:-dodo}}" BASE_RAW="${2:-${EXIT_QUOTE_BASE_RAW:-6187975}}" ENGINE_LC="$(printf '%s' "$ENGINE" | tr '[:upper:]' '[:lower:]')" CWUSDC="${CWUSDC_MAINNET:-0x2de5F116bFcE3d0f922d9C8351e0c5Fc24b9284a}" USDC="${MAINNET_USDC:-0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48}" CHAIN_ID=1 DEC_OUT=6 if ! [[ "$BASE_RAW" =~ ^[0-9]+$ ]] || ((BASE_RAW < 1)); then echo "[fail] base_raw must be a positive integer (cWUSDC raw, 6 decimals)" >&2 exit 1 fi quote_dodo() { local api_key url slippage user_addr body code api_key="${DODO_API_KEY:-${DODO_SECRET_KEY:-${DODO_DEVELOPER_API_KEY:-}}}" if [[ -z "$api_key" ]]; then echo "[fail] DODO quote needs DODO_API_KEY, DODO_SECRET_KEY, or DODO_DEVELOPER_API_KEY" >&2 return 1 fi url="${DODO_QUOTE_URL:-https://api.dodoex.io/route-service/developer/swap}" slippage="${DODO_SLIPPAGE:-0.005}" user_addr="${DODO_USER_ADDRESS:-${USER_ADDR:-${DEPLOYER_ADDRESS:-}}}" if [[ -z "$user_addr" && -n "${PRIVATE_KEY:-}" ]] && command -v cast >/dev/null 2>&1; then user_addr="$(cast wallet address --private-key "$PRIVATE_KEY" 2>/dev/null || true)" fi user_addr="${user_addr:-0x0000000000000000000000000000000000000001}" code="$( curl -sS -G -o "$tmp" -w "%{http_code}" --max-time 45 "$url" \ --data-urlencode "chainId=${CHAIN_ID}" \ --data-urlencode "fromAmount=${BASE_RAW}" \ --data-urlencode "fromTokenAddress=${CWUSDC}" \ --data-urlencode "toTokenAddress=${USDC}" \ --data-urlencode "apikey=${api_key}" \ --data-urlencode "slippage=${slippage}" \ --data-urlencode "userAddr=${user_addr}" )" || code="000" if [[ "$code" != "200" ]]; then echo "[fail] DODO HTTP ${code}: $(head -c 400 "$tmp" 2>/dev/null || true)" >&2 return 1 fi python3 - "$BASE_RAW" "$DEC_OUT" <"$tmp" <<'PY' import json, sys, decimal base_in = int(sys.argv[1]) dec_out = int(sys.argv[2]) j = json.load(sys.stdin) data = j.get("data") if isinstance(j.get("data"), dict) else {} msg = (data.get("msgError") or data.get("message") or "").strip() if msg: print(f"[fail] DODO msgError={msg}", file=sys.stderr) sys.exit(1) raw = data.get("resAmount") or data.get("toTokenAmount") or j.get("resAmount") if raw is None or raw == "": print("[fail] DODO missing resAmount / toTokenAmount", file=sys.stderr) sys.exit(1) s = str(raw).strip() try: if "." in s: out_raw = int((decimal.Decimal(s) * (decimal.Decimal(10) ** dec_out)).to_integral_value(rounding=decimal.ROUND_DOWN)) else: out_raw = int(s) except Exception as e: print(f"[fail] DODO parse resAmount={raw!r}: {e}", file=sys.stderr) sys.exit(1) if out_raw <= 0 or base_in <= 0: print("[fail] DODO non-positive amounts", file=sys.stderr) sys.exit(1) ratio = out_raw / base_in out = f"{ratio:.12f}".rstrip("0").rstrip(".") print(out or "0") PY } quote_oneinch() { local key base url code key="${ONEINCH_API_KEY:-}" if [[ -z "$key" ]]; then echo "[fail] 1inch quote needs ONEINCH_API_KEY (see .env.master.example)" >&2 return 1 fi base="${ONEINCH_API_URL:-https://api.1inch.dev/swap/v6.0}" base="${base%/}" url="${base}/${CHAIN_ID}/quote?src=${CWUSDC}&dst=${USDC}&amount=${BASE_RAW}" code="$( curl -sS -o "$tmp" -w "%{http_code}" --max-time 45 \ -H "Authorization: Bearer ${key}" \ -H "Accept: application/json" \ "$url" )" || code="000" if [[ "$code" != "200" ]]; then echo "[fail] 1inch HTTP ${code}: $(head -c 400 "$tmp" 2>/dev/null || true)" >&2 return 1 fi python3 - "$BASE_RAW" <"$tmp" <<'PY' import json, sys base_in = int(sys.argv[1]) j = json.load(sys.stdin) dst = j.get("dstAmount") or j.get("toAmount") or j.get("toTokenAmount") if not dst: print("[fail] 1inch missing dstAmount", file=sys.stderr) sys.exit(1) out_raw = int(str(dst).strip()) if out_raw <= 0: print("[fail] 1inch non-positive dstAmount", file=sys.stderr) sys.exit(1) ratio = out_raw / base_in out = f"{ratio:.12f}".rstrip("0").rstrip(".") print(out or "0") PY } tmp="$(mktemp)" trap 'rm -f "$tmp"' EXIT case "$ENGINE_LC" in dodo) quote_dodo ;; 1inch | oneinch) quote_oneinch ;; *) echo "[fail] engine must be dodo or 1inch (got: $ENGINE)" >&2 exit 2 ;; esac