olas-operate-middleware 0.10.6__py3-none-any.whl → 0.10.8__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: olas-operate-middleware
3
- Version: 0.10.6
3
+ Version: 0.10.8
4
4
  Summary:
5
5
  Author: David Vilela
6
6
  Author-email: dvilelaf@gmail.com
@@ -1,4 +1,4 @@
1
- operate/__init__.py,sha256=ZQhHXOo_1L9Q5Ub_2FrZ-vu3BvORBrKgK8QcJyAsYa4,870
1
+ operate/__init__.py,sha256=PY5BUTF5cmNI-7UwAj2jJlPl6KJ44LoY5e16p7IrE3g,1335
2
2
  operate/account/__init__.py,sha256=suJ_vBMO7hLCvLYe3MVDtLXTNDd6P03og7bvUN7fZsE,804
3
3
  operate/account/user.py,sha256=y7DqqDpqgHjbVmnfL_cN0Me_JWl3Dh6GSVt2-9FdRVw,3044
4
4
  operate/bridge/bridge_manager.py,sha256=sUfhB1pZvBrF-kBfDOfFVoNeesCe7wd4_YahUF3FZDU,17119
@@ -6,8 +6,8 @@ operate/bridge/providers/lifi_provider.py,sha256=FpAlBAA_gOt-oOHKhGaOQhhTZIL-hgY
6
6
  operate/bridge/providers/native_bridge_provider.py,sha256=gG8bSyxUoAVEF6_J9tn1qKRv1PnXuMJdMwUAVJ4GJz8,24647
7
7
  operate/bridge/providers/provider.py,sha256=i54RL7m4wMSADM_D_V_quQump_ipPTmByUc-c-AOLPQ,19687
8
8
  operate/bridge/providers/relay_provider.py,sha256=L7D-PKepsuBadarJmrNneZk0bsrh88u05uD6_2MSss4,17341
9
- operate/cli.py,sha256=EalpBA15TXX5i3tD-2KIQOxkFwEOqOEiGMbjh36cOHw,51168
10
- operate/constants.py,sha256=syGWEVyw07zK50gPS4OSSwyAbt9R6P8IttDimDt9vCY,2746
9
+ operate/cli.py,sha256=UZmmQOmod4ytFsMl65MH5rtc881EEE3wQgYIYr-BGW0,55788
10
+ operate/constants.py,sha256=ilmLiv0H_xKZyfqmfEdqMci-TlLQyNPp9rrxxqtkqFI,2992
11
11
  operate/data/README.md,sha256=jGPyZTvg2LCGdllvmYxmFMkkkiXb6YWatbqIkcX3kv4,879
12
12
  operate/data/__init__.py,sha256=ttC51Yqk9c4ehpIgs1Qbe7aJvzkrbbdZ1ClaCxJYByE,864
13
13
  operate/data/contracts/__init__.py,sha256=_th54_WvL0ibGy-b6St0Ne9DX-fyjsh-tNOKDn-cWrg,809
@@ -39,6 +39,10 @@ operate/data/contracts/optimism_mintable_erc20/__init__.py,sha256=7MXE2uJ_XdnpaB
39
39
  operate/data/contracts/optimism_mintable_erc20/build/OptimismMintableERC20.json,sha256=77YyAhmsobhckux59r0JWgPd9fgmlSnEAJ_Wef-WwKI,9974
40
40
  operate/data/contracts/optimism_mintable_erc20/contract.py,sha256=WDaHgB5iugnpkDF_QRKoIPGhJmk___9y97S7nkbpInY,1566
41
41
  operate/data/contracts/optimism_mintable_erc20/contract.yaml,sha256=7bde2FbMEmFBae0wdvhpPrxWwm2GeVRb0jobFUJ-8dI,717
42
+ operate/data/contracts/recovery_module/__init__.py,sha256=mkoUIiE0NPmybypzUQbYGHOvU5-PSRH5YP2urAS07hE,868
43
+ operate/data/contracts/recovery_module/build/RecoveryModule.json,sha256=QRfZH_dAjXEhCRZMv3VOyEmSn6vy3ggDgXePLYhv6as,60660
44
+ operate/data/contracts/recovery_module/contract.py,sha256=-9eaMdUBXRN-0GCw3hozbc_ZyvrTAi_R0VGnAcDfiI0,1907
45
+ operate/data/contracts/recovery_module/contract.yaml,sha256=Df170LXxKjty2iqaoueLT_YIaxXJLoo8dAp-pHkjhM4,680
42
46
  operate/data/contracts/requester_activity_checker/__init__.py,sha256=BrOe5cib0jItJCsygrAYejy0v16xayRUoJUWubyw1yA,878
