olas-operate-middleware 0.14.1__py3-none-any.whl → 0.14.3__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.4
2
2
  Name: olas-operate-middleware
3
- Version: 0.14.1
3
+ Version: 0.14.3
4
4
  Summary:
5
5
  License-File: LICENSE
6
6
  Author: David Vilela
@@ -17,11 +17,11 @@ Requires-Dist: deepdiff (>=8.6.1,<9.0.0)
17
17
  Requires-Dist: fastapi (==0.110.3)
18
18
  Requires-Dist: halo (==0.0.31)
19
19
  Requires-Dist: multiaddr (==0.0.9)
20
- Requires-Dist: open-aea-cli-ipfs (>=2.0.6,<3.0.0)
21
- Requires-Dist: open-aea-ledger-cosmos (>=2.0.6,<3.0.0)
22
- Requires-Dist: open-aea-ledger-ethereum (>=2.0.6,<3.0.0)
23
- Requires-Dist: open-aea-ledger-ethereum-flashbots (>=2.0.6,<3.0.0)
24
- Requires-Dist: open-autonomy (>=0.21.5,<0.22.0)
20
+ Requires-Dist: open-aea-cli-ipfs (>=2.0.8,<3.0.0)
21
+ Requires-Dist: open-aea-ledger-cosmos (>=2.0.8,<3.0.0)
22
+ Requires-Dist: open-aea-ledger-ethereum (>=2.0.8,<3.0.0)
23
+ Requires-Dist: open-aea-ledger-ethereum-flashbots (>=2.0.8,<3.0.0)
24
+ Requires-Dist: open-autonomy (>=0.21.7,<0.22.0)
25
25
  Requires-Dist: psutil (>=5.9.8,<6.0.0)
26
26
  Requires-Dist: pyinstaller (>=6.8.0,<7.0.0)
27
27
  Requires-Dist: requests-mock (>=1.12.1,<2.0.0)
@@ -6,8 +6,8 @@ operate/bridge/providers/lifi_provider.py,sha256=UzAeEnX9FGpnCYYml5lcICeEZeHHqNR
6
6
  operate/bridge/providers/native_bridge_provider.py,sha256=vAx0MtVPIAxIdQ5OKSUDhnGurYVkC8tKVJRFK9NkIdk,25088
7
7
  operate/bridge/providers/provider.py,sha256=E5d3yFe7WYZd_IbQIgDVf6F5MMJ8vMOm1o-qeqJJSCk,16981
8
8
  operate/bridge/providers/relay_provider.py,sha256=QU9H9mCAUgQx-qMKXkCGfFNimi1WGoxmKO30F2K9erw,17628
9
- operate/cli.py,sha256=2Rg4pIRZMMK7U2xgMuX3CEueOXi280cUzk-avZ3E1aw,69786
10
- operate/constants.py,sha256=oBxZEnhETCd96GWz2QDUZd-0-ofV-1deKNvFPl4-mmM,3933
9
+ operate/cli.py,sha256=ZvbG2UZyi0oyvyXMRTMtKyeDcQxlrVEhBAGlZ0nEEww,72187
10
+ operate/constants.py,sha256=ek69ACPZ8Q3Bdiuducd8zjTZOMHerh5L_xdJ5K430Ew,4001
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,10 +39,6 @@ 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=kX4RBON_ajqOVJffhKtMSjf0FA0hMuVTUPUbCMaZCz4,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=Kxf2GCWPwT0QqjuhsM8eMYNm8YtTWE9y571tYq3NtlE,680
46
42
  operate/data/contracts/requester_activity_checker/__init__.py,sha256=BrOe5cib0jItJCsygrAYejy0v16xayRUoJUWubyw1yA,878
47
43
  operate/data/contracts/requester_activity_checker/build/RequesterActivityChecker.json,sha256=KbZbZDEred3LvxIY30igz5w9ubaD0-PvDbFdlg7F-y4,9748
