Skip to content

Commit ca75bab

Browse files
committed
feat: Add proxy list, reject commands and remove --all flag
Implements the missing proxy commands as outlined in issue #742: - Add 'btcli proxy list' command to query and display all proxies for an account - Add 'btcli proxy reject' command to reject announced proxy transactions - Add '--all' flag to 'btcli proxy remove' to remove all proxies at once All proxy functions properly use confirm_action with decline/quiet parameters to support the --no flag feature from PR #748. Includes comprehensive unit tests (22 tests) covering: - Success cases, JSON output, error handling - Prompt declined scenarios, wallet unlock failures - CLI command routing tests
1 parent c82f9f8 commit ca75bab

File tree

3 files changed

+1205
-95
lines changed

3 files changed

+1205
-95
lines changed

bittensor_cli/cli.py

Lines changed: 201 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1236,6 +1236,12 @@ def __init__(self):
12361236
"execute",
12371237
rich_help_panel=HELP_PANELS["PROXY"]["MGMT"],
12381238
)(self.proxy_execute_announced)
1239+
self.proxy_app.command("list", rich_help_panel=HELP_PANELS["PROXY"]["MGMT"])(
1240+
self.proxy_list
1241+
)
1242+
self.proxy_app.command("reject", rich_help_panel=HELP_PANELS["PROXY"]["MGMT"])(
1243+
self.proxy_reject
1244+
)
12391245

