olas-operate-middleware 0.1.0rc59__py3-none-any.whl → 0.13.2__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.
- olas_operate_middleware-0.13.2.dist-info/METADATA +75 -0
- olas_operate_middleware-0.13.2.dist-info/RECORD +101 -0
- {olas_operate_middleware-0.1.0rc59.dist-info → olas_operate_middleware-0.13.2.dist-info}/WHEEL +1 -1
- operate/__init__.py +17 -0
- operate/account/user.py +35 -9
- operate/bridge/bridge_manager.py +470 -0
- operate/bridge/providers/lifi_provider.py +377 -0
- operate/bridge/providers/native_bridge_provider.py +677 -0
- operate/bridge/providers/provider.py +469 -0
- operate/bridge/providers/relay_provider.py +457 -0
- operate/cli.py +1565 -417
- operate/constants.py +60 -12
- operate/data/README.md +19 -0
- operate/data/contracts/{service_staking_token → dual_staking_token}/__init__.py +2 -2
- operate/data/contracts/dual_staking_token/build/DualStakingToken.json +443 -0
- operate/data/contracts/dual_staking_token/contract.py +132 -0
- operate/data/contracts/dual_staking_token/contract.yaml +23 -0
- operate/{ledger/base.py → data/contracts/foreign_omnibridge/__init__.py} +2 -19
- operate/data/contracts/foreign_omnibridge/build/ForeignOmnibridge.json +1372 -0
- operate/data/contracts/foreign_omnibridge/contract.py +130 -0
- operate/data/contracts/foreign_omnibridge/contract.yaml +23 -0
- operate/{ledger/solana.py → data/contracts/home_omnibridge/__init__.py} +2 -20
- operate/data/contracts/home_omnibridge/build/HomeOmnibridge.json +1421 -0
- operate/data/contracts/home_omnibridge/contract.py +80 -0
- operate/data/contracts/home_omnibridge/contract.yaml +23 -0
- operate/data/contracts/l1_standard_bridge/__init__.py +20 -0
- operate/data/contracts/l1_standard_bridge/build/L1StandardBridge.json +831 -0
- operate/data/contracts/l1_standard_bridge/contract.py +158 -0
- operate/data/contracts/l1_standard_bridge/contract.yaml +23 -0
- operate/data/contracts/l2_standard_bridge/__init__.py +20 -0
- operate/data/contracts/l2_standard_bridge/build/L2StandardBridge.json +626 -0
- operate/data/contracts/l2_standard_bridge/contract.py +130 -0
- operate/data/contracts/l2_standard_bridge/contract.yaml +23 -0
- operate/data/contracts/mech_activity/__init__.py +20 -0
- operate/data/contracts/mech_activity/build/MechActivity.json +111 -0
- operate/data/contracts/mech_activity/contract.py +44 -0
- operate/data/contracts/mech_activity/contract.yaml +23 -0
- operate/data/contracts/optimism_mintable_erc20/__init__.py +20 -0
- operate/data/contracts/optimism_mintable_erc20/build/OptimismMintableERC20.json +491 -0
- operate/data/contracts/optimism_mintable_erc20/contract.py +45 -0
- operate/data/contracts/optimism_mintable_erc20/contract.yaml +23 -0
- operate/data/contracts/recovery_module/__init__.py +20 -0
- operate/data/contracts/recovery_module/build/RecoveryModule.json +811 -0
- operate/data/contracts/recovery_module/contract.py +61 -0
- operate/data/contracts/recovery_module/contract.yaml +23 -0
- operate/data/contracts/requester_activity_checker/__init__.py +20 -0
- operate/data/contracts/requester_activity_checker/build/RequesterActivityChecker.json +111 -0
- operate/data/contracts/requester_activity_checker/contract.py +33 -0
- operate/data/contracts/requester_activity_checker/contract.yaml +23 -0
- operate/data/contracts/staking_token/__init__.py +20 -0
- operate/data/contracts/staking_token/build/StakingToken.json +1336 -0
- operate/data/contracts/{service_staking_token → staking_token}/contract.py +27 -13
- operate/data/contracts/staking_token/contract.yaml +23 -0
- operate/data/contracts/uniswap_v2_erc20/contract.yaml +3 -1
- operate/data/contracts/uniswap_v2_erc20/tests/__init__.py +20 -0
- operate/data/contracts/uniswap_v2_erc20/tests/test_contract.py +363 -0
- operate/keys.py +118 -33
- operate/ledger/__init__.py +159 -56
- operate/ledger/profiles.py +321 -18
- operate/migration.py +555 -0
- operate/{http → operate_http}/__init__.py +3 -2
- operate/{http → operate_http}/exceptions.py +6 -4
- operate/operate_types.py +544 -0
- operate/pearl.py +13 -1
- operate/quickstart/analyse_logs.py +118 -0
- operate/quickstart/claim_staking_rewards.py +104 -0
- operate/quickstart/reset_configs.py +106 -0
- operate/quickstart/reset_password.py +70 -0
- operate/quickstart/reset_staking.py +145 -0
- operate/quickstart/run_service.py +726 -0
- operate/quickstart/stop_service.py +72 -0
- operate/quickstart/terminate_on_chain_service.py +83 -0
- operate/quickstart/utils.py +298 -0
- operate/resource.py +62 -3
- operate/services/agent_runner.py +202 -0
- operate/services/deployment_runner.py +868 -0
- operate/services/funding_manager.py +929 -0
- operate/services/health_checker.py +280 -0
- operate/services/manage.py +2356 -620
- operate/services/protocol.py +1246 -340
- operate/services/service.py +756 -391
- operate/services/utils/mech.py +103 -0
- operate/services/utils/tendermint.py +86 -12
- operate/settings.py +70 -0
- operate/utils/__init__.py +135 -0
- operate/utils/gnosis.py +407 -80
- operate/utils/single_instance.py +226 -0
- operate/utils/ssl.py +133 -0
- operate/wallet/master.py +708 -123
- operate/wallet/wallet_recovery_manager.py +507 -0
- olas_operate_middleware-0.1.0rc59.dist-info/METADATA +0 -304
- olas_operate_middleware-0.1.0rc59.dist-info/RECORD +0 -41
- operate/data/contracts/service_staking_token/build/ServiceStakingToken.json +0 -1273
- operate/data/contracts/service_staking_token/contract.yaml +0 -23
- operate/ledger/ethereum.py +0 -48
- operate/types.py +0 -260
- {olas_operate_middleware-0.1.0rc59.dist-info → olas_operate_middleware-0.13.2.dist-info}/entry_points.txt +0 -0
- {olas_operate_middleware-0.1.0rc59.dist-info → olas_operate_middleware-0.13.2.dist-info/licenses}/LICENSE +0 -0
operate/utils/gnosis.py
CHANGED
|
@@ -20,25 +20,33 @@
|
|
|
20
20
|
"""Safe helpers."""
|
|
21
21
|
|
|
22
22
|
import binascii
|
|
23
|
+
import itertools
|
|
23
24
|
import secrets
|
|
25
|
+
import time
|
|
24
26
|
import typing as t
|
|
25
27
|
from enum import Enum
|
|
26
28
|
|
|
27
29
|
from aea.crypto.base import Crypto, LedgerApi
|
|
30
|
+
from aea.helpers.logging import setup_logger
|
|
28
31
|
from autonomy.chain.base import registry_contracts
|
|
29
32
|
from autonomy.chain.config import ChainType as ChainProfile
|
|
33
|
+
from autonomy.chain.exceptions import ChainInteractionError
|
|
30
34
|
from autonomy.chain.tx import TxSettler
|
|
35
|
+
from web3 import Web3
|
|
31
36
|
|
|
32
37
|
from operate.constants import (
|
|
33
38
|
ON_CHAIN_INTERACT_RETRIES,
|
|
34
39
|
ON_CHAIN_INTERACT_SLEEP,
|
|
35
40
|
ON_CHAIN_INTERACT_TIMEOUT,
|
|
41
|
+
ZERO_ADDRESS,
|
|
36
42
|
)
|
|
43
|
+
from operate.ledger import get_default_ledger_api
|
|
44
|
+
from operate.operate_types import Chain
|
|
37
45
|
|
|
38
46
|
|
|
39
|
-
|
|
47
|
+
logger = setup_logger(name="operate.utils.gnosis")
|
|
40
48
|
MAX_UINT256 = 2**256 - 1
|
|
41
|
-
|
|
49
|
+
SENTINEL_OWNERS = "0x0000000000000000000000000000000000000001"
|
|
42
50
|
|
|
43
51
|
|
|
44
52
|
class SafeOperation(Enum):
|
|
@@ -65,8 +73,8 @@ def hash_payload_to_hex( # pylint: disable=too-many-arguments,too-many-locals
|
|
|
65
73
|
operation: int = SafeOperation.CALL.value,
|
|
66
74
|
base_gas: int = 0,
|
|
67
75
|
safe_gas_price: int = 0,
|
|
68
|
-
gas_token: str =
|
|
69
|
-
refund_receiver: str =
|
|
76
|
+
gas_token: str = ZERO_ADDRESS,
|
|
77
|
+
refund_receiver: str = ZERO_ADDRESS,
|
|
70
78
|
use_flashbots: bool = False,
|
|
71
79
|
gas_limit: int = 0,
|
|
72
80
|
raise_on_failed_simulation: bool = False,
|
|
@@ -156,9 +164,9 @@ def _get_nonce() -> int:
|
|
|
156
164
|
def create_safe(
|
|
157
165
|
ledger_api: LedgerApi,
|
|
158
166
|
crypto: Crypto,
|
|
159
|
-
|
|
167
|
+
backup_owner: t.Optional[str] = None,
|
|
160
168
|
salt_nonce: t.Optional[int] = None,
|
|
161
|
-
) -> t.Tuple[str, int]:
|
|
169
|
+
) -> t.Tuple[str, int, str]:
|
|
162
170
|
"""Create gnosis safe."""
|
|
163
171
|
salt_nonce = salt_nonce or _get_nonce()
|
|
164
172
|
|
|
@@ -168,7 +176,7 @@ def create_safe(
|
|
|
168
176
|
tx = registry_contracts.gnosis_safe.get_deploy_transaction(
|
|
169
177
|
ledger_api=ledger_api,
|
|
170
178
|
deployer_address=crypto.address,
|
|
171
|
-
owners=[crypto.address]
|
|
179
|
+
owners=([crypto.address]),
|
|
172
180
|
threshold=1,
|
|
173
181
|
salt_nonce=salt_nonce,
|
|
174
182
|
)
|
|
@@ -193,12 +201,37 @@ def create_safe(
|
|
|
193
201
|
contract="",
|
|
194
202
|
kwargs={},
|
|
195
203
|
)
|
|
204
|
+
tx_hash = receipt.get("transactionHash", "").hex()
|
|
196
205
|
instance = registry_contracts.gnosis_safe_proxy_factory.get_instance(
|
|
197
206
|
ledger_api=ledger_api,
|
|
198
207
|
contract_address="0xa6b71e26c5e0845f74c812102ca7114b6a896ab2",
|
|
199
208
|
)
|
|
200
209
|
(event,) = instance.events.ProxyCreation().process_receipt(receipt)
|
|
201
|
-
|
|
210
|
+
safe_address = event["args"]["proxy"]
|
|
211
|
+
|
|
212
|
+
if backup_owner is not None:
|
|
213
|
+
retry_delays = [0, 60, 120, 180, 240]
|
|
214
|
+
for attempt in range(1, len(retry_delays) + 1):
|
|
215
|
+
try:
|
|
216
|
+
add_owner(
|
|
217
|
+
ledger_api=ledger_api,
|
|
218
|
+
crypto=crypto,
|
|
219
|
+
safe=safe_address,
|
|
220
|
+
owner=backup_owner,
|
|
221
|
+
)
|
|
222
|
+
break # success
|
|
223
|
+
except Exception as e: # pylint: disable=broad-except
|
|
224
|
+
if attempt == len(retry_delays):
|
|
225
|
+
raise RuntimeError(
|
|
226
|
+
f"Failed to add backup owner {backup_owner} after {len(retry_delays)} attempts: {e}"
|
|
227
|
+
) from e
|
|
228
|
+
next_delay = retry_delays[attempt]
|
|
229
|
+
logger.error(
|
|
230
|
+
f"Retry add owner {attempt}/{len(retry_delays)} in {next_delay} seconds due to error: {e}"
|
|
231
|
+
)
|
|
232
|
+
time.sleep(next_delay)
|
|
233
|
+
|
|
234
|
+
return safe_address, salt_nonce, tx_hash
|
|
202
235
|
|
|
203
236
|
|
|
204
237
|
def get_owners(ledger_api: LedgerApi, safe: str) -> t.List[str]:
|
|
@@ -214,49 +247,68 @@ def send_safe_txs(
|
|
|
214
247
|
safe: str,
|
|
215
248
|
ledger_api: LedgerApi,
|
|
216
249
|
crypto: Crypto,
|
|
217
|
-
|
|
250
|
+
to: t.Optional[str] = None,
|
|
251
|
+
) -> t.Optional[str]:
|
|
218
252
|
"""Send internal safe transaction."""
|
|
219
253
|
owner = ledger_api.api.to_checksum_address(
|
|
220
254
|
crypto.address,
|
|
221
255
|
)
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
)
|
|
239
|
-
|
|
240
|
-
|
|
256
|
+
to_address = to or safe
|
|
257
|
+
|
|
258
|
+
def _build_tx( # pylint: disable=unused-argument
|
|
259
|
+
*args: t.Any, **kwargs: t.Any
|
|
260
|
+
) -> t.Optional[str]:
|
|
261
|
+
safe_tx_hash = registry_contracts.gnosis_safe.get_raw_safe_transaction_hash(
|
|
262
|
+
ledger_api=ledger_api,
|
|
263
|
+
contract_address=safe,
|
|
264
|
+
value=0,
|
|
265
|
+
safe_tx_gas=0,
|
|
266
|
+
to_address=to_address,
|
|
267
|
+
data=txd,
|
|
268
|
+
operation=SafeOperation.CALL.value,
|
|
269
|
+
).get("tx_hash")
|
|
270
|
+
safe_tx_bytes = binascii.unhexlify(
|
|
271
|
+
safe_tx_hash[2:],
|
|
272
|
+
)
|
|
273
|
+
signatures = {
|
|
274
|
+
owner: crypto.sign_message(
|
|
275
|
+
message=safe_tx_bytes,
|
|
276
|
+
is_deprecated_mode=True,
|
|
277
|
+
)[2:]
|
|
278
|
+
}
|
|
279
|
+
return registry_contracts.gnosis_safe.get_raw_safe_transaction(
|
|
280
|
+
ledger_api=ledger_api,
|
|
281
|
+
contract_address=safe,
|
|
282
|
+
sender_address=owner,
|
|
283
|
+
owners=(owner,), # type: ignore
|
|
284
|
+
to_address=to_address,
|
|
285
|
+
value=0,
|
|
286
|
+
data=txd,
|
|
287
|
+
safe_tx_gas=0,
|
|
288
|
+
signatures_by_owner=signatures,
|
|
289
|
+
operation=SafeOperation.CALL.value,
|
|
290
|
+
nonce=ledger_api.api.eth.get_transaction_count(owner),
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
tx_settler = TxSettler(
|
|
241
294
|
ledger_api=ledger_api,
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
signatures_by_owner=signatures,
|
|
250
|
-
operation=SafeOperation.CALL.value,
|
|
251
|
-
nonce=ledger_api.api.eth.get_transaction_count(owner),
|
|
295
|
+
crypto=crypto,
|
|
296
|
+
chain_type=Chain.from_id(
|
|
297
|
+
ledger_api._chain_id # pylint: disable=protected-access
|
|
298
|
+
),
|
|
299
|
+
timeout=ON_CHAIN_INTERACT_TIMEOUT,
|
|
300
|
+
retries=ON_CHAIN_INTERACT_RETRIES,
|
|
301
|
+
sleep=ON_CHAIN_INTERACT_SLEEP,
|
|
252
302
|
)
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
303
|
+
setattr(tx_settler, "build", _build_tx) # noqa: B010
|
|
304
|
+
tx_receipt = tx_settler.transact(
|
|
305
|
+
method=lambda: {},
|
|
306
|
+
contract="",
|
|
307
|
+
kwargs={},
|
|
308
|
+
dry_run=False,
|
|
259
309
|
)
|
|
310
|
+
tx_hash = tx_receipt.get("transactionHash", "").hex()
|
|
311
|
+
return tx_hash
|
|
260
312
|
|
|
261
313
|
|
|
262
314
|
def add_owner(
|
|
@@ -285,14 +337,81 @@ def add_owner(
|
|
|
285
337
|
)
|
|
286
338
|
|
|
287
339
|
|
|
288
|
-
def
|
|
340
|
+
def get_prev_owner(ledger_api: LedgerApi, safe: str, owner: str) -> str:
|
|
341
|
+
"""Retrieve the previous owner in the owners list of the Safe."""
|
|
342
|
+
|
|
343
|
+
owners = get_owners(ledger_api=ledger_api, safe=safe)
|
|
344
|
+
|
|
345
|
+
try:
|
|
346
|
+
index = owners.index(owner) - 1
|
|
347
|
+
except ValueError as e:
|
|
348
|
+
raise ValueError(
|
|
349
|
+
f"Owner {owner} not found in the owners' list of the Safe."
|
|
350
|
+
) from e
|
|
351
|
+
|
|
352
|
+
if index < 0:
|
|
353
|
+
return SENTINEL_OWNERS
|
|
354
|
+
return owners[index]
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def swap_owner(
|
|
289
358
|
ledger_api: LedgerApi,
|
|
290
359
|
crypto: Crypto,
|
|
291
360
|
safe: str,
|
|
292
361
|
old_owner: str,
|
|
293
362
|
new_owner: str,
|
|
294
363
|
) -> None:
|
|
295
|
-
"""Swap owner
|
|
364
|
+
"""Swap owner of a safe."""
|
|
365
|
+
|
|
366
|
+
prev_owner = get_prev_owner(ledger_api=ledger_api, safe=safe, owner=old_owner)
|
|
367
|
+
instance = registry_contracts.gnosis_safe.get_instance(
|
|
368
|
+
ledger_api=ledger_api,
|
|
369
|
+
contract_address=safe,
|
|
370
|
+
)
|
|
371
|
+
txd = instance.encodeABI(
|
|
372
|
+
fn_name="swapOwner",
|
|
373
|
+
args=[
|
|
374
|
+
prev_owner,
|
|
375
|
+
old_owner,
|
|
376
|
+
new_owner,
|
|
377
|
+
],
|
|
378
|
+
)
|
|
379
|
+
send_safe_txs(
|
|
380
|
+
txd=bytes.fromhex(txd[2:]),
|
|
381
|
+
safe=safe,
|
|
382
|
+
ledger_api=ledger_api,
|
|
383
|
+
crypto=crypto,
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def remove_owner(
|
|
388
|
+
ledger_api: LedgerApi,
|
|
389
|
+
crypto: Crypto,
|
|
390
|
+
safe: str,
|
|
391
|
+
owner: str,
|
|
392
|
+
threshold: int,
|
|
393
|
+
) -> None:
|
|
394
|
+
"""Remove owner from a safe."""
|
|
395
|
+
|
|
396
|
+
prev_owner = get_prev_owner(ledger_api=ledger_api, safe=safe, owner=owner)
|
|
397
|
+
instance = registry_contracts.gnosis_safe.get_instance(
|
|
398
|
+
ledger_api=ledger_api,
|
|
399
|
+
contract_address=safe,
|
|
400
|
+
)
|
|
401
|
+
txd = instance.encodeABI(
|
|
402
|
+
fn_name="removeOwner",
|
|
403
|
+
args=[
|
|
404
|
+
prev_owner,
|
|
405
|
+
owner,
|
|
406
|
+
threshold,
|
|
407
|
+
],
|
|
408
|
+
)
|
|
409
|
+
send_safe_txs(
|
|
410
|
+
txd=bytes.fromhex(txd[2:]),
|
|
411
|
+
safe=safe,
|
|
412
|
+
ledger_api=ledger_api,
|
|
413
|
+
crypto=crypto,
|
|
414
|
+
)
|
|
296
415
|
|
|
297
416
|
|
|
298
417
|
def transfer(
|
|
@@ -301,47 +420,255 @@ def transfer(
|
|
|
301
420
|
safe: str,
|
|
302
421
|
to: str,
|
|
303
422
|
amount: t.Union[float, int],
|
|
304
|
-
) ->
|
|
423
|
+
) -> t.Optional[str]:
|
|
305
424
|
"""Transfer assets from safe to given address."""
|
|
306
425
|
amount = int(amount)
|
|
307
426
|
owner = ledger_api.api.to_checksum_address(
|
|
308
427
|
crypto.address,
|
|
309
428
|
)
|
|
310
|
-
|
|
429
|
+
|
|
430
|
+
def _build_tx( # pylint: disable=unused-argument
|
|
431
|
+
*args: t.Any, **kwargs: t.Any
|
|
432
|
+
) -> t.Optional[str]:
|
|
433
|
+
safe_tx_hash = registry_contracts.gnosis_safe.get_raw_safe_transaction_hash(
|
|
434
|
+
ledger_api=ledger_api,
|
|
435
|
+
contract_address=safe,
|
|
436
|
+
value=amount,
|
|
437
|
+
safe_tx_gas=0,
|
|
438
|
+
to_address=to,
|
|
439
|
+
data=b"",
|
|
440
|
+
operation=SafeOperation.CALL.value,
|
|
441
|
+
).get("tx_hash")
|
|
442
|
+
safe_tx_bytes = binascii.unhexlify(
|
|
443
|
+
safe_tx_hash[2:],
|
|
444
|
+
)
|
|
445
|
+
signatures = {
|
|
446
|
+
owner: crypto.sign_message(
|
|
447
|
+
message=safe_tx_bytes,
|
|
448
|
+
is_deprecated_mode=True,
|
|
449
|
+
)[2:]
|
|
450
|
+
}
|
|
451
|
+
return registry_contracts.gnosis_safe.get_raw_safe_transaction(
|
|
452
|
+
ledger_api=ledger_api,
|
|
453
|
+
contract_address=safe,
|
|
454
|
+
sender_address=owner,
|
|
455
|
+
owners=(owner,), # type: ignore
|
|
456
|
+
to_address=to,
|
|
457
|
+
value=amount,
|
|
458
|
+
data=b"",
|
|
459
|
+
safe_tx_gas=0,
|
|
460
|
+
signatures_by_owner=signatures,
|
|
461
|
+
operation=SafeOperation.CALL.value,
|
|
462
|
+
nonce=ledger_api.api.eth.get_transaction_count(owner),
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
tx_settler = TxSettler(
|
|
311
466
|
ledger_api=ledger_api,
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
467
|
+
crypto=crypto,
|
|
468
|
+
chain_type=Chain.from_id(
|
|
469
|
+
ledger_api._chain_id # pylint: disable=protected-access
|
|
470
|
+
),
|
|
471
|
+
timeout=ON_CHAIN_INTERACT_TIMEOUT,
|
|
472
|
+
retries=ON_CHAIN_INTERACT_RETRIES,
|
|
473
|
+
sleep=ON_CHAIN_INTERACT_SLEEP,
|
|
474
|
+
)
|
|
475
|
+
setattr(tx_settler, "build", _build_tx) # noqa: B010
|
|
476
|
+
tx_receipt = tx_settler.transact(
|
|
477
|
+
method=lambda: {},
|
|
478
|
+
contract="",
|
|
479
|
+
kwargs={},
|
|
480
|
+
dry_run=False,
|
|
481
|
+
)
|
|
482
|
+
tx_hash = tx_receipt.get("transactionHash", "").hex()
|
|
483
|
+
return tx_hash
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def transfer_erc20_from_safe(
|
|
487
|
+
ledger_api: LedgerApi,
|
|
488
|
+
crypto: Crypto,
|
|
489
|
+
safe: str,
|
|
490
|
+
token: str,
|
|
491
|
+
to: str,
|
|
492
|
+
amount: t.Union[float, int],
|
|
493
|
+
) -> t.Optional[str]:
|
|
494
|
+
"""Transfer ERC20 assets from safe to given address."""
|
|
495
|
+
amount = int(amount)
|
|
496
|
+
instance = registry_contracts.erc20.get_instance(
|
|
497
|
+
ledger_api=ledger_api,
|
|
498
|
+
contract_address=token,
|
|
321
499
|
)
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
500
|
+
txd = instance.encodeABI(
|
|
501
|
+
fn_name="transfer",
|
|
502
|
+
args=[
|
|
503
|
+
to,
|
|
504
|
+
amount,
|
|
505
|
+
],
|
|
506
|
+
)
|
|
507
|
+
return send_safe_txs(
|
|
508
|
+
txd=bytes.fromhex(txd[2:]),
|
|
509
|
+
safe=safe,
|
|
329
510
|
ledger_api=ledger_api,
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
owners=(owner,), # type: ignore
|
|
333
|
-
to_address=to,
|
|
334
|
-
value=amount,
|
|
335
|
-
data=b"",
|
|
336
|
-
safe_tx_gas=0,
|
|
337
|
-
signatures_by_owner=signatures,
|
|
338
|
-
operation=SafeOperation.CALL.value,
|
|
339
|
-
nonce=ledger_api.api.eth.get_transaction_count(owner),
|
|
511
|
+
crypto=crypto,
|
|
512
|
+
to=token,
|
|
340
513
|
)
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
def estimate_transfer_tx_fee(chain: Chain, sender_address: str, to: str) -> int:
|
|
517
|
+
"""Estimate transfer transaction fee."""
|
|
518
|
+
ledger_api = get_default_ledger_api(chain)
|
|
519
|
+
tx = ledger_api.get_transfer_transaction(
|
|
520
|
+
sender_address=sender_address,
|
|
521
|
+
destination_address=to,
|
|
522
|
+
amount=0,
|
|
523
|
+
tx_fee=0,
|
|
524
|
+
tx_nonce="0x",
|
|
525
|
+
chain_id=chain.id,
|
|
526
|
+
raise_on_try=True,
|
|
527
|
+
)
|
|
528
|
+
tx = ledger_api.update_with_gas_estimate(
|
|
529
|
+
transaction=tx,
|
|
530
|
+
raise_on_try=False,
|
|
531
|
+
)
|
|
532
|
+
chain_fee = tx["gas"] * tx["maxFeePerGas"]
|
|
533
|
+
if chain in (
|
|
534
|
+
Chain.ARBITRUM_ONE,
|
|
535
|
+
Chain.BASE,
|
|
536
|
+
Chain.OPTIMISM,
|
|
537
|
+
Chain.MODE,
|
|
538
|
+
):
|
|
539
|
+
chain_fee += ledger_api.get_l1_data_fee(tx)
|
|
540
|
+
return chain_fee
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
def drain_eoa(
|
|
544
|
+
ledger_api: LedgerApi,
|
|
545
|
+
crypto: Crypto,
|
|
546
|
+
withdrawal_address: str,
|
|
547
|
+
chain_id: int,
|
|
548
|
+
) -> t.Optional[str]:
|
|
549
|
+
"""Drain all the native tokens from the crypto wallet."""
|
|
550
|
+
tx_helper = TxSettler(
|
|
551
|
+
ledger_api=ledger_api,
|
|
552
|
+
crypto=crypto,
|
|
553
|
+
chain_type=ChainProfile.CUSTOM,
|
|
554
|
+
timeout=ON_CHAIN_INTERACT_TIMEOUT,
|
|
555
|
+
retries=ON_CHAIN_INTERACT_RETRIES,
|
|
556
|
+
sleep=ON_CHAIN_INTERACT_SLEEP,
|
|
347
557
|
)
|
|
558
|
+
|
|
559
|
+
def _build_tx( # pylint: disable=unused-argument
|
|
560
|
+
*args: t.Any, **kwargs: t.Any
|
|
561
|
+
) -> t.Dict:
|
|
562
|
+
"""Build transaction"""
|
|
563
|
+
chain_fee = estimate_transfer_tx_fee(
|
|
564
|
+
chain=Chain.from_id(chain_id),
|
|
565
|
+
sender_address=crypto.address,
|
|
566
|
+
to=withdrawal_address,
|
|
567
|
+
)
|
|
568
|
+
|
|
569
|
+
amount = ledger_api.get_balance(crypto.address) - chain_fee
|
|
570
|
+
if amount <= 0:
|
|
571
|
+
raise ChainInteractionError(
|
|
572
|
+
f"No balance to drain from wallet: {crypto.address}"
|
|
573
|
+
)
|
|
574
|
+
|
|
575
|
+
tx = ledger_api.get_transfer_transaction(
|
|
576
|
+
sender_address=crypto.address,
|
|
577
|
+
destination_address=withdrawal_address,
|
|
578
|
+
amount=amount,
|
|
579
|
+
tx_fee=0,
|
|
580
|
+
tx_nonce="0x",
|
|
581
|
+
chain_id=chain_id,
|
|
582
|
+
raise_on_try=True,
|
|
583
|
+
)
|
|
584
|
+
empty_tx = tx.copy()
|
|
585
|
+
empty_tx["value"] = 0
|
|
586
|
+
empty_tx = ledger_api.update_with_gas_estimate(
|
|
587
|
+
transaction=empty_tx,
|
|
588
|
+
raise_on_try=False,
|
|
589
|
+
)
|
|
590
|
+
tx["gas"] = empty_tx["gas"]
|
|
591
|
+
|
|
592
|
+
logger.info(
|
|
593
|
+
f"Draining {tx['value']} native units from wallet: {crypto.address}"
|
|
594
|
+
)
|
|
595
|
+
|
|
596
|
+
return tx
|
|
597
|
+
|
|
598
|
+
setattr(tx_helper, "build", _build_tx) # noqa: B010
|
|
599
|
+
try:
|
|
600
|
+
tx_receipt = tx_helper.transact(
|
|
601
|
+
method=lambda: {},
|
|
602
|
+
contract="",
|
|
603
|
+
kwargs={},
|
|
604
|
+
dry_run=False,
|
|
605
|
+
)
|
|
606
|
+
except ChainInteractionError as e:
|
|
607
|
+
if "No balance to drain from wallet" in str(e):
|
|
608
|
+
logger.warning(f"Failed to drain wallet {crypto.address} with error: {e}.")
|
|
609
|
+
return None
|
|
610
|
+
|
|
611
|
+
raise e
|
|
612
|
+
|
|
613
|
+
tx_hash = tx_receipt.get("transactionHash", None)
|
|
614
|
+
if tx_hash is not None:
|
|
615
|
+
return tx_hash.hex()
|
|
616
|
+
|
|
617
|
+
return None
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
def get_asset_balance(
|
|
621
|
+
ledger_api: LedgerApi,
|
|
622
|
+
asset_address: str,
|
|
623
|
+
address: str,
|
|
624
|
+
raise_on_invalid_address: bool = True,
|
|
625
|
+
) -> int:
|
|
626
|
+
"""
|
|
627
|
+
Get the balance of a native asset or ERC20 token.
|
|
628
|
+
|
|
629
|
+
If contract address is a zero address, return the native balance.
|
|
630
|
+
"""
|
|
631
|
+
if not Web3.is_address(address):
|
|
632
|
+
if raise_on_invalid_address:
|
|
633
|
+
raise ValueError(f"Invalid address: {address}")
|
|
634
|
+
return 0
|
|
635
|
+
|
|
636
|
+
try:
|
|
637
|
+
if asset_address == ZERO_ADDRESS:
|
|
638
|
+
return ledger_api.get_balance(address, raise_on_try=True)
|
|
639
|
+
return (
|
|
640
|
+
registry_contracts.erc20.get_instance(
|
|
641
|
+
ledger_api=ledger_api,
|
|
642
|
+
contract_address=asset_address,
|
|
643
|
+
)
|
|
644
|
+
.functions.balanceOf(address)
|
|
645
|
+
.call()
|
|
646
|
+
)
|
|
647
|
+
except Exception as e:
|
|
648
|
+
raise RuntimeError(
|
|
649
|
+
f"Cannot get balance of {address=} {asset_address=} rpc={ledger_api._api.provider.endpoint_uri}." # pylint: disable=protected-access
|
|
650
|
+
) from e
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
def get_assets_balances(
|
|
654
|
+
ledger_api: LedgerApi,
|
|
655
|
+
asset_addresses: t.Set[str],
|
|
656
|
+
addresses: t.Set[str],
|
|
657
|
+
raise_on_invalid_address: bool = True,
|
|
658
|
+
) -> t.Dict[str, t.Dict[str, int]]:
|
|
659
|
+
"""
|
|
660
|
+
Get the balances of a list of native assets or ERC20 tokens.
|
|
661
|
+
|
|
662
|
+
If asset address is a zero address, return the native balance.
|
|
663
|
+
"""
|
|
664
|
+
output: t.Dict[str, t.Dict[str, int]] = {}
|
|
665
|
+
|
|
666
|
+
for asset, address in itertools.product(asset_addresses, addresses):
|
|
667
|
+
output.setdefault(address, {})[asset] = get_asset_balance(
|
|
668
|
+
ledger_api=ledger_api,
|
|
669
|
+
asset_address=asset,
|
|
670
|
+
address=address,
|
|
671
|
+
raise_on_invalid_address=raise_on_invalid_address,
|
|
672
|
+
)
|
|
673
|
+
|
|
674
|
+
return output
|