olas-operate-middleware 0.11.5__tar.gz → 0.12.1__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 (100) hide show
  1. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/PKG-INFO +1 -1
  2. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/bridge/bridge_manager.py +52 -42
  3. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/bridge/providers/native_bridge_provider.py +26 -13
  4. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/cli.py +14 -8
  5. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/data/contracts/l1_standard_bridge/contract.py +20 -0
  6. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/data/contracts/l1_standard_bridge/contract.yaml +1 -1
  7. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/keys.py +75 -70
  8. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/migration.py +66 -1
  9. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/quickstart/reset_password.py +3 -7
  10. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/resource.py +9 -25
  11. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/services/deployment_runner.py +61 -27
  12. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/services/manage.py +2 -1
  13. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/services/protocol.py +24 -13
  14. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/services/service.py +25 -7
  15. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/utils/__init__.py +46 -0
  16. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/pyproject.toml +1 -1
  17. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/LICENSE +0 -0
  18. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/README.md +0 -0
  19. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/__init__.py +0 -0
  20. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/account/__init__.py +0 -0
  21. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/account/user.py +0 -0
  22. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/bridge/providers/lifi_provider.py +0 -0
  23. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/bridge/providers/provider.py +0 -0
  24. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/bridge/providers/relay_provider.py +0 -0
  25. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/constants.py +0 -0
  26. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/data/README.md +0 -0
  27. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/data/__init__.py +0 -0
  28. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/data/contracts/__init__.py +0 -0
  29. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/data/contracts/dual_staking_token/__init__.py +0 -0
  30. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/data/contracts/dual_staking_token/build/DualStakingToken.json +0 -0
  31. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/data/contracts/dual_staking_token/contract.py +0 -0
  32. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/data/contracts/dual_staking_token/contract.yaml +0 -0
  33. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/data/contracts/foreign_omnibridge/__init__.py +0 -0
  34. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/data/contracts/foreign_omnibridge/build/ForeignOmnibridge.json +0 -0
  35. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/data/contracts/foreign_omnibridge/contract.py +0 -0
  36. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/data/contracts/foreign_omnibridge/contract.yaml +0 -0
  37. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/data/contracts/home_omnibridge/__init__.py +0 -0
  38. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/data/contracts/home_omnibridge/build/HomeOmnibridge.json +0 -0
  39. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/data/contracts/home_omnibridge/contract.py +0 -0
  40. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/data/contracts/home_omnibridge/contract.yaml +0 -0
  41. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/data/contracts/l1_standard_bridge/__init__.py +0 -0
  42. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/data/contracts/l1_standard_bridge/build/L1StandardBridge.json +0 -0
  43. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/data/contracts/l2_standard_bridge/__init__.py +0 -0
  44. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/data/contracts/l2_standard_bridge/build/L2StandardBridge.json +0 -0
  45. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/data/contracts/l2_standard_bridge/contract.py +0 -0
  46. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/data/contracts/l2_standard_bridge/contract.yaml +0 -0
  47. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/data/contracts/mech_activity/__init__.py +0 -0
  48. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/data/contracts/mech_activity/build/MechActivity.json +0 -0
  49. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/data/contracts/mech_activity/contract.py +0 -0
  50. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/data/contracts/mech_activity/contract.yaml +0 -0
  51. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/data/contracts/optimism_mintable_erc20/__init__.py +0 -0
  52. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/data/contracts/optimism_mintable_erc20/build/OptimismMintableERC20.json +0 -0
  53. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/data/contracts/optimism_mintable_erc20/contract.py +0 -0
  54. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/data/contracts/optimism_mintable_erc20/contract.yaml +0 -0
  55. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/data/contracts/recovery_module/__init__.py +0 -0
  56. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/data/contracts/recovery_module/build/RecoveryModule.json +0 -0
  57. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/data/contracts/recovery_module/contract.py +0 -0
  58. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/data/contracts/recovery_module/contract.yaml +0 -0
  59. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/data/contracts/requester_activity_checker/__init__.py +0 -0
  60. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/data/contracts/requester_activity_checker/build/RequesterActivityChecker.json +0 -0
  61. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/data/contracts/requester_activity_checker/contract.py +0 -0
  62. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/data/contracts/requester_activity_checker/contract.yaml +0 -0
  63. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/data/contracts/staking_token/__init__.py +0 -0
  64. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/data/contracts/staking_token/build/StakingToken.json +0 -0
  65. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/data/contracts/staking_token/contract.py +0 -0
  66. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/data/contracts/staking_token/contract.yaml +0 -0
  67. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/data/contracts/uniswap_v2_erc20/__init__.py +0 -0
  68. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/data/contracts/uniswap_v2_erc20/build/IUniswapV2ERC20.json +0 -0
  69. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/data/contracts/uniswap_v2_erc20/contract.py +0 -0
  70. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/data/contracts/uniswap_v2_erc20/contract.yaml +0 -0
  71. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/data/contracts/uniswap_v2_erc20/tests/__init__.py +0 -0
  72. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/data/contracts/uniswap_v2_erc20/tests/test_contract.py +0 -0
  73. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/ledger/__init__.py +0 -0
  74. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/ledger/profiles.py +0 -0
  75. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/operate_http/__init__.py +0 -0
  76. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/operate_http/exceptions.py +0 -0
  77. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/operate_types.py +0 -0
  78. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/pearl.py +0 -0
  79. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/quickstart/analyse_logs.py +0 -0
  80. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/quickstart/claim_staking_rewards.py +0 -0
  81. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/quickstart/reset_configs.py +0 -0
  82. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/quickstart/reset_staking.py +0 -0
  83. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/quickstart/run_service.py +0 -0
  84. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/quickstart/stop_service.py +0 -0
  85. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/quickstart/terminate_on_chain_service.py +0 -0
  86. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/quickstart/utils.py +0 -0
  87. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/services/__init__.py +0 -0
  88. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/services/agent_runner.py +0 -0
  89. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/services/funding_manager.py +0 -0
  90. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/services/health_checker.py +0 -0
  91. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/services/utils/__init__.py +0 -0
  92. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/services/utils/mech.py +0 -0
  93. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/services/utils/tendermint.py +0 -0
  94. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/settings.py +0 -0
  95. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/utils/gnosis.py +0 -0
  96. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/utils/single_instance.py +0 -0
  97. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/utils/ssl.py +0 -0
  98. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/wallet/__init__.py +0 -0
  99. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/wallet/master.py +0 -0
  100. {olas_operate_middleware-0.11.5 → olas_operate_middleware-0.12.1}/operate/wallet/wallet_recovery_manager.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: olas-operate-middleware
