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,378 @@
1
+ """Olas Services Router."""
2
+
3
+ import logging
4
+ from typing import Optional
5
+
6
+ from fastapi import APIRouter, Depends, HTTPException
7
+ from pydantic import BaseModel, Field
8
+
9
+ from iwa.core.models import Config
10
+ from iwa.plugins.olas.models import OlasConfig
11
+ from iwa.web.dependencies import verify_auth, wallet
12
+
13
+ logger = logging.getLogger(__name__)
14
+ router = APIRouter(tags=["olas"])
15
+
16
+
17
+ class CreateServiceRequest(BaseModel):
18
+ """Request model for creating an Olas service."""
19
+
20
+ service_name: str = Field(description="Human-readable name for the service")
21
+ chain: str = Field(default="gnosis", description="Chain to create the service on")
22
+ agent_type: str = Field(default="trader", description="Agent type (trader)")
23
+ token_address: Optional[str] = Field(
24
+ default="OLAS", description="Token address or name for bonding (OLAS for staking)"
25
+ )
26
+ stake_on_create: bool = Field(default=False, description="Whether to stake after creation")
27
+ staking_contract: Optional[str] = Field(
28
+ default=None, description="Staking contract address if staking"
29
+ )
30
+
31
+
32
+ @router.post(
33
+ "/create",
34
+ summary="Create Service",
35
+ description="Create a new Olas service on the specified chain and deploy it.",
36
+ )
37
+ def create_service(req: CreateServiceRequest, auth: bool = Depends(verify_auth)):
38
+ """Create a new Olas service using spin_up for seamless deployment."""
39
+ try:
40
+ from web3 import Web3
41
+
42
+ from iwa.plugins.olas.contracts.staking import StakingContract
43
+ from iwa.plugins.olas.service_manager import ServiceManager
44
+
45
+ manager = ServiceManager(wallet)
46
+
47
+ # Determine bond amount based on staking contract
48
+ bond_amount = 1 # Default for native token (1 wei)
49
+
50
+ staking_contract = None
51
+ if req.token_address:
52
+ if req.staking_contract:
53
+ # If a contract is specified, we MUST use its requirements
54
+ logger.info(f"Fetching requirements from {req.staking_contract}...")
55
+ staking_contract = StakingContract(req.staking_contract, req.chain)
56
+ reqs = staking_contract.get_requirements()
57
+ bond_amount = reqs["required_agent_bond"]
58
+ logger.info(f"Required bond amount from contract: {bond_amount} wei")
59
+ else:
60
+ # Default to 1 wei of the service token if no staking contract specified
61
+ bond_amount = Web3.to_wei(1, "wei")
62
+
63
+ # Step 1: Create the service (PRE_REGISTRATION state)
64
+ logger.info(
65
+ f"Calling manager.create with: chain={req.chain}, name={req.service_name}, "
66
+ f"token={req.token_address}, bond={bond_amount}"
67
+ )
68
+ try:
69
+ service_id = manager.create(
70
+ chain_name=req.chain,
71
+ service_name=req.service_name,
72
+ token_address_or_tag=req.token_address,
73
+ bond_amount_wei=bond_amount,
74
+ )
75
+ except Exception as create_error:
76
+ logger.error(f"manager.create raised exception: {create_error}")
77
+ raise HTTPException(
78
+ status_code=400, detail=f"Service creation error: {create_error}"
79
+ ) from None
80
+
81
+ if not service_id:
82
+ logger.error("manager.create returned None - check service_manager logs")
83
+ raise HTTPException(
84
+ status_code=400, detail="Failed to create service - see server logs"
85
+ )
86
+
87
+ logger.info(f"Service {service_id} created. Running spin_up...")
88
+
89
+ # Step 2: Spin up the service (activate → register → deploy → optionally stake)
90
+ # Only pass staking_contract if user wants to stake on create
91
+ spin_up_staking = staking_contract if req.stake_on_create else None
92
+
93
+ success = manager.spin_up(
94
+ service_id=service_id,
95
+ staking_contract=spin_up_staking,
96
+ bond_amount_wei=bond_amount,
97
+ )
98
+
99
+ if not success:
100
+ raise HTTPException(
101
+ status_code=400,
102
+ detail="Service created but spin_up failed. Check logs for details.",
103
+ )
104
+
105
+ # Get final state
106
+ final_state = manager.get_service_state()
107
+
108
+ return {
109
+ "status": "success",
110
+ "service_id": service_id,
111
+ "service_key": manager.service.key if manager.service else None,
112
+ "multisig": str(manager.service.multisig_address) if manager.service else None,
113
+ "final_state": final_state,
114
+ "staked": req.stake_on_create and spin_up_staking is not None,
115
+ }
116
+
117
+ except HTTPException:
118
+ raise
119
+ except Exception as e:
120
+ logger.error(f"Error creating service: {e}")
121
+ raise HTTPException(status_code=400, detail=str(e)) from None
122
+
123
+
124
+ @router.post(
125
+ "/deploy/{service_key}",
126
+ summary="Deploy Service",
127
+ description="Deploy an existing PRE_REGISTRATION service using spin_up.",
128
+ )
129
+ def deploy_service(
130
+ service_key: str,
131
+ staking_contract: Optional[str] = None,
132
+ auth: bool = Depends(verify_auth),
133
+ ):
134
+ """Deploy an existing service (spin_up from PRE_REGISTRATION to DEPLOYED/STAKED)."""
135
+ try:
136
+ from iwa.plugins.olas.contracts.staking import StakingContract
137
+ from iwa.plugins.olas.service_manager import ServiceManager
138
+
139
+ config = Config()
140
+ if "olas" not in config.plugins:
141
+ raise HTTPException(status_code=404, detail="Olas plugin not configured")
142
+
143
+ olas_config = OlasConfig.model_validate(config.plugins["olas"])
144
+ service = olas_config.services.get(service_key)
145
+
146
+ if not service:
147
+ raise HTTPException(status_code=404, detail="Service not found")
148
+
149
+ manager = ServiceManager(wallet)
150
+ manager.service = service
151
+ manager._init_contracts(service.chain_name)
152
+
153
+ # Get current state
154
+ current_state = manager.get_service_state()
155
+ if current_state != "PRE_REGISTRATION":
156
+ raise HTTPException(
157
+ status_code=400,
158
+ detail=f"Service is not in PRE_REGISTRATION state (current: {current_state})",
159
+ )
160
+
161
+ # Set up staking contract if provided
162
+ staking_obj = None
163
+ if staking_contract:
164
+ try:
165
+ staking_obj = StakingContract(staking_contract, service.chain_name)
166
+ logger.info(f"Will stake in {staking_contract} after deployment")
167
+ except Exception as e:
168
+ logger.warning(f"Could not set up staking contract: {e}")
169
+
170
+ logger.info(f"Running spin_up for service {service_key}...")
171
+
172
+ # Use spin_up to deploy (and optionally stake)
173
+ success = manager.spin_up(
174
+ service_id=service.service_id,
175
+ staking_contract=staking_obj,
176
+ )
177
+
178
+ if not success:
179
+ raise HTTPException(
180
+ status_code=400,
181
+ detail="spin_up failed. Check server logs for details.",
182
+ )
183
+
184
+ final_state = manager.get_service_state()
185
+ return {
186
+ "status": "success",
187
+ "service_key": service_key,
188
+ "final_state": final_state,
189
+ "staked": staking_obj is not None,
190
+ }
191
+
192
+ except HTTPException:
193
+ raise
194
+ except Exception as e:
195
+ logger.error(f"Error deploying service: {e}")
196
+ raise HTTPException(status_code=400, detail=str(e)) from None
197
+
198
+
199
+ @router.get(
200
+ "/services/basic",
201
+ summary="Get Basic Services",
202
+ description="Get a lightweight list of configured Olas services without RPC calls.",
203
+ )
204
+ def get_olas_services_basic(chain: str = "gnosis", auth: bool = Depends(verify_auth)):
205
+ """Get basic Olas service info from config (fast, no RPC calls)."""
206
+ if not chain.replace("-", "").isalnum():
207
+ raise HTTPException(status_code=400, detail="Invalid chain name")
208
+
209
+ try:
210
+ from iwa.plugins.olas.service_manager import ServiceManager
211
+
212
+ config = Config()
213
+ if "olas" not in config.plugins:
214
+ return []
215
+
216
+ olas_config = OlasConfig.model_validate(config.plugins["olas"])
217
+
218
+ result = []
219
+ for service_key, service in olas_config.services.items():
220
+ if service.chain_name != chain:
221
+ continue
222
+
223
+ # Get service state from registry
224
+ state = "UNKNOWN"
225
+ try:
226
+ manager = ServiceManager(wallet)
227
+ manager.service = service
228
+ state = manager.get_service_state()
229
+ except Exception as e:
230
+ logger.warning(f"Could not get state for {service_key}: {e}")
231
+
232
+ # Get tags from wallet storage (fast, local lookup)
233
+ accounts = {}
234
+ for role, addr in [
235
+ ("agent", service.agent_address),
236
+ ("safe", str(service.multisig_address) if service.multisig_address else None),
237
+ ("owner", service.service_owner_address),
238
+ ]:
239
+ if addr:
240
+ stored = wallet.key_storage.find_stored_account(addr)
241
+ accounts[role] = {
242
+ "address": addr,
243
+ "tag": stored.tag if stored else None,
244
+ "native": None, # Will be filled by details endpoint
245
+ "olas": None,
246
+ }
247
+
248
+ result.append(
249
+ {
250
+ "key": service_key,
251
+ "name": service.service_name,
252
+ "service_id": service.service_id,
253
+ "chain": service.chain_name,
254
+ "state": state,
255
+ "accounts": accounts,
256
+ "staking": {"is_staked": bool(service.staking_contract_address)}
257
+ if service.staking_contract_address
258
+ else None,
259
+ }
260
+ )
261
+
262
+ return result
263
+
264
+ except ImportError:
265
+ return []
266
+ except Exception as e:
267
+ logger.error(f"Error getting basic Olas services: {e}")
268
+ raise HTTPException(status_code=500, detail=str(e)) from None
269
+
270
+
271
+ @router.get(
272
+ "/services/{service_key}/details",
273
+ summary="Get Service Details",
274
+ description="Get detailed status, balances, and staking info for a specific Olas service.",
275
+ )
276
+ def get_olas_service_details(service_key: str, auth: bool = Depends(verify_auth)):
277
+ """Get full details for a single Olas service (staking, balances)."""
278
+ try:
279
+ from iwa.plugins.olas.service_manager import ServiceManager
280
+
281
+ config = Config()
282
+ if "olas" not in config.plugins:
283
+ raise HTTPException(status_code=404, detail="Olas plugin not configured")
284
+
285
+ olas_config = OlasConfig.model_validate(config.plugins["olas"])
286
+ if service_key not in olas_config.services:
287
+ raise HTTPException(status_code=404, detail=f"Service '{service_key}' not found")
288
+
289
+ service = olas_config.services[service_key]
290
+ chain = service.chain_name
291
+
292
+ manager = ServiceManager(wallet)
293
+ manager.service = service
294
+ staking_status = manager.get_staking_status()
295
+ service_state = manager.get_service_state()
296
+
297
+ # Get balances
298
+ balances = {}
299
+ for role, addr in [
300
+ ("agent", service.agent_address),
301
+ ("safe", str(service.multisig_address) if service.multisig_address else None),
302
+ ("owner", service.service_owner_address),
303
+ ]:
304
+ if addr:
305
+ native_bal = wallet.get_native_balance_eth(addr, chain)
306
+ olas_bal = wallet.balance_service.get_erc20_balance_wei(addr, "OLAS", chain)
307
+ olas_bal_eth = float(olas_bal) / 1e18 if olas_bal else 0
308
+ stored = wallet.key_storage.find_stored_account(addr)
309
+ balances[role] = {
310
+ "address": addr,
311
+ "tag": stored.tag if stored else None,
312
+ "native": f"{native_bal:.2f}" if native_bal else "0.00",
313
+ "olas": f"{olas_bal_eth:.2f}",
314
+ }
315
+
316
+ staking = None
317
+ if staking_status:
318
+ staking = {
319
+ "is_staked": staking_status.is_staked,
320
+ "staking_state": staking_status.staking_state,
321
+ "staking_contract_address": staking_status.staking_contract_address,
322
+ "staking_contract_name": staking_status.staking_contract_name,
323
+ "accrued_reward_olas": staking_status.accrued_reward_olas,
324
+ "accrued_reward_wei": staking_status.accrued_reward_wei,
325
+ "epoch_number": staking_status.epoch_number,
326
+ "epoch_end_utc": staking_status.epoch_end_utc,
327
+ "remaining_epoch_seconds": staking_status.remaining_epoch_seconds,
328
+ "mech_requests_this_epoch": staking_status.mech_requests_this_epoch,
329
+ "required_mech_requests": staking_status.required_mech_requests,
330
+ "has_enough_requests": staking_status.has_enough_requests,
331
+ "liveness_ratio_passed": staking_status.liveness_ratio_passed,
332
+ "unstake_available_at": staking_status.unstake_available_at,
333
+ }
334
+
335
+ return {
336
+ "key": service_key,
337
+ "state": service_state,
338
+ "accounts": balances,
339
+ "staking": staking,
340
+ }
341
+
342
+ except HTTPException:
343
+ raise
344
+ except Exception as e:
345
+ logger.error(f"Error getting service details: {e}")
346
+ raise HTTPException(status_code=500, detail=str(e)) from None
347
+
348
+
349
+ @router.get(
350
+ "/services",
351
+ summary="Get All Services",
352
+ description="Get comprehensive list of Olas services with full details (slower than basic).",
353
+ )
354
+ def get_olas_services(chain: str = "gnosis", auth: bool = Depends(verify_auth)):
355
+ """Get all Olas services with staking status for a specific chain."""
356
+ if not chain.replace("-", "").isalnum():
357
+ raise HTTPException(status_code=400, detail="Invalid chain name")
358
+
359
+ try:
360
+ # Re-using detail logic iteratively (inefficient but safe for now)
361
+ # Ideally we refactor this to be more efficient bulk query later
362
+ basic = get_olas_services_basic(chain, auth)
363
+ result = []
364
+ for svc in basic:
365
+ try:
366
+ details = get_olas_service_details(svc["key"], auth)
367
+ # Merge details into basic info
368
+ svc["staking"] = details["staking"]
369
+ svc["accounts"] = details["accounts"]
370
+ result.append(svc)
371
+ except Exception as e:
372
+ logger.error(f"Failed to get details for {svc['key']}: {e}")
373
+ result.append(svc) # Return basic info if details fail
374
+
375
+ return result
376
+ except Exception as e:
377
+ logger.error(f"Error getting Olas services: {e}")
378
+ raise HTTPException(status_code=500, detail=str(e)) from None