olas-operate-middleware 0.10.7__tar.gz → 0.10.8__tar.gz

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.
Files changed (97) hide show
  1. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/PKG-INFO +1 -1
  2. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/__init__.py +12 -0
  3. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/cli.py +145 -23
  4. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/constants.py +9 -0
  5. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/keys.py +9 -3
  6. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/ledger/__init__.py +38 -24
  7. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/migration.py +73 -14
  8. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/quickstart/reset_password.py +3 -2
  9. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/quickstart/run_service.py +7 -2
  10. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/services/manage.py +30 -39
  11. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/services/protocol.py +7 -9
  12. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/services/service.py +40 -70
  13. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/wallet/master.py +7 -4
  14. olas_operate_middleware-0.10.8/operate/wallet/wallet_recovery_manager.py +210 -0
  15. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/pyproject.toml +1 -1
  16. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/LICENSE +0 -0
  17. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/README.md +0 -0
  18. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/account/__init__.py +0 -0
  19. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/account/user.py +0 -0
  20. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/bridge/bridge_manager.py +0 -0
  21. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/bridge/providers/lifi_provider.py +0 -0
  22. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/bridge/providers/native_bridge_provider.py +0 -0
  23. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/bridge/providers/provider.py +0 -0
  24. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/bridge/providers/relay_provider.py +0 -0
  25. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/data/README.md +0 -0
  26. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/data/__init__.py +0 -0
  27. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/data/contracts/__init__.py +0 -0
  28. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/data/contracts/dual_staking_token/__init__.py +0 -0
  29. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/data/contracts/dual_staking_token/build/DualStakingToken.json +0 -0
  30. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/data/contracts/dual_staking_token/contract.py +0 -0
  31. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/data/contracts/dual_staking_token/contract.yaml +0 -0
  32. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/data/contracts/foreign_omnibridge/__init__.py +0 -0
  33. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/data/contracts/foreign_omnibridge/build/ForeignOmnibridge.json +0 -0
  34. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/data/contracts/foreign_omnibridge/contract.py +0 -0
  35. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/data/contracts/foreign_omnibridge/contract.yaml +0 -0
  36. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/data/contracts/home_omnibridge/__init__.py +0 -0
  37. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/data/contracts/home_omnibridge/build/HomeOmnibridge.json +0 -0
  38. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/data/contracts/home_omnibridge/contract.py +0 -0
  39. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/data/contracts/home_omnibridge/contract.yaml +0 -0
  40. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/data/contracts/l1_standard_bridge/__init__.py +0 -0
  41. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/data/contracts/l1_standard_bridge/build/L1StandardBridge.json +0 -0
  42. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/data/contracts/l1_standard_bridge/contract.py +0 -0
  43. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/data/contracts/l1_standard_bridge/contract.yaml +0 -0
  44. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/data/contracts/l2_standard_bridge/__init__.py +0 -0
  45. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/data/contracts/l2_standard_bridge/build/L2StandardBridge.json +0 -0
  46. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/data/contracts/l2_standard_bridge/contract.py +0 -0
  47. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/data/contracts/l2_standard_bridge/contract.yaml +0 -0
  48. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/data/contracts/mech_activity/__init__.py +0 -0
  49. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/data/contracts/mech_activity/build/MechActivity.json +0 -0
  50. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/data/contracts/mech_activity/contract.py +0 -0
  51. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/data/contracts/mech_activity/contract.yaml +0 -0
  52. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/data/contracts/optimism_mintable_erc20/__init__.py +0 -0
  53. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/data/contracts/optimism_mintable_erc20/build/OptimismMintableERC20.json +0 -0
  54. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/data/contracts/optimism_mintable_erc20/contract.py +0 -0
  55. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/data/contracts/optimism_mintable_erc20/contract.yaml +0 -0
  56. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/data/contracts/recovery_module/__init__.py +0 -0
  57. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/data/contracts/recovery_module/build/RecoveryModule.json +0 -0
  58. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/data/contracts/recovery_module/contract.py +0 -0
  59. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/data/contracts/recovery_module/contract.yaml +0 -0
  60. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/data/contracts/requester_activity_checker/__init__.py +0 -0
  61. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/data/contracts/requester_activity_checker/build/RequesterActivityChecker.json +0 -0
  62. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/data/contracts/requester_activity_checker/contract.py +0 -0
  63. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/data/contracts/requester_activity_checker/contract.yaml +0 -0
  64. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/data/contracts/staking_token/__init__.py +0 -0
  65. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/data/contracts/staking_token/build/StakingToken.json +0 -0
  66. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/data/contracts/staking_token/contract.py +0 -0
  67. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/data/contracts/staking_token/contract.yaml +0 -0
  68. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/data/contracts/uniswap_v2_erc20/__init__.py +0 -0
  69. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/data/contracts/uniswap_v2_erc20/build/IUniswapV2ERC20.json +0 -0
  70. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/data/contracts/uniswap_v2_erc20/contract.py +0 -0
  71. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/data/contracts/uniswap_v2_erc20/contract.yaml +0 -0
  72. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/data/contracts/uniswap_v2_erc20/tests/__init__.py +0 -0
  73. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/data/contracts/uniswap_v2_erc20/tests/test_contract.py +0 -0
  74. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/ledger/profiles.py +0 -0
  75. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/operate_http/__init__.py +0 -0
  76. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/operate_http/exceptions.py +0 -0
  77. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/operate_types.py +0 -0
  78. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/pearl.py +0 -0
  79. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/quickstart/analyse_logs.py +0 -0
  80. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/quickstart/claim_staking_rewards.py +0 -0
  81. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/quickstart/reset_configs.py +0 -0
  82. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/quickstart/reset_staking.py +0 -0
  83. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/quickstart/stop_service.py +0 -0
  84. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/quickstart/terminate_on_chain_service.py +0 -0
  85. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/quickstart/utils.py +0 -0
  86. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/resource.py +0 -0
  87. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/services/__init__.py +0 -0
  88. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/services/agent_runner.py +0 -0
  89. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/services/deployment_runner.py +0 -0
  90. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/services/health_checker.py +0 -0
  91. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/services/utils/__init__.py +0 -0
  92. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/services/utils/mech.py +0 -0
  93. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/services/utils/tendermint.py +0 -0
  94. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/utils/__init__.py +0 -0
  95. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/utils/gnosis.py +0 -0
  96. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/utils/ssl.py +0 -0
  97. {olas_operate_middleware-0.10.7 → olas_operate_middleware-0.10.8}/operate/wallet/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: olas-operate-middleware
