The proper way to calculate a token price is by asking the liquidity pool (the pair of this token against the local PEG or some USD token) for the ratio of how much PEG was inserted agains how may tokens (for more details about what a liquidity pool represents, see https://uniswap.org/docs/v2/core-concepts/pools/).
So for python use:
from web3 import Web3
from web3.middleware import geth_poa_middleware # Needed for Binance
from json import loads
from decimal import Decimal
ETHER = 10 ** 18
WBNB = '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c'
CAKE_ROUTER_V2 = web3.toChecksumAddress('0x10ed43c718714eb63d5aa57b78b54704e256024e')
web3 = Web3(Web3.HTTPProvider('https://bsc-dataseed1.binance.org:443'))
web3.middleware_onion.inject(geth_poa_middleware, layer=0) # Again, this is needed for Binance, not Ethirium
ABI = loads('[{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"token0","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"getPair","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"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"}]')
def get_price(token, decimals, pair_contract, is_reversed, is_price_in_peg):
peg_reserve = 0
token_reserve = 0
(reserve0, reserve1, blockTimestampLast) = pair_contract.functions.getReserves().call()
if is_reversed:
peg_reserve = reserve0
token_reserve = reserve1
else:
peg_reserve = reserve1
token_reserve = reserve0
if token_reserve and peg_reserve:
if is_price_in_peg:
# CALCULATE PRICE BY TOKEN PER PEG
price = (Decimal(token_reserve) / 10 ** decimals) / (Decimal(peg_reserve) / ETHER)
else:
# CALCULATE PRICE BY PEG PER TOKEN
price = (Decimal(peg_reserve) / ETHER) / (Decimal(token_reserve) / 10 ** decimals)
return price
return Decimal('0')
if __name__ == '__main__':
CAKE_FACTORY_V2 = web3.eth.contract(address=CAKE_ROUTER_V2, abi=ABI).functions.factory().call()
token = web3.toChecksumAddress('0x126f5f2a88451d24544f79d11f869116351d46e1')
pair = web3.eth.contract(address=CAKE_FACTORY_V2, abi=ABI).functions.getPair(token, WBNB).call()
pair_contract = web3.eth.contract(address=pair, abi=ABI)
is_reversed = pair_contract.functions.token0().call() == WBNB
decimals = web3.eth.contract(address=token, abi=ABI).functions.decimals().call()
is_price_in_peg = True
print(get_price(token, decimals, pair_contract, is_reversed, is_price_in_peg), 'BNB')
And for JS use:
var ETHER = Math.pow(10, 18);
var WBNB = '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c';
var CAKE_ROUTER_V2 = Web3.utils.toChecksumAddress('0x10ed43c718714eb63d5aa57b78b54704e256024e');
var web3 = new Web3('https://bsc-dataseed1.binance.org:443');
var ABI = [{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"token0","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"getPair","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"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"}];
var get_price = async function(token, decimals, pair_contract, is_reverse, is_price_in_peg) {
var price,
peg_reserve = 0,
token_reserve = 0,
res = await pair_contract.methods.getReserves().call(),
reserve0 = res[0],
reserve1 = res[1];
if (is_reverse) {
peg_reserve = reserve0;
token_reserve = reserve1;
} else {
peg_reserve = reserve1;
token_reserve = reserve0;
}
if (token_reserve && peg_reserve) {
if (is_price_in_peg) {
// CALCULATE PRICE BY TOKEN PER PEG
price = (Number(token_reserve) / Number(Math.pow(10, decimals))) / (Number(peg_reserve) / Number(ETHER));
} else {
// CALCULATE PRICE BY PEG PER TOKEN
price = (Number(peg_reserve) / Number(ETHER)) / (Number(token_reserve) / Number(Math.pow(10, decimals)));
}
return price;
}
return Number(0);
};
var token = Web3.utils.toChecksumAddress('0x126f5f2a88451d24544f79d11f869116351d46e1');
var pair = await (await (new web3.eth.Contract(ABI, CAKE_FACTORY_V2))).methods.getPair(token, WBNB).call();
var pair_contract = await new web3.eth.Contract(ABI, pair);
var is_reversed = (await pair_contract.methods.token0().call()) == WBNB;
var decimals = await (await new web3.eth.Contract(ABI, token)).methods.decimals().call();
var is_price_in_peg = true;
console.log(await get_price(token, decimals, pair_contract, is_reversed, is_price_in_peg), 'BNB')
NOTE 1: This applies only for tokens that have liquidity against WBNB. If the liquidity is against some other coin, you have to recursively understand all the prices in that chain and correlate them one to another until you reach WBNB (or any other PEG in other networks).
NOTE 2. According to https://arxiv.org/pdf/2009.14021.pdf :
Expected Execution Price (E[P]): When a liquidity
taker issues a trade on X/Y , the taker wishes to
execute the trade with the expected execution price
E[P] (based on the AMM algorithm and X/Y state),
given the expected slippage.
Execution Price (P): During the time difference between a liquidity taker issuing a transaction, and the
transaction being executed (e.g. mined in a block),
the state of the AMM market X/Y may change.
This state change may induce unexpected slippage
resulting in an execution price P != E[P].
Unexpected Price Slippage (P − E[P]): is the difference between P and E[P].
Unexpected Slippage Rate ((P − E[P]) / E[P]): is the
unexpected slippage over the expected price.
So in our situation, E[P]
is the result of our get_price()
and P
is the result from getAmounsOut()
of an amount (1Kwei for example) divided by the amount we provided (1K in this example) and thus we can even calculate the slippage by eventually subtracting P − E[P]