htcli 1.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. htcli-1.1.0.dist-info/METADATA +509 -0
  2. htcli-1.1.0.dist-info/RECORD +140 -0
  3. htcli-1.1.0.dist-info/WHEEL +4 -0
  4. htcli-1.1.0.dist-info/entry_points.txt +2 -0
  5. htcli-1.1.0.dist-info/licenses/LICENSE +21 -0
  6. src/__init__.py +0 -0
  7. src/htcli/__init__.py +5 -0
  8. src/htcli/client/__init__.py +338 -0
  9. src/htcli/client/extrinsics/__init__.py +26 -0
  10. src/htcli/client/extrinsics/base.py +487 -0
  11. src/htcli/client/extrinsics/consensus.py +79 -0
  12. src/htcli/client/extrinsics/governance.py +714 -0
  13. src/htcli/client/extrinsics/identity.py +490 -0
  14. src/htcli/client/extrinsics/node.py +1054 -0
  15. src/htcli/client/extrinsics/overwatch.py +401 -0
  16. src/htcli/client/extrinsics/staking.py +1504 -0
  17. src/htcli/client/extrinsics/subnet.py +2218 -0
  18. src/htcli/client/extrinsics/validator.py +203 -0
  19. src/htcli/client/extrinsics/wallet.py +323 -0
  20. src/htcli/client/offchain/__init__.py +10 -0
  21. src/htcli/client/offchain/backup.py +385 -0
  22. src/htcli/client/offchain/config.py +541 -0
  23. src/htcli/client/offchain/wallet.py +839 -0
  24. src/htcli/client/rpc/__init__.py +20 -0
  25. src/htcli/client/rpc/chain.py +568 -0
  26. src/htcli/client/rpc/node.py +783 -0
  27. src/htcli/client/rpc/overwatch.py +680 -0
  28. src/htcli/client/rpc/staking.py +216 -0
  29. src/htcli/client/rpc/subnet.py +2104 -0
  30. src/htcli/client/rpc/wallet.py +912 -0
  31. src/htcli/commands/__init__.py +31 -0
  32. src/htcli/commands/chain/__init__.py +66 -0
  33. src/htcli/commands/chain/display.py +204 -0
  34. src/htcli/commands/chain/handlers.py +260 -0
  35. src/htcli/commands/config/__init__.py +158 -0
  36. src/htcli/commands/config/display.py +353 -0
  37. src/htcli/commands/config/handlers.py +347 -0
  38. src/htcli/commands/config/prompts.py +357 -0
  39. src/htcli/commands/consensus/__init__.py +61 -0
  40. src/htcli/commands/consensus/handlers.py +100 -0
  41. src/htcli/commands/governance/__init__.py +49 -0
  42. src/htcli/commands/governance/handlers.py +81 -0
  43. src/htcli/commands/node/__init__.py +304 -0
  44. src/htcli/commands/node/display.py +749 -0
  45. src/htcli/commands/node/error_handling.py +470 -0
  46. src/htcli/commands/node/handlers.py +844 -0
  47. src/htcli/commands/node/prompts.py +346 -0
  48. src/htcli/commands/overwatch/__init__.py +219 -0
  49. src/htcli/commands/overwatch/display.py +396 -0
  50. src/htcli/commands/overwatch/error_handling.py +276 -0
  51. src/htcli/commands/overwatch/handlers.py +443 -0
  52. src/htcli/commands/overwatch/prompts.py +359 -0
  53. src/htcli/commands/stake/__init__.py +736 -0
  54. src/htcli/commands/stake/display.py +1103 -0
  55. src/htcli/commands/stake/error_handling.py +425 -0
  56. src/htcli/commands/stake/handlers.py +1902 -0
  57. src/htcli/commands/stake/prompts.py +1080 -0
  58. src/htcli/commands/subnet/__init__.py +639 -0
  59. src/htcli/commands/subnet/display.py +801 -0
  60. src/htcli/commands/subnet/error_handling.py +524 -0
  61. src/htcli/commands/subnet/handlers.py +2855 -0
  62. src/htcli/commands/subnet/prompts.py +1225 -0
  63. src/htcli/commands/validator/__init__.py +192 -0
  64. src/htcli/commands/validator/display.py +54 -0
  65. src/htcli/commands/validator/handlers.py +340 -0
  66. src/htcli/commands/wallet/__init__.py +546 -0
  67. src/htcli/commands/wallet/display.py +806 -0
  68. src/htcli/commands/wallet/error_handling.py +210 -0
  69. src/htcli/commands/wallet/handlers.py +3040 -0
  70. src/htcli/commands/wallet/prompts.py +1518 -0
  71. src/htcli/config.py +184 -0
  72. src/htcli/dependencies.py +186 -0
  73. src/htcli/errors/__init__.py +63 -0
  74. src/htcli/errors/base.py +141 -0
  75. src/htcli/errors/display.py +20 -0
  76. src/htcli/errors/handlers.py +710 -0
  77. src/htcli/main.py +343 -0
  78. src/htcli/models/__init__.py +21 -0
  79. src/htcli/models/enums/enum_types.py +35 -0
  80. src/htcli/models/errors.py +103 -0
  81. src/htcli/models/requests/__init__.py +197 -0
  82. src/htcli/models/requests/config.py +70 -0
  83. src/htcli/models/requests/consensus.py +19 -0
  84. src/htcli/models/requests/governance.py +38 -0
  85. src/htcli/models/requests/identity.py +51 -0
  86. src/htcli/models/requests/key.py +22 -0
  87. src/htcli/models/requests/node.py +91 -0
  88. src/htcli/models/requests/overwatch.py +64 -0
  89. src/htcli/models/requests/staking.py +580 -0
  90. src/htcli/models/requests/subnet.py +195 -0
  91. src/htcli/models/requests/validator.py +139 -0
  92. src/htcli/models/requests/wallet.py +118 -0
  93. src/htcli/models/responses/__init__.py +147 -0
  94. src/htcli/models/responses/base.py +18 -0
  95. src/htcli/models/responses/chain.py +39 -0
  96. src/htcli/models/responses/config.py +58 -0
  97. src/htcli/models/responses/identity.py +102 -0
  98. src/htcli/models/responses/overwatch.py +51 -0
  99. src/htcli/models/responses/staking.py +502 -0
  100. src/htcli/models/responses/subnet.py +856 -0
  101. src/htcli/models/responses/wallet.py +185 -0
  102. src/htcli/ui/__init__.py +87 -0
  103. src/htcli/ui/colors.py +309 -0
  104. src/htcli/ui/components/__init__.py +60 -0
  105. src/htcli/ui/components/panels.py +174 -0
  106. src/htcli/ui/components/progress.py +166 -0
  107. src/htcli/ui/components/spinners.py +92 -0
  108. src/htcli/ui/components/tables.py +809 -0
  109. src/htcli/ui/components/trees.py +721 -0
  110. src/htcli/ui/display.py +336 -0
  111. src/htcli/ui/prompts.py +870 -0
  112. src/htcli/utils/__init__.py +76 -0
  113. src/htcli/utils/blockchain/__init__.py +75 -0
  114. src/htcli/utils/blockchain/formatting.py +368 -0
  115. src/htcli/utils/blockchain/patches.py +286 -0
  116. src/htcli/utils/blockchain/peer_id.py +186 -0
  117. src/htcli/utils/blockchain/staking.py +448 -0
  118. src/htcli/utils/blockchain/type_registry.py +1373 -0
  119. src/htcli/utils/blockchain/validation.py +179 -0
  120. src/htcli/utils/cache.py +613 -0
  121. src/htcli/utils/constants.py +38 -0
  122. src/htcli/utils/legacy/__init__.py +12 -0
  123. src/htcli/utils/legacy/colors.py +311 -0
  124. src/htcli/utils/legacy/crypto.py +1176 -0
  125. src/htcli/utils/legacy/formatting.py +452 -0
  126. src/htcli/utils/legacy/interactive.py +306 -0
  127. src/htcli/utils/legacy/subnet_manifest.py +265 -0
  128. src/htcli/utils/legacy/validation.py +488 -0
  129. src/htcli/utils/logging.py +183 -0
  130. src/htcli/utils/network/__init__.py +20 -0
  131. src/htcli/utils/network/subnet.py +344 -0
  132. src/htcli/utils/prompts.py +27 -0
  133. src/htcli/utils/scale_codec.py +155 -0
  134. src/htcli/utils/validation/__init__.py +57 -0
  135. src/htcli/utils/validation/prompt_validators.py +267 -0
  136. src/htcli/utils/wallet/__init__.py +65 -0
  137. src/htcli/utils/wallet/auth.py +151 -0
  138. src/htcli/utils/wallet/core.py +1069 -0
  139. src/htcli/utils/wallet/crypto.py +1615 -0
  140. src/htcli/utils/wallet/migration.py +159 -0
