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.
Files changed (191) hide show
  1. conftest.py +22 -0
  2. iwa/__init__.py +1 -0
  3. iwa/__main__.py +6 -0
  4. iwa/core/__init__.py +1 -0
  5. iwa/core/chain/__init__.py +68 -0
  6. iwa/core/chain/errors.py +47 -0
  7. iwa/core/chain/interface.py +514 -0
  8. iwa/core/chain/manager.py +38 -0
  9. iwa/core/chain/models.py +128 -0
  10. iwa/core/chain/rate_limiter.py +193 -0
  11. iwa/core/cli.py +210 -0
  12. iwa/core/constants.py +28 -0
  13. iwa/core/contracts/__init__.py +1 -0
  14. iwa/core/contracts/contract.py +297 -0
  15. iwa/core/contracts/erc20.py +79 -0
  16. iwa/core/contracts/multisend.py +71 -0
  17. iwa/core/db.py +317 -0
  18. iwa/core/keys.py +361 -0
  19. iwa/core/mnemonic.py +385 -0
  20. iwa/core/models.py +344 -0
  21. iwa/core/monitor.py +209 -0
  22. iwa/core/plugins.py +45 -0
  23. iwa/core/pricing.py +91 -0
  24. iwa/core/services/__init__.py +17 -0
  25. iwa/core/services/account.py +57 -0
  26. iwa/core/services/balance.py +113 -0
  27. iwa/core/services/plugin.py +88 -0
  28. iwa/core/services/safe.py +392 -0
  29. iwa/core/services/transaction.py +172 -0
  30. iwa/core/services/transfer/__init__.py +166 -0
  31. iwa/core/services/transfer/base.py +260 -0
  32. iwa/core/services/transfer/erc20.py +247 -0
  33. iwa/core/services/transfer/multisend.py +386 -0
  34. iwa/core/services/transfer/native.py +262 -0
  35. iwa/core/services/transfer/swap.py +326 -0
  36. iwa/core/settings.py +95 -0
  37. iwa/core/tables.py +60 -0
  38. iwa/core/test.py +27 -0
  39. iwa/core/tests/test_wallet.py +255 -0
  40. iwa/core/types.py +59 -0
  41. iwa/core/ui.py +99 -0
  42. iwa/core/utils.py +59 -0
  43. iwa/core/wallet.py +380 -0
  44. iwa/plugins/__init__.py +1 -0
  45. iwa/plugins/gnosis/__init__.py +5 -0
  46. iwa/plugins/gnosis/cow/__init__.py +6 -0
  47. iwa/plugins/gnosis/cow/quotes.py +148 -0
  48. iwa/plugins/gnosis/cow/swap.py +403 -0
  49. iwa/plugins/gnosis/cow/types.py +20 -0
  50. iwa/plugins/gnosis/cow_utils.py +44 -0
  51. iwa/plugins/gnosis/plugin.py +68 -0
  52. iwa/plugins/gnosis/safe.py +157 -0
  53. iwa/plugins/gnosis/tests/test_cow.py +227 -0
  54. iwa/plugins/gnosis/tests/test_safe.py +100 -0
  55. iwa/plugins/olas/__init__.py +5 -0
  56. iwa/plugins/olas/constants.py +106 -0
  57. iwa/plugins/olas/contracts/activity_checker.py +93 -0
  58. iwa/plugins/olas/contracts/base.py +10 -0
  59. iwa/plugins/olas/contracts/mech.py +49 -0
  60. iwa/plugins/olas/contracts/mech_marketplace.py +43 -0
  61. iwa/plugins/olas/contracts/service.py +215 -0
  62. iwa/plugins/olas/contracts/staking.py +403 -0
  63. iwa/plugins/olas/importer.py +736 -0
  64. iwa/plugins/olas/mech_reference.py +135 -0
  65. iwa/plugins/olas/models.py +110 -0
  66. iwa/plugins/olas/plugin.py +243 -0
  67. iwa/plugins/olas/scripts/test_full_mech_flow.py +259 -0
  68. iwa/plugins/olas/scripts/test_simple_lifecycle.py +74 -0
  69. iwa/plugins/olas/service_manager/__init__.py +60 -0
  70. iwa/plugins/olas/service_manager/base.py +113 -0
  71. iwa/plugins/olas/service_manager/drain.py +336 -0
  72. iwa/plugins/olas/service_manager/lifecycle.py +839 -0
  73. iwa/plugins/olas/service_manager/mech.py +322 -0
  74. iwa/plugins/olas/service_manager/staking.py +530 -0
  75. iwa/plugins/olas/tests/conftest.py +30 -0
  76. iwa/plugins/olas/tests/test_importer.py +128 -0
  77. iwa/plugins/olas/tests/test_importer_error_handling.py +349 -0
  78. iwa/plugins/olas/tests/test_mech_contracts.py +85 -0
  79. iwa/plugins/olas/tests/test_olas_contracts.py +249 -0
  80. iwa/plugins/olas/tests/test_olas_integration.py +561 -0
  81. iwa/plugins/olas/tests/test_olas_models.py +144 -0
  82. iwa/plugins/olas/tests/test_olas_view.py +258 -0
  83. iwa/plugins/olas/tests/test_olas_view_actions.py +137 -0
  84. iwa/plugins/olas/tests/test_olas_view_modals.py +120 -0
  85. iwa/plugins/olas/tests/test_plugin.py +70 -0
  86. iwa/plugins/olas/tests/test_plugin_full.py +212 -0
  87. iwa/plugins/olas/tests/test_service_lifecycle.py +150 -0
  88. iwa/plugins/olas/tests/test_service_manager.py +1065 -0
  89. iwa/plugins/olas/tests/test_service_manager_errors.py +208 -0
  90. iwa/plugins/olas/tests/test_service_manager_flows.py +497 -0
  91. iwa/plugins/olas/tests/test_service_manager_mech.py +135 -0
  92. iwa/plugins/olas/tests/test_service_manager_rewards.py +360 -0
  93. iwa/plugins/olas/tests/test_service_manager_validation.py +145 -0
  94. iwa/plugins/olas/tests/test_service_staking.py +342 -0
  95. iwa/plugins/olas/tests/test_staking_integration.py +269 -0
  96. iwa/plugins/olas/tests/test_staking_validation.py +109 -0
  97. iwa/plugins/olas/tui/__init__.py +1 -0
  98. iwa/plugins/olas/tui/olas_view.py +952 -0
  99. iwa/tools/check_profile.py +67 -0
  100. iwa/tools/release.py +111 -0
  101. iwa/tools/reset_env.py +111 -0
  102. iwa/tools/reset_tenderly.py +362 -0
  103. iwa/tools/restore_backup.py +82 -0
  104. iwa/tui/__init__.py +1 -0
  105. iwa/tui/app.py +174 -0
  106. iwa/tui/modals/__init__.py +5 -0
  107. iwa/tui/modals/base.py +406 -0
  108. iwa/tui/rpc.py +63 -0
  109. iwa/tui/screens/__init__.py +1 -0
  110. iwa/tui/screens/wallets.py +749 -0
  111. iwa/tui/tests/test_app.py +125 -0
  112. iwa/tui/tests/test_rpc.py +139 -0
  113. iwa/tui/tests/test_wallets_refactor.py +30 -0
  114. iwa/tui/tests/test_widgets.py +123 -0
  115. iwa/tui/widgets/__init__.py +5 -0
  116. iwa/tui/widgets/base.py +100 -0
  117. iwa/tui/workers.py +42 -0
  118. iwa/web/dependencies.py +76 -0
  119. iwa/web/models.py +76 -0
  120. iwa/web/routers/accounts.py +115 -0
  121. iwa/web/routers/olas/__init__.py +24 -0
  122. iwa/web/routers/olas/admin.py +169 -0
  123. iwa/web/routers/olas/funding.py +135 -0
  124. iwa/web/routers/olas/general.py +29 -0
  125. iwa/web/routers/olas/services.py +378 -0
  126. iwa/web/routers/olas/staking.py +341 -0
  127. iwa/web/routers/state.py +65 -0
  128. iwa/web/routers/swap.py +617 -0
  129. iwa/web/routers/transactions.py +153 -0
  130. iwa/web/server.py +155 -0
  131. iwa/web/tests/test_web_endpoints.py +713 -0
  132. iwa/web/tests/test_web_olas.py +430 -0
  133. iwa/web/tests/test_web_swap.py +103 -0
  134. iwa-0.0.1a2.dist-info/METADATA +234 -0
  135. iwa-0.0.1a2.dist-info/RECORD +186 -0
  136. iwa-0.0.1a2.dist-info/entry_points.txt +2 -0
  137. iwa-0.0.1a2.dist-info/licenses/LICENSE +21 -0
  138. iwa-0.0.1a2.dist-info/top_level.txt +4 -0
  139. tests/legacy_cow.py +248 -0
  140. tests/legacy_safe.py +93 -0
  141. tests/legacy_transaction_retry_logic.py +51 -0
  142. tests/legacy_tui.py +440 -0
  143. tests/legacy_wallets_screen.py +554 -0
  144. tests/legacy_web.py +243 -0
  145. tests/test_account_service.py +120 -0
  146. tests/test_balance_service.py +186 -0
  147. tests/test_chain.py +490 -0
  148. tests/test_chain_interface.py +210 -0
  149. tests/test_cli.py +139 -0
  150. tests/test_contract.py +195 -0
  151. tests/test_db.py +180 -0
  152. tests/test_drain_coverage.py +174 -0
  153. tests/test_erc20.py +95 -0
  154. tests/test_gnosis_plugin.py +111 -0
  155. tests/test_keys.py +449 -0
  156. tests/test_legacy_wallet.py +1285 -0
  157. tests/test_main.py +13 -0
  158. tests/test_mnemonic.py +217 -0
  159. tests/test_modals.py +109 -0
  160. tests/test_models.py +213 -0
  161. tests/test_monitor.py +202 -0
  162. tests/test_multisend.py +84 -0
  163. tests/test_plugin_service.py +119 -0
  164. tests/test_pricing.py +143 -0
  165. tests/test_rate_limiter.py +199 -0
  166. tests/test_reset_tenderly.py +202 -0
  167. tests/test_rpc_view.py +73 -0
  168. tests/test_safe_coverage.py +139 -0
  169. tests/test_safe_service.py +168 -0
  170. tests/test_service_manager_integration.py +61 -0
  171. tests/test_service_manager_structure.py +31 -0
  172. tests/test_service_transaction.py +176 -0
  173. tests/test_staking_router.py +71 -0
  174. tests/test_staking_simple.py +31 -0
  175. tests/test_tables.py +76 -0
  176. tests/test_transaction_service.py +161 -0
  177. tests/test_transfer_multisend.py +179 -0
  178. tests/test_transfer_native.py +220 -0
  179. tests/test_transfer_security.py +93 -0
  180. tests/test_transfer_structure.py +37 -0
  181. tests/test_transfer_swap_unit.py +155 -0
  182. tests/test_ui_coverage.py +66 -0
  183. tests/test_utils.py +53 -0
  184. tests/test_workers.py +91 -0
  185. tools/verify_drain.py +183 -0
  186. __init__.py +0 -2
  187. hello.py +0 -6
  188. iwa-0.0.0.dist-info/METADATA +0 -10
  189. iwa-0.0.0.dist-info/RECORD +0 -6
  190. iwa-0.0.0.dist-info/top_level.txt +0 -2
  191. {iwa-0.0.0.dist-info → iwa-0.0.1a2.dist-info}/WHEEL +0 -0