3
- Version: 0.11.5
3
+ Version: 0.12.1
4
4
  Summary:
5
5
  License-File: LICENSE
6
6
  Author: David Vilela
@@ -57,52 +57,60 @@ RELAY_PROVIDER_ID = "relay-provider"
57
57
 
58
58
  NATIVE_BRIDGE_PROVIDER_CONFIGS: t.Dict[str, t.Any] = {
59
59
  "native-ethereum-to-base": {
60
- "from_chain": "ethereum",
60
+ "from_chain": Chain.ETHEREUM.value,
61
61
  "from_bridge": "0x3154Cf16ccdb4C6d922629664174b904d80F2C35",
62
- "to_chain": "base",
62
+ "to_chain": Chain.BASE.value,
63
63
  "to_bridge": "0x4200000000000000000000000000000000000010",
64
64
  "bridge_eta": 300,
65
65
  "bridge_contract_adaptor_class": OptimismContractAdaptor,
66
66
  },
67
+ "native-ethereum-to-celo": {
68
+ "from_chain": Chain.ETHEREUM.value,
69
+ "from_bridge": "0x9C4955b92F34148dbcfDCD82e9c9eCe5CF2badfe",
70
+ "to_chain": Chain.CELO.value,
71
+ "to_bridge": "0x4200000000000000000000000000000000000010",
72
+ "bridge_eta": 300,
73
+ "bridge_contract_adaptor_class": OptimismContractAdaptor,
74
+ },
75
+ "native-ethereum-to-gnosis": {
76
+ "from_chain": Chain.ETHEREUM.value,
77
+ "from_bridge": "0x88ad09518695c6c3712AC10a214bE5109a655671",
78
+ "to_chain": Chain.GNOSIS.value,
79
+ "to_bridge": "0xf6A78083ca3e2a662D6dd1703c939c8aCE2e268d",
80
+ "bridge_eta": 1800,
81
+ "bridge_contract_adaptor_class": OmnibridgeContractAdaptor,
82
+ },
67
83
  "native-ethereum-to-mode": {
68
- "from_chain": "ethereum",
84
+ "from_chain": Chain.ETHEREUM.value,
69
85
  "from_bridge": "0x735aDBbE72226BD52e818E7181953f42E3b0FF21",
70
- "to_chain": "mode",
86
+ "to_chain": Chain.MODE.value,
71
87
  "to_bridge": "0x4200000000000000000000000000000000000010",
72
88
  "bridge_eta": 300,
73
89
  "bridge_contract_adaptor_class": OptimismContractAdaptor,
74
90
  },
75
91
  "native-ethereum-to-optimism": {
76
- "from_chain": "ethereum",
92
+ "from_chain": Chain.ETHEREUM.value,
77
93
  "from_bridge": "0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1",
78
- "to_chain": "optimism",
94
+ "to_chain": Chain.OPTIMISM.value,
79
95
  "to_bridge": "0x4200000000000000000000000000000000000010",
80
96
  "bridge_eta": 300,
81
97
  "bridge_contract_adaptor_class": OptimismContractAdaptor,
82
98
  },
83
- "native-ethereum-to-gnosis": {
84
- "from_chain": "ethereum",
85
- "from_bridge": "0x88ad09518695c6c3712AC10a214bE5109a655671",
86
- "to_chain": "gnosis",
87
- "to_bridge": "0xf6A78083ca3e2a662D6dd1703c939c8aCE2e268d",
88
- "bridge_eta": 1800,
89
- "bridge_contract_adaptor_class": OmnibridgeContractAdaptor,
90
- },
91
99
  }
