#!/usr/bin/env bash # Update All Oracle Contracts with Current Token Prices # This script reads the token list and updates oracle contracts for all tokens # Usage: ./scripts/update-all-oracle-prices.sh [rpc-url] [private-key] set -euo pipefail # Colors RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' CYAN='\033[0;36m' NC='\033[0m' log_info() { echo -e "${BLUE}[INFO]${NC} $1"; } log_success() { echo -e "${GREEN}[✓]${NC} $1"; } log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } log_error() { echo -e "${RED}[ERROR]${NC} $1"; } log_detail() { echo -e "${CYAN}[DETAIL]${NC} $1"; } RPC_URL="${1:-https://rpc-http-pub.d-bis.org}" PRIVATE_KEY="${2:-${DEPLOYER_PRIVATE_KEY:-}}" if [ -z "$PRIVATE_KEY" ]; then log_error "Private key required" log_info "Usage: $0 [rpc-url] [private-key]" log_info " OR: Set DEPLOYER_PRIVATE_KEY environment variable" exit 1 fi SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" TOKEN_LIST_FILE="${TOKEN_LIST_FILE:-$PROJECT_ROOT/token-lists/lists/dbis-138.tokenlist.json}" if [ ! -f "$TOKEN_LIST_FILE" ]; then log_error "Token list file not found: $TOKEN_LIST_FILE" exit 1 fi echo "=========================================" echo "Update All Oracle Prices" echo "=========================================" echo "" log_info "RPC URL: $RPC_URL" log_info "Token List: $TOKEN_LIST_FILE" echo "" # Check RPC connectivity log_info "Checking RPC connectivity..." if ! curl -s -X POST -H "Content-Type: application/json" \ --data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \ "$RPC_URL" >/dev/null 2>&1; then log_error "RPC is not accessible at $RPC_URL" exit 1 fi log_success "RPC is accessible" echo "" # Parse token list and map tokens to CoinGecko IDs # Token mapping: symbol -> coingecko_id declare -A TOKEN_MAP=( ["ETH"]="ethereum" ["WETH"]="ethereum" # WETH tracks ETH price ["WETH10"]="ethereum" # WETH10 tracks ETH price ["LINK"]="chainlink" ["ETH-USD"]="ethereum" # Oracle contract for ETH/USD ) # Map of oracle addresses (token symbol -> aggregator address) # Note: Updates go to the aggregator, but dApps read from the proxy # Aggregator: 0x99b3511a2d315a497c8112c1fdd8d508d4b1e506 (for updates) # Proxy: 0x3304b747e565a97ec8ac220b0b6a1f6ffdb837e6 (for reading) declare -A ORACLE_ADDRESSES=( ["ETH-USD"]="0x99b3511a2d315a497c8112c1fdd8d508d4b1e506" # Aggregator address for updates ["ETH"]="0x99b3511a2d315a497c8112c1fdd8d508d4b1e506" ["WETH"]="0x99b3511a2d315a497c8112c1fdd8d508d4b1e506" # Uses ETH/USD oracle ["WETH10"]="0x99b3511a2d315a497c8112c1fdd8d508d4b1e506" # Uses ETH/USD oracle # ["LINK-USD"]="0x..." # Would need separate oracle contract ) # Extract tokens from token list TOKENS=$(jq -r '.tokens[] | select(.tags[]? | contains("oracle") or contains("pricefeed")) | "\(.symbol)|\(.address)|\(.name)"' "$TOKEN_LIST_FILE" 2>/dev/null || echo "") if [ -z "$TOKENS" ]; then log_warn "No oracle/pricefeed tokens found in token list" log_info "Looking for all tokens..." TOKENS=$(jq -r '.tokens[] | "\(.symbol)|\(.address)|\(.name)"' "$TOKEN_LIST_FILE" 2>/dev/null || echo "") fi if [ -z "$TOKENS" ]; then log_error "Could not parse tokens from token list" exit 1 fi # Build list of unique CoinGecko IDs to fetch COINGECKO_IDS=() while IFS='|' read -r SYMBOL ADDRESS NAME; do if [ -z "$SYMBOL" ]; then continue; fi COINGECKO_ID="${TOKEN_MAP[$SYMBOL]}" if [ -n "$COINGECKO_ID" ] && [[ ! " ${COINGECKO_IDS[@]} " =~ " ${COINGECKO_ID} " ]]; then COINGECKO_IDS+=("$COINGECKO_ID") fi done <<< "$TOKENS" if [ ${#COINGECKO_IDS[@]} -eq 0 ]; then log_error "No valid CoinGecko IDs found for tokens" exit 1 fi log_info "Tokens to update: ${COINGECKO_IDS[*]}" echo "" # Fetch prices from CoinGecko log_info "Fetching prices from CoinGecko..." COINGECKO_IDS_CSV=$(IFS=,; echo "${COINGECKO_IDS[*]}") PRICES_JSON=$(curl -s "https://api.coingecko.com/api/v3/simple/price?ids=${COINGECKO_IDS_CSV}&vs_currencies=usd" 2>/dev/null || echo "{}") if [ -z "$PRICES_JSON" ] || [ "$PRICES_JSON" = "{}" ]; then log_error "Failed to fetch prices from CoinGecko" exit 1 fi log_success "Prices fetched successfully" echo "" # Update each oracle UPDATED=0 FAILED=0 log_info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" log_info "Updating Oracle Contracts" log_info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" while IFS='|' read -r SYMBOL ADDRESS NAME; do if [ -z "$SYMBOL" ]; then continue; fi # Skip if not an oracle/pricefeed token (unless it's a wrapped token that uses ETH oracle) if [[ ! "$NAME" =~ "Price Feed" ]] && [[ ! "$SYMBOL" =~ ^(WETH|WETH10|ETH)$ ]]; then continue fi COINGECKO_ID="${TOKEN_MAP[$SYMBOL]}" if [ -z "$COINGECKO_ID" ]; then log_warn "No CoinGecko mapping for $SYMBOL ($NAME), skipping..." continue fi ORACLE_ADDRESS="${ORACLE_ADDRESSES[$SYMBOL]}" if [ -z "$ORACLE_ADDRESS" ]; then log_warn "No oracle address configured for $SYMBOL, skipping..." continue fi # Get price from CoinGecko response PRICE=$(echo "$PRICES_JSON" | jq -r ".[\"$COINGECKO_ID\"].usd // empty" 2>/dev/null || echo "") if [ -z "$PRICE" ] || [ "$PRICE" = "null" ] || [ "$PRICE" = "0" ]; then log_error "Could not get price for $SYMBOL (CoinGecko ID: $COINGECKO_ID)" ((FAILED++)) continue fi log_info "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" log_info "Updating: $NAME ($SYMBOL)" log_detail "Oracle Address: $ORACLE_ADDRESS" log_detail "Current Price: \$$PRICE USD" # Convert to 8 decimals (oracle format) PRICE_DECIMALS=$(python3 << PYEOF price = float("$PRICE") decimals = int(price * 100000000) print(decimals) PYEOF ) log_detail "Price in 8 decimals: $PRICE_DECIMALS" # Check current oracle price CURRENT_ORACLE_DATA=$(cast call "$ORACLE_ADDRESS" \ "latestRoundData()" \ --rpc-url "$RPC_URL" 2>/dev/null || echo "") if [ -n "$CURRENT_ORACLE_DATA" ] && [ "$CURRENT_ORACLE_DATA" != "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" ]; then CURRENT_ANSWER=$(echo "$CURRENT_ORACLE_DATA" | cut -c 131-194) CURRENT_PRICE=$(python3 << PYEOF answer_hex = "$CURRENT_ANSWER" answer_int = int(answer_hex, 16) if answer_int > 2**255: answer_int = answer_int - 2**256 price = answer_int / 100000000 print(price) PYEOF ) log_detail "Current oracle price: \$$(printf '%.2f' $CURRENT_PRICE)" # Check if update is needed (only update if difference > 0.5%) PRICE_DIFF=$(python3 << PYEOF current = float("$CURRENT_PRICE") new = float("$PRICE") if current == 0: print(100) # Always update if current is zero else: diff = abs(new - current) / current * 100 print(diff) PYEOF ) if (( $(echo "$PRICE_DIFF < 0.5" | bc -l 2>/dev/null || echo "0") )); then log_warn "Price difference is less than 0.5% (${PRICE_DIFF}%)" log_info "Skipping update to save gas" echo "" continue fi fi # Update oracle contract log_info "Sending transaction to update oracle..." # Try different update methods (use uint256 for updateAnswer) TX_HASH="" for METHOD_SIG in "updateAnswer(uint256)" "transmit(int256)" "setLatestAnswer(int256)"; do METHOD=$(echo "$METHOD_SIG" | cut -d'(' -f1) TX_HASH=$(cast send "$ORACLE_ADDRESS" \ "$METHOD_SIG" \ "$PRICE_DECIMALS" \ --rpc-url "$RPC_URL" \ --private-key "$PRIVATE_KEY" \ --json 2>/dev/null | python3 -c "import sys, json; print(json.load(sys.stdin).get('transactionHash', ''))" 2>/dev/null || echo "") if [ -n "$TX_HASH" ] && [ "$TX_HASH" != "null" ]; then log_success "Transaction sent using $METHOD: $TX_HASH" break fi done if [ -z "$TX_HASH" ]; then log_error "Failed to update oracle for $SYMBOL" log_warn "Tried methods: updateAnswer, transmit, setLatestAnswer" ((FAILED++)) echo "" continue fi # Wait briefly for confirmation sleep 3 # Verify update NEW_ORACLE_DATA=$(cast call "$ORACLE_ADDRESS" \ "latestRoundData()" \ --rpc-url "$RPC_URL" 2>/dev/null || echo "") if [ -n "$NEW_ORACLE_DATA" ]; then NEW_ANSWER=$(echo "$NEW_ORACLE_DATA" | cut -c 131-194) NEW_PRICE=$(python3 << PYEOF answer_hex = "$NEW_ANSWER" answer_int = int(answer_hex, 16) if answer_int > 2**255: answer_int = answer_int - 2**256 price = answer_int / 100000000 print(price) PYEOF ) log_success "Oracle updated: \$$(printf '%.2f' $NEW_PRICE) USD" ((UPDATED++)) else log_warn "Could not verify update (may need more time)" ((UPDATED++)) fi echo "" done <<< "$TOKENS" echo "" echo "=========================================" echo "Summary" echo "=========================================" echo "" log_success "Successfully updated: $UPDATED oracle(s)" if [ $FAILED -gt 0 ]; then log_warn "Failed to update: $FAILED oracle(s)" fi echo "" if [ $UPDATED -gt 0 ]; then log_info "Oracle prices have been updated successfully!" log_info "" log_info "Next steps:" log_info " 1. Verify prices using: cast call 'latestRoundData()' --rpc-url $RPC_URL" log_info " 2. Check oracle publisher service is running (VMID 3500) for automatic updates" log_info " 3. dApps can now query oracle contracts for USD prices" fi echo ""