3
- Version: 0.10.7
3
+ Version: 0.10.8
4
4
  Summary:
5
5
  Author: David Vilela
6
6
  Author-email: dvilelaf@gmail.com
@@ -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)
@@ -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,
@@ -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
@@ -92,16 +92,22 @@ class KeysManager(metaclass=SingletonMeta):
92
92
  suffix=".txt",
93
93
  delete=False, # Handle cleanup manually
94
94
  ) as temp_file:
95
+ temp_file_name = temp_file.name
95
96
  temp_file.write(key.private_key)
96
97
  temp_file.flush()
97
98
  temp_file.close() # Close the file before reading
98
99
 
99
100
  # Set proper file permissions (readable by owner only)
100
- os.chmod(temp_file.name, 0o600)
101
- crypto = EthereumCrypto(private_key_path=temp_file.name)
101
+ os.chmod(temp_file_name, 0o600)
102
+ crypto = EthereumCrypto(private_key_path=temp_file_name)
102
103
 
103
104
  try:
104
- os.unlink(temp_file.name) # Clean up the temporary file
105
+ with open(temp_file_name, "r+", encoding="utf-8") as f:
106
+ f.seek(0)
107
+ f.write("\0" * len(key.private_key))
108
+ f.flush()
109
+ f.close()
110
+ os.unlink(temp_file_name) # Clean up the temporary file
105
111
  except OSError as e:
106
112
  self.logger.error(f"Failed to delete temp file {temp_file.name}: {e}")
107
113
 
@@ -24,62 +24,76 @@ import os
24
24
  from operate.operate_types import Chain
25
25
 
26
26
 
27
- ETHEREUM_PUBLIC_RPC = os.environ.get("ETHEREUM_RPC", "https://ethereum.publicnode.com")
28
- GNOSIS_PUBLIC_RPC = os.environ.get("GNOSIS_RPC", "https://gnosis-rpc.publicnode.com")
29
- SOLANA_PUBLIC_RPC = os.environ.get("SOLANA_RPC", "https://api.mainnet-beta.solana.com")
27
+ ARBITRUM_ONE_PUBLIC_RPC = os.environ.get(
28
+ "ARBITRUM_ONE_RPC", "https://arb1.arbitrum.io/rpc"
29
+ )
30
30
  BASE_PUBLIC_RPC = os.environ.get("BASE_RPC", "https://mainnet.base.org")
31
31
  CELO_PUBLIC_RPC = os.environ.get("CELO_RPC", "https://forno.celo.org")
32
- OPTIMISM_PUBLIC_RPC = os.environ.get("OPTIMISM_RPC", "https://mainnet.optimism.io")
32
+ ETHEREUM_PUBLIC_RPC = os.environ.get("ETHEREUM_RPC", "https://ethereum.publicnode.com")
33
+ GNOSIS_PUBLIC_RPC = os.environ.get("GNOSIS_RPC", "https://gnosis-rpc.publicnode.com")
33
34
  MODE_PUBLIC_RPC = os.environ.get("MODE_RPC", "https://mainnet.mode.network/")
35
+ OPTIMISM_PUBLIC_RPC = os.environ.get("OPTIMISM_RPC", "https://mainnet.optimism.io")
36
+ POLYGON_PUBLIC_RPC = os.environ.get("POLYGON_RPC", "https://polygon-rpc.com")
37
+ SOLANA_PUBLIC_RPC = os.environ.get("SOLANA_RPC", "https://api.mainnet-beta.solana.com")
34
38
 
35
- ETHEREUM_RPC = os.environ.get("ETHEREUM_RPC", "https://ethereum.publicnode.com")
36
- GNOSIS_RPC = os.environ.get("GNOSIS_RPC", "https://rpc-gate.autonolas.tech/gnosis-rpc/")
37
- SOLANA_RPC = os.environ.get("SOLANA_RPC", "https://api.mainnet-beta.solana.com")
39
+ ARBITRUM_ONE_RPC = os.environ.get("ARBITRUM_ONE_RPC", "https://arb1.arbitrum.io/rpc")
38
40
  BASE_RPC = os.environ.get("BASE_RPC", "https://mainnet.base.org")
39
41
  CELO_RPC = os.environ.get("CELO_RPC", "https://forno.celo.org")
40
- OPTIMISM_RPC = os.environ.get("OPTIMISM_RPC", "https://mainnet.optimism.io")
42
+ ETHEREUM_RPC = os.environ.get("ETHEREUM_RPC", "https://ethereum.publicnode.com")
43
+ GNOSIS_RPC = os.environ.get("GNOSIS_RPC", "https://rpc-gate.autonolas.tech/gnosis-rpc/")
41
44
  MODE_RPC = os.environ.get("MODE_RPC", "https://mainnet.mode.network/")
45
+ OPTIMISM_RPC = os.environ.get("OPTIMISM_RPC", "https://mainnet.optimism.io")
46
+ POLYGON_RPC = os.environ.get("POLYGON_RPC", "https://polygon-rpc.com")
47
+ SOLANA_RPC = os.environ.get("SOLANA_RPC", "https://api.mainnet-beta.solana.com")
42
48
 
