Source code for eth_defi.uniswap_v2.pair

"""Uniswap v2 pair info.

- See :py:class:`PairDetails` for a helper class
  to get price and other data from the trading pairs of :Uniswap v2 like DEXes
"""

from dataclasses import dataclass
from decimal import Decimal
from typing import Union, Optional

from eth_typing import HexAddress
from web3.contract import Contract

from eth_defi.abi import get_deployed_contract
from eth_defi.token import TokenDetails, fetch_erc20_details


[docs]@dataclass(frozen=True, slots=True) class PairDetails: """Uniswap v2 trading pair info. An example usage how to get the latest price of a pair on PancakeSwap. The `PairDetails` class will do an automatic conversion of prices to human-readable, decimal format: .. code-block:: python 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") """ #: Pool contract #: #: https://docs.uniswap.org/contracts/v2/reference/smart-contracts/pair#getreserves contract: Contract #: One pair of tokens token0: TokenDetails #: One pair of tokens token1: TokenDetails #: Store the human readable token order on this data. #: #: If false then pair reads as token0 symbol (WETH) - token1 symbol (USDC). #: #: If true then pair reads as token1 symbol (USDC) - token0 symbol (WETH). reverse_token_order: Optional[bool] = None def __eq__(self, other): """Implemented for set()""" assert isinstance(other, PairDetails) return self.address == other.address def __hash__(self) -> int: """Implemented for set()""" return int(self.address, 16) def __repr__(self): return f"<Pair {self.get_base_token().symbol}-{self.get_quote_token().symbol} at {self.address}>" @property def address(self) -> HexAddress: """Get pair contract address""" return self.contract.address @property def checksum_free_address(self) -> str: """Get pair contract address, all lowercase.""" return self.contract.address.lower()
[docs] def get_base_token(self) -> TokenDetails: """Get human-ordered base token.""" assert self.reverse_token_order is not None, "Reverse token order flag must be check before this operation is possible" if self.reverse_token_order: return self.token1 else: return self.token0
[docs] def get_quote_token(self) -> TokenDetails: """Get human-ordered quote token.""" assert self.reverse_token_order is not None, "Reverse token order flag must be check before this operation is possible" if self.reverse_token_order: return self.token0 else: return self.token1
[docs] def convert_price_to_human(self, reserve0: int, reserve1: int, reverse_token_order=None): """Convert the price obtained through Sync event :param reverse_token_order: Decide token order for human (base, quote token) order. If set, assume quote token is token0. IF set to None, use value from the data. """ if reverse_token_order is None: reverse_token_order = self.reverse_token_order if reverse_token_order is None: reverse_token_order = False token0_amount = self.token0.convert_to_decimals(reserve0) token1_amount = self.token1.convert_to_decimals(reserve1) if reverse_token_order: return token0_amount / token1_amount else: return token1_amount / token0_amount
[docs] def get_current_mid_price(self) -> Decimal: """Return the price in this pool. Calls `getReserves()` over JSON-RPC and calculate the current price basede on the pair reserves. See https://docs.uniswap.org/contracts/v2/reference/smart-contracts/pair#getreserves :return: Quote token / base token price in human digestible form """ assert self.reverse_token_order is not None, "Reverse token order must be set to get the natural price" reserve0, reserve1, timestamp = self.contract.functions.getReserves().call() return self.convert_price_to_human(reserve0, reserve1, self.reverse_token_order)
[docs]def fetch_pair_details( web3, pair_contact_address: Union[str, HexAddress], reverse_token_order: Optional[bool] = None, base_token_address: Optional[str] = None, quote_token_address: Optional[str] = None, ) -> PairDetails: """Get pair info for PancakeSwap, others. See also :py:class:`PairDetails`. :param web3: Web3 instance :param pair_contact_address: Smart contract address of trading pair :param reverse_token_order: Set the human readable token order. See :py:class`PairDetails` for more info. :param base_token_address: Automatically determine token order from addresses. :param quote_token_address: Automatically determine token order from addresses. """ if base_token_address or quote_token_address: assert reverse_token_order is None, f"Give either (base_token_address, quote_token_address) or reverse_token_order" reverse_token_order = int(base_token_address, 16) > int(quote_token_address, 16) pool = get_deployed_contract(web3, "sushi/UniswapV2Pair.json", pair_contact_address) token0_address = pool.functions.token0().call() token1_address = pool.functions.token1().call() token0 = fetch_erc20_details(web3, token0_address) token1 = fetch_erc20_details(web3, token1_address) return PairDetails( pool, token0, token1, reverse_token_order=reverse_token_order, )