48
44
  operate/data/contracts/requester_activity_checker/contract.py,sha256=preNvyk8kmtUE0LgEIZMLorYevgCmoxBqI-1STrX0_4,1252
@@ -59,31 +55,31 @@ operate/data/contracts/uniswap_v2_erc20/tests/__init__.py,sha256=3Arw8dsCsJz6hVO
59
55
  operate/data/contracts/uniswap_v2_erc20/tests/test_contract.py,sha256=z4GfybA_oZUA6-sl61qaJ78rXdbcW_rk4APzgMzQD38,13456
60
56
  operate/keys.py,sha256=qfT3-ZS1R2jG_t7BdqUdgrAYqHnj6dNrtK5c0-_AODU,6179
61
57
  operate/ledger/__init__.py,sha256=c41SqGLSpLBvG_q1cPyDh1aunByopsIIC5Mfk1vdmQE,6207
62
- operate/ledger/profiles.py,sha256=ElxkFcZ9gt9Cetn6nWC0v63Ca0Eo8YTt9_amnI1v6RQ,15153
58
+ operate/ledger/profiles.py,sha256=e37WUpS48E3icEYqp8sBwbuSKRzhiQappF7JjF1br-U,15788
63
59
  operate/migration.py,sha256=hdZlhhdkoPPzkOD0CFyNYAp-eqUrVu_PJnw8_PoxpWk,21015
64
60
  operate/operate_http/__init__.py,sha256=MTS1tMZ5qnA_WzaoeLxlK9IJToMGIpkNr7_vyBeAqZ8,4680
65
61
  operate/operate_http/exceptions.py,sha256=4UFzrn-GyDD71RhkaOyFPBynL6TrrtP3eywaaU3o4fc,1339
66
- operate/operate_types.py,sha256=tsGL1vC0dSZidseG1A_2oAPEpxEHXM7YhtGVcuifBDw,13855
62
+ operate/operate_types.py,sha256=czaqIsF46bZn2ePpGNKPZ487d-5wYsab9Z-6DPJoips,14458
67
63
  operate/pearl.py,sha256=yrTpSXLu_ML3qT-uNxq3kScOyo31JyxBujiSMfMUbcg,1690
68
64
  operate/quickstart/analyse_logs.py,sha256=cAeAL2iUy0Po8Eor70tq54-Ibg-Dn8rkuaS167yjE_I,4198
69
65
  operate/quickstart/claim_staking_rewards.py,sha256=K7X1Yq0mxe3qWmFLb1Xu9-Jghhml95lS_LpM_BXii0o,3533
70
66
  operate/quickstart/reset_configs.py,sha256=DVPM4mh6Djunwq16hf8lD9-nGkkm7wVtwr2JUXr1if8,3380
71
67
  operate/quickstart/reset_password.py,sha256=jEBk2ROR1q8PkTIHlqum7E8PRQtXHwrauiy0_bik3RQ,2394
72
68
  operate/quickstart/reset_staking.py,sha256=SB5LZq9EctG4SYn2M6oPZ7R7ARHSFLRGzAqfKkpRcy0,5111
73
- operate/quickstart/run_service.py,sha256=tt4PYeZJ2MtYeG59vohOdMbOiCYGGUmGnjeZwYl0shQ,30383
69
+ operate/quickstart/run_service.py,sha256=Xag5daVvMBVY8aiAez3ZmYhcz5ZNLWaD5G87QDNatWU,30483
74
70
  operate/quickstart/stop_service.py,sha256=a3-1vVyZma2UtFUPKMvVrOso1Iwpz5Rzpus9VAI4qOc,2169
75
71
  operate/quickstart/terminate_on_chain_service.py,sha256=5ENU8_mkj06i80lKUX-v1QbLU0YzKeOZDUL1e_jzySE,2914
76
72
  operate/quickstart/utils.py,sha256=i1juhJCPkzB7ZKgSk5tNiRmYxGcx8MG-dTjVyC5sKys,9287
