├── README.md ├── examples ├── extract_p2p_order_history.py ├── order_auto_confirm.py ├── send_by_tg_username.py └── token_to_file.py ├── requirements.txt ├── setup.py ├── tests ├── __init__.py ├── test_account.py ├── test_p2p.py └── utils.py └── wallet ├── __init__.py ├── rest └── __init__.py ├── types ├── __init__.py ├── balances.py ├── offers.py └── transactions.py └── web_client.py /README.md: -------------------------------------------------------------------------------- 1 |
2 |

3 | 4 | Logo 5 | 6 | 7 |

Telegram Wallet API

8 | 9 |

10 | Unofficial library for managing your personal Wallet account 11 |
12 |
13 |

14 |

15 | 16 | 17 | 18 | ## Table Of Contents 19 | 20 | * [About the Project](#about-the-project) 21 | * [Built With](#built-with) 22 | * [Getting Started](#getting-started) 23 | * [Example](#example) 24 | * [Realised methods](#realised-methods) 25 | 26 | ## About The Project 27 | 28 | Send and receive tokens | Trade on P2P Market 29 | 30 | ## Built With 31 | 32 | To obtain an authorization token, the "selenium" library is used. 33 | Working with the API is implemented using "requests" 34 | 35 | ## Getting Started 36 | 37 | ```bazaar 38 | pip install pyTelegramWalletApi 39 | ``` 40 | 41 | ### Send by address 42 | 43 | ```python 44 | from wallet import Wallet, Client 45 | 46 | # use chromedriver to loging tg and parse auth_token 47 | c = Client(profile='main', headless=True) 48 | auth_token = c.get_token() 49 | 50 | w = Wallet(auth_token) 51 | balance = w.get_user_balances('USDT') 52 | print(f'[*] Balance: {balance.available_balance} TON') 53 | 54 | w.send_to_address(balance.available_balance, 'USDT', 'UQC_tRAxYGgMuU4sSqN6eRWArwvL4qI3gXn_', network='ton') 55 | ``` 56 | 57 | ### Send by username 58 | ```python 59 | from wallet import Client, Wallet 60 | 61 | c = Client(profile='main', headless=True) 62 | 63 | # To send coins within a telegram, you need to receive an “auth_token” 64 | # from Wallet directly in chat with the recipient 65 | 66 | auth_token = c.get_token(username='username_to_recieve') 67 | print(c.receiver_id) # browser parse tg_id by username 68 | 69 | w = Wallet(auth_token, log_response=True) 70 | w.send_to_user(amount=1, currency='USDT', receiver_tg_id=c.receiver_id) 71 | ``` 72 | 73 | ### Parse P2P market 74 | 75 | ```python 76 | import time 77 | from wallet import Wallet 78 | 79 | w = Wallet.token_from_file('token.txt') # load token from file method 80 | 81 | while True: 82 | try: 83 | o = w.get_p2p_market('TON', 'RUB', 'SALE', desired_amount=5000)[0] 84 | 85 | print(f'Top offer ID: {o.id}\n' 86 | f'Price: {o.price.value} {o.price.quoteCurrencyCode}\n' 87 | f'Volume: {o.availableVolume} TON\n' 88 | f'User: {o.user.nickname} | Orders: {o.user.statistics.totalOrdersCount}\n' 89 | f'=============================') 90 | """ 91 | Top offer ID: 187885 92 | Price: 590.0 RUB 93 | Volume: 30.682475494 TON 94 | User: Lucky Goose | Orders: 3127 95 | ============================= 96 | """ 97 | 98 | except Exception as e: 99 | print(f"[!] Error: {e}") 100 | 101 | time.sleep(5) 102 | 103 | ``` 104 | 105 | ### Create own offers 106 | 107 | ```python 108 | from wallet import Wallet 109 | 110 | w = Wallet.token_from_file('token.txt') # load token from file method 111 | 112 | new_offer = w.create_p2p_offer( 113 | comment='Hello', 114 | currency='TON', 115 | fiat='RUB', 116 | amount=10, 117 | margine=90, 118 | offer_type='PURCHASE', 119 | order_min_price=500, 120 | pay_methods=['tinkoff'], 121 | ) 122 | 123 | w.activate_p2p_offer(new_offer.id, new_offer.type) 124 | ``` 125 | 126 | ## Wallet realised methods 127 | 128 | ### Account methods 129 | 130 | ``` 131 | get_user_balances(currency) 132 | ``` 133 | ``` 134 | get_transactions(limit, last_id, crypto_currency, transaction_type, transaction_gateway) 135 | ``` 136 | ``` 137 | get_wallet(crypto_currency) 138 | ``` 139 | ``` 140 | get_exchange_pair_info(from_currency, to_currency, from_amount) 141 | ``` 142 | ``` 143 | get_earn_campings() 144 | ``` 145 | ``` 146 | test_get_transaction_details(tx_id) 147 | ``` 148 | ``` 149 | get_user_region_verification() 150 | ``` 151 | ``` 152 | send_to_address(amount, currency, address, network) 153 | ``` 154 | ``` 155 | send_to_user(amount, currency, receiver_tg_id) 156 | ``` 157 | ``` 158 | create_exchange(from_currency, to_currency, from_amount, local_currency) 159 | ``` 160 | 161 | ### P2P matket methods 162 | 163 | 164 | ``` 165 | get_own_p2p_user_info() 166 | ``` 167 | ``` 168 | get_user_p2p_payments() 169 | ``` 170 | ``` 171 | get_own_p2p_order_history(offset, limit, status) 172 | ``` 173 | ``` 174 | get_p2p_order(order_id) 175 | ``` 176 | ``` 177 | get_p2p_rate(base, quote) 178 | ``` 179 | ``` 180 | get_p2p_payment_methods_by_currency(currency) 181 | ``` 182 | ``` 183 | create_p2p_offer(currency, amount, fiat, margine, offer_type, 184 | order_min_price, pay_methods, comment, price_type, 185 | confirm_timeout_tag:) 186 | ``` 187 | ``` 188 | edit_p2p_offer(currency, offer_id, margine, volume, order_amount_min, comment) 189 | ``` 190 | ``` 191 | activate_p2p_offer(offer_id, offer_type) 192 | ``` 193 | ``` 194 | deactivate_p2p_offer(offer_id, offer_type) 195 | ``` 196 | ``` 197 | enable_p2p_bidding() 198 | ``` 199 | ``` 200 | disable_p2p_bidding() 201 | ``` 202 | ``` 203 | get_own_p2p_offers(offset limit, offer_type, status) 204 | ``` 205 | ``` 206 | get_p2p_offer(offer_id) 207 | ``` 208 | ``` 209 | get_own_p2p_offer(offer_id) 210 | ``` 211 | ``` 212 | accept_p2p_order(order_id, offer_type) 213 | ``` 214 | ``` 215 | get_p2p_market(base_currency_code, quote_currency_code, offer_type, offset, limit,desired_amount) 216 | ``` 217 | 218 | -------------------------------------------------------------------------------- /examples/extract_p2p_order_history.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | import openpyxl 4 | 5 | from wallet import Wallet, Client 6 | from wallet.types import Order 7 | 8 | # web_client = Client(profile='main') 9 | # auth_token = web_client.get_token() 10 | 11 | w = Wallet.token_from_file('../token.txt') 12 | 13 | offset = 0 14 | limit = 200 15 | i = 1 16 | history: list[Order] = [] 17 | temp = [] 18 | 19 | history += w.get_own_p2p_order_history(offset, limit, status='ALL_COMPLETED') 20 | 21 | while len(history) / i == limit: 22 | time.sleep(1) 23 | 24 | i += 1 25 | offset += limit 26 | history += w.get_own_p2p_order_history(offset, limit, status='ALL_COMPLETED') 27 | print(len(history)) 28 | break 29 | 30 | for order in history: 31 | temp.append( 32 | ( 33 | i, 34 | order.number, 35 | order.offerType, 36 | order.buyer.nickname, 37 | order.seller.nickname, 38 | round(order.price.value, 2), 39 | round(order.volume.amount, 2), 40 | round(order.amount.amount, 2), 41 | order.paymentDetails.paymentMethod.name, 42 | order.acceptDateTime, 43 | ) 44 | ) 45 | 46 | wb = openpyxl.Workbook() 47 | work_list = wb.active 48 | 49 | work_list.append( 50 | ('№', 'OrderID', 'Type ', 'Buyer', 'Seller', 'Price', 51 | 'Volume', "Sum", "Payment Method", "Order End Date") 52 | ) 53 | 54 | for row in temp: 55 | work_list.append(row) 56 | 57 | wb.save('my_orders.xlsx') 58 | -------------------------------------------------------------------------------- /examples/order_auto_confirm.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from wallet import Wallet 4 | # from wallet.web_client import Client 5 | 6 | if __name__ == '__main__': 7 | # c = Client(profile='main', headless=True) 8 | # auth_token = c.get_token() 9 | # w = Wallet(auth_token) 10 | 11 | w = Wallet.token_from_file('token.txt') 12 | 13 | while True: 14 | w.update_token() 15 | active_orders = w.get_own_p2p_order_history(status='ALL_ACTIVE') 16 | print(f"Available: {len(active_orders)}") 17 | 18 | for order in active_orders: 19 | if order.status == 'NEW': 20 | o = w.accept_p2p_order(order_id=order.id, offer_type=order.offerType) 21 | 22 | if o.offerType == 'PURCHASE': 23 | s_orders = o.seller.statistics.totalOrdersCount 24 | s_rate = o.seller.statistics.successRate 25 | if o.seller.isVerified: 26 | verif = ' ✅' 27 | else: 28 | verif = '' 29 | 30 | print(f'[*] Order accept! \n' 31 | f' Seller: {o.seller.nickname}{verif} | ID: {o.seller.userId}\n' 32 | f" Stats: {s_orders} orders | Success {s_rate}%") 33 | 34 | print('[*] Payment Details:') 35 | 36 | for m in o.paymentDetails.attributes.values: 37 | print(f" {m.name} | {m.value}") 38 | 39 | time.sleep(10) 40 | -------------------------------------------------------------------------------- /examples/send_by_tg_username.py: -------------------------------------------------------------------------------- 1 | from wallet import Client, Wallet 2 | 3 | 4 | if __name__ == '__main__': 5 | c = Client(profile='main', headless=True) 6 | 7 | # To send coins within a telegram, you need to receive an “auth_token” 8 | # from Wallet directly in chat with the recipient 9 | 10 | auth_token = c.get_token(username='username_to_recieve') 11 | print(c.receiver_id) # browser parse tg_id by username 12 | 13 | w = Wallet(auth_token, log_response=True) 14 | w.send_to_user(amount=1, currency='USDT', receiver_tg_id=c.receiver_id) 15 | -------------------------------------------------------------------------------- /examples/token_to_file.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | from wallet import Client 4 | 5 | 6 | def token_to_file(): 7 | while True: 8 | try: 9 | client = Client(profile='main', headless=False) 10 | 11 | try: 12 | auth_token = client.get_token() 13 | print(auth_token) 14 | with open('token.txt', 'w') as f: 15 | f.write(auth_token) 16 | except Exception as e: 17 | raise Exception(f"[!] Error get_token: {e}") 18 | finally: 19 | client.stop() 20 | 21 | time.sleep(60*5) 22 | except Exception as e: 23 | print(f"[!] Error update_token: {e}") 24 | time.sleep(15) 25 | 26 | 27 | if __name__ == '__main__': 28 | token_to_file() 29 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests~=2.31.0 2 | selenium-wire 3 | pytest 4 | selenium~=4.13.0 5 | openpyxl~=3.1.2 6 | setuptools~=68.1.2 7 | dataclasses-json 8 | blinker==1.7.0 -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | from pathlib import Path 3 | 4 | requirements = ["requests", "selenium-wire", "pytest"] 5 | this_directory = Path(__file__).parent 6 | long_description = (this_directory / "README.md").read_text() 7 | 8 | setup( 9 | name='pyTelegramWalletApi', 10 | version='0.2.40', 11 | author='no-name-user-name', 12 | description='Telegram Wallet API', 13 | packages=find_packages(), 14 | install_requires=requirements, 15 | author_email='dimazver61@gmail.com', 16 | long_description=long_description, 17 | long_description_content_type='text/markdown' 18 | ) -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/no-name-user-name/pyTelegramWalletApi/de87899e5e20c0f20246a79b99af4caf37e56b5b/tests/__init__.py -------------------------------------------------------------------------------- /tests/test_account.py: -------------------------------------------------------------------------------- 1 | from tests.utils import check_api_update 2 | from wallet import Wallet 3 | 4 | w = Wallet.token_from_file('../token.txt') 5 | 6 | 7 | def test_get_earn_campings(): 8 | assert 'campaigns' in w.get_earn_campings() 9 | 10 | 11 | def test_get_user_balance(): 12 | check_api_update( 13 | w.get_user_balances(currency='TON') 14 | ) 15 | 16 | 17 | def test_get_transactions(): 18 | txs = w.get_transactions() 19 | assert len(txs) > 0 20 | 21 | 22 | def test_get_transaction_details(): 23 | check_api_update( 24 | w.get_transaction_details(tx_id=199165225) 25 | ) 26 | 27 | 28 | def test_get_passcode_info(): 29 | assert 'recoveryEmail' in w.get_passcode_info() 30 | 31 | 32 | def test_get_recovery_email(): 33 | assert 'email' in w.get_recovery_email('wallet') 34 | 35 | 36 | def test_get_user_region_verification(): 37 | assert 'blockedPermissions' in w.get_user_region_verification() 38 | 39 | 40 | def test_get_wallet(): 41 | assert 'address' in w.get_wallet(crypto_currency='NOT')[0] 42 | 43 | 44 | def test_exchange_pair_info(): 45 | assert 'rate' in w.get_exchange_pair_info('TON', 'USDT') 46 | 47 | 48 | def test_exchange(): 49 | uid = w.create_exchange('USDT', 'TON', 1, 'RUB')['uid'] 50 | assert 'transaction_id' in w.submit_exchange(uid) 51 | -------------------------------------------------------------------------------- /tests/test_p2p.py: -------------------------------------------------------------------------------- 1 | from tests.utils import check_api_update 2 | from wallet.rest import Wallet 3 | 4 | w = Wallet.token_from_file('../token.txt') 5 | 6 | 7 | def test_get_own_info(): 8 | assert 'nickname' in w.get_own_p2p_user_info() 9 | 10 | 11 | def test_get_own_permissions(): 12 | print(w.get_own_p2p_permissions()) 13 | 14 | 15 | def test_get_user_p2p_payment(): 16 | assert 'name' in w.get_user_p2p_payments()[0] 17 | 18 | 19 | def test_get_own_p2p_order_history(): 20 | orders = w.get_own_p2p_order_history(status='COMPLETED_FOR_REQUESTER') 21 | for o in orders: 22 | check_api_update(o) 23 | 24 | 25 | def test_get_p2p_order(): 26 | o = w.get_own_p2p_order_history(status='CANCELLED_OR_CANCELLING')[0] 27 | check_api_update( 28 | w.get_p2p_order(order_id=o.id) 29 | ) 30 | 31 | 32 | def test_get_p2p_rate(): 33 | assert 'rate' in w.get_p2p_rate() 34 | 35 | 36 | def test_get_p2p_payment_methods_by_currency(): 37 | assert 'name' in w.get_p2p_payment_methods_by_currency()[0] 38 | 39 | 40 | def test_create_p2p_order_SALE(): 41 | pid = w.get_user_p2p_payments()[0]['id'] 42 | 43 | new_offer = w.create_p2p_offer( 44 | comment='', 45 | currency='USDT', 46 | fiat='RUB', 47 | amount=10, 48 | margine=120, 49 | offer_type='SALE', 50 | order_min_price=500, 51 | order_max_price=1000, 52 | pay_methods=[pid], 53 | order_rounding_required=True, 54 | ) 55 | check_api_update(new_offer) 56 | w.activate_p2p_offer(new_offer.id, new_offer.type) 57 | 58 | 59 | def test_create_p2p_order_PURCHASE(): 60 | new_offer = w.create_p2p_offer( 61 | comment='', 62 | currency='USDT', 63 | fiat='RUB', 64 | amount=10, 65 | margine=90, 66 | offer_type='PURCHASE', 67 | order_min_price=500, 68 | pay_methods=['tinkoff'], 69 | ) 70 | check_api_update(new_offer) 71 | w.activate_p2p_offer(new_offer.id, new_offer.type) 72 | 73 | 74 | def test_enable_p2p_bidding(): 75 | assert w.enable_p2p_bidding() 76 | 77 | 78 | def test_disable_p2p_bidding(): 79 | assert w.disable_p2p_bidding() 80 | 81 | 82 | def test_edit_p2p_offer(): 83 | offer = w.get_own_p2p_offers()[0] 84 | check_api_update( 85 | w.edit_p2p_offer(offer.id, volume=11, comment='Test') 86 | ) 87 | 88 | 89 | def test_get_own_p2p_offers(): 90 | check_api_update( 91 | w.get_own_p2p_offers()[0] 92 | ) 93 | 94 | 95 | def test_get_own_p2p_offer(): 96 | o = w.get_own_p2p_offers()[0] 97 | check_api_update( 98 | w.get_own_p2p_offer(o.id) 99 | ) 100 | 101 | 102 | def test_get_p2p_offer(): 103 | o = w.get_own_p2p_offers()[0] 104 | check_api_update( 105 | w.get_p2p_offer(o.id) 106 | ) 107 | 108 | 109 | def test_accept_p2p_order(): 110 | o = w.get_own_p2p_order_history(status='ALL_ACTIVE')[0] 111 | check_api_update( 112 | w.accept_p2p_order(o.id, o.offerType) 113 | ) 114 | 115 | 116 | def test_get_p2p_market(): 117 | check_api_update( 118 | w.get_p2p_market('TON', 'RUB', 'SALE')[0] 119 | ) 120 | 121 | 122 | def test_get_p2p_market_verified(): 123 | check_api_update( 124 | w.get_p2p_market('TON', 'RUB', 'SALE', merchant_verified="VERIFIED")[0] 125 | ) 126 | 127 | 128 | def test_send_to_address(): 129 | assert 'transaction_id' in w.send_to_address(amount=5, currency='TON', address='address') 130 | -------------------------------------------------------------------------------- /tests/utils.py: -------------------------------------------------------------------------------- 1 | from dataclasses import fields 2 | 3 | 4 | def check_unknown_things(n, c, p): 5 | if type(c) is list: 6 | c = c[0] 7 | if '__dataclass_fields__' in dir(c): 8 | for _field in fields(c): 9 | next_p = p.copy() 10 | next_p.append(_field.name) 11 | check_unknown_things(_field.name, getattr(c, _field.name), next_p) 12 | else: 13 | if n == 'unknown_things' and c: 14 | raise Exception('New params in API! ' + ' -> '.join(p), c) 15 | 16 | 17 | def check_api_update(_dataclass): 18 | """ Only for dataclasses | Find new params in API 19 | 20 | :return: 21 | """ 22 | for field in fields(_dataclass): 23 | path = [field.name] 24 | check_unknown_things(field.name, getattr(_dataclass, field.name), path) 25 | -------------------------------------------------------------------------------- /wallet/__init__.py: -------------------------------------------------------------------------------- 1 | from wallet.rest import Wallet 2 | from wallet.web_client import Client 3 | -------------------------------------------------------------------------------- /wallet/rest/__init__.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import time 3 | import uuid 4 | from typing import Optional, Literal 5 | 6 | import requests 7 | 8 | from wallet.types import Offer, Balances, BalanceAccount, TxResponse, TxDetails, Order, MarketOffer 9 | from wallet.types.offers import OrderHistory 10 | 11 | 12 | class Wallet: 13 | def __init__(self, auth_token: str, log_response=False, token_file_path=None): 14 | self.token_file_path = token_file_path 15 | self.log_response = log_response 16 | self.auth_token = auth_token 17 | self.session = requests.Session() 18 | self.session.headers = { 19 | 'content-type': 'application/json', 20 | } 21 | 22 | self.endpoint = "https://walletbot.me" 23 | self.endpoint_p2p = "https://p2p.walletbot.me" 24 | 25 | if auth_token == '' or len(auth_token) < 30: 26 | raise Exception('[!] Bad auth token!') 27 | 28 | @staticmethod 29 | def token_from_file(path, log_response=False): 30 | with open(path, 'r') as f: 31 | auth_token = f.read() 32 | return Wallet(auth_token, log_response, token_file_path=path) 33 | 34 | def update_token(self): 35 | """ For dynamically token update from "token_file_path" 36 | 37 | :return: 38 | """ 39 | if self.token_file_path is None: 40 | raise Exception('[!] token_file_path not set!') 41 | with open(self.token_file_path, 'r') as f: 42 | self.auth_token = f.read() 43 | 44 | def request(self, path: str, api_version: str = "api/v1", method: str = "GET", json_data=None, params=None, 45 | max_attempts=3, endpoint_name: Optional[Literal['p2p', "main"]] = 'main', auth_type_bearer=True): 46 | 47 | if endpoint_name == 'p2p': 48 | url = f"{self.endpoint_p2p}/{api_version}/{path}" 49 | elif endpoint_name == 'main': 50 | url = f"{self.endpoint}/{api_version}/{path}" 51 | else: 52 | raise Exception(f'[!] Wrong Endpoint Name "{endpoint_name}"!') 53 | 54 | if auth_type_bearer: 55 | self.session.headers['authorization'] = 'Bearer ' + self.auth_token 56 | else: 57 | self.session.headers['authorization'] = self.auth_token 58 | 59 | counter = 0 60 | while counter < max_attempts: 61 | counter += 1 62 | try: 63 | response = self.session.request(method, url, json=json_data, params=params, timeout=10) 64 | status_code = response.status_code 65 | 66 | if status_code == 200: 67 | if self.log_response: 68 | print(response.text) 69 | jdata = response.json() 70 | 71 | if type(jdata) is list: 72 | return jdata 73 | 74 | status = jdata.get("status") 75 | if status: 76 | if status == 'SUCCESS' or status == "success": 77 | if 'data' in jdata: 78 | return jdata['data'] 79 | else: 80 | return jdata 81 | else: 82 | raise Exception(f'Bad request status: {jdata["status"]}') 83 | else: 84 | return jdata 85 | elif status_code == 401 or status_code == 400: 86 | raise Exception(f'API ERROR - {response.text}') 87 | elif status_code == 429: 88 | raise Exception('You are Rate Limited') 89 | else: 90 | raise Exception(f'REQ STATUS CODE: {status_code}') 91 | 92 | except Exception as e: 93 | print(f'[!] Request error: {e}. Try attempt #{counter}') 94 | time.sleep(2) 95 | 96 | raise Exception('[!] Request attempts end') 97 | 98 | def get_earn_campings(self): 99 | return self.request('earn/campaigns', 'v2api') 100 | 101 | def get_user_balances(self, currency: str = None) -> Balances | BalanceAccount: 102 | """ Account balances 103 | 104 | :param currency: TON NOT BTC USDT 105 | :return: 106 | """ 107 | balances: Balances = Balances.from_dict(self.request(f'accounts/', auth_type_bearer=False)) 108 | if currency: 109 | for a in balances.accounts: 110 | if a.currency == currency: 111 | return a 112 | else: 113 | return balances 114 | 115 | def get_transactions(self, limit: int = 20, last_id: int = None, crypto_currency: str = None, 116 | transaction_type: str = None, transaction_gateway: str = None) -> list[TxDetails]: 117 | """ Account in withdraw | deposit txs 118 | 119 | :param transaction_gateway: withdraw_onchain | tg_transfer | top_up | p2p_order | earn | internal 120 | :param transaction_type: withdraw | deposit 121 | :param crypto_currency: BTC USDT TON NOT 122 | :param limit: 123 | :param last_id: 124 | :return: 125 | """ 126 | params = { 127 | 'limit': limit, 128 | 'last_id': last_id, 129 | 'crypto_currency': crypto_currency, 130 | 'transaction_type': transaction_type, 131 | 'transaction_gateway': transaction_gateway 132 | } 133 | return TxResponse.from_dict(self.request(f'transactions/', params=params, auth_type_bearer=False)).transactions 134 | 135 | def get_transaction_details(self, tx_id: int) -> TxDetails: 136 | """ One tx info 137 | 138 | :param tx_id: 139 | :return: 140 | """ 141 | 142 | return TxDetails.from_dict(self.request(f'transactions/details/{tx_id}/', auth_type_bearer=False)) 143 | 144 | def get_passcode_info(self) -> dict: 145 | return self.request(f'passcode/info', api_version='v2api') 146 | 147 | def get_recovery_email(self, product: str): 148 | """ 149 | 150 | :param product: wallet | ton-space 151 | :return: 152 | """ 153 | params = {'product': product} 154 | return self.request(f'recovery-email/', api_version='v2api', params=params) 155 | 156 | def get_user_region_verification(self) -> dict: 157 | return self.request('region-verification/status', api_version='users/public-api/v2') 158 | 159 | def get_wallet(self, crypto_currency: str) -> list: 160 | """ Get address your wallet to deposit 161 | 162 | :param crypto_currency: TON 163 | :return: 164 | """ 165 | return self.request(f'users/get_or_create_wallets/{crypto_currency}', auth_type_bearer=False) 166 | 167 | def get_exchange_pair_info(self, from_currency: str, to_currency=str, from_amount: float = 1) -> dict: 168 | """ 169 | 170 | :param from_currency: BTC USDT TON NOT 171 | :param to_currency: BTC USDT TON NOT 172 | :param from_amount: 173 | :return: 174 | """ 175 | data = { 176 | "from_amount": from_amount, 177 | "from_currency": from_currency, 178 | "to_currency": to_currency 179 | } 180 | return self.request('exchange/convert/', method='POST', json_data=data, auth_type_bearer=False) 181 | 182 | def create_exchange(self, from_currency: str, to_currency: str, from_amount: float, local_currency: str): 183 | """ 184 | Create Swap simulate in Wallet. To complete swap -> submit_exchange( uid ) 185 | 186 | :param local_currency: "RUB" ...1 187 | :param from_currency: "USDT" "TON"... 188 | :param to_currency: "USDT" "TON"... 189 | :param from_amount: 190 | :return: { 191 | "uid": "................", 192 | "ttl": 30, 193 | "from_amount": 1, 194 | "from_balance_after": 1.240279, 195 | "from_currency": "USDT", 196 | "to_amount": 0.122889771, 197 | "to_balance_after": 0.130665475, 198 | "to_currency": "TON", 199 | "from_fiat_amount": 88.78, 200 | "from_fiat_currency": "RUB", 201 | "to_fiat_amount": 87.52, 202 | "to_fiat_currency": "RUB", 203 | "rate": 0.122889771, 204 | "fee_amount": 0, 205 | "fee_currency": "USDT" 206 | } 207 | """ 208 | data = { 209 | "from_amount": from_amount, 210 | "from_currency": from_currency, 211 | "to_currency": to_currency, 212 | "local_currency": local_currency 213 | } 214 | return self.request('exchange/create_exchange/', method='POST', json_data=data, auth_type_bearer=False) 215 | 216 | def submit_exchange(self, uid): 217 | return self.request(f'exchange/submit_exchange/{uid}/', method='POST', auth_type_bearer=False) 218 | 219 | def get_available_exchanges(self): 220 | """ 221 | https://walletbot.me/api/v1/exchange/get-available-exchanges/ 222 | GET 223 | """ 224 | raise Exception('Not realized') 225 | 226 | # P2P Market 227 | 228 | def get_own_p2p_user_info(self): 229 | data = {'deviceId': hashlib.md5(uuid.UUID(int=uuid.getnode()).hex.encode('utf-8')).hexdigest()[:20]} 230 | return self.request('user/get', api_version='users/public-api/v2', method='POST', json_data=data) 231 | 232 | def get_own_p2p_permissions(self): 233 | """ 234 | return { 'can_buy': True, 'can_top_up': True, 'can_exchange': True, 'can_withdraw_inner': True, 235 | 'can_withdraw_outer': True, 'can_use_wpay_as_payer': True, 'can_use_wpay_as_merchant': True, 236 | 'can_usdt_raffle': False, 'can_use_p2p': True } :return: 237 | """ 238 | return self.request('users/permissions/', api_version='api/v1', method='GET', auth_type_bearer=False) 239 | 240 | def get_user_p2p_payments(self) -> dict: 241 | """ List own payment methods 242 | 243 | :return: 244 | """ 245 | return self.request('payment-details/get/by-user-id', api_version='p2p/public-api/v3', method='POST', 246 | endpoint_name="p2p") 247 | 248 | def get_supported_p2p_fiat(self): 249 | """ 250 | https://walletbot.me/p2p/public-api/v2/currency/all-supported-fiat 251 | POST 252 | """ 253 | raise Exception('Not realized') 254 | 255 | def get_offer_settings(self): 256 | """ 257 | https://walletbot.me/p2p/public-api/v2/offer/settings/get 258 | POST 259 | """ 260 | raise Exception('Not realized') 261 | 262 | def get_own_p2p_order_history(self, offset=0, limit=20, status: str = 'ALL_ACTIVE') -> list[Order]: 263 | """ 264 | 265 | :param offset: 266 | :param limit: 267 | :param status: ALL_ACTIVE | COMPLETED_FOR_REQUESTER | CANCELLED_OR_CANCELLING 268 | :return: 269 | """ 270 | data = { 271 | 'offset': offset, 272 | 'limit': limit, 273 | 'filter': { 274 | 'status': status, 275 | }, 276 | } 277 | 278 | orders_raw = self.request( 279 | "offer/order/history/get-by-user-id", 280 | method='POST', 281 | api_version='p2p/public-api/v2', 282 | json_data=data, endpoint_name="p2p") 283 | 284 | return [OrderHistory.from_dict(o) for o in orders_raw] 285 | 286 | def get_p2p_order(self, order_id: int): 287 | """ Order - its active or completed offer 288 | 289 | :param order_id: 290 | :return: 291 | """ 292 | data = {'orderId': order_id} 293 | return Order.from_dict( 294 | self.request('offer/order/get', api_version='p2p/public-api/v2', method='POST', json_data=data)) 295 | 296 | def get_p2p_rate(self, base='TON', quote='RUB') -> dict: 297 | params = {'base': base, 'quote': quote} 298 | return self.request(f'rate/crypto-to-fiat', api_version='rates/public-api/v1', params=params) 299 | 300 | def get_p2p_price_limits(self): 301 | """ 302 | https://walletbot.me/p2p/public-api/v2/offer/limits/fixed-price/get 303 | POST 304 | { 305 | "baseCurrencyCode": "TON", 306 | "quoteCurrencyCode": "RUB" 307 | } 308 | 309 | :return: 310 | """ 311 | raise Exception('Not realized') 312 | 313 | def get_p2p_payment_methods_by_currency(self, currency='RUB') -> dict: 314 | data = {"currencyCode": currency} 315 | return self.request( 316 | 'payment-details/get-methods/by-currency-code', 317 | api_version=f'p2p/public-api/v3', 318 | method='POST', 319 | json_data=data, endpoint_name="p2p") 320 | 321 | def create_p2p_order(self, offer_id: int, payment_details_id: int, offer_type, currency_code: str, amount: float): 322 | """ Create order. To confirm this order -> confirm_p2p_order 323 | 324 | :param offer_id: 325 | :param payment_details_id: 326 | :param offer_type: PURCHASE | SALE 327 | :param currency_code: USDT 328 | :param amount: 329 | :return: 330 | """ 331 | data = { 332 | "offerId": offer_id, 333 | "paymentDetailsId": payment_details_id, 334 | "volume": { 335 | "currencyCode": currency_code, 336 | "amount": amount 337 | }, 338 | "type": offer_type 339 | } 340 | return self.request('offer/order/create-by-volume', api_version='p2p/public-api/v2', method='POST', 341 | json_data=data, endpoint_name="p2p") 342 | 343 | def confirm_p2p_order(self, order_id, order_type): 344 | """ 345 | 346 | :param order_id: 347 | :param order_type: 348 | :return: 349 | """ 350 | 351 | data = { 352 | 'orderId': order_id, 353 | 'type': order_type, 354 | } 355 | return Order.from_dict( 356 | self.request('offer/order/confirm', api_version='p2p/public-api/v2', method='POST', json_data=data, 357 | endpoint_name='p2p')) 358 | 359 | def create_p2p_offer(self, currency: str, amount: float, fiat: str, margine: int, offer_type: str, 360 | order_min_price: float, pay_methods: list, comment: str = None, price_type: str = "FLOATING", 361 | confirm_timeout_tag: str = "PT15M", order_max_price: float | None = None, 362 | order_rounding_required=False) -> Offer: 363 | """ 364 | 365 | :param order_rounding_required: 366 | :param pay_methods: list of payment methods ids or list of payment methods codes 367 | if SALE - get_user_p2p_payments() 368 | if PURCHASE - get_p2p_payment_methods_by_currency() 369 | 370 | :param confirm_timeout_tag: PT15M PT5M PT10M 371 | :param comment: 372 | :param order_min_price: 373 | :param order_max_price: 374 | :param margine: 90 - 120 375 | :param fiat: RUB | KZT etc... 376 | :param price_type: FLOATING | FIXED 377 | :param amount: 378 | :param currency: TON | BTC 379 | :param offer_type: SALE | PURCHASE 380 | :return: 381 | """ 382 | 383 | data = { 384 | "comment": comment, 385 | "initVolume": { 386 | "currencyCode": currency, 387 | "amount": str(amount) 388 | }, 389 | "orderAmountLimits": { 390 | "min": order_min_price, 391 | "max": order_max_price 392 | }, 393 | "orderRoundingRequired": order_rounding_required, 394 | "paymentConfirmTimeout": confirm_timeout_tag, 395 | "price": { 396 | "type": price_type, 397 | "baseCurrencyCode": currency, 398 | "quoteCurrencyCode": fiat, 399 | "value": margine 400 | }, 401 | "type": offer_type, 402 | } 403 | 404 | if offer_type == 'PURCHASE': 405 | data['paymentMethodCodes'] = pay_methods 406 | else: 407 | data['paymentDetailsIds'] = pay_methods 408 | 409 | return Offer.from_dict( 410 | self.request('offer/create', api_version='p2p/public-api/v2', method='POST', json_data=data, 411 | endpoint_name='p2p')) 412 | 413 | def activate_p2p_offer(self, offer_id, offer_type): 414 | data = {"type": offer_type, "offerId": offer_id} 415 | return self.request('offer/activate', api_version='p2p/public-api/v2', method='POST', json_data=data, 416 | endpoint_name="p2p") 417 | 418 | def deactivate_p2p_offer(self, offer_id, offer_type): 419 | data = { 420 | "type": offer_type, 421 | "offerId": offer_id 422 | } 423 | return self.request('offer/deactivate', api_version='p2p/public-api/v2', method='POST', json_data=data, 424 | endpoint_name="p2p") 425 | 426 | def disable_p2p_bidding(self): 427 | return self.request('user-settings/disable-bidding', api_version='p2p/public-api/v2', method='POST', 428 | endpoint_name="p2p") 429 | 430 | def enable_p2p_bidding(self): 431 | return self.request('user-settings/enable-bidding', api_version='p2p/public-api/v2', method='POST', 432 | endpoint_name="p2p") 433 | 434 | def get_own_p2p_offers(self, offset: int = 0, limit: int = 10, 435 | offer_type: str = 'PURCHASE', status: str | None = None) -> list[Offer]: 436 | """ 437 | :param offset: 438 | :param limit: None | PURCHASE | SALE 439 | :param offer_type: 440 | :param status: None | ACTIVE | INACTIVE 441 | :return: 442 | """ 443 | data = { 444 | "offset": offset, 445 | "limit": limit, 446 | "offerType": offer_type 447 | } 448 | raw_offers = self.request( 449 | 'offer/user-own/list', api_version='p2p/public-api/v2', method='POST', json_data=data) 450 | 451 | offers = [Offer.from_dict(o) for o in raw_offers] 452 | 453 | if status is not None: 454 | offers = [Offer.from_dict(o) for o in raw_offers if o['status'] == status] 455 | 456 | return offers 457 | 458 | def get_own_p2p_offer(self, offer_id: int) -> Offer: 459 | json_data = {"offerId": offer_id} 460 | return Offer.from_dict(self.request( 461 | 'offer/get-user-own', api_version='p2p/public-api/v2', method='POST', json_data=json_data)) 462 | 463 | def get_p2p_offer(self, offer_id: int) -> Offer: 464 | data = {"offerId": offer_id} 465 | return Offer.from_dict( 466 | self.request('offer/get', api_version='p2p/public-api/v2', method='POST', json_data=data)) 467 | 468 | def edit_p2p_offer(self, offer_id: int, margine: int = None, volume: float = None, 469 | order_amount_min: float = None, comment: str = None): 470 | """ 471 | 472 | :param margine: 473 | :param comment: 474 | :param order_amount_min: 475 | :param volume: 476 | :param offer_id: 477 | :return: 478 | """ 479 | offer = self.get_p2p_offer(offer_id) 480 | 481 | data = { 482 | "offerId": offer_id, 483 | "paymentConfirmTimeout": offer.paymentConfirmTimeout, 484 | "type": offer.type, 485 | "price": { 486 | "type": offer.price.type, 487 | "value": offer.price.value 488 | }, 489 | "orderAmountLimits": { 490 | "min": offer.orderAmountLimits.min 491 | }, 492 | "comment": offer.comment, 493 | "volume": 5, 494 | } 495 | 496 | if margine: 497 | data['price']['value'] = margine 498 | 499 | if volume: 500 | data['volume'] = volume 501 | 502 | if order_amount_min: 503 | data['orderAmountLimits']['min'] = order_amount_min 504 | 505 | if comment: 506 | data['comment'] = comment 507 | 508 | return Offer.from_dict( 509 | self.request('offer/edit', api_version='p2p/public-api/v2', method='POST', json_data=data, 510 | endpoint_name='p2p')) 511 | 512 | def accept_p2p_order(self, order_id: int, offer_type: str) -> Order: 513 | """ 514 | 515 | :param order_id: 516 | :param offer_type: SALE | PURCHASE 517 | :return: 518 | """ 519 | data = { 520 | 'orderId': order_id, 521 | 'type': offer_type, 522 | } 523 | return Order.from_dict( 524 | self.request('offer/order/accept', api_version='p2p/public-api/v2', method='POST', json_data=data, 525 | endpoint_name='p2p')) 526 | 527 | def get_p2p_market(self, base_currency_code: str, quote_currency_code: str, offer_type='SALE', offset=0, limit=100, 528 | desired_amount=None, payment_method_codes: list = None, 529 | merchant_verified: Optional[Literal[None, "VERIFIED", "TRUSTED"]] = None) -> list[MarketOffer]: 530 | """ 531 | 532 | :param payment_method_codes: ["sbp", "sberbankru"] - use get_p2p_payment_methods_by_currency() to get needed method code 533 | :param desired_amount: 534 | :param base_currency_code: TON | BTC 535 | :param quote_currency_code: RUB | KZT etc. 536 | :param offer_type: SALE | PURCHASE 537 | :param offset: 538 | :param limit: 539 | :param merchant_verified: str "VERIFIED" | "TRUSTED" | None (All) 540 | :return: 541 | """ 542 | data = { 543 | "baseCurrencyCode": base_currency_code, 544 | "quoteCurrencyCode": quote_currency_code, 545 | "offerType": offer_type, 546 | "offset": offset, 547 | "limit": limit, 548 | "desiredAmount": desired_amount, 549 | "paymentMethodCodes": payment_method_codes, 550 | "merchantVerified": merchant_verified 551 | } 552 | market = self.request( 553 | 'offer/depth-of-market', api_version='p2p/public-api/v2', method='POST', json_data=data) 554 | return [MarketOffer.from_dict(offer) for offer in market] 555 | 556 | def send_to_address(self, amount, currency, address, network=None): 557 | """ 558 | 559 | :param network: for USDT - ton | tron 560 | :param amount: 561 | :param currency: 562 | :param address: 563 | :return: dict {'transaction_id': 000000} 564 | """ 565 | 566 | if currency == 'USDT' and not network: 567 | raise Exception('NETWORK NOT SET | For USDT set network="ton" or network="tron"') 568 | 569 | validate = self.withdrawals_validate_amount(amount, currency, address=address, network=network) 570 | if validate['status'] == 'OK': 571 | withdraw_request = self._create_withdraw_request(amount, currency, address, network) 572 | return self._process_withdraw_request(withdraw_request['uid']) 573 | else: 574 | raise Exception(f'[!] Bad validate status: {validate}') 575 | 576 | def _process_withdraw_request(self, uid): 577 | return self.request(f'withdrawals/process_withdraw_request/{uid}/', method='POST') 578 | 579 | def withdrawals_validate_amount(self, amount: float, currency: str, receiver_tg_id: int = None, 580 | address: str = None, network: str = None): 581 | params = { 582 | 'amount': amount, 583 | 'currency': currency, 584 | 'receiver_tg_id': receiver_tg_id, 585 | 'address': address, 586 | 'network': network.lower() if network else None 587 | } 588 | return self.request(f'withdrawals/validate_amount/', params=params) 589 | 590 | def _create_withdraw_request(self, amount: float, currency: str, address: str, network: str): 591 | params = { 592 | 'amount': str(amount), 593 | 'currency': currency, 594 | 'max_value': 'false', 595 | 'address': address, 596 | "network": network 597 | } 598 | return self.request('withdrawals/create_withdraw_request/', method='POST', params=params) 599 | 600 | def process_transfer(self, tx_id: str): 601 | return self.request(f'transfers/process_transfer/{tx_id}/', method='POST') 602 | 603 | def send_to_user(self, amount, currency, receiver_tg_id): 604 | v = self.withdrawals_validate_amount(amount, currency, receiver_tg_id) 605 | 606 | if v['status'] == 'OK': 607 | data = { 608 | 'amount': amount, 609 | 'currency': currency 610 | } 611 | tx = self.request( 612 | f'transfers/create_transfer_request/', method='POST', json_data=data)['transfer_request_id'] 613 | 614 | return self.process_transfer(tx) 615 | 616 | else: 617 | raise Exception(f'[!] Error withdrawals_validate_amount: {v}') 618 | -------------------------------------------------------------------------------- /wallet/types/__init__.py: -------------------------------------------------------------------------------- 1 | from wallet.types.balances import Balances, BalanceAccount 2 | from wallet.types.offers import Offer, Order, MarketOffer 3 | from wallet.types.transactions import TxResponse, TxDetails 4 | -------------------------------------------------------------------------------- /wallet/types/balances.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from dataclasses_json import CatchAll, Undefined, dataclass_json 4 | 5 | 6 | @dataclass_json(undefined=Undefined.INCLUDE) 7 | @dataclass 8 | class AccountAddress: 9 | address: str | None 10 | network: str 11 | network_code: str 12 | unknown_things: CatchAll = None 13 | 14 | 15 | @dataclass_json(undefined=Undefined.INCLUDE) 16 | @dataclass 17 | class BalanceAccount: 18 | currency: str 19 | available_balance: float 20 | available_balance_fiat_amount: float 21 | available_balance_fiat_currency: str 22 | has_transactions: bool 23 | addresses: list[AccountAddress] 24 | rate: float 25 | is_created: bool 26 | unknown_things: CatchAll = None 27 | 28 | 29 | @dataclass_json(undefined=Undefined.INCLUDE) 30 | @dataclass 31 | class Balances: 32 | available_balance_total_fiat_amount: float 33 | available_balance_total_fiat_currency: str 34 | accounts: list[BalanceAccount] 35 | unknown_things: CatchAll = None 36 | 37 | @staticmethod 38 | def to_dict(): 39 | pass 40 | 41 | @staticmethod 42 | def from_dict(data): 43 | pass 44 | -------------------------------------------------------------------------------- /wallet/types/offers.py: -------------------------------------------------------------------------------- 1 | import pprint 2 | from dataclasses import dataclass, asdict 3 | from typing import Literal 4 | 5 | from dataclasses_json import dataclass_json, Undefined, CatchAll 6 | 7 | 8 | @dataclass_json(undefined=Undefined.INCLUDE) 9 | @dataclass 10 | class Value: 11 | name: str 12 | value: str 13 | unknown_things: CatchAll = None 14 | 15 | 16 | @dataclass_json(undefined=Undefined.INCLUDE) 17 | @dataclass 18 | class Attributes: 19 | version: str 20 | values: list[Value] 21 | unknown_things: CatchAll = None 22 | 23 | 24 | @dataclass_json(undefined=Undefined.INCLUDE) 25 | @dataclass 26 | class AvailableVolume: 27 | currencyCode: str 28 | amount: float 29 | unknown_things: CatchAll = None 30 | 31 | 32 | @dataclass_json(undefined=Undefined.INCLUDE) 33 | @dataclass 34 | class AmountLimits: 35 | currencyCode: str 36 | min: float 37 | max: float 38 | approximate: bool 39 | makerDefinedMax: str | None = None 40 | unknown_things: CatchAll = None 41 | 42 | 43 | @dataclass_json(undefined=Undefined.INCLUDE) 44 | @dataclass 45 | class OrderVolumeLimits: 46 | currencyCode: str 47 | min: float 48 | max: float 49 | approximate: bool 50 | unknown_things: CatchAll = None 51 | 52 | 53 | @dataclass_json(undefined=Undefined.INCLUDE) 54 | @dataclass 55 | class PaymentMethod: 56 | code: str 57 | name: str 58 | originNameLocale: str 59 | nameEng: str 60 | unknown_things: CatchAll = None 61 | 62 | @staticmethod 63 | def to_dict(): 64 | pass 65 | 66 | 67 | @dataclass_json(undefined=Undefined.INCLUDE) 68 | @dataclass 69 | class Price: 70 | type: str 71 | baseCurrencyCode: str 72 | quoteCurrencyCode: str 73 | value: float 74 | estimated: float | None = None 75 | unknown_things: CatchAll = None 76 | 77 | 78 | @dataclass_json(undefined=Undefined.INCLUDE) 79 | @dataclass 80 | class Statistics: 81 | userId: int 82 | totalOrdersCount: int 83 | successRate: str 84 | successPercent: int 85 | unknown_things: CatchAll = None 86 | 87 | 88 | @dataclass_json(undefined=Undefined.INCLUDE) 89 | @dataclass 90 | class User: 91 | userId: int 92 | nickname: str 93 | avatarCode: str 94 | statistics: Statistics 95 | isVerified: bool 96 | onlineStatus: Literal["ONLINE", "OFFLINE"] 97 | lastOnlineMinutesAgo: int | None = None 98 | unknown_things: CatchAll = None 99 | 100 | 101 | @dataclass_json(undefined=Undefined.INCLUDE) 102 | @dataclass 103 | class UserID: 104 | userId: int 105 | unknown_things: CatchAll = None 106 | 107 | 108 | @dataclass_json(undefined=Undefined.INCLUDE) 109 | @dataclass 110 | class Volume: 111 | currencyCode: str 112 | amount: float 113 | unknown_things: CatchAll = None 114 | 115 | 116 | @dataclass_json(undefined=Undefined.INCLUDE) 117 | @dataclass 118 | class ChangeLogItem: 119 | status: str 120 | createDateTime: str 121 | initiatorUserId: int | None = None 122 | unknown_things: CatchAll = None 123 | 124 | 125 | @dataclass_json(undefined=Undefined.INCLUDE) 126 | @dataclass 127 | class ChangeLog: 128 | items: list[ChangeLogItem] 129 | unknown_things: CatchAll = None 130 | 131 | 132 | @dataclass_json(undefined=Undefined.INCLUDE) 133 | @dataclass 134 | class PaymentDetails: 135 | id: int 136 | userId: int 137 | paymentMethod: PaymentMethod 138 | currency: str 139 | attributes: Attributes 140 | name: str 141 | unknown_things: CatchAll = None 142 | 143 | 144 | @dataclass_json(undefined=Undefined.INCLUDE) 145 | @dataclass 146 | class PaymentDetailsHistory: 147 | paymentMethod: PaymentMethod 148 | unknown_things: CatchAll = None 149 | 150 | 151 | @dataclass_json(undefined=Undefined.INCLUDE) 152 | @dataclass 153 | class Fee: 154 | rate: float 155 | # initVolume: Volume 156 | availableVolume: Volume 157 | unknown_things: CatchAll = None 158 | 159 | 160 | @dataclass_json(undefined=Undefined.INCLUDE) 161 | @dataclass 162 | class OrderRounding: 163 | mode: str 164 | scale: float 165 | unknown_things: CatchAll = None 166 | 167 | 168 | @dataclass_json(undefined=Undefined.INCLUDE) 169 | @dataclass 170 | class Offer: 171 | id: int 172 | number: str 173 | type: str 174 | price: Price 175 | availableVolume: AvailableVolume 176 | orderAmountLimits: AmountLimits 177 | orderVolumeLimits: OrderVolumeLimits 178 | orderRounding: OrderRounding | None = None 179 | status: str | None = None 180 | changeLog: ChangeLog | None = None 181 | comment: str | None = None 182 | createDateTime: str | None = None 183 | paymentConfirmTimeout: str | None = None 184 | # initVolume: Volume | None = None 185 | user: User | None = None 186 | paymentMethods: list[PaymentMethod] | None = None 187 | fee: Fee | None = None 188 | paymentDetails: list[PaymentDetails] | None = None 189 | orderConfirmationTimeout: str | None = None 190 | orderAcceptTimeout: str | None = None 191 | unknown_things: CatchAll = None 192 | 193 | @staticmethod 194 | def to_dict(): 195 | pass 196 | 197 | @staticmethod 198 | def from_dict(data): 199 | pass 200 | 201 | 202 | @dataclass_json(undefined=Undefined.INCLUDE) 203 | @dataclass 204 | class MarketOffer: 205 | id: int 206 | number: str 207 | user: User 208 | type: str 209 | price: Price 210 | orderAmountLimits: AmountLimits 211 | orderVolumeLimits: AmountLimits 212 | paymentMethods: list[PaymentMethod] 213 | availableVolume: float 214 | unknown_things: CatchAll = None 215 | 216 | @staticmethod 217 | def to_dict(): 218 | pass 219 | 220 | @staticmethod 221 | def from_dict(data): 222 | pass 223 | 224 | 225 | @dataclass_json(undefined=Undefined.INCLUDE) 226 | @dataclass 227 | class Order: 228 | id: int 229 | number: str 230 | seller: User 231 | buyer: User 232 | offerId: int 233 | offerType: str 234 | offerComment: str 235 | isExpress: bool 236 | price: Price 237 | paymentDetails: PaymentDetails 238 | volume: Volume 239 | amount: Volume 240 | feeVolume: Volume 241 | paymentConfirmTimeout: str 242 | createDateTime: str 243 | holdRestrictionsWillBeApplied: bool 244 | status: str 245 | changeLog: ChangeLog 246 | buyerSendingPaymentConfirmationTimeout: str 247 | confirmationDateTime: str | None 248 | statusUpdateDateTime: str | None 249 | cancelReason: str | None = None 250 | acceptDateTime: str | None = None 251 | unknown_things: CatchAll = None 252 | 253 | @staticmethod 254 | def to_dict(): 255 | pass 256 | 257 | @staticmethod 258 | def from_dict(data): 259 | pass 260 | 261 | 262 | @dataclass_json(undefined=Undefined.INCLUDE) 263 | @dataclass 264 | class OrderHistory: 265 | amount: Volume 266 | buyer: UserID 267 | id: int 268 | number: str 269 | paymentDetails: PaymentDetailsHistory 270 | seller: UserID 271 | status: str 272 | statusUpdateDateTime: str | None 273 | volume: Volume 274 | unknown_things: CatchAll = None 275 | 276 | @staticmethod 277 | def to_dict(): 278 | pass 279 | 280 | @staticmethod 281 | def from_dict(data): 282 | pass 283 | -------------------------------------------------------------------------------- /wallet/types/transactions.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass 2 | 3 | from dataclasses_json import CatchAll, Undefined, dataclass_json 4 | 5 | 6 | @dataclass_json(undefined=Undefined.INCLUDE) 7 | @dataclass 8 | class AccountAddress: 9 | address: str | None 10 | network: str 11 | network_code: str 12 | unknown_things: CatchAll = None 13 | 14 | 15 | @dataclass_json(undefined=Undefined.INCLUDE) 16 | @dataclass 17 | class BalanceAccount: 18 | currency: str 19 | available_balance: float 20 | available_balance_fiat_amount: float 21 | available_balance_fiat_currency: str 22 | has_transactions: bool 23 | addresses: list[AccountAddress] 24 | unknown_things: CatchAll = None 25 | 26 | 27 | @dataclass_json(undefined=Undefined.INCLUDE) 28 | @dataclass 29 | class TxDetails: 30 | id: int 31 | type: str | None = None 32 | created_at: str | None = None 33 | crypto_amount: float | None = None 34 | fiat_amount: float | None = None 35 | crypto_currency: str | None = None 36 | fiat_currency: str | None = None 37 | value_at_time: float | None = None 38 | username: str | None = None 39 | tg_id: int | None = None 40 | mention: str | None = None 41 | status: str | None = None 42 | gateway: str | None = None 43 | input_addresses: str | None = None 44 | recipient_wallet_address: str | None = None 45 | transaction_link: str | None = None 46 | full_number_of_activations: int | None = None 47 | number_of_activations: int | None = None 48 | amount_of_activations: float | None = None 49 | remaining_amount: float | None = None 50 | fee_currency: str | None = None 51 | fee_amount: float | None = None 52 | activation_channel_title: str | None = None 53 | activation_channel_name: str | None = None 54 | check_url: str | None = None 55 | seller: str | None = None 56 | buyer: str | None = None 57 | avatar_code: str | None = None 58 | entity_id: int | None = None 59 | photo_url: str | None = None 60 | details_for_user: str | None = None 61 | pair_transaction_amount: float | None = None 62 | pair_transaction_currency: str | None = None 63 | is_blocked: str | None = None 64 | block: str | None = None 65 | is_cancellable: bool | None = None 66 | comment: str | None = None 67 | giveaway_gift: str | None = None 68 | network: str | None = None 69 | amount: int | None = None 70 | activated_amount: int | None = None 71 | currency: str | None = None 72 | block_reason: str | None = None 73 | cryptocurrency_exchange: str | None = None 74 | unknown_things: CatchAll = None 75 | 76 | @staticmethod 77 | def to_dict(): 78 | pass 79 | 80 | @staticmethod 81 | def from_dict(data): 82 | pass 83 | 84 | 85 | @dataclass_json(undefined=Undefined.INCLUDE) 86 | @dataclass 87 | class TxResponse: 88 | limit: int 89 | size: int 90 | transactions: list[TxDetails] 91 | offset: int = None 92 | unknown_things: CatchAll = None 93 | 94 | @staticmethod 95 | def to_dict(): 96 | pass 97 | 98 | @staticmethod 99 | def from_dict(data): 100 | pass 101 | -------------------------------------------------------------------------------- /wallet/web_client.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import json 3 | import os.path 4 | import threading 5 | import time 6 | 7 | from selenium.common import TimeoutException 8 | from selenium.webdriver import ChromeOptions 9 | from selenium.webdriver.common.by import By 10 | from selenium.webdriver.support import expected_conditions as EC 11 | from selenium.webdriver.support.wait import WebDriverWait 12 | from seleniumwire import webdriver 13 | 14 | 15 | class Client: 16 | def __init__(self, headless: bool = True, profile: str = 'main', temp_dir_path: str = None, 17 | page_load_strategy='none'): 18 | """ 19 | 20 | :param headless: hide or open browser 21 | :param profile: chrome profile 22 | :param temp_dir_path: profiles directory 23 | :param page_load_strategy: https://www.selenium.dev/documentation/webdriver/drivers/options/ 24 | """ 25 | self.receiver_id = None 26 | 27 | full_dir_path = '_temp/profile_' + str(profile) 28 | if not os.path.isdir(full_dir_path): 29 | os.makedirs(full_dir_path) 30 | self.auth(profile=profile, temp_dir_path=temp_dir_path) 31 | if temp_dir_path is None: 32 | temp_dir_path = os.getcwd() + '\\_temp\\' + 'profile_' + str(profile) 33 | os.makedirs(temp_dir_path, exist_ok=True) 34 | 35 | options = ChromeOptions() 36 | options.add_argument(f"--user-data-dir={temp_dir_path}") 37 | options.page_load_strategy = page_load_strategy 38 | if headless: 39 | options.add_argument('--headless=new') 40 | 41 | self.driver = webdriver.Chrome(chrome_options=options) 42 | self.driver.implicitly_wait(7) 43 | 44 | @classmethod 45 | def auth(cls, profile, temp_dir_path=None): 46 | c = Client(headless=False, profile=profile, temp_dir_path=temp_dir_path) 47 | start_page = "https://web.telegram.org/a/#1985737506" 48 | c.driver.get(start_page) 49 | print("Connect Telegram Account") 50 | 51 | try: 52 | WebDriverWait(c.driver, 5).until( 53 | EC.presence_of_element_located((By.ID, 'auth-qr-form')) 54 | ) 55 | print('Auth by QR') 56 | 57 | while True: 58 | if c.driver.current_url == start_page: 59 | return c 60 | else: 61 | time.sleep(1) 62 | 63 | except Exception as e: 64 | pass 65 | 66 | def _parse_token(self, wallet_endpoint): 67 | try: 68 | iframe = WebDriverWait(self.driver, 10).until( 69 | EC.presence_of_element_located((By.TAG_NAME, 'iframe')) 70 | ) 71 | except Exception as e: 72 | raise Exception('Iframe not find! Try start client with attr "headless"=False to recognize problem') 73 | 74 | self.driver.switch_to.frame(iframe) 75 | 76 | check_limit = 10 77 | start_t = time.time() 78 | while start_t + check_limit > time.time(): 79 | for request in self.driver.requests: 80 | if wallet_endpoint in request.url: 81 | if 'v1/users/auth/' in request.url: 82 | init_data = json.loads(request.body.decode('utf-8')) 83 | self.receiver_id = init_data['web_view_init_data']['receiver']['id'] 84 | if 'authorization' in request.headers: 85 | token = request.headers['authorization'] 86 | if 'Bearer' in token: 87 | token = token.split()[1] 88 | if not token: 89 | raise Exception('Token not find') 90 | return token 91 | time.sleep(0.5) 92 | raise Exception('Token not find') 93 | 94 | def get_token(self, username: str = 'wallet', wallet_endpoint='walletbot.me') -> str: 95 | self._log('Start parse token') 96 | start_t = time.time() 97 | 98 | self.driver.get( 99 | f"https://web.telegram.org/k/#?tgaddr=tg%3A%2F%2Fresolve%3Fdomain%3D{username}%26attach%3Dwallet") 100 | threading.Thread(target=self._confirm_popup, daemon=False).start() 101 | 102 | token = self._parse_token(wallet_endpoint) 103 | self._log(f'Token find. Time spent: {round(time.time() - start_t, 2)} sec') 104 | return token 105 | 106 | @staticmethod 107 | def _log(m): 108 | print(f"[{datetime.datetime.now()}] {m}") 109 | 110 | def _confirm_popup(self): 111 | try: 112 | element = WebDriverWait(self.driver, 2).until( 113 | EC.presence_of_element_located((By.CLASS_NAME, 'popup-button'))) 114 | if element: 115 | element.click() 116 | except TimeoutException: 117 | pass 118 | 119 | def stop(self): 120 | self.driver.quit() 121 | --------------------------------------------------------------------------------