olas-operate-middleware 0.13.2__py3-none-any.whl → 0.13.4__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.
Files changed (40) hide show
  1. {olas_operate_middleware-0.13.2.dist-info → olas_operate_middleware-0.13.4.dist-info}/METADATA +8 -27
  2. {olas_operate_middleware-0.13.2.dist-info → olas_operate_middleware-0.13.4.dist-info}/RECORD +40 -40
  3. operate/bridge/providers/provider.py +23 -31
  4. operate/cli.py +4 -17
  5. operate/constants.py +1 -0
  6. operate/data/contracts/dual_staking_token/contract.py +3 -3
  7. operate/data/contracts/dual_staking_token/contract.yaml +2 -2
  8. operate/data/contracts/foreign_omnibridge/contract.yaml +1 -1
  9. operate/data/contracts/home_omnibridge/contract.py +2 -2
  10. operate/data/contracts/home_omnibridge/contract.yaml +2 -2
  11. operate/data/contracts/l1_standard_bridge/contract.yaml +1 -1
  12. operate/data/contracts/l2_standard_bridge/contract.py +4 -4
  13. operate/data/contracts/l2_standard_bridge/contract.yaml +2 -2
  14. operate/data/contracts/mech_activity/contract.yaml +1 -1
  15. operate/data/contracts/optimism_mintable_erc20/contract.yaml +1 -1
  16. operate/data/contracts/recovery_module/contract.yaml +1 -1
  17. operate/data/contracts/requester_activity_checker/contract.yaml +1 -1
  18. operate/data/contracts/staking_token/contract.py +3 -3
  19. operate/data/contracts/staking_token/contract.yaml +2 -2
  20. operate/data/contracts/uniswap_v2_erc20/contract.yaml +3 -3
  21. operate/data/contracts/uniswap_v2_erc20/tests/test_contract.py +5 -5
  22. operate/keys.py +5 -3
  23. operate/ledger/__init__.py +1 -7
  24. operate/ledger/profiles.py +0 -1
  25. operate/operate_http/__init__.py +0 -2
  26. operate/operate_types.py +3 -93
  27. operate/quickstart/run_service.py +63 -6
  28. operate/quickstart/utils.py +8 -4
  29. operate/resource.py +2 -2
  30. operate/services/agent_runner.py +3 -3
  31. operate/services/manage.py +14 -17
  32. operate/services/protocol.py +124 -169
  33. operate/services/utils/mech.py +3 -3
  34. operate/services/utils/tendermint.py +5 -3
  35. operate/utils/gnosis.py +76 -101
  36. operate/wallet/master.py +42 -47
  37. operate/wallet/wallet_recovery_manager.py +8 -6
  38. {olas_operate_middleware-0.13.2.dist-info → olas_operate_middleware-0.13.4.dist-info}/WHEEL +0 -0
  39. {olas_operate_middleware-0.13.2.dist-info → olas_operate_middleware-0.13.4.dist-info}/entry_points.txt +0 -0
  40. {olas_operate_middleware-0.13.2.dist-info → olas_operate_middleware-0.13.4.dist-info}/licenses/LICENSE +0 -0
operate/keys.py CHANGED
@@ -144,9 +144,11 @@ class KeysManager:
144
144
  key = Key(
145
145
  ledger=LedgerType.ETHEREUM,
146
146
  address=crypto.address,
147
- private_key=crypto.encrypt(password=self.password)
148
- if self.password is not None
149
- else crypto.private_key,
147
+ private_key=(
148
+ crypto.encrypt(password=self.password)
149
+ if self.password is not None
150
+ else crypto.private_key
151
+ ),
150
152
  )
