token_analysis.tokensniffer

Documentation for eth_defi.token_analysis.tokensniffer Python module.

TokenSniffer API.

Functions

is_tradeable_token(data[, symbol, ...])

Risk assessment for open-ended trade universe.

Classes

CachedTokenSniffer

Add file-system based cache for TokenSniffer API.

TokenSniffer

TokenSniffer API.

TokenSnifferReply

TokenSniffer JSON payload.

Exceptions

TokenSnifferError

Wrap bad API replies from TokenSniffer.

exception TokenSnifferError

Bases: Exception

Wrap bad API replies from TokenSniffer.

  • Has attribute status_code

Parameters

status_code – to reflect the HTTP code (e.g. 404 if TokenSniffer does not have data)

__init__(msg, status_code, address)
Parameters
  • status_code (int) – to reflect the HTTP code (e.g. 404 if TokenSniffer does not have data)

  • msg (str) –

  • address (str) –

__new__(**kwargs)
add_note()

Exception.add_note(note) – add a note to the exception

with_traceback()

Exception.with_traceback(tb) – set self.__traceback__ to tb and return self.

class TokenSnifferReply

Bases: TypedDict

TokenSniffer JSON payload.

Example data:

{'address': '0x873259322be8e50d80a4b868d186cc5ab148543a',
 'balances': {'burn_balance': 0.002441962189654333,
              'deployer_balance': 0,
              'lock_balance': 0,
              'owner_balance': 0,
              'top_holders': [{'address': '0x15ef07c7ec863081b757f34c497452dbb65f16f7',
                               'balance': 9332.365029778688,
                               'is_contract': False},
                              {'address': '0x0453bef3490d4e4cbb01ec94737b75bbc051c750',
                               'balance': 7251.968154354711,
                               'is_contract': False},
                              {'address': '0x90ba15d4ad2c6ed1aa6296d4c06b3a7ad1599750',
                               'balance': 3711.3650926786295,
                               'is_contract': False},
                              {'address': '0x788b293db0068b17c1147d289aebcd1c7cc11229',
                               'balance': 1918.5942148506324,
                               'is_contract': False},
                              {'address': '0x08c2d690340998bf3f74e6a6496fc2868ced75d5',
                               'balance': 1764.577012673702,
                               'is_contract': False},
                              {'address': '0xe8c97650aa7e4525cc45851af5b2f5f81403432a',
                               'balance': 1759.1752683280474,
                               'is_contract': False},
                              {'address': '0xd4913c03ba8b00a85634c170a404b99ef01fe4f6',
                               'balance': 1499.323423358631,
                               'is_contract': False},
                              {'address': '0x8e54b18ea37a97914149e4bec2b4146503ba14ed',
                               'balance': 1285.0936630338015,
                               'is_contract': False},
                              {'address': '0xcd9f53208390399de0e2ba5914b7bd53afc62835',
                               'balance': 1243.9131637279036,
                               'is_contract': False},
                              {'address': '0x2e43eac73fabe2b207d014726d7c157054beccde',
                               'balance': 1177.2629555707488,
                               'is_contract': False},
                              {'address': '0x396e7c0cdd9dcec52f2b40948f8f703f8d750e10',
                               'balance': 1101.9120660748333,
                               'is_contract': False},
                              {'address': '0x4a63eef3060ad8eabd67c4cd4b9f908c37f2e1c1',
                               'balance': 1068.880217996999,
                               'is_contract': False},
                              {'address': '0x4eeee62a0c41fd39285af411fc9be030dc40a691',
                               'balance': 1028.24351924691,
                               'is_contract': False},
                              {'address': '0xe1ef21cd83316467823b7cd33b43cd87b9ed645a',
                               'balance': 948.0084401939899,
                               'is_contract': False},
                              {'address': '0xe8ea1eab72af70471e3cfa999f4c0eff173473ed',
                               'balance': 904.7122650717333,
                               'is_contract': False},
                              {'address': '0x457d90dc48ba7549c1c04922dc0f3dea23c3a9f2',
                               'balance': 897.8435736232242,
                               'is_contract': False},
                              {'address': '0x3f747d527666d752706fd5d96d5c857a8de4a517',
                               'balance': 868.5356019052704,
                               'is_contract': False},
                              {'address': '0xc731022481a88f40541346fff53eaaf38a5d86ba',
                               'balance': 811.2400720078341,
                               'is_contract': False},
                              {'address': '0xafc17077adcd32cf9110f8f9f271e250b7680fd1',
                               'balance': 804.5682720521471,
                               'is_contract': False},
                              {'address': '0xa735df3b21a6f665e9cb54d7a29918f4047b638d',
                               'balance': 778.4391820978005,
                               'is_contract': False}]},
 'cached': True,
 'chainId': '1',
 'contract': {'has_blocklist': True,
              'has_fee_modifier': True,
              'has_max_transaction_amount': False,
              'has_mint': False,
              'has_pausable': False,
              'has_proxy': False,
              'is_source_verified': True},
 'created_at': 1718627231000,
 'decimals': 18,
 'deployer_addr': '0x5a0e7c0f651dfbb45cbc130a3e7422d3e2c8dc57',
 'exploits': [],
 'is_flagged': True,
 'message': 'OK',
 'name': 'Ponzio The Cat',
 'permissions': {'is_ownership_renounced': True,
                 'owner_address': '0x0000000000000000000000000000000000000000'},
 'pools': [{'address': '0x90908e414d3525e33733d320798b5681508255ea',
            'base_address': '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2',
            'base_reserve': 320.06561592285965,
            'base_symbol': 'ETH',
            'burn_balance': 3.24037034920393e+22,
            'decimals': 18,
            'deployer_balance': 0,
            'initial_base_reserve': 0.25798805413538606,
            'lock_balance': 0,
            'locks': [],
            'name': 'Uniswap v2',
            'owner_balance': 1e-15,
            'top_holders': [{'address': '0x46030f5e33afa7d0b7c0c54a3a8017e10140a979',
                             'balance': 135224.0254206525},
                            {'address': '0x000000000000000000000000000000000000dead',
                             'balance': 32403.7034920393},
                            {'address': '0xf76a09d5930285456162fb3c5317d4d79498990a',
                             'balance': 234.8025868953592},
                            {'address': '0x1fd1e7bc0e6a5255f94047470bfff8dcafdf2bfa',
                             'balance': 45.550801246839946},
                            {'address': '0xa147ebe368a411b2e757f36cef91c592e52adeb2',
                             'balance': 36.74201290998386},
                            {'address': '0xf5af46bc5f3a9d412c27ba53c5e57f0ccf9b8ab5',
                             'balance': 19.0041259704864},
                            {'address': '0x538f8a3181e6b192629591c06116c882b6be2b7c',
                             'balance': 16.154725552250454},
                            {'address': '0x5cc71b76c0ea69c27362e9a595969512933c94c7',
                             'balance': 15.743890726031212},
                            {'address': '0xab25362ca38b11975885ffd66b4e7d928159cb56',
                             'balance': 15.599079045130523},
                            {'address': '0xeafbfc76e54fbad22e3314008cc1b0d4fa8c1691',
                             'balance': 11.270345823990574},
                            {'address': '0xc38798d5444f4b8af98e4dd890bde225f2e2da59',
                             'balance': 11.108066605155567},
                            {'address': '0xc425591420ecc0ae301d7c2e223ec6d34ce56902',
                             'balance': 8.060042964227087},
                            {'address': '0x16a0ce3e805dd11c7074e9851ab33bfac0cc5bb5',
                             'balance': 7.429134425519216},
                            {'address': '0x02cd35dd57d37b97da3df69a526e458f2e8beaa3',
                             'balance': 6.554675101704869},
                            {'address': '0x6b96559df5bce0d46487efa92ef41fe68f901f5c',
                             'balance': 5.054292341485139},
                            {'address': '0x3286e7eca9da5f6fd9b4f9aad2d13cd0d625e16f',
                             'balance': 4.972721217335674},
                            {'address': '0xbfba29a3ca51ad0a4265bfbd223d3da9b0955cd9',
                             'balance': 4.660561980388217},
                            {'address': '0xe1908233a1c3b9b22389535e479eb1272f2e9d15',
                             'balance': 4.639894642263144},
                            {'address': '0x3aa3419475eca32efde41560de0135cf87c040ab',
                             'balance': 4.496294877137668},
                            {'address': '0x3ee117f85f58aae2d9e12ab30e3754e8921bb733',
                             'balance': 4.460782725068053}],
            'total_supply': 1.681493252109614e+23,
            'version': '2'}],
 'refreshed_at': 1725437810442,
 'riskLevel': 'high',
 'score': 0,
 'similar': [{'address': '0xbe80849ef400b2dfb616c8c268e4e4fa04fb8b8e',
              'chainId': 'ETH',
              'stcore': 93},
             {'address': '0x31e81092412bf5eb329ac7bf3ccaf0971f84e2c2',
              'chainId': 'ETH',
              'stcore': 91}],
 'status': 'ready',
 'swap_simulation': {'buy_fee': 1.525060573608289e-14,
                     'is_sellable': True,
                     'sell_fee': 0},
 'symbol': 'Ponzio',
 'tests': [{'description': 'Verified contract source',
            'id': 'testForMissingSource',
            'result': False},
           {'description': 'Source does not contain a proxy contract',
            'id': 'testForProxy',
            'result': False},
           {'description': 'Source does not contain a pausable contract',
            'id': 'testForPausable',
            'result': False},
           {'description': 'Source does not contain a mint function',
            'id': 'testForMint',
            'result': False},
           {'description': 'Source does not contain a function to restore '
                           'ownership',
            'id': 'testForRestoreOwnership',
            'result': False},
           {'description': 'Source does not contain a function to set maximum '
                           'transaction amount',
            'id': 'testForMaxTransactionAmount',
            'result': False},
           {'description': 'Source does not contain a function to modify the '
                           'fee',
            'id': 'testForModifiableFee',
            'result': True},
           {'description': 'Source does not contain a function to blacklist '
                           'holders',
            'id': 'testForBlacklist',
            'result': True},
           {'description': 'Ownership renounced or source does not contain an '
                           'owner contract',
            'id': 'testForOwnershipNotRenounced',
            'result': False},
           {'description': 'Creator not authorized for special permission',
            'id': 'testForAuthorization',
            'result': False},
           {'description': 'Tokens locked/burned',
            'id': 'testForTokensLockedOrBurned',
            'result': True,
            'value': 0.002441962189654333,
            'valuePct': 9.353727652891257e-05},
           {'description': 'Creator wallet contains less than 5% of token '
                           'supply',
            'id': 'testForHighCreatorTokenBalance',
            'result': False,
            'value': 0,
            'valuePct': 0},
           {'description': 'Owner wallet contains less than 5% of token supply',
            'id': 'testForHighOwnerTokenBalance',
            'result': False,
            'value': 0,
            'valuePct': 0},
           {'data': [{'address': '0x15ef07c7ec863081b757f34c497452dbb65f16f7',
                      'balance': 9332.365029778688,
                      'is_contract': False},
                     {'address': '0x0453bef3490d4e4cbb01ec94737b75bbc051c750',
                      'balance': 7251.968154354711,
                      'is_contract': False},
                     {'address': '0x90ba15d4ad2c6ed1aa6296d4c06b3a7ad1599750',
                      'balance': 3711.3650926786295,
                      'is_contract': False},
                     {'address': '0x788b293db0068b17c1147d289aebcd1c7cc11229',
                      'balance': 1918.5942148506324,
                      'is_contract': False},
                     {'address': '0x08c2d690340998bf3f74e6a6496fc2868ced75d5',
                      'balance': 1764.577012673702,
                      'is_contract': False},
                     {'address': '0xe8c97650aa7e4525cc45851af5b2f5f81403432a',
                      'balance': 1759.1752683280474,
                      'is_contract': False},
                     {'address': '0xd4913c03ba8b00a85634c170a404b99ef01fe4f6',
                      'balance': 1499.323423358631,
                      'is_contract': False},
                     {'address': '0x8e54b18ea37a97914149e4bec2b4146503ba14ed',
                      'balance': 1285.0936630338015,
                      'is_contract': False},
                     {'address': '0xcd9f53208390399de0e2ba5914b7bd53afc62835',
                      'balance': 1243.9131637279036,
                      'is_contract': False},
                     {'address': '0x2e43eac73fabe2b207d014726d7c157054beccde',
                      'balance': 1177.2629555707488,
                      'is_contract': False},
                     {'address': '0x396e7c0cdd9dcec52f2b40948f8f703f8d750e10',
                      'balance': 1101.9120660748333,
                      'is_contract': False},
                     {'address': '0x4a63eef3060ad8eabd67c4cd4b9f908c37f2e1c1',
                      'balance': 1068.880217996999,
                      'is_contract': False},
                     {'address': '0x4eeee62a0c41fd39285af411fc9be030dc40a691',
                      'balance': 1028.24351924691,
                      'is_contract': False},
                     {'address': '0xe1ef21cd83316467823b7cd33b43cd87b9ed645a',
                      'balance': 948.0084401939899,
                      'is_contract': False},
                     {'address': '0xe8ea1eab72af70471e3cfa999f4c0eff173473ed',
                      'balance': 904.7122650717333,
                      'is_contract': False},
                     {'address': '0x457d90dc48ba7549c1c04922dc0f3dea23c3a9f2',
                      'balance': 897.8435736232242,
                      'is_contract': False},
                     {'address': '0x3f747d527666d752706fd5d96d5c857a8de4a517',
                      'balance': 868.5356019052704,
                      'is_contract': False},
                     {'address': '0xc731022481a88f40541346fff53eaaf38a5d86ba',
                      'balance': 811.2400720078341,
                      'is_contract': False},
                     {'address': '0xafc17077adcd32cf9110f8f9f271e250b7680fd1',
                      'balance': 804.5682720521471,
                      'is_contract': False},
                     {'address': '0xa735df3b21a6f665e9cb54d7a29918f4047b638d',
                      'balance': 778.4391820978005,
                      'is_contract': False}],
            'description': 'All other wallets contain less than 5% of token '
                           'supply',
            'id': 'testForHighWalletTokenBalance',
            'result': True},
           {'description': 'Burned amount exceeds total token supply',
            'id': 'testForBurnedBalanceExceedsSupply',
            'result': False},
           {'description': 'All wallets combined contain less than 100% of '
                           'token supply',
            'id': 'testForCombinedWalletsExceedSupply',
            'result': True},
           {'description': 'All wallets contain less than 100% of token supply',
            'id': 'testForImpossibleWalletTokenBalance',
            'result': True},
           {'currency': 'ETH',
            'description': 'Adequate current liquidity',
            'id': 'testForInadequateLiquidity',
            'result': False,
            'value': 320.06561592285965,
            'valuePct': 320.06561592285965},
           {'description': 'Adequate initial liquidity',
            'id': 'testForInadequateInitialLiquidity',
            'result': True,
            'value': 0.25798805413538606,
            'valuePct': 0.6449701353384651},
           {'description': 'At least 95% of liquidity locked/burned',
            'id': 'testForInadeqateLiquidityLockedOrBurned',
            'result': True,
            'value': 3.24037034920393e+22,
            'valuePct': 0.19270790085767736},
           {'description': 'Creator wallet contains less than 5% of liquidity',
            'id': 'testForHighCreatorLPBalance',
            'result': False,
            'value': 0,
            'valuePct': 0},
           {'description': 'Owner wallet contains less than 5% of liquidity',
            'id': 'testForHighOwnerLPBalance',
            'result': False,
            'value': 1e-15,
            'valuePct': 5.947094933300462e-39},
           {'description': 'Token is sellable',
            'id': 'testForUnableToSell',
            'result': False},
           {'description': 'Buy fee is less than 5%',
            'id': 'testForHighBuyFee',
            'result': False,
            'valuePct': 0},
           {'description': 'Sell fee is less than 5%',
            'id': 'testForHighSellFee',
            'result': False,
            'valuePct': 0},
           {'description': 'Buy/sell fee is less than 30%',
            'id': 'testForExtremeFee',
            'result': False}],
 'total_supply': 21000000}