77
- operate/resource.py,sha256=0KeQkWojN737er4USSJMVC9tQEmWtXWdXj8xPEfaHqI,3954
73
+ operate/resource.py,sha256=Z0P1weLWiNRan_3gJw3etvptnBnHvBEKBZ2kBjGMuhw,4254
78
74
  operate/serialization.py,sha256=mf2uJCj1WULaTIJSYQ-XuaW3bdwc0vn-cxk2q0IJQf4,3885
79
75
  operate/services/__init__.py,sha256=isrThS-Ccu5Sc15JZgkN4uTAVaSg-NwUUSDeTyJEqLk,855
80
76
  operate/services/agent_runner.py,sha256=JGjyrzA5hX4Nuh79h81-dl2hdt74ZkC63t7UsGXY6Rw,7500
81
77
  operate/services/deployment_runner.py,sha256=7A94QpZu100BwIk1Q9Cr0SVK5Sj7nTWx2GRCwr0gvaM,30772
82
- operate/services/funding_manager.py,sha256=q-mO6kQWthCLI5uW8qbrMQw_6i--zd1rJ3SG4tbw6FM,41672
78
+ operate/services/funding_manager.py,sha256=pURhGwo2-3c7_EiQE_BuECLQsD7t_86KgL27bzliHkc,41680
83
79
  operate/services/health_checker.py,sha256=dARikrgzU1jEuK4NUqlZ7N0DQq4Ah1ZiRKHmrlh8v-A,11472
84
80
  operate/services/manage.py,sha256=9dTjXhBq6tgcFWfhU61JQL67L2h3_b_7p8OICimWWB4,113292
85
- operate/services/protocol.py,sha256=4wAMHoELkqJJlETsMJa-XT8xVA0jfueiaKMn7X4SUKo,71898
86
- operate/services/service.py,sha256=YOU0rlPe5PjDnFRlAcThYLvr5MrPOftBDKZHMRNzZ78,46647
81
+ operate/services/protocol.py,sha256=afq9x0MbWV0ry2h7DusVTxFWUVe6Q3pVAZGhzb2RznQ,71310
82
+ operate/services/service.py,sha256=IIgDS2y72CxM8km1msVSDcdHXARunPx4wC5pTDNBOt4,50420
87
83
  operate/services/utils/__init__.py,sha256=TvioaZ1mfTRUSCtrQoLNAp4WMVXyqEJqFJM4PxSQCRU,24
88
84
  operate/services/utils/mech.py,sha256=98gNw8pMNvv_O34V1blr7JUwenqxFeeyFuXLuSYv10w,3864
89
85
  operate/services/utils/tendermint.py,sha256=M4zjF97SOJomhmj97bWKIphnia30lbDie65fs_vy_q8,25686
@@ -95,8 +91,8 @@ operate/utils/ssl.py,sha256=O5DrDoZD4T4qQuHP8GLwWUVxQ-1qXeefGp6uDJiF2lM,4308
95
91
  operate/wallet/__init__.py,sha256=NGiozD3XhvkBi7_FaOWQ8x1thZPK4uGpokJaeDY_o2w,813
96
92
  operate/wallet/master.py,sha256=019VvWsMfzAV3fUienhcYVLB12BcSVvub3weAeMOlSA,33808
97
93
  operate/wallet/wallet_recovery_manager.py,sha256=kZIKBCIVb-ufntUoCE0IqAJ-Q2YUIl7955UYY6sp8Os,19856
