├── 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 |
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 |
--------------------------------------------------------------------------------