iwa 0.0.29__py3-none-any.whl → 0.0.31__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.
@@ -425,7 +425,32 @@ class TransactionService:
425
425
  return False, {}
426
426
 
427
427
  except Exception as e:
428
- logger.exception(f"Safe transaction failed: {e}")
428
+ # Try to decode detailed revert reason
429
+ error_text = str(e)
430
+ decoded_msg = None
431
+
432
+ # Extract hex data from common error patterns
433
+ # Pattern 1: ('execution reverted', '0x...')
434
+ import re
435
+ hex_match = re.search(r"0x[0-9a-fA-F]{8,}", error_text)
436
+
437
+ if hex_match:
438
+ try:
439
+ from iwa.core.contracts.decoder import ErrorDecoder
440
+ data = hex_match.group(0)
441
+ decoded = ErrorDecoder().decode(data)
442
+ if decoded:
443
+ # Use the first successful decoding
444
+ name, msg, source = decoded[0]
445
+ decoded_msg = f"{msg} (from {source})"
446
+ except Exception:
447
+ pass
448
+
449
+ if decoded_msg:
450
+ logger.error(f"Safe transaction failed: {decoded_msg}")
451
+ else:
452
+ logger.exception(f"Safe transaction failed: {e}")
453
+
429
454
  return False, {}
430
455
 
431
456
  def _is_gas_too_low_error(self, err_text: str) -> bool:
@@ -888,8 +888,10 @@ class OlasServiceImporter:
888
888
  def _generate_tag(self, key: DiscoveredKey, service_name: Optional[str]) -> str:
889
889
  """Generate a unique tag for an imported key.
890
890
 
891
- Tags follow the pattern: {service_name}_{role}
892
- Example: trader_alpha_agent, trader_alpha_operator
891
+ Tags follow the pattern: {service_name}_{role}[_eoa]
892
+ Examples:
893
+ - trader_alpha_agent
894
+ - trader_alpha_owner_eoa (EOA keys for owner role)
893
895
  """
894
896
  # Use service name as prefix, or 'imported' as fallback
895
897
  prefix = service_name or "imported"
@@ -898,7 +900,11 @@ class OlasServiceImporter:
898
900
  prefix = re.sub(r"[^a-z0-9]+", "_", prefix.lower()).strip("_")
899
901
  role = re.sub(r"[^a-z0-9]+", "_", key.role.lower()).strip("_")
900
902
 
901
- base_tag = f"{prefix}_{role}"
903
+ # Add _eoa suffix for owner/operator keys to distinguish from owner_safe
904
+ if role in ["owner", "operator"]:
905
+ base_tag = f"{prefix}_{role}_eoa"
906
+ else:
907
+ base_tag = f"{prefix}_{role}"
902
908
 
903
909
  # Check if tag already exists
