4

When interacting with functions price0CumulativeLast and price1CumulativeLast of any smart contract on Uniswap V2 I keep getting incomprehensive values. I am not able to figure out how to extract the latest price.

Here is the Python snippet I am using, thank you so much for the help:

import json
from web3 import Web3
from eth_utils import to_checksum_address


contract_abi=[ABI GOES HERE]
# Have an Ethereum mainnet archive node here so we can fetch from any block in the past, not just the immediate past 128 blocks
w3 = Web3(Web3.HTTPProvider("https://eth-mainnet.g.alchemy.com/v2/ALCHEMY API KEY"))


contract_address = "0x0d4a11d5eeaac28ec3f61d100daf4d40471f1852"
contract_address = to_checksum_address(contract_address)
contract = w3.eth.contract(address=contract_address, abi=contract_abi)


result = contract.functions.price0CumulativeLast().call()
result1 = contract.functions.price1CumulativeLast().call()


print(result, result1)
print(result1/result)

Thanks again.

1

2 Answers 2

6

Dev advocate at Chainstack here.

You can get prices directly from the Uniswap V2 pair contract, like you are attempting now, by analyzing the reserves of each token in the pair. The key to obtaining accurate pricing information is properly interpreting the reserves and considering the number of decimals for each token.

Code

Here's a Python code snippet that uses Web3.py to interact with a Uniswap V2 pair contract:

from web3 import Web3
from eth_utils import to_checksum_address

# Initialize Web3
w3 = Web3(
    Web3.HTTPProvider(
        "YOUR_ENDPOINT_HERE"
    )
)

# Pair contract address
pair_address = "0x0d4a11d5eeaac28ec3f61d100daf4d40471f1852"  # WETH/USDT in this example
pair_address = to_checksum_address(pair_address)

# Minimal ABI for Uniswap V2 Pair contract, only including methods used
pair_abi = [
    {
        "constant": True,
        "inputs": [],
        "name": "getReserves",
        "outputs": [
            {"internalType": "uint112", "name": "_reserve0", "type": "uint112"},
            {"internalType": "uint112", "name": "_reserve1", "type": "uint112"},
            {"internalType": "uint32", "name": "_blockTimestampLast", "type": "uint32"},
        ],
        "payable": False,
        "stateMutability": "view",
        "type": "function",
    },
    {
        "constant": True,
        "inputs": [],
        "name": "token0",
        "outputs": [{"internalType": "address", "name": "", "type": "address"}],
        "payable": False,
        "stateMutability": "view",
        "type": "function",
    },
    {
        "constant": True,
        "inputs": [],
        "name": "token1",
        "outputs": [{"internalType": "address", "name": "", "type": "address"}],
        "payable": False,
        "stateMutability": "view",
        "type": "function",
    },
]

# Minimal ABI for ERC20 token contract, only including methods used
erc20_token_abi = [
    {
        "constant": True,
        "inputs": [],
        "name": "decimals",
        "outputs": [{"internalType": "uint8", "name": "", "type": "uint8"}],
        "payable": False,
        "stateMutability": "view",
        "type": "function",
    },
    {
        "constant": True,
        "inputs": [],
        "name": "name",
        "outputs": [{"internalType": "string", "name": "", "type": "string"}],
        "payable": False,
        "stateMutability": "view",
        "type": "function",
    },
    {
        "constant": True,
        "inputs": [],
        "name": "symbol",
        "outputs": [{"internalType": "string", "name": "", "type": "string"}],
        "payable": False,
        "stateMutability": "view",
        "type": "function",
    },
]

# Initialize pair contract
pair_contract = w3.eth.contract(address=pair_address, abi=pair_abi)

# Fetch reserves
reserves = pair_contract.functions.getReserves().call()
reserve0 = reserves[0]
reserve1 = reserves[1]

# Fetch token addresses from the pair contract
token0_address = pair_contract.functions.token0().call()
token1_address = pair_contract.functions.token1().call()

# Initialize tokens contracts
token0_contract = w3.eth.contract(address=token0_address, abi=erc20_token_abi)
token1_contract = w3.eth.contract(address=token1_address, abi=erc20_token_abi)

# Fetch name and symbol for each token
name0 = token0_contract.functions.name().call()
symbol0 = token0_contract.functions.symbol().call()
name1 = token1_contract.functions.name().call()
symbol1 = token1_contract.functions.symbol().call()

# Fetch decimals for each token
decimals0 = token0_contract.functions.decimals().call()
decimals1 = token1_contract.functions.decimals().call()

# Adjust reserves for decimals
adjusted_reserve0 = reserve0 / (10**decimals0)
adjusted_reserve1 = reserve1 / (10**decimals1)

# Calculate adjusted price for Token0 in terms of Token1
if adjusted_reserve1 > 0:
    price0_in_terms_of_1 = adjusted_reserve0 / adjusted_reserve1
else:
    price0_in_terms_of_1 = "Infinite or Undefined"

