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,31 @@
1
+ """
2
+ HTCLI Commands Module.
3
+
4
+ This module contains all command implementations organized by domain.
5
+ Each command is a folder with its own entry point, prompts, handlers, and display logic.
6
+ """
7
+
8
+ # Import all command apps for main.py
9
+ from .chain import app as chain_app
10
+ from .config import app as config_app
11
+ from .consensus import app as consensus_app
12
+ from .governance import app as governance_app
13
+ from .node import app as node_app
14
+ from .overwatch import app as overwatch_app
15
+ from .stake import app as stake_app
16
+ from .subnet import app as subnet_app
17
+ from .validator import app as validator_app
18
+ from .wallet import app as wallet_app
19
+
20
+ __all__ = [
21
+ "wallet_app",
22
+ "subnet_app",
23
+ "stake_app",
24
+ "chain_app",
25
+ "node_app",
26
+ "config_app",
27
+ "overwatch_app",
28
+ "consensus_app",
29
+ "governance_app",
30
+ "validator_app",
31
+ ]
@@ -0,0 +1,66 @@
1
+ """
2
+ Chain command module.
3
+
4
+ Provides blockchain information and network statistics.
5
+ """
6
+
7
+ import typer
8
+
9
+ from .handlers import (
10
+ chain_info_handler,
11
+ chain_ping_handler,
12
+ chain_stats_handler,
13
+ get_account_handler,
14
+ get_block_handler,
15
+ get_transaction_handler,
16
+ registration_cost_handler,
17
+ )
18
+
19
+ app = typer.Typer(name="chain", help="Chain operations")
20
+
21
+
22
+ @app.command("info")
23
+ def chain_info():
24
+ """Get comprehensive chain information."""
25
+ chain_info_handler()
26
+
27
+
28
+ @app.command("ping")
29
+ def chain_ping():
30
+ """Ping the configured chain endpoint and show node health."""
31
+ chain_ping_handler()
32
+
33
+
34
+ @app.command("stats")
35
+ def chain_stats():
36
+ """Get network statistics."""
37
+ chain_stats_handler()
38
+
39
+
40
+ @app.command("cost")
41
+ def registration_cost():
42
+ """Query current subnet registration cost."""
43
+ registration_cost_handler()
44
+
45
+
46
+ @app.command("block")
47
+ def block_info(block_number: int = typer.Argument(..., help="Block number to query")):
48
+ """Get block details for a specific block number."""
49
+ get_block_handler(block_number)
50
+
51
+
52
+ @app.command("tx")
53
+ def transaction_info(
54
+ tx_hash: str = typer.Argument(..., help="Transaction hash to query"),
55
+ ):
56
+ """Get basic transaction details for a specific hash."""
57
+ get_transaction_handler(tx_hash)
58
+
59
+
60
+ @app.command("account")
61
+ def account_info(address: str = typer.Argument(..., help="Account address to query")):
62
+ """Get basic account information."""
63
+ get_account_handler(address)
64
+
65
+
66
+ __all__ = ["app"]
@@ -0,0 +1,204 @@
1
+ """
2
+ Chain command display functions.
3
+
4
+ Handles formatting and displaying blockchain information and statistics.
5
+ """
6
+
7
+ from typing import Optional
8
+
9
+ from ...ui.components import HTCLIPanel, HTCLITable
10
+ from ...ui.display import HTCLIConsole
11
+
12
+ console = HTCLIConsole()
13
+
14
+
15
+ def display_chain_info(chain_data: dict):
16
+ """Display chain information in a structured table."""
17
+ table = HTCLITable(
18
+ title="[bold blue]⛓️ Chain Information[/bold blue]",
19
+ border_style="blue",
20
+ header_style="bold cyan",
21
+ )
22
+ table.add_column("Property", style="bold white", width=25)
23
+ table.add_column("Value", style="bright_white", width=50)
24
+
25
+ # Add chain data
26
+ table.add_row("Chain Name", str(chain_data.get("chain_name", "Unknown")))
27
+
28
+ # Format block number safely
29
+ block_number = chain_data.get("block_number")
30
+ block_display = f"{block_number:,}" if block_number is not None else "N/A"
31
+ table.add_row("Current Block", block_display)
32
+
33
+ # Format epoch number safely
34
+ current_epoch = chain_data.get("current_epoch")
35
+ epoch_display = f"{current_epoch:,}" if current_epoch is not None else "N/A"
36
+ table.add_row("Current Epoch", epoch_display)
37
+
38
+ table.add_row("Runtime Version", str(chain_data.get("runtime_version", "Unknown")))
39
+ table.add_row("Token Decimals", str(chain_data.get("token_decimals", 18)))
40
+
41
+ table.render()
42
+
43
+
44
+ def display_chain_stats(stats_data: dict):
45
+ """Display network statistics in a panel."""
46
+ block_number = stats_data.get("block_number")
47
+ current_epoch = stats_data.get("current_epoch")
48
+ total_subnets = stats_data.get("total_subnets", 0)
49
+ active_subnets = stats_data.get("active_subnets", 0)
50
+ total_nodes = stats_data.get("total_nodes", 0)
51
+ active_nodes = stats_data.get("active_nodes", 0)
52
+ registration_cost = stats_data.get("registration_cost")
53
+
54
+ # Format safely
55
+ block_display = f"{block_number:,}" if block_number is not None else "N/A"
56
+ epoch_display = f"{current_epoch:,}" if current_epoch is not None else "N/A"
57
+
58
+ cost_display = (
59
+ f"{registration_cost / 1e18:,.2f} TENSOR" if registration_cost else "N/A"
60
+ )
61
+
62
+ content = f"""[htcli.accent]Network Statistics[/htcli.accent]
63
+
64
+ [htcli.value]Block Number:[/htcli.value] {block_display}
65
+ [htcli.value]Current Epoch:[/htcli.value] {epoch_display}
66
+
67
+ [htcli.accent]Subnets[/htcli.accent]
68
+ [htcli.value]Total Subnets:[/htcli.value] {total_subnets}
69
+ [htcli.success]Active Subnets:[/htcli.success] {active_subnets}
70
+
71
+ [htcli.accent]Nodes[/htcli.accent]
72
+ [htcli.value]Total Nodes:[/htcli.value] {total_nodes}
73
+ [htcli.success]Active Nodes:[/htcli.success] {active_nodes}
74
+
75
+ [htcli.accent]Costs[/htcli.accent]
76
+ [htcli.value]Subnet Registration:[/htcli.value] {cost_display}
77
+ """
78
+
79
+ panel = HTCLIPanel(
80
+ content,
81
+ title="📊 Network Statistics",
82
+ border_style="htcli.info",
83
+ highlight=True,
84
+ )
85
+ panel.render()
86
+
87
+
88
+ def display_chain_ping(ping_data: dict):
89
+ """Display node ping and health information."""
90
+ latency = ping_data.get("latency_ms")
91
+ latency_display = f"{latency:,.2f} ms" if latency is not None else "N/A"
92
+ is_syncing = ping_data.get("is_syncing")
93
+ syncing_display = "Unknown" if is_syncing is None else str(is_syncing)
94
+ should_have_peers = ping_data.get("should_have_peers")
95
+ should_have_peers_display = (
96
+ "Unknown" if should_have_peers is None else str(should_have_peers)
97
+ )
98
+
99
+ content = f"""[htcli.accent]Node Health[/htcli.accent]
100
+
101
+ [htcli.value]Endpoint:[/htcli.value] {ping_data.get("endpoint", "Unknown")}
102
+ [htcli.value]Latency:[/htcli.value] {latency_display}
103
+ [htcli.value]Peers:[/htcli.value] {ping_data.get("peers", "Unknown")}
104
+ [htcli.value]Syncing:[/htcli.value] {syncing_display}
105
+ [htcli.value]Should Have Peers:[/htcli.value] {should_have_peers_display}
106
+ """
107
+
108
+ panel = HTCLIPanel(
109
+ content,
110
+ title="Node Ping",
111
+ border_style="htcli.success",
112
+ highlight=True,
113
+ )
114
+ panel.render()
115
+
116
+
117
+ def display_registration_cost(
118
+ current_cost: Optional[int],
119
+ block_number: Optional[int],
120
+ current_epoch: Optional[int],
121
+ ):
122
+ """Display subnet registration cost information."""
123
+ if current_cost is None:
124
+ console.print("[htcli.error]❌ Failed to query registration cost[/]")
125
+ return
126
+
127
+ cost_tensor = current_cost / 1e18
128
+
129
+ # Format safely
130
+ block_display = f"{block_number:,}" if block_number is not None else "N/A"
131
+ epoch_display = f"{current_epoch:,}" if current_epoch is not None else "N/A"
132
+
133
+ content = f"""[htcli.accent]Subnet Registration Cost[/htcli.accent]
134
+
135
+ [htcli.value]Current Cost:[/htcli.value] {cost_tensor:,.2f} TENSOR
136
+ [htcli.value]In Wei:[/htcli.value] {current_cost:,}
137
+
138
+ [htcli.value]Current Block:[/htcli.value] {block_display}
139
+ [htcli.value]Current Epoch:[/htcli.value] {epoch_display}
140
+
141
+ [htcli.info]💡 Cost Information:[/htcli.info]
142
+ • The cost decreases over time using exponential decay
143
+ • Cost increases immediately after each new subnet registration (typically doubles)
144
+ • Recommended: Set --max-cost to at least {cost_tensor * 1.1:,.2f} TENSOR (10% buffer)
145
+
146
+ [htcli.accent]Example Usage:[/htcli.accent]
147
+ htcli subnet register --name "My Subnet" --max-cost {cost_tensor * 1.1:,.2f} ...
148
+ """
149
+
150
+ panel = HTCLIPanel(
151
+ content,
152
+ title="💰 Subnet Registration Cost",
153
+ border_style="htcli.warning",
154
+ highlight=True,
155
+ )
156
+ panel.render()
157
+
158
+
159
+ def display_block_info(block_data: dict):
160
+ """Display block details in a compact table."""
161
+ table = HTCLITable(
162
+ title="[bold blue]🧱 Block Information[/bold blue]",
163
+ border_style="blue",
164
+ header_style="bold cyan",
165
+ )
166
+ table.add_column("Property", style="bold white", width=25)
167
+ table.add_column("Value", style="bright_white", width=60)
168
+
169
+ table.add_row("Block Number", str(block_data.get("number", "N/A")))
170
+ table.add_row("Block Hash", str(block_data.get("hash", "N/A")))
171
+ table.add_row("Parent Hash", str(block_data.get("parent_hash", "N/A")))
172
+ table.add_row("State Root", str(block_data.get("state_root", "N/A")))
173
+ table.add_row("Extrinsics Root", str(block_data.get("extrinsics_root", "N/A")))
174
+ table.add_row("Extrinsics Count", str(block_data.get("extrinsics_count", "N/A")))
175
+ table.render()
176
+
177
+
178
+ def display_transaction_info(tx_hash: str):
179
+ """Display a simple transaction lookup result."""
180
+ panel = HTCLIPanel(
181
+ f"[htcli.value]Transaction Hash:[/htcli.value] {tx_hash}\n\n"
182
+ "[htcli.info]Transaction lookup is not fully implemented yet.[/htcli.info]",
183
+ title="🧾 Transaction Lookup",
184
+ border_style="htcli.info",
185
+ highlight=True,
186
+ )
187
+ panel.render()
188
+
189
+
190
+ def display_account_info(address: str, balance: Optional[int]):
191
+ """Display account details."""
192
+ balance_display = (
193
+ f"{balance / 1e18:,.4f} TENSOR ({balance} wei)"
194
+ if balance is not None
195
+ else "Unavailable"
196
+ )
197
+ panel = HTCLIPanel(
198
+ f"[htcli.value]Account:[/htcli.value] {address}\n"
199
+ f"[htcli.value]Balance:[/htcli.value] {balance_display}",
200
+ title="👤 Account Information",
201
+ border_style="htcli.success",
202
+ highlight=True,
203
+ )
204
+ panel.render()
@@ -0,0 +1,260 @@
1
+ """
2
+ Chain command handlers.
3
+
4
+ Handlers for blockchain information and statistics.
5
+ """
6
+
7
+ import re
8
+
9
+ from ...dependencies import get_client
10
+ from ...errors.base import AddressValidationError, HTCLIError
11
+ from ...errors.handlers import handle_and_display_error
12
+ from ...ui.components import HTCLILoadingContext
13
+ from .display import (
14
+ display_account_info,
15
+ display_block_info,
16
+ display_chain_info,
17
+ display_chain_ping,
18
+ display_chain_stats,
19
+ display_registration_cost,
20
+ )
21
+
22
+
23
+ def chain_info_handler():
24
+ """Handle chain information display."""
25
+ try:
26
+ client = get_client()
27
+
28
+ with HTCLILoadingContext("Fetching chain information..."):
29
+ # Gather chain data
30
+ block_number = client.rpc.chain.get_block_number()
31
+ current_epoch = client.rpc.chain.get_current_epoch()
32
+
33
+ # Try to get additional chain properties
34
+ try:
35
+ chain_properties = (
36
+ client.substrate.properties if client.substrate else {}
37
+ )
38
+ runtime_version = (
39
+ client.substrate.runtime_version if client.substrate else "Unknown"
40
+ )
41
+ except Exception:
42
+ chain_properties = {}
43
+ runtime_version = "Unknown"
44
+
45
+ # Prepare chain data
46
+ chain_data = {
47
+ "block_number": block_number,
48
+ "current_epoch": current_epoch,
49
+ "chain_name": chain_properties.get("tokenSymbol", "Unknown"),
50
+ "runtime_version": runtime_version,
51
+ "token_decimals": chain_properties.get("tokenDecimals", 18),
52
+ }
53
+
54
+ # Display
55
+ display_chain_info(chain_data)
56
+
57
+ except RuntimeError as e:
58
+ # Handle connection errors specifically
59
+ from ...ui.display import print_error
60
+
61
+ error_str = str(e).lower()
62
+ if "connect" in error_str or "blockchain" in error_str:
63
+ print_error(f"Blockchain Connection Error: {str(e)}")
64
+ else:
65
+ print_error(f"Error: {str(e)}")
66
+ except Exception as e:
67
+ handle_and_display_error(e)
68
+
69
+
70
+ def chain_ping_handler():
71
+ """Handle chain node ping."""
72
+ try:
73
+ client = get_client()
74
+ endpoint = getattr(getattr(client, "config", None), "network", None)
75
+ endpoint_value = getattr(endpoint, "endpoint", "Unknown")
76
+
77
+ with HTCLILoadingContext("Pinging chain endpoint..."):
78
+ result = client.rpc.chain.ping()
79
+
80
+ if not result.get("success"):
81
+ raise RuntimeError(result.get("message", "Failed to ping chain endpoint"))
82
+
83
+ ping_data = result.get("data", {})
84
+ ping_data["endpoint"] = endpoint_value
85
+ display_chain_ping(ping_data)
86
+
87
+ except RuntimeError as e:
88
+ from ...ui.display import print_error
89
+
90
+ error_str = str(e).lower()
91
+ if "connect" in error_str or "blockchain" in error_str:
92
+ print_error(f"Blockchain Connection Error: {str(e)}")
93
+ else:
94
+ print_error(f"Error: {str(e)}")
95
+ except Exception as e:
96
+ handle_and_display_error(e)
97
+
98
+
99
+ def chain_stats_handler():
100
+ """Handle network statistics display."""
101
+ try:
102
+ client = get_client()
103
+
104
+ with HTCLILoadingContext("Fetching network statistics..."):
105
+ # Get block and epoch
106
+ block_number = client.rpc.chain.get_block_number()
107
+ current_epoch = client.rpc.chain.get_current_epoch()
108
+
109
+ # Get subnet data
110
+ all_subnets = client.rpc.subnet.get_all_subnets_info()
111
+ total_subnets = len(all_subnets) if all_subnets else 0
112
+ # Active subnets are those with state == "Active" (not just having active nodes)
113
+ active_subnets = (
114
+ sum(
115
+ 1
116
+ for s in all_subnets
117
+ if hasattr(s, "state")
118
+ and (
119
+ (
120
+ hasattr(s.state, "value")
121
+ and s.state.value.lower() == "active"
122
+ )
123
+ or (isinstance(s.state, str) and s.state.lower() == "active")
124
+ )
125
+ )
126
+ if all_subnets
127
+ else 0
128
+ )
129
+
130
+ # Get node data
131
+ all_nodes = client.rpc.node.get_all_subnet_nodes_info()
132
+ total_nodes = len(all_nodes) if all_nodes else 0
133
+
134
+ # Count active nodes (classification is not Registered)
135
+ active_nodes = 0
136
+ if all_nodes:
137
+ for node in all_nodes:
138
+ if hasattr(node, "classification"):
139
+ classification = node.classification
140
+ if isinstance(classification, dict):
141
+ node_class = classification.get("node_class", "Registered")
142
+ else:
143
+ node_class = str(classification)
144
+
145
+ # Active means not in Registered state
146
+ if node_class != "Registered":
147
+ active_nodes += 1
148
+
149
+ # Get registration cost
150
+ registration_cost = client.rpc.chain.get_subnet_registration_cost()
151
+
152
+ # Prepare stats data
153
+ stats_data = {
154
+ "block_number": block_number,
155
+ "current_epoch": current_epoch,
156
+ "total_subnets": total_subnets,
157
+ "active_subnets": active_subnets,
158
+ "total_nodes": total_nodes,
159
+ "active_nodes": active_nodes,
160
+ "registration_cost": registration_cost,
161
+ }
162
+
163
+ # Display
164
+ display_chain_stats(stats_data)
165
+
166
+ except RuntimeError as e:
167
+ # Handle connection errors specifically
168
+ from ...ui.display import print_error
169
+
170
+ error_str = str(e).lower()
171
+ if "connect" in error_str or "blockchain" in error_str:
172
+ print_error(f"Blockchain Connection Error: {str(e)}")
173
+ else:
174
+ print_error(f"Error: {str(e)}")
175
+ except Exception as e:
176
+ handle_and_display_error(e)
177
+
178
+
179
+ def registration_cost_handler():
180
+ """Handle subnet registration cost query."""
181
+ try:
182
+ client = get_client()
183
+
184
+ with HTCLILoadingContext("Querying subnet registration cost..."):
185
+ current_cost = client.rpc.chain.get_subnet_registration_cost()
186
+ block_number = client.rpc.chain.get_block_number()
187
+ current_epoch = client.rpc.chain.get_current_epoch()
188
+
189
+ # Display - provide helpful message if cost is None
190
+ if current_cost is None:
191
+ from ...ui.display import print_error, print_info
192
+
193
+ print_error("Failed to query registration cost from blockchain.")
194
+ print_info(
195
+ "This may indicate a connection issue or the storage values are not set yet."
196
+ )
197
+ else:
198
+ display_registration_cost(current_cost, block_number, current_epoch)
199
+
200
+ except RuntimeError as e:
201
+ # Handle connection errors specifically
202
+ from ...ui.display import print_error
203
+
204
+ error_str = str(e).lower()
205
+ if "connect" in error_str or "blockchain" in error_str:
206
+ print_error(f"Blockchain Connection Error: {str(e)}")
207
+ else:
208
+ print_error(f"Error: {str(e)}")
209
+ except Exception as e:
210
+ handle_and_display_error(e)
211
+
212
+
213
+ def get_block_handler(block_number: int):
214
+ """Handle block lookup by block number."""
215
+ try:
216
+ if block_number < 0:
217
+ raise HTCLIError("Invalid block number: must be a positive integer")
218
+
219
+ client = get_client()
220
+
221
+ with HTCLILoadingContext("Fetching block information..."):
222
+ result = client.rpc.chain.get_block_info(block_number)
223
+
224
+ if not result.get("success"):
225
+ raise RuntimeError(result.get("message", "Failed to fetch block info"))
226
+
227
+ display_block_info(result.get("data", {}))
228
+
229
+ except Exception as e:
230
+ handle_and_display_error(e)
231
+
232
+
233
+ def get_transaction_handler(tx_hash: str):
234
+ """Handle transaction lookup by hash."""
235
+ try:
236
+ if not re.fullmatch(r"0x[a-fA-F0-9]{64}", tx_hash):
237
+ raise HTCLIError("Invalid transaction hash format")
238
+
239
+ raise HTCLIError("Transaction lookup is not implemented yet")
240
+ except Exception as e:
241
+ handle_and_display_error(e)
242
+
243
+
244
+ def get_account_handler(address: str):
245
+ """Handle account lookup by address."""
246
+ try:
247
+ if not re.fullmatch(
248
+ r"(0x[a-fA-F0-9]{40}|[1-9A-HJ-NP-Za-km-z]{32,64})", address
249
+ ):
250
+ raise AddressValidationError("Invalid address format")
251
+
252
+ client = get_client()
253
+
254
+ with HTCLILoadingContext("Fetching account information..."):
255
+ balance = client.rpc.chain.get_account_balance(address)
256
+
257
+ display_account_info(address, balance)
258
+
259
+ except Exception as e:
260
+ handle_and_display_error(e)