43
47
  operate/data/contracts/requester_activity_checker/build/RequesterActivityChecker.json,sha256=KbZbZDEred3LvxIY30igz5w9ubaD0-PvDbFdlg7F-y4,9748
44
48
  operate/data/contracts/requester_activity_checker/contract.py,sha256=preNvyk8kmtUE0LgEIZMLorYevgCmoxBqI-1STrX0_4,1252
@@ -53,20 +57,20 @@ operate/data/contracts/uniswap_v2_erc20/contract.py,sha256=MwBks4QmZ3XouMT_TqWLn
53
57
  operate/data/contracts/uniswap_v2_erc20/contract.yaml,sha256=XUdz-XtKtmZgLfItbO8usP-QPbtUkAxKGn0hL7OftAg,741
54
58
  operate/data/contracts/uniswap_v2_erc20/tests/__init__.py,sha256=3Arw8dsCsJz6hVOl0t9UjFASHXbV9yp3hw6x4HqgXpU,847
55
59
  operate/data/contracts/uniswap_v2_erc20/tests/test_contract.py,sha256=FzZbw9OTcr_yvjOXpk9YcO-K40eyDARyybcfSHDg2Ps,13392
56
- operate/keys.py,sha256=JdnFFbzBOe9t4eS7i72EShOe25OkQdzRm_asCQHmmH0,4944
57
- operate/ledger/__init__.py,sha256=ksyctDd5PU_SToN9e-_N9fAap9ZNCHw48j5hHep-erA,3353
58
- operate/ledger/profiles.py,sha256=fQ9vqnXCIBZa3VHZKIvrJ-Jhn_SckFUcPC94JM5brQ8,12343
59
- operate/migration.py,sha256=YIJ9P8CdW2IsMm4gW9GdAJ7EKMnUfUMUB5TItiQ0dH8,15323
60
+ operate/keys.py,sha256=_TAS7x9TyU9a8AMnw2FacAHUeqLB0kBSJECc3Msxv54,5207
61
+ operate/ledger/__init__.py,sha256=TCZxTdUTCHjhYErVAjsZgSbv7YUdAzYKOe-vJDvCkGk,3963
62
+ operate/ledger/profiles.py,sha256=7aLLf0pJTIHerpXbwpjeKE9inuwiw0FG0GgTWU_0vGE,11977
63
+ operate/migration.py,sha256=mh921s8FG48XMfDu63ojrdhU7j_RZsdl5E2BvxsbkWE,16937
60
64
  operate/operate_http/__init__.py,sha256=dxCIVSUos23M4R-PFZZG6k5QrOlEiK0SxhCYSFNxh7U,4711
61
65
  operate/operate_http/exceptions.py,sha256=4UFzrn-GyDD71RhkaOyFPBynL6TrrtP3eywaaU3o4fc,1339
62
- operate/operate_types.py,sha256=DmQBVu-WPYGrrRwKU30QmF99JheFQo666GxP5ObHgAw,8033
66
+ operate/operate_types.py,sha256=oVOzd6K_CTbmAe1pbSFr4LCajNJ_n0y6J5Wwdf_6ev8,8102
63
67
  operate/pearl.py,sha256=yrTpSXLu_ML3qT-uNxq3kScOyo31JyxBujiSMfMUbcg,1690
64
68
  operate/quickstart/analyse_logs.py,sha256=K11AWWevkddUIUzTe75J3fYVS6aLfi6kT_dAI9OjrX8,4195
65
69
  operate/quickstart/claim_staking_rewards.py,sha256=AqfLMRef2YijQtWPaTuGwX2sOItNEkoyoi6Q9SICp_I,4026
66
70
  operate/quickstart/reset_configs.py,sha256=ipPpbYyB9gQ4KOKS-xBrRi8fT5LvwctSkQi-8XiUMig,3341