@@ -0,0 +1,61 @@
1
+ from typing import Optional
2
+ from textwrap import dedent
3
+
4
+ import typer
5
+
6
+ from .handlers import propose_attestation_handler, attest_handler
7
+
8
+ app = typer.Typer(name="consensus", help="Consensus operations")
9
+
10
+
11
+ PROPOSE_HELP = dedent(
12
+ """\
13
+ Propose an attestation on a subnet.
14
+
15
+ Validators use this command to submit a proposal (arbitrary data) for consensus.
16
+ Requires a registered hotkey.
17
+
18
+ \b
19
+ Example:
20
+ htcli consensus propose --wallet my-hotkey --subnet 1 --data "0x1234..."
21
+ """
22
+ )
23
+
24
+ ATTEST_HELP = dedent(
25
+ """\
26
+ Attest to an existing proposal.
27
+
28
+ Validators use this command to vote for a proposal by its hash.
29
+ Requires a registered hotkey.
30
+
31
+ \b
32
+ Example:
33
+ htcli consensus attest --wallet my-hotkey --subnet 1 --hash "0xabcd..."
34
+ """
35
+ )
36
+
37
+
38
+ @app.command("propose", help=PROPOSE_HELP)
39
+ def propose(
40
+ wallet: str = typer.Option(..., "--wallet", "-w", help="Wallet name (hotkey)"),
41
+ subnet_id: int = typer.Option(..., "--subnet", "-s", help="Subnet ID"),
42
+ data: str = typer.Option(..., "--data", "-d", help="Data to propose"),
43
+ password: Optional[str] = typer.Option(
44
+ None, "--password", "-p", help="Wallet password"
45
+ ),
46
+ ):
47
+ """Propose an attestation on a subnet."""
48
+ propose_attestation_handler(wallet, subnet_id, data, password)
49
+
50
+
51
+ @app.command("attest", help=ATTEST_HELP)
52
+ def attest(
53
+ wallet: str = typer.Option(..., "--wallet", "-w", help="Wallet name (hotkey)"),
54
+ subnet_id: int = typer.Option(..., "--subnet", "-s", help="Subnet ID"),
55
+ proposal_hash: str = typer.Option(..., "--hash", "-h", help="Proposal hash"),
56
+ password: Optional[str] = typer.Option(
57
+ None, "--password", "-p", help="Wallet password"
58
+ ),
59
+ ):
60
+ """Attest to an existing proposal."""
61
+ attest_handler(wallet, subnet_id, proposal_hash, password)
@@ -0,0 +1,100 @@
1
+ """
2
+ Consensus command handlers.
3
+ """
4
+
5
+ from typing import Optional
6
+
7
+ from rich.console import Console
8
+
9
+ from ...dependencies import get_client
10
+ from ...models.requests.consensus import AttestRequest, ProposeAttestationRequest
11
+ from ...ui.components.spinners import HTCLISpinner
12
+ from ...ui.display import print_error, print_info, print_success
13
+ from ...ui.prompts import confirm_prompt
14
+ from ...utils.wallet.core import _get_keypair_with_password_retry
15
+
16
+ console = Console()
17
+
18
+
19
+ def propose_attestation_handler(
20
+ wallet_name: str,
21
+ subnet_id: int,
22
+ data: str,
23
+ password: Optional[str] = None,
24
+ ):
25
+ """Handle propose attestation command."""
26
+ try:
27
+ client = get_client()
28
+ if not client.connect():
29
+ return
30
+
31
+ # Get keypair
32
+ print_info(f"🔐 Unlocking wallet '{wallet_name}'...", emoji=False)
33
+ wallet_manager = client.offchain.wallet
34
+ # Assuming validators sign with hotkeys for consensus
35
+ wallet_info = wallet_manager.get_wallet_info(wallet_name, is_hotkey=True)
36
+ _, keypair = _get_keypair_with_password_retry(
37
+ client, wallet_name, wallet_info["data"], 3, password
38
+ )
39
+
40
+ # Confirm
41
+ if not confirm_prompt(
42
+ f"Propose attestation for subnet {subnet_id}?", default=True
43
+ ):
44
+ return
45
+
46
+ # Submit
47
+ with HTCLISpinner("Submitting proposal..."):
48
+ request = ProposeAttestationRequest(subnet_id=subnet_id, data=data)
49
+ response = client.extrinsics.consensus.propose_attestation(request, keypair)
50
+
51
+ if response["success"]:
52
+ print_success("✅ Proposal submitted successfully!")
53
+ console.print(f" • Block Hash: {response.get('block_hash')}")
54
+ else:
55
+ print_error(f"Proposal failed: {response.get('error')}")
56
+
57
+ except Exception as e:
58
+ print_error(f"Error: {str(e)}")
59
+
60
+
61
+ def attest_handler(
62
+ wallet_name: str,
63
+ subnet_id: int,
64
+ proposal_hash: str,
65
+ password: Optional[str] = None,
66
+ ):
67
+ """Handle attest command."""
68
+ try:
69
+ client = get_client()
70
+ if not client.connect():
71
+ return
72
+
73
+ # Get keypair
74
+ print_info(f"🔐 Unlocking wallet '{wallet_name}'...", emoji=False)
75
+ wallet_manager = client.offchain.wallet
76
+ # Assuming validators sign with hotkeys for consensus
77
+ wallet_info = wallet_manager.get_wallet_info(wallet_name, is_hotkey=True)
78
+ _, keypair = _get_keypair_with_password_retry(
79
+ client, wallet_name, wallet_info["data"], 3, password
80
+ )
81
+
82
+ # Confirm
83
+ if not confirm_prompt(
84
+ f"Attest to proposal {proposal_hash} on subnet {subnet_id}?", default=True
85
+ ):
86
+ return
87
+
88
+ # Submit
89
+ with HTCLISpinner("Submitting attestation..."):
90
+ request = AttestRequest(subnet_id=subnet_id, proposal_hash=proposal_hash)
91
+ response = client.extrinsics.consensus.attest(request, keypair)
92
+
93
+ if response["success"]:
94
+ print_success("✅ Attestation submitted successfully!")
95
+ console.print(f" • Block Hash: {response.get('block_hash')}")
96
+ else:
97
+ print_error(f"Attestation failed: {response.get('error')}")
98
+
99
+ except Exception as e:
100
+ print_error(f"Error: {str(e)}")
@@ -0,0 +1,49 @@
1
+ from typing import Optional
2
+ from textwrap import dedent
3
+
4
+ import typer
5
+
6
+ from .handlers import set_parameter_handler
7
+
8
+ app = typer.Typer(name="governance", help="Governance operations")
9
+
10
+
11
+ SET_PARAM_HELP = dedent(
12
+ """\
13
+ Update a network governance parameter.
14
+
15
+ Requires administrative privileges (sudo/root key).
16
+
17
+ \b
18
+ Available Parameters:
19
+ - min_registration_cost
20
+ - target_registrations_per_interval
21
+ - max_registrations_per_interval
22
+ - adjustment_alpha
23
+ - target_max_subnet_nodes
24
+ - tx_rate_limit
25
+ - min_delegate_stake_percentage
26
+ - max_delegate_stake_percentage
27
+ - min_max_registered_nodes
28
+ - max_max_registered_nodes
29
+ - network_min_stake_balance
30
+ - network_max_stake_balance
31
+
32
+ \b
33
+ Example:
34
+ htcli governance set-param --wallet root --param min_registration_cost --value 1000
35
+ """
36
+ )
37
+
38
+
39
+ @app.command("set-param", help=SET_PARAM_HELP)
40
+ def set_param(
41
+ wallet: str = typer.Option(..., "--wallet", "-w", help="Governance wallet name"),
42
+ parameter: str = typer.Option(..., "--param", "-p", help="Parameter name"),
43
+ value: int = typer.Option(..., "--value", "-v", help="New value"),
44
+ password: Optional[str] = typer.Option(
45
+ None, "--password", "-pw", help="Wallet password"
46
+ ),
47
+ ):
48
+ """Update a network governance parameter."""
49
+ set_parameter_handler(wallet, parameter, value, password)
@@ -0,0 +1,81 @@
1
+ """
2
+ Governance command handlers.
3
+ """
4
+
5
+ from typing import Optional
6
+
7
+ from rich.console import Console
8
+
9
+ from ...dependencies import get_client
10
+ from ...models.requests.governance import GovernanceParameterUpdateRequest
11
+ from ...ui.components.spinners import HTCLISpinner
12
+ from ...ui.display import print_error, print_info, print_success
13
+ from ...ui.prompts import confirm_prompt
14
+ from ...utils.wallet.core import _get_keypair_with_password_retry
15
+
16
+ console = Console()
17
+
18
+
19
+ def set_parameter_handler(
20
+ wallet_name: str,
21
+ parameter: str,
22
+ value: int,
23
+ password: Optional[str] = None,
24
+ ):
25
+ """Handle parameter setting command."""
26
+ try:
27
+ # Map parameter names to client method names before attempting wallet unlock
28
+ method_name_map = {
29
+ "min_registration_cost": "set_min_registration_cost",
30
+ "target_registrations_per_interval": "set_target_registrations_per_interval",
31
+ "max_registrations_per_interval": "set_max_registrations_per_interval",
32
+ "adjustment_alpha": "set_adjustment_alpha",
33
+ "target_max_subnet_nodes": "set_target_max_subnet_nodes",
34
+ "tx_rate_limit": "set_tx_rate_limit",
35
+ "min_delegate_stake_percentage": "set_min_delegate_stake_percentage",
36
+ "max_delegate_stake_percentage": "set_max_delegate_stake_percentage",
37
+ "min_max_registered_nodes": "set_min_max_registered_nodes",
38
+ "max_max_registered_nodes": "set_max_max_registered_nodes",
39
+ "network_min_stake_balance": "set_network_min_stake_balance",
40
+ "network_max_stake_balance": "set_network_max_stake_balance",
41
+ }
42
+
43
+ if parameter not in method_name_map:
44
+ print_error(f"Unknown parameter: {parameter}")
45
+ console.print("Available parameters:")
46
+ for p in method_name_map.keys():
47
+ console.print(f" • {p}")
48
+ return
49
+
50
+ client = get_client()
51
+ if not client.connect():
52
+ return
53
+
54
+ # Get keypair (must be governance)
55
+ print_info(f"🔐 Unlocking governance wallet '{wallet_name}'...", emoji=False)
56
+ wallet_manager = client.offchain.wallet
57
+ # Assuming governance keys are coldkeys
58
+ wallet_info = wallet_manager.get_wallet_info(wallet_name, is_hotkey=False)
59
+ _, keypair = _get_keypair_with_password_retry(
60
+ client, wallet_name, wallet_info["data"], 3, password
61
+ )
62
+
63
+ method = getattr(client.extrinsics.governance, method_name_map[parameter])
64
+
65
+ # Confirm
66
+ if not confirm_prompt(f"Set {parameter} to {value}?", default=False):
67
+ return
68
+
69
+ # Submit
70
+ with HTCLISpinner(f"Setting {parameter}..."):
71
+ request = GovernanceParameterUpdateRequest(value=value)
72
+ response = method(request, keypair)
73
+
74
+ if response["success"]:
75
+ print_success(f"✅ {parameter} set successfully!")
76
+ console.print(f" • Block Hash: {response.get('block_hash')}")
77
+ else:
78
+ print_error(f"Failed to set parameter: {response.get('error')}")
79
+
80
+ except Exception as e:
81
+ print_error(f"Error: {str(e)}")
@@ -0,0 +1,304 @@
1
+ """
2
+ Node command module - placeholder implementation.
3
+ """
4
+
5
+ from textwrap import dedent
6
+ from typing import Optional
7
+
8
+ import typer
9
+
10
+ from ..subnet import handlers as subnet_handlers
11
+ from . import handlers as node_handlers
12
+
13
+ app = typer.Typer(name="node", help="Node operations")
14
+
15
+
16
+ UPDATE_NODE_HELP = dedent(
17
+ """\
18
+ Update node configuration.
19
+
20
+ This command allows updating various properties of a registered node.
21
+ You can update multiple fields in a single call. Any provided field will be
22
+ updated sequentially and a consolidated summary will be displayed.
23
+
24
+ \b
25
+ Examples:
26
+ htcli node update --subnet-id 1 --node-id 5 --delegate-rate 10
27
+ htcli node update --subnet-id 1 --node-id 5 --peer-id "new-peer-id"
28
+ """
29
+ )
30
+
31
+
32
+ @app.command("register")
33
+ def register_node(
34
+ subnet_id: Optional[int] = typer.Option(
35
+ None, "--subnet-id", help="Subnet ID to register on"
36
+ ),
37
+ validator_id: Optional[int] = typer.Option(
38
+ None, "--validator-id", help="Validator ID that owns the node"
39
+ ),
40
+ hotkey: Optional[str] = typer.Option(
41
+ None, "--hotkey", help="Hotkey address for the node"
42
+ ),
43
+ stake: Optional[float] = typer.Option(
44
+ None, "--stake", help="Initial stake amount (TENSOR)"
45
+ ),
46
+ peer_id: Optional[str] = typer.Option(
47
+ None, "--peer-id", help="Peer ID (auto-generated if not provided)"
48
+ ),
49
+ coldkey: Optional[str] = typer.Option(
50
+ None,
51
+ "--coldkey",
52
+ help="Coldkey wallet name or address to sign the registration extrinsic",
53
+ ),
54
+ ):
55
+ """Register a new node on a subnet."""
56
+ node_handlers.register_node_handler(
57
+ subnet_id=subnet_id,
58
+ validator_id=validator_id,
59
+ hotkey=hotkey,
60
+ stake_amount=stake,
61
+ peer_id=peer_id,
62
+ coldkey=coldkey,
63
+ )
64
+
65
+
66
+ @app.command("list")
67
+ def list_nodes(
68
+ subnet_id: Optional[int] = typer.Option(
69
+ None, "--subnet-id", "--subnetid", help="The unique ID (UID) of the subnet."
70
+ ),
71
+ live: bool = typer.Option(
72
+ False,
73
+ "--live",
74
+ help="Enable live updates when supported.",
75
+ ),
76
+ coldkey: Optional[str] = typer.Option(
77
+ None, "--coldkey", help="Coldkey wallet name or address to list nodes for"
78
+ ),
79
+ ):
80
+ """List all nodes registered to a specific subnet using RPC. Use --coldkey to filter by coldkey."""
81
+ _ = live
82
+ subnet_handlers.get_subnet_nodes_handler(subnet_id, coldkey)
83
+
84
+
85
+ @app.command("all")
86
+ def get_all_nodes(
87
+ live: bool = typer.Option(
88
+ False,
89
+ "--live",
90
+ help="Enable live updates when supported.",
91
+ ),
92
+ ):
93
+ """Get all nodes across all subnets using RPC."""
94
+ _ = live
95
+ node_handlers.get_all_nodes_handler()
96
+
97
+
98
+ @app.command("status")
99
+ def get_node_status(
100
+ subnet_id: Optional[int] = typer.Argument(
101
+ None,
102
+ help="The subnet ID",
103
+ ),
104
+ node_id: Optional[int] = typer.Argument(
105
+ None,
106
+ help="The node ID within the subnet",
107
+ ),
108
+ ):
109
+ """Get the status for a specific node."""
110
+ subnet_handlers.get_node_status_handler(subnet_id, node_id)
111
+
112
+
113
+ @app.command("info")
114
+ def get_node_info(
115
+ subnet_id: Optional[int] = typer.Option(
116
+ None, "--subnet-id", "--subnetid", help="The subnet ID"
117
+ ),
118
+ node_id: Optional[int] = typer.Option(
119
+ None, "--node-id", "--nodeid", help="The node ID"
120
+ ),
121
+ ):
122
+ """Get detailed information about a specific node using RPC."""
123
+ node_handlers.get_node_info_handler(subnet_id, node_id)
124
+
125
+
126
+ @app.command("overwatch-commits", context_settings={"allow_extra_args": True})
127
+ def get_overwatch_commits(
128
+ ctx: typer.Context,
129
+ epoch: Optional[int] = typer.Option(None, "--epoch", "-e", help="The epoch number"),
130
+ overwatch_node_id: Optional[int] = typer.Option(
131
+ None, "--node-id", "-n", help="The overwatch node ID"
132
+ ),
133
+ ):
134
+ """Get overwatch commits for a specific node and epoch using RPC."""
135
+ if epoch is None and len(ctx.args) > 0:
136
+ try:
137
+ epoch = int(ctx.args[0])
138
+ except ValueError:
139
+ pass
140
+ if overwatch_node_id is None and len(ctx.args) > 1:
141
+ try:
142
+ overwatch_node_id = int(ctx.args[1])
143
+ except ValueError:
144
+ pass
145
+ node_handlers.get_overwatch_commits_handler(epoch, overwatch_node_id)
146
+
147
+
148
+ @app.command("overwatch-reveals", context_settings={"allow_extra_args": True})
149
+ def get_overwatch_reveals(
150
+ ctx: typer.Context,
151
+ epoch: Optional[int] = typer.Option(None, "--epoch", "-e", help="The epoch number"),
152
+ overwatch_node_id: Optional[int] = typer.Option(
153
+ None, "--node-id", "-n", help="The overwatch node ID"
154
+ ),
155
+ ):
156
+ """Get overwatch reveals for a specific node and epoch using RPC."""
157
+ if epoch is None and len(ctx.args) > 0:
158
+ try:
159
+ epoch = int(ctx.args[0])
160
+ except ValueError:
161
+ pass
162
+ if overwatch_node_id is None and len(ctx.args) > 1:
163
+ try:
164
+ overwatch_node_id = int(ctx.args[1])
165
+ except ValueError:
166
+ pass
167
+ node_handlers.get_overwatch_reveals_handler(epoch, overwatch_node_id)
168
+
169
+
170
+ @app.command("remove")
171
+ def remove_node(
172
+ subnet_id: Optional[int] = typer.Option(
173
+ None,
174
+ "--subnet-id",
175
+ help="Subnet ID of the node to remove",
176
+ ),
177
+ node_id: Optional[int] = typer.Option(
178
+ None,
179
+ "--node-id",
180
+ help="Node ID to remove",
181
+ ),
182
+ coldkey: Optional[str] = typer.Option(
183
+ None,
184
+ "--coldkey",
185
+ help="Coldkey wallet name or address to sign the removal extrinsic",
186
+ ),
187
+ ):
188
+ """Remove a node from a subnet."""
189
+ node_handlers.remove_node_handler(
190
+ subnet_id=subnet_id, node_id=node_id, coldkey=coldkey
191
+ )
192
+
193
+
194
+ @app.command("activate")
195
+ def activate_node(
196
+ subnet_id: Optional[int] = typer.Option(
197
+ None,
198
+ "--subnet-id",
199
+ help="Subnet ID of the node to activate",
200
+ ),
201
+ node_id: Optional[int] = typer.Option(
202
+ None,
203
+ "--node-id",
204
+ help="Node ID to activate",
205
+ ),
206
+ coldkey: Optional[str] = typer.Option(
207
+ None,
208
+ "--coldkey",
209
+ help="Coldkey wallet name or address to sign the activation extrinsic",
210
+ ),
211
+ ):
212
+ """Activate a registered node on a subnet."""
213
+ node_handlers.activate_node_handler(
214
+ subnet_id=subnet_id, node_id=node_id, coldkey=coldkey
215
+ )
216
+
217
+
218
+ @app.command("deactivate")
219
+ def deactivate_node(
220
+ subnet_id: Optional[int] = typer.Option(
221
+ None,
222
+ "--subnet-id",
223
+ help="Subnet ID of the node to deactivate",
224
+ ),
225
+ node_id: Optional[int] = typer.Option(
226
+ None,
227
+ "--node-id",
228
+ help="Node ID to deactivate",
229
+ ),
230
+ coldkey: Optional[str] = typer.Option(
231
+ None,
232
+ "--coldkey",
233
+ help="Coldkey wallet name or address to sign the deactivation extrinsic",
234
+ ),
235
+ ):
236
+ """Deactivate a node on a subnet."""
237
+ node_handlers.deactivate_node_handler(
238
+ subnet_id=subnet_id, node_id=node_id, coldkey=coldkey
239
+ )
240
+
241
+
242
+ @app.command("update", help=UPDATE_NODE_HELP)
243
+ def update_node(
244
+ subnet_id: Optional[int] = typer.Option(
245
+ None, "--subnet-id", help="Subnet ID of the node"
246
+ ),
247
+ node_id: Optional[int] = typer.Option(None, "--node-id", help="Node ID to update"),
248
+ peer_id: Optional[str] = typer.Option(
249
+ None,
250
+ "--peer-id",
251
+ help="New primary peer ID for the node",
252
+ ),
253
+ bootnode: Optional[str] = typer.Option(
254
+ None,
255
+ "--bootnode",
256
+ help="New bootnode multiaddress for the node (empty string to clear)",
257
+ ),
258
+ bootnode_peer_id: Optional[str] = typer.Option(
259
+ None,
260
+ "--bootnode-peer-id",
261
+ help="New bootnode peer ID",
262
+ ),
263
+ client_peer_id: Optional[str] = typer.Option(
264
+ None,
265
+ "--client-peer-id",
266
+ help="New client peer ID",
267
+ ),
268
+ delegate_rate: Optional[int] = typer.Option(
269
+ None,
270
+ "--delegate-rate",
271
+ help="New delegate reward rate (0-100 as percent, or 1e18-format value)",
272
+ ),
273
+ unique: Optional[str] = typer.Option(
274
+ None,
275
+ "--unique",
276
+ help="New unique metadata value (empty string to clear)",
277
+ ),
278
+ non_unique: Optional[str] = typer.Option(
279
+ None,
280
+ "--non-unique",
281
+ help="New non-unique metadata value (empty string to clear)",
282
+ ),
283
+ coldkey: Optional[str] = typer.Option(
284
+ None,
285
+ "--coldkey",
286
+ help="Coldkey wallet name or address to sign the update transaction",
287
+ ),
288
+ ):
289
+ """Update node configuration."""
290
+ node_handlers.update_node_handler(
291
+ subnet_id=subnet_id,
292
+ node_id=node_id,
293
+ peer_id=peer_id,
294
+ bootnode=bootnode,
295
+ bootnode_peer_id=bootnode_peer_id,
296
+ client_peer_id=client_peer_id,
297
+ delegate_reward_rate=delegate_rate,
298
+ unique=unique,
299
+ non_unique=non_unique,
300
+ coldkey=coldkey,
301
+ )
302
+
303
+
304
+ __all__ = ["app"]