From 76a8793bbd4f1aa356f6a10e6d3646ed0bd2a4bb Mon Sep 17 00:00:00 2001 From: ilyarolf Date: Thu, 26 Sep 2024 23:42:00 +0300 Subject: [PATCH 01/17] implementing ethereum as crypto payment --- models/eth_account.py | 12 ++++++++++++ models/trx_account.py | 13 +++++++++++++ models/user.py | 13 ++++++++++--- services/eth_account.py | 28 ++++++++++++++++++++++++++++ services/trx_account.py | 28 ++++++++++++++++++++++++++++ services/user.py | 11 ++++++++--- utils/CryptoAddressGenerator.py | 10 +++++++++- 7 files changed, 108 insertions(+), 7 deletions(-) create mode 100644 models/eth_account.py create mode 100644 models/trx_account.py create mode 100644 services/eth_account.py create mode 100644 services/trx_account.py diff --git a/models/eth_account.py b/models/eth_account.py new file mode 100644 index 00000000..7855a56a --- /dev/null +++ b/models/eth_account.py @@ -0,0 +1,12 @@ +from models.base import Base +from sqlalchemy import Column, Integer, String, Float + + +class EthAccount(Base): + __tablename__ = 'eth_accounts' + + id = Column(Integer, primary_key=True) + address = Column(String, nullable=False, unique=True) + eth_balance = Column(Float, default=0.0) + usdt_balance = Column(Float, default=0.0) + usdc_balance = Column(Float, default=0.0) diff --git a/models/trx_account.py b/models/trx_account.py new file mode 100644 index 00000000..07ce8c68 --- /dev/null +++ b/models/trx_account.py @@ -0,0 +1,13 @@ +from sqlalchemy import Column, Integer, Float, String + +from models.base import Base + + +class TrxAccount(Base): + __tablename__ = 'trx_accounts' + + id = Column(Integer, primary_key=True) + address = Column(String, nullable=False, unique=True) + eth_balance = Column(Float, default=0.0) + usdt_balance = Column(Float, default=0.0) + usdd_balance = Column(Float, default=0.0) diff --git a/models/user.py b/models/user.py index 7a7779f7..a8531030 100644 --- a/models/user.py +++ b/models/user.py @@ -1,4 +1,5 @@ -from sqlalchemy import Column, Integer, DateTime, String, Boolean, Float, func +from sqlalchemy import Column, Integer, DateTime, String, Boolean, Float, func, ForeignKey +from sqlalchemy.orm import relationship, backref from models.base import Base @@ -11,13 +12,19 @@ class User(Base): telegram_id = Column(Integer, nullable=False) btc_address = Column(String, nullable=False, unique=True) ltc_address = Column(String, nullable=False, unique=True) - trx_address = Column(String, nullable=False, unique=True) last_balance_refresh = Column(DateTime) top_up_amount = Column(Float, default=0.0) consume_records = Column(Float, default=0.0) btc_balance = Column(Float, nullable=False, default=0.0) ltc_balance = Column(Float, nullable=False, default=0.0) - usdt_balance = Column(Float, nullable=False, default=0.0) + eth_account_id = Column(Integer, ForeignKey('eth_accounts.id'), nullable=False) + eth_account = relationship("EthAccount", backref=backref("eth_accounts", cascade="all"), + passive_deletes="all", + lazy="joined") + trx_account_id = Column(Integer, ForeignKey('trx_accounts.id'), nullable=False) + trx_account = relationship("TrxAccount", backref=backref("trx_accounts", cascade="all"), + passive_deletes="all", + lazy="joined") registered_at = Column(DateTime, default=func.now()) seed = Column(String, nullable=False, unique=True) can_receive_messages = Column(Boolean, default=True) diff --git a/services/eth_account.py b/services/eth_account.py new file mode 100644 index 00000000..77944206 --- /dev/null +++ b/services/eth_account.py @@ -0,0 +1,28 @@ +from sqlalchemy import select + +from db import async_session_maker +from models.eth_account import EthAccount + + +class EthAccountService: + + @staticmethod + async def get_next_user_id() -> int: + async with async_session_maker() as session: + query = select(EthAccount.id).order_by(EthAccount.id.desc()).limit(1) + last_user_id = await session.execute(query) + last_user_id = last_user_id.scalar() + if last_user_id is None: + return 0 + else: + return int(last_user_id) + 1 + + @staticmethod + async def create(address: str) -> int: + async with async_session_maker() as session: + next_account_id = await EthAccountService.get_next_user_id() + eth_acc = EthAccount(id=next_account_id, + address=address) + session.add(eth_acc) + await session.commit() + return next_account_id diff --git a/services/trx_account.py b/services/trx_account.py new file mode 100644 index 00000000..30c1897a --- /dev/null +++ b/services/trx_account.py @@ -0,0 +1,28 @@ +from sqlalchemy import select + +from db import async_session_maker +from models.trx_account import TrxAccount + + +class TrxAccountService: + + @staticmethod + async def get_next_user_id() -> int: + async with async_session_maker() as session: + query = select(TrxAccount.id).order_by(TrxAccount.id.desc()).limit(1) + last_user_id = await session.execute(query) + last_user_id = last_user_id.scalar() + if last_user_id is None: + return 0 + else: + return int(last_user_id) + 1 + + @staticmethod + async def create(address: str) -> int: + async with async_session_maker() as session: + next_account_id = await TrxAccountService.get_next_user_id() + eth_acc = TrxAccount(id=next_account_id, + address=address) + session.add(eth_acc) + await session.commit() + return next_account_id diff --git a/services/user.py b/services/user.py index d5fa8d4d..d661ca8f 100644 --- a/services/user.py +++ b/services/user.py @@ -8,6 +8,8 @@ from db import async_session_maker from models.user import User +from services.eth_account import EthAccountService +from services.trx_account import TrxAccountService from utils.CryptoAddressGenerator import CryptoAddressGenerator @@ -34,17 +36,20 @@ async def get_next_user_id() -> int: @staticmethod async def create(telegram_id: int, telegram_username: str): + crypto_addr_gen = CryptoAddressGenerator() + crypto_addresses = crypto_addr_gen.get_addresses(i=0) + eth_account_id = await EthAccountService.create(crypto_addresses['eth']) + trx_account_id = await TrxAccountService.create(crypto_addresses['trx']) async with async_session_maker() as session: next_user_id = await UserService.get_next_user_id() - crypto_addr_gen = CryptoAddressGenerator() - crypto_addresses = crypto_addr_gen.get_addresses(i=0) new_user = User( id=next_user_id, telegram_username=telegram_username, telegram_id=telegram_id, btc_address=crypto_addresses['btc'], ltc_address=crypto_addresses['ltc'], - trx_address=crypto_addresses['trx'], + eth_account_id=eth_account_id, + trx_account_id=trx_account_id, seed=crypto_addr_gen.mnemonic_str ) session.add(new_user) diff --git a/utils/CryptoAddressGenerator.py b/utils/CryptoAddressGenerator.py index 97cf67e7..1f21ba21 100644 --- a/utils/CryptoAddressGenerator.py +++ b/utils/CryptoAddressGenerator.py @@ -29,7 +29,15 @@ def __generate_trx_pair(self, i: int) -> str: bip44_addr_ctx = bip44_chg_ctx.AddressIndex(i).PublicKey().ToAddress() return bip44_addr_ctx + def __generate_eth_pair(self, i: int) -> str: + bip44_mst_ctx = Bip44.FromSeed(self.seed_bytes, Bip44Coins.ETHEREUM) + bip44_acc_ctx = bip44_mst_ctx.Purpose().Coin().Account(0) + bip44_chg_ctx = bip44_acc_ctx.Change(Bip44Changes.CHAIN_EXT) + bip44_addr_ctx = bip44_chg_ctx.AddressIndex(i).PublicKey().ToAddress() + return bip44_addr_ctx + def get_addresses(self, i): return {'btc': self.__generate_btc_pair(i), 'ltc': self.__generate_ltc_pair(i), - 'trx': self.__generate_trx_pair(i)} + 'trx': self.__generate_trx_pair(i), + 'eth': self.__generate_eth_pair(i)} From 8ab76585afa6ed77c69c833b96ad781098a543f3 Mon Sep 17 00:00:00 2001 From: ilyarolf Date: Thu, 26 Sep 2024 23:58:35 +0300 Subject: [PATCH 02/17] implementing ethereum as crypto payment --- config.py | 1 + handlers/user/my_profile.py | 16 ++++++++++++---- services/user.py | 13 ++++++++++--- 3 files changed, 23 insertions(+), 7 deletions(-) diff --git a/config.py b/config.py index 135aee14..ae9127d5 100644 --- a/config.py +++ b/config.py @@ -18,3 +18,4 @@ PAGE_ENTRIES = int(os.environ.get("PAGE_ENTRIES")) LANGUAGE = os.environ.get("LANGUAGE") MULTIBOT = os.environ.get("MULTIBOT", False) == 'true' +ETHERSCAN_TOKEN = os.environ.get("ETHERSCAN_TOKEN") diff --git a/handlers/user/my_profile.py b/handlers/user/my_profile.py index cd56dc85..fd893dad 100644 --- a/handlers/user/my_profile.py +++ b/handlers/user/my_profile.py @@ -43,12 +43,18 @@ class MyProfileConstants: async def get_my_profile_message(telegram_id: int): user = await UserService.get_by_tgid(telegram_id) btc_balance = user.btc_balance - usdt_balance = user.usdt_balance + usdt_trc20_balance = user.trx_account.usdt_balance + usdd_trc20_balance = user.trx_account.usdd_balance + usdt_erc20_balance = user.eth_account.usdt_balance + usdc_erc20_balance = user.eth_account.usdc_balance ltc_balance = user.ltc_balance usd_balance = round(user.top_up_amount - user.consume_records, 2) return Localizator.get_text_from_key("my_profile_msg").format(telegram_id=telegram_id, btc_balance=btc_balance, - usdt_balance=usdt_balance, + usdt_trc20_balance=usdt_trc20_balance, + usdd_trc20_balance=usdd_trc20_balance, + usdt_erc20_balance=usdt_erc20_balance, + usdc_erc20_balance=usdc_erc20_balance, ltc_balance=ltc_balance, usd_balance=usd_balance) @@ -88,7 +94,8 @@ async def top_up_balance(callback: CallbackQuery): user = await UserService.get_by_tgid(telegram_id) current_level = 1 btc_address = user.btc_address - trx_address = user.trx_address + trx_address = user.trx_account.address + eth_address = user.eth_account.address ltc_address = user.ltc_address back_to_profile_button = types.InlineKeyboardButton(text=Localizator.get_text_from_key("admin_back_button"), callback_data=create_callback_profile(current_level - 1)) @@ -100,7 +107,8 @@ async def top_up_balance(callback: CallbackQuery): text=Localizator.get_text_from_key("top_up_balance_msg").format(bot_name=bot_entity.first_name, btc_address=btc_address, trx_address=trx_address, - ltc_address=ltc_address), + ltc_address=ltc_address, + eth_address=eth_address), parse_mode=ParseMode.HTML, reply_markup=back_button_markup) await callback.answer() diff --git a/services/user.py b/services/user.py index d661ca8f..0632a513 100644 --- a/services/user.py +++ b/services/user.py @@ -1,8 +1,8 @@ import datetime -import logging import math from sqlalchemy import select, update, func +from sqlalchemy.orm import joinedload import config from db import async_session_maker @@ -67,9 +67,16 @@ async def update_username(telegram_id: int, telegram_username: str): @staticmethod async def get_by_tgid(telegram_id: int) -> User: async with async_session_maker() as session: - stmt = select(User).where(User.telegram_id == telegram_id) + stmt = ( + select(User) + .options( + joinedload(User.eth_account), + joinedload(User.trx_account) + ) + .where(User.telegram_id == telegram_id) + ) user_from_db = await session.execute(stmt) - user_from_db = user_from_db.scalar() + user_from_db = user_from_db.scalar_one_or_none() # Use scalar_one_or_none to handle None case return user_from_db @staticmethod From e14b6f24e9323ab3aa25b3079ed9d574ac9ea4e7 Mon Sep 17 00:00:00 2001 From: ilyarolf Date: Fri, 27 Sep 2024 16:02:55 +0300 Subject: [PATCH 03/17] implementing ethereum as crypto payment --- l10n/en.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/l10n/en.json b/l10n/en.json index 76f87742..e97dd627 100644 --- a/l10n/en.json +++ b/l10n/en.json @@ -62,11 +62,11 @@ "out_of_stock": "Out of stock!", "purchased_item": "Item#{count}\nData:{private_data}\n", "back_to_my_profile": "⤵\uFE0FBack my profile", - "my_profile_msg": "Your profile\nID: {telegram_id}\n\nYour BTC balance:\n{btc_balance}\nYour USDT balance:\n{usdt_balance}\nYour LTC balance:\n{ltc_balance}\nYour balance in USD:\n{usd_balance}$", + "my_profile_msg": "Your profile\nID: {telegram_id}\n\nYour BTC balance:\n{btc_balance}\nYour USDT TRC-20 balance:\n{usdt_trc20_balance}\nYour USDD TRC-20 balance:\n{usdd_trc20_balance}\nYour USDT ERC-20 balance:\n{usdt_erc20_balance}\nYour USDC ERC-20 balance:\n{usdc_erc20_balance}\nYour LTC balance:\n{ltc_balance}\nYour balance in USD:\n{usd_balance}$", "top_up_balance_button": "Top Up balance", "purchase_history_button": "Purchase history", "refresh_balance_button": "Refresh balance", - "top_up_balance_msg": "Deposit to the address the amount you want to top up the {bot_name} \n\nImportant\nA unique BTC/LTC/USDT addresses is given for each deposit\nThe top up takes place within 5 minutes after the transfer\n\nYour BTC address\n{btc_address}\nYour USDT TRC-20 address\n{trx_address}\nYour LTC address\n{ltc_address}\n", + "top_up_balance_msg": "Deposit to the address the amount you want to top up the {bot_name} \n\nImportant\nA unique BTC/LTC/USDT addresses is given for each deposit\nThe top up takes place within 5 minutes after the transfer\n\nYour BTC address\n{btc_address}\nYour USDT,USDD TRC-20 address\n{trx_address}\nYour USDT,USDC ERC-20 address\n{eth_address}\nYour LTC address\n{ltc_address}\n", "purchase_history_item": "{subcategory_name} | Total Price: {total_price}$ | Quantity: {quantity} pcs", "no_purchases": "You haven't had any purchases yet", "purchases": "Your purchases:", From 82e0cf53d5d4fe3e1d1f4973269687ccd5654bc9 Mon Sep 17 00:00:00 2001 From: ilyarolf Date: Fri, 27 Sep 2024 18:41:42 +0300 Subject: [PATCH 04/17] implementing eth/trx and erc-20/trc-20 refresh balance functionality --- config.py | 2 +- crypto_api/CryptoApiManager.py | 56 +++++++++++++++++++++++++++++++--- models/trx_account.py | 2 +- services/user.py | 30 +++++++++++++----- 4 files changed, 75 insertions(+), 15 deletions(-) diff --git a/config.py b/config.py index ae9127d5..6141f230 100644 --- a/config.py +++ b/config.py @@ -18,4 +18,4 @@ PAGE_ENTRIES = int(os.environ.get("PAGE_ENTRIES")) LANGUAGE = os.environ.get("LANGUAGE") MULTIBOT = os.environ.get("MULTIBOT", False) == 'true' -ETHERSCAN_TOKEN = os.environ.get("ETHERSCAN_TOKEN") +ETHPLORER_API_KEY = os.environ.get("ETHPLORER_API_KEY") diff --git a/crypto_api/CryptoApiManager.py b/crypto_api/CryptoApiManager.py index 93b3d723..5c457088 100644 --- a/crypto_api/CryptoApiManager.py +++ b/crypto_api/CryptoApiManager.py @@ -1,19 +1,61 @@ -from typing import Any +import aiohttp import grequests +import config + class CryptoApiManager: - def __init__(self, btc_address, ltc_address, trx_address): + def __init__(self, btc_address, ltc_address, trx_address, eth_address): self.btc_address = btc_address.strip() self.ltc_address = ltc_address.strip() self.trx_address = trx_address.strip() + self.eth_address = eth_address.strip() + + @staticmethod + async def fetch_api_request(url: str) -> dict: + async with aiohttp.ClientSession() as session: + async with session.get(url) as response: + if response.status == 200: + data = await response.json() + return data + else: + return 0.0 + + async def get_btc_balance(self) -> float: + url = f'https://blockchain.info/rawaddr/{self.btc_address}' + data = await self.fetch_api_request(url) + return float(data['total_received']) / 100_000_000 + + async def get_ltc_balance(self) -> float: + url = f"https://api.blockcypher.com/v1/ltc/main/addrs/{self.ltc_address}" + data = await self.fetch_api_request(url) + return float(data['total_received']) / 100_000_000 + + async def get_trx_and_trc_20_balance(self) -> dict: + url = f'https://apilist.tronscan.org/api/account?address={self.trx_address}&includeToken=true' + data = await self.fetch_api_request(url) + trx_account_data = {'trx_balance': float(data["balance"]) / 1_000_000, + 'in_transactions': data["transactions_in"]} + for token in data['trc20token_balances']: + if token['tokenId'] == 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t': + trx_account_data['usdt_balance'] = round(float(token['balance']) * pow(10, -token['tokenDecimal']), 6) + elif token['tokenId'] == 'TPYmHEhy5n8TCEfYGqW2rPxsghSfzghPDn': + trx_account_data['usdd_balance'] = round(float(token['balance']) * pow(10, -token['tokenDecimal']), 6) + return trx_account_data + + async def get_eth_and_erc_20_balance(self) -> dict: + url = f'https://api.ethplorer.io/getAddressInfo/{self.eth_address}?apiKey={config.ETHPLORER_API_KEY}' + data = await self.fetch_api_request(url) + eth_account_data = {"eth_balance": data["ETH"]["balance"], + } async def get_top_ups(self): urls = { "btc_balance": f'https://blockchain.info/rawaddr/{self.btc_address}', "usdt_balance": f'https://apilist.tronscan.org/api/account?address={self.trx_address}&includeToken=true', - "ltc_balance": f'https://api.blockcypher.com/v1/ltc/main/addrs/{self.ltc_address}' + "ltc_balance": f'https://api.blockcypher.com/v1/ltc/main/addrs/{self.ltc_address}', + # "eth_balance": "" } balances = {} rs = (grequests.get(url) for url in urls.values()) @@ -43,11 +85,15 @@ async def get_top_ups(self): @staticmethod async def get_crypto_prices() -> dict[str, float]: + # TODO("NEED API FOR USDD-TRC-20") usd_crypto_prices = {} urls = { "btc": 'https://api.kraken.com/0/public/Ticker?pair=BTCUSDT', "usdt": 'https://api.kraken.com/0/public/Ticker?pair=USDTUSD', - "ltc": 'https://api.kraken.com/0/public/Ticker?pair=LTCUSD' + "usdc": "https://api.kraken.com/0/public/Ticker?pair=USDCUSD", + "ltc": 'https://api.kraken.com/0/public/Ticker?pair=LTCUSD', + "eth": 'https://api.kraken.com/0/public/Ticker?pair=ETHUSD', + "trx": "https://api.kraken.com/0/public/Ticker?pair=TRXUSD" } responses = (grequests.get(url) for url in urls.values()) datas = grequests.map(responses) @@ -55,5 +101,5 @@ async def get_crypto_prices() -> dict[str, float]: data = data.json() price = float(next(iter(data['result'].values()))['l'][1]) usd_crypto_prices[symbol] = price + usd_crypto_prices["usdd"] = 1 # 1USDD=1USD return usd_crypto_prices - diff --git a/models/trx_account.py b/models/trx_account.py index 07ce8c68..001a1ff3 100644 --- a/models/trx_account.py +++ b/models/trx_account.py @@ -8,6 +8,6 @@ class TrxAccount(Base): id = Column(Integer, primary_key=True) address = Column(String, nullable=False, unique=True) - eth_balance = Column(Float, default=0.0) + trx_balance = Column(Float, default=0.0) usdt_balance = Column(Float, default=0.0) usdd_balance = Column(Float, default=0.0) diff --git a/services/user.py b/services/user.py index 0632a513..cbdb9e9d 100644 --- a/services/user.py +++ b/services/user.py @@ -102,21 +102,35 @@ async def create_last_balance_refresh_data(telegram_id: int): @staticmethod async def get_balances(telegram_id: int) -> dict: - async with async_session_maker() as session: - stmt = select(User.btc_balance, User.ltc_balance, User.usdt_balance).where(User.telegram_id == telegram_id) + async with (async_session_maker() as session): + stmt = select(User).options(joinedload(User.eth_account), + joinedload(User.trx_account)).where( + User.telegram_id == telegram_id) user_balances = await session.execute(stmt) - user_balances = user_balances.fetchone() - keys = ["btc_balance", "ltc_balance", "usdt_balance"] + user_balances = user_balances.scalar() + user_balances = [user_balances.btc_balance, user_balances.ltc_balance, + user_balances.trx_account.trx_balance, + user_balances.trx_account.usdt_balance, user_balances.trx_account.usdd_balance, + user_balances.eth_account.eth_balance, user_balances.eth_account.usdt_balance, + user_balances.eth_account.usdc_balance] + keys = ["btc_balance", + "ltc_balance", + "trx_balance", "trc_20_usdt_balance", "trc_20_usdd_balance", + "eth_balance", "erc_20_usdt_balance", "erc_20_usdc_balance"] user_balances = dict(zip(keys, user_balances)) return user_balances @staticmethod async def get_addresses(telegram_id: int) -> dict: - async with async_session_maker() as session: - stmt = select(User.btc_address, User.ltc_address, User.trx_address).where(User.telegram_id == telegram_id) + async with (async_session_maker() as session): + stmt = select(User).options(joinedload(User.eth_account), + joinedload(User.trx_account) + ).where(User.telegram_id == telegram_id) user_addresses = await session.execute(stmt) - user_addresses = user_addresses.fetchone() - keys = ["btc_address", "ltc_address", "trx_address"] + user_addresses = user_addresses.scalar() + user_addresses = [user_addresses.btc_address, user_addresses.ltc_address, + user_addresses.trx_account.address,user_addresses.eth_account.address] + keys = ["btc_address", "ltc_address", "trx_address", "eth_address"] user_addresses = dict(zip(keys, user_addresses)) return user_addresses From 2d010c66a2bd861ce6ffe9e9543857107f355ad7 Mon Sep 17 00:00:00 2001 From: ilyarolf Date: Fri, 27 Sep 2024 23:27:01 +0300 Subject: [PATCH 05/17] implementing eth/trx and erc-20/trc-20 refresh balance functionality --- crypto_api/CryptoApiManager.py | 153 +++++++++++++++++++---------- handlers/user/my_profile.py | 6 +- models/deposit.py | 15 +++ services/deposit.py | 40 ++++++++ services/user.py | 17 ++-- {typesDTO => types/dto}/itemDTO.py | 0 types/enum/deposit_network.py | 8 ++ types/enum/token_type.py | 8 ++ utils/new_items_generator.py | 2 +- 9 files changed, 186 insertions(+), 63 deletions(-) create mode 100644 models/deposit.py create mode 100644 services/deposit.py rename {typesDTO => types/dto}/itemDTO.py (100%) create mode 100644 types/enum/deposit_network.py create mode 100644 types/enum/token_type.py diff --git a/crypto_api/CryptoApiManager.py b/crypto_api/CryptoApiManager.py index 5c457088..5d111898 100644 --- a/crypto_api/CryptoApiManager.py +++ b/crypto_api/CryptoApiManager.py @@ -1,16 +1,19 @@ +from datetime import datetime, timedelta import aiohttp import grequests import config +from services.deposit import DepositService class CryptoApiManager: - def __init__(self, btc_address, ltc_address, trx_address, eth_address): + def __init__(self, btc_address, ltc_address, trx_address, eth_address, user_id): self.btc_address = btc_address.strip() self.ltc_address = ltc_address.strip() self.trx_address = trx_address.strip() self.eth_address = eth_address.strip() + self.user_id = user_id @staticmethod async def fetch_api_request(url: str) -> dict: @@ -19,30 +22,73 @@ async def fetch_api_request(url: str) -> dict: if response.status == 200: data = await response.json() return data - else: - return 0.0 - async def get_btc_balance(self) -> float: - url = f'https://blockchain.info/rawaddr/{self.btc_address}' + async def get_btc_balance(self, deposits) -> float: + url = f'https://mempool.space/api/address/{self.btc_address}/utxo' data = await self.fetch_api_request(url) - return float(data['total_received']) / 100_000_000 + deposits = [deposit.tx_id for deposit in deposits if deposit.network == "BTC"] + deposit_sum = 0.0 + for deposit in data: + if deposit["txid"] not in deposits and deposit['status']['confirmed']: + await DepositService.create(deposit['txid'], self.user_id, "BTC", None, deposit["value"]) + deposit_sum += float(deposit["value"]) / 100_000_000 + return deposit_sum - async def get_ltc_balance(self) -> float: - url = f"https://api.blockcypher.com/v1/ltc/main/addrs/{self.ltc_address}" + async def get_ltc_balance(self, deposits) -> float: + url = f"https://api.blockcypher.com/v1/ltc/main/addrs/{self.ltc_address}?unspendOnly=true" data = await self.fetch_api_request(url) - return float(data['total_received']) / 100_000_000 + deposits = [deposit.tx_id for deposit in deposits if deposit.network == "LTC"] + deposits_sum = 0.0 + if data['n_tx'] > 0: + for deposit in data['txrefs']: + if deposit["confirmations"] > 0 and deposit['tx_hash'] not in deposits: + await DepositService.create(deposit['tx_hash'], self.user_id, "LTC", None, deposit["value"]) + deposits_sum += float(deposit['value']) / 100_000_000 + return deposits_sum - async def get_trx_and_trc_20_balance(self) -> dict: - url = f'https://apilist.tronscan.org/api/account?address={self.trx_address}&includeToken=true' + async def get_usdt_trc20_balance(self, deposits) -> float: + now = datetime.now() + earlier_time = now - timedelta(hours=6) + min_timestamp = int(earlier_time.timestamp() * 1000) + url = f"https://api.trongrid.io/v1/accounts/{self.trx_address}/transactions/trc20?only_confirmed=true&min_timestamp={min_timestamp}&contract_address=TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t&only_to=true" data = await self.fetch_api_request(url) - trx_account_data = {'trx_balance': float(data["balance"]) / 1_000_000, - 'in_transactions': data["transactions_in"]} - for token in data['trc20token_balances']: - if token['tokenId'] == 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t': - trx_account_data['usdt_balance'] = round(float(token['balance']) * pow(10, -token['tokenDecimal']), 6) - elif token['tokenId'] == 'TPYmHEhy5n8TCEfYGqW2rPxsghSfzghPDn': - trx_account_data['usdd_balance'] = round(float(token['balance']) * pow(10, -token['tokenDecimal']), 6) - return trx_account_data + deposits = [deposits.tx_id for deposit in deposits if + deposit.network == "TRX" and deposit.token_name == "USDT_TRC20"] + deposits_sum = 0.0 + for deposit in data['data']: + if deposit['transaction_id'] not in deposits: + await DepositService.create(deposit['transaction_id'], self.user_id, "TRX", + "USDT_TRC20", deposit['value']) + deposits_sum += float(deposit['value']) / pow(10, deposit['token_info']['decimals']) + return deposits_sum + + async def get_usdd_trc20_balance(self, deposits) -> float: + now = datetime.now() + earlier_time = now - timedelta(hours=6) + min_timestamp = int(earlier_time.timestamp() * 1000) + url = f"https://api.trongrid.io/v1/accounts/{self.trx_address}/transactions/trc20?only_confirmed=true&min_timestamp={min_timestamp}&contract_address=TPYmHEhy5n8TCEfYGqW2rPxsghSfzghPDn&only_to=true" + data = await self.fetch_api_request(url) + deposits = [deposits.tx_id for deposit in deposits if + deposit.network == "TRX" and deposit.token_name == "USDT_TRC20"] + deposits_sum = 0.0 + for deposit in data['data']: + if deposit['transaction_id'] not in deposits: + await DepositService.create(deposit['transaction_id'], self.user_id, "TRX", + "USDD_TRC20", deposit['value']) + deposits_sum += float(deposit['value']) / pow(10, deposit['token_info']['decimals']) + return deposits_sum + + async def get_trx_balance(self, deposits) -> float: + url = f'https://apilist.tronscanapi.com/api/new/transfer?sort=-timestamp&count=true&limit=100&start=0&address={self.trx_address}' + data = await self.fetch_api_request(url) + deposits = [deposits.tx_id for deposit in deposits if deposit.network == "TRX" and deposit.token_name is None] + deposit_sum = 0.0 + for deposit in data['data']: + if deposit['confirmed'] and deposit['transactionHash'] not in deposits and deposit[ + 'transferToAddress'] == self.trx_address: + await DepositService.create(deposit['transactionHash'], self.user_id, "TRX", None, deposit['amount']) + deposit_sum += float(deposit['amount'] / pow(10, deposit['tokenInfo']['tokenDecimal'])) + return deposit_sum async def get_eth_and_erc_20_balance(self) -> dict: url = f'https://api.ethplorer.io/getAddressInfo/{self.eth_address}?apiKey={config.ETHPLORER_API_KEY}' @@ -51,37 +97,44 @@ async def get_eth_and_erc_20_balance(self) -> dict: } async def get_top_ups(self): - urls = { - "btc_balance": f'https://blockchain.info/rawaddr/{self.btc_address}', - "usdt_balance": f'https://apilist.tronscan.org/api/account?address={self.trx_address}&includeToken=true', - "ltc_balance": f'https://api.blockcypher.com/v1/ltc/main/addrs/{self.ltc_address}', - # "eth_balance": "" - } - balances = {} - rs = (grequests.get(url) for url in urls.values()) - data_list = grequests.map(rs) - - for symbol, data in zip(urls.keys(), data_list): - response_code = data.status_code - if response_code != 200: - balances[symbol] = 0 - else: - data = data.json() - if 'total_received' in data: - balance = float(data['total_received']) / 100000000 - balances[symbol] = balance - else: - usdt_balance = None - for token in data['trc20token_balances']: - if token['tokenName'] == 'Tether USD': - usdt_balance = round(float(token['balance']) * pow(10, -token['tokenDecimal']), 6) - break - if usdt_balance is not None: - balances[symbol] = usdt_balance - else: - balances[symbol] = 0.0 - - return balances + user_deposits = await DepositService.get_by_user_id(self.user_id) + balances = {"btc_deposit": await self.get_btc_balance(user_deposits), + "ltc_deposit": await self.get_ltc_balance(user_deposits), + "usdt_trc20_deposit": await self.get_usdt_trc20_balance(user_deposits), + "usdd_trc20_deposit": await self.get_usdd_trc20_balance(user_deposits), + "trx_deposit": await self.get_trx_balance(user_deposits)} + print(balances) + # urls = { + # "btc_balance": f'https://blockchain.info/rawaddr/{self.btc_address}', + # "usdt_balance": f'https://apilist.tronscan.org/api/account?address={self.trx_address}&includeToken=true', + # "ltc_balance": f'https://api.blockcypher.com/v1/ltc/main/addrs/{self.ltc_address}', + # # "eth_balance": "" + # } + # balances = {} + # rs = (grequests.get(url) for url in urls.values()) + # data_list = grequests.map(rs) + # + # for symbol, data in zip(urls.keys(), data_list): + # response_code = data.status_code + # if response_code != 200: + # balances[symbol] = 0 + # else: + # data = data.json() + # if 'total_received' in data: + # balance = float(data['total_received']) / 100000000 + # balances[symbol] = balance + # else: + # usdt_balance = None + # for token in data['trc20token_balances']: + # if token['tokenName'] == 'Tether USD': + # usdt_balance = round(float(token['balance']) * pow(10, -token['tokenDecimal']), 6) + # break + # if usdt_balance is not None: + # balances[symbol] = usdt_balance + # else: + # balances[symbol] = 0.0 + # + # return balances @staticmethod async def get_crypto_prices() -> dict[str, float]: diff --git a/handlers/user/my_profile.py b/handlers/user/my_profile.py index fd893dad..5c8a5bac 100644 --- a/handlers/user/my_profile.py +++ b/handlers/user/my_profile.py @@ -156,12 +156,14 @@ async def purchase_history(callback: CallbackQuery): async def refresh_balance(callback: CallbackQuery): telegram_id = callback.from_user.id - if await UserService.can_refresh_balance(telegram_id): + # if await UserService.can_refresh_balance(telegram_id): + if True: await callback.answer(Localizator.get_text_from_key("balance_refreshing")) old_crypto_balances = await UserService.get_balances(telegram_id) await UserService.create_last_balance_refresh_data(telegram_id) + user = await UserService.get_by_tgid(telegram_id) addresses = await UserService.get_addresses(telegram_id) - new_crypto_balances = await CryptoApiManager(**addresses).get_top_ups() + new_crypto_balances = await CryptoApiManager(**addresses, user_id=user.id).get_top_ups() crypto_prices = await CryptoApiManager.get_crypto_prices() deposit_usd_amount = 0.0 bot_obj = callback.bot diff --git a/models/deposit.py b/models/deposit.py new file mode 100644 index 00000000..b2d88a80 --- /dev/null +++ b/models/deposit.py @@ -0,0 +1,15 @@ +from sqlalchemy import Integer, Column, String, ForeignKey, Float +from sqlalchemy.orm import relationship, backref + +from models.base import Base + + +class Deposit(Base): + __tablename__ = 'deposits' + id = Column(Integer, primary_key=True) + tx_id = Column(String, nullable=False, unique=True) + user_id = Column(Integer, ForeignKey('users.id'), nullable=False) + user = relationship("User", backref=backref("users",lazy="joined")) + network = Column(String, nullable=False) + token_name = Column(String, nullable=True) + amount = Column(Float, nullable=False) diff --git a/services/deposit.py b/services/deposit.py new file mode 100644 index 00000000..fbc70a44 --- /dev/null +++ b/services/deposit.py @@ -0,0 +1,40 @@ +from sqlalchemy import select + +from db import async_session_maker +from models.deposit import Deposit + + +class DepositService: + + @staticmethod + async def get_next_user_id() -> int: + async with async_session_maker() as session: + query = select(Deposit.id).order_by(Deposit.id.desc()).limit(1) + last_user_id = await session.execute(query) + last_user_id = last_user_id.scalar() + if last_user_id is None: + return 0 + else: + return int(last_user_id) + 1 + + @staticmethod + async def create(tx_id, user_id, network, token_name, amount): + async with async_session_maker() as session: + next_deposit_id = await DepositService.get_next_user_id() + dep = Deposit(id=next_deposit_id, + user_id=user_id, + tx_id=tx_id, + network=network, + token_name=token_name, + amount=amount) + session.add(dep) + await session.commit() + return next_deposit_id + + @staticmethod + async def get_by_user_id(user_id): + async with async_session_maker() as session: + stmt = select(Deposit).where(Deposit.user_id == user_id) + deposits = await session.execute(stmt) + deposits = deposits.scalars().all() + return deposits diff --git a/services/user.py b/services/user.py index cbdb9e9d..9da5059c 100644 --- a/services/user.py +++ b/services/user.py @@ -6,6 +6,8 @@ import config from db import async_session_maker +from models.eth_account import EthAccount +from models.trx_account import TrxAccount from models.user import User from services.eth_account import EthAccountService @@ -68,15 +70,11 @@ async def update_username(telegram_id: int, telegram_username: str): async def get_by_tgid(telegram_id: int) -> User: async with async_session_maker() as session: stmt = ( - select(User) - .options( - joinedload(User.eth_account), - joinedload(User.trx_account) - ) + select(User).join(EthAccount, User.eth_account_id == EthAccount.id).join(TrxAccount, User.trx_account_id == TrxAccount.id) .where(User.telegram_id == telegram_id) ) user_from_db = await session.execute(stmt) - user_from_db = user_from_db.scalar_one_or_none() # Use scalar_one_or_none to handle None case + user_from_db = user_from_db.scalar() return user_from_db @staticmethod @@ -102,9 +100,8 @@ async def create_last_balance_refresh_data(telegram_id: int): @staticmethod async def get_balances(telegram_id: int) -> dict: - async with (async_session_maker() as session): - stmt = select(User).options(joinedload(User.eth_account), - joinedload(User.trx_account)).where( + async with async_session_maker() as session: + stmt = select(User).join(EthAccount, User.eth_account_id==EthAccount.id).join(TrxAccount, User.trx_account_id == TrxAccount.id).where( User.telegram_id == telegram_id) user_balances = await session.execute(stmt) user_balances = user_balances.scalar() @@ -129,7 +126,7 @@ async def get_addresses(telegram_id: int) -> dict: user_addresses = await session.execute(stmt) user_addresses = user_addresses.scalar() user_addresses = [user_addresses.btc_address, user_addresses.ltc_address, - user_addresses.trx_account.address,user_addresses.eth_account.address] + user_addresses.trx_account.address, user_addresses.eth_account.address] keys = ["btc_address", "ltc_address", "trx_address", "eth_address"] user_addresses = dict(zip(keys, user_addresses)) return user_addresses diff --git a/typesDTO/itemDTO.py b/types/dto/itemDTO.py similarity index 100% rename from typesDTO/itemDTO.py rename to types/dto/itemDTO.py diff --git a/types/enum/deposit_network.py b/types/enum/deposit_network.py new file mode 100644 index 00000000..ae11f54f --- /dev/null +++ b/types/enum/deposit_network.py @@ -0,0 +1,8 @@ +from enum import Enum + + +class DepositNetwork(Enum): + BTC = "BTC" + LTC = "LTC" + TRX = "TRX" + ETH = "ETH" diff --git a/types/enum/token_type.py b/types/enum/token_type.py new file mode 100644 index 00000000..20f15ea6 --- /dev/null +++ b/types/enum/token_type.py @@ -0,0 +1,8 @@ +from enum import Enum + + +class TokenType(Enum): + USDT_TRC20 = "USDT_TRC20" + USDD_TRC20 = "USDD_TRC20" + USDT_ERC20 = "USDT_ERC20" + USDC_ERC20 = "USDC_ERC20" diff --git a/utils/new_items_generator.py b/utils/new_items_generator.py index a048582b..eb00ea9c 100644 --- a/utils/new_items_generator.py +++ b/utils/new_items_generator.py @@ -1,6 +1,6 @@ import json -from typesDTO.itemDTO import ItemDTO +from typesDTO.dto.itemDTO import ItemDTO from dataclasses import asdict From 694af314a55c6c277aab7f10e45af724804ad19a Mon Sep 17 00:00:00 2001 From: ilyarolf Date: Sat, 28 Sep 2024 17:30:08 +0300 Subject: [PATCH 06/17] implementing eth/trx and erc-20/trc-20 refresh balance functionality --- crypto_api/CryptoApiManager.py | 38 ++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/crypto_api/CryptoApiManager.py b/crypto_api/CryptoApiManager.py index 5d111898..7a3221e7 100644 --- a/crypto_api/CryptoApiManager.py +++ b/crypto_api/CryptoApiManager.py @@ -79,7 +79,7 @@ async def get_usdd_trc20_balance(self, deposits) -> float: return deposits_sum async def get_trx_balance(self, deposits) -> float: - url = f'https://apilist.tronscanapi.com/api/new/transfer?sort=-timestamp&count=true&limit=100&start=0&address={self.trx_address}' + url = f'https://apilist.tronscanapi.com/api/new/transfer?sort=-timestamp&count=true&limit=1000&start=0&address={self.trx_address}' data = await self.fetch_api_request(url) deposits = [deposits.tx_id for deposit in deposits if deposit.network == "TRX" and deposit.token_name is None] deposit_sum = 0.0 @@ -90,11 +90,35 @@ async def get_trx_balance(self, deposits) -> float: deposit_sum += float(deposit['amount'] / pow(10, deposit['tokenInfo']['tokenDecimal'])) return deposit_sum - async def get_eth_and_erc_20_balance(self) -> dict: - url = f'https://api.ethplorer.io/getAddressInfo/{self.eth_address}?apiKey={config.ETHPLORER_API_KEY}' + async def get_usdt_erc20_balance(self, deposits) -> float: + url = f'https://api.ethplorer.io/getAddressHistory/{self.eth_address}?type=transfer&token=0xdAC17F958D2ee523a2206206994597C13D831ec7&apiKey={config.ETHPLORER_API_KEY}&limit=1000' data = await self.fetch_api_request(url) - eth_account_data = {"eth_balance": data["ETH"]["balance"], - } + deposits = [deposits.tx_id for deposit in deposits if + deposit.network == "ETH" and deposit.token_name == "USDT_ERC20"] + deposits_sum = 0.0 + for deposit in data['operations']: + if deposit['transactionHash'] not in deposits and deposit['to'] == self.eth_address: + await DepositService.create(deposit['transactionHash'], self.user_id, "ETH", "USDT_ERC20", + deposit['value']) + deposits_sum += float(deposit['value']) / pow(10, 6) + return deposits_sum + + async def get_usdc_erc20_balance(self, deposits): + url = f'https://api.ethplorer.io/getAddressHistory/{self.eth_address}?type=transfer&token=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48&apiKey={config.ETHPLORER_API_KEY}&limit=1000' + data = await self.fetch_api_request(url) + deposits = [deposits.tx_id for deposit in deposits if + deposit.network == "ETH" and deposit.token_name == "USDC_ERC20"] + deposits_sum = 0.0 + for deposit in data['operations']: + if deposit['transactionHash'] not in deposits and deposit['to'] == self.eth_address: + await DepositService.create(deposit['transactionHash'], self.user_id, "ETH", "USDC_ERC20", + deposit['value']) + deposits_sum += float(deposit['value']) / pow(10, 6) + return deposits_sum + + async def get_eth_balance(self, deposits): + # TODO (fetch eth deposits) + url = f'' async def get_top_ups(self): user_deposits = await DepositService.get_by_user_id(self.user_id) @@ -102,7 +126,9 @@ async def get_top_ups(self): "ltc_deposit": await self.get_ltc_balance(user_deposits), "usdt_trc20_deposit": await self.get_usdt_trc20_balance(user_deposits), "usdd_trc20_deposit": await self.get_usdd_trc20_balance(user_deposits), - "trx_deposit": await self.get_trx_balance(user_deposits)} + "trx_deposit": await self.get_trx_balance(user_deposits), + "usdt_erc20_deposit": await self.get_usdt_erc20_balance(user_deposits), + "usdc_erc20_deposit": await self.get_usdc_erc20_balance(user_deposits)} print(balances) # urls = { # "btc_balance": f'https://blockchain.info/rawaddr/{self.btc_address}', From eb5fb1a1f704b6830acde0c0c472385065db0d0e Mon Sep 17 00:00:00 2001 From: ilyarolf Date: Sun, 29 Sep 2024 19:03:33 +0300 Subject: [PATCH 07/17] implementing erc-20/trc-20 refresh balance functionality --- crypto_api/CryptoApiManager.py | 79 +++++++++++----------------------- handlers/user/my_profile.py | 18 ++++---- services/eth_account.py | 7 +++ services/trx_account.py | 7 +++ services/user.py | 22 +++++++--- utils/notification_manager.py | 14 +++--- 6 files changed, 71 insertions(+), 76 deletions(-) diff --git a/crypto_api/CryptoApiManager.py b/crypto_api/CryptoApiManager.py index 7a3221e7..ef48d1e2 100644 --- a/crypto_api/CryptoApiManager.py +++ b/crypto_api/CryptoApiManager.py @@ -14,6 +14,8 @@ def __init__(self, btc_address, ltc_address, trx_address, eth_address, user_id): self.trx_address = trx_address.strip() self.eth_address = eth_address.strip() self.user_id = user_id + # self.min_timestamp = 0 + self.min_timestamp = int((datetime.now() - timedelta(hours=6)).timestamp()) * 1000 @staticmethod async def fetch_api_request(url: str) -> dict: @@ -47,12 +49,9 @@ async def get_ltc_balance(self, deposits) -> float: return deposits_sum async def get_usdt_trc20_balance(self, deposits) -> float: - now = datetime.now() - earlier_time = now - timedelta(hours=6) - min_timestamp = int(earlier_time.timestamp() * 1000) - url = f"https://api.trongrid.io/v1/accounts/{self.trx_address}/transactions/trc20?only_confirmed=true&min_timestamp={min_timestamp}&contract_address=TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t&only_to=true" + url = f"https://api.trongrid.io/v1/accounts/{self.trx_address}/transactions/trc20?only_confirmed=true&min_timestamp={self.min_timestamp}&contract_address=TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t&only_to=true" data = await self.fetch_api_request(url) - deposits = [deposits.tx_id for deposit in deposits if + deposits = [deposit.tx_id for deposit in deposits if deposit.network == "TRX" and deposit.token_name == "USDT_TRC20"] deposits_sum = 0.0 for deposit in data['data']: @@ -63,13 +62,10 @@ async def get_usdt_trc20_balance(self, deposits) -> float: return deposits_sum async def get_usdd_trc20_balance(self, deposits) -> float: - now = datetime.now() - earlier_time = now - timedelta(hours=6) - min_timestamp = int(earlier_time.timestamp() * 1000) - url = f"https://api.trongrid.io/v1/accounts/{self.trx_address}/transactions/trc20?only_confirmed=true&min_timestamp={min_timestamp}&contract_address=TPYmHEhy5n8TCEfYGqW2rPxsghSfzghPDn&only_to=true" + url = f"https://api.trongrid.io/v1/accounts/{self.trx_address}/transactions/trc20?only_confirmed=true&min_timestamp={self.min_timestamp}&contract_address=TPYmHEhy5n8TCEfYGqW2rPxsghSfzghPDn&only_to=true" data = await self.fetch_api_request(url) - deposits = [deposits.tx_id for deposit in deposits if - deposit.network == "TRX" and deposit.token_name == "USDT_TRC20"] + deposits = [deposit.tx_id for deposit in deposits if + deposit.network == "TRX" and deposit.token_name == "USDD_TRC20"] deposits_sum = 0.0 for deposit in data['data']: if deposit['transaction_id'] not in deposits: @@ -81,19 +77,19 @@ async def get_usdd_trc20_balance(self, deposits) -> float: async def get_trx_balance(self, deposits) -> float: url = f'https://apilist.tronscanapi.com/api/new/transfer?sort=-timestamp&count=true&limit=1000&start=0&address={self.trx_address}' data = await self.fetch_api_request(url) - deposits = [deposits.tx_id for deposit in deposits if deposit.network == "TRX" and deposit.token_name is None] + deposits = [deposit.tx_id for deposit in deposits if deposit.network == "TRX" and deposit.token_name is None] deposit_sum = 0.0 for deposit in data['data']: if deposit['confirmed'] and deposit['transactionHash'] not in deposits and deposit[ 'transferToAddress'] == self.trx_address: await DepositService.create(deposit['transactionHash'], self.user_id, "TRX", None, deposit['amount']) - deposit_sum += float(deposit['amount'] / pow(10, deposit['tokenInfo']['tokenDecimal'])) + deposit_sum += deposit['amount'] / pow(10, deposit['tokenInfo']['tokenDecimal']) return deposit_sum async def get_usdt_erc20_balance(self, deposits) -> float: url = f'https://api.ethplorer.io/getAddressHistory/{self.eth_address}?type=transfer&token=0xdAC17F958D2ee523a2206206994597C13D831ec7&apiKey={config.ETHPLORER_API_KEY}&limit=1000' data = await self.fetch_api_request(url) - deposits = [deposits.tx_id for deposit in deposits if + deposits = [deposit.tx_id for deposit in deposits if deposit.network == "ETH" and deposit.token_name == "USDT_ERC20"] deposits_sum = 0.0 for deposit in data['operations']: @@ -106,7 +102,7 @@ async def get_usdt_erc20_balance(self, deposits) -> float: async def get_usdc_erc20_balance(self, deposits): url = f'https://api.ethplorer.io/getAddressHistory/{self.eth_address}?type=transfer&token=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48&apiKey={config.ETHPLORER_API_KEY}&limit=1000' data = await self.fetch_api_request(url) - deposits = [deposits.tx_id for deposit in deposits if + deposits = [deposit.tx_id for deposit in deposits if deposit.network == "ETH" and deposit.token_name == "USDC_ERC20"] deposits_sum = 0.0 for deposit in data['operations']: @@ -118,50 +114,27 @@ async def get_usdc_erc20_balance(self, deposits): async def get_eth_balance(self, deposits): # TODO (fetch eth deposits) - url = f'' + url = f'https://api.ethplorer.io/getAddressTransactions/{self.eth_address}?limit=1000&showZeroValues=false&apiKey={config.ETHPLORER_API_KEY}' + data = await self.fetch_api_request(url) + deposits = [deposit.tx_id for deposit in deposits if + deposit.network == "ETH" and deposit.token_name is None] + deposits_sum = 0.0 + for deposit in data: + if deposit['hash'] not in deposits and deposit['success'] is True and deposit['to'] == self.eth_address: + await DepositService.create(deposit['hash'], self.user_id, "ETH", None, + deposit['value'] * pow(10, 9)) + deposits_sum += deposit['value'] + return deposits_sum async def get_top_ups(self): user_deposits = await DepositService.get_by_user_id(self.user_id) - balances = {"btc_deposit": await self.get_btc_balance(user_deposits), - "ltc_deposit": await self.get_ltc_balance(user_deposits), + balances = {"btc__deposit": await self.get_btc_balance(user_deposits), + "ltc__deposit": await self.get_ltc_balance(user_deposits), "usdt_trc20_deposit": await self.get_usdt_trc20_balance(user_deposits), "usdd_trc20_deposit": await self.get_usdd_trc20_balance(user_deposits), - "trx_deposit": await self.get_trx_balance(user_deposits), "usdt_erc20_deposit": await self.get_usdt_erc20_balance(user_deposits), "usdc_erc20_deposit": await self.get_usdc_erc20_balance(user_deposits)} - print(balances) - # urls = { - # "btc_balance": f'https://blockchain.info/rawaddr/{self.btc_address}', - # "usdt_balance": f'https://apilist.tronscan.org/api/account?address={self.trx_address}&includeToken=true', - # "ltc_balance": f'https://api.blockcypher.com/v1/ltc/main/addrs/{self.ltc_address}', - # # "eth_balance": "" - # } - # balances = {} - # rs = (grequests.get(url) for url in urls.values()) - # data_list = grequests.map(rs) - # - # for symbol, data in zip(urls.keys(), data_list): - # response_code = data.status_code - # if response_code != 200: - # balances[symbol] = 0 - # else: - # data = data.json() - # if 'total_received' in data: - # balance = float(data['total_received']) / 100000000 - # balances[symbol] = balance - # else: - # usdt_balance = None - # for token in data['trc20token_balances']: - # if token['tokenName'] == 'Tether USD': - # usdt_balance = round(float(token['balance']) * pow(10, -token['tokenDecimal']), 6) - # break - # if usdt_balance is not None: - # balances[symbol] = usdt_balance - # else: - # balances[symbol] = 0.0 - # - # return balances - + return balances @staticmethod async def get_crypto_prices() -> dict[str, float]: # TODO("NEED API FOR USDD-TRC-20") @@ -180,5 +153,5 @@ async def get_crypto_prices() -> dict[str, float]: data = data.json() price = float(next(iter(data['result'].values()))['l'][1]) usd_crypto_prices[symbol] = price - usd_crypto_prices["usdd"] = 1 # 1USDD=1USD + usd_crypto_prices["usdd"] = 1.0 # 1USDD=1USD return usd_crypto_prices diff --git a/handlers/user/my_profile.py b/handlers/user/my_profile.py index 5c8a5bac..7c29ae8a 100644 --- a/handlers/user/my_profile.py +++ b/handlers/user/my_profile.py @@ -156,27 +156,25 @@ async def purchase_history(callback: CallbackQuery): async def refresh_balance(callback: CallbackQuery): telegram_id = callback.from_user.id - # if await UserService.can_refresh_balance(telegram_id): - if True: + if await UserService.can_refresh_balance(telegram_id): + # if True: await callback.answer(Localizator.get_text_from_key("balance_refreshing")) - old_crypto_balances = await UserService.get_balances(telegram_id) await UserService.create_last_balance_refresh_data(telegram_id) user = await UserService.get_by_tgid(telegram_id) addresses = await UserService.get_addresses(telegram_id) - new_crypto_balances = await CryptoApiManager(**addresses, user_id=user.id).get_top_ups() + new_crypto_deposits = await CryptoApiManager(**addresses, user_id=user.id).get_top_ups() crypto_prices = await CryptoApiManager.get_crypto_prices() deposit_usd_amount = 0.0 bot_obj = callback.bot - if sum(new_crypto_balances.values()) > sum(old_crypto_balances.values()): - merged_deposit = {key: new_crypto_balances[key] - old_crypto_balances[key] for key in - new_crypto_balances.keys()} - for balance_key, balance in merged_deposit.items(): + if sum(new_crypto_deposits.values()) > 0: + for balance_key, balance in new_crypto_deposits.items(): balance_key = balance_key.split('_')[0] crypto_balance_in_usd = balance * crypto_prices[balance_key] deposit_usd_amount += crypto_balance_in_usd - await UserService.update_crypto_balances(telegram_id, new_crypto_balances) + await UserService.update_crypto_balances(telegram_id, user.eth_account_id, + user.trx_account_id, new_crypto_deposits) await UserService.update_top_up_amount(telegram_id, deposit_usd_amount * 0.95) - await NotificationManager.new_deposit(old_crypto_balances, new_crypto_balances, deposit_usd_amount, + await NotificationManager.new_deposit(new_crypto_deposits, deposit_usd_amount, telegram_id, bot_obj) await my_profile(callback) else: diff --git a/services/eth_account.py b/services/eth_account.py index 77944206..5621e663 100644 --- a/services/eth_account.py +++ b/services/eth_account.py @@ -26,3 +26,10 @@ async def create(address: str) -> int: session.add(eth_acc) await session.commit() return next_account_id + + @staticmethod + async def get_by_id(id: int) -> EthAccount: + async with async_session_maker() as session: + stmt = select(EthAccount).where(EthAccount.id == id) + result = await session.execute(stmt) + return result.scalar_one() diff --git a/services/trx_account.py b/services/trx_account.py index 30c1897a..6268e9f2 100644 --- a/services/trx_account.py +++ b/services/trx_account.py @@ -26,3 +26,10 @@ async def create(address: str) -> int: session.add(eth_acc) await session.commit() return next_account_id + + @staticmethod + async def get_by_id(id: int) -> TrxAccount: + async with async_session_maker() as session: + stmt = select(TrxAccount).where(TrxAccount.id == id) + result = await session.execute(stmt) + return result.scalar_one() diff --git a/services/user.py b/services/user.py index 9da5059c..52fd0aad 100644 --- a/services/user.py +++ b/services/user.py @@ -70,7 +70,8 @@ async def update_username(telegram_id: int, telegram_username: str): async def get_by_tgid(telegram_id: int) -> User: async with async_session_maker() as session: stmt = ( - select(User).join(EthAccount, User.eth_account_id == EthAccount.id).join(TrxAccount, User.trx_account_id == TrxAccount.id) + select(User).join(EthAccount, User.eth_account_id == EthAccount.id).join(TrxAccount, + User.trx_account_id == TrxAccount.id) .where(User.telegram_id == telegram_id) ) user_from_db = await session.execute(stmt) @@ -101,7 +102,8 @@ async def create_last_balance_refresh_data(telegram_id: int): @staticmethod async def get_balances(telegram_id: int) -> dict: async with async_session_maker() as session: - stmt = select(User).join(EthAccount, User.eth_account_id==EthAccount.id).join(TrxAccount, User.trx_account_id == TrxAccount.id).where( + stmt = select(User).join(EthAccount, User.eth_account_id == EthAccount.id).join(TrxAccount, + User.trx_account_id == TrxAccount.id).where( User.telegram_id == telegram_id) user_balances = await session.execute(stmt) user_balances = user_balances.scalar() @@ -132,14 +134,22 @@ async def get_addresses(telegram_id: int) -> dict: return user_addresses @staticmethod - async def update_crypto_balances(telegram_id: int, new_crypto_balances: dict): + async def update_crypto_balances(telegram_id: int, eth_account_id: int, trx_account_id: int, + new_crypto_balances: dict): async with async_session_maker() as session: stmt = update(User).where(User.telegram_id == telegram_id).values( - btc_balance=new_crypto_balances["btc_balance"], - ltc_balance=new_crypto_balances["ltc_balance"], - usdt_balance=new_crypto_balances["usdt_balance"], + btc_balance=new_crypto_balances["btc__deposit"], + ltc_balance=new_crypto_balances["ltc__deposit"] ) await session.execute(stmt) + stmt = update(EthAccount).where(EthAccount.id == eth_account_id).values( + usdt_balance=new_crypto_balances['usdt_erc20_deposit'], + usdc_balance=new_crypto_balances['usdc_erc20_deposit']) + await session.execute(stmt) + stmt = update(TrxAccount).where(TrxAccount.id == trx_account_id).values( + usdt_balance=new_crypto_balances['usdt_trc20_deposit'], + usdd_balance=new_crypto_balances['usdd_trc20_deposit']) + await session.execute(stmt) await session.commit() @staticmethod diff --git a/utils/notification_manager.py b/utils/notification_manager.py index a27dfab8..c7f626d6 100644 --- a/utils/notification_manager.py +++ b/utils/notification_manager.py @@ -4,7 +4,9 @@ from aiogram import types from aiogram.utils.keyboard import InlineKeyboardBuilder +from services.eth_account import EthAccountService from services.subcategory import SubcategoryService +from services.trx_account import TrxAccountService from services.user import UserService from config import ADMIN_ID_LIST from models.user import User @@ -40,16 +42,15 @@ async def make_user_button(username: Union[str, None]): return user_button_builder.as_markup() @staticmethod - async def new_deposit(old_crypto_balances: dict, new_crypto_balances: dict, deposit_amount_usd, telegram_id: int, + async def new_deposit(new_crypto_balances: dict, deposit_amount_usd, telegram_id: int, bot): deposit_amount_usd = round(deposit_amount_usd, 2) - merged_crypto_balances = [new_balance - old_balance for (new_balance, old_balance) in - zip(new_crypto_balances.values(), - old_crypto_balances.values())] merged_crypto_balances_keys = [key.split('_')[0] for key in new_crypto_balances.keys()] - merged_crypto_balances = zip(merged_crypto_balances_keys, merged_crypto_balances) + merged_crypto_balances = zip(merged_crypto_balances_keys, new_crypto_balances.values()) user = await UserService.get_by_tgid(telegram_id) user = user.__dict__ + trx_account = await TrxAccountService.get_by_id(user['trx_account_id']) + eth_account = await EthAccountService.get_by_id(user['eth_account_id']) username = user['telegram_username'] user_button = await NotificationManager.make_user_button(username) if username: @@ -66,8 +67,7 @@ async def new_deposit(old_crypto_balances: dict, new_crypto_balances: dict, depo message += Localizator.get_text_from_key("usdt_deposit_notification_part").format( value=value, crypto_name=crypto_name.upper(), - trx_address=user[ - 'trx_address']) + trx_address=trx_account.address) else: crypto_address = user[f'{crypto_name}_address'] message += Localizator.get_text_from_key("crypto_deposit_notification_part").format( From d3f5529636d1593fc4efeaea4e70e01fbb1de421 Mon Sep 17 00:00:00 2001 From: ilyarolf Date: Sun, 29 Sep 2024 22:49:20 +0300 Subject: [PATCH 08/17] implementing erc-20/trc-20 refresh balance functionality --- crypto_api/CryptoApiManager.py | 26 ++++++++ handlers/user/my_profile.py | 105 +++++++++++++++++++++++---------- l10n/en.json | 11 +++- models/eth_account.py | 12 ---- models/trx_account.py | 13 ---- models/user.py | 14 ++--- services/eth_account.py | 35 ----------- services/trx_account.py | 35 ----------- services/user.py | 59 ++++++------------ utils/notification_manager.py | 8 +-- 10 files changed, 134 insertions(+), 184 deletions(-) delete mode 100644 models/eth_account.py delete mode 100644 models/trx_account.py delete mode 100644 services/eth_account.py delete mode 100644 services/trx_account.py diff --git a/crypto_api/CryptoApiManager.py b/crypto_api/CryptoApiManager.py index ef48d1e2..b52fb03a 100644 --- a/crypto_api/CryptoApiManager.py +++ b/crypto_api/CryptoApiManager.py @@ -135,6 +135,32 @@ async def get_top_ups(self): "usdt_erc20_deposit": await self.get_usdt_erc20_balance(user_deposits), "usdc_erc20_deposit": await self.get_usdc_erc20_balance(user_deposits)} return balances + + async def get_top_up_by_crypto_name(self, crypto_name: str): + user_deposits = await DepositService.get_by_user_id(self.user_id) + + crypto_functions = { + "BTC": ("btc_deposit", self.get_btc_balance), + "LTC": ("ltc_deposit", self.get_ltc_balance), + "TRX_USDT": ("usdt_trc20_deposit", self.get_usdt_trc20_balance), + "TRX_USDD": ("usdd_trc20_deposit", self.get_usdd_trc20_balance), + "ETH_USDT": ("usdt_erc20_deposit", self.get_usdt_erc20_balance), + "ETH_USDC": ("usdc_erc20_deposit", self.get_usdc_erc20_balance), + } + + if "_" in crypto_name: + base, token = crypto_name.split('_') + else: + base, token = crypto_name, None + + key = f"{base}_{token}" if token else base + deposit_name, balance_func = crypto_functions.get(key, (None, None)) + + if deposit_name and balance_func: + return {deposit_name: await balance_func(user_deposits)} + + raise ValueError(f"Unsupported crypto name: {crypto_name}") + @staticmethod async def get_crypto_prices() -> dict[str, float]: # TODO("NEED API FOR USDD-TRC-20") diff --git a/handlers/user/my_profile.py b/handlers/user/my_profile.py index 7c29ae8a..99d042dc 100644 --- a/handlers/user/my_profile.py +++ b/handlers/user/my_profile.py @@ -43,10 +43,10 @@ class MyProfileConstants: async def get_my_profile_message(telegram_id: int): user = await UserService.get_by_tgid(telegram_id) btc_balance = user.btc_balance - usdt_trc20_balance = user.trx_account.usdt_balance - usdd_trc20_balance = user.trx_account.usdd_balance - usdt_erc20_balance = user.eth_account.usdt_balance - usdc_erc20_balance = user.eth_account.usdc_balance + usdt_trc20_balance = user.usdt_trc20_balance + usdd_trc20_balance = user.usdd_trc20_balance + usdt_erc20_balance = user.usdt_erc20_balance + usdc_erc20_balance = user.usdc_erc20_balance ltc_balance = user.ltc_balance usd_balance = round(user.top_up_amount - user.consume_records, 2) return Localizator.get_text_from_key("my_profile_msg").format(telegram_id=telegram_id, @@ -66,11 +66,12 @@ async def my_profile(message: Union[Message, CallbackQuery]): purchase_history_button = types.InlineKeyboardButton(text=Localizator.get_text_from_key("purchase_history_button"), callback_data=create_callback_profile(current_level + 2, "purchase_history")) - update_balance = types.InlineKeyboardButton(text=Localizator.get_text_from_key("refresh_balance_button"), - callback_data=create_callback_profile(current_level + 3, - "refresh_balance")) + # update_balance = types.InlineKeyboardButton(text=Localizator.get_text_from_key("refresh_balance_button"), + # callback_data=create_callback_profile(current_level + 3, + # "refresh_balance")) my_profile_builder = InlineKeyboardBuilder() - my_profile_builder.add(top_up_button, purchase_history_button, update_balance) + # my_profile_builder.add(top_up_button, purchase_history_button, update_balance) + my_profile_builder.add(top_up_button, purchase_history_button) my_profile_builder.adjust(2) my_profile_markup = my_profile_builder.as_markup() @@ -90,27 +91,34 @@ async def my_profile(message: Union[Message, CallbackQuery]): async def top_up_balance(callback: CallbackQuery): - telegram_id = callback.message.chat.id - user = await UserService.get_by_tgid(telegram_id) current_level = 1 - btc_address = user.btc_address - trx_address = user.trx_account.address - eth_address = user.eth_account.address - ltc_address = user.ltc_address back_to_profile_button = types.InlineKeyboardButton(text=Localizator.get_text_from_key("admin_back_button"), callback_data=create_callback_profile(current_level - 1)) - back_button_builder = InlineKeyboardBuilder() - back_button_builder.add(back_to_profile_button) - back_button_markup = back_button_builder.as_markup() - bot_entity = await callback.bot.get_me() + top_up_methods_builder = InlineKeyboardBuilder() + top_up_methods_builder.row(types.InlineKeyboardButton(text=Localizator.get_text_from_key("btc_top_up"), + callback_data=create_callback_profile(current_level + 1, + args_for_action="BTC"))) + top_up_methods_builder.row(types.InlineKeyboardButton(text=Localizator.get_text_from_key("ltc_top_up"), + callback_data=create_callback_profile(current_level + 1, + args_for_action="LTC"))) + top_up_methods_builder.row(types.InlineKeyboardButton(text=Localizator.get_text_from_key("usdt_trc20_top_up"), + callback_data=create_callback_profile(current_level + 1, + args_for_action="TRX_USDT"))) + top_up_methods_builder.row(types.InlineKeyboardButton(text=Localizator.get_text_from_key("usdd_trc20_top_up"), + callback_data=create_callback_profile(current_level + 1, + args_for_action="TRX_USDD"))) + top_up_methods_builder.row(types.InlineKeyboardButton(text=Localizator.get_text_from_key("usdt_erc20_top_up"), + callback_data=create_callback_profile(current_level + 1, + args_for_action="ETH_USDT"))) + top_up_methods_builder.row(types.InlineKeyboardButton(text=Localizator.get_text_from_key("usdc_trc20_top_up"), + callback_data=create_callback_profile(current_level + 1, + args_for_action="ETH_USDC"))) + top_up_methods_builder.row(back_to_profile_button) + await callback.message.edit_text( - text=Localizator.get_text_from_key("top_up_balance_msg").format(bot_name=bot_entity.first_name, - btc_address=btc_address, - trx_address=trx_address, - ltc_address=ltc_address, - eth_address=eth_address), + text=Localizator.get_text_from_key("choose_top_up_method"), parse_mode=ParseMode.HTML, - reply_markup=back_button_markup) + reply_markup=top_up_methods_builder.as_markup()) await callback.answer() @@ -149,20 +157,23 @@ async def purchase_history(callback: CallbackQuery): reply_markup=orders_markup_builder.as_markup(), parse_mode=ParseMode.HTML) else: - await callback.message.edit_text(Localizator.get_text_from_key("purchases"), reply_markup=orders_markup_builder.as_markup(), + await callback.message.edit_text(Localizator.get_text_from_key("purchases"), + reply_markup=orders_markup_builder.as_markup(), parse_mode=ParseMode.HTML) await callback.answer() async def refresh_balance(callback: CallbackQuery): telegram_id = callback.from_user.id - if await UserService.can_refresh_balance(telegram_id): - # if True: + unpacked_cb = MyProfileCallback.unpack(callback.data) + crypto_info = unpacked_cb.args_for_action + # if await UserService.can_refresh_balance(telegram_id): + if True: await callback.answer(Localizator.get_text_from_key("balance_refreshing")) await UserService.create_last_balance_refresh_data(telegram_id) user = await UserService.get_by_tgid(telegram_id) addresses = await UserService.get_addresses(telegram_id) - new_crypto_deposits = await CryptoApiManager(**addresses, user_id=user.id).get_top_ups() + new_crypto_deposits = await CryptoApiManager(**addresses, user_id=user.id).get_top_up_by_crypto_name(crypto_info) crypto_prices = await CryptoApiManager.get_crypto_prices() deposit_usd_amount = 0.0 bot_obj = callback.bot @@ -171,8 +182,7 @@ async def refresh_balance(callback: CallbackQuery): balance_key = balance_key.split('_')[0] crypto_balance_in_usd = balance * crypto_prices[balance_key] deposit_usd_amount += crypto_balance_in_usd - await UserService.update_crypto_balances(telegram_id, user.eth_account_id, - user.trx_account_id, new_crypto_deposits) + await UserService.update_crypto_balances(telegram_id, new_crypto_deposits) await UserService.update_top_up_amount(telegram_id, deposit_usd_amount * 0.95) await NotificationManager.new_deposit(new_crypto_deposits, deposit_usd_amount, telegram_id, bot_obj) @@ -193,6 +203,35 @@ async def get_order_from_history(callback: CallbackQuery): await callback.message.edit_text(text=message, parse_mode=ParseMode.HTML, reply_markup=back_builder.as_markup()) +async def top_up_by_method(callback: CallbackQuery): + unpacked_cb = MyProfileCallback.unpack(callback.data) + current_level = unpacked_cb.level + payment_method = unpacked_cb.args_for_action + addr = "" + user = await UserService.get_by_tgid(callback.from_user.id) + bot = await callback.bot.get_me() + if payment_method == "BTC": + addr = user.btc_address + elif payment_method == "LTC": + addr = user.ltc_address + elif "ETH" in payment_method : + addr = user.eth_address + elif "TRX" in payment_method: + addr = user.trx_address + msg = Localizator.get_text_from_key("top_up_balance_msg").format(bot_name=bot.first_name, + crypto_name=payment_method.split("_")[0], + addr=addr) + refresh_balance_builder = InlineKeyboardBuilder() + refresh_balance_builder.row(types.InlineKeyboardButton(text=Localizator.get_text_from_key("refresh_balance_button"), + callback_data=create_callback_profile(current_level + 1, + args_for_action=payment_method))) + refresh_balance_builder.row(types.InlineKeyboardButton(text=Localizator.get_text_from_key("admin_back_button"), + callback_data=create_callback_profile( + level=current_level - 1))) + await callback.message.edit_text(text=msg, parse_mode=ParseMode.HTML, + reply_markup=refresh_balance_builder.as_markup()) + + @my_profile_router.callback_query(MyProfileCallback.filter(), IsUserExistFilter()) async def navigate(callback: CallbackQuery, callback_data: MyProfileCallback): current_level = callback_data.level @@ -200,9 +239,11 @@ async def navigate(callback: CallbackQuery, callback_data: MyProfileCallback): levels = { 0: my_profile, 1: top_up_balance, - 2: purchase_history, + 2: top_up_by_method, 3: refresh_balance, - 4: get_order_from_history + + # 2: purchase_history, + # 4: get_order_from_history } current_level_function = levels[current_level] diff --git a/l10n/en.json b/l10n/en.json index e97dd627..4bb8529d 100644 --- a/l10n/en.json +++ b/l10n/en.json @@ -66,7 +66,7 @@ "top_up_balance_button": "Top Up balance", "purchase_history_button": "Purchase history", "refresh_balance_button": "Refresh balance", - "top_up_balance_msg": "Deposit to the address the amount you want to top up the {bot_name} \n\nImportant\nA unique BTC/LTC/USDT addresses is given for each deposit\nThe top up takes place within 5 minutes after the transfer\n\nYour BTC address\n{btc_address}\nYour USDT,USDD TRC-20 address\n{trx_address}\nYour USDT,USDC ERC-20 address\n{eth_address}\nYour LTC address\n{ltc_address}\n", + "top_up_balance_msg": "Deposit to the address the amount you want to top up the {bot_name} \n\nImportant\nA unique BTC/LTC/TRX/ETH addresses is given for each user\nThe top up takes place within 5 minutes after the transfer.\n\nAfter a successful balance refresh, the balance must change in your profile.\n\nYour {crypto_name} address\n{addr}", "purchase_history_item": "{subcategory_name} | Total Price: {total_price}$ | Quantity: {quantity} pcs", "no_purchases": "You haven't had any purchases yet", "purchases": "Your purchases:", @@ -82,5 +82,12 @@ "new_purchase_notification_with_username": "A new purchase by user with ID:{telegram_id} for the amount of ${total_price} for the purchase of a {quantity} pcs {subcategory_name}.", "new_items_message_update": "\uD83D\uDCC5 Update {update_data}\n", "new_items_message_category": "\n\uD83D\uDCC1 Category {category}\n\n", - "new_items_message_subcategory": "\uD83D\uDCC4 Subcategory {subcategory_name} {items_len} pcs\n" + "new_items_message_subcategory": "\uD83D\uDCC4 Subcategory {subcategory_name} {items_len} pcs\n", + "usdt_trc20_top_up":"USDT TRC-20", + "usdd_trc20_top_up":"USDD TRC-20", + "usdt_erc20_top_up":"USDT ERC-20", + "usdc_trc20_top_up":"USDC ERC-20", + "btc_top_up":"BTC", + "ltc_top_up":"LTC", + "choose_top_up_method": "Choose a top-up method:" } \ No newline at end of file diff --git a/models/eth_account.py b/models/eth_account.py deleted file mode 100644 index 7855a56a..00000000 --- a/models/eth_account.py +++ /dev/null @@ -1,12 +0,0 @@ -from models.base import Base -from sqlalchemy import Column, Integer, String, Float - - -class EthAccount(Base): - __tablename__ = 'eth_accounts' - - id = Column(Integer, primary_key=True) - address = Column(String, nullable=False, unique=True) - eth_balance = Column(Float, default=0.0) - usdt_balance = Column(Float, default=0.0) - usdc_balance = Column(Float, default=0.0) diff --git a/models/trx_account.py b/models/trx_account.py deleted file mode 100644 index 001a1ff3..00000000 --- a/models/trx_account.py +++ /dev/null @@ -1,13 +0,0 @@ -from sqlalchemy import Column, Integer, Float, String - -from models.base import Base - - -class TrxAccount(Base): - __tablename__ = 'trx_accounts' - - id = Column(Integer, primary_key=True) - address = Column(String, nullable=False, unique=True) - trx_balance = Column(Float, default=0.0) - usdt_balance = Column(Float, default=0.0) - usdd_balance = Column(Float, default=0.0) diff --git a/models/user.py b/models/user.py index a8531030..84a1bf95 100644 --- a/models/user.py +++ b/models/user.py @@ -12,19 +12,17 @@ class User(Base): telegram_id = Column(Integer, nullable=False) btc_address = Column(String, nullable=False, unique=True) ltc_address = Column(String, nullable=False, unique=True) + trx_address = Column(String, nullable=False, unique=True) + eth_address = Column(String, nullable=False, unique=True) last_balance_refresh = Column(DateTime) top_up_amount = Column(Float, default=0.0) consume_records = Column(Float, default=0.0) btc_balance = Column(Float, nullable=False, default=0.0) ltc_balance = Column(Float, nullable=False, default=0.0) - eth_account_id = Column(Integer, ForeignKey('eth_accounts.id'), nullable=False) - eth_account = relationship("EthAccount", backref=backref("eth_accounts", cascade="all"), - passive_deletes="all", - lazy="joined") - trx_account_id = Column(Integer, ForeignKey('trx_accounts.id'), nullable=False) - trx_account = relationship("TrxAccount", backref=backref("trx_accounts", cascade="all"), - passive_deletes="all", - lazy="joined") + usdt_trc20_balance = Column(Float, nullable=False, default=0.0) + usdd_trc20_balance = Column(Float, nullable=False, default=0.0) + usdt_erc20_balance = Column(Float, nullable=False, default=0.0) + usdc_erc20_balance = Column(Float, nullable=False, default=0.0) registered_at = Column(DateTime, default=func.now()) seed = Column(String, nullable=False, unique=True) can_receive_messages = Column(Boolean, default=True) diff --git a/services/eth_account.py b/services/eth_account.py deleted file mode 100644 index 5621e663..00000000 --- a/services/eth_account.py +++ /dev/null @@ -1,35 +0,0 @@ -from sqlalchemy import select - -from db import async_session_maker -from models.eth_account import EthAccount - - -class EthAccountService: - - @staticmethod - async def get_next_user_id() -> int: - async with async_session_maker() as session: - query = select(EthAccount.id).order_by(EthAccount.id.desc()).limit(1) - last_user_id = await session.execute(query) - last_user_id = last_user_id.scalar() - if last_user_id is None: - return 0 - else: - return int(last_user_id) + 1 - - @staticmethod - async def create(address: str) -> int: - async with async_session_maker() as session: - next_account_id = await EthAccountService.get_next_user_id() - eth_acc = EthAccount(id=next_account_id, - address=address) - session.add(eth_acc) - await session.commit() - return next_account_id - - @staticmethod - async def get_by_id(id: int) -> EthAccount: - async with async_session_maker() as session: - stmt = select(EthAccount).where(EthAccount.id == id) - result = await session.execute(stmt) - return result.scalar_one() diff --git a/services/trx_account.py b/services/trx_account.py deleted file mode 100644 index 6268e9f2..00000000 --- a/services/trx_account.py +++ /dev/null @@ -1,35 +0,0 @@ -from sqlalchemy import select - -from db import async_session_maker -from models.trx_account import TrxAccount - - -class TrxAccountService: - - @staticmethod - async def get_next_user_id() -> int: - async with async_session_maker() as session: - query = select(TrxAccount.id).order_by(TrxAccount.id.desc()).limit(1) - last_user_id = await session.execute(query) - last_user_id = last_user_id.scalar() - if last_user_id is None: - return 0 - else: - return int(last_user_id) + 1 - - @staticmethod - async def create(address: str) -> int: - async with async_session_maker() as session: - next_account_id = await TrxAccountService.get_next_user_id() - eth_acc = TrxAccount(id=next_account_id, - address=address) - session.add(eth_acc) - await session.commit() - return next_account_id - - @staticmethod - async def get_by_id(id: int) -> TrxAccount: - async with async_session_maker() as session: - stmt = select(TrxAccount).where(TrxAccount.id == id) - result = await session.execute(stmt) - return result.scalar_one() diff --git a/services/user.py b/services/user.py index 52fd0aad..20612860 100644 --- a/services/user.py +++ b/services/user.py @@ -2,16 +2,10 @@ import math from sqlalchemy import select, update, func -from sqlalchemy.orm import joinedload - import config from db import async_session_maker -from models.eth_account import EthAccount -from models.trx_account import TrxAccount from models.user import User -from services.eth_account import EthAccountService -from services.trx_account import TrxAccountService from utils.CryptoAddressGenerator import CryptoAddressGenerator @@ -40,8 +34,6 @@ async def get_next_user_id() -> int: async def create(telegram_id: int, telegram_username: str): crypto_addr_gen = CryptoAddressGenerator() crypto_addresses = crypto_addr_gen.get_addresses(i=0) - eth_account_id = await EthAccountService.create(crypto_addresses['eth']) - trx_account_id = await TrxAccountService.create(crypto_addresses['trx']) async with async_session_maker() as session: next_user_id = await UserService.get_next_user_id() new_user = User( @@ -50,8 +42,8 @@ async def create(telegram_id: int, telegram_username: str): telegram_id=telegram_id, btc_address=crypto_addresses['btc'], ltc_address=crypto_addresses['ltc'], - eth_account_id=eth_account_id, - trx_account_id=trx_account_id, + trx_address=crypto_addresses['trx'], + eth_address=crypto_addresses['eth'], seed=crypto_addr_gen.mnemonic_str ) session.add(new_user) @@ -69,11 +61,7 @@ async def update_username(telegram_id: int, telegram_username: str): @staticmethod async def get_by_tgid(telegram_id: int) -> User: async with async_session_maker() as session: - stmt = ( - select(User).join(EthAccount, User.eth_account_id == EthAccount.id).join(TrxAccount, - User.trx_account_id == TrxAccount.id) - .where(User.telegram_id == telegram_id) - ) + stmt = select(User).where(User.telegram_id == telegram_id) user_from_db = await session.execute(stmt) user_from_db = user_from_db.scalar() return user_from_db @@ -102,54 +90,41 @@ async def create_last_balance_refresh_data(telegram_id: int): @staticmethod async def get_balances(telegram_id: int) -> dict: async with async_session_maker() as session: - stmt = select(User).join(EthAccount, User.eth_account_id == EthAccount.id).join(TrxAccount, - User.trx_account_id == TrxAccount.id).where( - User.telegram_id == telegram_id) + stmt = select(User).where(User.telegram_id == telegram_id) user_balances = await session.execute(stmt) user_balances = user_balances.scalar() user_balances = [user_balances.btc_balance, user_balances.ltc_balance, - user_balances.trx_account.trx_balance, - user_balances.trx_account.usdt_balance, user_balances.trx_account.usdd_balance, - user_balances.eth_account.eth_balance, user_balances.eth_account.usdt_balance, - user_balances.eth_account.usdc_balance] - keys = ["btc_balance", - "ltc_balance", - "trx_balance", "trc_20_usdt_balance", "trc_20_usdd_balance", - "eth_balance", "erc_20_usdt_balance", "erc_20_usdc_balance"] + user_balances.usdt_trc20_balance, user_balances.usdd_trc20_balance, + user_balances.usdt_erc20_balance, user_balances.usdc_erc20_balance] + keys = ["btc_balance", "ltc_balance", "trc_20_usdt_balance", "trc_20_usdd_balance", "erc_20_usdt_balance", + "erc_20_usdc_balance"] user_balances = dict(zip(keys, user_balances)) return user_balances @staticmethod async def get_addresses(telegram_id: int) -> dict: async with (async_session_maker() as session): - stmt = select(User).options(joinedload(User.eth_account), - joinedload(User.trx_account) - ).where(User.telegram_id == telegram_id) + stmt = select(User).where(User.telegram_id == telegram_id) user_addresses = await session.execute(stmt) user_addresses = user_addresses.scalar() user_addresses = [user_addresses.btc_address, user_addresses.ltc_address, - user_addresses.trx_account.address, user_addresses.eth_account.address] + user_addresses.trx_address, user_addresses.eth_address] keys = ["btc_address", "ltc_address", "trx_address", "eth_address"] user_addresses = dict(zip(keys, user_addresses)) return user_addresses @staticmethod - async def update_crypto_balances(telegram_id: int, eth_account_id: int, trx_account_id: int, - new_crypto_balances: dict): + async def update_crypto_balances(telegram_id: int, new_crypto_balances: dict): async with async_session_maker() as session: stmt = update(User).where(User.telegram_id == telegram_id).values( - btc_balance=new_crypto_balances["btc__deposit"], - ltc_balance=new_crypto_balances["ltc__deposit"] + btc_balance=new_crypto_balances["btc_deposit"], + ltc_balance=new_crypto_balances["ltc_deposit"], + usdt_trc20_balance=new_crypto_balances['usdt_trc20_deposit'], + usdd_trc20_balance=new_crypto_balances['usdd_trc20_deposit'], + usdd_erc20_balance=new_crypto_balances['usdd_erc20_deposit'], + usdc_erc20_balance=new_crypto_balances['usdd_erc20_deposit'], ) await session.execute(stmt) - stmt = update(EthAccount).where(EthAccount.id == eth_account_id).values( - usdt_balance=new_crypto_balances['usdt_erc20_deposit'], - usdc_balance=new_crypto_balances['usdc_erc20_deposit']) - await session.execute(stmt) - stmt = update(TrxAccount).where(TrxAccount.id == trx_account_id).values( - usdt_balance=new_crypto_balances['usdt_trc20_deposit'], - usdd_balance=new_crypto_balances['usdd_trc20_deposit']) - await session.execute(stmt) await session.commit() @staticmethod diff --git a/utils/notification_manager.py b/utils/notification_manager.py index c7f626d6..d26a1b59 100644 --- a/utils/notification_manager.py +++ b/utils/notification_manager.py @@ -3,10 +3,7 @@ from aiogram import types from aiogram.utils.keyboard import InlineKeyboardBuilder - -from services.eth_account import EthAccountService from services.subcategory import SubcategoryService -from services.trx_account import TrxAccountService from services.user import UserService from config import ADMIN_ID_LIST from models.user import User @@ -48,9 +45,10 @@ async def new_deposit(new_crypto_balances: dict, deposit_amount_usd, telegram_id merged_crypto_balances_keys = [key.split('_')[0] for key in new_crypto_balances.keys()] merged_crypto_balances = zip(merged_crypto_balances_keys, new_crypto_balances.values()) user = await UserService.get_by_tgid(telegram_id) + #TODO("FIX") + trx_account = user.trx_account.address + eth_account = user.eth_account.address user = user.__dict__ - trx_account = await TrxAccountService.get_by_id(user['trx_account_id']) - eth_account = await EthAccountService.get_by_id(user['eth_account_id']) username = user['telegram_username'] user_button = await NotificationManager.make_user_button(username) if username: From fa9d5976d48290c869159c96f5d27f250e3592ef Mon Sep 17 00:00:00 2001 From: ilyarolf Date: Mon, 30 Sep 2024 23:08:08 +0300 Subject: [PATCH 09/17] added erc20/trc20 usdd,usdt,usdc,usdd --- crypto_api/CryptoApiManager.py | 3 +- l10n/en.json | 1 - services/user.py | 33 +++++++++++++------- utils/notification_manager.py | 55 +++++++++++++++++----------------- 4 files changed, 52 insertions(+), 40 deletions(-) diff --git a/crypto_api/CryptoApiManager.py b/crypto_api/CryptoApiManager.py index b52fb03a..de23d38d 100644 --- a/crypto_api/CryptoApiManager.py +++ b/crypto_api/CryptoApiManager.py @@ -14,8 +14,7 @@ def __init__(self, btc_address, ltc_address, trx_address, eth_address, user_id): self.trx_address = trx_address.strip() self.eth_address = eth_address.strip() self.user_id = user_id - # self.min_timestamp = 0 - self.min_timestamp = int((datetime.now() - timedelta(hours=6)).timestamp()) * 1000 + self.min_timestamp = int((datetime.now() - timedelta(hours=24)).timestamp()) * 1000 @staticmethod async def fetch_api_request(url: str) -> dict: diff --git a/l10n/en.json b/l10n/en.json index 4bb8529d..0d9633da 100644 --- a/l10n/en.json +++ b/l10n/en.json @@ -75,7 +75,6 @@ "user_notification_refund": "You have been refunded ${total_price} for the purchase of {quantity} pieces of {subcategory}", "admin_notification_new_deposit_username": "New deposit by user with username @{username} for ${deposit_amount_usd} with ", "admin_notification_new_deposit_id": "New deposit by user with ID {telegram_id} for ${deposit_amount_usd} with ", - "usdt_deposit_notification_part": "{value} {crypto_name}\nTRX address:{trx_address}\n", "crypto_deposit_notification_part": "{value} {crypto_name}\n{crypto_name} address:{crypto_address}\n", "seed_notification_part": "Seed: {seed}", "new_purchase_notification_with_tgid": "A new purchase by user @{username} for the amount of ${total_price} for the purchase of a {quantity} pcs {subcategory_name}.", diff --git a/services/user.py b/services/user.py index 20612860..0843c756 100644 --- a/services/user.py +++ b/services/user.py @@ -116,16 +116,29 @@ async def get_addresses(telegram_id: int) -> dict: @staticmethod async def update_crypto_balances(telegram_id: int, new_crypto_balances: dict): async with async_session_maker() as session: - stmt = update(User).where(User.telegram_id == telegram_id).values( - btc_balance=new_crypto_balances["btc_deposit"], - ltc_balance=new_crypto_balances["ltc_deposit"], - usdt_trc20_balance=new_crypto_balances['usdt_trc20_deposit'], - usdd_trc20_balance=new_crypto_balances['usdd_trc20_deposit'], - usdd_erc20_balance=new_crypto_balances['usdd_erc20_deposit'], - usdc_erc20_balance=new_crypto_balances['usdd_erc20_deposit'], - ) - await session.execute(stmt) - await session.commit() + get_old_values_stmt = select(User).where(User.telegram_id == telegram_id) + result = await session.execute(get_old_values_stmt) + user = result.scalar() + balance_fields_map = { + "btc_deposit": "btc_balance", + "ltc_deposit": "ltc_balance", + "usdt_trc20_deposit": "usdt_trc20_balance", + "usdd_trc20_deposit": "usdd_trc20_balance", + "usdd_erc20_deposit": "usdd_erc20_balance", + "usdc_erc20_deposit": "usdc_erc20_balance", + } + update_values = {} + + for key, value in new_crypto_balances.items(): + if key in balance_fields_map: + field_name = balance_fields_map[key] + current_balance = getattr(user, field_name) + update_values[field_name] = current_balance + value + + if update_values: + stmt = update(User).where(User.telegram_id == telegram_id).values(**update_values) + await session.execute(stmt) + await session.commit() @staticmethod async def update_top_up_amount(telegram_id, deposit_amount): diff --git a/utils/notification_manager.py b/utils/notification_manager.py index d26a1b59..5db2cd8b 100644 --- a/utils/notification_manager.py +++ b/utils/notification_manager.py @@ -39,40 +39,41 @@ async def make_user_button(username: Union[str, None]): return user_button_builder.as_markup() @staticmethod - async def new_deposit(new_crypto_balances: dict, deposit_amount_usd, telegram_id: int, - bot): + async def new_deposit(new_crypto_balances: dict, deposit_amount_usd, telegram_id: int, bot): deposit_amount_usd = round(deposit_amount_usd, 2) - merged_crypto_balances_keys = [key.split('_')[0] for key in new_crypto_balances.keys()] - merged_crypto_balances = zip(merged_crypto_balances_keys, new_crypto_balances.values()) + merged_crypto_balances = { + key.replace('_deposit', "").replace('_', ' ').upper(): value + for key, value in new_crypto_balances.items() + } + user = await UserService.get_by_tgid(telegram_id) - #TODO("FIX") - trx_account = user.trx_account.address - eth_account = user.eth_account.address - user = user.__dict__ - username = user['telegram_username'] - user_button = await NotificationManager.make_user_button(username) - if username: + user_button = await NotificationManager.make_user_button(user.telegram_username) + address_map = { + "TRC": user.trx_address, + "ERC": user.eth_address, + "BTC": user.btc_address, + "LTC": user.ltc_address + } + crypto_key = list(merged_crypto_balances.keys())[0] + addr = next((address_map[key] for key in address_map if key in crypto_key), "") + if user.telegram_username: message = Localizator.get_text_from_key("admin_notification_new_deposit_username").format( - username=username, - deposit_amount_usd=deposit_amount_usd) + username=user.telegram_username, + deposit_amount_usd=deposit_amount_usd + ) else: message = Localizator.get_text_from_key("admin_notification_new_deposit_id").format( telegram_id=telegram_id, - deposit_amount_usd=deposit_amount_usd) - for crypto_name, value in merged_crypto_balances: + deposit_amount_usd=deposit_amount_usd + ) + for crypto_name, value in merged_crypto_balances.items(): if value > 0: - if crypto_name == "usdt": - message += Localizator.get_text_from_key("usdt_deposit_notification_part").format( - value=value, - crypto_name=crypto_name.upper(), - trx_address=trx_account.address) - else: - crypto_address = user[f'{crypto_name}_address'] - message += Localizator.get_text_from_key("crypto_deposit_notification_part").format( - value=value, - crypto_name=crypto_name.upper(), - crypto_address=crypto_address) - message += Localizator.get_text_from_key("seed_notification_part").format(seed=user['seed']) + message += Localizator.get_text_from_key("crypto_deposit_notification_part").format( + value=value, + crypto_name=crypto_name, + crypto_address=addr + ) + message += Localizator.get_text_from_key("seed_notification_part").format(seed=user.seed) await NotificationManager.send_to_admins(message, user_button, bot) @staticmethod From bb3c7e43c1e85cb80b494f01ee218026c49ff780 Mon Sep 17 00:00:00 2001 From: ilyarolf Date: Fri, 11 Oct 2024 21:35:21 +0300 Subject: [PATCH 10/17] finalized erc20/trc20 tokens --- crypto_api/CryptoApiManager.py | 8 +++--- handlers/user/my_profile.py | 3 +-- models/deposit.py | 6 +++-- services/deposit.py | 5 ++-- utils/CryptoAddressGenerator.py | 44 ++++++++++++++++++++------------- 5 files changed, 40 insertions(+), 26 deletions(-) diff --git a/crypto_api/CryptoApiManager.py b/crypto_api/CryptoApiManager.py index de23d38d..443d248e 100644 --- a/crypto_api/CryptoApiManager.py +++ b/crypto_api/CryptoApiManager.py @@ -31,19 +31,21 @@ async def get_btc_balance(self, deposits) -> float: deposit_sum = 0.0 for deposit in data: if deposit["txid"] not in deposits and deposit['status']['confirmed']: - await DepositService.create(deposit['txid'], self.user_id, "BTC", None, deposit["value"]) + await DepositService.create(deposit['txid'], self.user_id, "BTC", None, + deposit["value"], deposit['vout']) deposit_sum += float(deposit["value"]) / 100_000_000 return deposit_sum async def get_ltc_balance(self, deposits) -> float: - url = f"https://api.blockcypher.com/v1/ltc/main/addrs/{self.ltc_address}?unspendOnly=true" + url = f"https://api.blockcypher.com/v1/ltc/main/addrs/{self.ltc_address}?unspentOnly=true" data = await self.fetch_api_request(url) deposits = [deposit.tx_id for deposit in deposits if deposit.network == "LTC"] deposits_sum = 0.0 if data['n_tx'] > 0: for deposit in data['txrefs']: if deposit["confirmations"] > 0 and deposit['tx_hash'] not in deposits: - await DepositService.create(deposit['tx_hash'], self.user_id, "LTC", None, deposit["value"]) + await DepositService.create(deposit['tx_hash'], self.user_id, "LTC", None, + deposit["value"], deposit['tx_output_n']) deposits_sum += float(deposit['value']) / 100_000_000 return deposits_sum diff --git a/handlers/user/my_profile.py b/handlers/user/my_profile.py index 99d042dc..a8460e9e 100644 --- a/handlers/user/my_profile.py +++ b/handlers/user/my_profile.py @@ -167,8 +167,7 @@ async def refresh_balance(callback: CallbackQuery): telegram_id = callback.from_user.id unpacked_cb = MyProfileCallback.unpack(callback.data) crypto_info = unpacked_cb.args_for_action - # if await UserService.can_refresh_balance(telegram_id): - if True: + if await UserService.can_refresh_balance(telegram_id): await callback.answer(Localizator.get_text_from_key("balance_refreshing")) await UserService.create_last_balance_refresh_data(telegram_id) user = await UserService.get_by_tgid(telegram_id) diff --git a/models/deposit.py b/models/deposit.py index b2d88a80..afdf54f6 100644 --- a/models/deposit.py +++ b/models/deposit.py @@ -1,4 +1,4 @@ -from sqlalchemy import Integer, Column, String, ForeignKey, Float +from sqlalchemy import Integer, Column, String, ForeignKey, Boolean, BigInteger from sqlalchemy.orm import relationship, backref from models.base import Base @@ -12,4 +12,6 @@ class Deposit(Base): user = relationship("User", backref=backref("users",lazy="joined")) network = Column(String, nullable=False) token_name = Column(String, nullable=True) - amount = Column(Float, nullable=False) + amount = Column(BigInteger, nullable=False) + is_withdrawn = Column(Boolean, default=False) + vout = Column(Integer, nullable=True) diff --git a/services/deposit.py b/services/deposit.py index fbc70a44..1044d807 100644 --- a/services/deposit.py +++ b/services/deposit.py @@ -18,7 +18,7 @@ async def get_next_user_id() -> int: return int(last_user_id) + 1 @staticmethod - async def create(tx_id, user_id, network, token_name, amount): + async def create(tx_id, user_id, network, token_name, amount, vout=None): async with async_session_maker() as session: next_deposit_id = await DepositService.get_next_user_id() dep = Deposit(id=next_deposit_id, @@ -26,7 +26,8 @@ async def create(tx_id, user_id, network, token_name, amount): tx_id=tx_id, network=network, token_name=token_name, - amount=amount) + amount=amount, + vout=vout) session.add(dep) await session.commit() return next_deposit_id diff --git a/utils/CryptoAddressGenerator.py b/utils/CryptoAddressGenerator.py index 1f21ba21..09b4b8c8 100644 --- a/utils/CryptoAddressGenerator.py +++ b/utils/CryptoAddressGenerator.py @@ -3,41 +3,51 @@ class CryptoAddressGenerator: - def __init__(self): - mnemonic_gen = Bip39MnemonicGenerator().FromWordsNumber(Bip39WordsNum.WORDS_NUM_12) - self.mnemonic_str = mnemonic_gen.ToStr() - self.seed_bytes = Bip39SeedGenerator(self.mnemonic_str).Generate() + def __init__(self, seed_str: str = None): + if seed_str is not None: + self.mnemonic_str = seed_str + self.seed_bytes = Bip39SeedGenerator(self.mnemonic_str).Generate() + else: + mnemonic_gen = Bip39MnemonicGenerator().FromWordsNumber(Bip39WordsNum.WORDS_NUM_12) + self.mnemonic_str = mnemonic_gen.ToStr() + self.seed_bytes = Bip39SeedGenerator(self.mnemonic_str).Generate() - def __generate_btc_pair(self, i: int) -> str: + def __generate_btc_pair(self, i: int) -> tuple: bip84_mst_ctx = Bip84.FromSeed(self.seed_bytes, Bip84Coins.BITCOIN) bip84_acc_ctx = bip84_mst_ctx.Purpose().Coin().Account(0) bip84_chg_ctx = bip84_acc_ctx.Change(Bip44Changes.CHAIN_EXT) bip84_addr_ctx = bip84_chg_ctx.AddressIndex(i).PublicKey().ToAddress() - return bip84_addr_ctx + return bip84_addr_ctx, bip84_chg_ctx.AddressIndex(i).PrivateKey().ToWif() - def __generate_ltc_pair(self, i: int) -> str: + def __generate_ltc_pair(self, i: int) -> tuple: bip84_mst_ctx = Bip84.FromSeed(self.seed_bytes, Bip84Coins.LITECOIN) bip84_acc_ctx = bip84_mst_ctx.Purpose().Coin().Account(0) bip84_chg_ctx = bip84_acc_ctx.Change(Bip44Changes.CHAIN_EXT) bip84_addr_ctx = bip84_chg_ctx.AddressIndex(i).PublicKey().ToAddress() - return bip84_addr_ctx + return bip84_addr_ctx, bip84_chg_ctx.AddressIndex(i).PrivateKey().ToWif() - def __generate_trx_pair(self, i: int) -> str: + def __generate_trx_pair(self, i: int) -> tuple: bip44_mst_ctx = Bip44.FromSeed(self.seed_bytes, Bip44Coins.TRON) bip44_acc_ctx = bip44_mst_ctx.Purpose().Coin().Account(0) bip44_chg_ctx = bip44_acc_ctx.Change(Bip44Changes.CHAIN_EXT) bip44_addr_ctx = bip44_chg_ctx.AddressIndex(i).PublicKey().ToAddress() - return bip44_addr_ctx + return bip44_addr_ctx, bip44_chg_ctx.AddressIndex(i).PrivateKey().ToWif() - def __generate_eth_pair(self, i: int) -> str: + def __generate_eth_pair(self, i: int) -> tuple: bip44_mst_ctx = Bip44.FromSeed(self.seed_bytes, Bip44Coins.ETHEREUM) bip44_acc_ctx = bip44_mst_ctx.Purpose().Coin().Account(0) bip44_chg_ctx = bip44_acc_ctx.Change(Bip44Changes.CHAIN_EXT) bip44_addr_ctx = bip44_chg_ctx.AddressIndex(i).PublicKey().ToAddress() - return bip44_addr_ctx + return bip44_addr_ctx, bip44_chg_ctx.AddressIndex(i).PrivateKey().ToWif() - def get_addresses(self, i): - return {'btc': self.__generate_btc_pair(i), - 'ltc': self.__generate_ltc_pair(i), - 'trx': self.__generate_trx_pair(i), - 'eth': self.__generate_eth_pair(i)} + def get_private_keys(self, i: int) -> dict: + return {'btc': self.__generate_btc_pair(i)[1], + 'ltc': self.__generate_ltc_pair(i)[1], + 'trx': self.__generate_trx_pair(i)[1], + 'eth': self.__generate_eth_pair(i)[1]} + + def get_addresses(self, i: int): + return {'btc': self.__generate_btc_pair(i)[0], + 'ltc': self.__generate_ltc_pair(i)[0], + 'trx': self.__generate_trx_pair(i)[0], + 'eth': self.__generate_eth_pair(i)[0]} From 9b7ee96148b2213d2dd34c6c4f5e01630ad7f086 Mon Sep 17 00:00:00 2001 From: ilyarolf Date: Fri, 11 Oct 2024 21:53:46 +0300 Subject: [PATCH 11/17] fix fetch erc20 tokens functionality --- crypto_api/CryptoApiManager.py | 28 +--------------------------- 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/crypto_api/CryptoApiManager.py b/crypto_api/CryptoApiManager.py index 443d248e..c34626f7 100644 --- a/crypto_api/CryptoApiManager.py +++ b/crypto_api/CryptoApiManager.py @@ -12,7 +12,7 @@ def __init__(self, btc_address, ltc_address, trx_address, eth_address, user_id): self.btc_address = btc_address.strip() self.ltc_address = ltc_address.strip() self.trx_address = trx_address.strip() - self.eth_address = eth_address.strip() + self.eth_address = eth_address.strip().lower() self.user_id = user_id self.min_timestamp = int((datetime.now() - timedelta(hours=24)).timestamp()) * 1000 @@ -75,18 +75,6 @@ async def get_usdd_trc20_balance(self, deposits) -> float: deposits_sum += float(deposit['value']) / pow(10, deposit['token_info']['decimals']) return deposits_sum - async def get_trx_balance(self, deposits) -> float: - url = f'https://apilist.tronscanapi.com/api/new/transfer?sort=-timestamp&count=true&limit=1000&start=0&address={self.trx_address}' - data = await self.fetch_api_request(url) - deposits = [deposit.tx_id for deposit in deposits if deposit.network == "TRX" and deposit.token_name is None] - deposit_sum = 0.0 - for deposit in data['data']: - if deposit['confirmed'] and deposit['transactionHash'] not in deposits and deposit[ - 'transferToAddress'] == self.trx_address: - await DepositService.create(deposit['transactionHash'], self.user_id, "TRX", None, deposit['amount']) - deposit_sum += deposit['amount'] / pow(10, deposit['tokenInfo']['tokenDecimal']) - return deposit_sum - async def get_usdt_erc20_balance(self, deposits) -> float: url = f'https://api.ethplorer.io/getAddressHistory/{self.eth_address}?type=transfer&token=0xdAC17F958D2ee523a2206206994597C13D831ec7&apiKey={config.ETHPLORER_API_KEY}&limit=1000' data = await self.fetch_api_request(url) @@ -113,20 +101,6 @@ async def get_usdc_erc20_balance(self, deposits): deposits_sum += float(deposit['value']) / pow(10, 6) return deposits_sum - async def get_eth_balance(self, deposits): - # TODO (fetch eth deposits) - url = f'https://api.ethplorer.io/getAddressTransactions/{self.eth_address}?limit=1000&showZeroValues=false&apiKey={config.ETHPLORER_API_KEY}' - data = await self.fetch_api_request(url) - deposits = [deposit.tx_id for deposit in deposits if - deposit.network == "ETH" and deposit.token_name is None] - deposits_sum = 0.0 - for deposit in data: - if deposit['hash'] not in deposits and deposit['success'] is True and deposit['to'] == self.eth_address: - await DepositService.create(deposit['hash'], self.user_id, "ETH", None, - deposit['value'] * pow(10, 9)) - deposits_sum += deposit['value'] - return deposits_sum - async def get_top_ups(self): user_deposits = await DepositService.get_by_user_id(self.user_id) balances = {"btc__deposit": await self.get_btc_balance(user_deposits), From 7c4568ce41f844c7f57a6c3797ab0b8c4ba6fe3f Mon Sep 17 00:00:00 2001 From: ilyarolf Date: Fri, 11 Oct 2024 22:06:20 +0300 Subject: [PATCH 12/17] fix purchase history functionality in my profile --- handlers/user/my_profile.py | 17 ++++++----------- types/enum/deposit_network.py | 8 -------- types/enum/token_type.py | 8 -------- 3 files changed, 6 insertions(+), 27 deletions(-) delete mode 100644 types/enum/deposit_network.py delete mode 100644 types/enum/token_type.py diff --git a/handlers/user/my_profile.py b/handlers/user/my_profile.py index a8460e9e..03ed4e3d 100644 --- a/handlers/user/my_profile.py +++ b/handlers/user/my_profile.py @@ -64,13 +64,9 @@ async def my_profile(message: Union[Message, CallbackQuery]): top_up_button = types.InlineKeyboardButton(text=Localizator.get_text_from_key("top_up_balance_button"), callback_data=create_callback_profile(current_level + 1, "top_up")) purchase_history_button = types.InlineKeyboardButton(text=Localizator.get_text_from_key("purchase_history_button"), - callback_data=create_callback_profile(current_level + 2, + callback_data=create_callback_profile(current_level + 4, "purchase_history")) - # update_balance = types.InlineKeyboardButton(text=Localizator.get_text_from_key("refresh_balance_button"), - # callback_data=create_callback_profile(current_level + 3, - # "refresh_balance")) my_profile_builder = InlineKeyboardBuilder() - # my_profile_builder.add(top_up_button, purchase_history_button, update_balance) my_profile_builder.add(top_up_button, purchase_history_button) my_profile_builder.adjust(2) my_profile_markup = my_profile_builder.as_markup() @@ -131,7 +127,7 @@ async def create_purchase_history_keyboard_builder(page: int, user_id: int): buy_id = order.id buy_item = await BuyItemService.get_buy_item_by_buy_id(buy_id) item = await ItemService.get_by_primary_key(buy_item.item_id) - item_from_history_callback = create_callback_profile(4, action="get_order", + item_from_history_callback = create_callback_profile(5, action="get_order", args_for_action=str(buy_id)) order_inline = types.InlineKeyboardButton( text=Localizator.get_text_from_key("purchase_history_item").format(subcategory_name=item.subcategory.name, @@ -191,13 +187,13 @@ async def refresh_balance(callback: CallbackQuery): async def get_order_from_history(callback: CallbackQuery): - current_level = 4 + current_level = 5 buy_id = MyProfileCallback.unpack(callback.data).args_for_action items = await ItemService.get_items_by_buy_id(buy_id) message = await create_message_with_bought_items(items) back_builder = InlineKeyboardBuilder() back_button = types.InlineKeyboardButton(text=Localizator.get_text_from_key("admin_back_button"), - callback_data=create_callback_profile(level=current_level - 2)) + callback_data=create_callback_profile(level=current_level - 1)) back_builder.add(back_button) await callback.message.edit_text(text=message, parse_mode=ParseMode.HTML, reply_markup=back_builder.as_markup()) @@ -240,9 +236,8 @@ async def navigate(callback: CallbackQuery, callback_data: MyProfileCallback): 1: top_up_balance, 2: top_up_by_method, 3: refresh_balance, - - # 2: purchase_history, - # 4: get_order_from_history + 4: purchase_history, + 5: get_order_from_history } current_level_function = levels[current_level] diff --git a/types/enum/deposit_network.py b/types/enum/deposit_network.py deleted file mode 100644 index ae11f54f..00000000 --- a/types/enum/deposit_network.py +++ /dev/null @@ -1,8 +0,0 @@ -from enum import Enum - - -class DepositNetwork(Enum): - BTC = "BTC" - LTC = "LTC" - TRX = "TRX" - ETH = "ETH" diff --git a/types/enum/token_type.py b/types/enum/token_type.py deleted file mode 100644 index 20f15ea6..00000000 --- a/types/enum/token_type.py +++ /dev/null @@ -1,8 +0,0 @@ -from enum import Enum - - -class TokenType(Enum): - USDT_TRC20 = "USDT_TRC20" - USDD_TRC20 = "USDD_TRC20" - USDT_ERC20 = "USDT_ERC20" - USDC_ERC20 = "USDC_ERC20" From f651ac7e52dd5e1d18af2b213200fcfcce663616 Mon Sep 17 00:00:00 2001 From: ilyarolf Date: Sat, 12 Oct 2024 19:24:10 +0300 Subject: [PATCH 13/17] vout is not null in deposit.py --- models/deposit.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/models/deposit.py b/models/deposit.py index afdf54f6..fb043827 100644 --- a/models/deposit.py +++ b/models/deposit.py @@ -14,4 +14,4 @@ class Deposit(Base): token_name = Column(String, nullable=True) amount = Column(BigInteger, nullable=False) is_withdrawn = Column(Boolean, default=False) - vout = Column(Integer, nullable=True) + vout = Column(Integer, nullable=False) From 77097f968a906f449bca67f9af7285ccfe4b728a Mon Sep 17 00:00:00 2001 From: ilyarolf Date: Sat, 12 Oct 2024 19:42:55 +0300 Subject: [PATCH 14/17] final fixes --- .env | 3 ++- db.py | 1 + models/deposit.py | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.env b/.env index c0fec24f..3d69fb48 100644 --- a/.env +++ b/.env @@ -8,4 +8,5 @@ DB_NAME = "" NGROK_TOKEN = "" PAGE_ENTRIES = "" LANGUAGE = "" -MULTIBOT = "" \ No newline at end of file +MULTIBOT = "" +ETHPLORER_API_KEY = "" \ No newline at end of file diff --git a/db.py b/db.py index 2cc3505a..b991d2cb 100644 --- a/db.py +++ b/db.py @@ -15,6 +15,7 @@ from models.buyItem import BuyItem from models.category import Category from models.subcategory import Subcategory +from models.deposit import Deposit url = f"sqlite+aiosqlite:///data/{DB_NAME}" data_folder = Path("data") diff --git a/models/deposit.py b/models/deposit.py index fb043827..afdf54f6 100644 --- a/models/deposit.py +++ b/models/deposit.py @@ -14,4 +14,4 @@ class Deposit(Base): token_name = Column(String, nullable=True) amount = Column(BigInteger, nullable=False) is_withdrawn = Column(Boolean, default=False) - vout = Column(Integer, nullable=False) + vout = Column(Integer, nullable=True) From f72fe1aa73dec8f9eb1b7c618b1131a12d2974f4 Mon Sep 17 00:00:00 2001 From: ilyarolf Date: Sat, 12 Oct 2024 19:43:55 +0300 Subject: [PATCH 15/17] docker-compose.yml fix --- docker-compose.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker-compose.yml b/docker-compose.yml index a613cd14..0dce889e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,6 +16,7 @@ services: PAGE_ENTRIES: 20 # Items per page LANGUAGE: "en" # The name of your file from the l10n folder without the .json suffix MULTIBOT: "false" # Allows the use of a multibot + ETHPLORER_API_KEY: "" # API key from Ethplorer ports: - "4040:4040" - "5000:5000" # ${WEBAPP_PORT}:${WEBAPP_PORT} From b061fad12b039dc921b9667fe67d5ce3729128dc Mon Sep 17 00:00:00 2001 From: ilyarolf Date: Tue, 15 Oct 2024 19:37:52 +0300 Subject: [PATCH 16/17] deposit model fixes --- models/deposit.py | 1 - 1 file changed, 1 deletion(-) diff --git a/models/deposit.py b/models/deposit.py index afdf54f6..99a46719 100644 --- a/models/deposit.py +++ b/models/deposit.py @@ -9,7 +9,6 @@ class Deposit(Base): id = Column(Integer, primary_key=True) tx_id = Column(String, nullable=False, unique=True) user_id = Column(Integer, ForeignKey('users.id'), nullable=False) - user = relationship("User", backref=backref("users",lazy="joined")) network = Column(String, nullable=False) token_name = Column(String, nullable=True) amount = Column(BigInteger, nullable=False) From aa93d0e6ef88e52619f2116f3b35e69f45ed7204 Mon Sep 17 00:00:00 2001 From: ilyarolf Date: Tue, 15 Oct 2024 19:57:25 +0300 Subject: [PATCH 17/17] added datetime for deposit table --- models/deposit.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/models/deposit.py b/models/deposit.py index 99a46719..afd38df7 100644 --- a/models/deposit.py +++ b/models/deposit.py @@ -1,5 +1,4 @@ -from sqlalchemy import Integer, Column, String, ForeignKey, Boolean, BigInteger -from sqlalchemy.orm import relationship, backref +from sqlalchemy import Integer, Column, String, ForeignKey, Boolean, BigInteger, DateTime, func from models.base import Base @@ -14,3 +13,4 @@ class Deposit(Base): amount = Column(BigInteger, nullable=False) is_withdrawn = Column(Boolean, default=False) vout = Column(Integer, nullable=True) + deposit_datetime = Column(DateTime, default=func.now())