bittensor-cli 8.4.3__py3-none-any.whl → 9.0.0rc2__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.
- bittensor_cli/__init__.py +2 -2
- bittensor_cli/cli.py +1508 -1385
- bittensor_cli/src/__init__.py +627 -197
- bittensor_cli/src/bittensor/balances.py +41 -8
- bittensor_cli/src/bittensor/chain_data.py +557 -428
- bittensor_cli/src/bittensor/extrinsics/registration.py +161 -47
- bittensor_cli/src/bittensor/extrinsics/root.py +14 -8
- bittensor_cli/src/bittensor/extrinsics/transfer.py +14 -21
- bittensor_cli/src/bittensor/minigraph.py +46 -8
- bittensor_cli/src/bittensor/subtensor_interface.py +572 -253
- bittensor_cli/src/bittensor/utils.py +326 -75
- bittensor_cli/src/commands/stake/__init__.py +154 -0
- bittensor_cli/src/commands/stake/children_hotkeys.py +121 -87
- bittensor_cli/src/commands/stake/move.py +1000 -0
- bittensor_cli/src/commands/stake/stake.py +1637 -1264
- bittensor_cli/src/commands/subnets/__init__.py +0 -0
- bittensor_cli/src/commands/subnets/price.py +867 -0
- bittensor_cli/src/commands/subnets/subnets.py +2055 -0
- bittensor_cli/src/commands/sudo.py +529 -26
- bittensor_cli/src/commands/wallets.py +234 -544
- bittensor_cli/src/commands/weights.py +15 -11
- {bittensor_cli-8.4.3.dist-info → bittensor_cli-9.0.0rc2.dist-info}/METADATA +7 -4
- bittensor_cli-9.0.0rc2.dist-info/RECORD +32 -0
- bittensor_cli/src/bittensor/async_substrate_interface.py +0 -2748
- bittensor_cli/src/commands/root.py +0 -1752
- bittensor_cli/src/commands/subnets.py +0 -897
- bittensor_cli-8.4.3.dist-info/RECORD +0 -31
- {bittensor_cli-8.4.3.dist-info → bittensor_cli-9.0.0rc2.dist-info}/WHEEL +0 -0
- {bittensor_cli-8.4.3.dist-info → bittensor_cli-9.0.0rc2.dist-info}/entry_points.txt +0 -0
- {bittensor_cli-8.4.3.dist-info → bittensor_cli-9.0.0rc2.dist-info}/top_level.txt +0 -0
@@ -1,1448 +1,1821 @@
|
|
1
1
|
import asyncio
|
2
|
-
import
|
3
|
-
import json
|
4
|
-
import sqlite3
|
5
|
-
from contextlib import suppress
|
2
|
+
from functools import partial
|
6
3
|
|
7
|
-
from typing import TYPE_CHECKING, Optional
|
8
|
-
|
9
|
-
from bittensor_wallet import Wallet
|
10
|
-
from rich.prompt import Confirm
|
11
|
-
from rich.table import Table, Column
|
4
|
+
from typing import TYPE_CHECKING, Optional
|
12
5
|
import typer
|
13
6
|
|
14
|
-
|
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
|
15
18
|
from bittensor_cli.src.bittensor.balances import Balance
|
19
|
+
from bittensor_cli.src.bittensor.chain_data import StakeInfo
|
16
20
|
from bittensor_cli.src.bittensor.utils import (
|
21
|
+
# TODO add back in caching
|
17
22
|
console,
|
18
|
-
create_table,
|
19
23
|
err_console,
|
20
24
|
print_verbose,
|
21
25
|
print_error,
|
22
|
-
get_coldkey_wallets_for_path,
|
23
26
|
get_hotkey_wallets_for_wallet,
|
24
27
|
is_valid_ss58_address,
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
validate_coldkey_presence,
|
30
|
-
unlock_key,
|
28
|
+
format_error_message,
|
29
|
+
group_subnets,
|
30
|
+
millify_tao,
|
31
|
+
get_subnet_name,
|
31
32
|
)
|
32
33
|
|
33
34
|
if TYPE_CHECKING:
|
34
35
|
from bittensor_cli.src.bittensor.subtensor_interface import SubtensorInterface
|
35
36
|
|
36
37
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
async def _get_threshold_amount(
|
41
|
-
subtensor: "SubtensorInterface", block_hash: str
|
42
|
-
) -> Balance:
|
43
|
-
mrs = await subtensor.substrate.query(
|
44
|
-
module="SubtensorModule",
|
45
|
-
storage_function="NominatorMinRequiredStake",
|
46
|
-
block_hash=block_hash,
|
47
|
-
)
|
48
|
-
min_req_stake: Balance = Balance.from_rao(mrs)
|
49
|
-
return min_req_stake
|
50
|
-
|
51
|
-
|
52
|
-
async def _check_threshold_amount(
|
38
|
+
async def stake_add(
|
39
|
+
wallet: Wallet,
|
53
40
|
subtensor: "SubtensorInterface",
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
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
|
+
):
|
58
51
|
"""
|
59
|
-
Checks if the new stake balance will be above the minimum required stake threshold.
|
60
52
|
|
61
|
-
:
|
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:
|
62
67
|
|
63
|
-
:return: (success, threshold)
|
64
|
-
`True` if the staking balance is above the threshold, or `False` if the staking balance is below the
|
65
|
-
threshold.
|
66
|
-
The threshold balance required to stake.
|
67
68
|
"""
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
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
|
+
)
|
76
86
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
"""
|
88
|
-
Adds the specified amount of stake to passed hotkey `uid`.
|
89
|
-
|
90
|
-
:param subtensor: the initialized SubtensorInterface object to use
|
91
|
-
:param wallet: Bittensor wallet object.
|
92
|
-
:param old_balance: the balance prior to the staking
|
93
|
-
:param hotkey_ss58: The `ss58` address of the hotkey account to stake to defaults to the wallet's hotkey.
|
94
|
-
:param amount: Amount to stake as Bittensor balance, `None` if staking all.
|
95
|
-
:param wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning `True`, or returns
|
96
|
-
`False` if the extrinsic fails to enter the block within the timeout.
|
97
|
-
:param wait_for_finalization: If set, waits for the extrinsic to be finalized on the chain before returning `True`,
|
98
|
-
or returns `False` if the extrinsic fails to be finalized within the timeout.
|
99
|
-
:param prompt: If `True`, the call waits for confirmation from the user before proceeding.
|
100
|
-
|
101
|
-
:return: success: Flag is `True` if extrinsic was finalized or included in the block. If we did not wait for
|
102
|
-
finalization/inclusion, the response is `True`.
|
103
|
-
"""
|
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
|
104
97
|
|
105
|
-
|
106
|
-
if
|
107
|
-
|
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
|
108
109
|
|
109
|
-
|
110
|
-
|
111
|
-
|
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)]
|
112
137
|
|
113
|
-
|
114
|
-
|
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
|
+
)
|
115
158
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
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:
|
132
199
|
err_console.print(
|
133
|
-
f"
|
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]"
|
134
202
|
)
|
135
203
|
return False
|
204
|
+
remaining_wallet_balance -= amount_to_stake_as_balance
|
136
205
|
|
137
|
-
#
|
138
|
-
|
139
|
-
|
140
|
-
storage_function="Delegates",
|
141
|
-
params=[hotkey_ss58],
|
142
|
-
block_hash=block_hash,
|
206
|
+
# Slippage warning
|
207
|
+
received_amount, _, slippage_pct_float = (
|
208
|
+
dynamic_info.tao_to_alpha_with_slippage(amount_to_stake_as_balance)
|
143
209
|
)
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
# Stake it all.
|
163
|
-
staking_balance = Balance.from_tao(old_balance.tao)
|
164
|
-
else:
|
165
|
-
staking_balance = Balance.from_tao(amount)
|
166
|
-
|
167
|
-
# Leave existential balance to keep key alive.
|
168
|
-
if staking_balance > old_balance - existential_deposit:
|
169
|
-
# If we are staking all, we need to leave at least the existential deposit.
|
170
|
-
staking_balance = old_balance - existential_deposit
|
171
|
-
else:
|
172
|
-
staking_balance = staking_balance
|
173
|
-
|
174
|
-
# Check enough to stake.
|
175
|
-
if staking_balance > old_balance:
|
176
|
-
err_console.print(
|
177
|
-
f":cross_mark: [red]Not enough stake[/red]:[bold white]\n"
|
178
|
-
f"\tbalance:\t{old_balance}\n"
|
179
|
-
f"\tamount:\t{staking_balance}\n"
|
180
|
-
f"\tcoldkey:\t{wallet.name}[/bold white]"
|
181
|
-
)
|
182
|
-
return False
|
183
|
-
|
184
|
-
# If nominating, we need to check if the new stake balance will be above the minimum required stake threshold.
|
185
|
-
if not own_hotkey:
|
186
|
-
new_stake_balance = old_stake + staking_balance
|
187
|
-
print_verbose("Fetching threshold amount")
|
188
|
-
is_above_threshold, threshold = await _check_threshold_amount(
|
189
|
-
subtensor, new_stake_balance, block_hash
|
190
|
-
)
|
191
|
-
if not is_above_threshold:
|
192
|
-
err_console.print(
|
193
|
-
f":cross_mark: [red]New stake balance of {new_stake_balance} is below the minimum required nomination"
|
194
|
-
f" stake threshold {threshold}.[/red]"
|
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
|
+
)
|
195
228
|
)
|
196
|
-
|
197
|
-
|
198
|
-
|
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
|
+
)
|
199
273
|
if prompt:
|
200
|
-
if not
|
201
|
-
|
202
|
-
if not Confirm.ask(
|
203
|
-
f"Do you want to delegate:[bold white]\n"
|
204
|
-
f"\tamount: {staking_balance}\n"
|
205
|
-
f"\tto: {hotkey_ss58}\n"
|
206
|
-
f"\ttake: {hotkey_take}\n[/bold white]"
|
207
|
-
f"\towner: {hotkey_owner}\n"
|
208
|
-
):
|
209
|
-
return False
|
210
|
-
else:
|
211
|
-
if not Confirm.ask(
|
212
|
-
f"Do you want to stake:[bold white]\n"
|
213
|
-
f"\tamount: {staking_balance}\n"
|
214
|
-
f"\tto: {wallet.hotkey_str}\n"
|
215
|
-
f"\taddress: {hotkey_ss58}[/bold white]\n"
|
216
|
-
):
|
217
|
-
return False
|
274
|
+
if not Confirm.ask("Would you like to continue?"):
|
275
|
+
raise typer.Exit()
|
218
276
|
|
219
|
-
|
220
|
-
|
221
|
-
spinner="earth",
|
277
|
+
async def send_extrinsic(
|
278
|
+
netuid_i, amount_, current, staking_address_ss58, status=None
|
222
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
|
+
)
|
223
284
|
call = await subtensor.substrate.compose_call(
|
224
285
|
call_module="SubtensorModule",
|
225
286
|
call_function="add_stake",
|
226
|
-
call_params={
|
287
|
+
call_params={
|
288
|
+
"hotkey": staking_address_ss58,
|
289
|
+
"netuid": netuid_i,
|
290
|
+
"amount_staked": amount_.rao,
|
291
|
+
},
|
227
292
|
)
|
228
|
-
|
229
|
-
call, wallet
|
293
|
+
extrinsic = await subtensor.substrate.create_signed_extrinsic(
|
294
|
+
call=call, keypair=wallet.coldkey
|
230
295
|
)
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
return True
|
235
|
-
|
236
|
-
console.print(":white_heavy_check_mark: [green]Finalized[/green]")
|
237
|
-
with console.status(
|
238
|
-
f":satellite: Checking Balance on: [white]{subtensor}[/white] ..."
|
239
|
-
):
|
240
|
-
new_block_hash = await subtensor.substrate.get_chain_head()
|
241
|
-
new_balance, new_stake = await asyncio.gather(
|
242
|
-
subtensor.get_balance(
|
243
|
-
wallet.coldkeypub.ss58_address, block_hash=new_block_hash
|
244
|
-
),
|
245
|
-
subtensor.get_stake_for_coldkey_and_hotkey(
|
246
|
-
coldkey_ss58=wallet.coldkeypub.ss58_address,
|
247
|
-
hotkey_ss58=hotkey_ss58,
|
248
|
-
block_hash=new_block_hash,
|
249
|
-
),
|
250
|
-
)
|
251
|
-
|
252
|
-
console.print(
|
253
|
-
f"Balance:\n"
|
254
|
-
f"\t[blue]{old_balance}[/blue] :arrow_right: "
|
255
|
-
f"[green]{new_balance[wallet.coldkeypub.ss58_address]}[/green]"
|
296
|
+
try:
|
297
|
+
response = await subtensor.substrate.submit_extrinsic(
|
298
|
+
extrinsic, wait_for_inclusion=True, wait_for_finalization=False
|
256
299
|
)
|
257
|
-
|
258
|
-
|
259
|
-
f"\
|
300
|
+
except SubstrateRequestException as e:
|
301
|
+
err_out(
|
302
|
+
f"\n{failure_prelude} with error: {format_error_message(e, subtensor.substrate)}"
|
260
303
|
)
|
261
|
-
return
|
262
|
-
|
263
|
-
|
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)")
|
264
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
|
265
368
|
|
266
369
|
|
267
|
-
async def
|
370
|
+
async def unstake_selection(
|
268
371
|
subtensor: "SubtensorInterface",
|
269
372
|
wallet: Wallet,
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
:param subtensor: The initialized SubtensorInterface object.
|
280
|
-
:param wallet: Bittensor wallet object for the coldkey.
|
281
|
-
:param old_balance: The balance of the wallet prior to staking.
|
282
|
-
:param hotkey_ss58s: List of hotkeys to stake to.
|
283
|
-
:param amounts: List of amounts to stake. If `None`, stake all to the first hotkey.
|
284
|
-
:param wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning `True`, or returns
|
285
|
-
`False` if the extrinsic fails to enter the block within the timeout.
|
286
|
-
:param wait_for_finalization: If set, waits for the extrinsic to be finalized on the chain before returning `True`,
|
287
|
-
or returns `False` if the extrinsic fails to be finalized within the timeout.
|
288
|
-
:param prompt: If `True`, the call waits for confirmation from the user before proceeding.
|
289
|
-
|
290
|
-
:return: success: `True` if extrinsic was finalized or included in the block. `True` if any wallet was staked. If
|
291
|
-
we did not wait for finalization/inclusion, the response is `True`.
|
292
|
-
"""
|
293
|
-
|
294
|
-
if len(hotkey_ss58s) == 0:
|
295
|
-
return True
|
296
|
-
|
297
|
-
if amounts is not None and len(amounts) != len(hotkey_ss58s):
|
298
|
-
raise ValueError("amounts must be a list of the same length as hotkey_ss58s")
|
299
|
-
|
300
|
-
new_amounts: Sequence[Optional[Balance]]
|
301
|
-
if amounts is None:
|
302
|
-
new_amounts = [None] * len(hotkey_ss58s)
|
303
|
-
else:
|
304
|
-
new_amounts = [Balance.from_tao(amount) for amount in amounts]
|
305
|
-
if sum(amount.tao for amount in new_amounts) == 0:
|
306
|
-
# Staking 0 tao
|
307
|
-
return True
|
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
|
+
)
|
308
381
|
|
309
|
-
|
310
|
-
|
311
|
-
|
382
|
+
if not stake_infos:
|
383
|
+
print_error("You have no stakes to unstake.")
|
384
|
+
raise typer.Exit()
|
312
385
|
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
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
|
+
}
|
324
422
|
)
|
325
423
|
|
326
|
-
#
|
327
|
-
|
328
|
-
|
329
|
-
[
|
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",
|
330
481
|
)
|
331
|
-
if total_staking_rao == 0:
|
332
|
-
# Staking all to the first wallet.
|
333
|
-
if old_balance.rao > 1000:
|
334
|
-
old_balance -= Balance.from_rao(1000)
|
335
|
-
|
336
|
-
elif total_staking_rao < 1000:
|
337
|
-
# Staking less than 1000 rao to the wallets.
|
338
|
-
pass
|
339
|
-
else:
|
340
|
-
# Staking more than 1000 rao to the wallets.
|
341
|
-
## Reduce the amount to stake to each wallet to keep the balance above 1000 rao.
|
342
|
-
percent_reduction = 1 - (1000 / total_staking_rao)
|
343
|
-
new_amounts = [
|
344
|
-
Balance.from_tao(amount.tao * percent_reduction)
|
345
|
-
for amount in cast(Sequence[Balance], new_amounts)
|
346
|
-
]
|
347
|
-
|
348
|
-
successful_stakes = 0
|
349
|
-
for idx, (hotkey_ss58, amount, old_stake) in enumerate(
|
350
|
-
zip(hotkey_ss58s, new_amounts, old_stakes)
|
351
|
-
):
|
352
|
-
staking_all = False
|
353
|
-
# Convert to bittensor.Balance
|
354
|
-
if amount is None:
|
355
|
-
# Stake it all.
|
356
|
-
staking_balance = Balance.from_tao(old_balance.tao)
|
357
|
-
staking_all = True
|
358
|
-
else:
|
359
|
-
# Amounts are cast to balance earlier in the function
|
360
|
-
assert isinstance(amount, Balance)
|
361
|
-
staking_balance = amount
|
362
482
|
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
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",
|
369
498
|
)
|
370
|
-
continue
|
371
499
|
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
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
|
+
)
|
380
519
|
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
)
|
386
|
-
staking_response, err_msg = await subtensor.sign_and_send_extrinsic(
|
387
|
-
call, wallet, wait_for_inclusion, wait_for_finalization
|
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_)
|
388
524
|
)
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
new_stake, new_balance_ = await asyncio.gather(
|
423
|
-
subtensor.get_stake_for_coldkey_and_hotkey(
|
424
|
-
coldkey_ss58=wallet.coldkeypub.ss58_address,
|
425
|
-
hotkey_ss58=hotkey_ss58,
|
426
|
-
block_hash=new_block_hash,
|
427
|
-
),
|
428
|
-
subtensor.get_balance(
|
429
|
-
wallet.coldkeypub.ss58_address, block_hash=new_block_hash
|
430
|
-
),
|
431
|
-
)
|
432
|
-
new_balance = new_balance_[wallet.coldkeypub.ss58_address]
|
433
|
-
console.print(
|
434
|
-
"Stake ({}): [blue]{}[/blue] :arrow_right: [green]{}[/green]".format(
|
435
|
-
hotkey_ss58, old_stake, new_stake
|
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']}])"
|
436
558
|
)
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
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
|
+
)
|
443
582
|
|
444
583
|
else:
|
445
|
-
|
446
|
-
continue
|
447
|
-
|
448
|
-
if successful_stakes != 0:
|
449
|
-
with console.status(
|
450
|
-
f":satellite: Checking Balance on: ([white]{subtensor}[/white] ..."
|
451
|
-
):
|
452
|
-
new_balance_ = await subtensor.get_balance(
|
453
|
-
wallet.coldkeypub.ss58_address, reuse_block=False
|
454
|
-
)
|
455
|
-
new_balance = new_balance_[wallet.coldkeypub.ss58_address]
|
456
|
-
console.print(
|
457
|
-
f"Balance: [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance}[/green]"
|
458
|
-
)
|
459
|
-
return True
|
584
|
+
console.print("[red]Invalid input. Please enter 'y', 'n', or 'q'.[/red]")
|
460
585
|
|
461
|
-
return False
|
462
586
|
|
463
|
-
|
464
|
-
async def unstake_extrinsic(
|
465
|
-
subtensor: "SubtensorInterface",
|
587
|
+
async def _unstake_all(
|
466
588
|
wallet: Wallet,
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
wait_for_finalization: bool = False,
|
471
|
-
prompt: bool = False,
|
589
|
+
subtensor: "SubtensorInterface",
|
590
|
+
unstake_all_alpha: bool = False,
|
591
|
+
prompt: bool = True,
|
472
592
|
) -> bool:
|
473
|
-
"""
|
474
|
-
|
475
|
-
:param subtensor: the initialized SubtensorInterface object to use
|
476
|
-
:param wallet: Bittensor wallet object.
|
477
|
-
:param hotkey_ss58: The `ss58` address of the hotkey to unstake from. By default, the wallet hotkey is used.
|
478
|
-
:param amount: Amount to stake as Bittensor balance, or `None` is unstaking all
|
479
|
-
:param wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning `True`, or returns
|
480
|
-
`False` if the extrinsic fails to enter the block within the timeout.
|
481
|
-
:param wait_for_finalization: If set, waits for the extrinsic to be finalized on the chain before returning `True`,
|
482
|
-
or returns `False` if the extrinsic fails to be finalized within the timeout.
|
483
|
-
:param prompt: If `True`, the call waits for confirmation from the user before proceeding.
|
484
|
-
|
485
|
-
:return: success: `True` if extrinsic was finalized or included in the block. If we did not wait for
|
486
|
-
finalization/inclusion, the response is `True`.
|
487
|
-
"""
|
488
|
-
# Decrypt coldkey
|
489
|
-
if not unlock_key(wallet).success:
|
490
|
-
return False
|
491
|
-
|
492
|
-
if hotkey_ss58 is None:
|
493
|
-
hotkey_ss58 = wallet.hotkey.ss58_address # Default to wallet's own hotkey.
|
593
|
+
"""Unstakes all stakes from all hotkeys in all subnets."""
|
494
594
|
|
495
595
|
with console.status(
|
496
|
-
f"
|
497
|
-
spinner="
|
498
|
-
)
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
),
|
510
|
-
subtensor.
|
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),
|
511
611
|
)
|
512
612
|
|
513
|
-
|
613
|
+
if unstake_all_alpha:
|
614
|
+
stake_info = [stake for stake in stake_info if stake.netuid != 0]
|
514
615
|
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
unstaking_balance = old_stake
|
519
|
-
else:
|
520
|
-
unstaking_balance = Balance.from_tao(amount)
|
521
|
-
|
522
|
-
# Check enough to unstake.
|
523
|
-
stake_on_uid = old_stake
|
524
|
-
if unstaking_balance > stake_on_uid:
|
525
|
-
err_console.print(
|
526
|
-
f":cross_mark: [red]Not enough stake[/red]: "
|
527
|
-
f"[green]{stake_on_uid}[/green] to unstake: "
|
528
|
-
f"[blue]{unstaking_balance}[/blue] from hotkey:"
|
529
|
-
f" [white]{wallet.hotkey_str}[/white]"
|
530
|
-
)
|
531
|
-
return False
|
616
|
+
if not stake_info:
|
617
|
+
console.print("[red]No stakes found to unstake[/red]")
|
618
|
+
return False
|
532
619
|
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
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"]
|
542
643
|
)
|
543
|
-
|
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
|
544
668
|
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
f"\thotkey: {wallet.hotkey_str}[/bold white ]?"
|
551
|
-
):
|
552
|
-
return False
|
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
|
+
)
|
553
674
|
|
554
|
-
|
555
|
-
|
556
|
-
|
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?"
|
557
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"
|
558
736
|
call = await subtensor.substrate.compose_call(
|
559
737
|
call_module="SubtensorModule",
|
560
|
-
call_function=
|
561
|
-
call_params={
|
562
|
-
"hotkey": hotkey_ss58,
|
563
|
-
"amount_unstaked": unstaking_balance.rao,
|
564
|
-
},
|
738
|
+
call_function=call_function,
|
739
|
+
call_params={"hotkey": wallet.hotkey.ss58_address},
|
565
740
|
)
|
566
|
-
|
567
|
-
call,
|
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,
|
568
746
|
)
|
569
747
|
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
console.print(":white_heavy_check_mark: [green]Finalized[/green]")
|
576
|
-
with console.status(
|
577
|
-
f":satellite: Checking Balance on: [white]{subtensor}[/white] ..."
|
578
|
-
):
|
579
|
-
new_block_hash = await subtensor.substrate.get_chain_head()
|
580
|
-
new_balance, new_stake = await asyncio.gather(
|
581
|
-
subtensor.get_balance(
|
582
|
-
wallet.coldkeypub.ss58_address, block_hash=new_block_hash
|
583
|
-
),
|
584
|
-
subtensor.get_stake_for_coldkey_and_hotkey(
|
585
|
-
hotkey_ss58, wallet.coldkeypub.ss58_address, new_block_hash
|
586
|
-
),
|
587
|
-
)
|
588
|
-
console.print(
|
589
|
-
f"Balance:\n"
|
590
|
-
f" [blue]{old_balance[wallet.coldkeypub.ss58_address]}[/blue] :arrow_right:"
|
591
|
-
f" [green]{new_balance[wallet.coldkeypub.ss58_address]}[/green]"
|
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]"
|
592
753
|
)
|
754
|
+
console.print(success_message)
|
755
|
+
new_balance = await subtensor.get_balance(wallet.coldkeypub.ss58_address)
|
593
756
|
console.print(
|
594
|
-
f"
|
757
|
+
f"Balance:\n [blue]{current_wallet_balance}[/blue] :arrow_right: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_balance}"
|
595
758
|
)
|
596
759
|
return True
|
597
|
-
|
598
|
-
|
599
|
-
|
760
|
+
else:
|
761
|
+
err_console.print(
|
762
|
+
f":cross_mark: [red]Failed to unstake[/red]: {error_message}"
|
763
|
+
)
|
764
|
+
return False
|
600
765
|
|
601
766
|
|
602
|
-
async def
|
603
|
-
subtensor: "SubtensorInterface",
|
767
|
+
async def unstake(
|
604
768
|
wallet: Wallet,
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
:
|
615
|
-
:
|
616
|
-
:
|
617
|
-
|
618
|
-
|
619
|
-
`False` if the extrinsic fails to enter the block within the timeout.
|
620
|
-
:param wait_for_finalization: If set, waits for the extrinsic to be finalized on the chain before returning `True`,
|
621
|
-
or returns `False` if the extrinsic fails to be finalized within the timeout.
|
622
|
-
:param prompt: If `True`, the call waits for confirmation from the user before proceeding.
|
623
|
-
|
624
|
-
:return: success: `True` if extrinsic was finalized or included in the block. Flag is `True` if any wallet was
|
625
|
-
unstaked. If we did not wait for finalization/inclusion, the response is `True`.
|
626
|
-
"""
|
627
|
-
if not isinstance(hotkey_ss58s, list) or not all(
|
628
|
-
isinstance(hotkey_ss58, str) for hotkey_ss58 in hotkey_ss58s
|
629
|
-
):
|
630
|
-
raise TypeError("hotkey_ss58s must be a list of str")
|
631
|
-
|
632
|
-
if len(hotkey_ss58s) == 0:
|
633
|
-
return True
|
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)."""
|
634
783
|
|
635
|
-
if
|
636
|
-
|
784
|
+
if unstake_all or unstake_all_alpha:
|
785
|
+
return await _unstake_all(wallet, subtensor, unstake_all_alpha, prompt)
|
637
786
|
|
638
|
-
|
639
|
-
|
787
|
+
unstake_all_from_hk = False
|
788
|
+
with console.status(
|
789
|
+
f"Retrieving subnet data & identities from {subtensor.network}...",
|
790
|
+
spinner="earth",
|
640
791
|
):
|
641
|
-
|
642
|
-
|
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,
|
643
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})
|
644
812
|
|
645
|
-
new_amounts: Sequence[Optional[Balance]]
|
646
|
-
if amounts is None:
|
647
|
-
new_amounts = [None] * len(hotkey_ss58s)
|
648
813
|
else:
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
return True
|
814
|
+
netuids = (
|
815
|
+
[int(netuid)]
|
816
|
+
if netuid is not None
|
817
|
+
else await subtensor.get_all_subnet_netuids()
|
818
|
+
)
|
655
819
|
|
656
|
-
|
657
|
-
|
658
|
-
|
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)]
|
659
862
|
|
660
863
|
with console.status(
|
661
|
-
f"
|
864
|
+
f"Retrieving stake data from {subtensor.network}...",
|
865
|
+
spinner="earth",
|
662
866
|
):
|
663
|
-
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
old_stakes_ = asyncio.gather(
|
669
|
-
*[
|
670
|
-
subtensor.get_stake_for_coldkey_and_hotkey(
|
671
|
-
h, wallet.coldkeypub.ss58_address, block_hash
|
672
|
-
)
|
673
|
-
for h in hotkey_ss58s
|
674
|
-
]
|
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
|
675
872
|
)
|
676
|
-
|
677
|
-
|
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,
|
678
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
|
+
)
|
679
888
|
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
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"
|
685
894
|
)
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
if amount is None:
|
697
|
-
# Unstake it all.
|
698
|
-
unstaking_balance = old_stake
|
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]
|
699
905
|
else:
|
700
|
-
|
906
|
+
staking_address_name, staking_address_ss58 = hotkey
|
907
|
+
netuids_to_process = netuids
|
701
908
|
|
702
|
-
|
703
|
-
|
704
|
-
if
|
705
|
-
|
706
|
-
|
707
|
-
f" [green]{stake_on_uid}[/green] to unstake:"
|
708
|
-
f" [blue]{unstaking_balance}[/blue] from hotkey:"
|
709
|
-
f" [white]{wallet.hotkey_str}[/white]"
|
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"
|
710
914
|
)
|
711
|
-
continue
|
712
915
|
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
|
719
|
-
|
720
|
-
|
721
|
-
|
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,
|
722
940
|
)
|
723
|
-
|
724
|
-
|
725
|
-
|
726
|
-
|
727
|
-
|
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)
|
728
956
|
)
|
729
|
-
|
730
|
-
|
731
|
-
|
732
|
-
|
733
|
-
|
734
|
-
|
735
|
-
|
736
|
-
f"ss58: {hotkey_ss58}[/bold white ]?"
|
737
|
-
):
|
738
|
-
continue
|
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)
|
739
964
|
|
740
|
-
|
741
|
-
|
742
|
-
|
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:
|
743
1072
|
call = await subtensor.substrate.compose_call(
|
744
1073
|
call_module="SubtensorModule",
|
745
|
-
call_function="
|
1074
|
+
call_function="unstake_all",
|
746
1075
|
call_params={
|
747
|
-
"hotkey":
|
748
|
-
"amount_unstaked": unstaking_balance.rao,
|
1076
|
+
"hotkey": unstake_all_hk_ss58,
|
749
1077
|
},
|
750
1078
|
)
|
751
|
-
|
752
|
-
call, wallet
|
1079
|
+
extrinsic = await subtensor.substrate.create_signed_extrinsic(
|
1080
|
+
call=call, keypair=wallet.coldkey
|
753
1081
|
)
|
754
|
-
|
755
|
-
|
756
|
-
|
757
|
-
|
758
|
-
if
|
759
|
-
|
760
|
-
|
761
|
-
|
762
|
-
|
763
|
-
block_hash=block_hash,
|
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,
|
764
1091
|
)
|
765
|
-
|
766
|
-
|
767
|
-
|
768
|
-
if tx_rate_limit_blocks > 0:
|
769
|
-
console.print(
|
770
|
-
":hourglass: [yellow]Waiting for tx rate limit:"
|
771
|
-
f" [white]{tx_rate_limit_blocks}[/white] blocks,"
|
772
|
-
f" estimated time: [white]{tx_rate_limit_blocks * 12} [/white] seconds[/yellow]"
|
773
|
-
)
|
774
|
-
await asyncio.sleep(
|
775
|
-
tx_rate_limit_blocks * 12
|
776
|
-
) # 12 seconds per block
|
777
|
-
|
778
|
-
if not wait_for_finalization and not wait_for_inclusion:
|
779
|
-
successful_unstakes += 1
|
780
|
-
continue
|
781
|
-
|
782
|
-
console.print(":white_heavy_check_mark: [green]Finalized[/green]")
|
783
|
-
with console.status(
|
784
|
-
f":satellite: Checking stake balance on: [white]{subtensor}[/white] ..."
|
785
|
-
):
|
786
|
-
new_stake = await subtensor.get_stake_for_coldkey_and_hotkey(
|
787
|
-
coldkey_ss58=wallet.coldkeypub.ss58_address,
|
788
|
-
hotkey_ss58=hotkey_ss58,
|
789
|
-
block_hash=(await subtensor.substrate.get_chain_head()),
|
1092
|
+
else:
|
1093
|
+
new_balance = await subtensor.get_balance(
|
1094
|
+
wallet.coldkeypub.ss58_address
|
790
1095
|
)
|
1096
|
+
console.print(":white_heavy_check_mark: [green]Finalized[/green]")
|
791
1097
|
console.print(
|
792
|
-
"
|
793
|
-
hotkey_ss58, stake_on_uid, new_stake
|
794
|
-
)
|
1098
|
+
f"Balance:\n [blue]{current_wallet_balance}[/blue] :arrow_right: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_balance}"
|
795
1099
|
)
|
796
|
-
successful_unstakes += 1
|
797
1100
|
else:
|
798
|
-
|
799
|
-
|
800
|
-
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
|
805
|
-
|
806
|
-
|
807
|
-
|
808
|
-
f" :arrow_right: [green]{new_balance[wallet.coldkeypub.ss58_address]}[/green]"
|
809
|
-
)
|
810
|
-
return True
|
811
|
-
|
812
|
-
return False
|
813
|
-
|
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
|
+
)
|
814
1111
|
|
815
|
-
|
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
|
+
)
|
816
1160
|
|
817
1161
|
|
818
|
-
async def
|
1162
|
+
async def stake_list(
|
819
1163
|
wallet: Wallet,
|
820
|
-
|
821
|
-
|
822
|
-
|
823
|
-
|
824
|
-
|
1164
|
+
coldkey_ss58: str,
|
1165
|
+
subtensor: "SubtensorInterface",
|
1166
|
+
live: bool = False,
|
1167
|
+
verbose: bool = False,
|
1168
|
+
prompt: bool = False,
|
825
1169
|
):
|
826
|
-
|
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
|
+
)
|
827
1191
|
|
828
|
-
|
829
|
-
|
830
|
-
|
831
|
-
|
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
|
832
1268
|
|
833
|
-
|
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
|
+
)
|
834
1300
|
|
835
|
-
|
836
|
-
|
1301
|
+
# Alpha value cell
|
1302
|
+
alpha_value = Balance.from_rao(int(substake_.stake.rao)).set_unit(netuid)
|
837
1303
|
|
838
|
-
|
1304
|
+
# TAO value cell
|
1305
|
+
tao_value = pool.alpha_to_tao(alpha_value)
|
1306
|
+
total_tao_value += tao_value
|
839
1307
|
|
840
|
-
|
841
|
-
|
842
|
-
|
843
|
-
|
844
|
-
|
845
|
-
get_stakes_from_hotkeys(wallet_, block_hash=block_hash),
|
846
|
-
get_stakes_from_delegates(wallet_),
|
847
|
-
)
|
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
|
848
1313
|
|
849
|
-
|
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']}]"
|
850
1319
|
|
851
|
-
|
852
|
-
|
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
|
+
)
|
853
1328
|
|
854
|
-
|
855
|
-
|
1329
|
+
# TAO locked cell
|
1330
|
+
tao_locked = pool.tao_in
|
856
1331
|
|
857
|
-
|
858
|
-
|
859
|
-
"balance": cold_balance,
|
860
|
-
"accounts": wallet_stake_accounts,
|
861
|
-
}
|
1332
|
+
# Issuance cell
|
1333
|
+
issuance = pool.alpha_out if pool.is_dynamic else tao_locked
|
862
1334
|
|
863
|
-
|
864
|
-
|
865
|
-
|
866
|
-
|
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])}"
|
867
1359
|
|
868
|
-
|
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 = {}
|
869
1399
|
|
870
|
-
|
871
|
-
|
1400
|
+
total_tao_ownership = Balance(0)
|
1401
|
+
total_tao_value = Balance(0)
|
1402
|
+
total_swapped_tao_value = Balance(0)
|
872
1403
|
|
873
|
-
|
874
|
-
|
875
|
-
|
876
|
-
|
877
|
-
|
878
|
-
|
879
|
-
|
880
|
-
|
881
|
-
|
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)}"
|
882
1414
|
)
|
883
|
-
|
884
|
-
|
885
|
-
|
886
|
-
|
887
|
-
|
888
|
-
|
889
|
-
|
890
|
-
|
891
|
-
|
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)}"
|
892
1426
|
)
|
893
|
-
return
|
894
|
-
|
895
|
-
|
896
|
-
|
897
|
-
get_all_neurons_for_pubkey(hk),
|
898
|
-
subtensor.substrate.query(
|
899
|
-
module="SubtensorModule",
|
900
|
-
storage_function="Stake",
|
901
|
-
params=[hk, wallet_.coldkeypub.ss58_address],
|
902
|
-
block_hash=block_hash,
|
903
|
-
),
|
1427
|
+
return (
|
1428
|
+
f"{formatted_value} {unit}{change_text}"
|
1429
|
+
if not unit_first
|
1430
|
+
else f"{unit} {formatted_value}{change_text}"
|
904
1431
|
)
|
905
|
-
emission_ = sum([n.emission for n in neurons]) if neurons else 0.0
|
906
|
-
return emission_, Balance.from_rao(stake) if stake else Balance(0)
|
907
1432
|
|
908
|
-
|
909
|
-
|
910
|
-
|
911
|
-
|
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,
|
912
1441
|
)
|
913
|
-
|
914
|
-
stakes[hot.hotkey.ss58_address] = {
|
915
|
-
"name": hot.hotkey_str,
|
916
|
-
"stake": hotkey_stake,
|
917
|
-
"rate": emission,
|
918
|
-
}
|
919
|
-
return stakes
|
1442
|
+
sorted_substakes = root_stakes + other_stakes
|
920
1443
|
|
921
|
-
|
922
|
-
|
923
|
-
|
924
|
-
|
925
|
-
|
926
|
-
|
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
|
927
1450
|
|
928
|
-
|
929
|
-
|
930
|
-
|
931
|
-
|
932
|
-
|
933
|
-
|
934
|
-
|
935
|
-
|
936
|
-
|
937
|
-
|
938
|
-
|
939
|
-
|
940
|
-
|
941
|
-
|
942
|
-
|
943
|
-
|
944
|
-
|
945
|
-
|
946
|
-
|
947
|
-
|
948
|
-
|
949
|
-
|
950
|
-
|
951
|
-
|
952
|
-
|
953
|
-
|
954
|
-
|
955
|
-
|
956
|
-
|
957
|
-
|
958
|
-
"""
|
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
|
+
}
|
959
1481
|
|
960
|
-
|
961
|
-
|
962
|
-
|
963
|
-
|
964
|
-
|
965
|
-
|
966
|
-
|
967
|
-
|
968
|
-
|
969
|
-
|
970
|
-
|
971
|
-
|
972
|
-
print_error(f"No coldkeypub found for wallet: ({invalid_wallet.name})")
|
973
|
-
else:
|
974
|
-
wallets = [wallet]
|
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
|
+
)
|
975
1494
|
|
976
|
-
|
977
|
-
|
978
|
-
|
979
|
-
|
980
|
-
|
981
|
-
|
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,
|
982
1502
|
)
|
983
|
-
accounts = await get_all_wallet_accounts(block_hash=block_hash_)
|
984
1503
|
|
985
|
-
|
986
|
-
|
987
|
-
|
988
|
-
|
989
|
-
|
990
|
-
|
991
|
-
|
992
|
-
cast(Balance, acc["balance"])
|
993
|
-
rows.append([acc["name"], str(acc["balance"]), "", "", ""])
|
994
|
-
db_rows.append(
|
995
|
-
[acc["name"], float(acc["balance"]), None, None, None, None, 0]
|
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,
|
996
1511
|
)
|
997
|
-
|
998
|
-
|
999
|
-
|
1000
|
-
|
1001
|
-
|
1002
|
-
|
1003
|
-
rows.append(
|
1004
|
-
[
|
1005
|
-
"",
|
1006
|
-
"",
|
1007
|
-
account_display_name,
|
1008
|
-
key,
|
1009
|
-
str(value["stake"]),
|
1010
|
-
str(value["rate"]),
|
1011
|
-
]
|
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
|
1012
1518
|
)
|
1013
|
-
|
1014
|
-
|
1015
|
-
|
1016
|
-
|
1017
|
-
|
1018
|
-
|
1019
|
-
|
1020
|
-
|
1021
|
-
|
1022
|
-
|
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}%)"
|
1023
1533
|
)
|
1024
|
-
|
1025
|
-
|
1026
|
-
|
1027
|
-
|
1028
|
-
|
1029
|
-
|
1030
|
-
|
1031
|
-
|
1032
|
-
|
1033
|
-
|
1034
|
-
"stakeshow",
|
1035
|
-
[
|
1036
|
-
("COLDKEY", "TEXT"),
|
1037
|
-
("BALANCE", "REAL"),
|
1038
|
-
("ACCOUNT", "TEXT"),
|
1039
|
-
("STAKE", "REAL"),
|
1040
|
-
("RATE", "REAL"),
|
1041
|
-
("HOTKEY", "TEXT"),
|
1042
|
-
("CHILD", "INTEGER"),
|
1043
|
-
],
|
1044
|
-
db_rows,
|
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,
|
1045
1544
|
)
|
1046
|
-
|
1047
|
-
|
1048
|
-
|
1049
|
-
metadata = get_metadata_table("stakeshow")
|
1050
|
-
rows = json.loads(metadata["rows"])
|
1051
|
-
except sqlite3.OperationalError:
|
1052
|
-
err_console.print(
|
1053
|
-
"[red]Error[/red] Unable to retrieve table data. This is usually caused by attempting to use "
|
1054
|
-
"`--reuse-last` before running the command a first time. In rare cases, this could also be due to "
|
1055
|
-
"a corrupted database. Re-run the command (do not use `--reuse-last`) and see if that resolves your "
|
1056
|
-
"issue."
|
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])}"
|
1057
1548
|
)
|
1058
|
-
|
1059
|
-
|
1060
|
-
|
1061
|
-
|
1062
|
-
|
1063
|
-
|
1064
|
-
|
1065
|
-
|
1066
|
-
|
1067
|
-
|
1068
|
-
|
1069
|
-
|
1070
|
-
|
1071
|
-
|
1072
|
-
|
1073
|
-
|
1074
|
-
|
1075
|
-
|
1076
|
-
|
1077
|
-
|
1078
|
-
|
1079
|
-
|
1080
|
-
|
1081
|
-
),
|
1082
|
-
title=f"[underline dark_orange]Stake Show[/underline dark_orange]\n[dark_orange]Network: {subtensor.network}\n",
|
1083
|
-
show_footer=True,
|
1084
|
-
show_edge=False,
|
1085
|
-
expand=False,
|
1086
|
-
border_style="bright_black",
|
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,
|
1087
1572
|
)
|
1088
1573
|
|
1089
|
-
for
|
1090
|
-
is_last_row = i + 1 == len(rows)
|
1574
|
+
for row in rows:
|
1091
1575
|
table.add_row(*row)
|
1092
1576
|
|
1093
|
-
|
1094
|
-
if is_last_row or (rows[i + 1][0] != ""):
|
1095
|
-
table.add_row(end_section=True)
|
1096
|
-
console.print(table)
|
1577
|
+
return table, current_data
|
1097
1578
|
|
1098
|
-
|
1099
|
-
|
1100
|
-
|
1101
|
-
|
1102
|
-
|
1103
|
-
|
1104
|
-
|
1105
|
-
{
|
1106
|
-
"title": "Balance",
|
1107
|
-
"field": "BALANCE",
|
1108
|
-
"formatter": "money",
|
1109
|
-
"formatterParams": {"symbol": "τ", "precision": 5},
|
1110
|
-
},
|
1111
|
-
{
|
1112
|
-
"title": "Account",
|
1113
|
-
"field": "ACCOUNT",
|
1114
|
-
"width": 425,
|
1115
|
-
},
|
1116
|
-
{
|
1117
|
-
"title": "Stake",
|
1118
|
-
"field": "STAKE",
|
1119
|
-
"formatter": "money",
|
1120
|
-
"formatterParams": {"symbol": "τ", "precision": 5},
|
1121
|
-
},
|
1122
|
-
{
|
1123
|
-
"title": "Daily Rate",
|
1124
|
-
"field": "RATE",
|
1125
|
-
"formatter": "money",
|
1126
|
-
"formatterParams": {"symbol": "τ", "precision": 5},
|
1127
|
-
},
|
1128
|
-
{
|
1129
|
-
"title": "Hotkey",
|
1130
|
-
"field": "HOTKEY",
|
1131
|
-
"width": 425,
|
1132
|
-
},
|
1133
|
-
],
|
1134
|
-
0,
|
1135
|
-
)
|
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)
|
1136
1586
|
|
1587
|
+
# Iterate over substakes and aggregate them by hotkey.
|
1588
|
+
hotkeys_to_substakes: dict[str, list[StakeInfo]] = {}
|
1137
1589
|
|
1138
|
-
|
1139
|
-
|
1140
|
-
|
1141
|
-
|
1142
|
-
|
1143
|
-
|
1144
|
-
|
1145
|
-
exclude_hotkeys: list[str],
|
1146
|
-
all_hotkeys: bool,
|
1147
|
-
prompt: bool,
|
1148
|
-
hotkey_ss58: Optional[str] = None,
|
1149
|
-
) -> None:
|
1150
|
-
"""Stake token of amount to hotkey(s)."""
|
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)
|
1151
1597
|
|
1152
|
-
|
1153
|
-
|
1598
|
+
if not hotkeys_to_substakes:
|
1599
|
+
print_error(f"No stakes found for coldkey ss58: ({coldkey_address})")
|
1600
|
+
raise typer.Exit()
|
1154
1601
|
|
1155
|
-
|
1156
|
-
|
1157
|
-
|
1158
|
-
|
1159
|
-
|
1160
|
-
|
1161
|
-
|
1162
|
-
|
1163
|
-
|
1164
|
-
|
1165
|
-
|
1166
|
-
all_hotkeys_: list[Wallet] = get_hotkey_wallets_for_wallet(wallet=wallet)
|
1167
|
-
# Get the hotkeys to exclude. (d)efault to no exclusions.
|
1168
|
-
# Exclude hotkeys that are specified.
|
1169
|
-
hotkeys_to_stake_to = [
|
1170
|
-
(wallet.hotkey_str, wallet.hotkey.ss58_address)
|
1171
|
-
for wallet in all_hotkeys_
|
1172
|
-
if wallet.hotkey_str not in exclude_hotkeys
|
1173
|
-
and wallet.hotkey.ss58_address not in exclude_hotkeys
|
1174
|
-
] # definitely wallets
|
1175
|
-
|
1176
|
-
elif include_hotkeys:
|
1177
|
-
print_verbose("Staking to only included hotkeys")
|
1178
|
-
# Stake to specific hotkeys.
|
1179
|
-
for hotkey_ss58_or_hotkey_name in include_hotkeys:
|
1180
|
-
if is_valid_ss58_address(hotkey_ss58_or_hotkey_name):
|
1181
|
-
# If the hotkey is a valid ss58 address, we add it to the list.
|
1182
|
-
hotkeys_to_stake_to.append((None, hotkey_ss58_or_hotkey_name))
|
1183
|
-
else:
|
1184
|
-
# If the hotkey is not a valid ss58 address, we assume it is a hotkey name.
|
1185
|
-
# We then get the hotkey from the wallet and add it to the list.
|
1186
|
-
wallet_ = Wallet(
|
1187
|
-
path=wallet.path,
|
1188
|
-
name=wallet.name,
|
1189
|
-
hotkey=hotkey_ss58_or_hotkey_name,
|
1190
|
-
)
|
1191
|
-
hotkeys_to_stake_to.append(
|
1192
|
-
(wallet_.hotkey_str, wallet_.hotkey.ss58_address)
|
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
|
1193
1613
|
)
|
1194
|
-
|
1195
|
-
# Only config.wallet.hotkey is specified.
|
1196
|
-
# so we stake to that single hotkey.
|
1197
|
-
print_verbose(
|
1198
|
-
f"Staking to hotkey: ({wallet.hotkey_str}) in wallet: ({wallet.name})"
|
1199
|
-
)
|
1200
|
-
assert wallet.hotkey is not None
|
1201
|
-
hotkey_ss58_or_name = wallet.hotkey.ss58_address
|
1202
|
-
hotkeys_to_stake_to = [(None, hotkey_ss58_or_name)]
|
1614
|
+
console.print(f"[{idx}] [{COLOR_PALETTE['GENERAL']['HEADER']}]{name}")
|
1203
1615
|
|
1204
|
-
|
1205
|
-
|
1206
|
-
|
1207
|
-
wallet_balance_: dict[str, Balance] = await subtensor.get_balance(
|
1208
|
-
wallet.coldkeypub.ss58_address
|
1209
|
-
)
|
1210
|
-
block_hash = subtensor.substrate.last_block_hash
|
1211
|
-
wallet_balance: Balance = wallet_balance_[wallet.coldkeypub.ss58_address]
|
1212
|
-
old_balance = copy.copy(wallet_balance)
|
1213
|
-
final_hotkeys: list[tuple[Optional[str], str]] = []
|
1214
|
-
final_amounts: list[Union[float, Balance]] = []
|
1215
|
-
hotkey: tuple[Optional[str], str] # (hotkey_name (or None), hotkey_ss58)
|
1216
|
-
|
1217
|
-
print_verbose("Checking if hotkeys are registered")
|
1218
|
-
registered_ = asyncio.gather(
|
1219
|
-
*[is_hotkey_registered_any(h[1], block_hash) for h in hotkeys_to_stake_to]
|
1220
|
-
)
|
1221
|
-
if max_stake:
|
1222
|
-
hotkey_stakes_ = asyncio.gather(
|
1223
|
-
*[
|
1224
|
-
subtensor.get_stake_for_coldkey_and_hotkey(
|
1225
|
-
hotkey_ss58=h[1],
|
1226
|
-
coldkey_ss58=wallet.coldkeypub.ss58_address,
|
1227
|
-
block_hash=block_hash,
|
1228
|
-
)
|
1229
|
-
for h in hotkeys_to_stake_to
|
1230
|
-
]
|
1616
|
+
selected_idx = Prompt.ask(
|
1617
|
+
"Enter hotkey index",
|
1618
|
+
choices=[str(i) for i in range(len(hotkeys_to_substakes))],
|
1231
1619
|
)
|
1620
|
+
selected_hotkey = list(hotkeys_to_substakes.keys())[int(selected_idx)]
|
1621
|
+
selected_stakes = hotkeys_to_substakes[selected_hotkey]
|
1232
1622
|
else:
|
1623
|
+
selected_hotkey = list(hotkeys_to_substakes.keys())[0]
|
1624
|
+
selected_stakes = hotkeys_to_substakes[selected_hotkey]
|
1233
1625
|
|
1234
|
-
|
1235
|
-
|
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
|
+
)
|
1236
1631
|
|
1237
|
-
|
1238
|
-
|
1239
|
-
|
1240
|
-
|
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
|
+
]
|
1241
1659
|
|
1242
|
-
|
1243
|
-
|
1244
|
-
|
1245
|
-
|
1246
|
-
|
1247
|
-
|
1248
|
-
|
1249
|
-
|
1250
|
-
f"[red]Hotkey [bold]{hotkey[1]}[/bold] is not registered. Aborting.[/red]"
|
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)
|
1251
1668
|
)
|
1252
|
-
|
1253
|
-
|
1254
|
-
|
1255
|
-
|
1256
|
-
|
1669
|
+
|
1670
|
+
table, current_data = create_live_table(
|
1671
|
+
selected_stakes,
|
1672
|
+
registered_delegate_info,
|
1673
|
+
dynamic_info,
|
1674
|
+
hotkey_name,
|
1675
|
+
previous_data,
|
1257
1676
|
)
|
1258
|
-
continue
|
1259
|
-
|
1260
|
-
stake_amount_tao: float = amount
|
1261
|
-
if max_stake:
|
1262
|
-
stake_amount_tao = max_stake - hotkey_stake.tao
|
1263
|
-
|
1264
|
-
# If the max_stake is greater than the current wallet balance, stake the entire balance.
|
1265
|
-
stake_amount_tao = min(stake_amount_tao, wallet_balance.tao)
|
1266
|
-
if (
|
1267
|
-
stake_amount_tao <= 0.00001
|
1268
|
-
): # Threshold because of fees, might create a loop otherwise
|
1269
|
-
# Skip hotkey if max_stake is less than current stake.
|
1270
|
-
continue
|
1271
|
-
wallet_balance = Balance.from_tao(wallet_balance.tao - stake_amount_tao)
|
1272
|
-
|
1273
|
-
if wallet_balance.tao < 0:
|
1274
|
-
# No more balance to stake.
|
1275
|
-
break
|
1276
1677
|
|
1277
|
-
|
1278
|
-
|
1678
|
+
previous_data = current_data
|
1679
|
+
progress.reset(progress_task)
|
1680
|
+
start_time = asyncio.get_event_loop().time()
|
1279
1681
|
|
1280
|
-
|
1281
|
-
|
1282
|
-
|
1283
|
-
|
1284
|
-
|
1285
|
-
raise ValueError
|
1286
|
-
|
1287
|
-
if len(final_hotkeys) == 1:
|
1288
|
-
# do regular stake
|
1289
|
-
await add_stake_extrinsic(
|
1290
|
-
subtensor,
|
1291
|
-
wallet=wallet,
|
1292
|
-
old_balance=old_balance,
|
1293
|
-
hotkey_ss58=final_hotkeys[0][1],
|
1294
|
-
amount=None if stake_all else final_amounts[0],
|
1295
|
-
wait_for_inclusion=True,
|
1296
|
-
prompt=prompt,
|
1297
|
-
)
|
1298
|
-
else:
|
1299
|
-
await add_stake_multiple_extrinsic(
|
1300
|
-
subtensor,
|
1301
|
-
wallet=wallet,
|
1302
|
-
old_balance=old_balance,
|
1303
|
-
hotkey_ss58s=[hotkey_ss58 for _, hotkey_ss58 in final_hotkeys],
|
1304
|
-
amounts=None if stake_all else final_amounts,
|
1305
|
-
wait_for_inclusion=True,
|
1306
|
-
prompt=prompt,
|
1307
|
-
)
|
1308
|
-
except ValueError:
|
1309
|
-
pass
|
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
|
+
)
|
1310
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)
|
1311
1691
|
|
1312
|
-
|
1313
|
-
|
1314
|
-
|
1315
|
-
|
1316
|
-
|
1317
|
-
|
1318
|
-
|
1319
|
-
|
1320
|
-
|
1321
|
-
|
1322
|
-
prompt: bool,
|
1323
|
-
):
|
1324
|
-
"""Unstake token of amount from hotkey(s)."""
|
1325
|
-
|
1326
|
-
# Get the hotkey_names (if any) and the hotkey_ss58s.
|
1327
|
-
hotkeys_to_unstake_from: list[tuple[Optional[str], str]] = []
|
1328
|
-
if hotkey_ss58_address:
|
1329
|
-
print_verbose(f"Unstaking from ss58 ({hotkey_ss58_address})")
|
1330
|
-
# Unstake to specific hotkey.
|
1331
|
-
hotkeys_to_unstake_from = [(None, hotkey_ss58_address)]
|
1332
|
-
elif all_hotkeys:
|
1333
|
-
print_verbose("Unstaking from all hotkeys")
|
1334
|
-
# Unstake to all hotkeys.
|
1335
|
-
all_hotkeys_: list[Wallet] = get_hotkey_wallets_for_wallet(wallet=wallet)
|
1336
|
-
# Exclude hotkeys that are specified.
|
1337
|
-
hotkeys_to_unstake_from = [
|
1338
|
-
(wallet.hotkey_str, wallet.hotkey.ss58_address)
|
1339
|
-
for wallet in all_hotkeys_
|
1340
|
-
if wallet.hotkey_str not in exclude_hotkeys
|
1341
|
-
and wallet.hotkey.ss58_address not in hotkeys_to_unstake_from
|
1342
|
-
] # definitely wallets
|
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
|
1343
1702
|
|
1344
|
-
elif include_hotkeys:
|
1345
|
-
print_verbose("Unstaking from included hotkeys")
|
1346
|
-
# Unstake to specific hotkeys.
|
1347
|
-
for hotkey_ss58_or_hotkey_name in include_hotkeys:
|
1348
|
-
if is_valid_ss58_address(hotkey_ss58_or_hotkey_name):
|
1349
|
-
# If the hotkey is a valid ss58 address, we add it to the list.
|
1350
|
-
hotkeys_to_unstake_from.append((None, hotkey_ss58_or_hotkey_name))
|
1351
|
-
else:
|
1352
|
-
# If the hotkey is not a valid ss58 address, we assume it is a hotkey name.
|
1353
|
-
# We then get the hotkey from the wallet and add it to the list.
|
1354
|
-
wallet_ = Wallet(
|
1355
|
-
name=wallet.name,
|
1356
|
-
path=wallet.path,
|
1357
|
-
hotkey=hotkey_ss58_or_hotkey_name,
|
1358
|
-
)
|
1359
|
-
hotkeys_to_unstake_from.append(
|
1360
|
-
(wallet_.hotkey_str, wallet_.hotkey.ss58_address)
|
1361
|
-
)
|
1362
1703
|
else:
|
1363
|
-
#
|
1364
|
-
|
1365
|
-
|
1366
|
-
|
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
|
1367
1723
|
)
|
1368
|
-
|
1369
|
-
|
1370
|
-
|
1371
|
-
|
1372
|
-
|
1373
|
-
|
1374
|
-
|
1375
|
-
|
1376
|
-
f"
|
1377
|
-
|
1378
|
-
|
1379
|
-
|
1380
|
-
|
1381
|
-
|
1382
|
-
|
1383
|
-
|
1384
|
-
|
1385
|
-
block_hash=block_hash,
|
1386
|
-
)
|
1387
|
-
for hotkey in hotkeys_to_unstake_from
|
1388
|
-
]
|
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})"
|
1389
1741
|
)
|
1390
|
-
|
1391
|
-
|
1392
|
-
|
1393
|
-
|
1394
|
-
|
1395
|
-
|
1396
|
-
|
1397
|
-
|
1398
|
-
|
1399
|
-
|
1400
|
-
|
1401
|
-
|
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
|
+
)
|
1402
1756
|
else:
|
1403
|
-
|
1404
|
-
|
1405
|
-
|
1406
|
-
|
1407
|
-
|
1408
|
-
|
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
|
+
)
|
1409
1764
|
|
1410
|
-
|
1411
|
-
|
1412
|
-
|
1413
|
-
|
1414
|
-
|
1415
|
-
|
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
|
+
]
|
1416
1812
|
|
1417
|
-
|
1418
|
-
|
1419
|
-
|
1420
|
-
|
1421
|
-
+ "".join(
|
1422
|
-
[
|
1423
|
-
f" [bold white]- {hotkey[0] + ':' if hotkey[0] else ''}{hotkey[1]}: "
|
1424
|
-
f"{f'{amount} {Balance.unit}' if amount else 'All'}[/bold white]\n"
|
1425
|
-
for hotkey, amount in zip(final_hotkeys, final_amounts)
|
1426
|
-
]
|
1813
|
+
description_table.add_column(
|
1814
|
+
"Field",
|
1815
|
+
no_wrap=True,
|
1816
|
+
style="bold tan",
|
1427
1817
|
)
|
1428
|
-
|
1429
|
-
|
1430
|
-
|
1431
|
-
|
1432
|
-
await unstake_extrinsic(
|
1433
|
-
subtensor,
|
1434
|
-
wallet=wallet,
|
1435
|
-
hotkey_ss58=final_hotkeys[0][1],
|
1436
|
-
amount=None if unstake_all else final_amounts[0],
|
1437
|
-
wait_for_inclusion=True,
|
1438
|
-
prompt=prompt,
|
1439
|
-
)
|
1440
|
-
else:
|
1441
|
-
await unstake_multiple_extrinsic(
|
1442
|
-
subtensor,
|
1443
|
-
wallet=wallet,
|
1444
|
-
hotkey_ss58s=[hotkey_ss58 for _, hotkey_ss58 in final_hotkeys],
|
1445
|
-
amounts=None if unstake_all else final_amounts,
|
1446
|
-
wait_for_inclusion=True,
|
1447
|
-
prompt=prompt,
|
1448
|
-
)
|
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)
|