67
- operate/quickstart/reset_password.py,sha256=p_gNmhWD4hb-QXUAiQRalPtVTVyB5TEPhs7GnScYzqs,2535
71
+ operate/quickstart/reset_password.py,sha256=78riP7zyFM2JMa1H8Dh6pg-PtLJLQS7PFzx9SECPACQ,2571
68
72
  operate/quickstart/reset_staking.py,sha256=SB5LZq9EctG4SYn2M6oPZ7R7ARHSFLRGzAqfKkpRcy0,5111
69
- operate/quickstart/run_service.py,sha256=6opWEeedraCsPb6jEd44oV34jVQZJSNH8WOr-Kmo2LM,27116
73
+ operate/quickstart/run_service.py,sha256=ruEShJadhIa0E2IsPWu41ObkzZsaWF9XkVH8Vb7uRUU,27146
70
74
  operate/quickstart/stop_service.py,sha256=CNcCucI4sqfZG0wTxxh-k14xGcbOh50UGyXqTZVQJP0,2048
71
75
  operate/quickstart/terminate_on_chain_service.py,sha256=5ENU8_mkj06i80lKUX-v1QbLU0YzKeOZDUL1e_jzySE,2914
72
76
  operate/quickstart/utils.py,sha256=rmd9e7whQIsYpRKqWBEQxMA_SHrivBg6DppFY5ECtQQ,9135
@@ -75,9 +79,9 @@ operate/services/__init__.py,sha256=isrThS-Ccu5Sc15JZgkN4uTAVaSg-NwUUSDeTyJEqLk,
75
79
  operate/services/agent_runner.py,sha256=6tJePUJmlRxlIugT2fDaCJHSrQlDnl1t9pbg3-7EmCQ,7560
76
80
  operate/services/deployment_runner.py,sha256=Su73o7cdH6fkQfj468K77J04a_TWiokJwbMyVQ25xko,27067
77
81
  operate/services/health_checker.py,sha256=2KSEDxG3YmGolUDU--648ny0UJpTAAKvxkcr_VZQv-I,9654
78
- operate/services/manage.py,sha256=Af_hLMxj1SJhR52R-QrDmf1pXvjjGjGWBchrsm9E_3c,108530
79
- operate/services/protocol.py,sha256=n3lmNCYvAES3oh1OKk4GZeKrvOeNevag_dkP6DFwa1U,60154
80
- operate/services/service.py,sha256=ltKox10YC1VINGqdlVqy2nPXkR7XCHoysSGKMngPTSU,39669
82
+ operate/services/manage.py,sha256=AoJsa4vB3C9cyMLgKBQxsUBv9okyDqxtLjCpsG3UzdQ,115769
83
+ operate/services/protocol.py,sha256=FoJmHz5nukI01-VYtReMsIl3JT5RgZZSLX-mf9EXQ90,62903
84
+ operate/services/service.py,sha256=ZbkTryuK3kBkdvhxd9QDyXRxRFsGGG9WZqbLGOUPloc,39301
81
85
  operate/services/utils/__init__.py,sha256=TvioaZ1mfTRUSCtrQoLNAp4WMVXyqEJqFJM4PxSQCRU,24
82
86
  operate/services/utils/mech.py,sha256=W2x4dqodivNKXjWU-Brp40QhoUHsIMyNAO7-caMoR0Q,3821
83
87
  operate/services/utils/tendermint.py,sha256=3h9nDb2Z89T0RwUr_AaVjqtymQmsu3u6DAVCfL_k1U0,25591
@@ -85,9 +89,10 @@ operate/utils/__init__.py,sha256=DZNUgg0V9yfNfDrUynp10PErSieJkoxU0AKvsEFIhAw,467
85
89
  operate/utils/gnosis.py,sha256=CS07ZqvrO7uelkFe09VMyPBcLzKONUI1ZqFvi41BDhc,17924
86
90
  operate/utils/ssl.py,sha256=O5DrDoZD4T4qQuHP8GLwWUVxQ-1qXeefGp6uDJiF2lM,4308
87
91
  operate/wallet/__init__.py,sha256=NGiozD3XhvkBi7_FaOWQ8x1thZPK4uGpokJaeDY_o2w,813
