def test_new_password_process(self): # empty folder, new password is required self.assertFalse(Security.any_encryped_files()) self.assertTrue(Security.new_password_required()) # login will pass with any password result = Security.login("a") self.assertTrue(result) Security.update_secure_config("new_key", "new_value") self.assertTrue(os.path.exists(f"{temp_folder}encrypted_new_key.json")) self.assertTrue(Security.encrypted_file_exists("new_key"))
async def validate_n_connect_celo(self, to_reconnect: bool = False, celo_address: str = None, celo_password: str = None) -> Optional[str]: if celo_address is None: celo_address = global_config_map["celo_address"].value if celo_password is None: await Security.wait_til_decryption_done() celo_password = Security.decrypted_value("celo_password") if celo_address is None or celo_password is None: return "Celo address and/or password have not been added." if CeloCLI.unlocked and not to_reconnect: return None err_msg = CeloCLI.validate_node_synced() if err_msg is not None: return err_msg err_msg = CeloCLI.unlock_account(celo_address, celo_password) return err_msg
async def update_exchange_balance( self, exchange_name: str, client_config_map: ClientConfigMap) -> Optional[str]: is_gateway_market = self.is_gateway_market(exchange_name) if is_gateway_market and exchange_name in self._markets: # we want to refresh gateway connectors always, since the applicable tokens change over time. # doing this will reinitialize and fetch balances for active trading pair del self._markets[exchange_name] if exchange_name in self._markets: return await self._update_balances(self._markets[exchange_name]) else: await Security.wait_til_decryption_done() api_keys = Security.api_keys( exchange_name) if not is_gateway_market else {} return await self.add_exchange(exchange_name, client_config_map, **api_keys)
async def quick_start(args: argparse.Namespace): config_file_name = args.config_file_name password = args.config_password if args.auto_set_permissions is not None: autofix_permissions(args.auto_set_permissions) if password is not None and not Security.login(password): logging.getLogger().error("Invalid password.") return await Security.wait_til_decryption_done() await create_yml_files() init_logging("hummingbot_logs.yml") await read_system_configs_from_yml() AllConnectorSettings.initialize_paper_trade_settings(global_config_map.get("paper_trade_exchanges").value) hb = HummingbotApplication.main_application() # Todo: validate strategy and config_file_name before assinging if config_file_name is not None: hb.strategy_file_name = config_file_name hb.strategy_name = await update_strategy_config_map_from_file(os.path.join(CONF_FILE_PATH, config_file_name)) # To ensure quickstart runs with the default value of False for kill_switch_enabled if not present if not global_config_map.get("kill_switch_enabled"): global_config_map.get("kill_switch_enabled").value = False if hb.strategy_name and hb.strategy_file_name: if not all_configs_complete(hb.strategy_name): hb.status() # The listener needs to have a named variable for keeping reference, since the event listener system # uses weak references to remove unneeded listeners. start_listener: UIStartListener = UIStartListener(hb) hb.app.add_listener(HummingbotUIEvent.Start, start_listener) tasks: List[Coroutine] = [hb.run(), start_existing_gateway_container()] if global_config_map.get("debug_console").value: management_port: int = detect_available_port(8211) tasks.append(start_management_console(locals(), host="localhost", port=management_port)) await safe_gather(*tasks)
def test_login(self): password = "******" secrets_manager = ETHKeyFileSecretManger(password) store_password_verification(secrets_manager) Security.login(secrets_manager) config_map = self.store_binance_config() self.async_run_with_timeout(Security.wait_til_decryption_done(), timeout=2) self.assertTrue(Security.is_decryption_done()) self.assertTrue(Security.any_secure_configs()) self.assertTrue(Security.connector_config_file_exists(self.connector)) api_keys = Security.api_keys(self.connector) expected_keys = api_keys_from_connector_config_map(config_map) self.assertEqual(expected_keys, api_keys)
async def connect_ethereum( self, # type: HummingbotApplication ): self.placeholder_mode = True self.app.hide_input = True ether_wallet = global_config_map["ethereum_wallet"].value to_connect = True if ether_wallet is not None: answer = await self.app.prompt( prompt= f"Would you like to replace your existing Ethereum wallet " f"{ether_wallet} (Yes/No)? >>> ") if self.app.to_stop_config: self.app.to_stop_config = False return if answer.lower() not in ("yes", "y"): to_connect = False if to_connect: private_key = await self.app.prompt( prompt="Enter your wallet private key >>> ", is_password=True) public_address = Security.add_private_key(private_key) global_config_map["ethereum_wallet"].value = public_address if global_config_map["ethereum_rpc_url"].value is None: await self.prompt_a_config( global_config_map["ethereum_rpc_url"]) if global_config_map["ethereum_rpc_ws_url"].value is None: await self.prompt_a_config( global_config_map["ethereum_rpc_ws_url"]) if self.app.to_stop_config: self.app.to_stop_config = False return save_to_yml(settings.GLOBAL_CONFIG_PATH, global_config_map) err_msg = UserBalances.validate_ethereum_wallet() if err_msg is None: self._notify( f"Wallet {public_address} connected to hummingbot.") else: self._notify(f"\nError: {err_msg}") self.placeholder_mode = False self.app.hide_input = False self.app.change_prompt(prompt=">>> ")
async def connection_df(self # type: HummingbotApplication ): columns = ["Exchange", " Keys Added", " Keys Confirmed", " Status"] data = [] failed_msgs = {} network_timeout = float( global_config_map["other_commands_timeout"].value) try: err_msgs = await asyncio.wait_for( UserBalances.instance().update_exchanges(reconnect=True), network_timeout) except asyncio.TimeoutError: self.notify( "\nA network error prevented the connection table to populate. See logs for more details." ) raise for option in sorted(OPTIONS): keys_added = "No" keys_confirmed = 'No' status = get_connector_status(option) if option == "celo": celo_address = global_config_map["celo_address"].value if celo_address is not None and Security.encrypted_file_exists( "celo_password"): keys_added = "Yes" err_msg = await self.validate_n_connect_celo(True) if err_msg is not None: failed_msgs[option] = err_msg else: keys_confirmed = 'Yes' else: api_keys = (await Security.api_keys(option)).values() if len(api_keys) > 0: keys_added = "Yes" err_msg = err_msgs.get(option) if err_msg is not None: failed_msgs[option] = err_msg else: keys_confirmed = 'Yes' data.append([option, keys_added, keys_confirmed, status]) return pd.DataFrame(data=data, columns=columns), failed_msgs
def login_prompt(): from hummingbot.client.config.security import Security import time err_msg = None if Security.new_password_required(): show_welcome() password = input_dialog( title="Set Password", text="Create a password to protect your sensitive data. " "This password is not shared with us nor with anyone else, so please store it securely." "\n\nEnter your new password:"******"Set Password", text="Please re-enter your password:"******"Passwords entered do not match, please try again." else: Security.login(password) # encrypt current timestamp as a dummy to prevent promping for password if bot exits without connecting an exchange dummy = f"{time.time()}" Security.update_secure_config("default", dummy) else: password = input_dialog(title="Welcome back to Hummingbot", text="Enter your password:"******"Invalid password - please try again." if err_msg is not None: message_dialog(title='Error', text=err_msg, style=dialog_style).run() return login_prompt() return True
async def export_keys(self, # type: HummingbotApplication ): if not Security.any_encryped_files() and not Security.any_wallets(): self.notify("There are no keys to export.") return await Security.wait_til_decryption_done() self.placeholder_mode = True self.app.hide_input = True if await self.check_password(): await Security.wait_til_decryption_done() self.notify("\nWarning: Never disclose API keys or private keys. Anyone with your keys can steal any " "assets held in your account.") if Security.all_decrypted_values(): self.notify("\nAPI keys:") for key, value in Security.all_decrypted_values().items(): self.notify(f"{key}: {value}") if Security.private_keys(): self.notify("\nEthereum wallets:") for key, value in Security.private_keys().items(): self.notify(f"Public address: {key}\nPrivate Key: {value.hex()}") self.app.change_prompt(prompt=">>> ") self.app.hide_input = False self.placeholder_mode = False
def test_update_secure_config(self): password = "******" secrets_manager = ETHKeyFileSecretManger(password) store_password_verification(secrets_manager) Security.login(secrets_manager) binance_config = ClientConfigAdapter( BinanceConfigMap(binance_api_key=self.api_key, binance_api_secret=self.api_secret) ) self.async_run_with_timeout(Security.wait_til_decryption_done()) Security.update_secure_config(binance_config) self.reset_security() Security.login(secrets_manager) self.async_run_with_timeout(Security.wait_til_decryption_done(), timeout=2) binance_loaded_config = Security.decrypted_value(binance_config.connector) self.assertEqual(binance_config, binance_loaded_config) binance_config.binance_api_key = "someOtherApiKey" Security.update_secure_config(binance_config) self.reset_security() Security.login(secrets_manager) self.async_run_with_timeout(Security.wait_til_decryption_done(), timeout=2) binance_loaded_config = Security.decrypted_value(binance_config.connector) self.assertEqual(binance_config, binance_loaded_config)
async def connect_exchange( self, # type: HummingbotApplication exchange): self.app.clear_input() self.placeholder_mode = True self.app.hide_input = True if exchange == "kraken": self._notify( "Reminder: Please ensure your Kraken API Key Nonce Window is at least 10." ) exchange_configs = [ c for c in global_config_map.values() if c.key in settings.CONNECTOR_SETTINGS[exchange].config_keys and c.is_connect_key ] to_connect = True if Security.encrypted_file_exists(exchange_configs[0].key): await Security.wait_til_decryption_done() api_key_config = [ c for c in exchange_configs if "api_key" in c.key ] if api_key_config: api_key_config = api_key_config[0] api_key = Security.decrypted_value(api_key_config.key) prompt = f"Would you like to replace your existing {exchange} API key {api_key} (Yes/No)? >>> " else: prompt = f"Would you like to replace your existing {exchange_configs[0].key} (Yes/No)? >>> " answer = await self.app.prompt(prompt=prompt) if self.app.to_stop_config: self.app.to_stop_config = False return if answer.lower() not in ("yes", "y"): to_connect = False if to_connect: for config in exchange_configs: await self.prompt_a_config(config) if self.app.to_stop_config: self.app.to_stop_config = False return Security.update_secure_config(config.key, config.value) api_keys = await Security.api_keys(exchange) network_timeout = float( global_config_map["other_commands_timeout"].value) try: err_msg = await asyncio.wait_for( UserBalances.instance().add_exchange(exchange, **api_keys), network_timeout) except asyncio.TimeoutError: self._notify( "\nA network error prevented the connection to complete. See logs for more details." ) self.placeholder_mode = False self.app.hide_input = False self.app.change_prompt(prompt=">>> ") raise if err_msg is None: self._notify(f"\nYou are now connected to {exchange}.") else: self._notify(f"\nError: {err_msg}") self.placeholder_mode = False self.app.hide_input = False self.app.change_prompt(prompt=">>> ")
async def status_check_all(self, # type: HummingbotApplication notify_success=True) -> bool: if self.strategy is not None: return self.strategy_status() # Preliminary checks. self._notify("\nPreliminary checks:") if self.strategy_name is None or self.strategy_file_name is None: self._notify(' - Strategy check: Please import or create a strategy.') return False if not Security.is_decryption_done(): self._notify(' - Security check: Encrypted files are being processed. Please wait and try again later.') return False invalid_conns = await self.validate_required_connections() if invalid_conns: self._notify(' - Exchange check: Invalid connections:') for ex, err_msg in invalid_conns.items(): self._notify(f" {ex}: {err_msg}") elif notify_success: self._notify(' - Exchange check: All connections confirmed.') missing_configs = self.missing_configurations() if missing_configs: self._notify(" - Strategy check: Incomplete strategy configuration. The following values are missing.") for config in missing_configs: self._notify(f" {config.key}") elif notify_success: self._notify(' - Strategy check: All required parameters confirmed.') if invalid_conns or missing_configs: return False if self.wallet is not None: # Only check node url when a wallet has been initialized eth_node_valid = check_web3(global_config_map.get("ethereum_rpc_url").value) if not eth_node_valid: self._notify(' - Node check: Bad ethereum rpc url. ' 'Please re-configure by entering "config ethereum_rpc_url"') return False elif notify_success: self._notify(" - Node check: Ethereum node running and current.") if self.wallet.network_status is NetworkStatus.CONNECTED: if self._trading_required: has_minimum_eth = self.wallet.get_balance("ETH") > 0.01 if not has_minimum_eth: self._notify(" - ETH wallet check: Not enough ETH in wallet. " "A small amount of Ether is required for sending transactions on " "Decentralized Exchanges") return False elif notify_success: self._notify(" - ETH wallet check: Minimum ETH requirement satisfied") else: self._notify(" - ETH wallet check: ETH wallet is not connected.") loading_markets: List[MarketBase] = [] for market in self.markets.values(): if not market.ready: loading_markets.append(market) if len(loading_markets) > 0: self._notify(f" - Exchange connectors check: Waiting for exchange connectors " + ",".join([m.name.capitalize() for m in loading_markets]) + f" to get ready for trading. \n" f" Please keep the bot running and try to start again in a few minutes. \n") for market in loading_markets: market_status_df = pd.DataFrame(data=market.status_dict.items(), columns=["description", "status"]) self._notify( f" - {market.display_name.capitalize()} connector status:\n" + "\n".join([" " + line for line in market_status_df.to_string(index=False,).split("\n")]) + "\n" ) return False elif not all([market.network_status is NetworkStatus.CONNECTED for market in self.markets.values()]): offline_markets: List[str] = [ market_name for market_name, market in self.markets.items() if market.network_status is not NetworkStatus.CONNECTED ] for offline_market in offline_markets: self._notify(f" - Exchange connector check: {offline_market} is currently offline.") return False self.application_warning() self._notify(f" - All checks: Confirmed.") return True
async def status_check_all(self, # type: HummingbotApplication notify_success=True, live=False) -> bool: if self.strategy is not None: if live: await self.stop_live_update() self.app.live_updates = True while self.app.live_updates and self.strategy: script_status = '\n Status from script would not appear here. ' \ 'Simply run the status command without "--live" to see script status.' await self.cls_display_delay( await self.strategy_status(live=True) + script_status + "\n\n Press escape key to stop update.", 1 ) self._notify("Stopped live status display update.") else: self._notify(await self.strategy_status()) return True # Preliminary checks. self._notify("\nPreliminary checks:") if self.strategy_name is None or self.strategy_file_name is None: self._notify(' - Strategy check: Please import or create a strategy.') return False if not Security.is_decryption_done(): self._notify(' - Security check: Encrypted files are being processed. Please wait and try again later.') return False invalid_conns = await self.validate_required_connections() if invalid_conns: self._notify(' - Exchange check: Invalid connections:') for ex, err_msg in invalid_conns.items(): self._notify(f" {ex}: {err_msg}") elif notify_success: self._notify(' - Exchange check: All connections confirmed.') missing_configs = self.missing_configurations() if missing_configs: self._notify(" - Strategy check: Incomplete strategy configuration. The following values are missing.") for config in missing_configs: self._notify(f" {config.key}") elif notify_success: self._notify(' - Strategy check: All required parameters confirmed.') if invalid_conns or missing_configs: return False if self.wallet is not None: # Only check node url when a wallet has been initialized eth_node_valid = check_web3(global_config_map.get("ethereum_rpc_url").value) if not eth_node_valid: self._notify(' - Node check: Bad ethereum rpc url. ' 'Please re-configure by entering "config ethereum_rpc_url"') return False elif notify_success: self._notify(" - Node check: Ethereum node running and current.") if self.wallet.network_status is NetworkStatus.CONNECTED: if self._trading_required: has_minimum_eth = self.wallet.get_balance("ETH") > 0.01 if not has_minimum_eth: self._notify(" - ETH wallet check: Not enough ETH in wallet. " "A small amount of Ether is required for sending transactions on " "Decentralized Exchanges") return False elif notify_success: self._notify(" - ETH wallet check: Minimum ETH requirement satisfied") else: self._notify(" - ETH wallet check: ETH wallet is not connected.") loading_markets: List[ConnectorBase] = [] for market in self.markets.values(): if not market.ready: loading_markets.append(market) if len(loading_markets) > 0: self._notify(" - Connectors check: Waiting for connectors " + ",".join([m.name.capitalize() for m in loading_markets]) + " to get ready for trading. \n" " Please keep the bot running and try to start again in a few minutes. \n") for market in loading_markets: market_status_df = pd.DataFrame(data=market.status_dict.items(), columns=["description", "status"]) self._notify( f" - {market.display_name.capitalize()} connector status:\n" + "\n".join([" " + line for line in market_status_df.to_string(index=False,).split("\n")]) + "\n" ) return False elif not all([market.network_status is NetworkStatus.CONNECTED for market in self.markets.values()]): offline_markets: List[str] = [ market_name for market_name, market in self.markets.items() if market.network_status is not NetworkStatus.CONNECTED ] for offline_market in offline_markets: self._notify(f" - Connector check: {offline_market} is currently offline.") return False # Paper trade mode is currently not available for connectors other than exchanges. # Todo: This check is hard coded at the moment, when we get a clearer direction on how we should handle this, # this section will need updating. if global_config_map.get("paper_trade_enabled").value: if "balancer" in required_exchanges and \ str(global_config_map.get("ethereum_chain_name").value).lower() != "kovan": self._notify("Error: Paper trade mode is not available on balancer at the moment.") return False if "binance_perpetual" in required_exchanges: self._notify("Error: Paper trade mode is not available on binance_perpetual at the moment.") return False self.application_warning() self._notify(" - All checks: Confirmed.") return True
async def status_check_all( self, # type: HummingbotApplication notify_success=True, live=False) -> bool: if self.strategy is not None: if live: await self.stop_live_update() self.app.live_updates = True while self.app.live_updates and self.strategy: script_status = '\n Status from script would not appear here. ' \ 'Simply run the status command without "--live" to see script status.' await self.cls_display_delay( await self.strategy_status(live=True) + script_status + "\n\n Press escape key to stop update.", 1) self.app.live_updates = False self._notify("Stopped live status display update.") else: self._notify(await self.strategy_status()) return True # Preliminary checks. self._notify("\nPreliminary checks:") if self.strategy_name is None or self.strategy_file_name is None: self._notify( ' - Strategy check: Please import or create a strategy.') return False if not Security.is_decryption_done(): self._notify( ' - Security check: Encrypted files are being processed. Please wait and try again later.' ) return False network_timeout = float( global_config_map["other_commands_timeout"].value) try: invalid_conns = await asyncio.wait_for( self.validate_required_connections(), network_timeout) except asyncio.TimeoutError: self._notify( "\nA network error prevented the connection check to complete. See logs for more details." ) raise if invalid_conns: self._notify(' - Exchange check: Invalid connections:') for ex, err_msg in invalid_conns.items(): self._notify(f" {ex}: {err_msg}") elif notify_success: self._notify(' - Exchange check: All connections confirmed.') missing_configs = self.missing_configurations() if missing_configs: self._notify( " - Strategy check: Incomplete strategy configuration. The following values are missing." ) for config in missing_configs: self._notify(f" {config.key}") elif notify_success: self._notify( ' - Strategy check: All required parameters confirmed.') if invalid_conns or missing_configs: return False loading_markets: List[ConnectorBase] = [] for market in self.markets.values(): if not market.ready: loading_markets.append(market) if len(loading_markets) > 0: self._notify( " - Connectors check: Waiting for connectors " + ",".join([m.name.capitalize() for m in loading_markets]) + " to get ready for trading. \n" " Please keep the bot running and try to start again in a few minutes. \n" ) for market in loading_markets: market_status_df = pd.DataFrame( data=market.status_dict.items(), columns=["description", "status"]) self._notify( f" - {market.display_name.capitalize()} connector status:\n" + "\n".join([ " " + line for line in market_status_df.to_string( index=False, ).split("\n") ]) + "\n") return False elif not all([ market.network_status is NetworkStatus.CONNECTED for market in self.markets.values() ]): offline_markets: List[str] = [ market_name for market_name, market in self.markets.items() if market.network_status is not NetworkStatus.CONNECTED ] for offline_market in offline_markets: self._notify( f" - Connector check: {offline_market} is currently offline." ) return False self.application_warning() self._notify(" - All checks: Confirmed.") return True
def _initialize_markets(self, market_names: List[Tuple[str, List[str]]]): # aggregate trading_pairs if there are duplicate markets for market_name, trading_pairs in market_names: if market_name not in self.market_trading_pairs_map: self.market_trading_pairs_map[market_name] = [] for hb_trading_pair in trading_pairs: self.market_trading_pairs_map[market_name].append( hb_trading_pair) for connector_name, trading_pairs in self.market_trading_pairs_map.items( ): conn_setting = CONNECTOR_SETTINGS[connector_name] if global_config_map.get( "paper_trade_enabled" ).value and conn_setting.type == ConnectorType.Exchange: try: connector = create_paper_trade_market( connector_name, trading_pairs) except Exception: raise paper_trade_account_balance = global_config_map.get( "paper_trade_account_balance").value for asset, balance in paper_trade_account_balance.items(): connector.set_balance(asset, balance) else: Security.update_config_map(global_config_map) keys = { key: config.value for key, config in global_config_map.items() if key in conn_setting.config_keys } init_params = conn_setting.conn_init_parameters(keys) init_params.update(trading_pairs=trading_pairs, trading_required=self._trading_required) if conn_setting.use_ethereum_wallet: ethereum_rpc_url = global_config_map.get( "ethereum_rpc_url").value # Todo: Hard coded this execption for now until we figure out how to handle all ethereum connectors. if connector_name in [ "balancer", "uniswap", "uniswap_v3", "perpetual_finance" ]: private_key = get_eth_wallet_private_key() init_params.update(wallet_private_key=private_key, ethereum_rpc_url=ethereum_rpc_url) else: assert self.wallet is not None init_params.update(wallet=self.wallet, ethereum_rpc_url=ethereum_rpc_url) connector_class = get_connector_class(connector_name) connector = connector_class(**init_params) self.markets[connector_name] = connector self.markets_recorder = MarketsRecorder( self.trade_fill_db, list(self.markets.values()), self.strategy_file_name, self.strategy_name, ) self.markets_recorder.start()
async def update_all_secure_configs(self # type: HummingbotApplication ): await Security.wait_til_decryption_done() Security.update_config_map(global_config_map) if self.strategy_config_map is not None: Security.update_config_map(self.strategy_config_map)
def load_secure_values(config_map): for key, config in config_map.items(): if config.is_secure: config.value = Security.decrypted_value(key)
async def quick_start(args): strategy = args.strategy config_file_name = args.config_file_name wallet = args.wallet password = args.config_password if args.auto_set_permissions is not None: autofix_permissions(args.auto_set_permissions) if password is not None and not Security.login(password): logging.getLogger().error("Invalid password.") return await Security.wait_til_decryption_done() await create_yml_files() init_logging("hummingbot_logs.yml") read_system_configs_from_yml() hb = HummingbotApplication.main_application() # Todo: validate strategy and config_file_name before assinging if config_file_name is not None and strategy is not None: hb.strategy_name = strategy hb.strategy_file_name = config_file_name update_strategy_config_map_from_file( os.path.join(CONF_FILE_PATH, config_file_name)) # To ensure quickstart runs with the default value of False for kill_switch_enabled if not present if not global_config_map.get("kill_switch_enabled"): global_config_map.get("kill_switch_enabled").value = False if wallet and password: global_config_map.get("ethereum_wallet").value = wallet if hb.strategy_name and hb.strategy_file_name: if not all_configs_complete(hb.strategy_name): hb.status() with patch_stdout(log_field=hb.app.log_field): dev_mode = check_dev_mode() if dev_mode: hb.app.log( "Running from dev branches. Full remote logging will be enabled." ) log_level = global_config_map.get("log_level").value init_logging("hummingbot_logs.yml", override_log_level=log_level, dev_mode=dev_mode) if hb.strategy_file_name is not None and hb.strategy_name is not None: await write_config_to_yml(hb.strategy_name, hb.strategy_file_name) hb.start(log_level) tasks: List[Coroutine] = [hb.run()] if global_config_map.get("debug_console").value: management_port: int = detect_available_port(8211) tasks.append( start_management_console(locals(), host="localhost", port=management_port)) await safe_gather(*tasks)