cached: bool

Added to the response if it was locally cached

message: str

OK if success

status: str
score: int

0-100 d

pools: list[dict]

Trading pool data

__init__(*args, **kwargs)
__new__(**kwargs)
clear() None.  Remove all items from D.
copy() a shallow copy of D
fromkeys(value=None, /)

Create a new dictionary with keys from iterable and values set to value.

get(key, default=None, /)

Return the value for key if key is in the dictionary, else default.

items() a set-like object providing a view on D's items
keys() a set-like object providing a view on D's keys
pop(k[, d]) v, remove specified key and return the corresponding value.

If the key is not found, return the default if given; otherwise, raise a KeyError.

popitem()

Remove and return a (key, value) pair as a 2-tuple.

Pairs are returned in LIFO (last-in, first-out) order. Raises KeyError if the dict is empty.

setdefault(key, default=None, /)

Insert key with a value of default if key is not in the dictionary.

Return the value for key if key is in the dictionary, else default.

update([E, ]**F) None.  Update D from mapping/iterable E and F.

If E is present and has a .keys() method, then does: for k in E.keys(): D[k] = E[k] If E is present and lacks a .keys() method, then does: for k, v in E: D[k] = v In either case, this is followed by: for k in F: D[k] = F[k]

values() an object providing a view on D's values
class TokenSniffer