92
100
 
93
-
94
- ROUTES = {
101
+ # Routes are defined as the tuples (from_chain, from_token, to_chain, to_token)
102
+ PREFERRED_ROUTES = {
95
103
  (
96
- Chain.ETHEREUM, # from_chain
97
- USDC[Chain.ETHEREUM], # from_token
98
- Chain.OPTIMISM, # to_chain
99
- USDC[Chain.OPTIMISM], # to_token
104
+ Chain.ETHEREUM,
105
+ USDC[Chain.ETHEREUM],
106
+ Chain.OPTIMISM,
107
+ USDC[Chain.OPTIMISM],
100
108
  ): LIFI_PROVIDER_ID,
101
109
  (
102
- Chain.ETHEREUM, # from_chain
103
- USDC[Chain.ETHEREUM], # from_token
104
- Chain.BASE, # to_chain
105
- USDC[Chain.BASE], # to_token
110
+ Chain.ETHEREUM,
111
+ USDC[Chain.ETHEREUM],
112
+ Chain.BASE,
113
+ USDC[Chain.BASE],
106
114
  ): LIFI_PROVIDER_ID,
107
115
  (Chain.ETHEREUM, ZERO_ADDRESS, Chain.GNOSIS, ZERO_ADDRESS): RELAY_PROVIDER_ID,
108
116
  }
@@ -260,24 +268,26 @@ class BridgeManager:
260
268
 
261
269
  provider_requests = []
262
270
  for params in requests_params:
263
- for provider in self._native_bridge_providers.values():
264
- if provider.can_handle_request(params):
265
- provider_requests.append(provider.create_request(params=params))
266
- break
267
- else:
268
- provider_id = ROUTES.get(
269
- (
270
- Chain(params["from"]["chain"]),
271
- params["from"]["token"],
272
- Chain(params["to"]["chain"]),
273
- params["to"]["token"],
274
- ),
275
- RELAY_PROVIDER_ID,
276
- )
277
-
278
- provider_requests.append(
279
- self._providers[provider_id].create_request(params=params)
280
- )
271
+ route = (
272
+ Chain(params["from"]["chain"]),
273
+ params["from"]["token"],
274
+ Chain(params["to"]["chain"]),
275
+ params["to"]["token"],
276
+ )
277
+ provider_id = PREFERRED_ROUTES.get(route)
278
+
279
+ if not provider_id:
280
+ for provider in self._native_bridge_providers.values():
281
+ if provider.can_handle_request(params):
282
+ provider_id = provider.provider_id
283
+ break
284
+
285
+ if not provider_id:
286
+ provider_id = RELAY_PROVIDER_ID
287
+
288
+ provider_requests.append(
289
+ self._providers[provider_id].create_request(params=params)
290
+ )
281
291
 
282
292
  bundle = ProviderRequestBundle(
283
293
  id=f"{BRIDGE_REQUEST_BUNDLE_PREFIX}{uuid.uuid4()}",
@@ -53,7 +53,11 @@ from operate.data.contracts.l2_standard_bridge.contract import L2StandardBridge
53
53
  from operate.data.contracts.optimism_mintable_erc20.contract import (
54
54
  OptimismMintableERC20,
55
55
  )
56
- from operate.ledger import update_tx_with_gas_estimate, update_tx_with_gas_pricing
56
+ from operate.ledger import (
57
+ get_default_ledger_api,
58
+ update_tx_with_gas_estimate,
59
+ update_tx_with_gas_pricing,
60
+ )
57
61
  from operate.ledger.profiles import ERC20_TOKENS, EXPLORER_URL
58
62
  from operate.operate_types import Chain
59
63
  from operate.wallet.master import MasterWalletManager
@@ -75,13 +79,15 @@ class BridgeContractAdaptor(ABC):
75
79
  ) -> None:
76
80
  """Initialize the bridge contract adaptor."""
77
81
  super().__init__()
82
+ if from_chain == to_chain:
83
+ raise ValueError("from_chain and to_chain cannot be the same.")
78
84
  self.from_chain = from_chain
79
85
  self.from_bridge = from_bridge
80
86
  self.to_chain = to_chain
81
87
  self.to_bridge = to_bridge
82
88
  self.bridge_eta = bridge_eta
83
89
 
84
- def can_handle_request(self, to_ledger_api: LedgerApi, params: t.Dict) -> bool:
90
+ def can_handle_request(self, params: t.Dict) -> bool:
85
91
  """Returns 'true' if the contract adaptor can handle a request for 'params'."""
86
92
  from_chain = params["from"]["chain"]
87
93
  from_token = Web3.to_checksum_address(params["from"]["token"])
@@ -158,11 +164,24 @@ class OptimismContractAdaptor(BridgeContractAdaptor):
158
164
  ),
