olas-operate-middleware 0.10.19__py3-none-any.whl → 0.11.0__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.
- {olas_operate_middleware-0.10.19.dist-info → olas_operate_middleware-0.11.0.dist-info}/METADATA +3 -1
- {olas_operate_middleware-0.10.19.dist-info → olas_operate_middleware-0.11.0.dist-info}/RECORD +30 -27
- operate/bridge/bridge_manager.py +10 -12
- operate/bridge/providers/lifi_provider.py +5 -4
- operate/bridge/providers/native_bridge_provider.py +6 -5
- operate/bridge/providers/provider.py +22 -87
- operate/bridge/providers/relay_provider.py +5 -4
- operate/cli.py +446 -168
- operate/constants.py +22 -2
- operate/keys.py +13 -0
- operate/ledger/__init__.py +107 -2
- operate/ledger/profiles.py +79 -11
- operate/operate_types.py +205 -2
- operate/quickstart/run_service.py +6 -10
- operate/services/agent_runner.py +5 -3
- operate/services/deployment_runner.py +3 -0
- operate/services/funding_manager.py +904 -0
- operate/services/health_checker.py +4 -4
- operate/services/manage.py +183 -310
- operate/services/protocol.py +392 -140
- operate/services/service.py +81 -5
- operate/settings.py +70 -0
- operate/utils/__init__.py +0 -29
- operate/utils/gnosis.py +79 -24
- operate/utils/single_instance.py +226 -0
- operate/wallet/master.py +221 -181
- operate/wallet/wallet_recovery_manager.py +5 -5
- {olas_operate_middleware-0.10.19.dist-info → olas_operate_middleware-0.11.0.dist-info}/WHEEL +0 -0
- {olas_operate_middleware-0.10.19.dist-info → olas_operate_middleware-0.11.0.dist-info}/entry_points.txt +0 -0
- {olas_operate_middleware-0.10.19.dist-info → olas_operate_middleware-0.11.0.dist-info}/licenses/LICENSE +0 -0
operate/constants.py
CHANGED
|
@@ -33,20 +33,37 @@ DEPLOYMENT_JSON = "deployment.json"
|
|
|
33
33
|
CONFIG_JSON = "config.json"
|
|
34
34
|
USER_JSON = "user.json"
|
|
35
35
|
HEALTHCHECK_JSON = "healthcheck.json"
|
|
36
|
+
VERSION_FILE = "operate.version"
|
|
37
|
+
SETTINGS_JSON = "settings.json"
|
|
38
|
+
FUNDING_REQUIREMENTS_JSON = "funding_requirements.json"
|
|
39
|
+
DEFAULT_TOPUP_THRESHOLD = 0.5
|
|
40
|
+
|
|
41
|
+
MASTER_EOA_PLACEHOLDER = "master_eoa"
|
|
42
|
+
MASTER_SAFE_PLACEHOLDER = "master_safe"
|
|
43
|
+
AGENT_EOA_PLACEHOLDER = "agent_eoa"
|
|
44
|
+
SERVICE_SAFE_PLACEHOLDER = "service_safe"
|
|
45
|
+
|
|
46
|
+
FERNET_KEY_LENGTH = 32
|
|
36
47
|
|
|
37
48
|
AGENT_PERSISTENT_STORAGE_DIR = "persistent_data"
|
|
38
49
|
AGENT_PERSISTENT_STORAGE_ENV_VAR = "STORE_PATH"
|
|
39
50
|
AGENT_LOG_DIR = "benchmarks"
|
|
40
51
|
AGENT_LOG_ENV_VAR = "LOG_DIR"
|
|
52
|
+
AGENT_RUNNER_PREFIX = "agent_runner"
|
|
41
53
|
|
|
42
54
|
ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"
|
|
43
55
|
|
|
56
|
+
MIN_AGENT_BOND = 1
|
|
57
|
+
MIN_SECURITY_DEPOSIT = 1
|
|
58
|
+
|
|
44
59
|
ON_CHAIN_INTERACT_TIMEOUT = 120.0
|
|
45
|
-
ON_CHAIN_INTERACT_RETRIES =
|
|
46
|
-
ON_CHAIN_INTERACT_SLEEP =
|
|
60
|
+
ON_CHAIN_INTERACT_RETRIES = 12
|
|
61
|
+
ON_CHAIN_INTERACT_SLEEP = 5.0
|
|
47
62
|
MIN_PASSWORD_LENGTH = 8
|
|
63
|
+
DEFAULT_FUNDING_REQUESTS_COOLDOWN_SECONDS = 300 # Seconds to wait after an agent has been funded during which it will not be asked for fund requirements again
|
|
48
64
|
|
|
49
65
|
HEALTH_CHECK_URL = "http://127.0.0.1:8716/healthcheck" # possible DNS issues on windows so use IP address
|
|
66
|
+
AGENT_FUNDS_STATUS_URL = "http://127.0.0.1:8716/funds-status"
|
|
50
67
|
SAFE_WEBAPP_URL = "https://app.safe.global/home?safe=gno:"
|
|
51
68
|
TM_CONTROL_URL = "http://localhost:8080"
|
|
52
69
|
IPFS_ADDRESS = "https://gateway.autonolas.tech/ipfs/f01701220{hash}"
|
|
@@ -63,3 +80,6 @@ NO_STAKING_PROGRAM_ID = "no_staking"
|
|
|
63
80
|
|
|
64
81
|
DEPLOYMENT_START_TRIES_NUM = 3
|
|
65
82
|
IPFS_CHECK_URL = "https://gateway.autonolas.tech/ipfs/bafybeigcllaxn4ycjjvika3zd6eicksuriez2wtg67gx7pamhcazl3tv54/echo/README.md"
|
|
83
|
+
MSG_NEW_PASSWORD_MISSING = "'new_password' is required." # nosec
|
|
84
|
+
MSG_INVALID_PASSWORD = "Password is not valid." # nosec
|
|
85
|
+
MSG_INVALID_MNEMONIC = "Seed phrase is not valid." # nosec
|
operate/keys.py
CHANGED
|
@@ -76,6 +76,19 @@ class KeysManager(metaclass=SingletonMeta):
|
|
|
76
76
|
)
|
|
77
77
|
)
|
|
78
78
|
|
|
79
|
+
def get_private_key_file(self, address: str) -> Path:
|
|
80
|
+
"""Get the path to the private key file for the given address."""
|
|
81
|
+
path = self.path / f"{address}_private_key"
|
|
82
|
+
|
|
83
|
+
if path.is_file():
|
|
84
|
+
return path
|
|
85
|
+
|
|
86
|
+
key = self.get(address)
|
|
87
|
+
private_key = key.private_key
|
|
88
|
+
path.write_text(private_key, encoding="utf-8")
|
|
89
|
+
os.chmod(path, 0o600)
|
|
90
|
+
return path
|
|
91
|
+
|
|
79
92
|
def get_crypto_instance(self, address: str) -> EthereumCrypto:
|
|
80
93
|
"""Get EthereumCrypto instance for the given address."""
|
|
81
94
|
key: Key = Key.from_json( # type: ignore
|
operate/ledger/__init__.py
CHANGED
|
@@ -20,6 +20,14 @@
|
|
|
20
20
|
"""Ledger helpers."""
|
|
21
21
|
|
|
22
22
|
import os
|
|
23
|
+
import typing as t
|
|
24
|
+
from copy import deepcopy
|
|
25
|
+
from math import ceil
|
|
26
|
+
|
|
27
|
+
from aea.crypto.base import LedgerApi
|
|
28
|
+
from aea.crypto.registries import make_ledger_api
|
|
29
|
+
from aea_ledger_ethereum import DEFAULT_GAS_PRICE_STRATEGIES, EIP1559, GWEI, to_wei
|
|
30
|
+
from web3.middleware import geth_poa_middleware
|
|
23
31
|
|
|
24
32
|
from operate.operate_types import Chain
|
|
25
33
|
|
|
@@ -85,17 +93,114 @@ CURRENCY_SMALLEST_UNITS = {
|
|
|
85
93
|
Chain.SOLANA: "Lamport",
|
|
86
94
|
}
|
|
87
95
|
|
|
96
|
+
NATIVE_CURRENCY_DECIMALS = 18
|
|
97
|
+
|
|
88
98
|
|
|
89
99
|
def get_default_rpc(chain: Chain) -> str:
|
|
90
100
|
"""Get default RPC chain type."""
|
|
91
|
-
return DEFAULT_RPCS
|
|
101
|
+
return DEFAULT_RPCS[chain]
|
|
92
102
|
|
|
93
103
|
|
|
94
104
|
def get_currency_denom(chain: Chain) -> str:
|
|
95
105
|
"""Get currency denom by chain type."""
|
|
96
|
-
return CURRENCY_DENOMS.get(chain, "
|
|
106
|
+
return CURRENCY_DENOMS.get(chain, "NATIVE")
|
|
97
107
|
|
|
98
108
|
|
|
99
109
|
def get_currency_smallest_unit(chain: Chain) -> str:
|
|
100
110
|
"""Get currency denom by chain type."""
|
|
101
111
|
return CURRENCY_SMALLEST_UNITS.get(chain, "Wei")
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
DEFAULT_LEDGER_APIS: t.Dict[Chain, LedgerApi] = {}
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def make_chain_ledger_api(
|
|
118
|
+
chain: Chain,
|
|
119
|
+
rpc: t.Optional[str] = None,
|
|
120
|
+
) -> LedgerApi:
|
|
121
|
+
"""Get default RPC chain type."""
|
|
122
|
+
|
|
123
|
+
if chain not in DEFAULT_LEDGER_APIS:
|
|
124
|
+
if chain == Chain.SOLANA: # TODO: Complete when Solana is supported
|
|
125
|
+
raise NotImplementedError("Solana not yet supported.")
|
|
126
|
+
|
|
127
|
+
gas_price_strategies = deepcopy(DEFAULT_GAS_PRICE_STRATEGIES)
|
|
128
|
+
if chain in (Chain.BASE, Chain.MODE, Chain.OPTIMISM):
|
|
129
|
+
gas_price_strategies[EIP1559]["fallback_estimate"]["maxFeePerGas"] = to_wei(
|
|
130
|
+
5, GWEI
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
ledger_api = make_ledger_api(
|
|
134
|
+
chain.ledger_type.name.lower(),
|
|
135
|
+
address=rpc or get_default_rpc(chain=chain),
|
|
136
|
+
chain_id=chain.id,
|
|
137
|
+
gas_price_strategies=gas_price_strategies,
|
|
138
|
+
poa_chain=chain == Chain.POLYGON,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
if chain == Chain.OPTIMISM:
|
|
142
|
+
ledger_api.api.middleware_onion.inject(geth_poa_middleware, layer=0)
|
|
143
|
+
|
|
144
|
+
DEFAULT_LEDGER_APIS[chain] = ledger_api
|
|
145
|
+
|
|
146
|
+
return DEFAULT_LEDGER_APIS[chain]
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def get_default_ledger_api(chain: Chain) -> LedgerApi:
|
|
150
|
+
"""Get default RPC chain type."""
|
|
151
|
+
if chain not in DEFAULT_LEDGER_APIS:
|
|
152
|
+
DEFAULT_LEDGER_APIS[chain] = make_chain_ledger_api(
|
|
153
|
+
chain=chain, rpc=get_default_rpc(chain=chain)
|
|
154
|
+
)
|
|
155
|
+
return DEFAULT_LEDGER_APIS[chain]
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
GAS_ESTIMATE_FALLBACK_ADDRESSES = [
|
|
159
|
+
"0x000000000000000000000000000000000000dEaD",
|
|
160
|
+
"0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", # nosec
|
|
161
|
+
]
|
|
162
|
+
GAS_ESTIMATE_BUFFER = 1.10
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
# TODO backport to open aea/autonomy
|
|
166
|
+
# TODO This gas pricing management should be done at a lower level in the library
|
|
167
|
+
def update_tx_with_gas_pricing(tx: t.Dict, ledger_api: LedgerApi) -> None:
|
|
168
|
+
"""Update transaction with gas pricing."""
|
|
169
|
+
tx.pop("maxFeePerGas", None)
|
|
170
|
+
tx.pop("gasPrice", None)
|
|
171
|
+
tx.pop("maxPriorityFeePerGas", None)
|
|
172
|
+
|
|
173
|
+
gas_pricing = ledger_api.try_get_gas_pricing()
|
|
174
|
+
if gas_pricing is None:
|
|
175
|
+
raise RuntimeError("Unable to retrieve gas pricing.")
|
|
176
|
+
|
|
177
|
+
if "maxFeePerGas" in gas_pricing and "maxPriorityFeePerGas" in gas_pricing:
|
|
178
|
+
tx["maxFeePerGas"] = gas_pricing["maxFeePerGas"]
|
|
179
|
+
tx["maxPriorityFeePerGas"] = gas_pricing["maxPriorityFeePerGas"]
|
|
180
|
+
elif "gasPrice" in gas_pricing:
|
|
181
|
+
tx["gasPrice"] = gas_pricing["gasPrice"]
|
|
182
|
+
else:
|
|
183
|
+
raise RuntimeError("Retrieved invalid gas pricing.")
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
# TODO backport to open aea/autonomy
|
|
187
|
+
# TODO This gas management should be done at a lower level in the library
|
|
188
|
+
def update_tx_with_gas_estimate(tx: t.Dict, ledger_api: LedgerApi) -> None:
|
|
189
|
+
"""Update transaction with gas estimate."""
|
|
190
|
+
print(f"[LEDGER] Trying to update transaction gas {tx['from']=} {tx['gas']=}.")
|
|
191
|
+
original_from = tx["from"]
|
|
192
|
+
original_gas = tx.get("gas", 1)
|
|
193
|
+
|
|
194
|
+
for address in [original_from] + GAS_ESTIMATE_FALLBACK_ADDRESSES:
|
|
195
|
+
tx["from"] = address
|
|
196
|
+
tx["gas"] = 1
|
|
197
|
+
ledger_api.update_with_gas_estimate(tx)
|
|
198
|
+
if tx["gas"] > 1:
|
|
199
|
+
print(f"[LEDGER] Gas estimated successfully {tx['from']=} {tx['gas']=}.")
|
|
200
|
+
break
|
|
201
|
+
|
|
202
|
+
tx["from"] = original_from
|
|
203
|
+
if tx["gas"] == 1:
|
|
204
|
+
tx["gas"] = original_gas
|
|
205
|
+
print(f"[LEDGER] Unable to estimate gas. Restored {tx['gas']=}.")
|
|
206
|
+
tx["gas"] = ceil(tx["gas"] * GAS_ESTIMATE_BUFFER)
|
operate/ledger/profiles.py
CHANGED
|
@@ -20,11 +20,18 @@
|
|
|
20
20
|
"""Chain profiles."""
|
|
21
21
|
|
|
22
22
|
import typing as t
|
|
23
|
+
from functools import cache
|
|
23
24
|
|
|
25
|
+
from autonomy.chain.base import registry_contracts
|
|
24
26
|
from autonomy.chain.constants import CHAIN_PROFILES, DEFAULT_MULTISEND
|
|
25
27
|
|
|
26
28
|
from operate.constants import NO_STAKING_PROGRAM_ID, ZERO_ADDRESS
|
|
27
|
-
from operate.ledger import
|
|
29
|
+
from operate.ledger import (
|
|
30
|
+
CHAINS,
|
|
31
|
+
NATIVE_CURRENCY_DECIMALS,
|
|
32
|
+
get_currency_denom,
|
|
33
|
+
get_default_ledger_api,
|
|
34
|
+
)
|
|
28
35
|
from operate.operate_types import Chain, ContractAddresses
|
|
29
36
|
|
|
30
37
|
|
|
@@ -177,7 +184,22 @@ WRAPPED_NATIVE_ASSET = {
|
|
|
177
184
|
Chain.POLYGON: "0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270",
|
|
178
185
|
}
|
|
179
186
|
|
|
180
|
-
ERC20_TOKENS =
|
|
187
|
+
ERC20_TOKENS = {
|
|
188
|
+
"OLAS": OLAS,
|
|
189
|
+
"USDC": USDC,
|
|
190
|
+
"WRAPPED_NATIVE": WRAPPED_NATIVE_ASSET,
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
DUST = {
|
|
194
|
+
Chain.ARBITRUM_ONE: int(1e14),
|
|
195
|
+
Chain.BASE: int(1e14),
|
|
196
|
+
Chain.CELO: int(1e15),
|
|
197
|
+
Chain.ETHEREUM: int(1e14),
|
|
198
|
+
Chain.GNOSIS: int(1e15),
|
|
199
|
+
Chain.MODE: int(1e14),
|
|
200
|
+
Chain.OPTIMISM: int(1e14),
|
|
201
|
+
Chain.POLYGON: int(1e14),
|
|
202
|
+
}
|
|
181
203
|
|
|
182
204
|
DEFAULT_NEW_SAFE_FUNDS: t.Dict[Chain, t.Dict[str, int]] = {
|
|
183
205
|
Chain.ARBITRUM_ONE: {
|
|
@@ -206,17 +228,24 @@ DEFAULT_NEW_SAFE_FUNDS: t.Dict[Chain, t.Dict[str, int]] = {
|
|
|
206
228
|
},
|
|
207
229
|
}
|
|
208
230
|
|
|
209
|
-
|
|
210
|
-
Chain.ARBITRUM_ONE: {ZERO_ADDRESS:
|
|
211
|
-
Chain.BASE: {ZERO_ADDRESS:
|
|
212
|
-
Chain.CELO: {ZERO_ADDRESS:
|
|
213
|
-
Chain.ETHEREUM: {ZERO_ADDRESS:
|
|
214
|
-
Chain.GNOSIS: {ZERO_ADDRESS:
|
|
215
|
-
Chain.MODE: {ZERO_ADDRESS:
|
|
216
|
-
Chain.OPTIMISM: {ZERO_ADDRESS:
|
|
217
|
-
Chain.POLYGON: {ZERO_ADDRESS:
|
|
231
|
+
DEFAULT_EOA_TOPUPS = {
|
|
232
|
+
Chain.ARBITRUM_ONE: {ZERO_ADDRESS: 2_500_000_000_000_000},
|
|
233
|
+
Chain.BASE: {ZERO_ADDRESS: 2_500_000_000_000_000},
|
|
234
|
+
Chain.CELO: {ZERO_ADDRESS: 750_000_000_000_000_000},
|
|
235
|
+
Chain.ETHEREUM: {ZERO_ADDRESS: 10_000_000_000_000_000},
|
|
236
|
+
Chain.GNOSIS: {ZERO_ADDRESS: 750_000_000_000_000_000},
|
|
237
|
+
Chain.MODE: {ZERO_ADDRESS: 250_000_000_000_000},
|
|
238
|
+
Chain.OPTIMISM: {ZERO_ADDRESS: 2_500_000_000_000_000},
|
|
239
|
+
Chain.POLYGON: {ZERO_ADDRESS: 750_000_000_000_000_000},
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
DEFAULT_EOA_TOPUPS_WITHOUT_SAFE = {
|
|
243
|
+
chain: {asset: amount * 2 for asset, amount in amounts.items()}
|
|
244
|
+
for chain, amounts in DEFAULT_EOA_TOPUPS.items()
|
|
218
245
|
}
|
|
219
246
|
|
|
247
|
+
DEFAULT_EOA_THRESHOLD = 0.5
|
|
248
|
+
|
|
220
249
|
EXPLORER_URL = {
|
|
221
250
|
Chain.ARBITRUM_ONE: {
|
|
222
251
|
"tx": "https://arbiscan.io/tx/{tx_hash}",
|
|
@@ -253,6 +282,45 @@ EXPLORER_URL = {
|
|
|
253
282
|
}
|
|
254
283
|
|
|
255
284
|
|
|
285
|
+
@cache
|
|
286
|
+
def get_asset_name(chain: Chain, asset_address: str) -> str:
|
|
287
|
+
"""Get token name."""
|
|
288
|
+
if asset_address == ZERO_ADDRESS:
|
|
289
|
+
return get_currency_denom(chain)
|
|
290
|
+
|
|
291
|
+
if WRAPPED_NATIVE_ASSET.get(chain) == asset_address:
|
|
292
|
+
return f"W{get_currency_denom(chain)}"
|
|
293
|
+
|
|
294
|
+
for symbol, tokens in ERC20_TOKENS.items():
|
|
295
|
+
if tokens.get(chain) == asset_address:
|
|
296
|
+
return symbol
|
|
297
|
+
|
|
298
|
+
return asset_address
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
@cache
|
|
302
|
+
def get_asset_decimals(chain: Chain, asset_address: str) -> int:
|
|
303
|
+
"""Get token decimals."""
|
|
304
|
+
if asset_address == ZERO_ADDRESS:
|
|
305
|
+
return NATIVE_CURRENCY_DECIMALS
|
|
306
|
+
erc20_token = registry_contracts.erc20.get_instance(
|
|
307
|
+
ledger_api=get_default_ledger_api(chain),
|
|
308
|
+
contract_address=asset_address,
|
|
309
|
+
)
|
|
310
|
+
return erc20_token.functions.decimals().call()
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def format_asset_amount(
|
|
314
|
+
chain: Chain, asset_address: str, amount: int, precision: int = 4
|
|
315
|
+
) -> str:
|
|
316
|
+
"""Convert smallest unit to human-readable string with token symbol."""
|
|
317
|
+
decimals = get_asset_decimals(chain, asset_address)
|
|
318
|
+
symbol = get_asset_name(chain, asset_address)
|
|
319
|
+
value = amount / (10**decimals)
|
|
320
|
+
return f"{value:.{precision}f} {symbol}"
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
# TODO: Deprecate in favour of StakingManager method
|
|
256
324
|
def get_staking_contract(
|
|
257
325
|
chain: str, staking_program_id: t.Optional[str]
|
|
258
326
|
) -> t.Optional[str]:
|
operate/operate_types.py
CHANGED
|
@@ -19,16 +19,22 @@
|
|
|
19
19
|
|
|
20
20
|
"""Types module."""
|
|
21
21
|
|
|
22
|
+
import base64
|
|
23
|
+
import copy
|
|
22
24
|
import enum
|
|
23
25
|
import os
|
|
24
26
|
import typing as t
|
|
25
|
-
from dataclasses import dataclass
|
|
27
|
+
from dataclasses import dataclass, field
|
|
28
|
+
from pathlib import Path
|
|
26
29
|
|
|
30
|
+
import argon2
|
|
31
|
+
from aea_ledger_ethereum import cast
|
|
27
32
|
from autonomy.chain.config import ChainType
|
|
28
33
|
from autonomy.chain.constants import CHAIN_NAME_TO_CHAIN_ID
|
|
34
|
+
from cryptography.fernet import Fernet
|
|
29
35
|
from typing_extensions import TypedDict
|
|
30
36
|
|
|
31
|
-
from operate.constants import NO_STAKING_PROGRAM_ID
|
|
37
|
+
from operate.constants import FERNET_KEY_LENGTH, NO_STAKING_PROGRAM_ID
|
|
32
38
|
from operate.resource import LocalResource
|
|
33
39
|
|
|
34
40
|
|
|
@@ -55,6 +61,11 @@ class LedgerType(str, enum.Enum):
|
|
|
55
61
|
"""Key filename."""
|
|
56
62
|
return f"{self.name.lower()}.txt"
|
|
57
63
|
|
|
64
|
+
@property
|
|
65
|
+
def mnemonic_file(self) -> str:
|
|
66
|
+
"""Mnemonic filename."""
|
|
67
|
+
return f"{self.name.lower()}.mnemonic.json"
|
|
68
|
+
|
|
58
69
|
@classmethod
|
|
59
70
|
def from_id(cls, chain_id: int) -> "LedgerType":
|
|
60
71
|
"""Load from chain ID."""
|
|
@@ -323,3 +334,195 @@ class MechMarketplaceConfig:
|
|
|
323
334
|
mech_marketplace_address: str
|
|
324
335
|
priority_mech_address: str
|
|
325
336
|
priority_mech_service_id: int
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
class Version:
|
|
340
|
+
"""Version class."""
|
|
341
|
+
|
|
342
|
+
def __init__(self, version: str) -> None:
|
|
343
|
+
"""Initialize the version."""
|
|
344
|
+
version = version.strip()
|
|
345
|
+
version_parts = version.split(".") if version else []
|
|
346
|
+
self.major = int(version_parts[0]) if len(version_parts) > 0 else 0
|
|
347
|
+
self.minor = int(version_parts[1]) if len(version_parts) > 1 else 0
|
|
348
|
+
self.patch = int(version_parts[2]) if len(version_parts) > 2 else 0
|
|
349
|
+
|
|
350
|
+
def __str__(self) -> str:
|
|
351
|
+
"""String representation of the version."""
|
|
352
|
+
return f"{self.major}.{self.minor}.{self.patch}"
|
|
353
|
+
|
|
354
|
+
def __eq__(self, other: object) -> bool:
|
|
355
|
+
"""Equality comparison."""
|
|
356
|
+
if not isinstance(other, Version):
|
|
357
|
+
return NotImplemented
|
|
358
|
+
return (
|
|
359
|
+
self.major == other.major
|
|
360
|
+
and self.minor == other.minor
|
|
361
|
+
and self.patch == other.patch
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
def __lt__(self, other: "Version") -> bool:
|
|
365
|
+
"""Less than comparison."""
|
|
366
|
+
if self.major != other.major:
|
|
367
|
+
return self.major < other.major
|
|
368
|
+
if self.minor != other.minor:
|
|
369
|
+
return self.minor < other.minor
|
|
370
|
+
return self.patch < other.patch
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
class ChainAmounts(dict[str, dict[str, dict[str, int]]]):
|
|
374
|
+
"""
|
|
375
|
+
Class that represents chain amounts as a dictionary
|
|
376
|
+
|
|
377
|
+
The standard format follows the convention {chain: {address: {token: amount}}}
|
|
378
|
+
"""
|
|
379
|
+
|
|
380
|
+
@classmethod
|
|
381
|
+
def shortfalls(
|
|
382
|
+
cls, requirements: "ChainAmounts", balances: "ChainAmounts"
|
|
383
|
+
) -> "ChainAmounts":
|
|
384
|
+
"""Return the shortfalls between requirements and balances."""
|
|
385
|
+
result: dict[str, dict[str, dict[str, int]]] = {}
|
|
386
|
+
|
|
387
|
+
for chain, addresses in requirements.items():
|
|
388
|
+
for address, assets in addresses.items():
|
|
389
|
+
for asset, required_amount in assets.items():
|
|
390
|
+
available = balances.get(chain, {}).get(address, {}).get(asset, 0)
|
|
391
|
+
shortfall = max(required_amount - available, 0)
|
|
392
|
+
result.setdefault(chain, {}).setdefault(address, {})[
|
|
393
|
+
asset
|
|
394
|
+
] = shortfall
|
|
395
|
+
|
|
396
|
+
return cls(result)
|
|
397
|
+
|
|
398
|
+
@classmethod
|
|
399
|
+
def add(cls, *chainamounts: "ChainAmounts") -> "ChainAmounts":
|
|
400
|
+
"""Add multiple ChainAmounts"""
|
|
401
|
+
result: dict[str, dict[str, dict[str, int]]] = {}
|
|
402
|
+
|
|
403
|
+
for ca in chainamounts:
|
|
404
|
+
for chain, addresses in ca.items():
|
|
405
|
+
result_addresses = result.setdefault(chain, {})
|
|
406
|
+
for address, assets in addresses.items():
|
|
407
|
+
result_assets = result_addresses.setdefault(address, {})
|
|
408
|
+
for asset, amount in assets.items():
|
|
409
|
+
result_assets[asset] = result_assets.get(asset, 0) + amount
|
|
410
|
+
|
|
411
|
+
return cls(result)
|
|
412
|
+
|
|
413
|
+
def __add__(self, other: "ChainAmounts") -> "ChainAmounts":
|
|
414
|
+
"""Add two ChainAmounts"""
|
|
415
|
+
return ChainAmounts.add(self, other)
|
|
416
|
+
|
|
417
|
+
def __mul__(self, multiplier: float) -> "ChainAmounts":
|
|
418
|
+
"""Multiply all amounts by the specified multiplier"""
|
|
419
|
+
output = copy.deepcopy(self)
|
|
420
|
+
for _, addresses in output.items():
|
|
421
|
+
for _, balances in addresses.items():
|
|
422
|
+
for asset, amount in balances.items():
|
|
423
|
+
balances[asset] = int(amount * multiplier)
|
|
424
|
+
return output
|
|
425
|
+
|
|
426
|
+
def __sub__(self, other: "ChainAmounts") -> "ChainAmounts":
|
|
427
|
+
"""Subtract two ChainAmounts"""
|
|
428
|
+
return self + (other * -1)
|
|
429
|
+
|
|
430
|
+
def __floordiv__(self, divisor: float) -> "ChainAmounts":
|
|
431
|
+
"""Divide all amounts by the specified divisor"""
|
|
432
|
+
if divisor == 0:
|
|
433
|
+
raise ValueError("Cannot divide by zero")
|
|
434
|
+
|
|
435
|
+
output = copy.deepcopy(self)
|
|
436
|
+
for _, addresses in output.items():
|
|
437
|
+
for _, balances in addresses.items():
|
|
438
|
+
for asset, amount in balances.items():
|
|
439
|
+
balances[asset] = int(amount // divisor)
|
|
440
|
+
return output
|
|
441
|
+
|
|
442
|
+
def __lt__(self, other: "ChainAmounts") -> bool:
|
|
443
|
+
"""Return True if all amounts in self are strictly less than the corresponding amounts in other."""
|
|
444
|
+
for chain, addresses in self.items():
|
|
445
|
+
for address, assets in addresses.items():
|
|
446
|
+
for asset, amount in assets.items():
|
|
447
|
+
if amount >= other.get(chain, {}).get(address, {}).get(asset, 0):
|
|
448
|
+
return False
|
|
449
|
+
return True
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
@dataclass
|
|
453
|
+
class EncryptedData(LocalResource):
|
|
454
|
+
"""EncryptedData type."""
|
|
455
|
+
|
|
456
|
+
path: Path
|
|
457
|
+
version: int
|
|
458
|
+
cipher: str
|
|
459
|
+
cipherparams: t.Dict[str, t.Union[int, str]] = field(repr=False)
|
|
460
|
+
ciphertext: str = field(repr=False)
|
|
461
|
+
kdf: str
|
|
462
|
+
kdfparams: t.Dict[str, t.Union[int, str]] = field(repr=False)
|
|
463
|
+
|
|
464
|
+
@classmethod
|
|
465
|
+
def new(cls, path: Path, password: str, plaintext_bytes: bytes) -> "EncryptedData":
|
|
466
|
+
"""Creates a new EncryptedData"""
|
|
467
|
+
ph = argon2.PasswordHasher()
|
|
468
|
+
salt = os.urandom(ph.salt_len)
|
|
469
|
+
time_cost = ph.time_cost
|
|
470
|
+
memory_cost = ph.memory_cost
|
|
471
|
+
parallelism = ph.parallelism
|
|
472
|
+
hash_len = FERNET_KEY_LENGTH
|
|
473
|
+
argon2_type = argon2.Type.ID
|
|
474
|
+
key = argon2.low_level.hash_secret_raw(
|
|
475
|
+
secret=password.encode(),
|
|
476
|
+
salt=salt,
|
|
477
|
+
time_cost=time_cost,
|
|
478
|
+
memory_cost=memory_cost,
|
|
479
|
+
parallelism=parallelism,
|
|
480
|
+
hash_len=hash_len,
|
|
481
|
+
type=argon2_type,
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
fernet_key = base64.urlsafe_b64encode(key)
|
|
485
|
+
fernet = Fernet(fernet_key)
|
|
486
|
+
ciphertext_bytes = fernet.encrypt(plaintext_bytes)
|
|
487
|
+
|
|
488
|
+
return cls(
|
|
489
|
+
path=path,
|
|
490
|
+
version=1,
|
|
491
|
+
cipher=f"{fernet.__class__.__module__}.{fernet.__class__.__qualname__}",
|
|
492
|
+
cipherparams={ # Fernet token (ciphertext variable) already stores them
|
|
493
|
+
"version": ciphertext_bytes[0]
|
|
494
|
+
},
|
|
495
|
+
ciphertext=ciphertext_bytes.hex(),
|
|
496
|
+
kdf=f"{ph.__class__.__module__}.{ph.__class__.__qualname__}",
|
|
497
|
+
kdfparams={
|
|
498
|
+
"salt": salt.hex(),
|
|
499
|
+
"time_cost": time_cost,
|
|
500
|
+
"memory_cost": memory_cost,
|
|
501
|
+
"parallelism": parallelism,
|
|
502
|
+
"hash_len": hash_len,
|
|
503
|
+
"type": argon2_type.name,
|
|
504
|
+
},
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
def decrypt(self, password: str) -> bytes:
|
|
508
|
+
"""Decrypts the EncryptedData"""
|
|
509
|
+
kdfparams = self.kdfparams
|
|
510
|
+
key = argon2.low_level.hash_secret_raw(
|
|
511
|
+
secret=password.encode(),
|
|
512
|
+
salt=bytes.fromhex(kdfparams["salt"]),
|
|
513
|
+
time_cost=kdfparams["time_cost"],
|
|
514
|
+
memory_cost=kdfparams["memory_cost"],
|
|
515
|
+
parallelism=kdfparams["parallelism"],
|
|
516
|
+
hash_len=kdfparams["hash_len"],
|
|
517
|
+
type=argon2.Type[kdfparams["type"]],
|
|
518
|
+
)
|
|
519
|
+
fernet_key = base64.urlsafe_b64encode(key)
|
|
520
|
+
fernet = Fernet(fernet_key)
|
|
521
|
+
ciphertext_bytes = bytes.fromhex(self.ciphertext)
|
|
522
|
+
plaintext_bytes = fernet.decrypt(ciphertext_bytes)
|
|
523
|
+
return plaintext_bytes
|
|
524
|
+
|
|
525
|
+
@classmethod
|
|
526
|
+
def load(cls, path: Path) -> "EncryptedData":
|
|
527
|
+
"""Load EncryptedData."""
|
|
528
|
+
return cast(EncryptedData, super().load(path))
|
|
@@ -34,12 +34,7 @@ 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
|
|
38
|
-
IPFS_ADDRESS,
|
|
39
|
-
NO_STAKING_PROGRAM_ID,
|
|
40
|
-
OPERATE_HOME,
|
|
41
|
-
USER_JSON,
|
|
42
|
-
)
|
|
37
|
+
from operate.constants import IPFS_ADDRESS, NO_STAKING_PROGRAM_ID, USER_JSON
|
|
43
38
|
from operate.data import DATA_DIR
|
|
44
39
|
from operate.data.contracts.staking_token.contract import StakingTokenContract
|
|
45
40
|
from operate.ledger.profiles import STAKING, get_staking_contract
|
|
@@ -145,7 +140,8 @@ def ask_confirm_password() -> str:
|
|
|
145
140
|
|
|
146
141
|
def load_local_config(operate: "OperateApp", service_name: str) -> QuickstartConfig:
|
|
147
142
|
"""Load the local quickstart configuration."""
|
|
148
|
-
|
|
143
|
+
operate_home = operate._path
|
|
144
|
+
old_path = operate_home / "local_config.json"
|
|
149
145
|
if old_path.exists(): # Migrate to new naming scheme
|
|
150
146
|
config = t.cast(QuickstartConfig, QuickstartConfig.load(old_path))
|
|
151
147
|
service_manager = operate.service_manager()
|
|
@@ -178,13 +174,13 @@ def load_local_config(operate: "OperateApp", service_name: str) -> QuickstartCon
|
|
|
178
174
|
shutil.move(old_path, config.path)
|
|
179
175
|
break
|
|
180
176
|
|
|
181
|
-
for qs_config in
|
|
177
|
+
for qs_config in operate_home.glob("*-quickstart-config.json"):
|
|
182
178
|
if f"{service_name}-quickstart-config.json" == qs_config.name:
|
|
183
179
|
config = t.cast(QuickstartConfig, QuickstartConfig.load(qs_config))
|
|
184
180
|
break
|
|
185
181
|
else:
|
|
186
182
|
config = QuickstartConfig(
|
|
187
|
-
|
|
183
|
+
operate_home / f"{service_name}-quickstart-config.json"
|
|
188
184
|
)
|
|
189
185
|
|
|
190
186
|
return config
|
|
@@ -694,7 +690,7 @@ def run_service(
|
|
|
694
690
|
)
|
|
695
691
|
|
|
696
692
|
print_section("Funding the service")
|
|
697
|
-
manager.
|
|
693
|
+
manager.funding_manager.fund_service_initial(service=service)
|
|
698
694
|
|
|
699
695
|
print_section("Deploying the service")
|
|
700
696
|
manager.deploy_service_locally(
|
operate/services/agent_runner.py
CHANGED
|
@@ -32,6 +32,8 @@ import requests
|
|
|
32
32
|
from aea.configurations.data_types import PublicId
|
|
33
33
|
from aea.helpers.logging import setup_logger
|
|
34
34
|
|
|
35
|
+
from operate.constants import AGENT_RUNNER_PREFIX
|
|
36
|
+
|
|
35
37
|
|
|
36
38
|
@dataclass
|
|
37
39
|
class AgentRelease:
|
|
@@ -65,10 +67,10 @@ class AgentRelease:
|
|
|
65
67
|
# list of agents releases supported
|
|
66
68
|
AGENTS_SUPPORTED = {
|
|
67
69
|
"valory/trader": AgentRelease(
|
|
68
|
-
owner="valory-xyz", repo="trader", release="v0.27.2-rc.
|
|
70
|
+
owner="valory-xyz", repo="trader", release="v0.27.2-1-rc.2"
|
|
69
71
|
),
|
|
70
72
|
"valory/optimus": AgentRelease(
|
|
71
|
-
owner="valory-xyz", repo="optimus", release="v0.0.
|
|
73
|
+
owner="valory-xyz", repo="optimus", release="v0.6.0-rc.1"
|
|
72
74
|
),
|
|
73
75
|
"dvilela/memeooorr": AgentRelease(
|
|
74
76
|
owner="valory-xyz", repo="meme-ooorr", release="v0.0.101"
|
|
@@ -101,7 +103,7 @@ class AgentRunnerManager:
|
|
|
101
103
|
else:
|
|
102
104
|
raise ValueError(f"unsupported arch: {platform.machine()}")
|
|
103
105
|
|
|
104
|
-
exec_name = f"
|
|
106
|
+
exec_name = f"{AGENT_RUNNER_PREFIX}_{os_name}_{arch}"
|
|
105
107
|
if platform.system() == "Windows":
|
|
106
108
|
exec_name += ".exe"
|
|
107
109
|
return exec_name
|
|
@@ -224,6 +224,9 @@ class BaseDeploymentRunner(AbstractDeploymentRunner, metaclass=ABCMeta):
|
|
|
224
224
|
)
|
|
225
225
|
|
|
226
226
|
self._run_aea_command("-s", "add-key", "ethereum", cwd=working_dir / "agent")
|
|
227
|
+
self._run_aea_command(
|
|
228
|
+
"-s", "add-key", "ethereum", "--connection", cwd=working_dir / "agent"
|
|
229
|
+
)
|
|
227
230
|
|
|
228
231
|
self._run_aea_command("-s", "issue-certificates", cwd=working_dir / "agent")
|
|
229
232
|
|