Chainlink Price Feed Addresses: Ethereum, Base, and BNB Chain Reference

Chainlink price feed contract addresses for ETH/USD, BTC/USD, and USDC/USD on Ethereum, Base, and BNB Smart Chain, with decimals, heartbeat intervals, and a working REST example.

evmquery team··6 min read
Share
Chainlink price feed addresses, Ethereum, Base, and BNB Smart Chain reference

Every Chainlink price feed is a contract you can read directly, no SDK required, but the addresses live in a registry that changes per chain and per pair. Get one wrong and you’re reading a stale feed, a deprecated aggregator, or the wrong asset entirely. This page is a checked reference for the pairs developers ask for most: ETH/USD, BTC/USD, and USDC/USD on Ethereum, Base, and BNB Smart Chain.

Chainlink feed key facts

  • latestRoundData() returns five fields: roundId, answer, startedAt, updatedAt, and answeredInRound. answer is the raw price; the other four exist so you can validate freshness.
  • Decimals are not always 8. USD-quoted pairs on the chains below all use 8, but some crypto-quoted pairs elsewhere use 18. Call decimals() and scale with it, don’t hardcode 1e8.
  • Staleness must be checked against updatedAt, never assumed. Compare updatedAt to the current time and to the feed’s documented heartbeat before trusting the value.
  • Every address below was verified two ways: cross-checked against Chainlink’s own reference data directory and confirmed live on-chain by calling decimals() and description() against a public RPC for each chain.

TL;DR

Chainlink price feed addresses differ per chain and per pair, and some duplicated entries in Chainlink’s own registry point at internal streams variants you shouldn’t use publicly. The tables below list the canonical proxy address for ETH/USD, BTC/USD, and USDC/USD on Ethereum, Base, and BNB Smart Chain, each independently verified on-chain.

Why the wrong address is an easy mistake

Chainlink publishes far more than one contract per pair. A single pair like ETH/USD typically has a primary proxy address plus one or two internal “shared SVR” variants used for Chainlink’s own low-latency data streams infrastructure, not intended for public latestRoundData() reads. All of them show up under the same "ETH / USD" label in Chainlink’s reference feed listing, and only one is the address that data.chain.link documents and that Chainlink’s own reference data directory files under the plain, unsuffixed feed path.

Pull the wrong one from a stale blog post, a forum answer, or a hallucinated LLM response, and you either get a revert, a feed that silently stopped updating months ago, or a decimals mismatch that scales your price 10 billion times too small. The addresses below are the canonical proxy addresses, the same ones the Chainlink price feed reader tool on this site uses.

Ethereum

Pair Address Decimals Heartbeat
ETH / USD 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419 8 3600s (1h)
BTC / USD 0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c 8 3600s (1h)
USDC / USD 0x8fFfFfd4AfB6115b954Bd326cbe7B4BA576818f6 8 82800s (23h)

Base

Pair Address Decimals Heartbeat
ETH / USD 0x71041dddad3595F9CEd3DcCFBe3D1F4b0a16Bb70 8 1200s (20min)
BTC / USD 0x64c911996D3c6aC71f9b455B1E8E7266BcbD848F 8 1200s (20min)
USDC / USD 0x7e860098F58bBFC8648a4311b374B1D669a2bc6B 8 86400s (24h)

BNB Smart Chain

Pair Address Decimals Heartbeat
ETH / USD 0x9ef1B8c0E4F7dc8bF5719Ea496883DC6401d5b2e 8 60s
BTC / USD 0x264990fbd0A4796A3E3d8E37C4d5F87a3aCa5Ebf 8 60s
USDC / USD 0x51597f405303C4377E36123cBc172b13269EA163 8 900s (15min)

Heartbeat is the maximum interval Chainlink’s node operators commit to between updates, even with no price movement. It’s the upper bound on how stale a “latest” answer can be; a feed can also update sooner if the price moves past its deviation threshold. BNB Smart Chain’s feeds above update roughly every minute; Ethereum’s USDC/USD feed only commits to once every 23 hours, which is normal for a stablecoin pair that rarely moves.

