Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ out = ".test/artifacts"
cache_path = ".test/cache"
libs = ["lib", "node_modules"]

# Foundry deploy scripts (e.g. public/samples/DataFeeds/Foundry/*.s.sol) are
# meant to run inside a user's Foundry project where forge-std and a src/ layout
# are available. They cannot compile in this repo, so skip them.
skip = ["*/DataFeeds/Foundry/*"]

optimizer = true
optimizer_runs = 1_000_000

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

/* solhint-disable no-console */

import {DataConsumerV3} from "../../src/DataFeeds/DataConsumerV3.sol";
import {AggregatorV3Interface} from "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol";
import {Script} from "forge-std/Script.sol";
import {console2} from "forge-std/console2.sol";

/**
* THIS IS EXAMPLE CODE THAT USES HARDCODED VALUES FOR CLARITY.
* THIS IS EXAMPLE CODE THAT USES UN-AUDITED CODE.
* DO NOT USE THIS CODE IN PRODUCTION.
*
* Deploy DataConsumerV3 and read the latest BTC/USD price on Sepolia.
*
* Usage:
* forge script script/DataFeeds/DeployAndReadDataConsumerV3.s.sol \
* --rpc-url $SEPOLIA_RPC_URL \
* --broadcast \
* --private-key $PRIVATE_KEY
*/
contract DeployAndReadDataConsumerV3 is Script {
// Sepolia BTC / USD price feed proxy address
address public constant SEPOLIA_BTC_USD = 0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43;

function run() public {
vm.startBroadcast();

// 1. Deploy the consumer contract
DataConsumerV3 consumer = new DataConsumerV3();
console2.log("DataConsumerV3 deployed at:", address(consumer));

vm.stopBroadcast();

// 2. Read the latest price through the consumer
int256 answer = consumer.getChainlinkDataFeedLatestAnswer();
console2.log("Latest answer (raw):", uint256(answer));

// 3. Read decimals directly from the feed to scale the answer
uint8 decimals = AggregatorV3Interface(SEPOLIA_BTC_USD).decimals();
console2.log("Decimals:", decimals);
console2.log("Latest price (scaled): %s", _scale(answer, decimals));
}

function _scale(
int256 answer,
uint8 decimals
) internal pure returns (string memory) {
// Convert the integer answer to a human-readable price string.
// For 8 decimals, 3030914000000 -> "30309.14000000"
uint256 magnitude = uint256(answer);
uint256 base = 10 ** decimals;
uint256 whole = magnitude / base;
uint256 fraction = magnitude % base;
return string.concat(vm.toString(whole), ".", vm.toString(fraction));
}
}
47 changes: 47 additions & 0 deletions public/samples/DataFeeds/Hardhat/DeployAndReadDataConsumerV3.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { network } from "hardhat"

/**
* THIS IS EXAMPLE CODE THAT USES HARDCODED VALUES FOR CLARITY.
* THIS IS EXAMPLE CODE THAT USES UN-AUDITED CODE.
* DO NOT USE THIS CODE IN PRODUCTION.
*/

const { ethers } = await network.create()

async function main() {
// 1. Deploy the DataConsumerV3 contract
const consumer = await ethers.deployContract("DataConsumerV3")
await consumer.waitForDeployment()

const consumerAddress = await consumer.getAddress()
console.log("DataConsumerV3 deployed at:", consumerAddress)

// 2. Read the latest price through the consumer contract
const answer = await consumer.getChainlinkDataFeedLatestAnswer()
console.log("Latest answer (raw):", answer.toString())

// 3. Read decimals directly from the feed to scale the answer
// Sepolia BTC / USD price feed proxy address
const feedAddress = "0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43"
const AggregatorV3Interface = [
{
inputs: [],
name: "decimals",
outputs: [{ internalType: "uint8", name: "", type: "uint8" }],
stateMutability: "view",
type: "function",
},
]
const feed = await ethers.getContractAt(AggregatorV3Interface, feedAddress)
const decimals = await feed.decimals()
console.log("Decimals:", decimals)

// 4. Scale and print the human-readable price
const scaled = Number(answer) / 10 ** Number(decimals)
console.log("Latest price (USD):", scaled)
}

