-
Notifications
You must be signed in to change notification settings - Fork 46
/
wallet.py
170 lines (131 loc) · 5.1 KB
/
wallet.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
import json
import re
import click
from algosdk import account
from algokit.cli.tasks.utils import get_private_key_from_mnemonic, validate_address
from algokit.core.tasks.wallet import (
WALLET_ALIASING_MAX_LIMIT,
WalletAliasingLimitError,
add_alias,
get_alias,
get_aliases,
remove_alias,
)
def _validate_alias_name(alias_name: str) -> None:
pattern = r"^[\w-]{1,20}$"
if not re.match(pattern, alias_name):
raise click.ClickException(
"Invalid alias name. It should have at most 20 characters consisting of numbers, "
"letters, dashes, or underscores."
)
@click.group()
def wallet() -> None:
"""Create short aliases for your addresses and accounts on AlgoKit CLI."""
@wallet.command("add")
@click.argument("alias_name", type=click.STRING)
@click.option("--address", "-a", type=click.STRING, required=True, help="The address of the account.")
@click.option(
"--mnemonic",
"-m",
"use_mnemonic",
is_flag=True,
help="If specified then prompt the user for a mnemonic phrase interactively using masked input.",
)
@click.option("--force", "-f", is_flag=True, help="Allow overwriting an existing alias.", type=click.BOOL)
def add(*, alias_name: str, address: str, use_mnemonic: bool, force: bool) -> None:
"""Add an address or account to be stored against a named alias (at most 50 aliases)."""
_validate_alias_name(alias_name)
validate_address(address)
private_key = get_private_key_from_mnemonic() if use_mnemonic else None
if use_mnemonic:
derived_address = account.address_from_private_key(private_key) # type: ignore[no-untyped-call]
if derived_address != address:
click.echo(
"Warning: Address from the mnemonic doesn't match the provided address. "
"It won't work unless the account has been rekeyed."
)
if get_alias(alias_name) and not force:
response = click.prompt(
f"Alias '{alias_name}' already exists. Overwrite?",
type=click.Choice(["y", "n"]),
default="n",
)
if response == "n":
return
try:
add_alias(alias_name, address, private_key)
except WalletAliasingLimitError as ex:
raise click.ClickException(f"Reached the max of {WALLET_ALIASING_MAX_LIMIT} aliases.") from ex
except Exception as ex:
raise click.ClickException("Failed to add alias") from ex
else:
click.echo(f"Alias '{alias_name}' added successfully.")
@wallet.command("get")
@click.argument("alias", type=click.STRING)
def get(alias: str) -> None:
"""Get an address or account stored against a named alias."""
alias_data = get_alias(alias)
if not alias_data:
raise click.ClickException(f"Alias `{alias}` does not exist.")
click.echo(
f"Address for alias `{alias}`: {alias_data.address}"
f"{' (🔐 includes private key)' if alias_data.private_key else ''}"
)
@wallet.command("list")
def list_all() -> None:
"""List all addresses and accounts stored against a named alias."""
aliases = get_aliases()
output = [
{
"alias": alias_data.alias,
"address": alias_data.address,
"has_private_key": bool(alias_data.private_key),
}
for alias_data in aliases
]
content = (
json.dumps(output, indent=2)
if output
else "You don't have any aliases stored yet. Create one using `algokit task wallet add`."
)
click.echo(content)
@wallet.command("remove")
@click.argument("alias", type=click.STRING)
@click.option("--force", "-f", is_flag=True, help="Allow removing an alias without confirmation.")
def remove(*, alias: str, force: bool) -> None:
"""Remove an address or account stored against a named alias."""
alias_data = get_alias(alias)
if not alias_data:
raise click.ClickException(f"Alias `{alias}` does not exist.")
if not force:
response = click.prompt(
f"🚨 This is a destructive action that will remove the `{alias_data.alias}` alias. Are you sure?",
type=click.Choice(["y", "n"]),
default="n",
)
if response == "n":
return
remove_alias(alias)
click.echo(f"Alias `{alias}` removed successfully.")
@wallet.command("reset")
@click.option("--force", "-f", is_flag=True, help="Allow removing all aliases without confirmation.")
def reset(*, force: bool) -> None:
"""Remove all aliases."""
aliases = get_aliases()
if not aliases:
click.echo("Warning: No aliases available to reset.")
return
if not force:
response = click.prompt(
"🚨 This is a destructive action that will clear all aliases. Are you sure?",
type=click.Choice(["y", "n"]),
default="n",
)
if response == "n":
return
for alias_data in aliases:
try:
remove_alias(alias_data.alias)
except Exception as ex:
raise click.ClickException(f"Failed to remove alias {alias_data.alias}") from ex
click.echo("All aliases have been cleared.")