43
49
  PUBLIC_RPCS = {
44
- Chain.ETHEREUM: ETHEREUM_PUBLIC_RPC,
45
- Chain.GNOSIS: GNOSIS_PUBLIC_RPC,
46
- Chain.SOLANA: SOLANA_PUBLIC_RPC,
50
+ Chain.ARBITRUM_ONE: ARBITRUM_ONE_PUBLIC_RPC,
47
51
  Chain.BASE: BASE_PUBLIC_RPC,
48
52
  Chain.CELO: CELO_PUBLIC_RPC,
49
- Chain.OPTIMISM: OPTIMISM_PUBLIC_RPC,
53
+ Chain.ETHEREUM: ETHEREUM_PUBLIC_RPC,
54
+ Chain.GNOSIS: GNOSIS_PUBLIC_RPC,
50
55
  Chain.MODE: MODE_PUBLIC_RPC,
56
+ Chain.OPTIMISM: OPTIMISM_PUBLIC_RPC,
57
+ Chain.POLYGON: POLYGON_PUBLIC_RPC,
58
+ Chain.SOLANA: SOLANA_PUBLIC_RPC,
51
59
  }
52
60
 
53
61
  DEFAULT_RPCS = {
54
- Chain.ETHEREUM: ETHEREUM_RPC,
55
- Chain.GNOSIS: GNOSIS_RPC,
56
- Chain.SOLANA: SOLANA_RPC,
62
+ Chain.ARBITRUM_ONE: ARBITRUM_ONE_RPC,
57
63
  Chain.BASE: BASE_RPC,
58
64
  Chain.CELO: CELO_RPC,
59
- Chain.OPTIMISM: OPTIMISM_RPC,
65
+ Chain.ETHEREUM: ETHEREUM_RPC,
66
+ Chain.GNOSIS: GNOSIS_RPC,
60
67
  Chain.MODE: MODE_RPC,
68
+ Chain.OPTIMISM: OPTIMISM_RPC,
69
+ Chain.POLYGON: POLYGON_RPC,
70
+ Chain.SOLANA: SOLANA_RPC,
61
71
  }
62
72
 
63
73
  # Base currency for each chain
64
74
  CURRENCY_DENOMS = {
65
- Chain.ETHEREUM: "ETH",
66
- Chain.GNOSIS: "xDAI",
67
- Chain.SOLANA: "SOL",
75
+ Chain.ARBITRUM_ONE: "ETH",
68
76
  Chain.BASE: "ETH",
69
77
  Chain.CELO: "CELO",
70
- Chain.OPTIMISM: "ETH",
78
+ Chain.ETHEREUM: "ETH",
79
+ Chain.GNOSIS: "xDAI",
71
80
  Chain.MODE: "ETH",
81
+ Chain.OPTIMISM: "ETH",
82
+ Chain.POLYGON: "POL",
83
+ Chain.SOLANA: "SOL",
72
84
  }
73
85
 
74
86
  # Smallest denomination for each chain
75
87
  CURRENCY_SMALLEST_UNITS = {
76
- Chain.ETHEREUM: "Wei",
77
- Chain.GNOSIS: "Wei",
78
- Chain.SOLANA: "Lamport",
88
+ Chain.ARBITRUM_ONE: "Wei",
79
89
  Chain.BASE: "Wei",
80
90
  Chain.CELO: "Wei",
81
- Chain.OPTIMISM: "Wei",
91
+ Chain.ETHEREUM: "Wei",
92
+ Chain.GNOSIS: "Wei",
82
93
  Chain.MODE: "Wei",
94
+ Chain.OPTIMISM: "Wei",
95
+ Chain.POLYGON: "Wei",
96
+ Chain.SOLANA: "Lamport",
83
97
  }
84
98
 
85
99
 
@@ -29,11 +29,10 @@ from time import time
29
29
 
30
30
  from aea_cli_ipfs.ipfs_utils import IPFSTool
31
31
 
32
- from operate.constants import ZERO_ADDRESS
32
+ from operate.constants import USER_JSON, ZERO_ADDRESS
33
33
  from operate.operate_types import Chain, LedgerType
34
34
  from operate.services.manage import ServiceManager