Bases: object

TokenSniffer API.

__init__(api_key, session=None)
Parameters
  • api_key (str) –

  • session (requests.sessions.Session) –

fetch_token_info(chain_id, address)

Get TokenSniffer token data.

This is a synchronous method and may block long time if TokenSniffer does not have cached results.

https://tokensniffer.com/api/v2/tokens/{chain_id}/{address}

Parameters
Returns

Raw TokenSniffer JSON reply.

Return type

eth_defi.token_analysis.tokensniffer.TokenSnifferReply

class CachedTokenSniffer

Bases: eth_defi.token_analysis.tokensniffer.TokenSniffer

Add file-system based cache for TokenSniffer API.

  • See TokenSniffer class for details

  • Use SQLite DB as a key-value cache backend, or your custom cache interface

  • No support for multithreading/etc. fancy stuff

Example usage:

from eth_defi.token_analysis.tokensniffer import CachedTokenSniffer, is_tradeable_token

#
# Setup TokenSniffer
#

db_file = Path(cache_path) / "tokensniffer.sqlite"

tokensniffer_threshold = 24  # Quite low threshold, 0 = total scam
sniffer = CachedTokenSniffer(
    db_file,
    TOKENSNIFFER_API_KEY,
)

ticker = make_full_ticker(pair_metadata[pair_id])
address = pair_metadata[pair_id]["base_token_address"]
sniffed_data = sniffer.fetch_token_info(chain_id.value, address)
if not is_tradeable_token(sniffed_data, risk_score_threshold=tokensniffer_threshold):
    score = sniffed_data["score"]
    print(f"WARN: Skipping pair {ticker} as the TokenSniffer score {score} is below our risk threshold")
    continue

