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