Source code for tradeexecutor.ethereum.token
"""ERC-20 token data helpers."""
from web3 import Web3
from eth_defi.token import TokenDetails, fetch_erc20_details
from tradeexecutor.state.identifier import AssetIdentifier
from tradeexecutor.state.portfolio import Portfolio
from tradeexecutor.state.position import TradingPosition
from tradeexecutor.state.types import JSONHexAddress
from tradingstrategy.pair import PandasPairUniverse
class NonSpotPosition(Exception):
    """Portfolio has non-spot position when spot-only is supported"""
[docs]def fetch_token_as_asset(web3: Web3, contract_address: str) -> AssetIdentifier:
    """Get ERC-20 token details suitable for persistent stroage.
    :param contract_address:
        Address of an ERC-20 contract
    :return:
        Asset identifier that can be use with persistent storage.
    """
    token = fetch_erc20_details(web3, contract_address)
    return translate_token_details(token)
[docs]def translate_token_details(token: TokenDetails) -> AssetIdentifier:
    """Translate on-chain fetched ERC-20 details to the persistent format.
    :param token:
        On-chain token data
    :return:
        Persistent asset identifier
    """
    web3 = token.contract.w3
    return AssetIdentifier(
        chain_id=web3.eth.chain_id,
        address=token.address,
        token_symbol=token.symbol,
        decimals=token.decimals,
        internal_id=None,
        info_url=None,
    )
[docs]def create_spot_token_map_existing_positions(
    portfolio: Portfolio,
    raise_on_unsupported=True,
) -> dict[JSONHexAddress, TradingPosition]:
    """Create a map of spot ERC-20 tokens in our portfolio.
    :return:
        ERC-20 address -> position map for all open/frozen spot positions.
    """
    position_map = {}
    for p in portfolio.get_open_and_frozen_positions():
        if not p.is_spot():
            if raise_on_unsupported:
                raise NonSpotPosition(f"Not ERC-20 spot position: {p}")
            else:
                continue
        position_map[p.pair.base.address] = p
    return position_map
[docs]def create_spot_token_map_potential_positions(
    pair_universe: PandasPairUniverse,
    portfolio: Portfolio,
    raise_on_unsupported=True,
) -> dict[AssetIdentifier, TradingPosition | None]:
    """Create a map of spot ERC-20 tokens in our portfolio.
    :return:
        ERC-20 address -> position map for all open/frozen spot positions and potential positions.
        IF there is not yet existing position, return None.
    """
    position_map = create_spot_token_map_existing_positions(portfolio, raise_on_unsupported)
    # Include tokens for which we do not have a position mapped yet
    for pair in pair_universe.iterate_pairs():
        if pair.base_token_address not in position_map:
            position_map[pair.base_token_address] = None
    return position_map