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.
@@ -1,4 +1,6 @@
1
1
  import asyncio
2
+ import json
3
+ from collections import defaultdict
2
4
  from functools import partial
3
5
 
4
6
  from typing import TYPE_CHECKING, Optional
@@ -17,6 +19,7 @@ from bittensor_cli.src.bittensor.utils import (
17
19
  print_error,
18
20
  print_verbose,
19
21
  unlock_key,
22
+ json_console,
20
23
  )
21
24
  from bittensor_wallet import Wallet
22
25
 
@@ -38,6 +41,8 @@ async def stake_add(
38
41
  safe_staking: bool,
39
42
  rate_tolerance: float,
40
43
  allow_partial_stake: bool,
44
+ json_output: bool,
45
+ era: int,
41
46
  ):
42
47
  """
43
48
  Args:
@@ -53,6 +58,8 @@ async def stake_add(
53
58
  safe_staking: whether to use safe staking
54
59
  rate_tolerance: rate tolerance percentage for stake operations
55
60
  allow_partial_stake: whether to allow partial stake
61
+ json_output: whether to output stake info in JSON format
62
+ era: Blocks for which the transaction should be valid.
56
63
 
57
64
  Returns:
58
65
  bool: True if stake operation is successful, False otherwise
@@ -65,28 +72,31 @@ async def stake_add(
65
72
  hotkey_ss58_: str,
66
73
  price_limit: Balance,
67
74
  status=None,
68
- ) -> None:
75
+ ) -> bool:
69
76
  err_out = partial(print_error, status=status)
70
77
  failure_prelude = (
71
78
  f":cross_mark: [red]Failed[/red] to stake {amount_} on Netuid {netuid_}"
72
79
  )
73
- current_balance = await subtensor.get_balance(wallet.coldkeypub.ss58_address)
74
- next_nonce = await subtensor.substrate.get_account_next_index(
75
- wallet.coldkeypub.ss58_address
76
- )
77
- call = await subtensor.substrate.compose_call(
78
- call_module="SubtensorModule",
79
- call_function="add_stake_limit",
80
- call_params={
81
- "hotkey": hotkey_ss58_,
82
- "netuid": netuid_,
83
- "amount_staked": amount_.rao,
84
- "limit_price": price_limit,
85
- "allow_partial": allow_partial_stake,
86
- },
80
+ current_balance, next_nonce, call = await asyncio.gather(
81
+ subtensor.get_balance(wallet.coldkeypub.ss58_address),
82
+ subtensor.substrate.get_account_next_index(wallet.coldkeypub.ss58_address),
83
+ subtensor.substrate.compose_call(
84
+ call_module="SubtensorModule",
85
+ call_function="add_stake_limit",
86
+ call_params={
87
+ "hotkey": hotkey_ss58_,
88
+ "netuid": netuid_,
89
+ "amount_staked": amount_.rao,
90
+ "limit_price": price_limit,
91
+ "allow_partial": allow_partial_stake,
92
+ },
93
+ ),
87
94
  )
88
95
  extrinsic = await subtensor.substrate.create_signed_extrinsic(
89
- call=call, keypair=wallet.coldkey, nonce=next_nonce
96
+ call=call,
97
+ keypair=wallet.coldkey,
98
+ nonce=next_nonce,
99
+ era={"period": era},
90
100
  )
91
101
  try:
92
102
  response = await subtensor.substrate.submit_extrinsic(
@@ -100,73 +110,80 @@ async def stake_add(
100
110
  f"Either increase price tolerance or enable partial staking.",
101
111
  status=status,
102
112
  )
103
- return
113
+ return False
104
114
  else:
105
115
  err_out(f"\n{failure_prelude} with error: {format_error_message(e)}")
106
- return
116
+ return False
117
+ if not await response.is_success:
118
+ err_out(
119
+ f"\n{failure_prelude} with error: {format_error_message(await response.error_message)}"
120
+ )
121
+ return False
107
122
  else:
108
- await response.process_events()
109
- if not await response.is_success:
110
- err_out(
111
- f"\n{failure_prelude} with error: {format_error_message(await response.error_message)}"
112
- )
113
- else:
114
- block_hash = await subtensor.substrate.get_chain_head()
115
- new_balance, new_stake = await asyncio.gather(
116
- subtensor.get_balance(wallet.coldkeypub.ss58_address, block_hash),
117
- subtensor.get_stake(
118
- hotkey_ss58=hotkey_ss58_,
119
- coldkey_ss58=wallet.coldkeypub.ss58_address,
120
- netuid=netuid_,
121
- block_hash=block_hash,
122
- ),
123
- )
124
- console.print(
125
- f":white_heavy_check_mark: [dark_sea_green3]Finalized. Stake added to netuid: {netuid_}[/dark_sea_green3]"
126
- )
127
- console.print(
128
- f"Balance:\n [blue]{current_balance}[/blue] :arrow_right: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_balance}"
129
- )
130
-
131
- amount_staked = current_balance - new_balance
132
- if allow_partial_stake and (amount_staked != amount_):
133
- console.print(
134
- "Partial stake transaction. Staked:\n"
135
- f" [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{amount_staked}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}] "
136
- f"instead of "
137
- f"[blue]{amount_}[/blue]"
138
- )
123
+ if json_output:
124
+ # the rest of this checking is not necessary if using json_output
125
+ return True
126
+ block_hash = await subtensor.substrate.get_chain_head()
127
+ new_balance, new_stake = await asyncio.gather(
128
+ subtensor.get_balance(wallet.coldkeypub.ss58_address, block_hash),
129
+ subtensor.get_stake(
130
+ hotkey_ss58=hotkey_ss58_,
131
+ coldkey_ss58=wallet.coldkeypub.ss58_address,
132
+ netuid=netuid_,
133
+ block_hash=block_hash,
134
+ ),
135
+ )
136
+ console.print(
137
+ f":white_heavy_check_mark: [dark_sea_green3]Finalized. "
138
+ f"Stake added to netuid: {netuid_}[/dark_sea_green3]"
139
+ )
140
+ console.print(
141
+ f"Balance:\n [blue]{current_balance}[/blue] :arrow_right: "
142
+ f"[{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_balance}"
143
+ )
139
144
 
145
+ amount_staked = current_balance - new_balance
146
+ if allow_partial_stake and (amount_staked != amount_):
140
147
  console.print(
141
- f"Subnet: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{netuid_}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] "
142
- f"Stake:\n"
143
- f" [blue]{current_stake}[/blue] "
144
- f":arrow_right: "
145
- f"[{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_stake}\n"
148
+ "Partial stake transaction. Staked:\n"
149
+ f" [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{amount_staked}"
150
+ f"[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}] "
151
+ f"instead of "
152
+ f"[blue]{amount_}[/blue]"
146
153
  )
147
154
 
155
+ console.print(
156
+ f"Subnet: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]"
157
+ f"{netuid_}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] "
158
+ f"Stake:\n"
159
+ f" [blue]{current_stake}[/blue] "
160
+ f":arrow_right: "
161
+ f"[{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_stake}\n"
162
+ )
163
+ return True
164
+
148
165
  async def stake_extrinsic(
149
166
  netuid_i, amount_, current, staking_address_ss58, status=None
150
- ):
167
+ ) -> bool:
151
168
  err_out = partial(print_error, status=status)
152
- current_balance = await subtensor.get_balance(wallet.coldkeypub.ss58_address)
169
+ current_balance, next_nonce, call = await asyncio.gather(
170
+ subtensor.get_balance(wallet.coldkeypub.ss58_address),
171
+ subtensor.substrate.get_account_next_index(wallet.coldkeypub.ss58_address),
172
+ subtensor.substrate.compose_call(
173
+ call_module="SubtensorModule",
174
+ call_function="add_stake",
175
+ call_params={
176
+ "hotkey": staking_address_ss58,
177
+ "netuid": netuid_i,
178
+ "amount_staked": amount_.rao,
179
+ },
180
+ ),
181
+ )
153
182
  failure_prelude = (
154
183
  f":cross_mark: [red]Failed[/red] to stake {amount} on Netuid {netuid_i}"
155
184
  )
156
- next_nonce = await subtensor.substrate.get_account_next_index(
157
- wallet.coldkeypub.ss58_address
158
- )
159
- call = await subtensor.substrate.compose_call(
160
- call_module="SubtensorModule",
161
- call_function="add_stake",
162
- call_params={
163
- "hotkey": staking_address_ss58,
164
- "netuid": netuid_i,
165
- "amount_staked": amount_.rao,
166
- },
167
- )
168
185
  extrinsic = await subtensor.substrate.create_signed_extrinsic(
169
- call=call, keypair=wallet.coldkey, nonce=next_nonce
186
+ call=call, keypair=wallet.coldkey, nonce=next_nonce, era={"period": era}
170
187
  )
171
188
  try:
172
189
  response = await subtensor.substrate.submit_extrinsic(
@@ -174,35 +191,46 @@ async def stake_add(
174
191
  )
175
192
  except SubstrateRequestException as e:
176
193
  err_out(f"\n{failure_prelude} with error: {format_error_message(e)}")
177
- return
194
+ return False
178
195
  else:
179
- await response.process_events()
180
196
  if not await response.is_success:
181
197
  err_out(
182
198
  f"\n{failure_prelude} with error: {format_error_message(await response.error_message)}"
183
199
  )
200
+ return False
184
201
  else:
202
+ if json_output:
203
+ # the rest of this is not necessary if using json_output
204
+ return True
205
+ new_block_hash = await subtensor.substrate.get_chain_head()
185
206
  new_balance, new_stake = await asyncio.gather(
186
- subtensor.get_balance(wallet.coldkeypub.ss58_address),
207
+ subtensor.get_balance(
208
+ wallet.coldkeypub.ss58_address, block_hash=new_block_hash
209
+ ),
187
210
  subtensor.get_stake(
188
211
  hotkey_ss58=staking_address_ss58,
189
212
  coldkey_ss58=wallet.coldkeypub.ss58_address,
190
213
  netuid=netuid_i,
214
+ block_hash=new_block_hash,
191
215
  ),
192
216
  )
193
217
  console.print(
194
- f":white_heavy_check_mark: [dark_sea_green3]Finalized. Stake added to netuid: {netuid_i}[/dark_sea_green3]"
218
+ f":white_heavy_check_mark: "
219
+ f"[dark_sea_green3]Finalized. Stake added to netuid: {netuid_i}[/dark_sea_green3]"
195
220
  )
196
221
  console.print(
197
- f"Balance:\n [blue]{current_balance}[/blue] :arrow_right: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_balance}"
222
+ f"Balance:\n [blue]{current_balance}[/blue] :arrow_right: "
223
+ f"[{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_balance}"
198
224
  )
199
225
  console.print(
200
- f"Subnet: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{netuid_i}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] "
226
+ f"Subnet: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]"
227
+ f"{netuid_i}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] "
201
228
  f"Stake:\n"
202
229
  f" [blue]{current}[/blue] "
203
230
  f":arrow_right: "
204
231
  f"[{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_stake}\n"
205
232
  )
233
+ return True
206
234
 
207
235
  netuids = (
208
236
  [int(netuid)]
@@ -282,10 +310,24 @@ async def stake_add(
282
310
  return False
283
311
  remaining_wallet_balance -= amount_to_stake
284
312
 
285
- # Calculate slippage
286
- received_amount, slippage_pct, slippage_pct_float, rate = (
287
- _calculate_slippage(subnet_info, amount_to_stake)
313
+ stake_fee = await subtensor.get_stake_fee(
314
+ origin_hotkey_ss58=None,
315
+ origin_netuid=None,
316
+ origin_coldkey_ss58=wallet.coldkeypub.ss58_address,
317
+ destination_hotkey_ss58=hotkey[1],
318
+ destination_netuid=netuid,
319
+ destination_coldkey_ss58=wallet.coldkeypub.ss58_address,
320
+ amount=amount_to_stake.rao,
288
321
  )
322
+
323
+ # Calculate slippage
324
+ try:
325
+ received_amount, slippage_pct, slippage_pct_float, rate = (
326
+ _calculate_slippage(subnet_info, amount_to_stake, stake_fee)
327
+ )
328
+ except ValueError:
329
+ return False
330
+
289
331
  max_slippage = max(slippage_pct_float, max_slippage)
290
332
 
291
333
  # Add rows for the table
@@ -296,6 +338,7 @@ async def stake_add(
296
338
  str(rate)
297
339
  + f" {Balance.get_unit(netuid)}/{Balance.get_unit(0)} ", # rate
298
340
  str(received_amount.set_unit(netuid)), # received
341
+ str(stake_fee), # fee
299
342
  str(slippage_pct), # slippage
300
343
  ]
301
344
 
@@ -318,7 +361,9 @@ async def stake_add(
318
361
  base_row.extend(
319
362
  [
320
363
  f"{rate_with_tolerance} {Balance.get_unit(netuid)}/{Balance.get_unit(0)} ",
321
- f"[{'dark_sea_green3' if allow_partial_stake else 'red'}]{allow_partial_stake}[/{'dark_sea_green3' if allow_partial_stake else 'red'}]", # safe staking
364
+ f"[{'dark_sea_green3' if allow_partial_stake else 'red'}]"
365
+ # safe staking
366
+ f"{allow_partial_stake}[/{'dark_sea_green3' if allow_partial_stake else 'red'}]",
322
367
  ]
323
368
  )
324
369
 
@@ -337,7 +382,7 @@ async def stake_add(
337
382
  return False
338
383
 
339
384
  if safe_staking:
340
- stake_coroutines = []
385
+ stake_coroutines = {}
341
386
  for i, (ni, am, curr, price_with_tolerance) in enumerate(
342
387
  zip(
343
388
  netuids, amounts_to_stake, current_stake_balances, prices_with_tolerance
@@ -346,27 +391,23 @@ async def stake_add(
346
391
  for _, staking_address in hotkeys_to_stake_to:
347
392
  # Regular extrinsic for root subnet
348
393
  if ni == 0:
349
- stake_coroutines.append(
350
- stake_extrinsic(
351
- netuid_i=ni,
352
- amount_=am,
353
- current=curr,
354
- staking_address_ss58=staking_address,
355
- )
394
+ stake_coroutines[(ni, staking_address)] = stake_extrinsic(
395
+ netuid_i=ni,
396
+ amount_=am,
397
+ current=curr,
398
+ staking_address_ss58=staking_address,
356
399
  )
357
400
  else:
358
- stake_coroutines.append(
359
- safe_stake_extrinsic(
360
- netuid_=ni,
361
- amount_=am,
362
- current_stake=curr,
363
- hotkey_ss58_=staking_address,
364
- price_limit=price_with_tolerance,
365
- )
401
+ stake_coroutines[(ni, staking_address)] = safe_stake_extrinsic(
402
+ netuid_=ni,
403
+ amount_=am,
404
+ current_stake=curr,
405
+ hotkey_ss58_=staking_address,
406
+ price_limit=price_with_tolerance,
366
407
  )
367
408
  else:
368
- stake_coroutines = [
369
- stake_extrinsic(
409
+ stake_coroutines = {
410
+ (ni, staking_address): stake_extrinsic(
370
411
  netuid_i=ni,
371
412
  amount_=am,
372
413
  current=curr,
@@ -376,12 +417,15 @@ async def stake_add(
376
417
  zip(netuids, amounts_to_stake, current_stake_balances)
377
418
  )
378
419
  for _, staking_address in hotkeys_to_stake_to
379
- ]
380
-
420
+ }
421
+ successes = defaultdict(dict)
381
422
  with console.status(f"\n:satellite: Staking on netuid(s): {netuids} ..."):
382
423
  # We can gather them all at once but balance reporting will be in race-condition.
383
- for coroutine in stake_coroutines:
384
- await coroutine
424
+ for (ni, staking_address), coroutine in stake_coroutines.items():
425
+ success = await coroutine
426
+ successes[ni][staking_address] = success
427
+ if json_output:
428
+ json_console.print(json.dumps({"staking_success": successes}))
385
429
 
386
430
 
387
431
  # Helper functions
@@ -531,6 +575,11 @@ def _define_stake_table(
531
575
  justify="center",
532
576
  style=COLOR_PALETTE["POOLS"]["TAO_EQUIV"],
533
577
  )
578
+ table.add_column(
579
+ "Fee (τ)",
580
+ justify="center",
581
+ style=COLOR_PALETTE["STAKE"]["STAKE_AMOUNT"],
582
+ )
534
583
  table.add_column(
535
584
  "Slippage", justify="center", style=COLOR_PALETTE["STAKE"]["SLIPPAGE_PERCENT"]
536
585
  )
@@ -585,29 +634,41 @@ The columns are as follows:
585
634
 
586
635
 
587
636
  def _calculate_slippage(
588
- subnet_info, amount: Balance
637
+ subnet_info, amount: Balance, stake_fee: Balance
589
638
  ) -> tuple[Balance, str, float, str]:
590
639
  """Calculate slippage when adding stake.
