Files
proxmox/docs/04-configuration/metamask/ORACLE_PRICE_FEED_SETUP.md
defiQUG bea1903ac9
Some checks failed
Deploy to Phoenix / deploy (push) Has been cancelled
Sync all local changes: docs, config, scripts, submodule refs, verification evidence
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-21 15:46:06 -08:00

11 KiB

Oracle Price Feed Setup for MetaMask and Wallets

Last Updated: 2026-01-31
Document Version: 1.0
Status: Active Documentation


Date: 2026-01-26
Purpose: Complete guide for setting up oracle price feeds to provide market data to MetaMask and other wallets


📋 Overview

This guide explains how to configure the oracle price feed system to provide accurate ETH/USD pricing and market data to MetaMask and other wallets on ChainID 138.


🔗 Oracle Contract Information

Property Value
Oracle Proxy Address 0x3304b747e565a97ec8ac220b0b6a1f6ffdb837e6
Oracle Aggregator 0x99b3511a2d315a497c8112c1fdd8d508d4b1e506
Price Feed ETH/USD
Decimals 8
Update Frequency 60 seconds (heartbeat)
ChainID 138
RPC Endpoint https://rpc-http-pub.d-bis.org

⚠️ MetaMask Price Feed Limitation

Important: MetaMask does NOT automatically query oracle contracts for USD prices on custom chains.

MetaMask Price Sources (in order):

  1. CoinGecko API - Primary source (requires token listing)
  2. Token Lists - Limited price metadata support
  3. Oracle Contracts - NOT automatically queried

Implication: Even with a working oracle, MetaMask may not display USD values unless tokens are listed on CoinGecko.


Solution 1: Oracle Publisher Service

Overview

The Oracle Publisher Service fetches prices from external APIs (CoinGecko, Binance) and updates the oracle contract on-chain.

Service Configuration

Service Location: VMID 3500 (Oracle Publisher Container)

Environment Variables:

# Oracle Contract Addresses
ORACLE_ADDRESS=0x3304b747e565a97ec8ac220b0b6a1f6ffdb837e6
AGGREGATOR_ADDRESS=0x99b3511a2d315a497c8112c1fdd8d508d4b1e506

# Network Configuration
RPC_URL=https://rpc-http-pub.d-bis.org
CHAIN_ID=138

# Update Configuration
UPDATE_INTERVAL=60  # seconds
PRICE_SOURCE=coingecko  # or binance, coinbase

# API Keys (if needed)
COINGECKO_API_KEY=your-coingecko-api-key  # ✅ Configured - Demo API key
BINANCE_API_KEY=    # Optional

Service Status Check

# Check if container exists
ssh root@192.168.11.10 "pct list | grep 3500"

# Check service status
ssh root@192.168.11.10 "pct exec 3500 -- systemctl status oracle-publisher.service"

# Check service logs
ssh root@192.168.11.10 "pct exec 3500 -- journalctl -u oracle-publisher.service -n 50"

Service Setup

If the service doesn't exist, create it:

# 1. Create container (VMID 3500)
# 2. Install Node.js/Python runtime
# 3. Install Oracle Publisher service
# 4. Configure environment variables
# 5. Start service

Service Script Example:

// oracle-publisher.js
const { ethers } = require('ethers');
const axios = require('axios');

const ORACLE_ADDRESS = process.env.ORACLE_ADDRESS;
const RPC_URL = process.env.RPC_URL;
const UPDATE_INTERVAL = parseInt(process.env.UPDATE_INTERVAL || '60');
const COINGECKO_API_KEY = process.env.COINGECKO_API_KEY; // ✅ Configured

const provider = new ethers.JsonRpcProvider(RPC_URL);
const signer = new ethers.Wallet(process.env.PRIVATE_KEY, provider);

const oracleABI = [
  "function updateAnswer(int256 answer) external",
  "function latestRoundData() external view returns (uint80, int256, uint256, uint256, uint80)"
];

const oracle = new ethers.Contract(ORACLE_ADDRESS, oracleABI, signer);