You can also use your own cache interface instead of SQLite. Here is an example SQLALchemy implementation:

class TokenInternalCache(UserDict):
    def __init__(self, dbsession: Session):
        self.dbsession = dbsession

    def match_token(self, token_spec: str) -> Token:
        # Sniffer interface gives us tokens as {chain}-{address} strings
        chain, address = token_spec.split("-")
        chain_id = int(chain)
        address = HexBytes(address)
        return self.dbsession.query(Token).filter(Token.chain_id == chain_id, Token.address == address).one_or_none()

    def __getitem__(self, name) -> None | str:
        token = self.match_token(name)
        if token is not None:
            if token.etherscan_data is not None:
                return token.etherscan_data.get("tokensniffer_data")

        return None

    def __setitem__(self, name, value):
        token = self.match_token(name)
        if token.etherscan_data is None:
            token.etherscan_data = {}
        token.etherscan_data["tokensniffer_data"] = value

    def __contains__(self, key):
        return self.get(key) is not None


# And then usage:

weth = dbsession.query(Token).filter_by(symbol="WETH", chain_id=1).one()

sniffer = CachedTokenSniffer(
    cache_file=None,
    api_key=TOKENSNIFFER_API_KEY,
    cache=cast(dict, TokenInternalCache(dbsession)),
)