98
- olas_operate_middleware-0.14.1.dist-info/METADATA,sha256=f-5TTJaF2_pDz2a0j7nRdvqdokleT2m2-gJ94jf2YI4,1492
99
- olas_operate_middleware-0.14.1.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
100
- olas_operate_middleware-0.14.1.dist-info/entry_points.txt,sha256=dM1g2I7ODApKQFcgl5J4NGA7pfBTo6qsUTXM-j2OLlw,44
101
- olas_operate_middleware-0.14.1.dist-info/licenses/LICENSE,sha256=mdBDB-mWKV5Cz4ejBzBiKqan6Z8zVLAh9xwM64O2FW4,11339
102
- olas_operate_middleware-0.14.1.dist-info/RECORD,,
94
+ olas_operate_middleware-0.14.3.dist-info/METADATA,sha256=S0sVuYBq2QEhXsNV82KosmqI8mrw8rzlNBaDB86gEC8,1492
95
+ olas_operate_middleware-0.14.3.dist-info/WHEEL,sha256=3ny-bZhpXrU6vSQ1UPG34FoxZBp3lVcvK0LkgUz6VLk,88
96
+ olas_operate_middleware-0.14.3.dist-info/entry_points.txt,sha256=dM1g2I7ODApKQFcgl5J4NGA7pfBTo6qsUTXM-j2OLlw,44
97
+ olas_operate_middleware-0.14.3.dist-info/licenses/LICENSE,sha256=mdBDB-mWKV5Cz4ejBzBiKqan6Z8zVLAh9xwM64O2FW4,11339
98
+ olas_operate_middleware-0.14.3.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.2.1
2
+ Generator: poetry-core 2.3.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
operate/cli.py CHANGED
@@ -37,7 +37,7 @@ from types import FrameType
37
37
  import autonomy.chain.tx
38
38
  from aea.helpers.logging import setup_logger
39
39
  from clea import group, params, run
40
- from fastapi import FastAPI, Request
40
+ from fastapi import FastAPI, Query, Request
41
41
  from fastapi.middleware.cors import CORSMiddleware
42
42
  from fastapi.responses import JSONResponse
43
43
  from typing_extensions import Annotated
