From 2ebce3b4dcf161bf1c4ca0ef223fea21b6d69280 Mon Sep 17 00:00:00 2001 From: Soil King <157099073+soilking@users.noreply.github.com> Date: Thu, 27 Jun 2024 17:56:10 -0700 Subject: [PATCH 01/17] rename subgraph fields --- bots/util.py | 6 +++--- data_access/graphs.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bots/util.py b/bots/util.py index d2e176f..ae7475f 100644 --- a/bots/util.py +++ b/bots/util.py @@ -574,9 +574,9 @@ def period_string(self): per_well_str += "\n- šŸŒ± " if well["id"] == BEAN_ETH_WELL_ADDR.lower() else "\nšŸ’¦ " per_well_str += f'{TOKEN_SYMBOL_MAP.get(well["id"])} Liquidity: ${round_num_auto(float(well["dailySnapshots"][0]["totalLiquidityUSD"]), sig_fig_min=2, abbreviate=True)}' total_liquidity += float(well["dailySnapshots"][0]["totalLiquidityUSD"]) - daily_volume += float(well["dailySnapshots"][0]["deltaVolumeUSD"]) + daily_volume += float(well["dailySnapshots"][0]["deltaTradeVolumeUSD"]) for snapshot in well["dailySnapshots"]: - weekly_volume += float(snapshot["deltaVolumeUSD"]) + weekly_volume += float(snapshot["deltaTradeVolumeUSD"]) ret_str += f"\nšŸŒŠ Total Liquidity: ${round_num_auto(total_liquidity, sig_fig_min=2, abbreviate=True)}" ret_str += ( @@ -2176,7 +2176,7 @@ def _monitor_method(self): for well in wells: if well["id"].lower() == BEAN_ETH_WELL_ADDR.lower(): bean_eth_liquidity += float(well["totalLiquidityUSD"]) - bean_eth_volume += float(well["cumulativeVolumeUSD"]) + bean_eth_volume += float(well["cumulativeTradeVolumeUSD"]) if bean_eth_liquidity == 0: logging.warning( diff --git a/data_access/graphs.py b/data_access/graphs.py index e8895f1..b9ab9e0 100644 --- a/data_access/graphs.py +++ b/data_access/graphs.py @@ -387,7 +387,7 @@ def get_latest_well_snapshots(self, num_snapshots): symbol dailySnapshots(first: {num_snapshots}, orderBy: day, orderDirection: desc) {{ totalLiquidityUSD - deltaVolumeUSD + deltaTradeVolumeUSD }} }} }} @@ -401,7 +401,7 @@ def get_wells_stats(self): query {{ wells(orderBy: totalLiquidityUSD, orderDirection: desc, where: {{totalLiquidityUSD_gt: 1000}}) {{ id - cumulativeVolumeUSD + cumulativeTradeVolumeUSD totalLiquidityUSD }} }} From 42a0945e0412edf9332c3029b0ade2a174413dc8 Mon Sep 17 00:00:00 2001 From: Soil King <157099073+soilking@users.noreply.github.com> Date: Fri, 28 Jun 2024 11:51:36 -0700 Subject: [PATCH 02/17] Add dev run command with environment vars --- .env.example | 2 ++ .gitignore | 1 + README.md | 2 ++ bots/telegram_basin_bot.py | 3 +++ dev.sh | 5 +++++ 5 files changed, 13 insertions(+) create mode 100644 .env.example create mode 100755 dev.sh diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..b2de588 --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +ALCHEMY_ETH_API_KEY=your_alchemy_eth_api_key +DISCORD_BOT_TOKEN=your_discord_bot_token \ No newline at end of file diff --git a/.gitignore b/.gitignore index a838fe3..052acb0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ __pycache__ *.log .DS_Store +.env.dev \ No newline at end of file diff --git a/README.md b/README.md index 80be030..ffb7a58 100644 --- a/README.md +++ b/README.md @@ -14,3 +14,5 @@ Environment variables necessary: - `SUBGRAPH_API_KEY` - `DISCORD_BOT_TOKEN` (`DISCORD_BOT_TOKEN_PROD` for prod application) - `TELE_BOT_KEY` (`TELEGRAM_BOT_TOKEN_PROD` for prod application) + +Create an `env.dev` file using the provided example and place your varaibles there. Then, execute `./dev.sh `. For example, to run the main set of bots, execute `./dev.sh bots.discord_bot`. \ No newline at end of file diff --git a/bots/telegram_basin_bot.py b/bots/telegram_basin_bot.py index 958bc13..30bbeb4 100644 --- a/bots/telegram_basin_bot.py +++ b/bots/telegram_basin_bot.py @@ -33,6 +33,9 @@ def __init__(self, token, prod=False): ) self.well_monitor_bean_eth.start() + # TODO + # self.well_monitor_misc = + def send_msg(self, msg): # Ignore empty messages. if not msg: diff --git a/dev.sh b/dev.sh new file mode 100755 index 0000000..1037575 --- /dev/null +++ b/dev.sh @@ -0,0 +1,5 @@ +# Load environment variables from .env.dev file +export $(grep -v '^#' .env.dev | xargs) + +# Start the requested bot +python3.8 -m $1 \ No newline at end of file From f4d5b5d141f6f011e3e79f17f4da0c731f9491b9 Mon Sep 17 00:00:00 2001 From: Soil King <157099073+soilking@users.noreply.github.com> Date: Fri, 28 Jun 2024 11:57:13 -0700 Subject: [PATCH 03/17] update readme/example for DISCORD_BASIN_BOT_TOKEN --- .env.example | 3 ++- README.md | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.env.example b/.env.example index b2de588..3ddf43c 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,3 @@ ALCHEMY_ETH_API_KEY=your_alchemy_eth_api_key -DISCORD_BOT_TOKEN=your_discord_bot_token \ No newline at end of file +DISCORD_BOT_TOKEN=your_discord_bot_token +DISCORD_BASIN_BOT_TOKEN=your_discord_basin_bot_token \ No newline at end of file diff --git a/README.md b/README.md index ffb7a58..5992d68 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,9 @@ Included in this repo is a set of bots that disseminate information to Beanstalk ### Running locally To run the bots locally you will need to set several environment variables with your own keys. Environment variables necessary: -- `ETH_CHAIN_API_KEY` -- `SUBGRAPH_API_KEY` +- `ALCHEMY_ETH_API_KEY` - `DISCORD_BOT_TOKEN` (`DISCORD_BOT_TOKEN_PROD` for prod application) -- `TELE_BOT_KEY` (`TELEGRAM_BOT_TOKEN_PROD` for prod application) +- `DISCORD_BASIN_BOT_TOKEN` (`DISCORD_BASIN_BOT_TOKEN_PROD` for prod application) +- (`TELEGRAM_BOT_TOKEN_PROD` for prod application) Create an `env.dev` file using the provided example and place your varaibles there. Then, execute `./dev.sh `. For example, to run the main set of bots, execute `./dev.sh bots.discord_bot`. \ No newline at end of file From a4679aebd38cc10fc6a64a9bdcfc546809d17efb Mon Sep 17 00:00:00 2001 From: Soil King <157099073+soilking@users.noreply.github.com> Date: Fri, 28 Jun 2024 16:11:38 -0700 Subject: [PATCH 04/17] BoreWell message --- README.md | 2 ++ bots/discord_basin_bot.py | 6 +++++ bots/util.py | 51 ++++++++++++++++++++++++++++++++++++--- data_access/eth_chain.py | 12 +++++++++ 4 files changed, 67 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5992d68..19b6c7b 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,8 @@ Included in this repo is a set of bots that disseminate information to Beanstalk - **Contract Bot** - sends a message detailing each interaction with the Beanstalk contact ### Running locally +First, install the necessary requirements using `pip3 install -r beanstalk-py/requirements.txt`. + To run the bots locally you will need to set several environment variables with your own keys. Environment variables necessary: - `ALCHEMY_ETH_API_KEY` diff --git a/bots/discord_basin_bot.py b/bots/discord_basin_bot.py index 841c499..59aa3ef 100644 --- a/bots/discord_basin_bot.py +++ b/bots/discord_basin_bot.py @@ -52,6 +52,12 @@ def __init__(self, prod=False): ) self.period_monitor.start() + # TODO: change msg function when the real channel gets created + self.well_monitor_misc = util.MiscWellsMonitor( + self.send_msg_report, prod=prod, dry_run=True + ) + self.well_monitor_misc.start() + self.well_monitor_bean_eth = util.WellMonitor( self.send_msg_bean_eth, BEAN_ETH_WELL_ADDR, prod=prod, dry_run=False ) diff --git a/bots/util.py b/bots/util.py index ae7475f..7314ba0 100644 --- a/bots/util.py +++ b/bots/util.py @@ -511,9 +511,6 @@ class BasinPeriodicMonitor(Monitor): def __init__(self, message_function, prod=False, dry_run=False): super().__init__(f"basin", message_function, POOL_CHECK_RATE, prod=prod, dry_run=dry_run) - self.pool_type = EventClientType.AQUIFER - self._eth_event_client = EthEventsClient(self.pool_type, AQUIFER_ADDR) - self.well_client = WellClient(BEAN_ETH_WELL_ADDR) self.update_period = 60 * 60 * 24 self.update_ref_time = int( # 9:05am PST/12:05pm EST. Subgraph takes daily snapshot in tandem with the sunrise, @@ -603,7 +600,53 @@ def get_well_name(bore_well_log): name += ":" name += symbol +class MiscWellsMonitor(Monitor): + def __init__(self, message_function, prod=False, dry_run=False): + super().__init__("wells", message_function, POOL_CHECK_RATE, prod=prod, dry_run=dry_run) + self._eth_aquifer = EthEventsClient(EventClientType.AQUIFER, AQUIFER_ADDR) + + def _monitor_method(self): + last_check_time = 0 + while self._thread_active: + if time.time() < last_check_time + POOL_CHECK_RATE: + time.sleep(0.5) + continue + last_check_time = time.time() + for txn_pair in self._eth_aquifer.get_new_logs(dry_run=self._dry_run): + self._handle_txn_logs(txn_pair.txn_hash, txn_pair.logs) + + def _handle_txn_logs(self, txn_hash, event_logs): + for event_log in event_logs: + event_str = self.any_event_str(event_log) + if event_str: + self.message_function(event_str) + + def any_event_str(self, event_log): + event_str = "" + + if event_log.event == "BoreWell": + well = event_log.args.get("well") + tokens = event_log.args.get("tokens") + + erc20_info_0 = get_erc20_info(tokens[0]) + erc20_info_1 = get_erc20_info(tokens[1]) + + # TODO: need to not include the emojis on telegram (or provide an alternative) + + def erc20_linkstr(info): + result = f"[{info.symbol}](https://etherscan.io/address/{info.addr.lower()})" + if info.symbol == "BEAN": + result = '<:bean:1256384062340464750> ' + result + return result + + event_str = ( + f"New Well created - {erc20_linkstr(erc20_info_0)}/{erc20_linkstr(erc20_info_1)}" + f"\n<:basin:1256383927610769478> https://basin.exchange/#/wells/{well.lower()}" + ) + + return event_str +# Monitors a specific Well. # NOTE arguments for doing 1 monitor for all wells and 1 monitor per well. In first pass wells will each get their # own discord channel, which will require human intervention in this code anyway, so going to go for 1 channel # per well for now. @@ -618,7 +661,7 @@ class WellMonitor(Monitor): """ def __init__(self, message_function, address, bean_reporting=False, prod=False, dry_run=False): - super().__init__(f"wells", message_function, POOL_CHECK_RATE, prod=prod, dry_run=dry_run) + super().__init__(f"specific well", message_function, POOL_CHECK_RATE, prod=prod, dry_run=dry_run) self.pool_type = EventClientType.WELL self._eth_event_client = EthEventsClient(self.pool_type, address) self.well_client = WellClient(address) diff --git a/data_access/eth_chain.py b/data_access/eth_chain.py index 71e657b..cb4f246 100644 --- a/data_access/eth_chain.py +++ b/data_access/eth_chain.py @@ -2632,6 +2632,18 @@ def get_test_entries(): } ), ### + + # BoreWell + AttributeDict( + { + "transactionHash": HexBytes( + "0x2a05799ce2d8a9ed710fc0e52242c1abaf26c994bb3f57f1bd20af64c12874b4" + ), + "topics": [ + HexBytes("0xff64a5823907c85a1e7c0400576024f76bd1640c74350033bd0d689f793202f2") + ] + } + ) ] return entries From 08fb4917037555cc56d0dfde5a7b836caf7cdbf6 Mon Sep 17 00:00:00 2001 From: Soil King <157099073+soilking@users.noreply.github.com> Date: Fri, 28 Jun 2024 16:20:20 -0700 Subject: [PATCH 05/17] discord emojis dont print on telegram --- bots/discord_basin_bot.py | 2 +- bots/util.py | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/bots/discord_basin_bot.py b/bots/discord_basin_bot.py index 59aa3ef..0d9588e 100644 --- a/bots/discord_basin_bot.py +++ b/bots/discord_basin_bot.py @@ -54,7 +54,7 @@ def __init__(self, prod=False): # TODO: change msg function when the real channel gets created self.well_monitor_misc = util.MiscWellsMonitor( - self.send_msg_report, prod=prod, dry_run=True + self.send_msg_report, discord=True, prod=prod, dry_run=True ) self.well_monitor_misc.start() diff --git a/bots/util.py b/bots/util.py index 7314ba0..f59511f 100644 --- a/bots/util.py +++ b/bots/util.py @@ -601,8 +601,9 @@ def get_well_name(bore_well_log): name += symbol class MiscWellsMonitor(Monitor): - def __init__(self, message_function, prod=False, dry_run=False): + def __init__(self, message_function, discord=False, prod=False, dry_run=False): super().__init__("wells", message_function, POOL_CHECK_RATE, prod=prod, dry_run=dry_run) + self._discord = discord self._eth_aquifer = EthEventsClient(EventClientType.AQUIFER, AQUIFER_ADDR) def _monitor_method(self): @@ -631,17 +632,17 @@ def any_event_str(self, event_log): erc20_info_0 = get_erc20_info(tokens[0]) erc20_info_1 = get_erc20_info(tokens[1]) - # TODO: need to not include the emojis on telegram (or provide an alternative) - def erc20_linkstr(info): result = f"[{info.symbol}](https://etherscan.io/address/{info.addr.lower()})" - if info.symbol == "BEAN": - result = '<:bean:1256384062340464750> ' + result + # Embellish with discord emojis + if self._discord: + if info.symbol == "BEAN": + result = '<:bean:1256384062340464750> ' + result return result event_str = ( - f"New Well created - {erc20_linkstr(erc20_info_0)}/{erc20_linkstr(erc20_info_1)}" - f"\n<:basin:1256383927610769478> https://basin.exchange/#/wells/{well.lower()}" + f"New Well created - {erc20_linkstr(erc20_info_0)} / {erc20_linkstr(erc20_info_1)}" + f"\n{"<:basin:1256383927610769478> " if self._discord else ""}https://basin.exchange/#/wells/{well.lower()}" ) return event_str From 65e4d00688e95e2b2c68edd51fee446e4ba51c20 Mon Sep 17 00:00:00 2001 From: Soil King <157099073+soilking@users.noreply.github.com> Date: Fri, 28 Jun 2024 17:58:03 -0700 Subject: [PATCH 06/17] address is optional to web3.eth.filter --- bots/util.py | 4 +++- data_access/eth_chain.py | 22 ++++++++++------------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/bots/util.py b/bots/util.py index f59511f..a16e78a 100644 --- a/bots/util.py +++ b/bots/util.py @@ -605,6 +605,8 @@ def __init__(self, message_function, discord=False, prod=False, dry_run=False): super().__init__("wells", message_function, POOL_CHECK_RATE, prod=prod, dry_run=dry_run) self._discord = discord self._eth_aquifer = EthEventsClient(EventClientType.AQUIFER, AQUIFER_ADDR) + # All addresses + self._eth_all_wells = EthEventsClient(EventClientType.WELL, address=None) def _monitor_method(self): last_check_time = 0 @@ -642,7 +644,7 @@ def erc20_linkstr(info): event_str = ( f"New Well created - {erc20_linkstr(erc20_info_0)} / {erc20_linkstr(erc20_info_1)}" - f"\n{"<:basin:1256383927610769478> " if self._discord else ""}https://basin.exchange/#/wells/{well.lower()}" + f"\n{'<:basin:1256383927610769478> ' if self._discord else ''}https://basin.exchange/#/wells/{well.lower()}" ) return event_str diff --git a/data_access/eth_chain.py b/data_access/eth_chain.py index cb4f246..c361400 100644 --- a/data_access/eth_chain.py +++ b/data_access/eth_chain.py @@ -1020,25 +1020,21 @@ def __init__(self, event_client_type, address=""): self._contract_addresses = [AQUIFER_ADDR] self._events_dict = AQUIFER_EVENT_MAP self._signature_list = AQUIFER_SIGNATURES_LIST - self._set_filters() elif self._event_client_type == EventClientType.WELL: self._contracts = [get_well_contract(self._web3, address)] self._contract_addresses = [address] self._events_dict = WELL_EVENT_MAP self._signature_list = WELL_SIGNATURES_LIST - self._set_filters() elif self._event_client_type == EventClientType.CURVE_BEAN_3CRV_POOL: self._contracts = [get_bean_3crv_pool_contract(self._web3)] self._contract_addresses = [CURVE_BEAN_3CRV_ADDR] self._events_dict = CURVE_POOL_EVENT_MAP self._signature_list = CURVE_POOL_SIGNATURES_LIST - self._set_filters() elif self._event_client_type == EventClientType.UNI_V3_ROOT_BEAN_POOL: self._contracts = [get_uniswap_v3_contract(UNI_V3_ROOT_BEAN_ADDR, self._web3)] self._contract_addresses = [UNI_V3_ROOT_BEAN_ADDR] self._events_dict = UNISWAP_V3_POOL_EVENT_MAP self._signature_list = UNISWAP_V3_POOL_SIGNATURES_LIST - self._set_filters() elif self._event_client_type == EventClientType.BEANSTALK: self._contracts = [ get_beanstalk_contract(self._web3), @@ -1047,25 +1043,21 @@ def __init__(self, event_client_type, address=""): self._contract_addresses = [BEANSTALK_ADDR, FERTILIZER_ADDR] self._events_dict = BEANSTALK_EVENT_MAP self._signature_list = BEANSTALK_SIGNATURES_LIST - self._set_filters() elif self._event_client_type == EventClientType.MARKET: self._contracts = [get_beanstalk_contract(self._web3)] self._contract_addresses = [BEANSTALK_ADDR] self._events_dict = MARKET_EVENT_MAP self._signature_list = MARKET_SIGNATURES_LIST - self._set_filters() elif self._event_client_type == EventClientType.BARN_RAISE: self._contracts = [get_fertilizer_contract(self._web3)] self._contract_addresses = [FERTILIZER_ADDR] self._events_dict = FERTILIZER_EVENT_MAP self._signature_list = FERTILIZER_SIGNATURES_LIST - self._set_filters() elif self._event_client_type == EventClientType.ROOT_TOKEN: self._contracts = [get_root_contract(self._web3)] self._contract_addresses = [ROOT_ADDR] self._events_dict = ROOT_EVENT_MAP self._signature_list = ROOT_SIGNATURES_LIST - self._set_filters() elif self._event_client_type == EventClientType.BETTING: self._contracts = [ get_betting_admin_contract(self._web3), @@ -1074,9 +1066,9 @@ def __init__(self, event_client_type, address=""): self._contract_addresses = [BETTING_ADMIN_ADDR, BETTING_ADDR] self._events_dict = BETTING_EVENT_MAP self._signature_list = BETTING_SIGNATURES_LIST - self._set_filters() else: raise ValueError("Unsupported event client type.") + self._set_filters() def _set_filters(self): """This is located in a method so it can be reset on the fly.""" @@ -1278,9 +1270,15 @@ def safe_create_filter(web3, address, topics, from_block, to_block): try_count = 0 while try_count < max_tries: try: - return web3.eth.filter( - {"address": address, "topics": topics, "fromBlock": from_block, "toBlock": to_block} - ) + filter_params = { + "topics": topics, + "fromBlock": from_block, + "toBlock": to_block + } + # Include the address in the filter params only if it is not None + if address: + filter_params["address"] = address + return web3.eth.filter(filter_params) except websockets.exceptions.ConnectionClosedError as e: logging.warning(e, exc_info=True) time.sleep(2) From 85a49b7369ddaf50b7d8b54ae791fcbc93972337 Mon Sep 17 00:00:00 2001 From: Soil King <157099073+soilking@users.noreply.github.com> Date: Fri, 28 Jun 2024 18:08:07 -0700 Subject: [PATCH 07/17] prevent embed link --- bots/discord_basin_bot.py | 4 ++-- bots/util.py | 30 ++++++++++++++++++------------ 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/bots/discord_basin_bot.py b/bots/discord_basin_bot.py index 0d9588e..3d887e4 100644 --- a/bots/discord_basin_bot.py +++ b/bots/discord_basin_bot.py @@ -53,10 +53,10 @@ def __init__(self, prod=False): self.period_monitor.start() # TODO: change msg function when the real channel gets created - self.well_monitor_misc = util.MiscWellsMonitor( + self.well_monitor_all = util.AllWellsMonitor( self.send_msg_report, discord=True, prod=prod, dry_run=True ) - self.well_monitor_misc.start() + self.well_monitor_all.start() self.well_monitor_bean_eth = util.WellMonitor( self.send_msg_bean_eth, BEAN_ETH_WELL_ADDR, prod=prod, dry_run=False diff --git a/bots/util.py b/bots/util.py index a16e78a..e52c377 100644 --- a/bots/util.py +++ b/bots/util.py @@ -600,7 +600,7 @@ def get_well_name(bore_well_log): name += ":" name += symbol -class MiscWellsMonitor(Monitor): +class AllWellsMonitor(Monitor): def __init__(self, message_function, discord=False, prod=False, dry_run=False): super().__init__("wells", message_function, POOL_CHECK_RATE, prod=prod, dry_run=dry_run) self._discord = discord @@ -616,15 +616,17 @@ def _monitor_method(self): continue last_check_time = time.time() for txn_pair in self._eth_aquifer.get_new_logs(dry_run=self._dry_run): - self._handle_txn_logs(txn_pair.txn_hash, txn_pair.logs) - - def _handle_txn_logs(self, txn_hash, event_logs): - for event_log in event_logs: - event_str = self.any_event_str(event_log) - if event_str: - self.message_function(event_str) - - def any_event_str(self, event_log): + for event_log in txn_pair.logs: + event_str = self.aquifer_event_str(event_log) + if event_str: + self.message_function(event_str) + for txn_pair in self._eth_all_wells.get_new_logs(dry_run=self._dry_run): + for event_log in txn_pair.logs: + event_str = self.well_event_str(event_log) + if event_str: + self.message_function(event_str) + + def aquifer_event_str(self, event_log): event_str = "" if event_log.event == "BoreWell": @@ -635,7 +637,7 @@ def any_event_str(self, event_log): erc20_info_1 = get_erc20_info(tokens[1]) def erc20_linkstr(info): - result = f"[{info.symbol}](https://etherscan.io/address/{info.addr.lower()})" + result = f"[{info.symbol}]()" # Embellish with discord emojis if self._discord: if info.symbol == "BEAN": @@ -644,10 +646,14 @@ def erc20_linkstr(info): event_str = ( f"New Well created - {erc20_linkstr(erc20_info_0)} / {erc20_linkstr(erc20_info_1)}" - f"\n{'<:basin:1256383927610769478> ' if self._discord else ''}https://basin.exchange/#/wells/{well.lower()}" + f"\n{'<:basin:1256383927610769478> ' if self._discord else ''}" ) return event_str + + def well_event_str(self, event_log): + event_str = "" + return event_str # Monitors a specific Well. # NOTE arguments for doing 1 monitor for all wells and 1 monitor per well. In first pass wells will each get their From e931ed9bd1b190248cdef2415016b00b7a6dc181 Mon Sep 17 00:00:00 2001 From: Soil King <157099073+soilking@users.noreply.github.com> Date: Fri, 28 Jun 2024 18:57:11 -0700 Subject: [PATCH 08/17] refactor well str --- bots/discord_basin_bot.py | 2 +- bots/util.py | 22 ++++++++++------------ data_access/eth_chain.py | 15 ++++++++------- 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/bots/discord_basin_bot.py b/bots/discord_basin_bot.py index 3d887e4..c8336bc 100644 --- a/bots/discord_basin_bot.py +++ b/bots/discord_basin_bot.py @@ -59,7 +59,7 @@ def __init__(self, prod=False): self.well_monitor_all.start() self.well_monitor_bean_eth = util.WellMonitor( - self.send_msg_bean_eth, BEAN_ETH_WELL_ADDR, prod=prod, dry_run=False + self.send_msg_bean_eth, BEAN_ETH_WELL_ADDR, prod=prod, dry_run=True ) self.well_monitor_bean_eth.start() diff --git a/bots/util.py b/bots/util.py index e52c377..2a888b4 100644 --- a/bots/util.py +++ b/bots/util.py @@ -648,7 +648,7 @@ def erc20_linkstr(info): f"New Well created - {erc20_linkstr(erc20_info_0)} / {erc20_linkstr(erc20_info_1)}" f"\n{'<:basin:1256383927610769478> ' if self._discord else ''}" ) - + event_str += "\n_ _" return event_str def well_event_str(self, event_log): @@ -672,7 +672,8 @@ class WellMonitor(Monitor): def __init__(self, message_function, address, bean_reporting=False, prod=False, dry_run=False): super().__init__(f"specific well", message_function, POOL_CHECK_RATE, prod=prod, dry_run=dry_run) self.pool_type = EventClientType.WELL - self._eth_event_client = EthEventsClient(self.pool_type, address) + self.pool_address = address + self._eth_event_client = EthEventsClient(self.pool_type, self.pool_address) self.well_client = WellClient(address) self.bean_client = BeanClient() self.basin_graph_client = BasinSqlClient() @@ -705,7 +706,7 @@ def _handle_txn_logs(self, txn_hash, event_logs): def any_event_str(self, event_log): bdv = value = None event_str = "" - bean_well_value = self.bean_client.well_bean_eth_bean_price() + address = event_log.get("address") # Parse possible values of interest from the event log. Not all will be populated. fromToken = event_log.args.get("fromToken") toToken = event_log.args.get("toToken") @@ -722,7 +723,6 @@ def any_event_str(self, event_log): lpAmountOut = event_log.args.get("lpAmountOut") tokens = self.well_client.tokens() - logging.info(f"well tokens: {tokens}") is_swapish = False is_lpish = False @@ -730,7 +730,6 @@ def any_event_str(self, event_log): if event_log.event == "AddLiquidity": is_lpish = True event_str += f"šŸ“„ LP added - " - lp_amount = lpAmountOut for i in range(len(tokens)): erc20_info = get_erc20_info(tokens[i]) event_str += f"{round_token(tokenAmountsIn[i], erc20_info.decimals, erc20_info.addr)} {erc20_info.symbol}" @@ -738,7 +737,7 @@ def any_event_str(self, event_log): event_str += " and" event_str += f" " bdv = token_to_float(lpAmountOut, WELL_LP_DECIMALS) * get_constant_product_well_lp_bdv( - BEAN_ETH_WELL_ADDR, web3=self._web3 + address, web3=self._web3 ) elif event_log.event == "Sync": is_lpish = True @@ -758,7 +757,7 @@ def any_event_str(self, event_log): else: bdv = token_to_float( lpAmountOut, WELL_LP_DECIMALS - ) * get_constant_product_well_lp_bdv(BEAN_ETH_WELL_ADDR, web3=self._web3) + ) * get_constant_product_well_lp_bdv(address, web3=self._web3) elif event_log.event == "RemoveLiquidity" or event_log.event == "RemoveLiquidityOneToken": is_lpish = True event_str += f"šŸ“¤ LP removed - " @@ -777,7 +776,7 @@ def any_event_str(self, event_log): event_str += f" and" event_str += f" " bdv = token_to_float(lpAmountIn, WELL_LP_DECIMALS) * get_constant_product_well_lp_bdv( - BEAN_ETH_WELL_ADDR, web3=self._web3 + address, web3=self._web3 ) elif event_log.event == "Swap": is_swapish = True @@ -806,8 +805,7 @@ def any_event_str(self, event_log): self._web3, 0 ) erc20_info_in = get_erc20_info(BEAN_ADDR) - amount_in = self.well_client.get_beans_sent(event_log.transactionHash, event_log.address, event_log.logIndex) - logging.info(f"Shift: BEAN Tokens in: {amount_in}") + amount_in = get_beans_sent(event_log.transactionHash, event_log.address, event_log.logIndex) if amount_in: bdv = bean_to_float(amount_in) amount_in_str = round_token( @@ -842,9 +840,9 @@ def any_event_str(self, event_log): if value is not None: event_str += f"({round_num(value, 0, avoid_zero=True, incl_dollar=True)})" if (is_swapish or is_lpish) and self.bean_reporting: - event_str += f"\n_{latest_pool_price_str(self.bean_client, BEAN_ETH_WELL_ADDR)}_ " + event_str += f"\n_{latest_pool_price_str(self.bean_client, address)}_ " if is_lpish and not self.bean_reporting: - event_str += f"\n_{latest_well_lp_str(self.bean_client, BEAN_ETH_WELL_ADDR)}_ " + event_str += f"\n_{latest_well_lp_str(self.bean_client, address)}_ " event_str += f"\n{value_to_emojis(value)}" event_str += f"\n" diff --git a/data_access/eth_chain.py b/data_access/eth_chain.py index c361400..4d91237 100644 --- a/data_access/eth_chain.py +++ b/data_access/eth_chain.py @@ -760,13 +760,14 @@ def tokens(self, web3=None): """Returns a list of ERC20 tokens supported by the Well.""" return call_contract_function_with_retry(self.contract.functions.tokens()) - def get_beans_sent(self, txn_hash, recipient, log_end_index): - """Return the amount (as a float) of BEAN sent in a transaction to the given recipient, prior to the provided log index""" - logs = get_erc20_transfer_logs_in_txn(BEAN_ADDR, txn_hash, recipient, log_end_index) - total_sum = 0 - for entry in logs: - total_sum += int(entry.data, 16) - return total_sum + +def get_beans_sent(txn_hash, recipient, log_end_index): + """Return the amount (as a float) of BEAN sent in a transaction to the given recipient, prior to the provided log index""" + logs = get_erc20_transfer_logs_in_txn(BEAN_ADDR, txn_hash, recipient, log_end_index) + total_sum = 0 + for entry in logs: + total_sum += int(entry.data, 16) + return total_sum def get_eth_sent(txn_hash, recipient, web3, log_end_index): From a238e9121d2b84a9b1c7ba05117ba72461b235b2 Mon Sep 17 00:00:00 2001 From: Soil King <157099073+soilking@users.noreply.github.com> Date: Mon, 1 Jul 2024 09:40:34 -0700 Subject: [PATCH 09/17] lift well_event_str method --- bots/util.py | 282 ++++++++++++++++++++++++++------------------------- 1 file changed, 143 insertions(+), 139 deletions(-) diff --git a/bots/util.py b/bots/util.py index 2a888b4..443e96b 100644 --- a/bots/util.py +++ b/bots/util.py @@ -674,7 +674,6 @@ def __init__(self, message_function, address, bean_reporting=False, prod=False, self.pool_type = EventClientType.WELL self.pool_address = address self._eth_event_client = EthEventsClient(self.pool_type, self.pool_address) - self.well_client = WellClient(address) self.bean_client = BeanClient() self.basin_graph_client = BasinSqlClient() self.bean_reporting = bean_reporting @@ -699,156 +698,161 @@ def _handle_txn_logs(self, txn_hash, event_logs): return for event_log in event_logs: - event_str = self.any_event_str(event_log) + event_str = well_event_str(event_log, self.bean_reporting, self.basin_graph_client, self.bean_client, web3=self._web3) if event_str: self.message_function(event_str) - - def any_event_str(self, event_log): - bdv = value = None - event_str = "" - address = event_log.get("address") - # Parse possible values of interest from the event log. Not all will be populated. - fromToken = event_log.args.get("fromToken") - toToken = event_log.args.get("toToken") - amountIn = event_log.args.get("amountIn") - amountOut = event_log.args.get("amountOut") - # recipient = event_log.args.get('recipient') - tokenAmountsIn = event_log.args.get("tokenAmountsIn") # int[] - lpAmountOut = event_log.args.get("lpAmountOut") # int - lpAmountIn = event_log.args.get("lpAmountIn") - tokenOut = event_log.args.get("tokenOut") - tokenAmountOut = event_log.args.get("tokenAmountOut") - tokenAmountsOut = event_log.args.get("tokenAmountsOut") - # = event_log.args.get('reserves') - lpAmountOut = event_log.args.get("lpAmountOut") - - tokens = self.well_client.tokens() - - is_swapish = False - is_lpish = False - - if event_log.event == "AddLiquidity": - is_lpish = True - event_str += f"šŸ“„ LP added - " + +def well_event_str(event_log, bean_reporting, basin_graph_client, bean_client, web3=None): + bdv = value = None + event_str = "" + address = event_log.get("address") + # Parse possible values of interest from the event log. Not all will be populated. + fromToken = event_log.args.get("fromToken") + toToken = event_log.args.get("toToken") + amountIn = event_log.args.get("amountIn") + amountOut = event_log.args.get("amountOut") + # recipient = event_log.args.get('recipient') + tokenAmountsIn = event_log.args.get("tokenAmountsIn") # int[] + lpAmountOut = event_log.args.get("lpAmountOut") # int + lpAmountIn = event_log.args.get("lpAmountIn") + tokenOut = event_log.args.get("tokenOut") + tokenAmountOut = event_log.args.get("tokenAmountOut") + tokenAmountsOut = event_log.args.get("tokenAmountsOut") + # = event_log.args.get('reserves') + lpAmountOut = event_log.args.get("lpAmountOut") + + well_client = WellClient(address) + tokens = well_client.tokens() + + is_swapish = False + is_lpish = False + + if event_log.event == "AddLiquidity": + is_lpish = True + event_str += f"šŸ“„ LP added - " + for i in range(len(tokens)): + erc20_info = get_erc20_info(tokens[i]) + event_str += f"{round_token(tokenAmountsIn[i], erc20_info.decimals, erc20_info.addr)} {erc20_info.symbol}" + if i < len(tokens) - 1: + event_str += " and" + event_str += f" " + bdv = token_to_float(lpAmountOut, WELL_LP_DECIMALS) * get_constant_product_well_lp_bdv( + address, web3=web3 + ) + elif event_log.event == "Sync": + is_lpish = True + event_str += f"šŸ“„ LP added - " + # subgraph may be down, providing no deposit data. + deposit = basin_graph_client.try_get_well_deposit_info( + event_log.transactionHash, event_log.logIndex + ) + if deposit: for i in range(len(tokens)): - erc20_info = get_erc20_info(tokens[i]) - event_str += f"{round_token(tokenAmountsIn[i], erc20_info.decimals, erc20_info.addr)} {erc20_info.symbol}" + erc20_info = get_erc20_info(deposit["tokens"][i]["id"]) + event_str += f'{round_token(deposit["reserves"][i], erc20_info.decimals, erc20_info.addr)} {erc20_info.symbol}' if i < len(tokens) - 1: event_str += " and" event_str += f" " - bdv = token_to_float(lpAmountOut, WELL_LP_DECIMALS) * get_constant_product_well_lp_bdv( - address, web3=self._web3 - ) - elif event_log.event == "Sync": - is_lpish = True - event_str += f"šŸ“„ LP added - " - # subgraph may be down, providing no deposit data. - deposit = self.basin_graph_client.try_get_well_deposit_info( - event_log.transactionHash, event_log.logIndex - ) - if deposit: - for i in range(len(tokens)): - erc20_info = get_erc20_info(deposit["tokens"][i]["id"]) - event_str += f'{round_token(deposit["reserves"][i], erc20_info.decimals, erc20_info.addr)} {erc20_info.symbol}' - if i < len(tokens) - 1: - event_str += " and" - event_str += f" " - value = float(deposit["amountUSD"]) - else: - bdv = token_to_float( - lpAmountOut, WELL_LP_DECIMALS - ) * get_constant_product_well_lp_bdv(address, web3=self._web3) - elif event_log.event == "RemoveLiquidity" or event_log.event == "RemoveLiquidityOneToken": - is_lpish = True - event_str += f"šŸ“¤ LP removed - " - for i in range(len(tokens)): - erc20_info = get_erc20_info(tokens[i]) - if event_log.event == "RemoveLiquidityOneToken": - if tokenOut == tokens[i]: - out_amount = tokenAmountOut - else: - out_amount = 0 + value = float(deposit["amountUSD"]) + else: + bdv = token_to_float( + lpAmountOut, WELL_LP_DECIMALS + ) * get_constant_product_well_lp_bdv(address, web3=web3) + elif event_log.event == "RemoveLiquidity" or event_log.event == "RemoveLiquidityOneToken": + is_lpish = True + event_str += f"šŸ“¤ LP removed - " + for i in range(len(tokens)): + erc20_info = get_erc20_info(tokens[i]) + if event_log.event == "RemoveLiquidityOneToken": + if tokenOut == tokens[i]: + out_amount = tokenAmountOut else: - out_amount = tokenAmountsOut[i] - event_str += f"{round_token(out_amount, erc20_info.decimals, erc20_info.addr)} {erc20_info.symbol}" - - if i < len(tokens) - 1: - event_str += f" and" - event_str += f" " - bdv = token_to_float(lpAmountIn, WELL_LP_DECIMALS) * get_constant_product_well_lp_bdv( - address, web3=self._web3 - ) - elif event_log.event == "Swap": - is_swapish = True - # value = lpAmountIn * lp_value - erc20_info_in = get_erc20_info(fromToken) - erc20_info_out = get_erc20_info(toToken) - amount_in = amountIn + out_amount = 0 + else: + out_amount = tokenAmountsOut[i] + event_str += f"{round_token(out_amount, erc20_info.decimals, erc20_info.addr)} {erc20_info.symbol}" + + if i < len(tokens) - 1: + event_str += f" and" + event_str += f" " + bdv = token_to_float(lpAmountIn, WELL_LP_DECIMALS) * get_constant_product_well_lp_bdv( + address, web3=web3 + ) + elif event_log.event == "Swap": + is_swapish = True + # value = lpAmountIn * lp_value + erc20_info_in = get_erc20_info(fromToken) + erc20_info_out = get_erc20_info(toToken) + amount_in = amountIn + amount_in_str = round_token(amount_in, erc20_info_in.decimals, erc20_info_in.addr) + amount_out = amountOut + amount_out_str = round_token(amount_out, erc20_info_out.decimals, erc20_info_out.addr) + if fromToken == BEAN_ADDR: + bdv = bean_to_float(amountIn) + elif toToken == BEAN_ADDR: + bdv = bean_to_float(amountOut) + elif event_log.event == "Shift": + erc20_info_out = get_erc20_info(toToken) + + # TODO: derive erc20_info_in using self.well_client.tokens() and then get the amount + + amount_in = None + if event_log.address == BEAN_ETH_WELL_ADDR and toToken == BEAN_ADDR: + bdv = bean_to_float(amountOut) + erc20_info_in = get_erc20_info(WRAPPED_ETH) + amount_in = get_eth_sent(event_log.transactionHash, event_log.address, web3, event_log.logIndex) amount_in_str = round_token(amount_in, erc20_info_in.decimals, erc20_info_in.addr) - amount_out = amountOut - amount_out_str = round_token(amount_out, erc20_info_out.decimals, erc20_info_out.addr) - if fromToken == BEAN_ADDR: - bdv = bean_to_float(amountIn) - elif toToken == BEAN_ADDR: - bdv = bean_to_float(amountOut) - elif event_log.event == "Shift": - erc20_info_out = get_erc20_info(toToken) - - amount_in = None - if event_log.address == BEAN_ETH_WELL_ADDR and toToken == BEAN_ADDR: - bdv = bean_to_float(amountOut) - erc20_info_in = get_erc20_info(WRAPPED_ETH) - amount_in = get_eth_sent(event_log.transactionHash, event_log.address, self._web3, event_log.logIndex) - amount_in_str = round_token(amount_in, erc20_info_in.decimals, erc20_info_in.addr) - elif event_log.address == BEAN_ETH_WELL_ADDR and toToken == WRAPPED_ETH: - value = token_to_float(amountOut, erc20_info_out.decimals) * get_twa_eth_price( - self._web3, 0 + elif event_log.address == BEAN_ETH_WELL_ADDR and toToken == WRAPPED_ETH: + value = token_to_float(amountOut, erc20_info_out.decimals) * get_twa_eth_price( + web3, 0 + ) + erc20_info_in = get_erc20_info(BEAN_ADDR) + amount_in = get_beans_sent(event_log.transactionHash, event_log.address, event_log.logIndex) + if amount_in: + bdv = bean_to_float(amount_in) + amount_in_str = round_token( + amount_in, erc20_info_in.decimals, erc20_info_in.addr ) - erc20_info_in = get_erc20_info(BEAN_ADDR) - amount_in = get_beans_sent(event_log.transactionHash, event_log.address, event_log.logIndex) - if amount_in: - bdv = bean_to_float(amount_in) - amount_in_str = round_token( - amount_in, erc20_info_in.decimals, erc20_info_in.addr - ) - amount_out = amountOut - amount_out_str = round_token(amount_out, erc20_info_out.decimals, erc20_info_out.addr) - if ( - amount_in is not None and amount_in > 0 - ): # not None and not 0, then it is a pseudo swap - is_swapish = True - else: # one sided shift - event_str += f"šŸ”€ {amount_out_str} {erc20_info_out.symbol} shifted out " - else: - logging.warning(f"Unexpected event log seen in Well ({event_log.event}). Ignoring.") - return "" - - if bdv is not None: - value = bdv * self.bean_client.avg_bean_price() + amount_out = amountOut + amount_out_str = round_token(amount_out, erc20_info_out.decimals, erc20_info_out.addr) + if ( + amount_in is not None and amount_in > 0 + ): # not None and not 0, then it is a pseudo swap + is_swapish = True + else: # one sided shift + event_str += f"šŸ”€ {amount_out_str} {erc20_info_out.symbol} shifted out " + else: + logging.warning(f"Unexpected event log seen in Well ({event_log.event}). Ignoring.") + return "" - if is_swapish: - if self.bean_reporting and erc20_info_out.symbol == "BEAN": - event_str += f"šŸ“— {amount_out_str} {erc20_info_out.symbol} bought for {amount_in_str} {erc20_info_in.symbol} @ ${round_num(value/bean_to_float(amount_out), 4)} " - elif self.bean_reporting and erc20_info_in.symbol == "BEAN": - event_str += f"šŸ“• {amount_in_str} {erc20_info_in.symbol} sold for {amount_out_str} {erc20_info_out.symbol} @ ${round_num(value/bean_to_float(amount_in), 4)} " - else: - event_str += ( - f"šŸ” {amount_in_str} {erc20_info_in.symbol} swapped " - f"for {amount_out_str} {erc20_info_out.symbol} " - ) + if bdv is not None: + value = bdv * bean_client.avg_bean_price() - if value is not None: - event_str += f"({round_num(value, 0, avoid_zero=True, incl_dollar=True)})" - if (is_swapish or is_lpish) and self.bean_reporting: - event_str += f"\n_{latest_pool_price_str(self.bean_client, address)}_ " - if is_lpish and not self.bean_reporting: - event_str += f"\n_{latest_well_lp_str(self.bean_client, address)}_ " - event_str += f"\n{value_to_emojis(value)}" + if is_swapish: + if bean_reporting and erc20_info_out.symbol == "BEAN": + event_str += f"šŸ“— {amount_out_str} {erc20_info_out.symbol} bought for {amount_in_str} {erc20_info_in.symbol} @ ${round_num(value/bean_to_float(amount_out), 4)} " + elif bean_reporting and erc20_info_in.symbol == "BEAN": + event_str += f"šŸ“• {amount_in_str} {erc20_info_in.symbol} sold for {amount_out_str} {erc20_info_out.symbol} @ ${round_num(value/bean_to_float(amount_in), 4)} " + else: + event_str += ( + f"šŸ” {amount_in_str} {erc20_info_in.symbol} swapped " + f"for {amount_out_str} {erc20_info_out.symbol} " + ) - event_str += f"\n" - # Empty line that does not get stripped. - event_str += "\n_ _" - return event_str + if value is not None and value != 0: + event_str += f"({round_num(value, 0, avoid_zero=True, incl_dollar=True)})" + if (is_swapish or is_lpish) and bean_reporting: + event_str += f"\n_{latest_pool_price_str(bean_client, address)}_ " + if is_lpish and not bean_reporting: + liqudity_usd = latest_well_lp_str(bean_client, address) + if liqudity_usd != 0: + event_str += f"\n_{liqudity_usd}_ " + event_str += f"\n{value_to_emojis(value)}" + + event_str += f"\n" + # Empty line that does not get stripped. + event_str += "\n_ _" + return event_str class CurvePoolMonitor(Monitor): From 6a144a4e3a631db53c880dc438cf7bb1612541f5 Mon Sep 17 00:00:00 2001 From: Soil King <157099073+soilking@users.noreply.github.com> Date: Tue, 2 Jul 2024 11:35:03 -0700 Subject: [PATCH 10/17] generalize shift event --- bots/util.py | 33 ++++++++++++++++----------------- data_access/eth_chain.py | 6 +++--- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/bots/util.py b/bots/util.py index 443e96b..9fef3f2 100644 --- a/bots/util.py +++ b/bots/util.py @@ -792,27 +792,26 @@ def well_event_str(event_log, bean_reporting, basin_graph_client, bean_client, w elif toToken == BEAN_ADDR: bdv = bean_to_float(amountOut) elif event_log.event == "Shift": - erc20_info_out = get_erc20_info(toToken) + shift_from_token = tokens[0] if tokens[1] == toToken else tokens[1] - # TODO: derive erc20_info_in using self.well_client.tokens() and then get the amount + erc20_info_in = get_erc20_info(shift_from_token) + erc20_info_out = get_erc20_info(toToken) amount_in = None - if event_log.address == BEAN_ETH_WELL_ADDR and toToken == BEAN_ADDR: + + if shift_from_token == WRAPPED_ETH: + amount_in = get_eth_sent(event_log.transactionHash, address, web3, event_log.logIndex) + else: + amount_in = get_tokens_sent(shift_from_token, event_log.transactionHash, event_log.address, event_log.logIndex) + + if toToken == BEAN_ADDR: bdv = bean_to_float(amountOut) - erc20_info_in = get_erc20_info(WRAPPED_ETH) - amount_in = get_eth_sent(event_log.transactionHash, event_log.address, web3, event_log.logIndex) - amount_in_str = round_token(amount_in, erc20_info_in.decimals, erc20_info_in.addr) - elif event_log.address == BEAN_ETH_WELL_ADDR and toToken == WRAPPED_ETH: - value = token_to_float(amountOut, erc20_info_out.decimals) * get_twa_eth_price( - web3, 0 - ) - erc20_info_in = get_erc20_info(BEAN_ADDR) - amount_in = get_beans_sent(event_log.transactionHash, event_log.address, event_log.logIndex) - if amount_in: - bdv = bean_to_float(amount_in) - amount_in_str = round_token( - amount_in, erc20_info_in.decimals, erc20_info_in.addr - ) + elif shift_from_token == BEAN_ADDR: + bdv = bean_to_float(amount_in) + + erc20_info_in = get_erc20_info(shift_from_token) + amount_in_str = round_token(amount_in, erc20_info_in.decimals, erc20_info_in.addr) + amount_out = amountOut amount_out_str = round_token(amount_out, erc20_info_out.decimals, erc20_info_out.addr) if ( diff --git a/data_access/eth_chain.py b/data_access/eth_chain.py index 4d91237..bb841b2 100644 --- a/data_access/eth_chain.py +++ b/data_access/eth_chain.py @@ -761,9 +761,9 @@ def tokens(self, web3=None): return call_contract_function_with_retry(self.contract.functions.tokens()) -def get_beans_sent(txn_hash, recipient, log_end_index): - """Return the amount (as a float) of BEAN sent in a transaction to the given recipient, prior to the provided log index""" - logs = get_erc20_transfer_logs_in_txn(BEAN_ADDR, txn_hash, recipient, log_end_index) +def get_tokens_sent(token, txn_hash, recipient, log_end_index): + """Return the amount (as a float) of token sent in a transaction to the given recipient, prior to the provided log index""" + logs = get_erc20_transfer_logs_in_txn(token, txn_hash, recipient, log_end_index) total_sum = 0 for entry in logs: total_sum += int(entry.data, 16) From 2e1b30bd8cd49f111c7d30f9848646f44e3c1176 Mon Sep 17 00:00:00 2001 From: Soil King <157099073+soilking@users.noreply.github.com> Date: Tue, 2 Jul 2024 11:43:38 -0700 Subject: [PATCH 11/17] prevent double log --- bots/util.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/bots/util.py b/bots/util.py index 9fef3f2..fbf3871 100644 --- a/bots/util.py +++ b/bots/util.py @@ -607,6 +607,8 @@ def __init__(self, message_function, discord=False, prod=False, dry_run=False): self._eth_aquifer = EthEventsClient(EventClientType.AQUIFER, AQUIFER_ADDR) # All addresses self._eth_all_wells = EthEventsClient(EventClientType.WELL, address=None) + self.basin_graph_client = BasinSqlClient() + self.bean_client = BeanClient() def _monitor_method(self): last_check_time = 0 @@ -622,13 +624,13 @@ def _monitor_method(self): self.message_function(event_str) for txn_pair in self._eth_all_wells.get_new_logs(dry_run=self._dry_run): for event_log in txn_pair.logs: - event_str = self.well_event_str(event_log) - if event_str: - self.message_function(event_str) + # Avoids double-reporting on whitelisted wells having a dedicated channel + if event_log.get("address") not in [BEAN_ETH_WELL_ADDR]: + event_str = well_event_str(event_log, False, self.basin_graph_client, self.bean_client, web3=self._web3) + if event_str: + self.message_function(event_str) def aquifer_event_str(self, event_log): - event_str = "" - if event_log.event == "BoreWell": well = event_log.args.get("well") tokens = event_log.args.get("tokens") @@ -650,10 +652,6 @@ def erc20_linkstr(info): ) event_str += "\n_ _" return event_str - - def well_event_str(self, event_log): - event_str = "" - return event_str # Monitors a specific Well. # NOTE arguments for doing 1 monitor for all wells and 1 monitor per well. In first pass wells will each get their @@ -674,8 +672,8 @@ def __init__(self, message_function, address, bean_reporting=False, prod=False, self.pool_type = EventClientType.WELL self.pool_address = address self._eth_event_client = EthEventsClient(self.pool_type, self.pool_address) - self.bean_client = BeanClient() self.basin_graph_client = BasinSqlClient() + self.bean_client = BeanClient() self.bean_reporting = bean_reporting def _monitor_method(self): From 65443a6c4c9f7b8292fbfb5cbf4decb421972331 Mon Sep 17 00:00:00 2001 From: Soil King <157099073+soilking@users.noreply.github.com> Date: Tue, 2 Jul 2024 11:54:22 -0700 Subject: [PATCH 12/17] add new channel --- bots/discord_basin_bot.py | 16 +++++++++++++--- bots/util.py | 5 +++-- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/bots/discord_basin_bot.py b/bots/discord_basin_bot.py index c8336bc..33e853b 100644 --- a/bots/discord_basin_bot.py +++ b/bots/discord_basin_bot.py @@ -15,12 +15,14 @@ DISCORD_CHANNEL_ID_TEST = 1117767077420339220 DISCORD_CHANNEL_ID_BEAN_ETH = 1117768417861193819 DISCORD_CHANNEL_ID_DAILY = 1117768293625888868 +DISCORD_CHANNEL_ID_WELLS_OTHER = 1257633554427543633 class Channel(Enum): REPORT = 0 DAILY = 1 BEAN_ETH = 2 + WELLS_OTHER = 3 class DiscordClient(discord.ext.commands.Bot): @@ -32,11 +34,13 @@ def __init__(self, prod=False): self._chat_id_report = DISCORD_CHANNEL_ID_TEST self._chat_id_daily = DISCORD_CHANNEL_ID_DAILY self._chat_id_bean_eth = DISCORD_CHANNEL_ID_BEAN_ETH + self._chat_id_other_wells = DISCORD_CHANNEL_ID_WELLS_OTHER logging.info("Configured as a production instance.") else: self._chat_id_report = DISCORD_CHANNEL_ID_TEST self._chat_id_daily = DISCORD_CHANNEL_ID_TEST self._chat_id_bean_eth = DISCORD_CHANNEL_ID_TEST + self._chat_id_other_wells = DISCORD_CHANNEL_ID_TEST logging.info("Configured as a staging instance.") self.msg_queue = [] @@ -52,9 +56,8 @@ def __init__(self, prod=False): ) self.period_monitor.start() - # TODO: change msg function when the real channel gets created self.well_monitor_all = util.AllWellsMonitor( - self.send_msg_report, discord=True, prod=prod, dry_run=True + self.send_msg_wells_other, [BEAN_ETH_WELL_ADDR], discord=True, prod=prod, dry_run=True ) self.well_monitor_all.start() @@ -90,13 +93,18 @@ def send_msg_bean_eth(self, text): """Send a message through the Discord bot in the bean eth well channel.""" self.msg_queue.append((Channel.BEAN_ETH, text)) + def send_msg_wells_other(self, text): + """Send a message through the Discord bot in the other wells channel.""" + self.msg_queue.append((Channel.WELLS_OTHER, text)) + async def on_ready(self): self._channel_report = self.get_channel(self._chat_id_report) self._channel_daily = self.get_channel(self._chat_id_daily) self._channel_bean_eth = self.get_channel(self._chat_id_bean_eth) + self._channel_wells_other = self.get_channel(self._chat_id_other_wells) logging.info( - f"Discord channels are {self._channel_report}, {self._channel_daily}, {self._channel_bean_eth}" + f"Discord channels are {self._channel_report}, {self._channel_daily}, {self._channel_bean_eth}, {self._chat_id_other_wells}" ) # Guild IDs for all servers this bot is in. @@ -144,6 +152,8 @@ async def send_queued_messages(self): await self._channel_daily.send(msg) elif channel is Channel.BEAN_ETH: await self._channel_bean_eth.send(msg) + elif channel is Channel.WELLS_OTHER: + await self._channel_wells_other.send(msg) else: logging.error("Unknown channel seen in msg queue: {channel}") self.msg_queue = self.msg_queue[1:] diff --git a/bots/util.py b/bots/util.py index fbf3871..f8ebcdc 100644 --- a/bots/util.py +++ b/bots/util.py @@ -601,8 +601,9 @@ def get_well_name(bore_well_log): name += symbol class AllWellsMonitor(Monitor): - def __init__(self, message_function, discord=False, prod=False, dry_run=False): + def __init__(self, message_function, ignorelist, discord=False, prod=False, dry_run=False): super().__init__("wells", message_function, POOL_CHECK_RATE, prod=prod, dry_run=dry_run) + self._ignorelist = ignorelist self._discord = discord self._eth_aquifer = EthEventsClient(EventClientType.AQUIFER, AQUIFER_ADDR) # All addresses @@ -625,7 +626,7 @@ def _monitor_method(self): for txn_pair in self._eth_all_wells.get_new_logs(dry_run=self._dry_run): for event_log in txn_pair.logs: # Avoids double-reporting on whitelisted wells having a dedicated channel - if event_log.get("address") not in [BEAN_ETH_WELL_ADDR]: + if event_log.get("address") not in self._ignorelist: event_str = well_event_str(event_log, False, self.basin_graph_client, self.bean_client, web3=self._web3) if event_str: self.message_function(event_str) From 326d0525efa30dcd859ab6a98f6cee6b03f72236 Mon Sep 17 00:00:00 2001 From: Soil King <157099073+soilking@users.noreply.github.com> Date: Tue, 2 Jul 2024 11:55:11 -0700 Subject: [PATCH 13/17] disable dry_run --- bots/discord_basin_bot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bots/discord_basin_bot.py b/bots/discord_basin_bot.py index 33e853b..d848d6a 100644 --- a/bots/discord_basin_bot.py +++ b/bots/discord_basin_bot.py @@ -57,12 +57,12 @@ def __init__(self, prod=False): self.period_monitor.start() self.well_monitor_all = util.AllWellsMonitor( - self.send_msg_wells_other, [BEAN_ETH_WELL_ADDR], discord=True, prod=prod, dry_run=True + self.send_msg_wells_other, [BEAN_ETH_WELL_ADDR], discord=True, prod=prod, dry_run=False ) self.well_monitor_all.start() self.well_monitor_bean_eth = util.WellMonitor( - self.send_msg_bean_eth, BEAN_ETH_WELL_ADDR, prod=prod, dry_run=True + self.send_msg_bean_eth, BEAN_ETH_WELL_ADDR, prod=prod, dry_run=False ) self.well_monitor_bean_eth.start() From 36f50e278b2d8716388dc6e30d6fc8bdc19ede63 Mon Sep 17 00:00:00 2001 From: Soil King <157099073+soilking@users.noreply.github.com> Date: Tue, 2 Jul 2024 12:01:52 -0700 Subject: [PATCH 14/17] enable telegram monitor --- bots/discord_basin_bot.py | 10 +++++----- bots/telegram_basin_bot.py | 6 ++++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/bots/discord_basin_bot.py b/bots/discord_basin_bot.py index d848d6a..cdf05b6 100644 --- a/bots/discord_basin_bot.py +++ b/bots/discord_basin_bot.py @@ -56,16 +56,16 @@ def __init__(self, prod=False): ) self.period_monitor.start() - self.well_monitor_all = util.AllWellsMonitor( - self.send_msg_wells_other, [BEAN_ETH_WELL_ADDR], discord=True, prod=prod, dry_run=False - ) - self.well_monitor_all.start() - self.well_monitor_bean_eth = util.WellMonitor( self.send_msg_bean_eth, BEAN_ETH_WELL_ADDR, prod=prod, dry_run=False ) self.well_monitor_bean_eth.start() + self.well_monitor_all = util.AllWellsMonitor( + self.send_msg_wells_other, [BEAN_ETH_WELL_ADDR], discord=True, prod=prod, dry_run=False + ) + self.well_monitor_all.start() + # Ignore exceptions of this type and retry. Note that no logs will be generated. # Ignore base class, because we always want to reconnect. # https://discordpy.readthedocs.io/en/latest/api.html#discord.ClientUser.edit diff --git a/bots/telegram_basin_bot.py b/bots/telegram_basin_bot.py index 30bbeb4..6da08bb 100644 --- a/bots/telegram_basin_bot.py +++ b/bots/telegram_basin_bot.py @@ -33,8 +33,10 @@ def __init__(self, token, prod=False): ) self.well_monitor_bean_eth.start() - # TODO - # self.well_monitor_misc = + self.well_monitor_all = util.AllWellsMonitor( + self.send_msg, [BEAN_ETH_WELL_ADDR], discord=False, prod=prod, dry_run=False + ) + self.well_monitor_all.start() def send_msg(self, msg): # Ignore empty messages. From 599d309193d6184290d22da022c160e0139ea564 Mon Sep 17 00:00:00 2001 From: Soil King <157099073+soilking@users.noreply.github.com> Date: Tue, 2 Jul 2024 12:24:00 -0700 Subject: [PATCH 15/17] report liquidity from subgraph --- bots/util.py | 8 +++----- data_access/eth_chain.py | 12 ++++++++++++ data_access/graphs.py | 12 ++++++++++++ 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/bots/util.py b/bots/util.py index f8ebcdc..cdca1f0 100644 --- a/bots/util.py +++ b/bots/util.py @@ -842,7 +842,7 @@ def well_event_str(event_log, bean_reporting, basin_graph_client, bean_client, w if (is_swapish or is_lpish) and bean_reporting: event_str += f"\n_{latest_pool_price_str(bean_client, address)}_ " if is_lpish and not bean_reporting: - liqudity_usd = latest_well_lp_str(bean_client, address) + liqudity_usd = latest_well_lp_str(basin_graph_client, address) if liqudity_usd != 0: event_str += f"\n_{liqudity_usd}_ " event_str += f"\n{value_to_emojis(value)}" @@ -2487,10 +2487,8 @@ def latest_pool_price_str(bean_client, addr): return f"{type_str}: deltaB [{round_num(delta_b, 0)}], price [${round_num(price, 4)}]" -def latest_well_lp_str(bean_client, addr): - pool_info = bean_client.get_pool_info(addr) - # lp_price = token_to_float(pool_info['lp_usd'], BEAN_DECIMALS) - liquidity = token_to_float(pool_info["liquidity"], BEAN_DECIMALS) +def latest_well_lp_str(basin_client, addr): + liquidity = basin_client.get_well_liquidity(addr) return f"Well liquidity: ${round_num(liquidity, 0)}" diff --git a/data_access/eth_chain.py b/data_access/eth_chain.py index bb841b2..66cd9b7 100644 --- a/data_access/eth_chain.py +++ b/data_access/eth_chain.py @@ -2642,6 +2642,18 @@ def get_test_entries(): HexBytes("0xff64a5823907c85a1e7c0400576024f76bd1640c74350033bd0d689f793202f2") ] } + ), + + # Add liquidity to non whitelisted well + AttributeDict( + { + "transactionHash": HexBytes( + "0xa4b1538758fe42fc48ccf6301c9dfaeb5377da5978d31e621fd60a25204275b4" + ), + "topics": [ + HexBytes("0x91a6d8e872c9887412278189089c9936e99450551cc971309ff282f79bfef56f") + ] + } ) ] return entries diff --git a/data_access/graphs.py b/data_access/graphs.py index b9ab9e0..8ac9b63 100644 --- a/data_access/graphs.py +++ b/data_access/graphs.py @@ -408,6 +408,18 @@ def get_wells_stats(self): """ # Create gql query and execute. return execute(self._client, query_str)["wells"] + + def get_well_liquidity(self, well): + """Get the current USD liquidity for the requested Well""" + query_str = f""" + query {{ + well(id: "{well}") {{ + totalLiquidityUSD + }} + }} + """ + # Create gql query and execute. + return execute(self._client, query_str).get("well").get("totalLiquidityUSD") def try_get_well_deposit_info(self, txn_hash, log_index): """Get deposit tokens. Retry if data not available. Return {} if it does not become available. From a6e4db4dda1e906fecc1ac9fdb3baef8506c7e98 Mon Sep 17 00:00:00 2001 From: Soil King <157099073+soilking@users.noreply.github.com> Date: Tue, 2 Jul 2024 14:26:02 -0700 Subject: [PATCH 16/17] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 19b6c7b..9aede44 100644 --- a/README.md +++ b/README.md @@ -17,4 +17,4 @@ Environment variables necessary: - `DISCORD_BASIN_BOT_TOKEN` (`DISCORD_BASIN_BOT_TOKEN_PROD` for prod application) - (`TELEGRAM_BOT_TOKEN_PROD` for prod application) -Create an `env.dev` file using the provided example and place your varaibles there. Then, execute `./dev.sh `. For example, to run the main set of bots, execute `./dev.sh bots.discord_bot`. \ No newline at end of file +Create an `.env.dev` file using the provided example and place your varaibles there. Then, execute `./dev.sh `. For example, to run the main set of bots, execute `./dev.sh bots.discord_bot`. \ No newline at end of file From 6d3df1c8a510e7a92d5218b02b549073e2a2668b Mon Sep 17 00:00:00 2001 From: Soil King <157099073+soilking@users.noreply.github.com> Date: Mon, 8 Jul 2024 23:02:30 +0200 Subject: [PATCH 17/17] Move basin emoji --- bots/util.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bots/util.py b/bots/util.py index cdca1f0..b18f720 100644 --- a/bots/util.py +++ b/bots/util.py @@ -648,8 +648,8 @@ def erc20_linkstr(info): return result event_str = ( - f"New Well created - {erc20_linkstr(erc20_info_0)} / {erc20_linkstr(erc20_info_1)}" - f"\n{'<:basin:1256383927610769478> ' if self._discord else ''}" + f"{'<:basin:1256383927610769478> ' if self._discord else ''} New Well created - {erc20_linkstr(erc20_info_0)} / {erc20_linkstr(erc20_info_1)}" + f"\n" ) event_str += "\n_ _" return event_str