Files
proxmox/scripts/verify/check-dodo-api-chain138-route-support.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

298 lines
11 KiB
Bash

#!/usr/bin/env bash
# Safely probe DODO's hosted SmartTrade API support for Chain 138 without leaking secrets.
#
# What it checks:
# 1. Official DODO docs swagger for published chain support.
# 2. Official DODO contract inventory for published on-chain support.
# 3. Hosted DODO SmartTrade quotes when a developer API key is available.
# 4. Local/public Chain 138 token-aggregation quote behavior for the same pairs.
#
# Usage:
# bash scripts/verify/check-dodo-api-chain138-route-support.sh
# DODO_API_KEY=... bash scripts/verify/check-dodo-api-chain138-route-support.sh
# BASE_URL=https://explorer.d-bis.org bash scripts/verify/check-dodo-api-chain138-route-support.sh
#
# Optional env:
# DODO_API_KEY / DODO_SECRET_KEY / DODO_DEVELOPER_API_KEY
# CHAIN_ID=138
# USER_ADDR=0x...
# BASE_URL=https://explorer.d-bis.org
# DODO_SLIPPAGE=0.03
# AMOUNTS_WEI="1000000000000000000 5000000000000000000 25000000000000000000"
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
cd "$PROJECT_ROOT"
source "$PROJECT_ROOT/scripts/lib/load-project-env.sh" >/dev/null 2>&1 || true
DOCS_URL="${DODO_DOCS_URL:-https://docs.dodoex.io/en/developer/developers-portal/api/smart-trade/api}"
CONTRACT_LIST_URL="${DODO_CONTRACT_LIST_URL:-https://api.dodoex.io/dodo-contract/list?version=v1,v2}"
QUOTE_URL="${DODO_QUOTE_URL:-https://api.dodoex.io/route-service/developer/swap}"
CHAIN_ID="${CHAIN_ID:-138}"
BASE_URL="${BASE_URL:-https://explorer.d-bis.org}"
BASE_URL="${BASE_URL%/}"
DODO_SLIPPAGE="${DODO_SLIPPAGE:-0.03}"
API_KEY="${DODO_API_KEY:-${DODO_SECRET_KEY:-${DODO_DEVELOPER_API_KEY:-}}}"
WETH="${CHAIN138_WETH_ADDRESS:-0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2}"
USDT="${OFFICIAL_USDT_ADDRESS:-0x004b63A7B5b0E06f6bB6adb4a5F9f590BF3182D1}"
USDC="${OFFICIAL_USDC_ADDRESS:-0x71D6687F38b93CCad569Fa6352c876eea967201b}"
CUSDT="${COMPLIANT_USDT_ADDRESS:-0x93E66202A11B1772E55407B32B44e5Cd8eda7f22}"
CUSDC="${COMPLIANT_USDC_ADDRESS:-0xf22258f57794CC8E06237084b353Ab30fFfa640b}"
AMOUNTS_WEI="${AMOUNTS_WEI:-1000000000000000000 5000000000000000000 25000000000000000000}"
TMP_DIR="$(mktemp -d)"
trap 'rm -rf "$TMP_DIR"' EXIT
log() { printf '%s\n' "$*"; }
ok() { printf '[OK] %s\n' "$*"; }
warn() { printf '[WARN] %s\n' "$*"; }
fail() { printf '[FAIL] %s\n' "$*"; }
need_cmd() {
command -v "$1" >/dev/null 2>&1 || {
printf 'ERROR: missing required command: %s\n' "$1" >&2
exit 1
}
}
need_cmd curl
need_cmd jq
need_cmd python3
derive_user_addr() {
if [[ -n "${USER_ADDR:-}" ]]; then
printf '%s\n' "$USER_ADDR"
return 0
fi
if command -v cast >/dev/null 2>&1 && [[ -n "${PRIVATE_KEY:-}" ]]; then
cast wallet address --private-key "$PRIVATE_KEY" 2>/dev/null || true
return 0
fi
printf '%s\n' "0x4A666F96fC8764181194447A7dFdb7d471b301C8"
}
USER_ADDR="$(derive_user_addr)"
detect_token_aggregation_prefix() {
local prefix response status body
for prefix in "" "/token-aggregation"; do
response="$(curl -sSL --max-time 20 -w $'\n%{http_code}' "${BASE_URL}${prefix}/api/v1/networks" 2>/dev/null)" || continue
status="$(printf '%s' "$response" | tail -n 1)"
body="$(printf '%s' "$response" | sed '$d')"
if [[ "$status" != "200" ]]; then
response="$(curl -sSL --max-time 20 -w $'\n%{http_code}' "${BASE_URL}${prefix}/api/v1/quote?chainId=${CHAIN_ID}&tokenIn=${CUSDT}&tokenOut=${CUSDC}&amountIn=1000000" 2>/dev/null)" || continue
status="$(printf '%s' "$response" | tail -n 1)"
body="$(printf '%s' "$response" | sed '$d')"
fi
if printf '%s' "$body" | jq -e 'type == "object" and (.networks | type == "array")' >/dev/null 2>&1; then
printf '%s\n' "$prefix"
return 0
fi
if printf '%s' "$body" | jq -e 'type == "object" and (.amountOut != null or .executorAddress != null)' >/dev/null 2>&1; then
printf '%s\n' "$prefix"
return 0
fi
done
printf '%s\n' ""
}
extract_docs_swagger() {
python3 -c "
import re
import sys
import urllib.parse
html = sys.stdin.read()
m = re.search(r'href=\"data:text/plain;charset=utf-8,([^\"]+)\"', html)
if not m:
sys.exit(1)
print(urllib.parse.unquote(m.group(1)))
" < <(curl -sSL --max-time 30 "$DOCS_URL")
}
docs_chain_supported() {
local swagger_json description
if ! swagger_json="$(extract_docs_swagger 2>/dev/null)"; then
warn "Could not extract SmartTrade swagger from official docs."
return 1
fi
description="$(printf '%s' "$swagger_json" | jq -r '.paths["/route-service/developer/swap"].get.parameters[] | select(.name == "chainId") | .description' 2>/dev/null || true)"
if [[ -z "$description" || "$description" == "null" ]]; then
warn "Swagger did not expose the published chain list."
return 1
fi
if python3 -c "
import re
import sys
chain_id = sys.argv[1]
text = sys.stdin.read()
nums = set(re.findall(r'(?<!\d)\d+(?!\d)', text))
sys.exit(0 if chain_id in nums else 1)
" "$CHAIN_ID" <<<"$description"
then
ok "Official DODO SmartTrade docs publish chainId=${CHAIN_ID} support."
return 0
fi
fail "Official DODO SmartTrade docs do not publish chainId=${CHAIN_ID} support."
return 1
}
contract_list_support() {
local body
body="$(curl -sSL --max-time 30 "$CONTRACT_LIST_URL")" || {
warn "Failed to fetch DODO contract inventory."
return 1
}
if printf '%s' "$body" | jq -e --arg chain "$CHAIN_ID" '.data | has($chain)' >/dev/null 2>&1; then
ok "Official DODO contract inventory includes chainId=${CHAIN_ID}."
return 0
fi
fail "Official DODO contract inventory does not include chainId=${CHAIN_ID}."
return 1
}
probe_dodo_quote() {
local label="$1"
local token_in="$2"
local token_out="$3"
local amount="$4"
local body_file="$TMP_DIR/dodo-${label//[^a-zA-Z0-9_-]/_}-${amount}.json"
local code status res_amount use_source msg_error
if [[ -z "$API_KEY" ]]; then
warn "Skipping hosted DODO quote for ${label} amount=${amount}: no DODO_API_KEY/DODO_SECRET_KEY set."
return 2
fi
code="$(
curl -sS -G -o "$body_file" -w "%{http_code}" --max-time 30 "$QUOTE_URL" \
--data-urlencode "chainId=${CHAIN_ID}" \
--data-urlencode "fromAmount=${amount}" \
--data-urlencode "fromTokenAddress=${token_in}" \
--data-urlencode "toTokenAddress=${token_out}" \
--data-urlencode "apikey=${API_KEY}" \
--data-urlencode "slippage=${DODO_SLIPPAGE}" \
--data-urlencode "userAddr=${USER_ADDR}"
)" || code="000"
if [[ "$code" != "200" ]]; then
if [[ -f "$body_file" ]]; then
fail "Hosted DODO quote ${label} amount=${amount} returned HTTP ${code}: $(head -c 220 "$body_file")"
else
fail "Hosted DODO quote ${label} amount=${amount} failed with HTTP ${code}."
fi
return 1
fi
status="$(jq -r '.status // empty' "$body_file" 2>/dev/null || true)"
res_amount="$(jq -r '.data.resAmount // empty' "$body_file" 2>/dev/null || true)"
use_source="$(jq -r '.data.useSource // empty' "$body_file" 2>/dev/null || true)"
msg_error="$(jq -r '.data.msgError // empty' "$body_file" 2>/dev/null || true)"
if [[ "$status" == "200" && -n "$res_amount" && "$res_amount" != "0" && "$res_amount" != "0.0" && -z "$msg_error" ]]; then
ok "Hosted DODO quote ${label} amount=${amount} succeeded via ${use_source:-unknown} with resAmount=${res_amount}."
return 0
fi
fail "Hosted DODO quote ${label} amount=${amount} returned no executable route. status=${status:-n/a} source=${use_source:-n/a} msgError=${msg_error:-n/a} resAmount=${res_amount:-n/a}"
return 1
}
probe_local_quote() {
local label="$1"
local token_in="$2"
local token_out="$3"
local amount="$4"
local prefix="$5"
local body_file="$TMP_DIR/local-${label//[^a-zA-Z0-9_-]/_}-${amount}.json"
local code error executor amount_out
code="$(
curl -sS -o "$body_file" -w "%{http_code}" --max-time 30 \
"${BASE_URL}${prefix}/api/v1/quote?chainId=${CHAIN_ID}&tokenIn=${token_in}&tokenOut=${token_out}&amountIn=${amount}"
)" || code="000"
if [[ "$code" != "200" ]]; then
fail "Local token-aggregation quote ${label} amount=${amount} returned HTTP ${code}."
return 1
fi
if jq -e 'type == "object" and has("error")' "$body_file" >/dev/null 2>&1; then
error="$(jq -r '.error' "$body_file" 2>/dev/null || true)"
fail "Local token-aggregation quote ${label} amount=${amount} returned error: ${error:-unknown error}"
return 1
fi
executor="$(jq -r '.executorAddress // empty' "$body_file" 2>/dev/null || true)"
amount_out="$(jq -r '.amountOut // empty' "$body_file" 2>/dev/null || true)"
if [[ -n "$amount_out" && "$amount_out" != "0" && "$amount_out" != "0.0" ]]; then
ok "Local token-aggregation quote ${label} amount=${amount} succeeded with amountOut=${amount_out} executor=${executor:-n/a}."
return 0
fi
fail "Local token-aggregation quote ${label} amount=${amount} returned no amountOut."
return 1
}
run_pair_suite() {
local name="$1"
local token_in="$2"
local token_out="$3"
local prefix="$4"
local amount
log ""
log "Pair: ${name}"
for amount in $AMOUNTS_WEI; do
probe_dodo_quote "$name" "$token_in" "$token_out" "$amount" || true
probe_local_quote "$name" "$token_in" "$token_out" "$amount" "$prefix" || true
done
}
TA_PREFIX="$(detect_token_aggregation_prefix)"
log "=== DODO SmartTrade / Chain 138 support probe ==="
log "Docs URL: $DOCS_URL"
log "Contract list: $CONTRACT_LIST_URL"
log "Quote endpoint: $QUOTE_URL"
log "Chain ID: $CHAIN_ID"
log "Base URL: $BASE_URL"
log "User address: $USER_ADDR"
if [[ -n "$API_KEY" ]]; then
ok "DODO API key present in environment (value intentionally not printed)."
else
warn "No DODO API key found in environment; hosted quote probes will be skipped."
fi
if [[ -n "$TA_PREFIX" ]]; then
ok "Detected token-aggregation prefix: ${TA_PREFIX}/api/v1"
else
warn "Could not auto-detect token-aggregation prefix; defaulting to root /api/v1."
fi
log ""
log "=== Published support checks ==="
docs_chain_supported || true
contract_list_support || true
log ""
log "=== Live quote probes ==="
run_pair_suite "WETH->USDT" "$WETH" "$USDT" "$TA_PREFIX"
run_pair_suite "WETH->USDC" "$WETH" "$USDC" "$TA_PREFIX"
log ""
log "=== Positive-control local pairs ==="
probe_local_quote "cUSDT->USDT" "$CUSDT" "$USDT" "1000000" "$TA_PREFIX" || true
probe_local_quote "cUSDT->cUSDC" "$CUSDT" "$CUSDC" "1000000" "$TA_PREFIX" || true
log ""
log "Notes:"
log " - Hosted DODO SmartTrade probes are quote-only and never print the API key."
log " - If official docs and contract inventory both omit chainId=${CHAIN_ID}, the hosted API should be treated as unsupported until DODO adds the chain."
log " - Local token-aggregation probes show current Chain 138 route availability regardless of hosted DODO support."