Skip to content

Commit 4880527

Browse files
Merge pull request #13 from ElrondNetwork/ledger-integration
migrate erdpy ledger support from old repo
2 parents c514b4d + 9c36ba3 commit 4880527

15 files changed

Lines changed: 473 additions & 33 deletions

README.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ Elrond Python Command Line Tools and SDK for interacting with the Elrond Network
44
## Documentation
55
[docs.elrond.com](https://docs.elrond.com/sdk-and-tools/erdpy/erdpy/)
66

7-
[pkg.go.dev](https://pkg.go.dev/github.com/ElrondNetwork/elrond-sdk/erdgo)
8-
97
## CLI
108
[CLI](CLI.md)
119

erdpy/CLI.md

Lines changed: 104 additions & 24 deletions
Large diffs are not rendered by default.

erdpy/accounts.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from typing import Any, Union
66

77
from erdpy import constants, errors, utils
8+
from erdpy.errors import LedgerError
89
from erdpy.interfaces import IAccount, IAddress
910
from erdpy.wallet import bech32, generate_pair, pem
1011
from erdpy.wallet.keyfile import get_password, load_from_key_file
@@ -44,11 +45,13 @@ def get_all(self):
4445

4546

4647
class Account(IAccount):
47-
def __init__(self, address: Any = None, pem_file: Union[str, None] = None, pem_index: int = 0, key_file: str = "", pass_file: str = ""):
48+
def __init__(self, address: Any = None, pem_file: Union[str, None] = None, pem_index: int = 0, key_file: str = "", pass_file: str = "",
49+
ledger: bool = False):
4850
self.address = Address(address)
4951
self.pem_file = pem_file
5052
self.pem_index = int(pem_index)
5153
self.nonce: int = 0
54+
self.ledger = ledger
5255

5356
if pem_file:
5457
seed, pubkey = pem.parse(self.pem_file, self.pem_index)
@@ -66,6 +69,8 @@ def sync_nonce(self, proxy: Any):
6669
logger.info(f"Account.sync_nonce() done: {self.nonce}")
6770

6871
def get_seed(self) -> bytes:
72+
if self.ledger:
73+
raise LedgerError("cannot get seed from a Ledger account")
6974
return unhexlify(self.private_key_seed)
7075

7176

erdpy/cli.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import erdpy.cli_data
1414
import erdpy.cli_deps
1515
import erdpy.cli_dispatcher
16+
import erdpy.cli_ledger
1617
import erdpy.cli_network
1718
import erdpy.cli_testnet
1819
import erdpy.cli_transactions
@@ -85,6 +86,7 @@ def setup_parser():
8586
commands.append(erdpy.cli_transactions.setup_parser(subparsers))
8687
commands.append(erdpy.cli_validators.setup_parser(subparsers))
8788
commands.append(erdpy.cli_accounts.setup_parser(subparsers))
89+
commands.append(erdpy.cli_ledger.setup_parser(subparsers))
8890
commands.append(erdpy.cli_wallet.setup_parser(subparsers))
8991
commands.append(erdpy.cli_network.setup_parser(subparsers))
9092
commands.append(erdpy.cli_cost.setup_parser(subparsers))

erdpy/cli_ledger.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
from erdpy.ledger.ledger_app_handler import ElrondLedgerApp
2+
from erdpy import cli_shared
3+
import logging
4+
from typing import Any
5+
6+
logger = logging.getLogger("cli.ledger")
7+
8+
9+
def setup_parser(subparsers: Any) -> Any:
10+
parser = cli_shared.add_group_subparser(subparsers, "ledger", "Get Ledger App addresses and version")
11+
subparsers = parser.add_subparsers()
12+
13+
sub = cli_shared.add_command_subparser(subparsers, "ledger", "addresses", "Get the addresses within Ledger")
14+
sub.add_argument("--num-addresses", required=False, type=int, default=10, help="The number of addresses to fetch")
15+
sub.set_defaults(func=print_addresses)
16+
17+
sub = cli_shared.add_command_subparser(subparsers, "ledger", "version", "Get the version of the Elrond App for Ledger")
18+
sub.set_defaults(func=print_version)
19+
20+
return subparsers
21+
22+
23+
def print_addresses(args):
24+
ledger_app = ElrondLedgerApp()
25+
for i in range(args.num_addresses):
26+
address = ledger_app.get_address(0, i)
27+
print('account index = %d | address index = %d | address: %s' % (0, i, address))
28+
ledger_app.close()
29+
30+
31+
def print_version(args):
32+
ledger_app = ElrondLedgerApp()
33+
print("Elrond App version: " + ledger_app.get_version())
34+
ledger_app.close()

erdpy/cli_shared.py

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import argparse
22
import ast
3+
import string
34
import sys
45
from argparse import FileType
56
from typing import Any, List, Text
67

78
from erdpy import config, errors, scope, utils
89
from erdpy.accounts import Account
10+
from erdpy.ledger.ledger_functions import do_get_ledger_address
911
from erdpy.proxy.core import ElrondProxy
1012
from erdpy.transactions import Transaction
1113

@@ -69,18 +71,22 @@ def add_tx_args(sub: Any, with_nonce: bool = True, with_receiver: bool = True, w
6971

7072
sub.add_argument("--chain", default=scope.get_chain_id(), help="the chain identifier (default: %(default)s)")
7173
sub.add_argument("--version", type=int, default=scope.get_tx_version(), help="the transaction version (default: %(default)s)")
74+
sub.add_argument("--options", type=int, default=0, help="the transaction options (default: 0)")
7275

7376

7477
def add_wallet_args(sub: Any):
75-
sub.add_argument("--pem", required=not (utils.is_arg_present("--keyfile", sys.argv)), help="🔑 the PEM file, if keyfile not provided")
78+
sub.add_argument("--pem", required=check_if_sign_method_required("--pem"), help="🔑 the PEM file, if keyfile not provided")
7679
sub.add_argument("--pem-index", default=0, help="🔑 the index in the PEM file (default: %(default)s)")
77-
sub.add_argument("--keyfile", required=not (utils.is_arg_present("--pem", sys.argv)), help="🔑 a JSON keyfile, if PEM not provided")
78-
sub.add_argument("--passfile", required=not (utils.is_arg_present("--pem", sys.argv)), help="🔑 a file containing keyfile's password, if keyfile provided")
80+
sub.add_argument("--keyfile", required=check_if_sign_method_required("--keyfile"), help="🔑 a JSON keyfile, if PEM not provided")
81+
sub.add_argument("--passfile", required=(utils.is_arg_present("--keyfile", sys.argv)), help="🔑 a file containing keyfile's password, if keyfile provided")
82+
sub.add_argument("--ledger", action="store_true", required=check_if_sign_method_required("--ledger"), default=False, help="🔐 bool flag for signing transaction using ledger")
83+
sub.add_argument("--ledger-account-index", type=int, default=0, help="🔐 the index of the account when using Ledger")
84+
sub.add_argument("--ledger-address-index", type=int, default=0, help="🔐 the index of the address when using Ledger")
7985
sub.add_argument("--sender-username", required=False, help="🖄 the username of the sender")
8086

8187

8288
def add_proxy_arg(sub: Any):
83-
sub.add_argument("--proxy", default=scope.get_proxy(), help="🖧 the URL of the proxy (default: %(default)s)")
89+
sub.add_argument("--proxy", default=scope.get_proxy(), help="🔗 the URL of the proxy (default: %(default)s)")
8490

8591

8692
def add_outfile_arg(sub: Any, what: str = ""):
@@ -109,6 +115,9 @@ def prepare_nonce_in_args(args: Any):
109115
account = Account(pem_file=args.pem, pem_index=args.pem_index)
110116
elif args.keyfile and args.passfile:
111117
account = Account(key_file=args.keyfile, pass_file=args.passfile)
118+
elif args.ledger:
119+
address = do_get_ledger_address(account_index=args.ledger_account_index, address_index=args.ledger_address_index)
120+
account = Account(address=address)
112121
else:
113122
raise errors.NoWalletProvided()
114123

@@ -138,3 +147,17 @@ def send_or_simulate(tx: Transaction, args: Any):
138147
elif args.simulate:
139148
response = tx.simulate(ElrondProxy(args.proxy))
140149
utils.dump_out_json(response)
150+
151+
152+
def check_if_sign_method_required(checked_method: string) -> bool:
153+
methods = ["--pem", "--keyfile", "--ledger"]
154+
rest_of_methods = []
155+
for method in methods:
156+
if method != checked_method:
157+
rest_of_methods.append(method)
158+
159+
for method in rest_of_methods:
160+
if utils.is_arg_present(method, sys.argv):
161+
return False
162+
163+
return True

erdpy/cli_transactions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from typing import Any
33

44
from erdpy import cli_shared, utils
5+
from erdpy.ledger.ledger_functions import do_get_ledger_address
56
from erdpy.proxy.core import ElrondProxy
67
from erdpy.transactions import Transaction, do_prepare_transaction
78

@@ -51,6 +52,9 @@ def create_transaction(args: Any):
5152
cli_shared.check_broadcast_args(args)
5253
cli_shared.prepare_nonce_in_args(args)
5354

55+
if args.ledger:
56+
args.ledger_address = do_get_ledger_address(account_index=args.ledger_account_index, address_index=args.ledger_address_index)
57+
5458
if args.data_file:
5559
args.data = utils.read_file(args.data_file)
5660

erdpy/errors.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,3 +178,8 @@ def __init__(self):
178178
class TestnetError(KnownError):
179179
def __init__(self, message: str):
180180
super().__init__(message)
181+
182+
183+
class LedgerError(KnownError):
184+
def __init__(self, message: str):
185+
super().__init__("Ledger error: " + message)

erdpy/ledger/__init__.py

Whitespace-only changes.

erdpy/ledger/config.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import string
2+
3+
4+
def load_ledger_config_from_response(response: bytes):
5+
config = ElrondLedgerAppConfiguration()
6+
7+
config.data_activated = False
8+
if response[0] == 0x01:
9+
config.data_activated = True
10+
11+
config.account_index = response[1]
12+
config.address_index = response[2]
13+
14+
version = str(response[3]) + "." + str(response[4]) + "." + str(response[5])
15+
config.version = version
16+
17+
return config
18+
19+
20+
def compare_versions(version1: string, version2: string) -> int:
21+
version1_tuple = version_tuple(version1)
22+
version2_tuple = version_tuple(version2)
23+
if version1_tuple == version2_tuple:
24+
return 0
25+
if version1_tuple < version2_tuple:
26+
return -1
27+
return 1
28+
29+
30+
def version_tuple(v):
31+
filled = []
32+
for point in v.split("."):
33+
filled.append(point.zfill(8))
34+
return tuple(filled)
35+
36+
37+
class ElrondLedgerAppConfiguration:
38+
data_activated: bool
39+
account_index: int
40+
address_index: int
41+
version: string

0 commit comments

Comments
 (0)