904
910
  existing_tags = {
@@ -877,8 +877,14 @@ class LifecycleManagerMixin:
877
877
  """Terminate the service."""
878
878
  # Check that the service is deployed
879
879
  service_state = self.registry.get_service(self.service.service_id)["state"]
880
- if service_state != ServiceState.DEPLOYED:
881
- logger.error("Service is not deployed, cannot terminate")
880
+ if service_state not in [
881
+ ServiceState.DEPLOYED,
882
+ ServiceState.ACTIVE_REGISTRATION,
883
+ ServiceState.FINISHED_REGISTRATION,
884
+ ]:
885
+ logger.error(
886
+ f"Service is in {service_state.name}, cannot terminate (must be active or deployed)"
887
+ )
882
888
  return False
883
889
 
884
890
  # Check that the service is not staked
@@ -1133,8 +1139,13 @@ class LifecycleManagerMixin:
1133
1139
 
1134
1140
  def _process_wind_down_state(self, current_state: ServiceState) -> bool:
1135
1141
  """Process a single state transition for wind down."""
1136
- if current_state == ServiceState.DEPLOYED:
1137
- logger.info("Terminating service...")
1142
+ # Allow termination from any active state > PRE_REGISTRATION
1143
+ if current_state in [
1144
+ ServiceState.DEPLOYED,
1145
+ ServiceState.ACTIVE_REGISTRATION,
1146
+ ServiceState.FINISHED_REGISTRATION,
1147
+ ]:
1148
+ logger.info(f"Terminating service from state {current_state.name}...")
1138
1149
  if not self.terminate():
1139
1150
  logger.error("Failed to terminate service")
1140
1151
  return False
@@ -1146,7 +1157,7 @@ class LifecycleManagerMixin:
1146
1157
  else:
1147
1158
  # Should not happen if logic is correct map of transitions
1148
1159
  logger.error(
1149
- f"State {current_state.name} is not a valid start for wind_down (expected DEPLOYED or TERMINATED_BONDED)"
1160
+ f"State {current_state.name} is not a valid start for wind_down (expected active state or TERMINATED_BONDED)"
1150
1161
  )
1151
1162
  return False
1152
1163
  return True
@@ -508,14 +508,33 @@ class OlasView(Static):
508
508
  self._show_stake_contracts_modal(filtered_contracts, service_key)
509
509
 
510
510
  def _get_service_bond(self, service_key: str) -> Optional[int]:
511
- """Fetch the security deposit (bond) for a service."""
511
+ """Fetch the security deposit (bond) for a service.
512
+
513
+ Uses ServiceRegistryTokenUtilityContract.get_service_token_deposit() which returns
514
+ the persistent bond value even for terminated services.
515
+ """
516
+ from iwa.core.contracts.cache import ContractCache
517
+ from iwa.plugins.olas.constants import OLAS_CONTRACTS
518
+ from iwa.plugins.olas.contracts.service import ServiceRegistryTokenUtilityContract
512
519
  from iwa.plugins.olas.service_manager import ServiceManager
513
520
 
514
521
  try:
515
522
  manager = ServiceManager(self._wallet, service_key=service_key)
516
523
  if manager.service:
517
- service_info = manager.registry.get_service(manager.service.service_id)
518
- return service_info.get("security_deposit", 0)
524
+ service_id = manager.service.service_id
525
+ chain_name = manager.service.chain_name
526
+
527
+ protocol_contracts = OLAS_CONTRACTS.get(chain_name.lower(), {})
528
+ utility_address = protocol_contracts.get("OLAS_SERVICE_REGISTRY_TOKEN_UTILITY")
529
+
530
+ if utility_address:
531
+ token_utility = ContractCache().get_contract(
532
+ ServiceRegistryTokenUtilityContract,
533
+ address=str(utility_address),
534
+ chain_name=chain_name,
535
+ )
536
+ _, security_deposit = token_utility.get_service_token_deposit(service_id)
537
+ return security_deposit
519
538
  except Exception as e:
520
539
  self.notify(f"Warning: Could not fetch service bond: {e}", severity="warning")
521
540
  return None
@@ -68,30 +68,54 @@ def get_staking_contracts(
68
68
 
69
69
 
70
70
  def _get_service_filter_info(service_key: Optional[str]) -> tuple[Optional[int], Optional[str]]:
71
- """Retrieve service bond and token if service_key is provided."""
71
+ """Retrieve service bond and token if service_key is provided.
72
+
73
+ Uses ServiceRegistryTokenUtilityContract.get_service_token_deposit() which returns
74
+ the persistent bond value, even for terminated services. This is the correct source
75
+ because bonds are stored separately from service state.
76
+ """
72
77
  service_bond = None
73
78
  service_token = None
74
79
 
75
80
  if service_key:
76
81
  try:
82
+ from iwa.core.contracts.cache import ContractCache
83
+ from iwa.plugins.olas.constants import OLAS_CONTRACTS
84
+ from iwa.plugins.olas.contracts.service import ServiceRegistryTokenUtilityContract
77
85
  from iwa.plugins.olas.service_manager import ServiceManager
78
86
 
79
87
  # Initialize wallet dependencies for ServiceManager
80
88
  manager = ServiceManager(wallet, service_key)
81
89
  if manager.service:
82
- # Get service requirements
83
- service_token = (manager.service.token_address or "").lower()
84
90
  service_id_int = manager.service.service_id
91
+ chain_name = manager.service.chain_name
85
92
 
86
- # Get security deposit from registry - this is the actual bond value
93
+ # Get token and deposit from TokenUtility contract (persists after terminate)
87
94
  try:
88
- service_info = manager.registry.get_service(service_id_int)
89
- service_bond = service_info.get("security_deposit", 0)
90
- logger.info(
91
- f"Filtering for service {service_key}: security_deposit={service_bond}, token={service_token}"
92
- )
95
+ protocol_contracts = OLAS_CONTRACTS.get(chain_name.lower(), {})
96
+ utility_address = protocol_contracts.get("OLAS_SERVICE_REGISTRY_TOKEN_UTILITY")
97
+
98
+ if utility_address:
99
+ token_utility = ContractCache().get_contract(
100
+ ServiceRegistryTokenUtilityContract,
101
+ address=str(utility_address),
102
+ chain_name=chain_name,
103
+ )
104
+ token_addr, security_deposit = token_utility.get_service_token_deposit(
105
+ service_id_int
106
+ )
107
+ service_bond = security_deposit
108
+ service_token = token_addr.lower() if token_addr else ""
109
+ logger.info(
110
+ f"Filtering for service {service_key}: security_deposit={service_bond}, token={service_token}"
111
+ )
112
+ else:
113
+ logger.warning(f"Token utility address not found for chain {chain_name}")
114
+ service_token = (manager.service.token_address or "").lower()
93
115
  except Exception as e:
94
- logger.warning(f"Failed to get service info for filtering: {e}")
116
+ logger.warning(f"Failed to get token deposit for filtering: {e}")
117
+ # Fallback to configured token_address
118
+ service_token = (manager.service.token_address or "").lower()
95
119
 
96
120
  except Exception as e:
97
121
  logger.warning(f"Could not fetch service details for filtering: {e}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iwa
3
- Version: 0.0.29
3
+ Version: 0.0.31
4
4
  Summary: A secure, modular, and plugin-based framework for crypto agents and ops
5
5
  Requires-Python: <4.0,>=3.12
6
6
  Description-Content-Type: text/markdown
@@ -40,7 +40,7 @@ iwa/core/services/account.py,sha256=01MoEvl6FJlMnMB4fGwsPtnGa4kgA-d5hJeKu_ACg7Y,
40
40
  iwa/core/services/balance.py,sha256=mPE12CuOFfCaJXaQXWOcQM1O03ZF3ghpy_-oOjNk_GE,4104
41
41
  iwa/core/services/plugin.py,sha256=GNNlbtELyHl7MNVChrypF76GYphxXduxDog4kx1MLi8,3277
42
42
  iwa/core/services/safe.py,sha256=ZmgVwbQhYlH5r3qhlY5uP8nCPtkkvV3sNnYG7_UCWUQ,14831
43
- iwa/core/services/transaction.py,sha256=1O8FAmXZak2oVu15ooUQsc0ByIS2F-O0vWxcAV2ylqk,17525
43
+ iwa/core/services/transaction.py,sha256=FP-Qw7v0LrWTkgDPWeuoB48D7Gl9TrKVQt_trGNolRc,18462
44
44
  iwa/core/services/transfer/__init__.py,sha256=ZJfshFxJRsp8rkOqfVvd1cqEzIJ9tqBJh8pc0l90GLk,5576
45
45
  iwa/core/services/transfer/base.py,sha256=sohz-Ss2i-pGYGl4x9bD93cnYKcSvsXaXyvyRawvgQs,9043
46
46
  iwa/core/services/transfer/erc20.py,sha256=958ctXPWxq_KSQNoaG7RqWbC8SRb9NB3MzhtC2dp_NU,8960
@@ -62,7 +62,7 @@ iwa/plugins/gnosis/tests/test_safe.py,sha256=hQHVHBWQhGnuvzvx4U9fOWEwASJWwql42q6
62
62
  iwa/plugins/olas/__init__.py,sha256=_NhBczzM61fhGYwGhnWfEeL8Jywyy_730GASe2BxzeQ,106
63
63
  iwa/plugins/olas/constants.py,sha256=iTFoO2QW3KbhL5k5sKsJxxyDytl9wVIb_9hAih55KrE,7728
64
64
  iwa/plugins/olas/events.py,sha256=SWD3wYdQ-l6dLUJSkfh_WsLmedH4Vsw_EvYXg7QC3yc,5970
65
- iwa/plugins/olas/importer.py,sha256=KQr8YXTL9SzoEBTAezh_zmJrIJqDQA_yqy8WoBX6wyM,41796
65
+ iwa/plugins/olas/importer.py,sha256=CLWalbnXRnOwuCBVvUFpOkr1cGdkunrElfWNUl6NQMs,42040
66
66
  iwa/plugins/olas/mech_reference.py,sha256=CaSCpQnQL4F7wOG6Ox6Zdoy-uNEQ78YBwVLILQZKL8Q,5782
67
67
  iwa/plugins/olas/models.py,sha256=36N2Wuia7DVfpekJJ2pXPeS0lqhe5ED-Hjdox9YQG2c,5230
68
68
  iwa/plugins/olas/plugin.py,sha256=zOPWyoVkSVh6guJ3TZj5enJFuiIbP3fRM8FkziPB-c0,15606
@@ -88,7 +88,7 @@ iwa/plugins/olas/scripts/test_simple_lifecycle.py,sha256=8T50tOZx3afeECSfCNAb0rA
88
88
  iwa/plugins/olas/service_manager/__init__.py,sha256=GXiThMEY3nPgHUl1i-DLrF4h96z9jPxxI8Jepo2E1PM,1926
89
89
  iwa/plugins/olas/service_manager/base.py,sha256=EBPg0ymqgtAb7ZvVSfTt31QYgv_6gp4UAc6je00NLAg,5009
90
90
  iwa/plugins/olas/service_manager/drain.py,sha256=1Ku7axThwLtKxaNTkwhP4j1yjIXbFXAqNFDrCSmgfto,12569
91
- iwa/plugins/olas/service_manager/lifecycle.py,sha256=Aibd4D-R_UjFEqSTEsVaMFKbrtEOY8fo0Td0eFYjcDI,48732
91
+ iwa/plugins/olas/service_manager/lifecycle.py,sha256=-YE2mqgF8twO_Q4iYhnFBFXN4W0KJ1qJzNXJRXJJdUo,49151
92
92
  iwa/plugins/olas/service_manager/mech.py,sha256=NVzVbEmyOe3wK92VEzCCOSuy3HDkEP1MSoVt7Av8Psk,27949
93
93
  iwa/plugins/olas/service_manager/staking.py,sha256=4WS1m1E1ddX5EbQuvNnNswZjT20nc7I9IKzjrBuUCFw,28701
94
94
  iwa/plugins/olas/tests/conftest.py,sha256=4vM7EI00SrTGyeP0hNzsGSQHEj2-iznVgzlNh2_OGfo,739
@@ -114,7 +114,7 @@ iwa/plugins/olas/tests/test_service_staking.py,sha256=miNGZZoIrZf0Am-pJ8gTyvVAru
114
114
  iwa/plugins/olas/tests/test_staking_integration.py,sha256=QCBQf6P2ZmmsEGt2k8W2r53lG2aVRuoMJE-aFxVDLss,9701
115
115
  iwa/plugins/olas/tests/test_staking_validation.py,sha256=uug64jFcXYJ3Nw_lNa3O4fnhNr5wAWHHIrchSbR2MVE,4020
116
116
  iwa/plugins/olas/tui/__init__.py,sha256=5ZRsbC7J3z1xfkZRiwr4bLEklf78rNVjdswe2p7SlS8,28
117
- iwa/plugins/olas/tui/olas_view.py,sha256=qcPxhurDPJjHWln6R64ZVAJ2h82IXzw48unhRvQVZqQ,36448
117
+ iwa/plugins/olas/tui/olas_view.py,sha256=OlhciDK1Ni4BdzggqTzQeYnP2azB-We02hH6jQhbZuU,37388
118
118
  iwa/tools/__init__.py,sha256=jQyuwDQGRigSe7S9JMb4yK3CXPgZFJNffzt6N2v9PU0,21
119
119
  iwa/tools/check_profile.py,sha256=0LAv9wx4wMM610mX88-6tIoDi2I5LDzh0W9nkprt42s,2177
120
120
  iwa/tools/list_contracts.py,sha256=2w-LYB2RVS-eGil2kLiBIEm3tYYhYzT4lAuGO6VtLJU,4861
@@ -150,7 +150,7 @@ iwa/web/routers/olas/admin.py,sha256=PMRdNelqYgQ1xbqh3floFV5xrVtBRQiwZPd8J9_ffxg
150
150
  iwa/web/routers/olas/funding.py,sha256=f8fADNtbZEBFl-vuVKfas6os38Vot6K5tJBTenZmCD0,4832
151
151
  iwa/web/routers/olas/general.py,sha256=dPsBQppTGoQY1RztliUhseOHOZGeeCR10lhThD9kyXo,803
152
152
  iwa/web/routers/olas/services.py,sha256=IpjvkpJeCwREbdHt47gov-fvTl9bY4EBUmZZZEHi3iI,16310
153
- iwa/web/routers/olas/staking.py,sha256=Ak29SIIMNAgPqIij6sdNAG4z7LUeUGRVRpplZw4hsAU,12666
153
+ iwa/web/routers/olas/staking.py,sha256=jktJ2C1Q9X4aC0tWJByN3sHpEXY0EIvr3rr4N0MtXXc,14081
154
154
  iwa/web/static/app.js,sha256=CWm_TR2nKSDe8z0-nUQp7VaBHIGJg7mAOU-XJDveFsk,113487
155
155
  iwa/web/static/index.html,sha256=q7s7plnMbN1Nkzr5bRxZgvgOFerUChEGIZW7SpAVtPc,28514
156
156
  iwa/web/static/style.css,sha256=aTtE42mmfYV6y7xfo9cUgUhT8x-KyNC1zmPjSdskxIk,24315
@@ -158,7 +158,7 @@ iwa/web/tests/test_web_endpoints.py,sha256=C264MH-CTyDW4GLUrTXBgLJKUk4-89pFAScBd
158
158
  iwa/web/tests/test_web_olas.py,sha256=0CVSsrncOeJ3x0ECV7mVLQV_CXZRrOqGiVjgLIi6hZ8,16308
159
159
  iwa/web/tests/test_web_swap.py,sha256=7A4gBJFL01kIXPtW1E1J17SCsVc_0DmUn-R8kKrnnVA,2974
160
160
  iwa/web/tests/test_web_swap_coverage.py,sha256=zGNrzlhZ_vWDCvWmLcoUwFgqxnrp_ACbo49AtWBS_Kw,5584
161
- iwa-0.0.29.dist-info/licenses/LICENSE,sha256=eIubm_IlBHPYRQlLNZKbBNKhJUUP3JH0A2miZUhAVfI,1078
161
+ iwa-0.0.31.dist-info/licenses/LICENSE,sha256=eIubm_IlBHPYRQlLNZKbBNKhJUUP3JH0A2miZUhAVfI,1078
162
162
  tests/legacy_cow.py,sha256=oOkZvIxL70ReEoD9oHQbOD5GpjIr6AGNHcOCgfPlerU,8389
163
163
  tests/legacy_safe.py,sha256=AssM2g13E74dNGODu_H0Q0y412lgqsrYnEzI97nm_Ts,2972
164
164
  tests/legacy_transaction_retry_logic.py,sha256=D9RqZ7DBu61Xr2djBAodU2p9UE939LL-DnQXswX5iQk,1497
@@ -211,8 +211,8 @@ tests/test_utils.py,sha256=vkP49rYNI8BRzLpWR3WnKdDr8upeZjZcs7Rx0pjbQMo,1292
211
211
  tests/test_workers.py,sha256=MInwdkFY5LdmFB3o1odIaSD7AQZb3263hNafO1De5PE,2793
212
212
  tools/create_and_stake_service.py,sha256=1xwy_bJQI1j9yIQ968Oc9Db_F6mk1659LuuZntTASDE,3742
213
213
  tools/verify_drain.py,sha256=PkMjblyOOAuQge88FwfEzRtCYeEtJxXhPBmtQYCoQ-8,6743
214
- iwa-0.0.29.dist-info/METADATA,sha256=zgMk6FzzMD7GUiUl9TBDKFuRvoaRgOYxnWOWt3s14AU,7295
215
- iwa-0.0.29.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
216
- iwa-0.0.29.dist-info/entry_points.txt,sha256=nwB6kscrfA7M00pYmL2j-sBH6eF6h2ga9IK1BZxdiyQ,241
217
- iwa-0.0.29.dist-info/top_level.txt,sha256=kedS9cRUbm4JE2wYeabIXilhHjN8KCw0IGbqqqsw0Bs,16
218
- iwa-0.0.29.dist-info/RECORD,,
214
+ iwa-0.0.31.dist-info/METADATA,sha256=RiJtuR4SmuW363r5cA7niuwufEjRFzIBV_zg9eeb5DM,7295
215
+ iwa-0.0.31.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
216
+ iwa-0.0.31.dist-info/entry_points.txt,sha256=nwB6kscrfA7M00pYmL2j-sBH6eF6h2ga9IK1BZxdiyQ,241
217
+ iwa-0.0.31.dist-info/top_level.txt,sha256=kedS9cRUbm4JE2wYeabIXilhHjN8KCw0IGbqqqsw0Bs,16
218
+ iwa-0.0.31.dist-info/RECORD,,
File without changes