main().catch((error) => {
console.error(error)
process.exit(1)
})
247 changes: 247 additions & 0 deletions src/content/data-feeds/getting-started-foundry.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
---
section: dataFeeds
date: Last Modified
title: "Getting Started with Data Feeds (using Foundry)"
metadata:
title: "Getting Started with Chainlink Data Feeds — Foundry"
description: "Read Chainlink Data Feeds using the Foundry CLI. Deploy a consumer contract on Sepolia with forge, read the BTC/USD price feed onchain, and read feeds offchain with cast call."
excerpt: "Deploy a consumer contract with forge and read Data Feeds with cast"
image: "/files/1a63254-link.png"
difficulty: "beginner"
estimatedTime: "15 minutes"
whatsnext:
{
"Read Data Feeds on other EVM chains and with other Web3 libraries": "/data-feeds/using-data-feeds",
"Retrieve Historical Price Data": "/data-feeds/historical-data",
"Read the Data Feeds API Reference": "/data-feeds/api-reference",
"Find Price Feed addresses on other networks": "/data-feeds/price-feeds/addresses",
}
---

import { Aside, CodeSample, CopyText, PageTabs } from "@components"

Chainlink Data Feeds are the fastest way to connect your smart contracts to real-world data such as asset prices, reserve balances, and L2 sequencer health. Each feed is aggregated by many independent Chainlink node operators and published onchain through a decentralized oracle network, giving your contracts a reliable, manipulation-resistant source of data.