data = sniffer.fetch_token_info(weth.chain_id, weth.address.hex())
assert data["cached"] is False

data = sniffer.fetch_token_info(weth.chain_id, weth.address.hex())
assert data["cached"] is True
Parameters
  • api_key – TokenSniffer API key.

  • session – requests.Session for persistent HTTP connections

  • cache_file

    Path to a local file system SQLite file used as a cached.

    For simple local use cases.

  • cache

    Direct custom cache interface as a Python dict interface.

    For your own database caching.

    Cache keys are format: cache_key = f”{chain_id}-{address}”. Cache values are JSON blobs as string.

__init__(cache_file, api_key, session=None, cache=None)
Parameters
  • api_key (str) – TokenSniffer API key.

  • session (requests.sessions.Session) – requests.Session for persistent HTTP connections

  • cache_file (pathlib.Path | None) –

    Path to a local file system SQLite file used as a cached.

    For simple local use cases.

  • cache (dict | None) –

    Direct custom cache interface as a Python dict interface.

    For your own database caching.

    Cache keys are format: cache_key = f”{chain_id}-{address}”. Cache values are JSON blobs as string.

fetch_token_info(chain_id, address)

Get TokenSniffer info.

Use local file cache if available.

Returns

Data passed through TokenSniffer.

A special member cached is set depending on whether the reply was cached or not.

