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.
@@ -1,1821 +0,0 @@
1
- import asyncio
2
- from functools import partial
3
-
4
- from typing import TYPE_CHECKING, Optional
5
- import typer
6
-
7
- from bittensor_wallet import Wallet
8
- from bittensor_wallet.errors import KeyFileError
9
- from rich.prompt import Confirm, FloatPrompt, Prompt
10
- from rich.table import Table
11
- from rich import box
12
- from rich.progress import Progress, BarColumn, TextColumn
13
- from rich.console import Group
14
- from rich.live import Live
15
- from async_substrate_interface.errors import SubstrateRequestException
16
-
17
- from bittensor_cli.src import COLOR_PALETTE
18
- from bittensor_cli.src.bittensor.balances import Balance
19
- from bittensor_cli.src.bittensor.chain_data import StakeInfo
20
- from bittensor_cli.src.bittensor.utils import (
21
- # TODO add back in caching
22
- console,
23
- err_console,
24
- print_verbose,
25
- print_error,
26
- get_hotkey_wallets_for_wallet,
27
- is_valid_ss58_address,
28
- format_error_message,
29
- group_subnets,
30
- millify_tao,
31
- get_subnet_name,
32
- )
33
-
34
- if TYPE_CHECKING:
35
- from bittensor_cli.src.bittensor.subtensor_interface import SubtensorInterface
36
-
37
-
38
- async def stake_add(
39
- wallet: Wallet,
40
- subtensor: "SubtensorInterface",
41
- netuid: Optional[int],
42
- stake_all: bool,
43
- amount: float,
44
- delegate: bool,
45
- prompt: bool,
46
- max_stake: float,
47
- all_hotkeys: bool,
48
- include_hotkeys: list[str],
49
- exclude_hotkeys: list[str],
50
- ):
51
- """
52
-
53
- Args:
54
- wallet: wallet object
55
- subtensor: SubtensorInterface object
56
- netuid: the netuid to stake to (None indicates all subnets)
57
- stake_all: whether to stake all available balance
58
- amount: specified amount of balance to stake
59
- delegate: whether to delegate stake, currently unused
60
- prompt: whether to prompt the user
61
- max_stake: maximum amount to stake (used in combination with stake_all), currently unused
62
- all_hotkeys: whether to stake all hotkeys
63
- include_hotkeys: list of hotkeys to include in staking process (if not specifying `--all`)
64
- exclude_hotkeys: list of hotkeys to exclude in staking (if specifying `--all`)
65
-
66
- Returns:
67
-
68
- """
69
- netuids = (
70
- [int(netuid)]
71
- if netuid is not None
72
- else await subtensor.get_all_subnet_netuids()
73
- )
74
- # Init the table.
75
- table = Table(
76
- title=f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Staking to: \nWallet: [{COLOR_PALETTE['GENERAL']['COLDKEY']}]{wallet.name}[/{COLOR_PALETTE['GENERAL']['COLDKEY']}], Coldkey ss58: [{COLOR_PALETTE['GENERAL']['COLDKEY']}]{wallet.coldkeypub.ss58_address}[/{COLOR_PALETTE['GENERAL']['COLDKEY']}]\nNetwork: {subtensor.network}[/{COLOR_PALETTE['GENERAL']['HEADER']}]\n",
77
- show_footer=True,
78
- show_edge=False,
79
- header_style="bold white",
80
- border_style="bright_black",
81
- style="bold",
82
- title_justify="center",
83
- show_lines=False,
84
- pad_edge=True,
85
- )
86
-
87
- # Determine the amount we are staking.
88
- rows = []
89
- stake_amount_balance = []
90
- current_stake_balances = []
91
- current_wallet_balance_ = await subtensor.get_balance(
92
- wallet.coldkeypub.ss58_address
93
- )
94
- current_wallet_balance = current_wallet_balance_.set_unit(0)
95
- remaining_wallet_balance = current_wallet_balance
96
- max_slippage = 0.0
97
-
98
- hotkeys_to_stake_to: list[tuple[Optional[str], str]] = []
99
- if all_hotkeys:
100
- # Stake to all hotkeys.
101
- all_hotkeys_: list[Wallet] = get_hotkey_wallets_for_wallet(wallet=wallet)
102
- # Get the hotkeys to exclude. (d)efault to no exclusions.
103
- # Exclude hotkeys that are specified.
104
- hotkeys_to_stake_to = [
105
- (wallet.hotkey_str, wallet.hotkey.ss58_address)
106
- for wallet in all_hotkeys_
107
- if wallet.hotkey_str not in exclude_hotkeys
108
- ] # definitely wallets
109
-
110
- elif include_hotkeys:
111
- print_verbose("Staking to only included hotkeys")
112
- # Stake to specific hotkeys.
113
- for hotkey_ss58_or_hotkey_name in include_hotkeys:
114
- if is_valid_ss58_address(hotkey_ss58_or_hotkey_name):
115
- # If the hotkey is a valid ss58 address, we add it to the list.
116
- hotkeys_to_stake_to.append((None, hotkey_ss58_or_hotkey_name))
117
- else:
118
- # If the hotkey is not a valid ss58 address, we assume it is a hotkey name.
119
- # We then get the hotkey from the wallet and add it to the list.
120
- wallet_ = Wallet(
121
- path=wallet.path,
122
- name=wallet.name,
123
- hotkey=hotkey_ss58_or_hotkey_name,
124
- )
125
- hotkeys_to_stake_to.append(
126
- (wallet_.hotkey_str, wallet_.hotkey.ss58_address)
127
- )
128
- else:
129
- # Only config.wallet.hotkey is specified.
130
- # so we stake to that single hotkey.
131
- print_verbose(
132
- f"Staking to hotkey: ({wallet.hotkey_str}) in wallet: ({wallet.name})"
133
- )
134
- assert wallet.hotkey is not None
135
- hotkey_ss58_or_name = wallet.hotkey.ss58_address
136
- hotkeys_to_stake_to = [(None, hotkey_ss58_or_name)]
137
-
138
- starting_chain_head = await subtensor.substrate.get_chain_head()
139
- _all_dynamic_info, stake_info_dict = await asyncio.gather(
140
- subtensor.all_subnets(),
141
- subtensor.get_stake_for_coldkey(
142
- coldkey_ss58=wallet.coldkeypub.ss58_address,
143
- block_hash=starting_chain_head,
144
- ),
145
- )
146
- all_dynamic_info = {di.netuid: di for di in _all_dynamic_info}
147
- initial_stake_balances = {}
148
- for hotkey_ss58 in [x[1] for x in hotkeys_to_stake_to]:
149
- initial_stake_balances[hotkey_ss58] = {}
150
- for netuid in netuids:
151
- initial_stake_balances[hotkey_ss58][netuid] = Balance.from_rao(0)
152
-
153
- for stake_info in stake_info_dict:
154
- if stake_info.hotkey_ss58 in initial_stake_balances:
155
- initial_stake_balances[stake_info.hotkey_ss58][stake_info.netuid] = (
156
- stake_info.stake
157
- )
158
-
159
- for hk_name, hk_ss58 in hotkeys_to_stake_to:
160
- if not is_valid_ss58_address(hk_ss58):
161
- print_error(
162
- f"The entered hotkey ss58 address is incorrect: {hk_name} | {hk_ss58}"
163
- )
164
- return False
165
- for hotkey in hotkeys_to_stake_to:
166
- for netuid in netuids:
167
- # Check that the subnet exists.
168
- dynamic_info = all_dynamic_info.get(netuid)
169
- if not dynamic_info:
170
- err_console.print(f"Subnet with netuid: {netuid} does not exist.")
171
- continue
172
- current_stake_balances.append(initial_stake_balances[hotkey[1]][netuid])
173
-
174
- # Get the amount.
175
- amount_to_stake_as_balance = Balance(0)
176
- if amount:
177
- amount_to_stake_as_balance = Balance.from_tao(amount)
178
- elif stake_all:
179
- amount_to_stake_as_balance = current_wallet_balance / len(netuids)
180
- elif not amount and not max_stake:
181
- if Confirm.ask(f"Stake all: [bold]{remaining_wallet_balance}[/bold]?"):
182
- amount_to_stake_as_balance = remaining_wallet_balance
183
- else:
184
- try:
185
- amount = FloatPrompt.ask(
186
- f"Enter amount to stake in {Balance.get_unit(0)} to subnet: {netuid}"
187
- )
188
- amount_to_stake_as_balance = Balance.from_tao(amount)
189
- except ValueError:
190
- err_console.print(
191
- f":cross_mark:[red]Invalid amount: {amount}[/red]"
192
- )
193
- return False
194
- stake_amount_balance.append(amount_to_stake_as_balance)
195
-
196
- # Check enough to stake.
197
- amount_to_stake_as_balance.set_unit(0)
198
- if amount_to_stake_as_balance > remaining_wallet_balance:
199
- err_console.print(
200
- f"[red]Not enough stake[/red]:[bold white]\n wallet balance:{remaining_wallet_balance} < "
201
- f"staking amount: {amount_to_stake_as_balance}[/bold white]"
202
- )
203
- return False
204
- remaining_wallet_balance -= amount_to_stake_as_balance
205
-
206
- # Slippage warning
207
- received_amount, _, slippage_pct_float = (
208
- dynamic_info.tao_to_alpha_with_slippage(amount_to_stake_as_balance)
209
- )
210
- if dynamic_info.is_dynamic:
211
- slippage_pct = f"{slippage_pct_float:.4f} %"
212
- rate = str(1 / (float(dynamic_info.price) or 1))
213
- else:
214
- slippage_pct_float = 0
215
- slippage_pct = f"[{COLOR_PALETTE['STAKE']['SLIPPAGE_TEXT']}]N/A[/{COLOR_PALETTE['STAKE']['SLIPPAGE_TEXT']}]"
216
- rate = str(1)
217
- max_slippage = max(slippage_pct_float, max_slippage)
218
- rows.append(
219
- (
220
- str(netuid),
221
- # f"{staking_address_ss58[:3]}...{staking_address_ss58[-3:]}",
222
- f"{hotkey[1]}",
223
- str(amount_to_stake_as_balance),
224
- rate + f" {Balance.get_unit(netuid)}/{Balance.get_unit(0)} ",
225
- str(received_amount.set_unit(netuid)),
226
- str(slippage_pct),
227
- )
228
- )
229
- table.add_column("Netuid", justify="center", style="grey89")
230
- table.add_column(
231
- "Hotkey", justify="center", style=COLOR_PALETTE["GENERAL"]["HOTKEY"]
232
- )
233
- table.add_column(
234
- f"Amount ({Balance.get_unit(0)})",
235
- justify="center",
236
- style=COLOR_PALETTE["POOLS"]["TAO"],
237
- )
238
- table.add_column(
239
- f"Rate (per {Balance.get_unit(0)})",
240
- justify="center",
241
- style=COLOR_PALETTE["POOLS"]["RATE"],
242
- )
243
- table.add_column(
244
- "Received",
245
- justify="center",
246
- style=COLOR_PALETTE["POOLS"]["TAO_EQUIV"],
247
- )
248
- table.add_column(
249
- "Slippage", justify="center", style=COLOR_PALETTE["STAKE"]["SLIPPAGE_PERCENT"]
250
- )
251
- for row in rows:
252
- table.add_row(*row)
253
- console.print(table)
254
- message = ""
255
- if max_slippage > 5:
256
- message += f"[{COLOR_PALETTE['STAKE']['SLIPPAGE_TEXT']}]-------------------------------------------------------------------------------------------------------------------\n"
257
- message += f"[bold]WARNING:[/bold] The slippage on one of your operations is high: [{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}]{max_slippage} %[/{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}], this may result in a loss of funds.\n"
258
- message += "-------------------------------------------------------------------------------------------------------------------\n"
259
- console.print(message)
260
- console.print(
261
- """
262
- [bold white]Description[/bold white]:
263
- The table displays information about the stake operation you are about to perform.
264
- The columns are as follows:
265
- - [bold white]Netuid[/bold white]: The netuid of the subnet you are staking to.
266
- - [bold white]Hotkey[/bold white]: The ss58 address of the hotkey you are staking to.
267
- - [bold white]Amount[/bold white]: The TAO you are staking into this subnet onto this hotkey.
268
- - [bold white]Rate[/bold white]: The rate of exchange between your TAO and the subnet's stake.
269
- - [bold white]Received[/bold white]: The amount of stake you will receive on this subnet after slippage.
270
- - [bold white]Slippage[/bold white]: The slippage percentage of the stake operation. (0% if the subnet is not dynamic i.e. root).
271
- """
272
- )
273
- if prompt:
274
- if not Confirm.ask("Would you like to continue?"):
275
- raise typer.Exit()
276
-
277
- async def send_extrinsic(
278
- netuid_i, amount_, current, staking_address_ss58, status=None
279
- ):
280
- err_out = partial(print_error, status=status)
281
- failure_prelude = (
282
- f":cross_mark: [red]Failed[/red] to stake {amount} on Netuid {netuid_i}"
283
- )
284
- call = await subtensor.substrate.compose_call(
285
- call_module="SubtensorModule",
286
- call_function="add_stake",
287
- call_params={
288
- "hotkey": staking_address_ss58,
289
- "netuid": netuid_i,
290
- "amount_staked": amount_.rao,
291
- },
292
- )
293
- extrinsic = await subtensor.substrate.create_signed_extrinsic(
294
- call=call, keypair=wallet.coldkey
295
- )
296
- try:
297
- response = await subtensor.substrate.submit_extrinsic(
298
- extrinsic, wait_for_inclusion=True, wait_for_finalization=False
299
- )
300
- except SubstrateRequestException as e:
301
- err_out(
302
- f"\n{failure_prelude} with error: {format_error_message(e, subtensor.substrate)}"
303
- )
304
- return
305
- else:
306
- await response.process_events()
307
- if not await response.is_success:
308
- err_out(
309
- f"\n{failure_prelude} with error: {format_error_message(await response.error_message, subtensor.substrate)}"
310
- )
311
- else:
312
- new_balance, stake_info_dict = await asyncio.gather(
313
- subtensor.get_balance(wallet.coldkeypub.ss58_address),
314
- subtensor.get_stake_for_coldkey(
315
- coldkey_ss58=wallet.coldkeypub.ss58_address,
316
- ),
317
- )
318
- new_stake = Balance.from_rao(0)
319
- for stake_info in stake_info_dict:
320
- if (
321
- stake_info.hotkey_ss58 == staking_address_ss58
322
- and stake_info.netuid == netuid_i
323
- ):
324
- new_stake = stake_info.stake.set_unit(netuid_i)
325
- break
326
- console.print(":white_heavy_check_mark: [green]Finalized[/green]")
327
- console.print(
328
- f"Balance:\n [blue]{current_wallet_balance}[/blue] :arrow_right: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_balance}"
329
- )
330
- console.print(
331
- f"Subnet: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{netuid_i}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] Stake:\n [blue]{current}[/blue] :arrow_right: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_stake}"
332
- )
333
-
334
- # Perform staking operation.
335
- try:
336
- wallet.unlock_coldkey()
337
- except KeyFileError:
338
- err_console.print("Error decrypting coldkey (possibly incorrect password)")
339
- return False
340
- extrinsics_coroutines = [
341
- send_extrinsic(ni, am, curr, staking_address)
342
- for i, (ni, am, curr) in enumerate(
343
- zip(netuids, stake_amount_balance, current_stake_balances)
344
- )
345
- for _, staking_address in hotkeys_to_stake_to
346
- ]
347
- if len(extrinsics_coroutines) == 1:
348
- with console.status(f"\n:satellite: Staking on netuid(s): {netuids} ..."):
349
- await extrinsics_coroutines[0]
350
- else:
351
- with console.status(":satellite: Checking transaction rate limit ..."):
352
- tx_rate_limit_blocks = await subtensor.query(
353
- module="SubtensorModule", storage_function="TxRateLimit"
354
- )
355
- netuid_hk_pairs = [(ni, hk) for ni in netuids for hk in hotkeys_to_stake_to]
356
- for item, kp in zip(extrinsics_coroutines, netuid_hk_pairs):
357
- ni, hk = kp
358
- with console.status(
359
- f"\n:satellite: Staking on netuid {ni} with hotkey {hk}... ..."
360
- ):
361
- await item
362
- if tx_rate_limit_blocks > 0:
363
- with console.status(
364
- f":hourglass: [yellow]Waiting for tx rate limit:"
365
- f" [white]{tx_rate_limit_blocks}[/white] blocks[/yellow]"
366
- ):
367
- await asyncio.sleep(tx_rate_limit_blocks * 12) # 12 sec per block
368
-
369
-
370
- async def unstake_selection(
371
- subtensor: "SubtensorInterface",
372
- wallet: Wallet,
373
- dynamic_info,
374
- identities,
375
- old_identities,
376
- netuid: Optional[int] = None,
377
- ):
378
- stake_infos = await subtensor.get_stake_for_coldkey(
379
- coldkey_ss58=wallet.coldkeypub.ss58_address
380
- )
381
-
382
- if not stake_infos:
383
- print_error("You have no stakes to unstake.")
384
- raise typer.Exit()
385
-
386
- hotkey_stakes = {}
387
- for stake_info in stake_infos:
388
- if netuid is not None and stake_info.netuid != netuid:
389
- continue
390
- hotkey_ss58 = stake_info.hotkey_ss58
391
- netuid_ = stake_info.netuid
392
- stake_amount = stake_info.stake
393
- if stake_amount.tao > 0:
394
- hotkey_stakes.setdefault(hotkey_ss58, {})[netuid_] = stake_amount
395
-
396
- if not hotkey_stakes:
397
- if netuid is not None:
398
- print_error(f"You have no stakes to unstake in subnet {netuid}.")
399
- else:
400
- print_error("You have no stakes to unstake.")
401
- raise typer.Exit()
402
-
403
- hotkeys_info = []
404
- for idx, (hotkey_ss58, netuid_stakes) in enumerate(hotkey_stakes.items()):
405
- if hk_identity := identities["hotkeys"].get(hotkey_ss58):
406
- hotkey_name = hk_identity.get("identity", {}).get(
407
- "name", ""
408
- ) or hk_identity.get("display", "~")
409
- elif old_identity := old_identities.get(hotkey_ss58):
410
- hotkey_name = old_identity.display
411
- else:
412
- hotkey_name = "~"
413
- # TODO: Add wallet ids here.
414
-
415
- hotkeys_info.append(
416
- {
417
- "index": idx,
418
- "identity": hotkey_name,
419
- "netuids": list(netuid_stakes.keys()),
420
- "hotkey_ss58": hotkey_ss58,
421
- }
422
- )
423
-
424
- # Display existing hotkeys, id, and staked netuids.
425
- subnet_filter = f" for Subnet {netuid}" if netuid is not None else ""
426
- table = Table(
427
- title=f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Hotkeys with Stakes{subnet_filter}\n",
428
- show_footer=True,
429
- show_edge=False,
430
- header_style="bold white",
431
- border_style="bright_black",
432
- style="bold",
433
- title_justify="center",
434
- show_lines=False,
435
- pad_edge=True,
436
- )
437
- table.add_column("Index", justify="right")
438
- table.add_column("Identity", style=COLOR_PALETTE["GENERAL"]["SUBHEADING"])
439
- table.add_column("Netuids", style=COLOR_PALETTE["GENERAL"]["NETUID"])
440
- table.add_column("Hotkey Address", style=COLOR_PALETTE["GENERAL"]["HOTKEY"])
441
-
442
- for hotkey_info in hotkeys_info:
443
- index = str(hotkey_info["index"])
444
- identity = hotkey_info["identity"]
445
- netuids = group_subnets([n for n in hotkey_info["netuids"]])
446
- hotkey_ss58 = hotkey_info["hotkey_ss58"]
447
- table.add_row(index, identity, netuids, hotkey_ss58)
448
-
449
- console.print("\n", table)
450
-
451
- # Prompt to select hotkey to unstake.
452
- hotkey_options = [str(hotkey_info["index"]) for hotkey_info in hotkeys_info]
453
- hotkey_idx = Prompt.ask(
454
- "\nEnter the index of the hotkey you want to unstake from",
455
- choices=hotkey_options,
456
- )
457
- selected_hotkey_info = hotkeys_info[int(hotkey_idx)]
458
- selected_hotkey_ss58 = selected_hotkey_info["hotkey_ss58"]
459
- selected_hotkey_name = selected_hotkey_info["identity"]
460
- netuid_stakes = hotkey_stakes[selected_hotkey_ss58]
461
-
462
- # Display hotkey's staked netuids with amount.
463
- table = Table(
464
- title=f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Stakes for hotkey \n[{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{selected_hotkey_name}\n{selected_hotkey_ss58}\n",
465
- show_footer=True,
466
- show_edge=False,
467
- header_style="bold white",
468
- border_style="bright_black",
469
- style="bold",
470
- title_justify="center",
471
- show_lines=False,
472
- pad_edge=True,
473
- )
474
- table.add_column("Subnet", justify="right")
475
- table.add_column("Symbol", style=COLOR_PALETTE["GENERAL"]["SYMBOL"])
476
- table.add_column("Stake Amount", style=COLOR_PALETTE["STAKE"]["STAKE_AMOUNT"])
477
- table.add_column(
478
- f"[bold white]RATE ({Balance.get_unit(0)}_in/{Balance.get_unit(1)}_in)",
479
- style=COLOR_PALETTE["POOLS"]["RATE"],
480
- justify="left",
481
- )
482
-
483
- for netuid_, stake_amount in netuid_stakes.items():
484
- symbol = dynamic_info[netuid_].symbol
485
- rate = f"{dynamic_info[netuid_].price.tao:.4f} τ/{symbol}"
486
- table.add_row(str(netuid_), symbol, str(stake_amount), rate)
487
- console.print("\n", table, "\n")
488
-
489
- # Ask which netuids to unstake from for the selected hotkey.
490
- unstake_all = False
491
- if netuid is not None:
492
- selected_netuids = [netuid]
493
- else:
494
- while True:
495
- netuid_input = Prompt.ask(
496
- "\nEnter the netuids of the [blue]subnets to unstake[/blue] from (comma-separated), or '[blue]all[/blue]' to unstake from all",
497
- default="all",
498
- )
499
-
500
- if netuid_input.lower() == "all":
501
- selected_netuids = list(netuid_stakes.keys())
502
- unstake_all = True
503
- break
504
- else:
505
- try:
506
- netuid_list = [int(n.strip()) for n in netuid_input.split(",")]
507
- invalid_netuids = [n for n in netuid_list if n not in netuid_stakes]
508
- if invalid_netuids:
509
- print_error(
510
- f"The following netuids are invalid or not available: {', '.join(map(str, invalid_netuids))}. Please try again."
511
- )
512
- else:
513
- selected_netuids = netuid_list
514
- break
515
- except ValueError:
516
- print_error(
517
- "Please enter valid netuids (numbers), separated by commas, or 'all'."
518
- )
519
-
520
- hotkeys_to_unstake_from = []
521
- for netuid_ in selected_netuids:
522
- hotkeys_to_unstake_from.append(
523
- (selected_hotkey_name, selected_hotkey_ss58, netuid_)
524
- )
525
- return hotkeys_to_unstake_from, unstake_all
526
-
527
-
528
- def ask_unstake_amount(
529
- current_stake_balance: Balance,
530
- netuid: int,
531
- staking_address_name: str,
532
- staking_address_ss58: str,
533
- interactive: bool,
534
- ) -> Optional[Balance]:
535
- """Prompt the user to decide the amount to unstake."""
536
- while True:
537
- response = Prompt.ask(
538
- f"Unstake all: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{current_stake_balance}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]"
539
- f" from [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{staking_address_name if staking_address_name else staking_address_ss58}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]"
540
- f" on netuid: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{netuid}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]? [y/n/q]",
541
- choices=["y", "n", "q"],
542
- default="n" if interactive else "y",
543
- show_choices=True,
544
- ).lower()
545
-
546
- if response == "q":
547
- return None # Quit
548
-
549
- elif response == "y":
550
- return current_stake_balance
551
-
552
- elif response == "n":
553
- while True:
554
- amount_input = Prompt.ask(
555
- f"Enter amount to unstake in [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{Balance.get_unit(netuid)}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]"
556
- f" from subnet: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{netuid}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]"
557
- f" (Max: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{current_stake_balance}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}])"
558
- )
559
- if amount_input.lower() == "q":
560
- return None # Quit
561
-
562
- try:
563
- amount_value = float(amount_input)
564
- if amount_value <= 0:
565
- console.print("[red]Amount must be greater than zero.[/red]")
566
- continue # Re-prompt
567
-
568
- amount_to_unstake = Balance.from_tao(amount_value)
569
- amount_to_unstake.set_unit(netuid)
570
- if amount_to_unstake > current_stake_balance:
571
- console.print(
572
- f"[red]Amount exceeds current stake balance of {current_stake_balance}.[/red]"
573
- )
574
- continue # Re-prompt
575
-
576
- return amount_to_unstake
577
-
578
- except ValueError:
579
- console.print(
580
- "[red]Invalid input. Please enter a numeric value or 'q' to quit.[/red]"
581
- )
582
-
583
- else:
584
- console.print("[red]Invalid input. Please enter 'y', 'n', or 'q'.[/red]")
585
-
586
-
587
- async def _unstake_all(
588
- wallet: Wallet,
589
- subtensor: "SubtensorInterface",
590
- unstake_all_alpha: bool = False,
591
- prompt: bool = True,
592
- ) -> bool:
593
- """Unstakes all stakes from all hotkeys in all subnets."""
594
-
595
- with console.status(
596
- f"Retrieving stake information & identities from {subtensor.network}...",
597
- spinner="earth",
598
- ):
599
- (
600
- stake_info,
601
- ck_hk_identities,
602
- old_identities,
603
- all_sn_dynamic_info_,
604
- current_wallet_balance,
605
- ) = await asyncio.gather(
606
- subtensor.get_stake_for_coldkey(wallet.coldkeypub.ss58_address),
607
- subtensor.fetch_coldkey_hotkey_identities(),
608
- subtensor.get_delegate_identities(),
609
- subtensor.all_subnets(),
610
- subtensor.get_balance(wallet.coldkeypub.ss58_address),
611
- )
612
-
613
- if unstake_all_alpha:
614
- stake_info = [stake for stake in stake_info if stake.netuid != 0]
615
-
616
- if not stake_info:
617
- console.print("[red]No stakes found to unstake[/red]")
618
- return False
619
-
620
- all_sn_dynamic_info = {info.netuid: info for info in all_sn_dynamic_info_}
621
-
622
- # Calculate total value and slippage for all stakes
623
- total_received_value = Balance(0)
624
- table_title = (
625
- "Unstaking Summary - All Stakes"
626
- if not unstake_all_alpha
627
- else "Unstaking Summary - All Alpha Stakes"
628
- )
629
- table = Table(
630
- title=f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]{table_title}\nWallet: [{COLOR_PALETTE['GENERAL']['COLDKEY']}]{wallet.name}[/{COLOR_PALETTE['GENERAL']['COLDKEY']}], Coldkey ss58: [{COLOR_PALETTE['GENERAL']['COLDKEY']}]{wallet.coldkeypub.ss58_address}[/{COLOR_PALETTE['GENERAL']['COLDKEY']}]\nNetwork: {subtensor.network}[/{COLOR_PALETTE['GENERAL']['HEADER']}]\n",
631
- show_footer=True,
632
- show_edge=False,
633
- header_style="bold white",
634
- border_style="bright_black",
635
- style="bold",
636
- title_justify="center",
637
- show_lines=False,
638
- pad_edge=True,
639
- )
640
- table.add_column("Netuid", justify="center", style="grey89")
641
- table.add_column(
642
- "Hotkey", justify="center", style=COLOR_PALETTE["GENERAL"]["HOTKEY"]
643
- )
644
- table.add_column(
645
- f"Current Stake ({Balance.get_unit(1)})",
646
- justify="center",
647
- style=COLOR_PALETTE["STAKE"]["STAKE_ALPHA"],
648
- )
649
- table.add_column(
650
- f"Rate ({Balance.unit}/{Balance.get_unit(1)})",
651
- justify="center",
652
- style=COLOR_PALETTE["POOLS"]["RATE"],
653
- )
654
- table.add_column(
655
- f"Recieved ({Balance.unit})",
656
- justify="center",
657
- style=COLOR_PALETTE["POOLS"]["TAO_EQUIV"],
658
- )
659
- table.add_column(
660
- "Slippage",
661
- justify="center",
662
- style=COLOR_PALETTE["STAKE"]["SLIPPAGE_PERCENT"],
663
- )
664
- max_slippage = 0.0
665
- for stake in stake_info:
666
- if stake.stake.rao == 0:
667
- continue
668
-
669
- dynamic_info = all_sn_dynamic_info.get(stake.netuid)
670
- stake_amount = stake.stake
671
- received_amount, _, slippage_pct_float = (
672
- dynamic_info.alpha_to_tao_with_slippage(stake_amount)
673
- )
674
-
675
- total_received_value += received_amount
676
-
677
- # Get hotkey identity
678
- if hk_identity := ck_hk_identities["hotkeys"].get(stake.hotkey_ss58):
679
- hotkey_name = hk_identity.get("identity", {}).get(
680
- "name", ""
681
- ) or hk_identity.get("display", "~")
682
- hotkey_display = f"{hotkey_name}"
683
- elif old_identity := old_identities.get(stake.hotkey_ss58):
684
- hotkey_name = old_identity.display
685
- hotkey_display = f"{hotkey_name}"
686
- else:
687
- hotkey_display = stake.hotkey_ss58
688
-
689
- if dynamic_info.is_dynamic:
690
- slippage_pct = f"{slippage_pct_float:.4f} %"
691
- else:
692
- slippage_pct_float = 0
693
- slippage_pct = "[red]N/A[/red]"
694
-
695
- max_slippage = max(max_slippage, slippage_pct_float)
696
-
697
- table.add_row(
698
- str(stake.netuid),
699
- hotkey_display,
700
- str(stake_amount),
701
- str(float(dynamic_info.price))
702
- + f"({Balance.get_unit(0)}/{Balance.get_unit(stake.netuid)})",
703
- str(received_amount),
704
- slippage_pct,
705
- )
706
- console.print(table)
707
- message = ""
708
- if max_slippage > 5:
709
- message += f"[{COLOR_PALETTE['STAKE']['SLIPPAGE_TEXT']}]-------------------------------------------------------------------------------------------------------------------\n"
710
- message += f"[bold]WARNING:[/bold] The slippage on one of your operations is high: [{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}]{max_slippage:.4f}%[/{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}], this may result in a loss of funds.\n"
711
- message += "-------------------------------------------------------------------------------------------------------------------\n"
712
- console.print(message)
713
-
714
- console.print(
715
- f"Expected return after slippage: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{total_received_value}"
716
- )
717
-
718
- if prompt and not Confirm.ask(
719
- "\nDo you want to proceed with unstaking everything?"
720
- ):
721
- return False
722
-
723
- try:
724
- wallet.unlock_coldkey()
725
- except KeyFileError:
726
- err_console.print("Error decrypting coldkey (possibly incorrect password)")
727
- return False
728
-
729
- console_status = (
730
- ":satellite: Unstaking all Alpha stakes..."
731
- if unstake_all_alpha
732
- else ":satellite: Unstaking all stakes..."
733
- )
734
- with console.status(console_status):
735
- call_function = "unstake_all_alpha" if unstake_all_alpha else "unstake_all"
736
- call = await subtensor.substrate.compose_call(
737
- call_module="SubtensorModule",
738
- call_function=call_function,
739
- call_params={"hotkey": wallet.hotkey.ss58_address},
740
- )
741
- success, error_message = await subtensor.sign_and_send_extrinsic(
742
- call=call,
743
- wallet=wallet,
744
- wait_for_inclusion=True,
745
- wait_for_finalization=False,
746
- )
747
-
748
- if success:
749
- success_message = (
750
- ":white_heavy_check_mark: [green]Successfully unstaked all stakes[/green]"
751
- if not unstake_all_alpha
752
- else ":white_heavy_check_mark: [green]Successfully unstaked all Alpha stakes[/green]"
753
- )
754
- console.print(success_message)
755
- new_balance = await subtensor.get_balance(wallet.coldkeypub.ss58_address)
756
- console.print(
757
- f"Balance:\n [blue]{current_wallet_balance}[/blue] :arrow_right: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_balance}"
758
- )
759
- return True
760
- else:
761
- err_console.print(
762
- f":cross_mark: [red]Failed to unstake[/red]: {error_message}"
763
- )
764
- return False
765
-
766
-
767
- async def unstake(
768
- wallet: Wallet,
769
- subtensor: "SubtensorInterface",
770
- hotkey_ss58_address: str,
771
- all_hotkeys: bool,
772
- include_hotkeys: list[str],
773
- exclude_hotkeys: list[str],
774
- amount: float,
775
- keep_stake: float,
776
- unstake_all: bool,
777
- prompt: bool,
778
- interactive: bool = False,
779
- netuid: Optional[int] = None,
780
- unstake_all_alpha: bool = False,
781
- ):
782
- """Unstake tokens from hotkey(s)."""
783
-
784
- if unstake_all or unstake_all_alpha:
785
- return await _unstake_all(wallet, subtensor, unstake_all_alpha, prompt)
786
-
787
- unstake_all_from_hk = False
788
- with console.status(
789
- f"Retrieving subnet data & identities from {subtensor.network}...",
790
- spinner="earth",
791
- ):
792
- all_sn_dynamic_info_, ck_hk_identities, old_identities = await asyncio.gather(
793
- subtensor.all_subnets(),
794
- subtensor.fetch_coldkey_hotkey_identities(),
795
- subtensor.get_delegate_identities(),
796
- )
797
- all_sn_dynamic_info = {info.netuid: info for info in all_sn_dynamic_info_}
798
-
799
- if interactive:
800
- hotkeys_to_unstake_from, unstake_all_from_hk = await unstake_selection(
801
- subtensor,
802
- wallet,
803
- all_sn_dynamic_info,
804
- ck_hk_identities,
805
- old_identities,
806
- netuid=netuid,
807
- )
808
- if not hotkeys_to_unstake_from:
809
- console.print("[red]No unstake operations to perform.[/red]")
810
- return False
811
- netuids = list({netuid for _, _, netuid in hotkeys_to_unstake_from})
812
-
813
- else:
814
- netuids = (
815
- [int(netuid)]
816
- if netuid is not None
817
- else await subtensor.get_all_subnet_netuids()
818
- )
819
-
820
- # Get the hotkey_names (if any) and the hotkey_ss58s.
821
- hotkeys_to_unstake_from: list[tuple[Optional[str], str]] = []
822
- if hotkey_ss58_address:
823
- print_verbose(f"Unstaking from ss58 ({hotkey_ss58_address})")
824
- # Unstake from specific hotkey.
825
- hotkeys_to_unstake_from = [(None, hotkey_ss58_address)]
826
- elif all_hotkeys:
827
- print_verbose("Unstaking from all hotkeys")
828
- # Unstake from all hotkeys.
829
- all_hotkeys_: list[Wallet] = get_hotkey_wallets_for_wallet(wallet=wallet)
830
- # Exclude hotkeys that are specified.
831
- hotkeys_to_unstake_from = [
832
- (wallet.hotkey_str, wallet.hotkey.ss58_address)
833
- for wallet in all_hotkeys_
834
- if wallet.hotkey_str not in exclude_hotkeys
835
- ]
836
- elif include_hotkeys:
837
- print_verbose("Unstaking from included hotkeys")
838
- # Unstake from specific hotkeys.
839
- for hotkey_identifier in include_hotkeys:
840
- if is_valid_ss58_address(hotkey_identifier):
841
- # If the hotkey is a valid ss58 address, we add it to the list.
842
- hotkeys_to_unstake_from.append((None, hotkey_identifier))
843
- else:
844
- # If the hotkey is not a valid ss58 address, we assume it is a hotkey name.
845
- # We then get the hotkey from the wallet and add it to the list.
846
- wallet_ = Wallet(
847
- name=wallet.name,
848
- path=wallet.path,
849
- hotkey=hotkey_identifier,
850
- )
851
- hotkeys_to_unstake_from.append(
852
- (wallet_.hotkey_str, wallet_.hotkey.ss58_address)
853
- )
854
- else:
855
- # Only cli.config.wallet.hotkey is specified.
856
- # So we unstake from that single hotkey.
857
- print_verbose(
858
- f"Unstaking from wallet: ({wallet.name}) from hotkey: ({wallet.hotkey_str})"
859
- )
860
- assert wallet.hotkey is not None
861
- hotkeys_to_unstake_from = [(wallet.hotkey_str, wallet.hotkey.ss58_address)]
862
-
863
- with console.status(
864
- f"Retrieving stake data from {subtensor.network}...",
865
- spinner="earth",
866
- ):
867
- # Prepare unstaking transactions
868
- unstake_operations = []
869
- total_received_amount = Balance.from_tao(0)
870
- current_wallet_balance: Balance = await subtensor.get_balance(
871
- wallet.coldkeypub.ss58_address
872
- )
873
- max_float_slippage = 0
874
-
875
- # Fetch stake balances
876
- chain_head = await subtensor.substrate.get_chain_head()
877
- stake_info_list = await subtensor.get_stake_for_coldkey(
878
- coldkey_ss58=wallet.coldkeypub.ss58_address,
879
- block_hash=chain_head,
880
- )
881
- stake_in_netuids = {}
882
- for stake_info in stake_info_list:
883
- if stake_info.hotkey_ss58 not in stake_in_netuids:
884
- stake_in_netuids[stake_info.hotkey_ss58] = {}
885
- stake_in_netuids[stake_info.hotkey_ss58][stake_info.netuid] = (
886
- stake_info.stake
887
- )
888
-
889
- # Flag to check if user wants to quit
890
- skip_remaining_subnets = False
891
- if hotkeys_to_unstake_from:
892
- console.print(
893
- "[dark_sea_green3]Tip: Enter 'q' any time to skip further entries and process existing unstakes"
894
- )
895
-
896
- # Iterate over hotkeys and netuids to collect unstake operations
897
- unstake_all_hk_ss58 = None
898
- for hotkey in hotkeys_to_unstake_from:
899
- if skip_remaining_subnets:
900
- break
901
-
902
- if interactive:
903
- staking_address_name, staking_address_ss58, netuid = hotkey
904
- netuids_to_process = [netuid]
905
- else:
906
- staking_address_name, staking_address_ss58 = hotkey
907
- netuids_to_process = netuids
908
-
909
- initial_amount = amount
910
-
911
- if len(netuids_to_process) > 1:
912
- console.print(
913
- "[dark_sea_green3]Tip: Enter 'q' any time to stop going over remaining subnets and process current unstakes.\n"
914
- )
915
-
916
- for netuid in netuids_to_process:
917
- if skip_remaining_subnets:
918
- break # Exit the loop over netuids
919
-
920
- dynamic_info = all_sn_dynamic_info.get(netuid)
921
- current_stake_balance = stake_in_netuids[staking_address_ss58][netuid]
922
- if current_stake_balance.tao == 0:
923
- continue # No stake to unstake
924
-
925
- # Determine the amount we are unstaking.
926
- if unstake_all_from_hk or unstake_all:
927
- amount_to_unstake_as_balance = current_stake_balance
928
- unstake_all_hk_ss58 = staking_address_ss58
929
- elif initial_amount:
930
- amount_to_unstake_as_balance = Balance.from_tao(initial_amount)
931
- else:
932
- amount_to_unstake_as_balance = ask_unstake_amount(
933
- current_stake_balance,
934
- netuid,
935
- staking_address_name
936
- if staking_address_name
937
- else staking_address_ss58,
938
- staking_address_ss58,
939
- interactive,
940
- )
941
- if amount_to_unstake_as_balance is None:
942
- skip_remaining_subnets = True
943
- break
944
-
945
- # Check enough stake to remove.
946
- amount_to_unstake_as_balance.set_unit(netuid)
947
- if amount_to_unstake_as_balance > current_stake_balance:
948
- err_console.print(
949
- f"[red]Not enough stake to remove[/red]:\n Stake balance: [dark_orange]{current_stake_balance}[/dark_orange]"
950
- f" < Unstaking amount: [dark_orange]{amount_to_unstake_as_balance}[/dark_orange]"
951
- )
952
- continue # Skip to the next subnet - useful when single amount is specified for all subnets
953
-
954
- received_amount, _, slippage_pct_float = (
955
- dynamic_info.alpha_to_tao_with_slippage(amount_to_unstake_as_balance)
956
- )
957
- total_received_amount += received_amount
958
- if dynamic_info.is_dynamic:
959
- slippage_pct = f"{slippage_pct_float:.4f} %"
960
- else:
961
- slippage_pct_float = 0
962
- slippage_pct = "[red]N/A[/red]"
963
- max_float_slippage = max(max_float_slippage, slippage_pct_float)
964
-
965
- unstake_operations.append(
966
- {
967
- "netuid": netuid,
968
- "hotkey_name": staking_address_name
969
- if staking_address_name
970
- else staking_address_ss58,
971
- "hotkey_ss58": staking_address_ss58,
972
- "amount_to_unstake": amount_to_unstake_as_balance,
973
- "current_stake_balance": current_stake_balance,
974
- "received_amount": received_amount,
975
- "slippage_pct": slippage_pct,
976
- "slippage_pct_float": slippage_pct_float,
977
- "dynamic_info": dynamic_info,
978
- }
979
- )
980
-
981
- if not unstake_operations:
982
- console.print("[red]No unstake operations to perform.[/red]")
983
- return False
984
-
985
- # Build the table
986
- table = Table(
987
- title=f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Unstaking to: \nWallet: [{COLOR_PALETTE['GENERAL']['COLDKEY']}]{wallet.name}[/{COLOR_PALETTE['GENERAL']['COLDKEY']}],"
988
- f" Coldkey ss58: [{COLOR_PALETTE['GENERAL']['COLDKEY']}]{wallet.coldkeypub.ss58_address}[/{COLOR_PALETTE['GENERAL']['COLDKEY']}]\n"
989
- f"Network: {subtensor.network}[/{COLOR_PALETTE['GENERAL']['HEADER']}]\n",
990
- show_footer=True,
991
- show_edge=False,
992
- header_style="bold white",
993
- border_style="bright_black",
994
- style="bold",
995
- title_justify="center",
996
- show_lines=False,
997
- pad_edge=True,
998
- )
999
- table.add_column("Netuid", justify="center", style="grey89")
1000
- table.add_column(
1001
- "Hotkey", justify="center", style=COLOR_PALETTE["GENERAL"]["HOTKEY"]
1002
- )
1003
- table.add_column(
1004
- f"Amount ({Balance.get_unit(1)})",
1005
- justify="center",
1006
- style=COLOR_PALETTE["POOLS"]["TAO"],
1007
- )
1008
- table.add_column(
1009
- f"Rate ({Balance.get_unit(0)}/{Balance.get_unit(1)})",
1010
- justify="center",
1011
- style=COLOR_PALETTE["POOLS"]["RATE"],
1012
- )
1013
- table.add_column(
1014
- f"Received ({Balance.get_unit(0)})",
1015
- justify="center",
1016
- style=COLOR_PALETTE["POOLS"]["TAO_EQUIV"],
1017
- footer=f"{total_received_amount}",
1018
- )
1019
- table.add_column(
1020
- "Slippage", justify="center", style=COLOR_PALETTE["STAKE"]["SLIPPAGE_PERCENT"]
1021
- )
1022
-
1023
- for op in unstake_operations:
1024
- dynamic_info = op["dynamic_info"]
1025
- table.add_row(
1026
- str(op["netuid"]),
1027
- op["hotkey_name"],
1028
- str(op["amount_to_unstake"]),
1029
- str(float(dynamic_info.price))
1030
- + f"({Balance.get_unit(0)}/{Balance.get_unit(op['netuid'])})",
1031
- str(op["received_amount"]),
1032
- op["slippage_pct"],
1033
- )
1034
-
1035
- console.print(table)
1036
-
1037
- if max_float_slippage > 5:
1038
- console.print(
1039
- "\n"
1040
- f"[{COLOR_PALETTE['STAKE']['SLIPPAGE_TEXT']}]-------------------------------------------------------------------------------------------------------------------\n"
1041
- f"[bold]WARNING:[/bold] The slippage on one of your operations is high: [{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}]{max_float_slippage} %[/{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}],"
1042
- " this may result in a loss of funds.\n"
1043
- f"-------------------------------------------------------------------------------------------------------------------\n"
1044
- )
1045
-
1046
- console.print(
1047
- """
1048
- [bold white]Description[/bold white]:
1049
- The table displays information about the stake remove operation you are about to perform.
1050
- The columns are as follows:
1051
- - [bold white]Netuid[/bold white]: The netuid of the subnet you are unstaking from.
1052
- - [bold white]Hotkey[/bold white]: The ss58 address or identity of the hotkey you are unstaking from.
1053
- - [bold white]Amount[/bold white]: The stake amount you are removing from this key.
1054
- - [bold white]Rate[/bold white]: The rate of exchange between TAO and the subnet's stake.
1055
- - [bold white]Received[/bold white]: The amount of free balance TAO you will receive on this subnet after slippage.
1056
- - [bold white]Slippage[/bold white]: The slippage percentage of the unstake operation. (0% if the subnet is not dynamic i.e. root).
1057
- """
1058
- )
1059
- if prompt:
1060
- if not Confirm.ask("Would you like to continue?"):
1061
- raise typer.Exit()
1062
-
1063
- # Perform unstaking operations
1064
- try:
1065
- wallet.unlock_coldkey()
1066
- except KeyFileError:
1067
- err_console.print("Error decrypting coldkey (possibly incorrect password)")
1068
- return False
1069
-
1070
- with console.status("\n:satellite: Performing unstaking operations...") as status:
1071
- if unstake_all_from_hk:
1072
- call = await subtensor.substrate.compose_call(
1073
- call_module="SubtensorModule",
1074
- call_function="unstake_all",
1075
- call_params={
1076
- "hotkey": unstake_all_hk_ss58,
1077
- },
1078
- )
1079
- extrinsic = await subtensor.substrate.create_signed_extrinsic(
1080
- call=call, keypair=wallet.coldkey
1081
- )
1082
- response = await subtensor.substrate.submit_extrinsic(
1083
- extrinsic, wait_for_inclusion=True, wait_for_finalization=False
1084
- )
1085
- await response.process_events()
1086
- if not await response.is_success:
1087
- print_error(
1088
- f":cross_mark: [red]Failed[/red] with error: "
1089
- f"{format_error_message(await response.error_message, subtensor.substrate)}",
1090
- status,
1091
- )
1092
- else:
1093
- new_balance = await subtensor.get_balance(
1094
- wallet.coldkeypub.ss58_address
1095
- )
1096
- console.print(":white_heavy_check_mark: [green]Finalized[/green]")
1097
- console.print(
1098
- f"Balance:\n [blue]{current_wallet_balance}[/blue] :arrow_right: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_balance}"
1099
- )
1100
- else:
1101
- for op in unstake_operations:
1102
- netuid_i = op["netuid"]
1103
- staking_address_name = op["hotkey_name"]
1104
- staking_address_ss58 = op["hotkey_ss58"]
1105
- amount = op["amount_to_unstake"]
1106
- current_stake_balance = op["current_stake_balance"]
1107
-
1108
- status.update(
1109
- f"\n:satellite: Unstaking {amount} from {staking_address_name} on netuid: {netuid_i} ..."
1110
- )
1111
-
1112
- call = await subtensor.substrate.compose_call(
1113
- call_module="SubtensorModule",
1114
- call_function="remove_stake",
1115
- call_params={
1116
- "hotkey": staking_address_ss58,
1117
- "netuid": netuid_i,
1118
- "amount_unstaked": amount.rao,
1119
- },
1120
- )
1121
- extrinsic = await subtensor.substrate.create_signed_extrinsic(
1122
- call=call, keypair=wallet.coldkey
1123
- )
1124
- response = await subtensor.substrate.submit_extrinsic(
1125
- extrinsic, wait_for_inclusion=True, wait_for_finalization=False
1126
- )
1127
- await response.process_events()
1128
- if not await response.is_success:
1129
- print_error(
1130
- f":cross_mark: [red]Failed[/red] with error: "
1131
- f"{format_error_message(await response.error_message, subtensor.substrate)}",
1132
- status,
1133
- )
1134
- else:
1135
- new_balance = await subtensor.get_balance(
1136
- wallet.coldkeypub.ss58_address
1137
- )
1138
- new_stake_info = await subtensor.get_stake_for_coldkey(
1139
- coldkey_ss58=wallet.coldkeypub.ss58_address,
1140
- )
1141
- new_stake = Balance.from_rao(0)
1142
- for stake_info in new_stake_info:
1143
- if (
1144
- stake_info.hotkey_ss58 == staking_address_ss58
1145
- and stake_info.netuid == netuid_i
1146
- ):
1147
- new_stake = stake_info.stake.set_unit(netuid_i)
1148
- break
1149
- console.print(":white_heavy_check_mark: [green]Finalized[/green]")
1150
- console.print(
1151
- f"Balance:\n [blue]{current_wallet_balance}[/blue] :arrow_right: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_balance}"
1152
- )
1153
- console.print(
1154
- f"Subnet: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{netuid_i}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}]"
1155
- f" Stake:\n [blue]{current_stake_balance}[/blue] :arrow_right: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_stake}"
1156
- )
1157
- console.print(
1158
- f"[{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]Unstaking operations completed."
1159
- )
1160
-
1161
-
1162
- async def stake_list(
1163
- wallet: Wallet,
1164
- coldkey_ss58: str,
1165
- subtensor: "SubtensorInterface",
1166
- live: bool = False,
1167
- verbose: bool = False,
1168
- prompt: bool = False,
1169
- ):
1170
- coldkey_address = coldkey_ss58 if coldkey_ss58 else wallet.coldkeypub.ss58_address
1171
-
1172
- async def get_stake_data(block_hash: str = None):
1173
- (
1174
- sub_stakes,
1175
- registered_delegate_info,
1176
- _dynamic_info,
1177
- ) = await asyncio.gather(
1178
- subtensor.get_stake_for_coldkey(
1179
- coldkey_ss58=coldkey_address, block_hash=block_hash
1180
- ),
1181
- subtensor.get_delegate_identities(block_hash=block_hash),
1182
- subtensor.all_subnets(),
1183
- )
1184
- # sub_stakes = substakes[coldkey_address]
1185
- dynamic_info = {info.netuid: info for info in _dynamic_info}
1186
- return (
1187
- sub_stakes,
1188
- registered_delegate_info,
1189
- dynamic_info,
1190
- )
1191
-
1192
- def define_table(
1193
- hotkey_name: str,
1194
- rows: list[list[str]],
1195
- total_tao_ownership: Balance,
1196
- total_tao_value: Balance,
1197
- total_swapped_tao_value: Balance,
1198
- live: bool = False,
1199
- ):
1200
- title = f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Hotkey: {hotkey_name}\nNetwork: {subtensor.network}\n\n"
1201
- # TODO: Add hint back in after adding columns descriptions
1202
- # if not live:
1203
- # title += f"[{COLOR_PALETTE['GENERAL']['HINT']}]See below for an explanation of the columns\n"
1204
- table = Table(
1205
- title=title,
1206
- show_footer=True,
1207
- show_edge=False,
1208
- header_style="bold white",
1209
- border_style="bright_black",
1210
- style="bold",
1211
- title_justify="center",
1212
- show_lines=False,
1213
- pad_edge=True,
1214
- )
1215
- table.add_column(
1216
- "[white]Netuid",
1217
- footer=f"{len(rows)}",
1218
- footer_style="overline white",
1219
- style="grey89",
1220
- )
1221
- table.add_column(
1222
- "[white]Name",
1223
- style="cyan",
1224
- justify="left",
1225
- no_wrap=True,
1226
- )
1227
- table.add_column(
1228
- f"[white]Value \n({Balance.get_unit(1)} x {Balance.unit}/{Balance.get_unit(1)})",
1229
- footer_style="overline white",
1230
- style=COLOR_PALETTE["STAKE"]["TAO"],
1231
- justify="right",
1232
- footer=f"τ {millify_tao(total_tao_value.tao)}"
1233
- if not verbose
1234
- else f"{total_tao_value}",
1235
- )
1236
- table.add_column(
1237
- f"[white]Stake ({Balance.get_unit(1)})",
1238
- footer_style="overline white",
1239
- style=COLOR_PALETTE["STAKE"]["STAKE_ALPHA"],
1240
- justify="center",
1241
- )
1242
- table.add_column(
1243
- f"[white]Price \n({Balance.unit}_in/{Balance.get_unit(1)}_in)",
1244
- footer_style="white",
1245
- style=COLOR_PALETTE["POOLS"]["RATE"],
1246
- justify="center",
1247
- )
1248
- table.add_column(
1249
- f"[white]Swap ({Balance.get_unit(1)} -> {Balance.unit})",
1250
- footer_style="overline white",
1251
- style=COLOR_PALETTE["STAKE"]["STAKE_SWAP"],
1252
- justify="right",
1253
- footer=f"τ {millify_tao(total_swapped_tao_value.tao)}"
1254
- if not verbose
1255
- else f"{total_swapped_tao_value}",
1256
- )
1257
- table.add_column(
1258
- "[white]Registered",
1259
- style=COLOR_PALETTE["STAKE"]["STAKE_ALPHA"],
1260
- justify="right",
1261
- )
1262
- table.add_column(
1263
- f"[white]Emission \n({Balance.get_unit(1)}/block)",
1264
- style=COLOR_PALETTE["POOLS"]["EMISSION"],
1265
- justify="right",
1266
- )
1267
- return table
1268
-
1269
- def create_table(hotkey_: str, substakes: list[StakeInfo]):
1270
- name = (
1271
- f"{registered_delegate_info[hotkey_].display} ({hotkey_})"
1272
- if hotkey_ in registered_delegate_info
1273
- else hotkey_
1274
- )
1275
- rows = []
1276
- total_tao_ownership = Balance(0)
1277
- total_tao_value = Balance(0)
1278
- total_swapped_tao_value = Balance(0)
1279
- root_stakes = [s for s in substakes if s.netuid == 0]
1280
- other_stakes = sorted(
1281
- [s for s in substakes if s.netuid != 0],
1282
- key=lambda x: dynamic_info[x.netuid]
1283
- .alpha_to_tao(Balance.from_rao(int(x.stake.rao)).set_unit(x.netuid))
1284
- .tao,
1285
- reverse=True,
1286
- )
1287
- sorted_substakes = root_stakes + other_stakes
1288
- for substake_ in sorted_substakes:
1289
- netuid = substake_.netuid
1290
- pool = dynamic_info[netuid]
1291
- symbol = f"{Balance.get_unit(netuid)}\u200e"
1292
- # TODO: what is this price var for?
1293
- price = (
1294
- "{:.4f}{}".format(
1295
- pool.price.__float__(), f" τ/{Balance.get_unit(netuid)}\u200e"
1296
- )
1297
- if pool.is_dynamic
1298
- else (f" 1.0000 τ/{symbol} ")
1299
- )
1300
-
1301
- # Alpha value cell
1302
- alpha_value = Balance.from_rao(int(substake_.stake.rao)).set_unit(netuid)
1303
-
1304
- # TAO value cell
1305
- tao_value = pool.alpha_to_tao(alpha_value)
1306
- total_tao_value += tao_value
1307
-
1308
- # Swapped TAO value and slippage cell
1309
- swapped_tao_value, _, slippage_percentage_ = (
1310
- pool.alpha_to_tao_with_slippage(substake_.stake)
1311
- )
1312
- total_swapped_tao_value += swapped_tao_value
1313
-
1314
- # Slippage percentage cell
1315
- if pool.is_dynamic:
1316
- slippage_percentage = f"[{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}]{slippage_percentage_:.3f}%[/{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}]"
1317
- else:
1318
- slippage_percentage = f"[{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}]0.000%[/{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}]"
1319
-
1320
- if netuid == 0:
1321
- swap_value = f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]N/A[/{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}] ({slippage_percentage})"
1322
- else:
1323
- swap_value = (
1324
- f"τ {millify_tao(swapped_tao_value.tao)} ({slippage_percentage})"
1325
- if not verbose
1326
- else f"{swapped_tao_value} ({slippage_percentage})"
1327
- )
1328
-
1329
- # TAO locked cell
1330
- tao_locked = pool.tao_in
1331
-
1332
- # Issuance cell
1333
- issuance = pool.alpha_out if pool.is_dynamic else tao_locked
1334
-
1335
- # Per block emission cell
1336
- per_block_emission = substake_.emission.tao / pool.tempo
1337
- # Alpha ownership and TAO ownership cells
1338
- if alpha_value.tao > 0.00009:
1339
- if issuance.tao != 0:
1340
- # TODO figure out why this alpha_ownership does nothing
1341
- alpha_ownership = "{:.4f}".format(
1342
- (alpha_value.tao / issuance.tao) * 100
1343
- )
1344
- tao_ownership = Balance.from_tao(
1345
- (alpha_value.tao / issuance.tao) * tao_locked.tao
1346
- )
1347
- total_tao_ownership += tao_ownership
1348
- else:
1349
- # TODO what's this var for?
1350
- alpha_ownership = "0.0000"
1351
- tao_ownership = Balance.from_tao(0)
1352
-
1353
- stake_value = (
1354
- millify_tao(substake_.stake.tao)
1355
- if not verbose
1356
- else f"{substake_.stake.tao:,.4f}"
1357
- )
1358
- subnet_name_cell = f"[{COLOR_PALETTE['GENERAL']['SYMBOL']}]{symbol if netuid != 0 else 'τ'}[/{COLOR_PALETTE['GENERAL']['SYMBOL']}] {get_subnet_name(dynamic_info[netuid])}"
1359
-
1360
- rows.append(
1361
- [
1362
- str(netuid), # Number
1363
- subnet_name_cell, # Symbol + name
1364
- f"τ {millify_tao(tao_value.tao)}"
1365
- if not verbose
1366
- else f"{tao_value}", # Value (α x τ/α)
1367
- f"{stake_value} {symbol}"
1368
- if netuid != 0
1369
- else f"{symbol} {stake_value}", # Stake (a)
1370
- f"{pool.price.tao:.4f} τ/{symbol}", # Rate (t/a)
1371
- # f"τ {millify_tao(tao_ownership.tao)}" if not verbose else f"{tao_ownership}", # TAO equiv
1372
- swap_value, # Swap(α) -> τ
1373
- "YES"
1374
- if substake_.is_registered
1375
- else f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]NO", # Registered
1376
- str(Balance.from_tao(per_block_emission).set_unit(netuid)),
1377
- # Removing this flag for now, TODO: Confirm correct values are here w.r.t CHKs
1378
- # if substake_.is_registered
1379
- # else f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]N/A", # Emission(α/block)
1380
- ]
1381
- )
1382
- table = define_table(
1383
- name, rows, total_tao_ownership, total_tao_value, total_swapped_tao_value
1384
- )
1385
- for row in rows:
1386
- table.add_row(*row)
1387
- console.print(table)
1388
- return total_tao_ownership, total_tao_value
1389
-
1390
- def create_live_table(
1391
- substakes: list,
1392
- registered_delegate_info: dict,
1393
- dynamic_info: dict,
1394
- hotkey_name: str,
1395
- previous_data: Optional[dict] = None,
1396
- ) -> tuple[Table, dict, Balance, Balance, Balance]:
1397
- rows = []
1398
- current_data = {}
1399
-
1400
- total_tao_ownership = Balance(0)
1401
- total_tao_value = Balance(0)
1402
- total_swapped_tao_value = Balance(0)
1403
-
1404
- def format_cell(
1405
- value, previous_value, unit="", unit_first=False, precision=4, millify=False
1406
- ):
1407
- if previous_value is not None:
1408
- change = value - previous_value
1409
- if abs(change) > 10 ** (-precision):
1410
- formatted_change = (
1411
- f"{change:.{precision}f}"
1412
- if not millify
1413
- else f"{millify_tao(change)}"
1414
- )
1415
- change_text = (
1416
- f" [pale_green3](+{formatted_change})[/pale_green3]"
1417
- if change > 0
1418
- else f" [hot_pink3]({formatted_change})[/hot_pink3]"
1419
- )
1420
- else:
1421
- change_text = ""
1422
- else:
1423
- change_text = ""
1424
- formatted_value = (
1425
- f"{value:,.{precision}f}" if not millify else f"{millify_tao(value)}"
1426
- )
1427
- return (
1428
- f"{formatted_value} {unit}{change_text}"
1429
- if not unit_first
1430
- else f"{unit} {formatted_value}{change_text}"
1431
- )
1432
-
1433
- # Sort subnets by value
1434
- root_stakes = [s for s in substakes if s.netuid == 0]
1435
- other_stakes = sorted(
1436
- [s for s in substakes if s.netuid != 0],
1437
- key=lambda x: dynamic_info[x.netuid]
1438
- .alpha_to_tao(Balance.from_rao(int(x.stake.rao)).set_unit(x.netuid))
1439
- .tao,
1440
- reverse=True,
1441
- )
1442
- sorted_substakes = root_stakes + other_stakes
1443
-
1444
- # Process each stake
1445
- for substake in sorted_substakes:
1446
- netuid = substake.netuid
1447
- pool = dynamic_info.get(netuid)
1448
- if substake.stake.rao == 0 or not pool:
1449
- continue
1450
-
1451
- # Calculate base values
1452
- symbol = f"{Balance.get_unit(netuid)}\u200e"
1453
- alpha_value = Balance.from_rao(int(substake.stake.rao)).set_unit(netuid)
1454
- tao_value = pool.alpha_to_tao(alpha_value)
1455
- total_tao_value += tao_value
1456
- swapped_tao_value, slippage = pool.alpha_to_tao_with_slippage(
1457
- substake.stake
1458
- )
1459
- total_swapped_tao_value += swapped_tao_value
1460
-
1461
- # Calculate TAO ownership
1462
- tao_locked = pool.tao_in
1463
- issuance = pool.alpha_out if pool.is_dynamic else tao_locked
1464
- if alpha_value.tao > 0.00009 and issuance.tao != 0:
1465
- tao_ownership = Balance.from_tao(
1466
- (alpha_value.tao / issuance.tao) * tao_locked.tao
1467
- )
1468
- total_tao_ownership += tao_ownership
1469
- else:
1470
- tao_ownership = Balance.from_tao(0)
1471
-
1472
- # Store current values for future delta tracking
1473
- current_data[netuid] = {
1474
- "stake": alpha_value.tao,
1475
- "price": pool.price.tao,
1476
- "tao_value": tao_value.tao,
1477
- "swapped_value": swapped_tao_value.tao,
1478
- "emission": substake.emission.tao / pool.tempo,
1479
- "tao_ownership": tao_ownership.tao,
1480
- }
1481
-
1482
- # Get previous values for delta tracking
1483
- prev = previous_data.get(netuid, {}) if previous_data else {}
1484
- unit_first = True if netuid == 0 else False
1485
-
1486
- stake_cell = format_cell(
1487
- alpha_value.tao,
1488
- prev.get("stake"),
1489
- unit=symbol,
1490
- unit_first=unit_first,
1491
- precision=4,
1492
- millify=True if not verbose else False,
1493
- )
1494
-
1495
- rate_cell = format_cell(
1496
- pool.price.tao,
1497
- prev.get("price"),
1498
- unit=f"τ/{symbol}",
1499
- unit_first=False,
1500
- precision=5,
1501
- millify=True if not verbose else False,
1502
- )
1503
-
1504
- exchange_cell = format_cell(
1505
- tao_value.tao,
1506
- prev.get("tao_value"),
1507
- unit="τ",
1508
- unit_first=True,
1509
- precision=4,
1510
- millify=True if not verbose else False,
1511
- )
1512
-
1513
- if pool.is_dynamic:
1514
- slippage_pct = (
1515
- 100 * float(slippage) / float(slippage + swapped_tao_value)
1516
- if slippage + swapped_tao_value != 0
1517
- else 0
1518
- )
1519
- else:
1520
- slippage_pct = 0
1521
-
1522
- if netuid != 0:
1523
- swap_cell = (
1524
- format_cell(
1525
- swapped_tao_value.tao,
1526
- prev.get("swapped_value"),
1527
- unit="τ",
1528
- unit_first=True,
1529
- precision=4,
1530
- millify=True if not verbose else False,
1531
- )
1532
- + f" ({slippage_pct:.2f}%)"
1533
- )
1534
- else:
1535
- swap_cell = f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]N/A[/{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}] ({slippage_pct}%)"
1536
-
1537
- emission_value = substake.emission.tao / pool.tempo
1538
- emission_cell = format_cell(
1539
- emission_value,
1540
- prev.get("emission"),
1541
- unit=symbol,
1542
- unit_first=unit_first,
1543
- precision=4,
1544
- )
1545
- subnet_name_cell = (
1546
- f"[{COLOR_PALETTE['GENERAL']['SYMBOL']}]{symbol if netuid != 0 else 'τ'}[/{COLOR_PALETTE['GENERAL']['SYMBOL']}]"
1547
- f" {get_subnet_name(dynamic_info[netuid])}"
1548
- )
1549
-
1550
- rows.append(
1551
- [
1552
- str(netuid), # Netuid
1553
- subnet_name_cell,
1554
- exchange_cell, # Exchange value
1555
- stake_cell, # Stake amount
1556
- rate_cell, # Rate
1557
- swap_cell, # Swap value with slippage
1558
- "YES"
1559
- if substake.is_registered
1560
- else f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]NO", # Registration status
1561
- emission_cell, # Emission rate
1562
- ]
1563
- )
1564
-
1565
- table = define_table(
1566
- hotkey_name,
1567
- rows,
1568
- total_tao_ownership,
1569
- total_tao_value,
1570
- total_swapped_tao_value,
1571
- live=True,
1572
- )
1573
-
1574
- for row in rows:
1575
- table.add_row(*row)
1576
-
1577
- return table, current_data
1578
-
1579
- # Main execution
1580
- (
1581
- sub_stakes,
1582
- registered_delegate_info,
1583
- dynamic_info,
1584
- ) = await get_stake_data()
1585
- balance = await subtensor.get_balance(coldkey_address)
1586
-
1587
- # Iterate over substakes and aggregate them by hotkey.
1588
- hotkeys_to_substakes: dict[str, list[StakeInfo]] = {}
1589
-
1590
- for substake in sub_stakes:
1591
- hotkey = substake.hotkey_ss58
1592
- if substake.stake.rao == 0:
1593
- continue
1594
- if hotkey not in hotkeys_to_substakes:
1595
- hotkeys_to_substakes[hotkey] = []
1596
- hotkeys_to_substakes[hotkey].append(substake)
1597
-
1598
- if not hotkeys_to_substakes:
1599
- print_error(f"No stakes found for coldkey ss58: ({coldkey_address})")
1600
- raise typer.Exit()
1601
-
1602
- if live:
1603
- # Select one hokkey for live monitoring
1604
- if len(hotkeys_to_substakes) > 1:
1605
- console.print(
1606
- "\n[bold]Multiple hotkeys found. Please select one for live monitoring:[/bold]"
1607
- )
1608
- for idx, hotkey in enumerate(hotkeys_to_substakes.keys()):
1609
- name = (
1610
- f"{registered_delegate_info[hotkey].display} ({hotkey})"
1611
- if hotkey in registered_delegate_info
1612
- else hotkey
1613
- )
1614
- console.print(f"[{idx}] [{COLOR_PALETTE['GENERAL']['HEADER']}]{name}")
1615
-
1616
- selected_idx = Prompt.ask(
1617
- "Enter hotkey index",
1618
- choices=[str(i) for i in range(len(hotkeys_to_substakes))],
1619
- )
1620
- selected_hotkey = list(hotkeys_to_substakes.keys())[int(selected_idx)]
1621
- selected_stakes = hotkeys_to_substakes[selected_hotkey]
1622
- else:
1623
- selected_hotkey = list(hotkeys_to_substakes.keys())[0]
1624
- selected_stakes = hotkeys_to_substakes[selected_hotkey]
1625
-
1626
- hotkey_name = (
1627
- f"{registered_delegate_info[selected_hotkey].display} ({selected_hotkey})"
1628
- if selected_hotkey in registered_delegate_info
1629
- else selected_hotkey
1630
- )
1631
-
1632
- refresh_interval = 10 # seconds
1633
- progress = Progress(
1634
- TextColumn("[progress.description]{task.description}"),
1635
- BarColumn(bar_width=20),
1636
- TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
1637
- console=console,
1638
- )
1639
- progress_task = progress.add_task("Updating: ", total=refresh_interval)
1640
-
1641
- previous_block = None
1642
- current_block = None
1643
- previous_data = None
1644
-
1645
- with Live(console=console, screen=True, auto_refresh=True) as live:
1646
- try:
1647
- while True:
1648
- block_hash = await subtensor.substrate.get_chain_head()
1649
- (
1650
- sub_stakes,
1651
- registered_delegate_info,
1652
- dynamic_info_,
1653
- ) = await get_stake_data(block_hash)
1654
- selected_stakes = [
1655
- stake
1656
- for stake in sub_stakes
1657
- if stake.hotkey_ss58 == selected_hotkey
1658
- ]
1659
-
1660
- block_number = await subtensor.substrate.get_block_number(None)
1661
-
1662
- previous_block = current_block
1663
- current_block = block_number
1664
- new_blocks = (
1665
- "N/A"
1666
- if previous_block is None
1667
- else str(current_block - previous_block)
1668
- )
1669
-
1670
- table, current_data = create_live_table(
1671
- selected_stakes,
1672
- registered_delegate_info,
1673
- dynamic_info,
1674
- hotkey_name,
1675
- previous_data,
1676
- )
1677
-
1678
- previous_data = current_data
1679
- progress.reset(progress_task)
1680
- start_time = asyncio.get_event_loop().time()
1681
-
1682
- block_info = (
1683
- f"Previous: [dark_sea_green]{previous_block}[/dark_sea_green] "
1684
- f"Current: [dark_sea_green]{current_block}[/dark_sea_green] "
1685
- f"Diff: [dark_sea_green]{new_blocks}[/dark_sea_green]"
1686
- )
1687
-
1688
- message = f"\nLive stake view - Press [bold red]Ctrl+C[/bold red] to exit\n{block_info}"
1689
- live_render = Group(message, progress, table)
1690
- live.update(live_render)
1691
-
1692
- while not progress.finished:
1693
- await asyncio.sleep(0.1)
1694
- elapsed = asyncio.get_event_loop().time() - start_time
1695
- progress.update(
1696
- progress_task, completed=min(elapsed, refresh_interval)
1697
- )
1698
-
1699
- except KeyboardInterrupt:
1700
- console.print("\n[bold]Stopped live updates[/bold]")
1701
- return
1702
-
1703
- else:
1704
- # Iterate over each hotkey and make a table
1705
- counter = 0
1706
- num_hotkeys = len(hotkeys_to_substakes)
1707
- all_hotkeys_total_global_tao = Balance(0)
1708
- all_hotkeys_total_tao_value = Balance(0)
1709
- for hotkey in hotkeys_to_substakes.keys():
1710
- counter += 1
1711
- stake, value = create_table(hotkey, hotkeys_to_substakes[hotkey])
1712
- all_hotkeys_total_global_tao += stake
1713
- all_hotkeys_total_tao_value += value
1714
-
1715
- if num_hotkeys > 1 and counter < num_hotkeys and prompt:
1716
- console.print("\nPress Enter to continue to the next hotkey...")
1717
- input()
1718
-
1719
- total_tao_value = (
1720
- f"τ {millify_tao(all_hotkeys_total_tao_value.tao)}"
1721
- if not verbose
1722
- else all_hotkeys_total_tao_value
1723
- )
1724
- total_tao_ownership = (
1725
- f"τ {millify_tao(all_hotkeys_total_global_tao.tao)}"
1726
- if not verbose
1727
- else all_hotkeys_total_global_tao
1728
- )
1729
-
1730
- console.print("\n\n")
1731
- console.print(
1732
- f"Wallet:\n"
1733
- f" Coldkey SS58: [{COLOR_PALETTE['GENERAL']['COLDKEY']}]{coldkey_address}[/{COLOR_PALETTE['GENERAL']['COLDKEY']}]\n"
1734
- f" Free Balance: [{COLOR_PALETTE['GENERAL']['BALANCE']}]{balance}[/{COLOR_PALETTE['GENERAL']['BALANCE']}]\n"
1735
- f" Total TAO ({Balance.unit}): [{COLOR_PALETTE['GENERAL']['BALANCE']}]{total_tao_ownership}[/{COLOR_PALETTE['GENERAL']['BALANCE']}]\n"
1736
- f" Total Value ({Balance.unit}): [{COLOR_PALETTE['GENERAL']['BALANCE']}]{total_tao_value}[/{COLOR_PALETTE['GENERAL']['BALANCE']}]"
1737
- )
1738
- if not sub_stakes:
1739
- console.print(
1740
- f"\n[blue]No stakes found for coldkey ss58: ({coldkey_address})"
1741
- )
1742
- else:
1743
- # TODO: Temporarily returning till we update docs
1744
- return
1745
- display_table = Prompt.ask(
1746
- "\nPress Enter to view column descriptions or type 'q' to skip:",
1747
- choices=["", "q"],
1748
- default="",
1749
- show_choices=True,
1750
- ).lower()
1751
-
1752
- if display_table == "q":
1753
- console.print(
1754
- f"[{COLOR_PALETTE['GENERAL']['SUBHEADING_EXTRA_1']}]Column descriptions skipped."
1755
- )
1756
- else:
1757
- header = """
1758
- [bold white]Description[/bold white]: Each table displays information about stake associated with a hotkey. The columns are as follows:
1759
- """
1760
- console.print(header)
1761
- description_table = Table(
1762
- show_header=False, box=box.SIMPLE, show_edge=False, show_lines=True
1763
- )
1764
-
1765
- fields = [
1766
- ("[bold tan]Netuid[/bold tan]", "The netuid of the subnet."),
1767
- (
1768
- "[bold tan]Symbol[/bold tan]",
1769
- "The symbol for the subnet's dynamic TAO token.",
1770
- ),
1771
- (
1772
- "[bold tan]Stake (α)[/bold tan]",
1773
- "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].",
1774
- ),
1775
- (
1776
- "[bold tan]TAO Reserves (τ_in)[/bold tan]",
1777
- '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].',
1778
- ),
1779
- (
1780
- "[bold tan]Alpha Reserves (α_in)[/bold tan]",
1781
- "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].",
1782
- ),
1783
- (
1784
- "[bold tan]RATE (τ_in/α_in)[/bold tan]",
1785
- "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].",
1786
- ),
1787
- (
1788
- "[bold tan]Alpha out (α_out)[/bold tan]",
1789
- "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",
1790
- ),
1791
- (
1792
- "[bold tan]TAO Equiv (τ_in x α/α_out)[/bold tan]",
1793
- '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].',
1794
- ),
1795
- (
1796
- "[bold tan]Exchange Value (α x τ/α)[/bold tan]",
1797
- "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].",
1798
- ),
1799
- (
1800
- "[bold tan]Swap (α → τ)[/bold tan]",
1801
- "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].",
1802
- ),
1803
- (
1804
- "[bold tan]Registered[/bold tan]",
1805
- "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].",
1806
- ),
1807
- (
1808
- "[bold tan]Emission (α/block)[/bold tan]",
1809
- "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].",
1810
- ),
1811
- ]
1812
-
1813
- description_table.add_column(
1814
- "Field",
1815
- no_wrap=True,
1816
- style="bold tan",
1817
- )
1818
- description_table.add_column("Description", overflow="fold")
1819
- for field_name, description in fields:
1820
- description_table.add_row(field_name, description)
1821
- console.print(description_table)