defi-state-querier 0.4.30__py3-none-any.whl → 0.5.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- defi_services/__init__.py +1 -1
- defi_services/abis/multicall_v3_abi.py +440 -0
- defi_services/constants/network_constants.py +387 -0
- defi_services/jobs/processors/call_state_processor.py +12 -0
- defi_services/jobs/processors/multi_call_state_processor.py +12 -0
- defi_services/jobs/processors/multi_state_processor.py +1 -1
- defi_services/jobs/queriers/call_state_querier.py +101 -0
- defi_services/services/eth/__init__.py +0 -0
- defi_services/services/eth/eth_services.py +91 -0
- defi_services/services/multicall/__init__.py +0 -0
- defi_services/services/multicall/batch_queries_service.py +102 -0
- defi_services/services/multicall/multicall_v2.py +492 -0
- defi_services/services/multicall/state_query_service.py +549 -0
- defi_services/services/token_services.py +4 -3
- defi_services/utils/dict_utils.py +95 -0
- {defi_state_querier-0.4.30.dist-info → defi_state_querier-0.5.1.dist-info}/METADATA +1 -1
- {defi_state_querier-0.4.30.dist-info → defi_state_querier-0.5.1.dist-info}/RECORD +20 -8
- {defi_state_querier-0.4.30.dist-info → defi_state_querier-0.5.1.dist-info}/LICENSE +0 -0
- {defi_state_querier-0.4.30.dist-info → defi_state_querier-0.5.1.dist-info}/WHEEL +0 -0
- {defi_state_querier-0.4.30.dist-info → defi_state_querier-0.5.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,549 @@
|
|
1
|
+
import itertools
|
2
|
+
|
3
|
+
from query_state_lib.base.mappers.eth_call_balance_of_mapper import EthCallBalanceOf
|
4
|
+
from query_state_lib.base.mappers.get_balance_mapper import GetBalance
|
5
|
+
from query_state_lib.client.client_querier import ClientQuerier
|
6
|
+
from web3 import Web3, HTTPProvider
|
7
|
+
from web3.middleware import geth_poa_middleware
|
8
|
+
|
9
|
+
from defi_services.abis.dex.pancakeswap.pancakeswap_lp_token_abi import LP_TOKEN_ABI
|
10
|
+
from defi_services.abis.token.erc20_abi import ERC20_ABI
|
11
|
+
from defi_services.abis.token.erc721_abi import ERC721_ABI
|
12
|
+
from defi_services.constants.network_constants import NATIVE_TOKEN
|
13
|
+
from defi_services.services.eth.eth_services import EthService
|
14
|
+
from defi_services.services.multicall.batch_queries_service import add_rpc_call, decode_data_response_ignore_error
|
15
|
+
from defi_services.utils.logger_utils import get_logger
|
16
|
+
|
17
|
+
logger = get_logger('State Query Service')
|
18
|
+
|
19
|
+
|
20
|
+
class StateQueryService:
|
21
|
+
def __init__(self, provider_uri):
|
22
|
+
self._w3 = Web3(HTTPProvider(provider_uri))
|
23
|
+
self._w3.middleware_onion.inject(geth_poa_middleware, layer=0)
|
24
|
+
|
25
|
+
self.client_querier = ClientQuerier(provider_url=provider_uri)
|
26
|
+
|
27
|
+
def get_web3(self):
|
28
|
+
return self._w3
|
29
|
+
|
30
|
+
def to_checksum(self, address):
|
31
|
+
return self._w3.to_checksum_address(address)
|
32
|
+
|
33
|
+
def get_contract(self, address, abi):
|
34
|
+
try:
|
35
|
+
return self._w3.eth.contract(self.to_checksum(address), abi=abi)
|
36
|
+
except TypeError:
|
37
|
+
print('a')
|
38
|
+
|
39
|
+
def is_contract(self, address):
|
40
|
+
code = self._w3.eth.get_code(self.to_checksum(address))
|
41
|
+
code_str = code.hex()
|
42
|
+
if code_str == '0x':
|
43
|
+
return False
|
44
|
+
else:
|
45
|
+
return True
|
46
|
+
|
47
|
+
def decode_tx(self, address, abi, tx_input):
|
48
|
+
contract = self.get_contract(address, abi)
|
49
|
+
func_obj, func_params = contract.decode_function_input(tx_input)
|
50
|
+
return func_obj, func_params
|
51
|
+
|
52
|
+
def is_address(self, address):
|
53
|
+
return self._w3.is_address(address)
|
54
|
+
|
55
|
+
def balance_of(self, address, token, abi=ERC20_ABI, block_number='latest'):
|
56
|
+
token_contract = self._w3.eth.contract(self._w3.to_checksum_address(token), abi=abi)
|
57
|
+
decimals = token_contract.functions.decimals().call()
|
58
|
+
balance = token_contract.functions.balanceOf(self._w3.to_checksum_address(address)).call(
|
59
|
+
block_identifier=block_number)
|
60
|
+
return balance / 10 ** decimals
|
61
|
+
|
62
|
+
def batch_balance_of(self, address, tokens, block_number: int = 'latest', batch_size=100):
|
63
|
+
balances = {}
|
64
|
+
tokens = {token['address']: token['decimals'] or 18 for token in tokens}
|
65
|
+
rpc_requests = []
|
66
|
+
for token in tokens:
|
67
|
+
if token != "0x" and token != NATIVE_TOKEN:
|
68
|
+
query_id = f'{address}_{token}'
|
69
|
+
call_balance_of = EthCallBalanceOf(
|
70
|
+
contract_address=Web3.to_checksum_address(token),
|
71
|
+
address=Web3.to_checksum_address(address),
|
72
|
+
block_number=block_number,
|
73
|
+
id=query_id
|
74
|
+
)
|
75
|
+
else:
|
76
|
+
query_id = f"{address}_{NATIVE_TOKEN}"
|
77
|
+
call_balance_of = GetBalance(
|
78
|
+
address=Web3.to_checksum_address(address),
|
79
|
+
block_number=block_number,
|
80
|
+
id=query_id
|
81
|
+
)
|
82
|
+
rpc_requests.append(call_balance_of)
|
83
|
+
rpc_responses = self.client_querier.sent_batch_to_provider(rpc_requests, batch_size=batch_size)
|
84
|
+
for token, decimals in tokens.items():
|
85
|
+
if token == '0x':
|
86
|
+
token = NATIVE_TOKEN
|
87
|
+
balance = rpc_responses.get(f'{address}_{token}').result or 0
|
88
|
+
balance = balance / 10 ** decimals
|
89
|
+
balances[token] = balance
|
90
|
+
|
91
|
+
return balances
|
92
|
+
|
93
|
+
def batch_balance_of_multiple_addresses(self, addresses, tokens, block_number='latest', batch_size=100):
|
94
|
+
tokens_decimals = {token['address']: token.get('decimals') or 18 for token in tokens}
|
95
|
+
|
96
|
+
rpc_requests = []
|
97
|
+
for address, interacted_tokens in addresses.items():
|
98
|
+
for token_address in interacted_tokens:
|
99
|
+
if token_address != "0x" and token_address != NATIVE_TOKEN:
|
100
|
+
query_id = f'{address}_{token_address}'
|
101
|
+
call_balance_of = EthCallBalanceOf(
|
102
|
+
contract_address=Web3.to_checksum_address(token_address),
|
103
|
+
address=Web3.to_checksum_address(address),
|
104
|
+
block_number=block_number,
|
105
|
+
id=query_id
|
106
|
+
)
|
107
|
+
else:
|
108
|
+
query_id = f"{address}_{NATIVE_TOKEN}"
|
109
|
+
call_balance_of = GetBalance(
|
110
|
+
address=Web3.to_checksum_address(address),
|
111
|
+
block_number=block_number,
|
112
|
+
id=query_id
|
113
|
+
)
|
114
|
+
rpc_requests.append(call_balance_of)
|
115
|
+
|
116
|
+
rpc_responses = self.client_querier.sent_batch_to_provider(rpc_requests, batch_size=batch_size)
|
117
|
+
|
118
|
+
results = {}
|
119
|
+
for address, interacted_tokens in addresses.items():
|
120
|
+
results[address] = {}
|
121
|
+
for token_address in interacted_tokens:
|
122
|
+
balance = rpc_responses.get(f'{address}_{token_address}').result or 0
|
123
|
+
|
124
|
+
decimals = tokens_decimals.get(token_address) or 18
|
125
|
+
balance = balance / 10 ** decimals
|
126
|
+
results[address][token_address] = balance
|
127
|
+
|
128
|
+
return results, len(rpc_requests)
|
129
|
+
|
130
|
+
def get_block_number_by_timestamp(self, block_timestamp):
|
131
|
+
eth_service = EthService(self._w3)
|
132
|
+
block_number = eth_service.get_block_for_timestamp(block_timestamp)
|
133
|
+
return block_number
|
134
|
+
|
135
|
+
def batch_native_balance_of_wallets(self, addresses, blocks, decimals=18, batch_size=100):
|
136
|
+
rpc_requests = []
|
137
|
+
for address in addresses:
|
138
|
+
for block_number in blocks:
|
139
|
+
query_id = f"{address}_{block_number}"
|
140
|
+
call_balance_of = GetBalance(
|
141
|
+
address=Web3.to_checksum_address(address),
|
142
|
+
block_number=block_number,
|
143
|
+
id=query_id
|
144
|
+
)
|
145
|
+
rpc_requests.append(call_balance_of)
|
146
|
+
|
147
|
+
rpc_responses = self.client_querier.sent_batch_to_provider(rpc_requests, batch_size=batch_size)
|
148
|
+
|
149
|
+
data = {}
|
150
|
+
for address in addresses:
|
151
|
+
data[address] = {}
|
152
|
+
for block_number in blocks:
|
153
|
+
balance = rpc_responses.get(f'{address}_{block_number}').result or 0
|
154
|
+
balance = balance / 10 ** decimals
|
155
|
+
data[address][block_number] = balance
|
156
|
+
|
157
|
+
return data
|
158
|
+
|
159
|
+
def batch_balance_of_wallets(self, addresses, tokens, blocks, batch_size=100):
|
160
|
+
rpc_requests = []
|
161
|
+
|
162
|
+
for address, token, block_number in itertools.product(addresses, tokens, blocks):
|
163
|
+
token_address = token['address']
|
164
|
+
query_id = f'{address}_{token_address}_{block_number}'
|
165
|
+
if token_address != "0x" and token_address != NATIVE_TOKEN:
|
166
|
+
call_balance_of = EthCallBalanceOf(
|
167
|
+
contract_address=Web3.to_checksum_address(token_address),
|
168
|
+
address=Web3.to_checksum_address(address),
|
169
|
+
block_number=block_number,
|
170
|
+
id=query_id
|
171
|
+
)
|
172
|
+
else:
|
173
|
+
call_balance_of = GetBalance(
|
174
|
+
address=Web3.to_checksum_address(address),
|
175
|
+
block_number=block_number,
|
176
|
+
id=query_id
|
177
|
+
)
|
178
|
+
rpc_requests.append(call_balance_of)
|
179
|
+
|
180
|
+
rpc_responses = self.client_querier.sent_batch_to_provider(rpc_requests, batch_size=batch_size)
|
181
|
+
|
182
|
+
data = {}
|
183
|
+
for address, token, block_number in itertools.product(addresses, tokens, blocks):
|
184
|
+
token_address = token['address']
|
185
|
+
query_id = f'{address}_{token_address}_{block_number}'
|
186
|
+
balance = rpc_responses.get(query_id).result or 0
|
187
|
+
|
188
|
+
decimals = token.get('decimals') or 18
|
189
|
+
|
190
|
+
if address not in data:
|
191
|
+
data[address] = {}
|
192
|
+
if token_address not in data[address]:
|
193
|
+
data[address][token_address] = {}
|
194
|
+
data[address][token_address][block_number] = balance / 10 ** decimals
|
195
|
+
|
196
|
+
return data
|
197
|
+
|
198
|
+
def batch_balance_of_wallets_block(self, addresses, tokens, block_number='latest', batch_size=100):
|
199
|
+
rpc_requests = []
|
200
|
+
for address, token in itertools.product(addresses, tokens):
|
201
|
+
token_address = token['address']
|
202
|
+
query_id = f'{address}_{token_address}_{block_number}'
|
203
|
+
if token_address != "0x" and token_address != NATIVE_TOKEN:
|
204
|
+
call_balance_of = EthCallBalanceOf(
|
205
|
+
contract_address=Web3.to_checksum_address(token_address),
|
206
|
+
address=Web3.to_checksum_address(address),
|
207
|
+
block_number=block_number,
|
208
|
+
id=query_id
|
209
|
+
)
|
210
|
+
else:
|
211
|
+
call_balance_of = GetBalance(
|
212
|
+
address=Web3.to_checksum_address(address),
|
213
|
+
block_number=block_number,
|
214
|
+
id=query_id
|
215
|
+
)
|
216
|
+
rpc_requests.append(call_balance_of)
|
217
|
+
|
218
|
+
rpc_responses = self.client_querier.sent_batch_to_provider(rpc_requests, batch_size=batch_size)
|
219
|
+
|
220
|
+
data = {}
|
221
|
+
for address, token in itertools.product(addresses, tokens):
|
222
|
+
token_address = token['address']
|
223
|
+
query_id = f'{address}_{token_address}_{block_number}'
|
224
|
+
balance = rpc_responses.get(query_id).result or 0
|
225
|
+
decimals = token.get('decimals') or 18
|
226
|
+
if address not in data:
|
227
|
+
data[address] = {}
|
228
|
+
if token_address not in data[address]:
|
229
|
+
data[address][token_address] = {}
|
230
|
+
data[address][token_address] = balance / 10 ** decimals
|
231
|
+
|
232
|
+
return data
|
233
|
+
|
234
|
+
def batch_balance_of_wallet_info(self, wallet_info: dict, block_number='latest', batch_size=100):
|
235
|
+
rpc_requests = []
|
236
|
+
for address, tokens in wallet_info.items():
|
237
|
+
for token in tokens:
|
238
|
+
token_address = token['address']
|
239
|
+
query_id = f'{address}_{token_address}_{block_number}'
|
240
|
+
if token_address != "0x" and token_address != NATIVE_TOKEN:
|
241
|
+
call_balance_of = EthCallBalanceOf(
|
242
|
+
contract_address=Web3.to_checksum_address(token_address),
|
243
|
+
address=Web3.to_checksum_address(address),
|
244
|
+
block_number=block_number,
|
245
|
+
id=query_id
|
246
|
+
)
|
247
|
+
else:
|
248
|
+
call_balance_of = GetBalance(
|
249
|
+
address=Web3.to_checksum_address(address),
|
250
|
+
block_number=block_number,
|
251
|
+
id=query_id
|
252
|
+
)
|
253
|
+
rpc_requests.append(call_balance_of)
|
254
|
+
|
255
|
+
rpc_responses = self.client_querier.sent_batch_to_provider(rpc_requests, batch_size=batch_size)
|
256
|
+
|
257
|
+
data = {}
|
258
|
+
for address, tokens in wallet_info.items():
|
259
|
+
for token in tokens:
|
260
|
+
token_address = token['address']
|
261
|
+
query_id = f'{address}_{token_address}_{block_number}'
|
262
|
+
balance = rpc_responses.get(query_id).result or 0
|
263
|
+
decimals = token.get('decimals') or 18
|
264
|
+
if address not in data:
|
265
|
+
data[address] = {}
|
266
|
+
if token_address not in data[address]:
|
267
|
+
data[address][token_address] = {}
|
268
|
+
data[address][token_address] = balance / 10 ** decimals
|
269
|
+
|
270
|
+
return data
|
271
|
+
|
272
|
+
def batch_balance_of_wallet_info_with_block_number(self, wallets_info: list, batch_size=100):
|
273
|
+
rpc_requests = []
|
274
|
+
for wallet_info in wallets_info:
|
275
|
+
address = wallet_info['address']
|
276
|
+
tokens = wallet_info.get('tokens', [])
|
277
|
+
block_number = wallet_info.get('block_number', 'latest')
|
278
|
+
|
279
|
+
for token in tokens:
|
280
|
+
token_address = token['address']
|
281
|
+
query_id = f'{address}_{token_address}_{block_number}'
|
282
|
+
if token_address != "0x" and token_address != NATIVE_TOKEN:
|
283
|
+
call_balance_of = EthCallBalanceOf(
|
284
|
+
contract_address=Web3.to_checksum_address(token_address),
|
285
|
+
address=Web3.to_checksum_address(address),
|
286
|
+
block_number=block_number,
|
287
|
+
id=query_id
|
288
|
+
)
|
289
|
+
else:
|
290
|
+
call_balance_of = GetBalance(
|
291
|
+
address=Web3.to_checksum_address(address),
|
292
|
+
block_number=block_number,
|
293
|
+
id=query_id
|
294
|
+
)
|
295
|
+
rpc_requests.append(call_balance_of)
|
296
|
+
|
297
|
+
rpc_responses = self.client_querier.sent_batch_to_provider(rpc_requests, batch_size=batch_size)
|
298
|
+
|
299
|
+
data = {}
|
300
|
+
for wallet_info in wallets_info:
|
301
|
+
address = wallet_info['address']
|
302
|
+
tokens = wallet_info.get('tokens', [])
|
303
|
+
block_number = wallet_info.get('block_number', 'latest')
|
304
|
+
|
305
|
+
for token in tokens:
|
306
|
+
token_address = token['address']
|
307
|
+
query_id = f'{address}_{token_address}_{block_number}'
|
308
|
+
balance = rpc_responses.get(query_id).result or 0
|
309
|
+
decimals = token.get('decimals')
|
310
|
+
if decimals is None:
|
311
|
+
continue
|
312
|
+
|
313
|
+
if address not in data:
|
314
|
+
data[address] = {}
|
315
|
+
if token_address not in data[address]:
|
316
|
+
data[address][token_address] = {}
|
317
|
+
data[address][token_address] = balance / 10 ** decimals
|
318
|
+
|
319
|
+
return data
|
320
|
+
|
321
|
+
def recheck_liquidity_pool_v2_reserves(self, liquidity_pools: list, batch_size=100):
|
322
|
+
list_rpc_call = []
|
323
|
+
list_call_id = []
|
324
|
+
|
325
|
+
for liquidity_pool in liquidity_pools:
|
326
|
+
address = liquidity_pool['address']
|
327
|
+
block_number = liquidity_pool.get('block_number', 'latest')
|
328
|
+
|
329
|
+
add_rpc_call(
|
330
|
+
abi=LP_TOKEN_ABI, contract_address=Web3.to_checksum_address(address),
|
331
|
+
fn_name="getReserves", block_number=block_number,
|
332
|
+
list_call_id=list_call_id, list_rpc_call=list_rpc_call
|
333
|
+
)
|
334
|
+
|
335
|
+
responses = self.client_querier.sent_batch_to_provider(list_rpc_call, batch_size=batch_size)
|
336
|
+
decoded_data = decode_data_response_ignore_error(data_responses=responses, list_call_id=list_call_id)
|
337
|
+
|
338
|
+
data = {}
|
339
|
+
for liquidity_pool in liquidity_pools:
|
340
|
+
address = liquidity_pool['address']
|
341
|
+
tokens = liquidity_pool.get('tokens', [])
|
342
|
+
block_number = liquidity_pool.get('block_number', 'latest')
|
343
|
+
|
344
|
+
reserves = decoded_data.get(f'getReserves_{address}_{block_number}'.lower())
|
345
|
+
if not reserves:
|
346
|
+
continue
|
347
|
+
for token in tokens:
|
348
|
+
token_address = token['address']
|
349
|
+
balance = reserves[token['idx']]
|
350
|
+
decimals = token.get('decimals')
|
351
|
+
if decimals is None:
|
352
|
+
continue
|
353
|
+
|
354
|
+
if address not in data:
|
355
|
+
data[address] = {}
|
356
|
+
if token_address not in data[address]:
|
357
|
+
data[address][token_address] = {}
|
358
|
+
data[address][token_address] = balance / 10 ** decimals
|
359
|
+
|
360
|
+
return data
|
361
|
+
|
362
|
+
def batch_total_supply_pool_info_with_block_number(self, liquidity_pools: list, batch_size=100):
|
363
|
+
list_rpc_call = []
|
364
|
+
list_call_id = []
|
365
|
+
for liquidity_pool in liquidity_pools:
|
366
|
+
address = liquidity_pool['address']
|
367
|
+
block_number = liquidity_pool['block_number']
|
368
|
+
|
369
|
+
add_rpc_call(
|
370
|
+
abi=LP_TOKEN_ABI, contract_address=Web3.to_checksum_address(address),
|
371
|
+
fn_name="totalSupply", block_number=block_number,
|
372
|
+
list_call_id=list_call_id, list_rpc_call=list_rpc_call
|
373
|
+
)
|
374
|
+
add_rpc_call(
|
375
|
+
abi=LP_TOKEN_ABI, contract_address=Web3.to_checksum_address(address),
|
376
|
+
fn_name="decimals", block_number="latest",
|
377
|
+
list_call_id=list_call_id, list_rpc_call=list_rpc_call
|
378
|
+
)
|
379
|
+
try:
|
380
|
+
responses = self.client_querier.sent_batch_to_provider(list_rpc_call, batch_size=batch_size)
|
381
|
+
decoded_data = decode_data_response_ignore_error(data_responses=responses, list_call_id=list_call_id)
|
382
|
+
except Exception as ex:
|
383
|
+
err_detail = str(ex)
|
384
|
+
if err_detail.strip().startswith('Response data err'):
|
385
|
+
return
|
386
|
+
raise ex
|
387
|
+
data = {}
|
388
|
+
for liquidity_pool in liquidity_pools:
|
389
|
+
try:
|
390
|
+
address = liquidity_pool['address']
|
391
|
+
block_number = liquidity_pool['block_number']
|
392
|
+
total_supply = decoded_data.get(f'totalSupply_{address}_{block_number}'.lower())
|
393
|
+
decimals = decoded_data.get(f'decimals_{address}_latest'.lower())
|
394
|
+
data[address] = {
|
395
|
+
"liquidityAmount": total_supply / 10 ** decimals,
|
396
|
+
'decimals': decimals,
|
397
|
+
}
|
398
|
+
|
399
|
+
except Exception:
|
400
|
+
continue
|
401
|
+
|
402
|
+
return data
|
403
|
+
|
404
|
+
def batch_nfts_info(self, addresses, batch_size=100):
|
405
|
+
list_rpc_call = []
|
406
|
+
list_call_id = []
|
407
|
+
for address in addresses:
|
408
|
+
add_rpc_call(
|
409
|
+
abi=ERC721_ABI, contract_address=Web3.to_checksum_address(address),
|
410
|
+
fn_name="name", block_number='latest',
|
411
|
+
list_call_id=list_call_id, list_rpc_call=list_rpc_call
|
412
|
+
)
|
413
|
+
|
414
|
+
add_rpc_call(
|
415
|
+
abi=ERC721_ABI, contract_address=Web3.to_checksum_address(address),
|
416
|
+
fn_name="symbol", block_number='latest',
|
417
|
+
list_call_id=list_call_id, list_rpc_call=list_rpc_call
|
418
|
+
)
|
419
|
+
|
420
|
+
responses = self.client_querier.sent_batch_to_provider(list_rpc_call, batch_size=batch_size)
|
421
|
+
decoded_data = decode_data_response_ignore_error(data_responses=responses, list_call_id=list_call_id)
|
422
|
+
|
423
|
+
data = {}
|
424
|
+
for address in addresses:
|
425
|
+
query_name_id = f'name_{address}_latest'
|
426
|
+
query_symbol_id = f'symbol_{address}_latest'
|
427
|
+
|
428
|
+
name = decoded_data.get(query_name_id)
|
429
|
+
symbol = decoded_data.get(query_symbol_id)
|
430
|
+
if name and symbol:
|
431
|
+
data[address] = {
|
432
|
+
"name": name,
|
433
|
+
"symbol": symbol
|
434
|
+
}
|
435
|
+
|
436
|
+
return data
|
437
|
+
|
438
|
+
def batch_liquidity_pools_tokens(self, liquidity_pools, batch_size=100):
|
439
|
+
list_rpc_call = []
|
440
|
+
list_call_id = []
|
441
|
+
for liquidity_pool in liquidity_pools:
|
442
|
+
address = liquidity_pool['address']
|
443
|
+
|
444
|
+
add_rpc_call(
|
445
|
+
abi=LP_TOKEN_ABI, contract_address=Web3.to_checksum_address(address),
|
446
|
+
fn_name="token0", block_number="latest",
|
447
|
+
list_call_id=list_call_id, list_rpc_call=list_rpc_call
|
448
|
+
)
|
449
|
+
add_rpc_call(
|
450
|
+
abi=LP_TOKEN_ABI, contract_address=Web3.to_checksum_address(address),
|
451
|
+
fn_name="token1", block_number="latest",
|
452
|
+
list_call_id=list_call_id, list_rpc_call=list_rpc_call
|
453
|
+
)
|
454
|
+
add_rpc_call(
|
455
|
+
abi=LP_TOKEN_ABI, contract_address=Web3.to_checksum_address(address),
|
456
|
+
fn_name="factory", block_number="latest",
|
457
|
+
list_call_id=list_call_id, list_rpc_call=list_rpc_call
|
458
|
+
)
|
459
|
+
|
460
|
+
try:
|
461
|
+
responses = self.client_querier.sent_batch_to_provider(list_rpc_call, batch_size=batch_size)
|
462
|
+
decoded_data = decode_data_response_ignore_error(data_responses=responses, list_call_id=list_call_id)
|
463
|
+
except Exception as ex:
|
464
|
+
err_detail = str(ex)
|
465
|
+
if err_detail.strip().startswith('Response data err'):
|
466
|
+
return
|
467
|
+
raise ex
|
468
|
+
|
469
|
+
for liquidity_pool in liquidity_pools:
|
470
|
+
address = liquidity_pool['address']
|
471
|
+
|
472
|
+
token0_address = decoded_data.get(f'token0_{address}_latest'.lower())
|
473
|
+
token1_address = decoded_data.get(f'token1_{address}_latest'.lower())
|
474
|
+
factory = decoded_data.get(f'factory_{address}_latest'.lower())
|
475
|
+
|
476
|
+
if token0_address and token1_address:
|
477
|
+
liquidity_pool.update({
|
478
|
+
"address": address,
|
479
|
+
"factoryAddress": factory,
|
480
|
+
"tokens": [
|
481
|
+
{
|
482
|
+
"idx": 0,
|
483
|
+
"address": token0_address.lower()
|
484
|
+
},
|
485
|
+
{
|
486
|
+
"idx": 1,
|
487
|
+
"address": token1_address.lower()
|
488
|
+
}
|
489
|
+
]
|
490
|
+
})
|
491
|
+
|
492
|
+
return liquidity_pools
|
493
|
+
|
494
|
+
def batch_liquidity_pools_info(self, liquidity_pools, batch_size=100):
|
495
|
+
list_rpc_call = []
|
496
|
+
list_call_id = []
|
497
|
+
for liquidity_pool in liquidity_pools:
|
498
|
+
address = liquidity_pool['address']
|
499
|
+
block_number = liquidity_pool['block_number']
|
500
|
+
|
501
|
+
for token in liquidity_pool.get('tokens', []):
|
502
|
+
add_rpc_call(
|
503
|
+
abi=ERC20_ABI, contract_address=Web3.to_checksum_address(token['address']),
|
504
|
+
fn_name="decimals", block_number='latest',
|
505
|
+
list_call_id=list_call_id, list_rpc_call=list_rpc_call
|
506
|
+
)
|
507
|
+
add_rpc_call(
|
508
|
+
abi=ERC20_ABI, contract_address=Web3.to_checksum_address(token['address']),
|
509
|
+
fn_name="symbol", block_number='latest',
|
510
|
+
list_call_id=list_call_id, list_rpc_call=list_rpc_call
|
511
|
+
)
|
512
|
+
add_rpc_call(
|
513
|
+
abi=ERC20_ABI, contract_address=Web3.to_checksum_address(token['address']),
|
514
|
+
fn_name="balanceOf", fn_paras=address, block_number=block_number,
|
515
|
+
list_call_id=list_call_id, list_rpc_call=list_rpc_call
|
516
|
+
)
|
517
|
+
|
518
|
+
try:
|
519
|
+
responses = self.client_querier.sent_batch_to_provider(list_rpc_call, batch_size=batch_size)
|
520
|
+
decoded_data = decode_data_response_ignore_error(data_responses=responses, list_call_id=list_call_id)
|
521
|
+
except Exception as ex:
|
522
|
+
err_detail = str(ex)
|
523
|
+
if err_detail.strip().startswith('Response data err'):
|
524
|
+
return
|
525
|
+
raise ex
|
526
|
+
|
527
|
+
for liquidity_pool in liquidity_pools:
|
528
|
+
address = liquidity_pool['address']
|
529
|
+
block_number = liquidity_pool['block_number']
|
530
|
+
|
531
|
+
try:
|
532
|
+
for token in liquidity_pool.get('tokens', []):
|
533
|
+
symbol = decoded_data.get(f'symbol_{token["address"]}_latest'.lower())
|
534
|
+
symbol = symbol.upper() if symbol else ""
|
535
|
+
decimals = decoded_data.get(f'decimals_{token["address"]}_latest'.lower())
|
536
|
+
|
537
|
+
liquidity_amount = decoded_data.get(f'balanceOf_{token["address"]}_{address}_{block_number}'.lower(), 0)
|
538
|
+
token['liquidityAmount'] = liquidity_amount / 10 ** decimals
|
539
|
+
token['symbol'] = symbol
|
540
|
+
token['decimals'] = decimals
|
541
|
+
|
542
|
+
liquidity_pool['symbol'] = ' - '.join([t['symbol'] for t in liquidity_pool.get('tokens', [])])
|
543
|
+
except Exception as ex:
|
544
|
+
logger.exception(ex)
|
545
|
+
|
546
|
+
# Add to invalid liquidity pools
|
547
|
+
liquidity_pool.pop('tokens')
|
548
|
+
|
549
|
+
return liquidity_pools
|
@@ -21,7 +21,7 @@ class TokenServices:
|
|
21
21
|
def get_data(wallet: str, token: str, decoded_data: dict, block_number: int = "latest", **kwargs):
|
22
22
|
token_prices = kwargs.get("token_prices", {})
|
23
23
|
decimals_key = f"decimals_{token}_{block_number}".lower()
|
24
|
-
balance_key = f"balanceOf_{
|
24
|
+
balance_key = f"balanceOf_{token}_{[wallet]}_{block_number}".lower()
|
25
25
|
if balance_key in decoded_data:
|
26
26
|
balance = decoded_data.get(balance_key) or 0
|
27
27
|
decimals = decoded_data.get(decimals_key) or 18
|
@@ -50,11 +50,12 @@ class TokenServices:
|
|
50
50
|
return result
|
51
51
|
|
52
52
|
def get_function_balance_info(self, wallet: str, token: str, block_number: int = "latest"):
|
53
|
-
|
53
|
+
fn_paras = [wallet]
|
54
|
+
key = f"balanceOf_{token}_{fn_paras}_{block_number}".lower()
|
54
55
|
if token == Token.native_token:
|
55
56
|
return {key: self.state_service.get_native_token_balance_info(wallet, block_number)}
|
56
57
|
|
57
|
-
return {key: self.state_service.get_function_info(token, ERC20_ABI, "balanceOf",
|
58
|
+
return {key: self.state_service.get_function_info(token, ERC20_ABI, "balanceOf", fn_paras, block_number)}
|
58
59
|
|
59
60
|
def get_decimals_info(self, token: str, block_number: int = "latest"):
|
60
61
|
decimals_token = token
|
@@ -0,0 +1,95 @@
|
|
1
|
+
import copy
|
2
|
+
import inspect
|
3
|
+
|
4
|
+
|
5
|
+
def flatten_dict(d):
|
6
|
+
out = {}
|
7
|
+
for key, val in d.items():
|
8
|
+
if isinstance(val, dict):
|
9
|
+
val = [val]
|
10
|
+
if isinstance(val, list):
|
11
|
+
array = []
|
12
|
+
for subdict in val:
|
13
|
+
if not isinstance(subdict, dict):
|
14
|
+
array.append(subdict)
|
15
|
+
else:
|
16
|
+
deeper = flatten_dict(subdict).items()
|
17
|
+
out.update({str(key) + '.' + str(key2): val2 for key2, val2 in deeper})
|
18
|
+
if array:
|
19
|
+
out.update({str(key): array})
|
20
|
+
else:
|
21
|
+
out[str(key)] = val
|
22
|
+
return out
|
23
|
+
|
24
|
+
|
25
|
+
def reverse_flatten_dict(d: dict) -> dict:
|
26
|
+
result = {}
|
27
|
+
for key, val in d.items():
|
28
|
+
nested_keys = key.split('.')
|
29
|
+
d_ = result
|
30
|
+
for k in nested_keys[:-1]:
|
31
|
+
if k not in d_:
|
32
|
+
d_[k] = {}
|
33
|
+
d_ = d_[k]
|
34
|
+
d_[nested_keys[-1]] = val
|
35
|
+
|
36
|
+
return result
|
37
|
+
|
38
|
+
|
39
|
+
def add_dict(first_dict: dict, second_dict: dict):
|
40
|
+
ans_dict = dict()
|
41
|
+
for key in first_dict.keys():
|
42
|
+
ans_dict[key] = first_dict[key] + second_dict[key]
|
43
|
+
return ans_dict
|
44
|
+
|
45
|
+
|
46
|
+
def remove_none_value_dict(a_dict):
|
47
|
+
copy_dict = a_dict.copy()
|
48
|
+
for key, value in copy_dict.items():
|
49
|
+
if value is None:
|
50
|
+
a_dict.pop(key)
|
51
|
+
return a_dict
|
52
|
+
|
53
|
+
|
54
|
+
def delete_none(_dict):
|
55
|
+
"""Delete None values recursively from all the dictionaries"""
|
56
|
+
for key, value in list(_dict.items()):
|
57
|
+
if isinstance(value, dict):
|
58
|
+
delete_none(value)
|
59
|
+
elif value is None:
|
60
|
+
del _dict[key]
|
61
|
+
elif isinstance(value, list):
|
62
|
+
for v_i in value:
|
63
|
+
if isinstance(v_i, dict):
|
64
|
+
delete_none(v_i)
|
65
|
+
|
66
|
+
return _dict
|
67
|
+
|
68
|
+
|
69
|
+
def filter_doc_by_keys(doc, keys):
|
70
|
+
doc_ = copy.deepcopy(doc)
|
71
|
+
if keys is None:
|
72
|
+
return doc_
|
73
|
+
return dict(filter(lambda x: x[0] in keys, doc_.items()))
|
74
|
+
|
75
|
+
|
76
|
+
def get_class_constant_keys(cls):
|
77
|
+
keys = []
|
78
|
+
for attr in inspect.getmembers(cls):
|
79
|
+
# to remove private and protected functions
|
80
|
+
if not attr[0].startswith('_'):
|
81
|
+
# To remove function
|
82
|
+
if not inspect.ismethod(attr[1]):
|
83
|
+
keys.append(attr[1])
|
84
|
+
|
85
|
+
return keys
|
86
|
+
|
87
|
+
|
88
|
+
def short_address(address: str, n_start=3, n_end=4):
|
89
|
+
if not address:
|
90
|
+
return ''
|
91
|
+
return address[:n_start + 2] + '...' + address[-n_end:]
|
92
|
+
|
93
|
+
|
94
|
+
def all_equal(array):
|
95
|
+
return (not array) or (array.count(array[0]) == len(array))
|