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,341 @@
|
|
|
1
|
+
"""Olas Staking Router."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from fastapi import APIRouter, Depends, HTTPException, Request
|
|
7
|
+
from slowapi import Limiter
|
|
8
|
+
from slowapi.util import get_remote_address
|
|
9
|
+
|
|
10
|
+
from iwa.core.models import Config
|
|
11
|
+
from iwa.plugins.olas.models import OlasConfig
|
|
12
|
+
from iwa.web.dependencies import get_config, verify_auth, wallet
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
router = APIRouter(tags=["olas"])
|
|
16
|
+
limiter = Limiter(key_func=get_remote_address)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@router.get(
|
|
20
|
+
"/staking-contracts",
|
|
21
|
+
summary="Get Staking Contracts",
|
|
22
|
+
description="Get the list of available OLAS staking contracts for a specific chain.",
|
|
23
|
+
)
|
|
24
|
+
def get_staking_contracts(
|
|
25
|
+
chain: str = "gnosis",
|
|
26
|
+
service_key: Optional[str] = None,
|
|
27
|
+
auth: bool = Depends(verify_auth), # noqa: B008
|
|
28
|
+
config: Config = Depends(get_config), # noqa: B008
|
|
29
|
+
):
|
|
30
|
+
"""Get available staking contracts for a chain, optionally filtered by service bond."""
|
|
31
|
+
if not chain.replace("-", "").isalnum():
|
|
32
|
+
from fastapi import HTTPException
|
|
33
|
+
|
|
34
|
+
raise HTTPException(status_code=400, detail="Invalid chain name")
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
import json
|
|
38
|
+
|
|
39
|
+
from iwa.core.chain import ChainInterface
|
|
40
|
+
from iwa.plugins.olas.constants import OLAS_TRADER_STAKING_CONTRACTS
|
|
41
|
+
from iwa.plugins.olas.contracts.base import OLAS_ABI_PATH
|
|
42
|
+
|
|
43
|
+
contracts = OLAS_TRADER_STAKING_CONTRACTS.get(chain, {})
|
|
44
|
+
|
|
45
|
+
# Get service bond and token if filtered
|
|
46
|
+
service_bond, service_token = _get_service_filter_info(service_key)
|
|
47
|
+
|
|
48
|
+
# Load ABI once
|
|
49
|
+
with open(OLAS_ABI_PATH / "staking.json", "r") as f:
|
|
50
|
+
abi = json.load(f)
|
|
51
|
+
|
|
52
|
+
# Get correct web3 instance
|
|
53
|
+
w3 = ChainInterface(chain).web3
|
|
54
|
+
|
|
55
|
+
results = _fetch_all_contracts(contracts, w3, abi)
|
|
56
|
+
filtered_results = _filter_contracts(results, service_bond, service_token)
|
|
57
|
+
|
|
58
|
+
# Return with filter metadata so frontend can explain filtering
|
|
59
|
+
return {
|
|
60
|
+
"contracts": filtered_results,
|
|
61
|
+
"filter_info": {
|
|
62
|
+
"service_bond": service_bond,
|
|
63
|
+
"service_bond_olas": service_bond / 10**18 if service_bond else None,
|
|
64
|
+
"total_contracts": len(results),
|
|
65
|
+
"filtered_count": len(filtered_results),
|
|
66
|
+
"is_filtered": service_key is not None,
|
|
67
|
+
},
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
except Exception as e:
|
|
71
|
+
import traceback
|
|
72
|
+
|
|
73
|
+
traceback.print_exc()
|
|
74
|
+
logger.error(f"Error fetching staking contracts: {e}")
|
|
75
|
+
return []
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _get_service_filter_info(service_key: Optional[str]) -> tuple[Optional[int], Optional[str]]:
|
|
79
|
+
"""Retrieve service bond and token if service_key is provided."""
|
|
80
|
+
service_bond = None
|
|
81
|
+
service_token = None
|
|
82
|
+
|
|
83
|
+
if service_key:
|
|
84
|
+
try:
|
|
85
|
+
from iwa.plugins.olas.service_manager import ServiceManager
|
|
86
|
+
|
|
87
|
+
# Initialize wallet dependencies for ServiceManager
|
|
88
|
+
manager = ServiceManager(wallet, service_key)
|
|
89
|
+
if manager.service:
|
|
90
|
+
# Get service requirements
|
|
91
|
+
service_token = (manager.service.token_address or "").lower()
|
|
92
|
+
service_id_int = manager.service.service_id
|
|
93
|
+
|
|
94
|
+
# Get security deposit from registry - this is the actual bond value
|
|
95
|
+
try:
|
|
96
|
+
service_info = manager.registry.get_service(service_id_int)
|
|
97
|
+
service_bond = service_info.get("security_deposit", 0)
|
|
98
|
+
logger.info(
|
|
99
|
+
f"Filtering for service {service_key}: security_deposit={service_bond}, token={service_token}"
|
|
100
|
+
)
|
|
101
|
+
except Exception as e:
|
|
102
|
+
logger.warning(f"Failed to get service info for filtering: {e}")
|
|
103
|
+
|
|
104
|
+
except Exception as e:
|
|
105
|
+
logger.warning(f"Could not fetch service details for filtering: {e}")
|
|
106
|
+
# Don't fail the request, just skip filtering
|
|
107
|
+
pass
|
|
108
|
+
|
|
109
|
+
return service_bond, service_token
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _check_availability(name, address, w3, abi):
|
|
113
|
+
"""Check availability of a single staking contract."""
|
|
114
|
+
try:
|
|
115
|
+
contract = w3.eth.contract(address=address, abi=abi)
|
|
116
|
+
service_ids = contract.functions.getServiceIds().call()
|
|
117
|
+
max_services = contract.functions.maxNumServices().call()
|
|
118
|
+
min_deposit = contract.functions.minStakingDeposit().call()
|
|
119
|
+
staking_token = contract.functions.stakingToken().call()
|
|
120
|
+
used = len(service_ids)
|
|
121
|
+
|
|
122
|
+
return {
|
|
123
|
+
"name": name,
|
|
124
|
+
"address": address,
|
|
125
|
+
"usage": {
|
|
126
|
+
"used": used,
|
|
127
|
+
"max": max_services,
|
|
128
|
+
"available_slots": max_services - used,
|
|
129
|
+
"available": used < max_services,
|
|
130
|
+
},
|
|
131
|
+
"min_staking_deposit": min_deposit,
|
|
132
|
+
"staking_token": staking_token,
|
|
133
|
+
}
|
|
134
|
+
except Exception as e:
|
|
135
|
+
logger.warning(f"Failed to check availability for {name} ({address}): {e}")
|
|
136
|
+
return {
|
|
137
|
+
"name": name,
|
|
138
|
+
"address": address,
|
|
139
|
+
"usage": None, # Could not verify
|
|
140
|
+
"min_staking_deposit": None,
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def _fetch_all_contracts(contracts: dict, w3, abi) -> list:
|
|
145
|
+
"""Fetch availability for all contracts using threads."""
|
|
146
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
147
|
+
|
|
148
|
+
results = []
|
|
149
|
+
with ThreadPoolExecutor(max_workers=10) as executor:
|
|
150
|
+
# Pass w3 and abi to the helper
|
|
151
|
+
futures = [
|
|
152
|
+
executor.submit(_check_availability, name, addr, w3, abi)
|
|
153
|
+
for name, addr in contracts.items()
|
|
154
|
+
]
|
|
155
|
+
for future in futures:
|
|
156
|
+
results.append(future.result())
|
|
157
|
+
return results
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def _filter_contracts(
|
|
161
|
+
results: list, service_bond: Optional[int], service_token: Optional[str]
|
|
162
|
+
) -> list:
|
|
163
|
+
"""Filter contracts based on usage and service compatibility."""
|
|
164
|
+
filtered_results = []
|
|
165
|
+
for r in results:
|
|
166
|
+
# 1. Availability check
|
|
167
|
+
if r["usage"] is not None and not r["usage"]["available"]:
|
|
168
|
+
continue
|
|
169
|
+
|
|
170
|
+
# 2. Compatibility check (if service info is known)
|
|
171
|
+
if service_bond is not None and r.get("min_staking_deposit") is not None:
|
|
172
|
+
# Bond Check
|
|
173
|
+
if service_bond < r["min_staking_deposit"]:
|
|
174
|
+
# Incompatible: Service bond is too low for this contract
|
|
175
|
+
continue
|
|
176
|
+
|
|
177
|
+
# Token Check
|
|
178
|
+
contract_token = str(r.get("staking_token", "")).lower()
|
|
179
|
+
if service_token and contract_token and service_token != contract_token:
|
|
180
|
+
# Incompatible: Tokens do not match
|
|
181
|
+
continue
|
|
182
|
+
|
|
183
|
+
filtered_results.append(r)
|
|
184
|
+
return filtered_results
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
@router.post(
|
|
188
|
+
"/stake/{service_key}",
|
|
189
|
+
summary="Stake Service",
|
|
190
|
+
description="Stake a service into a staking contract.",
|
|
191
|
+
)
|
|
192
|
+
@limiter.limit("5/minute")
|
|
193
|
+
def stake_service(
|
|
194
|
+
request: Request,
|
|
195
|
+
service_key: str,
|
|
196
|
+
staking_contract: str,
|
|
197
|
+
auth: bool = Depends(verify_auth),
|
|
198
|
+
):
|
|
199
|
+
"""Stake a service into a staking contract."""
|
|
200
|
+
try:
|
|
201
|
+
from iwa.plugins.olas.contracts.staking import StakingContract
|
|
202
|
+
from iwa.plugins.olas.service_manager import ServiceManager
|
|
203
|
+
|
|
204
|
+
config = Config()
|
|
205
|
+
olas_config = OlasConfig.model_validate(config.plugins["olas"])
|
|
206
|
+
service = olas_config.services.get(service_key)
|
|
207
|
+
|
|
208
|
+
if not service:
|
|
209
|
+
raise HTTPException(status_code=404, detail="Service not found")
|
|
210
|
+
|
|
211
|
+
manager = ServiceManager(wallet)
|
|
212
|
+
manager.service = service
|
|
213
|
+
|
|
214
|
+
# Ensure staking_contract is a valid address format
|
|
215
|
+
if not staking_contract.startswith("0x"):
|
|
216
|
+
raise HTTPException(
|
|
217
|
+
status_code=400, detail=f"Invalid staking contract address: {staking_contract}"
|
|
218
|
+
)
|
|
219
|
+
|
|
220
|
+
staking = StakingContract(staking_contract, service.chain_name)
|
|
221
|
+
success = manager.stake(staking)
|
|
222
|
+
|
|
223
|
+
if success:
|
|
224
|
+
return {"status": "success"}
|
|
225
|
+
else:
|
|
226
|
+
raise HTTPException(status_code=400, detail="Failed to stake service")
|
|
227
|
+
|
|
228
|
+
except HTTPException:
|
|
229
|
+
raise
|
|
230
|
+
except Exception as e:
|
|
231
|
+
logger.error(f"Error staking service: {e}")
|
|
232
|
+
raise HTTPException(status_code=400, detail=str(e)) from None
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
@router.post(
|
|
236
|
+
"/claim/{service_key}",
|
|
237
|
+
summary="Claim Rewards",
|
|
238
|
+
description="Claim accrued staking rewards for a specific service.",
|
|
239
|
+
)
|
|
240
|
+
def claim_rewards(service_key: str, auth: bool = Depends(verify_auth)):
|
|
241
|
+
"""Claim accrued staking rewards for a service."""
|
|
242
|
+
try:
|
|
243
|
+
from iwa.plugins.olas.service_manager import ServiceManager
|
|
244
|
+
|
|
245
|
+
config = Config()
|
|
246
|
+
olas_config = OlasConfig.model_validate(config.plugins["olas"])
|
|
247
|
+
service = olas_config.services.get(service_key)
|
|
248
|
+
|
|
249
|
+
if not service:
|
|
250
|
+
raise HTTPException(status_code=404, detail="Service not found")
|
|
251
|
+
|
|
252
|
+
manager = ServiceManager(wallet)
|
|
253
|
+
manager.service = service
|
|
254
|
+
|
|
255
|
+
success, amount = manager.claim_rewards()
|
|
256
|
+
if success:
|
|
257
|
+
return {"status": "success", "amount": amount}
|
|
258
|
+
else:
|
|
259
|
+
raise HTTPException(status_code=400, detail="Failed to claim rewards")
|
|
260
|
+
|
|
261
|
+
except HTTPException:
|
|
262
|
+
raise
|
|
263
|
+
except Exception as e:
|
|
264
|
+
logger.error(f"Error claiming rewards: {e}")
|
|
265
|
+
raise HTTPException(status_code=500, detail=str(e)) from None
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
@router.post(
|
|
269
|
+
"/unstake/{service_key}",
|
|
270
|
+
summary="Unstake Service",
|
|
271
|
+
description="Unstake a service from the registry.",
|
|
272
|
+
)
|
|
273
|
+
def unstake_service(service_key: str, auth: bool = Depends(verify_auth)):
|
|
274
|
+
"""Unstake a service."""
|
|
275
|
+
try:
|
|
276
|
+
from iwa.plugins.olas.contracts.staking import StakingContract
|
|
277
|
+
from iwa.plugins.olas.service_manager import ServiceManager
|
|
278
|
+
|
|
279
|
+
config = Config()
|
|
280
|
+
olas_config = OlasConfig.model_validate(config.plugins["olas"])
|
|
281
|
+
service = olas_config.services.get(service_key)
|
|
282
|
+
|
|
283
|
+
if not service or not service.staking_contract_address:
|
|
284
|
+
raise HTTPException(status_code=404, detail="Service not found or not staked")
|
|
285
|
+
|
|
286
|
+
manager = ServiceManager(wallet)
|
|
287
|
+
manager.service = service
|
|
288
|
+
|
|
289
|
+
# We need the staking contract instance
|
|
290
|
+
staking_contract = StakingContract(service.staking_contract_address, service.chain_name)
|
|
291
|
+
|
|
292
|
+
success = manager.unstake(staking_contract)
|
|
293
|
+
if success:
|
|
294
|
+
return {"status": "success"}
|
|
295
|
+
else:
|
|
296
|
+
raise HTTPException(status_code=400, detail="Failed to unstake")
|
|
297
|
+
|
|
298
|
+
except HTTPException:
|
|
299
|
+
raise
|
|
300
|
+
except Exception as e:
|
|
301
|
+
logger.error(f"Error unstaking: {e}")
|
|
302
|
+
raise HTTPException(status_code=500, detail=str(e)) from None
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
@router.post(
|
|
306
|
+
"/checkpoint/{service_key}",
|
|
307
|
+
summary="Checkpoint Service",
|
|
308
|
+
description="Trigger a checkpoint for a staked service to update its liveness.",
|
|
309
|
+
)
|
|
310
|
+
def checkpoint_service(service_key: str, auth: bool = Depends(verify_auth)):
|
|
311
|
+
"""Checkpoint a service."""
|
|
312
|
+
try:
|
|
313
|
+
from iwa.plugins.olas.contracts.staking import StakingContract
|
|
314
|
+
from iwa.plugins.olas.service_manager import ServiceManager
|
|
315
|
+
|
|
316
|
+
config = Config()
|
|
317
|
+
olas_config = OlasConfig.model_validate(config.plugins["olas"])
|
|
318
|
+
service = olas_config.services.get(service_key)
|
|
319
|
+
|
|
320
|
+
if not service or not service.staking_contract_address:
|
|
321
|
+
raise HTTPException(status_code=404, detail="Service not found or not staked")
|
|
322
|
+
|
|
323
|
+
manager = ServiceManager(wallet)
|
|
324
|
+
manager.service = service
|
|
325
|
+
|
|
326
|
+
staking_contract = StakingContract(service.staking_contract_address, service.chain_name)
|
|
327
|
+
|
|
328
|
+
success = manager.call_checkpoint(staking_contract)
|
|
329
|
+
if success:
|
|
330
|
+
return {"status": "success"}
|
|
331
|
+
else:
|
|
332
|
+
# Check if it was just not needed
|
|
333
|
+
if not staking_contract.is_checkpoint_needed():
|
|
334
|
+
return {"status": "skipped", "message": "Checkpoint not needed yet"}
|
|
335
|
+
raise HTTPException(status_code=400, detail="Failed to checkpoint")
|
|
336
|
+
|
|
337
|
+
except HTTPException:
|
|
338
|
+
raise
|
|
339
|
+
except Exception as e:
|
|
340
|
+
logger.error(f"Error checkpointing: {e}")
|
|
341
|
+
raise HTTPException(status_code=500, detail=str(e)) from None
|
iwa/web/routers/state.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""State Router for Web API."""
|
|
2
|
+
|
|
3
|
+
from fastapi import APIRouter, Depends
|
|
4
|
+
|
|
5
|
+
from iwa.core.chain import ChainInterfaces
|
|
6
|
+
from iwa.web.dependencies import verify_auth
|
|
7
|
+
|
|
8
|
+
router = APIRouter(prefix="/api", tags=["state"])
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@router.get(
|
|
12
|
+
"/state",
|
|
13
|
+
summary="Get App State",
|
|
14
|
+
description="Get the current application state, including configured chains and default settings.",
|
|
15
|
+
)
|
|
16
|
+
def get_state(auth: bool = Depends(verify_auth)):
|
|
17
|
+
"""Get the current application state (configured chains, etc)."""
|
|
18
|
+
# Build native currencies map, tokens map, and collect chain names
|
|
19
|
+
chain_names = []
|
|
20
|
+
native_currencies = {}
|
|
21
|
+
tokens = {}
|
|
22
|
+
for name, interface in ChainInterfaces().items():
|
|
23
|
+
chain_names.append(name)
|
|
24
|
+
native_currencies[name] = interface.chain.native_currency
|
|
25
|
+
# Get token symbols from the interface (dict of symbol -> address)
|
|
26
|
+
tokens[name] = list(interface.tokens.keys())
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
"chains": chain_names,
|
|
30
|
+
"tokens": tokens,
|
|
31
|
+
"native_currencies": native_currencies,
|
|
32
|
+
"default_chain": "gnosis",
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _obscure_url(url: str) -> str:
|
|
37
|
+
"""Obscure API keys in URL."""
|
|
38
|
+
if any(param in url for param in ["api_key", "project_id", "key"]):
|
|
39
|
+
return url.split("?")[0] + "?***"
|
|
40
|
+
return url
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@router.get(
|
|
44
|
+
"/rpc-status",
|
|
45
|
+
summary="Get RPC Status",
|
|
46
|
+
description="Check the connectivity and sync status of RPC endpoints for all chains.",
|
|
47
|
+
)
|
|
48
|
+
def get_rpc_status(auth: bool = Depends(verify_auth)):
|
|
49
|
+
"""Get status of RPC endpoints."""
|
|
50
|
+
from iwa.core.chain import ChainInterfaces
|
|
51
|
+
|
|
52
|
+
status = {}
|
|
53
|
+
for name, interface in ChainInterfaces().items():
|
|
54
|
+
try:
|
|
55
|
+
# Simple check using block number
|
|
56
|
+
block = interface.web3.eth.block_number
|
|
57
|
+
rpcs = [_obscure_url(rpc) for rpc in interface.chain.rpcs]
|
|
58
|
+
status[name] = {"status": "online", "block": block, "rpcs": rpcs}
|
|
59
|
+
except Exception as e:
|
|
60
|
+
status[name] = {
|
|
61
|
+
"status": "offline",
|
|
62
|
+
"error": str(e),
|
|
63
|
+
"rpcs": [_obscure_url(rpc) for rpc in interface.chain.rpcs],
|
|
64
|
+
}
|
|
65
|
+
return status
|