@@ -1066,6 +1066,68 @@ def create_app( # pylint: disable=too-many-locals, unused-argument, too-many-st
1066
1066
  deployment_json["healthcheck"] = service.get_latest_healthcheck()
1067
1067
  return JSONResponse(content=deployment_json)
1068
1068
 
1069
+ @app.get("/api/v2/service/{service_config_id}/achievements")
1070
+ async def _get_service_achievements(
1071
+ request: Request, include_acknowledged: bool = Query(False) # noqa: B008
1072
+ ) -> JSONResponse:
1073
+ """Get the service achievements."""
1074
+ service_config_id = request.path_params["service_config_id"]
1075
+
1076
+ if not operate.service_manager().exists(service_config_id=service_config_id):
1077
+ return service_not_found_error(service_config_id=service_config_id)
1078
+
1079
+ service = operate.service_manager().load(service_config_id=service_config_id)
1080
+
1081
+ achievements_json = service.get_achievements_notifications(
1082
+ include_acknowledged=include_acknowledged,
1083
+ )
1084
+
1085
+ return JSONResponse(content=achievements_json)
1086
+
1087
+ @app.post(
1088
+ "/api/v2/service/{service_config_id}/achievement/{achievement_id}/acknowledge"
1089
+ )
1090
+ async def _acknowledge_achievement(request: Request) -> JSONResponse:
1091
+ """Update a service."""
1092
+ if operate.password is None:
1093
+ return USER_NOT_LOGGED_IN_ERROR
1094
+
1095
+ service_config_id = request.path_params["service_config_id"]
1096
+ manager = operate.service_manager()
1097
+
1098
+ if not manager.exists(service_config_id=service_config_id):
1099
+ return service_not_found_error(service_config_id=service_config_id)
1100
+
1101
+ service = operate.service_manager().load(service_config_id=service_config_id)
1102
+
1103
+ achievement_id = request.path_params["achievement_id"]
1104
+
1105
+ try:
1106
+ service.acknowledge_achievement(
1107
+ achievement_id=achievement_id,
1108
+ )
1109
+ except KeyError:
1110
+ return JSONResponse(
1111
+ content={
1112
+ "error": f"Achievement {achievement_id} does not exist for service {service_config_id}."
1113
+ },
1114
+ status_code=HTTPStatus.NOT_FOUND,
1115
+ )
1116
+ except ValueError:
1117
+ return JSONResponse(
1118
+ content={
1119
+ "error": f"Achievement {achievement_id} was already acknowledged for service {service_config_id}."
1120
+ },
1121
+ status_code=HTTPStatus.BAD_REQUEST,
1122
+ )
1123
+
1124
+ return JSONResponse(
1125
+ content={
1126
+ "error": None,
1127
+ "message": f"Acknowledged achievement_id {achievement_id} for service {service_config_id} successfully.",
1128
+ }
1129
+ )
1130
+
1069
1131
  @app.get("/api/v2/service/{service_config_id}/agent_performance")
1070
1132
  async def _get_agent_performance(request: Request) -> JSONResponse:
1071
1133
  """Get the service refill requirements."""
operate/constants.py CHANGED
@@ -35,6 +35,7 @@ DEPLOYMENT_JSON = "deployment.json"
35
35
  CONFIG_JSON = "config.json"
36
36
  USER_JSON = "user.json"
37
37
  HEALTHCHECK_JSON = "healthcheck.json"
38
+ ACHIEVEMENTS_NOTIFICATIONS_JSON = "achievements_notifications.json"
38
39
  VERSION_FILE = "operate.version"
39
40
  SETTINGS_JSON = "settings.json"
40
41
  FUNDING_REQUIREMENTS_JSON = "funding_requirements.json"
@@ -102,6 +102,8 @@ STAKING: t.Dict[Chain, t.Dict[str, str]] = {
102
102
  "quickstart_beta_mech_marketplace_expert_8": "0x168aED532a0CD8868c22Fc77937Af78b363652B1",
103
103
  "quickstart_beta_mech_marketplace_expert_9": "0xdDa9cD214F12e7C2D58E871404A0A3B1177065C8",
104
104
  "quickstart_beta_mech_marketplace_expert_10": "0x53a38655B4e659eF4C7F88A26fbF5c67932C7156",
105
+ "quickstart_beta_mech_marketplace_expert_11": "0x1eaDe40561C61fa7AcC5D816b1FC55a8d9B58519",
106
+ "quickstart_beta_mech_marketplace_expert_12": "0x99Fe6B5C9980Fc3A44b1Dc32A76Db6aDfcf4c75e",
105
107
  "mech_marketplace": "0x998dEFafD094817EF329f6dc79c703f1CF18bC90",
106
108
  "marketplace_supply_alpha": "0xCAbD0C941E54147D40644CF7DA7e36d70DF46f44",
107
109
  "marketplace_demand_alpha_1": "0x9d6e7aB0B5B48aE5c146936147C639fEf4575231",
@@ -140,7 +142,11 @@ STAKING: t.Dict[Chain, t.Dict[str, str]] = {
140
142
  "modius_alpha_3": "0x9034D0413D122015710f1744A19eFb1d7c2CEB13",
141
143
  "modius_alpha_4": "0x8BcAdb2c291C159F9385964e5eD95a9887302862",
142
144
  },
143
- Chain.POLYGON: {},
145
+ Chain.POLYGON: {
146
+ "polygon_beta_1": "0x9F1936f6afB5EAaA2220032Cf5e265F2Cc9511Cc",
147
+ "polygon_beta_2": "0x22D58680F643333F93205B956a4Aa1dC203a16Ad",
148
+ "polygon_beta_3": "0x8887C2852986e7cbaC99B6065fFe53074A6BCC26", # Note: “Polygon Alpha 3” is a typo in apps — the correct name is Polygon Beta 3.
149
+ },
144
150
  }
145
151
 
146
152
 
@@ -153,6 +159,10 @@ DEFAULT_PRIORITY_MECH = { # maps mech marketplace address to its default priori
153
159
  "0xC05e7412439bD7e91730a6880E18d5D5873F632C",
154
160
  2182,
155
161
  ),
162
+ "0x343F2B005cF6D70bA610CD9F1F1927049414B582": (
163
+ "0x45F25db135E83d7a010b05FFc1202F8473E3ae7D",
164
+ 25,
165
+ ),
156
166
  }
