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.
Files changed (33) hide show
  1. bittensor_cli/__init__.py +1 -1
  2. bittensor_cli/cli.py +1827 -1392
  3. bittensor_cli/src/__init__.py +623 -168
  4. bittensor_cli/src/bittensor/balances.py +41 -8
  5. bittensor_cli/src/bittensor/chain_data.py +557 -428
  6. bittensor_cli/src/bittensor/extrinsics/registration.py +129 -23
  7. bittensor_cli/src/bittensor/extrinsics/root.py +3 -3
  8. bittensor_cli/src/bittensor/extrinsics/transfer.py +6 -11
  9. bittensor_cli/src/bittensor/minigraph.py +46 -8
  10. bittensor_cli/src/bittensor/subtensor_interface.py +567 -250
  11. bittensor_cli/src/bittensor/utils.py +399 -25
  12. bittensor_cli/src/commands/stake/__init__.py +154 -0
  13. bittensor_cli/src/commands/stake/add.py +625 -0
  14. bittensor_cli/src/commands/stake/children_hotkeys.py +103 -75
  15. bittensor_cli/src/commands/stake/list.py +687 -0
  16. bittensor_cli/src/commands/stake/move.py +1000 -0
  17. bittensor_cli/src/commands/stake/remove.py +1146 -0
  18. bittensor_cli/src/commands/subnets/__init__.py +0 -0
  19. bittensor_cli/src/commands/subnets/price.py +867 -0
  20. bittensor_cli/src/commands/subnets/subnets.py +2028 -0
  21. bittensor_cli/src/commands/sudo.py +554 -12
  22. bittensor_cli/src/commands/wallets.py +225 -531
  23. bittensor_cli/src/commands/weights.py +2 -2
  24. {bittensor_cli-8.4.3.dist-info → bittensor_cli-9.0.0.dist-info}/METADATA +7 -4
  25. bittensor_cli-9.0.0.dist-info/RECORD +34 -0
  26. bittensor_cli/src/bittensor/async_substrate_interface.py +0 -2748
  27. bittensor_cli/src/commands/root.py +0 -1752
  28. bittensor_cli/src/commands/stake/stake.py +0 -1448
  29. bittensor_cli/src/commands/subnets.py +0 -897
  30. bittensor_cli-8.4.3.dist-info/RECORD +0 -31
  31. {bittensor_cli-8.4.3.dist-info → bittensor_cli-9.0.0.dist-info}/WHEEL +0 -0
  32. {bittensor_cli-8.4.3.dist-info → bittensor_cli-9.0.0.dist-info}/entry_points.txt +0 -0
  33. {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
- )