88
- operate/wallet/master.py,sha256=MGXynIV0LQU3n28AceK7RlaxAtfsVowsXzisesG4iJM,30760
89
- olas_operate_middleware-0.10.6.dist-info/LICENSE,sha256=mdBDB-mWKV5Cz4ejBzBiKqan6Z8zVLAh9xwM64O2FW4,11339
90
- olas_operate_middleware-0.10.6.dist-info/METADATA,sha256=ts-khdb5moeMptLjpAHazBQ-VsYAU8_8fAUTeJHXnJE,2035
91
- olas_operate_middleware-0.10.6.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
92
- olas_operate_middleware-0.10.6.dist-info/entry_points.txt,sha256=dM1g2I7ODApKQFcgl5J4NGA7pfBTo6qsUTXM-j2OLlw,44
93
- olas_operate_middleware-0.10.6.dist-info/RECORD,,
92
+ operate/wallet/master.py,sha256=jLFLcRgO6ADok2DigpK59PFJ4mnc_0H9a4RRioYzlvw,30811
93
+ operate/wallet/wallet_recovery_manager.py,sha256=sXEZyvFMePxQKf9NJg4HT90mPg4-7ZcTbvggMnKKzhA,7795
94
+ olas_operate_middleware-0.10.8.dist-info/LICENSE,sha256=mdBDB-mWKV5Cz4ejBzBiKqan6Z8zVLAh9xwM64O2FW4,11339
95
+ olas_operate_middleware-0.10.8.dist-info/METADATA,sha256=1kSqMoV3eXtdFxGckk49yl2kpcubr6PAub2RJ2nZipQ,2035
96
+ olas_operate_middleware-0.10.8.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
97
+ olas_operate_middleware-0.10.8.dist-info/entry_points.txt,sha256=dM1g2I7ODApKQFcgl5J4NGA7pfBTo6qsUTXM-j2OLlw,44
98
+ olas_operate_middleware-0.10.8.dist-info/RECORD,,
operate/__init__.py CHANGED
@@ -20,6 +20,18 @@
20
20
  """Operate app."""
21
21
 
22
22
  import logging
23
+ from importlib.metadata import PackageNotFoundError, version
23
24
 
24
25
 
26
+ try:
27
+ # Prefer the distribution name if installed; fall back to the module name
28
+ __version__ = version("olas-operate-middleware")
29
+ except PackageNotFoundError:
30
+ try:
31
+ __version__ = version("operate")
32
+ except PackageNotFoundError:
33
+ logger = logging.getLogger("operate")
34
+ logger.warning("Could not determine version, using 0.0.0+local")
35
+ __version__ = "0.0.0+local"
36
+
25
37
  logging.getLogger("aea").setLevel(logging.ERROR)
operate/cli.py CHANGED
@@ -20,7 +20,6 @@
20
20
  """Operate app CLI module."""
21
21
  import asyncio
22
22
  import atexit
23
- import logging
24
23
  import multiprocessing
25
24
  import os
26
25
  import signal
@@ -46,7 +45,7 @@ from typing_extensions import Annotated
46
45
  from uvicorn.config import Config
47
46
  from uvicorn.server import Server
48
47
 
49
- from operate import services
48
+ from operate import __version__, services
50
49
  from operate.account.user import UserAccount
51
50
  from operate.bridge.bridge_manager import BridgeManager
