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
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.
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.
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.
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.
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.