iwa 0.0.0__py3-none-any.whl → 0.0.1a2__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.
- conftest.py +22 -0
- iwa/__init__.py +1 -0
- iwa/__main__.py +6 -0
- iwa/core/__init__.py +1 -0
- iwa/core/chain/__init__.py +68 -0
- iwa/core/chain/errors.py +47 -0
- iwa/core/chain/interface.py +514 -0
- iwa/core/chain/manager.py +38 -0
- iwa/core/chain/models.py +128 -0
- iwa/core/chain/rate_limiter.py +193 -0
- iwa/core/cli.py +210 -0
- iwa/core/constants.py +28 -0
- iwa/core/contracts/__init__.py +1 -0
- iwa/core/contracts/contract.py +297 -0
- iwa/core/contracts/erc20.py +79 -0
- iwa/core/contracts/multisend.py +71 -0
- iwa/core/db.py +317 -0
- iwa/core/keys.py +361 -0
- iwa/core/mnemonic.py +385 -0
- iwa/core/models.py +344 -0
- iwa/core/monitor.py +209 -0
- iwa/core/plugins.py +45 -0
- iwa/core/pricing.py +91 -0
- iwa/core/services/__init__.py +17 -0
- iwa/core/services/account.py +57 -0
- iwa/core/services/balance.py +113 -0
- iwa/core/services/plugin.py +88 -0
- iwa/core/services/safe.py +392 -0
- iwa/core/services/transaction.py +172 -0
- iwa/core/services/transfer/__init__.py +166 -0
- iwa/core/services/transfer/base.py +260 -0
- iwa/core/services/transfer/erc20.py +247 -0
- iwa/core/services/transfer/multisend.py +386 -0
- iwa/core/services/transfer/native.py +262 -0
- iwa/core/services/transfer/swap.py +326 -0
- iwa/core/settings.py +95 -0
- iwa/core/tables.py +60 -0
- iwa/core/test.py +27 -0
- iwa/core/tests/test_wallet.py +255 -0
- iwa/core/types.py +59 -0
- iwa/core/ui.py +99 -0
- iwa/core/utils.py +59 -0
- iwa/core/wallet.py +380 -0
- iwa/plugins/__init__.py +1 -0
- iwa/plugins/gnosis/__init__.py +5 -0
- iwa/plugins/gnosis/cow/__init__.py +6 -0
- iwa/plugins/gnosis/cow/quotes.py +148 -0
- iwa/plugins/gnosis/cow/swap.py +403 -0
- iwa/plugins/gnosis/cow/types.py +20 -0
- iwa/plugins/gnosis/cow_utils.py +44 -0
- iwa/plugins/gnosis/plugin.py +68 -0
- iwa/plugins/gnosis/safe.py +157 -0
- iwa/plugins/gnosis/tests/test_cow.py +227 -0
- iwa/plugins/gnosis/tests/test_safe.py +100 -0
- iwa/plugins/olas/__init__.py +5 -0
- iwa/plugins/olas/constants.py +106 -0
- iwa/plugins/olas/contracts/activity_checker.py +93 -0
- iwa/plugins/olas/contracts/base.py +10 -0
- iwa/plugins/olas/contracts/mech.py +49 -0
- iwa/plugins/olas/contracts/mech_marketplace.py +43 -0
- iwa/plugins/olas/contracts/service.py +215 -0
- iwa/plugins/olas/contracts/staking.py +403 -0
- iwa/plugins/olas/importer.py +736 -0
- iwa/plugins/olas/mech_reference.py +135 -0
- iwa/plugins/olas/models.py +110 -0
- iwa/plugins/olas/plugin.py +243 -0
- iwa/plugins/olas/scripts/test_full_mech_flow.py +259 -0
- iwa/plugins/olas/scripts/test_simple_lifecycle.py +74 -0
- iwa/plugins/olas/service_manager/__init__.py +60 -0
- iwa/plugins/olas/service_manager/base.py +113 -0
- iwa/plugins/olas/service_manager/drain.py +336 -0
- iwa/plugins/olas/service_manager/lifecycle.py +839 -0
- iwa/plugins/olas/service_manager/mech.py +322 -0
- iwa/plugins/olas/service_manager/staking.py +530 -0
- iwa/plugins/olas/tests/conftest.py +30 -0
- iwa/plugins/olas/tests/test_importer.py +128 -0
- iwa/plugins/olas/tests/test_importer_error_handling.py +349 -0
- iwa/plugins/olas/tests/test_mech_contracts.py +85 -0
- iwa/plugins/olas/tests/test_olas_contracts.py +249 -0
- iwa/plugins/olas/tests/test_olas_integration.py +561 -0
- iwa/plugins/olas/tests/test_olas_models.py +144 -0
- iwa/plugins/olas/tests/test_olas_view.py +258 -0
- iwa/plugins/olas/tests/test_olas_view_actions.py +137 -0
- iwa/plugins/olas/tests/test_olas_view_modals.py +120 -0
- iwa/plugins/olas/tests/test_plugin.py +70 -0
- iwa/plugins/olas/tests/test_plugin_full.py +212 -0
- iwa/plugins/olas/tests/test_service_lifecycle.py +150 -0
- iwa/plugins/olas/tests/test_service_manager.py +1065 -0
- iwa/plugins/olas/tests/test_service_manager_errors.py +208 -0
- iwa/plugins/olas/tests/test_service_manager_flows.py +497 -0
- iwa/plugins/olas/tests/test_service_manager_mech.py +135 -0
- iwa/plugins/olas/tests/test_service_manager_rewards.py +360 -0
- iwa/plugins/olas/tests/test_service_manager_validation.py +145 -0
- iwa/plugins/olas/tests/test_service_staking.py +342 -0
- iwa/plugins/olas/tests/test_staking_integration.py +269 -0
- iwa/plugins/olas/tests/test_staking_validation.py +109 -0
- iwa/plugins/olas/tui/__init__.py +1 -0
- iwa/plugins/olas/tui/olas_view.py +952 -0
- iwa/tools/check_profile.py +67 -0
- iwa/tools/release.py +111 -0
- iwa/tools/reset_env.py +111 -0
- iwa/tools/reset_tenderly.py +362 -0
- iwa/tools/restore_backup.py +82 -0
- iwa/tui/__init__.py +1 -0
- iwa/tui/app.py +174 -0
- iwa/tui/modals/__init__.py +5 -0
- iwa/tui/modals/base.py +406 -0
- iwa/tui/rpc.py +63 -0
- iwa/tui/screens/__init__.py +1 -0
- iwa/tui/screens/wallets.py +749 -0
- iwa/tui/tests/test_app.py +125 -0
- iwa/tui/tests/test_rpc.py +139 -0
- iwa/tui/tests/test_wallets_refactor.py +30 -0
- iwa/tui/tests/test_widgets.py +123 -0
- iwa/tui/widgets/__init__.py +5 -0
- iwa/tui/widgets/base.py +100 -0
- iwa/tui/workers.py +42 -0
- iwa/web/dependencies.py +76 -0
- iwa/web/models.py +76 -0
- iwa/web/routers/accounts.py +115 -0
- iwa/web/routers/olas/__init__.py +24 -0
- iwa/web/routers/olas/admin.py +169 -0
- iwa/web/routers/olas/funding.py +135 -0
- iwa/web/routers/olas/general.py +29 -0
- iwa/web/routers/olas/services.py +378 -0
- iwa/web/routers/olas/staking.py +341 -0
- iwa/web/routers/state.py +65 -0
- iwa/web/routers/swap.py +617 -0
- iwa/web/routers/transactions.py +153 -0
- iwa/web/server.py +155 -0
- iwa/web/tests/test_web_endpoints.py +713 -0
- iwa/web/tests/test_web_olas.py +430 -0
- iwa/web/tests/test_web_swap.py +103 -0
- iwa-0.0.1a2.dist-info/METADATA +234 -0
- iwa-0.0.1a2.dist-info/RECORD +186 -0
- iwa-0.0.1a2.dist-info/entry_points.txt +2 -0
- iwa-0.0.1a2.dist-info/licenses/LICENSE +21 -0
- iwa-0.0.1a2.dist-info/top_level.txt +4 -0
- tests/legacy_cow.py +248 -0
- tests/legacy_safe.py +93 -0
- tests/legacy_transaction_retry_logic.py +51 -0
- tests/legacy_tui.py +440 -0
- tests/legacy_wallets_screen.py +554 -0
- tests/legacy_web.py +243 -0
- tests/test_account_service.py +120 -0
- tests/test_balance_service.py +186 -0
- tests/test_chain.py +490 -0
- tests/test_chain_interface.py +210 -0
- tests/test_cli.py +139 -0
- tests/test_contract.py +195 -0
- tests/test_db.py +180 -0
- tests/test_drain_coverage.py +174 -0
- tests/test_erc20.py +95 -0
- tests/test_gnosis_plugin.py +111 -0
- tests/test_keys.py +449 -0
- tests/test_legacy_wallet.py +1285 -0
- tests/test_main.py +13 -0
- tests/test_mnemonic.py +217 -0
- tests/test_modals.py +109 -0
- tests/test_models.py +213 -0
- tests/test_monitor.py +202 -0
- tests/test_multisend.py +84 -0
- tests/test_plugin_service.py +119 -0
- tests/test_pricing.py +143 -0
- tests/test_rate_limiter.py +199 -0
- tests/test_reset_tenderly.py +202 -0
- tests/test_rpc_view.py +73 -0
- tests/test_safe_coverage.py +139 -0
- tests/test_safe_service.py +168 -0
- tests/test_service_manager_integration.py +61 -0
- tests/test_service_manager_structure.py +31 -0
- tests/test_service_transaction.py +176 -0
- tests/test_staking_router.py +71 -0
- tests/test_staking_simple.py +31 -0
- tests/test_tables.py +76 -0
- tests/test_transaction_service.py +161 -0
- tests/test_transfer_multisend.py +179 -0
- tests/test_transfer_native.py +220 -0
- tests/test_transfer_security.py +93 -0
- tests/test_transfer_structure.py +37 -0
- tests/test_transfer_swap_unit.py +155 -0
- tests/test_ui_coverage.py +66 -0
- tests/test_utils.py +53 -0
- tests/test_workers.py +91 -0
- tools/verify_drain.py +183 -0
- __init__.py +0 -2
- hello.py +0 -6
- iwa-0.0.0.dist-info/METADATA +0 -10
- iwa-0.0.0.dist-info/RECORD +0 -6
- iwa-0.0.0.dist-info/top_level.txt +0 -2
- {iwa-0.0.0.dist-info → iwa-0.0.1a2.dist-info}/WHEEL +0 -0
iwa/core/wallet.py
ADDED
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
"""Wallet module."""
|
|
2
|
+
|
|
3
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
4
|
+
from typing import List, Optional, Tuple
|
|
5
|
+
|
|
6
|
+
from web3.types import Wei
|
|
7
|
+
|
|
8
|
+
from iwa.core.chain import SupportedChain
|
|
9
|
+
from iwa.core.db import init_db
|
|
10
|
+
from iwa.core.keys import KeyStorage
|
|
11
|
+
from iwa.core.models import EthereumAddress, StoredSafeAccount
|
|
12
|
+
from iwa.core.services import (
|
|
13
|
+
AccountService,
|
|
14
|
+
BalanceService,
|
|
15
|
+
PluginService,
|
|
16
|
+
SafeService,
|
|
17
|
+
TransactionService,
|
|
18
|
+
TransferService,
|
|
19
|
+
)
|
|
20
|
+
from iwa.core.utils import configure_logger
|
|
21
|
+
from iwa.plugins.gnosis.cow import OrderType
|
|
22
|
+
|
|
23
|
+
logger = configure_logger()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Wallet:
|
|
27
|
+
"""Wallet management coordinator."""
|
|
28
|
+
|
|
29
|
+
def __init__(self):
|
|
30
|
+
"""Initialize wallet."""
|
|
31
|
+
self.key_storage = KeyStorage()
|
|
32
|
+
self.account_service = AccountService(self.key_storage)
|
|
33
|
+
self.balance_service = BalanceService(self.key_storage, self.account_service)
|
|
34
|
+
self.safe_service = SafeService(self.key_storage, self.account_service)
|
|
35
|
+
# self.transaction_manager = TransactionManager(self.key_storage, self.account_service)
|
|
36
|
+
self.transaction_service = TransactionService(self.key_storage, self.account_service)
|
|
37
|
+
|
|
38
|
+
self.transfer_service = TransferService(
|
|
39
|
+
self.key_storage,
|
|
40
|
+
self.account_service,
|
|
41
|
+
self.balance_service,
|
|
42
|
+
self.safe_service,
|
|
43
|
+
self.transaction_service,
|
|
44
|
+
)
|
|
45
|
+
self.plugin_service = PluginService()
|
|
46
|
+
|
|
47
|
+
init_db()
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def master_account(self) -> Optional[StoredSafeAccount]:
|
|
51
|
+
"""Get master account"""
|
|
52
|
+
return self.account_service.master_account
|
|
53
|
+
|
|
54
|
+
def get_token_address(
|
|
55
|
+
self, token_address_or_name: str, chain: SupportedChain
|
|
56
|
+
) -> Optional[EthereumAddress]:
|
|
57
|
+
"""Get token address from address or name"""
|
|
58
|
+
return self.account_service.get_token_address(token_address_or_name, chain)
|
|
59
|
+
|
|
60
|
+
def get_accounts_balances(
|
|
61
|
+
self, chain_name: str, token_names: Optional[list[str]] = None
|
|
62
|
+
) -> Tuple[dict, Optional[dict]]:
|
|
63
|
+
"""Get accounts data and balances."""
|
|
64
|
+
accounts_data = self.account_service.get_account_data()
|
|
65
|
+
token_names = token_names or []
|
|
66
|
+
|
|
67
|
+
if not token_names:
|
|
68
|
+
return accounts_data, None
|
|
69
|
+
|
|
70
|
+
token_balances = {addr: {} for addr in accounts_data.keys()}
|
|
71
|
+
|
|
72
|
+
def fetch_balance(addr, t_name):
|
|
73
|
+
try:
|
|
74
|
+
if t_name == "native":
|
|
75
|
+
return (
|
|
76
|
+
addr,
|
|
77
|
+
t_name,
|
|
78
|
+
self.balance_service.get_native_balance_eth(addr, chain_name),
|
|
79
|
+
)
|
|
80
|
+
else:
|
|
81
|
+
return (
|
|
82
|
+
addr,
|
|
83
|
+
t_name,
|
|
84
|
+
self.balance_service.get_erc20_balance_eth(addr, t_name, chain_name),
|
|
85
|
+
)
|
|
86
|
+
except Exception as e:
|
|
87
|
+
logger.error(f"Error fetching {t_name} balance for {addr}: {e}")
|
|
88
|
+
return addr, t_name, 0.0
|
|
89
|
+
|
|
90
|
+
# Use ThreadPoolExecutor for parallel balance fetching
|
|
91
|
+
with ThreadPoolExecutor(max_workers=20) as executor:
|
|
92
|
+
tasks = []
|
|
93
|
+
for addr in accounts_data.keys():
|
|
94
|
+
for t_name in token_names:
|
|
95
|
+
tasks.append(executor.submit(fetch_balance, addr, t_name))
|
|
96
|
+
|
|
97
|
+
for future in tasks:
|
|
98
|
+
addr, t_name, bal = future.result()
|
|
99
|
+
token_balances[addr][t_name] = bal
|
|
100
|
+
|
|
101
|
+
return accounts_data, token_balances
|
|
102
|
+
|
|
103
|
+
def send_native_transfer(
|
|
104
|
+
self,
|
|
105
|
+
from_address: str,
|
|
106
|
+
to_address: str,
|
|
107
|
+
value_wei: Wei,
|
|
108
|
+
chain_name: str = "gnosis",
|
|
109
|
+
) -> Tuple[bool, Optional[str]]:
|
|
110
|
+
"""Send a native currency transfer (e.g., ETH, xDAI).
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
from_address: Sender's address or tag.
|
|
114
|
+
to_address: Recipient's address or tag.
|
|
115
|
+
value_wei: Amount to send in Wei.
|
|
116
|
+
chain_name: Target blockchain name (default: "gnosis").
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
Tuple containing:
|
|
120
|
+
- bool: True if transaction was successfully sent (status=1).
|
|
121
|
+
- Optional[str]: Transaction hash if successful, None otherwise.
|
|
122
|
+
|
|
123
|
+
"""
|
|
124
|
+
tx_hash = self.transfer_service.send(
|
|
125
|
+
from_address_or_tag=from_address,
|
|
126
|
+
to_address_or_tag=to_address,
|
|
127
|
+
amount_wei=value_wei,
|
|
128
|
+
token_address_or_name="native",
|
|
129
|
+
chain_name=chain_name,
|
|
130
|
+
)
|
|
131
|
+
return bool(tx_hash), tx_hash
|
|
132
|
+
|
|
133
|
+
def sign_and_send_transaction(
|
|
134
|
+
self,
|
|
135
|
+
transaction: dict,
|
|
136
|
+
signer_address_or_tag: str,
|
|
137
|
+
chain_name: str = "gnosis",
|
|
138
|
+
tags: Optional[List[str]] = None,
|
|
139
|
+
) -> Tuple[bool, dict]:
|
|
140
|
+
"""Sign and send a raw transaction dictionary.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
transaction: Dictionary containing transaction parameters.
|
|
144
|
+
signer_address_or_tag: Address or tag of the signing account.
|
|
145
|
+
chain_name: Target blockchain name (default: "gnosis").
|
|
146
|
+
tags: List of tags to associate with the transaction.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
Tuple containing:
|
|
150
|
+
- bool: True if successful.
|
|
151
|
+
- dict: Transaction receipt or error details.
|
|
152
|
+
|
|
153
|
+
"""
|
|
154
|
+
return self.transaction_service.sign_and_send(
|
|
155
|
+
transaction, signer_address_or_tag, chain_name, tags
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
def send_erc20_transfer(
|
|
159
|
+
self,
|
|
160
|
+
from_address: str,
|
|
161
|
+
to_address: str,
|
|
162
|
+
amount_wei: Wei,
|
|
163
|
+
token_address: str,
|
|
164
|
+
chain_name: str = "gnosis",
|
|
165
|
+
) -> Tuple[bool, Optional[str]]:
|
|
166
|
+
"""Send an ERC20 token transfer.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
from_address: Sender's address or tag.
|
|
170
|
+
to_address: Recipient's address or tag.
|
|
171
|
+
amount_wei: Amount to send in Wei.
|
|
172
|
+
token_address: Token address or name (e.g., "OLAS").
|
|
173
|
+
chain_name: Target blockchain name (default: "gnosis").
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
Tuple containing:
|
|
177
|
+
- bool: True if transaction was successfully sent (status=1).
|
|
178
|
+
- Optional[str]: Transaction hash if successful, None otherwise.
|
|
179
|
+
|
|
180
|
+
"""
|
|
181
|
+
tx_hash = self.transfer_service.send(
|
|
182
|
+
from_address_or_tag=from_address,
|
|
183
|
+
to_address_or_tag=to_address,
|
|
184
|
+
amount_wei=amount_wei,
|
|
185
|
+
token_address_or_name=token_address,
|
|
186
|
+
chain_name=chain_name,
|
|
187
|
+
)
|
|
188
|
+
return bool(tx_hash), tx_hash
|
|
189
|
+
|
|
190
|
+
def send(
|
|
191
|
+
self,
|
|
192
|
+
from_address_or_tag: str,
|
|
193
|
+
to_address_or_tag: str,
|
|
194
|
+
amount_wei: Wei,
|
|
195
|
+
token_address_or_name: str = "native",
|
|
196
|
+
chain_name: str = "gnosis",
|
|
197
|
+
) -> Optional[str]:
|
|
198
|
+
"""Send native currency or ERC20 tokens.
|
|
199
|
+
|
|
200
|
+
Unified interface for transferring assets.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
from_address_or_tag: Sender's address or tag.
|
|
204
|
+
to_address_or_tag: Recipient's address or tag.
|
|
205
|
+
amount_wei: Amount to send in Wei.
|
|
206
|
+
token_address_or_name: Token address, name, or "native".
|
|
207
|
+
chain_name: Target blockchain name.
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
Optional[str]: Transaction hash if successful, None otherwise.
|
|
211
|
+
|
|
212
|
+
"""
|
|
213
|
+
return self.transfer_service.send(
|
|
214
|
+
from_address_or_tag,
|
|
215
|
+
to_address_or_tag,
|
|
216
|
+
amount_wei,
|
|
217
|
+
token_address_or_name,
|
|
218
|
+
chain_name,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
def multi_send(
|
|
222
|
+
self,
|
|
223
|
+
from_address_or_tag: str,
|
|
224
|
+
transactions: list,
|
|
225
|
+
chain_name: str = "gnosis",
|
|
226
|
+
):
|
|
227
|
+
"""Send multiple transactions in a single multisend transaction"""
|
|
228
|
+
return self.transfer_service.multi_send(from_address_or_tag, transactions, chain_name)
|
|
229
|
+
|
|
230
|
+
def get_native_balance_eth(
|
|
231
|
+
self, account_address: str, chain_name: str = "gnosis"
|
|
232
|
+
) -> Optional[float]:
|
|
233
|
+
"""Get native currency balance"""
|
|
234
|
+
return self.balance_service.get_native_balance_eth(account_address, chain_name)
|
|
235
|
+
|
|
236
|
+
def get_native_balance_wei(
|
|
237
|
+
self, account_address: str, chain_name: str = "gnosis"
|
|
238
|
+
) -> Optional[Wei]:
|
|
239
|
+
"""Get native currency balance"""
|
|
240
|
+
return self.balance_service.get_native_balance_wei(account_address, chain_name)
|
|
241
|
+
|
|
242
|
+
def get_erc20_balance_eth(
|
|
243
|
+
self, account_address_or_tag: str, token_address_or_name: str, chain_name: str = "gnosis"
|
|
244
|
+
) -> Optional[float]:
|
|
245
|
+
"""Get ERC20 token balance"""
|
|
246
|
+
return self.balance_service.get_erc20_balance_eth(
|
|
247
|
+
account_address_or_tag, token_address_or_name, chain_name
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
def get_erc20_balance_wei(
|
|
251
|
+
self, account_address_or_tag: str, token_address_or_name: str, chain_name: str = "gnosis"
|
|
252
|
+
) -> Optional[Wei]:
|
|
253
|
+
"""Get ERC20 token balance"""
|
|
254
|
+
return self.balance_service.get_erc20_balance_wei(
|
|
255
|
+
account_address_or_tag, token_address_or_name, chain_name
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
def get_erc20_allowance(
|
|
259
|
+
self,
|
|
260
|
+
owner_address_or_tag: str,
|
|
261
|
+
spender_address: str,
|
|
262
|
+
token_address_or_name: str,
|
|
263
|
+
chain_name: str = "gnosis",
|
|
264
|
+
) -> Optional[float]:
|
|
265
|
+
"""Get ERC20 token allowance.
|
|
266
|
+
|
|
267
|
+
Args:
|
|
268
|
+
owner_address_or_tag: Token owner's address or tag.
|
|
269
|
+
spender_address: Address authorized to spend tokens.
|
|
270
|
+
token_address_or_name: Token address or name.
|
|
271
|
+
chain_name: Target blockchain name.
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
Optional[float]: Allowance amount in Ether (float) or None on error.
|
|
275
|
+
|
|
276
|
+
"""
|
|
277
|
+
return self.transfer_service.get_erc20_allowance(
|
|
278
|
+
owner_address_or_tag, spender_address, token_address_or_name, chain_name
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
def approve_erc20(
|
|
282
|
+
self,
|
|
283
|
+
owner_address_or_tag: str,
|
|
284
|
+
spender_address_or_tag: str,
|
|
285
|
+
token_address_or_name: str,
|
|
286
|
+
amount_wei: Wei,
|
|
287
|
+
chain_name: str = "gnosis",
|
|
288
|
+
) -> Optional[str]:
|
|
289
|
+
"""Approve ERC20 token allowance.
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
owner_address_or_tag: Token owner's address or tag.
|
|
293
|
+
spender_address_or_tag: Spender's address or tag.
|
|
294
|
+
token_address_or_name: Token address or name.
|
|
295
|
+
amount_wei: Amount to approve in Wei.
|
|
296
|
+
chain_name: Target blockchain name.
|
|
297
|
+
|
|
298
|
+
Returns:
|
|
299
|
+
Optional[str]: Transaction hash if successful, None otherwise.
|
|
300
|
+
|
|
301
|
+
"""
|
|
302
|
+
return self.transfer_service.approve_erc20(
|
|
303
|
+
owner_address_or_tag,
|
|
304
|
+
spender_address_or_tag,
|
|
305
|
+
token_address_or_name,
|
|
306
|
+
amount_wei,
|
|
307
|
+
chain_name,
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
def transfer_from_erc20(
|
|
311
|
+
self,
|
|
312
|
+
from_address_or_tag: str,
|
|
313
|
+
sender_address_or_tag: str,
|
|
314
|
+
recipient_address_or_tag: str,
|
|
315
|
+
token_address_or_name: str,
|
|
316
|
+
amount_wei: Wei,
|
|
317
|
+
chain_name: str = "gnosis",
|
|
318
|
+
):
|
|
319
|
+
"""TransferFrom ERC20 tokens"""
|
|
320
|
+
return self.transfer_service.transfer_from_erc20(
|
|
321
|
+
from_address_or_tag,
|
|
322
|
+
sender_address_or_tag,
|
|
323
|
+
recipient_address_or_tag,
|
|
324
|
+
token_address_or_name,
|
|
325
|
+
amount_wei,
|
|
326
|
+
chain_name,
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
async def swap(
|
|
330
|
+
self,
|
|
331
|
+
account_address_or_tag: str,
|
|
332
|
+
amount_eth: Optional[float],
|
|
333
|
+
sell_token_name: str,
|
|
334
|
+
buy_token_name: str,
|
|
335
|
+
chain_name: str = "gnosis",
|
|
336
|
+
order_type: OrderType = OrderType.SELL,
|
|
337
|
+
) -> bool:
|
|
338
|
+
"""Swap ERC-20 tokens on CowSwap.
|
|
339
|
+
|
|
340
|
+
Args:
|
|
341
|
+
account_address_or_tag: Account address or tag initiating the swap.
|
|
342
|
+
amount_eth: Amount to swap (sell or buy amount depending on order_type).
|
|
343
|
+
sell_token_name: Name of the token to sell.
|
|
344
|
+
buy_token_name: Name of the token to buy.
|
|
345
|
+
chain_name: Blockchain name (must supports CowSwap, e.g., "gnosis").
|
|
346
|
+
order_type: OrderType.SELL or OrderType.BUY.
|
|
347
|
+
|
|
348
|
+
Returns:
|
|
349
|
+
bool: True if swap order was created and filled successfully.
|
|
350
|
+
|
|
351
|
+
"""
|
|
352
|
+
return await self.transfer_service.swap(
|
|
353
|
+
account_address_or_tag,
|
|
354
|
+
amount_eth,
|
|
355
|
+
sell_token_name,
|
|
356
|
+
buy_token_name,
|
|
357
|
+
chain_name,
|
|
358
|
+
order_type,
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
def drain(
|
|
362
|
+
self,
|
|
363
|
+
from_address_or_tag: str,
|
|
364
|
+
to_address_or_tag: str = "master",
|
|
365
|
+
chain_name: str = "gnosis",
|
|
366
|
+
) -> Optional[str]:
|
|
367
|
+
"""Drain entire balance of an account to another account.
|
|
368
|
+
|
|
369
|
+
Transfers all native currency and known ERC20 tokens.
|
|
370
|
+
|
|
371
|
+
Args:
|
|
372
|
+
from_address_or_tag: Source account address or tag.
|
|
373
|
+
to_address_or_tag: Destination account address or tag (default: "master").
|
|
374
|
+
chain_name: Target blockchain name.
|
|
375
|
+
|
|
376
|
+
Returns:
|
|
377
|
+
Optional[str]: Summary of the operation or transaction hash of the last transfer.
|
|
378
|
+
|
|
379
|
+
"""
|
|
380
|
+
return self.transfer_service.drain(from_address_or_tag, to_address_or_tag, chain_name)
|
iwa/plugins/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Plugins package."""
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""CoW Swap quote utilities."""
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from eth_typing.evm import ChecksumAddress
|
|
6
|
+
from web3.types import Wei
|
|
7
|
+
|
|
8
|
+
from iwa.plugins.gnosis.cow_utils import get_cowpy_module
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from cowdao_cowpy.order_book.config import Envs
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Placeholders for cowdao_cowpy functions/classes to allow patching in tests
|
|
15
|
+
get_order_quote = None
|
|
16
|
+
OrderQuoteRequest = None
|
|
17
|
+
OrderQuoteSide1 = None
|
|
18
|
+
OrderQuoteSide3 = None
|
|
19
|
+
OrderQuoteSideKindBuy = None
|
|
20
|
+
OrderQuoteSideKindSell = None
|
|
21
|
+
TokenAmount = None
|
|
22
|
+
SupportedChainId = None
|
|
23
|
+
OrderBookApi = None
|
|
24
|
+
OrderBookAPIConfigFactory = None
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
async def get_max_sell_amount_wei(
|
|
28
|
+
amount_wei: Wei,
|
|
29
|
+
sell_token: ChecksumAddress,
|
|
30
|
+
buy_token: ChecksumAddress,
|
|
31
|
+
chain_id_val: int,
|
|
32
|
+
account_address: ChecksumAddress,
|
|
33
|
+
safe_address: ChecksumAddress | None = None,
|
|
34
|
+
app_data: str | None = None,
|
|
35
|
+
env: "Envs" = "prod",
|
|
36
|
+
slippage_tolerance: float = 0.005,
|
|
37
|
+
) -> int:
|
|
38
|
+
"""Calculate the estimated sell amount needed to buy a fixed amount of tokens."""
|
|
39
|
+
if app_data is None:
|
|
40
|
+
app_data = get_cowpy_module("DEFAULT_APP_DATA_HASH")
|
|
41
|
+
|
|
42
|
+
# In testing context, these might be patched
|
|
43
|
+
global \
|
|
44
|
+
get_order_quote, \
|
|
45
|
+
OrderQuoteRequest, \
|
|
46
|
+
OrderQuoteSide3, \
|
|
47
|
+
OrderQuoteSideKindBuy, \
|
|
48
|
+
TokenAmount, \
|
|
49
|
+
SupportedChainId, \
|
|
50
|
+
OrderBookApi, \
|
|
51
|
+
OrderBookAPIConfigFactory
|
|
52
|
+
|
|
53
|
+
_get_order_quote = get_order_quote or get_cowpy_module("get_order_quote")
|
|
54
|
+
_order_quote_request_cls = OrderQuoteRequest or get_cowpy_module("OrderQuoteRequest")
|
|
55
|
+
_order_quote_side_cls = OrderQuoteSide3 or get_cowpy_module("OrderQuoteSide3")
|
|
56
|
+
_order_quote_side_kind_buy_cls = OrderQuoteSideKindBuy or get_cowpy_module(
|
|
57
|
+
"OrderQuoteSideKindBuy"
|
|
58
|
+
)
|
|
59
|
+
_token_amount_cls = TokenAmount or get_cowpy_module("TokenAmount")
|
|
60
|
+
_supported_chain_id_cls = SupportedChainId or get_cowpy_module("SupportedChainId")
|
|
61
|
+
_order_book_api_cls = OrderBookApi or get_cowpy_module("OrderBookApi")
|
|
62
|
+
_order_book_api_config_factory_cls = OrderBookAPIConfigFactory or get_cowpy_module(
|
|
63
|
+
"OrderBookAPIConfigFactory"
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
chain_id = _supported_chain_id_cls(chain_id_val)
|
|
67
|
+
order_book_api = _order_book_api_cls(
|
|
68
|
+
_order_book_api_config_factory_cls.get_config(env, chain_id)
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
order_quote_request = _order_quote_request_cls(
|
|
72
|
+
sellToken=sell_token,
|
|
73
|
+
buyToken=buy_token,
|
|
74
|
+
from_=safe_address if safe_address is not None else account_address,
|
|
75
|
+
appData=app_data,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
order_side = _order_quote_side_cls(
|
|
79
|
+
kind=_order_quote_side_kind_buy_cls.buy,
|
|
80
|
+
buyAmountAfterFee=_token_amount_cls(str(amount_wei)),
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
order_quote = await _get_order_quote(order_quote_request, order_side, order_book_api)
|
|
84
|
+
|
|
85
|
+
sell_amount_wei = int(int(order_quote.quote.sellAmount.root) * (1.0 + slippage_tolerance))
|
|
86
|
+
return sell_amount_wei
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
async def get_max_buy_amount_wei(
|
|
90
|
+
sell_amount_wei: Wei,
|
|
91
|
+
sell_token: ChecksumAddress,
|
|
92
|
+
buy_token: ChecksumAddress,
|
|
93
|
+
chain_id_val: int,
|
|
94
|
+
account_address: ChecksumAddress,
|
|
95
|
+
safe_address: ChecksumAddress | None = None,
|
|
96
|
+
app_data: str | None = None,
|
|
97
|
+
env: "Envs" = "prod",
|
|
98
|
+
slippage_tolerance: float = 0.005,
|
|
99
|
+
) -> int:
|
|
100
|
+
"""Calculate the maximum buy amount for a given sell amount."""
|
|
101
|
+
if app_data is None:
|
|
102
|
+
app_data = get_cowpy_module("DEFAULT_APP_DATA_HASH")
|
|
103
|
+
|
|
104
|
+
global \
|
|
105
|
+
get_order_quote, \
|
|
106
|
+
OrderQuoteRequest, \
|
|
107
|
+
OrderQuoteSide1, \
|
|
108
|
+
OrderQuoteSideKindSell, \
|
|
109
|
+
TokenAmount, \
|
|
110
|
+
SupportedChainId, \
|
|
111
|
+
OrderBookApi, \
|
|
112
|
+
OrderBookAPIConfigFactory
|
|
113
|
+
|
|
114
|
+
_get_order_quote = get_order_quote or get_cowpy_module("get_order_quote")
|
|
115
|
+
_order_quote_request_cls = OrderQuoteRequest or get_cowpy_module("OrderQuoteRequest")
|
|
116
|
+
_order_quote_side_cls = OrderQuoteSide1 or get_cowpy_module("OrderQuoteSide1")
|
|
117
|
+
_order_quote_side_kind_sell_cls = OrderQuoteSideKindSell or get_cowpy_module(
|
|
118
|
+
"OrderQuoteSideKindSell"
|
|
119
|
+
)
|
|
120
|
+
_token_amount_cls = TokenAmount or get_cowpy_module("TokenAmount")
|
|
121
|
+
_supported_chain_id_cls = SupportedChainId or get_cowpy_module("SupportedChainId")
|
|
122
|
+
_order_book_api_cls = OrderBookApi or get_cowpy_module("OrderBookApi")
|
|
123
|
+
_order_book_api_config_factory_cls = OrderBookAPIConfigFactory or get_cowpy_module(
|
|
124
|
+
"OrderBookAPIConfigFactory"
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
chain_id = _supported_chain_id_cls(chain_id_val)
|
|
128
|
+
order_book_api = _order_book_api_cls(
|
|
129
|
+
_order_book_api_config_factory_cls.get_config(env, chain_id)
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
order_quote_request = _order_quote_request_cls(
|
|
133
|
+
sellToken=sell_token,
|
|
134
|
+
buyToken=buy_token,
|
|
135
|
+
from_=safe_address if safe_address is not None else account_address,
|
|
136
|
+
appData=app_data,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
order_side = _order_quote_side_cls(
|
|
140
|
+
kind=_order_quote_side_kind_sell_cls.sell,
|
|
141
|
+
sellAmountBeforeFee=_token_amount_cls(str(sell_amount_wei)),
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
order_quote = await _get_order_quote(order_quote_request, order_side, order_book_api)
|
|
145
|
+
|
|
146
|
+
# Apply slippage (reduce buy amount)
|
|
147
|
+
buy_amount_wei = int(int(order_quote.quote.buyAmount.root) * (1.0 - slippage_tolerance))
|
|
148
|
+
return buy_amount_wei
|