olas-operate-middleware 0.6.3__py3-none-any.whl → 0.7.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.
@@ -0,0 +1,442 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # ------------------------------------------------------------------------------
4
+ #
5
+ # Copyright 2024 Valory AG
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #
19
+ # ------------------------------------------------------------------------------
20
+ """Relay provider."""
21
+
22
+
23
+ import enum
24
+ import json
25
+ import math
26
+ import time
27
+ import typing as t
28
+ from http import HTTPStatus
29
+ from urllib.parse import urlencode
30
+
31
+ import requests
32
+
33
+ from operate.bridge.providers.provider import (
34
+ DEFAULT_MAX_QUOTE_RETRIES,
35
+ MESSAGE_EXECUTION_FAILED,
36
+ MESSAGE_QUOTE_ZERO,
37
+ Provider,
38
+ ProviderRequest,
39
+ ProviderRequestStatus,
40
+ QuoteData,
41
+ )
42
+ from operate.operate_types import Chain
43
+
44
+
45
+ GAS_ESTIMATE_FALLBACK_ADDRESS = "0x000000000000000000000000000000000000dEaD"
46
+
47
+ # The following constants were determined empirically (+ margin) from the Relay API/Dapp.
48
+ RELAY_DEFAULT_GAS = {
49
+ Chain.ETHEREUM: {
50
+ "deposit": 50_000,
51
+ "approve": 200_000,
52
+ "authorize": 1,
53
+ "authorize1": 1,
54
+ "authorize2": 1,
55
+ "swap": 400_000,
56
+ "send": 1,
57
+ },
58
+ Chain.BASE: {
59
+ "deposit": 50_000,
60
+ "approve": 200_000,
61
+ "authorize": 1,
62
+ "authorize1": 1,
63
+ "authorize2": 1,
64
+ "swap": 400_000,
65
+ "send": 1,
66
+ },
67
+ Chain.CELO: {
68
+ "deposit": 50_000,
69
+ "approve": 200_000,
70
+ "authorize": 1,
71
+ "authorize1": 1,
72
+ "authorize2": 1,
73
+ "swap": 400_000,
74
+ "send": 1,
75
+ },
76
+ Chain.GNOSIS: {
77
+ "deposit": 350_000,
78
+ "approve": 200_000,
79
+ "authorize": 1,
80
+ "authorize1": 1,
81
+ "authorize2": 1,
82
+ "swap": 500_000,
83
+ "send": 1,
84
+ },
85
+ Chain.MODE: {
86
+ "deposit": 50_000,
87
+ "approve": 200_000,
88
+ "authorize": 1,
89
+ "authorize1": 1,
90
+ "authorize2": 1,
91
+ "swap": 1_500_000,
92
+ "send": 1,
93
+ },
94
+ Chain.OPTIMISTIC: {
95
+ "deposit": 50_000,
96
+ "approve": 200_000,
97
+ "authorize": 1,
98
+ "authorize1": 1,
99
+ "authorize2": 1,
100
+ "swap": 400_000,
101
+ "send": 1,
102
+ },
103
+ }
104
+
105
+
106
+ class RelayExecutionStatus(str, enum.Enum):
107
+ """Relay execution status."""
108
+
109
+ REFUND = "refund"
110
+ DELAYED = "delayed"
111
+ WAITING = "waiting"
112
+ FAILURE = "failure"
113
+ PENDING = "pending"
114
+ SUCCESS = "success"
115
+
116
+ def __str__(self) -> str:
117
+ """__str__"""
118
+ return self.value
119
+
120
+
121
+ class RelayProvider(Provider):
122
+ """Relay provider."""
123
+
124
+ def description(self) -> str:
125
+ """Get a human-readable description of the provider."""
126
+ return "Relay Protocol https://www.relay.link/"
127
+
128
+ def quote(self, provider_request: ProviderRequest) -> None:
129
+ """Update the request with the quote."""
130
+ self._validate(provider_request)
131
+
132
+ if provider_request.status not in (
133
+ ProviderRequestStatus.CREATED,
134
+ ProviderRequestStatus.QUOTE_DONE,
135
+ ProviderRequestStatus.QUOTE_FAILED,
136
+ ):
137
+ raise RuntimeError(
138
+ f"Cannot quote request {provider_request.id} with status {provider_request.status}."
139
+ )
140
+
141
+ if provider_request.execution_data:
142
+ raise RuntimeError(
143
+ f"Cannot quote request {provider_request.id}: execution already present."
144
+ )
145
+
146
+ from_chain = provider_request.params["from"]["chain"]
147
+ from_address = provider_request.params["from"]["address"]
148
+ from_token = provider_request.params["from"]["token"]
149
+ to_chain = provider_request.params["to"]["chain"]
150
+ to_address = provider_request.params["to"]["address"]
151
+ to_token = provider_request.params["to"]["token"]
152
+ to_amount = provider_request.params["to"]["amount"]
153
+
154
+ if to_amount == 0:
155
+ self.logger.info(f"[RELAY PROVIDER] {MESSAGE_QUOTE_ZERO}")
156
+ quote_data = QuoteData(
157
+ eta=0,
158
+ elapsed_time=0,
159
+ message=MESSAGE_QUOTE_ZERO,
160
+ provider_data=None,
161
+ timestamp=int(time.time()),
162
+ )
163
+ provider_request.quote_data = quote_data
164
+ provider_request.status = ProviderRequestStatus.QUOTE_DONE
165
+ return
166
+
167
+ url = "https://api.relay.link/quote"
168
+ headers = {"Content-Type": "application/json"}
169
+ payload = {
170
+ "originChainId": Chain(from_chain).id,
171
+ "user": from_address,
172
+ "originCurrency": from_token,
173
+ "destinationChainId": Chain(to_chain).id,
174
+ "recipient": to_address,
175
+ "destinationCurrency": to_token,
176
+ "amount": to_amount,
177
+ "tradeType": "EXACT_OUTPUT",
178
+ "enableTrueExactOutput": False,
179
+ }
180
+ for attempt in range(1, DEFAULT_MAX_QUOTE_RETRIES + 1):
181
+ start = time.time()
182
+ try:
183
+ self.logger.info(f"[RELAY PROVIDER] POST {url}")
184
+ self.logger.info(
185
+ f"[RELAY PROVIDER] BODY {json.dumps(payload, indent=2, sort_keys=True)}"
186
+ )
187
+ response = requests.post(
188
+ url=url, headers=headers, json=payload, timeout=30
189
+ )
190
+ response.raise_for_status()
191
+ response_json = response.json()
192
+
193
+ # Gas will be returned as 0 (unable to estimate) by the API endpoint when simulation fails.
194
+ # This happens when 'from_address'
195
+ # * does not have enough funds/ERC20,
196
+ # * requires to approve an ERC20 before another transaction.
197
+ # Call the API again using the default 'from_address' placeholder used by Relay DApp.
198
+ gas_missing = any(
199
+ "gas" not in item.get("data", {})
200
+ for step in response_json.get("steps", [])
201
+ for item in step.get("items", [])
202
+ )
203
+
204
+ if gas_missing:
205
+ placeholder_payload = payload.copy()
206
+ placeholder_payload["user"] = GAS_ESTIMATE_FALLBACK_ADDRESS
207
+ self.logger.info(f"[RELAY PROVIDER] POST {url}")
208
+ self.logger.info(
209
+ f"[RELAY PROVIDER] BODY {json.dumps(placeholder_payload, indent=2, sort_keys=True)}"
210
+ )
211
+ placeholder_response = requests.post(
212
+ url=url, headers=headers, json=placeholder_payload, timeout=30
213
+ )
214
+ response_json_placeholder = placeholder_response.json()
215
+
216
+ for i, step in enumerate(response_json.get("steps", [])):
217
+ for j, item in enumerate(step.get("items", [])):
218
+ if "gas" not in item.get("data", {}):
219
+ placeholder_gas = (
220
+ response_json_placeholder.get("steps", {i: {}})[i]
221
+ .get("items", {j: {}})[j]
222
+ .get("data", {})
223
+ .get("gas")
224
+ )
225
+ item["data"]["gas"] = (
226
+ placeholder_gas
227
+ or RELAY_DEFAULT_GAS[Chain(from_chain)][step["id"]]
228
+ )
229
+
230
+ quote_data = QuoteData(
231
+ eta=math.ceil(response_json["details"]["timeEstimate"]),
232
+ elapsed_time=time.time() - start,
233
+ message=None,
234
+ provider_data={
235
+ "attempts": attempt,
236
+ "response": response_json,
237
+ "response_status": response.status_code,
238
+ },
239
+ timestamp=int(time.time()),
240
+ )
241
+ provider_request.quote_data = quote_data
242
+ provider_request.status = ProviderRequestStatus.QUOTE_DONE
243
+ return
244
+ except requests.Timeout as e:
245
+ self.logger.warning(
246
+ f"[RELAY PROVIDER] Timeout request on attempt {attempt}/{DEFAULT_MAX_QUOTE_RETRIES}: {e}."
247
+ )
248
+ quote_data = QuoteData(
249
+ eta=None,
250
+ elapsed_time=time.time() - start,
251
+ message=str(e),
252
+ provider_data={
253
+ "attempts": attempt,
254
+ "response": None,
255
+ "response_status": HTTPStatus.GATEWAY_TIMEOUT,
256
+ },
257
+ timestamp=int(time.time()),
258
+ )
259
+ except requests.RequestException as e:
260
+ self.logger.warning(
261
+ f"[RELAY PROVIDER] Request failed on attempt {attempt}/{DEFAULT_MAX_QUOTE_RETRIES}: {e}."
262
+ )
263
+ response_json = response.json()
264
+ quote_data = QuoteData(
265
+ eta=None,
266
+ elapsed_time=time.time() - start,
267
+ message=response_json.get("message") or str(e),
268
+ provider_data={
269
+ "attempts": attempt,
270
+ "response": response_json,
271
+ "response_status": getattr(
272
+ response, "status_code", HTTPStatus.BAD_GATEWAY
273
+ ),
274
+ },
275
+ timestamp=int(time.time()),
276
+ )
277
+ except Exception as e: # pylint:disable=broad-except
278
+ self.logger.warning(
279
+ f"[RELAY PROVIDER] Request failed on attempt {attempt}/{DEFAULT_MAX_QUOTE_RETRIES}: {e}."
280
+ )
281
+ quote_data = QuoteData(
282
+ eta=None,
283
+ elapsed_time=time.time() - start,
284
+ message=str(e),
285
+ provider_data={
286
+ "attempts": attempt,
287
+ "response": None,
288
+ "response_status": HTTPStatus.INTERNAL_SERVER_ERROR,
289
+ },
290
+ timestamp=int(time.time()),
291
+ )
292
+ if attempt >= DEFAULT_MAX_QUOTE_RETRIES:
293
+ self.logger.error(
294
+ f"[RELAY PROVIDER] Request failed after {DEFAULT_MAX_QUOTE_RETRIES} attempts."
295
+ )
296
+ provider_request.quote_data = quote_data
297
+ provider_request.status = ProviderRequestStatus.QUOTE_FAILED
298
+ return
299
+
300
+ time.sleep(2)
301
+
302
+ def _get_txs(
303
+ self, provider_request: ProviderRequest, *args: t.Any, **kwargs: t.Any
304
+ ) -> t.List[t.Tuple[str, t.Dict]]:
305
+ """Get the sorted list of transactions to execute the quote."""
306
+
307
+ if provider_request.params["to"]["amount"] == 0:
308
+ return []
309
+
310
+ quote_data = provider_request.quote_data
311
+ if not quote_data:
312
+ raise RuntimeError(
313
+ f"Cannot get transaction builders {provider_request.id}: quote data not present."
314
+ )
315
+
316
+ provider_data = quote_data.provider_data
317
+ if not provider_data:
318
+ raise RuntimeError(
319
+ f"Cannot get transaction builders {provider_request.id}: provider data not present."
320
+ )
321
+
322
+ txs: t.List[t.Tuple[str, t.Dict]] = []
323
+
324
+ response = provider_data.get("response")
325
+ if not response:
326
+ return txs
327
+
328
+ steps = response.get("steps", [])
329
+ from_ledger_api = self._from_ledger_api(provider_request)
330
+
331
+ for step in steps:
332
+ for i, item in enumerate(step["items"]):
333
+ tx = item["data"].copy()
334
+ tx["to"] = from_ledger_api.api.to_checksum_address(tx["to"])
335
+ tx["value"] = int(tx.get("value", 0))
336
+ tx["gas"] = int(tx.get("gas", 1))
337
+ tx["maxFeePerGas"] = int(tx.get("maxFeePerGas", 0))
338
+ tx["maxPriorityFeePerGas"] = int(tx.get("maxPriorityFeePerGas", 0))
339
+ tx["nonce"] = from_ledger_api.api.eth.get_transaction_count(tx["from"])
340
+ Provider._update_with_gas_pricing(tx, from_ledger_api)
341
+ Provider._update_with_gas_estimate(tx, from_ledger_api)
342
+ txs.append((f"{step['id']}-{i}", tx))
343
+
344
+ return txs
345
+
346
+ def _update_execution_status(self, provider_request: ProviderRequest) -> None:
347
+ """Update the execution status."""
348
+
349
+ if provider_request.status not in (
350
+ ProviderRequestStatus.EXECUTION_PENDING,
351
+ ProviderRequestStatus.EXECUTION_UNKNOWN,
352
+ ):
353
+ return
354
+
355
+ execution_data = provider_request.execution_data
356
+ if not execution_data:
357
+ raise RuntimeError(
358
+ f"Cannot update request {provider_request.id}: execution data not present."
359
+ )
360
+
361
+ from_tx_hash = execution_data.from_tx_hash
362
+ if not from_tx_hash:
363
+ execution_data.message = (
364
+ f"{MESSAGE_EXECUTION_FAILED} missing transaction hash."
365
+ )
366
+ provider_request.status = ProviderRequestStatus.EXECUTION_FAILED
367
+ return
368
+
369
+ relay_status = RelayExecutionStatus.WAITING
370
+ url = "https://api.relay.link/requests/v2"
371
+ headers = {"accept": "application/json"}
372
+ params = {
373
+ "hash": from_tx_hash,
374
+ "sortBy": "createdAt",
375
+ }
376
+
377
+ try:
378
+ self.logger.info(f"[RELAY PROVIDER] GET {url}?{urlencode(params)}")
379
+ response = requests.get(url=url, headers=headers, params=params, timeout=30)
380
+ response_json = response.json()
381
+ relay_requests = response_json.get("requests")
382
+ if relay_requests:
383
+ relay_status = relay_requests[0].get(
384
+ "status", str(RelayExecutionStatus.WAITING)
385
+ )
386
+ execution_data.message = str(relay_status)
387
+ response.raise_for_status()
388
+ except Exception as e:
389
+ self.logger.error(
390
+ f"[RELAY PROVIDER] Failed to update status for request {provider_request.id}: {e}"
391
+ )
392
+
393
+ if relay_status == RelayExecutionStatus.SUCCESS:
394
+ self.logger.info(
395
+ f"[RELAY PROVIDER] Execution done for {provider_request.id}."
396
+ )
397
+ from_ledger_api = self._from_ledger_api(provider_request)
398
+ to_ledger_api = self._to_ledger_api(provider_request)
399
+ to_tx_hash = response_json["requests"][0]["data"]["outTxs"][0]["hash"]
400
+ execution_data.message = response_json.get("details", None)
401
+ execution_data.to_tx_hash = to_tx_hash
402
+ execution_data.elapsed_time = Provider._tx_timestamp(
403
+ to_tx_hash, to_ledger_api
404
+ ) - Provider._tx_timestamp(from_tx_hash, from_ledger_api)
405
+ provider_request.status = ProviderRequestStatus.EXECUTION_DONE
406
+ execution_data.provider_data = {
407
+ "response": response_json,
408
+ }
409
+ elif relay_status in (
410
+ RelayExecutionStatus.FAILURE,
411
+ RelayExecutionStatus.REFUND,
412
+ ):
413
+ provider_request.status = ProviderRequestStatus.EXECUTION_FAILED
414
+ elif relay_status in (
415
+ RelayExecutionStatus.PENDING,
416
+ RelayExecutionStatus.DELAYED,
417
+ ):
418
+ provider_request.status = ProviderRequestStatus.EXECUTION_PENDING
419
+ else:
420
+ provider_request.status = ProviderRequestStatus.EXECUTION_UNKNOWN
421
+
422
+ def _get_explorer_link(self, provider_request: ProviderRequest) -> t.Optional[str]:
423
+ """Get the explorer link for a transaction."""
424
+ if not provider_request.execution_data:
425
+ return None
426
+
427
+ quote_data = provider_request.quote_data
428
+ if not quote_data:
429
+ raise RuntimeError(
430
+ f"Cannot get explorer link for request {provider_request.id}: quote data not present."
431
+ )
432
+
433
+ provider_data = quote_data.provider_data
434
+ if not provider_data:
435
+ return None
436
+
437
+ steps = provider_data.get("response", {}).get("steps", [])
438
+ if not steps:
439
+ return None
440
+
441
+ request_id = steps[-1].get("requestId")
442
+ return f"https://relay.link/transaction/{request_id}"
operate/cli.py CHANGED
@@ -45,8 +45,15 @@ from uvicorn.server import Server
45
45
 