157
167
 
158
168
 
operate/operate_types.py CHANGED
@@ -34,7 +34,11 @@ from autonomy.chain.config import LedgerType as LedgerTypeOA
34
34
  from cryptography.fernet import Fernet
35
35
  from typing_extensions import TypedDict
36
36
 
37
- from operate.constants import FERNET_KEY_LENGTH, NO_STAKING_PROGRAM_ID
37
+ from operate.constants import (
38
+ ACHIEVEMENTS_NOTIFICATIONS_JSON,
39
+ FERNET_KEY_LENGTH,
40
+ NO_STAKING_PROGRAM_ID,
41
+ )
38
42
  from operate.resource import LocalResource
39
43
  from operate.serialization import BigInt, serialize
40
44
 
@@ -220,6 +224,30 @@ class OnChainData(LocalResource):
220
224
  user_params: OnChainUserParams
221
225
 
222
226
 
227
+ @dataclass
228
+ class AchievementNotification(LocalResource):
229
+ """AchievementNotification"""
230
+
231
+ achievement_id: str
232
+ acknowledged: bool
233
+ acknowledgement_timestamp: int
234
+
235
+ @classmethod
236
+ def from_json(cls, obj: t.Dict) -> "ChainConfig":
237
+ """Load the chain config."""
238
+ return super().from_json(obj) # type: ignore
239
+
240
+
241
+ @dataclass
242
+ class AchievementsNotifications(LocalResource):
243
+ """AchievementsNotifications"""
244
+
245
+ path: Path
246
+ notifications: t.Dict[str, AchievementNotification]
247
+
248
+ _file = ACHIEVEMENTS_NOTIFICATIONS_JSON
249
+
250
+
223
251
  @dataclass
224
252
  class ChainConfig(LocalResource):
225
253
  """Chain config."""
@@ -109,6 +109,8 @@ QS_STAKING_PROGRAMS: t.Dict[Chain, t.Dict[str, str]] = {
109
109
  "quickstart_beta_mech_marketplace_expert_8": "trader",
110
110
  "quickstart_beta_mech_marketplace_expert_9": "trader",
111
111
  "quickstart_beta_mech_marketplace_expert_10": "trader",
112
+ "quickstart_beta_mech_marketplace_expert_11": "trader",
113
+ "quickstart_beta_mech_marketplace_expert_12": "trader",
112
114
  "mech_marketplace": "mech",
113
115
  "marketplace_supply_alpha": "mech",
114
116
  },