591
640
 
592
641
  Args:
593
642
  subnet_info: Subnet dynamic info
594
643
  amount: Amount being staked
644
+ stake_fee: Transaction fee for the stake operation
595
645
 
596
646
  Returns:
597
647
  tuple containing:
598
- - received_amount: Amount received after slippage
648
+ - received_amount: Amount received after slippage and fees
599
649
  - slippage_str: Formatted slippage percentage string
600
650
  - slippage_float: Raw slippage percentage value
651
+ - rate: Exchange rate string
601
652
  """
602
- received_amount, _, slippage_pct_float = subnet_info.tao_to_alpha_with_slippage(
603
- amount
604
- )
653
+ amount_after_fee = amount - stake_fee
654
+
655
+ if amount_after_fee < 0:
656
+ print_error("You don't have enough balance to cover the stake fee.")
657
+ raise ValueError()
658
+
659
+ received_amount, _, _ = subnet_info.tao_to_alpha_with_slippage(amount_after_fee)
660
+
605
661
  if subnet_info.is_dynamic:
662
+ ideal_amount = subnet_info.tao_to_alpha(amount)
663
+ total_slippage = ideal_amount - received_amount
664
+ slippage_pct_float = 100 * (total_slippage.tao / ideal_amount.tao)
606
665
  slippage_str = f"{slippage_pct_float:.4f} %"
607
666
  rate = f"{(1 / subnet_info.price.tao or 1):.4f}"
608
667
  else:
609
- slippage_pct_float = 0
610
- slippage_str = f"[{COLOR_PALETTE['STAKE']['SLIPPAGE_TEXT']}]N/A[/{COLOR_PALETTE['STAKE']['SLIPPAGE_TEXT']}]"
668
+ slippage_pct_float = (
669
+ 100 * float(stake_fee.tao) / float(amount.tao) if amount.tao != 0 else 0
670
+ )
671
+ slippage_str = f"{slippage_pct_float:.4f} %"
611
672
  rate = "1"
612
673
 
613
674
  return received_amount, slippage_str, slippage_pct_float, rate