35
35
  from operate.services.service import (
36
- DEFAULT_TRADER_ENV_VARS,
37
36
  NON_EXISTENT_MULTISIG,
38
37
  SERVICE_CONFIG_PREFIX,
39
38
  SERVICE_CONFIG_VERSION,
@@ -43,11 +42,67 @@ from operate.utils import create_backup
43
42
  from operate.wallet.master import LEDGER_TYPE_TO_WALLET_CLASS, MasterWalletManager
44
43
 
45
44
 
45
+ DEFAULT_TRADER_ENV_VARS = {
46
+ "GNOSIS_LEDGER_RPC": {
47
+ "name": "Gnosis ledger RPC",
48
+ "description": "",
49
+ "value": "",
50
+ "provision_type": "computed",
51
+ },
52
+ "STAKING_CONTRACT_ADDRESS": {
53
+ "name": "Staking contract address",
54
+ "description": "",
55
+ "value": "",
56
+ "provision_type": "computed",
57
+ },
58
+ "MECH_MARKETPLACE_CONFIG": {
59
+ "name": "Mech marketplace configuration",
60
+ "description": "",
61
+ "value": "",
62
+ "provision_type": "computed",
63
+ },
64
+ "MECH_ACTIVITY_CHECKER_CONTRACT": {
65
+ "name": "Mech activity checker contract",
66
+ "description": "",
67
+ "value": "",
68
+ "provision_type": "computed",
69
+ },
70
+ "MECH_CONTRACT_ADDRESS": {
71
+ "name": "Mech contract address",
72
+ "description": "",
73
+ "value": "",
74
+ "provision_type": "computed",
75
+ },
76
+ "MECH_REQUEST_PRICE": {
77
+ "name": "Mech request price",
78
+ "description": "",
79
+ "value": "10000000000000000",
80
+ "provision_type": "computed",
81
+ },
82
+ "USE_MECH_MARKETPLACE": {
83
+ "name": "Use Mech marketplace",
84
+ "description": "",
85
+ "value": "False",
86
+ "provision_type": "computed",
87
+ },
88
+ "REQUESTER_STAKING_INSTANCE_ADDRESS": {
89
+ "name": "Requester staking instance address",
90
+ "description": "",
91
+ "value": "",
92
+ "provision_type": "computed",
93
+ },
94
+ "PRIORITY_MECH_ADDRESS": {
95
+ "name": "Priority Mech address",
96
+ "description": "",
97
+ "value": "",
98
+ "provision_type": "computed",
99
+ },
100
+ }
101
+
102
+
46
103
  class MigrationManager:
47
104
  """MigrationManager"""
48
105
 
49
- # TODO Backport here migration for services/config.json, etc.
50
-
51
106
  def __init__(
52
107
  self,
53
108
  home: Path,
@@ -62,12 +117,12 @@ class MigrationManager:
62
117
  """Log directories present in `path`."""
63
118
  directories = [f" - {str(p)}" for p in path.iterdir() if p.is_dir()]
64
119
  directories_str = "\n".join(directories)
65
- self.logger.info(f"Directories in {path}\n: {directories_str}")
120
+ self.logger.info(f"Directories in {path}:\n{directories_str}")
66
121
 
67
122
  def migrate_user_account(self) -> None:
68
123
  """Migrates user.json"""
69
124
 
70
- path = self._path / "user.json"
125
+ path = self._path / USER_JSON
71
126
  if not path.exists():
72
127
  return
73
128
 
@@ -102,18 +157,22 @@ class MigrationManager:
102
157
 
103
158
  self.logger.info("Migrating wallet configs done.")
104
159
 
105
- @staticmethod
106
160
  def _migrate_service( # pylint: disable=too-many-statements,too-many-locals
161
+ self,
107
162
  path: Path,
108
163
  ) -> bool:
109
164
  """Migrate the JSON file format if needed."""
110
165
 
111
166
  if not path.is_dir():
167
+ self.logger.warning(f"Service config path {path} is not a directory.")
112
168
  return False
113
169
 
114
170
  if not path.name.startswith(SERVICE_CONFIG_PREFIX) and not path.name.startswith(
115
171
  "bafybei"
116
172
  ):
173
+ self.logger.warning(
174
+ f"Service config path {path} is not a valid service config."
175
+ )
117
176
  return False
118
177
 
119
178
  if path.name.startswith("bafybei"):
@@ -155,6 +214,10 @@ class MigrationManager:
155
214
  if version == SERVICE_CONFIG_VERSION:
156
215
  return False
157
216
 
217
+ self.logger.info(
218
+ f"Migrating service config in {path} from version {version} to {SERVICE_CONFIG_VERSION}..."
219
+ )
220
+
158
221
  # Migration steps for older versions
159
222
  if version == 0:
160
223
  new_data = {
@@ -344,13 +407,9 @@ class MigrationManager:
344
407
  paths = list(service_manager.path.iterdir())
345
408
  for path in paths:
346
409
  try:
347
- if path.name.startswith(SERVICE_CONFIG_PREFIX) or path.name.startswith(
348
- "bafybei"
349
- ):
350
- self.logger.info(f"migrate_service_configs {str(path)}")
351
- migrated = self._migrate_service(path)
352
- if migrated:
353
- self.logger.info(f"Folder {str(path)} has been migrated.")
410
+ migrated = self._migrate_service(path)
411
+ if migrated:
412
+ self.logger.info(f"Folder {str(path)} has been migrated.")
354
413
  except Exception as e: # pylint: disable=broad-except
355
414
  self.logger.error(
356
415
  f"Failed to migrate service: {path.name}. Exception {e}: {traceback.format_exc()}"
@@ -21,6 +21,7 @@
21
21
  from typing import TYPE_CHECKING
22
22
 
23
23
  from operate.account.user import UserAccount
24
+ from operate.constants import USER_JSON
24
25
  from operate.operate_types import LedgerType
25
26
  from operate.quickstart.run_service import ask_confirm_password
26
27
  from operate.quickstart.utils import ask_or_get_from_env, print_section, print_title
@@ -36,7 +37,7 @@ def reset_password(operate: "OperateApp") -> None:
36
37
  print_title("Reset your password")
37
38
 
38
39
  # check if agent was started before
39
- if not (operate._path / "user.json").exists():
40
+ if not (operate._path / USER_JSON).exists():
40
41
  print("No previous agent setup found. Exiting.")
41
42
  return
42
43
 
@@ -57,7 +58,7 @@ def reset_password(operate: "OperateApp") -> None:
57
58
  print("Resetting password of user account...")
58
59
  UserAccount.new(
59
60
  password=old_password,
60
- path=operate._path / "user.json",
61
+ path=operate._path / USER_JSON,
61
62
  ).update(
62
63
  old_password=old_password,
63
64
  new_password=new_password,
@@ -34,7 +34,12 @@ from halo import Halo # type: ignore[import]
34
34
  from web3.exceptions import Web3Exception
35
35
 
36
36
  from operate.account.user import UserAccount
37
- from operate.constants import IPFS_ADDRESS, NO_STAKING_PROGRAM_ID, OPERATE_HOME
37
+ from operate.constants import (
38
+ IPFS_ADDRESS,
39
+ NO_STAKING_PROGRAM_ID,
40
+ OPERATE_HOME,
41
+ USER_JSON,
42
+ )
38
43
  from operate.data import DATA_DIR
39
44
  from operate.data.contracts.staking_token.contract import StakingTokenContract
40
45
  from operate.ledger.profiles import STAKING, get_staking_contract
@@ -453,7 +458,7 @@ def ask_password_if_needed(operate: "OperateApp") -> None:
453
458
  password = ask_confirm_password()
454
459
  UserAccount.new(
455
460
  password=password,
456
- path=operate._path / "user.json",
461
+ path=operate._path / USER_JSON,
457
462
  )
458
463
  else:
459
464
  _password = None