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.
- htcli-1.1.0.dist-info/METADATA +509 -0
- htcli-1.1.0.dist-info/RECORD +140 -0
- htcli-1.1.0.dist-info/WHEEL +4 -0
- htcli-1.1.0.dist-info/entry_points.txt +2 -0
- htcli-1.1.0.dist-info/licenses/LICENSE +21 -0
- src/__init__.py +0 -0
- src/htcli/__init__.py +5 -0
- src/htcli/client/__init__.py +338 -0
- src/htcli/client/extrinsics/__init__.py +26 -0
- src/htcli/client/extrinsics/base.py +487 -0
- src/htcli/client/extrinsics/consensus.py +79 -0
- src/htcli/client/extrinsics/governance.py +714 -0
- src/htcli/client/extrinsics/identity.py +490 -0
- src/htcli/client/extrinsics/node.py +1054 -0
- src/htcli/client/extrinsics/overwatch.py +401 -0
- src/htcli/client/extrinsics/staking.py +1504 -0
- src/htcli/client/extrinsics/subnet.py +2218 -0
- src/htcli/client/extrinsics/validator.py +203 -0
- src/htcli/client/extrinsics/wallet.py +323 -0
- src/htcli/client/offchain/__init__.py +10 -0
- src/htcli/client/offchain/backup.py +385 -0
- src/htcli/client/offchain/config.py +541 -0
- src/htcli/client/offchain/wallet.py +839 -0
- src/htcli/client/rpc/__init__.py +20 -0
- src/htcli/client/rpc/chain.py +568 -0
- src/htcli/client/rpc/node.py +783 -0
- src/htcli/client/rpc/overwatch.py +680 -0
- src/htcli/client/rpc/staking.py +216 -0
- src/htcli/client/rpc/subnet.py +2104 -0
- src/htcli/client/rpc/wallet.py +912 -0
- src/htcli/commands/__init__.py +31 -0
- src/htcli/commands/chain/__init__.py +66 -0
- src/htcli/commands/chain/display.py +204 -0
- src/htcli/commands/chain/handlers.py +260 -0
- src/htcli/commands/config/__init__.py +158 -0
- src/htcli/commands/config/display.py +353 -0
- src/htcli/commands/config/handlers.py +347 -0
- src/htcli/commands/config/prompts.py +357 -0
- src/htcli/commands/consensus/__init__.py +61 -0
- src/htcli/commands/consensus/handlers.py +100 -0
- src/htcli/commands/governance/__init__.py +49 -0
- src/htcli/commands/governance/handlers.py +81 -0
- src/htcli/commands/node/__init__.py +304 -0
- src/htcli/commands/node/display.py +749 -0
- src/htcli/commands/node/error_handling.py +470 -0
- src/htcli/commands/node/handlers.py +844 -0
- src/htcli/commands/node/prompts.py +346 -0
- src/htcli/commands/overwatch/__init__.py +219 -0
- src/htcli/commands/overwatch/display.py +396 -0
- src/htcli/commands/overwatch/error_handling.py +276 -0
- src/htcli/commands/overwatch/handlers.py +443 -0
- src/htcli/commands/overwatch/prompts.py +359 -0
- src/htcli/commands/stake/__init__.py +736 -0
- src/htcli/commands/stake/display.py +1103 -0
- src/htcli/commands/stake/error_handling.py +425 -0
- src/htcli/commands/stake/handlers.py +1902 -0
- src/htcli/commands/stake/prompts.py +1080 -0
- src/htcli/commands/subnet/__init__.py +639 -0
- src/htcli/commands/subnet/display.py +801 -0
- src/htcli/commands/subnet/error_handling.py +524 -0
- src/htcli/commands/subnet/handlers.py +2855 -0
- src/htcli/commands/subnet/prompts.py +1225 -0
- src/htcli/commands/validator/__init__.py +192 -0
- src/htcli/commands/validator/display.py +54 -0
- src/htcli/commands/validator/handlers.py +340 -0
- src/htcli/commands/wallet/__init__.py +546 -0
- src/htcli/commands/wallet/display.py +806 -0
- src/htcli/commands/wallet/error_handling.py +210 -0
- src/htcli/commands/wallet/handlers.py +3040 -0
- src/htcli/commands/wallet/prompts.py +1518 -0
- src/htcli/config.py +184 -0
- src/htcli/dependencies.py +186 -0
- src/htcli/errors/__init__.py +63 -0
- src/htcli/errors/base.py +141 -0
- src/htcli/errors/display.py +20 -0
- src/htcli/errors/handlers.py +710 -0
- src/htcli/main.py +343 -0
- src/htcli/models/__init__.py +21 -0
- src/htcli/models/enums/enum_types.py +35 -0
- src/htcli/models/errors.py +103 -0
- src/htcli/models/requests/__init__.py +197 -0
- src/htcli/models/requests/config.py +70 -0
- src/htcli/models/requests/consensus.py +19 -0
- src/htcli/models/requests/governance.py +38 -0
- src/htcli/models/requests/identity.py +51 -0
- src/htcli/models/requests/key.py +22 -0
- src/htcli/models/requests/node.py +91 -0
- src/htcli/models/requests/overwatch.py +64 -0
- src/htcli/models/requests/staking.py +580 -0
- src/htcli/models/requests/subnet.py +195 -0
- src/htcli/models/requests/validator.py +139 -0
- src/htcli/models/requests/wallet.py +118 -0
- src/htcli/models/responses/__init__.py +147 -0
- src/htcli/models/responses/base.py +18 -0
- src/htcli/models/responses/chain.py +39 -0
- src/htcli/models/responses/config.py +58 -0
- src/htcli/models/responses/identity.py +102 -0
- src/htcli/models/responses/overwatch.py +51 -0
- src/htcli/models/responses/staking.py +502 -0
- src/htcli/models/responses/subnet.py +856 -0
- src/htcli/models/responses/wallet.py +185 -0
- src/htcli/ui/__init__.py +87 -0
- src/htcli/ui/colors.py +309 -0
- src/htcli/ui/components/__init__.py +60 -0
- src/htcli/ui/components/panels.py +174 -0
- src/htcli/ui/components/progress.py +166 -0
- src/htcli/ui/components/spinners.py +92 -0
- src/htcli/ui/components/tables.py +809 -0
- src/htcli/ui/components/trees.py +721 -0
- src/htcli/ui/display.py +336 -0
- src/htcli/ui/prompts.py +870 -0
- src/htcli/utils/__init__.py +76 -0
- src/htcli/utils/blockchain/__init__.py +75 -0
- src/htcli/utils/blockchain/formatting.py +368 -0
- src/htcli/utils/blockchain/patches.py +286 -0
- src/htcli/utils/blockchain/peer_id.py +186 -0
- src/htcli/utils/blockchain/staking.py +448 -0
- src/htcli/utils/blockchain/type_registry.py +1373 -0
- src/htcli/utils/blockchain/validation.py +179 -0
- src/htcli/utils/cache.py +613 -0
- src/htcli/utils/constants.py +38 -0
- src/htcli/utils/legacy/__init__.py +12 -0
- src/htcli/utils/legacy/colors.py +311 -0
- src/htcli/utils/legacy/crypto.py +1176 -0
- src/htcli/utils/legacy/formatting.py +452 -0
- src/htcli/utils/legacy/interactive.py +306 -0
- src/htcli/utils/legacy/subnet_manifest.py +265 -0
- src/htcli/utils/legacy/validation.py +488 -0
- src/htcli/utils/logging.py +183 -0
- src/htcli/utils/network/__init__.py +20 -0
- src/htcli/utils/network/subnet.py +344 -0
- src/htcli/utils/prompts.py +27 -0
- src/htcli/utils/scale_codec.py +155 -0
- src/htcli/utils/validation/__init__.py +57 -0
- src/htcli/utils/validation/prompt_validators.py +267 -0
- src/htcli/utils/wallet/__init__.py +65 -0
- src/htcli/utils/wallet/auth.py +151 -0
- src/htcli/utils/wallet/core.py +1069 -0
- src/htcli/utils/wallet/crypto.py +1615 -0
- 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)
|