151
153
  for path in (
152
154
  self.path / f"{crypto.address}.bak",
@@ -27,7 +27,6 @@ from math import ceil
27
27
  from aea.crypto.base import LedgerApi
28
28
  from aea.crypto.registries import make_ledger_api
29
29
  from aea_ledger_ethereum import DEFAULT_GAS_PRICE_STRATEGIES, EIP1559, GWEI, to_wei
30
- from web3.middleware import geth_poa_middleware
31
30
 
32
31
  from operate.operate_types import Chain
33
32
 
@@ -133,12 +132,9 @@ def make_chain_ledger_api(
133
132
  address=rpc or get_default_rpc(chain=chain),
134
133
  chain_id=chain.id,
135
134
  gas_price_strategies=gas_price_strategies,
136
- poa_chain=chain == Chain.POLYGON,
135
+ poa_chain=chain in (Chain.OPTIMISM, Chain.POLYGON),
137
136
  )
138
137
 
139
- if chain == Chain.OPTIMISM:
140
- ledger_api.api.middleware_onion.inject(geth_poa_middleware, layer=0)
141
-
142
138
  return ledger_api
143
139
 
144
140
 
@@ -183,7 +179,6 @@ def update_tx_with_gas_pricing(tx: t.Dict, ledger_api: LedgerApi) -> None:
183
179
  # TODO This gas management should be done at a lower level in the library
184
180
  def update_tx_with_gas_estimate(tx: t.Dict, ledger_api: LedgerApi) -> None:
185
181
  """Update transaction with gas estimate."""
186
- print(f"[LEDGER] Trying to update transaction gas {tx['from']=} {tx['gas']=}.")
187
182
  original_from = tx["from"]
188
183
  original_gas = tx.get("gas", 1)
189
184
 
@@ -192,7 +187,6 @@ def update_tx_with_gas_estimate(tx: t.Dict, ledger_api: LedgerApi) -> None:
192
187
  tx["gas"] = 1
193
188
  ledger_api.update_with_gas_estimate(tx)
194
189
  if tx["gas"] > 1:
195
- print(f"[LEDGER] Gas estimated successfully {tx['from']=} {tx['gas']=}.")
196
190
  break
197
191
 
198
192
  tx["from"] = original_from
@@ -46,7 +46,6 @@ for _chain in CHAINS:
46
46
  "service_registry_token_utility": profile[
47
47
  "service_registry_token_utility"
48
48
  ],
49
- "service_manager": profile["service_manager_token"],
50
49
  "gnosis_safe_proxy_factory": profile["gnosis_safe_proxy_factory"],
51
50
  "gnosis_safe_same_address_multisig": profile[
52
51
  "gnosis_safe_same_address_multisig"
@@ -32,8 +32,6 @@ from starlette.types import Receive, Scope, Send
32
32
  from operate.operate_http.exceptions import NotAllowed, ResourceException
33
33
 
34
34
 
35
- # pylint: disable=no-self-use
36
-
37
35
  GenericResource = t.TypeVar("GenericResource")
38
36
  PostPayload = t.TypeVar("PostPayload")
39
37
  PostResponse = t.TypeVar("PostResponse")
operate/operate_types.py CHANGED
@@ -30,7 +30,7 @@ from pathlib import Path
30
30
  import argon2
31
31
  from aea_ledger_ethereum import cast
32
32
  from autonomy.chain.config import ChainType
33
- from autonomy.chain.constants import CHAIN_NAME_TO_CHAIN_ID
33
+ from autonomy.chain.config import LedgerType as LedgerTypeOA
34
34
  from cryptography.fernet import Fernet
35
35
  from typing_extensions import TypedDict
36
36
 
@@ -38,98 +38,8 @@ from operate.constants import FERNET_KEY_LENGTH, NO_STAKING_PROGRAM_ID
38
38
  from operate.resource import LocalResource
39
39
 
40
40
 
41
- CHAIN_NAME_TO_CHAIN_ID["solana"] = 900
42
-
43
- _CHAIN_ID_TO_CHAIN_NAME = {
44
- chain_id: chain_name for chain_name, chain_id in CHAIN_NAME_TO_CHAIN_ID.items()
45
- }
46
-
47
-
48
- class LedgerType(str, enum.Enum):
49
- """Ledger type enum."""
50
-
51
- ETHEREUM = "ethereum"
52
- SOLANA = "solana"
53
-
54
- @property
55
- def config_file(self) -> str:
56
- """Config filename."""
57
- return f"{self.name.lower()}.json"
58
-
59
- @property
60
- def key_file(self) -> str:
61
- """Key filename."""
62
- return f"{self.name.lower()}.txt"
63
-
64
- @property
65
- def mnemonic_file(self) -> str:
66
- """Mnemonic filename."""
67
- return f"{self.name.lower()}.mnemonic.json"
68
-
69
- @classmethod
70
- def from_id(cls, chain_id: int) -> "LedgerType":
71
- """Load from chain ID."""
72
- return Chain(_CHAIN_ID_TO_CHAIN_NAME[chain_id]).ledger_type
73
-
74
-
75
- # Dynamically create the Chain enum from the ChainType
76
- # TODO: Migrate this to open-autonomy and remove this modified version of Chain here and use the one from open-autonomy
77
- # This version of open-autonomy must support the LedgerType to support SOLANA in the future
78
- # If solana support is not fuly implemented, decide to keep this half-baked feature.
79
- #
80
- # TODO: Once the above issue is properly implemented in Open Autonomy, remove the following
81
- # lines from tox.ini:
82
- #
83
- # exclude = ^(operate/operate_types\.py|scripts/setup_wallet\.py|operate/ledger/profiles\.py|operate/ledger/__init__\.py|operate/wallet/master\.py|operate/services/protocol\.py|operate/services/manage\.py|operate/cli\.py)$
84
- #
85
- # [mypy-operate.*]
86
- # follow_imports = skip # noqa
87
- #
88
- # These lines were itroduced to resolve mypy issues with the temporary Chain/ChainType solution.
89
- Chain = enum.Enum(
90
- "Chain",
91
- [(member.name, member.value) for member in ChainType]
92
- + [
93
- ("SOLANA", "solana"),
94
- ],
95
- )
96
-
97
-
98
- class ChainMixin:
99
- """Mixin for some new functions in the ChainType class."""
100
-
101
- @property
102
- def id(self) -> t.Optional[int]:
103
- """Chain ID"""
104
- if self == Chain.CUSTOM:
105
- chain_id = os.environ.get("CUSTOM_CHAIN_ID")
106
- if chain_id is None:
107
- return None
108
- return int(chain_id)
109
- return CHAIN_NAME_TO_CHAIN_ID[self.value]
110
-
111
- @property
112
- def ledger_type(self) -> LedgerType:
113
- """Ledger type."""
114
- if self in (Chain.SOLANA,):
115
- return LedgerType.SOLANA
116
- return LedgerType.ETHEREUM
117
-
118
- @classmethod
119
- def from_string(cls, chain: str) -> "Chain":
120
- """Load from string."""
121
- return Chain(chain.lower())
122
-
123
- @classmethod
124
- def from_id(cls, chain_id: int) -> "Chain":
125
- """Load from chain ID."""
126
- return Chain(_CHAIN_ID_TO_CHAIN_NAME[chain_id])
127
-
128
-
129
- # Add the ChainMixin methods to the Chain enum
130
- for name in dir(ChainMixin):
131
- if not name.startswith("__"):
132
- setattr(Chain, name, getattr(ChainMixin, name))
41
+ LedgerType = LedgerTypeOA
42
+ Chain = ChainType
133
43
 
134
44
 
135
45
  class DeploymentStatus(enum.IntEnum):
@@ -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, USER_JSON
37
+ from operate.constants import (
38
+ DEFAULT_TIMEOUT,
39
+ IPFS_ADDRESS,
40
+ NO_STAKING_PROGRAM_ID,
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 import DEFAULT_RPCS
@@ -49,6 +54,7 @@ from operate.quickstart.utils import (
49
54
  CHAIN_TO_METADATA,
50
55
  QuickstartConfig,
51
56
  ask_or_get_from_env,
57
+ ask_yes_or_no,
52
58
  check_rpc,
53
59
  print_box,
54
60
  print_section,
@@ -124,6 +130,42 @@ QS_STAKING_PROGRAMS: t.Dict[Chain, t.Dict[str, str]] = {
124
130
  },
125
131
  }
126
132
 
133
+ DEPRECATED_QS_STAKING_PROGRAMS = {
134
+ "quickstart_beta_hobbyist",
135
+ "quickstart_beta_hobbyist_2",
136
+ "quickstart_beta_expert",
137
+ "quickstart_beta_expert_2",
138
+ "quickstart_beta_expert_3",
139
+ "quickstart_beta_expert_4",
140
+ "quickstart_beta_expert_5",
141
+ "quickstart_beta_expert_6",
142
+ "quickstart_beta_expert_7",
143
+ "quickstart_beta_expert_8",
144
+ "quickstart_beta_expert_9",
145
+ "quickstart_beta_expert_10",
146
+ "quickstart_beta_expert_11",
147
+ "quickstart_beta_expert_12",
148
+ "quickstart_beta_expert_15_mech_marketplace",
149
+ "quickstart_beta_expert_16_mech_marketplace",
150
+ "quickstart_beta_expert_17_mech_marketplace",
151
+ "quickstart_beta_expert_18_mech_marketplace",
152
+ }
153
+
154
+
155
+ def _deprecated_program_warning(program_id: str) -> bool:
156
+ """Confirm deprecated program warning."""
157
+ if program_id not in DEPRECATED_QS_STAKING_PROGRAMS:
158
+ return True
159
+
160
+ print_box(
161
+ """
162
+ WARNING
163
+ The selected staking program is deprecated.
164
+ Using it may prevent your agent from earning staking rewards.
165
+ """
166
+ )
167
+ return ask_yes_or_no("Do you want to proceed anyway?")
168
+
127
169
 
128
170
  def ask_confirm_password() -> str:
129
171
  """Ask for password confirmation."""
@@ -221,6 +263,7 @@ def configure_local_config(
221
263
  LedgerType.ETHEREUM.lower(),
222
264
  address=config.rpc[config.principal_chain], # type: ignore[index]
223
265
  chain_id=home_chain.id,
266
+ poa_chain=chain in (Chain.OPTIMISM.value, Chain.POLYGON.value),
224
267
  )
225
268
 
226
269
  if config.staking_program_id is None:
@@ -253,7 +296,7 @@ def configure_local_config(
253
296
  try:
254
297
  metadata_hash = instance.functions.metadataHash().call().hex()
255
298
  ipfs_address = IPFS_ADDRESS.format(hash=metadata_hash)
256
- response = requests.get(ipfs_address)
299
+ response = requests.get(ipfs_address, timeout=DEFAULT_TIMEOUT)
257
300
  if response.status_code != HTTPStatus.OK:
258
301
  raise requests.RequestException(
259
302
  f"Failed to fetch data from {ipfs_address}: {response.status_code}"
@@ -276,7 +319,10 @@ def configure_local_config(
276
319
  except Web3Exception:
277
320
  metadata["available_staking_slots"] = "?"
278
321
 
279
- name = metadata["name"]
322
+ deprecated_str = (
323
+ "[DEPRECATED] " if program_id in DEPRECATED_QS_STAKING_PROGRAMS else ""
324
+ )
325
+ name = deprecated_str + metadata["name"]
280
326
  description = metadata["description"]
281
327
  if "available_staking_slots" in metadata:
282
328
  available_slots_str = (
@@ -313,12 +359,23 @@ def configure_local_config(
313
359
  for idx, prog in available_choices.items():
314
360
  print(f"{idx}) {prog['name']} : {prog['slots']}")
315
361
  continue
362
+
363
+ if not _deprecated_program_warning(
364
+ available_choices[choice]["program_id"]
365
+ ):
366
+ continue
367
+
316
368
  selected_program = available_choices[choice]
317
369
  config.staking_program_id = selected_program["program_id"]
318
370
  print(f"Selected staking program: {selected_program['name']}")
319
371
  break
320
372
  except ValueError:
321
373
  if input_value in ids:
374
+ if not _deprecated_program_warning(
375
+ available_choices[choice]["program_id"]
376
+ ):
377
+ continue
378
+
322
379
  config.staking_program_id = input_value
323
380
  break
324
381
  else:
@@ -431,9 +488,9 @@ def configure_local_config(
431
488
 
432
489
  print()
433
490
 
434
- template["env_variables"][env_var_name][
435
- "value"
436
- ] = config.user_provided_args[env_var_name]
491
+ template["env_variables"][env_var_name]["value"] = (
492
+ config.user_provided_args[env_var_name]
493
+ )
437
494
 
438
495
  # TODO: Handle it in a more generic way
439
496
  if (
@@ -27,9 +27,10 @@ from pathlib import Path
27
27
  from typing import Dict, Optional, Union, get_args, get_origin
28
28
 
29
29
  import requests
30
- from halo import Halo # type: ignore[import] # pylint: disable=import-error
30
+ from halo import Halo
31
+ from web3.exceptions import Web3RPCError
31
32
 
32
- from operate.constants import ZERO_ADDRESS
33
+ from operate.constants import DEFAULT_TIMEOUT, ZERO_ADDRESS
33
34
  from operate.ledger.profiles import OLAS, USDC
34
35
  from operate.operate_types import Chain
35
36
  from operate.resource import LocalResource, deserialize
@@ -225,11 +226,14 @@ def check_rpc(chain: str, rpc_url: Optional[str] = None) -> bool:
225
226
 
226
227
  try:
227
228
  response = requests.post(
228
- rpc_url, json=rpc_data, headers={"Content-Type": "application/json"}
229
+ rpc_url,
230
+ json=rpc_data,
231
+ headers={"Content-Type": "application/json"},
232
+ timeout=DEFAULT_TIMEOUT,
229
233
  )
230
234
  response.raise_for_status()
231
235
  rpc_response = response.json()
232
- except (requests.exceptions.RequestException, ValueError, TypeError) as e:
236
+ except (requests.exceptions.RequestException, Web3RPCError, TypeError) as e:
233
237
  spinner.fail(f"Error: Failed to send {chain} RPC request: {e}")
234
238
  return False
235
239
 
operate/resource.py CHANGED
@@ -40,7 +40,7 @@ N_BACKUPS = 5
40
40
 
41
41
  def serialize(obj: t.Any) -> t.Any:
42
42
  """Serialize object."""
43
- if is_dataclass(obj):
43
+ if is_dataclass(obj) and not isinstance(obj, type):
44
44
  return serialize(asdict(obj))
45
45
  if isinstance(obj, Path):
46
46
  return str(obj)
@@ -88,7 +88,7 @@ def deserialize(obj: t.Any, otype: t.Any) -> t.Any:
88
88
  return otype(obj)
89
89
  if otype is Path:
90
90
  return Path(obj)
91
- if is_dataclass(otype):
91
+ if is_dataclass(otype) and hasattr(otype, "from_json"):
92
92
  return otype.from_json(obj)
93
93
  if otype is bytes:
94
94
  return bytes.fromhex(obj)
@@ -33,7 +33,7 @@ import requests
33
33
  from aea.configurations.data_types import PublicId
34
34
  from aea.helpers.logging import setup_logger
35
35
 
36
- from operate.constants import AGENT_RUNNER_PREFIX, CONFIG_JSON
36
+ from operate.constants import AGENT_RUNNER_PREFIX, CONFIG_JSON, DEFAULT_TIMEOUT
37
37
 
38
38
 
39
39
  @dataclass
@@ -52,7 +52,7 @@ class AgentRelease:
52
52
 
53
53
  def get_url_and_hash(self, asset_name: str) -> tuple[str, str]:
54
54
  """Get download url and asset sha256 hash."""
55
- release_data = requests.get(self.release_url).json()
55
+ release_data = requests.get(self.release_url, timeout=DEFAULT_TIMEOUT).json()
56
56
 
57
57
  assets_filtered = [i for i in release_data["assets"] if i["name"] == asset_name]
58
58
  if not assets_filtered:
@@ -106,7 +106,7 @@ class AgentRunnerManager:
106
106
  """Download file of agent runner."""
107
107
  try:
108
108
  # Send a GET request to the URL
109
- response = requests.get(url, stream=True)
109
+ response = requests.get(url, stream=True, timeout=DEFAULT_TIMEOUT)
110
110
  response.raise_for_status() # Raise an error for bad status codes (4xx or 5xx)
111
111
 
112
112
  # Open the file in binary write mode and save the content
@@ -1551,7 +1551,7 @@ class ServiceManager:
1551
1551
  f"Cannot enable recovery module. Safe {service_safe_address} has inconsistent owners."
1552
1552
  )
1553
1553
 
1554
- def _get_current_staking_program( # pylint: disable=no-self-use
1554
+ def _get_current_staking_program(
1555
1555
  self, service: Service, chain: str
1556
1556
  ) -> t.Optional[str]:
1557
1557
  staking_manager = StakingManager(Chain(chain))
@@ -1940,7 +1940,7 @@ class ServiceManager:
1940
1940
 
1941
1941
  # transfer claimed amount from agents safe to master safe
1942
1942
  # TODO: remove after staking contract directly starts sending the rewards to master safe
1943
- amount_claimed = int(receipt["logs"][0]["data"].hex(), 16)
1943
+ amount_claimed = int(receipt["logs"][0]["data"].to_0x_hex(), 16)
1944
1944
  self.logger.info(f"Claimed amount: {amount_claimed}")
1945
1945
  ethereum_crypto = self.keys_manager.get_crypto_instance(
1946
1946
  service.agent_addresses[0]
@@ -2352,9 +2352,9 @@ class ServiceManager:
2352
2352
  allow_start_agent = False
2353
2353
 
2354
2354
  # Protocol asset requirements
2355
- protocol_asset_requirements[
2356
- chain
2357
- ] = self._compute_protocol_asset_requirements(service_config_id, chain)
2355
+ protocol_asset_requirements[chain] = (
2356
+ self._compute_protocol_asset_requirements(service_config_id, chain)
2357
+ )
2358
2358
  service_asset_requirements = chain_data.user_params.fund_requirements
2359
2359
 
2360
2360
  # Bonded assets
@@ -2461,15 +2461,12 @@ class ServiceManager:
2461
2461
  asset_address
2462
2462
  ] = recommended_refill
2463
2463
 
2464
- total_requirements[chain].setdefault(master_safe, {})[
2465
- asset_address
2466
- ] = sum(
2467
- agent_asset_funding_values[address]["topup"]
2468
- for address in agent_asset_funding_values
2469
- ) + protocol_asset_requirements[
2470
- chain
2471
- ].get(
2472
- asset_address, 0
2464
+ total_requirements[chain].setdefault(master_safe, {})[asset_address] = (
2465
+ sum(
2466
+ agent_asset_funding_values[address]["topup"]
2467
+ for address in agent_asset_funding_values
2468
+ )
2469
+ + protocol_asset_requirements[chain].get(asset_address, 0)
2473
2470
  )
2474
2471
 
2475
2472
  if asset_address == ZERO_ADDRESS and any(
@@ -2498,9 +2495,9 @@ class ServiceManager:
2498
2495
  ZERO_ADDRESS
2499
2496
  ] = eoa_recommended_refill
2500
2497
 
2501
- total_requirements[chain].setdefault(master_eoa, {})[
2502
- ZERO_ADDRESS
2503
- ] = eoa_funding_values["topup"]
2498
+ total_requirements[chain].setdefault(master_eoa, {})[ZERO_ADDRESS] = (
2499
+ eoa_funding_values["topup"]
2500
+ )
2504
2501
 
2505
2502
  is_refill_required = any(
2506
2503
  amount > 0