Verify before you ship

Chainlink occasionally deprecates or migrates feeds. Before hardcoding any address into production code, confirm it against data.chain.link for the pair and chain you need, the same check this reference relied on.

Checking staleness yourself

latestRoundData() gives you everything needed to decide whether an answer is trustworthy, but it doesn’t decide that for you. The pattern:

  1. Read answer and updatedAt together, in the same call.
  2. Compare updatedAt to your current time. If the gap exceeds the feed’s heartbeat by a wide margin, the feed is stuck, not just running a little behind schedule.
  3. Reject or flag the read if step 2 fails, rather than silently using a stale price.
import { createPublicClient, http } from "viem";
import { mainnet } from "viem/chains";
const AGGREGATOR_ABI = [
{
name: "latestRoundData",
type: "function",
stateMutability: "view",
inputs: [],
outputs: [
{ name: "roundId", type: "uint80" },
{ name: "answer", type: "int256" },
{ name: "startedAt", type: "uint256" },
{ name: "updatedAt", type: "uint256" },
{ name: "answeredInRound", type: "uint80" },
],
},
] as const;
const ETH_USD = "0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419";
const HEARTBEAT_SECONDS = 3600;
const client = createPublicClient({ chain: mainnet, transport: http() });
const { answer, updatedAt } = await client.readContract({
address: ETH_USD,
abi: AGGREGATOR_ABI,
functionName: "latestRoundData",
});
const ageSeconds = Math.floor(Date.now() / 1000) - Number(updatedAt);
if (ageSeconds > HEARTBEAT_SECONDS * 2) {
throw new Error(`ETH/USD feed is stale: last updated ${ageSeconds}s ago`);
}

Doubling the heartbeat before treating a feed as stale gives room for normal network variance without masking a genuinely stuck oracle.

Reading a feed with evmquery

The same read collapses into one REST call with evmquery, scaling answer by decimals() server-side instead of leaving that math to the client:

curl -X POST https://api.evmquery.com/api/v1/query \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"chain": "evm_ethereum",
"schema": {
"contracts": { "feed": { "address": "0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419" } }
},
"expression": "cel.bind(d, feed.decimals(), cel.bind(r, feed.latestRoundData(), { \"description\": feed.description(), \"price\": string(formatUnits(r.answer, d)), \"roundId\": string(formatUnits(r.roundId, 0)), \"startedAt\": string(formatUnits(r.startedAt, 0)), \"updatedAt\": string(formatUnits(r.updatedAt, 0)), \"answeredInRound\": string(formatUnits(r.answeredInRound, 0)) }))"
}'

This is the same expression shape validated live against the ETH/USD feed above; it resolves to a map with the pair description, the human-readable price, and all four round-integrity fields as strings, in one round trip and four billed units total: one for the CEL evaluation plus three calls for decimals(), latestRoundData(), and description(). The Chainlink price feed reader tool on this site runs a simpler version of this expression (just description, scaled answer, and decimals) if you want to try a feed before writing any code.

If you’re pulling more than one pair at a time, for example a treasury dashboard reading ETH/USD, BTC/USD, and USDC/USD in the same request, each additional feed just adds another entry to schema.contracts and another key to the expression’s output map. The Multicall3 guide covers the batching mechanics if you’re building this by hand instead of through evmquery.

When you’d automate this

A one-off price check rarely needs staleness handling this careful, but a scheduled job does. If you’re building a price alert, a liquidation monitor, or a treasury dashboard that polls Chainlink on a timer, see evmquery for automation for wiring the same expression into a scheduled workflow instead of a script you have to babysit.

Next steps

Share

Read a live Chainlink feed in one request

No credit card, no monthly cap on the free tier. Grab an API key and drop the snippet below into your project.