bittensor-cli 9.19.0__tar.gz → 9.20.0__tar.gz

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 (67) hide show
  1. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/PKG-INFO +1 -1
  2. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/cli.py +39 -9
  3. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/chain_data.py +3 -3
  4. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/subtensor_interface.py +80 -2
  5. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/stake/add.py +156 -49
  6. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/stake/remove.py +242 -45
  7. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/wallets.py +150 -4
  8. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/pyproject.toml +1 -1
  9. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/README.md +0 -0
  10. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/__init__.py +0 -0
  11. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/doc_generation_helper.py +0 -0
  12. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/__init__.py +0 -0
  13. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/__init__.py +0 -0
  14. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/balances.py +0 -0
  15. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/extrinsics/__init__.py +0 -0
  16. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/extrinsics/mev_shield.py +0 -0
  17. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/extrinsics/registration.py +0 -0
  18. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/extrinsics/root.py +0 -0
  19. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/extrinsics/serving.py +0 -0
  20. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/extrinsics/transfer.py +0 -0
  21. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/minigraph.py +0 -0
  22. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/networking.py +0 -0
  23. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/templates/main-filters.j2 +0 -0
  24. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/templates/main-header.j2 +0 -0
  25. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/templates/neuron-details.j2 +0 -0
  26. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/templates/price-multi.j2 +0 -0
  27. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/templates/price-single.j2 +0 -0
  28. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/templates/subnet-details-header.j2 +0 -0
  29. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/templates/subnet-details.j2 +0 -0
  30. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/templates/subnet-metrics.j2 +0 -0
  31. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/templates/subnets-table.j2 +0 -0
  32. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/templates/table.j2 +0 -0
  33. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/templates/view.css +0 -0
  34. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/templates/view.j2 +0 -0
  35. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/templates/view.js +0 -0
  36. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/bittensor/utils.py +0 -0
  37. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/__init__.py +0 -0
  38. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/axon/__init__.py +0 -0
  39. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/axon/axon.py +0 -0
  40. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/crowd/__init__.py +0 -0
  41. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/crowd/contribute.py +0 -0
  42. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/crowd/contributors.py +0 -0
  43. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/crowd/create.py +0 -0
  44. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/crowd/dissolve.py +0 -0
  45. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/crowd/refund.py +0 -0
  46. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/crowd/update.py +0 -0
  47. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/crowd/utils.py +0 -0
  48. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/crowd/view.py +0 -0
  49. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/liquidity/__init__.py +0 -0
  50. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/liquidity/liquidity.py +0 -0
  51. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/liquidity/utils.py +0 -0
  52. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/proxy.py +0 -0
  53. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/stake/__init__.py +0 -0
  54. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/stake/auto_staking.py +0 -0
  55. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/stake/children_hotkeys.py +0 -0
  56. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/stake/claim.py +0 -0
  57. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/stake/list.py +0 -0
  58. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/stake/move.py +0 -0
  59. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/stake/wizard.py +0 -0
  60. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/subnets/__init__.py +0 -0
  61. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/subnets/mechanisms.py +0 -0
  62. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/subnets/price.py +0 -0
  63. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/subnets/subnets.py +0 -0
  64. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/sudo.py +0 -0
  65. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/view.py +0 -0
  66. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/src/commands/weights.py +0 -0
  67. {bittensor_cli-9.19.0 → bittensor_cli-9.20.0}/bittensor_cli/version.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bittensor-cli
3
- Version: 9.19.0
3
+ Version: 9.20.0
4
4
  Summary: Bittensor CLI
5
5
  Author: bittensor.com
6
6
  Requires-Python: >=3.10
@@ -4144,7 +4144,11 @@ class CLIManager:
4144
4144
  self,
4145
4145
  action: str = typer.Argument(
4146
4146
  None,
4147
- help="Action to perform: 'announce' to announce intent, 'execute' to complete swap after delay, 'dispute' to freeze the swap.",
4147
+ help=(
4148
+ "Action to perform: 'announce' to announce intent, "
4149
+ "'execute' to complete swap after delay, 'dispute' to freeze the swap, "
4150
+ "'clear' to withdraw announcement, 'check' to view status."
4151
+ ),
4148
4152
  ),
4149
4153
  wallet_name: Optional[str] = Options.wallet_name,
4150
4154
  wallet_path: Optional[str] = Options.wallet_path,
@@ -4176,6 +4180,9 @@ class CLIManager:
4176
4180
  If you suspect compromise, you can [bold]Dispute[/bold] an active announcement to freeze
4177
4181
  all activity for the coldkey until the triumvirate can intervene.
4178
4182
 
4183
+ If you want to withdraw your announcement, you can [bold]Clear[/bold] (withdraw) an announcement once the
4184
+ reannouncement delay has elapsed.
4185
+
4179
4186
  EXAMPLES
4180
4187
 
4181
4188
  Step 1 - Announce your intent to swap:
@@ -4190,9 +4197,13 @@ class CLIManager:
4190
4197
 
4191
4198
  [green]$[/green] btcli wallet swap-coldkey dispute
4192
4199
 
4193
- Check status of pending swaps:
4200
+ Clear (withdraw) an announcement:
4201
+
4202
+ [green]$[/green] btcli wallet swap-coldkey clear
4194
4203
 