@@ -490,15 +492,13 @@ def configure_local_config(
490
492
 
491
493
  print()
492
494
 
493
- template["env_variables"][env_var_name]["value"] = (
495
+ template["env_variables"][env_var_name]["value"] = str(
494
496
  config.user_provided_args[env_var_name]
495
497
  )
496
498
 
497
499
  # TODO: Handle it in a more generic way
498
500
  if (
499
- template["env_variables"][env_var_name]["provision_type"]
500
- == ServiceEnvProvisionType.COMPUTED
501
- and "SUBGRAPH_API_KEY" in config.user_provided_args
501
+ "SUBGRAPH_API_KEY" in config.user_provided_args
502
502
  and "{SUBGRAPH_API_KEY}" in template["env_variables"][env_var_name]["value"]
503
503
  ):
504
504
  template["env_variables"][env_var_name]["value"] = template[
@@ -567,7 +567,10 @@ def get_service(manager: ServiceManager, template: ServiceTemplate) -> Service:
567
567
  if env_var_name not in service.env_variables:
568
568
  service.env_variables[env_var_name] = env_var_data
569
569
 
570
- if env_var_data["provision_type"] == ServiceEnvProvisionType.FIXED:
570
+ if env_var_data["provision_type"] in (
571
+ ServiceEnvProvisionType.FIXED,
572
+ ServiceEnvProvisionType.USER,
573
+ ):
571
574
  service.env_variables[env_var_name]["value"] = env_var_data["value"]
572
575
 
573
576
  service.update_user_params_from_template(service_template=template)
operate/resource.py CHANGED
@@ -65,6 +65,16 @@ class LocalResource:
65
65
  kwargs[pname] = deserialize(obj=obj[pname], otype=ptype)
66
66
  return cls(**kwargs)
67
67
 
68
+ @classmethod
69
+ def exists_at(cls, path: Path) -> bool:
70
+ """Verifies if local resource exists at specified path."""
71
+ file = (
72
+ path / cls._file
73
+ if cls._file is not None and path.name != cls._file
74
+ else path
75
+ )
76
+ return file.exists()
77
+
68
78
  @classmethod
69
79
  def load(cls, path: Path) -> "LocalResource":
70
80
  """Load local resource."""
@@ -388,7 +388,7 @@ class FundingManager:
388
388
  )
389
389
 
390
390
  if not staking_contract:
391
- return dict(bonded_assets)
391
+ return ChainAmounts(bonded_assets)
392
392
 
393
393
  staking_manager = StakingManager(Chain(chain))
394
394
  staking_params = staking_manager.get_staking_params(
@@ -69,7 +69,6 @@ from operate.constants import (
69
69
  )
70
70
  from operate.data import DATA_DIR
71
71
  from operate.data.contracts.dual_staking_token.contract import DualStakingTokenContract
72
- from operate.data.contracts.recovery_module.contract import RecoveryModule
73
72
  from operate.data.contracts.staking_token.contract import StakingTokenContract
74
73
  from operate.ledger import (
75
74
  get_default_ledger_api,
@@ -1807,21 +1806,10 @@ class EthSafeTxBuilder(_ChainUtil):
1807
1806
 
1808
1807
  def get_recover_access_data(self, service_id: int) -> t.Dict:
1809
1808
  """Get recover access tx data."""
1810
- instance = t.cast(
1811
- RecoveryModule,
1812
- RecoveryModule.from_dir(
1813
- directory=str(DATA_DIR / "contracts" / "recovery_module"),
1814
- ),
1815
- ).get_instance(
1809
+ instance = registry_contracts.recovery_module.get_instance(
1816
1810
  ledger_api=self.ledger_api,
1817
1811
  contract_address=self.contracts["recovery_module"],
1818
1812
  )
1819
- # TODO Replace the line above by this one once the recovery_module is
1820
- # included in the release of OpenAutonomy.
1821
- # instance = registry_contracts.recovery_module.get_instance( # noqa: E800
1822
- # ledger_api=self.ledger_api, # noqa: E800
1823
- # contract_address=self.contracts["recovery_module"], # noqa: E800
1824
- # ) # noqa: E800
1825
1813
  txd = instance.encode_abi(
1826
1814
  abi_element_identifier="recoverAccess",
1827
1815
  args=[service_id],
@@ -80,6 +80,8 @@ from operate.ledger import get_default_ledger_api, get_default_rpc
80
80
  from operate.ledger.profiles import WRAPPED_NATIVE_ASSET
81
81
  from operate.operate_http.exceptions import NotAllowed
82
82
  from operate.operate_types import (
83
+ AchievementNotification,
84
+ AchievementsNotifications,
83
85
  AgentRelease,
84
86
  Chain,
85
87
  ChainAmounts,
@@ -1028,13 +1030,107 @@ class Service(LocalResource):
1028
1030
  if isinstance(data, dict):
1029
1031
  agent_performance.update(data)
1030
1032
  except (json.JSONDecodeError, OSError) as e:
1031
- # Keep default values if file is invalid
1032
- print(
1033
- f"Error reading file 'agent_performance.json': {e}"
1034
- ) # TODO Use logger
1033
+ logger.warning(f"Cannot read file 'agent_performance.json': {e}")
1035
1034
 
1036
1035
  return dict(sorted(agent_performance.items()))
1037
1036
 
1037
+ def _load_achievements_notifications(
1038
+ self,
1039
+ ) -> t.Tuple[AchievementsNotifications, t.Dict[str, t.Any]]:
1040
+ if not AchievementsNotifications.exists_at(self.path):
1041
+ AchievementsNotifications(
1042
+ path=self.path,
1043
+ notifications={},
1044
+ ).store()
1045
+
1046
+ achievements_notifications: AchievementsNotifications = t.cast(
1047
+ AchievementsNotifications, AchievementsNotifications.load(self.path)
1048
+ )
1049
+
1050
+ agent_achievements_json_path = (
1051
+ Path(
1052
+ self.env_variables.get(
1053
+ AGENT_PERSISTENT_STORAGE_ENV_VAR, {"value": "."}
1054
+ ).get("value", ".")
1055
+ )
1056
+ / "achievements.json"
1057
+ )
1058
+
1059
+ agent_achievements: t.Dict[str, t.Any] = {}
1060
+ if agent_achievements_json_path.exists():
1061
+ try:
1062
+ with open(agent_achievements_json_path, "r", encoding="utf-8") as f:
1063
+ agent_achievements = json.load(f)
1064
+ except (json.JSONDecodeError, OSError) as e:
1065
+ print(f"Error reading file 'achievements.json': {e}")
1066
+
1067
+ save_changes = False
1068
+ for achievement_id in agent_achievements:
1069
+ if achievement_id not in achievements_notifications.notifications:
1070
+ achievements_notifications.notifications[achievement_id] = (
1071
+ AchievementNotification(
1072
+ achievement_id=achievement_id,
1073
+ acknowledged=False,
1074
+ acknowledgement_timestamp=0,
1075
+ )
1076
+ )
1077
+ save_changes = True
1078
+
1079
+ if save_changes:
1080
+ achievements_notifications.store()
1081
+
1082
+ return achievements_notifications, agent_achievements
1083
+
1084
+ def get_achievements_notifications(
1085
+ self, include_acknowledged: bool
1086
+ ) -> t.List[t.Dict]:
1087
+ """Return the achievements notifications"""
1088
+
1089
+ achievements_notifications, agent_achievements = (
1090
+ self._load_achievements_notifications()
1091
+ )
1092
+
1093
+ output: t.Dict[str, t.Dict] = {}
1094
+
1095
+ for (
1096
+ achievement_id,
1097
+ achievement_notification,
1098
+ ) in achievements_notifications.notifications.items():
1099
+ acknowledged = achievement_notification.acknowledged
1100
+ if not acknowledged or (acknowledged and include_acknowledged):
1101
+ if achievement_id in agent_achievements:
1102
+ output[achievement_id] = achievement_notification.json
1103
+ output[achievement_id].update(agent_achievements[achievement_id])
1104
+ else:
1105
+ logger.warning(
1106
+ f"Achievement {achievement_id} from notifications database is not present in agent achievements file (Corrupted file?)."
1107
+ )
1108
+
1109
+ return list(output.values())
1110
+
1111
+ def acknowledge_achievement(self, achievement_id: str) -> None:
1112
+ """Acknowledge an achievement id"""
1113
+
1114
+ achievements_notifications, _ = self._load_achievements_notifications()
1115
+
1116
+ if achievement_id not in achievements_notifications.notifications:
1117
+ raise KeyError(
1118
+ f"Achievement {achievement_id} does not exist for service {self.service_config_id}."
1119
+ )
1120
+
1121
+ achievement_notification = achievements_notifications.notifications[
1122
+ achievement_id
1123
+ ]
1124
+
1125
+ if achievement_notification.acknowledged:
1126
+ raise ValueError(
1127
+ f"Achievement {achievement_id} was already acknowledged for service {self.service_config_id}."
1128
+ )
1129
+
1130
+ achievement_notification.acknowledgement_timestamp = int(time.time())
1131
+ achievement_notification.acknowledged = True
1132
+ achievements_notifications.store()
1133
+
1038
1134
  def update(
1039
1135
  self,
1040
1136
  service_template: ServiceTemplate,
@@ -1,20 +0,0 @@
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."""