So lately, I've been working on restoring more of my blockchain management system (with the ultimate goal to get back to some arbitrage experiments 😎), and right now, I have a first version of a “Binance Smart Chain” blockchain, “ERC20” compatible Token and Pair classes, and “PancakeSwap” DEX based on a unified “UniswapBase” exchange representation.
And from the PanscakeSwap DEX (ie. Decentralized exchange) object I can request to collect all the pairs available on the exchange and all the tokens used in those pairs, to store information about them in a database. It's working pretty well already, but I still have a few exceptions from time to time due to very specific errors, and I'm still in the process of collecting the pairs only for PancakeSwap (version 1 I mean 😅): there are about 133K pairs already there!
What I will do here is to provide some improvements/extensions around this concept to try to improve it further, so let's get to work!
def token_row_from_desc(self, desc, validate_symbol=True, sym_names=None): """Convert a token desc dict to a token row""" min_slippage = desc.get('min_slippage') swap_fees = desc.get('swap_fees') minslip = int(min_slippage*1000) if min_slippage is not None else None sfp = int(swap_fees*1000) if swap_fees is not None else None cls = 0 clval = desc.get('classification') if clval is not None: cls = int(TokenClassification[clval.upper()]) if isinstance(clval, str) else int(clval) sym = desc['symbol'] name = desc['name'] addr = desc['address'] # Leave some room for duplicate values if len(sym) > 30: logger.warning("Detected too long token symbol: %s", sym) sym = sym[:30] # Remove the null characters in the symbols and names if any: if '\x00' in sym: sym = sym.replace('\x00', '') logger.warning("Removed null characters in token %s symbol %s", addr, sym) # Note: if the name or symbol are too long then we should cut them: if len(name) > 64: logger.warning("Detected too long token name: %s", name) name = name[:64] if '\x00' in name: name = name.replace('\x00', '') logger.warning("Removed null characters in token %s name %s", addr, name)
"""blockchain manager class""" import logging from nvp.nvp_component import NVPComponent from nvp.nvp_context import NVPContext from nvh.crypto.blockchain.evm_blockchain import EVMBlockchain from nvh.crypto.blockchain.uniswap_base import UniswapBase logger = logging.getLogger(__name__) class BlockchainManager(NVPComponent): """BlockchainManager component class""" def __init__(self, ctx): """BlockchainManager base constructor""" NVPComponent.__init__(self, ctx) def process_command(self, cmd): """Check if this component can process the given command""" if cmd == 'collect-gas-price': chain_name = self.get_param("chain") chain: EVMBlockchain = self.get_component(f"{chain_name}_chain") chain.collect_gas_price_statistics() return True if cmd == 'update-pairs': # Retrieve the exchange name: ex_name = self.get_param("exchange") # Retrieve that exchange: dex: UniswapBase = self.get_component(ex_name) # Update the pairs: dex.update_pairs() return True return False if __name__ == "__main__": # Create the context: context = NVPContext() # Add our component: comp = context.register_component("chain_man", BlockchainManager(context)) context.define_subparsers("main", { "collect-gas-price": None, "update-pairs": None }) psr = context.get_parser('main.collect-gas-price') psr.add_argument("-c", "--chain", dest="chain", type=str, default="bsc", help="Blockchain from where to collect the gas prices.") psr = context.get_parser('main.update-pairs') psr.add_argument("exchange", type=str, help="exchange name where to update the pairs") comp.run()
$ nvp bchain collect-gas-price -c bsc $ nvp bchain collect-gas-price -c eth
$ nvp bchain update-pairs pancakeswap
if cmd == 'find-token': chain_name = self.get_param("chain") chain: EVMBlockchain = self.get_component(f"{chain_name}_chain") pat = self.get_param("pattern") # We check if we have a token for that address: token = chain.get_token_desc(symbol=pat) if token is not None: logger.info("Found token: %s", self.pretty_print(token)) else: logger.info("Token not found.") return True
$ nvp bchain find-token -c bsc WBNB 2022/05/27 08:11:16 [__main__] INFO: Found token: { 'address': '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c', 'classification': 0, 'decimals': 18, 'id': 3, 'min_slippage': None, 'name': 'Wrapped BNB', 'swap_fees': None, 'symbol': 'WBNB'} $ nvp bchain find-token -c bsc PANDA 2022/05/27 08:11:25 [__main__] INFO: Found token: { 'address': '0xc757025cFcC34a279BD56Ae70F00b4aA601648ed', 'classification': 0, 'decimals': 18, 'id': 5781, 'min_slippage': None, 'name': 'PandaSwap', 'swap_fees': None, 'symbol': 'PANDA'}
2022/05/27 03:44:37 [nvh.crypto.blockchain.evm_blockchain] INFO: Adding new token 0x0b2534c22bb60a51C1AB87c2266b14182e3b063E (PANDA<nul><nul<nul>) 2022/05/27 03:45:18 [nvp.communication.email_handler] INFO: Should send the email message <p style="color: #fd0202;">**WARNING:** an exception occured in the following command:</p><p><em>['/mnt/data1/dev/projects/NervProj/.pyenvs/bsc_env/bin/python3', '/mnt/data1/dev/projects/NervHome/nvh/crypto/bsc/binance_smart_chain.py', 'update-pairs', 'pancakeswap']</em></p><p>cwd=None</p><p >=> Check the logs for details.</p> 2022/05/27 03:45:18 [nvp.components.runner] ERROR: Error occured in script command: ['/mnt/data1/dev/projects/NervProj/.pyenvs/bsc_env/bin/python3', '/mnt/data1/dev/projects/NervHome/nvh/crypto/bsc/binance_smart_chain.py', 'update-pairs', 'pancakeswap'] (cwd=None)
$ nvp bchain find-token -c bsc 0x0b2534c22bb60a51C1AB87c2266b14182e3b063E 2022/05/27 08:36:22 [__main__] INFO: Token not found.
if cmd == 'search-pair': chain_name = self.get_param("chain") chain: EVMBlockchain = self.get_component(f"{chain_name}_chain") pat = self.get_param("pattern") # Search for a pair containing the given tokens: # Not that we may have only 1 token provided here: parts = pat.split("/") ntokens = len(parts) self.check(ntokens == 1 or ntokens == 2, "Invalid number of tokens in %s", pat) chain_db = chain.get_db() t0 = chain.get_token_address(parts[0]) t1 = chain.get_token_address(parts[1]) if ntokens == 2 else None pairs = chain_db.find_pairs_with(token0=t0, token1=t1) if pairs is not None: logger.info("Found %d pairs: %s", len(pairs), self.pretty_print(pairs)) else: logger.info("No token pair found.") return True
find_pairs_with
in the ChainDB: def find_pairs_with(self, token0, token1=None): """Find the pairs with the given token0 and token1""" if token1 is None: # Single token search: sql = "SELECT * FROM pairs WHERE (token0=%s) OR (token1=%s)" cur = self.execute(sql, (token0, token0)) else: sql = "SELECT * FROM pairs WHERE ((token0=%s AND token1=%s) OR (token0=%s AND token1=%s))" cur = self.execute(sql, (token0, token1, token1, token0)) rows = cur.fetchall() return [self.pair_row_to_desc(row) for row in rows]
$ nvp bchain search-pair -c bsc ADA/WBNB 2022/05/27 09:09:57 [__main__] INFO: Found 1 pairs: [ { 'address': '0xBA51D1AB95756ca4eaB8737eCD450cd8F05384cF', 'decimals': 18, 'exchange_id': 1, 'id': 7, 'name': 'Pancake LPs', 'pool_id': 6, 'symbol': 'Cake-LP', 'token0': '0x3EE2200Efb3400fAbB9AacF31297cBdD1d435D47', 'token1': '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c'}]
$ nvp bchain search-pair -c bsc ADA 2022/05/27 09:11:46 [__main__] INFO: Found 79 pairs: [ { 'address': '0xBA51D1AB95756ca4eaB8737eCD450cd8F05384cF', 'decimals': 18, 'exchange_id': 1, 'id': 7, 'name': 'Pancake LPs', 'pool_id': 6, 'symbol': 'Cake-LP', 'token0': '0x3EE2200Efb3400fAbB9AacF31297cBdD1d435D47', 'token1': '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c'}, { 'address': '0xF35145e28e1dd67055221739D8554E0b5C7Adcd5', 'decimals': 18, 'exchange_id': 1, 'id': 162, 'name': 'Pancake LPs', 'pool_id': 161, 'symbol': 'Cake-LP', 'token0': '0x3EE2200Efb3400fAbB9AacF31297cBdD1d435D47', 'token1': '0xe9e7CEA3DedcA5984780Bafc599bD69ADd087D56'}, { 'address': '0x4D673a471d442fFF0dF4f9e71Dc406A75DE30Eb4', 'decimals': 18, 'exchange_id': 1, 'id': 163, 'name': 'Pancake LPs', 'pool_id': 162, 'symbol': 'Cake-LP', 'token0': '0x3EE2200Efb3400fAbB9AacF31297cBdD1d435D47', 'token1': '0x4B0F1812e5Df2A09796481Ff14017e6005508003'}, (...more pairs here...) ]
$ nvp bchain search-pair -c bsc 0x0b2534c22bb60a51C1AB87c2266b14182e3b063E 2022/05/27 09:14:50 [__main__] INFO: Found 1 pairs: [ { 'address': '0x3863Dd0b3E7b1b88498007c11dcc77f5ec0e70e8', 'decimals': 18, 'exchange_id': 1, 'id': 46679, 'name': 'Pancake LPs', 'pool_id': 46678, 'symbol': 'Cake-LP', 'token0': '0x0b2534c22bb60a51C1AB87c2266b14182e3b063E', 'token1': '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c'}]
# Load the tokens: self.t0 = self.chain.get_token(self.desc['token0']) self.t1 = self.chain.get_token(self.desc['token1'])
SQL_CREATE_RPC_REQUESTS_TABLE = """ CREATE TABLE IF NOT EXISTS rpc_requests ( id SERIAL PRIMARY KEY, timestamp float, url varchar(128), duration float, function varchar(128) ); """
if cmd == 'update-pairs': purl = self.get_param("provider_url") chain_name = self.get_param("chain") if purl is not None: logger.info("Using provider url %s for chain %s", purl, chain_name) self.config['blockchains'][chain_name]['provider_url'] = purl # Retrieve the exchange name: ex_name = self.get_param("exchange") # Retrieve that exchange: dex: UniswapBase = self.get_component(ex_name) # Update the pairs: dex.update_pairs() return True
$ nvp bchain update-pairs julswap -c bsc -p "https://bscrpc.com/erigonbsc"
$ nvp bchain update-pairs bakeryswap -c bsc -p "https://bscrpc.com"
$ nvp bchain update-pairs apeswap -c bsc -p "https://bsc-dataseed1.defibit.io"
$ nvp bchain update-pairs mdexswap -c bsc -p "https://bsc-dataseed1.ninicoin.io"
2022/05/28 08:37:48 [nvh.crypto.blockchain.evm_blockchain] ERROR: Exception occured while calling name: Could not transact with/call contract function, is contract deployed correctly and chain synced? 2022/05/28 08:37:50 [nvh.crypto.blockchain.evm_blockchain] ERROR: Exception occured while calling name: Could not transact with/call contract function, is contract deployed correctly and chain synced? 2022/05/28 08:37:52 [nvh.crypto.blockchain.evm_blockchain] ERROR: Exception occured while calling name: Could not transact with/call contract function, is contract deployed correctly and chain synced? 2022/05/28 08:37:54 [nvh.crypto.blockchain.evm_blockchain] ERROR: Exception occured while calling name: Could not transact with/call contract function, is contract deployed correctly and chain synced?
2022/05/28 08:49:32 [nvh.crypto.blockchain.evm_smart_contract] ERROR: Exception occured while calling 'name' (on 0xecC736C39b6f8ccAEE79a3EdaA31A41fE1f8b914): Could not transact with/call contract function, is contract deployed correctly and chain synced?
2022/05/28 09:04:15 [nvh.crypto.blockchain.erc20_pair] INFO: Registering pair at 0x83Bf4ccdD9B61B9e4373bfc90739bac035A43d4a 2022/05/28 09:04:16 [nvh.crypto.blockchain.evm_smart_contract] ERROR: Exception occured while calling 'name' (on 0x441388fFC0235944C9Ad3aa5d834C0037EcB684C): Could not transact with/call contract function, is contract deployed correctly and chain synced? 2022/05/28 09:04:19 [nvh.crypto.blockchain.evm_smart_contract] ERROR: Exception occured while calling 'name' (on 0x441388fFC0235944C9Ad3aa5d834C0037EcB684C): Could not transact with/call contract function, is contract deployed correctly and chain synced? 2022/05/28 09:04:21 [nvh.crypto.blockchain.evm_smart_contract] ERROR: Exception occured while calling 'name' (on 0x441388fFC0235944C9Ad3aa5d834C0037EcB684C): Could not transact with/call contract function, is contract deployed correctly and chain synced?
web3.eth.getCode(address))
to check that (cf. https://ethereum.stackexchange.com/questions/66315/check-if-contract-is-destructed)chain.web3.eth.getCode("0xecC736C39b6f8ccAEE79a3EdaA31A41fE1f8b914") HexBytes('0x')
has_code()
method in the evm_blockchain class: def has_code(self, addr): """Check if there is some code for a given address (ie. a smart contract) If there is no code, then we can consider the contract was self-destructed if this was supposed to be a valid contract somehow.""" return self.web3.eth.getCode(addr).hex() != '0x'
if desc is None: # We should register this new token: logger.debug("Registering token at %s", address) # First we really need to check here if the contract was not self destructed: destructed = False if not self.chain.has_code(address): logger.info("Note: contract at %s was self-destructed.", address) destructed = True sct = self.contract try: desc = { 'address': address, 'name': '<destructed-token>' if destructed else sct.call_function("name"), 'symbol': '<destructed>' if destructed else sct.call_function("symbol"), 'decimals': 0 if destructed else sct.call_function("decimals"), } except Exception as err: # pylint: disable=broad-except logger.error("Exception occured while trying to initiate token at %s: %s", address, err) desc = { 'address': address, 'name': "???", 'symbol': "???", 'decimals': 0 } desc = self.chain.add_token_desc(desc)
$ nvp bchain check-db -c bsc 2022/05/29 07:05:21 [nvh.crypto.blockchain.evm_blockchain] INFO: Collected 147585 pairs and 135759 tokens
def check_db_consistency(self): """Check that the tokens/pairs tables are consistents""" # get all pairs: allpairs = self.get_db().get_all_pairs() npairs = len(allpairs) alltokens = self.get_db().get_all_tokens() ntokens = len(alltokens) logger.info("Collected %d pairs and %d tokens", npairs, ntokens) # We should iterate on each pair and collect its pool index: # each pair row contains: # id,address,token0,token1,name,symbol,decimals,pool_id,exchange_id # each token row conotains: # id,address,name,symbol,decimals,min_slippage,swap_fees,classification # Create a dict of all token addresses: taddrs = {} for token in alltokens: taddr = token[1] taddrs[taddr] = True pool_ids = {} for idx, pair in enumerate(allpairs): # pool_id: pidx = pair[7] # ex_id: ex_id = pair[8] if not ex_id in pool_ids: pool_ids[ex_id] = [] # add the pool id to the list: pool_ids[ex_id].append(pidx) t0addr = pair[2] t1addr = pair[3] if t0addr not in taddrs: logger.info("Token at %s is not registered.", t0addr) # we register that token now: self.get_token(t0addr) # And then add the address to the list: taddrs[t0addr] = True if t1addr not in taddrs: logger.info("Token at %s is not registered.", t1addr) # we register that token now: self.get_token(t1addr) # And then add the address to the list: taddrs[t1addr] = True # check that all the pool ids are registered for each # available exchange: for ex_id, pids in pool_ids.items(): pids.sort() idx = 0 tgt = 0 npids = len(pids) while idx < npids: if pids[idx] == tgt: # increment the target: tgt += 1 idx += 1 else: logger.warning("Missing pool id %d in exchange %d", tgt, ex_id) # we just increment the target pool id in this case: tgt += 1 logger.info("Done checking pairs/tokens db consistency on %s", self.short_name)
$ nvp bchain check-db -c bsc 2022/05/29 07:25:51 [nvh.crypto.blockchain.evm_blockchain] INFO: Collected 147585 pairs and 135759 tokens 2022/05/29 07:25:51 [nvh.crypto.blockchain.evm_blockchain] INFO: Token at 0x0b2534c22bb60a51C1AB87c2266b14182e3b063E is not registered. 2022/05/29 07:25:52 [nvh.crypto.blockchain.evm_blockchain] INFO: Adding new token 0x0b2534c22bb60a51C1AB87c2266b14182e3b063E (PANDA ) 2022/05/29 07:25:52 [nvh.crypto.blockchain.chain_db] WARNING: Removed null characters in token 0x0b2534c22bb60a51C1AB87c2266b14182e3b063E symbol PANDA 2022/05/29 07:25:52 [nvh.crypto.blockchain.chain_db] WARNING: Removed null characters in token 0x0b2534c22bb60a51C1AB87c2266b14182e3b063E name Panda 2022/05/29 07:25:52 [nvh.crypto.blockchain.chain_db] INFO: Replacing symbol PANDA with PANDA#40 2022/05/29 07:25:52 [nvh.crypto.blockchain.evm_blockchain] INFO: Token at 0x9a2129134944117e2C16514311E5AF94Ddaa3E05 is not registered. 2022/05/29 07:25:53 [nvh.crypto.blockchain.evm_blockchain] INFO: Adding new token 0x9a2129134944117e2C16514311E5AF94Ddaa3E05 (COMCOIN) 2022/05/29 07:25:53 [nvh.crypto.blockchain.chain_db] INFO: Replacing symbol COMCOIN with COMCOIN#2 2022/05/29 07:25:53 [nvh.crypto.blockchain.evm_blockchain] INFO: Token at 0xD4031a4964d7f7c8587cBda36Dcd7DB3E6B29f4C is not registered. 2022/05/29 07:25:53 [nvh.crypto.blockchain.erc20_token] INFO: Note: contract at 0xD4031a4964d7f7c8587cBda36Dcd7DB3E6B29f4C was self-destructed. 2022/05/29 07:25:53 [nvh.crypto.blockchain.evm_blockchain] INFO: Adding new token 0xD4031a4964d7f7c8587cBda36Dcd7DB3E6B29f4C (<destructed>) 2022/05/29 07:25:55 [nvh.crypto.blockchain.chain_db] INFO: Replacing symbol <destructed> with <destructed>#2302 2022/05/29 07:25:55 [nvh.crypto.blockchain.evm_blockchain] INFO: Token at 0x7eb2E0F97cbEb98cc3F5787bcAE7CF3898E8209A is not registered. 2022/05/29 07:25:55 [nvh.crypto.blockchain.erc20_token] INFO: Note: contract at 0x7eb2E0F97cbEb98cc3F5787bcAE7CF3898E8209A was self-destructed. 2022/05/29 07:25:55 [nvh.crypto.blockchain.evm_blockchain] INFO: Adding new token 0x7eb2E0F97cbEb98cc3F5787bcAE7CF3898E8209A (<destructed>) 2022/05/29 07:25:57 [nvh.crypto.blockchain.chain_db] INFO: Replacing symbol <destructed> with <destructed>#2303 2022/05/29 07:25:57 [nvh.crypto.blockchain.evm_blockchain] INFO: Token at 0x7dD2290E434699aCf9f54bDA414D8f136f5eA121 is not registered. 2022/05/29 07:25:57 [nvh.crypto.blockchain.erc20_token] INFO: Note: contract at 0x7dD2290E434699aCf9f54bDA414D8f136f5eA121 was self-destructed. 2022/05/29 07:25:57 [nvh.crypto.blockchain.evm_blockchain] INFO: Adding new token 0x7dD2290E434699aCf9f54bDA414D8f136f5eA121 (<destructed>) 2022/05/29 07:25:59 [nvh.crypto.blockchain.chain_db] INFO: Replacing symbol <destructed> with <destructed>#2304 2022/05/29 07:25:59 [nvh.crypto.blockchain.evm_blockchain] INFO: Token at 0xecC736C39b6f8ccAEE79a3EdaA31A41fE1f8b914 is not registered. 2022/05/29 07:25:59 [nvh.crypto.blockchain.erc20_token] INFO: Note: contract at 0xecC736C39b6f8ccAEE79a3EdaA31A41fE1f8b914 was self-destructed. 2022/05/29 07:25:59 [nvh.crypto.blockchain.evm_blockchain] INFO: Adding new token 0xecC736C39b6f8ccAEE79a3EdaA31A41fE1f8b914 (<destructed>) 2022/05/29 07:26:01 [nvh.crypto.blockchain.chain_db] INFO: Replacing symbol <destructed> with <destructed>#2305 2022/05/29 07:26:01 [nvh.crypto.blockchain.evm_blockchain] INFO: Done checking pairs/tokens db consistency on bsc
$ nvp bchain check-db -c bsc 2022/05/29 07:29:11 [nvh.crypto.blockchain.evm_blockchain] INFO: Collected 147585 pairs and 135765 tokens 2022/05/29 07:29:11 [nvh.crypto.blockchain.evm_blockchain] INFO: Done checking pairs/tokens db consistency on bsc