159
165
  )
160
166
 
161
- def can_handle_request(self, to_ledger_api: LedgerApi, params: t.Dict) -> bool:
167
+ def can_handle_request(self, params: t.Dict) -> bool:
162
168
  """Returns 'true' if the contract adaptor can handle a request for 'params'."""
163
169
 
170
+ if not super().can_handle_request(params):
171
+ return False
172
+
173
+ from_chain = params["from"]["chain"]
164
174
  from_token = Web3.to_checksum_address(params["from"]["token"])
175
+ from_ledger_api = get_default_ledger_api(Chain(from_chain))
176
+ to_chain = params["to"]["chain"]
165
177
  to_token = Web3.to_checksum_address(params["to"]["token"])
178
+ to_ledger_api = get_default_ledger_api(Chain(to_chain))
179
+
180
+ if from_token == ZERO_ADDRESS and to_token == ZERO_ADDRESS:
181
+ if not self._l1_standard_bridge_contract.supports_bridge_eth_to(
182
+ ledger_api=from_ledger_api, contract_address=self.from_bridge
183
+ ):
184
+ return False
166
185
 
167
186
  if to_token != ZERO_ADDRESS:
168
187
  try:
@@ -176,7 +195,7 @@ class OptimismContractAdaptor(BridgeContractAdaptor):
176
195
  except Exception: # pylint: disable=broad-except
177
196
  return False
178
197
 
179
- return super().can_handle_request(to_ledger_api, params)
198
+ return True
180
199
 
181
200
  def build_bridge_tx(
182
201
  self, from_ledger_api: LedgerApi, provider_request: ProviderRequest
@@ -288,13 +307,13 @@ class OmnibridgeContractAdaptor(BridgeContractAdaptor):
288
307
  ),
289
308
  )
290
309
 
291
- def can_handle_request(self, to_ledger_api: LedgerApi, params: t.Dict) -> bool:
310
+ def can_handle_request(self, params: t.Dict) -> bool:
292
311
  """Returns 'true' if the contract adaptor can handle a request for 'params'."""
293
312
  from_token = Web3.to_checksum_address(params["from"]["token"])
294
313
  if from_token == ZERO_ADDRESS:
295
314
  return False
296
315
 
297
- return super().can_handle_request(to_ledger_api, params)
316
+ return super().can_handle_request(params)
298
317
 
