async def on_enter(self, message: Message): if message.text == self.loc.BUTTON_SM_BACK_MM: await self.go_back(message) else: await StakeStates.MAIN_MENU.set() address = message.text.strip() if address: if MyStakeAddress.is_good_address(address): self.add_address(address, BNB_CHAIN) else: await message.answer(code(self.loc.TEXT_INVALID_ADDRESS), disable_notification=True) await self.display_addresses(message) msg = self.loc.TEXT_SELECT_ADDRESS_ABOVE if self.my_addresses else '' msg += self.loc.TEXT_SELECT_ADDRESS_SEND_ME await message.answer(msg, reply_markup=kbd([self.loc.BUTTON_SM_BACK_MM]), disable_notification=True)
async def display_error(message: Message, e: Exception): tag = secrets.token_hex(8) logger.exception(f"TAG: {tag}") await message.answer( code(f"Sorry! An error occurred: {str(e)}. Incident ID is {tag}.") + f"\nFeedback/support: {CREATOR_TG}.")
class RussianLocalization(BaseLocalization): # ---- WELCOME ---- def help_message(self): return ( f"Этот бот уведомляет о крупных движениях с сети {link(self.THORCHAIN_LINK, 'THORChain')}.\n" f"Команды:\n" f"/help – эта помощь\n" f"/start – запуск и перезапуск бота\n" f"/lang – изменить язык\n" f"/cap – текущий кап для стейка в пулах Chaosnet\n" f"/price – текущая цена {self.R}.\n" f"<b>⚠️ Бот теперь уведомляет только в канале @thorchain_alert!</b>\n" f"🤗 Отзывы и поддержка: {CREATOR_TG}.") def welcome_message(self, info: ThorInfo): return ( f"Привет! Здесь ты можешь найти метрики THORChain и узнать результаты предоставления ликвидности в пулы.\n" f"Цена {self.R} сейчас <code>{info.price:.3f} BUSD</code>.\n" f"<b>⚠️ Бот теперь уведомляет только в канале @thorchain_alert!</b>\n" f"Набери /help, чтобы видеть список команд.\n" f"🤗 Отзывы и поддержка: {CREATOR_TG}.") def unknown_command(self): return ("🙄 Извини, я не знаю такой команды.\n" "Нажми на /help, чтобы увидеть доступные команды.") # ----- MAIN MENU ------ BUTTON_MM_MY_ADDRESS = '🏦 Мои адреса' BUTTON_MM_METRICS = '📐 Метрики' BUTTON_MM_SETTINGS = f'⚙️ Настройки' BUTTON_MM_MAKE_AVATAR = f'🦹️️ Сделай аву' # ------ STAKE INFO ----- BUTTON_SM_ADD_ADDRESS = '➕ Добавить новый адрес' BUTTON_BACK = '🔙 Назад' BUTTON_SM_BACK_TO_LIST = '🔙 Назад к адресам' BUTTON_SM_BACK_MM = '🔙 Главное меню' BUTTON_SM_SUMMARY = '💲 Сводка' BUTTON_VIEW_RUNESTAKEINFO = '🌎 Открыть на runestake.info' BUTTON_VIEW_VALUE_ON = 'Скрыть деньги: НЕТ' BUTTON_VIEW_VALUE_OFF = 'Скрыть деньги: ДА' BUTTON_REMOVE_THIS_ADDRESS = '❌ Удалить этот адресс' TEXT_NO_ADDRESSES = "🔆 Вы еще не добавили никаких адресов. Пришлите мне адрес, чтобы добавить." TEXT_YOUR_ADDRESSES = '🔆 Вы добавили следующие адреса:' TEXT_INVALID_ADDRESS = code('⛔️ Ошибка в формате адреса!') TEXT_SELECT_ADDRESS_ABOVE = 'Выбери адрес выше ☝️ ' TEXT_SELECT_ADDRESS_SEND_ME = 'Если хотите добавить адрес, пришлите его мне 👇' TEXT_LP_NO_POOLS_FOR_THIS_ADDRESS = '📪 <b>На этом адресе нет пулов ликвидности.</b> ' \ 'Выберите другой адрес или добавьте новый.' TEXT_LP_IMG_CAPTION = f'Сгенерировано: {link(BaseLocalization.START_ME, "@thorchain_monitoring_bot")}' LP_PIC_POOL = 'ПУЛ' LP_PIC_RUNE = 'RUNE' LP_PIC_ADDED = 'Добавлено' LP_PIC_WITHDRAWN = 'Выведено' LP_PIC_REDEEM = 'Можно забрать' LP_PIC_GAIN_LOSS = 'Доход / убыток' LP_PIC_IN_USD = 'в USD' LP_PIC_R_RUNE = f'{RAIDO_GLYPH}une' LP_PIC_ADDED_VALUE = 'Добавлено всего' LP_PIC_WITHDRAWN_VALUE = 'Выведено всего' LP_PIC_CURRENT_VALUE = 'Осталось в пуле' LP_PIC_PRICE_CHANGE = 'Изменение цены' LP_PIC_PRICE_CHANGE_2 = 'с момента 1го стейка' LP_PIC_LP_VS_HOLD = 'Против ХОЛД' LP_PIC_LP_APY = 'Годовых' LP_PIC_EARLY = 'Еще рано...' LP_PIC_FOOTER = "Испольует runestake.info от Bigboss" LP_PIC_SUMMARY_HEADER = 'Сводка по пулам ликвидности' LP_PIC_SUMMARY_ADDED_VALUE = 'Добавлено' LP_PIC_SUMMARY_WITHDRAWN_VALUE = 'Выведено' LP_PIC_SUMMARY_CURRENT_VALUE = 'Сейчас в пуле' LP_PIC_SUMMARY_TOTAL_GAIN_LOSS = 'Доход/убыток' LP_PIC_SUMMARY_TOTAL_GAIN_LOSS_PERCENT = 'Доход/убыток %' LP_PIC_SUMMARY_AS_IF_IN_RUNE = f'Если все в {RAIDO_GLYPH}' LP_PIC_SUMMARY_AS_IF_IN_USD = 'Если все в $' LP_PIC_SUMMARY_TOTAL_LP_VS_HOLD = 'Итого холд против пулов, $' def pic_stake_days(self, total_days, first_stake_ts): start_date = datetime.fromtimestamp(first_stake_ts).strftime( '%d.%m.%Y') return f'{ceil(total_days)} дн. ({start_date})' def text_stake_loading_pools(self, address): return f'⏳ <b>Пожалуйста, подождите.</b>\n' \ f'Идет загрузка пулов для адреса {pre(address)}...\n' \ f'Иногда она может идти долго, если Midgard сильно нагружен.' def text_stake_provides_liq_to_pools(self, address, pools): pools = pre(', '.join(pools)) thor_tx = link(self.thor_explore_address(address), 'viewblock.io') bnb_tx = link(self.binance_explore_address(address), 'explorer.binance.org') return f'🛳️ {pre(address)}\n' \ f'поставляет ликвидность в следующие пулы:\n{pools}.\n\n' \ f"🔍 Explorers: {thor_tx}; {bnb_tx}.\n\n" \ f'👇 Выберите пул, чтобы получить подробную карточку информаци.' def text_stake_today(self): today = datetime.now().strftime('%d.%m.%Y') return f'Сегодня: {today}' # ----- CAP ------ def notification_text_cap_change(self, old: ThorInfo, new: ThorInfo): verb = "подрос" if old.cap < new.cap else "упал" call = "Ай-да застейкаем!\n" if new.cap > old.cap else '' return ( f'<b>Кап {verb} с {pretty_money(old.cap)} до {pretty_money(new.cap)}!</b>\n' f'Сейчас в пулы помещено <b>{pretty_money(new.stacked)}</b> {self.R}.\n' f"{self._cap_progress_bar(new)}" f'Цена {self.R} в пуле <code>{new.price:.3f} BUSD</code>.\n' f'{call}' f'https://chaosnet.bepswap.com/') # ------ PRICE ------- PRICE_GRAPH_TITLE = f'Цена {RAIDO_GLYPH}уны' PRICE_GRAPH_LEGEND_DET_PRICE = 'Детерминистская цена' PRICE_GRAPH_LEGEND_ACTUAL_PRICE = 'Рыночная цена' def price_message(self, info: ThorInfo, fair_price: RuneFairPrice): return ( f"Последняя цена {self.R}: <code>{info.price:.3f} BUSD</code>.\n" f"Детерминистическая цена {self.R} сейчас: <code>${fair_price.fair_price:.3f}</code>." ) # ------ TXS ------- def notification_text_large_tx(self, tx: StakeTx, dollar_per_rune: float, pool: StakePoolStats, pool_info: PoolInfo): msg = '' if tx.type == 'stake': msg += f'🐳 <b>Кит добавил ликвидности</b> 🟢\n' elif tx.type == 'unstake': msg += f'🐳 <b>Кит вывел ликвидность</b> 🔴\n' rp, ap = tx.symmetry_rune_vs_asset() total_usd_volume = tx.full_rune * dollar_per_rune if dollar_per_rune != 0 else 0.0 pool_depth_usd = pool_info.usd_depth(dollar_per_rune) thor_tx = link(self.thor_explore_address(tx.address), short_address(tx.address)) bnb_tx = link(self.binance_explore_address(tx.address), short_address(tx.address)) percent_of_pool = pool_info.percent_share(tx.full_rune) return ( f"<b>{pretty_money(tx.rune_amount)} {self.R}</b> ({rp:.0f}%) ↔️ " f"<b>{pretty_money(tx.asset_amount)} {short_asset_name(tx.pool)}</b> ({ap:.0f}%)\n" f"Всего: <code>${pretty_money(total_usd_volume)}</code> ({percent_of_pool:.2f}% от всего пула).\n" f"Глубина пула сейчас: <b>${pretty_money(pool_depth_usd)}</b>.\n" f"Thor обозреватель: {thor_tx} / Binance обозреватель: {bnb_tx}.") # ------- QUEUE ------- def notification_text_queue_update(self, item_type, step, value): if step == 0: return f"☺️ Очередь {item_type} снова опустела!" else: return f"🤬 <b>Внимание!</b> Очередь {code(item_type)} имеет {value} транзакций!" # ------- PRICE ------- def notification_text_price_update(self, p: PriceReport, ath=False): title = bold('Обновление цены') if not ath else bold( '🚀 Достигнуть новый исторический максимум!') c_gecko_url = 'https://www.coingecko.com/ru/' \ '%D0%9A%D1%80%D0%B8%D0%BF%D1%82%D0%BE%D0%B2%D0%B0%D0%BB%D1%8E%D1%82%D1%8B/thorchain' c_gecko_link = link(c_gecko_url, 'RUNE') message = f"{title} | {c_gecko_link}\n\n" price = p.fair_price.real_rune_price btc_price = f"₿ {p.btc_real_rune_price:.8f}" pr_text = f"${price:.2f}" message += f"Цена <b>RUNE</b> сейчас {code(pr_text)} ({btc_price}).\n" last_ath = p.last_ath if last_ath is not None and ath: message += f"Последний ATH был ${last_ath.ath_price:2.f} ({format_time_ago(last_ath.ath_date)}).\n" time_combos = zip(('1ч.', '24ч.', '7дн.'), (p.price_1h, p.price_24h, p.price_7d)) for title, old_price in time_combos: if old_price: pc = calc_percent_change(old_price, price) message += pre( f"{title.rjust(5)}:{adaptive_round_to_str(pc, True).rjust(8)} % " f"{emoji_for_percent_change(pc).ljust(4).rjust(6)}") + "\n" fp = p.fair_price if fp.rank >= 1: message += f"Капитализация: {bold(pretty_dollar(fp.market_cap))} (#{bold(fp.rank)} место)\n" if fp.tlv_usd >= 1: message += ( f"TLV (кроме RUNE): ${pre(pretty_money(fp.tlv_usd))}\n" f"Детерминистическая цена: {code(pretty_money(fp.fair_price, prefix='$'))}\n" f"Спекулятивый множитель: {pre(x_ses(fp.fair_price, price))}\n" ) return message.rstrip() # ------- POOL CHURN ------- def notification_text_pool_churn(self, added_pools, removed_pools, changed_status_pools): message = bold('🏊 Изменения в пулах ликвидности:') + '\n\n' statuses = {'Enabled': 'включен', 'Bootstrap': 'загружается'} def pool_text(pool_name, status, to_status=None): t = link(self.pool_link(pool_name), pool_name) extra = '' if to_status is None else f' → {ital(statuses[to_status])}' return f'{t} ({ital(statuses[status])}{extra})' if added_pools: message += '✅ Пулы добавлены: ' + ', '.join( [pool_text(*a) for a in added_pools]) + '\n' if removed_pools: message += '❌ Пулы удалены: ' + ', '.join( [pool_text(*a) for a in removed_pools]) + '\n' if changed_status_pools: message += '🔄 Пулы изменились: ' + ', '.join( [pool_text(*a) for a in changed_status_pools]) + '\n' return message.rstrip() # -------- SETTINGS -------- BUTTON_SET_LANGUAGE = '🌐 Язык' TEXT_SETTING_INTRO = '<b>Настройки</b>\nЧто вы хотите поменять в настройках?' # -------- METRICS ---------- BUTTON_METR_CAP = '📊 Кап ливкидности' BUTTON_METR_PRICE = f'💲 {BaseLocalization.R} инфо о цене' BUTTON_METR_QUEUE = f'👥 Очередь' TEXT_METRICS_INTRO = 'Что вы хотите узнать?' def cap_message(self, info: ThorInfo): return ( f"<b>{pretty_money(info.stacked)}</b> монет из " f"<b>{pretty_money(info.cap)}</b> сейчас застейканы.\n" f"{self._cap_progress_bar(info)}" f"Цена {bold(self.R)} сейчас <code>{info.price:.3f} BUSD</code>.\n" ) def queue_message(self, queue_info: QueueInfo): return ( f"<b>Информация об очередях:</b>\n" f"Исходящие транзакции (outbound): {code(queue_info.outbound)} шт.\n" f"Очередь обменов (swap): {code(queue_info.swap)} шт.\n" ) + ( f"Если в очереди много транзакций, ваши операции могут занять гораздо больше времени, чем обычно." if queue_info.is_full else '') TEXT_PRICE_INFO_ASK_DURATION = 'За какой период времени вы хотите получить график?' BUTTON_1_HOUR = '1 часов' BUTTON_24_HOURS = '24 часа' BUTTON_1_WEEK = '1 неделя' BUTTON_30_DAYS = '30 дней' # ------- AVATAR ------- TEXT_AVA_WELCOME = '🖼️ Скинь мне квадратное фото, и я сделаю для тебя аватар в стиле THORChain ' \ 'с градиентной рамкой.' TEXT_AVA_ERR_INVALID = '⚠️ Фото неправильного формата!' TEXT_AVA_ERR_SQUARE = '🖼️ Фото должно быть строго квадратное!' TEXT_AVA_ERR_NO_PIC = '⚠️ Не удалось загрузить твое фото из профиля!' TEXT_AVA_READY = '🥳 <b>Твой THORChain аватар готов!</b> ' \ 'Скачай это фото и установи его в Телеграм и социальных сетях.' BUTTON_AVA_FROM_MY_USERPIC = '😀 Из фото профиля'
def error_message(exc, tag): # fixme: use CREATOR_TG from the config return code(f"Sorry! An error occurred: {str(exc)}. Incident ID is {tag}.") + \ f"\nFeedback/support: {CREATOR_TG}."
class BaseLocalization(ABC): # == English # ----- WELCOME ------ START_ME = 'https://telegram.me/thorchain_monitoring_bot?start=1' @staticmethod def _cap_progress_bar(info: ThorInfo): return f'{progressbar(info.stacked, info.cap, 10)} ({format_percent(info.stacked, info.cap)})\n' # ---- WELCOME ---- def help_message(self): return ( f"This bot is for {link(self.THORCHAIN_LINK, 'THORChain')} monitoring.\n" f"Command list:\n" f"/help – this help page\n" f"/start – start/restart the bot\n" f"/lang – set the language\n" f"/cap – the current liquidity cap of Chaosnet\n" f"/price – the current Rune price.\n" f"<b>⚠️ All notifications are forwarded to @thorchain_alert channel!</b>\n" f"🤗 Support and feedback: {CREATOR_TG}.") def welcome_message(self, info: ThorInfo): return ( f"Hello! Here you can find THORChain metrics and review your liquidity results.\n" f"The {self.R} price is <code>${info.price:.3f}</code> now.\n" f"<b>⚠️ All notifications are forwarded to @thorchain_alert channel!</b>\n" f"🤗 Support and feedback: {CREATOR_TG}.") BUTTON_RUS = 'Русский' BUTTON_ENG = 'English' THORCHAIN_LINK = 'https://thorchain.org/' R = 'Rune' def lang_help(self): return (f'Пожалуйста, выберите язык / Please select a language', kbd([self.BUTTON_RUS, self.BUTTON_ENG], one_time=True)) def unknown_command(self): return ("🙄 Sorry, I didn't understand that command.\n" "Use /help to see available commands.") # ----- MAIN MENU ------ BUTTON_MM_MY_ADDRESS = '🏦 Manage my address' BUTTON_MM_METRICS = '📐 Metrics' BUTTON_MM_SETTINGS = f'⚙️ Settings' BUTTON_MM_MAKE_AVATAR = f'🦹️️ THOR Avatar' def kbd_main_menu(self): return kbd([[self.BUTTON_MM_MY_ADDRESS, self.BUTTON_MM_METRICS], [self.BUTTON_MM_MAKE_AVATAR, self.BUTTON_MM_SETTINGS]]) # ------- STAKE INFO MENU ------- BUTTON_SM_ADD_ADDRESS = '➕ Add an address' BUTTON_BACK = '🔙 Back' BUTTON_SM_BACK_TO_LIST = '🔙 Back to list' BUTTON_SM_BACK_MM = '🔙 Main menu' BUTTON_SM_SUMMARY = '💲 Summary' BUTTON_VIEW_RUNESTAKEINFO = '🌎 View it on runestake.info' BUTTON_VIEW_VALUE_ON = 'Show value: ON' BUTTON_VIEW_VALUE_OFF = 'Show value: OFF' BUTTON_REMOVE_THIS_ADDRESS = '❌ Remove this address' TEXT_NO_ADDRESSES = "🔆 You have not added any addresses yet. Send me one." TEXT_YOUR_ADDRESSES = '🔆 You added addresses:' TEXT_INVALID_ADDRESS = code('⛔️ Invalid address!') TEXT_SELECT_ADDRESS_ABOVE = 'Select one from above. ☝️ ' TEXT_SELECT_ADDRESS_SEND_ME = 'If you want to add one more, please send me it. 👇' TEXT_LP_NO_POOLS_FOR_THIS_ADDRESS = "📪 <b>This address doesn't participate in any liquidity pools.</b> " \ "Please choose another one or add new." TEXT_LP_IMG_CAPTION = f'Generated by {link(START_ME, "@thorchain_monitoring_bot")}' LP_PIC_POOL = 'POOL' LP_PIC_RUNE = 'RUNE' LP_PIC_ADDED = 'Added' LP_PIC_WITHDRAWN = 'Withdrawn' LP_PIC_REDEEM = 'Redeemable' LP_PIC_GAIN_LOSS = 'Gain / Loss' LP_PIC_IN_USD = 'in USD' LP_PIC_R_RUNE = f'{RAIDO_GLYPH}une' LP_PIC_ADDED_VALUE = 'Added value' LP_PIC_WITHDRAWN_VALUE = 'Withdrawn value' LP_PIC_CURRENT_VALUE = 'Current value' LP_PIC_PRICE_CHANGE = 'Price change' LP_PIC_PRICE_CHANGE_2 = 'since the first addition' LP_PIC_LP_VS_HOLD = 'LP vs HOLD' LP_PIC_LP_APY = 'LP APY' LP_PIC_EARLY = 'Early...' LP_PIC_FOOTER = "Powered by Bigboss' runestake.info" LP_PIC_SUMMARY_HEADER = 'Liquidity pools summary' LP_PIC_SUMMARY_ADDED_VALUE = 'Added value' LP_PIC_SUMMARY_WITHDRAWN_VALUE = 'Withdrawn' LP_PIC_SUMMARY_CURRENT_VALUE = 'Current value' LP_PIC_SUMMARY_TOTAL_GAIN_LOSS = 'Total gain/loss' LP_PIC_SUMMARY_TOTAL_GAIN_LOSS_PERCENT = 'Total gain/loss %' LP_PIC_SUMMARY_AS_IF_IN_RUNE = f'Total as {RAIDO_GLYPH}' LP_PIC_SUMMARY_AS_IF_IN_USD = 'Total as $' LP_PIC_SUMMARY_TOTAL_LP_VS_HOLD = 'Total LP vs Hold $' def pic_stake_days(self, total_days, first_stake_ts): start_date = datetime.fromtimestamp(first_stake_ts).strftime( '%d.%m.%Y') day_count_str = 'days' if total_days >= 2 else 'day' return f'{ceil(total_days)} {day_count_str} ({start_date})' def text_stake_loading_pools(self, address): return f'⏳ <b>Please wait.</b>\n' \ f'Loading pools information for {pre(address)}...' def text_stake_provides_liq_to_pools(self, address, pools): pools = pre(', '.join(pools)) thor_tx = link(self.thor_explore_address(address), 'viewblock.io') bnb_tx = link(self.binance_explore_address(address), 'explorer.binance.org') return f'🛳️ {pre(address)}\nprovides liquidity to the following pools:\n' \ f'{pools}.\n\n' \ f"🔍 Explorers: {thor_tx}; {bnb_tx}.\n\n" \ f'👇 Click on the button to get a detailed card.' def text_stake_today(self): today = datetime.now().strftime('%d.%m.%Y') return f'Today is {today}' # ------- CAP ------- def notification_text_cap_change(self, old: ThorInfo, new: ThorInfo): verb = "has been increased" if old.cap < new.cap else "has been decreased" call = "Come on, add more liquidity!\n" if new.cap > old.cap else '' message = ( f'<b>Pool cap {verb} from {pretty_money(old.cap)} to {pretty_money(new.cap)}!</b>\n' f'Currently <b>{pretty_money(new.stacked)}</b> {self.R} are in the liquidity pools.\n' f"{self._cap_progress_bar(new)}" f'The price of {self.R} in the pool is <code>{new.price:.3f} BUSD</code>.\n' f'{call}' f'https://chaosnet.bepswap.com/') return message # ------ PRICE ------- PRICE_GRAPH_TITLE = f'Rune price, USD' PRICE_GRAPH_LEGEND_DET_PRICE = f'Deterministic {RAIDO_GLYPH} price' PRICE_GRAPH_LEGEND_ACTUAL_PRICE = f'Market {RAIDO_GLYPH} price' def price_message(self, info: ThorInfo, fair_price: RuneFairPrice): return ( f"Last real price of {self.R} is <code>${info.price:.3f}</code>.\n" f"Deterministic price of {self.R} is <code>${fair_price.fair_price:.3f}</code>." ) # ------- NOTIFY STAKES ------- @staticmethod def thor_explore_address(address): return f'https://viewblock.io/thorchain/address/{address}' @staticmethod def binance_explore_address(address): return f'https://explorer.binance.org/address/{address}' def notification_text_large_tx(self, tx: StakeTx, dollar_per_rune: float, pool: StakePoolStats, pool_info: PoolInfo): msg = '' if tx.type == 'stake': msg += f'🐳 <b>Whale added liquidity</b> 🟢\n' elif tx.type == 'unstake': msg += f'🐳 <b>Whale removed liquidity</b> 🔴\n' total_usd_volume = tx.full_rune * dollar_per_rune if dollar_per_rune != 0 else 0.0 pool_depth_usd = pool_info.usd_depth(dollar_per_rune) thor_tx = link(self.thor_explore_address(tx.address), short_address(tx.address)) bnb_tx = link(self.binance_explore_address(tx.address), short_address(tx.address)) rp, ap = tx.symmetry_rune_vs_asset() rune_side_usd = tx.rune_amount * dollar_per_rune rune_side_usd_short = short_money(rune_side_usd) asset_side_usd_short = short_money(total_usd_volume - rune_side_usd) percent_of_pool = pool_info.percent_share(tx.full_rune) msg += ( f"<b>{pretty_money(tx.rune_amount)} {self.R}</b> ({rp:.0f}% = {rune_side_usd_short}) ↔️ " f"<b>{pretty_money(tx.asset_amount)} {short_asset_name(tx.pool)}</b> ({ap:.0f}% = {asset_side_usd_short})\n" f"Total: <code>${pretty_money(total_usd_volume)}</code> ({percent_of_pool:.2f}% of the whole pool).\n" f"Pool depth is <b>${pretty_money(pool_depth_usd)}</b> now.\n" f"Thor explorer: {thor_tx} / Binance explorer: {bnb_tx}.") return msg # ------- QUEUE ------- def notification_text_queue_update(self, item_type, step, value): if step == 0: return f"☺️ Queue {code(item_type)} is empty again!" else: return ( f"🤬 <b>Attention!</b> Queue {code(item_type)} has {value} transactions!\n" f"{code(item_type)} transactions may be delayed.") # ------- PRICE ------- DET_PRICE_HELP_PAGE = 'https://docs.thorchain.org/how-it-works/incentive-pendulum' def notification_text_price_update(self, p: PriceReport, ath=False): title = bold('Price update') if not ath else bold( '🚀 A new all-time high has been achieved!') c_gecko_url = 'https://www.coingecko.com/en/coins/thorchain' c_gecko_link = link(c_gecko_url, 'RUNE') message = f"{title} | {c_gecko_link}\n\n" price = p.fair_price.real_rune_price pr_text = f"${price:.3f}" btc_price = f"₿ {p.btc_real_rune_price:.8f}" message += f"<b>RUNE</b> price is {code(pr_text)} ({btc_price}) now.\n" last_ath = p.last_ath if last_ath is not None and ath: last_ath_pr = f'{last_ath.ath_price:.2f}' message += f"Last ATH was ${pre(last_ath_pr)} ({format_time_ago(last_ath.ath_date)}).\n" time_combos = zip(('1h', '24h', '7d'), (p.price_1h, p.price_24h, p.price_7d)) for title, old_price in time_combos: if old_price: pc = calc_percent_change(old_price, price) message += pre( f"{title.rjust(4)}:{adaptive_round_to_str(pc, True).rjust(8)} % " f"{emoji_for_percent_change(pc).ljust(4).rjust(6)}") + "\n" fp = p.fair_price if fp.rank >= 1: message += f"Coin market cap is {bold(pretty_dollar(fp.market_cap))} (#{bold(fp.rank)})\n" if fp.tlv_usd >= 1: det_link = link(self.DET_PRICE_HELP_PAGE, 'deterministic price') message += ( f"TVL of non-RUNE assets: ${pre(pretty_money(fp.tlv_usd))}\n" f"So {det_link} of RUNE is {code(pretty_money(fp.fair_price, prefix='$'))}\n" f"Speculative multiplier is {pre(x_ses(fp.fair_price, price))}\n" ) return message.rstrip() # ------- POOL CHURN ------- @staticmethod def pool_link(pool_name): return f'https://chaosnet.bepswap.com/pool/{asset_name_cut_chain(pool_name)}' def notification_text_pool_churn(self, added_pools, removed_pools, changed_status_pools): message = bold('🏊 Liquidity pool churn!') + '\n\n' def pool_text(pool_name, status, to_status=None): t = link(self.pool_link(pool_name), pool_name) extra = '' if to_status is None else f' → {ital(to_status)}' return f' • {t} ({ital(status)}{extra})' if added_pools: message += '✅ Pools added:\n' + '\n'.join( [pool_text(*a) for a in added_pools]) + '\n\n' if removed_pools: message += '❌ Pools removed:\n' + '\n'.join( [pool_text(*a) for a in removed_pools]) + '\n\n' if changed_status_pools: message += '🔄 Pools changed:\n' + '\n'.join( [pool_text(*a) for a in changed_status_pools]) + '\n\n' return message.rstrip() # -------- SETTINGS -------- BUTTON_SET_LANGUAGE = '🌐 Language' TEXT_SETTING_INTRO = '<b>Settings</b>\nWhat would you like to tune?' # -------- METRICS ---------- BUTTON_METR_CAP = '📊 Liquidity cap' BUTTON_METR_PRICE = f'💲 {R} price info' BUTTON_METR_QUEUE = f'👥 Queue' TEXT_METRICS_INTRO = 'What metrics would you like to know?' TEXT_QUEUE_PLOT_TITLE = 'THORChain Queue' def cap_message(self, info: ThorInfo): return ( f"Hello! <b>{pretty_money(info.stacked)} {self.R}</b> of " f"<b>{pretty_money(info.cap)} {self.R}</b> pooled.\n" f"{self._cap_progress_bar(info)}" f"The {bold(self.R)} price is <code>${info.price:.3f}</code> now.\n" ) def queue_message(self, queue_info: QueueInfo): return ( f"<b>Queue info:</b>\n" f"- <b>Outbound</b>: {code(queue_info.outbound)} txs {self.queue_to_smile(queue_info.outbound)}\n" f"- <b>Swap</b>: {code(queue_info.swap)} txs {self.queue_to_smile(queue_info.swap)}\n" ) + ( f"If there are many transactions in the queue, your operations may take much longer than usual." if queue_info.is_full else '') @staticmethod def queue_to_smile(n): if n <= 3: return '🟢' elif n <= 20: return '🟡' elif n <= 50: return '🔴' elif n <= 100: return '🤬!!' TEXT_PRICE_INFO_ASK_DURATION = 'For what period of time do you want to get a graph?' BUTTON_1_HOUR = '1 hour' BUTTON_24_HOURS = '24 hours' BUTTON_1_WEEK = '1 week' BUTTON_30_DAYS = '30 days' # ------- AVATAR ------- TEXT_AVA_WELCOME = '🖼️ Drop me a square picture and I make you THORChain-styled avatar with a gradient frame.' TEXT_AVA_ERR_INVALID = '⚠️ Your picture has invalid format!' TEXT_AVA_ERR_SQUARE = '🖼️ Your picture is not square!' TEXT_AVA_ERR_NO_PIC = '⚠️ You have no user pic...' TEXT_AVA_READY = '🥳 <b>Your THORChain avatar is ready!</b> Download this image and set it as a profile picture' \ ' at Telegram and other social networks.' BUTTON_AVA_FROM_MY_USERPIC = '😀 From my userpic'