Parameters
Return type

eth_defi.token_analysis.tokensniffer.TokenSnifferReply

get_diagnostics()

Get a diagnostics message.

  • Use for logging what kind of data we have collected

Example output:

Token sniffer info is:

        TokenSniffer cache database /Users/moo/.cache/tradingstrategy/tokensniffer.sqlite summary:

        Entries: 195
        Max score: 100
        Min score: 0
        Avg score: 56.6
Returns

Multi-line human readable string

Return type

str

is_tradeable_token(data, symbol=None, risk_score_threshold=65, whitelist={'AAVE', 'DAI', 'ETH', 'FLOKI', 'MKR', 'NEXO', 'PEPE', 'SNX', 'SYN', 'USDC', 'USDS', 'USDT', 'WBNB', 'WBTC', 'WETH', 'cbBTC'})

Risk assessment for open-ended trade universe.

  • Based on TokenSniffer reply, determine if we want to trade this token or not

Note

This will alert for USDT/USDC, etc. so be careful.

Some example thresholds:

WARN: Skipping pair USDT-USDC-uniswap-v2-30bps, address 0xdac17f958d2ee523a2206206994597c13d831ec7 as the TokenSniffer score 45 is below our risk threshold, liquidity is 2,447,736.44 USD
WARN: Skipping pair MKR-DAI-uniswap-v2-30bps as the TokenSniffer score 70 is below our risk threshold, liquidity is 76,978,850.37
WARN: Skipping pair PEPE-WETH-uniswap-v2-30bps as the TokenSniffer score 70 is below our risk threshold, liquidity is 19,104,516.38
WARN: Skipping pair XXi-WETH-uniswap-v2-30bps as the TokenSniffer score 50 is below our risk threshold, liquidity is 10,234,803.81
WARN: Skipping pair PAXG-WETH-uniswap-v2-30bps as the TokenSniffer score 20 is below our risk threshold, liquidity is 9,197,796.28
WARN: Skipping pair FLOKI-WETH-uniswap-v2-30bps as the TokenSniffer score 69 is below our risk threshold, liquidity is 8,786,378.77
WARN: Skipping pair BEAM-WETH-uniswap-v2-30bps as the TokenSniffer score 70 is below our risk threshold, liquidity is 5,192,385.34
Parameters
Returns

True if we want to trade

Return type

bool