DEV Community

Cover image for How to Write a Token Price Oracle Smart Contract
Mark Santiago
Mark Santiago

Posted on

How to Write a Token Price Oracle Smart Contract

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);
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode
  • If WETH is token1:
else {
    ethPrice = (reserve1 * (10 ** tokenDecimals)) / reserve0;
    tokenPrice = (reserve0 * (10 ** 18)) / reserve1;
}
Enter fullscreen mode Exit fullscreen mode

3. Batch Price Fetching

function getMultipleTokenPrices(address[] calldata tokens) external returns (
    uint256[] memory tokenPrices,
    uint256[] memory ethPrices,
    string[] memory symbols
)
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

Frontend Implementation with ethers.js

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

const PriceTracker = () => {
    const [price, setPrice] = useState<string>('0');
    const [priceImpact, setPriceImpact] = useState<string>('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 (
        <div>
            <h2>Token Price: {price} ETH</h2>
            <h3>Price Impact: {priceImpact}%</h3>
        </div>
    );
};

export default PriceTracker;
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (7)

Collapse
 
btc415 profile image
LovelyBTC

Thanks for your article.
And one more thing, we can also get token price from Uniswap using Uniswap V2 Library.
What do you think of the difference?

Collapse
 
stevendev0822 profile image
Steven

Yeah, we can also get token using Uniswap v2 library.
Each has its own pros and cons
Library approach is more gas efficient due to optimized calculation, while pair & Factory approach can show real-time access to current pool states.

Collapse
 
stevendev0822 profile image
Steven

Great article for getting token price from Uniswap protocol
Thanks

Collapse
 
ichikawa0822 profile image
Ichikawa Hiroshi

Thanks for sharing

Collapse
 
robert_angelo_484 profile image
Robert Angelo

Great post.
Thanks🌹

Collapse
 
james_takahashi_77908481e profile image
James Takahashi

Your posts always help me a lot.
Thank you.

Collapse
 
james_takahashi_286ac1676 profile image
James Takahashi

Thanks for your effort.
⭐⭐⭐