olas-operate-middleware 0.1.0rc59__py3-none-any.whl → 0.13.2__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.13.2.dist-info/METADATA +75 -0
- olas_operate_middleware-0.13.2.dist-info/RECORD +101 -0
- {olas_operate_middleware-0.1.0rc59.dist-info → olas_operate_middleware-0.13.2.dist-info}/WHEEL +1 -1
- operate/__init__.py +17 -0
- operate/account/user.py +35 -9
- operate/bridge/bridge_manager.py +470 -0
- operate/bridge/providers/lifi_provider.py +377 -0
- operate/bridge/providers/native_bridge_provider.py +677 -0
- operate/bridge/providers/provider.py +469 -0
- operate/bridge/providers/relay_provider.py +457 -0
- operate/cli.py +1565 -417
- operate/constants.py +60 -12
- operate/data/README.md +19 -0
- operate/data/contracts/{service_staking_token → dual_staking_token}/__init__.py +2 -2
- operate/data/contracts/dual_staking_token/build/DualStakingToken.json +443 -0
- operate/data/contracts/dual_staking_token/contract.py +132 -0
- operate/data/contracts/dual_staking_token/contract.yaml +23 -0
- operate/{ledger/base.py → data/contracts/foreign_omnibridge/__init__.py} +2 -19
- operate/data/contracts/foreign_omnibridge/build/ForeignOmnibridge.json +1372 -0
- operate/data/contracts/foreign_omnibridge/contract.py +130 -0
- operate/data/contracts/foreign_omnibridge/contract.yaml +23 -0
- operate/{ledger/solana.py → data/contracts/home_omnibridge/__init__.py} +2 -20
- operate/data/contracts/home_omnibridge/build/HomeOmnibridge.json +1421 -0
- operate/data/contracts/home_omnibridge/contract.py +80 -0
- operate/data/contracts/home_omnibridge/contract.yaml +23 -0
- operate/data/contracts/l1_standard_bridge/__init__.py +20 -0
- operate/data/contracts/l1_standard_bridge/build/L1StandardBridge.json +831 -0
- operate/data/contracts/l1_standard_bridge/contract.py +158 -0
- operate/data/contracts/l1_standard_bridge/contract.yaml +23 -0
- operate/data/contracts/l2_standard_bridge/__init__.py +20 -0
- operate/data/contracts/l2_standard_bridge/build/L2StandardBridge.json +626 -0
- operate/data/contracts/l2_standard_bridge/contract.py +130 -0
- operate/data/contracts/l2_standard_bridge/contract.yaml +23 -0
- operate/data/contracts/mech_activity/__init__.py +20 -0
- operate/data/contracts/mech_activity/build/MechActivity.json +111 -0
- operate/data/contracts/mech_activity/contract.py +44 -0
- operate/data/contracts/mech_activity/contract.yaml +23 -0
- operate/data/contracts/optimism_mintable_erc20/__init__.py +20 -0
- operate/data/contracts/optimism_mintable_erc20/build/OptimismMintableERC20.json +491 -0
- operate/data/contracts/optimism_mintable_erc20/contract.py +45 -0
- operate/data/contracts/optimism_mintable_erc20/contract.yaml +23 -0
- operate/data/contracts/recovery_module/__init__.py +20 -0
- operate/data/contracts/recovery_module/build/RecoveryModule.json +811 -0
- operate/data/contracts/recovery_module/contract.py +61 -0
- operate/data/contracts/recovery_module/contract.yaml +23 -0
- operate/data/contracts/requester_activity_checker/__init__.py +20 -0
- operate/data/contracts/requester_activity_checker/build/RequesterActivityChecker.json +111 -0
- operate/data/contracts/requester_activity_checker/contract.py +33 -0
- operate/data/contracts/requester_activity_checker/contract.yaml +23 -0
- operate/data/contracts/staking_token/__init__.py +20 -0
- operate/data/contracts/staking_token/build/StakingToken.json +1336 -0
- operate/data/contracts/{service_staking_token → staking_token}/contract.py +27 -13
- operate/data/contracts/staking_token/contract.yaml +23 -0
- operate/data/contracts/uniswap_v2_erc20/contract.yaml +3 -1
- operate/data/contracts/uniswap_v2_erc20/tests/__init__.py +20 -0
- operate/data/contracts/uniswap_v2_erc20/tests/test_contract.py +363 -0
- operate/keys.py +118 -33
- operate/ledger/__init__.py +159 -56
- operate/ledger/profiles.py +321 -18
- operate/migration.py +555 -0
- operate/{http → operate_http}/__init__.py +3 -2
- operate/{http → operate_http}/exceptions.py +6 -4
- operate/operate_types.py +544 -0
- operate/pearl.py +13 -1
- operate/quickstart/analyse_logs.py +118 -0
- operate/quickstart/claim_staking_rewards.py +104 -0
- operate/quickstart/reset_configs.py +106 -0
- operate/quickstart/reset_password.py +70 -0
- operate/quickstart/reset_staking.py +145 -0
- operate/quickstart/run_service.py +726 -0
- operate/quickstart/stop_service.py +72 -0
- operate/quickstart/terminate_on_chain_service.py +83 -0
- operate/quickstart/utils.py +298 -0
- operate/resource.py +62 -3
- operate/services/agent_runner.py +202 -0
- operate/services/deployment_runner.py +868 -0
- operate/services/funding_manager.py +929 -0
- operate/services/health_checker.py +280 -0
- operate/services/manage.py +2356 -620
- operate/services/protocol.py +1246 -340
- operate/services/service.py +756 -391
- operate/services/utils/mech.py +103 -0
- operate/services/utils/tendermint.py +86 -12
- operate/settings.py +70 -0
- operate/utils/__init__.py +135 -0
- operate/utils/gnosis.py +407 -80
- operate/utils/single_instance.py +226 -0
- operate/utils/ssl.py +133 -0
- operate/wallet/master.py +708 -123
- operate/wallet/wallet_recovery_manager.py +507 -0
- olas_operate_middleware-0.1.0rc59.dist-info/METADATA +0 -304
- olas_operate_middleware-0.1.0rc59.dist-info/RECORD +0 -41
- operate/data/contracts/service_staking_token/build/ServiceStakingToken.json +0 -1273
- operate/data/contracts/service_staking_token/contract.yaml +0 -23
- operate/ledger/ethereum.py +0 -48
- operate/types.py +0 -260
- {olas_operate_middleware-0.1.0rc59.dist-info → olas_operate_middleware-0.13.2.dist-info}/entry_points.txt +0 -0
- {olas_operate_middleware-0.1.0rc59.dist-info → olas_operate_middleware-0.13.2.dist-info/licenses}/LICENSE +0 -0
|
@@ -0,0 +1,726 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# ------------------------------------------------------------------------------
|
|
3
|
+
#
|
|
4
|
+
# Copyright 2024 Valory AG
|
|
5
|
+
#
|
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
# you may not use this file except in compliance with the License.
|
|
8
|
+
# You may obtain a copy of the License at
|
|
9
|
+
#
|
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
#
|
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
# See the License for the specific language governing permissions and
|
|
16
|
+
# limitations under the License.
|
|
17
|
+
#
|
|
18
|
+
# ------------------------------------------------------------------------------
|
|
19
|
+
"""Agent Quickstart script."""
|
|
20
|
+
|
|
21
|
+
import json
|
|
22
|
+
import os
|
|
23
|
+
import shutil
|
|
24
|
+
import textwrap
|
|
25
|
+
import time
|
|
26
|
+
import typing as t
|
|
27
|
+
import warnings
|
|
28
|
+
from http import HTTPStatus
|
|
29
|
+
|
|
30
|
+
import requests
|
|
31
|
+
from aea.crypto.registries import make_ledger_api
|
|
32
|
+
from aea_ledger_ethereum import LedgerApi
|
|
33
|
+
from halo import Halo # type: ignore[import]
|
|
34
|
+
from web3.exceptions import Web3Exception
|
|
35
|
+
|
|
36
|
+
from operate.account.user import UserAccount
|
|
37
|
+
from operate.constants import IPFS_ADDRESS, NO_STAKING_PROGRAM_ID, USER_JSON
|
|
38
|
+
from operate.data import DATA_DIR
|
|
39
|
+
from operate.data.contracts.staking_token.contract import StakingTokenContract
|
|
40
|
+
from operate.ledger import DEFAULT_RPCS
|
|
41
|
+
from operate.ledger.profiles import STAKING, get_staking_contract
|
|
42
|
+
from operate.operate_types import (
|
|
43
|
+
Chain,
|
|
44
|
+
LedgerType,
|
|
45
|
+
ServiceEnvProvisionType,
|
|
46
|
+
ServiceTemplate,
|
|
47
|
+
)
|
|
48
|
+
from operate.quickstart.utils import (
|
|
49
|
+
CHAIN_TO_METADATA,
|
|
50
|
+
QuickstartConfig,
|
|
51
|
+
ask_or_get_from_env,
|
|
52
|
+
check_rpc,
|
|
53
|
+
print_box,
|
|
54
|
+
print_section,
|
|
55
|
+
print_title,
|
|
56
|
+
wei_to_token,
|
|
57
|
+
)
|
|
58
|
+
from operate.services.manage import ServiceManager
|
|
59
|
+
from operate.services.service import Service
|
|
60
|
+
from operate.utils.gnosis import get_asset_balance
|
|
61
|
+
from operate.wallet.master import MasterWallet
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
warnings.filterwarnings("ignore", category=UserWarning)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
if t.TYPE_CHECKING:
|
|
68
|
+
from operate.cli import OperateApp
|
|
69
|
+
|
|
70
|
+
NO_STAKING_PROGRAM_METADATA = {
|
|
71
|
+
"name": "No staking",
|
|
72
|
+
"description": "Your agent will still work as expected, but it will not be staked within any staking program.",
|
|
73
|
+
}
|
|
74
|
+
CUSTOM_PROGRAM_ID = "custom_staking"
|
|
75
|
+
QS_STAKING_PROGRAMS: t.Dict[Chain, t.Dict[str, str]] = {
|
|
76
|
+
Chain.GNOSIS: {
|
|
77
|
+
"quickstart_beta_hobbyist": "trader",
|
|
78
|
+
"quickstart_beta_hobbyist_2": "trader",
|
|
79
|
+
"quickstart_beta_expert": "trader",
|
|
80
|
+
"quickstart_beta_expert_2": "trader",
|
|
81
|
+
"quickstart_beta_expert_3": "trader",
|
|
82
|
+
"quickstart_beta_expert_4": "trader",
|
|
83
|
+
"quickstart_beta_expert_5": "trader",
|
|
84
|
+
"quickstart_beta_expert_6": "trader",
|
|
85
|
+
"quickstart_beta_expert_7": "trader",
|
|
86
|
+
"quickstart_beta_expert_8": "trader",
|
|
87
|
+
"quickstart_beta_expert_9": "trader",
|
|
88
|
+
"quickstart_beta_expert_10": "trader",
|
|
89
|
+
"quickstart_beta_expert_11": "trader",
|
|
90
|
+
"quickstart_beta_expert_12": "trader",
|
|
91
|
+
"quickstart_beta_expert_15_mech_marketplace": "trader",
|
|
92
|
+
"quickstart_beta_expert_16_mech_marketplace": "trader",
|
|
93
|
+
"quickstart_beta_expert_17_mech_marketplace": "trader",
|
|
94
|
+
"quickstart_beta_expert_18_mech_marketplace": "trader",
|
|
95
|
+
"quickstart_beta_mech_marketplace_expert_1": "trader",
|
|
96
|
+
"quickstart_beta_mech_marketplace_expert_2": "trader",
|
|
97
|
+
"quickstart_beta_mech_marketplace_expert_3": "trader",
|
|
98
|
+
"quickstart_beta_mech_marketplace_expert_4": "trader",
|
|
99
|
+
"quickstart_beta_mech_marketplace_expert_5": "trader",
|
|
100
|
+
"quickstart_beta_mech_marketplace_expert_6": "trader",
|
|
101
|
+
"quickstart_beta_mech_marketplace_expert_7": "trader",
|
|
102
|
+
"quickstart_beta_mech_marketplace_expert_8": "trader",
|
|
103
|
+
"quickstart_beta_mech_marketplace_expert_9": "trader",
|
|
104
|
+
"quickstart_beta_mech_marketplace_expert_10": "trader",
|
|
105
|
+
"mech_marketplace": "mech",
|
|
106
|
+
"marketplace_supply_alpha": "mech",
|
|
107
|
+
},
|
|
108
|
+
Chain.OPTIMISM: {
|
|
109
|
+
"optimus_alpha_2": "optimus",
|
|
110
|
+
"optimus_alpha_3": "optimus",
|
|
111
|
+
"optimus_alpha_4": "optimus",
|
|
112
|
+
},
|
|
113
|
+
Chain.ETHEREUM: {},
|
|
114
|
+
Chain.BASE: {
|
|
115
|
+
"meme_base_alpha_2": "memeooorr",
|
|
116
|
+
"marketplace_supply_alpha": "mech",
|
|
117
|
+
"agents_fun_1": "memeooorr",
|
|
118
|
+
"agents_fun_2": "memeooorr",
|
|
119
|
+
"agents_fun_3": "memeooorr",
|
|
120
|
+
},
|
|
121
|
+
Chain.CELO: {},
|
|
122
|
+
Chain.MODE: {
|
|
123
|
+
"optimus_alpha": "modius",
|
|
124
|
+
},
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def ask_confirm_password() -> str:
|
|
129
|
+
"""Ask for password confirmation."""
|
|
130
|
+
while True:
|
|
131
|
+
password = ask_or_get_from_env(
|
|
132
|
+
"Please input your password (or press enter): ", True, "OPERATE_PASSWORD"
|
|
133
|
+
)
|
|
134
|
+
confirm_password = ask_or_get_from_env(
|
|
135
|
+
"Please confirm your password: ", True, "OPERATE_PASSWORD"
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
if password == confirm_password:
|
|
139
|
+
return password
|
|
140
|
+
else:
|
|
141
|
+
print("Passwords do not match!")
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def load_local_config(operate: "OperateApp", service_name: str) -> QuickstartConfig:
|
|
145
|
+
"""Load the local quickstart configuration."""
|
|
146
|
+
operate_home = operate._path
|
|
147
|
+
old_path = operate_home / "local_config.json"
|
|
148
|
+
if old_path.exists(): # Migrate to new naming scheme
|
|
149
|
+
config = t.cast(QuickstartConfig, QuickstartConfig.load(old_path))
|
|
150
|
+
service_manager = operate.service_manager()
|
|
151
|
+
services = service_manager.json
|
|
152
|
+
if config.staking_program_id == NO_STAKING_PROGRAM_ID:
|
|
153
|
+
for service in services:
|
|
154
|
+
if service["name"] == service_name:
|
|
155
|
+
config.path = (
|
|
156
|
+
config.path.parent / f"{service_name}-quickstart-config.json"
|
|
157
|
+
)
|
|
158
|
+
shutil.move(old_path, config.path)
|
|
159
|
+
break
|
|
160
|
+
else:
|
|
161
|
+
for staking_program, _agent_keyword in QS_STAKING_PROGRAMS[
|
|
162
|
+
Chain.from_string(config.principal_chain)
|
|
163
|
+
].items():
|
|
164
|
+
if staking_program == config.staking_program_id:
|
|
165
|
+
break
|
|
166
|
+
else:
|
|
167
|
+
raise ValueError(
|
|
168
|
+
f"Staking program {config.staking_program_id} not found in {QS_STAKING_PROGRAMS[Chain.from_string(config.principal_chain)].keys()}.\n"
|
|
169
|
+
"Please resolve manually!"
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
for service in services:
|
|
173
|
+
if _agent_keyword in service["name"].lower():
|
|
174
|
+
config.path = (
|
|
175
|
+
config.path.parent / f"{service['name']}-quickstart-config.json"
|
|
176
|
+
)
|
|
177
|
+
shutil.move(old_path, config.path)
|
|
178
|
+
break
|
|
179
|
+
|
|
180
|
+
for qs_config in operate_home.glob("*-quickstart-config.json"):
|
|
181
|
+
if f"{service_name}-quickstart-config.json" == qs_config.name:
|
|
182
|
+
config = t.cast(QuickstartConfig, QuickstartConfig.load(qs_config))
|
|
183
|
+
break
|
|
184
|
+
else:
|
|
185
|
+
config = QuickstartConfig(
|
|
186
|
+
operate_home / f"{service_name}-quickstart-config.json"
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
return config
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def configure_local_config(
|
|
193
|
+
template: ServiceTemplate, operate: "OperateApp"
|
|
194
|
+
) -> QuickstartConfig:
|
|
195
|
+
"""Configure local quickstart configuration."""
|
|
196
|
+
config = load_local_config(operate=operate, service_name=template["name"])
|
|
197
|
+
|
|
198
|
+
if config.rpc is None:
|
|
199
|
+
config.rpc = {}
|
|
200
|
+
|
|
201
|
+
for chain in template["configurations"]:
|
|
202
|
+
while not check_rpc(chain, config.rpc.get(chain)):
|
|
203
|
+
config.rpc[chain] = ask_or_get_from_env(
|
|
204
|
+
f"Enter a {CHAIN_TO_METADATA[chain]['name']} RPC that supports eth_newFilter [hidden input]: ",
|
|
205
|
+
True,
|
|
206
|
+
f"{chain.upper()}_LEDGER_RPC",
|
|
207
|
+
)
|
|
208
|
+
os.environ[f"{chain.upper()}_LEDGER_RPC"] = config.rpc[chain]
|
|
209
|
+
DEFAULT_RPCS[Chain.from_string(chain)] = config.rpc[chain]
|
|
210
|
+
|
|
211
|
+
config.principal_chain = template["home_chain"]
|
|
212
|
+
|
|
213
|
+
home_chain = Chain.from_string(config.principal_chain)
|
|
214
|
+
staking_ctr = t.cast(
|
|
215
|
+
StakingTokenContract,
|
|
216
|
+
StakingTokenContract.from_dir(
|
|
217
|
+
directory=str(DATA_DIR / "contracts" / "staking_token")
|
|
218
|
+
),
|
|
219
|
+
)
|
|
220
|
+
ledger_api = make_ledger_api(
|
|
221
|
+
LedgerType.ETHEREUM.lower(),
|
|
222
|
+
address=config.rpc[config.principal_chain], # type: ignore[index]
|
|
223
|
+
chain_id=home_chain.id,
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
if config.staking_program_id is None:
|
|
227
|
+
print_section("Please, select your staking program preference")
|
|
228
|
+
available_choices = {}
|
|
229
|
+
ids = (
|
|
230
|
+
[NO_STAKING_PROGRAM_ID]
|
|
231
|
+
+ [
|
|
232
|
+
id
|
|
233
|
+
for id in STAKING[home_chain]
|
|
234
|
+
if id in QS_STAKING_PROGRAMS[home_chain]
|
|
235
|
+
and QS_STAKING_PROGRAMS[home_chain][id] in template["name"].lower()
|
|
236
|
+
]
|
|
237
|
+
+ [CUSTOM_PROGRAM_ID]
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
for index, program_id in enumerate(ids):
|
|
241
|
+
if program_id == NO_STAKING_PROGRAM_ID:
|
|
242
|
+
metadata = NO_STAKING_PROGRAM_METADATA
|
|
243
|
+
elif program_id == CUSTOM_PROGRAM_ID:
|
|
244
|
+
metadata = {
|
|
245
|
+
"name": "Custom Staking contract",
|
|
246
|
+
"description": "If you choose this option, you will be asked to provide the staking contract address.",
|
|
247
|
+
}
|
|
248
|
+
else:
|
|
249
|
+
instance = staking_ctr.get_instance(
|
|
250
|
+
ledger_api=ledger_api,
|
|
251
|
+
contract_address=STAKING[home_chain][program_id],
|
|
252
|
+
)
|
|
253
|
+
try:
|
|
254
|
+
metadata_hash = instance.functions.metadataHash().call().hex()
|
|
255
|
+
ipfs_address = IPFS_ADDRESS.format(hash=metadata_hash)
|
|
256
|
+
response = requests.get(ipfs_address)
|
|
257
|
+
if response.status_code != HTTPStatus.OK:
|
|
258
|
+
raise requests.RequestException(
|
|
259
|
+
f"Failed to fetch data from {ipfs_address}: {response.status_code}"
|
|
260
|
+
)
|
|
261
|
+
metadata = response.json()
|
|
262
|
+
except (Web3Exception, requests.RequestException):
|
|
263
|
+
metadata = {
|
|
264
|
+
"name": program_id,
|
|
265
|
+
"description": program_id,
|
|
266
|
+
"available_staking_slots": "?",
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
# Add staking slots count to successful response
|
|
270
|
+
try:
|
|
271
|
+
max_services = instance.functions.maxNumServices().call()
|
|
272
|
+
current_services = instance.functions.getServiceIds().call()
|
|
273
|
+
metadata["available_staking_slots"] = max_services - len(
|
|
274
|
+
current_services
|
|
275
|
+
)
|
|
276
|
+
except Web3Exception:
|
|
277
|
+
metadata["available_staking_slots"] = "?"
|
|
278
|
+
|
|
279
|
+
name = metadata["name"]
|
|
280
|
+
description = metadata["description"]
|
|
281
|
+
if "available_staking_slots" in metadata:
|
|
282
|
+
available_slots_str = (
|
|
283
|
+
f"(available slots : {metadata['available_staking_slots']})"
|
|
284
|
+
)
|
|
285
|
+
else:
|
|
286
|
+
available_slots_str = ""
|
|
287
|
+
|
|
288
|
+
wrapped_description = textwrap.fill(
|
|
289
|
+
description, width=80, initial_indent=" ", subsequent_indent=" "
|
|
290
|
+
)
|
|
291
|
+
print(
|
|
292
|
+
f"{index + 1}) {name}\t{available_slots_str}\n{wrapped_description}\n"
|
|
293
|
+
)
|
|
294
|
+
if available_slots_str or program_id in (
|
|
295
|
+
NO_STAKING_PROGRAM_ID,
|
|
296
|
+
CUSTOM_PROGRAM_ID,
|
|
297
|
+
):
|
|
298
|
+
available_choices[index + 1] = {
|
|
299
|
+
"program_id": program_id,
|
|
300
|
+
"slots": available_slots_str,
|
|
301
|
+
"name": name,
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
while True:
|
|
305
|
+
try:
|
|
306
|
+
input_value = ask_or_get_from_env(
|
|
307
|
+
f"Enter your choice (1 - {len(ids)}): ", False, "STAKING_PROGRAM"
|
|
308
|
+
)
|
|
309
|
+
try:
|
|
310
|
+
choice = int(input_value)
|
|
311
|
+
if choice not in available_choices:
|
|
312
|
+
print("\nPlease select a program with available slots:")
|
|
313
|
+
for idx, prog in available_choices.items():
|
|
314
|
+
print(f"{idx}) {prog['name']} : {prog['slots']}")
|
|
315
|
+
continue
|
|
316
|
+
selected_program = available_choices[choice]
|
|
317
|
+
config.staking_program_id = selected_program["program_id"]
|
|
318
|
+
print(f"Selected staking program: {selected_program['name']}")
|
|
319
|
+
break
|
|
320
|
+
except ValueError:
|
|
321
|
+
if input_value in ids:
|
|
322
|
+
config.staking_program_id = input_value
|
|
323
|
+
break
|
|
324
|
+
else:
|
|
325
|
+
raise ValueError(f"STAKING_PROGRAM must be one of {ids}")
|
|
326
|
+
except Exception as e:
|
|
327
|
+
print(f"Error in getting input: {str(e)}")
|
|
328
|
+
raise
|
|
329
|
+
|
|
330
|
+
if config.staking_program_id == CUSTOM_PROGRAM_ID:
|
|
331
|
+
while True:
|
|
332
|
+
try:
|
|
333
|
+
config.staking_program_id = ask_or_get_from_env(
|
|
334
|
+
"Enter the staking contract address: ",
|
|
335
|
+
False,
|
|
336
|
+
"STAKING_CONTRACT_ADDRESS",
|
|
337
|
+
)
|
|
338
|
+
instance = staking_ctr.get_instance(
|
|
339
|
+
ledger_api=ledger_api,
|
|
340
|
+
contract_address=config.staking_program_id,
|
|
341
|
+
)
|
|
342
|
+
max_services = instance.functions.maxNumServices().call()
|
|
343
|
+
current_services = instance.functions.getServiceIds().call()
|
|
344
|
+
available_slots = max_services - len(current_services)
|
|
345
|
+
if available_slots > 0:
|
|
346
|
+
print(f"Found {available_slots} available staking slots.")
|
|
347
|
+
break
|
|
348
|
+
else:
|
|
349
|
+
print(
|
|
350
|
+
"No available staking slots found. Please enter another address."
|
|
351
|
+
)
|
|
352
|
+
except Exception:
|
|
353
|
+
print("This address is not a valid staking contract address.")
|
|
354
|
+
|
|
355
|
+
# set chain configs in the service template
|
|
356
|
+
for chain in template["configurations"]:
|
|
357
|
+
if chain == config.principal_chain:
|
|
358
|
+
staking_contract_address = get_staking_contract(
|
|
359
|
+
chain, config.staking_program_id
|
|
360
|
+
)
|
|
361
|
+
if staking_contract_address is None:
|
|
362
|
+
min_staking_deposit = 1
|
|
363
|
+
else:
|
|
364
|
+
instance = staking_ctr.get_instance(
|
|
365
|
+
ledger_api=ledger_api,
|
|
366
|
+
contract_address=staking_contract_address,
|
|
367
|
+
)
|
|
368
|
+
min_staking_deposit = int(instance.functions.minStakingDeposit().call())
|
|
369
|
+
|
|
370
|
+
template["configurations"][chain] |= {
|
|
371
|
+
"staking_program_id": config.staking_program_id,
|
|
372
|
+
"rpc": config.rpc[chain],
|
|
373
|
+
"cost_of_bond": min_staking_deposit,
|
|
374
|
+
}
|
|
375
|
+
else:
|
|
376
|
+
template["configurations"][chain] |= {
|
|
377
|
+
"staking_program_id": NO_STAKING_PROGRAM_ID,
|
|
378
|
+
"rpc": config.rpc[chain],
|
|
379
|
+
"cost_of_bond": 1,
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if config.user_provided_args is None:
|
|
383
|
+
config.user_provided_args = {}
|
|
384
|
+
|
|
385
|
+
if any(
|
|
386
|
+
(
|
|
387
|
+
env_var_data["provision_type"] == ServiceEnvProvisionType.USER
|
|
388
|
+
and env_var_name not in config.user_provided_args
|
|
389
|
+
)
|
|
390
|
+
for env_var_name, env_var_data in template["env_variables"].items()
|
|
391
|
+
):
|
|
392
|
+
print_section("Please enter the arguments that will be used by the service.")
|
|
393
|
+
|
|
394
|
+
service_manager = operate.service_manager()
|
|
395
|
+
mech_configs = service_manager.get_mech_configs(
|
|
396
|
+
chain=config.principal_chain,
|
|
397
|
+
ledger_api=ledger_api,
|
|
398
|
+
staking_program_id=config.staking_program_id,
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
for env_var_name, env_var_data in template["env_variables"].items():
|
|
402
|
+
if env_var_data["provision_type"] == ServiceEnvProvisionType.USER:
|
|
403
|
+
# PRIORITY_MECH_ADDRESS and PRIORITY_MECH_SERVICE_ID are given dynamic default values
|
|
404
|
+
if env_var_name == "PRIORITY_MECH_ADDRESS":
|
|
405
|
+
env_var_data["value"] = mech_configs.priority_mech_address
|
|
406
|
+
if (
|
|
407
|
+
env_var_name in config.user_provided_args
|
|
408
|
+
and env_var_data["value"] != config.user_provided_args[env_var_name]
|
|
409
|
+
):
|
|
410
|
+
del config.user_provided_args[env_var_name]
|
|
411
|
+
|
|
412
|
+
if env_var_name == "PRIORITY_MECH_SERVICE_ID":
|
|
413
|
+
env_var_data["value"] = mech_configs.priority_mech_service_id
|
|
414
|
+
if (
|
|
415
|
+
env_var_name in config.user_provided_args
|
|
416
|
+
and env_var_data["value"] != config.user_provided_args[env_var_name]
|
|
417
|
+
):
|
|
418
|
+
del config.user_provided_args[env_var_name]
|
|
419
|
+
|
|
420
|
+
if env_var_name not in config.user_provided_args:
|
|
421
|
+
print(f"Description: {env_var_data['description']}")
|
|
422
|
+
if env_var_data["value"] is not None and env_var_data["value"] != "":
|
|
423
|
+
print(f"Default: {env_var_data['value']}")
|
|
424
|
+
|
|
425
|
+
user_provided_arg = ask_or_get_from_env(
|
|
426
|
+
f"Please enter {env_var_data['name']}: ", False, env_var_name
|
|
427
|
+
)
|
|
428
|
+
config.user_provided_args[env_var_name] = env_var_data["value"]
|
|
429
|
+
if user_provided_arg:
|
|
430
|
+
config.user_provided_args[env_var_name] = user_provided_arg
|
|
431
|
+
|
|
432
|
+
print()
|
|
433
|
+
|
|
434
|
+
template["env_variables"][env_var_name][
|
|
435
|
+
"value"
|
|
436
|
+
] = config.user_provided_args[env_var_name]
|
|
437
|
+
|
|
438
|
+
# TODO: Handle it in a more generic way
|
|
439
|
+
if (
|
|
440
|
+
template["env_variables"][env_var_name]["provision_type"]
|
|
441
|
+
== ServiceEnvProvisionType.COMPUTED
|
|
442
|
+
and "SUBGRAPH_API_KEY" in config.user_provided_args
|
|
443
|
+
and "{SUBGRAPH_API_KEY}" in template["env_variables"][env_var_name]["value"]
|
|
444
|
+
):
|
|
445
|
+
template["env_variables"][env_var_name]["value"] = template[
|
|
446
|
+
"env_variables"
|
|
447
|
+
][env_var_name]["value"].format(
|
|
448
|
+
SUBGRAPH_API_KEY=config.user_provided_args["SUBGRAPH_API_KEY"],
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
config.store()
|
|
452
|
+
return config
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
def ask_password_if_needed(operate: "OperateApp") -> None:
|
|
456
|
+
"""Ask password if needed."""
|
|
457
|
+
if operate.user_account is None:
|
|
458
|
+
print_section("Set up local user account")
|
|
459
|
+
print("Creating a new local user account...")
|
|
460
|
+
password = ask_confirm_password()
|
|
461
|
+
UserAccount.new(
|
|
462
|
+
password=password,
|
|
463
|
+
path=operate._path / USER_JSON,
|
|
464
|
+
)
|
|
465
|
+
else:
|
|
466
|
+
_password = None
|
|
467
|
+
while _password is None:
|
|
468
|
+
_password = ask_or_get_from_env(
|
|
469
|
+
"\nEnter local user account password [hidden input]: ",
|
|
470
|
+
True,
|
|
471
|
+
"OPERATE_PASSWORD",
|
|
472
|
+
)
|
|
473
|
+
if operate.user_account.is_valid(password=_password):
|
|
474
|
+
break
|
|
475
|
+
_password = None
|
|
476
|
+
print("Invalid password!")
|
|
477
|
+
|
|
478
|
+
password = _password
|
|
479
|
+
|
|
480
|
+
operate.password = password
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
def get_service(manager: ServiceManager, template: ServiceTemplate) -> Service:
|
|
484
|
+
"""Get service."""
|
|
485
|
+
for service in manager.json:
|
|
486
|
+
if service["name"] == template["name"]:
|
|
487
|
+
old_hash = service["hash"]
|
|
488
|
+
old_version = service["agent_release"]["repository"]["version"]
|
|
489
|
+
if (
|
|
490
|
+
old_hash == template["hash"]
|
|
491
|
+
and old_version == template["agent_release"]["repository"]["version"]
|
|
492
|
+
):
|
|
493
|
+
print(f'Loading service {template["hash"]}')
|
|
494
|
+
service = manager.load(
|
|
495
|
+
service_config_id=service["service_config_id"],
|
|
496
|
+
)
|
|
497
|
+
else:
|
|
498
|
+
print(f"Updating service from {old_hash} to " + template["hash"])
|
|
499
|
+
service = manager.update(
|
|
500
|
+
service_config_id=service["service_config_id"],
|
|
501
|
+
service_template=template,
|
|
502
|
+
)
|
|
503
|
+
|
|
504
|
+
for env_var_name, env_var_data in template["env_variables"].items():
|
|
505
|
+
if env_var_name not in service.env_variables:
|
|
506
|
+
service.env_variables[env_var_name] = env_var_data
|
|
507
|
+
|
|
508
|
+
service.update_user_params_from_template(service_template=template)
|
|
509
|
+
service.store()
|
|
510
|
+
break
|
|
511
|
+
else:
|
|
512
|
+
print(f'Creating service {template["hash"]}')
|
|
513
|
+
service = manager.load_or_create(
|
|
514
|
+
hash=template["hash"],
|
|
515
|
+
service_template=template,
|
|
516
|
+
)
|
|
517
|
+
|
|
518
|
+
return service
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
def ask_funds_in_address(
|
|
522
|
+
ledger_api: LedgerApi,
|
|
523
|
+
required_balance: int,
|
|
524
|
+
asset_address: str,
|
|
525
|
+
recipient_name: str,
|
|
526
|
+
recipient_address: str,
|
|
527
|
+
chain: str,
|
|
528
|
+
) -> None:
|
|
529
|
+
"""Ask for funds in address."""
|
|
530
|
+
if required_balance == 0:
|
|
531
|
+
return
|
|
532
|
+
|
|
533
|
+
current_balance = get_asset_balance(ledger_api, asset_address, recipient_address)
|
|
534
|
+
print(
|
|
535
|
+
f"[{chain}] Please transfer at least {wei_to_token(required_balance, chain, asset_address)} "
|
|
536
|
+
f"to the {recipient_name} {recipient_address} "
|
|
537
|
+
)
|
|
538
|
+
spinner = Halo(
|
|
539
|
+
text=f"[{chain}] Waiting for at least {wei_to_token(required_balance, chain, asset_address)}...",
|
|
540
|
+
spinner="dots",
|
|
541
|
+
)
|
|
542
|
+
spinner.start()
|
|
543
|
+
|
|
544
|
+
while True:
|
|
545
|
+
time.sleep(1)
|
|
546
|
+
updated_balance = get_asset_balance(
|
|
547
|
+
ledger_api, asset_address, recipient_address
|
|
548
|
+
)
|
|
549
|
+
if updated_balance >= current_balance + required_balance:
|
|
550
|
+
break
|
|
551
|
+
|
|
552
|
+
remaining_requirement = current_balance + required_balance - updated_balance
|
|
553
|
+
spinner.text = f"[{chain}] Waiting for at least {wei_to_token(remaining_requirement, chain, asset_address)}..."
|
|
554
|
+
|
|
555
|
+
spinner.succeed(
|
|
556
|
+
f"[{chain}] {recipient_name} updated balance: {wei_to_token(updated_balance, chain, asset_address)}."
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
def _ask_funds_from_requirements(
|
|
561
|
+
manager: ServiceManager,
|
|
562
|
+
wallet: MasterWallet,
|
|
563
|
+
service: Service,
|
|
564
|
+
) -> bool:
|
|
565
|
+
"""Ask for funds from requirements."""
|
|
566
|
+
spinner = Halo(text="Calculating funds requirements...", spinner="dots")
|
|
567
|
+
spinner.start()
|
|
568
|
+
requirements = manager.funding_requirements(
|
|
569
|
+
service_config_id=service.service_config_id
|
|
570
|
+
)
|
|
571
|
+
spinner.stop()
|
|
572
|
+
|
|
573
|
+
wallet_names = (
|
|
574
|
+
{
|
|
575
|
+
wallet.crypto.address: "Master EOA",
|
|
576
|
+
"master_safe": "Master Safe",
|
|
577
|
+
"service_safe": "Service Safe",
|
|
578
|
+
}
|
|
579
|
+
| {safe_address: "Master Safe" for safe_address in wallet.safes.values()}
|
|
580
|
+
| {
|
|
581
|
+
chain_config.chain_data.multisig: "Service Safe"
|
|
582
|
+
for chain_config in service.chain_configs.values()
|
|
583
|
+
}
|
|
584
|
+
| {address: "Agent EOA" for address in service.agent_addresses}
|
|
585
|
+
)
|
|
586
|
+
|
|
587
|
+
if not requirements["is_refill_required"] and requirements["allow_start_agent"]:
|
|
588
|
+
for chain_name, balances in requirements["balances"].items():
|
|
589
|
+
ledger_api = wallet.ledger_api(
|
|
590
|
+
chain=Chain(chain_name),
|
|
591
|
+
rpc=service.chain_configs[chain_name].ledger_config.rpc,
|
|
592
|
+
)
|
|
593
|
+
for wallet_address, asset_balances in balances.items():
|
|
594
|
+
for asset_address, balance in asset_balances.items():
|
|
595
|
+
print(
|
|
596
|
+
f"[{chain_name}] {wallet_names[wallet_address]} has {wei_to_token(balance, chain_name, asset_address)}"
|
|
597
|
+
)
|
|
598
|
+
|
|
599
|
+
return True
|
|
600
|
+
|
|
601
|
+
for chain_name, chain_requirements in requirements["refill_requirements"].items():
|
|
602
|
+
chain = Chain(chain_name)
|
|
603
|
+
ledger_api = wallet.ledger_api(
|
|
604
|
+
chain=chain,
|
|
605
|
+
rpc=service.chain_configs[chain_name].ledger_config.rpc,
|
|
606
|
+
)
|
|
607
|
+
for wallet_address, requirements in chain_requirements.items():
|
|
608
|
+
if wallet_address in ("master_safe", "service_safe"):
|
|
609
|
+
continue # we can't ask funds in placeholder addresses
|
|
610
|
+
|
|
611
|
+
for asset_address, requirement in requirements.items():
|
|
612
|
+
ask_funds_in_address(
|
|
613
|
+
ledger_api=ledger_api,
|
|
614
|
+
chain=chain_name,
|
|
615
|
+
asset_address=asset_address,
|
|
616
|
+
required_balance=requirement,
|
|
617
|
+
recipient_address=wallet_address,
|
|
618
|
+
recipient_name=wallet_names[wallet_address],
|
|
619
|
+
)
|
|
620
|
+
|
|
621
|
+
return False
|
|
622
|
+
|
|
623
|
+
|
|
624
|
+
def _maybe_create_master_eoa(operate: "OperateApp") -> None:
|
|
625
|
+
"""Maybe create the Master EOA."""
|
|
626
|
+
if not operate.wallet_manager.exists(ledger_type=LedgerType.ETHEREUM):
|
|
627
|
+
print("Creating the Master EOA...")
|
|
628
|
+
wallet, mnemonic = operate.wallet_manager.create(
|
|
629
|
+
ledger_type=LedgerType.ETHEREUM
|
|
630
|
+
)
|
|
631
|
+
wallet.password = operate.password
|
|
632
|
+
print_box(
|
|
633
|
+
f"Please save the mnemonic phrase for the Master EOA:\n{', '.join(mnemonic)}",
|
|
634
|
+
0,
|
|
635
|
+
"-",
|
|
636
|
+
)
|
|
637
|
+
ask_or_get_from_env(
|
|
638
|
+
"Press enter to continue...", False, "CONTINUE", raise_if_missing=False
|
|
639
|
+
)
|
|
640
|
+
|
|
641
|
+
|
|
642
|
+
def ensure_enough_funds(operate: "OperateApp", service: Service) -> None:
|
|
643
|
+
"""Ensure enough funds."""
|
|
644
|
+
_maybe_create_master_eoa(operate)
|
|
645
|
+
wallet = operate.wallet_manager.load(ledger_type=LedgerType.ETHEREUM)
|
|
646
|
+
manager = operate.service_manager()
|
|
647
|
+
manager.funding_manager.is_for_quickstart = True
|
|
648
|
+
|
|
649
|
+
backup_owner = None
|
|
650
|
+
while not _ask_funds_from_requirements(manager, wallet, service):
|
|
651
|
+
for chain_name, chain_config in service.chain_configs.items():
|
|
652
|
+
chain = Chain.from_string(chain_name)
|
|
653
|
+
if wallet.safes.get(chain) is None:
|
|
654
|
+
print(f"[{chain_name}] Creating Master Safe")
|
|
655
|
+
if backup_owner is None:
|
|
656
|
+
backup_owner = ask_or_get_from_env(
|
|
657
|
+
"Please input your backup owner (leave empty to skip): ",
|
|
658
|
+
False,
|
|
659
|
+
"BACKUP_OWNER",
|
|
660
|
+
raise_if_missing=False,
|
|
661
|
+
)
|
|
662
|
+
|
|
663
|
+
wallet.create_safe(
|
|
664
|
+
chain=chain,
|
|
665
|
+
rpc=chain_config.ledger_config.rpc,
|
|
666
|
+
backup_owner=None if backup_owner == "" else backup_owner,
|
|
667
|
+
)
|
|
668
|
+
|
|
669
|
+
|
|
670
|
+
def run_service(
|
|
671
|
+
operate: "OperateApp",
|
|
672
|
+
config_path: str,
|
|
673
|
+
build_only: bool = False,
|
|
674
|
+
skip_dependency_check: bool = False,
|
|
675
|
+
use_binary: bool = False,
|
|
676
|
+
) -> None:
|
|
677
|
+
"""Run service."""
|
|
678
|
+
|
|
679
|
+
with open(config_path, "r") as config_file:
|
|
680
|
+
template = json.load(config_file)
|
|
681
|
+
|
|
682
|
+
print_title(f"{template['name']} quickstart")
|
|
683
|
+
|
|
684
|
+
ask_password_if_needed(operate)
|
|
685
|
+
_maybe_create_master_eoa(operate)
|
|
686
|
+
|
|
687
|
+
config = configure_local_config(template, operate)
|
|
688
|
+
manager = operate.service_manager()
|
|
689
|
+
service = get_service(manager, template)
|
|
690
|
+
|
|
691
|
+
# reload manger and config after setting operate.password
|
|
692
|
+
manager = operate.service_manager(skip_dependency_check=skip_dependency_check)
|
|
693
|
+
config = load_local_config(operate=operate, service_name=t.cast(str, service.name))
|
|
694
|
+
ensure_enough_funds(operate, service)
|
|
695
|
+
|
|
696
|
+
print_box("PLEASE, DO NOT INTERRUPT THIS PROCESS.")
|
|
697
|
+
print_section(f"Deploying on-chain service on {config.principal_chain}...")
|
|
698
|
+
print(
|
|
699
|
+
"Cancelling the on-chain service update prematurely could lead to an inconsistent state of the Safe or the on-chain service state, which may require manual intervention to resolve.\n"
|
|
700
|
+
)
|
|
701
|
+
manager.deploy_service_onchain_from_safe(
|
|
702
|
+
service_config_id=service.service_config_id
|
|
703
|
+
)
|
|
704
|
+
|
|
705
|
+
print_section("Funding the service")
|
|
706
|
+
service = get_service(manager, template)
|
|
707
|
+
manager.funding_manager.topup_service_initial(service=service)
|
|
708
|
+
|
|
709
|
+
print_section("Deploying the service")
|
|
710
|
+
if use_binary:
|
|
711
|
+
use_docker = False
|
|
712
|
+
use_k8s = False
|
|
713
|
+
else:
|
|
714
|
+
use_docker = True
|
|
715
|
+
use_k8s = True
|
|
716
|
+
|
|
717
|
+
manager.deploy_service_locally(
|
|
718
|
+
service_config_id=service.service_config_id,
|
|
719
|
+
use_docker=use_docker,
|
|
720
|
+
use_kubernetes=use_k8s,
|
|
721
|
+
build_only=build_only,
|
|
722
|
+
)
|
|
723
|
+
if build_only:
|
|
724
|
+
print_section(f"Built the {template['name']}")
|
|
725
|
+
else:
|
|
726
|
+
print_section(f"Starting the {template['name']}")
|