async function fetchETHPrice() {
  try {
    // Fetch from CoinGecko (with API key for higher rate limits)
    const apiKeyParam = COINGECKO_API_KEY ? `&x_cg_demo_api_key=${COINGECKO_API_KEY}` : '';
    const response = await axios.get(
      `https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd${apiKeyParam}`
    );
    const price = response.data.ethereum.usd;
    return Math.round(price * 1e8); // Convert to 8 decimals
  } catch (error) {
    console.error('Error fetching price:', error);
    return null;
  }
}

async function updateOracle() {
  const price = await fetchETHPrice();
  if (!price) {
    console.error('Failed to fetch price');
    return;
  }
  
  try {
    const tx = await oracle.updateAnswer(price);
    await tx.wait();
    console.log(`Oracle updated: ETH/USD = $${price / 1e8}`);
  } catch (error) {
    console.error('Error updating oracle:', error);
  }
}

// Update every 60 seconds
setInterval(updateOracle, UPDATE_INTERVAL * 1000);
updateOracle(); // Initial update

Solution 2: Verify Oracle Price Data

On-Chain Verification

# Get latest price from oracle
cast call 0x3304b747e565a97ec8ac220b0b6a1f6ffdb837e6 \
  "latestRoundData()" \
  --rpc-url https://rpc-http-pub.d-bis.org

# Expected output:
# (roundId, answer, startedAt, updatedAt, answeredInRound)
# answer is in 8 decimals (e.g., 3000000000 = $3000.00)

JavaScript Verification

const { ethers } = require('ethers');

async function verifyOraclePrice() {
  const provider = new ethers.JsonRpcProvider('https://rpc-http-pub.d-bis.org');
  const oracleABI = [
    "function latestRoundData() external view returns (uint80, int256, uint256, uint256, uint80)",
    "function decimals() external view returns (uint8)"
  ];
  
  const oracle = new ethers.Contract(
    '0x3304b747e565a97ec8ac220b0b6a1f6ffdb837e6',
    oracleABI,
    provider
  );
  
  const [roundId, answer, startedAt, updatedAt, answeredInRound] = await oracle.latestRoundData();
  const decimals = await oracle.decimals();
  const price = Number(answer) / Math.pow(10, decimals);
  const lastUpdate = new Date(Number(updatedAt) * 1000);
  const now = new Date();
  const ageMinutes = (now - lastUpdate) / 1000 / 60;
  
  console.log('Oracle Price Feed Status:');
  console.log(`  ETH/USD Price: $${price.toFixed(2)}`);
  console.log(`  Round ID: ${roundId}`);
  console.log(`  Last Update: ${lastUpdate.toISOString()}`);
  console.log(`  Age: ${ageMinutes.toFixed(1)} minutes`);
  console.log(`  Status: ${ageMinutes < 5 ? '✅ Fresh' : '❌ Stale'}`);
  
  return { price, lastUpdate, ageMinutes, fresh: ageMinutes < 5 };
}

verifyOraclePrice();

Solution 3: CoinGecko Listing (For Native MetaMask Support)

Why CoinGecko?

MetaMask primarily uses CoinGecko API for USD price display. To get native MetaMask support:

  1. Submit tokens to CoinGecko
  2. Provide market data
  3. Wait for listing approval

CoinGecko Submission Process

  1. Visit: https://www.coingecko.com/en/coins/new

  2. Required Information:

    • Token name and symbol
    • Contract address
    • Chain ID (138)
    • Decimals
    • Logo URL
    • Website and social links
    • Market data sources (DEX, liquidity pools)
  3. Market Data Requirements:

    • Trading volume
    • Liquidity pools
    • DEX listings
    • Price history
  4. Review Process:

    • Typically 1-2 weeks
    • CoinGecko team reviews submission
    • May request additional information

Current Token Status

Token CoinGecko Listed Action Required
ETH Yes None
WETH9 No Submit for listing
WETH10 No Submit for listing
cUSDT No Submit for listing
cUSDC No Submit for listing

Solution 4: dApp Integration (Query Oracle Directly)

For dApps, you can query the oracle contract directly and display USD values:

React Example

import { ethers } from 'ethers';
import { useEffect, useState } from 'react';

const ORACLE_ADDRESS = '0x3304b747e565a97ec8ac220b0b6a1f6ffdb837e6';
const RPC_URL = 'https://rpc-http-pub.d-bis.org';

const oracleABI = [
  "function latestRoundData() external view returns (uint80, int256, uint256, uint256, uint80)"
];

export function useETHPrice() {
  const [price, setPrice] = useState<number | null>(null);
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    async function fetchPrice() {
      try {
        const provider = new ethers.JsonRpcProvider(RPC_URL);
        const oracle = new ethers.Contract(ORACLE_ADDRESS, oracleABI, provider);
        const [, answer] = await oracle.latestRoundData();
        const ethPrice = Number(answer) / 1e8;
        setPrice(ethPrice);
      } catch (error) {
        console.error('Error fetching ETH price:', error);
      } finally {
        setLoading(false);
      }
    }
    
    fetchPrice();
    const interval = setInterval(fetchPrice, 60000); // Update every minute
    return () => clearInterval(interval);
  }, []);
  
  return { price, loading };
}

// Usage in component
function BalanceDisplay({ balance }: { balance: string }) {
  const { price, loading } = useETHPrice();
  const ethBalance = parseFloat(balance);
  const usdValue = price ? ethBalance * price : null;
  
  return (
    <div>
      <p>{ethBalance} ETH</p>
      {usdValue && <p>${usdValue.toFixed(2)} USD</p>}
      {loading && <p>Loading price...</p>}
    </div>
  );
}

📋 Complete Setup Checklist

Oracle Publisher Service

  • Service Exists - Verify VMID 3500 exists
  • Service Running - Check service status
  • Configuration - Verify environment variables
  • Price Updates - Verify prices updating every 60 seconds
  • Price Accuracy - Compare with CoinGecko/Binance

Oracle Contract

  • Contract Deployed - Verify oracle proxy at 0x3304b747e565a97ec8ac220b0b6a1f6ffdb837e6
  • Price Data - Verify contract has price data
  • Price Freshness - Verify prices updated within last 5 minutes
  • Decimals - Verify oracle returns 8 decimals

CoinGecko Listing (Optional)

  • Token Information - Prepare token details
  • Market Data - Gather trading volume and liquidity data
  • Submission - Submit tokens to CoinGecko
  • Follow-up - Respond to CoinGecko requests

dApp Integration

  • Oracle Integration - Add oracle querying to dApps
  • Price Display - Display USD values in UI
  • Error Handling - Handle oracle query failures
  • Caching - Cache prices to reduce RPC calls

🔍 Troubleshooting

Oracle Returns Zero Price

Problem: Oracle contract returns all zeros

Solutions:

  1. Check Oracle Publisher service is running
  2. Verify service has correct oracle address
  3. Check service logs for errors
  4. Verify RPC endpoint is accessible
  5. Check service has permission to update oracle

Price is Stale

Problem: Oracle price hasn't updated in >5 minutes

Solutions:

  1. Check Oracle Publisher service status
  2. Verify update interval is set correctly
  3. Check service logs for update errors
  4. Verify API keys (if required)
  5. Check network connectivity

MetaMask Not Showing USD

Problem: MetaMask doesn't display USD values

Solutions:

  1. For native tokens (ETH): Usually works automatically
  2. For custom tokens: Submit to CoinGecko
  3. For dApps: Query oracle directly and display USD
  4. Alternative: Use token list with price metadata (limited support)

  • Oracle Integration: metamask-integration/docs/METAMASK_ORACLE_INTEGRATION.md
  • Contract Addresses: docs/11-references/CONTRACT_ADDRESSES_REFERENCE.md
  • Token List Guide: docs/11-references/TOKEN_LIST_AUTHORING_GUIDE.md
  • WETH9/WETH10 Fix: docs/04-configuration/metamask/FIX_WETH9_WETH10_DECIMALS_AND_ORACLE.md

Last Updated: 2026-01-26
Status: Complete oracle setup guide