# Calculate adjusted price for Token1 in terms of Token0
if adjusted_reserve0 > 0:
    price1_in_terms_of_0 = adjusted_reserve1 / adjusted_reserve0
else:
    price1_in_terms_of_0 = "Infinite or Undefined"

print(f"Price of 1 {name0} ({symbol0}) in {name1} ({symbol1}): {price1_in_terms_of_0}")
print(f"Price of 1 {name1} ({symbol1}) in {name0} ({symbol0}): {price0_in_terms_of_1}")

Explanation

  1. ABI & Contract Address: The ABI and contract address for the Uniswap pair are specified. Here, I used a minimal ABI containing only the functions we're interested in.

  2. Fetch Reserves: We call the getReserves function to get the amount of each token in the liquidity pool. These reserves are used to calculate the price of one token in terms of the other.

  3. Fetch Token Details: The contract addresses for the two tokens (token0 and token1) in the pair are fetched. We then get their names, symbols, and decimals for a more user-friendly display.

  4. Price Calculation: The price of each token in terms of the other is calculated by dividing their respective reserves. Make sure to adjust for decimals, which can differ between tokens, so we fetch them directly from the tokens contracts.

  5. Print Prices: Finally, we print out the prices in a user-friendly manner, specifying the price of "1 unit" of each token in terms of the other.

By running this code, you'll get an output similar to:

Price of 1 Wrapped Ether (WETH) in Tether USD (USDT): 1786.033222386003
Price of 1 Tether USD (USDT) in Wrapped Ether (WETH): 0.0005598999993203245

This will give you the most recent and accurate exchange rate between the two tokens in the pair.

Feel free to reach out if you have any more questions.

2
  • thanks you so much @Dave Dev I have a quick question : is using block_identifier=xxxxxx like this in this line : reserves = pair_contract.functions.getReserves().call(block_identifier=18023369) is correct even beyond 128 block ? assuming I am using this end_point : eth-mainnet.g.alchemy.com/v2/API KEY Oct 26 at 21:19
  • 2
    Yes, you can specify the block from which you want to get the reserves. And it will work if you use an archive node, which Alchemy provides. So yes, it will work beyond the past 128 blocks if that pair exists on that block, so you might want to add some error handling. Oct 26 at 22:13
2

Here is an example how to get the last price on Uniswap v2 compatible DEXes using web3-ethereum-defi Python package.

The PairDetails class will do an automatic conversion of prices to human-readable, decimal format. You can also convert individual token raw amounts to human-readable decimals using TokenDetails class.

# Get the current BNB/BUSD price on PancakeSwap
from web3 import Web3, HTTPProvider

from eth_defi.chain import install_chain_middleware
from eth_defi.uniswap_v2.pair import fetch_pair_details

web3 = Web3(HTTPProvider("https://bsc-dataseed.bnbchain.org"))

print(f"Connected to chain {web3.eth.chain_id}")

# BNB Chain does its own things
install_chain_middleware(web3)

# Find pair addresses on TradingStrategy.ai
# https://tradingstrategy.ai/trading-view/binance/pancakeswap-v2/bnb-busd
pair_address = "0x58f876857a02d6762e0101bb5c46a8c1ed44dc16"
wbnb = "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c"
busd = "0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56"

# PancakeSwap has this low level encoded token0/token1 as BNB/BUSD
# in human-readable token order
# and we do not need to swap around
reverse_token_order = False

pair = fetch_pair_details(
    web3,
    pair_address,
    reverse_token_order,
)

assert pair.token0.address == wbnb
assert pair.token1.address == busd

price = pair.get_current_mid_price()

# Assume 1 BUSD = 1 USD
print(f"The current price of PancakeSwap BNB/BUSD is {price:.4f} USD")

Prints

Connected to chain 56
The current price of PancakeSwap BNB/BUSD is 225.4127 USD

To find any pair and token contract addresses you can use Trading Strategy search, which is running the code from the linked library which I built.

6
  • 3
    You should disclose your affiliation with TradingStrategy.ai per the code of conduct. Oct 26 at 18:36
  • thanks for the reply, if I have access to archive node I am wondering if pair.get_current_mid_price() can take an argument to return old block mid price, for example using something like this pair.get_current_mid_price(block_identifier=oldBlockNumber) ? if possible can I go beyong 128 blocks If I have access to an archive node ? Oct 26 at 21:20
  • 1
    @AnissItaoui You can ask a historical price if you need it. However, the example function in get_current_mid_price() returns the current price. For the historical price, please see the source code and modify it for your needs. Any web3.py contract function call can take a historical block number as a parameter. Oct 26 at 21:50
  • 2
    Any archive node works with the code, including ones like Chainstack and Alchemy. Ethereum JSON-RPC APIs are standard and JSON-RPC providers can be mixed and changed freely, assuming they have the necessary chain support. Oct 26 at 21:53
  • 1
    thank you so much the valuable help, have a great day Oct 26 at 22:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.