299
318
  def build_bridge_tx(
300
319
  self, from_ledger_api: LedgerApi, provider_request: ProviderRequest
@@ -428,16 +447,10 @@ class NativeBridgeProvider(Provider):
428
447
 
429
448
  def can_handle_request(self, params: t.Dict) -> bool:
430
449
  """Returns 'true' if the provider can handle a request for 'params'."""
431
-
432
450
  if not super().can_handle_request(params):
433
451
  return False
434
452
 
435
- to_chain = params["to"]["chain"]
436
- chain = Chain(to_chain)
437
- wallet = self.wallet_manager.load(chain.ledger_type)
438
- to_ledger_api = wallet.ledger_api(chain)
439
-
440
- if not self.bridge_contract_adaptor.can_handle_request(to_ledger_api, params):
453
+ if not self.bridge_contract_adaptor.can_handle_request(params):
441
454
  return False
442
455
 
443
456
  return True
@@ -64,6 +64,7 @@ from operate.constants import (
64
64
  WALLET_RECOVERY_DIR,
65
65
  ZERO_ADDRESS,
66
66
  )
67
+ from operate.keys import KeysManager
67
68
  from operate.ledger.profiles import (
68
69
  DEFAULT_EOA_TOPUPS,
69
70
  DEFAULT_NEW_SAFE_FUNDS,
@@ -143,7 +144,7 @@ def service_not_found_error(service_config_id: str) -> JSONResponse:
143
144
  )
144
145
 
145
146
 
146
- class OperateApp:
147
+ class OperateApp: # pylint: disable=too-many-instance-attributes
147
148
  """Operate app."""
148
149
 
149
150
  def __init__(
@@ -157,11 +158,13 @@ class OperateApp:
157
158
  self.setup()
158
159
  self._backup_operate_if_new_version()
159
160
 
160
- services.manage.KeysManager(
161
+ self._password: t.Optional[str] = os.environ.get("OPERATE_USER_PASSWORD")
162
+ KeysManager._instances.clear() # reset singleton instance
163
+ self._keys_manager: KeysManager = KeysManager(
161
164
  path=self._keys,
162
165
  logger=logger,
166
+ password=self._password,
163
167
  )
164
- self._password: t.Optional[str] = os.environ.get("OPERATE_USER_PASSWORD")
165
168
  self.settings = Settings(path=self._path)
166
169
 
167
170
  self._wallet_manager = MasterWalletManager(
@@ -174,11 +177,11 @@ class OperateApp:
174
177
  logger=logger,
175
178
  )
176
179
 
177
- mm = MigrationManager(self._path, logger)
178
- mm.migrate_user_account()
179
- mm.migrate_services(self.service_manager())
180
- mm.migrate_wallets(self.wallet_manager)
181
- mm.migrate_qs_configs()
180
+ self._migration_manager = MigrationManager(self._path, logger)
181
+ self._migration_manager.migrate_user_account()
182
+ self._migration_manager.migrate_services(self.service_manager())
183
+ self._migration_manager.migrate_wallets(self.wallet_manager)
184
+ self._migration_manager.migrate_qs_configs()
182
185
 
183
186
  @property
184
187
  def password(self) -> t.Optional[str]:
@@ -189,7 +192,9 @@ class OperateApp:
189
192
  def password(self, value: t.Optional[str]) -> None:
190
193
  """Set the password."""
191
194
  self._password = value
195
+ self._keys_manager.password = value
192
196
  self._wallet_manager.password = value
197
+ self._migration_manager.migrate_keys(self._keys_manager)
193
198
 
194
199
  def _backup_operate_if_new_version(self) -> None:
195
200
  """Backup .operate directory if this is a new version."""
@@ -256,6 +261,7 @@ class OperateApp:
256
261
  wallet_manager = self.wallet_manager
257
262
  wallet_manager.password = old_password
258
263
  wallet_manager.update_password(new_password)
264
+ self._keys_manager.update_password(new_password)
259
265
  self.user_account.update(old_password, new_password)
260
266
 
261
267
  def update_password_with_mnemonic(self, mnemonic: str, new_password: str) -> None:
@@ -45,6 +45,26 @@ class L1StandardBridge(Contract):
45
45
 
46
46
  contract_id = PublicId.from_str("valory/l1_standard_bridge:0.1.0")
47
47
 
48
+ @classmethod
49
+ def supports_bridge_eth_to(
50
+ cls,
51
+ ledger_api: LedgerApi,
52
+ contract_address: str,
53
+ ) -> bool:
54
+ """Checks if native ETH bridging via `bridgeETHTo` is supported."""
55
+ contract_instance = cls.get_instance(
56
+ ledger_api=ledger_api, contract_address=contract_address
57
+ )
58
+ try:
59
+ contract_instance.functions.bridgeETHTo(
60
+ "0x0000000000000000000000000000000000000000",
61
+ 0,
62
+ b""
63
+ ).call({"from": PLACEHOLDER_NATIVE_TOKEN_ADDRESS, "value": 0})
64
+ return True
65
+ except Exception: # pylint: disable=broad-except
66
+ return False
67
+
48
68
  @classmethod
49
69
  def build_bridge_eth_to_tx(
50
70
  cls,
@@ -8,7 +8,7 @@ aea_version: '>=1.0.0, <2.0.0'
8
8
  fingerprint:
9
9
  __init__.py: bafybeifsbxn6hlccnpgqnpvaz3ph6ajl4is4mcyerr6aqp7heggagcphye
10
10
  build/L1StandardBridge.json: bafybeidq6jt7zmedtuxbbyggiqhu7w6543bunyd2vrbibg6y2svxsi2q5m
11
- contract.py: bafybeib2kiztts3436vccvraeura3jb23oihgzxgrt3biakbed24edcaai
11
+ contract.py: bafybeienitro6gds2ny7guziwdu3uapmj3xxuzmqyvsect4zqmrouity2e
12
12
  fingerprint_ignore_patterns: []
13
13
  contracts: []
14
14
  class_name: L1StandardBridge
@@ -21,17 +21,17 @@
21
21
 
22
22
  import json
23
23
  import os
24
- import shutil
25
24
  import tempfile
26
25
  from dataclasses import dataclass
26
+ from logging import Logger
27
27
  from pathlib import Path
28
- from typing import Any
28
+ from typing import Any, Optional
29
29
 
30
30
  from aea_ledger_ethereum.ethereum import EthereumCrypto
31
31
 
32
32
  from operate.operate_types import LedgerType
33
33
  from operate.resource import LocalResource
34
- from operate.utils import SingletonMeta
34
+ from operate.utils import SingletonMeta, unrecoverable_delete
35
35
 
36
36
 
37
37
  @dataclass
@@ -42,6 +42,15 @@ class Key(LocalResource):
42
42
  address: str
43
43
  private_key: str
44
44
 
45
+ def get_decrypted(self, password: str) -> dict:
46
+ """Get decrypted key json."""
47
+ return {
48
+ "ledger": self.ledger.value,
49
+ "address": self.address,
50
+ "private_key": "0x"
51
+ + EthereumCrypto.decrypt(self.private_key, password=password),
52
+ }
53
+
45
54
  @classmethod
46
55
  def load(cls, path: Path) -> "Key":
47
56
  """Load a service"""
@@ -61,13 +70,41 @@ class KeysManager(metaclass=SingletonMeta):
61
70
  if "path" not in kwargs:
62
71
  raise ValueError("Path must be provided for KeysManager")
63
72
 
64
- self.path = kwargs["path"]
65
- self.logger = kwargs["logger"]
73
+ self.path: Path = kwargs["path"]
74
+ self.logger: Logger = kwargs["logger"]
75
+ self.password: Optional[str] = kwargs.get("password")
66
76
  self.path.mkdir(exist_ok=True, parents=True)
67
77
 
78
+ def private_key_to_crypto(
79
+ self, private_key: str, password: Optional[str]
80
+ ) -> EthereumCrypto:
81
+ """Convert private key string to EthereumCrypto instance."""
82
+ with tempfile.NamedTemporaryFile(
83
+ dir=self.path,
84
+ mode="w",
85
+ suffix=".txt",
86
+ delete=False, # Handle cleanup manually
87
+ ) as temp_file:
88
+ temp_file_name = temp_file.name
89
+ temp_file.write(private_key)
90
+ temp_file.flush()
91
+ temp_file.close() # Close the file before reading
92
+
93
+ # Set proper file permissions (readable by owner only)
94
+ os.chmod(temp_file_name, 0o600)
95
+ crypto = EthereumCrypto(private_key_path=temp_file_name, password=password)
96
+
97
+ try:
98
+ unrecoverable_delete(
99
+ Path(temp_file.name)
100
+ ) # Clean up the temporary file
101
+ except OSError as e:
102
+ self.logger.error(f"Failed to delete temp file {temp_file.name}: {e}")
103
+
104
+ return crypto
105
+
68
106
  def get(self, key: str) -> Key:
69
107
  """Get key object."""
70
- KeysManager.migrate_format(self.path / key)
71
108
  return Key.from_json( # type: ignore
72
109
  obj=json.loads(
73
110
  (self.path / key).read_text(
@@ -91,46 +128,20 @@ class KeysManager(metaclass=SingletonMeta):
91
128
 
92
129
  def get_crypto_instance(self, address: str) -> EthereumCrypto:
93
130
  """Get EthereumCrypto instance for the given address."""
94
- key: Key = Key.from_json( # type: ignore
95
- obj=json.loads(
96
- (self.path / address).read_text(
97
- encoding="utf-8",
98
- )
99
- )
100
- )
101
- private_key = key.private_key
102
- # Create temporary file with delete=False to handle it manually
103
- with tempfile.NamedTemporaryFile(
104
- dir=self.path,
105
- mode="w",
106
- suffix=".txt",
107
- delete=False, # Handle cleanup manually
108
- ) as temp_file:
109
- temp_file_name = temp_file.name
110
- temp_file.write(private_key)
111
- temp_file.flush()
112
- temp_file.close() # Close the file before reading
113
-
114
- # Set proper file permissions (readable by owner only)
115
- os.chmod(temp_file_name, 0o600)
116
- crypto = EthereumCrypto(private_key_path=temp_file_name)
117
-
118
- try:
119
- with open(temp_file_name, "r+", encoding="utf-8") as f:
120
- f.seek(0)
121
- f.write("\0" * len(private_key))
122
- f.flush()
123
- f.close()
124
- os.unlink(temp_file_name) # Clean up the temporary file
125
- except OSError as e:
126
- self.logger.error(f"Failed to delete temp file {temp_file.name}: {e}")
127
-
128
- return crypto
131
+ key: Key = self.get(address)
132
+ return self.private_key_to_crypto(key.private_key, self.password)
129
133
 
130
134
  def create(self) -> str:
131
135
  """Creates new key."""
132
136
  self.path.mkdir(exist_ok=True, parents=True)
133
- crypto = EthereumCrypto()
137
+ crypto = EthereumCrypto(password=self.password)
138
+ key = Key(
139
+ ledger=LedgerType.ETHEREUM,
140
+ address=crypto.address,
141
+ private_key=crypto.encrypt(password=self.password)
142
+ if self.password is not None
143
+ else crypto.private_key,
144
+ )
134
145
  for path in (
135
146
  self.path / f"{crypto.address}.bak",
136
147
  self.path / crypto.address,
@@ -140,12 +151,8 @@ class KeysManager(metaclass=SingletonMeta):
140
151
 
141
152
  path.write_text(
142
153
  json.dumps(
143
- Key(
144
- ledger=LedgerType.ETHEREUM,
145
- address=crypto.address,
146
- private_key=crypto.private_key,
147
- ).json,
148
- indent=4,
154
+ key.json,
155
+ indent=2,
149
156
  ),
150
157
  encoding="utf-8",
151
158
  )
@@ -156,25 +163,23 @@ class KeysManager(metaclass=SingletonMeta):
156
163
  """Delete key."""
157
164
  os.remove(self.path / key)
158
165
 
159
- @classmethod
160
- def migrate_format(cls, path: Path) -> bool:
161
- """Migrate the JSON file format if needed."""
162
- migrated = False
163
- backup_path = path.with_suffix(".bak")
164
- if not backup_path.is_file():
165
- shutil.copyfile(path, backup_path)
166
- migrated = True
167
-
168
- with open(path, "r", encoding="utf-8") as file:
169
- data = json.load(file)
170
-
171
- old_to_new_ledgers = {0: "ethereum", 1: "solana"}
172
- if data.get("ledger") in old_to_new_ledgers:
173
- data["ledger"] = old_to_new_ledgers.get(data["ledger"])
174
- migrated = True
175
-
176
- if migrated:
177
- with open(path, "w", encoding="utf-8") as file:
178
- json.dump(data, file, indent=2)
179
-
180
- return migrated
166
+ def update_password(self, new_password: str) -> None:
167
+ """Update password for all keys."""
168
+ for key_file in self.path.iterdir():
169
+ if not key_file.is_file() or key_file.suffix == ".bak":
170
+ continue
171
+
172
+ key = self.get(key_file.name)
173
+ crypto = self.get_crypto_instance(key_file.name)
174
+ encrypted_private_key = crypto.encrypt(password=new_password)
175
+ key.private_key = encrypted_private_key
176
+ key.path = self.path / key_file.name
177
+ key.store()
178
+
179
+ backup_path = self.path / f"{key.address}.bak"
180
+ backup_path.write_text(
181
+ json.dumps(key.json, indent=2),
182
+ encoding="utf-8",
183
+ )
184
+
185
+ self.password = new_password
@@ -28,8 +28,11 @@ from pathlib import Path
28
28
  from time import time
29
29
 
30
30
  from aea_cli_ipfs.ipfs_utils import IPFSTool
31
+ from aea_ledger_ethereum import EthereumCrypto
32
+ from web3 import Web3
31
33
 
32
34
  from operate.constants import USER_JSON, ZERO_ADDRESS
35
+ from operate.keys import KeysManager
33
36
  from operate.operate_types import Chain, LedgerType
34
37
  from operate.services.manage import ServiceManager
35
38
  from operate.services.service import (
@@ -38,7 +41,7 @@ from operate.services.service import (
38
41
  SERVICE_CONFIG_VERSION,
39
42
  Service,
40
43
  )
41
- from operate.utils import create_backup
44
+ from operate.utils import create_backup, unrecoverable_delete
42
45
  from operate.wallet.master import LEDGER_TYPE_TO_WALLET_CLASS, MasterWalletManager
43
46
 
44
47
 
@@ -454,3 +457,65 @@ class MigrationManager:
454
457
  self.logger.info(
455
458
  "[MIGRATION MANAGER] Migrated quickstart config: %s.", qs_config.name
456
459
  )
460
+
461
+ def migrate_keys(self, keys_manager: KeysManager) -> None:
462
+ """Migrate keys format if needed."""
463
+ self.logger.info("Migrating keys...")
464
+
465
+ for key_file in keys_manager.path.iterdir():
466
+ if (
467
+ not key_file.is_file()
468
+ or key_file.suffix == ".bak"
469
+ or not Web3.is_address(key_file.name)
470
+ ):
471
+ self.logger.warning(f"Skipping non-key file: {key_file}")
472
+ continue
473
+
474
+ migrated = False
475
+ backup_path = key_file.with_suffix(".bak")
476
+
477
+ try:
478
+ with open(key_file, "r", encoding="utf-8") as file:
479
+ data = json.load(file)
480
+ except Exception as e: # pylint: disable=broad-except
481
+ self.logger.error(
482
+ f"Failed to read key file: {key_file}\n"
483
+ f"Key file content:\n{key_file.read_text(encoding='utf-8')}\n"
484
+ f"Exception {e}: {traceback.format_exc()}"
485
+ )
486
+ raise e
487
+
488
+ old_to_new_ledgers = {0: "ethereum", 1: "solana"}
489
+ if data.get("ledger") in old_to_new_ledgers:
490
+ data["ledger"] = old_to_new_ledgers.get(data["ledger"])
491
+ with open(key_file, "w", encoding="utf-8") as file:
492
+ json.dump(data, file, indent=2)
493
+
494
+ migrated = True
495
+
496
+ private_key = data.get("private_key")
497
+ if (
498
+ private_key
499
+ and keys_manager.password is not None
500
+ and private_key.startswith("0x")
501
+ ):
502
+ crypto: EthereumCrypto = keys_manager.private_key_to_crypto(
503
+ private_key=private_key,
504
+ password=None,
505
+ )
506
+ encrypted_private_key = crypto.encrypt(password=keys_manager.password)
507
+ data["private_key"] = encrypted_private_key
508
+ if backup_path.exists():
509
+ unrecoverable_delete(backup_path)
510
+
511
+ migrated = True
512
+
513
+ if migrated:
514
+ with open(key_file, "w", encoding="utf-8") as file:
515
+ json.dump(data, file, indent=2)
516
+
517
+ if not backup_path.exists():
518
+ shutil.copyfile(key_file, backup_path)
519
+
520
+ if migrated:
521
+ self.logger.info(f"Key {key_file.name} has been migrated.")
@@ -22,10 +22,9 @@ from typing import TYPE_CHECKING
22
22
 
23
23
  from operate.account.user import UserAccount
24
24
  from operate.constants import USER_JSON
25
- from operate.operate_types import LedgerType
25
+ from operate.keys import KeysManager
26
26
  from operate.quickstart.run_service import ask_confirm_password
27
27
  from operate.quickstart.utils import ask_or_get_from_env, print_section, print_title
28
- from operate.wallet.master import EthereumMasterWallet
29
28
 
30
29
 
31
30
  if TYPE_CHECKING:
@@ -66,10 +65,7 @@ def reset_password(operate: "OperateApp") -> None:
66
65
 
67
66
  print('Resetting password of "ethereum" wallet...')
68
67
  operate.password = old_password
69
- operate.wallet_manager.password = old_password
70
- wallet: EthereumMasterWallet = operate.wallet_manager.load(
71
- ledger_type=LedgerType.ETHEREUM
72
- )
73
- wallet.update_password(new_password=new_password)
68
+ operate.wallet_manager.update_password(new_password=new_password)
69
+ KeysManager().update_password(new_password=new_password)
74
70
 
75
71
  print_section("Password reset done!")