bittensor-cli 8.4.3__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 -1392
- 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 +399 -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.3.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 -1752
- bittensor_cli/src/commands/stake/stake.py +0 -1448
- 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.0.dist-info}/WHEEL +0 -0
- {bittensor_cli-8.4.3.dist-info → bittensor_cli-9.0.0.dist-info}/entry_points.txt +0 -0
- {bittensor_cli-8.4.3.dist-info → bittensor_cli-9.0.0.dist-info}/top_level.txt +0 -0
@@ -1,1448 +0,0 @@
|
|
1
|
-
import asyncio
|
2
|
-
import copy
|
3
|
-
import json
|
4
|
-
import sqlite3
|
5
|
-
from contextlib import suppress
|
6
|
-
|
7
|
-
from typing import TYPE_CHECKING, Optional, Sequence, Union, cast
|
8
|
-
|
9
|
-
from bittensor_wallet import Wallet
|
10
|
-
from rich.prompt import Confirm
|
11
|
-
from rich.table import Table, Column
|
12
|
-
import typer
|
13
|
-
|
14
|
-
|
15
|
-
from bittensor_cli.src.bittensor.balances import Balance
|
16
|
-
from bittensor_cli.src.bittensor.utils import (
|
17
|
-
console,
|
18
|
-
create_table,
|
19
|
-
err_console,
|
20
|
-
print_verbose,
|
21
|
-
print_error,
|
22
|
-
get_coldkey_wallets_for_path,
|
23
|
-
get_hotkey_wallets_for_wallet,
|
24
|
-
is_valid_ss58_address,
|
25
|
-
get_metadata_table,
|
26
|
-
update_metadata_table,
|
27
|
-
render_tree,
|
28
|
-
u16_normalized_float,
|
29
|
-
validate_coldkey_presence,
|
30
|
-
unlock_key,
|
31
|
-
)
|
32
|
-
|
33
|
-
if TYPE_CHECKING:
|
34
|
-
from bittensor_cli.src.bittensor.subtensor_interface import SubtensorInterface
|
35
|
-
|
36
|
-
|
37
|
-
# Helpers and Extrinsics
|
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(
|
53
|
-
subtensor: "SubtensorInterface",
|
54
|
-
sb: Balance,
|
55
|
-
block_hash: str,
|
56
|
-
min_req_stake: Optional[Balance] = None,
|
57
|
-
) -> tuple[bool, Balance]:
|
58
|
-
"""
|
59
|
-
Checks if the new stake balance will be above the minimum required stake threshold.
|
60
|
-
|
61
|
-
:param sb: the balance to check for threshold limits.
|
62
|
-
|
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
|
-
if not min_req_stake:
|
69
|
-
min_req_stake = await _get_threshold_amount(subtensor, block_hash)
|
70
|
-
|
71
|
-
if min_req_stake > sb:
|
72
|
-
return False, min_req_stake
|
73
|
-
else:
|
74
|
-
return True, min_req_stake
|
75
|
-
|
76
|
-
|
77
|
-
async def add_stake_extrinsic(
|
78
|
-
subtensor: "SubtensorInterface",
|
79
|
-
wallet: Wallet,
|
80
|
-
old_balance: Balance,
|
81
|
-
hotkey_ss58: Optional[str] = None,
|
82
|
-
amount: Optional[Balance] = None,
|
83
|
-
wait_for_inclusion: bool = True,
|
84
|
-
wait_for_finalization: bool = False,
|
85
|
-
prompt: bool = False,
|
86
|
-
) -> bool:
|
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
|
-
"""
|
104
|
-
|
105
|
-
# Decrypt keys,
|
106
|
-
if not unlock_key(wallet).success:
|
107
|
-
return False
|
108
|
-
|
109
|
-
# Default to wallet's own hotkey if the value is not passed.
|
110
|
-
if hotkey_ss58 is None:
|
111
|
-
hotkey_ss58 = wallet.hotkey.ss58_address
|
112
|
-
|
113
|
-
# Flag to indicate if we are using the wallet's own hotkey.
|
114
|
-
own_hotkey: bool
|
115
|
-
|
116
|
-
with console.status(
|
117
|
-
f":satellite: Syncing with chain: [white]{subtensor}[/white] ...",
|
118
|
-
spinner="aesthetic",
|
119
|
-
) as status:
|
120
|
-
block_hash = await subtensor.substrate.get_chain_head()
|
121
|
-
# Get hotkey owner
|
122
|
-
print_verbose("Confirming hotkey owner", status)
|
123
|
-
hotkey_owner = await subtensor.get_hotkey_owner(
|
124
|
-
hotkey_ss58=hotkey_ss58, block_hash=block_hash
|
125
|
-
)
|
126
|
-
own_hotkey = wallet.coldkeypub.ss58_address == hotkey_owner
|
127
|
-
if not own_hotkey:
|
128
|
-
# This is not the wallet's own hotkey, so we are delegating.
|
129
|
-
if not await subtensor.is_hotkey_delegate(
|
130
|
-
hotkey_ss58, block_hash=block_hash
|
131
|
-
):
|
132
|
-
err_console.print(
|
133
|
-
f"Hotkey {hotkey_ss58} is not a delegate on the chain."
|
134
|
-
)
|
135
|
-
return False
|
136
|
-
|
137
|
-
# Get hotkey take
|
138
|
-
hk_result = await subtensor.substrate.query(
|
139
|
-
module="SubtensorModule",
|
140
|
-
storage_function="Delegates",
|
141
|
-
params=[hotkey_ss58],
|
142
|
-
block_hash=block_hash,
|
143
|
-
)
|
144
|
-
hotkey_take = u16_normalized_float(hk_result or 0)
|
145
|
-
else:
|
146
|
-
hotkey_take = None
|
147
|
-
|
148
|
-
# Get current stake
|
149
|
-
print_verbose("Fetching current stake", status)
|
150
|
-
old_stake = await subtensor.get_stake_for_coldkey_and_hotkey(
|
151
|
-
coldkey_ss58=wallet.coldkeypub.ss58_address,
|
152
|
-
hotkey_ss58=hotkey_ss58,
|
153
|
-
block_hash=block_hash,
|
154
|
-
)
|
155
|
-
|
156
|
-
print_verbose("Fetching existential deposit", status)
|
157
|
-
# Grab the existential deposit.
|
158
|
-
existential_deposit = await subtensor.get_existential_deposit()
|
159
|
-
|
160
|
-
# Convert to bittensor.Balance
|
161
|
-
if amount is None:
|
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]"
|
195
|
-
)
|
196
|
-
return False
|
197
|
-
|
198
|
-
# Ask before moving on.
|
199
|
-
if prompt:
|
200
|
-
if not own_hotkey:
|
201
|
-
# We are delegating.
|
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
|
218
|
-
|
219
|
-
with console.status(
|
220
|
-
f":satellite: Staking to: [bold white]{subtensor}[/bold white] ...",
|
221
|
-
spinner="earth",
|
222
|
-
):
|
223
|
-
call = await subtensor.substrate.compose_call(
|
224
|
-
call_module="SubtensorModule",
|
225
|
-
call_function="add_stake",
|
226
|
-
call_params={"hotkey": hotkey_ss58, "amount_staked": staking_balance.rao},
|
227
|
-
)
|
228
|
-
staking_response, err_msg = await subtensor.sign_and_send_extrinsic(
|
229
|
-
call, wallet, wait_for_inclusion, wait_for_finalization
|
230
|
-
)
|
231
|
-
if staking_response is True: # If we successfully staked.
|
232
|
-
# We only wait here if we expect finalization.
|
233
|
-
if not wait_for_finalization and not wait_for_inclusion:
|
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]"
|
256
|
-
)
|
257
|
-
console.print(
|
258
|
-
f"Stake:\n"
|
259
|
-
f"\t[blue]{old_stake}[/blue] :arrow_right: [green]{new_stake}[/green]"
|
260
|
-
)
|
261
|
-
return True
|
262
|
-
else:
|
263
|
-
err_console.print(f":cross_mark: [red]Failed[/red]: {err_msg}")
|
264
|
-
return False
|
265
|
-
|
266
|
-
|
267
|
-
async def add_stake_multiple_extrinsic(
|
268
|
-
subtensor: "SubtensorInterface",
|
269
|
-
wallet: Wallet,
|
270
|
-
old_balance: Balance,
|
271
|
-
hotkey_ss58s: list[str],
|
272
|
-
amounts: Optional[list[Balance]] = None,
|
273
|
-
wait_for_inclusion: bool = True,
|
274
|
-
wait_for_finalization: bool = False,
|
275
|
-
prompt: bool = False,
|
276
|
-
) -> bool:
|
277
|
-
"""Adds stake to each ``hotkey_ss58`` in the list, using each amount, from a common coldkey.
|
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
|
308
|
-
|
309
|
-
# Decrypt coldkey.
|
310
|
-
if not unlock_key(wallet).success:
|
311
|
-
return False
|
312
|
-
|
313
|
-
with console.status(
|
314
|
-
f":satellite: Syncing with chain: [white]{subtensor}[/white] ..."
|
315
|
-
):
|
316
|
-
block_hash = await subtensor.substrate.get_chain_head()
|
317
|
-
old_stakes = await asyncio.gather(
|
318
|
-
*[
|
319
|
-
subtensor.get_stake_for_coldkey_and_hotkey(
|
320
|
-
hk, wallet.coldkeypub.ss58_address, block_hash=block_hash
|
321
|
-
)
|
322
|
-
for hk in hotkey_ss58s
|
323
|
-
]
|
324
|
-
)
|
325
|
-
|
326
|
-
# Remove existential balance to keep key alive.
|
327
|
-
## Keys must maintain a balance of at least 1000 rao to stay alive.
|
328
|
-
total_staking_rao = sum(
|
329
|
-
[amount.rao if amount is not None else 0 for amount in new_amounts]
|
330
|
-
)
|
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
|
-
|
363
|
-
# Check enough to stake
|
364
|
-
if staking_balance > old_balance:
|
365
|
-
err_console.print(
|
366
|
-
f":cross_mark: [red]Not enough balance[/red]:"
|
367
|
-
f" [green]{old_balance}[/green] to stake: [blue]{staking_balance}[/blue]"
|
368
|
-
f" from coldkey: [white]{wallet.name}[/white]"
|
369
|
-
)
|
370
|
-
continue
|
371
|
-
|
372
|
-
# Ask before moving on.
|
373
|
-
if prompt:
|
374
|
-
if not Confirm.ask(
|
375
|
-
f"Do you want to stake:\n"
|
376
|
-
f"\t[bold white]amount: {staking_balance}\n"
|
377
|
-
f"\thotkey: {wallet.hotkey_str}[/bold white ]?"
|
378
|
-
):
|
379
|
-
continue
|
380
|
-
|
381
|
-
call = await subtensor.substrate.compose_call(
|
382
|
-
call_module="SubtensorModule",
|
383
|
-
call_function="add_stake",
|
384
|
-
call_params={"hotkey": hotkey_ss58, "amount_staked": staking_balance.rao},
|
385
|
-
)
|
386
|
-
staking_response, err_msg = await subtensor.sign_and_send_extrinsic(
|
387
|
-
call, wallet, wait_for_inclusion, wait_for_finalization
|
388
|
-
)
|
389
|
-
|
390
|
-
if staking_response is True: # If we successfully staked.
|
391
|
-
# We only wait here if we expect finalization.
|
392
|
-
|
393
|
-
if idx < len(hotkey_ss58s) - 1:
|
394
|
-
# Wait for tx rate limit.
|
395
|
-
tx_query = await subtensor.substrate.query(
|
396
|
-
module="SubtensorModule",
|
397
|
-
storage_function="TxRateLimit",
|
398
|
-
block_hash=block_hash,
|
399
|
-
)
|
400
|
-
tx_rate_limit_blocks: int = tx_query
|
401
|
-
if tx_rate_limit_blocks > 0:
|
402
|
-
with console.status(
|
403
|
-
f":hourglass: [yellow]Waiting for tx rate limit:"
|
404
|
-
f" [white]{tx_rate_limit_blocks}[/white] blocks[/yellow]"
|
405
|
-
):
|
406
|
-
await asyncio.sleep(
|
407
|
-
tx_rate_limit_blocks * 12
|
408
|
-
) # 12 seconds per block
|
409
|
-
|
410
|
-
if not wait_for_finalization and not wait_for_inclusion:
|
411
|
-
old_balance -= staking_balance
|
412
|
-
successful_stakes += 1
|
413
|
-
if staking_all:
|
414
|
-
# If staked all, no need to continue
|
415
|
-
break
|
416
|
-
|
417
|
-
continue
|
418
|
-
|
419
|
-
console.print(":white_heavy_check_mark: [green]Finalized[/green]")
|
420
|
-
|
421
|
-
new_block_hash = await subtensor.substrate.get_chain_head()
|
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
|
436
|
-
)
|
437
|
-
)
|
438
|
-
old_balance = new_balance
|
439
|
-
successful_stakes += 1
|
440
|
-
if staking_all:
|
441
|
-
# If staked all, no need to continue
|
442
|
-
break
|
443
|
-
|
444
|
-
else:
|
445
|
-
err_console.print(f":cross_mark: [red]Failed[/red]: {err_msg}")
|
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
|
460
|
-
|
461
|
-
return False
|
462
|
-
|
463
|
-
|
464
|
-
async def unstake_extrinsic(
|
465
|
-
subtensor: "SubtensorInterface",
|
466
|
-
wallet: Wallet,
|
467
|
-
hotkey_ss58: Optional[str] = None,
|
468
|
-
amount: Optional[Balance] = None,
|
469
|
-
wait_for_inclusion: bool = True,
|
470
|
-
wait_for_finalization: bool = False,
|
471
|
-
prompt: bool = False,
|
472
|
-
) -> bool:
|
473
|
-
"""Removes stake into the wallet coldkey from the specified hotkey ``uid``.
|
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.
|
494
|
-
|
495
|
-
with console.status(
|
496
|
-
f":satellite: Syncing with chain: [white]{subtensor}[/white] ...",
|
497
|
-
spinner="aesthetic",
|
498
|
-
) as status:
|
499
|
-
print_verbose("Fetching balance and stake", status)
|
500
|
-
block_hash = await subtensor.substrate.get_chain_head()
|
501
|
-
old_balance, old_stake, hotkey_owner = await asyncio.gather(
|
502
|
-
subtensor.get_balance(
|
503
|
-
wallet.coldkeypub.ss58_address, block_hash=block_hash
|
504
|
-
),
|
505
|
-
subtensor.get_stake_for_coldkey_and_hotkey(
|
506
|
-
coldkey_ss58=wallet.coldkeypub.ss58_address,
|
507
|
-
hotkey_ss58=hotkey_ss58,
|
508
|
-
block_hash=block_hash,
|
509
|
-
),
|
510
|
-
subtensor.get_hotkey_owner(hotkey_ss58, block_hash),
|
511
|
-
)
|
512
|
-
|
513
|
-
own_hotkey: bool = wallet.coldkeypub.ss58_address == hotkey_owner
|
514
|
-
|
515
|
-
# Convert to bittensor.Balance
|
516
|
-
if amount is None:
|
517
|
-
# Unstake it all.
|
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
|
532
|
-
|
533
|
-
print_verbose("Fetching threshold amount")
|
534
|
-
# If nomination stake, check threshold.
|
535
|
-
if not own_hotkey and not await _check_threshold_amount(
|
536
|
-
subtensor=subtensor,
|
537
|
-
sb=(stake_on_uid - unstaking_balance),
|
538
|
-
block_hash=block_hash,
|
539
|
-
):
|
540
|
-
console.print(
|
541
|
-
":warning: [yellow]This action will unstake the entire staked balance![/yellow]"
|
542
|
-
)
|
543
|
-
unstaking_balance = stake_on_uid
|
544
|
-
|
545
|
-
# Ask before moving on.
|
546
|
-
if prompt:
|
547
|
-
if not Confirm.ask(
|
548
|
-
f"Do you want to unstake:\n"
|
549
|
-
f"[bold white]\tamount: {unstaking_balance}\n"
|
550
|
-
f"\thotkey: {wallet.hotkey_str}[/bold white ]?"
|
551
|
-
):
|
552
|
-
return False
|
553
|
-
|
554
|
-
with console.status(
|
555
|
-
f":satellite: Unstaking from chain: [white]{subtensor}[/white] ...",
|
556
|
-
spinner="earth",
|
557
|
-
):
|
558
|
-
call = await subtensor.substrate.compose_call(
|
559
|
-
call_module="SubtensorModule",
|
560
|
-
call_function="remove_stake",
|
561
|
-
call_params={
|
562
|
-
"hotkey": hotkey_ss58,
|
563
|
-
"amount_unstaked": unstaking_balance.rao,
|
564
|
-
},
|
565
|
-
)
|
566
|
-
staking_response, err_msg = await subtensor.sign_and_send_extrinsic(
|
567
|
-
call, wallet, wait_for_inclusion, wait_for_finalization
|
568
|
-
)
|
569
|
-
|
570
|
-
if staking_response is True: # If we successfully unstaked.
|
571
|
-
# We only wait here if we expect finalization.
|
572
|
-
if not wait_for_finalization and not wait_for_inclusion:
|
573
|
-
return True
|
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]"
|
592
|
-
)
|
593
|
-
console.print(
|
594
|
-
f"Stake:\n [blue]{old_stake}[/blue] :arrow_right: [green]{new_stake}[/green]"
|
595
|
-
)
|
596
|
-
return True
|
597
|
-
else:
|
598
|
-
err_console.print(f":cross_mark: [red]Failed[/red]: {err_msg}")
|
599
|
-
return False
|
600
|
-
|
601
|
-
|
602
|
-
async def unstake_multiple_extrinsic(
|
603
|
-
subtensor: "SubtensorInterface",
|
604
|
-
wallet: Wallet,
|
605
|
-
hotkey_ss58s: list[str],
|
606
|
-
amounts: Optional[list[Union[Balance, float]]] = None,
|
607
|
-
wait_for_inclusion: bool = True,
|
608
|
-
wait_for_finalization: bool = False,
|
609
|
-
prompt: bool = False,
|
610
|
-
) -> bool:
|
611
|
-
"""
|
612
|
-
Removes stake from each `hotkey_ss58` in the list, using each amount, to a common coldkey.
|
613
|
-
|
614
|
-
:param subtensor: the initialized SubtensorInterface object to use
|
615
|
-
:param wallet: The wallet with the coldkey to unstake to.
|
616
|
-
:param hotkey_ss58s: List of hotkeys to unstake from.
|
617
|
-
:param amounts: List of amounts to unstake. If ``None``, unstake all.
|
618
|
-
:param wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning `True`, or returns
|
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
|
634
|
-
|
635
|
-
if amounts is not None and len(amounts) != len(hotkey_ss58s):
|
636
|
-
raise ValueError("amounts must be a list of the same length as hotkey_ss58s")
|
637
|
-
|
638
|
-
if amounts is not None and not all(
|
639
|
-
isinstance(amount, (Balance, float)) for amount in amounts
|
640
|
-
):
|
641
|
-
raise TypeError(
|
642
|
-
"amounts must be a [list of bittensor.Balance or float] or None"
|
643
|
-
)
|
644
|
-
|
645
|
-
new_amounts: Sequence[Optional[Balance]]
|
646
|
-
if amounts is None:
|
647
|
-
new_amounts = [None] * len(hotkey_ss58s)
|
648
|
-
else:
|
649
|
-
new_amounts = [
|
650
|
-
Balance(amount) if not isinstance(amount, Balance) else amount
|
651
|
-
for amount in (amounts or [None] * len(hotkey_ss58s))
|
652
|
-
]
|
653
|
-
if sum(amount.tao for amount in new_amounts if amount is not None) == 0:
|
654
|
-
return True
|
655
|
-
|
656
|
-
# Unlock coldkey.
|
657
|
-
if not unlock_key(wallet).success:
|
658
|
-
return False
|
659
|
-
|
660
|
-
with console.status(
|
661
|
-
f":satellite: Syncing with chain: [white]{subtensor}[/white] ..."
|
662
|
-
):
|
663
|
-
block_hash = await subtensor.substrate.get_chain_head()
|
664
|
-
|
665
|
-
old_balance_ = subtensor.get_balance(
|
666
|
-
wallet.coldkeypub.ss58_address, block_hash=block_hash
|
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
|
-
]
|
675
|
-
)
|
676
|
-
hotkey_owners_ = asyncio.gather(
|
677
|
-
*[subtensor.get_hotkey_owner(h, block_hash) for h in hotkey_ss58s]
|
678
|
-
)
|
679
|
-
|
680
|
-
old_balance, old_stakes, hotkey_owners, threshold = await asyncio.gather(
|
681
|
-
old_balance_,
|
682
|
-
old_stakes_,
|
683
|
-
hotkey_owners_,
|
684
|
-
_get_threshold_amount(subtensor, block_hash),
|
685
|
-
)
|
686
|
-
own_hotkeys = [
|
687
|
-
wallet.coldkeypub.ss58_address == hotkey_owner
|
688
|
-
for hotkey_owner in hotkey_owners
|
689
|
-
]
|
690
|
-
|
691
|
-
successful_unstakes = 0
|
692
|
-
for idx, (hotkey_ss58, amount, old_stake, own_hotkey) in enumerate(
|
693
|
-
zip(hotkey_ss58s, new_amounts, old_stakes, own_hotkeys)
|
694
|
-
):
|
695
|
-
# Covert to bittensor.Balance
|
696
|
-
if amount is None:
|
697
|
-
# Unstake it all.
|
698
|
-
unstaking_balance = old_stake
|
699
|
-
else:
|
700
|
-
unstaking_balance = amount
|
701
|
-
|
702
|
-
# Check enough to unstake.
|
703
|
-
stake_on_uid = old_stake
|
704
|
-
if unstaking_balance > stake_on_uid:
|
705
|
-
err_console.print(
|
706
|
-
f":cross_mark: [red]Not enough stake[/red]:"
|
707
|
-
f" [green]{stake_on_uid}[/green] to unstake:"
|
708
|
-
f" [blue]{unstaking_balance}[/blue] from hotkey:"
|
709
|
-
f" [white]{wallet.hotkey_str}[/white]"
|
710
|
-
)
|
711
|
-
continue
|
712
|
-
|
713
|
-
# If nomination stake, check threshold.
|
714
|
-
if (
|
715
|
-
not own_hotkey
|
716
|
-
and (
|
717
|
-
await _check_threshold_amount(
|
718
|
-
subtensor=subtensor,
|
719
|
-
sb=(stake_on_uid - unstaking_balance),
|
720
|
-
block_hash=block_hash,
|
721
|
-
min_req_stake=threshold,
|
722
|
-
)
|
723
|
-
)[0]
|
724
|
-
is False
|
725
|
-
):
|
726
|
-
console.print(
|
727
|
-
":warning: [yellow]This action will unstake the entire staked balance![/yellow]"
|
728
|
-
)
|
729
|
-
unstaking_balance = stake_on_uid
|
730
|
-
|
731
|
-
# Ask before moving on.
|
732
|
-
if prompt:
|
733
|
-
if not Confirm.ask(
|
734
|
-
f"Do you want to unstake:\n"
|
735
|
-
f"[bold white]\tamount: {unstaking_balance}\n"
|
736
|
-
f"ss58: {hotkey_ss58}[/bold white ]?"
|
737
|
-
):
|
738
|
-
continue
|
739
|
-
|
740
|
-
with console.status(
|
741
|
-
f":satellite: Unstaking from chain: [white]{subtensor}[/white] ..."
|
742
|
-
):
|
743
|
-
call = await subtensor.substrate.compose_call(
|
744
|
-
call_module="SubtensorModule",
|
745
|
-
call_function="remove_stake",
|
746
|
-
call_params={
|
747
|
-
"hotkey": hotkey_ss58,
|
748
|
-
"amount_unstaked": unstaking_balance.rao,
|
749
|
-
},
|
750
|
-
)
|
751
|
-
staking_response, err_msg = await subtensor.sign_and_send_extrinsic(
|
752
|
-
call, wallet, wait_for_inclusion, wait_for_finalization
|
753
|
-
)
|
754
|
-
|
755
|
-
if staking_response is True: # If we successfully unstaked.
|
756
|
-
# We only wait here if we expect finalization.
|
757
|
-
|
758
|
-
if idx < len(hotkey_ss58s) - 1:
|
759
|
-
# Wait for tx rate limit.
|
760
|
-
tx_query = await subtensor.substrate.query(
|
761
|
-
module="SubtensorModule",
|
762
|
-
storage_function="TxRateLimit",
|
763
|
-
block_hash=block_hash,
|
764
|
-
)
|
765
|
-
tx_rate_limit_blocks: int = tx_query
|
766
|
-
|
767
|
-
# TODO: Handle in-case we have fast blocks
|
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()),
|
790
|
-
)
|
791
|
-
console.print(
|
792
|
-
"Stake ({}): [blue]{}[/blue] :arrow_right: [green]{}[/green]".format(
|
793
|
-
hotkey_ss58, stake_on_uid, new_stake
|
794
|
-
)
|
795
|
-
)
|
796
|
-
successful_unstakes += 1
|
797
|
-
else:
|
798
|
-
err_console.print(":cross_mark: [red]Failed[/red]: Unknown Error.")
|
799
|
-
continue
|
800
|
-
|
801
|
-
if successful_unstakes != 0:
|
802
|
-
with console.status(
|
803
|
-
f":satellite: Checking balance on: ([white]{subtensor}[/white] ..."
|
804
|
-
):
|
805
|
-
new_balance = await subtensor.get_balance(wallet.coldkeypub.ss58_address)
|
806
|
-
console.print(
|
807
|
-
f"Balance: [blue]{old_balance[wallet.coldkeypub.ss58_address]}[/blue]"
|
808
|
-
f" :arrow_right: [green]{new_balance[wallet.coldkeypub.ss58_address]}[/green]"
|
809
|
-
)
|
810
|
-
return True
|
811
|
-
|
812
|
-
return False
|
813
|
-
|
814
|
-
|
815
|
-
# Commands
|
816
|
-
|
817
|
-
|
818
|
-
async def show(
|
819
|
-
wallet: Wallet,
|
820
|
-
subtensor: Optional["SubtensorInterface"],
|
821
|
-
all_wallets: bool,
|
822
|
-
reuse_last: bool,
|
823
|
-
html_output: bool,
|
824
|
-
no_cache: bool,
|
825
|
-
):
|
826
|
-
"""Show all stake accounts."""
|
827
|
-
|
828
|
-
async def get_stake_accounts(
|
829
|
-
wallet_, block_hash: str
|
830
|
-
) -> dict[str, Union[str, Balance, dict[str, Union[str, Balance]]]]:
|
831
|
-
"""Get stake account details for the given wallet.
|
832
|
-
|
833
|
-
:param wallet_: The wallet object to fetch the stake account details for.
|
834
|
-
|
835
|
-
:return: A dictionary mapping SS58 addresses to their respective stake account details.
|
836
|
-
"""
|
837
|
-
|
838
|
-
wallet_stake_accounts = {}
|
839
|
-
|
840
|
-
# Get this wallet's coldkey balance.
|
841
|
-
cold_balance_, stakes_from_hk, stakes_from_d = await asyncio.gather(
|
842
|
-
subtensor.get_balance(
|
843
|
-
wallet_.coldkeypub.ss58_address, block_hash=block_hash
|
844
|
-
),
|
845
|
-
get_stakes_from_hotkeys(wallet_, block_hash=block_hash),
|
846
|
-
get_stakes_from_delegates(wallet_),
|
847
|
-
)
|
848
|
-
|
849
|
-
cold_balance = cold_balance_[wallet_.coldkeypub.ss58_address]
|
850
|
-
|
851
|
-
# Populate the stake accounts with local hotkeys data.
|
852
|
-
wallet_stake_accounts.update(stakes_from_hk)
|
853
|
-
|
854
|
-
# Populate the stake accounts with delegations data.
|
855
|
-
wallet_stake_accounts.update(stakes_from_d)
|
856
|
-
|
857
|
-
return {
|
858
|
-
"name": wallet_.name,
|
859
|
-
"balance": cold_balance,
|
860
|
-
"accounts": wallet_stake_accounts,
|
861
|
-
}
|
862
|
-
|
863
|
-
async def get_stakes_from_hotkeys(
|
864
|
-
wallet_, block_hash: str
|
865
|
-
) -> dict[str, dict[str, Union[str, Balance]]]:
|
866
|
-
"""Fetch stakes from hotkeys for the provided wallet.
|
867
|
-
|
868
|
-
:param wallet_: The wallet object to fetch the stakes for.
|
869
|
-
|
870
|
-
:return: A dictionary of stakes related to hotkeys.
|
871
|
-
"""
|
872
|
-
|
873
|
-
async def get_all_neurons_for_pubkey(hk):
|
874
|
-
netuids = await subtensor.get_netuids_for_hotkey(hk, block_hash=block_hash)
|
875
|
-
uid_query = await asyncio.gather(
|
876
|
-
*[
|
877
|
-
subtensor.substrate.query(
|
878
|
-
module="SubtensorModule",
|
879
|
-
storage_function="Uids",
|
880
|
-
params=[netuid, hk],
|
881
|
-
block_hash=block_hash,
|
882
|
-
)
|
883
|
-
for netuid in netuids
|
884
|
-
]
|
885
|
-
)
|
886
|
-
uids = [_result for _result in uid_query]
|
887
|
-
neurons = await asyncio.gather(
|
888
|
-
*[
|
889
|
-
subtensor.neuron_for_uid(uid, net)
|
890
|
-
for (uid, net) in zip(uids, netuids)
|
891
|
-
]
|
892
|
-
)
|
893
|
-
return neurons
|
894
|
-
|
895
|
-
async def get_emissions_and_stake(hk: str):
|
896
|
-
neurons, stake = await asyncio.gather(
|
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
|
-
),
|
904
|
-
)
|
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
|
-
|
908
|
-
hotkeys = cast(list[Wallet], get_hotkey_wallets_for_wallet(wallet_))
|
909
|
-
stakes = {}
|
910
|
-
query = await asyncio.gather(
|
911
|
-
*[get_emissions_and_stake(hot.hotkey.ss58_address) for hot in hotkeys]
|
912
|
-
)
|
913
|
-
for hot, (emission, hotkey_stake) in zip(hotkeys, query):
|
914
|
-
stakes[hot.hotkey.ss58_address] = {
|
915
|
-
"name": hot.hotkey_str,
|
916
|
-
"stake": hotkey_stake,
|
917
|
-
"rate": emission,
|
918
|
-
}
|
919
|
-
return stakes
|
920
|
-
|
921
|
-
async def get_stakes_from_delegates(
|
922
|
-
wallet_,
|
923
|
-
) -> dict[str, dict[str, Union[str, Balance]]]:
|
924
|
-
"""Fetch stakes from delegates for the provided wallet.
|
925
|
-
|
926
|
-
:param wallet_: The wallet object to fetch the stakes for.
|
927
|
-
|
928
|
-
:return: A dictionary of stakes related to delegates.
|
929
|
-
"""
|
930
|
-
delegates = await subtensor.get_delegated(
|
931
|
-
coldkey_ss58=wallet_.coldkeypub.ss58_address, block_hash=None
|
932
|
-
)
|
933
|
-
stakes = {}
|
934
|
-
for dele, staked in delegates:
|
935
|
-
for nom in dele.nominators:
|
936
|
-
if nom[0] == wallet_.coldkeypub.ss58_address:
|
937
|
-
delegate_name = (
|
938
|
-
registered_delegate_info[dele.hotkey_ss58].display
|
939
|
-
if dele.hotkey_ss58 in registered_delegate_info
|
940
|
-
else None
|
941
|
-
)
|
942
|
-
stakes[dele.hotkey_ss58] = {
|
943
|
-
"name": delegate_name if delegate_name else dele.hotkey_ss58,
|
944
|
-
"stake": nom[1],
|
945
|
-
"rate": dele.total_daily_return.tao
|
946
|
-
* (nom[1] / dele.total_stake.tao),
|
947
|
-
}
|
948
|
-
return stakes
|
949
|
-
|
950
|
-
async def get_all_wallet_accounts(
|
951
|
-
block_hash: str,
|
952
|
-
) -> list[dict[str, Union[str, Balance, dict[str, Union[str, Balance]]]]]:
|
953
|
-
"""Fetch stake accounts for all provided wallets using a ThreadPool.
|
954
|
-
|
955
|
-
:param block_hash: The block hash to fetch the stake accounts for.
|
956
|
-
|
957
|
-
:return: A list of dictionaries, each dictionary containing stake account details for each wallet.
|
958
|
-
"""
|
959
|
-
|
960
|
-
accounts_ = await asyncio.gather(
|
961
|
-
*[get_stake_accounts(w, block_hash=block_hash) for w in wallets]
|
962
|
-
)
|
963
|
-
return accounts_
|
964
|
-
|
965
|
-
if not reuse_last:
|
966
|
-
cast("SubtensorInterface", subtensor)
|
967
|
-
if all_wallets:
|
968
|
-
wallets = get_coldkey_wallets_for_path(wallet.path)
|
969
|
-
valid_wallets, invalid_wallets = validate_coldkey_presence(wallets)
|
970
|
-
wallets = valid_wallets
|
971
|
-
for invalid_wallet in invalid_wallets:
|
972
|
-
print_error(f"No coldkeypub found for wallet: ({invalid_wallet.name})")
|
973
|
-
else:
|
974
|
-
wallets = [wallet]
|
975
|
-
|
976
|
-
with console.status(
|
977
|
-
":satellite: Retrieving account data...", spinner="aesthetic"
|
978
|
-
):
|
979
|
-
block_hash_ = await subtensor.substrate.get_chain_head()
|
980
|
-
registered_delegate_info = await subtensor.get_delegate_identities(
|
981
|
-
block_hash=block_hash_
|
982
|
-
)
|
983
|
-
accounts = await get_all_wallet_accounts(block_hash=block_hash_)
|
984
|
-
|
985
|
-
total_stake: float = 0.0
|
986
|
-
total_balance: float = 0.0
|
987
|
-
total_rate: float = 0.0
|
988
|
-
rows = []
|
989
|
-
db_rows = []
|
990
|
-
for acc in accounts:
|
991
|
-
cast(str, acc["name"])
|
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]
|
996
|
-
)
|
997
|
-
total_balance += cast(Balance, acc["balance"]).tao
|
998
|
-
for key, value in cast(dict, acc["accounts"]).items():
|
999
|
-
if value["name"] and value["name"] != key:
|
1000
|
-
account_display_name = f"{value['name']}"
|
1001
|
-
else:
|
1002
|
-
account_display_name = "(~)"
|
1003
|
-
rows.append(
|
1004
|
-
[
|
1005
|
-
"",
|
1006
|
-
"",
|
1007
|
-
account_display_name,
|
1008
|
-
key,
|
1009
|
-
str(value["stake"]),
|
1010
|
-
str(value["rate"]),
|
1011
|
-
]
|
1012
|
-
)
|
1013
|
-
db_rows.append(
|
1014
|
-
[
|
1015
|
-
acc["name"],
|
1016
|
-
None,
|
1017
|
-
value["name"],
|
1018
|
-
float(value["stake"]),
|
1019
|
-
float(value["rate"]),
|
1020
|
-
key,
|
1021
|
-
1,
|
1022
|
-
]
|
1023
|
-
)
|
1024
|
-
total_stake += cast(Balance, value["stake"]).tao
|
1025
|
-
total_rate += float(value["rate"])
|
1026
|
-
metadata = {
|
1027
|
-
"total_stake": "\u03c4{:.5f}".format(total_stake),
|
1028
|
-
"total_balance": "\u03c4{:.5f}".format(total_balance),
|
1029
|
-
"total_rate": "\u03c4{:.5f}/d".format(total_rate),
|
1030
|
-
"rows": json.dumps(rows),
|
1031
|
-
}
|
1032
|
-
if not no_cache:
|
1033
|
-
create_table(
|
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,
|
1045
|
-
)
|
1046
|
-
update_metadata_table("stakeshow", metadata)
|
1047
|
-
else:
|
1048
|
-
try:
|
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."
|
1057
|
-
)
|
1058
|
-
return
|
1059
|
-
if not html_output:
|
1060
|
-
table = Table(
|
1061
|
-
Column("[bold white]Coldkey", style="dark_orange", ratio=1),
|
1062
|
-
Column(
|
1063
|
-
"[bold white]Balance",
|
1064
|
-
metadata["total_balance"],
|
1065
|
-
style="dark_sea_green",
|
1066
|
-
ratio=1,
|
1067
|
-
),
|
1068
|
-
Column("[bold white]Account", style="bright_cyan", ratio=3),
|
1069
|
-
Column("[bold white]Hotkey", ratio=7, no_wrap=True, style="bright_magenta"),
|
1070
|
-
Column(
|
1071
|
-
"[bold white]Stake",
|
1072
|
-
metadata["total_stake"],
|
1073
|
-
style="light_goldenrod2",
|
1074
|
-
ratio=1,
|
1075
|
-
),
|
1076
|
-
Column(
|
1077
|
-
"[bold white]Rate /d",
|
1078
|
-
metadata["total_rate"],
|
1079
|
-
style="rgb(42,161,152)",
|
1080
|
-
ratio=1,
|
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",
|
1087
|
-
)
|
1088
|
-
|
1089
|
-
for i, row in enumerate(rows):
|
1090
|
-
is_last_row = i + 1 == len(rows)
|
1091
|
-
table.add_row(*row)
|
1092
|
-
|
1093
|
-
# If last row or new coldkey starting next
|
1094
|
-
if is_last_row or (rows[i + 1][0] != ""):
|
1095
|
-
table.add_row(end_section=True)
|
1096
|
-
console.print(table)
|
1097
|
-
|
1098
|
-
else:
|
1099
|
-
render_tree(
|
1100
|
-
"stakeshow",
|
1101
|
-
f"Stakes | Total Balance: {metadata['total_balance']} - Total Stake: {metadata['total_stake']} "
|
1102
|
-
f"Total Rate: {metadata['total_rate']}",
|
1103
|
-
[
|
1104
|
-
{"title": "Coldkey", "field": "COLDKEY"},
|
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
|
-
)
|
1136
|
-
|
1137
|
-
|
1138
|
-
async def stake_add(
|
1139
|
-
wallet: Wallet,
|
1140
|
-
subtensor: "SubtensorInterface",
|
1141
|
-
amount: float,
|
1142
|
-
stake_all: bool,
|
1143
|
-
max_stake: float,
|
1144
|
-
include_hotkeys: list[str],
|
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)."""
|
1151
|
-
|
1152
|
-
async def is_hotkey_registered_any(hk: str, bh: str) -> bool:
|
1153
|
-
return len(await subtensor.get_netuids_for_hotkey(hk, bh)) > 0
|
1154
|
-
|
1155
|
-
# Get the hotkey_names (if any) and the hotkey_ss58s.
|
1156
|
-
hotkeys_to_stake_to: list[tuple[Optional[str], str]] = []
|
1157
|
-
if hotkey_ss58:
|
1158
|
-
if not is_valid_ss58_address(hotkey_ss58):
|
1159
|
-
print_error("The entered ss58 address is incorrect")
|
1160
|
-
typer.Exit()
|
1161
|
-
|
1162
|
-
# Stake to specific hotkey.
|
1163
|
-
hotkeys_to_stake_to = [(None, hotkey_ss58)]
|
1164
|
-
elif all_hotkeys:
|
1165
|
-
# Stake to all hotkeys.
|
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)
|
1193
|
-
)
|
1194
|
-
else:
|
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)]
|
1203
|
-
|
1204
|
-
try:
|
1205
|
-
# Get coldkey balance
|
1206
|
-
print_verbose("Fetching coldkey balance")
|
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
|
-
]
|
1231
|
-
)
|
1232
|
-
else:
|
1233
|
-
|
1234
|
-
async def null():
|
1235
|
-
return [None] * len(hotkeys_to_stake_to)
|
1236
|
-
|
1237
|
-
hotkey_stakes_ = null()
|
1238
|
-
registered: list[bool]
|
1239
|
-
hotkey_stakes: list[Optional[Balance]]
|
1240
|
-
registered, hotkey_stakes = await asyncio.gather(registered_, hotkey_stakes_)
|
1241
|
-
|
1242
|
-
for hotkey, reg, hotkey_stake in zip(
|
1243
|
-
hotkeys_to_stake_to, registered, hotkey_stakes
|
1244
|
-
):
|
1245
|
-
if not reg:
|
1246
|
-
# Hotkey is not registered.
|
1247
|
-
if len(hotkeys_to_stake_to) == 1:
|
1248
|
-
# Only one hotkey, error
|
1249
|
-
err_console.print(
|
1250
|
-
f"[red]Hotkey [bold]{hotkey[1]}[/bold] is not registered. Aborting.[/red]"
|
1251
|
-
)
|
1252
|
-
raise ValueError
|
1253
|
-
else:
|
1254
|
-
# Otherwise, print warning and skip
|
1255
|
-
console.print(
|
1256
|
-
f"[yellow]Hotkey [bold]{hotkey[1]}[/bold] is not registered. Skipping.[/yellow]"
|
1257
|
-
)
|
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
|
-
|
1277
|
-
final_amounts.append(stake_amount_tao)
|
1278
|
-
final_hotkeys.append(hotkey) # add both the name and the ss58 address.
|
1279
|
-
|
1280
|
-
if len(final_hotkeys) == 0:
|
1281
|
-
# No hotkeys to stake to.
|
1282
|
-
err_console.print(
|
1283
|
-
"Not enough balance to stake to any hotkeys or max_stake is less than current stake."
|
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
|
1310
|
-
|
1311
|
-
|
1312
|
-
async def unstake(
|
1313
|
-
wallet: Wallet,
|
1314
|
-
subtensor: "SubtensorInterface",
|
1315
|
-
hotkey_ss58_address: str,
|
1316
|
-
all_hotkeys: bool,
|
1317
|
-
include_hotkeys: list[str],
|
1318
|
-
exclude_hotkeys: list[str],
|
1319
|
-
amount: float,
|
1320
|
-
keep_stake: float,
|
1321
|
-
unstake_all: bool,
|
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
|
1343
|
-
|
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
|
-
else:
|
1363
|
-
# Only cli.config.wallet.hotkey is specified.
|
1364
|
-
# so we stake to that single hotkey.
|
1365
|
-
print_verbose(
|
1366
|
-
f"Unstaking from wallet: ({wallet.name}) from hotkey: ({wallet.hotkey_str})"
|
1367
|
-
)
|
1368
|
-
assert wallet.hotkey is not None
|
1369
|
-
hotkeys_to_unstake_from = [(None, wallet.hotkey.ss58_address)]
|
1370
|
-
|
1371
|
-
final_hotkeys: list[tuple[str, str]] = []
|
1372
|
-
final_amounts: list[Union[float, Balance]] = []
|
1373
|
-
hotkey: tuple[Optional[str], str] # (hotkey_name (or None), hotkey_ss58)
|
1374
|
-
with suppress(ValueError):
|
1375
|
-
with console.status(
|
1376
|
-
f":satellite:Syncing with chain {subtensor}", spinner="earth"
|
1377
|
-
) as status:
|
1378
|
-
print_verbose("Fetching stake", status)
|
1379
|
-
block_hash = await subtensor.substrate.get_chain_head()
|
1380
|
-
hotkey_stakes = await asyncio.gather(
|
1381
|
-
*[
|
1382
|
-
subtensor.get_stake_for_coldkey_and_hotkey(
|
1383
|
-
hotkey_ss58=hotkey[1],
|
1384
|
-
coldkey_ss58=wallet.coldkeypub.ss58_address,
|
1385
|
-
block_hash=block_hash,
|
1386
|
-
)
|
1387
|
-
for hotkey in hotkeys_to_unstake_from
|
1388
|
-
]
|
1389
|
-
)
|
1390
|
-
for hotkey, hotkey_stake in zip(hotkeys_to_unstake_from, hotkey_stakes):
|
1391
|
-
unstake_amount_tao: float = amount
|
1392
|
-
|
1393
|
-
if unstake_all:
|
1394
|
-
unstake_amount_tao = hotkey_stake.tao
|
1395
|
-
if keep_stake:
|
1396
|
-
# Get the current stake of the hotkey from this coldkey.
|
1397
|
-
unstake_amount_tao = hotkey_stake.tao - keep_stake
|
1398
|
-
amount = unstake_amount_tao
|
1399
|
-
if unstake_amount_tao < 0:
|
1400
|
-
# Skip if max_stake is greater than current stake.
|
1401
|
-
continue
|
1402
|
-
else:
|
1403
|
-
if unstake_amount_tao > hotkey_stake.tao:
|
1404
|
-
# Skip if the specified amount is greater than the current stake.
|
1405
|
-
continue
|
1406
|
-
|
1407
|
-
final_amounts.append(unstake_amount_tao)
|
1408
|
-
final_hotkeys.append(hotkey) # add both the name and the ss58 address.
|
1409
|
-
|
1410
|
-
if len(final_hotkeys) == 0:
|
1411
|
-
# No hotkeys to unstake from.
|
1412
|
-
err_console.print(
|
1413
|
-
"Not enough stake to unstake from any hotkeys or max_stake is more than current stake."
|
1414
|
-
)
|
1415
|
-
return None
|
1416
|
-
|
1417
|
-
# Ask to unstake
|
1418
|
-
if prompt:
|
1419
|
-
if not Confirm.ask(
|
1420
|
-
f"Do you want to unstake from the following keys to {wallet.name}:\n"
|
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
|
-
]
|
1427
|
-
)
|
1428
|
-
):
|
1429
|
-
return None
|
1430
|
-
if len(final_hotkeys) == 1:
|
1431
|
-
# do regular unstake
|
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
|
-
)
|