46
46
  from operate import services
47
47
  from operate.account.user import UserAccount
48
- from operate.bridge.bridge import BridgeManager
49
- from operate.constants import KEY, KEYS, OPERATE_HOME, SERVICES, ZERO_ADDRESS
48
+ from operate.bridge.bridge_manager import BridgeManager
49
+ from operate.constants import (
50
+ KEY,
51
+ KEYS,
52
+ MIN_PASSWORD_LENGTH,
53
+ OPERATE_HOME,
54
+ SERVICES,
55
+ ZERO_ADDRESS,
56
+ )
50
57
  from operate.ledger.profiles import (
51
58
  DEFAULT_MASTER_EOA_FUNDS,
52
59
  DEFAULT_NEW_SAFE_FUNDS,
@@ -407,13 +414,19 @@ def create_app( # pylint: disable=too-many-locals, unused-argument, too-many-st
407
414
  if operate.user_account is not None:
408
415
  return JSONResponse(
409
416
  content={"error": "Account already exists"},
417
+ status_code=HTTPStatus.CONFLICT,
418
+ )
419
+
420
+ password = (await request.json()).get("password")
421
+ if not password or len(password) < MIN_PASSWORD_LENGTH:
422
+ return JSONResponse(
423
+ content={
424
+ "error": f"Password must be at least {MIN_PASSWORD_LENGTH} characters long."
425
+ },
410
426
  status_code=HTTPStatus.BAD_REQUEST,
411
427
  )
412
428
 
413
- data = await request.json()
414
- operate.create_user_account(
415
- password=data["password"],
416
- )
429
+ operate.create_user_account(password=password)
417
430
  return JSONResponse(content={"error": None})
418
431
 
419
432
  @app.put("/api/account")
@@ -425,7 +438,7 @@ def create_app( # pylint: disable=too-many-locals, unused-argument, too-many-st
425
438
  if operate.user_account is None:
426
439
  return JSONResponse(
427
440
  content={"error": "Account does not exist."},
428
- status_code=HTTPStatus.BAD_REQUEST,
441
+ status_code=HTTPStatus.CONFLICT,
429
442
  )
430
443
 
431
444
  data = await request.json()
@@ -449,6 +462,14 @@ def create_app( # pylint: disable=too-many-locals, unused-argument, too-many-st
449
462
  status_code=HTTPStatus.BAD_REQUEST,
450
463
  )
451
464
 
465
+ if not new_password or len(new_password) < MIN_PASSWORD_LENGTH:
466
+ return JSONResponse(
467
+ content={
468
+ "error": f"Password must be at least {MIN_PASSWORD_LENGTH} characters long."
469
+ },
470
+ status_code=HTTPStatus.BAD_REQUEST,
471
+ )
472
+
452
473
  try:
453
474
  if old_password:
454
475
  operate.update_password(old_password, new_password)
operate/constants.py CHANGED
@@ -39,6 +39,7 @@ ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"
39
39
  ON_CHAIN_INTERACT_TIMEOUT = 120.0
40
40
  ON_CHAIN_INTERACT_RETRIES = 10
41
41
  ON_CHAIN_INTERACT_SLEEP = 3.0
42
+ MIN_PASSWORD_LENGTH = 8
42
43
 
43
44
  HEALTH_CHECK_URL = "http://127.0.0.1:8716/healthcheck" # possible DNS issues on windows so use IP address
44
45
  SAFE_WEBAPP_URL = "https://app.safe.global/home?safe=gno:"
@@ -19,6 +19,7 @@
19
19
 
20
20
  """Chain profiles."""
21
21
 
22
+ import enum
22
23
  import typing as t
23
24
 
24
25
  from operate.constants import ZERO_ADDRESS
@@ -154,7 +155,28 @@ STAKING: t.Dict[Chain, t.Dict[str, str]] = {
154
155
  },
155
156
  }
156
157
 
157
- DEFAULT_MECH_MARKETPLACE_PRIORITY_MECH = "0x552cEA7Bc33CbBEb9f1D90c1D11D2C6daefFd053"
158
+
159
+ class StakingProgramMechType(enum.Enum):
160
+ """Staking program mech type."""
161
+
162
+ LEGACY_MECH = "legacy_mech"
163
+ LEGACY_MECH_MARKETPLACE = "legacy_mech_marketplace"
164
+ MECH_MARKETPLACE = "mech_marketplace"
165
+
166
+ def __str__(self) -> str:
167
+ """Get the string representation of the enum."""
168
+ return self.value
169
+
170
+
171
+ DEFAULT_PRIORITY_MECH_ADDRESS = {
172
+ StakingProgramMechType.LEGACY_MECH: "0x77af31De935740567Cf4fF1986D04B2c964A786a",
173
+ StakingProgramMechType.LEGACY_MECH_MARKETPLACE: "0x552cEA7Bc33CbBEb9f1D90c1D11D2C6daefFd053",
174
+ StakingProgramMechType.MECH_MARKETPLACE: "0xC05e7412439bD7e91730a6880E18d5D5873F632C",
175
+ }
176
+ DEFAULT_PRIORITY_MECH_SERVICE_ID = {
177
+ StakingProgramMechType.LEGACY_MECH_MARKETPLACE: 975,
178
+ StakingProgramMechType.MECH_MARKETPLACE: 2182,
179
+ }
158
180
 
159
181
 
160
182
  # ERC20 token addresses
@@ -247,3 +269,17 @@ def get_staking_contract(
247
269
  staking_program_id,
248
270
  staking_program_id,
249
271
  )
272
+
273
+
274
+ def get_staking_program_mech_type(
275
+ staking_program_id: t.Optional[str],
276
+ ) -> StakingProgramMechType:
277
+ """Get the staking program mech type based on the staking program ID."""
278
+ if staking_program_id is None:
279
+ return StakingProgramMechType.LEGACY_MECH
280
+
281
+ if staking_program_id.startswith("quickstart_beta_mech_marketplace_expert"):
282
+ return StakingProgramMechType.MECH_MARKETPLACE
283
+ if "mech_marketplace" in staking_program_id:
284
+ return StakingProgramMechType.LEGACY_MECH_MARKETPLACE
285
+ return StakingProgramMechType.LEGACY_MECH
@@ -37,7 +37,14 @@ from operate.account.user import UserAccount
37
37
  from operate.constants import IPFS_ADDRESS, OPERATE_HOME
38
38
  from operate.data import DATA_DIR
39
39
  from operate.data.contracts.staking_token.contract import StakingTokenContract
40
- from operate.ledger.profiles import NO_STAKING_PROGRAM_ID, STAKING, get_staking_contract
40
+ from operate.ledger.profiles import (
41
+ DEFAULT_PRIORITY_MECH_ADDRESS,
42
+ DEFAULT_PRIORITY_MECH_SERVICE_ID,
43
+ NO_STAKING_PROGRAM_ID,
44
+ STAKING,
45
+ get_staking_contract,
46
+ get_staking_program_mech_type,
47
+ )
41
48
  from operate.operate_types import (
42
49
  Chain,
43
50
  LedgerType,
@@ -389,8 +396,31 @@ def configure_local_config(
389
396
  ):
390
397
  print_section("Please enter the arguments that will be used by the service.")
391
398
 
399
+ staking_program_mech_type = get_staking_program_mech_type(config.staking_program_id)
400
+
392
401
  for env_var_name, env_var_data in template["env_variables"].items():
393
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"] = DEFAULT_PRIORITY_MECH_ADDRESS[
406
+ staking_program_mech_type
407
+ ]
408
+ if (
409
+ env_var_name in config.user_provided_args
410
+ and env_var_data["value"] != config.user_provided_args[env_var_name]
411
+ ):
412
+ del config.user_provided_args[env_var_name]
413
+
414
+ if env_var_name == "PRIORITY_MECH_SERVICE_ID":
415
+ env_var_data["value"] = str(
416
+ DEFAULT_PRIORITY_MECH_SERVICE_ID.get(staking_program_mech_type, 0)
417
+ )
418
+ if (
419
+ env_var_name in config.user_provided_args
420
+ and env_var_data["value"] != config.user_provided_args[env_var_name]
421
+ ):
422
+ del config.user_provided_args[env_var_name]
423
+
394
424
  if env_var_name not in config.user_provided_args:
395
425
  print(f"Description: {env_var_data['description']}")
396
426
  if env_var_data["value"]: