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
|
@@ -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
|