olas-operate-middleware 0.6.3__py3-none-any.whl → 0.8.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.6.3.dist-info → olas_operate_middleware-0.8.0.dist-info}/METADATA +1 -1
- {olas_operate_middleware-0.6.3.dist-info → olas_operate_middleware-0.8.0.dist-info}/RECORD +21 -20
- operate/bridge/{bridge.py → bridge_manager.py} +99 -62
- operate/bridge/providers/{lifi_bridge_provider.py → lifi_provider.py} +94 -81
- operate/bridge/providers/native_bridge_provider.py +124 -115
- operate/bridge/providers/{bridge_provider.py → provider.py} +132 -150
- operate/bridge/providers/relay_provider.py +442 -0
- operate/cli.py +140 -110
- operate/constants.py +1 -0
- operate/ledger/profiles.py +11 -1
- operate/operate_types.py +10 -0
- operate/quickstart/claim_staking_rewards.py +1 -1
- operate/quickstart/reset_staking.py +1 -1
- operate/quickstart/run_service.py +34 -11
- operate/quickstart/terminate_on_chain_service.py +1 -1
- operate/quickstart/utils.py +0 -1
- operate/services/agent_runner.py +168 -91
- operate/services/manage.py +126 -72
- {olas_operate_middleware-0.6.3.dist-info → olas_operate_middleware-0.8.0.dist-info}/LICENSE +0 -0
- {olas_operate_middleware-0.6.3.dist-info → olas_operate_middleware-0.8.0.dist-info}/WHEEL +0 -0
- {olas_operate_middleware-0.6.3.dist-info → olas_operate_middleware-0.8.0.dist-info}/entry_points.txt +0 -0
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.
|
|
49
|
-
from operate.constants import
|
|
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,
|
|
@@ -68,12 +75,13 @@ from operate.utils.gnosis import get_assets_balances
|
|
|
68
75
|
from operate.wallet.master import MasterWalletManager
|
|
69
76
|
|
|
70
77
|
|
|
71
|
-
DEFAULT_HARDHAT_KEY = (
|
|
72
|
-
"0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
|
|
73
|
-
).encode()
|
|
74
78
|
DEFAULT_MAX_RETRIES = 3
|
|
75
79
|
USER_NOT_LOGGED_IN_ERROR = JSONResponse(
|
|
76
|
-
content={"error": "User not logged in
|
|
80
|
+
content={"error": "User not logged in."}, status_code=HTTPStatus.UNAUTHORIZED
|
|
81
|
+
)
|
|
82
|
+
ACCOUNT_NOT_FOUND_ERROR = JSONResponse(
|
|
83
|
+
content={"error": "User account not found."},
|
|
84
|
+
status_code=HTTPStatus.NOT_FOUND,
|
|
77
85
|
)
|
|
78
86
|
|
|
79
87
|
|
|
@@ -123,7 +131,7 @@ class OperateApp:
|
|
|
123
131
|
"""Updates current password"""
|
|
124
132
|
|
|
125
133
|
if not new_password:
|
|
126
|
-
raise ValueError("
|
|
134
|
+
raise ValueError("'password' is required.")
|
|
127
135
|
|
|
128
136
|
if not (
|
|
129
137
|
self.user_account.is_valid(old_password)
|
|
@@ -140,7 +148,7 @@ class OperateApp:
|
|
|
140
148
|
"""Updates current password using the mnemonic"""
|
|
141
149
|
|
|
142
150
|
if not new_password:
|
|
143
|
-
raise ValueError("
|
|
151
|
+
raise ValueError("'password' is required.")
|
|
144
152
|
|
|
145
153
|
mnemonic = mnemonic.strip().lower()
|
|
146
154
|
if not self.wallet_manager.is_mnemonic_valid(mnemonic):
|
|
@@ -345,30 +353,27 @@ def create_app( # pylint: disable=too-many-locals, unused-argument, too-many-st
|
|
|
345
353
|
"""Call the endpoint."""
|
|
346
354
|
logger.info(f"Calling `{f.__name__}` with retries enabled")
|
|
347
355
|
retries = 0
|
|
348
|
-
errors = []
|
|
349
356
|
while retries < DEFAULT_MAX_RETRIES:
|
|
350
357
|
try:
|
|
351
358
|
return await f(request)
|
|
352
359
|
except (APIError, ProjectError) as e:
|
|
353
360
|
logger.error(f"Error {e}\n{traceback.format_exc()}")
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
error["error"] = "Service is already running"
|
|
361
|
+
if "has active endpoints" in str(e):
|
|
362
|
+
error_msg = "Service is already running."
|
|
357
363
|
else:
|
|
358
|
-
|
|
359
|
-
errors.append(error)
|
|
364
|
+
error_msg = "Service deployment failed. Please check the logs."
|
|
360
365
|
return JSONResponse(
|
|
361
|
-
content={"
|
|
366
|
+
content={"error": error_msg},
|
|
362
367
|
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
|
|
363
368
|
)
|
|
364
369
|
except Exception as e: # pylint: disable=broad-except
|
|
365
|
-
|
|
366
|
-
{"error": str(e.args[0]), "traceback": traceback.format_exc()}
|
|
367
|
-
)
|
|
368
|
-
logger.error(f"Error {str(e.args[0])}\n{traceback.format_exc()}")
|
|
370
|
+
logger.error(f"Error {str(e)}\n{traceback.format_exc()}")
|
|
369
371
|
retries += 1
|
|
370
372
|
return JSONResponse(
|
|
371
|
-
content={
|
|
373
|
+
content={
|
|
374
|
+
"error": "Operation failed after multiple attempts. Please try again later."
|
|
375
|
+
},
|
|
376
|
+
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
|
|
372
377
|
)
|
|
373
378
|
|
|
374
379
|
return _call
|
|
@@ -406,14 +411,20 @@ def create_app( # pylint: disable=too-many-locals, unused-argument, too-many-st
|
|
|
406
411
|
"""Setup account."""
|
|
407
412
|
if operate.user_account is not None:
|
|
408
413
|
return JSONResponse(
|
|
409
|
-
content={"error": "Account already exists"},
|
|
414
|
+
content={"error": "Account already exists."},
|
|
415
|
+
status_code=HTTPStatus.CONFLICT,
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
password = (await request.json()).get("password")
|
|
419
|
+
if not password or len(password) < MIN_PASSWORD_LENGTH:
|
|
420
|
+
return JSONResponse(
|
|
421
|
+
content={
|
|
422
|
+
"error": f"Password must be at least {MIN_PASSWORD_LENGTH} characters long."
|
|
423
|
+
},
|
|
410
424
|
status_code=HTTPStatus.BAD_REQUEST,
|
|
411
425
|
)
|
|
412
426
|
|
|
413
|
-
|
|
414
|
-
operate.create_user_account(
|
|
415
|
-
password=data["password"],
|
|
416
|
-
)
|
|
427
|
+
operate.create_user_account(password=password)
|
|
417
428
|
return JSONResponse(content={"error": None})
|
|
418
429
|
|
|
419
430
|
@app.put("/api/account")
|
|
@@ -423,10 +434,7 @@ def create_app( # pylint: disable=too-many-locals, unused-argument, too-many-st
|
|
|
423
434
|
) -> t.Dict:
|
|
424
435
|
"""Update password."""
|
|
425
436
|
if operate.user_account is None:
|
|
426
|
-
return
|
|
427
|
-
content={"error": "Account does not exist."},
|
|
428
|
-
status_code=HTTPStatus.BAD_REQUEST,
|
|
429
|
-
)
|
|
437
|
+
return ACCOUNT_NOT_FOUND_ERROR
|
|
430
438
|
|
|
431
439
|
data = await request.json()
|
|
432
440
|
old_password = data.get("old_password")
|
|
@@ -436,7 +444,7 @@ def create_app( # pylint: disable=too-many-locals, unused-argument, too-many-st
|
|
|
436
444
|
if not old_password and not mnemonic:
|
|
437
445
|
return JSONResponse(
|
|
438
446
|
content={
|
|
439
|
-
"error": "
|
|
447
|
+
"error": "Exactly one of 'old_password' or 'mnemonic' (seed phrase) is required.",
|
|
440
448
|
},
|
|
441
449
|
status_code=HTTPStatus.BAD_REQUEST,
|
|
442
450
|
)
|
|
@@ -444,7 +452,15 @@ def create_app( # pylint: disable=too-many-locals, unused-argument, too-many-st
|
|
|
444
452
|
if old_password and mnemonic:
|
|
445
453
|
return JSONResponse(
|
|
446
454
|
content={
|
|
447
|
-
"error": "
|
|
455
|
+
"error": "Exactly one of 'old_password' or 'mnemonic' (seed phrase) is required.",
|
|
456
|
+
},
|
|
457
|
+
status_code=HTTPStatus.BAD_REQUEST,
|
|
458
|
+
)
|
|
459
|
+
|
|
460
|
+
if not new_password or len(new_password) < MIN_PASSWORD_LENGTH:
|
|
461
|
+
return JSONResponse(
|
|
462
|
+
content={
|
|
463
|
+
"error": f"New password must be at least {MIN_PASSWORD_LENGTH} characters long."
|
|
448
464
|
},
|
|
449
465
|
status_code=HTTPStatus.BAD_REQUEST,
|
|
450
466
|
)
|
|
@@ -453,27 +469,31 @@ def create_app( # pylint: disable=too-many-locals, unused-argument, too-many-st
|
|
|
453
469
|
if old_password:
|
|
454
470
|
operate.update_password(old_password, new_password)
|
|
455
471
|
return JSONResponse(
|
|
456
|
-
content={"error": None, "message": "Password updated."}
|
|
472
|
+
content={"error": None, "message": "Password updated successfully."}
|
|
457
473
|
)
|
|
458
474
|
if mnemonic:
|
|
459
475
|
operate.update_password_with_mnemonic(mnemonic, new_password)
|
|
460
476
|
return JSONResponse(
|
|
461
477
|
content={
|
|
462
478
|
"error": None,
|
|
463
|
-
"message": "Password updated using seed phrase.",
|
|
479
|
+
"message": "Password updated successfully using seed phrase.",
|
|
464
480
|
}
|
|
465
481
|
)
|
|
466
482
|
|
|
467
483
|
return JSONResponse(
|
|
468
|
-
content={"error":
|
|
484
|
+
content={"error": "Password update failed."},
|
|
485
|
+
status_code=HTTPStatus.BAD_REQUEST,
|
|
469
486
|
)
|
|
470
487
|
except ValueError as e:
|
|
488
|
+
logger.error(f"Password update error: {e}\n{traceback.format_exc()}")
|
|
471
489
|
return JSONResponse(
|
|
472
|
-
content={"error": str(e)},
|
|
490
|
+
content={"error": f"Failed to update password: {str(e)}"},
|
|
491
|
+
status_code=HTTPStatus.BAD_REQUEST,
|
|
473
492
|
)
|
|
474
493
|
except Exception as e: # pylint: disable=broad-except
|
|
494
|
+
logger.error(f"Password update error: {e}\n{traceback.format_exc()}")
|
|
475
495
|
return JSONResponse(
|
|
476
|
-
content={"error":
|
|
496
|
+
content={"error": "Failed to update password. Please check the logs."},
|
|
477
497
|
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
|
|
478
498
|
)
|
|
479
499
|
|
|
@@ -482,21 +502,18 @@ def create_app( # pylint: disable=too-many-locals, unused-argument, too-many-st
|
|
|
482
502
|
async def _validate_password(request: Request) -> t.Dict:
|
|
483
503
|
"""Validate password."""
|
|
484
504
|
if operate.user_account is None:
|
|
485
|
-
return
|
|
486
|
-
content={"error": "Account does not exist"},
|
|
487
|
-
status_code=HTTPStatus.BAD_REQUEST,
|
|
488
|
-
)
|
|
505
|
+
return ACCOUNT_NOT_FOUND_ERROR
|
|
489
506
|
|
|
490
507
|
data = await request.json()
|
|
491
508
|
if not operate.user_account.is_valid(password=data["password"]):
|
|
492
509
|
return JSONResponse(
|
|
493
|
-
content={"error": "Password is not valid"},
|
|
510
|
+
content={"error": "Password is not valid."},
|
|
494
511
|
status_code=HTTPStatus.UNAUTHORIZED,
|
|
495
512
|
)
|
|
496
513
|
|
|
497
514
|
operate.password = data["password"]
|
|
498
515
|
return JSONResponse(
|
|
499
|
-
content={"message": "Login successful"},
|
|
516
|
+
content={"message": "Login successful."},
|
|
500
517
|
status_code=HTTPStatus.OK,
|
|
501
518
|
)
|
|
502
519
|
|
|
@@ -514,16 +531,10 @@ def create_app( # pylint: disable=too-many-locals, unused-argument, too-many-st
|
|
|
514
531
|
async def _create_wallet(request: Request) -> t.List[t.Dict]:
|
|
515
532
|
"""Create wallet"""
|
|
516
533
|
if operate.user_account is None:
|
|
517
|
-
return
|
|
518
|
-
content={"error": "Cannot create wallet; User account does not exist!"},
|
|
519
|
-
status_code=HTTPStatus.BAD_REQUEST,
|
|
520
|
-
)
|
|
534
|
+
return ACCOUNT_NOT_FOUND_ERROR
|
|
521
535
|
|
|
522
536
|
if operate.password is None:
|
|
523
|
-
return
|
|
524
|
-
content={"error": "You need to login before creating a wallet"},
|
|
525
|
-
status_code=HTTPStatus.UNAUTHORIZED,
|
|
526
|
-
)
|
|
537
|
+
return USER_NOT_LOGGED_IN_ERROR
|
|
527
538
|
|
|
528
539
|
data = await request.json()
|
|
529
540
|
ledger_type = LedgerType(data["ledger_type"])
|
|
@@ -543,23 +554,15 @@ def create_app( # pylint: disable=too-many-locals, unused-argument, too-many-st
|
|
|
543
554
|
async def _get_private_key(request: Request) -> t.List[t.Dict]:
|
|
544
555
|
"""Get Master EOA private key."""
|
|
545
556
|
if operate.user_account is None:
|
|
546
|
-
return
|
|
547
|
-
content={
|
|
548
|
-
"error": "Cannot retrieve private key; User account does not exist!"
|
|
549
|
-
},
|
|
550
|
-
status_code=HTTPStatus.BAD_REQUEST,
|
|
551
|
-
)
|
|
557
|
+
return ACCOUNT_NOT_FOUND_ERROR
|
|
552
558
|
|
|
553
559
|
data = await request.json()
|
|
554
560
|
password = data.get("password")
|
|
555
|
-
error = None
|
|
556
561
|
if operate.password is None:
|
|
557
|
-
|
|
562
|
+
return USER_NOT_LOGGED_IN_ERROR
|
|
558
563
|
if operate.password != password:
|
|
559
|
-
error = {"error": "Password is not valid"}
|
|
560
|
-
if error is not None:
|
|
561
564
|
return JSONResponse(
|
|
562
|
-
content=error,
|
|
565
|
+
content={"error": "Password is not valid."},
|
|
563
566
|
status_code=HTTPStatus.UNAUTHORIZED,
|
|
564
567
|
)
|
|
565
568
|
|
|
@@ -597,12 +600,15 @@ def create_app( # pylint: disable=too-many-locals, unused-argument, too-many-st
|
|
|
597
600
|
manager = operate.wallet_manager
|
|
598
601
|
if not manager.exists(ledger_type=ledger_type):
|
|
599
602
|
return JSONResponse(
|
|
600
|
-
content={"error": "
|
|
603
|
+
content={"error": "No Master EOA found for this chain."},
|
|
601
604
|
status_code=HTTPStatus.NOT_FOUND,
|
|
602
605
|
)
|
|
603
606
|
safes = manager.load(ledger_type=ledger_type).safes
|
|
604
607
|
if safes is None or safes.get(chain) is None:
|
|
605
|
-
return JSONResponse(
|
|
608
|
+
return JSONResponse(
|
|
609
|
+
content={"error": "No Master Safe found for this chain."},
|
|
610
|
+
status_code=HTTPStatus.NOT_FOUND,
|
|
611
|
+
)
|
|
606
612
|
|
|
607
613
|
return JSONResponse(
|
|
608
614
|
content={
|
|
@@ -616,23 +622,17 @@ def create_app( # pylint: disable=too-many-locals, unused-argument, too-many-st
|
|
|
616
622
|
) -> t.List[t.Dict]:
|
|
617
623
|
"""Create wallet safe"""
|
|
618
624
|
if operate.user_account is None:
|
|
619
|
-
return
|
|
620
|
-
content={"error": "Cannot create safe; User account does not exist!"},
|
|
621
|
-
status_code=HTTPStatus.BAD_REQUEST,
|
|
622
|
-
)
|
|
625
|
+
return ACCOUNT_NOT_FOUND_ERROR
|
|
623
626
|
|
|
624
627
|
if operate.password is None:
|
|
625
|
-
return
|
|
626
|
-
content={"error": "You need to login before creating a safe"},
|
|
627
|
-
status_code=HTTPStatus.UNAUTHORIZED,
|
|
628
|
-
)
|
|
628
|
+
return USER_NOT_LOGGED_IN_ERROR
|
|
629
629
|
|
|
630
630
|
data = await request.json()
|
|
631
631
|
|
|
632
632
|
if "initial_funds" in data and "transfer_excess_assets" in data:
|
|
633
633
|
return JSONResponse(
|
|
634
634
|
content={
|
|
635
|
-
"error": "
|
|
635
|
+
"error": "Only specify one of 'initial_funds' or 'transfer_excess_assets', but not both."
|
|
636
636
|
},
|
|
637
637
|
status_code=HTTPStatus.BAD_REQUEST,
|
|
638
638
|
)
|
|
@@ -643,14 +643,17 @@ def create_app( # pylint: disable=too-many-locals, unused-argument, too-many-st
|
|
|
643
643
|
ledger_type = chain.ledger_type
|
|
644
644
|
manager = operate.wallet_manager
|
|
645
645
|
if not manager.exists(ledger_type=ledger_type):
|
|
646
|
-
return JSONResponse(
|
|
646
|
+
return JSONResponse(
|
|
647
|
+
content={"error": "No Master EOA found for this chain."},
|
|
648
|
+
status_code=HTTPStatus.NOT_FOUND,
|
|
649
|
+
)
|
|
647
650
|
|
|
648
651
|
wallet = manager.load(ledger_type=ledger_type)
|
|
649
652
|
if wallet.safes is not None and wallet.safes.get(chain) is not None:
|
|
650
653
|
return JSONResponse(
|
|
651
654
|
content={
|
|
652
655
|
"safe": wallet.safes.get(chain),
|
|
653
|
-
"message":
|
|
656
|
+
"message": "Safe already exists for this chain.",
|
|
654
657
|
}
|
|
655
658
|
)
|
|
656
659
|
|
|
@@ -704,14 +707,15 @@ def create_app( # pylint: disable=too-many-locals, unused-argument, too-many-st
|
|
|
704
707
|
"create_tx": create_tx,
|
|
705
708
|
"transfer_txs": transfer_txs,
|
|
706
709
|
"safe": safes.get(chain),
|
|
707
|
-
"message": "Safe created
|
|
708
|
-
}
|
|
710
|
+
"message": "Safe created successfully",
|
|
711
|
+
},
|
|
712
|
+
status_code=HTTPStatus.CREATED,
|
|
709
713
|
)
|
|
710
714
|
except Exception as e: # pylint: disable=broad-except
|
|
711
|
-
logger.error(traceback.format_exc())
|
|
715
|
+
logger.error(f"Safe creation failed: {e}\n{traceback.format_exc()}")
|
|
712
716
|
return JSONResponse(
|
|
713
717
|
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
|
|
714
|
-
content={"error":
|
|
718
|
+
content={"error": "Failed to create safe. Please check the logs."},
|
|
715
719
|
)
|
|
716
720
|
|
|
717
721
|
@app.put("/api/wallet/safe")
|
|
@@ -720,22 +724,16 @@ def create_app( # pylint: disable=too-many-locals, unused-argument, too-many-st
|
|
|
720
724
|
"""Update wallet safe"""
|
|
721
725
|
# TODO: Extract login check as decorator
|
|
722
726
|
if operate.user_account is None:
|
|
723
|
-
return
|
|
724
|
-
content={"error": "Cannot update safe; User account does not exist!"},
|
|
725
|
-
status_code=HTTPStatus.BAD_REQUEST,
|
|
726
|
-
)
|
|
727
|
+
return ACCOUNT_NOT_FOUND_ERROR
|
|
727
728
|
|
|
728
729
|
if operate.password is None:
|
|
729
|
-
return
|
|
730
|
-
content={"error": "You need to login before updating a safe."},
|
|
731
|
-
status_code=HTTPStatus.UNAUTHORIZED,
|
|
732
|
-
)
|
|
730
|
+
return USER_NOT_LOGGED_IN_ERROR
|
|
733
731
|
|
|
734
732
|
data = await request.json()
|
|
735
733
|
|
|
736
734
|
if "chain" not in data:
|
|
737
735
|
return JSONResponse(
|
|
738
|
-
content={"error": "
|
|
736
|
+
content={"error": "'chain' is required."},
|
|
739
737
|
status_code=HTTPStatus.BAD_REQUEST,
|
|
740
738
|
)
|
|
741
739
|
|
|
@@ -744,7 +742,7 @@ def create_app( # pylint: disable=too-many-locals, unused-argument, too-many-st
|
|
|
744
742
|
manager = operate.wallet_manager
|
|
745
743
|
if not manager.exists(ledger_type=ledger_type):
|
|
746
744
|
return JSONResponse(
|
|
747
|
-
content={"error": "
|
|
745
|
+
content={"error": "No Master EOA found for this chain."},
|
|
748
746
|
status_code=HTTPStatus.BAD_REQUEST,
|
|
749
747
|
)
|
|
750
748
|
|
|
@@ -757,12 +755,12 @@ def create_app( # pylint: disable=too-many-locals, unused-argument, too-many-st
|
|
|
757
755
|
|
|
758
756
|
backup_owner_updated = wallet.update_backup_owner(
|
|
759
757
|
chain=chain,
|
|
760
|
-
backup_owner=backup_owner,
|
|
758
|
+
backup_owner=backup_owner,
|
|
761
759
|
)
|
|
762
760
|
message = (
|
|
763
|
-
"Backup owner updated
|
|
761
|
+
"Backup owner updated successfully"
|
|
764
762
|
if backup_owner_updated
|
|
765
|
-
else "
|
|
763
|
+
else "Backup owner is already set to this address"
|
|
766
764
|
)
|
|
767
765
|
return JSONResponse(
|
|
768
766
|
content={
|
|
@@ -947,7 +945,7 @@ def create_app( # pylint: disable=too-many-locals, unused-argument, too-many-st
|
|
|
947
945
|
withdrawal_address = (await request.json()).get("withdrawal_address")
|
|
948
946
|
if withdrawal_address is None:
|
|
949
947
|
return JSONResponse(
|
|
950
|
-
content={"error": "withdrawal_address is required"},
|
|
948
|
+
content={"error": "'withdrawal_address' is required"},
|
|
951
949
|
status_code=HTTPStatus.BAD_REQUEST,
|
|
952
950
|
)
|
|
953
951
|
|
|
@@ -989,13 +987,13 @@ def create_app( # pylint: disable=too-many-locals, unused-argument, too-many-st
|
|
|
989
987
|
from_safe=False,
|
|
990
988
|
)
|
|
991
989
|
except Exception as e: # pylint: disable=broad-except
|
|
992
|
-
logger.error(traceback.format_exc())
|
|
990
|
+
logger.error(f"Withdrawal failed: {e}\n{traceback.format_exc()}")
|
|
993
991
|
return JSONResponse(
|
|
994
992
|
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
|
|
995
|
-
content={"error":
|
|
993
|
+
content={"error": "Failed to withdraw funds. Please check the logs."},
|
|
996
994
|
)
|
|
997
995
|
|
|
998
|
-
return JSONResponse(content={"error": None})
|
|
996
|
+
return JSONResponse(content={"error": None, "message": "Withdrawal successful"})
|
|
999
997
|
|
|
1000
998
|
@app.post("/api/bridge/bridge_refill_requirements")
|
|
1001
999
|
@with_retries
|
|
@@ -1016,19 +1014,26 @@ def create_app( # pylint: disable=too-many-locals, unused-argument, too-many-st
|
|
|
1016
1014
|
status_code=HTTPStatus.OK,
|
|
1017
1015
|
)
|
|
1018
1016
|
except ValueError as e:
|
|
1017
|
+
logger.error(f"Bridge refill requirements error: {e}")
|
|
1019
1018
|
return JSONResponse(
|
|
1020
|
-
content={"error":
|
|
1019
|
+
content={"error": "Invalid bridge request parameters."},
|
|
1020
|
+
status_code=HTTPStatus.BAD_REQUEST,
|
|
1021
1021
|
)
|
|
1022
1022
|
except Exception as e: # pylint: disable=broad-except
|
|
1023
|
+
logger.error(
|
|
1024
|
+
f"Bridge refill requirements error: {e}\n{traceback.format_exc()}"
|
|
1025
|
+
)
|
|
1023
1026
|
return JSONResponse(
|
|
1024
|
-
content={
|
|
1027
|
+
content={
|
|
1028
|
+
"error": "Failed to get bridge requirements. Please check the logs."
|
|
1029
|
+
},
|
|
1025
1030
|
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
|
|
1026
1031
|
)
|
|
1027
1032
|
|
|
1028
1033
|
@app.post("/api/bridge/execute")
|
|
1029
1034
|
@with_retries
|
|
1030
1035
|
async def _bridge_execute(request: Request) -> JSONResponse:
|
|
1031
|
-
"""
|
|
1036
|
+
"""Execute bridge transaction."""
|
|
1032
1037
|
if operate.password is None:
|
|
1033
1038
|
return USER_NOT_LOGGED_IN_ERROR
|
|
1034
1039
|
|
|
@@ -1041,12 +1046,17 @@ def create_app( # pylint: disable=too-many-locals, unused-argument, too-many-st
|
|
|
1041
1046
|
status_code=HTTPStatus.OK,
|
|
1042
1047
|
)
|
|
1043
1048
|
except ValueError as e:
|
|
1049
|
+
logger.error(f"Bridge execute error: {e}")
|
|
1044
1050
|
return JSONResponse(
|
|
1045
|
-
content={"error":
|
|
1051
|
+
content={"error": "Invalid bundle ID or transaction failed."},
|
|
1052
|
+
status_code=HTTPStatus.BAD_REQUEST,
|
|
1046
1053
|
)
|
|
1047
1054
|
except Exception as e: # pylint: disable=broad-except
|
|
1055
|
+
logger.error(f"Bridge execute error: {e}\n{traceback.format_exc()}")
|
|
1048
1056
|
return JSONResponse(
|
|
1049
|
-
content={
|
|
1057
|
+
content={
|
|
1058
|
+
"error": "Failed to execute bridge transaction. Please check the logs."
|
|
1059
|
+
},
|
|
1050
1060
|
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
|
|
1051
1061
|
)
|
|
1052
1062
|
|
|
@@ -1060,7 +1070,7 @@ def create_app( # pylint: disable=too-many-locals, unused-argument, too-many-st
|
|
|
1060
1070
|
@app.get("/api/bridge/status/{id}")
|
|
1061
1071
|
@with_retries
|
|
1062
1072
|
async def _bridge_status(request: Request) -> JSONResponse:
|
|
1063
|
-
"""Get
|
|
1073
|
+
"""Get bridge transaction status."""
|
|
1064
1074
|
|
|
1065
1075
|
quote_bundle_id = request.path_params["id"]
|
|
1066
1076
|
|
|
@@ -1072,12 +1082,17 @@ def create_app( # pylint: disable=too-many-locals, unused-argument, too-many-st
|
|
|
1072
1082
|
status_code=HTTPStatus.OK,
|
|
1073
1083
|
)
|
|
1074
1084
|
except ValueError as e:
|
|
1085
|
+
logger.error(f"Bridge status error: {e}")
|
|
1075
1086
|
return JSONResponse(
|
|
1076
|
-
content={"error":
|
|
1087
|
+
content={"error": "Invalid bundle ID."},
|
|
1088
|
+
status_code=HTTPStatus.BAD_REQUEST,
|
|
1077
1089
|
)
|
|
1078
1090
|
except Exception as e: # pylint: disable=broad-except
|
|
1091
|
+
logger.error(f"Bridge status error: {e}\n{traceback.format_exc()}")
|
|
1079
1092
|
return JSONResponse(
|
|
1080
|
-
content={
|
|
1093
|
+
content={
|
|
1094
|
+
"error": "Failed to get bridge status. Please check the logs."
|
|
1095
|
+
},
|
|
1081
1096
|
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
|
|
1082
1097
|
)
|
|
1083
1098
|
|
|
@@ -1093,20 +1108,35 @@ def _operate() -> None:
|
|
|
1093
1108
|
def _daemon(
|
|
1094
1109
|
host: Annotated[str, params.String(help="HTTP server host string")] = "localhost",
|
|
1095
1110
|
port: Annotated[int, params.Integer(help="HTTP server port")] = 8000,
|
|
1111
|
+
ssl_keyfile: Annotated[str, params.String(help="Path to SSL key file")] = "",
|
|
1112
|
+
ssl_certfile: Annotated[
|
|
1113
|
+
str, params.String(help="Path to SSL certificate file")
|
|
1114
|
+
] = "",
|
|
1096
1115
|
home: Annotated[
|
|
1097
1116
|
t.Optional[Path], params.Directory(long_flag="--home", help="Home directory")
|
|
1098
1117
|
] = None,
|
|
1099
1118
|
) -> None:
|
|
1100
1119
|
"""Launch operate daemon."""
|
|
1101
1120
|
app = create_app(home=home)
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1121
|
+
logger = setup_logger(name="daemon")
|
|
1122
|
+
|
|
1123
|
+
config_kwargs = {
|
|
1124
|
+
"app": app,
|
|
1125
|
+
"host": host,
|
|
1126
|
+
"port": port,
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
# Use SSL certificates if ssl_keyfile and ssl_certfile are provided
|
|
1130
|
+
if ssl_keyfile and ssl_certfile:
|
|
1131
|
+
logger.info(f"Using SSL certificates: {ssl_certfile}")
|
|
1132
|
+
config_kwargs.update(
|
|
1133
|
+
{
|
|
1134
|
+
"ssl_keyfile": ssl_keyfile,
|
|
1135
|
+
"ssl_certfile": ssl_certfile,
|
|
1136
|
+
}
|
|
1108
1137
|
)
|
|
1109
|
-
|
|
1138
|
+
|
|
1139
|
+
server = Server(Config(**config_kwargs))
|
|
1110
1140
|
app._server = server # pylint: disable=protected-access
|
|
1111
1141
|
server.run()
|
|
1112
1142
|
|
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:"
|
operate/ledger/profiles.py
CHANGED
|
@@ -154,7 +154,17 @@ STAKING: t.Dict[Chain, t.Dict[str, str]] = {
|
|
|
154
154
|
},
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
|
|
157
|
+
|
|
158
|
+
DEFAULT_PRIORITY_MECH = { # maps mech marketplace address to its default priority mech address and service id
|
|
159
|
+
"0x4554fE75c1f5576c1d7F765B2A036c199Adae329": (
|
|
160
|
+
"0x552cEA7Bc33CbBEb9f1D90c1D11D2C6daefFd053",
|
|
161
|
+
975,
|
|
162
|
+
),
|
|
163
|
+
"0x735FAAb1c4Ec41128c367AFb5c3baC73509f70bB": (
|
|
164
|
+
"0xC05e7412439bD7e91730a6880E18d5D5873F632C",
|
|
165
|
+
2182,
|
|
166
|
+
),
|
|
167
|
+
}
|
|
158
168
|
|
|
159
169
|
|
|
160
170
|
# ERC20 token addresses
|
operate/operate_types.py
CHANGED
|
@@ -329,3 +329,13 @@ class AssetFundingValues(TypedDict):
|
|
|
329
329
|
|
|
330
330
|
|
|
331
331
|
FundingValues = t.Dict[str, AssetFundingValues] # str is the asset address
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
@dataclass
|
|
335
|
+
class MechMarketplaceConfig:
|
|
336
|
+
"""Mech Marketplace config."""
|
|
337
|
+
|
|
338
|
+
use_mech_marketplace: bool
|
|
339
|
+
mech_marketplace_address: str
|
|
340
|
+
priority_mech_address: str
|
|
341
|
+
priority_mech_service_id: int
|
|
@@ -73,7 +73,7 @@ def claim_staking_rewards(operate: "OperateApp", config_path: str) -> None:
|
|
|
73
73
|
config = configure_local_config(template, operate)
|
|
74
74
|
manager = operate.service_manager()
|
|
75
75
|
service = get_service(manager, template)
|
|
76
|
-
ask_password_if_needed(operate
|
|
76
|
+
ask_password_if_needed(operate)
|
|
77
77
|
|
|
78
78
|
# reload manger and config after setting operate.password
|
|
79
79
|
manager = operate.service_manager()
|
|
@@ -82,7 +82,7 @@ def reset_staking(operate: "OperateApp", config_path: str) -> None:
|
|
|
82
82
|
print("Cancelled.")
|
|
83
83
|
return
|
|
84
84
|
|
|
85
|
-
ask_password_if_needed(operate
|
|
85
|
+
ask_password_if_needed(operate)
|
|
86
86
|
manager = operate.service_manager()
|
|
87
87
|
service = get_service(manager, template)
|
|
88
88
|
|