bittensor-cli 9.1.4__py3-none-any.whl → 9.3.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/cli.py +525 -131
- bittensor_cli/src/__init__.py +444 -465
- bittensor_cli/src/bittensor/chain_data.py +6 -2
- bittensor_cli/src/bittensor/extrinsics/registration.py +47 -23
- bittensor_cli/src/bittensor/extrinsics/root.py +10 -11
- bittensor_cli/src/bittensor/extrinsics/transfer.py +5 -3
- bittensor_cli/src/bittensor/subtensor_interface.py +125 -5
- bittensor_cli/src/bittensor/utils.py +4 -1
- bittensor_cli/src/commands/stake/add.py +169 -108
- bittensor_cli/src/commands/stake/children_hotkeys.py +120 -79
- bittensor_cli/src/commands/stake/list.py +54 -20
- bittensor_cli/src/commands/stake/move.py +58 -18
- bittensor_cli/src/commands/stake/remove.py +174 -92
- bittensor_cli/src/commands/subnets/price.py +11 -9
- bittensor_cli/src/commands/subnets/subnets.py +223 -80
- bittensor_cli/src/commands/sudo.py +76 -22
- bittensor_cli/src/commands/wallets.py +656 -40
- bittensor_cli/src/commands/weights.py +21 -11
- bittensor_cli/version.py +2 -1
- {bittensor_cli-9.1.4.dist-info → bittensor_cli-9.3.0.dist-info}/METADATA +30 -11
- bittensor_cli-9.3.0.dist-info/RECORD +35 -0
- {bittensor_cli-9.1.4.dist-info → bittensor_cli-9.3.0.dist-info}/WHEEL +1 -1
- bittensor_cli-9.1.4.dist-info/RECORD +0 -35
- {bittensor_cli-9.1.4.dist-info → bittensor_cli-9.3.0.dist-info}/entry_points.txt +0 -0
- {bittensor_cli-9.1.4.dist-info → bittensor_cli-9.3.0.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,5 @@
|
|
1
1
|
import asyncio
|
2
|
+
import json
|
2
3
|
from functools import partial
|
3
4
|
|
4
5
|
from typing import TYPE_CHECKING, Optional
|
@@ -20,6 +21,7 @@ from bittensor_cli.src.bittensor.utils import (
|
|
20
21
|
format_error_message,
|
21
22
|
group_subnets,
|
22
23
|
unlock_key,
|
24
|
+
json_console,
|
23
25
|
)
|
24
26
|
|
25
27
|
if TYPE_CHECKING:
|
@@ -41,6 +43,8 @@ async def unstake(
|
|
41
43
|
safe_staking: bool,
|
42
44
|
rate_tolerance: float,
|
43
45
|
allow_partial_stake: bool,
|
46
|
+
json_output: bool,
|
47
|
+
era: int,
|
44
48
|
):
|
45
49
|
"""Unstake from hotkey(s)."""
|
46
50
|
with console.status(
|
@@ -194,9 +198,25 @@ async def unstake(
|
|
194
198
|
)
|
195
199
|
continue # Skip to the next subnet - useful when single amount is specified for all subnets
|
196
200
|
|
197
|
-
|
198
|
-
|
201
|
+
stake_fee = await subtensor.get_stake_fee(
|
202
|
+
origin_hotkey_ss58=staking_address_ss58,
|
203
|
+
origin_netuid=netuid,
|
204
|
+
origin_coldkey_ss58=wallet.coldkeypub.ss58_address,
|
205
|
+
destination_hotkey_ss58=None,
|
206
|
+
destination_netuid=None,
|
207
|
+
destination_coldkey_ss58=wallet.coldkeypub.ss58_address,
|
208
|
+
amount=amount_to_unstake_as_balance.rao,
|
199
209
|
)
|
210
|
+
|
211
|
+
try:
|
212
|
+
received_amount, slippage_pct, slippage_pct_float = _calculate_slippage(
|
213
|
+
subnet_info=subnet_info,
|
214
|
+
amount=amount_to_unstake_as_balance,
|
215
|
+
stake_fee=stake_fee,
|
216
|
+
)
|
217
|
+
except ValueError:
|
218
|
+
continue
|
219
|
+
|
200
220
|
total_received_amount += received_amount
|
201
221
|
max_float_slippage = max(max_float_slippage, slippage_pct_float)
|
202
222
|
|
@@ -220,6 +240,7 @@ async def unstake(
|
|
220
240
|
str(amount_to_unstake_as_balance), # Amount to Unstake
|
221
241
|
str(subnet_info.price.tao)
|
222
242
|
+ f"({Balance.get_unit(0)}/{Balance.get_unit(netuid)})", # Rate
|
243
|
+
str(stake_fee), # Fee
|
223
244
|
str(received_amount), # Received Amount
|
224
245
|
slippage_pct, # Slippage Percent
|
225
246
|
]
|
@@ -241,8 +262,11 @@ async def unstake(
|
|
241
262
|
base_unstake_op["price_with_tolerance"] = price_with_tolerance
|
242
263
|
base_table_row.extend(
|
243
264
|
[
|
244
|
-
|
245
|
-
f"
|
265
|
+
# Rate with tolerance
|
266
|
+
f"{rate_with_tolerance:.4f} {Balance.get_unit(0)}/{Balance.get_unit(netuid)}",
|
267
|
+
# Partial unstake
|
268
|
+
f"[{'dark_sea_green3' if allow_partial_stake else 'red'}]"
|
269
|
+
f"{allow_partial_stake}[/{'dark_sea_green3' if allow_partial_stake else 'red'}]",
|
246
270
|
]
|
247
271
|
)
|
248
272
|
|
@@ -273,44 +297,45 @@ async def unstake(
|
|
273
297
|
if not unlock_key(wallet).success:
|
274
298
|
return False
|
275
299
|
|
300
|
+
successes = []
|
276
301
|
with console.status("\n:satellite: Performing unstaking operations...") as status:
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
status=status,
|
310
|
-
)
|
302
|
+
for op in unstake_operations:
|
303
|
+
common_args = {
|
304
|
+
"wallet": wallet,
|
305
|
+
"subtensor": subtensor,
|
306
|
+
"netuid": op["netuid"],
|
307
|
+
"amount": op["amount_to_unstake"],
|
308
|
+
"hotkey_ss58": op["hotkey_ss58"],
|
309
|
+
"status": status,
|
310
|
+
"era": era,
|
311
|
+
}
|
312
|
+
|
313
|
+
if safe_staking and op["netuid"] != 0:
|
314
|
+
func = _safe_unstake_extrinsic
|
315
|
+
specific_args = {
|
316
|
+
"price_limit": op["price_with_tolerance"],
|
317
|
+
"allow_partial_stake": allow_partial_stake,
|
318
|
+
}
|
319
|
+
else:
|
320
|
+
func = _unstake_extrinsic
|
321
|
+
specific_args = {"current_stake": op["current_stake_balance"]}
|
322
|
+
|
323
|
+
suc = await func(**common_args, **specific_args)
|
324
|
+
|
325
|
+
successes.append(
|
326
|
+
{
|
327
|
+
"netuid": op["netuid"],
|
328
|
+
"hotkey_ss58": op["hotkey_ss58"],
|
329
|
+
"unstake_amount": op["amount_to_unstake"].tao,
|
330
|
+
"success": suc,
|
331
|
+
}
|
332
|
+
)
|
333
|
+
|
311
334
|
console.print(
|
312
335
|
f"[{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]Unstaking operations completed."
|
313
336
|
)
|
337
|
+
if json_output:
|
338
|
+
json_console.print(json.dumps(successes))
|
314
339
|
|
315
340
|
|
316
341
|
async def unstake_all(
|
@@ -321,7 +346,9 @@ async def unstake_all(
|
|
321
346
|
all_hotkeys: bool = False,
|
322
347
|
include_hotkeys: Optional[list[str]] = None,
|
323
348
|
exclude_hotkeys: Optional[list[str]] = None,
|
349
|
+
era: int = 3,
|
324
350
|
prompt: bool = True,
|
351
|
+
json_output: bool = False,
|
325
352
|
) -> bool:
|
326
353
|
"""Unstakes all stakes from all hotkeys in all subnets."""
|
327
354
|
include_hotkeys = include_hotkeys or []
|
@@ -412,7 +439,12 @@ async def unstake_all(
|
|
412
439
|
style=COLOR_PALETTE["POOLS"]["RATE"],
|
413
440
|
)
|
414
441
|
table.add_column(
|
415
|
-
f"
|
442
|
+
f"Fee ({Balance.unit})",
|
443
|
+
justify="center",
|
444
|
+
style=COLOR_PALETTE["STAKE"]["STAKE_AMOUNT"],
|
445
|
+
)
|
446
|
+
table.add_column(
|
447
|
+
f"Received ({Balance.unit})",
|
416
448
|
justify="center",
|
417
449
|
style=COLOR_PALETTE["POOLS"]["TAO_EQUIV"],
|
418
450
|
)
|
@@ -432,9 +464,22 @@ async def unstake_all(
|
|
432
464
|
hotkey_display = hotkey_names.get(stake.hotkey_ss58, stake.hotkey_ss58)
|
433
465
|
subnet_info = all_sn_dynamic_info.get(stake.netuid)
|
434
466
|
stake_amount = stake.stake
|
435
|
-
|
436
|
-
|
467
|
+
stake_fee = await subtensor.get_stake_fee(
|
468
|
+
origin_hotkey_ss58=stake.hotkey_ss58,
|
469
|
+
origin_netuid=stake.netuid,
|
470
|
+
origin_coldkey_ss58=wallet.coldkeypub.ss58_address,
|
471
|
+
destination_hotkey_ss58=None,
|
472
|
+
destination_netuid=None,
|
473
|
+
destination_coldkey_ss58=wallet.coldkeypub.ss58_address,
|
474
|
+
amount=stake_amount.rao,
|
437
475
|
)
|
476
|
+
try:
|
477
|
+
received_amount, slippage_pct, slippage_pct_float = _calculate_slippage(
|
478
|
+
subnet_info=subnet_info, amount=stake_amount, stake_fee=stake_fee
|
479
|
+
)
|
480
|
+
except ValueError:
|
481
|
+
continue
|
482
|
+
|
438
483
|
max_slippage = max(max_slippage, slippage_pct_float)
|
439
484
|
total_received_value += received_amount
|
440
485
|
|
@@ -444,15 +489,21 @@ async def unstake_all(
|
|
444
489
|
str(stake_amount),
|
445
490
|
str(float(subnet_info.price))
|
446
491
|
+ f"({Balance.get_unit(0)}/{Balance.get_unit(stake.netuid)})",
|
492
|
+
str(stake_fee),
|
447
493
|
str(received_amount),
|
448
494
|
slippage_pct,
|
449
495
|
)
|
450
496
|
console.print(table)
|
451
|
-
message = ""
|
452
497
|
if max_slippage > 5:
|
453
|
-
message
|
454
|
-
|
455
|
-
|
498
|
+
message = (
|
499
|
+
f"[{COLOR_PALETTE['STAKE']['SLIPPAGE_TEXT']}]--------------------------------------------------------------"
|
500
|
+
f"-----------------------------------------------------\n"
|
501
|
+
f"[bold]WARNING:[/bold] The slippage on one of your operations is high: "
|
502
|
+
f"[{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}]{max_slippage:.4f}%"
|
503
|
+
f"[/{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}], this may result in a loss of funds.\n"
|
504
|
+
"----------------------------------------------------------------------------------------------------------"
|
505
|
+
"---------\n"
|
506
|
+
)
|
456
507
|
console.print(message)
|
457
508
|
|
458
509
|
console.print(
|
@@ -466,17 +517,20 @@ async def unstake_all(
|
|
466
517
|
|
467
518
|
if not unlock_key(wallet).success:
|
468
519
|
return False
|
469
|
-
|
520
|
+
successes = {}
|
470
521
|
with console.status("Unstaking all stakes...") as status:
|
471
522
|
for hotkey_ss58 in hotkey_ss58s:
|
472
|
-
await _unstake_all_extrinsic(
|
523
|
+
successes[hotkey_ss58] = await _unstake_all_extrinsic(
|
473
524
|
wallet=wallet,
|
474
525
|
subtensor=subtensor,
|
475
526
|
hotkey_ss58=hotkey_ss58,
|
476
527
|
hotkey_name=hotkey_names.get(hotkey_ss58, hotkey_ss58),
|
477
528
|
unstake_all_alpha=unstake_all_alpha,
|
478
529
|
status=status,
|
530
|
+
era=era,
|
479
531
|
)
|
532
|
+
if json_output:
|
533
|
+
return json_console.print(json.dumps({"success": successes}))
|
480
534
|
|
481
535
|
|
482
536
|
# Extrinsics
|
@@ -488,7 +542,8 @@ async def _unstake_extrinsic(
|
|
488
542
|
current_stake: Balance,
|
489
543
|
hotkey_ss58: str,
|
490
544
|
status=None,
|
491
|
-
|
545
|
+
era: int = 3,
|
546
|
+
) -> bool:
|
492
547
|
"""Execute a standard unstake extrinsic.
|
493
548
|
|
494
549
|
Args:
|
@@ -499,6 +554,7 @@ async def _unstake_extrinsic(
|
|
499
554
|
wallet: Wallet instance
|
500
555
|
subtensor: Subtensor interface
|
501
556
|
status: Optional status for console updates
|
557
|
+
era: blocks for which the transaction is valid
|
502
558
|
"""
|
503
559
|
err_out = partial(print_error, status=status)
|
504
560
|
failure_prelude = (
|
@@ -510,33 +566,32 @@ async def _unstake_extrinsic(
|
|
510
566
|
f"\n:satellite: Unstaking {amount} from {hotkey_ss58} on netuid: {netuid} ..."
|
511
567
|
)
|
512
568
|
|
513
|
-
current_balance = await
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
569
|
+
current_balance, call = await asyncio.gather(
|
570
|
+
subtensor.get_balance(wallet.coldkeypub.ss58_address),
|
571
|
+
subtensor.substrate.compose_call(
|
572
|
+
call_module="SubtensorModule",
|
573
|
+
call_function="remove_stake",
|
574
|
+
call_params={
|
575
|
+
"hotkey": hotkey_ss58,
|
576
|
+
"netuid": netuid,
|
577
|
+
"amount_unstaked": amount.rao,
|
578
|
+
},
|
579
|
+
),
|
522
580
|
)
|
523
581
|
extrinsic = await subtensor.substrate.create_signed_extrinsic(
|
524
|
-
call=call, keypair=wallet.coldkey
|
582
|
+
call=call, keypair=wallet.coldkey, era={"period": era}
|
525
583
|
)
|
526
584
|
|
527
585
|
try:
|
528
586
|
response = await subtensor.substrate.submit_extrinsic(
|
529
587
|
extrinsic, wait_for_inclusion=True, wait_for_finalization=False
|
530
588
|
)
|
531
|
-
await response.process_events()
|
532
|
-
|
533
589
|
if not await response.is_success:
|
534
590
|
err_out(
|
535
591
|
f"{failure_prelude} with error: "
|
536
592
|
f"{format_error_message(await response.error_message)}"
|
537
593
|
)
|
538
|
-
return
|
539
|
-
|
594
|
+
return False
|
540
595
|
# Fetch latest balance and stake
|
541
596
|
block_hash = await subtensor.substrate.get_chain_head()
|
542
597
|
new_balance, new_stake = await asyncio.gather(
|
@@ -557,9 +612,11 @@ async def _unstake_extrinsic(
|
|
557
612
|
f"Subnet: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{netuid}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}]"
|
558
613
|
f" Stake:\n [blue]{current_stake}[/blue] :arrow_right: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_stake}"
|
559
614
|
)
|
615
|
+
return True
|
560
616
|
|
561
617
|
except Exception as e:
|
562
618
|
err_out(f"{failure_prelude} with error: {str(e)}")
|
619
|
+
return False
|
563
620
|
|
564
621
|
|
565
622
|
async def _safe_unstake_extrinsic(
|
@@ -571,7 +628,8 @@ async def _safe_unstake_extrinsic(
|
|
571
628
|
price_limit: Balance,
|
572
629
|
allow_partial_stake: bool,
|
573
630
|
status=None,
|
574
|
-
|
631
|
+
era: int = 3,
|
632
|
+
) -> bool:
|
575
633
|
"""Execute a safe unstake extrinsic with price limit.
|
576
634
|
|
577
635
|
Args:
|
@@ -596,30 +654,31 @@ async def _safe_unstake_extrinsic(
|
|
596
654
|
|
597
655
|
block_hash = await subtensor.substrate.get_chain_head()
|
598
656
|
|
599
|
-
current_balance, next_nonce, current_stake = await asyncio.gather(
|
657
|
+
current_balance, next_nonce, current_stake, call = await asyncio.gather(
|
600
658
|
subtensor.get_balance(wallet.coldkeypub.ss58_address, block_hash),
|
601
659
|
subtensor.substrate.get_account_next_index(wallet.coldkeypub.ss58_address),
|
602
660
|
subtensor.get_stake(
|
603
661
|
hotkey_ss58=hotkey_ss58,
|
604
662
|
coldkey_ss58=wallet.coldkeypub.ss58_address,
|
605
663
|
netuid=netuid,
|
664
|
+
block_hash=block_hash,
|
665
|
+
),
|
666
|
+
subtensor.substrate.compose_call(
|
667
|
+
call_module="SubtensorModule",
|
668
|
+
call_function="remove_stake_limit",
|
669
|
+
call_params={
|
670
|
+
"hotkey": hotkey_ss58,
|
671
|
+
"netuid": netuid,
|
672
|
+
"amount_unstaked": amount.rao,
|
673
|
+
"limit_price": price_limit,
|
674
|
+
"allow_partial": allow_partial_stake,
|
675
|
+
},
|
676
|
+
block_hash=block_hash,
|
606
677
|
),
|
607
|
-
)
|
608
|
-
|
609
|
-
call = await subtensor.substrate.compose_call(
|
610
|
-
call_module="SubtensorModule",
|
611
|
-
call_function="remove_stake_limit",
|
612
|
-
call_params={
|
613
|
-
"hotkey": hotkey_ss58,
|
614
|
-
"netuid": netuid,
|
615
|
-
"amount_unstaked": amount.rao,
|
616
|
-
"limit_price": price_limit,
|
617
|
-
"allow_partial": allow_partial_stake,
|
618
|
-
},
|
619
678
|
)
|
620
679
|
|
621
680
|
extrinsic = await subtensor.substrate.create_signed_extrinsic(
|
622
|
-
call=call, keypair=wallet.coldkey, nonce=next_nonce
|
681
|
+
call=call, keypair=wallet.coldkey, nonce=next_nonce, era={"period": era}
|
623
682
|
)
|
624
683
|
|
625
684
|
try:
|
@@ -634,17 +693,15 @@ async def _safe_unstake_extrinsic(
|
|
634
693
|
f"Either increase price tolerance or enable partial unstaking.",
|
635
694
|
status=status,
|
636
695
|
)
|
637
|
-
return
|
638
696
|
else:
|
639
697
|
err_out(f"\n{failure_prelude} with error: {format_error_message(e)}")
|
640
|
-
return
|
698
|
+
return False
|
641
699
|
|
642
|
-
await response.process_events()
|
643
700
|
if not await response.is_success:
|
644
701
|
err_out(
|
645
702
|
f"\n{failure_prelude} with error: {format_error_message(await response.error_message)}"
|
646
703
|
)
|
647
|
-
return
|
704
|
+
return False
|
648
705
|
|
649
706
|
block_hash = await subtensor.substrate.get_chain_head()
|
650
707
|
new_balance, new_stake = await asyncio.gather(
|
@@ -675,6 +732,7 @@ async def _safe_unstake_extrinsic(
|
|
675
732
|
f"Subnet: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{netuid}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] "
|
676
733
|
f"Stake:\n [blue]{current_stake}[/blue] :arrow_right: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_stake}"
|
677
734
|
)
|
735
|
+
return True
|
678
736
|
|
679
737
|
|
680
738
|
async def _unstake_all_extrinsic(
|
@@ -684,6 +742,7 @@ async def _unstake_all_extrinsic(
|
|
684
742
|
hotkey_name: str,
|
685
743
|
unstake_all_alpha: bool,
|
686
744
|
status=None,
|
745
|
+
era: int = 3,
|
687
746
|
) -> None:
|
688
747
|
"""Execute an unstake all extrinsic.
|
689
748
|
|
@@ -734,13 +793,11 @@ async def _unstake_all_extrinsic(
|
|
734
793
|
try:
|
735
794
|
response = await subtensor.substrate.submit_extrinsic(
|
736
795
|
extrinsic=await subtensor.substrate.create_signed_extrinsic(
|
737
|
-
call=call,
|
738
|
-
keypair=wallet.coldkey,
|
796
|
+
call=call, keypair=wallet.coldkey, era={"period": era}
|
739
797
|
),
|
740
798
|
wait_for_inclusion=True,
|
741
799
|
wait_for_finalization=False,
|
742
800
|
)
|
743
|
-
await response.process_events()
|
744
801
|
|
745
802
|
if not await response.is_success:
|
746
803
|
err_out(
|
@@ -791,28 +848,47 @@ async def _unstake_all_extrinsic(
|
|
791
848
|
|
792
849
|
|
793
850
|
# Helpers
|
794
|
-
def _calculate_slippage(
|
851
|
+
def _calculate_slippage(
|
852
|
+
subnet_info, amount: Balance, stake_fee: Balance
|
853
|
+
) -> tuple[Balance, str, float]:
|
795
854
|
"""Calculate slippage and received amount for unstaking operation.
|
796
855
|
|
797
856
|
Args:
|
798
857
|
subnet_info: Subnet information containing price data
|
799
858
|
amount: Amount being unstaked
|
859
|
+
stake_fee: Stake fee to include in slippage calculation
|
800
860
|
|
801
861
|
Returns:
|
802
862
|
tuple containing:
|
803
|
-
- received_amount: Balance after slippage
|
863
|
+
- received_amount: Balance after slippage deduction
|
804
864
|
- slippage_pct: Formatted string of slippage percentage
|
805
865
|
- slippage_pct_float: Float value of slippage percentage
|
806
866
|
"""
|
807
|
-
received_amount, _,
|
808
|
-
|
809
|
-
|
867
|
+
received_amount, _, _ = subnet_info.alpha_to_tao_with_slippage(amount)
|
868
|
+
received_amount -= stake_fee
|
869
|
+
|
870
|
+
if received_amount < Balance.from_tao(0):
|
871
|
+
print_error("Not enough Alpha to pay the transaction fee.")
|
872
|
+
raise ValueError
|
810
873
|
|
811
874
|
if subnet_info.is_dynamic:
|
875
|
+
# Ideal amount w/o slippage
|
876
|
+
ideal_amount = subnet_info.alpha_to_tao(amount)
|
877
|
+
|
878
|
+
# Total slippage including fees
|
879
|
+
total_slippage = ideal_amount - received_amount
|
880
|
+
slippage_pct_float = (
|
881
|
+
100 * (float(total_slippage.tao) / float(ideal_amount.tao))
|
882
|
+
if ideal_amount.tao != 0
|
883
|
+
else 0
|
884
|
+
)
|
812
885
|
slippage_pct = f"{slippage_pct_float:.4f} %"
|
813
886
|
else:
|
814
|
-
|
815
|
-
|
887
|
+
# Root will only have fee-based slippage
|
888
|
+
slippage_pct_float = (
|
889
|
+
100 * float(stake_fee.tao) / float(amount.tao) if amount.tao != 0 else 0
|
890
|
+
)
|
891
|
+
slippage_pct = f"{slippage_pct_float:.4f} %"
|
816
892
|
|
817
893
|
return received_amount, slippage_pct, slippage_pct_float
|
818
894
|
|
@@ -1174,6 +1250,11 @@ def _create_unstake_table(
|
|
1174
1250
|
justify="center",
|
1175
1251
|
style=COLOR_PALETTE["POOLS"]["RATE"],
|
1176
1252
|
)
|
1253
|
+
table.add_column(
|
1254
|
+
f"Fee ({Balance.get_unit(0)})",
|
1255
|
+
justify="center",
|
1256
|
+
style=COLOR_PALETTE["STAKE"]["STAKE_AMOUNT"],
|
1257
|
+
)
|
1177
1258
|
table.add_column(
|
1178
1259
|
f"Received ({Balance.get_unit(0)})",
|
1179
1260
|
justify="center",
|
@@ -1227,7 +1308,8 @@ The columns are as follows:
|
|
1227
1308
|
- [bold white]Hotkey[/bold white]: The ss58 address or identity of the hotkey you are unstaking from.
|
1228
1309
|
- [bold white]Amount to Unstake[/bold white]: The stake amount you are removing from this key.
|
1229
1310
|
- [bold white]Rate[/bold white]: The rate of exchange between TAO and the subnet's stake.
|
1230
|
-
- [bold white]
|
1311
|
+
- [bold white]Fee[/bold white]: The transaction fee for this unstake operation.
|
1312
|
+
- [bold white]Received[/bold white]: The amount of free balance TAO you will receive on this subnet after slippage and fees.
|
1231
1313
|
- [bold white]Slippage[/bold white]: The slippage percentage of the unstake operation. (0% if the subnet is not dynamic i.e. root)."""
|
1232
1314
|
|
1233
1315
|
safe_staking_description = """
|
@@ -13,6 +13,7 @@ from bittensor_cli.src.bittensor.utils import (
|
|
13
13
|
err_console,
|
14
14
|
get_subnet_name,
|
15
15
|
print_error,
|
16
|
+
json_console,
|
16
17
|
)
|
17
18
|
|
18
19
|
if TYPE_CHECKING:
|
@@ -26,6 +27,7 @@ async def price(
|
|
26
27
|
interval_hours: int = 24,
|
27
28
|
html_output: bool = False,
|
28
29
|
log_scale: bool = False,
|
30
|
+
json_output: bool = False,
|
29
31
|
):
|
30
32
|
"""
|
31
33
|
Fetch historical price data for subnets and display it in a chart.
|
@@ -60,7 +62,7 @@ async def price(
|
|
60
62
|
all_subnet_infos = await asyncio.gather(*subnet_info_cors)
|
61
63
|
|
62
64
|
subnet_data = _process_subnet_data(
|
63
|
-
block_numbers, all_subnet_infos, netuids, all_netuids
|
65
|
+
block_numbers, all_subnet_infos, netuids, all_netuids
|
64
66
|
)
|
65
67
|
|
66
68
|
if not subnet_data:
|
@@ -71,17 +73,13 @@ async def price(
|
|
71
73
|
await _generate_html_output(
|
72
74
|
subnet_data, block_numbers, interval_hours, log_scale
|
73
75
|
)
|
76
|
+
elif json_output:
|
77
|
+
json_console.print(json.dumps(_generate_json_output(subnet_data)))
|
74
78
|
else:
|
75
79
|
_generate_cli_output(subnet_data, block_numbers, interval_hours, log_scale)
|
76
80
|
|
77
81
|
|
78
|
-
def _process_subnet_data(
|
79
|
-
block_numbers,
|
80
|
-
all_subnet_infos,
|
81
|
-
netuids,
|
82
|
-
all_netuids,
|
83
|
-
interval_hours,
|
84
|
-
):
|
82
|
+
def _process_subnet_data(block_numbers, all_subnet_infos, netuids, all_netuids):
|
85
83
|
"""
|
86
84
|
Process subnet data into a structured format for price analysis.
|
87
85
|
"""
|
@@ -772,6 +770,10 @@ async def _generate_html_output(
|
|
772
770
|
print_error(f"Error generating price chart: {e}")
|
773
771
|
|
774
772
|
|
773
|
+
def _generate_json_output(subnet_data):
|
774
|
+
return {netuid: data for netuid, data in subnet_data.items()}
|
775
|
+
|
776
|
+
|
775
777
|
def _generate_cli_output(subnet_data, block_numbers, interval_hours, log_scale):
|
776
778
|
"""
|
777
779
|
Render the price data in a textual CLI style with plotille ASCII charts.
|
@@ -802,7 +804,7 @@ def _generate_cli_output(subnet_data, block_numbers, interval_hours, log_scale):
|
|
802
804
|
|
803
805
|
fig.plot(
|
804
806
|
block_numbers,
|
805
|
-
|
807
|
+
prices,
|
806
808
|
label=f"Subnet {netuid} Price",
|
807
809
|
interp="linear",
|
808
810
|
lc="bae98f",
|