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.
Files changed (74) hide show
  1. meshtensor_cli/__init__.py +22 -0
  2. meshtensor_cli/cli.py +10742 -0
  3. meshtensor_cli/doc_generation_helper.py +4 -0
  4. meshtensor_cli/src/__init__.py +1085 -0
  5. meshtensor_cli/src/commands/__init__.py +0 -0
  6. meshtensor_cli/src/commands/axon/__init__.py +0 -0
  7. meshtensor_cli/src/commands/axon/axon.py +132 -0
  8. meshtensor_cli/src/commands/crowd/__init__.py +0 -0
  9. meshtensor_cli/src/commands/crowd/contribute.py +621 -0
  10. meshtensor_cli/src/commands/crowd/contributors.py +200 -0
  11. meshtensor_cli/src/commands/crowd/create.py +783 -0
  12. meshtensor_cli/src/commands/crowd/dissolve.py +219 -0
  13. meshtensor_cli/src/commands/crowd/refund.py +233 -0
  14. meshtensor_cli/src/commands/crowd/update.py +418 -0
  15. meshtensor_cli/src/commands/crowd/utils.py +124 -0
  16. meshtensor_cli/src/commands/crowd/view.py +991 -0
  17. meshtensor_cli/src/commands/governance/__init__.py +0 -0
  18. meshtensor_cli/src/commands/governance/governance.py +794 -0
  19. meshtensor_cli/src/commands/liquidity/__init__.py +0 -0
  20. meshtensor_cli/src/commands/liquidity/liquidity.py +699 -0
  21. meshtensor_cli/src/commands/liquidity/utils.py +202 -0
  22. meshtensor_cli/src/commands/proxy.py +700 -0
  23. meshtensor_cli/src/commands/stake/__init__.py +0 -0
  24. meshtensor_cli/src/commands/stake/add.py +799 -0
  25. meshtensor_cli/src/commands/stake/auto_staking.py +306 -0
  26. meshtensor_cli/src/commands/stake/children_hotkeys.py +865 -0
  27. meshtensor_cli/src/commands/stake/claim.py +770 -0
  28. meshtensor_cli/src/commands/stake/list.py +738 -0
  29. meshtensor_cli/src/commands/stake/move.py +1211 -0
  30. meshtensor_cli/src/commands/stake/remove.py +1466 -0
  31. meshtensor_cli/src/commands/stake/wizard.py +323 -0
  32. meshtensor_cli/src/commands/subnets/__init__.py +0 -0
  33. meshtensor_cli/src/commands/subnets/mechanisms.py +515 -0
  34. meshtensor_cli/src/commands/subnets/price.py +733 -0
  35. meshtensor_cli/src/commands/subnets/subnets.py +2908 -0
  36. meshtensor_cli/src/commands/sudo.py +1294 -0
  37. meshtensor_cli/src/commands/tc/__init__.py +0 -0
  38. meshtensor_cli/src/commands/tc/tc.py +190 -0
  39. meshtensor_cli/src/commands/treasury/__init__.py +0 -0
  40. meshtensor_cli/src/commands/treasury/treasury.py +194 -0
  41. meshtensor_cli/src/commands/view.py +354 -0
  42. meshtensor_cli/src/commands/wallets.py +2311 -0
  43. meshtensor_cli/src/commands/weights.py +467 -0
  44. meshtensor_cli/src/meshtensor/__init__.py +0 -0
  45. meshtensor_cli/src/meshtensor/balances.py +313 -0
  46. meshtensor_cli/src/meshtensor/chain_data.py +1263 -0
  47. meshtensor_cli/src/meshtensor/extrinsics/__init__.py +0 -0
  48. meshtensor_cli/src/meshtensor/extrinsics/mev_shield.py +174 -0
  49. meshtensor_cli/src/meshtensor/extrinsics/registration.py +1861 -0
  50. meshtensor_cli/src/meshtensor/extrinsics/root.py +550 -0
  51. meshtensor_cli/src/meshtensor/extrinsics/serving.py +255 -0
  52. meshtensor_cli/src/meshtensor/extrinsics/transfer.py +239 -0
  53. meshtensor_cli/src/meshtensor/meshtensor_interface.py +2598 -0
  54. meshtensor_cli/src/meshtensor/minigraph.py +254 -0
  55. meshtensor_cli/src/meshtensor/networking.py +12 -0
  56. meshtensor_cli/src/meshtensor/templates/main-filters.j2 +24 -0
  57. meshtensor_cli/src/meshtensor/templates/main-header.j2 +36 -0
  58. meshtensor_cli/src/meshtensor/templates/neuron-details.j2 +111 -0
  59. meshtensor_cli/src/meshtensor/templates/price-multi.j2 +113 -0
  60. meshtensor_cli/src/meshtensor/templates/price-single.j2 +99 -0
  61. meshtensor_cli/src/meshtensor/templates/subnet-details-header.j2 +49 -0
  62. meshtensor_cli/src/meshtensor/templates/subnet-details.j2 +32 -0
  63. meshtensor_cli/src/meshtensor/templates/subnet-metrics.j2 +57 -0
  64. meshtensor_cli/src/meshtensor/templates/subnets-table.j2 +28 -0
  65. meshtensor_cli/src/meshtensor/templates/table.j2 +267 -0
  66. meshtensor_cli/src/meshtensor/templates/view.css +1058 -0
  67. meshtensor_cli/src/meshtensor/templates/view.j2 +43 -0
  68. meshtensor_cli/src/meshtensor/templates/view.js +1053 -0
  69. meshtensor_cli/src/meshtensor/utils.py +2007 -0
  70. meshtensor_cli/version.py +23 -0
  71. meshtensor_cli-9.18.1.dist-info/METADATA +261 -0
  72. meshtensor_cli-9.18.1.dist-info/RECORD +74 -0
  73. meshtensor_cli-9.18.1.dist-info/WHEEL +4 -0
  74. 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
+ )