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
@@ -0,0 +1,322 @@
1
+ """Mech manager mixin."""
2
+
3
+ from typing import Optional
4
+
5
+ from loguru import logger
6
+ from web3 import Web3
7
+
8
+ from iwa.core.constants import ZERO_ADDRESS
9
+ from iwa.plugins.olas.constants import (
10
+ OLAS_CONTRACTS,
11
+ PAYMENT_TYPE_NATIVE,
12
+ )
13
+ from iwa.plugins.olas.contracts.mech import MechContract
14
+ from iwa.plugins.olas.contracts.mech_marketplace import MechMarketplaceContract
15
+
16
+
17
+ class MechManagerMixin:
18
+ """Mixin for Mech interactions."""
19
+
20
+ def send_mech_request(
21
+ self,
22
+ data: bytes,
23
+ value: Optional[int] = None,
24
+ mech_address: Optional[str] = None,
25
+ use_marketplace: bool = False,
26
+ use_new_abi: bool = False,
27
+ priority_mech: Optional[str] = None,
28
+ max_delivery_rate: Optional[int] = None,
29
+ payment_type: Optional[bytes] = None,
30
+ payment_data: bytes = b"",
31
+ response_timeout: int = 300,
32
+ ) -> Optional[str]:
33
+ """Send a Mech request from the service multisig.
34
+
35
+ Args:
36
+ data: The request data (IPFS hash bytes).
37
+ value: Payment value in wei. For marketplace, should match mech's maxDeliveryRate.
38
+ mech_address: Address of the Mech contract (for legacy/direct flow).
39
+ use_marketplace: Whether to use the Mech Marketplace flow.
40
+ use_new_abi: Whether to use new ABI for legacy flow.
41
+ priority_mech: Priority mech address (required for marketplace).
42
+ max_delivery_rate: Max delivery rate in wei (for marketplace). If None, uses value.
43
+ payment_type: Payment type bytes32 (for marketplace). Defaults to NATIVE.
44
+ payment_data: Payment data (for marketplace).
45
+ response_timeout: Timeout in seconds for marketplace request (60-300).
46
+
47
+ Returns:
48
+ The transaction hash if successful, None otherwise.
49
+
50
+ """
51
+ if not self.service:
52
+ logger.error("No active service loaded")
53
+ return None
54
+
55
+ service_id = self.service.service_id
56
+ multisig_address = self.service.multisig_address
57
+
58
+ if not multisig_address:
59
+ logger.error(f"Service {service_id} has no multisig address")
60
+ return None
61
+
62
+ if use_marketplace:
63
+ return self._send_marketplace_mech_request(
64
+ data=data,
65
+ value=value,
66
+ priority_mech=priority_mech,
67
+ max_delivery_rate=max_delivery_rate,
68
+ payment_type=payment_type,
69
+ payment_data=payment_data,
70
+ response_timeout=response_timeout,
71
+ )
72
+ else:
73
+ return self._send_legacy_mech_request(
74
+ data=data,
75
+ value=value,
76
+ mech_address=mech_address,
77
+ use_new_abi=use_new_abi,
78
+ )
79
+
80
+ def _send_legacy_mech_request(
81
+ self,
82
+ data: bytes,
83
+ value: Optional[int] = None,
84
+ mech_address: Optional[str] = None,
85
+ use_new_abi: bool = False,
86
+ ) -> Optional[str]:
87
+ """Send a legacy (direct) mech request."""
88
+ if not self.service:
89
+ logger.error("No active service")
90
+ return None
91
+
92
+ multisig_address = self.service.multisig_address
93
+ protocol_contracts = OLAS_CONTRACTS.get(self.chain_name, {})
94
+ mech_address = mech_address or protocol_contracts.get("OLAS_MECH")
95
+
96
+ if not mech_address:
97
+ logger.error(f"Legacy mech address not found for chain {self.chain_name}")
98
+ return None
99
+
100
+ mech = MechContract(str(mech_address), chain_name=self.chain_name, use_new_abi=use_new_abi)
101
+
102
+ # Get mech price if value not provided
103
+ if value is None:
104
+ value = mech.get_price()
105
+ logger.info(f"Using mech price: {value} wei")
106
+
107
+ tx_data = mech.prepare_request_tx(
108
+ from_address=multisig_address,
109
+ data=data,
110
+ value=value,
111
+ )
112
+
113
+ if not tx_data:
114
+ logger.error("Failed to prepare legacy mech request transaction")
115
+ return None
116
+
117
+ return self._execute_mech_tx(
118
+ tx_data=tx_data,
119
+ to_address=str(mech_address),
120
+ contract_instance=mech,
121
+ expected_event="Request",
122
+ )
123
+
124
+ def _validate_priority_mech(self, marketplace, priority_mech: str) -> bool:
125
+ """Validate priority mech is registered on marketplace."""
126
+ try:
127
+ mech_multisig = marketplace.call("checkMech", priority_mech)
128
+ if mech_multisig == ZERO_ADDRESS:
129
+ logger.error(f"Priority mech {priority_mech} is NOT registered on marketplace")
130
+ return False
131
+ logger.debug(f"Priority mech {priority_mech} -> multisig {mech_multisig}")
132
+ except Exception as e:
133
+ logger.error(f"Failed to verify priority mech registration: {e}")
134
+ return False
135
+
136
+ # Log mech factory info (optional validation)
137
+ try:
138
+ mech_factory = marketplace.call("mapAgentMechFactories", priority_mech)
139
+ if mech_factory == ZERO_ADDRESS:
140
+ logger.warning(
141
+ f"Priority mech {priority_mech} has no factory (may be unregistered)"
142
+ )
143
+ else:
144
+ logger.debug(f"Priority mech factory: {mech_factory}")
145
+ except Exception as e:
146
+ logger.warning(f"Could not fetch mech factory: {e}")
147
+
148
+ return True
149
+
150
+ def _validate_marketplace_params(
151
+ self, marketplace, response_timeout: int, payment_type: bytes
152
+ ) -> bool:
153
+ """Validate marketplace parameters."""
154
+ # Validate response_timeout bounds
155
+ try:
156
+ min_timeout = marketplace.call("minResponseTimeout")
157
+ max_timeout = marketplace.call("maxResponseTimeout")
158
+ if response_timeout < min_timeout or response_timeout > max_timeout:
159
+ logger.error(
160
+ f"response_timeout {response_timeout} out of bounds [{min_timeout}, {max_timeout}]"
161
+ )
162
+ return False
163
+ logger.debug(
164
+ f"Response timeout {response_timeout}s within bounds [{min_timeout}, {max_timeout}]"
165
+ )
166
+ except Exception as e:
167
+ logger.warning(f"Could not validate response_timeout bounds: {e}")
168
+
169
+ # Validate payment type has balance tracker
170
+ try:
171
+ balance_tracker = marketplace.call("mapPaymentTypeBalanceTrackers", payment_type)
172
+ if balance_tracker == ZERO_ADDRESS:
173
+ logger.error(f"No balance tracker for payment type 0x{payment_type.hex()}")
174
+ return False
175
+ logger.debug(f"Payment type balance tracker: {balance_tracker}")
176
+ except Exception as e:
177
+ logger.warning(f"Could not validate payment type: {e}")
178
+
179
+ return True
180
+
181
+ def _send_marketplace_mech_request(
182
+ self,
183
+ data: bytes,
184
+ value: Optional[int] = None,
185
+ priority_mech: Optional[str] = None,
186
+ max_delivery_rate: Optional[int] = None,
187
+ payment_type: Optional[bytes] = None,
188
+ payment_data: bytes = b"",
189
+ response_timeout: int = 300,
190
+ ) -> Optional[str]:
191
+ """Send a marketplace mech request with validation."""
192
+ if not self.service:
193
+ logger.error("No active service")
194
+ return None
195
+
196
+ multisig_address = self.service.multisig_address
197
+ chain_name = self.chain_name if self.service else getattr(self, "chain_name", "gnosis")
198
+ protocol_contracts = OLAS_CONTRACTS.get(chain_name, {})
199
+ marketplace_address = protocol_contracts.get("OLAS_MECH_MARKETPLACE")
200
+
201
+ if not marketplace_address:
202
+ logger.error(f"Mech Marketplace address not found for chain {chain_name}")
203
+ return None
204
+
205
+ if not priority_mech:
206
+ logger.error("priority_mech is required for marketplace requests")
207
+ return None
208
+
209
+ priority_mech = Web3.to_checksum_address(priority_mech)
210
+ marketplace = MechMarketplaceContract(str(marketplace_address), chain_name=chain_name)
211
+
212
+ if not self._validate_priority_mech(marketplace, priority_mech):
213
+ return None
214
+
215
+ # Set defaults for payment
216
+ if payment_type is None:
217
+ payment_type = bytes.fromhex(PAYMENT_TYPE_NATIVE)
218
+
219
+ if value is None:
220
+ value = 10_000_000_000_000_000
221
+ logger.info(f"Using default value: {value} wei (0.01 xDAI)")
222
+
223
+ if max_delivery_rate is None:
224
+ max_delivery_rate = value
225
+ logger.info(f"Using value as max_delivery_rate: {max_delivery_rate}")
226
+
227
+ if not self._validate_marketplace_params(marketplace, response_timeout, payment_type):
228
+ return None
229
+
230
+ # Prepare transaction
231
+ tx_data = marketplace.prepare_request_tx(
232
+ from_address=multisig_address,
233
+ request_data=data,
234
+ priority_mech=priority_mech,
235
+ response_timeout=response_timeout,
236
+ max_delivery_rate=max_delivery_rate,
237
+ payment_type=payment_type,
238
+ payment_data=payment_data,
239
+ value=value,
240
+ )
241
+
242
+ if not tx_data:
243
+ logger.error("Failed to prepare marketplace request transaction")
244
+ return None
245
+
246
+ return self._execute_mech_tx(
247
+ tx_data=tx_data,
248
+ to_address=str(marketplace_address),
249
+ contract_instance=marketplace,
250
+ expected_event="MarketplaceRequest",
251
+ )
252
+
253
+ def _execute_mech_tx(
254
+ self,
255
+ tx_data: dict,
256
+ to_address: str,
257
+ contract_instance,
258
+ expected_event: str,
259
+ ) -> Optional[str]:
260
+ """Execute a mech transaction and verify the event."""
261
+ if not self.service:
262
+ logger.error("No active service")
263
+ return None
264
+
265
+ multisig_address = self.service.multisig_address
266
+ tx_value = int(tx_data.get("value", 0))
267
+
268
+ from iwa.core.models import StoredSafeAccount
269
+
270
+ sender_account = self.wallet.account_service.resolve_account(str(multisig_address))
271
+ is_safe = isinstance(sender_account, StoredSafeAccount)
272
+
273
+ if is_safe:
274
+ logger.info(f"Sending mech request via Safe {multisig_address} (value: {tx_value} wei)")
275
+ try:
276
+ tx_hash = self.wallet.safe_service.execute_safe_transaction(
277
+ safe_address_or_tag=str(multisig_address),
278
+ to=to_address,
279
+ value=tx_value,
280
+ chain_name=self.chain_name,
281
+ data=tx_data["data"],
282
+ )
283
+ except Exception as e:
284
+ logger.error(f"Safe transaction failed: {e}")
285
+ return None
286
+ else:
287
+ logger.info(f"Sending mech request via EOA {multisig_address} (value: {tx_value} wei)")
288
+ tx = {
289
+ "to": to_address,
290
+ "value": tx_value,
291
+ "data": tx_data["data"],
292
+ }
293
+ success, receipt = self.wallet.sign_and_send_transaction(
294
+ transaction=tx,
295
+ signer_address_or_tag=str(multisig_address),
296
+ chain_name=self.chain_name,
297
+ tags=["olas_mech_request"],
298
+ )
299
+ tx_hash = receipt.get("transactionHash").hex() if success else None
300
+
301
+ if not tx_hash:
302
+ logger.error("Failed to send mech request transaction")
303
+ return None
304
+
305
+ logger.info(f"Mech request transaction sent: {tx_hash}")
306
+
307
+ # Verify event emission
308
+ try:
309
+ receipt = self.registry.chain_interface.web3.eth.wait_for_transaction_receipt(tx_hash)
310
+ events = contract_instance.extract_events(receipt)
311
+ event_found = next((e for e in events if e["name"] == expected_event), None)
312
+
313
+ if event_found:
314
+ logger.info(f"Event '{expected_event}' verified successfully")
315
+ return tx_hash
316
+ else:
317
+ logger.error(f"Event '{expected_event}' NOT found in transaction logs")
318
+ logger.debug(f"Found events: {[e['name'] for e in events]}")
319
+ return None
320
+ except Exception as e:
321
+ logger.error(f"Error verifying event emission: {e}")
322
+ return None