4195
- [green]$[/green] btcli wallet swap-check
4204
+ Check status of your swap:
4205
+
4206
+ [green]$[/green] btcli wallet swap-coldkey check
4196
4207
  """
4197
4208
  self.verbosity_handler(quiet, verbose, prompt=False, json_output=False)
4198
4209
 
@@ -4201,18 +4212,19 @@ class CLIManager:
4201
4212
  "\n[bold][blue]Coldkey Swap Actions:[/blue][/bold]\n"
4202
4213
  " [dark_sea_green3]announce[/dark_sea_green3] - Start the swap process (pays fee, starts delay timer)\n"
4203
4214
  " [dark_sea_green3]execute[/dark_sea_green3] - Complete the swap (after delay period)\n"
4204
- " [dark_sea_green3]dispute[/dark_sea_green3] - Freeze the swap process if you suspect compromise\n\n"
4205
- " [dim]You can check the current status of your swap with 'btcli wallet swap-check'.[/dim]\n"
4215
+ " [dark_sea_green3]dispute[/dark_sea_green3] - Freeze the swap process if you suspect compromise\n"
4216
+ " [dark_sea_green3]clear[/dark_sea_green3] - Withdraw your swap announcement\n"
4217
+ " [dark_sea_green3]check[/dark_sea_green3] - Check the status of your swap\n\n"
4206
4218
  )
4207
4219
  action = Prompt.ask(
4208
4220
  "Select action",
4209
- choices=["announce", "execute", "dispute"],
4221
+ choices=["announce", "execute", "dispute", "clear", "check"],
4210
4222
  default="announce",
4211
4223
  )
4212
4224
 
4213
- if action.lower() not in ("announce", "execute", "dispute"):
4225
+ if action.lower() not in ("announce", "execute", "dispute", "clear", "check"):
4214
4226
  print_error(
4215
- f"Invalid action: {action}. Must be 'announce', 'execute', or 'dispute'."
4227
+ f"Invalid action: {action}. Must be 'announce', 'execute', 'dispute', 'clear', or 'check'."
4216
4228
  )
4217
4229
  raise typer.Exit(1)
4218
4230
 
@@ -4233,7 +4245,7 @@ class CLIManager:
4233
4245
  )
4234
4246
 
4235
4247
  new_wallet_coldkey_ss58 = None
4236
- if action != "dispute":
4248
+ if action not in ("dispute", "clear", "check"):
4237
4249
  if not new_wallet_or_ss58:
4238
4250
  new_wallet_or_ss58 = Prompt.ask(
4239
4251
  "Enter the [blue]new wallet name[/blue] or [blue]SS58 address[/blue] of the new coldkey",
@@ -4285,6 +4297,24 @@ class CLIManager:
4285
4297
  mev_protection=mev_protection,
4286
4298
  )
4287
4299
  )
4300
+ elif action == "clear":
4301
+ return self._run_command(
4302
+ wallets.clear_coldkey_swap_announcement(
4303
+ wallet=wallet,
4304
+ subtensor=self.initialize_chain(network),
4305
+ decline=decline,
4306
+ quiet=quiet,
4307
+ prompt=prompt,
4308
+ mev_protection=mev_protection,
4309
+ )
4310
+ )
4311
+ elif action == "check":
4312
+ return self._run_command(
4313
+ wallets.check_swap_status(
4314
+ subtensor=self.initialize_chain(network),
4315
+ origin_ss58=wallet.coldkeypub.ss58_address,
4316
+ )
4317
+ )
4288
4318
  else:
4289
4319
  return self._run_command(
4290
4320
  wallets.execute_coldkey_swap(
@@ -76,9 +76,9 @@ def _chr_str(codes: tuple[int]) -> str:
76
76
 
77
77
 
78
78
  def process_nested(
79
- data: Sequence[dict[Hashable, tuple[int]]] | dict,
79
+ data: Sequence[dict[Hashable, tuple[int]]] | dict | Any,
80
80
  chr_transform: Callable[[tuple[int]], str],
81
- ) -> list[dict[Hashable, str]] | dict[Hashable, str]:
81
+ ) -> list[dict[Hashable, str]] | dict[Hashable, str] | Any:
82
82
  """Processes nested data structures by applying a transformation function to their elements."""
83
83
  if isinstance(data, Sequence):
84
84
  if len(data) > 0 and isinstance(data[0], dict):
@@ -93,7 +93,7 @@ def process_nested(
93
93
  elif isinstance(data, dict):
94
94
  return {k: chr_transform(v) for k, v in data.items()}
95
95
  else:
96
- raise TypeError(f"Unsupported data type {type(data)}")
96
+ return data
97
97
 
98
98
 
99
99
  @dataclass
@@ -1271,8 +1271,8 @@ class SubtensorInterface:
1271
1271
  err_msg = format_error_message(e)
1272
1272
  if mev_protection and "'result': 'invalid'" in str(e).lower():
1273
1273
  err_msg = (
1274
- f"MEV Shield extrinsic rejected as invalid. "
1275
- f"This usually means the MEV Shield NextKey changed between fetching and submission."
1274
+ "MEV Shield extrinsic rejected as invalid. "
1275
+ "This usually means the MEV Shield NextKey changed between fetching and submission."
1276
1276
  )
1277
1277
  if proxy and "Invalid Transaction" in err_msg:
1278
1278
  extrinsic_fee, signer_balance = await asyncio.gather(
@@ -1289,6 +1289,84 @@ class SubtensorInterface:
1289
1289
  )
1290
1290
  return False, err_msg, None
1291
1291
 
1292
+ async def sign_and_send_batch_extrinsic(
1293
+ self,
1294
+ calls: list[GenericCall],
1295
+ wallet: Wallet,
1296
+ wait_for_inclusion: bool = True,
1297
+ wait_for_finalization: bool = False,
1298
+ era: Optional[dict[str, int]] = None,
1299
+ proxy: Optional[str] = None,
1300
+ nonce: Optional[int] = None,
1301
+ sign_with: Literal["coldkey", "hotkey", "coldkeypub"] = "coldkey",
1302
+ announce_only: bool = False,
1303
+ mev_protection: bool = False,
1304
+ block_hash: Optional[str] = None,
1305
+ ) -> tuple[bool, str, Optional[AsyncExtrinsicReceipt]]:
1306
+ """
1307
+ Wraps multiple extrinsic calls into a single Utility.batch_all transaction
1308
+ and submits it. This reduces fees by combining N separate transactions into one.
1309
+
1310
+ batch_all is atomic: if any call in the batch fails, the entire batch reverts.
1311
+
1312
+ For a single call, this delegates directly to sign_and_send_extrinsic without
1313
+ wrapping, so there's no overhead for non-batch use cases.
1314
+
1315
+ :param calls: list of prepared GenericCall objects to batch together.
1316
+ :param wallet: the wallet whose key will sign the extrinsic.
1317
+ :param wait_for_inclusion: wait until the extrinsic is included on chain.
1318
+ :param wait_for_finalization: wait until the extrinsic is finalized on chain.
1319
+ :param era: validity period in blocks for the transaction.
1320
+ :param proxy: the real account if using a proxy. None otherwise.
1321
+ :param nonce: explicit nonce for submission. Fetched automatically if None.
1322
+ :param sign_with: which wallet keypair signs the extrinsic.
1323
+ :param announce_only: make the call as a proxy announcement.
1324
+ :param mev_protection: encrypt the extrinsic via MEV Shield.
1325
+ :param block_hash: cached block hash for compose_call. Fetched if None.
1326
+
1327
+ :return: (success, error message or inner hash, extrinsic receipt | None)
1328
+ """
1329
+ if not calls:
1330
+ return False, "No calls to batch", None
1331
+
1332
+ # No need to wrap a single call in a batch
1333
+ if len(calls) == 1:
1334
+ return await self.sign_and_send_extrinsic(
1335
+ call=calls[0],
1336
+ wallet=wallet,
1337
+ wait_for_inclusion=wait_for_inclusion,
1338
+ wait_for_finalization=wait_for_finalization,
1339
+ era=era,
1340
+ proxy=proxy,
1341
+ nonce=nonce,
1342
+ sign_with=sign_with,
1343
+ announce_only=announce_only,
1344
+ mev_protection=mev_protection,
1345
+ )
1346
+
1347
+ if block_hash is None:
1348
+ block_hash = await self.substrate.get_chain_head()
1349
+
1350
+ batch_call = await self.substrate.compose_call(
1351
+ call_module="Utility",
1352
+ call_function="batch_all",
1353
+ call_params={"calls": calls},
1354
+ block_hash=block_hash,
1355
+ )
1356
+
1357
+ return await self.sign_and_send_extrinsic(
1358
+ call=batch_call,
1359
+ wallet=wallet,
1360
+ wait_for_inclusion=wait_for_inclusion,
1361
+ wait_for_finalization=wait_for_finalization,
1362
+ era=era,
1363
+ proxy=proxy,
1364
+ nonce=nonce,
1365
+ sign_with=sign_with,
1366
+ announce_only=announce_only,
1367
+ mev_protection=mev_protection,
1368
+ )
1369
+
1292
1370
  async def get_children(self, hotkey, netuid) -> tuple[bool, list, str]:
1293
1371
  """
