-
Notifications
You must be signed in to change notification settings - Fork 46
/
wallet.py
169 lines (122 loc) · 4.76 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
import json
import logging
from dataclasses import dataclass
import keyring
logger = logging.getLogger(__name__)
WALLET_ALIAS_KEYRING_NAMESPACE = "algokit_alias"
WALLET_ALIASES_KEYRING_NAMESPACE = "algokit_aliases"
WALLET_ALIASES_KEYRING_USERNAME = "aliases"
# Windows Credentials locker has a max limit of 1280 chars per password length.
# Given that each alias is at most 20 chars, around ~50 alias keys can be stored within single password field.
# Hence the limitation of 50 aliases.
WALLET_ALIASING_MAX_LIMIT = 50
@dataclass
class WalletAliasKeyringData:
alias: str
address: str
private_key: str | None
class WalletAliasingLimitError(Exception):
pass
def _get_alias_keys() -> list[str]:
try:
response = keyring.get_password(
service_name=WALLET_ALIASES_KEYRING_NAMESPACE, username=WALLET_ALIASES_KEYRING_USERNAME
)
if not response:
return []
alias_keys: list[str] = json.loads(response)
return alias_keys
except Exception as ex:
logger.debug("Failed to get alias keys from keyring", exc_info=ex)
return []
def _update_alias_keys(alias_keys: list[str]) -> None:
keyring.set_password(
service_name=WALLET_ALIASES_KEYRING_NAMESPACE,
username=WALLET_ALIASES_KEYRING_USERNAME,
password=json.dumps(alias_keys, separators=(",", ":")),
)
def _add_alias_key(alias_name: str) -> None:
alias_keys = _get_alias_keys()
if len(alias_keys) >= WALLET_ALIASING_MAX_LIMIT:
raise WalletAliasingLimitError("You have reached the maximum number of aliases.")
if alias_name not in alias_keys:
alias_keys.append(alias_name)
_update_alias_keys(alias_keys)
def _remove_alias_key(alias_name: str) -> None:
alias_keys = _get_alias_keys()
if alias_name in alias_keys:
alias_keys.remove(alias_name)
_update_alias_keys(alias_keys)
def add_alias(alias_name: str, address: str, private_key: str | None) -> None:
"""
Add an address or account to be stored against a named alias in keyring.
Args:
alias_name (str): The name of the alias to be added.
address (str): The address or account to be stored against the alias.
private_key (str | None): The private key associated with the address or account.
It can be None if no private key is available.
Raises:
WalletAliasingLimitError: If the maximum number of aliases has been reached.
"""
try:
_add_alias_key(alias_name)
keyring.set_password(
service_name=WALLET_ALIAS_KEYRING_NAMESPACE,
username=alias_name,
password=json.dumps(
WalletAliasKeyringData(
alias=alias_name,
address=address,
private_key=private_key,
).__dict__
),
)
except Exception as ex:
logger.debug("Failed to add alias to keyring", exc_info=ex)
raise ex
def get_alias(alias_name: str) -> WalletAliasKeyringData | None:
"""
Get the address or account stored against a named alias in the keyring.
Args:
alias_name (str): The name of the alias to retrieve.
Returns:
WalletAliasKeyringData | None: An instance of the WalletAliasKeyringData class if the alias exists,
otherwise None.
Example Usage:
alias_data = get_alias("my_alias")
if alias_data:
print(alias_data.address)
"""
try:
response = keyring.get_password(service_name=WALLET_ALIAS_KEYRING_NAMESPACE, username=alias_name)
if not response:
return None
return WalletAliasKeyringData(**json.loads(response))
except Exception as ex:
logger.debug(f"`{alias_name}` does not exist", exc_info=ex)
return None
def get_aliases() -> list[WalletAliasKeyringData]:
"""
Retrieves a list of wallet aliases and their associated data from a keyring.
Returns:
A list of WalletAliasKeyringData objects representing the aliases and their associated data.
"""
try:
alias_keys = _get_alias_keys()
response: list[WalletAliasKeyringData] = []
for alias_name in alias_keys:
alias_data = get_alias(alias_name)
if alias_data:
response.append(alias_data)
return response
except Exception as ex:
logger.debug("Failed to get aliases from keyring", exc_info=ex)
return []
def remove_alias(alias_name: str) -> None:
"""
Remove an address or account stored against a named alias in keyring.
:param alias_name: The name of the alias to be removed.
:type alias_name: str
"""
keyring.delete_password(service_name=WALLET_ALIAS_KEYRING_NAMESPACE, username=alias_name)
_remove_alias_key(alias_name)