Each price feed has an onchain address and functions that enable contracts to read pricing data from that address, for example the [ETH / USD feed](https://data.chain.link/feeds/ethereum/mainnet/eth-usd).

This guide walks you through reading a Data Feed end-to-end using the [Foundry](https://book.getfoundry.sh/) CLI. You'll deploy a consumer contract on the Sepolia testnet with `forge`, read the BTC / USD price feed onchain, and read the same feed offchain with `cast call`.

## What you'll do

- Deploy a Solidity consumer contract that reads the BTC / USD price feed on Sepolia using `forge script`.
- Retrieve the latest price onchain by calling the consumer contract with `cast call`.
- Read the same feed offchain directly from the feed proxy with `cast call` — no consumer contract required.
- Learn the key safety checks to apply before moving to production.

The code for reading Data Feeds on Ethereum and other EVM-compatible blockchains is the same for every chain and every feed type. You choose different feeds for different use cases, but the request and response format is always the same. The answer's decimal length and expected value range may differ depending on the feed.

<PageTabs
pages={[
{
name: "Low code (Remix)",
url: "/data-feeds/getting-started",
icon: "/images/tutorial-icons/remix-icn.png",
},
{
name: "Foundry",
url: "/data-feeds/getting-started-foundry",
icon: "/images/tutorial-icons/foundry-icn.png",
},
{
name: "Hardhat",
url: "/data-feeds/getting-started-hardhat",
icon: "/images/tutorial-icons/hardhat-icn.png",
},
]}
/>

{/* prettier-ignore */}
<Aside type="caution" title="Using Data Feeds on L2 networks">
If you are using Chainlink Data Feeds on L2 networks like Arbitrum, OP, and Metis, you must also check the latest
answer from the L2 Sequencer Uptime Feed to ensure that the data is accurate in the event of an L2 sequencer outage.
See the [L2 Sequencer Uptime Feeds](/data-feeds/l2-sequencer-feeds) page to learn how to use Data Feeds on L2
networks.
</Aside>

## Before you begin

If you are new to smart contract development, complete the [Deploy Your First Smart Contract](/quickstarts/deploy-your-first-contract) quickstart first.

You will need:

- [Foundry](https://book.getfoundry.sh/getting-started/installation) installed (`forge`, `cast`).
- A funded wallet on the **Sepolia** testnet. Get testnet ETH from a [Sepolia faucet](/resources/link-token-contracts/#sepolia-testnet).
- A Sepolia RPC URL (e.g. from a [node provider](https://ethereum.org/en/developers/docs/nodes-and-clients/nodes-as-a-service/)).
- A funded deployer private key for Sepolia.

{/* prettier-ignore */}
<Aside type="note" title="Why a proxy?">
Consumer contracts call a _proxy_ contract, which forwards reads to the current _aggregator_. When Chainlink
upgrades an aggregator, the proxy is repointed to the new implementation and your consumer contract keeps working
unchanged. Always read from the proxy address listed on the
[Price Feed Addresses](/data-feeds/price-feeds/addresses) page, not from the underlying aggregator.
</Aside>

## Step 1: Examine the sample contract

The example contract below reads the latest answer from the [BTC / USD feed](/data-feeds/price-feeds/addresses) on Sepolia. You can modify it to read any of the [Types of Data Feeds](/data-feeds#types-of-data-feeds).

<CodeSample src="samples/DataFeeds/DataConsumerV3.sol" />

The contract has the following components:

- The `import` line brings in [`AggregatorV3Interface`](https://github.com/smartcontractkit/chainlink-evm/blob/contracts-v1.5.0/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol) — the Solidity interface that every Chainlink v3 price feed proxy implements. It exposes `latestRoundData()`, `getRoundData()`, `decimals()`, and `version()`. The sample uses `latestRoundData` to fetch the current price.
- The `constructor()` initializes a `dataFeed` object that uses `AggregatorV3Interface` pointing at the proxy aggregator deployed at <CopyText text="0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43" code/>. This is the proxy address for the Sepolia `BTC / USD` feed. The proxy lets the aggregator be upgraded without breaking consumer contracts.
- The `getChainlinkDataFeedLatestAnswer()` function calls your `dataFeed` object and runs the `latestRoundData()` function and returns the `answer` variable. When you deploy the contract, it initializes the `dataFeed` object to point to the aggregator at <CopyText text="0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43" code />, which is the proxy address for the Sepolia `BTC / USD` data feed. Your contract connects to that address and executes the function. The aggregator connects with several oracle nodes and aggregates the pricing data from those nodes. The response from the aggregator includes several variables, but `getChainlinkDataFeedLatestAnswer()` returns only the `answer` variable. The full response includes `roundId`, `startedAt`, `updatedAt`, and `answeredInRound` — see the [API Reference](/data-feeds/api-reference) for details.

## Step 2: Set up a Foundry project

1. Create a new Foundry project and add the Chainlink contracts dependency:

```bash
forge init data-feeds-quickstart
cd data-feeds-quickstart
forge install smartcontractkit/chainlink-evm@contracts-v1.5.0
```

1. Add this remapping to `remappings.txt` so Solidity can resolve `@chainlink/contracts`:

```text
@chainlink/contracts/=lib/chainlink-evm/contracts/
```

You can write it with a single command:

```bash
echo '@chainlink/contracts/=lib/chainlink-evm/contracts/' > remappings.txt
```

1. Create the directories for the sample consumer contract and deploy script:

```bash
mkdir -p src/DataFeeds script/DataFeeds
```

1. Create the following files and paste in the code from the rendered code blocks on this page:

- Create `src/DataFeeds/DataConsumerV3.sol` and paste in the contract from [Step 1: Examine the sample contract](#step-1-examine-the-sample-contract).
- Create `script/DataFeeds/DeployAndReadDataConsumerV3.s.sol` and paste in the script from [Step 3: Deploy the contract with a Forge script](#step-3-deploy-the-contract-with-a-forge-script).

Make sure the filenames match exactly — Foundry looks for the contract in `src/` and the script in `script/`.

1. Verify the project compiles:

```bash
forge build
```

You should see `Compiler run successful`. If you see an import error, double-check the `remappings.txt` line from step 2.

## Step 3: Deploy the contract with a Forge script

The deploy script (`script/DataFeeds/DeployAndReadDataConsumerV3.s.sol`) deploys `DataConsumerV3` and immediately reads the latest price through it. For reference, here is the script:

<CodeSample src="samples/DataFeeds/Foundry/DeployAndReadDataConsumerV3.s.sol" />

Run it against Sepolia. Replace `$SEPOLIA_RPC_URL` and `$PRIVATE_KEY` with your own values:

```bash
forge script script/DataFeeds/DeployAndReadDataConsumerV3.s.sol \
--rpc-url $SEPOLIA_RPC_URL \
--broadcast \
--private-key $PRIVATE_KEY
```

The script logs the deployed contract address, the raw integer answer, the feed's decimals, and the human-readable price. Save the deployed contract address for the next step.

{/* prettier-ignore */}
<Aside type="note" title="Prefer not to broadcast?">
Drop `--broadcast` and `--private-key` to run the script in simulation mode. Useful for verifying your setup before
spending testnet ETH.
</Aside>

## Step 4: Read the latest price onchain

Use `cast call` to read the latest answer through your deployed consumer. Replace `$CONSUMER_ADDRESS` with the address logged by the deploy script:

```bash
cast call $CONSUMER_ADDRESS "getChainlinkDataFeedLatestAnswer()(int256)" --rpc-url $SEPOLIA_RPC_URL
```

Read the feed's decimals directly to scale the answer yourself:

```bash
cast call 0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43 "decimals()(uint8)" --rpc-url $SEPOLIA_RPC_URL
```

The BTC / USD feed returns `8` for decimals. To convert the raw integer answer to a human-readable price, divide by `10 ** decimals`. For example, an answer of `3030914000000` with 8 decimals is `30309.14` USD.

{/* prettier-ignore */}
<Aside type="tip" title="Always scale by `decimals()`">
Never hardcode the decimal count. Call `decimals()` on the feed and scale the answer programmatically. This keeps
your contract correct if you later point it at a feed with a different precision (e.g., some feeds use 18 decimals).
</Aside>

## Step 5: Read the same feed offchain (no consumer contract required)

You can also read Data Feeds directly from the feed proxy without deploying a consumer contract. This is useful for backends, bots, dashboards, and pre-trade checks.

Read the latest answer with a single `cast call` against the feed proxy at <CopyText text="0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43" code/>:

```bash
cast call 0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43 "latestRoundData()(uint80,int256,uint256,uint256,uint80)" --rpc-url $SEPOLIA_RPC_URL
```

Read the decimals so you can scale the answer:

```bash
cast call 0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43 "decimals()(uint8)" --rpc-url $SEPOLIA_RPC_URL
```

The BTC / USD feed returns `8` for decimals. Divide the raw integer answer by `10 ** decimals` to get the human-readable price. For example, an answer of `3030914000000` with 8 decimals is `30309.14` USD.

## Before you go to production

The example intentionally omits the safety checks a production integration needs. Before shipping, review the following points and the [Developer Responsibilities](/data-feeds/developer-responsibilities) page.

### Check `updatedAt` and staleness

`latestRoundData()` returns `updatedAt` (the timestamp of the latest round) and `answeredInRound` (the round in which the answer was finalized). Always verify the feed is fresh:

```solidity
(uint80 roundId, int256 answer, , uint256 updatedAt, uint80 answeredInRound) = dataFeed.latestRoundData();

require(answeredInRound >= roundId, "Stale price");
require(block.timestamp - updatedAt < TIMEOUT, "Stale price");
```

Choose a `TIMEOUT` that matches your application's risk tolerance — shorter for trading, longer for less time-sensitive use cases.

### Use the right feed for your asset

Not all feeds are equal. Low-liquidity assets are more exposed to market manipulation. Review [Selecting Quality Data Feeds](/data-feeds/selecting-data-feeds) and the [Data Feed Categories](/data-feeds/selecting-data-feeds#data-feed-categories) before choosing a feed.

### Handle L2 sequencer risk

On L2s, a sequencer outage can cause stale or incorrect prices. Always pair L2 price feeds with a check on the [L2 Sequencer Uptime Feed](/data-feeds/l2-sequencer-feeds). The `DataConsumerWithSequencerCheck` sample shows the pattern. Try it out in Remix below:

<CodeSample src="samples/DataFeeds/DataConsumerWithSequencerCheck.sol" showButtonOnly />

### Audit your integration

The sample code is unaudited and hardcodes values for clarity. Before production, complete your own audit, review your dependencies, and apply the risk-mitigation practices described in [Developer Responsibilities](/data-feeds/developer-responsibilities).

## FAQ

### Do I need a consumer contract to read a Data Feed?

**Only if another smart contract needs the price onchain.** A consumer contract exists to wrap a feed read in your own contract's logic so that _your other contracts_ can use the price onchain — for collateral checks, settlements, circuit breakers, and so on. The read is atomic with your onchain action and verifiable on the blockchain.

If you only need the price in an offchain system (a backend, bot, dashboard, or pre-trade check), you do **not** need a consumer contract. The feed proxy is a public contract and `latestRoundData()` is a `view` function — anyone can call it directly from a script using `cast` or ethers.js. See [Step 5: Read the same feed offchain](#step-5-read-the-same-feed-offchain-no-consumer-contract-required) on this page, or the [Hardhat](/data-feeds/getting-started-hardhat) guide.

| | Consumer contract (onchain) | Direct read (offchain) |
| ------------------------------- | ------------------------------------- | ------------------------------------ |
| **Who needs the price?** | Another smart contract | A script, backend, bot, or dashboard |
| **Gas cost?** | Pay to deploy + gas for onchain reads | Free (view calls from a script) |
| **Deployment required?** | Yes | No |
| **Atomic with onchain action?** | Yes | No |
Loading
Loading