Introduction
In the rapidly evolving DeFi ecosystem, accurate and reliable token price data is fundamental for various applications like lending protocols, automated trading systems, and yield farming strategies. Token price oracles serve as the critical infrastructure that enables these protocols to access real-time price information directly from the blockchain.
This article shows you how to build a robust token price oracle smart contract that leverages Uniswap V2’s liquidity pools to derive token prices. By understanding and implementing this oracle system, developers can create DeFi applications that make informed decisions based on current market conditions.
Key benefits of building a token price oracle:
- Real-time price discovery from decentralized exchanges
- On-chain price verification
- Integration capability with any DeFi protocol
- Support for multiple tokens through batch queries
- Cost-effective price fetching through view functions
- Throughout this guide, we’ll explore a practical implementation that showcases:
How to interact with Uniswap V2 contracts
- Methods for accurate price calculations
- Handling different token decimals
- Batch processing for multiple tokens
- Event emission for price tracking
Okay, now let’s dive into the technical implementation and understand how each component works together to create a reliable price oracle system.
How to Implement Price Oracle Smart Contract Step by Step
We are going to use Uniswap V2’s liquidity pools to fetch token prices.
This approach offers several advantages:
- Decentralization: Uniswap V2 is a decentralized exchange, ensuring that price data is not controlled by a single entity.
- Liquidity: Uniswap V2 pools provide liquidity for various tokens, allowing for a wide range of price data.
- Flexibility: Uniswap V2 supports multiple tokens, enabling the oracle to fetch prices for multiple tokens simultaneously.
We need to import required interfaces like IUniswapV2Pair
, IUniswapV2Factory
, and IERC20
.
And define the contract’s state variables, events.
Key functions
1. Getting Pair Address
function getPairAddress(address tokenA, address tokenB) public view returns (address) {
return IUniswapV2Factory(UNISWAP_V2_FACTORY_ADDRESS).getPair(tokenA, tokenB);
}
This function retrieves the liquidity pair address for any token pair from Uniswap V2.
2. Token Price Calculation
function getTokenPrice(address token) public returns (uint256 tokenPrice, uint256 ethPrice, string memory symbol) {
address pairAddress = getPairAddress(token, WETH);
require(pairAddress != address(0), "Pair not found");
IUniswapV2Pair pairContract = IUniswapV2Pair(pairAddress);
(uint112 reserve0, uint112 reserve1, ) = pairContract.getReserves();
address token0 = pairContract.token0();
uint8 tokenDecimals = IERC20(token).decimals();
symbol = IERC20(token).symbol();
if (token0 == WETH) {
ethPrice = (reserve0 * (10 ** tokenDecimals)) / reserve1;
tokenPrice = (reserve1 * (10 ** 18)) / reserve0;
} else {
ethPrice = (reserve1 * (10 ** tokenDecimals)) / reserve0;
tokenPrice = (reserve0 * (10 ** 18)) / reserve1;
}
emit PriceUpdated(token, tokenPrice, ethPrice, symbol);
}
First we get the pair address for the token pair.
And if the pair is not found, it throws an error.
The price calculation logic handles two scenarios:
- If WETH is token0:
if (token0 == WETH) {
ethPrice = (reserve0 * (10 ** tokenDecimals)) / reserve1;
tokenPrice = (reserve1 * (10 ** 18)) / reserve0;
}
- If WETH is token1:
else {
ethPrice = (reserve1 * (10 ** tokenDecimals)) / reserve0;
tokenPrice = (reserve0 * (10 ** 18)) / reserve1;
}
3. Batch Price Fetching
function getMultipleTokenPrices(address[] calldata tokens) external returns (
uint256[] memory tokenPrices,
uint256[] memory ethPrices,
string[] memory symbols
)
This function efficiently fetches prices for multiple tokens in a single transaction.
Usage Example
We can use this price oracle contract in other smart contract and also frontend for getting real time token price.
Usage in other solidity Smart contract
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;
import "./UniswapV2PriceOracle.sol";
contract PriceConsumer {
UniswapV2PriceOracle public oracle;
constructor(address _oracle) {
oracle = UniswapV2PriceOracle(_oracle);
}
function getTokenPriceInETH(address token, uint256 amount) external view returns (uint256) {
return oracle.getTokenValueInETH(token, amount);
}
function checkPriceImpact(address token, uint256 amount) external view returns (uint256) {
return oracle.getPriceImpact(token, amount);
}
}
Frontend Implementation with ethers.js
import { ethers } from 'ethers';
import { useState, useEffect } from 'react';
const PriceTracker = () => {
const [price, setPrice] = useState('0');
const [priceImpact, setPriceImpact] = useState('0');
const ORACLE_ADDRESS = "YOUR_ORACLE_ADDRESS";
const TOKEN_ADDRESS = "YOUR_TOKEN_ADDRESS";
useEffect(() => {
const provider = new ethers.providers.Web3Provider(window.ethereum);
const oracle = new ethers.Contract(ORACLE_ADDRESS, oracleABI, provider);
const fetchPrices = async () => {
const amount = ethers.utils.parseEther("1");
// Get token price
const tokenPrice = await oracle.getTokenValueInETH(
TOKEN_ADDRESS,
amount
);
setPrice(ethers.utils.formatEther(tokenPrice));
// Get price impact
const impact = await oracle.getPriceImpact(
TOKEN_ADDRESS,
amount
);
setPriceImpact(ethers.utils.formatUnits(impact, 2));
};
fetchPrices();
// Listen for price updates
oracle.on("PriceUpdated", (token, ethPrice, tokenPrice, symbol) => {
if(token === TOKEN_ADDRESS) {
setPrice(ethers.utils.formatEther(ethPrice));
setTokenPrice(ethers.utils.formatEther(tokenPrice));
setSymbol(symbol);
} else {
// Track other token updates
setOtherTokenPrices(prev => ({
...prev,
[token]: {
ethPrice: ethers.utils.formatEther(ethPrice),
tokenPrice: ethers.utils.formatEther(tokenPrice),
symbol
}
}));
// Optionally notify about other token updates
console.log(`Price updated for ${symbol}: ${ethers.utils.formatEther(ethPrice)} ETH`);
}
});
return () => {
oracle.removeAllListeners();
};
}, []);
return (
Token Price: {price} ETH
Price Impact: {priceImpact}%
);
};
export default PriceTracker;
This implementation provides a reliable way to fetch token prices directly from Uniswap V2 liquidity pools, making it suitable for various DeFi applications requiring on-chain price data
Conclusion
The implementation of a token price oracle smart contract demonstrates the power and flexibility of on-chain price discovery through Uniswap V2 liquidity pools.
Future Possibilities
- Integration with additional DEX protocols
- Implementation of time-weighted average prices (TWAP)
- Price feed aggregation from multiple sources
- Enhanced security features and price manipulation protection
This oracle implementation serves as a robust starting point for developers building DeFi applications that require reliable, on-chain price data. By understanding and implementing these concepts, developers can create more sophisticated and reliable DeFi protocols.