The Oracle Problem
Smart contracts are deterministic and isolated — they can only access data that exists on the blockchain. They cannot fetch external data like asset prices, weather conditions, sports scores, or API responses on their own. Oracles solve this by acting as bridges between the blockchain and the outside world, feeding real-world data into smart contracts in a secure, decentralized manner.
Oracle Use Cases
- Price Feeds: ETH/USD, BTC/USD prices for DeFi protocols (lending, derivatives, stablecoins)
- Verifiable Randomness: Fair random number generation for NFT minting, gaming, lotteries
- External API Data: Weather data for parametric insurance, sports results for prediction markets
- Cross-chain Data: Reading state from one blockchain on another
- Proof of Reserve: Verifying that off-chain assets back on-chain tokens
Chainlink Price Feeds
Chainlink is the most widely used decentralized oracle network. Chainlink Price Feeds aggregate data from multiple independent sources and deliver it on-chain through a network of independent node operators. Each feed has a contract address that your smart contract can call to get the latest price.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@chainlink/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol";
contract PriceConsumer {
AggregatorV3Interface internal ethUsdFeed;
AggregatorV3Interface internal btcUsdFeed;
constructor() {
// Ethereum Mainnet ETH/USD feed
ethUsdFeed = AggregatorV3Interface(0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419);
// Ethereum Mainnet BTC/USD feed
btcUsdFeed = AggregatorV3Interface(0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c);
}
/// @notice Get the latest ETH/USD price
function getEthPrice() public view returns (int256 price, uint256 updatedAt) {
(
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 timeStamp,
uint80 answeredInRound
) = ethUsdFeed.latestRoundData();
// Validate the data is fresh and valid
require(answer > 0, "Invalid price");
require(timeStamp > 0, "Round not complete");
require(block.timestamp - timeStamp < 3600, "Stale price data"); // Max 1 hour old
return (answer, timeStamp);
// answer has 8 decimals: 300000000000 = $3,000.00
}
/// @notice Convert ETH amount to USD value
function ethToUsd(uint256 ethAmount) external view returns (uint256) {
(int256 price, ) = getEthPrice();
// ethAmount is in Wei (18 decimals), price has 8 decimals
// Result should have 18 decimals (standard)
return (ethAmount * uint256(price)) / 1e8;
}
/// @notice Check if collateral is sufficient
function isCollateralSufficient(
uint256 collateralEth,
uint256 debtUsd,
uint256 minRatio // e.g., 150 for 150%
) external view returns (bool) {
(int256 price, ) = getEthPrice();
uint256 collateralUsd = (collateralEth * uint256(price)) / 1e8;
return (collateralUsd * 100) / debtUsd >= minRatio;
}
}
Chainlink VRF (Verifiable Random Function)
Generating random numbers on a deterministic blockchain is inherently difficult. block.timestamp and block.prevrandao can be manipulated by validators. Chainlink VRF provides cryptographically secure, verifiable randomness that can be proven to be fair and unbiased.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@chainlink/contracts/src/v0.8/vrf/VRFConsumerBaseV2.sol";
import "@chainlink/contracts/src/v0.8/vrf/interfaces/VRFCoordinatorV2Interface.sol";
contract RandomNFTMinter is VRFConsumerBaseV2 {
VRFCoordinatorV2Interface private coordinator;
uint64 private subscriptionId;
bytes32 private keyHash;
uint32 private callbackGasLimit = 100000;
uint16 private requestConfirmations = 3;
uint32 private numWords = 1;
mapping(uint256 => address) public requestToSender;
mapping(uint256 => uint256) public requestToRandom;
event RandomnessRequested(uint256 requestId, address requester);
event RandomnessFulfilled(uint256 requestId, uint256 randomWord);
constructor(
address vrfCoordinator,
bytes32 _keyHash,
uint64 _subscriptionId
) VRFConsumerBaseV2(vrfCoordinator) {
coordinator = VRFCoordinatorV2Interface(vrfCoordinator);
keyHash = _keyHash;
subscriptionId = _subscriptionId;
}
/// @notice Request a random number (async — result comes in callback)
function requestRandomMint() external returns (uint256 requestId) {
requestId = coordinator.requestRandomWords(
keyHash,
subscriptionId,
requestConfirmations,
callbackGasLimit,
numWords
);
requestToSender[requestId] = msg.sender;
emit RandomnessRequested(requestId, msg.sender);
}
/// @notice Callback from Chainlink VRF with the random number
function fulfillRandomWords(
uint256 requestId,
uint256[] memory randomWords
) internal override {
uint256 randomValue = randomWords[0];
requestToRandom[requestId] = randomValue;
// Use randomness to determine rarity
uint256 rarity = randomValue % 100;
// 0-59: Common, 60-84: Rare, 85-94: Epic, 95-99: Legendary
address minter = requestToSender[requestId];
// Mint the NFT with determined rarity...
emit RandomnessFulfilled(requestId, randomValue);
}
}
Reading Oracle Data from Frontend
import { ethers } from 'ethers';
// Read Chainlink price feed directly from frontend
const AGGREGATOR_ABI = [
'function latestRoundData() view returns (uint80, int256, uint256, uint256, uint80)',
'function decimals() view returns (uint8)',
'function description() view returns (string)',
];
const ETH_USD_FEED = '0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419';
async function getEthPrice(provider: ethers.Provider): Promise<number> {
const feed = new ethers.Contract(ETH_USD_FEED, AGGREGATOR_ABI, provider);
const [, answer, , updatedAt] = await feed.latestRoundData();
const decimals = await feed.decimals();
// Check freshness
const age = Math.floor(Date.now() / 1000) - Number(updatedAt);
if (age > 3600) {
console.warn('Price data is stale:', age, 'seconds old');
}
return Number(ethers.formatUnits(answer, decimals));
}
// Usage
const price = await getEthPrice(provider);
console.log('ETH/USD:', price); // e.g., 3245.67
Oracle Best Practices
- Always validate freshness: Check that oracle data is not stale by comparing timestamps
- Use decentralized oracles: Never rely on a single data source — use Chainlink's decentralized network
- Handle zero/negative prices: Validate that the returned price is positive and within expected bounds
- Consider TWAP: For DEX-based price feeds, use time-weighted average prices to resist manipulation
- Circuit breakers: Implement price deviation checks to pause operations during extreme market events