meshtensor-cli 9.18.1__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.
- meshtensor_cli/__init__.py +22 -0
- meshtensor_cli/cli.py +10742 -0
- meshtensor_cli/doc_generation_helper.py +4 -0
- meshtensor_cli/src/__init__.py +1085 -0
- meshtensor_cli/src/commands/__init__.py +0 -0
- meshtensor_cli/src/commands/axon/__init__.py +0 -0
- meshtensor_cli/src/commands/axon/axon.py +132 -0
- meshtensor_cli/src/commands/crowd/__init__.py +0 -0
- meshtensor_cli/src/commands/crowd/contribute.py +621 -0
- meshtensor_cli/src/commands/crowd/contributors.py +200 -0
- meshtensor_cli/src/commands/crowd/create.py +783 -0
- meshtensor_cli/src/commands/crowd/dissolve.py +219 -0
- meshtensor_cli/src/commands/crowd/refund.py +233 -0
- meshtensor_cli/src/commands/crowd/update.py +418 -0
- meshtensor_cli/src/commands/crowd/utils.py +124 -0
- meshtensor_cli/src/commands/crowd/view.py +991 -0
- meshtensor_cli/src/commands/governance/__init__.py +0 -0
- meshtensor_cli/src/commands/governance/governance.py +794 -0
- meshtensor_cli/src/commands/liquidity/__init__.py +0 -0
- meshtensor_cli/src/commands/liquidity/liquidity.py +699 -0
- meshtensor_cli/src/commands/liquidity/utils.py +202 -0
- meshtensor_cli/src/commands/proxy.py +700 -0
- meshtensor_cli/src/commands/stake/__init__.py +0 -0
- meshtensor_cli/src/commands/stake/add.py +799 -0
- meshtensor_cli/src/commands/stake/auto_staking.py +306 -0
- meshtensor_cli/src/commands/stake/children_hotkeys.py +865 -0
- meshtensor_cli/src/commands/stake/claim.py +770 -0
- meshtensor_cli/src/commands/stake/list.py +738 -0
- meshtensor_cli/src/commands/stake/move.py +1211 -0
- meshtensor_cli/src/commands/stake/remove.py +1466 -0
- meshtensor_cli/src/commands/stake/wizard.py +323 -0
- meshtensor_cli/src/commands/subnets/__init__.py +0 -0
- meshtensor_cli/src/commands/subnets/mechanisms.py +515 -0
- meshtensor_cli/src/commands/subnets/price.py +733 -0
- meshtensor_cli/src/commands/subnets/subnets.py +2908 -0
- meshtensor_cli/src/commands/sudo.py +1294 -0
- meshtensor_cli/src/commands/tc/__init__.py +0 -0
- meshtensor_cli/src/commands/tc/tc.py +190 -0
- meshtensor_cli/src/commands/treasury/__init__.py +0 -0
- meshtensor_cli/src/commands/treasury/treasury.py +194 -0
- meshtensor_cli/src/commands/view.py +354 -0
- meshtensor_cli/src/commands/wallets.py +2311 -0
- meshtensor_cli/src/commands/weights.py +467 -0
- meshtensor_cli/src/meshtensor/__init__.py +0 -0
- meshtensor_cli/src/meshtensor/balances.py +313 -0
- meshtensor_cli/src/meshtensor/chain_data.py +1263 -0
- meshtensor_cli/src/meshtensor/extrinsics/__init__.py +0 -0
- meshtensor_cli/src/meshtensor/extrinsics/mev_shield.py +174 -0
- meshtensor_cli/src/meshtensor/extrinsics/registration.py +1861 -0
- meshtensor_cli/src/meshtensor/extrinsics/root.py +550 -0
- meshtensor_cli/src/meshtensor/extrinsics/serving.py +255 -0
- meshtensor_cli/src/meshtensor/extrinsics/transfer.py +239 -0
- meshtensor_cli/src/meshtensor/meshtensor_interface.py +2598 -0
- meshtensor_cli/src/meshtensor/minigraph.py +254 -0
- meshtensor_cli/src/meshtensor/networking.py +12 -0
- meshtensor_cli/src/meshtensor/templates/main-filters.j2 +24 -0
- meshtensor_cli/src/meshtensor/templates/main-header.j2 +36 -0
- meshtensor_cli/src/meshtensor/templates/neuron-details.j2 +111 -0
- meshtensor_cli/src/meshtensor/templates/price-multi.j2 +113 -0
- meshtensor_cli/src/meshtensor/templates/price-single.j2 +99 -0
- meshtensor_cli/src/meshtensor/templates/subnet-details-header.j2 +49 -0
- meshtensor_cli/src/meshtensor/templates/subnet-details.j2 +32 -0
- meshtensor_cli/src/meshtensor/templates/subnet-metrics.j2 +57 -0
- meshtensor_cli/src/meshtensor/templates/subnets-table.j2 +28 -0
- meshtensor_cli/src/meshtensor/templates/table.j2 +267 -0
- meshtensor_cli/src/meshtensor/templates/view.css +1058 -0
- meshtensor_cli/src/meshtensor/templates/view.j2 +43 -0
- meshtensor_cli/src/meshtensor/templates/view.js +1053 -0
- meshtensor_cli/src/meshtensor/utils.py +2007 -0
- meshtensor_cli/version.py +23 -0
- meshtensor_cli-9.18.1.dist-info/METADATA +261 -0
- meshtensor_cli-9.18.1.dist-info/RECORD +74 -0
- meshtensor_cli-9.18.1.dist-info/WHEEL +4 -0
- meshtensor_cli-9.18.1.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,2311 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import itertools
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from collections import defaultdict
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from typing import Generator, Optional, Union
|
|
8
|
+
|
|
9
|
+
import aiohttp
|
|
10
|
+
from meshtensor_wallet import Wallet, Keypair
|
|
11
|
+
from meshtensor_wallet.errors import KeyFileError
|
|
12
|
+
from meshtensor_wallet.keyfile import Keyfile
|
|
13
|
+
from rich import box
|
|
14
|
+
from rich.align import Align
|
|
15
|
+
from rich.table import Column, Table
|
|
16
|
+
from rich.tree import Tree
|
|
17
|
+
from rich.padding import Padding
|
|
18
|
+
from meshtensor_cli.src import COLOR_PALETTE, COLORS, Constants
|
|
19
|
+
from meshtensor_cli.src.meshtensor import utils
|
|
20
|
+
from meshtensor_cli.src.meshtensor.balances import Balance
|
|
21
|
+
from meshtensor_cli.src.meshtensor.chain_data import (
|
|
22
|
+
DelegateInfo,
|
|
23
|
+
NeuronInfoLite,
|
|
24
|
+
)
|
|
25
|
+
from meshtensor_cli.src.meshtensor.extrinsics.registration import (
|
|
26
|
+
run_faucet_extrinsic,
|
|
27
|
+
swap_hotkey_extrinsic,
|
|
28
|
+
)
|
|
29
|
+
from meshtensor_cli.src.meshtensor.extrinsics.transfer import transfer_extrinsic
|
|
30
|
+
from meshtensor_cli.src.meshtensor.networking import int_to_ip
|
|
31
|
+
from meshtensor_cli.src.meshtensor.meshtensor_interface import (
|
|
32
|
+
MeshtensorInterface,
|
|
33
|
+
GENESIS_ADDRESS,
|
|
34
|
+
)
|
|
35
|
+
from meshtensor_cli.src.meshtensor.utils import (
|
|
36
|
+
MESHLET_PER_MESH,
|
|
37
|
+
confirm_action,
|
|
38
|
+
console,
|
|
39
|
+
convert_blocks_to_time,
|
|
40
|
+
json_console,
|
|
41
|
+
print_error,
|
|
42
|
+
print_verbose,
|
|
43
|
+
get_all_wallets_for_path,
|
|
44
|
+
get_hotkey_wallets_for_wallet,
|
|
45
|
+
is_valid_ss58_address,
|
|
46
|
+
validate_coldkey_presence,
|
|
47
|
+
get_subnet_name,
|
|
48
|
+
millify_tao,
|
|
49
|
+
unlock_key,
|
|
50
|
+
WalletLike,
|
|
51
|
+
blocks_to_duration,
|
|
52
|
+
decode_account_id,
|
|
53
|
+
get_hotkey_pub_ss58,
|
|
54
|
+
print_extrinsic_id,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class SortByBalance(Enum):
|
|
59
|
+
name = "name"
|
|
60
|
+
free = "free"
|
|
61
|
+
staked = "staked"
|
|
62
|
+
total = "total"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _sort_by_balance_key(sort_by: SortByBalance):
|
|
66
|
+
"""Get the sort key function based on the enum"""
|
|
67
|
+
if sort_by == SortByBalance.name:
|
|
68
|
+
return lambda row: row[0].lower() # Case-insensitive alphabetical sort
|
|
69
|
+
elif sort_by == SortByBalance.free:
|
|
70
|
+
return lambda row: row[2]
|
|
71
|
+
elif sort_by == SortByBalance.staked:
|
|
72
|
+
return lambda row: row[3]
|
|
73
|
+
elif sort_by == SortByBalance.total:
|
|
74
|
+
return lambda row: row[4]
|
|
75
|
+
else:
|
|
76
|
+
raise ValueError("Invalid sort key")
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
async def associate_hotkey(
|
|
80
|
+
wallet: Wallet,
|
|
81
|
+
meshtensor: MeshtensorInterface,
|
|
82
|
+
hotkey_ss58: str,
|
|
83
|
+
hotkey_display: str,
|
|
84
|
+
prompt: bool = False,
|
|
85
|
+
decline: bool = False,
|
|
86
|
+
quiet: bool = False,
|
|
87
|
+
proxy: Optional[str] = None,
|
|
88
|
+
):
|
|
89
|
+
"""Associates a hotkey with a wallet"""
|
|
90
|
+
|
|
91
|
+
owner_ss58 = await meshtensor.get_hotkey_owner(hotkey_ss58)
|
|
92
|
+
if owner_ss58:
|
|
93
|
+
if owner_ss58 == wallet.coldkeypub.ss58_address:
|
|
94
|
+
console.print(
|
|
95
|
+
f":white_heavy_check_mark: {hotkey_display.capitalize()} is already "
|
|
96
|
+
f"associated with \nwallet [blue]{wallet.name}[/blue], "
|
|
97
|
+
f"SS58: [{COLORS.GENERAL.CK}]{owner_ss58}[/{COLORS.GENERAL.CK}]"
|
|
98
|
+
)
|
|
99
|
+
return True
|
|
100
|
+
else:
|
|
101
|
+
owner_wallet = _get_wallet_by_ss58(wallet.path, owner_ss58)
|
|
102
|
+
wallet_name = owner_wallet.name if owner_wallet else "unknown wallet"
|
|
103
|
+
console.print(
|
|
104
|
+
f"[yellow]Warning[/yellow]: {hotkey_display.capitalize()} is already associated with \n"
|
|
105
|
+
f"wallet: [blue]{wallet_name}[/blue], SS58: [{COLORS.GENERAL.CK}]{owner_ss58}[/{COLORS.GENERAL.CK}]"
|
|
106
|
+
)
|
|
107
|
+
return False
|
|
108
|
+
else:
|
|
109
|
+
console.print(
|
|
110
|
+
f"{hotkey_display.capitalize()} is not associated with any wallet"
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
if prompt and not confirm_action(
|
|
114
|
+
"Do you want to continue with the association?", decline=decline, quiet=quiet
|
|
115
|
+
):
|
|
116
|
+
return False
|
|
117
|
+
|
|
118
|
+
if not unlock_key(wallet).success:
|
|
119
|
+
return False
|
|
120
|
+
|
|
121
|
+
call = await meshtensor.substrate.compose_call(
|
|
122
|
+
call_module="MeshtensorModule",
|
|
123
|
+
call_function="try_associate_hotkey",
|
|
124
|
+
call_params={
|
|
125
|
+
"hotkey": hotkey_ss58,
|
|
126
|
+
},
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
with console.status(":satellite: Associating hotkey on-chain..."):
|
|
130
|
+
success, err_msg, ext_receipt = await meshtensor.sign_and_send_extrinsic(
|
|
131
|
+
call,
|
|
132
|
+
wallet,
|
|
133
|
+
wait_for_inclusion=True,
|
|
134
|
+
wait_for_finalization=False,
|
|
135
|
+
proxy=proxy,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
if not success:
|
|
139
|
+
print_error(f"Failed to associate hotkey: {err_msg}")
|
|
140
|
+
return False
|
|
141
|
+
|
|
142
|
+
console.print(
|
|
143
|
+
f":white_heavy_check_mark: Successfully associated {hotkey_display} with \n"
|
|
144
|
+
f"wallet [blue]{wallet.name}[/blue], "
|
|
145
|
+
f"SS58: [{COLORS.GENERAL.CK}]{wallet.coldkeypub.ss58_address}[/{COLORS.GENERAL.CK}]"
|
|
146
|
+
)
|
|
147
|
+
await print_extrinsic_id(ext_receipt)
|
|
148
|
+
return True
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
async def regen_coldkey(
|
|
152
|
+
wallet: Wallet,
|
|
153
|
+
mnemonic: Optional[str],
|
|
154
|
+
seed: Optional[str] = None,
|
|
155
|
+
json_path: Optional[str] = None,
|
|
156
|
+
json_password: Optional[str] = "",
|
|
157
|
+
use_password: Optional[bool] = True,
|
|
158
|
+
overwrite: Optional[bool] = False,
|
|
159
|
+
json_output: bool = False,
|
|
160
|
+
):
|
|
161
|
+
"""Creates a new coldkey under this wallet"""
|
|
162
|
+
json_str: Optional[str] = None
|
|
163
|
+
if json_path:
|
|
164
|
+
if not os.path.exists(json_path) or not os.path.isfile(json_path):
|
|
165
|
+
raise ValueError("File {} does not exist".format(json_path))
|
|
166
|
+
with open(json_path, "r") as f:
|
|
167
|
+
json_str = f.read()
|
|
168
|
+
try:
|
|
169
|
+
new_wallet = wallet.regenerate_coldkey(
|
|
170
|
+
mnemonic=mnemonic,
|
|
171
|
+
seed=seed,
|
|
172
|
+
json=(json_str, json_password) if all([json_str, json_password]) else None,
|
|
173
|
+
use_password=use_password,
|
|
174
|
+
overwrite=overwrite,
|
|
175
|
+
)
|
|
176
|
+
if isinstance(new_wallet, Wallet):
|
|
177
|
+
console.print(
|
|
178
|
+
"\n✅ [dark_sea_green]Regenerated coldkey successfully!\n",
|
|
179
|
+
f"[dark_sea_green]Wallet name: ({new_wallet.name}), "
|
|
180
|
+
f"path: ({new_wallet.path}), "
|
|
181
|
+
f"coldkey ss58: ({new_wallet.coldkeypub.ss58_address})",
|
|
182
|
+
)
|
|
183
|
+
if json_output:
|
|
184
|
+
json_console.print(
|
|
185
|
+
json.dumps(
|
|
186
|
+
{
|
|
187
|
+
"success": True,
|
|
188
|
+
"data": {
|
|
189
|
+
"name": new_wallet.name,
|
|
190
|
+
"path": new_wallet.path,
|
|
191
|
+
"hotkey": new_wallet.hotkey_str,
|
|
192
|
+
"hotkey_ss58": get_hotkey_pub_ss58(new_wallet),
|
|
193
|
+
"coldkey_ss58": new_wallet.coldkeypub.ss58_address,
|
|
194
|
+
},
|
|
195
|
+
"error": "",
|
|
196
|
+
}
|
|
197
|
+
)
|
|
198
|
+
)
|
|
199
|
+
except ValueError:
|
|
200
|
+
print_error("Mnemonic phrase is invalid")
|
|
201
|
+
if json_output:
|
|
202
|
+
json_console.print(
|
|
203
|
+
'{"success": false, "error": "Mnemonic phrase is invalid", "data": null}'
|
|
204
|
+
)
|
|
205
|
+
except KeyFileError:
|
|
206
|
+
print_error("KeyFileError: File is not writable")
|
|
207
|
+
if json_output:
|
|
208
|
+
json_console.print(
|
|
209
|
+
'{"success": false, "error": "Keyfile is not writable", "data": null}'
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
async def regen_coldkey_pub(
|
|
214
|
+
wallet: Wallet,
|
|
215
|
+
ss58_address: str,
|
|
216
|
+
public_key_hex: str,
|
|
217
|
+
overwrite: Optional[bool] = False,
|
|
218
|
+
json_output: bool = False,
|
|
219
|
+
):
|
|
220
|
+
"""Creates a new coldkeypub under this wallet."""
|
|
221
|
+
try:
|
|
222
|
+
new_coldkeypub = wallet.regenerate_coldkeypub(
|
|
223
|
+
ss58_address=ss58_address,
|
|
224
|
+
public_key=public_key_hex,
|
|
225
|
+
overwrite=overwrite,
|
|
226
|
+
)
|
|
227
|
+
if isinstance(new_coldkeypub, Wallet):
|
|
228
|
+
console.print(
|
|
229
|
+
"\n✅ [dark_sea_green]Regenerated coldkeypub successfully!\n",
|
|
230
|
+
f"[dark_sea_green]Wallet name: ({new_coldkeypub.name}), path: ({new_coldkeypub.path}), "
|
|
231
|
+
f"coldkey ss58: ({new_coldkeypub.coldkeypub.ss58_address})",
|
|
232
|
+
)
|
|
233
|
+
if json_output:
|
|
234
|
+
json_console.print(
|
|
235
|
+
json.dumps(
|
|
236
|
+
{
|
|
237
|
+
"success": True,
|
|
238
|
+
"data": {
|
|
239
|
+
"name": new_coldkeypub.name,
|
|
240
|
+
"path": new_coldkeypub.path,
|
|
241
|
+
"hotkey": new_coldkeypub.hotkey_str,
|
|
242
|
+
"hotkey_ss58": get_hotkey_pub_ss58(new_coldkeypub),
|
|
243
|
+
"coldkey_ss58": new_coldkeypub.coldkeypub.ss58_address,
|
|
244
|
+
},
|
|
245
|
+
"error": "",
|
|
246
|
+
}
|
|
247
|
+
)
|
|
248
|
+
)
|
|
249
|
+
except KeyFileError:
|
|
250
|
+
print_error("KeyFileError: File is not writable")
|
|
251
|
+
if json_output:
|
|
252
|
+
json_console.print(
|
|
253
|
+
'{"success": false, "error": "Keyfile is not writable", "data": null}'
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
async def regen_hotkey(
|
|
258
|
+
wallet: Wallet,
|
|
259
|
+
mnemonic: Optional[str],
|
|
260
|
+
seed: Optional[str],
|
|
261
|
+
json_path: Optional[str],
|
|
262
|
+
json_password: Optional[str] = "",
|
|
263
|
+
use_password: Optional[bool] = False,
|
|
264
|
+
overwrite: Optional[bool] = False,
|
|
265
|
+
json_output: bool = False,
|
|
266
|
+
):
|
|
267
|
+
"""Creates a new hotkey under this wallet."""
|
|
268
|
+
json_str: Optional[str] = None
|
|
269
|
+
if json_path:
|
|
270
|
+
if not os.path.exists(json_path) or not os.path.isfile(json_path):
|
|
271
|
+
print_error(f"File {json_path} does not exist")
|
|
272
|
+
return False
|
|
273
|
+
with open(json_path, "r") as f:
|
|
274
|
+
json_str = f.read()
|
|
275
|
+
|
|
276
|
+
try:
|
|
277
|
+
new_hotkey_ = wallet.regenerate_hotkey(
|
|
278
|
+
mnemonic=mnemonic,
|
|
279
|
+
seed=seed,
|
|
280
|
+
json=(json_str, json_password) if all([json_str, json_password]) else None,
|
|
281
|
+
use_password=use_password,
|
|
282
|
+
overwrite=overwrite,
|
|
283
|
+
)
|
|
284
|
+
if isinstance(new_hotkey_, Wallet):
|
|
285
|
+
console.print(
|
|
286
|
+
"\n✅ [dark_sea_green]Regenerated hotkey successfully!\n",
|
|
287
|
+
f"[dark_sea_green]Wallet name: ({new_hotkey_.name}), path: ({new_hotkey_.path}), "
|
|
288
|
+
f"hotkey ss58: ({new_hotkey_.hotkeypub.ss58_address})",
|
|
289
|
+
)
|
|
290
|
+
if json_output:
|
|
291
|
+
json_console.print(
|
|
292
|
+
json.dumps(
|
|
293
|
+
{
|
|
294
|
+
"success": True,
|
|
295
|
+
"data": {
|
|
296
|
+
"name": new_hotkey_.name,
|
|
297
|
+
"path": new_hotkey_.path,
|
|
298
|
+
"hotkey": new_hotkey_.hotkey_str,
|
|
299
|
+
"hotkey_ss58": new_hotkey_.hotkeypub.ss58_address,
|
|
300
|
+
"coldkey_ss58": new_hotkey_.coldkeypub.ss58_address,
|
|
301
|
+
},
|
|
302
|
+
"error": "",
|
|
303
|
+
}
|
|
304
|
+
)
|
|
305
|
+
)
|
|
306
|
+
except ValueError:
|
|
307
|
+
print_error("Mnemonic phrase is invalid")
|
|
308
|
+
if json_output:
|
|
309
|
+
json_console.print(
|
|
310
|
+
'{"success": false, "error": "Mnemonic phrase is invalid", "data": null}'
|
|
311
|
+
)
|
|
312
|
+
except KeyFileError:
|
|
313
|
+
print_error("KeyFileError: File is not writable")
|
|
314
|
+
if json_output:
|
|
315
|
+
json_console.print(
|
|
316
|
+
'{"success": false, "error": "Keyfile is not writable", "data": null}'
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
async def regen_hotkey_pub(
|
|
321
|
+
wallet: Wallet,
|
|
322
|
+
ss58_address: str,
|
|
323
|
+
public_key_hex: str,
|
|
324
|
+
overwrite: Optional[bool] = False,
|
|
325
|
+
json_output: bool = False,
|
|
326
|
+
):
|
|
327
|
+
"""Creates a new hotkeypub under this wallet."""
|
|
328
|
+
try:
|
|
329
|
+
new_hotkeypub = wallet.regenerate_hotkeypub(
|
|
330
|
+
ss58_address=ss58_address,
|
|
331
|
+
public_key=public_key_hex,
|
|
332
|
+
overwrite=overwrite,
|
|
333
|
+
)
|
|
334
|
+
if isinstance(new_hotkeypub, Wallet):
|
|
335
|
+
console.print(
|
|
336
|
+
"\n✅ [dark_sea_green]Regenerated coldkeypub successfully!\n",
|
|
337
|
+
f"[dark_sea_green]Wallet name: ({new_hotkeypub.name}), path: ({new_hotkeypub.path}), "
|
|
338
|
+
f"coldkey ss58: ({new_hotkeypub.coldkeypub.ss58_address})",
|
|
339
|
+
)
|
|
340
|
+
if json_output:
|
|
341
|
+
json_console.print(
|
|
342
|
+
json.dumps(
|
|
343
|
+
{
|
|
344
|
+
"success": True,
|
|
345
|
+
"data": {
|
|
346
|
+
"name": new_hotkeypub.name,
|
|
347
|
+
"path": new_hotkeypub.path,
|
|
348
|
+
"hotkey": new_hotkeypub.hotkey_str,
|
|
349
|
+
"hotkey_ss58": new_hotkeypub.hotkeypub.ss58_address,
|
|
350
|
+
"coldkey_ss58": new_hotkeypub.coldkeypub.ss58_address,
|
|
351
|
+
},
|
|
352
|
+
"error": "",
|
|
353
|
+
}
|
|
354
|
+
)
|
|
355
|
+
)
|
|
356
|
+
except KeyFileError:
|
|
357
|
+
print_error("KeyFileError: File is not writable")
|
|
358
|
+
if json_output:
|
|
359
|
+
json_console.print(
|
|
360
|
+
'{"success": false, "error": "Keyfile is not writable", "data": null}'
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
async def new_hotkey(
|
|
365
|
+
wallet: Wallet,
|
|
366
|
+
n_words: int,
|
|
367
|
+
use_password: bool,
|
|
368
|
+
uri: Optional[str] = None,
|
|
369
|
+
overwrite: Optional[bool] = False,
|
|
370
|
+
json_output: bool = False,
|
|
371
|
+
):
|
|
372
|
+
"""Creates a new hotkey under this wallet."""
|
|
373
|
+
try:
|
|
374
|
+
if uri:
|
|
375
|
+
try:
|
|
376
|
+
keypair = Keypair.create_from_uri(uri)
|
|
377
|
+
except Exception as e:
|
|
378
|
+
print_error(f"Failed to create keypair from URI {uri}: {str(e)}")
|
|
379
|
+
return
|
|
380
|
+
wallet.set_hotkey(keypair=keypair, encrypt=use_password)
|
|
381
|
+
console.print(
|
|
382
|
+
f"[dark_sea_green]Hotkey created from URI: {uri}[/dark_sea_green]"
|
|
383
|
+
)
|
|
384
|
+
else:
|
|
385
|
+
wallet.create_new_hotkey(
|
|
386
|
+
n_words=n_words,
|
|
387
|
+
use_password=use_password,
|
|
388
|
+
overwrite=overwrite,
|
|
389
|
+
)
|
|
390
|
+
console.print("[dark_sea_green]Hotkey created[/dark_sea_green]")
|
|
391
|
+
if json_output:
|
|
392
|
+
json_console.print(
|
|
393
|
+
json.dumps(
|
|
394
|
+
{
|
|
395
|
+
"success": True,
|
|
396
|
+
"data": {
|
|
397
|
+
"name": wallet.name,
|
|
398
|
+
"path": wallet.path,
|
|
399
|
+
"hotkey": wallet.hotkey_str,
|
|
400
|
+
"hotkey_ss58": get_hotkey_pub_ss58(wallet),
|
|
401
|
+
"coldkey_ss58": wallet.coldkeypub.ss58_address,
|
|
402
|
+
},
|
|
403
|
+
"error": "",
|
|
404
|
+
}
|
|
405
|
+
)
|
|
406
|
+
)
|
|
407
|
+
except KeyFileError:
|
|
408
|
+
print_error("KeyFileError: File is not writable")
|
|
409
|
+
if json_output:
|
|
410
|
+
json_console.print(
|
|
411
|
+
'{"success": false, "error": "Keyfile is not writable", "data": null}'
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
async def new_coldkey(
|
|
416
|
+
wallet: Wallet,
|
|
417
|
+
n_words: int,
|
|
418
|
+
use_password: bool,
|
|
419
|
+
uri: Optional[str] = None,
|
|
420
|
+
overwrite: Optional[bool] = False,
|
|
421
|
+
json_output: bool = False,
|
|
422
|
+
):
|
|
423
|
+
"""Creates a new coldkey under this wallet."""
|
|
424
|
+
try:
|
|
425
|
+
if uri:
|
|
426
|
+
try:
|
|
427
|
+
keypair = Keypair.create_from_uri(uri)
|
|
428
|
+
except Exception as e:
|
|
429
|
+
print_error(f"Failed to create keypair from URI {uri}: {str(e)}")
|
|
430
|
+
wallet.set_coldkey(keypair=keypair, encrypt=False, overwrite=False)
|
|
431
|
+
wallet.set_coldkeypub(keypair=keypair, encrypt=False, overwrite=False)
|
|
432
|
+
console.print(
|
|
433
|
+
f"[dark_sea_green]Coldkey created from URI: {uri}[/dark_sea_green]"
|
|
434
|
+
)
|
|
435
|
+
else:
|
|
436
|
+
wallet.create_new_coldkey(
|
|
437
|
+
n_words=n_words,
|
|
438
|
+
use_password=use_password,
|
|
439
|
+
overwrite=overwrite,
|
|
440
|
+
)
|
|
441
|
+
console.print("[dark_sea_green]Coldkey created[/dark_sea_green]")
|
|
442
|
+
if json_output:
|
|
443
|
+
json_console.print(
|
|
444
|
+
json.dumps(
|
|
445
|
+
{
|
|
446
|
+
"success": True,
|
|
447
|
+
"data": {
|
|
448
|
+
"name": wallet.name,
|
|
449
|
+
"path": wallet.path,
|
|
450
|
+
"coldkey_ss58": wallet.coldkeypub.ss58_address,
|
|
451
|
+
},
|
|
452
|
+
"error": "",
|
|
453
|
+
}
|
|
454
|
+
)
|
|
455
|
+
)
|
|
456
|
+
except KeyFileError as e:
|
|
457
|
+
print_error("KeyFileError: File is not writable")
|
|
458
|
+
if json_output:
|
|
459
|
+
json_console.print(
|
|
460
|
+
json.dumps(
|
|
461
|
+
{
|
|
462
|
+
"success": False,
|
|
463
|
+
"error": f"Keyfile is not writable: {e}",
|
|
464
|
+
"data": None,
|
|
465
|
+
}
|
|
466
|
+
)
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
async def wallet_create(
|
|
471
|
+
wallet: Wallet,
|
|
472
|
+
n_words: int = 12,
|
|
473
|
+
use_password: bool = True,
|
|
474
|
+
uri: Optional[str] = None,
|
|
475
|
+
overwrite: Optional[bool] = False,
|
|
476
|
+
json_output: bool = False,
|
|
477
|
+
):
|
|
478
|
+
"""Creates a new wallet."""
|
|
479
|
+
output_dict: dict[str, Optional[Union[bool, str, dict]]] = {
|
|
480
|
+
"success": False,
|
|
481
|
+
"error": "",
|
|
482
|
+
"data": None,
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
if uri:
|
|
486
|
+
try:
|
|
487
|
+
keypair = Keypair.create_from_uri(uri)
|
|
488
|
+
wallet.set_coldkey(keypair=keypair, encrypt=False, overwrite=overwrite)
|
|
489
|
+
wallet.set_coldkeypub(keypair=keypair, encrypt=False, overwrite=overwrite)
|
|
490
|
+
wallet.set_hotkey(keypair=keypair, encrypt=False, overwrite=overwrite)
|
|
491
|
+
wallet.set_hotkeypub(keypair=keypair, encrypt=False, overwrite=overwrite)
|
|
492
|
+
wallet.set_coldkeypub(keypair=keypair, encrypt=False, overwrite=overwrite)
|
|
493
|
+
output_dict["success"] = True
|
|
494
|
+
output_dict["data"] = {
|
|
495
|
+
"name": wallet.name,
|
|
496
|
+
"path": wallet.path,
|
|
497
|
+
"hotkey": wallet.hotkey_str,
|
|
498
|
+
"hotkey_ss58": wallet.hotkeypub.ss58_address,
|
|
499
|
+
"coldkey_ss58": wallet.coldkeypub.ss58_address,
|
|
500
|
+
}
|
|
501
|
+
except Exception as e:
|
|
502
|
+
err = f"Failed to create keypair from URI: {str(e)}"
|
|
503
|
+
print_error(err)
|
|
504
|
+
output_dict["error"] = err
|
|
505
|
+
console.print(
|
|
506
|
+
f"[dark_sea_green]Wallet created from URI: {uri}[/dark_sea_green]"
|
|
507
|
+
)
|
|
508
|
+
else:
|
|
509
|
+
try:
|
|
510
|
+
wallet.create_new_coldkey(
|
|
511
|
+
n_words=n_words,
|
|
512
|
+
use_password=use_password,
|
|
513
|
+
overwrite=overwrite,
|
|
514
|
+
)
|
|
515
|
+
console.print("[dark_sea_green]Coldkey created[/dark_sea_green]")
|
|
516
|
+
output_dict["success"] = True
|
|
517
|
+
output_dict["data"] = {
|
|
518
|
+
"name": wallet.name,
|
|
519
|
+
"path": wallet.path,
|
|
520
|
+
"hotkey": wallet.hotkey_str,
|
|
521
|
+
"coldkey_ss58": wallet.coldkeypub.ss58_address,
|
|
522
|
+
}
|
|
523
|
+
except KeyFileError as error:
|
|
524
|
+
err = str(error)
|
|
525
|
+
print_error(err)
|
|
526
|
+
output_dict["error"] = err
|
|
527
|
+
try:
|
|
528
|
+
wallet.create_new_hotkey(
|
|
529
|
+
n_words=n_words,
|
|
530
|
+
use_password=False,
|
|
531
|
+
overwrite=overwrite,
|
|
532
|
+
)
|
|
533
|
+
console.print("[dark_sea_green]Hotkey created[/dark_sea_green]")
|
|
534
|
+
output_dict["success"] = True
|
|
535
|
+
output_dict["data"] = {
|
|
536
|
+
"name": wallet.name,
|
|
537
|
+
"path": wallet.path,
|
|
538
|
+
"hotkey": wallet.hotkey_str,
|
|
539
|
+
"hotkey_ss58": wallet.hotkeypub.ss58_address,
|
|
540
|
+
}
|
|
541
|
+
except KeyFileError as error:
|
|
542
|
+
err = str(error)
|
|
543
|
+
print_error(err)
|
|
544
|
+
output_dict["error"] = err
|
|
545
|
+
if json_output:
|
|
546
|
+
json_console.print(json.dumps(output_dict))
|
|
547
|
+
|
|
548
|
+
|
|
549
|
+
def get_coldkey_wallets_for_path(path: str) -> list[Wallet]:
|
|
550
|
+
"""Get all coldkey wallet names from path."""
|
|
551
|
+
try:
|
|
552
|
+
wallet_names = next(os.walk(os.path.expanduser(path)))[1]
|
|
553
|
+
return [Wallet(path=path, name=name) for name in wallet_names]
|
|
554
|
+
except StopIteration:
|
|
555
|
+
# No wallet files found.
|
|
556
|
+
wallets = []
|
|
557
|
+
return wallets
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
def _get_wallet_by_ss58(path: str, ss58_address: str) -> Optional[Wallet]:
|
|
561
|
+
"""Find a wallet by its SS58 address in the given path."""
|
|
562
|
+
ss58_addresses, wallet_names = _get_coldkey_ss58_addresses_for_path(path)
|
|
563
|
+
for wallet_name, addr in zip(wallet_names, ss58_addresses):
|
|
564
|
+
if addr == ss58_address:
|
|
565
|
+
return Wallet(path=path, name=wallet_name)
|
|
566
|
+
return None
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
def _get_coldkey_ss58_addresses_for_path(path: str) -> tuple[list[str], list[str]]:
|
|
570
|
+
"""Get all coldkey ss58 addresses from path."""
|
|
571
|
+
|
|
572
|
+
abs_path = os.path.abspath(os.path.expanduser(path))
|
|
573
|
+
wallets = [
|
|
574
|
+
name
|
|
575
|
+
for name in os.listdir(abs_path)
|
|
576
|
+
if os.path.isdir(os.path.join(abs_path, name))
|
|
577
|
+
]
|
|
578
|
+
coldkey_paths = [
|
|
579
|
+
os.path.join(abs_path, wallet, "coldkeypub.txt")
|
|
580
|
+
for wallet in wallets
|
|
581
|
+
if os.path.isfile(os.path.join(abs_path, wallet, "coldkeypub.txt"))
|
|
582
|
+
]
|
|
583
|
+
ss58_addresses = [Keyfile(path).keypair.ss58_address for path in coldkey_paths]
|
|
584
|
+
|
|
585
|
+
return ss58_addresses, [
|
|
586
|
+
os.path.basename(os.path.dirname(path)) for path in coldkey_paths
|
|
587
|
+
]
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
async def wallet_balance(
|
|
591
|
+
wallet: Optional[Wallet],
|
|
592
|
+
meshtensor: MeshtensorInterface,
|
|
593
|
+
all_balances: bool,
|
|
594
|
+
ss58_addresses: Optional[str] = None,
|
|
595
|
+
sort_by: Optional[SortByBalance] = None,
|
|
596
|
+
json_output: bool = False,
|
|
597
|
+
):
|
|
598
|
+
"""Retrieves the current balance of the specified wallet"""
|
|
599
|
+
if ss58_addresses:
|
|
600
|
+
coldkeys = ss58_addresses
|
|
601
|
+
wallet_names = [f"Provided Address {i + 1}" for i in range(len(ss58_addresses))]
|
|
602
|
+
|
|
603
|
+
elif not all_balances:
|
|
604
|
+
if not wallet.coldkeypub_file.exists_on_device():
|
|
605
|
+
print_error("[bold red]No wallets found.[/bold red]")
|
|
606
|
+
return
|
|
607
|
+
|
|
608
|
+
with console.status("Retrieving balances", spinner="aesthetic") as status:
|
|
609
|
+
if ss58_addresses:
|
|
610
|
+
print_verbose(f"Fetching data for ss58 address: {ss58_addresses}", status)
|
|
611
|
+
elif all_balances:
|
|
612
|
+
print_verbose("Fetching data for all wallets", status)
|
|
613
|
+
coldkeys, wallet_names = _get_coldkey_ss58_addresses_for_path(wallet.path)
|
|
614
|
+
else:
|
|
615
|
+
print_verbose(f"Fetching data for wallet: {wallet.name}", status)
|
|
616
|
+
coldkeys = [wallet.coldkeypub.ss58_address]
|
|
617
|
+
wallet_names = [wallet.name]
|
|
618
|
+
|
|
619
|
+
block_hash = await meshtensor.substrate.get_chain_head()
|
|
620
|
+
free_balances, staked_balances = await asyncio.gather(
|
|
621
|
+
meshtensor.get_balances(*coldkeys, block_hash=block_hash),
|
|
622
|
+
meshtensor.get_total_stake_for_coldkey(*coldkeys, block_hash=block_hash),
|
|
623
|
+
)
|
|
624
|
+
|
|
625
|
+
total_free_balance: Balance = sum(free_balances.values())
|
|
626
|
+
total_staked_balance: Balance = sum(stake[0] for stake in staked_balances.values())
|
|
627
|
+
|
|
628
|
+
balances = {
|
|
629
|
+
name: (
|
|
630
|
+
coldkey,
|
|
631
|
+
free_balances[coldkey],
|
|
632
|
+
staked_balances[coldkey][0],
|
|
633
|
+
)
|
|
634
|
+
for (name, coldkey) in zip(wallet_names, coldkeys)
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
table = Table(
|
|
638
|
+
Column(
|
|
639
|
+
"[white]Wallet Name",
|
|
640
|
+
style=COLOR_PALETTE["GENERAL"]["SUBHEADING_MAIN"],
|
|
641
|
+
no_wrap=True,
|
|
642
|
+
),
|
|
643
|
+
Column(
|
|
644
|
+
"[white]Coldkey Address",
|
|
645
|
+
style=COLOR_PALETTE["GENERAL"]["COLDKEY"],
|
|
646
|
+
no_wrap=True,
|
|
647
|
+
),
|
|
648
|
+
Column(
|
|
649
|
+
"[white]Free Balance",
|
|
650
|
+
justify="right",
|
|
651
|
+
style=COLOR_PALETTE["GENERAL"]["BALANCE"],
|
|
652
|
+
no_wrap=True,
|
|
653
|
+
),
|
|
654
|
+
Column(
|
|
655
|
+
"[white]Staked Value",
|
|
656
|
+
justify="right",
|
|
657
|
+
style=COLOR_PALETTE["STAKE"]["STAKE_ALPHA"],
|
|
658
|
+
no_wrap=True,
|
|
659
|
+
),
|
|
660
|
+
Column(
|
|
661
|
+
"[white]Total Balance",
|
|
662
|
+
justify="right",
|
|
663
|
+
style=COLOR_PALETTE["GENERAL"]["BALANCE"],
|
|
664
|
+
no_wrap=True,
|
|
665
|
+
),
|
|
666
|
+
title=f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Wallet Coldkey Balance[/{COLOR_PALETTE['GENERAL']['HEADER']}]\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Network: {meshtensor.network}\n",
|
|
667
|
+
show_footer=True,
|
|
668
|
+
show_edge=False,
|
|
669
|
+
border_style="bright_black",
|
|
670
|
+
box=box.SIMPLE_HEAVY,
|
|
671
|
+
pad_edge=False,
|
|
672
|
+
width=None,
|
|
673
|
+
leading=True,
|
|
674
|
+
)
|
|
675
|
+
balance_rows = [
|
|
676
|
+
(name, coldkey, free, staked, free + staked)
|
|
677
|
+
for (name, (coldkey, free, staked)) in balances.items()
|
|
678
|
+
]
|
|
679
|
+
sorted_balances = (
|
|
680
|
+
sorted(
|
|
681
|
+
balance_rows,
|
|
682
|
+
key=_sort_by_balance_key(sort_by),
|
|
683
|
+
reverse=(sort_by != SortByBalance.name),
|
|
684
|
+
)
|
|
685
|
+
if sort_by is not None
|
|
686
|
+
else balance_rows
|
|
687
|
+
)
|
|
688
|
+
for name, coldkey, free, staked, total in sorted_balances:
|
|
689
|
+
table.add_row(
|
|
690
|
+
name,
|
|
691
|
+
coldkey,
|
|
692
|
+
str(free),
|
|
693
|
+
str(staked),
|
|
694
|
+
str(total),
|
|
695
|
+
)
|
|
696
|
+
table.add_row()
|
|
697
|
+
table.add_row(
|
|
698
|
+
"Total Balance",
|
|
699
|
+
"",
|
|
700
|
+
str(total_free_balance),
|
|
701
|
+
str(total_staked_balance),
|
|
702
|
+
str(total_free_balance + total_staked_balance),
|
|
703
|
+
)
|
|
704
|
+
console.print(Padding(table, (0, 0, 0, 4)))
|
|
705
|
+
if json_output:
|
|
706
|
+
output_balances = {
|
|
707
|
+
key: {
|
|
708
|
+
"coldkey": value[0],
|
|
709
|
+
"free": value[1].tao,
|
|
710
|
+
"staked": value[2].tao,
|
|
711
|
+
"total": (value[1] + value[2]).tao,
|
|
712
|
+
}
|
|
713
|
+
for (key, value) in balances.items()
|
|
714
|
+
}
|
|
715
|
+
output_dict = {
|
|
716
|
+
"balances": output_balances,
|
|
717
|
+
"totals": {
|
|
718
|
+
"free": total_free_balance.tao,
|
|
719
|
+
"staked": total_staked_balance.tao,
|
|
720
|
+
"total": (total_free_balance + total_staked_balance).tao,
|
|
721
|
+
},
|
|
722
|
+
}
|
|
723
|
+
json_console.print(json.dumps(output_dict))
|
|
724
|
+
return total_free_balance
|
|
725
|
+
|
|
726
|
+
|
|
727
|
+
async def get_wallet_transfers(wallet_address: str) -> list[dict]:
|
|
728
|
+
"""Get all transfers associated with the provided wallet address."""
|
|
729
|
+
|
|
730
|
+
api_url = "https://api.subquery.network/sq/TaoStats/meshtensor-indexer"
|
|
731
|
+
max_txn = 1000
|
|
732
|
+
graphql_query = """
|
|
733
|
+
query ($first: Int!, $after: Cursor, $filter: TransferFilter, $order: [TransfersOrderBy!]!) {
|
|
734
|
+
transfers(first: $first, after: $after, filter: $filter, orderBy: $order) {
|
|
735
|
+
nodes {
|
|
736
|
+
id
|
|
737
|
+
from
|
|
738
|
+
to
|
|
739
|
+
amount
|
|
740
|
+
extrinsicId
|
|
741
|
+
blockNumber
|
|
742
|
+
}
|
|
743
|
+
pageInfo {
|
|
744
|
+
endCursor
|
|
745
|
+
hasNextPage
|
|
746
|
+
hasPreviousPage
|
|
747
|
+
}
|
|
748
|
+
totalCount
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
"""
|
|
752
|
+
variables = {
|
|
753
|
+
"first": max_txn,
|
|
754
|
+
"filter": {
|
|
755
|
+
"or": [
|
|
756
|
+
{"from": {"equalTo": wallet_address}},
|
|
757
|
+
{"to": {"equalTo": wallet_address}},
|
|
758
|
+
]
|
|
759
|
+
},
|
|
760
|
+
"order": "BLOCK_NUMBER_DESC",
|
|
761
|
+
}
|
|
762
|
+
async with aiohttp.ClientSession() as session:
|
|
763
|
+
response = await session.post(
|
|
764
|
+
api_url, json={"query": graphql_query, "variables": variables}
|
|
765
|
+
)
|
|
766
|
+
data = await response.json()
|
|
767
|
+
|
|
768
|
+
# Extract nodes and pageInfo from the response
|
|
769
|
+
transfer_data = data.get("data", {}).get("transfers", {})
|
|
770
|
+
transfers = transfer_data.get("nodes", [])
|
|
771
|
+
|
|
772
|
+
return transfers
|
|
773
|
+
|
|
774
|
+
|
|
775
|
+
def create_transfer_history_table(transfers: list[dict]) -> Table:
|
|
776
|
+
"""Get output transfer table"""
|
|
777
|
+
|
|
778
|
+
taostats_url_base = "https://taostats.io/extrinsic"
|
|
779
|
+
|
|
780
|
+
# Create a table
|
|
781
|
+
table = Table(
|
|
782
|
+
show_footer=True,
|
|
783
|
+
box=box.SIMPLE,
|
|
784
|
+
pad_edge=False,
|
|
785
|
+
leading=True,
|
|
786
|
+
expand=False,
|
|
787
|
+
title="[underline dark_orange]Wallet Transfers[/underline dark_orange]\n\n[dark_orange]Network: finney",
|
|
788
|
+
)
|
|
789
|
+
|
|
790
|
+
table.add_column(
|
|
791
|
+
"[white]ID", style="dark_orange", no_wrap=True, justify="left", ratio=1.4
|
|
792
|
+
)
|
|
793
|
+
table.add_column(
|
|
794
|
+
"[white]From", style="bright_magenta", overflow="fold", justify="right", ratio=2
|
|
795
|
+
)
|
|
796
|
+
table.add_column(
|
|
797
|
+
"[white]To", style="bright_magenta", overflow="fold", justify="right", ratio=2
|
|
798
|
+
)
|
|
799
|
+
table.add_column(
|
|
800
|
+
"[white]Amount (Tao)",
|
|
801
|
+
style="light_goldenrod2",
|
|
802
|
+
no_wrap=True,
|
|
803
|
+
justify="right",
|
|
804
|
+
ratio=1,
|
|
805
|
+
)
|
|
806
|
+
table.add_column(
|
|
807
|
+
"[white]Extrinsic Id",
|
|
808
|
+
style="rgb(42,161,152)",
|
|
809
|
+
no_wrap=True,
|
|
810
|
+
justify="right",
|
|
811
|
+
ratio=0.75,
|
|
812
|
+
)
|
|
813
|
+
table.add_column(
|
|
814
|
+
"[white]Block Number",
|
|
815
|
+
style="dark_sea_green",
|
|
816
|
+
no_wrap=True,
|
|
817
|
+
justify="right",
|
|
818
|
+
ratio=1,
|
|
819
|
+
)
|
|
820
|
+
table.add_column(
|
|
821
|
+
"[white]URL (taostats)",
|
|
822
|
+
style="bright_cyan",
|
|
823
|
+
overflow="fold",
|
|
824
|
+
justify="right",
|
|
825
|
+
ratio=2,
|
|
826
|
+
)
|
|
827
|
+
|
|
828
|
+
for item in transfers:
|
|
829
|
+
try:
|
|
830
|
+
tao_amount = int(item["amount"]) / MESHLET_PER_MESH
|
|
831
|
+
except ValueError:
|
|
832
|
+
tao_amount = item["amount"]
|
|
833
|
+
table.add_row(
|
|
834
|
+
item["id"],
|
|
835
|
+
item["from"],
|
|
836
|
+
item["to"],
|
|
837
|
+
f"{tao_amount:.3f}",
|
|
838
|
+
str(item["extrinsicId"]),
|
|
839
|
+
item["blockNumber"],
|
|
840
|
+
f"{taostats_url_base}/{item['blockNumber']}-{item['extrinsicId']:04}",
|
|
841
|
+
)
|
|
842
|
+
table.add_row()
|
|
843
|
+
return table
|
|
844
|
+
|
|
845
|
+
|
|
846
|
+
async def wallet_history(wallet: Wallet):
|
|
847
|
+
"""Check the transfer history of the provided wallet."""
|
|
848
|
+
print_verbose(f"Fetching history for wallet: {wallet.name}")
|
|
849
|
+
wallet_address = wallet.get_coldkeypub().ss58_address
|
|
850
|
+
transfers = await get_wallet_transfers(wallet_address)
|
|
851
|
+
table = create_transfer_history_table(transfers)
|
|
852
|
+
console.print(table)
|
|
853
|
+
|
|
854
|
+
|
|
855
|
+
async def wallet_list(
|
|
856
|
+
wallet_path: str, json_output: bool, wallet_name: Optional[str] = None
|
|
857
|
+
):
|
|
858
|
+
"""Lists wallets."""
|
|
859
|
+
wallets = utils.get_coldkey_wallets_for_path(wallet_path)
|
|
860
|
+
print_verbose(f"Using wallets path: {wallet_path}")
|
|
861
|
+
if not wallets:
|
|
862
|
+
print_error(f"No wallets found in dir: {wallet_path}")
|
|
863
|
+
|
|
864
|
+
if wallet_name:
|
|
865
|
+
wallets = [wallet for wallet in wallets if wallet.name == wallet_name]
|
|
866
|
+
if not wallets:
|
|
867
|
+
print_error(f"Wallet '{wallet_name}' not found in dir: {wallet_path}")
|
|
868
|
+
|
|
869
|
+
root = Tree("Wallets")
|
|
870
|
+
main_data_dict = {"wallets": []}
|
|
871
|
+
for wallet in wallets:
|
|
872
|
+
if (
|
|
873
|
+
wallet.coldkeypub_file.exists_on_device()
|
|
874
|
+
and os.path.isfile(wallet.coldkeypub_file.path)
|
|
875
|
+
and not wallet.coldkeypub_file.is_encrypted()
|
|
876
|
+
):
|
|
877
|
+
coldkeypub_str = wallet.coldkeypub.ss58_address
|
|
878
|
+
else:
|
|
879
|
+
coldkeypub_str = "?"
|
|
880
|
+
|
|
881
|
+
wallet_tree = root.add(
|
|
882
|
+
f"[bold blue]Coldkey[/bold blue] [green]{wallet.name}[/green] ss58_address [green]{coldkeypub_str}[/green]"
|
|
883
|
+
)
|
|
884
|
+
wallet_hotkeys = []
|
|
885
|
+
wallet_dict = {
|
|
886
|
+
"name": wallet.name,
|
|
887
|
+
"ss58_address": coldkeypub_str,
|
|
888
|
+
"hotkeys": wallet_hotkeys,
|
|
889
|
+
}
|
|
890
|
+
main_data_dict["wallets"].append(wallet_dict)
|
|
891
|
+
hotkeys = utils.get_hotkey_wallets_for_wallet(
|
|
892
|
+
wallet, show_nulls=True, show_encrypted=True
|
|
893
|
+
)
|
|
894
|
+
for hkey in hotkeys:
|
|
895
|
+
data = f"[bold red]Hotkey[/bold red][green] {hkey}[/green] (?)"
|
|
896
|
+
hk_data = {"name": hkey.name, "ss58_address": "?"}
|
|
897
|
+
if hkey:
|
|
898
|
+
try:
|
|
899
|
+
hkey_ss58 = hkey.get_hotkey().ss58_address
|
|
900
|
+
pub_only = False
|
|
901
|
+
except KeyFileError:
|
|
902
|
+
hkey_ss58 = hkey.get_hotkeypub().ss58_address
|
|
903
|
+
pub_only = True
|
|
904
|
+
except AttributeError:
|
|
905
|
+
hkey_ss58 = hkey.hotkey.ss58_address
|
|
906
|
+
pub_only = False
|
|
907
|
+
try:
|
|
908
|
+
data = (
|
|
909
|
+
f"[bold red]Hotkey[/bold red] [green]{hkey.hotkey_str}[/green] "
|
|
910
|
+
f"ss58_address [green]{hkey_ss58}[/green]"
|
|
911
|
+
)
|
|
912
|
+
if pub_only:
|
|
913
|
+
data += " [blue](hotkeypub only)[/blue]\n"
|
|
914
|
+
else:
|
|
915
|
+
data += "\n"
|
|
916
|
+
hk_data["name"] = hkey.hotkey_str
|
|
917
|
+
hk_data["ss58_address"] = hkey_ss58
|
|
918
|
+
except UnicodeDecodeError:
|
|
919
|
+
pass
|
|
920
|
+
wallet_tree.add(data)
|
|
921
|
+
wallet_hotkeys.append(hk_data)
|
|
922
|
+
|
|
923
|
+
if not wallets:
|
|
924
|
+
print_verbose(f"No wallets found in path: {wallet_path}")
|
|
925
|
+
message = (
|
|
926
|
+
"[bold red]No wallets found."
|
|
927
|
+
if not wallet_name
|
|
928
|
+
else f"[bold red]Wallet '{wallet_name}' not found."
|
|
929
|
+
)
|
|
930
|
+
root.add(message)
|
|
931
|
+
if json_output:
|
|
932
|
+
json_console.print(json.dumps(main_data_dict))
|
|
933
|
+
else:
|
|
934
|
+
console.print(root)
|
|
935
|
+
|
|
936
|
+
|
|
937
|
+
async def _get_total_balance(
|
|
938
|
+
total_balance: Balance,
|
|
939
|
+
meshtensor: MeshtensorInterface,
|
|
940
|
+
wallet: Wallet,
|
|
941
|
+
all_wallets: bool = False,
|
|
942
|
+
block_hash: Optional[str] = None,
|
|
943
|
+
) -> tuple[list[Wallet], Balance]:
|
|
944
|
+
"""
|
|
945
|
+
Retrieves total balance of all or specified wallets
|
|
946
|
+
:param total_balance: Balance object for which to add the retrieved balance(s)
|
|
947
|
+
:param meshtensor: MeshtensorInterface object used to make the queries
|
|
948
|
+
:param wallet: Wallet object from which to derive the queries (or path if all_wallets is set)
|
|
949
|
+
:param all_wallets: Flag on whether to use all wallets in the wallet.path or just the specified wallet
|
|
950
|
+
:return: (all hotkeys used to derive the balance, total balance of these)
|
|
951
|
+
"""
|
|
952
|
+
if all_wallets:
|
|
953
|
+
cold_wallets = utils.get_coldkey_wallets_for_path(wallet.path)
|
|
954
|
+
_balance_cold_wallets = [
|
|
955
|
+
cold_wallet
|
|
956
|
+
for cold_wallet in cold_wallets
|
|
957
|
+
if (
|
|
958
|
+
cold_wallet.coldkeypub_file.exists_on_device()
|
|
959
|
+
and not cold_wallet.coldkeypub_file.is_encrypted()
|
|
960
|
+
)
|
|
961
|
+
]
|
|
962
|
+
total_balance += sum(
|
|
963
|
+
(
|
|
964
|
+
await meshtensor.get_balances(
|
|
965
|
+
*(x.coldkeypub.ss58_address for x in _balance_cold_wallets),
|
|
966
|
+
block_hash=block_hash,
|
|
967
|
+
)
|
|
968
|
+
).values()
|
|
969
|
+
)
|
|
970
|
+
all_hotkeys = []
|
|
971
|
+
for w in cold_wallets:
|
|
972
|
+
hotkeys_for_wallet = utils.get_hotkey_wallets_for_wallet(w)
|
|
973
|
+
if hotkeys_for_wallet:
|
|
974
|
+
all_hotkeys.extend(hotkeys_for_wallet)
|
|
975
|
+
else:
|
|
976
|
+
print_error(f"[red]No hotkeys found for wallet: ({w.name})")
|
|
977
|
+
else:
|
|
978
|
+
# We are only printing keys for a single coldkey
|
|
979
|
+
coldkey_wallet = wallet
|
|
980
|
+
if (
|
|
981
|
+
coldkey_wallet.coldkeypub_file.exists_on_device()
|
|
982
|
+
and not coldkey_wallet.coldkeypub_file.is_encrypted()
|
|
983
|
+
):
|
|
984
|
+
total_balance = sum(
|
|
985
|
+
(
|
|
986
|
+
await meshtensor.get_balances(
|
|
987
|
+
coldkey_wallet.coldkeypub.ss58_address, block_hash=block_hash
|
|
988
|
+
)
|
|
989
|
+
).values()
|
|
990
|
+
)
|
|
991
|
+
if not coldkey_wallet.coldkeypub_file.exists_on_device():
|
|
992
|
+
return [], None
|
|
993
|
+
all_hotkeys = utils.get_hotkey_wallets_for_wallet(coldkey_wallet)
|
|
994
|
+
|
|
995
|
+
if not all_hotkeys:
|
|
996
|
+
print_error(f"No hotkeys found for wallet ({coldkey_wallet.name})")
|
|
997
|
+
|
|
998
|
+
return all_hotkeys, total_balance
|
|
999
|
+
|
|
1000
|
+
|
|
1001
|
+
async def overview(
|
|
1002
|
+
wallet: Wallet,
|
|
1003
|
+
meshtensor: MeshtensorInterface,
|
|
1004
|
+
all_wallets: bool = False,
|
|
1005
|
+
sort_by: Optional[str] = None,
|
|
1006
|
+
sort_order: Optional[str] = None,
|
|
1007
|
+
include_hotkeys: Optional[list[str]] = None,
|
|
1008
|
+
exclude_hotkeys: Optional[list[str]] = None,
|
|
1009
|
+
netuids_filter: Optional[list[int]] = None,
|
|
1010
|
+
verbose: bool = False,
|
|
1011
|
+
json_output: bool = False,
|
|
1012
|
+
):
|
|
1013
|
+
"""Prints an overview for the wallet's coldkey."""
|
|
1014
|
+
|
|
1015
|
+
total_balance = Balance(0)
|
|
1016
|
+
|
|
1017
|
+
with console.status(
|
|
1018
|
+
f":satellite: Synchronizing with chain [white]{meshtensor.network}[/white]",
|
|
1019
|
+
spinner="aesthetic",
|
|
1020
|
+
) as status:
|
|
1021
|
+
# We are printing for every coldkey.
|
|
1022
|
+
block_hash = await meshtensor.substrate.get_chain_head()
|
|
1023
|
+
(
|
|
1024
|
+
(all_hotkeys, total_balance),
|
|
1025
|
+
_dynamic_info,
|
|
1026
|
+
block,
|
|
1027
|
+
all_netuids,
|
|
1028
|
+
) = await asyncio.gather(
|
|
1029
|
+
_get_total_balance(
|
|
1030
|
+
total_balance, meshtensor, wallet, all_wallets, block_hash=block_hash
|
|
1031
|
+
),
|
|
1032
|
+
meshtensor.all_subnets(block_hash=block_hash),
|
|
1033
|
+
meshtensor.substrate.get_block_number(block_hash=block_hash),
|
|
1034
|
+
meshtensor.get_all_subnet_netuids(block_hash=block_hash),
|
|
1035
|
+
)
|
|
1036
|
+
dynamic_info = {info.netuid: info for info in _dynamic_info}
|
|
1037
|
+
|
|
1038
|
+
# We are printing for a select number of hotkeys from all_hotkeys.
|
|
1039
|
+
if include_hotkeys or exclude_hotkeys:
|
|
1040
|
+
all_hotkeys = _get_hotkeys(include_hotkeys, exclude_hotkeys, all_hotkeys)
|
|
1041
|
+
|
|
1042
|
+
# Check we have keys to display.
|
|
1043
|
+
if not all_hotkeys:
|
|
1044
|
+
print_error("Aborting as no hotkeys found to process", status)
|
|
1045
|
+
return
|
|
1046
|
+
|
|
1047
|
+
# Pull neuron info for all keys.
|
|
1048
|
+
neurons: dict[str, list[NeuronInfoLite]] = {}
|
|
1049
|
+
|
|
1050
|
+
netuids = await meshtensor.filter_netuids_by_registered_hotkeys(
|
|
1051
|
+
all_netuids, netuids_filter, all_hotkeys, reuse_block=True
|
|
1052
|
+
)
|
|
1053
|
+
|
|
1054
|
+
for netuid in netuids:
|
|
1055
|
+
neurons[str(netuid)] = []
|
|
1056
|
+
|
|
1057
|
+
all_wallet_data = {(wallet.name, wallet.path) for wallet in all_hotkeys}
|
|
1058
|
+
|
|
1059
|
+
all_coldkey_wallets = [
|
|
1060
|
+
Wallet(name=wallet_name, path=wallet_path)
|
|
1061
|
+
for wallet_name, wallet_path in all_wallet_data
|
|
1062
|
+
]
|
|
1063
|
+
|
|
1064
|
+
all_coldkey_wallets, invalid_wallets = validate_coldkey_presence(
|
|
1065
|
+
all_coldkey_wallets
|
|
1066
|
+
)
|
|
1067
|
+
for invalid_wallet in invalid_wallets:
|
|
1068
|
+
print_error(
|
|
1069
|
+
f"No coldkeypub found for wallet: ({invalid_wallet.name})", status
|
|
1070
|
+
)
|
|
1071
|
+
all_hotkeys, _ = validate_coldkey_presence(all_hotkeys)
|
|
1072
|
+
|
|
1073
|
+
all_hotkey_addresses, hotkey_coldkey_to_hotkey_wallet = _get_key_address(
|
|
1074
|
+
all_hotkeys
|
|
1075
|
+
)
|
|
1076
|
+
|
|
1077
|
+
results = await _get_neurons_for_netuids(
|
|
1078
|
+
meshtensor, netuids, all_hotkey_addresses
|
|
1079
|
+
)
|
|
1080
|
+
neurons = _process_neuron_results(results, neurons, netuids)
|
|
1081
|
+
# Setup outer table.
|
|
1082
|
+
grid = Table.grid(pad_edge=True)
|
|
1083
|
+
data_dict = {
|
|
1084
|
+
"wallet": "",
|
|
1085
|
+
"network": meshtensor.network,
|
|
1086
|
+
"subnets": [],
|
|
1087
|
+
"total_balance": 0.0,
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
# Add title
|
|
1091
|
+
if not all_wallets:
|
|
1092
|
+
title = "[underline dark_orange]Wallet[/underline dark_orange]\n"
|
|
1093
|
+
details = (
|
|
1094
|
+
f"[bright_cyan]{wallet.name}[/bright_cyan] : "
|
|
1095
|
+
f"[bright_magenta]{wallet.coldkeypub.ss58_address}[/bright_magenta]"
|
|
1096
|
+
)
|
|
1097
|
+
grid.add_row(Align(title, vertical="middle", align="center"))
|
|
1098
|
+
grid.add_row(Align(details, vertical="middle", align="center"))
|
|
1099
|
+
data_dict["wallet"] = f"{wallet.name}|{wallet.coldkeypub.ss58_address}"
|
|
1100
|
+
else:
|
|
1101
|
+
title = "[underline dark_orange]All Wallets:[/underline dark_orange]"
|
|
1102
|
+
grid.add_row(Align(title, vertical="middle", align="center"))
|
|
1103
|
+
data_dict["wallet"] = "All"
|
|
1104
|
+
|
|
1105
|
+
grid.add_row(
|
|
1106
|
+
Align(
|
|
1107
|
+
f"[dark_orange]Network: {meshtensor.network}",
|
|
1108
|
+
vertical="middle",
|
|
1109
|
+
align="center",
|
|
1110
|
+
)
|
|
1111
|
+
)
|
|
1112
|
+
# Generate rows per netuid
|
|
1113
|
+
tempos = await asyncio.gather(
|
|
1114
|
+
*[
|
|
1115
|
+
meshtensor.get_hyperparameter("Tempo", netuid, block_hash)
|
|
1116
|
+
for netuid in netuids
|
|
1117
|
+
]
|
|
1118
|
+
)
|
|
1119
|
+
for netuid, subnet_tempo in zip(netuids, tempos):
|
|
1120
|
+
table_data = []
|
|
1121
|
+
subnet_dict = {
|
|
1122
|
+
"netuid": netuid,
|
|
1123
|
+
"tempo": subnet_tempo,
|
|
1124
|
+
"neurons": [],
|
|
1125
|
+
"name": "",
|
|
1126
|
+
"symbol": "",
|
|
1127
|
+
}
|
|
1128
|
+
data_dict["subnets"].append(subnet_dict)
|
|
1129
|
+
total_rank = 0.0
|
|
1130
|
+
total_trust = 0.0
|
|
1131
|
+
total_consensus = 0.0
|
|
1132
|
+
total_validator_trust = 0.0
|
|
1133
|
+
total_incentive = 0.0
|
|
1134
|
+
total_dividends = 0.0
|
|
1135
|
+
total_emission = 0
|
|
1136
|
+
total_stake = 0
|
|
1137
|
+
total_neurons = 0
|
|
1138
|
+
|
|
1139
|
+
for nn in neurons[str(netuid)]:
|
|
1140
|
+
hotwallet = hotkey_coldkey_to_hotkey_wallet.get(nn.hotkey, {}).get(
|
|
1141
|
+
nn.coldkey, None
|
|
1142
|
+
)
|
|
1143
|
+
if not hotwallet:
|
|
1144
|
+
# Indicates a mismatch between what the chain says the coldkey
|
|
1145
|
+
# is for this hotkey and the local wallet coldkey-hotkey pair
|
|
1146
|
+
hotwallet = WalletLike(name=nn.coldkey[:7], hotkey_str=nn.hotkey[:7])
|
|
1147
|
+
|
|
1148
|
+
nn: NeuronInfoLite
|
|
1149
|
+
uid = nn.uid
|
|
1150
|
+
active = nn.active
|
|
1151
|
+
stake = nn.total_stake.tao
|
|
1152
|
+
rank = nn.rank
|
|
1153
|
+
trust = nn.trust
|
|
1154
|
+
consensus = nn.consensus
|
|
1155
|
+
validator_trust = nn.validator_trust
|
|
1156
|
+
incentive = nn.incentive
|
|
1157
|
+
dividends = nn.dividends
|
|
1158
|
+
emission = int(nn.emission / (subnet_tempo + 1) * 1e9) # Per block
|
|
1159
|
+
last_update = int(block - nn.last_update)
|
|
1160
|
+
validator_permit = nn.validator_permit
|
|
1161
|
+
row = [
|
|
1162
|
+
hotwallet.name,
|
|
1163
|
+
hotwallet.hotkey_str,
|
|
1164
|
+
str(uid),
|
|
1165
|
+
str(active),
|
|
1166
|
+
f"{stake:.4f}" if verbose else millify_tao(stake),
|
|
1167
|
+
f"{rank:.4f}" if verbose else millify_tao(rank),
|
|
1168
|
+
f"{trust:.4f}" if verbose else millify_tao(trust),
|
|
1169
|
+
f"{consensus:.4f}" if verbose else millify_tao(consensus),
|
|
1170
|
+
f"{incentive:.4f}" if verbose else millify_tao(incentive),
|
|
1171
|
+
f"{dividends:.4f}" if verbose else millify_tao(dividends),
|
|
1172
|
+
f"{emission:.4f}",
|
|
1173
|
+
f"{validator_trust:.4f}" if verbose else millify_tao(validator_trust),
|
|
1174
|
+
"*" if validator_permit else "",
|
|
1175
|
+
str(last_update),
|
|
1176
|
+
(
|
|
1177
|
+
int_to_ip(nn.axon_info.ip) + ":" + str(nn.axon_info.port)
|
|
1178
|
+
if nn.axon_info.port != 0
|
|
1179
|
+
else "[yellow]none[/yellow]"
|
|
1180
|
+
),
|
|
1181
|
+
nn.hotkey[:10],
|
|
1182
|
+
]
|
|
1183
|
+
neuron_dict = {
|
|
1184
|
+
"coldkey": hotwallet.name,
|
|
1185
|
+
"hotkey": hotwallet.hotkey_str,
|
|
1186
|
+
"uid": uid,
|
|
1187
|
+
"active": active,
|
|
1188
|
+
"stake": stake,
|
|
1189
|
+
"rank": rank,
|
|
1190
|
+
"trust": trust,
|
|
1191
|
+
"consensus": consensus,
|
|
1192
|
+
"incentive": incentive,
|
|
1193
|
+
"dividends": dividends,
|
|
1194
|
+
"emission": emission,
|
|
1195
|
+
"validator_trust": validator_trust,
|
|
1196
|
+
"validator_permit": validator_permit,
|
|
1197
|
+
"last_update": last_update,
|
|
1198
|
+
"axon": int_to_ip(nn.axon_info.ip) + ":" + str(nn.axon_info.port)
|
|
1199
|
+
if nn.axon_info.port != 0
|
|
1200
|
+
else None,
|
|
1201
|
+
"hotkey_ss58": nn.hotkey,
|
|
1202
|
+
}
|
|
1203
|
+
|
|
1204
|
+
total_rank += rank
|
|
1205
|
+
total_trust += trust
|
|
1206
|
+
total_consensus += consensus
|
|
1207
|
+
total_incentive += incentive
|
|
1208
|
+
total_dividends += dividends
|
|
1209
|
+
total_emission += emission
|
|
1210
|
+
total_validator_trust += validator_trust
|
|
1211
|
+
total_stake += stake
|
|
1212
|
+
total_neurons += 1
|
|
1213
|
+
|
|
1214
|
+
table_data.append(row)
|
|
1215
|
+
subnet_dict["neurons"].append(neuron_dict)
|
|
1216
|
+
|
|
1217
|
+
# Add subnet header
|
|
1218
|
+
sn_name = get_subnet_name(dynamic_info[netuid])
|
|
1219
|
+
sn_symbol = dynamic_info[netuid].symbol
|
|
1220
|
+
grid.add_row(
|
|
1221
|
+
f"Subnet: [dark_orange]{netuid}: {sn_name} {sn_symbol}[/dark_orange]"
|
|
1222
|
+
)
|
|
1223
|
+
subnet_dict["name"] = sn_name
|
|
1224
|
+
subnet_dict["symbol"] = sn_symbol
|
|
1225
|
+
width = console.width
|
|
1226
|
+
table = Table(
|
|
1227
|
+
show_footer=False,
|
|
1228
|
+
pad_edge=True,
|
|
1229
|
+
box=box.SIMPLE,
|
|
1230
|
+
expand=True,
|
|
1231
|
+
width=width - 5,
|
|
1232
|
+
)
|
|
1233
|
+
|
|
1234
|
+
table.add_column("[white]COLDKEY", style="bold bright_cyan", ratio=2)
|
|
1235
|
+
table.add_column("[white]HOTKEY", style="bright_cyan", ratio=2)
|
|
1236
|
+
table.add_column(
|
|
1237
|
+
"[white]UID", str(total_neurons), style="rgb(42,161,152)", ratio=1
|
|
1238
|
+
)
|
|
1239
|
+
table.add_column(
|
|
1240
|
+
"[white]ACTIVE", justify="right", style="#8787ff", no_wrap=True, ratio=1
|
|
1241
|
+
)
|
|
1242
|
+
|
|
1243
|
+
_total_stake_formatted = (
|
|
1244
|
+
f"{total_stake:.4f}" if verbose else millify_tao(total_stake)
|
|
1245
|
+
)
|
|
1246
|
+
table.add_column(
|
|
1247
|
+
"[white]STAKE(\u03c4)"
|
|
1248
|
+
if netuid == 0
|
|
1249
|
+
else f"[white]STAKE({Balance.get_unit(netuid)})",
|
|
1250
|
+
f"{_total_stake_formatted} {Balance.get_unit(netuid)}"
|
|
1251
|
+
if netuid != 0
|
|
1252
|
+
else f"{Balance.get_unit(netuid)} {_total_stake_formatted}",
|
|
1253
|
+
justify="right",
|
|
1254
|
+
style="dark_orange",
|
|
1255
|
+
no_wrap=True,
|
|
1256
|
+
ratio=1.5,
|
|
1257
|
+
)
|
|
1258
|
+
table.add_column(
|
|
1259
|
+
"[white]RANK",
|
|
1260
|
+
f"{total_rank:.4f}",
|
|
1261
|
+
justify="right",
|
|
1262
|
+
style="medium_purple",
|
|
1263
|
+
no_wrap=True,
|
|
1264
|
+
ratio=1.5,
|
|
1265
|
+
)
|
|
1266
|
+
table.add_column(
|
|
1267
|
+
"[white]TRUST",
|
|
1268
|
+
f"{total_trust:.4f}",
|
|
1269
|
+
justify="right",
|
|
1270
|
+
style="green",
|
|
1271
|
+
no_wrap=True,
|
|
1272
|
+
ratio=1.5,
|
|
1273
|
+
)
|
|
1274
|
+
table.add_column(
|
|
1275
|
+
"[white]CONSENSUS",
|
|
1276
|
+
f"{total_consensus:.4f}",
|
|
1277
|
+
justify="right",
|
|
1278
|
+
style="rgb(42,161,152)",
|
|
1279
|
+
no_wrap=True,
|
|
1280
|
+
ratio=1.5,
|
|
1281
|
+
)
|
|
1282
|
+
table.add_column(
|
|
1283
|
+
"[white]INCENTIVE",
|
|
1284
|
+
f"{total_incentive:.4f}",
|
|
1285
|
+
justify="right",
|
|
1286
|
+
style="#5fd7ff",
|
|
1287
|
+
no_wrap=True,
|
|
1288
|
+
ratio=1.5,
|
|
1289
|
+
)
|
|
1290
|
+
table.add_column(
|
|
1291
|
+
"[white]DIVIDENDS",
|
|
1292
|
+
f"{total_dividends:.4f}",
|
|
1293
|
+
justify="right",
|
|
1294
|
+
style="#8787d7",
|
|
1295
|
+
no_wrap=True,
|
|
1296
|
+
ratio=1.5,
|
|
1297
|
+
)
|
|
1298
|
+
table.add_column(
|
|
1299
|
+
"[white]EMISSION(\u03c1)",
|
|
1300
|
+
f"\u03c1{total_emission}",
|
|
1301
|
+
justify="right",
|
|
1302
|
+
style="#d7d7ff",
|
|
1303
|
+
no_wrap=True,
|
|
1304
|
+
ratio=1.5,
|
|
1305
|
+
)
|
|
1306
|
+
table.add_column(
|
|
1307
|
+
"[white]VTRUST",
|
|
1308
|
+
f"{total_validator_trust:.4f}",
|
|
1309
|
+
justify="right",
|
|
1310
|
+
style="magenta",
|
|
1311
|
+
no_wrap=True,
|
|
1312
|
+
ratio=1.5,
|
|
1313
|
+
)
|
|
1314
|
+
table.add_column("[white]VPERMIT", justify="center", no_wrap=True, ratio=0.75)
|
|
1315
|
+
table.add_column("[white]UPDATED", justify="right", no_wrap=True, ratio=1)
|
|
1316
|
+
table.add_column(
|
|
1317
|
+
"[white]AXON", justify="left", style="light_goldenrod2", ratio=2.5
|
|
1318
|
+
)
|
|
1319
|
+
table.add_column("[white]HOTKEY_SS58", style="bright_magenta", ratio=2)
|
|
1320
|
+
table.show_footer = True
|
|
1321
|
+
|
|
1322
|
+
if sort_by:
|
|
1323
|
+
column_to_sort_by: int = 0
|
|
1324
|
+
sort_descending: bool = False # Default sort_order to ascending
|
|
1325
|
+
|
|
1326
|
+
for index, column in zip(range(len(table.columns)), table.columns):
|
|
1327
|
+
column_name = column.header.lower().replace("[white]", "")
|
|
1328
|
+
if column_name == sort_by.lower().strip():
|
|
1329
|
+
column_to_sort_by = index
|
|
1330
|
+
|
|
1331
|
+
if sort_order.lower() in {"desc", "descending", "reverse"}:
|
|
1332
|
+
# Sort descending if the sort_order matches desc, descending, or reverse
|
|
1333
|
+
sort_descending = True
|
|
1334
|
+
|
|
1335
|
+
def overview_sort_function(row_):
|
|
1336
|
+
data = row_[column_to_sort_by]
|
|
1337
|
+
# Try to convert to number if possible
|
|
1338
|
+
try:
|
|
1339
|
+
data = float(data)
|
|
1340
|
+
except ValueError:
|
|
1341
|
+
pass
|
|
1342
|
+
return data
|
|
1343
|
+
|
|
1344
|
+
table_data.sort(key=overview_sort_function, reverse=sort_descending)
|
|
1345
|
+
|
|
1346
|
+
for row in table_data:
|
|
1347
|
+
table.add_row(*row)
|
|
1348
|
+
|
|
1349
|
+
grid.add_row(table)
|
|
1350
|
+
|
|
1351
|
+
caption = (
|
|
1352
|
+
f"\n[italic][dim][bright_cyan]Wallet free balance: [dark_orange]{total_balance}"
|
|
1353
|
+
)
|
|
1354
|
+
data_dict["total_balance"] = total_balance.tao
|
|
1355
|
+
grid.add_row(Align(caption, vertical="middle", align="center"))
|
|
1356
|
+
|
|
1357
|
+
if console.width < 150:
|
|
1358
|
+
console.print(
|
|
1359
|
+
"[yellow]Warning: Your terminal width might be too small to view all information clearly"
|
|
1360
|
+
)
|
|
1361
|
+
# Print the entire table/grid
|
|
1362
|
+
if not json_output:
|
|
1363
|
+
console.print(grid, width=None)
|
|
1364
|
+
else:
|
|
1365
|
+
json_console.print(json.dumps(data_dict))
|
|
1366
|
+
|
|
1367
|
+
|
|
1368
|
+
def _get_hotkeys(
|
|
1369
|
+
include_hotkeys: list[str], exclude_hotkeys: list[str], all_hotkeys: list[Wallet]
|
|
1370
|
+
) -> list[Wallet]:
|
|
1371
|
+
"""Filters a set of hotkeys (all_hotkeys) based on whether they are included or excluded."""
|
|
1372
|
+
|
|
1373
|
+
def is_hotkey_matched(wallet: Wallet, item: str) -> bool:
|
|
1374
|
+
if is_valid_ss58_address(item):
|
|
1375
|
+
return get_hotkey_pub_ss58(wallet) == item
|
|
1376
|
+
else:
|
|
1377
|
+
return wallet.hotkey_str == item
|
|
1378
|
+
|
|
1379
|
+
if include_hotkeys:
|
|
1380
|
+
# We are only showing hotkeys that are specified.
|
|
1381
|
+
all_hotkeys = [
|
|
1382
|
+
hotkey
|
|
1383
|
+
for hotkey in all_hotkeys
|
|
1384
|
+
if any(is_hotkey_matched(hotkey, item) for item in include_hotkeys)
|
|
1385
|
+
]
|
|
1386
|
+
else:
|
|
1387
|
+
# We are excluding the specified hotkeys from all_hotkeys.
|
|
1388
|
+
all_hotkeys = [
|
|
1389
|
+
hotkey
|
|
1390
|
+
for hotkey in all_hotkeys
|
|
1391
|
+
if not any(is_hotkey_matched(hotkey, item) for item in exclude_hotkeys)
|
|
1392
|
+
]
|
|
1393
|
+
return all_hotkeys
|
|
1394
|
+
|
|
1395
|
+
|
|
1396
|
+
def _get_key_address(all_hotkeys: list[Wallet]) -> tuple[list[str], dict[str, Wallet]]:
|
|
1397
|
+
"""
|
|
1398
|
+
Maps the hotkeys specified to their respective addresses
|
|
1399
|
+
|
|
1400
|
+
:param all_hotkeys: list of hotkeys from which to derive the addresses
|
|
1401
|
+
|
|
1402
|
+
:return: (list of all hotkey addresses, mapping of them vs their coldkeys to respective wallets)
|
|
1403
|
+
"""
|
|
1404
|
+
hotkey_coldkey_to_hotkey_wallet = {}
|
|
1405
|
+
for hotkey_wallet in all_hotkeys:
|
|
1406
|
+
if hotkey_wallet.coldkeypub:
|
|
1407
|
+
hotkey_ss58 = get_hotkey_pub_ss58(hotkey_wallet)
|
|
1408
|
+
if hotkey_ss58 not in hotkey_coldkey_to_hotkey_wallet:
|
|
1409
|
+
hotkey_coldkey_to_hotkey_wallet[hotkey_ss58] = {}
|
|
1410
|
+
hotkey_coldkey_to_hotkey_wallet[hotkey_ss58][
|
|
1411
|
+
hotkey_wallet.coldkeypub.ss58_address
|
|
1412
|
+
] = hotkey_wallet
|
|
1413
|
+
else:
|
|
1414
|
+
# occurs when there is a hotkey without an associated coldkeypub
|
|
1415
|
+
# TODO log this, maybe display
|
|
1416
|
+
pass
|
|
1417
|
+
|
|
1418
|
+
all_hotkey_addresses = list(hotkey_coldkey_to_hotkey_wallet.keys())
|
|
1419
|
+
|
|
1420
|
+
return all_hotkey_addresses, hotkey_coldkey_to_hotkey_wallet
|
|
1421
|
+
|
|
1422
|
+
|
|
1423
|
+
async def _calculate_total_coldkey_stake(
|
|
1424
|
+
neurons: dict[str, list["NeuronInfoLite"]],
|
|
1425
|
+
) -> dict[str, Balance]:
|
|
1426
|
+
"""Maps coldkeys to their stakes (Balance) in the specified neurons"""
|
|
1427
|
+
total_coldkey_stake_from_metagraph = defaultdict(lambda: Balance(0.0))
|
|
1428
|
+
checked_hotkeys = set()
|
|
1429
|
+
for neuron_list in neurons.values():
|
|
1430
|
+
for neuron in neuron_list:
|
|
1431
|
+
if neuron.hotkey in checked_hotkeys:
|
|
1432
|
+
continue
|
|
1433
|
+
total_coldkey_stake_from_metagraph[neuron.coldkey] += neuron.stake_dict[
|
|
1434
|
+
neuron.coldkey
|
|
1435
|
+
]
|
|
1436
|
+
checked_hotkeys.add(neuron.hotkey)
|
|
1437
|
+
return total_coldkey_stake_from_metagraph
|
|
1438
|
+
|
|
1439
|
+
|
|
1440
|
+
def _process_neuron_results(
|
|
1441
|
+
results: list[tuple[int, list["NeuronInfoLite"], Optional[str]]],
|
|
1442
|
+
neurons: dict[str, list["NeuronInfoLite"]],
|
|
1443
|
+
netuids: list[int],
|
|
1444
|
+
) -> dict[str, list["NeuronInfoLite"]]:
|
|
1445
|
+
"""
|
|
1446
|
+
Filters a list of Neurons for their netuid, neuron info, and errors
|
|
1447
|
+
|
|
1448
|
+
:param results: [(netuid, neurons result, error message), ...]
|
|
1449
|
+
:param neurons: {netuid: [], ...}
|
|
1450
|
+
:param netuids: list of netuids to filter the neurons
|
|
1451
|
+
|
|
1452
|
+
:return: the filtered neurons dict
|
|
1453
|
+
"""
|
|
1454
|
+
for result in results:
|
|
1455
|
+
netuid, neurons_result, err_msg = result
|
|
1456
|
+
if err_msg is not None:
|
|
1457
|
+
console.print(f"netuid '{netuid}': {err_msg}")
|
|
1458
|
+
|
|
1459
|
+
if len(neurons_result) == 0:
|
|
1460
|
+
# Remove netuid from overview if no neurons are found.
|
|
1461
|
+
netuids.remove(netuid)
|
|
1462
|
+
del neurons[str(netuid)]
|
|
1463
|
+
else:
|
|
1464
|
+
# Add neurons to overview.
|
|
1465
|
+
neurons[str(netuid)] = neurons_result
|
|
1466
|
+
return neurons
|
|
1467
|
+
|
|
1468
|
+
|
|
1469
|
+
def _map_hotkey_to_neurons(
|
|
1470
|
+
all_neurons: list["NeuronInfoLite"],
|
|
1471
|
+
hot_wallets: list[str],
|
|
1472
|
+
netuid: int,
|
|
1473
|
+
) -> tuple[int, list["NeuronInfoLite"], Optional[str]]:
|
|
1474
|
+
"""Maps the hotkeys to their respective neurons"""
|
|
1475
|
+
result: list["NeuronInfoLite"] = []
|
|
1476
|
+
hotkey_to_neurons = {n.hotkey: n.uid for n in all_neurons}
|
|
1477
|
+
try:
|
|
1478
|
+
for hot_wallet_addr in hot_wallets:
|
|
1479
|
+
uid = hotkey_to_neurons.get(hot_wallet_addr)
|
|
1480
|
+
if uid is not None:
|
|
1481
|
+
nn = all_neurons[uid]
|
|
1482
|
+
result.append(nn)
|
|
1483
|
+
except Exception as e:
|
|
1484
|
+
return netuid, [], f"Error: {e}"
|
|
1485
|
+
|
|
1486
|
+
return netuid, result, None
|
|
1487
|
+
|
|
1488
|
+
|
|
1489
|
+
async def _fetch_neuron_for_netuid(
|
|
1490
|
+
netuid: int, meshtensor: MeshtensorInterface
|
|
1491
|
+
) -> tuple[int, list[NeuronInfoLite]]:
|
|
1492
|
+
"""
|
|
1493
|
+
Retrieves all neurons for a specified netuid
|
|
1494
|
+
|
|
1495
|
+
:param netuid: the netuid to query
|
|
1496
|
+
:param meshtensor: the MeshtensorInterface to make the query
|
|
1497
|
+
|
|
1498
|
+
:return: the original netuid, and a mapping of the neurons to their NeuronInfoLite objects
|
|
1499
|
+
"""
|
|
1500
|
+
neurons = await meshtensor.neurons_lite(netuid=netuid)
|
|
1501
|
+
return netuid, neurons
|
|
1502
|
+
|
|
1503
|
+
|
|
1504
|
+
async def _fetch_all_neurons(
|
|
1505
|
+
netuids: list[int], meshtensor
|
|
1506
|
+
) -> list[tuple[int, list[NeuronInfoLite]]]:
|
|
1507
|
+
"""Retrieves all neurons for each of the specified netuids"""
|
|
1508
|
+
return list(
|
|
1509
|
+
await asyncio.gather(
|
|
1510
|
+
*[_fetch_neuron_for_netuid(netuid, meshtensor) for netuid in netuids]
|
|
1511
|
+
)
|
|
1512
|
+
)
|
|
1513
|
+
|
|
1514
|
+
|
|
1515
|
+
async def _get_neurons_for_netuids(
|
|
1516
|
+
meshtensor: MeshtensorInterface, netuids: list[int], hot_wallets: list[str]
|
|
1517
|
+
) -> list[tuple[int, list["NeuronInfoLite"], Optional[str]]]:
|
|
1518
|
+
all_neurons = await _fetch_all_neurons(netuids, meshtensor)
|
|
1519
|
+
return [
|
|
1520
|
+
_map_hotkey_to_neurons(neurons, hot_wallets, netuid)
|
|
1521
|
+
for netuid, neurons in all_neurons
|
|
1522
|
+
]
|
|
1523
|
+
|
|
1524
|
+
|
|
1525
|
+
async def transfer(
|
|
1526
|
+
wallet: Wallet,
|
|
1527
|
+
meshtensor: MeshtensorInterface,
|
|
1528
|
+
destination: str,
|
|
1529
|
+
amount: float,
|
|
1530
|
+
transfer_all: bool,
|
|
1531
|
+
allow_death: bool,
|
|
1532
|
+
era: int,
|
|
1533
|
+
prompt: bool,
|
|
1534
|
+
json_output: bool,
|
|
1535
|
+
proxy: Optional[str] = None,
|
|
1536
|
+
announce_only: bool = False,
|
|
1537
|
+
):
|
|
1538
|
+
"""Transfer token of amount to destination."""
|
|
1539
|
+
result, ext_receipt = await transfer_extrinsic(
|
|
1540
|
+
meshtensor=meshtensor,
|
|
1541
|
+
wallet=wallet,
|
|
1542
|
+
destination=destination,
|
|
1543
|
+
amount=Balance.from_tao(amount),
|
|
1544
|
+
transfer_all=transfer_all,
|
|
1545
|
+
allow_death=allow_death,
|
|
1546
|
+
era=era,
|
|
1547
|
+
prompt=prompt,
|
|
1548
|
+
proxy=proxy,
|
|
1549
|
+
announce_only=announce_only,
|
|
1550
|
+
)
|
|
1551
|
+
ext_id = (await ext_receipt.get_extrinsic_identifier()) if result else None
|
|
1552
|
+
if json_output:
|
|
1553
|
+
json_console.print(
|
|
1554
|
+
json.dumps({"success": result, "extrinsic_identifier": ext_id})
|
|
1555
|
+
)
|
|
1556
|
+
else:
|
|
1557
|
+
await print_extrinsic_id(ext_receipt)
|
|
1558
|
+
return result
|
|
1559
|
+
|
|
1560
|
+
|
|
1561
|
+
async def inspect(
|
|
1562
|
+
wallet: Wallet,
|
|
1563
|
+
meshtensor: MeshtensorInterface,
|
|
1564
|
+
netuids_filter: list[int],
|
|
1565
|
+
all_wallets: bool = False,
|
|
1566
|
+
):
|
|
1567
|
+
# TODO add json_output when this is re-enabled and updated for dMESH
|
|
1568
|
+
def delegate_row_maker(
|
|
1569
|
+
delegates_: list[tuple[DelegateInfo, Balance]],
|
|
1570
|
+
) -> Generator[list[str], None, None]:
|
|
1571
|
+
for d_, staked in delegates_:
|
|
1572
|
+
if not staked.tao > 0:
|
|
1573
|
+
continue
|
|
1574
|
+
if d_.hotkey_ss58 in registered_delegate_info:
|
|
1575
|
+
delegate_name = registered_delegate_info[d_.hotkey_ss58].display
|
|
1576
|
+
else:
|
|
1577
|
+
delegate_name = d_.hotkey_ss58
|
|
1578
|
+
yield (
|
|
1579
|
+
[""] * 2
|
|
1580
|
+
+ [
|
|
1581
|
+
str(delegate_name),
|
|
1582
|
+
str(staked),
|
|
1583
|
+
str(
|
|
1584
|
+
d_.total_daily_return.tao * (staked.tao / d_.total_stake.tao)
|
|
1585
|
+
if d_.total_stake.tao != 0
|
|
1586
|
+
else 0
|
|
1587
|
+
),
|
|
1588
|
+
]
|
|
1589
|
+
+ [""] * 4
|
|
1590
|
+
)
|
|
1591
|
+
|
|
1592
|
+
def neuron_row_maker(
|
|
1593
|
+
wallet_, all_netuids_, nsd
|
|
1594
|
+
) -> Generator[list[str], None, None]:
|
|
1595
|
+
hotkeys = get_hotkey_wallets_for_wallet(wallet_)
|
|
1596
|
+
for netuid in all_netuids_:
|
|
1597
|
+
for n in nsd[netuid]:
|
|
1598
|
+
if n.coldkey == wallet_.coldkeypub.ss58_address:
|
|
1599
|
+
hotkey_name: str = ""
|
|
1600
|
+
if hotkey_names := [
|
|
1601
|
+
w.hotkey_str
|
|
1602
|
+
for w in hotkeys
|
|
1603
|
+
if get_hotkey_pub_ss58(w) == n.hotkey
|
|
1604
|
+
]:
|
|
1605
|
+
hotkey_name = f"{hotkey_names[0]}-"
|
|
1606
|
+
yield [""] * 5 + [
|
|
1607
|
+
str(netuid),
|
|
1608
|
+
f"{hotkey_name}{n.hotkey}",
|
|
1609
|
+
str(n.stake),
|
|
1610
|
+
str(Balance.from_tao(n.emission)),
|
|
1611
|
+
]
|
|
1612
|
+
|
|
1613
|
+
if all_wallets:
|
|
1614
|
+
print_verbose("Fetching data for all wallets")
|
|
1615
|
+
wallets = get_coldkey_wallets_for_path(wallet.path)
|
|
1616
|
+
all_hotkeys = get_all_wallets_for_path(
|
|
1617
|
+
wallet.path
|
|
1618
|
+
) # TODO verify this is correct
|
|
1619
|
+
|
|
1620
|
+
else:
|
|
1621
|
+
print_verbose(f"Fetching data for wallet: {wallet.name}")
|
|
1622
|
+
wallets = [wallet]
|
|
1623
|
+
all_hotkeys = get_hotkey_wallets_for_wallet(wallet)
|
|
1624
|
+
|
|
1625
|
+
with console.status("Synchronising with chain...", spinner="aesthetic") as status:
|
|
1626
|
+
block_hash = await meshtensor.substrate.get_chain_head()
|
|
1627
|
+
await meshtensor.substrate.init_runtime(block_hash=block_hash)
|
|
1628
|
+
|
|
1629
|
+
print_verbose("Fetching netuids of registered hotkeys", status)
|
|
1630
|
+
all_netuids = await meshtensor.filter_netuids_by_registered_hotkeys(
|
|
1631
|
+
(await meshtensor.get_all_subnet_netuids(block_hash)),
|
|
1632
|
+
netuids_filter,
|
|
1633
|
+
all_hotkeys,
|
|
1634
|
+
block_hash=block_hash,
|
|
1635
|
+
)
|
|
1636
|
+
# meshtensor.logging.debug(f"Netuids to check: {all_netuids}")
|
|
1637
|
+
with console.status("Pulling delegates info...", spinner="aesthetic"):
|
|
1638
|
+
registered_delegate_info = await meshtensor.get_delegate_identities()
|
|
1639
|
+
if not registered_delegate_info:
|
|
1640
|
+
console.print(
|
|
1641
|
+
":warning:[yellow]Could not get delegate info from chain.[/yellow]"
|
|
1642
|
+
)
|
|
1643
|
+
|
|
1644
|
+
table = Table(
|
|
1645
|
+
Column("[bold white]Coldkey", style="dark_orange"),
|
|
1646
|
+
Column("[bold white]Balance", style="dark_sea_green"),
|
|
1647
|
+
Column("[bold white]Delegate", style="bright_cyan", overflow="fold"),
|
|
1648
|
+
Column("[bold white]Stake", style="light_goldenrod2"),
|
|
1649
|
+
Column("[bold white]Emission", style="rgb(42,161,152)"),
|
|
1650
|
+
Column("[bold white]Netuid", style="dark_orange"),
|
|
1651
|
+
Column("[bold white]Hotkey", style="bright_magenta", overflow="fold"),
|
|
1652
|
+
Column("[bold white]Stake", style="light_goldenrod2"),
|
|
1653
|
+
Column("[bold white]Emission", style="rgb(42,161,152)"),
|
|
1654
|
+
title=f"[underline dark_orange]Wallets[/underline dark_orange]\n[dark_orange]Network: {meshtensor.network}\n",
|
|
1655
|
+
show_edge=False,
|
|
1656
|
+
expand=True,
|
|
1657
|
+
box=box.MINIMAL,
|
|
1658
|
+
border_style="bright_black",
|
|
1659
|
+
)
|
|
1660
|
+
rows = []
|
|
1661
|
+
wallets_with_ckp_file = [
|
|
1662
|
+
wallet for wallet in wallets if wallet.coldkeypub_file.exists_on_device()
|
|
1663
|
+
]
|
|
1664
|
+
all_delegates: list[list[tuple[DelegateInfo, Balance]]]
|
|
1665
|
+
with console.status("Pulling balance data...", spinner="aesthetic"):
|
|
1666
|
+
balances, all_neurons, all_delegates = await asyncio.gather(
|
|
1667
|
+
meshtensor.get_balances(
|
|
1668
|
+
*[w.coldkeypub.ss58_address for w in wallets_with_ckp_file],
|
|
1669
|
+
block_hash=block_hash,
|
|
1670
|
+
),
|
|
1671
|
+
asyncio.gather(
|
|
1672
|
+
*[
|
|
1673
|
+
meshtensor.neurons_lite(netuid=netuid, block_hash=block_hash)
|
|
1674
|
+
for netuid in all_netuids
|
|
1675
|
+
]
|
|
1676
|
+
),
|
|
1677
|
+
asyncio.gather(
|
|
1678
|
+
*[
|
|
1679
|
+
meshtensor.get_delegated(w.coldkeypub.ss58_address)
|
|
1680
|
+
for w in wallets_with_ckp_file
|
|
1681
|
+
]
|
|
1682
|
+
),
|
|
1683
|
+
)
|
|
1684
|
+
neuron_state_dict = {}
|
|
1685
|
+
for netuid, neuron in zip(all_netuids, all_neurons):
|
|
1686
|
+
neuron_state_dict[netuid] = neuron if neuron else []
|
|
1687
|
+
|
|
1688
|
+
for wall, d in zip(wallets_with_ckp_file, all_delegates):
|
|
1689
|
+
rows.append([wall.name, str(balances[wall.coldkeypub.ss58_address])] + [""] * 7)
|
|
1690
|
+
for row in itertools.chain(
|
|
1691
|
+
delegate_row_maker(d),
|
|
1692
|
+
neuron_row_maker(wall, all_netuids, neuron_state_dict),
|
|
1693
|
+
):
|
|
1694
|
+
rows.append(row)
|
|
1695
|
+
|
|
1696
|
+
for i, row in enumerate(rows):
|
|
1697
|
+
is_last_row = i + 1 == len(rows)
|
|
1698
|
+
table.add_row(*row)
|
|
1699
|
+
|
|
1700
|
+
# If last row or new coldkey starting next
|
|
1701
|
+
if is_last_row or (rows[i + 1][0] != ""):
|
|
1702
|
+
table.add_row(end_section=True)
|
|
1703
|
+
|
|
1704
|
+
return console.print(table)
|
|
1705
|
+
|
|
1706
|
+
|
|
1707
|
+
async def faucet(
|
|
1708
|
+
wallet: Wallet,
|
|
1709
|
+
meshtensor: MeshtensorInterface,
|
|
1710
|
+
threads_per_block: int,
|
|
1711
|
+
update_interval: int,
|
|
1712
|
+
processes: int,
|
|
1713
|
+
use_cuda: bool,
|
|
1714
|
+
dev_id: int,
|
|
1715
|
+
output_in_place: bool,
|
|
1716
|
+
log_verbose: bool,
|
|
1717
|
+
max_successes: int = 3,
|
|
1718
|
+
prompt: bool = True,
|
|
1719
|
+
):
|
|
1720
|
+
# TODO: - work out prompts to be passed through the cli
|
|
1721
|
+
success = await run_faucet_extrinsic(
|
|
1722
|
+
meshtensor,
|
|
1723
|
+
wallet,
|
|
1724
|
+
tpb=threads_per_block,
|
|
1725
|
+
prompt=prompt,
|
|
1726
|
+
update_interval=update_interval,
|
|
1727
|
+
num_processes=processes,
|
|
1728
|
+
cuda=use_cuda,
|
|
1729
|
+
dev_id=dev_id,
|
|
1730
|
+
output_in_place=output_in_place,
|
|
1731
|
+
log_verbose=log_verbose,
|
|
1732
|
+
max_successes=max_successes,
|
|
1733
|
+
)
|
|
1734
|
+
if not success:
|
|
1735
|
+
print_error("Faucet run failed.")
|
|
1736
|
+
|
|
1737
|
+
|
|
1738
|
+
async def swap_hotkey(
|
|
1739
|
+
original_wallet: Wallet,
|
|
1740
|
+
new_wallet: Wallet,
|
|
1741
|
+
meshtensor: MeshtensorInterface,
|
|
1742
|
+
netuid: Optional[int],
|
|
1743
|
+
proxy: Optional[str],
|
|
1744
|
+
prompt: bool,
|
|
1745
|
+
json_output: bool,
|
|
1746
|
+
):
|
|
1747
|
+
"""Swap your hotkey for all registered axons on the network."""
|
|
1748
|
+
result, ext_receipt = await swap_hotkey_extrinsic(
|
|
1749
|
+
meshtensor,
|
|
1750
|
+
original_wallet,
|
|
1751
|
+
new_wallet,
|
|
1752
|
+
netuid=netuid,
|
|
1753
|
+
prompt=prompt,
|
|
1754
|
+
proxy=proxy,
|
|
1755
|
+
)
|
|
1756
|
+
if result:
|
|
1757
|
+
ext_id = await ext_receipt.get_extrinsic_identifier()
|
|
1758
|
+
else:
|
|
1759
|
+
ext_id = None
|
|
1760
|
+
if json_output:
|
|
1761
|
+
json_console.print(
|
|
1762
|
+
json.dumps({"success": result, "extrinsic_identifier": ext_id})
|
|
1763
|
+
)
|
|
1764
|
+
else:
|
|
1765
|
+
await print_extrinsic_id(ext_receipt)
|
|
1766
|
+
return result
|
|
1767
|
+
|
|
1768
|
+
|
|
1769
|
+
def create_identity_table(title: str = None):
|
|
1770
|
+
if not title:
|
|
1771
|
+
title = "On-Chain Identity"
|
|
1772
|
+
|
|
1773
|
+
table = Table(
|
|
1774
|
+
Column(
|
|
1775
|
+
"Item",
|
|
1776
|
+
justify="right",
|
|
1777
|
+
style=COLOR_PALETTE["GENERAL"]["SUBHEADING_MAIN"],
|
|
1778
|
+
no_wrap=True,
|
|
1779
|
+
),
|
|
1780
|
+
Column("Value", style=COLOR_PALETTE["GENERAL"]["SUBHEADING"]),
|
|
1781
|
+
title=f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]{title}",
|
|
1782
|
+
show_footer=True,
|
|
1783
|
+
show_edge=False,
|
|
1784
|
+
header_style="bold white",
|
|
1785
|
+
border_style="bright_black",
|
|
1786
|
+
style="bold",
|
|
1787
|
+
title_justify="center",
|
|
1788
|
+
show_lines=False,
|
|
1789
|
+
pad_edge=True,
|
|
1790
|
+
)
|
|
1791
|
+
return table
|
|
1792
|
+
|
|
1793
|
+
|
|
1794
|
+
async def set_id(
|
|
1795
|
+
wallet: Wallet,
|
|
1796
|
+
meshtensor: MeshtensorInterface,
|
|
1797
|
+
name: str,
|
|
1798
|
+
web_url: str,
|
|
1799
|
+
image_url: str,
|
|
1800
|
+
discord: str,
|
|
1801
|
+
description: str,
|
|
1802
|
+
additional: str,
|
|
1803
|
+
github_repo: str,
|
|
1804
|
+
json_output: bool = False,
|
|
1805
|
+
proxy: Optional[str] = None,
|
|
1806
|
+
) -> bool:
|
|
1807
|
+
"""Create a new or update existing identity on-chain."""
|
|
1808
|
+
output_dict = {"success": False, "identity": None, "error": ""}
|
|
1809
|
+
identity_data = {
|
|
1810
|
+
"name": name.encode(),
|
|
1811
|
+
"url": web_url.encode(),
|
|
1812
|
+
"image": image_url.encode(),
|
|
1813
|
+
"discord": discord.encode(),
|
|
1814
|
+
"description": description.encode(),
|
|
1815
|
+
"additional": additional.encode(),
|
|
1816
|
+
"github_repo": github_repo.encode(),
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
if not unlock_key(wallet).success:
|
|
1820
|
+
return False
|
|
1821
|
+
|
|
1822
|
+
call = await meshtensor.substrate.compose_call(
|
|
1823
|
+
call_module="MeshtensorModule",
|
|
1824
|
+
call_function="set_identity",
|
|
1825
|
+
call_params=identity_data,
|
|
1826
|
+
)
|
|
1827
|
+
|
|
1828
|
+
with console.status(
|
|
1829
|
+
" :satellite: [dark_sea_green3]Updating identity on-chain...", spinner="earth"
|
|
1830
|
+
):
|
|
1831
|
+
success, err_msg, ext_receipt = await meshtensor.sign_and_send_extrinsic(
|
|
1832
|
+
call, wallet, proxy=proxy
|
|
1833
|
+
)
|
|
1834
|
+
|
|
1835
|
+
if not success:
|
|
1836
|
+
print_error(f"Failed! {err_msg}")
|
|
1837
|
+
output_dict["error"] = err_msg
|
|
1838
|
+
if json_output:
|
|
1839
|
+
json_console.print(json.dumps(output_dict))
|
|
1840
|
+
return False
|
|
1841
|
+
else:
|
|
1842
|
+
console.print(":white_heavy_check_mark: [dark_sea_green3]Success!")
|
|
1843
|
+
ext_id = await ext_receipt.get_extrinsic_identifier()
|
|
1844
|
+
await print_extrinsic_id(ext_receipt)
|
|
1845
|
+
output_dict["success"] = True
|
|
1846
|
+
identity = await meshtensor.query_identity(wallet.coldkeypub.ss58_address)
|
|
1847
|
+
|
|
1848
|
+
table = create_identity_table(title="New on-chain Identity")
|
|
1849
|
+
table.add_row("Address", wallet.coldkeypub.ss58_address)
|
|
1850
|
+
for key, value in identity.items():
|
|
1851
|
+
table.add_row(key, str(value) if value else "~")
|
|
1852
|
+
output_dict["identity"] = identity
|
|
1853
|
+
output_dict["extrinsic_identifier"] = ext_id
|
|
1854
|
+
if json_output:
|
|
1855
|
+
json_console.print(json.dumps(output_dict))
|
|
1856
|
+
else:
|
|
1857
|
+
console.print(table)
|
|
1858
|
+
return True
|
|
1859
|
+
|
|
1860
|
+
|
|
1861
|
+
async def get_id(
|
|
1862
|
+
meshtensor: MeshtensorInterface,
|
|
1863
|
+
ss58_address: str,
|
|
1864
|
+
title: str = None,
|
|
1865
|
+
json_output: bool = False,
|
|
1866
|
+
):
|
|
1867
|
+
with console.status(
|
|
1868
|
+
":satellite: [bold green]Querying chain identity...", spinner="earth"
|
|
1869
|
+
):
|
|
1870
|
+
identity = await meshtensor.query_identity(ss58_address)
|
|
1871
|
+
|
|
1872
|
+
if not identity:
|
|
1873
|
+
print_error(
|
|
1874
|
+
f"[blue]Existing identity not found[/blue]"
|
|
1875
|
+
f" for [{COLOR_PALETTE['GENERAL']['COLDKEY']}]{ss58_address}[/{COLOR_PALETTE['GENERAL']['COLDKEY']}]"
|
|
1876
|
+
f" on {meshtensor}"
|
|
1877
|
+
)
|
|
1878
|
+
if json_output:
|
|
1879
|
+
json_console.print("{}")
|
|
1880
|
+
return {}
|
|
1881
|
+
|
|
1882
|
+
table = create_identity_table(title)
|
|
1883
|
+
table.add_row("Address", ss58_address)
|
|
1884
|
+
for key, value in identity.items():
|
|
1885
|
+
table.add_row(key, str(value) if value else "~")
|
|
1886
|
+
|
|
1887
|
+
console.print(table)
|
|
1888
|
+
if json_output:
|
|
1889
|
+
json_console.print(json.dumps(identity))
|
|
1890
|
+
return identity
|
|
1891
|
+
|
|
1892
|
+
|
|
1893
|
+
async def check_coldkey_swap(wallet: Wallet, meshtensor: MeshtensorInterface):
|
|
1894
|
+
arbitration_check = len( # TODO verify this works
|
|
1895
|
+
(
|
|
1896
|
+
await meshtensor.query(
|
|
1897
|
+
module="MeshtensorModule",
|
|
1898
|
+
storage_function="ColdkeySwapDestinations",
|
|
1899
|
+
params=[wallet.coldkeypub.ss58_address],
|
|
1900
|
+
)
|
|
1901
|
+
)
|
|
1902
|
+
)
|
|
1903
|
+
if arbitration_check == 0:
|
|
1904
|
+
console.print(
|
|
1905
|
+
"[green]There has been no previous key swap initiated for your coldkey.[/green]"
|
|
1906
|
+
)
|
|
1907
|
+
elif arbitration_check == 1:
|
|
1908
|
+
arbitration_block = await meshtensor.query(
|
|
1909
|
+
module="MeshtensorModule",
|
|
1910
|
+
storage_function="ColdkeyArbitrationBlock",
|
|
1911
|
+
params=[wallet.coldkeypub.ss58_address],
|
|
1912
|
+
)
|
|
1913
|
+
arbitration_remaining = (
|
|
1914
|
+
arbitration_block.value - await meshtensor.substrate.get_block_number(None)
|
|
1915
|
+
)
|
|
1916
|
+
|
|
1917
|
+
hours, minutes, seconds = convert_blocks_to_time(arbitration_remaining)
|
|
1918
|
+
console.print(
|
|
1919
|
+
"[yellow]There has been 1 swap request made for this coldkey already."
|
|
1920
|
+
" By adding another swap request, the key will enter arbitration."
|
|
1921
|
+
f" Your key swap is scheduled for {hours} hours, {minutes} minutes, {seconds} seconds"
|
|
1922
|
+
" from now.[/yellow]"
|
|
1923
|
+
)
|
|
1924
|
+
elif arbitration_check > 1:
|
|
1925
|
+
console.print(
|
|
1926
|
+
f"[red]This coldkey is currently in arbitration with a total swaps of {arbitration_check}.[/red]"
|
|
1927
|
+
)
|
|
1928
|
+
|
|
1929
|
+
|
|
1930
|
+
async def sign(
|
|
1931
|
+
wallet: Wallet, message: str, use_hotkey: bool, json_output: bool = False
|
|
1932
|
+
):
|
|
1933
|
+
"""Sign a message using the provided wallet or hotkey."""
|
|
1934
|
+
|
|
1935
|
+
if not use_hotkey:
|
|
1936
|
+
if not unlock_key(wallet, "cold").success:
|
|
1937
|
+
return False
|
|
1938
|
+
keypair = wallet.coldkey
|
|
1939
|
+
print_verbose(
|
|
1940
|
+
f"Signing using [{COLOR_PALETTE['GENERAL']['COLDKEY']}]coldkey: {wallet.name}"
|
|
1941
|
+
)
|
|
1942
|
+
else:
|
|
1943
|
+
if not unlock_key(wallet, "hot").success:
|
|
1944
|
+
return False
|
|
1945
|
+
keypair = wallet.hotkey
|
|
1946
|
+
print_verbose(
|
|
1947
|
+
f"Signing using [{COLOR_PALETTE['GENERAL']['HOTKEY']}]hotkey: {wallet.hotkey_str}"
|
|
1948
|
+
)
|
|
1949
|
+
|
|
1950
|
+
signed_message = keypair.sign(message.encode("utf-8")).hex()
|
|
1951
|
+
signer_address = keypair.ss58_address
|
|
1952
|
+
console.print("[dark_sea_green3]Message signed successfully!\n")
|
|
1953
|
+
|
|
1954
|
+
if json_output:
|
|
1955
|
+
json_console.print(
|
|
1956
|
+
json.dumps(
|
|
1957
|
+
{"signed_message": signed_message, "signer_address": signer_address}
|
|
1958
|
+
)
|
|
1959
|
+
)
|
|
1960
|
+
else:
|
|
1961
|
+
console.print(f"[yellow]Signature:[/yellow]\n{signed_message}")
|
|
1962
|
+
console.print(f"[yellow]Signer address:[/yellow] {signer_address}")
|
|
1963
|
+
|
|
1964
|
+
|
|
1965
|
+
async def verify(
|
|
1966
|
+
message: str,
|
|
1967
|
+
signature: str,
|
|
1968
|
+
public_key_or_ss58: str,
|
|
1969
|
+
json_output: bool = False,
|
|
1970
|
+
):
|
|
1971
|
+
"""Verify a message signature using a public key or SS58 address."""
|
|
1972
|
+
|
|
1973
|
+
if is_valid_ss58_address(public_key_or_ss58):
|
|
1974
|
+
print_verbose(f"[blue]SS58 address detected:[/blue] {public_key_or_ss58}")
|
|
1975
|
+
keypair = Keypair(ss58_address=public_key_or_ss58)
|
|
1976
|
+
signer_address = public_key_or_ss58
|
|
1977
|
+
else:
|
|
1978
|
+
try:
|
|
1979
|
+
public_key_hex = public_key_or_ss58.strip().lower()
|
|
1980
|
+
if public_key_hex.startswith("0x"):
|
|
1981
|
+
public_key_hex = public_key_hex[2:]
|
|
1982
|
+
if len(public_key_hex) == 64:
|
|
1983
|
+
bytes.fromhex(public_key_hex)
|
|
1984
|
+
print_verbose("[blue]Hex public key detected[/blue] (64 characters)")
|
|
1985
|
+
keypair = Keypair(public_key=public_key_hex)
|
|
1986
|
+
signer_address = keypair.ss58_address
|
|
1987
|
+
print_verbose(
|
|
1988
|
+
f"[blue]Corresponding SS58 address:[/blue] {signer_address}"
|
|
1989
|
+
)
|
|
1990
|
+
else:
|
|
1991
|
+
raise ValueError("Public key must be 32 bytes (64 hex characters)")
|
|
1992
|
+
|
|
1993
|
+
except (ValueError, TypeError) as e:
|
|
1994
|
+
if json_output:
|
|
1995
|
+
json_console.print(
|
|
1996
|
+
json.dumps(
|
|
1997
|
+
{
|
|
1998
|
+
"verified": False,
|
|
1999
|
+
"error": f"Invalid public key or SS58 address: {str(e)}",
|
|
2000
|
+
}
|
|
2001
|
+
)
|
|
2002
|
+
)
|
|
2003
|
+
else:
|
|
2004
|
+
print_error(
|
|
2005
|
+
f"Invalid SS58 address or hex public key (64 chars, with or without 0x prefix)- {str(e)}"
|
|
2006
|
+
)
|
|
2007
|
+
return False
|
|
2008
|
+
|
|
2009
|
+
try:
|
|
2010
|
+
signature_bytes = bytes.fromhex(signature.strip().lower().replace("0x", ""))
|
|
2011
|
+
except ValueError as e:
|
|
2012
|
+
if json_output:
|
|
2013
|
+
json_console.print(
|
|
2014
|
+
json.dumps(
|
|
2015
|
+
{
|
|
2016
|
+
"verified": False,
|
|
2017
|
+
"error": f"Invalid signature format: {str(e)}",
|
|
2018
|
+
}
|
|
2019
|
+
)
|
|
2020
|
+
)
|
|
2021
|
+
else:
|
|
2022
|
+
print_error(f"Invalid signature format: {str(e)}")
|
|
2023
|
+
return False
|
|
2024
|
+
|
|
2025
|
+
is_valid = keypair.verify(message.encode("utf-8"), signature_bytes)
|
|
2026
|
+
|
|
2027
|
+
if json_output:
|
|
2028
|
+
json_console.print(
|
|
2029
|
+
json.dumps(
|
|
2030
|
+
{"verified": is_valid, "signer": signer_address, "message": message}
|
|
2031
|
+
)
|
|
2032
|
+
)
|
|
2033
|
+
else:
|
|
2034
|
+
if is_valid:
|
|
2035
|
+
console.print("[dark_sea_green3]Signature is valid!\n")
|
|
2036
|
+
console.print(f"[yellow]Signer:[/yellow] {signer_address}")
|
|
2037
|
+
else:
|
|
2038
|
+
print_error("Signature verification failed!")
|
|
2039
|
+
|
|
2040
|
+
return is_valid
|
|
2041
|
+
|
|
2042
|
+
|
|
2043
|
+
async def schedule_coldkey_swap(
|
|
2044
|
+
wallet: Wallet,
|
|
2045
|
+
meshtensor: MeshtensorInterface,
|
|
2046
|
+
new_coldkey_ss58: str,
|
|
2047
|
+
force_swap: bool = False,
|
|
2048
|
+
decline: bool = False,
|
|
2049
|
+
quiet: bool = False,
|
|
2050
|
+
proxy: Optional[str] = None,
|
|
2051
|
+
) -> bool:
|
|
2052
|
+
"""Schedules a coldkey swap operation to be executed at a future block.
|
|
2053
|
+
|
|
2054
|
+
Args:
|
|
2055
|
+
wallet (Wallet): The wallet initiating the coldkey swap
|
|
2056
|
+
meshtensor (MeshtensorInterface): Connection to the Meshtensor network
|
|
2057
|
+
new_coldkey_ss58 (str): SS58 address of the new coldkey
|
|
2058
|
+
force_swap (bool, optional): Whether to force the swap even if the new coldkey is already scheduled for a swap. Defaults to False.
|
|
2059
|
+
Returns:
|
|
2060
|
+
bool: True if the swap was scheduled successfully, False otherwise
|
|
2061
|
+
"""
|
|
2062
|
+
if not is_valid_ss58_address(new_coldkey_ss58):
|
|
2063
|
+
print_error(f"Invalid SS58 address format: {new_coldkey_ss58}")
|
|
2064
|
+
return False
|
|
2065
|
+
|
|
2066
|
+
scheduled_coldkey_swap = await meshtensor.get_scheduled_coldkey_swap()
|
|
2067
|
+
if wallet.coldkeypub.ss58_address in scheduled_coldkey_swap:
|
|
2068
|
+
print_error(
|
|
2069
|
+
f"Coldkey {wallet.coldkeypub.ss58_address} is already scheduled for a swap."
|
|
2070
|
+
)
|
|
2071
|
+
console.print("[dim]Use the force_swap (--force) flag to override this.[/dim]")
|
|
2072
|
+
if not force_swap:
|
|
2073
|
+
return False
|
|
2074
|
+
else:
|
|
2075
|
+
console.print(
|
|
2076
|
+
"[yellow]Continuing with the swap due to force_swap flag.[/yellow]\n"
|
|
2077
|
+
)
|
|
2078
|
+
|
|
2079
|
+
prompt_msg = (
|
|
2080
|
+
"You are [red]swapping[/red] your [blue]coldkey[/blue] to a new address.\n"
|
|
2081
|
+
f"Current ss58: [{COLORS.G.CK}]{wallet.coldkeypub.ss58_address}[/{COLORS.G.CK}]\n"
|
|
2082
|
+
f"New ss58: [{COLORS.G.CK}]{new_coldkey_ss58}[/{COLORS.G.CK}]\n"
|
|
2083
|
+
"Are you sure you want to continue?"
|
|
2084
|
+
)
|
|
2085
|
+
if not confirm_action(prompt_msg, decline=decline, quiet=quiet):
|
|
2086
|
+
return False
|
|
2087
|
+
|
|
2088
|
+
if not unlock_key(wallet).success:
|
|
2089
|
+
return False
|
|
2090
|
+
|
|
2091
|
+
block_pre_call, call = await asyncio.gather(
|
|
2092
|
+
meshtensor.substrate.get_block_number(),
|
|
2093
|
+
meshtensor.substrate.compose_call(
|
|
2094
|
+
call_module="MeshtensorModule",
|
|
2095
|
+
call_function="schedule_swap_coldkey",
|
|
2096
|
+
call_params={
|
|
2097
|
+
"new_coldkey": new_coldkey_ss58,
|
|
2098
|
+
},
|
|
2099
|
+
),
|
|
2100
|
+
)
|
|
2101
|
+
swap_info = None
|
|
2102
|
+
with console.status(":satellite: Scheduling coldkey swap on-chain..."):
|
|
2103
|
+
success, err_msg, ext_receipt = await meshtensor.sign_and_send_extrinsic(
|
|
2104
|
+
call,
|
|
2105
|
+
wallet,
|
|
2106
|
+
wait_for_inclusion=True,
|
|
2107
|
+
wait_for_finalization=True,
|
|
2108
|
+
proxy=proxy,
|
|
2109
|
+
)
|
|
2110
|
+
block_post_call = await meshtensor.substrate.get_block_number()
|
|
2111
|
+
|
|
2112
|
+
if not success:
|
|
2113
|
+
print_error(f"Failed to schedule coldkey swap: {err_msg}")
|
|
2114
|
+
return False
|
|
2115
|
+
|
|
2116
|
+
console.print(
|
|
2117
|
+
":white_heavy_check_mark: [green]Successfully scheduled coldkey swap"
|
|
2118
|
+
)
|
|
2119
|
+
await print_extrinsic_id(ext_receipt)
|
|
2120
|
+
for event in await ext_receipt.triggered_events:
|
|
2121
|
+
if (
|
|
2122
|
+
event.get("event", {}).get("module_id") == "MeshtensorModule"
|
|
2123
|
+
and event.get("event", {}).get("event_id") == "ColdkeySwapScheduled"
|
|
2124
|
+
):
|
|
2125
|
+
attributes = event["event"].get("attributes", {})
|
|
2126
|
+
old_coldkey = decode_account_id(attributes["old_coldkey"][0])
|
|
2127
|
+
|
|
2128
|
+
if old_coldkey == wallet.coldkeypub.ss58_address:
|
|
2129
|
+
swap_info = {
|
|
2130
|
+
"block_num": block_pre_call,
|
|
2131
|
+
"dest_coldkey": decode_account_id(attributes["new_coldkey"][0]),
|
|
2132
|
+
"execution_block": attributes["execution_block"],
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
if not swap_info:
|
|
2136
|
+
swap_info = await find_coldkey_swap_extrinsic(
|
|
2137
|
+
meshtensor=meshtensor,
|
|
2138
|
+
start_block=block_pre_call,
|
|
2139
|
+
end_block=block_post_call,
|
|
2140
|
+
wallet_ss58=wallet.coldkeypub.ss58_address,
|
|
2141
|
+
)
|
|
2142
|
+
|
|
2143
|
+
if not swap_info:
|
|
2144
|
+
console.print(
|
|
2145
|
+
"[yellow]Warning: Could not find the swap extrinsic in recent blocks"
|
|
2146
|
+
)
|
|
2147
|
+
return True
|
|
2148
|
+
|
|
2149
|
+
console.print(
|
|
2150
|
+
"\n[green]Coldkey swap details:[/green]"
|
|
2151
|
+
f"\nBlock number: {swap_info['block_num']}"
|
|
2152
|
+
f"\nOriginal address: [{COLORS.G.CK}]{wallet.coldkeypub.ss58_address}[/{COLORS.G.CK}]"
|
|
2153
|
+
f"\nDestination address: [{COLORS.G.CK}]{swap_info['dest_coldkey']}[/{COLORS.G.CK}]"
|
|
2154
|
+
f"\nThe swap will be completed at block: [green]{swap_info['execution_block']}[/green]"
|
|
2155
|
+
f"\n[dim]You can provide the block number to `meshcli wallet swap-check`[/dim]"
|
|
2156
|
+
)
|
|
2157
|
+
|
|
2158
|
+
|
|
2159
|
+
async def find_coldkey_swap_extrinsic(
|
|
2160
|
+
meshtensor: MeshtensorInterface,
|
|
2161
|
+
start_block: int,
|
|
2162
|
+
end_block: int,
|
|
2163
|
+
wallet_ss58: str,
|
|
2164
|
+
) -> dict:
|
|
2165
|
+
"""Search for a coldkey swap event in a range of blocks.
|
|
2166
|
+
|
|
2167
|
+
Args:
|
|
2168
|
+
meshtensor: MeshtensorInterface for chain queries
|
|
2169
|
+
start_block: Starting block number to search
|
|
2170
|
+
end_block: Ending block number to search (inclusive)
|
|
2171
|
+
wallet_ss58: SS58 address of the signing wallet
|
|
2172
|
+
|
|
2173
|
+
Returns:
|
|
2174
|
+
dict: Contains the following keys if found:
|
|
2175
|
+
- block_num: Block number where swap was scheduled
|
|
2176
|
+
- dest_coldkey: SS58 address of destination coldkey
|
|
2177
|
+
- execution_block: Block number when swap will execute
|
|
2178
|
+
Empty dict if not found
|
|
2179
|
+
"""
|
|
2180
|
+
|
|
2181
|
+
current_block, genesis_block = await asyncio.gather(
|
|
2182
|
+
meshtensor.substrate.get_block_number(), meshtensor.substrate.get_block_hash(0)
|
|
2183
|
+
)
|
|
2184
|
+
if (
|
|
2185
|
+
current_block - start_block > 300
|
|
2186
|
+
and genesis_block == Constants.genesis_block_hash_map["finney"]
|
|
2187
|
+
):
|
|
2188
|
+
console.print("Querying archive node for coldkey swap events...")
|
|
2189
|
+
await meshtensor.substrate.close()
|
|
2190
|
+
meshtensor.substrate.chain_endpoint = Constants.archive_entrypoint
|
|
2191
|
+
meshtensor.substrate.url = Constants.archive_entrypoint
|
|
2192
|
+
meshtensor.substrate.initialized = False
|
|
2193
|
+
await meshtensor.substrate.initialize()
|
|
2194
|
+
|
|
2195
|
+
block_hashes = await asyncio.gather(
|
|
2196
|
+
*[
|
|
2197
|
+
meshtensor.substrate.get_block_hash(block_num)
|
|
2198
|
+
for block_num in range(start_block, end_block + 1)
|
|
2199
|
+
]
|
|
2200
|
+
)
|
|
2201
|
+
block_events = await asyncio.gather(
|
|
2202
|
+
*[
|
|
2203
|
+
meshtensor.substrate.get_events(block_hash=block_hash)
|
|
2204
|
+
for block_hash in block_hashes
|
|
2205
|
+
]
|
|
2206
|
+
)
|
|
2207
|
+
|
|
2208
|
+
for block_num, events in zip(range(start_block, end_block + 1), block_events):
|
|
2209
|
+
for event in events:
|
|
2210
|
+
if (
|
|
2211
|
+
event.get("event", {}).get("module_id") == "MeshtensorModule"
|
|
2212
|
+
and event.get("event", {}).get("event_id") == "ColdkeySwapScheduled"
|
|
2213
|
+
):
|
|
2214
|
+
attributes = event["event"].get("attributes", {})
|
|
2215
|
+
old_coldkey = decode_account_id(attributes["old_coldkey"][0])
|
|
2216
|
+
|
|
2217
|
+
if old_coldkey == wallet_ss58:
|
|
2218
|
+
return {
|
|
2219
|
+
"block_num": block_num,
|
|
2220
|
+
"dest_coldkey": decode_account_id(attributes["new_coldkey"][0]),
|
|
2221
|
+
"execution_block": attributes["execution_block"],
|
|
2222
|
+
}
|
|
2223
|
+
|
|
2224
|
+
return {}
|
|
2225
|
+
|
|
2226
|
+
|
|
2227
|
+
async def check_swap_status(
|
|
2228
|
+
meshtensor: MeshtensorInterface,
|
|
2229
|
+
origin_ss58: Optional[str] = None,
|
|
2230
|
+
expected_block_number: Optional[int] = None,
|
|
2231
|
+
) -> None:
|
|
2232
|
+
"""
|
|
2233
|
+
Check the status of a coldkey swap.
|
|
2234
|
+
|
|
2235
|
+
Args:
|
|
2236
|
+
meshtensor: Connection to the network
|
|
2237
|
+
origin_ss58: The SS58 address of the original coldkey
|
|
2238
|
+
expected_block_number: Optional block number where the swap was scheduled
|
|
2239
|
+
|
|
2240
|
+
"""
|
|
2241
|
+
|
|
2242
|
+
if not origin_ss58:
|
|
2243
|
+
scheduled_swaps = await meshtensor.get_scheduled_coldkey_swap()
|
|
2244
|
+
if not scheduled_swaps:
|
|
2245
|
+
console.print("[yellow]No pending coldkey swaps found.[/yellow]")
|
|
2246
|
+
return
|
|
2247
|
+
|
|
2248
|
+
table = Table(
|
|
2249
|
+
Column(
|
|
2250
|
+
"Original Coldkey",
|
|
2251
|
+
justify="Left",
|
|
2252
|
+
style=COLOR_PALETTE["GENERAL"]["SUBHEADING_MAIN"],
|
|
2253
|
+
no_wrap=True,
|
|
2254
|
+
),
|
|
2255
|
+
Column("Status", style="dark_sea_green3"),
|
|
2256
|
+
title=f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Pending Coldkey Swaps\n",
|
|
2257
|
+
show_header=True,
|
|
2258
|
+
show_edge=False,
|
|
2259
|
+
header_style="bold white",
|
|
2260
|
+
border_style="bright_black",
|
|
2261
|
+
style="bold",
|
|
2262
|
+
title_justify="center",
|
|
2263
|
+
show_lines=False,
|
|
2264
|
+
pad_edge=True,
|
|
2265
|
+
)
|
|
2266
|
+
|
|
2267
|
+
for coldkey in scheduled_swaps:
|
|
2268
|
+
table.add_row(coldkey, "Pending")
|
|
2269
|
+
|
|
2270
|
+
console.print(table)
|
|
2271
|
+
console.print(
|
|
2272
|
+
"\n[dim]Tip: Check specific swap details by providing the original coldkey "
|
|
2273
|
+
"SS58 address and the block number.[/dim]"
|
|
2274
|
+
)
|
|
2275
|
+
return
|
|
2276
|
+
chain_reported_completion_block, destination_address = await meshtensor.query(
|
|
2277
|
+
"MeshtensorModule", "ColdkeySwapScheduled", [origin_ss58]
|
|
2278
|
+
)
|
|
2279
|
+
destination_address = decode_account_id(destination_address[0])
|
|
2280
|
+
if chain_reported_completion_block != 0 and destination_address != GENESIS_ADDRESS:
|
|
2281
|
+
is_pending = True
|
|
2282
|
+
else:
|
|
2283
|
+
is_pending = False
|
|
2284
|
+
|
|
2285
|
+
if not is_pending:
|
|
2286
|
+
console.print(
|
|
2287
|
+
f"[red]No pending swap found for coldkey:[/red] [{COLORS.G.CK}]{origin_ss58}[/{COLORS.G.CK}]"
|
|
2288
|
+
)
|
|
2289
|
+
return
|
|
2290
|
+
|
|
2291
|
+
console.print(
|
|
2292
|
+
f"[green]Found pending swap for coldkey:[/green] [{COLORS.G.CK}]{origin_ss58}[/{COLORS.G.CK}]"
|
|
2293
|
+
)
|
|
2294
|
+
|
|
2295
|
+
if expected_block_number is None:
|
|
2296
|
+
expected_block_number = chain_reported_completion_block
|
|
2297
|
+
|
|
2298
|
+
current_block = await meshtensor.substrate.get_block_number()
|
|
2299
|
+
remaining_blocks = expected_block_number - current_block
|
|
2300
|
+
|
|
2301
|
+
if remaining_blocks <= 0:
|
|
2302
|
+
console.print("[green]Swap period has completed![/green]")
|
|
2303
|
+
return
|
|
2304
|
+
|
|
2305
|
+
console.print(
|
|
2306
|
+
"\n[green]Coldkey swap details:[/green]"
|
|
2307
|
+
f"\nOriginal address: [{COLORS.G.CK}]{origin_ss58}[/{COLORS.G.CK}]"
|
|
2308
|
+
f"\nDestination address: [{COLORS.G.CK}]{destination_address}[/{COLORS.G.CK}]"
|
|
2309
|
+
f"\nCompletion block: {chain_reported_completion_block}"
|
|
2310
|
+
f"\nTime remaining: {blocks_to_duration(remaining_blocks)}"
|
|
2311
|
+
)
|