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/config.py b/config.py
index 135aee14..6141f230 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'
+ETHPLORER_API_KEY = os.environ.get("ETHPLORER_API_KEY")
diff --git a/crypto_api/CryptoApiManager.py b/crypto_api/CryptoApiManager.py
index 93b3d723..c34626f7 100644
--- a/crypto_api/CryptoApiManager.py
+++ b/crypto_api/CryptoApiManager.py
@@ -1,53 +1,152 @@
-from typing import Any
+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):
+ 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().lower()
+ self.user_id = user_id
+ self.min_timestamp = int((datetime.now() - timedelta(hours=24)).timestamp()) * 1000
+
+ @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
+
+ 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)
+ 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['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}?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"], deposit['tx_output_n'])
+ deposits_sum += float(deposit['value']) / 100_000_000
+ return deposits_sum
+
+ async def get_usdt_trc20_balance(self, deposits) -> float:
+ 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 = [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']:
+ 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:
+ 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 = [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:
+ 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_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 = [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']:
+ 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 = [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']:
+ 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_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}'
+ 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),
+ "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),
}
- 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
+ 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")
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 +154,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.0 # 1USDD=1USD
return usd_crypto_prices
-
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/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}
diff --git a/handlers/user/my_profile.py b/handlers/user/my_profile.py
index cd56dc85..03ed4e3d 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.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,
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)
@@ -58,13 +64,10 @@ 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()
@@ -84,25 +87,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_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),
+ 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()
@@ -115,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,
@@ -141,32 +153,33 @@ 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
+ unpacked_cb = MyProfileCallback.unpack(callback.data)
+ crypto_info = unpacked_cb.args_for_action
if await UserService.can_refresh_balance(telegram_id):
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_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
- 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, 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:
@@ -174,17 +187,46 @@ 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())
+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
@@ -192,9 +234,10 @@ 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
+ 4: purchase_history,
+ 5: get_order_from_history
}
current_level_function = levels[current_level]
diff --git a/l10n/en.json b/l10n/en.json
index 76f87742..0d9633da 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/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:",
@@ -75,12 +75,18 @@
"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}.",
"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/deposit.py b/models/deposit.py
new file mode 100644
index 00000000..afd38df7
--- /dev/null
+++ b/models/deposit.py
@@ -0,0 +1,16 @@
+from sqlalchemy import Integer, Column, String, ForeignKey, Boolean, BigInteger, DateTime, func
+
+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)
+ network = Column(String, nullable=False)
+ token_name = Column(String, nullable=True)
+ amount = Column(BigInteger, nullable=False)
+ is_withdrawn = Column(Boolean, default=False)
+ vout = Column(Integer, nullable=True)
+ deposit_datetime = Column(DateTime, default=func.now())
diff --git a/models/user.py b/models/user.py
index 7a7779f7..84a1bf95 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
@@ -12,12 +13,16 @@ class User(Base):
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)
- usdt_balance = Column(Float, nullable=False, default=0.0)
+ 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/deposit.py b/services/deposit.py
new file mode 100644
index 00000000..1044d807
--- /dev/null
+++ b/services/deposit.py
@@ -0,0 +1,41 @@
+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, vout=None):
+ 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,
+ vout=vout)
+ 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 d5fa8d4d..0843c756 100644
--- a/services/user.py
+++ b/services/user.py
@@ -1,9 +1,7 @@
import datetime
-import logging
import math
from sqlalchemy import select, update, func
-
import config
from db import async_session_maker
@@ -34,10 +32,10 @@ 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)
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,
@@ -45,6 +43,7 @@ async def create(telegram_id: int, telegram_username: str):
btc_address=crypto_addresses['btc'],
ltc_address=crypto_addresses['ltc'],
trx_address=crypto_addresses['trx'],
+ eth_address=crypto_addresses['eth'],
seed=crypto_addr_gen.mnemonic_str
)
session.add(new_user)
@@ -91,33 +90,55 @@ 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)
+ stmt = select(User).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.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.btc_address, User.ltc_address, User.trx_address).where(User.telegram_id == telegram_id)
+ async with (async_session_maker() as session):
+ stmt = select(User).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_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, 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"],
- )
- 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/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/utils/CryptoAddressGenerator.py b/utils/CryptoAddressGenerator.py
index 97cf67e7..09b4b8c8 100644
--- a/utils/CryptoAddressGenerator.py
+++ b/utils/CryptoAddressGenerator.py
@@ -3,33 +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 get_addresses(self, i):
- return {'btc': self.__generate_btc_pair(i),
- 'ltc': self.__generate_ltc_pair(i),
- 'trx': self.__generate_trx_pair(i)}
+ 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, bip44_chg_ctx.AddressIndex(i).PrivateKey().ToWif()
+
+ 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]}
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
diff --git a/utils/notification_manager.py b/utils/notification_manager.py
index a27dfab8..5db2cd8b 100644
--- a/utils/notification_manager.py
+++ b/utils/notification_manager.py
@@ -3,7 +3,6 @@
from aiogram import types
from aiogram.utils.keyboard import InlineKeyboardBuilder
-
from services.subcategory import SubcategoryService
from services.user import UserService
from config import ADMIN_ID_LIST
@@ -40,41 +39,41 @@ 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,
- 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 = [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 = {
+ key.replace('_deposit', "").replace('_', ' ').upper(): value
+ for key, value in new_crypto_balances.items()
+ }
+
user = await UserService.get_by_tgid(telegram_id)
- 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=user[
- 'trx_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