12401246
# Sub command aliases
12411247
# Wallet
@@ -9329,13 +9335,19 @@ def proxy_add(
93299335
def proxy_remove(
93309336
self,
93319337
delegate: Annotated[
9332-
str,
9338+
Optional[str],
93339339
typer.Option(
93349340
callback=is_valid_ss58_address_param,
9335-
prompt="Enter the SS58 address of the delegate to remove, e.g. 5dxds...",
93369341
help="The SS58 address of the delegate to remove",
93379342
),
9338-
] = "",
9343+
] = None,
9344+
all_proxies: Annotated[
9345+
bool,
9346+
typer.Option(
9347+
"--all",
9348+
help="Remove all proxies for the account",
9349+
),
9350+
] = False,
93399351
network: Optional[list[str]] = Options.network,
93409352
proxy_type: ProxyType = Options.proxy_type,
93419353
delay: int = typer.Option(0, help="Delay, in number of blocks"),
@@ -9367,10 +9379,10 @@ def proxy_remove(
93679379
[green]$[/green] btcli proxy remove --all
93689380
93699381
"""
9370-
# TODO should add a --all flag to call Proxy.remove_proxies ?
93719382
logger.debug(
93729383
"args:\n"
93739384
f"delegate: {delegate}\n"
9385+
f"all_proxies: {all_proxies}\n"
93749386
f"network: {network}\n"
93759387
f"proxy_type: {proxy_type}\n"
93769388
f"delay: {delay}\n"
@@ -9379,29 +9391,70 @@ def proxy_remove(
93799391
f"era: {period}\n"
93809392
)
93819393
self.verbosity_handler(quiet, verbose, json_output, prompt)
9394+
9395+
# Validate that either --all or --delegate is provided, but not both
9396+
if all_proxies and delegate:
9397+
err_console.print(
9398+
":cross_mark:[red]Cannot use both --all and --delegate. "
9399+
"Use --all to remove all proxies or --delegate to remove a specific proxy.[/red]"
9400+
)
9401+
raise typer.Exit(1)
9402+
9403+
if not all_proxies and not delegate:
9404+
if prompt:
9405+
delegate = Prompt.ask(
9406+
"Enter the SS58 address of the delegate to remove, e.g. 5dxds..."
9407+
)
9408+
if not is_valid_ss58_address(delegate):
9409+
err_console.print(
9410+
f":cross_mark:[red]Invalid SS58 address: {delegate}[/red]"
9411+
)
9412+
raise typer.Exit(1)
9413+
else:
9414+
err_console.print(
9415+
":cross_mark:[red]Either --delegate or --all must be specified.[/red]"
9416+
)
9417+
raise typer.Exit(1)
9418+
93829419
wallet = self.wallet_ask(
93839420
wallet_name=wallet_name,
93849421
wallet_path=wallet_path,
93859422
wallet_hotkey=wallet_hotkey,
93869423
ask_for=[WO.NAME, WO.PATH],
93879424
validate=WV.WALLET,
93889425
)
9389-
return self._run_command(
9390-
proxy_commands.remove_proxy(
9391-
subtensor=self.initialize_chain(network),
9392-
wallet=wallet,
9393-
delegate=delegate,
9394-
proxy_type=proxy_type,
9395-
delay=delay,
9396-
prompt=prompt,
9397-
decline=decline,
9398-
quiet=quiet,
9399-
wait_for_inclusion=wait_for_inclusion,
9400-
wait_for_finalization=wait_for_finalization,
9401-
period=period,
9402-
json_output=json_output,
9426+
9427+
if all_proxies:
9428+
return self._run_command(
9429+
proxy_commands.remove_all_proxies(
9430+
subtensor=self.initialize_chain(network),
9431+
wallet=wallet,
9432+
prompt=prompt,
9433+
decline=decline,
9434+
quiet=quiet,
9435+
wait_for_inclusion=wait_for_inclusion,
9436+
wait_for_finalization=wait_for_finalization,
9437+
period=period,
9438+
json_output=json_output,
9439+
)
9440+
)
9441+
else:
9442+
return self._run_command(
9443+
proxy_commands.remove_proxy(
9444+
subtensor=self.initialize_chain(network),
9445+
wallet=wallet,
9446+
proxy_type=proxy_type,
9447+
delegate=delegate,
9448+
delay=delay,
9449+
prompt=prompt,
9450+
decline=decline,
9451+
quiet=quiet,
9452+
wait_for_inclusion=wait_for_inclusion,
9453+
wait_for_finalization=wait_for_finalization,
9454+
period=period,
9455+
json_output=json_output,
9456+
)
94039457
)
9404-
)
94059458

94069459
def proxy_kill(
94079460
self,
@@ -9685,6 +9738,135 @@ def proxy_execute_announced(
96859738
with ProxyAnnouncements.get_db() as (conn, cursor):
96869739
ProxyAnnouncements.mark_as_executed(conn, cursor, got_call_from_db)
96879740

9741+
def proxy_list(
9742+
self,
9743+
address: Annotated[
9744+
Optional[str],
9745+
typer.Option(
9746+
callback=is_valid_ss58_address_param,
9747+
help="The SS58 address to list proxies for. If not provided, uses the wallet's coldkey.",
9748+
),
9749+
] = None,
9750+
network: Optional[list[str]] = Options.network,
9751+
wallet_name: str = Options.wallet_name,
9752+
wallet_path: str = Options.wallet_path,
9753+
wallet_hotkey: str = Options.wallet_hotkey,
9754+
quiet: bool = Options.quiet,
9755+
verbose: bool = Options.verbose,
9756+
json_output: bool = Options.json_output,
9757+
):
9758+
"""
9759+
Lists all proxies for an account.
9760+
9761+
Queries the chain to display all proxy delegates configured for the specified address,
9762+
including their proxy types and delay settings.
9763+
9764+
[bold]Common Examples:[/bold]
9765+
1. List proxies for your wallet
9766+
[green]$[/green] btcli proxy list
9767+
9768+
2. List proxies for a specific address
9769+
[green]$[/green] btcli proxy list --address 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY
9770+
9771+
"""
9772+
self.verbosity_handler(quiet, verbose, json_output)
9773+
9774+
# If no address provided, use wallet's coldkey
9775+
if address is None:
9776+
wallet = self.wallet_ask(
9777+
wallet_name=wallet_name,
9778+
wallet_path=wallet_path,
9779+
wallet_hotkey=wallet_hotkey,
9780+
ask_for=[WO.NAME, WO.PATH],
9781+
validate=WV.NONE,
9782+
)
9783+
address = wallet.coldkeypub.ss58_address
9784+
9785+
logger.debug(f"args:\naddress: {address}\nnetwork: {network}\n")
9786+
9787+
return self._run_command(
9788+
proxy_commands.list_proxies(
9789+
subtensor=self.initialize_chain(network),
9790+
address=address,
9791+
json_output=json_output,
9792+
)
9793+
)
9794+
9795+
def proxy_reject(
9796+
self,
9797+
delegate: Annotated[
9798+
str,
9799+
typer.Option(
9800+
callback=is_valid_ss58_address_param,
9801+
prompt="Enter the SS58 address of the delegate whose announcement to reject",
9802+
help="The SS58 address of the delegate who made the announcement",
9803+
),
9804+
] = "",
9805+
call_hash: Annotated[
9806+
str,
9807+
typer.Option(
9808+
prompt="Enter the call hash of the announcement to reject",
9809+
help="The hash of the announced call to reject",
9810+
),
9811+
] = "",
9812+
network: Optional[list[str]] = Options.network,
9813+
wallet_name: str = Options.wallet_name,
9814+
wallet_path: str = Options.wallet_path,
9815+
wallet_hotkey: str = Options.wallet_hotkey,
9816+
prompt: bool = Options.prompt,
9817+
wait_for_inclusion: bool = Options.wait_for_inclusion,
9818+
wait_for_finalization: bool = Options.wait_for_finalization,
9819+
period: int = Options.period,
9820+
quiet: bool = Options.quiet,
9821+
verbose: bool = Options.verbose,
9822+
json_output: bool = Options.json_output,
9823+
):
9824+
"""
9825+
Rejects an announced proxy call.
9826+
9827+
Removes a previously announced call from the pending announcements, preventing it
9828+
from being executed. This must be called by the real account (the account that
9829+
granted the proxy permissions).
9830+
9831+
[bold]Common Examples:[/bold]
9832+
1. Reject an announced call
9833+
[green]$[/green] btcli proxy reject --delegate 5GDel... --call-hash 0x1234...
9834+
9835+
"""
9836+
self.verbosity_handler(quiet, verbose, json_output, prompt)
9837+
9838+
logger.debug(
9839+
"args:\n"
9840+
f"delegate: {delegate}\n"
9841+
f"call_hash: {call_hash}\n"
9842+
f"network: {network}\n"
9843+
f"wait_for_finalization: {wait_for_finalization}\n"
9844+
f"wait_for_inclusion: {wait_for_inclusion}\n"
9845+
f"era: {period}\n"
9846+
)
9847+
9848+
wallet = self.wallet_ask(
9849+
wallet_name=wallet_name,
9850+
wallet_path=wallet_path,
9851+
wallet_hotkey=wallet_hotkey,
9852+
ask_for=[WO.NAME, WO.PATH],
9853+
validate=WV.WALLET,
9854+
)
9855+
9856+
return self._run_command(
9857+
proxy_commands.reject_announcement(
9858+
subtensor=self.initialize_chain(network),
9859+
wallet=wallet,
9860+
delegate=delegate,
9861+
call_hash=call_hash,
9862+
prompt=prompt,
9863+
wait_for_inclusion=wait_for_inclusion,
9864+
wait_for_finalization=wait_for_finalization,
9865+
period=period,
9866+
json_output=json_output,
9867+
)
9868+
)
9869+
96889870
@staticmethod
96899871
def convert(
96909872
from_rao: Optional[str] = typer.Option(

0 commit comments

Comments
 (0)