tests/legacy_cow.py ADDED
@@ -0,0 +1,248 @@
1
+ from unittest.mock import AsyncMock, MagicMock, patch
2
+
3
+ import pytest
4
+
5
+
6
+ # We need to mock cowdao_cowpy before importing CowSwap to avoid import errors
7
+ # if the library is not installed.
8
+ @pytest.fixture(autouse=True)
9
+ def mock_cowpy_modules():
10
+ """Mock cowdao_cowpy modules and submodules globally for these tests."""
11
+ mock_cow = MagicMock()
12
+ mock_common = MagicMock()
13
+ mock_chains = MagicMock()
14
+
15
+ # Configure Chain enum mock
16
+ mock_chain_enum = MagicMock()
17
+ mock_chain_enum.value = [100] # Gnosis
18
+ mock_chains.Chain = [mock_chain_enum]
19
+ mock_chains.SupportedChainId = lambda x: x
20
+
21
+ modules = {
22
+ "cowdao_cowpy": MagicMock(),
23
+ "cowdao_cowpy.cow": mock_cow,
24
+ "cowdao_cowpy.cow.swap": MagicMock(),
25
+ "cowdao_cowpy.common": mock_common,
26
+ "cowdao_cowpy.common.chains": mock_chains,
27
+ "cowdao_cowpy.app_data": MagicMock(),
28
+ "cowdao_cowpy.app_data.utils": MagicMock(),
29
+ "cowdao_cowpy.contracts": MagicMock(),
30
+ "cowdao_cowpy.contracts.order": MagicMock(),
31
+ "cowdao_cowpy.contracts.sign": MagicMock(),
32
+ "cowdao_cowpy.order_book": MagicMock(),
33
+ "cowdao_cowpy.order_book.api": MagicMock(),
34
+ "cowdao_cowpy.order_book.config": MagicMock(),
35
+ "cowdao_cowpy.order_book.generated": MagicMock(),
36
+ "cowdao_cowpy.order_book.generated.model": MagicMock(),
37
+ }
38
+
39
+ with patch.dict("sys.modules", modules):
40
+ yield modules
41
+
42
+
43
+ @pytest.fixture(autouse=True)
44
+ def clear_cowswap_cache():
45
+ """Clear the lazy loading cache in CowSwap module."""
46
+ from iwa.plugins.gnosis.cow import _cowpy_cache
47
+
48
+ _cowpy_cache.clear()
49
+ yield
50
+ _cowpy_cache.clear()
51
+
52
+
53
+ from iwa.core.chain import Gnosis
54
+ from iwa.plugins.gnosis.cow import CowSwap, OrderType
55
+
56
+
57
+ @pytest.fixture
58
+ def mock_account():
59
+ with patch("iwa.plugins.gnosis.cow.Account") as mock:
60
+ mock.from_key.return_value = MagicMock(address="0xAccount", _address="0xAccount")
61
+ yield mock
62
+
63
+
64
+ @pytest.fixture
65
+ def cow_swap(mock_account):
66
+ return CowSwap("private_key", Gnosis())
67
+
68
+
69
+ @pytest.mark.asyncio
70
+ async def test_swap_sell(cow_swap):
71
+ with patch("iwa.plugins.gnosis.cow.swap_tokens", new_callable=AsyncMock) as mock_swap_tokens:
72
+ mock_swap_tokens.return_value = MagicMock(uid=MagicMock(root="order_uid"))
73
+
74
+ with patch.object(CowSwap, "check_cowswap_order", return_value=True):
75
+ success = await cow_swap.swap(1000, "OLAS", "WXDAI", order_type=OrderType.SELL)
76
+ assert success is True
77
+ mock_swap_tokens.assert_called_once()
78
+
79
+
80
+ @pytest.mark.asyncio
81
+ async def test_swap_buy(cow_swap):
82
+ with patch(
83
+ "iwa.plugins.gnosis.cow.CowSwap.swap_tokens_to_exact_tokens", new_callable=AsyncMock
84
+ ) as mock_swap_tokens:
85
+ mock_swap_tokens.return_value = MagicMock(uid=MagicMock(root="order_uid"))
86
+
87
+ with patch.object(CowSwap, "check_cowswap_order", return_value=True):
88
+ success = await cow_swap.swap(1000, "OLAS", "WXDAI", order_type=OrderType.BUY)
89
+ assert success is True
90
+ mock_swap_tokens.assert_called_once()
91
+
92
+
93
+ def test_check_cowswap_order_executed(cow_swap):
94
+ order = MagicMock()
95
+ order.url = "http://order.url"
96
+
97
+ with patch("iwa.plugins.gnosis.cow.requests.get") as mock_get:
98
+ mock_get.return_value.status_code = 200
99
+ mock_get.return_value.json.return_value = {
100
+ "status": "fulfilled",
101
+ "executedSellAmount": "100",
102
+ "executedBuyAmount": "100",
103
+ "quote": {"sellTokenPrice": 1.0, "buyTokenPrice": 1.0},
104
+ }
105
+
106
+ success = cow_swap.check_cowswap_order(order)
107
+ assert success is True
108
+
109
+
110
+ def test_check_cowswap_order_expired(cow_swap):
111
+ order = MagicMock()
112
+ order.url = "http://order.url"
113
+
114
+ with patch("iwa.plugins.gnosis.cow.requests.get") as mock_get:
115
+ mock_get.return_value.status_code = 200
116
+ mock_get.return_value.json.return_value = {"status": "expired"}
117
+
118
+ success = cow_swap.check_cowswap_order(order)
119
+ assert success is False
120
+
121
+
122
+ def test_get_chain_unsupported(cow_swap):
123
+ # Set to an ID that won't be found in our mock Chain list
124
+ cow_swap.supported_chain_id = 999
125
+ with pytest.raises(ValueError, match="Unsupported SupportedChainId"):
126
+ cow_swap.get_chain()
127
+
128
+
129
+ def test_check_cowswap_order_retries(cow_swap):
130
+ order = MagicMock()
131
+ order.url = "http://order.url"
132
+
133
+ with (
134
+ patch("iwa.plugins.gnosis.cow.requests.get") as mock_get,
135
+ patch("iwa.plugins.gnosis.cow.time.sleep") as mock_sleep,
136
+ ):
137
+ mock_get.side_effect = [
138
+ MagicMock(status_code=404),
139
+ MagicMock(
140
+ status_code=200, json=lambda: {"status": "fulfilled", "executedSellAmount": "100"}
141
+ ),
142
+ ]
143
+
144
+ success = cow_swap.check_cowswap_order(order)
145
+ assert success is True
146
+ assert mock_get.call_count == 2
147
+ mock_sleep.assert_called_once()
148
+
149
+
150
+ def test_check_cowswap_order_max_retries(cow_swap):
151
+ order = MagicMock()
152
+ order.url = "http://order.url"
153
+
154
+ with (
155
+ patch("iwa.plugins.gnosis.cow.requests.get") as mock_get,
156
+ patch("iwa.plugins.gnosis.cow.time.sleep"),
157
+ ):
158
+ mock_get.return_value.status_code = 404
159
+
160
+ success = cow_swap.check_cowswap_order(order)
161
+ assert success is False
162
+ assert mock_get.call_count == 8
163
+
164
+
165
+ @pytest.mark.asyncio
166
+ async def test_swap_exception(cow_swap):
167
+ with patch("iwa.plugins.gnosis.cow.swap_tokens", side_effect=Exception("Swap failed")):
168
+ success = await cow_swap.swap(1000, "OLAS", "WXDAI")
169
+ assert success is False
170
+
171
+
172
+ @pytest.mark.asyncio
173
+ async def test_get_max_sell_amount_wei(cow_swap):
174
+ with patch("iwa.plugins.gnosis.cow.get_order_quote", new_callable=AsyncMock) as mock_get_quote:
175
+ mock_quote = MagicMock()
176
+ mock_quote.quote.sellAmount.root = "1000"
177
+ mock_get_quote.return_value = mock_quote
178
+
179
+ amount = await cow_swap.get_max_sell_amount_wei(1000, "0xSell", "0xBuy")
180
+ assert amount == int(1000 * 1.005)
181
+
182
+
183
+ @pytest.mark.asyncio
184
+ async def test_swap_tokens_to_exact_tokens(cow_swap):
185
+ with (
186
+ patch("iwa.plugins.gnosis.cow.get_order_quote", new_callable=AsyncMock) as mock_get_quote,
187
+ patch("iwa.plugins.gnosis.cow.post_order", new_callable=AsyncMock) as mock_post_order,
188
+ patch("iwa.plugins.gnosis.cow.sign_order"),
189
+ patch("iwa.plugins.gnosis.cow.CompletedOrder") as mock_completed_order,
190
+ ):
191
+ mock_quote = MagicMock()
192
+ mock_quote.quote.sellAmount.root = "1000"
193
+ mock_quote.quote.validTo = 1234567890
194
+ mock_get_quote.return_value = mock_quote
195
+
196
+ mock_post_order.return_value = MagicMock(root="order_uid")
197
+
198
+ cow_swap.order_book_api = MagicMock()
199
+ cow_swap.order_book_api.get_order_link.return_value = "http://order.link"
200
+
201
+ mock_order_instance = MagicMock()
202
+ mock_order_instance.uid = MagicMock(root="order_uid")
203
+ mock_order_instance.url = "http://order.link"
204
+ mock_completed_order.return_value = mock_order_instance
205
+
206
+ order = await CowSwap.swap_tokens_to_exact_tokens(
207
+ amount=1000,
208
+ account=MagicMock(address="0xAccount", _address="0xAccount"),
209
+ chain=MagicMock(value=[100]),
210
+ sell_token="0xSell",
211
+ buy_token="0xBuy",
212
+ )
213
+
214
+ assert order.uid.root == "order_uid"
215
+ assert order.url == "http://order.link"
216
+
217
+
218
+ def test_check_cowswap_order_pending(cow_swap):
219
+ order = MagicMock()
220
+ order.url = "http://order.url"
221
+
222
+ with (
223
+ patch("iwa.plugins.gnosis.cow.requests.get") as mock_get,
224
+ patch("iwa.plugins.gnosis.cow.time.sleep") as mock_sleep,
225
+ ):
226
+ mock_get.side_effect = [
227
+ MagicMock(
228
+ status_code=200,
229
+ json=lambda: {
230
+ "status": "open",
231
+ "executedSellAmount": "0",
232
+ "executedBuyAmount": "0",
233
+ },
234
+ ),
235
+ MagicMock(
236
+ status_code=200,
237
+ json=lambda: {
238
+ "status": "fulfilled",
239
+ "executedSellAmount": "100",
240
+ "executedBuyAmount": "0",
241
+ },
242
+ ),
243
+ ]
244
+
245
+ success = cow_swap.check_cowswap_order(order)
246
+ assert success is True
247
+ assert mock_get.call_count == 2
248
+ mock_sleep.assert_called_once()
tests/legacy_safe.py ADDED
@@ -0,0 +1,93 @@
1
+ import sys
2
+ from unittest.mock import MagicMock, patch
3
+
4
+ import pytest
5
+
6
+ # Mock safe_eth before importing SafeMultisig
7
+ sys.modules["safe_eth"] = MagicMock()
8
+ sys.modules["safe_eth.eth"] = MagicMock()
9
+ sys.modules["safe_eth.eth.constants"] = MagicMock()
10
+ sys.modules["safe_eth.safe"] = MagicMock()
11
+
12
+ from iwa.core.models import StoredSafeAccount
13
+ from iwa.plugins.gnosis.safe import SafeMultisig
14
+
15
+
16
+ @pytest.fixture
17
+ def mock_secrets():
18
+ with patch("iwa.plugins.gnosis.safe.settings") as mock:
19
+ mock.gnosis_rpc.get_secret_value.return_value = "http://rpc"
20
+ yield mock
21
+
22
+
23
+ @pytest.fixture
24
+ def mock_safe_account():
25
+ account = MagicMock(spec=StoredSafeAccount)
26
+ account.address = "0xSafe"
27
+ account.chains = ["gnosis"]
28
+ return account
29
+
30
+
31
+ @pytest.fixture
32
+ def mock_safe_lib():
33
+ with (
34
+ patch("iwa.plugins.gnosis.safe.Safe") as mock_safe,
35
+ patch("iwa.plugins.gnosis.safe.EthereumClient") as mock_client,
36
+ ):
37
+ yield mock_safe, mock_client
38
+
39
+
40
+ def test_init_success(mock_safe_account, mock_secrets, mock_safe_lib):
41
+ safe = SafeMultisig(mock_safe_account, "gnosis")
42
+ assert safe.multisig is not None
43
+ mock_safe_lib[1].assert_called_with("http://rpc") # EthereumClient
44
+ mock_safe_lib[0].assert_called_with("0xSafe", mock_safe_lib[1].return_value) # Safe
45
+
46
+
47
+ def test_init_invalid_chain(mock_safe_account, mock_secrets):
48
+ with pytest.raises(ValueError, match="Safe account is not deployed on chain"):
49
+ SafeMultisig(mock_safe_account, "ethereum")
50
+
51
+
52
+ def test_get_owners(mock_safe_account, mock_secrets, mock_safe_lib):
53
+ safe = SafeMultisig(mock_safe_account, "gnosis")
54
+ safe.multisig.retrieve_owners.return_value = ["0xOwner"]
55
+ assert safe.get_owners() == ["0xOwner"]
56
+
57
+
58
+ def test_get_threshold(mock_safe_account, mock_secrets, mock_safe_lib):
59
+ safe = SafeMultisig(mock_safe_account, "gnosis")
60
+ safe.multisig.retrieve_threshold.return_value = 2
61
+ assert safe.get_threshold() == 2
62
+
63
+
64
+ def test_get_nonce(mock_safe_account, mock_secrets, mock_safe_lib):
65
+ safe = SafeMultisig(mock_safe_account, "gnosis")
66
+ safe.multisig.retrieve_nonce.return_value = 5
67
+ assert safe.get_nonce() == 5
68
+
69
+
70
+ def test_retrieve_all_info(mock_safe_account, mock_secrets, mock_safe_lib):
71
+ safe = SafeMultisig(mock_safe_account, "gnosis")
72
+ safe.multisig.retrieve_all_info.return_value = {"owners": []}
73
+ assert safe.retrieve_all_info() == {"owners": []}
74
+
75
+
76
+ def test_send_tx_with_callback(mock_safe_account, mock_secrets, mock_safe_lib):
77
+ """Test the new send_tx method with callback."""
78
+ safe = SafeMultisig(mock_safe_account, "gnosis")
79
+ mock_tx = MagicMock()
80
+ safe.multisig.build_multisig_tx.return_value = mock_tx
81
+
82
+ def sign_callback(safe_tx):
83
+ return "0xCallbackHash"
84
+
85
+ result = safe.send_tx(
86
+ to="0xTo",
87
+ value=100,
88
+ sign_and_execute_callback=sign_callback,
89
+ data="0x1234",
90
+ )
91
+
92
+ assert result == "0xCallbackHash"
93
+ safe.multisig.build_multisig_tx.assert_called()
@@ -0,0 +1,51 @@
1
+ from unittest.mock import MagicMock, patch
2
+
3
+ import pytest
4
+
5
+ from iwa.tui.app import IwaApp
6
+ from iwa.tui.modals import CreateEOAModal
7
+ from iwa.tui.screens.wallets import WalletsScreen
8
+
9
+ # --- TUI Modal Tests ---
10
+
11
+
12
+ @pytest.mark.asyncio
13
+ async def test_create_eoa_modal():
14
+ modal = CreateEOAModal()
15
+ mock_event = MagicMock()
16
+ mock_event.button.id = "cancel"
17
+ with patch.object(modal, "dismiss") as mock_dismiss:
18
+ modal.on_button_pressed(mock_event)
19
+ mock_dismiss.assert_called()
20
+ mock_event.button.id = "create"
21
+ try:
22
+ modal.on_button_pressed(mock_event)
23
+ except Exception:
24
+ pass
25
+
26
+
27
+ # --- WalletsScreen Filtering Tests ---
28
+
29
+
30
+ @pytest.mark.asyncio
31
+ async def test_wallets_view_filtering():
32
+ with patch("iwa.core.db.db"):
33
+ app = IwaApp()
34
+ async with app.run_test() as _:
35
+ view = app.query_one(WalletsScreen)
36
+
37
+ with (
38
+ patch.object(view, "fetch_all_balances") as mock_fetch,
39
+ patch.object(view, "refresh_table_structure_and_data") as mock_refresh,
40
+ ):
41
+ mock_chk_event = MagicMock()
42
+ mock_chk_event.checkbox.id = "cb_Token"
43
+ mock_chk_event.value = True
44
+
45
+ view.on_checkbox_changed(mock_chk_event)
46
+
47
+ mock_fetch.assert_called_with(view.active_chain, ["Token"])
48
+
49
+ mock_chk_event.value = False
50
+ view.on_checkbox_changed(mock_chk_event)
51
+ mock_refresh.assert_called()