bittensor-cli 9.0.0rc2__py3-none-any.whl → 9.0.0rc4__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.
@@ -0,0 +1,687 @@
1
+ import asyncio
2
+
3
+ from typing import TYPE_CHECKING, Optional
4
+ import typer
5
+
6
+ from bittensor_wallet import Wallet
7
+ from rich.prompt import Prompt
8
+ from rich.table import Table
9
+ from rich import box
10
+ from rich.progress import Progress, BarColumn, TextColumn
11
+ from rich.console import Group
12
+ from rich.live import Live
13
+
14
+ from bittensor_cli.src import COLOR_PALETTE
15
+ from bittensor_cli.src.bittensor.balances import Balance
16
+ from bittensor_cli.src.bittensor.chain_data import StakeInfo
17
+ from bittensor_cli.src.bittensor.utils import (
18
+ console,
19
+ print_error,
20
+ millify_tao,
21
+ get_subnet_name,
22
+ )
23
+
24
+ if TYPE_CHECKING:
25
+ from bittensor_cli.src.bittensor.subtensor_interface import SubtensorInterface
26
+
27
+
28
+ async def stake_list(
29
+ wallet: Wallet,
30
+ coldkey_ss58: str,
31
+ subtensor: "SubtensorInterface",
32
+ live: bool = False,
33
+ verbose: bool = False,
34
+ prompt: bool = False,
35
+ ):
36
+ coldkey_address = coldkey_ss58 if coldkey_ss58 else wallet.coldkeypub.ss58_address
37
+
38
+ async def get_stake_data(block_hash: str = None):
39
+ (
40
+ sub_stakes,
41
+ registered_delegate_info,
42
+ _dynamic_info,
43
+ ) = await asyncio.gather(
44
+ subtensor.get_stake_for_coldkey(
45
+ coldkey_ss58=coldkey_address, block_hash=block_hash
46
+ ),
47
+ subtensor.get_delegate_identities(block_hash=block_hash),
48
+ subtensor.all_subnets(),
49
+ )
50
+ # sub_stakes = substakes[coldkey_address]
51
+ dynamic_info = {info.netuid: info for info in _dynamic_info}
52
+ return (
53
+ sub_stakes,
54
+ registered_delegate_info,
55
+ dynamic_info,
56
+ )
57
+
58
+ def define_table(
59
+ hotkey_name: str,
60
+ rows: list[list[str]],
61
+ total_tao_ownership: Balance,
62
+ total_tao_value: Balance,
63
+ total_swapped_tao_value: Balance,
64
+ live: bool = False,
65
+ ):
66
+ title = f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Hotkey: {hotkey_name}\nNetwork: {subtensor.network}\n\n"
67
+ # TODO: Add hint back in after adding columns descriptions
68
+ # if not live:
69
+ # title += f"[{COLOR_PALETTE['GENERAL']['HINT']}]See below for an explanation of the columns\n"
70
+ table = Table(
71
+ title=title,
72
+ show_footer=True,
73
+ show_edge=False,
74
+ header_style="bold white",
75
+ border_style="bright_black",
76
+ style="bold",
77
+ title_justify="center",
78
+ show_lines=False,
79
+ pad_edge=True,
80
+ )
81
+ table.add_column(
82
+ "[white]Netuid",
83
+ footer=f"{len(rows)}",
84
+ footer_style="overline white",
85
+ style="grey89",
86
+ )
87
+ table.add_column(
88
+ "[white]Name",
89
+ style="cyan",
90
+ justify="left",
91
+ no_wrap=True,
92
+ )
93
+ table.add_column(
94
+ f"[white]Value \n({Balance.get_unit(1)} x {Balance.unit}/{Balance.get_unit(1)})",
95
+ footer_style="overline white",
96
+ style=COLOR_PALETTE["STAKE"]["TAO"],
97
+ justify="right",
98
+ footer=f"τ {millify_tao(total_tao_value.tao)}"
99
+ if not verbose
100
+ else f"{total_tao_value}",
101
+ )
102
+ table.add_column(
103
+ f"[white]Stake ({Balance.get_unit(1)})",
104
+ footer_style="overline white",
105
+ style=COLOR_PALETTE["STAKE"]["STAKE_ALPHA"],
106
+ justify="center",
107
+ )
108
+ table.add_column(
109
+ f"[white]Price \n({Balance.unit}_in/{Balance.get_unit(1)}_in)",
110
+ footer_style="white",
111
+ style=COLOR_PALETTE["POOLS"]["RATE"],
112
+ justify="center",
113
+ )
114
+ table.add_column(
115
+ f"[white]Swap ({Balance.get_unit(1)} -> {Balance.unit})",
116
+ footer_style="overline white",
117
+ style=COLOR_PALETTE["STAKE"]["STAKE_SWAP"],
118
+ justify="right",
119
+ footer=f"τ {millify_tao(total_swapped_tao_value.tao)}"
120
+ if not verbose
121
+ else f"{total_swapped_tao_value}",
122
+ )
123
+ table.add_column(
124
+ "[white]Registered",
125
+ style=COLOR_PALETTE["STAKE"]["STAKE_ALPHA"],
126
+ justify="right",
127
+ )
128
+ table.add_column(
129
+ f"[white]Emission \n({Balance.get_unit(1)}/block)",
130
+ style=COLOR_PALETTE["POOLS"]["EMISSION"],
131
+ justify="right",
132
+ )
133
+ return table
134
+
135
+ def create_table(hotkey_: str, substakes: list[StakeInfo]):
136
+ name = (
137
+ f"{registered_delegate_info[hotkey_].display} ({hotkey_})"
138
+ if hotkey_ in registered_delegate_info
139
+ else hotkey_
140
+ )
141
+ rows = []
142
+ total_tao_ownership = Balance(0)
143
+ total_tao_value = Balance(0)
144
+ total_swapped_tao_value = Balance(0)
145
+ root_stakes = [s for s in substakes if s.netuid == 0]
146
+ other_stakes = sorted(
147
+ [s for s in substakes if s.netuid != 0],
148
+ key=lambda x: dynamic_info[x.netuid]
149
+ .alpha_to_tao(Balance.from_rao(int(x.stake.rao)).set_unit(x.netuid))
150
+ .tao,
151
+ reverse=True,
152
+ )
153
+ sorted_substakes = root_stakes + other_stakes
154
+ for substake_ in sorted_substakes:
155
+ netuid = substake_.netuid
156
+ pool = dynamic_info[netuid]
157
+ symbol = f"{Balance.get_unit(netuid)}\u200e"
158
+ # TODO: what is this price var for?
159
+ price = (
160
+ "{:.4f}{}".format(
161
+ pool.price.__float__(), f" τ/{Balance.get_unit(netuid)}\u200e"
162
+ )
163
+ if pool.is_dynamic
164
+ else (f" 1.0000 τ/{symbol} ")
165
+ )
166
+
167
+ # Alpha value cell
168
+ alpha_value = Balance.from_rao(int(substake_.stake.rao)).set_unit(netuid)
169
+
170
+ # TAO value cell
171
+ tao_value = pool.alpha_to_tao(alpha_value)
172
+ total_tao_value += tao_value
173
+
174
+ # Swapped TAO value and slippage cell
175
+ swapped_tao_value, _, slippage_percentage_ = (
176
+ pool.alpha_to_tao_with_slippage(substake_.stake)
177
+ )
178
+ total_swapped_tao_value += swapped_tao_value
179
+
180
+ # Slippage percentage cell
181
+ if pool.is_dynamic:
182
+ slippage_percentage = f"[{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}]{slippage_percentage_:.3f}%[/{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}]"
183
+ else:
184
+ slippage_percentage = f"[{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}]0.000%[/{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}]"
185
+
186
+ if netuid == 0:
187
+ swap_value = f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]N/A[/{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}] ({slippage_percentage})"
188
+ else:
189
+ swap_value = (
190
+ f"τ {millify_tao(swapped_tao_value.tao)} ({slippage_percentage})"
191
+ if not verbose
192
+ else f"{swapped_tao_value} ({slippage_percentage})"
193
+ )
194
+
195
+ # TAO locked cell
196
+ tao_locked = pool.tao_in
197
+
198
+ # Issuance cell
199
+ issuance = pool.alpha_out if pool.is_dynamic else tao_locked
200
+
201
+ # Per block emission cell
202
+ per_block_emission = substake_.emission.tao / pool.tempo
203
+ # Alpha ownership and TAO ownership cells
204
+ if alpha_value.tao > 0.00009:
205
+ if issuance.tao != 0:
206
+ # TODO figure out why this alpha_ownership does nothing
207
+ alpha_ownership = "{:.4f}".format(
208
+ (alpha_value.tao / issuance.tao) * 100
209
+ )
210
+ tao_ownership = Balance.from_tao(
211
+ (alpha_value.tao / issuance.tao) * tao_locked.tao
212
+ )
213
+ total_tao_ownership += tao_ownership
214
+ else:
215
+ # TODO what's this var for?
216
+ alpha_ownership = "0.0000"
217
+ tao_ownership = Balance.from_tao(0)
218
+
219
+ stake_value = (
220
+ millify_tao(substake_.stake.tao)
221
+ if not verbose
222
+ else f"{substake_.stake.tao:,.4f}"
223
+ )
224
+ subnet_name_cell = f"[{COLOR_PALETTE['GENERAL']['SYMBOL']}]{symbol if netuid != 0 else 'τ'}[/{COLOR_PALETTE['GENERAL']['SYMBOL']}] {get_subnet_name(dynamic_info[netuid])}"
225
+
226
+ rows.append(
227
+ [
228
+ str(netuid), # Number
229
+ subnet_name_cell, # Symbol + name
230
+ f"τ {millify_tao(tao_value.tao)}"
231
+ if not verbose
232
+ else f"{tao_value}", # Value (α x τ/α)
233
+ f"{stake_value} {symbol}"
234
+ if netuid != 0
235
+ else f"{symbol} {stake_value}", # Stake (a)
236
+ f"{pool.price.tao:.4f} τ/{symbol}", # Rate (t/a)
237
+ # f"τ {millify_tao(tao_ownership.tao)}" if not verbose else f"{tao_ownership}", # TAO equiv
238
+ swap_value, # Swap(α) -> τ
239
+ "YES"
240
+ if substake_.is_registered
241
+ else f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]NO", # Registered
242
+ str(Balance.from_tao(per_block_emission).set_unit(netuid)),
243
+ # Removing this flag for now, TODO: Confirm correct values are here w.r.t CHKs
244
+ # if substake_.is_registered
245
+ # else f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]N/A", # Emission(α/block)
246
+ ]
247
+ )
248
+ table = define_table(
249
+ name, rows, total_tao_ownership, total_tao_value, total_swapped_tao_value
250
+ )
251
+ for row in rows:
252
+ table.add_row(*row)
253
+ console.print(table)
254
+ return total_tao_ownership, total_tao_value
255
+
256
+ def create_live_table(
257
+ substakes: list,
258
+ registered_delegate_info: dict,
259
+ dynamic_info: dict,
260
+ hotkey_name: str,
261
+ previous_data: Optional[dict] = None,
262
+ ) -> tuple[Table, dict, Balance, Balance, Balance]:
263
+ rows = []
264
+ current_data = {}
265
+
266
+ total_tao_ownership = Balance(0)
267
+ total_tao_value = Balance(0)
268
+ total_swapped_tao_value = Balance(0)
269
+
270
+ def format_cell(
271
+ value, previous_value, unit="", unit_first=False, precision=4, millify=False
272
+ ):
273
+ if previous_value is not None:
274
+ change = value - previous_value
275
+ if abs(change) > 10 ** (-precision):
276
+ formatted_change = (
277
+ f"{change:.{precision}f}"
278
+ if not millify
279
+ else f"{millify_tao(change)}"
280
+ )
281
+ change_text = (
282
+ f" [pale_green3](+{formatted_change})[/pale_green3]"
283
+ if change > 0
284
+ else f" [hot_pink3]({formatted_change})[/hot_pink3]"
285
+ )
286
+ else:
287
+ change_text = ""
288
+ else:
289
+ change_text = ""
290
+ formatted_value = (
291
+ f"{value:,.{precision}f}" if not millify else f"{millify_tao(value)}"
292
+ )
293
+ return (
294
+ f"{formatted_value} {unit}{change_text}"
295
+ if not unit_first
296
+ else f"{unit} {formatted_value}{change_text}"
297
+ )
298
+
299
+ # Sort subnets by value
300
+ root_stakes = [s for s in substakes if s.netuid == 0]
301
+ other_stakes = sorted(
302
+ [s for s in substakes if s.netuid != 0],
303
+ key=lambda x: dynamic_info[x.netuid]
304
+ .alpha_to_tao(Balance.from_rao(int(x.stake.rao)).set_unit(x.netuid))
305
+ .tao,
306
+ reverse=True,
307
+ )
308
+ sorted_substakes = root_stakes + other_stakes
309
+
310
+ # Process each stake
311
+ for substake in sorted_substakes:
312
+ netuid = substake.netuid
313
+ pool = dynamic_info.get(netuid)
314
+ if substake.stake.rao == 0 or not pool:
315
+ continue
316
+
317
+ # Calculate base values
318
+ symbol = f"{Balance.get_unit(netuid)}\u200e"
319
+ alpha_value = Balance.from_rao(int(substake.stake.rao)).set_unit(netuid)
320
+ tao_value = pool.alpha_to_tao(alpha_value)
321
+ total_tao_value += tao_value
322
+ swapped_tao_value, slippage = pool.alpha_to_tao_with_slippage(
323
+ substake.stake
324
+ )
325
+ total_swapped_tao_value += swapped_tao_value
326
+
327
+ # Calculate TAO ownership
328
+ tao_locked = pool.tao_in
329
+ issuance = pool.alpha_out if pool.is_dynamic else tao_locked
330
+ if alpha_value.tao > 0.00009 and issuance.tao != 0:
331
+ tao_ownership = Balance.from_tao(
332
+ (alpha_value.tao / issuance.tao) * tao_locked.tao
333
+ )
334
+ total_tao_ownership += tao_ownership
335
+ else:
336
+ tao_ownership = Balance.from_tao(0)
337
+
338
+ # Store current values for future delta tracking
339
+ current_data[netuid] = {
340
+ "stake": alpha_value.tao,
341
+ "price": pool.price.tao,
342
+ "tao_value": tao_value.tao,
343
+ "swapped_value": swapped_tao_value.tao,
344
+ "emission": substake.emission.tao / pool.tempo,
345
+ "tao_ownership": tao_ownership.tao,
346
+ }
347
+
348
+ # Get previous values for delta tracking
349
+ prev = previous_data.get(netuid, {}) if previous_data else {}
350
+ unit_first = True if netuid == 0 else False
351
+
352
+ stake_cell = format_cell(
353
+ alpha_value.tao,
354
+ prev.get("stake"),
355
+ unit=symbol,
356
+ unit_first=unit_first,
357
+ precision=4,
358
+ millify=True if not verbose else False,
359
+ )
360
+
361
+ rate_cell = format_cell(
362
+ pool.price.tao,
363
+ prev.get("price"),
364
+ unit=f"τ/{symbol}",
365
+ unit_first=False,
366
+ precision=5,
367
+ millify=True if not verbose else False,
368
+ )
369
+
370
+ exchange_cell = format_cell(
371
+ tao_value.tao,
372
+ prev.get("tao_value"),
373
+ unit="τ",
374
+ unit_first=True,
375
+ precision=4,
376
+ millify=True if not verbose else False,
377
+ )
378
+
379
+ if pool.is_dynamic:
380
+ slippage_pct = (
381
+ 100 * float(slippage) / float(slippage + swapped_tao_value)
382
+ if slippage + swapped_tao_value != 0
383
+ else 0
384
+ )
385
+ else:
386
+ slippage_pct = 0
387
+
388
+ if netuid != 0:
389
+ swap_cell = (
390
+ format_cell(
391
+ swapped_tao_value.tao,
392
+ prev.get("swapped_value"),
393
+ unit="τ",
394
+ unit_first=True,
395
+ precision=4,
396
+ millify=True if not verbose else False,
397
+ )
398
+ + f" ({slippage_pct:.2f}%)"
399
+ )
400
+ else:
401
+ swap_cell = f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]N/A[/{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}] ({slippage_pct}%)"
402
+
403
+ emission_value = substake.emission.tao / pool.tempo
404
+ emission_cell = format_cell(
405
+ emission_value,
406
+ prev.get("emission"),
407
+ unit=symbol,
408
+ unit_first=unit_first,
409
+ precision=4,
410
+ )
411
+ subnet_name_cell = (
412
+ f"[{COLOR_PALETTE['GENERAL']['SYMBOL']}]{symbol if netuid != 0 else 'τ'}[/{COLOR_PALETTE['GENERAL']['SYMBOL']}]"
413
+ f" {get_subnet_name(dynamic_info[netuid])}"
414
+ )
415
+
416
+ rows.append(
417
+ [
418
+ str(netuid), # Netuid
419
+ subnet_name_cell,
420
+ exchange_cell, # Exchange value
421
+ stake_cell, # Stake amount
422
+ rate_cell, # Rate
423
+ swap_cell, # Swap value with slippage
424
+ "YES"
425
+ if substake.is_registered
426
+ else f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]NO", # Registration status
427
+ emission_cell, # Emission rate
428
+ ]
429
+ )
430
+
431
+ table = define_table(
432
+ hotkey_name,
433
+ rows,
434
+ total_tao_ownership,
435
+ total_tao_value,
436
+ total_swapped_tao_value,
437
+ live=True,
438
+ )
439
+
440
+ for row in rows:
441
+ table.add_row(*row)
442
+
443
+ return table, current_data
444
+
445
+ # Main execution
446
+ (
447
+ sub_stakes,
448
+ registered_delegate_info,
449
+ dynamic_info,
450
+ ) = await get_stake_data()
451
+ balance = await subtensor.get_balance(coldkey_address)
452
+
453
+ # Iterate over substakes and aggregate them by hotkey.
454
+ hotkeys_to_substakes: dict[str, list[StakeInfo]] = {}
455
+
456
+ for substake in sub_stakes:
457
+ hotkey = substake.hotkey_ss58
458
+ if substake.stake.rao == 0:
459
+ continue
460
+ if hotkey not in hotkeys_to_substakes:
461
+ hotkeys_to_substakes[hotkey] = []
462
+ hotkeys_to_substakes[hotkey].append(substake)
463
+
464
+ if not hotkeys_to_substakes:
465
+ print_error(f"No stakes found for coldkey ss58: ({coldkey_address})")
466
+ raise typer.Exit()
467
+
468
+ if live:
469
+ # Select one hokkey for live monitoring
470
+ if len(hotkeys_to_substakes) > 1:
471
+ console.print(
472
+ "\n[bold]Multiple hotkeys found. Please select one for live monitoring:[/bold]"
473
+ )
474
+ for idx, hotkey in enumerate(hotkeys_to_substakes.keys()):
475
+ name = (
476
+ f"{registered_delegate_info[hotkey].display} ({hotkey})"
477
+ if hotkey in registered_delegate_info
478
+ else hotkey
479
+ )
480
+ console.print(f"[{idx}] [{COLOR_PALETTE['GENERAL']['HEADER']}]{name}")
481
+
482
+ selected_idx = Prompt.ask(
483
+ "Enter hotkey index",
484
+ choices=[str(i) for i in range(len(hotkeys_to_substakes))],
485
+ )
486
+ selected_hotkey = list(hotkeys_to_substakes.keys())[int(selected_idx)]
487
+ selected_stakes = hotkeys_to_substakes[selected_hotkey]
488
+ else:
489
+ selected_hotkey = list(hotkeys_to_substakes.keys())[0]
490
+ selected_stakes = hotkeys_to_substakes[selected_hotkey]
491
+
492
+ hotkey_name = (
493
+ f"{registered_delegate_info[selected_hotkey].display} ({selected_hotkey})"
494
+ if selected_hotkey in registered_delegate_info
495
+ else selected_hotkey
496
+ )
497
+
498
+ refresh_interval = 10 # seconds
499
+ progress = Progress(
500
+ TextColumn("[progress.description]{task.description}"),
501
+ BarColumn(bar_width=20),
502
+ TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
503
+ console=console,
504
+ )
505
+ progress_task = progress.add_task("Updating: ", total=refresh_interval)
506
+
507
+ previous_block = None
508
+ current_block = None
509
+ previous_data = None
510
+
511
+ with Live(console=console, screen=True, auto_refresh=True) as live:
512
+ try:
513
+ while True:
514
+ block_hash = await subtensor.substrate.get_chain_head()
515
+ (
516
+ sub_stakes,
517
+ registered_delegate_info,
518
+ dynamic_info_,
519
+ ) = await get_stake_data(block_hash)
520
+ selected_stakes = [
521
+ stake
522
+ for stake in sub_stakes
523
+ if stake.hotkey_ss58 == selected_hotkey
524
+ ]
525
+
526
+ block_number = await subtensor.substrate.get_block_number(None)
527
+
528
+ previous_block = current_block
529
+ current_block = block_number
530
+ new_blocks = (
531
+ "N/A"
532
+ if previous_block is None
533
+ else str(current_block - previous_block)
534
+ )
535
+
536
+ table, current_data = create_live_table(
537
+ selected_stakes,
538
+ registered_delegate_info,
539
+ dynamic_info,
540
+ hotkey_name,
541
+ previous_data,
542
+ )
543
+
544
+ previous_data = current_data
545
+ progress.reset(progress_task)
546
+ start_time = asyncio.get_event_loop().time()
547
+
548
+ block_info = (
549
+ f"Previous: [dark_sea_green]{previous_block}[/dark_sea_green] "
550
+ f"Current: [dark_sea_green]{current_block}[/dark_sea_green] "
551
+ f"Diff: [dark_sea_green]{new_blocks}[/dark_sea_green]"
552
+ )
553
+
554
+ message = f"\nLive stake view - Press [bold red]Ctrl+C[/bold red] to exit\n{block_info}"
555
+ live_render = Group(message, progress, table)
556
+ live.update(live_render)
557
+
558
+ while not progress.finished:
559
+ await asyncio.sleep(0.1)
560
+ elapsed = asyncio.get_event_loop().time() - start_time
561
+ progress.update(
562
+ progress_task, completed=min(elapsed, refresh_interval)
563
+ )
564
+
565
+ except KeyboardInterrupt:
566
+ console.print("\n[bold]Stopped live updates[/bold]")
567
+ return
568
+
569
+ else:
570
+ # Iterate over each hotkey and make a table
571
+ counter = 0
572
+ num_hotkeys = len(hotkeys_to_substakes)
573
+ all_hotkeys_total_global_tao = Balance(0)
574
+ all_hotkeys_total_tao_value = Balance(0)
575
+ for hotkey in hotkeys_to_substakes.keys():
576
+ counter += 1
577
+ stake, value = create_table(hotkey, hotkeys_to_substakes[hotkey])
578
+ all_hotkeys_total_global_tao += stake
579
+ all_hotkeys_total_tao_value += value
580
+
581
+ if num_hotkeys > 1 and counter < num_hotkeys and prompt:
582
+ console.print("\nPress Enter to continue to the next hotkey...")
583
+ input()
584
+
585
+ total_tao_value = (
586
+ f"τ {millify_tao(all_hotkeys_total_tao_value.tao)}"
587
+ if not verbose
588
+ else all_hotkeys_total_tao_value
589
+ )
590
+ total_tao_ownership = (
591
+ f"τ {millify_tao(all_hotkeys_total_global_tao.tao)}"
592
+ if not verbose
593
+ else all_hotkeys_total_global_tao
594
+ )
595
+
596
+ console.print("\n\n")
597
+ console.print(
598
+ f"Wallet:\n"
599
+ f" Coldkey SS58: [{COLOR_PALETTE['GENERAL']['COLDKEY']}]{coldkey_address}[/{COLOR_PALETTE['GENERAL']['COLDKEY']}]\n"
600
+ f" Free Balance: [{COLOR_PALETTE['GENERAL']['BALANCE']}]{balance}[/{COLOR_PALETTE['GENERAL']['BALANCE']}]\n"
601
+ f" Total TAO ({Balance.unit}): [{COLOR_PALETTE['GENERAL']['BALANCE']}]{total_tao_ownership}[/{COLOR_PALETTE['GENERAL']['BALANCE']}]\n"
602
+ f" Total Value ({Balance.unit}): [{COLOR_PALETTE['GENERAL']['BALANCE']}]{total_tao_value}[/{COLOR_PALETTE['GENERAL']['BALANCE']}]"
603
+ )
604
+ if not sub_stakes:
605
+ console.print(
606
+ f"\n[blue]No stakes found for coldkey ss58: ({coldkey_address})"
607
+ )
608
+ else:
609
+ # TODO: Temporarily returning till we update docs
610
+ return
611
+ display_table = Prompt.ask(
612
+ "\nPress Enter to view column descriptions or type 'q' to skip:",
613
+ choices=["", "q"],
614
+ default="",
615
+ show_choices=True,
616
+ ).lower()
617
+
618
+ if display_table == "q":
619
+ console.print(
620
+ f"[{COLOR_PALETTE['GENERAL']['SUBHEADING_EXTRA_1']}]Column descriptions skipped."
621
+ )
622
+ else:
623
+ header = """
624
+ [bold white]Description[/bold white]: Each table displays information about stake associated with a hotkey. The columns are as follows:
625
+ """
626
+ console.print(header)
627
+ description_table = Table(
628
+ show_header=False, box=box.SIMPLE, show_edge=False, show_lines=True
629
+ )
630
+
631
+ fields = [
632
+ ("[bold tan]Netuid[/bold tan]", "The netuid of the subnet."),
633
+ (
634
+ "[bold tan]Symbol[/bold tan]",
635
+ "The symbol for the subnet's dynamic TAO token.",
636
+ ),
637
+ (
638
+ "[bold tan]Stake (α)[/bold tan]",
639
+ "The stake amount this hotkey holds in the subnet, expressed in subnet's alpha token currency. This can change whenever staking or unstaking occurs on this hotkey in this subnet. \nFor more, see [blue]https://docs.bittensor.com/dynamic-tao/dtao-guide#staking[/blue].",
640
+ ),
641
+ (
642
+ "[bold tan]TAO Reserves (τ_in)[/bold tan]",
643
+ 'Number of TAO in the TAO reserves of the pool for this subnet. Attached to every subnet is a subnet pool, containing a TAO reserve and the alpha reserve. See also "Alpha Pool (α_in)" description. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/dynamic-tao/dtao-guide#subnet-pool[/blue].',
644
+ ),
645
+ (
646
+ "[bold tan]Alpha Reserves (α_in)[/bold tan]",
647
+ "Number of subnet alpha tokens in the alpha reserves of the pool for this subnet. This reserve, together with 'TAO Pool (τ_in)', form the subnet pool for every subnet. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/dynamic-tao/dtao-guide#subnet-pool[/blue].",
648
+ ),
649
+ (
650
+ "[bold tan]RATE (τ_in/α_in)[/bold tan]",
651
+ "Exchange rate between TAO and subnet dTAO token. Calculated as the reserve ratio: (TAO Pool (τ_in) / Alpha Pool (α_in)). Note that the terms relative price, alpha token price, alpha price are the same as exchange rate. This rate can change every block. \nFor more, see [blue]https://docs.bittensor.com/dynamic-tao/dtao-guide#rate-%CF%84_in%CE%B1_in[/blue].",
652
+ ),
653
+ (
654
+ "[bold tan]Alpha out (α_out)[/bold tan]",
655
+ "Total stake in the subnet, expressed in subnet's alpha token currency. This is the sum of all the stakes present in all the hotkeys in this subnet. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/dynamic-tao/dtao-guide#stake-%CE%B1_out-or-alpha-out-%CE%B1_out",
656
+ ),
657
+ (
658
+ "[bold tan]TAO Equiv (τ_in x α/α_out)[/bold tan]",
659
+ 'TAO-equivalent value of the hotkeys stake α (i.e., Stake(α)). Calculated as (TAO Reserves(τ_in) x (Stake(α) / ALPHA Out(α_out)). This value is weighted with (1-γ), where γ is the local weight coefficient, and used in determining the overall stake weight of the hotkey in this subnet. Also see the "Local weight coeff (γ)" column of "btcli subnet list" command output. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/dynamic-tao/dtao-guide#local-weight-or-tao-equiv-%CF%84_in-x-%CE%B1%CE%B1_out[/blue].',
660
+ ),
661
+ (
662
+ "[bold tan]Exchange Value (α x τ/α)[/bold tan]",
663
+ "This is the potential τ you will receive, without considering slippage, if you unstake from this hotkey now on this subnet. See Swap(α → τ) column description. Note: The TAO Equiv(τ_in x α/α_out) indicates validator stake weight while this Exchange Value shows τ you will receive if you unstake now. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/dynamic-tao/dtao-guide#exchange-value-%CE%B1-x-%CF%84%CE%B1[/blue].",
664
+ ),
665
+ (
666
+ "[bold tan]Swap (α → τ)[/bold tan]",
667
+ "This is the actual τ you will receive, after factoring in the slippage charge, if you unstake from this hotkey now on this subnet. The slippage is calculated as 1 - (Swap(α → τ)/Exchange Value(α x τ/α)), and is displayed in brackets. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/dynamic-tao/dtao-guide#swap-%CE%B1--%CF%84[/blue].",
668
+ ),
669
+ (
670
+ "[bold tan]Registered[/bold tan]",
671
+ "Indicates if the hotkey is registered in this subnet or not. \nFor more, see [blue]https://docs.bittensor.com/learn/anatomy-of-incentive-mechanism#tempo[/blue].",
672
+ ),
673
+ (
674
+ "[bold tan]Emission (α/block)[/bold tan]",
675
+ "Shows the portion of the one α/block emission into this subnet that is received by this hotkey, according to YC2 in this subnet. This can change every block. \nFor more, see [blue]https://docs.bittensor.com/dynamic-tao/dtao-guide#emissions[/blue].",
676
+ ),
677
+ ]
678
+
679
+ description_table.add_column(
680
+ "Field",
681
+ no_wrap=True,
682
+ style="bold tan",
683
+ )
684
+ description_table.add_column("Description", overflow="fold")
685
+ for field_name, description in fields:
686
+ description_table.add_row(field_name, description)
687
+ console.print(description_table)