async def update_balances(self): asset_symbols: List[str] = [] asset_update_tasks: List[asyncio.Task] = [] for asset_name, contract in self._erc20_contracts.items(): asset_symbols.append(asset_name) asset_update_tasks.append( self._ev_loop.run_in_executor( hummingbot.get_executor(), contract.functions.balanceOf(self._account_address).call ) ) asset_symbols.append("ETH") asset_update_tasks.append(self._ev_loop.run_in_executor( hummingbot.get_executor(), self._w3.eth.getBalance, self._account_address )) try: asset_raw_balances: List[int] = await asyncio.gather(*asset_update_tasks) for asset_name, raw_balance in zip(asset_symbols, asset_raw_balances): self._raw_account_balances[asset_name] = raw_balance except asyncio.CancelledError: raise except Exception: self.logger().error("Unexpected error fetching account balance updates.", exc_info=True)
async def check_transaction_receipts(self): """ Look for failed transactions, and emit transaction fail event if any are found. """ ev_loop: asyncio.BaseEventLoop = asyncio.get_event_loop() tasks = [ ev_loop.run_in_executor(hummingbot.get_executor(), self._w3.eth.getTransactionReceipt, tx_hash) for tx_hash in self._pending_tx_dict.keys() ] transaction_receipts: List[AttributeDict] = [ tr for tr in await asyncio.gather(*tasks) if (tr is not None and tr.get("blockHash") is not None) ] block_hash_set: Set[HexBytes] = set(tr.blockHash for tr in transaction_receipts) fetch_block_tasks = [ ev_loop.run_in_executor(hummingbot.get_executor(), self._w3.eth.getBlock, block_hash) for block_hash in block_hash_set ] blocks: Dict[HexBytes, AttributeDict] = dict( (block.hash, block) for block in await asyncio.gather(*fetch_block_tasks) if block is not None) for receipt in transaction_receipts: # Emit gas used event. tx_hash: str = receipt.transactionHash.hex() gas_price_wei: int = self._pending_tx_dict[tx_hash] gas_used: int = receipt.gasUsed gas_eth_amount_raw: int = gas_price_wei * gas_used if receipt.blockHash in blocks: block: AttributeDict = blocks[receipt.blockHash] if receipt.status == 0: self.logger().warning( f"The transaction {tx_hash} has failed.") self.trigger_event(WalletEvent.TransactionFailure, tx_hash) self.trigger_event( WalletEvent.GasUsed, EthereumGasUsedEvent(float(block.timestamp), tx_hash, float(gas_price_wei * 1e-9), gas_price_wei, gas_used, float(gas_eth_amount_raw * 1e-18), gas_eth_amount_raw)) # Stop tracking the transaction. self._stop_tx_tracking(tx_hash)
async def _get_logs(self, event_filter_params: Dict[str, any], max_tries: Optional[int] = 30) -> List[Dict[str, any]]: ev_loop: asyncio.BaseEventLoop = self._ev_loop count: int = 0 logs = [] while True: try: count += 1 if count > max_tries: self.logger().debug( f"Error fetching logs from block with filters: '{event_filter_params}'." ) break logs = await ev_loop.run_in_executor( hummingbot.get_executor(), functools.partial(self._w3.eth.getLogs, event_filter_params)) break except asyncio.CancelledError: raise except Exception as e: self.logger().debug( f"Block not found with filters: '{event_filter_params}'. Retrying..." ) await asyncio.sleep(0.5) return logs
async def _get_contract_info(self): if self._name is not None and self._symbol is not None and self._decimals is not None: return ev_loop: asyncio.AbstractEventLoop = asyncio.get_event_loop() tasks: List[asyncio.Task] = [ ev_loop.run_in_executor(hummingbot.get_executor(), func, *args) for func, args in [(self.get_name_from_contract, [self._contract]), (self.get_symbol_from_contract, [self._contract]), (self._contract.functions.decimals().call, [])] ] try: name, symbol, decimals = await asyncio.gather(*tasks) self._name = name self._symbol = symbol self._decimals = decimals except asyncio.CancelledError: raise except Exception: self.logger().network( f"Error fetching token info for {self._contract.address}.", exc_info=True, app_warning_msg= f"Error fetching token info for {self._contract.address}. " f"Check wallet network connection")
async def get_timestamp_for_block(self, block_hash: HexBytes, max_tries: Optional[int] = 10) -> int: counter = 0 block: AttributeDict = None if block_hash in self._blocks_window: block = self._blocks_window[block_hash] return block.timestamp else: while block is None: try: if counter == max_tries: raise ValueError( f"Block hash {block_hash.hex()} does not exist.") counter += 1 async with timeout(10.0): ev_loop: asyncio.BaseEventLoop = self._ev_loop block = await ev_loop.run_in_executor( hummingbot.get_executor(), functools.partial(self._w3.eth.getBlock, block_hash, full_transactions=False)) except TimeoutError: self.logger().network( f"Timed out fetching new block - '{block_hash}'.", exc_info=True, app_warning_msg= f"Timed out fetching new block - '{block_hash}'. " f"Check wallet network connection") finally: await asyncio.sleep(0.5) return block.timestamp
async def get_block_reorganization( self, incoming_block: AttributeDict) -> List[AttributeDict]: ev_loop: asyncio.BaseEventLoop = self._ev_loop block_reorganization: List[AttributeDict] = [] expected_parent_hash: HexBytes = incoming_block.parentHash while expected_parent_hash not in self._blocks_window and len( block_reorganization) < len(self._blocks_window): replacement_block = None while replacement_block is None: replacement_block = await ev_loop.run_in_executor( hummingbot.get_executor(), functools.partial(self._w3.eth.getBlock, expected_parent_hash, full_transactions=True)) if replacement_block is None: await asyncio.sleep(0.5) replacement_block_number: int = replacement_block.number replacement_block_hash: HexBytes = replacement_block.hash replacement_block_parent_hash: HexBytes = replacement_block.parentHash self._block_number_to_hash_map[ replacement_block_number] = replacement_block_hash self._blocks_window[replacement_block_hash] = replacement_block block_reorganization.append(replacement_block) expected_parent_hash = replacement_block_parent_hash block_reorganization.reverse() return block_reorganization
async def fetch_new_blocks_loop(self): ev_loop: asyncio.BaseEventLoop = self._ev_loop while True: try: async with timeout(30.0): incoming_block: AttributeDict = await ev_loop.run_in_executor( hummingbot.get_executor(), functools.partial(self._w3.eth.getBlock, self._block_number_to_fetch, full_transactions=True)) if incoming_block is not None: current_block_hash: HexBytes = self._block_number_to_hash_map.get( self._current_block_number, None) incoming_block_hash: HexBytes = incoming_block.hash incoming_block_parent_hash: HexBytes = incoming_block.parentHash new_blocks: List[AttributeDict] = [] if current_block_hash is not None and current_block_hash != incoming_block_parent_hash: block_reorganization: List[ AttributeDict] = await self.get_block_reorganization( incoming_block) new_blocks += block_reorganization self._block_number_to_hash_map[ self._block_number_to_fetch] = incoming_block_hash self._blocks_window[ incoming_block_hash] = incoming_block new_blocks.append(incoming_block) self._current_block_number = self._block_number_to_fetch self._block_number_to_fetch += 1 self.trigger_event(NewBlocksWatcherEvent.NewBlocks, new_blocks) while len( self._blocks_window) > self._block_window_size: block_hash = self._block_number_to_hash_map.popitem( last=False)[1] del self._blocks_window[block_hash] except asyncio.CancelledError: raise except asyncio.TimeoutError: self.logger().network( f"Timed out fetching new block - '{block_hash}'.", exc_info=True, app_warning_msg= f"Timed out fetching new block - '{block_hash}'. " f"Check wallet network connection") except Exception: self.logger().network( f"Error fetching new block.", exc_info=True, app_warning_msg=f"Error fetching new block. " f"Check wallet network connection") now: float = time.time() next_second: float = (now // 1) + 1 await asyncio.sleep(next_second - now)
async def check_network(self) -> NetworkStatus: try: await self._ev_loop.run_in_executor(hummingbot.get_executor(), getattr, self._w3.eth, "blockNumber") except asyncio.CancelledError: raise except Exception: return NetworkStatus.NOT_CONNECTED return NetworkStatus.CONNECTED
async def call_async(self, func: Callable, *args, timeout_seconds: float = 5.0, app_warning_msg: str = "API call error.") -> any: coro: Coroutine = self._ev_loop.run_in_executor( hummingbot.get_executor(), func, *args, ) return await self.schedule_async_call(coro, timeout_seconds, app_warning_msg=app_warning_msg)
async def outgoing_eth_transactions_loop(self): ev_loop: asyncio.AbstractEventLoop = self._ev_loop while True: signed_transaction: AttributeDict = await self._outgoing_transactions_queue.get( ) tx_hash: str = signed_transaction.hash.hex() try: await ev_loop.run_in_executor( hummingbot.get_executor(), self._w3.eth.sendRawTransaction, signed_transaction.rawTransaction) except asyncio.CancelledError: raise except Exception: self.logger().error(f"Error sending transaction {tx_hash}.", exc_info=True) self.trigger_event(WalletEvent.TransactionFailure, tx_hash) self._local_nonce -= 1
async def handler_loop(self, bot: Bot, update: Update) -> None: try: input_text = update.message.text.strip() output = f"\n[Telegram Input] {input_text}" self._hb.app.log(output) # if the command does starts with any disabled commands if any([input_text.startswith(dc) for dc in DISABLED_COMMANDS]): self.add_msg_to_queue(f"Command {input_text} is disabled from telegram") else: await self._ev_loop.run_in_executor( hummingbot.get_executor(), self._hb._handle_command, input_text ) except Exception as e: self.add_msg_to_queue(str(e))
async def check_and_fix_approval_amounts(self, spender: str) -> List[str]: """ Maintain the approve amounts for a token. This function will be used to ensure trade execution using exchange protocols such as 0x, but should be defined in child classes Allowance amounts to manage: 1. Allow external contract to pull tokens from local wallet """ min_approve_amount: int = int(Decimal("1e35")) target_approve_amount: int = int(Decimal("1e36")) # Get currently approved amounts get_approved_amounts_tasks: List[asyncio.Task] = [ self._ev_loop.run_in_executor( hummingbot.get_executor(), erc20_token.contract.functions.allowance( self.address, spender).call) for erc20_token in self._erc20_token_list ] approved_amounts: List[int] = await asyncio.gather( *get_approved_amounts_tasks) # Check and fix the approved amounts tx_hashes: List[str] = [] for approved_amount, erc20_token in zip(approved_amounts, self._erc20_token_list): token_name: str = await erc20_token.get_name() token_contract: Contract = erc20_token.contract if approved_amount >= min_approve_amount: self.logger().info( f"Approval already exists for {token_name} from wallet address {self.address}." ) continue self.logger().info( f"Approving spender for drawing {token_name} from wallet address {self.address}." ) tx_hash: str = self.execute_transaction( token_contract.functions.approve(spender, target_approve_amount)) tx_hashes.append(tx_hash) return tx_hashes
async def check_incoming_eth(self, new_blocks: List[AttributeDict]): watch_addresses: Set[str] = self._watch_addresses filtered_blocks: List[AttributeDict] = [ block for block in new_blocks if block is not None ] block_to_timestamp: Dict[str, float] = dict( (block.hash, float(block.timestamp)) for block in filtered_blocks) transactions: List[AttributeDict] = list( cytoolz.concat(b.transactions for b in filtered_blocks)) incoming_eth_transactions: List[AttributeDict] = [ t for t in transactions if ((t.get("to") in watch_addresses) and (t.get("value", 0) > 0)) ] get_receipt_tasks: List[asyncio.Task] = [ self._ev_loop.run_in_executor(hummingbot.get_executor(), self._w3.eth.getTransactionReceipt, t.hash) for t in incoming_eth_transactions ] transaction_receipts: List[AttributeDict] = await asyncio.gather( *get_receipt_tasks) for incoming_transaction, receipt in zip(incoming_eth_transactions, transaction_receipts): # Filter out failed transactions. if receipt.status != 1: continue # Emit event. raw_eth_value: int = incoming_transaction.get("value") eth_value: float = raw_eth_value * 1e-18 from_address: str = incoming_transaction.get("from") to_address: str = incoming_transaction.get("to") timestamp: float = block_to_timestamp[incoming_transaction.get( "blockHash")] self.trigger_event( IncomingEthWatcherEvent.ReceivedEther, WalletReceivedAssetEvent(timestamp, incoming_transaction.hash.hex(), from_address, to_address, "ETH", eth_value, raw_eth_value))
async def _update_gas_price(self): new_gas_price: int = await self._ev_loop.run_in_executor( hummingbot.get_executor(), getattr, self._w3.eth, "gasPrice") self._gas_price = new_gas_price