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,259 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Integration test: Create service, send legacy mech request, send marketplace mech request.
|
|
3
|
+
|
|
4
|
+
This script tests the full mech request flow on a Tenderly fork:
|
|
5
|
+
1. Creates a new OLAS service (or uses existing one)
|
|
6
|
+
2. Spins up the service
|
|
7
|
+
3. Sends a legacy mech request and verifies the Request event
|
|
8
|
+
4. Sends a marketplace mech request and verifies the MarketplaceRequest event
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import sys
|
|
12
|
+
from pathlib import Path
|
|
13
|
+
|
|
14
|
+
# Add src to path (scripts are in src/iwa/plugins/olas/scripts/)
|
|
15
|
+
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent.parent))
|
|
16
|
+
|
|
17
|
+
from iwa.core.chain import ChainInterfaces
|
|
18
|
+
from iwa.core.wallet import Wallet
|
|
19
|
+
from iwa.plugins.olas.constants import OLAS_CONTRACTS
|
|
20
|
+
from iwa.plugins.olas.service_manager import ServiceManager
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def print_step(step: str, emoji: str = "🔵"):
|
|
24
|
+
"""Print a step with formatting."""
|
|
25
|
+
print(f"\n{emoji} {step}")
|
|
26
|
+
print("-" * 60)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def print_success(msg: str):
|
|
30
|
+
"""Print success message."""
|
|
31
|
+
print(f" ✅ {msg}")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def print_info(msg: str):
|
|
35
|
+
"""Print info message."""
|
|
36
|
+
print(f" ℹ️ {msg}")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def print_error(msg: str):
|
|
40
|
+
"""Print error message."""
|
|
41
|
+
print(f" ❌ {msg}")
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def verify_mech_event(chain, tx_hash, contract, expected_event_name, multisig_address):
|
|
45
|
+
"""Verify mech request event in transaction receipt."""
|
|
46
|
+
print_info("Fetching transaction receipt...")
|
|
47
|
+
receipt = chain.web3.eth.wait_for_transaction_receipt(tx_hash)
|
|
48
|
+
|
|
49
|
+
print_info(f"Extracting events (expecting '{expected_event_name}')...")
|
|
50
|
+
events = contract.extract_events(receipt)
|
|
51
|
+
|
|
52
|
+
request_event = next((e for e in events if e["name"] == expected_event_name), None)
|
|
53
|
+
|
|
54
|
+
if request_event:
|
|
55
|
+
print_success(f"Found '{expected_event_name}' event!")
|
|
56
|
+
print_info(f"Event Args: {request_event['args']}")
|
|
57
|
+
args = request_event["args"]
|
|
58
|
+
|
|
59
|
+
# Verify requester/sender matches multisig
|
|
60
|
+
requester_key = "requester" if expected_event_name == "MarketplaceRequest" else "sender"
|
|
61
|
+
event_requester = args.get(requester_key)
|
|
62
|
+
if event_requester and event_requester.lower() == multisig_address.lower():
|
|
63
|
+
print_success(f"Event {requester_key} matches multisig address")
|
|
64
|
+
else:
|
|
65
|
+
print_error(
|
|
66
|
+
f"Event {requester_key} ({event_requester}) does not match multisig ({multisig_address})"
|
|
67
|
+
)
|
|
68
|
+
return False
|
|
69
|
+
return True
|
|
70
|
+
else:
|
|
71
|
+
print_error(f"'{expected_event_name}' event not found in transaction logs")
|
|
72
|
+
print_info(f"Found events: {[e['name'] for e in events]}")
|
|
73
|
+
return False
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def main(): # noqa: C901
|
|
77
|
+
"""Run full mech flow integration test: create -> spin_up -> legacy request -> marketplace request."""
|
|
78
|
+
print("=" * 60)
|
|
79
|
+
print(" OLAS Mech Request Integration Test")
|
|
80
|
+
print("=" * 60)
|
|
81
|
+
|
|
82
|
+
# Initialize wallet
|
|
83
|
+
print_step("Initializing Wallet", "🔐")
|
|
84
|
+
wallet = Wallet()
|
|
85
|
+
print_success(f"Master account: {wallet.master_account.address}")
|
|
86
|
+
|
|
87
|
+
chain = ChainInterfaces().gnosis
|
|
88
|
+
|
|
89
|
+
# Check master balance
|
|
90
|
+
master_balance = chain.get_native_balance_eth(wallet.master_account.address)
|
|
91
|
+
print_info(f"Master xDAI Balance: {master_balance}")
|
|
92
|
+
|
|
93
|
+
# Step 1: Create/Load Service
|
|
94
|
+
print_step("Step 1: Create Service", "1️⃣")
|
|
95
|
+
manager = ServiceManager(wallet)
|
|
96
|
+
|
|
97
|
+
# Check if service already exists
|
|
98
|
+
if manager.service and manager.service.multisig_address:
|
|
99
|
+
print_info(f"Using existing service ID: {manager.service.service_id}")
|
|
100
|
+
print_info(f"Multisig: {manager.service.multisig_address}")
|
|
101
|
+
else:
|
|
102
|
+
service_id = manager.create(
|
|
103
|
+
chain_name="gnosis",
|
|
104
|
+
service_name="mech_test_service",
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
if not service_id:
|
|
108
|
+
print_error("Failed to create service")
|
|
109
|
+
return False
|
|
110
|
+
|
|
111
|
+
print_success(f"Service created with ID: {service_id}")
|
|
112
|
+
|
|
113
|
+
# Step 2: Spin up Service
|
|
114
|
+
print_step("Step 2: Spin up Service", "2️⃣")
|
|
115
|
+
success = manager.spin_up()
|
|
116
|
+
if not success:
|
|
117
|
+
print_error("Failed to spin up service")
|
|
118
|
+
return False
|
|
119
|
+
|
|
120
|
+
print_success("Service deployed!")
|
|
121
|
+
|
|
122
|
+
print_info(f"Service ID: {manager.service.service_id}")
|
|
123
|
+
print_info(f"Agent: {manager.service.agent_address}")
|
|
124
|
+
print_info(f"Multisig: {manager.service.multisig_address}")
|
|
125
|
+
|
|
126
|
+
multisig_address = manager.service.multisig_address
|
|
127
|
+
agent_address = manager.service.agent_address
|
|
128
|
+
|
|
129
|
+
# Fund multisig and agent if needed
|
|
130
|
+
multisig_balance = chain.get_native_balance_eth(multisig_address)
|
|
131
|
+
agent_balance = chain.get_native_balance_eth(agent_address)
|
|
132
|
+
print_info(f"Multisig xDAI Balance: {multisig_balance}")
|
|
133
|
+
print_info(f"Agent xDAI Balance: {agent_balance}")
|
|
134
|
+
|
|
135
|
+
required_payment = 0.05
|
|
136
|
+
if float(multisig_balance) < required_payment:
|
|
137
|
+
print_step("Funding Multisig", "💰")
|
|
138
|
+
success, tx = chain.send_native_transfer(
|
|
139
|
+
from_address=wallet.master_account.address,
|
|
140
|
+
to_address=multisig_address,
|
|
141
|
+
value_wei=int(required_payment * 2 * 1e18),
|
|
142
|
+
sign_callback=lambda tx: wallet.key_storage.sign_transaction(
|
|
143
|
+
tx, wallet.master_account.address
|
|
144
|
+
),
|
|
145
|
+
)
|
|
146
|
+
if success:
|
|
147
|
+
print_success(f"Funded multisig: {tx}")
|
|
148
|
+
else:
|
|
149
|
+
print_error("Failed to fund multisig")
|
|
150
|
+
return False
|
|
151
|
+
|
|
152
|
+
required_gas = 0.1
|
|
153
|
+
if float(agent_balance) < required_gas:
|
|
154
|
+
print_step("Funding Agent", "⛽")
|
|
155
|
+
success, tx = chain.send_native_transfer(
|
|
156
|
+
from_address=wallet.master_account.address,
|
|
157
|
+
to_address=agent_address,
|
|
158
|
+
value_wei=int(required_gas * 1e18),
|
|
159
|
+
sign_callback=lambda tx: wallet.key_storage.sign_transaction(
|
|
160
|
+
tx, wallet.master_account.address
|
|
161
|
+
),
|
|
162
|
+
)
|
|
163
|
+
if success:
|
|
164
|
+
print_success(f"Funded agent: {tx}")
|
|
165
|
+
else:
|
|
166
|
+
print_error("Failed to fund agent")
|
|
167
|
+
return False
|
|
168
|
+
|
|
169
|
+
# Dummy request data
|
|
170
|
+
dummy_data = b"test_request_data_12345"
|
|
171
|
+
payment_wei = int(0.01 * 1e18)
|
|
172
|
+
|
|
173
|
+
# Get contract addresses
|
|
174
|
+
protocol_contracts = OLAS_CONTRACTS.get("gnosis", {})
|
|
175
|
+
legacy_mech_address = protocol_contracts.get("OLAS_MECH")
|
|
176
|
+
marketplace_address = protocol_contracts.get("OLAS_MECH_MARKETPLACE")
|
|
177
|
+
|
|
178
|
+
# Step 3: Send Legacy Mech Request
|
|
179
|
+
print_step("Step 3: Send Legacy Mech Request", "3️⃣")
|
|
180
|
+
from iwa.plugins.olas.contracts.mech import MechContract
|
|
181
|
+
|
|
182
|
+
tx_hash_legacy = manager.send_mech_request(
|
|
183
|
+
data=dummy_data,
|
|
184
|
+
value=payment_wei,
|
|
185
|
+
use_marketplace=False,
|
|
186
|
+
mech_address=str(legacy_mech_address),
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
if not tx_hash_legacy:
|
|
190
|
+
print_error("Failed to send legacy mech request")
|
|
191
|
+
return False
|
|
192
|
+
|
|
193
|
+
print_success(f"Legacy mech request sent: {tx_hash_legacy}")
|
|
194
|
+
|
|
195
|
+
# Verify legacy event
|
|
196
|
+
print_step("Step 3b: Verify Legacy Mech Event", "✅")
|
|
197
|
+
legacy_mech = MechContract(str(legacy_mech_address), chain_name="gnosis")
|
|
198
|
+
if not verify_mech_event(chain, tx_hash_legacy, legacy_mech, "Request", multisig_address):
|
|
199
|
+
print_error("Legacy mech event verification failed")
|
|
200
|
+
return False
|
|
201
|
+
|
|
202
|
+
print_success("Legacy mech request verified!")
|
|
203
|
+
|
|
204
|
+
# Step 4: Send Marketplace Mech Request
|
|
205
|
+
print_step("Step 4: Send Marketplace Mech Request", "4️⃣")
|
|
206
|
+
from iwa.plugins.olas.contracts.mech_marketplace import MechMarketplaceContract
|
|
207
|
+
|
|
208
|
+
# Known registered mech on Gnosis marketplace
|
|
209
|
+
priority_mech = "0x601024E27f1C67B28209E24272CED8A31fc8151F"
|
|
210
|
+
|
|
211
|
+
# API uses smart defaults:
|
|
212
|
+
# - max_delivery_rate defaults to value
|
|
213
|
+
# - payment_type defaults to NATIVE
|
|
214
|
+
tx_hash_marketplace = manager.send_mech_request(
|
|
215
|
+
data=dummy_data,
|
|
216
|
+
value=payment_wei,
|
|
217
|
+
use_marketplace=True,
|
|
218
|
+
priority_mech=priority_mech,
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
if not tx_hash_marketplace:
|
|
222
|
+
print_error("Failed to send marketplace mech request")
|
|
223
|
+
return False
|
|
224
|
+
|
|
225
|
+
print_success(f"Marketplace mech request sent: {tx_hash_marketplace}")
|
|
226
|
+
|
|
227
|
+
# Verify marketplace event
|
|
228
|
+
print_step("Step 4b: Verify Marketplace Mech Event", "✅")
|
|
229
|
+
marketplace = MechMarketplaceContract(str(marketplace_address), chain_name="gnosis")
|
|
230
|
+
if not verify_mech_event(
|
|
231
|
+
chain, tx_hash_marketplace, marketplace, "MarketplaceRequest", multisig_address
|
|
232
|
+
):
|
|
233
|
+
print_error("Marketplace mech event verification failed")
|
|
234
|
+
return False
|
|
235
|
+
|
|
236
|
+
print_success("Marketplace mech request verified!")
|
|
237
|
+
|
|
238
|
+
# Summary
|
|
239
|
+
print("\n" + "=" * 60)
|
|
240
|
+
print(" 🎉 All Mech Requests Completed Successfully!")
|
|
241
|
+
print("=" * 60)
|
|
242
|
+
print(f" Service ID: {manager.service.service_id}")
|
|
243
|
+
print(f" Legacy Mech Tx: {tx_hash_legacy}")
|
|
244
|
+
print(f" Marketplace Mech Tx: {tx_hash_marketplace}")
|
|
245
|
+
print("=" * 60)
|
|
246
|
+
|
|
247
|
+
return True
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
if __name__ == "__main__":
|
|
251
|
+
try:
|
|
252
|
+
success = main()
|
|
253
|
+
sys.exit(0 if success else 1)
|
|
254
|
+
except Exception as e:
|
|
255
|
+
print(f"\n❌ Error: {e}")
|
|
256
|
+
import traceback
|
|
257
|
+
|
|
258
|
+
traceback.print_exc()
|
|
259
|
+
sys.exit(1)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Simple lifecycle test: create → spin_up → wind_down (no staking)."""
|
|
3
|
+
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
# Add src to path (scripts are in src/iwa/plugins/olas/scripts/)
|
|
8
|
+
sys.path.insert(0, str(Path(__file__).parent.parent.parent.parent.parent))
|
|
9
|
+
|
|
10
|
+
from iwa.core.wallet import Wallet
|
|
11
|
+
from iwa.plugins.olas.service_manager import ServiceManager
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def main():
|
|
15
|
+
"""Run simple lifecycle integration test: create -> spin_up."""
|
|
16
|
+
print("=" * 60)
|
|
17
|
+
print(" Simple Lifecycle Test: create → spin_up → wind_down")
|
|
18
|
+
print("=" * 60)
|
|
19
|
+
|
|
20
|
+
# Initialize wallet
|
|
21
|
+
print("\n🔐 Initializing Wallet...")
|
|
22
|
+
wallet = Wallet()
|
|
23
|
+
print(f" ✅ Master account: {wallet.master_account.address}")
|
|
24
|
+
|
|
25
|
+
# Create service
|
|
26
|
+
print("\n1️⃣ Creating Service...")
|
|
27
|
+
manager = ServiceManager(wallet)
|
|
28
|
+
service_id = manager.create(
|
|
29
|
+
chain_name="gnosis",
|
|
30
|
+
service_name="simple_test_service",
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
if not service_id:
|
|
34
|
+
print(" ❌ Failed to create service")
|
|
35
|
+
return False
|
|
36
|
+
|
|
37
|
+
print(f" ✅ Service created with ID: {service_id}")
|
|
38
|
+
|
|
39
|
+
# Spin up (activate → register → deploy)
|
|
40
|
+
print("\n2️⃣ Spinning up Service...")
|
|
41
|
+
success = manager.spin_up()
|
|
42
|
+
if not success:
|
|
43
|
+
print(" ❌ Failed to spin up service")
|
|
44
|
+
return False
|
|
45
|
+
|
|
46
|
+
print(" ✅ Service deployed!")
|
|
47
|
+
print(f" - Agent: {manager.service.agent_address}")
|
|
48
|
+
print(f" - Multisig: {manager.service.multisig_address}")
|
|
49
|
+
|
|
50
|
+
# Wind down (terminate → unbond)
|
|
51
|
+
# print("\n3️⃣ Winding down Service...")
|
|
52
|
+
# success = manager.wind_down()
|
|
53
|
+
# if not success:
|
|
54
|
+
# print(" ❌ Failed to wind down service")
|
|
55
|
+
# return False
|
|
56
|
+
|
|
57
|
+
# print(" ✅ Service wound down successfully!")
|
|
58
|
+
|
|
59
|
+
print("\n" + "=" * 60)
|
|
60
|
+
print(" 🎉 Full lifecycle completed!")
|
|
61
|
+
print("=" * 60)
|
|
62
|
+
return True
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
if __name__ == "__main__":
|
|
66
|
+
try:
|
|
67
|
+
success = main()
|
|
68
|
+
sys.exit(0 if success else 1)
|
|
69
|
+
except Exception as e:
|
|
70
|
+
print(f"\n❌ Error: {e}")
|
|
71
|
+
import traceback
|
|
72
|
+
|
|
73
|
+
traceback.print_exc()
|
|
74
|
+
sys.exit(1)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Service Manager package for OLAS services."""
|
|
2
|
+
|
|
3
|
+
from iwa.core.chain import ChainInterfaces
|
|
4
|
+
from iwa.core.contracts.erc20 import ERC20Contract
|
|
5
|
+
from iwa.core.models import Config
|
|
6
|
+
from iwa.plugins.olas.constants import OLAS_CONTRACTS
|
|
7
|
+
from iwa.plugins.olas.contracts.mech import MechContract
|
|
8
|
+
from iwa.plugins.olas.contracts.mech_marketplace import MechMarketplaceContract
|
|
9
|
+
from iwa.plugins.olas.contracts.service import (
|
|
10
|
+
ServiceManagerContract,
|
|
11
|
+
ServiceRegistryContract,
|
|
12
|
+
ServiceState,
|
|
13
|
+
)
|
|
14
|
+
from iwa.plugins.olas.contracts.staking import StakingContract, StakingState
|
|
15
|
+
from iwa.plugins.olas.models import OlasConfig, Service, StakingStatus
|
|
16
|
+
from iwa.plugins.olas.service_manager.base import ServiceManagerBase
|
|
17
|
+
from iwa.plugins.olas.service_manager.drain import DrainManagerMixin
|
|
18
|
+
from iwa.plugins.olas.service_manager.lifecycle import LifecycleManagerMixin
|
|
19
|
+
from iwa.plugins.olas.service_manager.mech import MechManagerMixin
|
|
20
|
+
from iwa.plugins.olas.service_manager.staking import StakingManagerMixin
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ServiceManager(
|
|
24
|
+
LifecycleManagerMixin,
|
|
25
|
+
DrainManagerMixin,
|
|
26
|
+
MechManagerMixin,
|
|
27
|
+
StakingManagerMixin,
|
|
28
|
+
ServiceManagerBase,
|
|
29
|
+
):
|
|
30
|
+
"""ServiceManager for OLAS services with multi-service support.
|
|
31
|
+
|
|
32
|
+
Combines functionality from:
|
|
33
|
+
- LifecycleManagerMixin: create, deploy, terminate, etc.
|
|
34
|
+
- StakingManagerMixin: stake, unstake, checkpoint
|
|
35
|
+
- DrainManagerMixin: drain, claim_rewards
|
|
36
|
+
- MechManagerMixin: send_mech_request
|
|
37
|
+
- ServiceManagerBase: init, common config
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
__all__ = [
|
|
44
|
+
"ServiceManager",
|
|
45
|
+
# Re-export commonly used types for backward compatibility
|
|
46
|
+
"Service",
|
|
47
|
+
"StakingStatus",
|
|
48
|
+
"OlasConfig",
|
|
49
|
+
"ServiceState",
|
|
50
|
+
"StakingState",
|
|
51
|
+
"StakingContract",
|
|
52
|
+
"ServiceRegistryContract",
|
|
53
|
+
"ServiceManagerContract",
|
|
54
|
+
"MechContract",
|
|
55
|
+
"MechMarketplaceContract",
|
|
56
|
+
"ERC20Contract",
|
|
57
|
+
"ChainInterfaces",
|
|
58
|
+
"Config",
|
|
59
|
+
"OLAS_CONTRACTS",
|
|
60
|
+
]
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""ServiceManager base class."""
|
|
2
|
+
|
|
3
|
+
from typing import Dict, Optional
|
|
4
|
+
|
|
5
|
+
from loguru import logger
|
|
6
|
+
|
|
7
|
+
from iwa.core.chain import ChainInterfaces
|
|
8
|
+
from iwa.core.models import Config
|
|
9
|
+
from iwa.core.wallet import Wallet
|
|
10
|
+
from iwa.plugins.olas.constants import OLAS_CONTRACTS
|
|
11
|
+
from iwa.plugins.olas.contracts.service import ServiceManagerContract, ServiceRegistryContract
|
|
12
|
+
from iwa.plugins.olas.models import OlasConfig
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ServiceManagerBase:
|
|
16
|
+
"""Base class for ServiceManager."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, wallet: Wallet, service_key: Optional[str] = None):
|
|
19
|
+
"""Initialize ServiceManager.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
wallet: The wallet instance for signing transactions.
|
|
23
|
+
service_key: Optional key (chain_name:service_id) to select a specific service.
|
|
24
|
+
If not provided, service operations require explicit service selection.
|
|
25
|
+
|
|
26
|
+
"""
|
|
27
|
+
self.wallet = wallet
|
|
28
|
+
self.global_config = Config()
|
|
29
|
+
|
|
30
|
+
self.olas_config = self.global_config.plugins.get("olas")
|
|
31
|
+
if isinstance(self.olas_config, dict):
|
|
32
|
+
self.olas_config = OlasConfig(**self.olas_config)
|
|
33
|
+
self.global_config.plugins["olas"] = self.olas_config
|
|
34
|
+
elif self.olas_config is None:
|
|
35
|
+
self.olas_config = OlasConfig()
|
|
36
|
+
self.global_config.plugins["olas"] = self.olas_config
|
|
37
|
+
|
|
38
|
+
# Get service by key if provided
|
|
39
|
+
self.service = None
|
|
40
|
+
if service_key and ":" in service_key:
|
|
41
|
+
chain_name, service_id = service_key.split(":", 1)
|
|
42
|
+
self.service = self.olas_config.get_service(chain_name, int(service_id))
|
|
43
|
+
|
|
44
|
+
# Initialize contracts (default to gnosis)
|
|
45
|
+
service_chain = getattr(self.service, "chain_name", "gnosis")
|
|
46
|
+
chain_name = service_chain if isinstance(service_chain, str) else "gnosis"
|
|
47
|
+
self._init_contracts(chain_name)
|
|
48
|
+
|
|
49
|
+
# Initialize TransferService from wallet
|
|
50
|
+
self.transfer_service = self.wallet.transfer_service
|
|
51
|
+
|
|
52
|
+
def _init_contracts(self, chain_name: str) -> None:
|
|
53
|
+
"""Initialize contracts for the given chain."""
|
|
54
|
+
chain_interface = ChainInterfaces().get(chain_name)
|
|
55
|
+
|
|
56
|
+
# Get protocol contracts from plugin-local constants
|
|
57
|
+
protocol_contracts = OLAS_CONTRACTS.get(chain_name.lower(), {})
|
|
58
|
+
registry_address = protocol_contracts.get("OLAS_SERVICE_REGISTRY")
|
|
59
|
+
manager_address = protocol_contracts.get("OLAS_SERVICE_MANAGER")
|
|
60
|
+
|
|
61
|
+
if not registry_address or not manager_address:
|
|
62
|
+
raise ValueError(f"OLAS contracts not found for chain: {chain_name}")
|
|
63
|
+
|
|
64
|
+
self.registry = ServiceRegistryContract(registry_address, chain_name=chain_name)
|
|
65
|
+
self.manager = ServiceManagerContract(manager_address, chain_name=chain_name)
|
|
66
|
+
logger.info(f"[SM-INIT] ServiceManager initialized. Chain: {chain_name}")
|
|
67
|
+
logger.info(f"[SM-INIT] Registry Address: {self.registry.address}")
|
|
68
|
+
logger.info(f"[SM-INIT] Manager Address: {self.manager.address}")
|
|
69
|
+
self.chain_interface = chain_interface
|
|
70
|
+
self.chain_name = chain_name.lower()
|
|
71
|
+
|
|
72
|
+
def _save_config(self) -> None:
|
|
73
|
+
"""Persist configuration to config.yaml."""
|
|
74
|
+
self.global_config.save_config()
|
|
75
|
+
|
|
76
|
+
def _update_and_save_service_state(self) -> None:
|
|
77
|
+
"""Update the service object in olas_config and persist to config.yaml."""
|
|
78
|
+
if self.service:
|
|
79
|
+
# Update the service object in the configuration dictionary
|
|
80
|
+
# This ensures that changes to self.service (which comes from the Router)
|
|
81
|
+
# are reflected in the ServiceManager's internal configuration state
|
|
82
|
+
# before saving to disk.
|
|
83
|
+
self.olas_config.add_service(self.service)
|
|
84
|
+
self._save_config()
|
|
85
|
+
|
|
86
|
+
def get(self) -> Optional[Dict]:
|
|
87
|
+
"""Get service details by ID."""
|
|
88
|
+
if not self.service:
|
|
89
|
+
logger.error("No active service")
|
|
90
|
+
return None
|
|
91
|
+
return self.registry.get_service(self.service.service_id)
|
|
92
|
+
|
|
93
|
+
def get_service_state(self, service_id: Optional[int] = None) -> str:
|
|
94
|
+
"""Get the state of a service as a string.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
service_id: Optional service ID. If not provided, uses the active service.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
The state name (e.g., 'DEPLOYED') or 'UNKNOWN' if not found.
|
|
101
|
+
|
|
102
|
+
"""
|
|
103
|
+
if service_id is None:
|
|
104
|
+
if not self.service:
|
|
105
|
+
return "UNKNOWN"
|
|
106
|
+
service_id = self.service.service_id
|
|
107
|
+
|
|
108
|
+
try:
|
|
109
|
+
info = self.registry.get_service(service_id)
|
|
110
|
+
return info["state"].name
|
|
111
|
+
except Exception as e:
|
|
112
|
+
logger.debug(f"Failed to get service state for {service_id}: {e}")
|
|
113
|
+
return "UNKNOWN"
|