52
51
  from operate.constants import (
@@ -54,6 +53,9 @@ from operate.constants import (
54
53
  MIN_PASSWORD_LENGTH,
55
54
  OPERATE_HOME,
56
55
  SERVICES_DIR,
56
+ USER_JSON,
57
+ WALLETS_DIR,
58
+ WALLET_RECOVERY_DIR,
57
59
  ZERO_ADDRESS,
58
60
  )
59
61
  from operate.ledger.profiles import (
@@ -76,18 +78,28 @@ from operate.services.health_checker import HealthChecker
76
78
  from operate.utils import subtract_dicts
77
79
  from operate.utils.gnosis import get_assets_balances
78
80
  from operate.wallet.master import MasterWalletManager
81
+ from operate.wallet.wallet_recovery_manager import (
82
+ WalletRecoveryError,
83
+ WalletRecoveryManager,
84
+ )
79
85
 
80
86
 
81
87
  DEFAULT_MAX_RETRIES = 3
82
88
  USER_NOT_LOGGED_IN_ERROR = JSONResponse(
83
89
  content={"error": "User not logged in."}, status_code=HTTPStatus.UNAUTHORIZED
84
90
  )
91
+ USER_LOGGED_IN_ERROR = JSONResponse(
92
+ content={"error": "User must be logged out to perform this operation."},
93
+ status_code=HTTPStatus.FORBIDDEN,
94
+ )
85
95
  ACCOUNT_NOT_FOUND_ERROR = JSONResponse(
86
96
  content={"error": "User account not found."},
87
97
  status_code=HTTPStatus.NOT_FOUND,
88
98
  )
89
99
  TRY_TO_SHUTDOWN_PREVIOUS_INSTANCE = True
90
100
 
101
+ logger = setup_logger(name="operate")
102
+
91
103
 
92
104
  def service_not_found_error(service_config_id: str) -> JSONResponse:
93
105
  """Service not found error response"""
@@ -103,7 +115,6 @@ class OperateApp:
103
115
  def __init__(
104
116
  self,
105
117
  home: t.Optional[Path] = None,
106
- logger: t.Optional[logging.Logger] = None,
107
118
  ) -> None:
108
119
  """Initialize object."""
109
120
  super().__init__()
@@ -112,14 +123,13 @@ class OperateApp:
112
123
  self._keys = self._path / KEYS_DIR
113
124
  self.setup()
114
125
 
115
- self.logger = logger or setup_logger(name="operate")
116
126
  services.manage.KeysManager(
117
127
  path=self._keys,
118
- logger=self.logger,
128
+ logger=logger,
119
129
  )
120
130
  self.password: t.Optional[str] = os.environ.get("OPERATE_USER_PASSWORD")
121
131
 
122
- mm = MigrationManager(self._path, self.logger)
132
+ mm = MigrationManager(self._path, logger)
123
133
  mm.migrate_user_account()
124
134
  mm.migrate_services(self.service_manager())
125
135
  mm.migrate_wallets(self.wallet_manager)
@@ -130,14 +140,14 @@ class OperateApp:
130
140
  self.password = password
131
141
  return UserAccount.new(
132
142
  password=password,
133
- path=self._path / "user.json",
143
+ path=self._path / USER_JSON,
134
144
  )
135
145
 
136
146
  def update_password(self, old_password: str, new_password: str) -> None:
137
147
  """Updates current password"""
138
148
 
139
149
  if not new_password:
140
- raise ValueError("'password' is required.")
150
+ raise ValueError("'new_password' is required.")
141
151
 
142
152
  if not (
143
153
  self.user_account.is_valid(old_password)
@@ -154,7 +164,7 @@ class OperateApp:
154
164
  """Updates current password using the mnemonic"""
155
165
 
156
166
  if not new_password:
157
- raise ValueError("'password' is required.")
167
+ raise ValueError("'new_password' is required.")
158
168
 
159
169
  mnemonic = mnemonic.strip().lower()
160
170
  if not self.wallet_manager.is_mnemonic_valid(mnemonic):
@@ -171,37 +181,45 @@ class OperateApp:
171
181
  return services.manage.ServiceManager(
172
182
  path=self._services,
173
183
  wallet_manager=self.wallet_manager,
174
- logger=self.logger,
184
+ logger=logger,
175
185
  skip_dependency_check=skip_dependency_check,
176
186
  )
177
187
 
178
188
  @property
179
189
  def user_account(self) -> t.Optional[UserAccount]:
180
190
  """Load user account."""
181
- return (
182
- UserAccount.load(self._path / "user.json")
183
- if (self._path / "user.json").exists()
184
- else None
185
- )
191
+ if (self._path / USER_JSON).exists():
192
+ return UserAccount.load(self._path / USER_JSON)
193
+ return None
186
194
 
187
195
  @property
188
196
  def wallet_manager(self) -> MasterWalletManager:
189
- """Load master wallet."""
197
+ """Load wallet manager."""
190
198
  manager = MasterWalletManager(
191
- path=self._path / "wallets",
199
+ path=self._path / WALLETS_DIR,
192
200
  password=self.password,
193
- logger=self.logger,
201
+ logger=logger,
194
202
  )
195
203
  manager.setup()
196
204
  return manager
197
205
 
206
+ @property
207
+ def wallet_recoverey_manager(self) -> WalletRecoveryManager:
208
+ """Load wallet recovery manager."""
209
+ manager = WalletRecoveryManager(
210
+ path=self._path / WALLET_RECOVERY_DIR,
211
+ wallet_manager=self.wallet_manager,
212
+ logger=self.logger,
213
+ )
214
+ return manager
215
+
198
216
  @property
199
217
  def bridge_manager(self) -> BridgeManager:
200
- """Load master wallet."""
218
+ """Load bridge manager."""
201
219
  manager = BridgeManager(
202
220
  path=self._path / "bridge",
203
221
  wallet_manager=self.wallet_manager,
204
- logger=self.logger,
222
+ logger=logger,
205
223
  )
206
224
  return manager
207
225
 
@@ -232,10 +250,9 @@ def create_app( # pylint: disable=too-many-locals, unused-argument, too-many-st
232
250
  )
233
251
  )
234
252
 
235
- logger = setup_logger(name="operate")
236
253
  if HEALTH_CHECKER_OFF:
237
254
  logger.warning("Healthchecker is off!!!")
238
- operate = OperateApp(home=home, logger=logger)
255
+ operate = OperateApp(home=home)
239
256
 
240
257
  funding_jobs: t.Dict[str, asyncio.Task] = {}
241
258
  health_checker = HealthChecker(
@@ -877,6 +894,21 @@ def create_app( # pylint: disable=too-many-locals, unused-argument, too-many-st
877
894
  deployment_json["healthcheck"] = service.get_latest_healthcheck()
878
895
  return JSONResponse(content=deployment_json)
879
896
 
897
+ @app.get("/api/v2/service/{service_config_id}/agent_performance")
898
+ @with_retries
899
+ async def _get_agent_performance(request: Request) -> JSONResponse:
900
+ """Get the service refill requirements."""
901
+ service_config_id = request.path_params["service_config_id"]
902
+
903
+ if not operate.service_manager().exists(service_config_id=service_config_id):
904
+ return service_not_found_error(service_config_id=service_config_id)
905
+
906
+ return JSONResponse(
907
+ content=operate.service_manager()
908
+ .load(service_config_id=service_config_id)
909
+ .get_agent_performance()
910
+ )
911
+
880
912
  @app.get("/api/v2/service/{service_config_id}/refill_requirements")
881
913
  @with_retries
882
914
  async def _get_refill_requirements(request: Request) -> JSONResponse:
@@ -1166,12 +1198,103 @@ def create_app( # pylint: disable=too-many-locals, unused-argument, too-many-st
1166
1198
  status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
1167
1199
  )
1168
1200
 
1201
+ @app.post("/api/wallet/recovery/initiate")
1202
+ @with_retries
1203
+ async def _wallet_recovery_initiate(request: Request) -> JSONResponse:
1204
+ """Initiate wallet recovery."""
1205
+ if operate.user_account is None:
1206
+ return ACCOUNT_NOT_FOUND_ERROR
1207
+
1208
+ if operate.password:
1209
+ return USER_LOGGED_IN_ERROR
1210
+
1211
+ data = await request.json()
1212
+ new_password = data.get("new_password")
1213
+
1214
+ if not new_password or len(new_password) < MIN_PASSWORD_LENGTH:
1215
+ return JSONResponse(
1216
+ content={
1217
+ "error": f"New password must be at least {MIN_PASSWORD_LENGTH} characters long."
1218
+ },
1219
+ status_code=HTTPStatus.BAD_REQUEST,
1220
+ )
1221
+
1222
+ try:
1223
+ output = operate.wallet_recoverey_manager.initiate_recovery(
1224
+ new_password=new_password
1225
+ )
1226
+ return JSONResponse(
1227
+ content=output,
1228
+ status_code=HTTPStatus.OK,
1229
+ )
1230
+ except (ValueError, WalletRecoveryError) as e:
1231
+ logger.error(f"_recovery_initiate error: {e}")
1232
+ return JSONResponse(
1233
+ content={"error": f"Failed to initiate recovery: {e}"},
1234
+ status_code=HTTPStatus.BAD_REQUEST,
1235
+ )
1236
+ except Exception as e: # pylint: disable=broad-except
1237
+ logger.error(f"_recovery_initiate error: {e}\n{traceback.format_exc()}")
1238
+ return JSONResponse(
1239
+ content={
1240
+ "error": "Failed to initiate recovery. Please check the logs."
1241
+ },
1242
+ status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
1243
+ )
1244
+
1245
+ @app.post("/api/wallet/recovery/complete")
1246
+ @with_retries
1247
+ async def _wallet_recovery_complete(request: Request) -> JSONResponse:
1248
+ """Complete wallet recovery."""
1249
+ if operate.user_account is None:
1250
+ return ACCOUNT_NOT_FOUND_ERROR
1251
+
1252
+ if operate.password:
1253
+ return USER_LOGGED_IN_ERROR
1254
+
1255
+ data = await request.json()
1256
+ bundle_id = data.get("id")
1257
+ password = data.get("password")
1258
+ raise_if_inconsistent_owners = data.get("require_consistent_owners", True)
1259
+
1260
+ try:
1261
+ operate.wallet_recoverey_manager.complete_recovery(
1262
+ bundle_id=bundle_id,
1263
+ password=password,
1264
+ raise_if_inconsistent_owners=raise_if_inconsistent_owners,
1265
+ )
1266
+ return JSONResponse(
1267
+ content=operate.wallet_manager.json,
1268
+ status_code=HTTPStatus.OK,
1269
+ )
1270
+ except KeyError as e:
1271
+ logger.error(f"_recovery_complete error: {e}")
1272
+ return JSONResponse(
1273
+ content={"error": f"Failed to complete recovery: {e}"},
1274
+ status_code=HTTPStatus.NOT_FOUND,
1275
+ )
1276
+ except (ValueError, WalletRecoveryError) as e:
1277
+ logger.error(f"_recovery_complete error: {e}")
1278
+ return JSONResponse(
1279
+ content={"error": f"Failed to complete recovery: {e}"},
1280
+ status_code=HTTPStatus.BAD_REQUEST,
1281
+ )
1282
+ except Exception as e: # pylint: disable=broad-except
1283
+ logger.error(f"_recovery_complete error: {e}\n{traceback.format_exc()}")
1284
+ return JSONResponse(
1285
+ content={
1286
+ "error": "Failed to complete recovery. Please check the logs."
1287
+ },
1288
+ status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
1289
+ )
1290
+
1169
1291
  return app
1170
1292
 
1171
1293
 
1172
1294
  @group(name="operate")
1173
1295
  def _operate() -> None:
1174
1296
  """Operate - deploy autonomous services."""
1297
+ logger.info(f"Operate version: {__version__}")
1175
1298
 
1176
1299
 
1177
1300
  @_operate.command(name="daemon")
@@ -1188,7 +1311,6 @@ def _daemon(
1188
1311
  ) -> None:
1189
1312
  """Launch operate daemon."""
1190
1313
  app = create_app(home=home)
1191
- logger = setup_logger(name="daemon")
1192
1314
 
1193
1315
  config_kwargs = {
1194
1316
  "app": app,
operate/constants.py CHANGED
@@ -26,9 +26,18 @@ OPERATE = ".operate"
26
26
  OPERATE_HOME = Path.cwd() / OPERATE
27
27
  SERVICES_DIR = "services"
28
28
  KEYS_DIR = "keys"
29
+ WALLETS_DIR = "wallets"
30
+ WALLET_RECOVERY_DIR = "wallet_recovery"
29
31
  DEPLOYMENT_DIR = "deployment"
30
32
  DEPLOYMENT_JSON = "deployment.json"
31
33
  CONFIG_JSON = "config.json"
34
+ USER_JSON = "user.json"
35
+
36
+ AGENT_PERSISTENT_STORAGE_DIR = "persistent_data"
37
+ AGENT_PERSISTENT_STORAGE_ENV_VAR = "STORE_PATH"
38
+ AGENT_LOG_DIR = "benchmarks"
39
+ AGENT_LOG_ENV_VAR = "LOG_DIR"
40
+
32
41
  ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"
33
42
 
34
43
  ON_CHAIN_INTERACT_TIMEOUT = 120.0
@@ -0,0 +1,20 @@
1
+ # -*- coding: utf-8 -*-
2
+ # ------------------------------------------------------------------------------
3
+ #
4
+ # Copyright 2025 Valory AG
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ #
18
+ # ------------------------------------------------------------------------------
19
+
20
+ """This module contains the support resources for the `RecoveryModule` contract."""