1294
1372
  This method retrieves the children of a given hotkey and netuid. It queries the SubtensorModule's ChildKeys
@@ -467,62 +467,169 @@ async def stake_add(
467
467
  if not unlock_key(wallet).success:
468
468
  return
469
469
 
470
+ # Build the list of (netuid, hotkey, amount, current_stake, price_limit) tuples
471
+ # that describe each staking operation we need to perform.
472
+ # The zip aligns netuids with amounts/balances (which are populated per
473
+ # hotkey-netuid pair, but the zip truncates to len(netuids), matching the
474
+ # original execution order). Each netuid's amount/price applies to all hotkeys.
475
+ operations = []
476
+ if safe_staking:
477
+ for ni, am, curr, price in zip(
478
+ netuids, amounts_to_stake, current_stake_balances, prices_with_tolerance
479
+ ):
480
+ for _, staking_address in hotkeys_to_stake_to:
481
+ operations.append((ni, staking_address, am, curr, price))
482
+ else:
483
+ for ni, am, curr in zip(netuids, amounts_to_stake, current_stake_balances):
484
+ for _, staking_address in hotkeys_to_stake_to:
485
+ operations.append((ni, staking_address, am, curr, None))
486
+
487
+ total_ops = len(operations)
488
+ use_batch = total_ops > 1
489
+
470
490
  successes = defaultdict(dict)
471
491
  error_messages = defaultdict(dict)
472
492
  extrinsic_ids = defaultdict(dict)
473
- with console.status(f"\n:satellite: Staking on netuid(s): {netuids} ...") as status:
474
- if safe_staking:
475
- stake_coroutines = {}
476
- for i, (ni, am, curr, price_with_tolerance) in enumerate(
477
- zip(
478
- netuids,
479
- amounts_to_stake,
480
- current_stake_balances,
481
- prices_with_tolerance,
482
- )
483
- ):
484
- for _, staking_address in hotkeys_to_stake_to:
485
- # Regular extrinsic for root subnet
486
- if ni == 0:
487
- stake_coroutines[(ni, staking_address)] = stake_extrinsic(
488
- netuid_i=ni,
489
- amount_=am,
490
- current=curr,
491
- staking_address_ss58=staking_address,
492
- status_=status,
493
+
494
+ if use_batch:
495
+ # Batch path: compose all calls, submit as a single Utility.batch_all transaction
496
+ with console.status(
497
+ f"\n:satellite: Batching {total_ops} stake operations on netuid(s): {netuids} ..."
498
+ ) as status:
499
+ batch_block_hash = await subtensor.substrate.get_chain_head()
500
+ current_balance = await subtensor.get_balance(
501
+ coldkey_ss58, block_hash=batch_block_hash
502
+ )
503
+
504
+ # compose_call with a block_hash does no I/O, so no need for gather
505
+ calls = []
506
+ for ni, hk, am, _, price in operations:
507
+ if safe_staking and ni != 0:
508
+ calls.append(
509
+ await subtensor.substrate.compose_call(
510
+ call_module="SubtensorModule",
511
+ call_function="add_stake_limit",
512
+ call_params={
513
+ "hotkey": hk,
514
+ "netuid": ni,
515
+ "amount_staked": am.rao,
516
+ "limit_price": price.rao,
517
+ "allow_partial": allow_partial_stake,
518
+ },
519
+ block_hash=batch_block_hash,
493
520
  )
494
- else:
495
- stake_coroutines[(ni, staking_address)] = safe_stake_extrinsic(
496
- netuid_=ni,
497
- amount_=am,
498
- current_stake=curr,
499
- hotkey_ss58_=staking_address,
500
- price_limit=price_with_tolerance,
501
- status_=status,
521
+ )
522
+ else:
523
+ calls.append(
524
+ await subtensor.substrate.compose_call(
525
+ call_module="SubtensorModule",
526
+ call_function="add_stake",
527
+ call_params={
528
+ "hotkey": hk,
529
+ "netuid": ni,
530
+ "amount_staked": am.rao,
531
+ },
532
+ block_hash=batch_block_hash,
502
533
  )
503
- else:
504
- stake_coroutines = {
505
- (ni, staking_address): stake_extrinsic(
506
- netuid_i=ni,
507
- amount_=am,
508
- current=curr,
509
- staking_address_ss58=staking_address,
510
- status_=status,
511
- )
512
- for i, (ni, am, curr) in enumerate(
513
- zip(netuids, amounts_to_stake, current_stake_balances)
534
+ )
535
+
536
+ success, err_msg, response = await subtensor.sign_and_send_batch_extrinsic(
537
+ calls=list(calls),
538
+ wallet=wallet,
539
+ era={"period": era},
540
+ proxy=proxy,
541
+ mev_protection=mev_protection,
542
+ block_hash=batch_block_hash,
543
+ )
544
+
545
+ if success and mev_protection:
546
+ inner_hash = err_msg
547
+ success, mev_error, response = await wait_for_extrinsic_by_hash(
548
+ subtensor=subtensor,
549
+ extrinsic_hash=inner_hash,
550
+ submit_block_hash=response.block_hash,
551
+ status=status,
514
552
  )
515
- for _, staking_address in hotkeys_to_stake_to
516
- }
517
- # We can gather them all at once but balance reporting will be in race-condition.
518
- for (ni, staking_address), coroutine in stake_coroutines.items():
519
- success, er_msg, ext_receipt = await coroutine
520
- successes[ni][staking_address] = success
521
- error_messages[ni][staking_address] = er_msg
553
+ if not success:
554
+ err_msg = mev_error
555
+
556
+ # batch_all is atomic: all succeed or all revert
557
+ for ni, hk, am, curr, _ in operations:
558
+ successes[ni][hk] = success
559
+ error_messages[ni][hk] = "" if success else err_msg
560
+
522
561
  if success:
523
- extrinsic_ids[ni][
524
- staking_address
525
- ] = await ext_receipt.get_extrinsic_identifier()
562
+ ext_id = await response.get_extrinsic_identifier()
563
+ for ni, hk, _, _, _ in operations:
564
+ extrinsic_ids[ni][hk] = ext_id
565
+
566
+ if not json_output:
567
+ await print_extrinsic_id(response)
568
+ new_block_hash = await subtensor.substrate.get_chain_head()
569
+ new_balance = await subtensor.get_balance(
570
+ coldkey_ss58, block_hash=new_block_hash
571
+ )
572
+ print_success(
573
+ f"[dark_sea_green3]Batch finalized. "
574
+ f"Staked across {total_ops} operations.[/dark_sea_green3]"
575
+ )
576
+ console.print(
577
+ f"Balance:\n [blue]{current_balance}[/blue] :arrow_right: "
578
+ f"[{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_balance}"
579
+ )
580
+ for ni, hk, am, curr, _ in operations:
581
+ new_stake = await subtensor.get_stake(
582
+ hotkey_ss58=hk,
583
+ coldkey_ss58=coldkey_ss58,
584
+ netuid=ni,
585
+ block_hash=new_block_hash,
586
+ )
587
+ console.print(
588
+ f"Subnet: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]"
589
+ f"{ni}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] "
590
+ f"Hotkey: [{COLOR_PALETTE['GENERAL']['HOTKEY']}]{hk}"
591
+ f"[/{COLOR_PALETTE['GENERAL']['HOTKEY']}] "
592
+ f"Stake:\n"
593
+ f" [blue]{curr}[/blue] "
594
+ f":arrow_right: "
595
+ f"[{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_stake}"
596
+ )
597
+ else:
598
+ print_error(
599
+ f":cross_mark: [red]Batch staking failed[/red]: {err_msg}",
600
+ status=status,
601
+ )
602
+ else:
603
+ # Single operation path: use the existing per-operation extrinsics
604
+ with console.status(
605
+ f"\n:satellite: Staking on netuid(s): {netuids} ..."
606
+ ) as status:
607
+ for ni, staking_address, am, curr, price in operations:
608
+ if safe_staking and ni != 0:
609
+ result = await safe_stake_extrinsic(
610
+ netuid_=ni,
611
+ amount_=am,
612
+ current_stake=curr,
613
+ hotkey_ss58_=staking_address,
614
+ price_limit=price,
615
+ status_=status,
616
+ )
617
+ else:
618
+ result = await stake_extrinsic(
619
+ netuid_i=ni,
620
+ amount_=am,
621
+ current=curr,
622
+ staking_address_ss58=staking_address,
623
+ status_=status,
624
+ )
625
+ success, er_msg, ext_receipt = result
626
+ successes[ni][staking_address] = success
627
+ error_messages[ni][staking_address] = er_msg
628
+ if success and ext_receipt:
629
+ extrinsic_ids[ni][
630
+ staking_address
631
+ ] = await ext_receipt.get_extrinsic_identifier()
632
+
526
633
  if json_output:
527
634
  json_console.print_json(
528
635
  data={
@@ -329,43 +329,163 @@ async def unstake(
329
329
  if not unlock_key(wallet).success:
330
330
  return False
331
331
 
332
+ total_ops = len(unstake_operations)
333
+ use_batch = total_ops > 1
332
334
  successes = []
333
- with console.status("\n:satellite: Performing unstaking operations...") as status:
334
- for op in unstake_operations:
335
- common_args = {
336
- "wallet": wallet,
337
- "subtensor": subtensor,
338
- "netuid": op["netuid"],
339
- "amount": op["amount_to_unstake"],
340
- "hotkey_ss58": op["hotkey_ss58"],
341
- "status": status,
342
- "era": era,
343
- "proxy": proxy,
344
- "mev_protection": mev_protection,
345
- }
346
335
 
347
- if safe_staking and op["netuid"] != 0:
348
- func = _safe_unstake_extrinsic
349
- specific_args = {
350
- "price_limit": op["price_with_tolerance"],
351
- "allow_partial_stake": allow_partial_stake,
352
- }
353
- else:
354
- func = _unstake_extrinsic
355
- specific_args = {"current_stake": op["current_stake_balance"]}
336
+ if use_batch:
337
+ # Batch path: compose all calls, submit as a single Utility.batch_all transaction
338
+ with console.status(
339
+ f"\n:satellite: Batching {total_ops} unstake operations..."
340
+ ) as status:
341
+ batch_block_hash = await subtensor.substrate.get_chain_head()
342
+ current_balance = await subtensor.get_balance(
343
+ coldkey_ss58, block_hash=batch_block_hash
344
+ )
356
345
 
357
- suc, ext_receipt = await func(**common_args, **specific_args)
358
- ext_id = await ext_receipt.get_extrinsic_identifier() if suc else None
346
+ # compose_call with a block_hash does no I/O, so no need for gather
347
+ calls = []
348
+ for op in unstake_operations:
349
+ if safe_staking and op["netuid"] != 0:
350
+ calls.append(
351
+ await subtensor.substrate.compose_call(
352
+ call_module="SubtensorModule",
353
+ call_function="remove_stake_limit",
354
+ call_params={
355
+ "hotkey": op["hotkey_ss58"],
356
+ "netuid": op["netuid"],
357
+ "amount_unstaked": op["amount_to_unstake"].rao,
358
+ "limit_price": op["price_with_tolerance"],
359
+ "allow_partial": allow_partial_stake,
360
+ },
361
+ block_hash=batch_block_hash,
362
+ )
363
+ )
364
+ else:
365
+ calls.append(
366
+ await subtensor.substrate.compose_call(
367
+ call_module="SubtensorModule",
368
+ call_function="remove_stake",
369
+ call_params={
370
+ "hotkey": op["hotkey_ss58"],
371
+ "netuid": op["netuid"],
372
+ "amount_unstaked": op["amount_to_unstake"].rao,
373
+ },
374
+ block_hash=batch_block_hash,
375
+ )
376
+ )
359
377
 
360
- successes.append(
361
- {
378
+ success, err_msg, response = await subtensor.sign_and_send_batch_extrinsic(
379
+ calls=list(calls),
380
+ wallet=wallet,
381
+ era={"period": era},
382
+ proxy=proxy,
383
+ mev_protection=mev_protection,
384
+ block_hash=batch_block_hash,
385
+ )
386
+
387
+ if success and mev_protection:
388
+ inner_hash = err_msg
389
+ success, mev_error, response = await wait_for_extrinsic_by_hash(
390
+ subtensor=subtensor,
391
+ extrinsic_hash=inner_hash,
392
+ submit_block_hash=response.block_hash,
393
+ status=status,
394
+ )
395
+ if not success:
396
+ err_msg = mev_error
397
+
398
+ ext_id = (
399
+ await response.get_extrinsic_identifier()
400
+ if success and response
401
+ else None
402
+ )
403
+
404
+ for op in unstake_operations:
405
+ successes.append(
406
+ {
407
+ "netuid": op["netuid"],
408
+ "hotkey_ss58": op["hotkey_ss58"],
409
+ "unstake_amount": op["amount_to_unstake"].tao,
410
+ "success": success,
411
+ "extrinsic_identifier": ext_id,
412
+ }
413
+ )
414
+
415
+ if success:
416
+ if not json_output:
417
+ await print_extrinsic_id(response)
418
+ new_block_hash = await subtensor.substrate.get_chain_head()
419
+ new_balance = await subtensor.get_balance(
420
+ coldkey_ss58, block_hash=new_block_hash
421
+ )
422
+ print_success(
423
+ f"Batch finalized. Unstaked across {total_ops} operations."
424
+ )
425
+ console.print(
426
+ f"Balance:\n [blue]{current_balance}[/blue] :arrow_right: "
427
+ f"[{COLOR_PALETTE.S.AMOUNT}]{new_balance}"
428
+ )
429
+ for op in unstake_operations:
430
+ new_stake = await subtensor.get_stake(
431
+ hotkey_ss58=op["hotkey_ss58"],
432
+ coldkey_ss58=coldkey_ss58,
433
+ netuid=op["netuid"],
434
+ block_hash=new_block_hash,
435
+ )
436
+ console.print(
437
+ f"Subnet: [{COLOR_PALETTE.G.SUBHEAD}]{op['netuid']}"
438
+ f"[/{COLOR_PALETTE.G.SUBHEAD}] "
439
+ f"Hotkey: [{COLOR_PALETTE.G.HK}]{op['hotkey_ss58']}"
440
+ f"[/{COLOR_PALETTE.G.HK}] "
441
+ f"Stake:\n [blue]{op['current_stake_balance']}[/blue] "
442
+ f":arrow_right: [{COLOR_PALETTE.S.AMOUNT}]{new_stake}"
443
+ )
444
+ else:
445
+ print_error(
446
+ f":cross_mark: [red]Batch unstaking failed[/red]: {err_msg}",
447
+ status=status,
448
+ )
449
+ else:
450
+ # Single operation path: use the existing per-operation extrinsics
451
+ with console.status(
452
+ "\n:satellite: Performing unstaking operations..."
453
+ ) as status:
454
+ for op in unstake_operations:
455
+ common_args = {
456
+ "wallet": wallet,
457
+ "subtensor": subtensor,
362
458
  "netuid": op["netuid"],
459
+ "amount": op["amount_to_unstake"],
363
460
  "hotkey_ss58": op["hotkey_ss58"],
364
- "unstake_amount": op["amount_to_unstake"].tao,
365
- "success": suc,
366
- "extrinsic_identifier": ext_id,
461
+ "status": status,
462
+ "era": era,
463
+ "proxy": proxy,
464
+ "mev_protection": mev_protection,
367
465
  }
368
- )
466
+
467
+ if safe_staking and op["netuid"] != 0:
468
+ func = _safe_unstake_extrinsic
469
+ specific_args = {
470
+ "price_limit": op["price_with_tolerance"],
471
+ "allow_partial_stake": allow_partial_stake,
472
+ }
473
+ else:
474
+ func = _unstake_extrinsic
475
+ specific_args = {"current_stake": op["current_stake_balance"]}
476
+
477
+ suc, ext_receipt = await func(**common_args, **specific_args)
478
+ ext_id = await ext_receipt.get_extrinsic_identifier() if suc else None
479
+
480
+ successes.append(
481
+ {
482
+ "netuid": op["netuid"],
483
+ "hotkey_ss58": op["hotkey_ss58"],
484
+ "unstake_amount": op["amount_to_unstake"].tao,
485
+ "success": suc,
486
+ "extrinsic_identifier": ext_id,
487
+ }
488
+ )
369
489
 
370
490
  console.print(
371
491
  f"[{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]Unstaking operations completed."
@@ -504,7 +624,7 @@ async def unstake_all(
504
624
  stake_amount = stake.stake
505
625
 
506
626
  try:
507
- current_price = subnet_info.price.tao
627
+ _ = subnet_info.price.tao
508
628
  extrinsic_type = (
509
629
  "unstake_all" if not unstake_all_alpha else "unstake_all_alpha"
510
630
  )
@@ -554,24 +674,101 @@ async def unstake_all(
554
674
  if not unlock_key(wallet).success:
555
675
  return
556
676
  successes = {}
557
- with console.status("Unstaking all stakes...") as status:
558
- for hotkey_ss58 in hotkey_ss58s:
559
- success, ext_receipt = await _unstake_all_extrinsic(
677
+ use_batch = len(hotkey_ss58s) > 1
678
+
679
+ if use_batch:
680
+ # Batch path: compose unstake_all calls for all hotkeys into one transaction
681
+ with console.status(
682
+ f"Batching unstake-all for {len(hotkey_ss58s)} hotkeys..."
683
+ ) as status:
684
+ batch_block_hash = await subtensor.substrate.get_chain_head()
685
+ call_function = "unstake_all_alpha" if unstake_all_alpha else "unstake_all"
686
+ # compose_call with a block_hash does no I/O, so no need for gather
687
+ calls = []
688
+ for hk in hotkey_ss58s:
689
+ calls.append(
690
+ await subtensor.substrate.compose_call(
691
+ call_module="SubtensorModule",
692
+ call_function=call_function,
693
+ call_params={"hotkey": hk},
694
+ block_hash=batch_block_hash,
695
+ )
696
+ )
697
+
698
+ success, err_msg, response = await subtensor.sign_and_send_batch_extrinsic(
699
+ calls=list(calls),
560
700
  wallet=wallet,
561
- subtensor=subtensor,
562
- hotkey_ss58=hotkey_ss58,
563
- hotkey_name=hotkey_names.get(hotkey_ss58, hotkey_ss58),
564
- unstake_all_alpha=unstake_all_alpha,
565
- status=status,
566
- era=era,
701
+ era={"period": era},
567
702
  proxy=proxy,
568
703
  mev_protection=mev_protection,
704
+ block_hash=batch_block_hash,
569
705
  )
570
- ext_id = await ext_receipt.get_extrinsic_identifier() if success else None
571
- successes[hotkey_ss58] = {
572
- "success": success,
573
- "extrinsic_identifier": ext_id,
574
- }
706
+
707
+ if success and mev_protection:
708
+ inner_hash = err_msg
709
+ success, mev_error, response = await wait_for_extrinsic_by_hash(
710
+ subtensor=subtensor,
711
+ extrinsic_hash=inner_hash,
712
+ submit_block_hash=response.block_hash,
713
+ status=status,
714
+ )
715
+ if not success:
716
+ err_msg = mev_error
717
+
718
+ ext_id = (
719
+ await response.get_extrinsic_identifier()
720
+ if success and response
721
+ else None
722
+ )
723
+
724
+ for hk in hotkey_ss58s:
725
+ successes[hk] = {
726
+ "success": success,
727
+ "extrinsic_identifier": ext_id,
728
+ }
729
+
730
+ if success:
731
+ await print_extrinsic_id(response)
732
+ msg_modifier = "Alpha " if unstake_all_alpha else ""
733
+ new_block_hash = await subtensor.substrate.get_chain_head()
734
+ new_balance = await subtensor.get_balance(
735
+ coldkey_ss58, block_hash=new_block_hash
736
+ )
737
+ print_success(
738
+ f"Batch finalized. Unstaked all {msg_modifier}stakes "
739
+ f"from {len(hotkey_ss58s)} hotkeys."
740
+ )
741
+ console.print(
742
+ f"Balance:\n [blue]{current_wallet_balance}[/blue] "
743
+ f":arrow_right: [{COLOR_PALETTE.S.AMOUNT}]{new_balance}"
744
+ )
745
+ else:
746
+ print_error(
747
+ f":cross_mark: [red]Batch unstake-all failed[/red]: {err_msg}",
748
+ status=status,
749
+ )
750
+ else:
751
+ # Single hotkey path: use existing per-hotkey extrinsic
752
+ with console.status("Unstaking all stakes...") as status:
753
+ for hotkey_ss58 in hotkey_ss58s:
754
+ success, ext_receipt = await _unstake_all_extrinsic(
755
+ wallet=wallet,
756
+ subtensor=subtensor,
757
+ hotkey_ss58=hotkey_ss58,
758
+ hotkey_name=hotkey_names.get(hotkey_ss58, hotkey_ss58),
759
+ unstake_all_alpha=unstake_all_alpha,
760
+ status=status,
761
+ era=era,
762
+ proxy=proxy,
763
+ mev_protection=mev_protection,
764
+ )
765
+ ext_id = (
766
+ await ext_receipt.get_extrinsic_identifier() if success else None
767
+ )
768
+ successes[hotkey_ss58] = {
769
+ "success": success,
770
+ "extrinsic_identifier": ext_id,
771
+ }
575
772
  if json_output:
576
773
  json_console.print(json.dumps({"success": successes}))
577
774
 
@@ -2224,7 +2224,7 @@ async def announce_coldkey_swap(
2224
2224
  console.print(details_table)
2225
2225
  console.print(
2226
2226
  f"\n[dim]After the delay, run:"
2227
- f"\n[green]btcli wallet swap-coldkey execute --new-coldkey {new_coldkey_ss58}[/green]"
2227
+ f"\n[green]btcli wallet swap-coldkey execute --new-coldkey {new_coldkey_ss58} --wallet-name {wallet.name}[/green]"
2228
2228
  )
2229
2229
 
2230
2230
  return True
@@ -2345,6 +2345,134 @@ async def dispute_coldkey_swap(
2345
2345
  return True
2346
2346
 
2347
2347
 
2348
+ async def clear_coldkey_swap_announcement(
2349
+ wallet: Wallet,
2350
+ subtensor: SubtensorInterface,
2351
+ decline: bool = False,
2352
+ quiet: bool = False,
2353
+ prompt: bool = True,
2354
+ mev_protection: bool = False,
2355
+ ) -> bool:
2356
+ """Clear (withdraw) a pending coldkey swap announcement.
2357
+
2358
+ The announcement can only be cleared after the reannouncement delay has elapsed
2359
+ past the execution block, and the swap must not be disputed.
2360
+
2361
+ Args:
2362
+ wallet: Wallet that owns the announcement (must be the announcing coldkey).
2363
+ subtensor: Connection to the Bittensor network.
2364
+ decline: If True, default to declining at confirmation prompt.
2365
+ quiet: If True, skip confirmation prompts and proceed.
2366
+ prompt: If True, show confirmation prompts.
2367
+ mev_protection: If True, encrypt the extrinsic with MEV protection.
2368
+
2369
+ Returns:
2370
+ bool: True if the clear extrinsic was included successfully, else False.
2371
+ """
2372
+ block_hash = await subtensor.substrate.get_chain_head()
2373
+ announcement, dispute, current_block, reannounce_delay = await asyncio.gather(
2374
+ subtensor.get_coldkey_swap_announcement(
2375
+ wallet.coldkeypub.ss58_address, block_hash=block_hash
2376
+ ),
2377
+ subtensor.get_coldkey_swap_dispute(
2378
+ wallet.coldkeypub.ss58_address, block_hash=block_hash
2379
+ ),
2380
+ subtensor.substrate.get_block_number(block_hash=block_hash),
2381
+ subtensor.get_coldkey_swap_reannouncement_delay(block_hash=block_hash),
2382
+ )
2383
+
2384
+ if not announcement:
2385
+ print_error(
2386
+ f"No coldkey swap announcement found for {wallet.coldkeypub.ss58_address}.\n"
2387
+ "Nothing to clear."
2388
+ )
2389
+ return False
2390
+
2391
+ if dispute is not None:
2392
+ console.print(
2393
+ f"[yellow]Swap is disputed at block {dispute}.[/yellow] "
2394
+ "Cannot clear a disputed announcement."
2395
+ )
2396
+ return False
2397
+
2398
+ clear_block = announcement.execution_block + reannounce_delay
2399
+ if current_block < clear_block:
2400
+ remaining = clear_block - current_block
2401
+ console.print(
2402
+ f"[yellow]Cannot clear yet.[/yellow] "
2403
+ f"You can clear after block {clear_block} ({blocks_to_duration(remaining)} from now).\n"
2404
+ f"Current block: {current_block}"
2405
+ )
2406
+ return False
2407
+
2408
+ info = create_key_value_table("Clear Coldkey Swap Announcement\n")
2409
+ info.add_row(
2410
+ "Coldkey", f"[{COLORS.G.CK}]{wallet.coldkeypub.ss58_address}[/{COLORS.G.CK}]"
2411
+ )
2412
+ info.add_row("Announced Hash", f"[dim]{announcement.new_coldkey_hash}[/dim]")
2413
+ info.add_row("Execution Block", str(announcement.execution_block))
2414
+ info.add_row(
2415
+ "Status",
2416
+ "[yellow]Pending[/yellow]"
2417
+ if current_block < announcement.execution_block
2418
+ else "[green]Ready[/green]",
2419
+ )
2420
+ console.print(info)
2421
+
2422
+ if prompt and not confirm_action(
2423
+ "Proceed with clearing this swap announcement?",
2424
+ decline=decline,
2425
+ quiet=quiet,
2426
+ ):
2427
+ return False
2428
+
2429
+ if not unlock_key(wallet).success:
2430
+ return False
2431
+
2432
+ with console.status(
2433
+ ":satellite: Clearing coldkey swap announcement on-chain..."
2434
+ ) as status:
2435
+ call = await subtensor.substrate.compose_call(
2436
+ call_module="SubtensorModule",
2437
+ call_function="clear_coldkey_swap_announcement",
2438
+ call_params={},
2439
+ )
2440
+ success, err_msg, ext_receipt = await subtensor.sign_and_send_extrinsic(
2441
+ call,
2442
+ wallet,
2443
+ wait_for_inclusion=True,
2444
+ wait_for_finalization=True,
2445
+ mev_protection=mev_protection,
2446
+ )
2447
+
2448
+ if not success:
2449
+ print_error(f"Failed to clear coldkey swap announcement: {err_msg}")
2450
+ return False
2451
+
2452
+ if mev_protection:
2453
+ inner_hash = err_msg
2454
+ mev_success, mev_error, ext_receipt = await wait_for_extrinsic_by_hash(
2455
+ subtensor=subtensor,
2456
+ extrinsic_hash=inner_hash,
2457
+ submit_block_hash=ext_receipt.block_hash,
2458
+ status=status,
2459
+ )
2460
+ if not mev_success:
2461
+ print_error(
2462
+ f"Failed to clear coldkey swap announcement: {mev_error}",
2463
+ status=status,
2464
+ )
2465
+ return False
2466
+
2467
+ print_success("[dark_sea_green3]Coldkey swap announcement cleared.")
2468
+ await print_extrinsic_id(ext_receipt)
2469
+
2470
+ console.print(
2471
+ "\n[dim]Your coldkey is no longer locked by a pending swap announcement.[/dim]"
2472
+ )
2473
+ return True
2474
+
2475
+
2348
2476
  async def execute_coldkey_swap(
2349
2477
  wallet: Wallet,
2350
2478
  subtensor: SubtensorInterface,
@@ -2506,10 +2634,11 @@ async def check_swap_status(
2506
2634
  """
2507
2635
  block_hash = await subtensor.substrate.get_chain_head()
2508
2636
  if origin_ss58:
2509
- announcement, dispute, current_block = await asyncio.gather(
2637
+ announcement, dispute, current_block, reannounce_delay = await asyncio.gather(
2510
2638
  subtensor.get_coldkey_swap_announcement(origin_ss58, block_hash=block_hash),
2511
2639
  subtensor.get_coldkey_swap_dispute(origin_ss58, block_hash=block_hash),
2512
2640
  subtensor.substrate.get_block_number(block_hash=block_hash),
2641
+ subtensor.get_coldkey_swap_reannouncement_delay(block_hash=block_hash),
2513
2642
  )
2514
2643
  if not announcement:
2515
2644
  console.print(
@@ -2521,10 +2650,11 @@ async def check_swap_status(
2521
2650
  disputes = [(origin_ss58, dispute)] if dispute is not None else []
2522
2651
 
2523
2652
  else:
2524
- announcements, disputes, current_block = await asyncio.gather(
2653
+ announcements, disputes, current_block, reannounce_delay = await asyncio.gather(
2525
2654
  subtensor.get_coldkey_swap_announcements(block_hash=block_hash),
2526
2655
  subtensor.get_coldkey_swap_disputes(block_hash=block_hash),
2527
2656
  subtensor.substrate.get_block_number(block_hash=block_hash),
2657
+ subtensor.get_coldkey_swap_reannouncement_delay(block_hash=block_hash),
2528
2658
  )
2529
2659
  if not announcements:
2530
2660
  console.print(
@@ -2563,6 +2693,7 @@ async def check_swap_status(
2563
2693
  Column("Execution Block", justify="right", style="dark_sea_green3"),
2564
2694
  Column("Time Remaining", justify="right", style="yellow"),
2565
2695
  Column("Status", justify="center", style="green"),
2696
+ Column("Clear Announcement", justify="right", style="yellow"),
2566
2697
  title=f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Pending Coldkey Swap Announcements\nCurrent Block: {current_block}\n",
2567
2698
  show_header=True,
2568
2699
  show_edge=False,
@@ -2577,18 +2708,28 @@ async def check_swap_status(
2577
2708
  for announcement in announcements:
2578
2709
  dispute_block = dispute_map.get(announcement.coldkey)
2579
2710
  remaining_blocks = announcement.execution_block - current_block
2711
+ clear_block = announcement.execution_block + reannounce_delay
2712
+ clear_remaining = clear_block - current_block
2580
2713
  if dispute_block is not None:
2581
2714
  status = "[red]Disputed[/red]"
2582
2715
  time_str = f"Disputed @ {dispute_block}"
2583
2716
  status_label = "disputed"
2717
+ clear_str = "[red]Disputed[/red]"
2584
2718
  elif remaining_blocks <= 0:
2585
2719
  status = "Ready"
2586
2720
  time_str = "[green]Ready[/green]"
2587
2721
  status_label = "ready"
2722
+ if clear_remaining <= 0:
2723
+ clear_str = "[green]Ready[/green]"
2724
+ else:
2725
+ clear_str = (
2726
+ f"Block {clear_block} ({blocks_to_duration(clear_remaining)})"
2727
+ )
2588
2728
  else:
2589
2729
  status = "Pending"
2590
2730
  time_str = blocks_to_duration(remaining_blocks)
2591
2731
  status_label = "pending"
2732
+ clear_str = f"Block {clear_block} ({blocks_to_duration(clear_remaining)})"
2592
2733
  hash_display = f"{announcement.new_coldkey_hash[:12]}...{announcement.new_coldkey_hash[-6:]}"
2593
2734
 
2594
2735
  table.add_row(
@@ -2597,6 +2738,7 @@ async def check_swap_status(
2597
2738
  str(announcement.execution_block),
2598
2739
  time_str,
2599
2740
  status,
2741
+ clear_str,
2600
2742
  )
2601
2743
 
2602
2744
  payload["announcements"].append(
@@ -2607,6 +2749,8 @@ async def check_swap_status(
2607
2749
  "status": status_label,
2608
2750
  "time_remaining_blocks": max(0, remaining_blocks),
2609
2751
  "disputed_block": dispute_block,
2752
+ "clear_block": clear_block,
2753
+ "clear_remaining_blocks": max(0, clear_remaining),
2610
2754
  }
2611
2755
  )
2612
2756
 
@@ -2617,5 +2761,7 @@ async def check_swap_status(
2617
2761
  console.print(table)
2618
2762
  console.print(
2619
2763
  "\n[dim]To execute a ready swap:[/dim] "
2620
- "[green]btcli wallet swap-coldkey execute[/green]"
2764
+ "[green]btcli wallet swap-coldkey execute[/green]\n"
2765
+ "[dim]To clear (withdraw) an announcement:[/dim] "
2766
+ "[green]btcli wallet swap-coldkey clear[/green]"
2621
2767
  )
@@ -4,7 +4,7 @@ build-backend = "flit_core.buildapi"
4
4
 
5
5
  [project]
6
6
  name = "bittensor-cli"
7
- version = "9.19.0"
7
+ version = "9.20.0"
8
8
  description = "Bittensor CLI"
9
9
  readme = "README.md"
10
10
  authors = [
File without changes