olas-operate-middleware 0.11.4__tar.gz → 0.12.0__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.
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/PKG-INFO +1 -1
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/cli.py +14 -8
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/keys.py +75 -70
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/migration.py +66 -1
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/quickstart/reset_password.py +3 -7
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/resource.py +9 -25
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/services/deployment_runner.py +61 -27
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/services/funding_manager.py +3 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/services/manage.py +2 -1
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/services/service.py +25 -7
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/utils/__init__.py +46 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/pyproject.toml +1 -1
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/LICENSE +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/README.md +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/__init__.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/account/__init__.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/account/user.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/bridge/bridge_manager.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/bridge/providers/lifi_provider.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/bridge/providers/native_bridge_provider.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/bridge/providers/provider.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/bridge/providers/relay_provider.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/constants.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/data/README.md +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/data/__init__.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/data/contracts/__init__.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/data/contracts/dual_staking_token/__init__.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/data/contracts/dual_staking_token/build/DualStakingToken.json +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/data/contracts/dual_staking_token/contract.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/data/contracts/dual_staking_token/contract.yaml +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/data/contracts/foreign_omnibridge/__init__.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/data/contracts/foreign_omnibridge/build/ForeignOmnibridge.json +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/data/contracts/foreign_omnibridge/contract.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/data/contracts/foreign_omnibridge/contract.yaml +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/data/contracts/home_omnibridge/__init__.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/data/contracts/home_omnibridge/build/HomeOmnibridge.json +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/data/contracts/home_omnibridge/contract.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/data/contracts/home_omnibridge/contract.yaml +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/data/contracts/l1_standard_bridge/__init__.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/data/contracts/l1_standard_bridge/build/L1StandardBridge.json +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/data/contracts/l1_standard_bridge/contract.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/data/contracts/l1_standard_bridge/contract.yaml +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/data/contracts/l2_standard_bridge/__init__.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/data/contracts/l2_standard_bridge/build/L2StandardBridge.json +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/data/contracts/l2_standard_bridge/contract.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/data/contracts/l2_standard_bridge/contract.yaml +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/data/contracts/mech_activity/__init__.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/data/contracts/mech_activity/build/MechActivity.json +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/data/contracts/mech_activity/contract.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/data/contracts/mech_activity/contract.yaml +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/data/contracts/optimism_mintable_erc20/__init__.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/data/contracts/optimism_mintable_erc20/build/OptimismMintableERC20.json +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/data/contracts/optimism_mintable_erc20/contract.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/data/contracts/optimism_mintable_erc20/contract.yaml +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/data/contracts/recovery_module/__init__.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/data/contracts/recovery_module/build/RecoveryModule.json +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/data/contracts/recovery_module/contract.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/data/contracts/recovery_module/contract.yaml +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/data/contracts/requester_activity_checker/__init__.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/data/contracts/requester_activity_checker/build/RequesterActivityChecker.json +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/data/contracts/requester_activity_checker/contract.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/data/contracts/requester_activity_checker/contract.yaml +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/data/contracts/staking_token/__init__.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/data/contracts/staking_token/build/StakingToken.json +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/data/contracts/staking_token/contract.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/data/contracts/staking_token/contract.yaml +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/data/contracts/uniswap_v2_erc20/__init__.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/data/contracts/uniswap_v2_erc20/build/IUniswapV2ERC20.json +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/data/contracts/uniswap_v2_erc20/contract.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/data/contracts/uniswap_v2_erc20/contract.yaml +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/data/contracts/uniswap_v2_erc20/tests/__init__.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/data/contracts/uniswap_v2_erc20/tests/test_contract.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/ledger/__init__.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/ledger/profiles.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/operate_http/__init__.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/operate_http/exceptions.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/operate_types.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/pearl.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/quickstart/analyse_logs.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/quickstart/claim_staking_rewards.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/quickstart/reset_configs.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/quickstart/reset_staking.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/quickstart/run_service.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/quickstart/stop_service.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/quickstart/terminate_on_chain_service.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/quickstart/utils.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/services/__init__.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/services/agent_runner.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/services/health_checker.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/services/protocol.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/services/utils/__init__.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/services/utils/mech.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/services/utils/tendermint.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/settings.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/utils/gnosis.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/utils/single_instance.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/utils/ssl.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/wallet/__init__.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/wallet/master.py +0 -0
- {olas_operate_middleware-0.11.4 → olas_operate_middleware-0.12.0}/operate/wallet/wallet_recovery_manager.py +0 -0
|
@@ -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
|
-
|
|
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
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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:
|
|
@@ -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 =
|
|
95
|
-
|
|
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
|
-
|
|
144
|
-
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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.
|
|
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.
|
|
70
|
-
|
|
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!")
|
|
@@ -24,12 +24,13 @@ import json
|
|
|
24
24
|
import os
|
|
25
25
|
import platform
|
|
26
26
|
import shutil
|
|
27
|
-
import time
|
|
28
27
|
import types
|
|
29
28
|
import typing as t
|
|
30
29
|
from dataclasses import asdict, is_dataclass
|
|
31
30
|
from pathlib import Path
|
|
32
31
|
|
|
32
|
+
from operate.utils import safe_file_operation
|
|
33
|
+
|
|
33
34
|
|
|
34
35
|
# pylint: disable=too-many-return-statements,no-member
|
|
35
36
|
|
|
@@ -94,23 +95,6 @@ def deserialize(obj: t.Any, otype: t.Any) -> t.Any:
|
|
|
94
95
|
return obj
|
|
95
96
|
|
|
96
97
|
|
|
97
|
-
def _safe_file_operation(operation: t.Callable, *args: t.Any, **kwargs: t.Any) -> None:
|
|
98
|
-
"""Safely perform file operation with retries on Windows."""
|
|
99
|
-
max_retries = 3 if platform.system() == "Windows" else 1
|
|
100
|
-
|
|
101
|
-
for attempt in range(max_retries):
|
|
102
|
-
try:
|
|
103
|
-
operation(*args, **kwargs)
|
|
104
|
-
return
|
|
105
|
-
except (PermissionError, FileNotFoundError, OSError) as e:
|
|
106
|
-
if attempt == max_retries - 1:
|
|
107
|
-
raise e
|
|
108
|
-
|
|
109
|
-
if platform.system() == "Windows":
|
|
110
|
-
# On Windows, wait a bit and retry
|
|
111
|
-
time.sleep(0.1)
|
|
112
|
-
|
|
113
|
-
|
|
114
98
|
class LocalResource:
|
|
115
99
|
"""Initialize local resource."""
|
|
116
100
|
|
|
@@ -163,13 +147,13 @@ class LocalResource:
|
|
|
163
147
|
bak0 = path.with_name(f"{path.name}.0.bak")
|
|
164
148
|
|
|
165
149
|
if path.exists() and not bak0.exists():
|
|
166
|
-
|
|
150
|
+
safe_file_operation(shutil.copy2, path, bak0)
|
|
167
151
|
|
|
168
152
|
tmp_path = path.parent / f".{path.name}.tmp"
|
|
169
153
|
|
|
170
154
|
# Clean up any existing tmp file
|
|
171
155
|
if tmp_path.exists():
|
|
172
|
-
|
|
156
|
+
safe_file_operation(tmp_path.unlink)
|
|
173
157
|
|
|
174
158
|
tmp_path.write_text(
|
|
175
159
|
json.dumps(
|
|
@@ -181,11 +165,11 @@ class LocalResource:
|
|
|
181
165
|
|
|
182
166
|
# Atomic replace to avoid corruption
|
|
183
167
|
try:
|
|
184
|
-
|
|
168
|
+
safe_file_operation(os.replace, tmp_path, path)
|
|
185
169
|
except (PermissionError, FileNotFoundError):
|
|
186
170
|
# On Windows, if the replace fails, clean up and skip
|
|
187
171
|
if platform.system() == "Windows":
|
|
188
|
-
|
|
172
|
+
safe_file_operation(tmp_path.unlink)
|
|
189
173
|
|
|
190
174
|
self.load(self.path) # Validate before making backup
|
|
191
175
|
|
|
@@ -195,7 +179,7 @@ class LocalResource:
|
|
|
195
179
|
older = path.with_name(f"{path.name}.{i + 1}.bak")
|
|
196
180
|
if newer.exists():
|
|
197
181
|
if older.exists():
|
|
198
|
-
|
|
199
|
-
|
|
182
|
+
safe_file_operation(older.unlink)
|
|
183
|
+
safe_file_operation(newer.rename, older)
|
|
200
184
|
|
|
201
|
-
|
|
185
|
+
safe_file_operation(shutil.copy2, path, bak0)
|