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,452 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Output formatting utility functions for the Hypertensor CLI.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from rich.console import Console
|
|
9
|
+
from rich.panel import Panel
|
|
10
|
+
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
11
|
+
from rich.table import Table
|
|
12
|
+
|
|
13
|
+
# TENSOR token precision constant
|
|
14
|
+
TENSOR_DECIMALS = 18
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def tensor_to_smallest_unit(tensor_amount: float) -> int:
|
|
18
|
+
"""Convert TENSOR amount to smallest unit (18 decimals)."""
|
|
19
|
+
return int(tensor_amount * (10**TENSOR_DECIMALS))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def smallest_unit_to_tensor(smallest_unit: int) -> float:
|
|
23
|
+
"""Convert smallest unit to TENSOR amount (18 decimals)."""
|
|
24
|
+
return smallest_unit / (10**TENSOR_DECIMALS)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def validate_tensor_amount(amount: float) -> bool:
|
|
28
|
+
"""Validate TENSOR amount has proper precision."""
|
|
29
|
+
# Check if amount has more than 18 decimal places
|
|
30
|
+
str_amount = f"{amount:.18f}"
|
|
31
|
+
if len(str_amount.split(".")[-1]) > TENSOR_DECIMALS:
|
|
32
|
+
return False
|
|
33
|
+
return True
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
console = Console()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def format_table(headers: list[str], rows: list[list[Any]], title: str = "") -> Table:
|
|
40
|
+
"""Create a formatted table."""
|
|
41
|
+
table = Table(title=title)
|
|
42
|
+
|
|
43
|
+
# Add columns
|
|
44
|
+
for header in headers:
|
|
45
|
+
table.add_column(header, style="cyan")
|
|
46
|
+
|
|
47
|
+
# Add rows
|
|
48
|
+
for row in rows:
|
|
49
|
+
table.add_row(*[str(cell) for cell in row])
|
|
50
|
+
|
|
51
|
+
return table
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def format_json(data: Any) -> str:
|
|
55
|
+
"""Format data as JSON."""
|
|
56
|
+
return json.dumps(data, indent=2, default=str)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def format_csv(headers: list[str], rows: list[list[Any]]) -> str:
|
|
60
|
+
"""Format data as CSV."""
|
|
61
|
+
import csv
|
|
62
|
+
from io import StringIO
|
|
63
|
+
|
|
64
|
+
output = StringIO()
|
|
65
|
+
writer = csv.writer(output)
|
|
66
|
+
writer.writerow(headers)
|
|
67
|
+
writer.writerows(rows)
|
|
68
|
+
return output.getvalue()
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def print_success(message: str):
|
|
72
|
+
"""Print a success message."""
|
|
73
|
+
console.print(f"✅ {message}", style="green")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def print_error(message: str):
|
|
77
|
+
"""Print an error message."""
|
|
78
|
+
console.print(f"❌ {message}", style="red")
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def print_warning(message: str):
|
|
82
|
+
"""Print a warning message."""
|
|
83
|
+
console.print(f"⚠️ {message}", style="yellow")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def print_info(message: str):
|
|
87
|
+
"""Print an info message."""
|
|
88
|
+
console.print(f"ℹ️ {message}", style="blue")
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def format_balance(amount: int, decimals: int = TENSOR_DECIMALS) -> str:
|
|
92
|
+
"""Format balance amount with proper decimal places for TENSOR token (18 decimals)."""
|
|
93
|
+
if amount == 0:
|
|
94
|
+
return "0 TENSOR"
|
|
95
|
+
|
|
96
|
+
# Convert from smallest unit (18 decimals for TENSOR)
|
|
97
|
+
balance = amount / (10**decimals)
|
|
98
|
+
return f"{balance:.18f} TENSOR".rstrip("0").rstrip(".")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def format_address(address: str, max_length: int = 20) -> str:
|
|
102
|
+
"""Format address with truncation."""
|
|
103
|
+
if len(address) <= max_length:
|
|
104
|
+
return address
|
|
105
|
+
return f"{address[: max_length // 2]}...{address[-max_length // 2 :]}"
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def format_block_number(block_number: int) -> str:
|
|
109
|
+
"""Format block number."""
|
|
110
|
+
return f"#{block_number:,}"
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def format_transaction_hash(tx_hash: str) -> str:
|
|
114
|
+
"""Format transaction hash."""
|
|
115
|
+
if len(tx_hash) <= 16:
|
|
116
|
+
return tx_hash
|
|
117
|
+
return f"{tx_hash[:8]}...{tx_hash[-8:]}"
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def create_subnet_table(subnets: list[dict[str, Any]]) -> Table:
|
|
121
|
+
"""Create a table for subnet information."""
|
|
122
|
+
table = Table(title="Subnets")
|
|
123
|
+
table.add_column("ID", style="cyan")
|
|
124
|
+
table.add_column("Path", style="white")
|
|
125
|
+
table.add_column("Status", style="green")
|
|
126
|
+
table.add_column("Nodes", style="yellow")
|
|
127
|
+
table.add_column("Stake", style="magenta")
|
|
128
|
+
|
|
129
|
+
for subnet in subnets:
|
|
130
|
+
status = "Active" if subnet.get("activated", 0) > 0 else "Inactive"
|
|
131
|
+
stake = format_balance(subnet.get("total_stake", 0))
|
|
132
|
+
|
|
133
|
+
table.add_row(
|
|
134
|
+
str(subnet.get("subnet_id", "N/A")),
|
|
135
|
+
subnet.get("path", "N/A"),
|
|
136
|
+
status,
|
|
137
|
+
str(subnet.get("node_count", 0)),
|
|
138
|
+
stake,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
return table
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def create_node_table(nodes: list[dict[str, Any]], subnet_id: int) -> Table:
|
|
145
|
+
"""Create a table for node information."""
|
|
146
|
+
table = Table(title=f"Nodes in Subnet {subnet_id}")
|
|
147
|
+
table.add_column("Node ID", style="cyan")
|
|
148
|
+
table.add_column("Peer ID", style="white")
|
|
149
|
+
table.add_column("Hotkey", style="green")
|
|
150
|
+
table.add_column("Stake", style="yellow")
|
|
151
|
+
|
|
152
|
+
for node in nodes:
|
|
153
|
+
stake = format_balance(node.get("stake", 0))
|
|
154
|
+
|
|
155
|
+
table.add_row(
|
|
156
|
+
str(node.get("node_id", "N/A")),
|
|
157
|
+
format_address(node.get("peer_id", "N/A")),
|
|
158
|
+
format_address(node.get("hotkey", "N/A")),
|
|
159
|
+
stake,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
return table
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def create_stake_info_panel(
|
|
166
|
+
stake_data: dict[str, Any], subnet_id: int, hotkey: str
|
|
167
|
+
) -> Panel:
|
|
168
|
+
"""Create a panel for stake information."""
|
|
169
|
+
stake_amount = format_balance(stake_data.get("stake", 0))
|
|
170
|
+
unbonding = format_balance(stake_data.get("unbonding", 0))
|
|
171
|
+
|
|
172
|
+
info_text = f"""
|
|
173
|
+
Subnet ID: {subnet_id}
|
|
174
|
+
Hotkey: {format_address(hotkey)}
|
|
175
|
+
Stake Amount: {stake_amount}
|
|
176
|
+
Unbonding: {unbonding}
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
return Panel(info_text, title="Stake Information")
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def create_network_stats_panel(stats: dict[str, Any]) -> Panel:
|
|
183
|
+
"""Create a panel for network statistics."""
|
|
184
|
+
total_stake = format_balance(stats.get("total_stake", 0))
|
|
185
|
+
|
|
186
|
+
info_text = f"""
|
|
187
|
+
Total Subnets: {stats.get("total_subnets", 0)}
|
|
188
|
+
Active Subnets: {stats.get("active_subnets", 0)}
|
|
189
|
+
Total Nodes: {stats.get("total_nodes", 0)}
|
|
190
|
+
Total Stake: {total_stake}
|
|
191
|
+
Current Epoch: {stats.get("current_epoch", 0)}
|
|
192
|
+
"""
|
|
193
|
+
|
|
194
|
+
return Panel(info_text, title="Network Statistics")
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def create_account_info_panel(account_data: dict[str, Any], address: str) -> Panel:
|
|
198
|
+
"""Create a panel for account information."""
|
|
199
|
+
balance = format_balance(account_data.get("balance", 0))
|
|
200
|
+
|
|
201
|
+
info_text = f"""
|
|
202
|
+
Address: {format_address(address)}
|
|
203
|
+
Balance: {balance}
|
|
204
|
+
Nonce: {account_data.get("nonce", 0)}
|
|
205
|
+
"""
|
|
206
|
+
|
|
207
|
+
return Panel(info_text, title="Account Information")
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def create_epoch_info_panel(epoch_data: dict[str, Any]) -> Panel:
|
|
211
|
+
"""Create a panel for epoch information."""
|
|
212
|
+
info_text = f"""
|
|
213
|
+
Epoch: {epoch_data.get("epoch", "N/A")}
|
|
214
|
+
Start Block: {epoch_data.get("start_block", "N/A")}
|
|
215
|
+
End Block: {epoch_data.get("end_block", "N/A")}
|
|
216
|
+
Blocks Remaining: {epoch_data.get("blocks_remaining", "N/A")}
|
|
217
|
+
Epoch Duration: {epoch_data.get("epoch_duration", "N/A")} blocks
|
|
218
|
+
Timestamp: {epoch_data.get("timestamp", "N/A")}
|
|
219
|
+
"""
|
|
220
|
+
return Panel(info_text, title="Epoch Information")
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def format_subnet_list(subnets: list[dict[str, Any]]):
|
|
224
|
+
"""Format and display subnet list."""
|
|
225
|
+
if not subnets:
|
|
226
|
+
console.print("No subnets found.")
|
|
227
|
+
return
|
|
228
|
+
|
|
229
|
+
table = Table(title="Subnets")
|
|
230
|
+
table.add_column("ID", style="cyan")
|
|
231
|
+
table.add_column("Path", style="green")
|
|
232
|
+
table.add_column("Status", style="yellow")
|
|
233
|
+
table.add_column("Nodes", style="blue")
|
|
234
|
+
table.add_column("Total Stake", style="magenta")
|
|
235
|
+
|
|
236
|
+
for subnet in subnets:
|
|
237
|
+
status = "Active" if subnet.get("activated", 0) > 0 else "Inactive"
|
|
238
|
+
table.add_row(
|
|
239
|
+
str(subnet.get("subnet_id", "N/A")),
|
|
240
|
+
subnet.get("path", "N/A"),
|
|
241
|
+
status,
|
|
242
|
+
str(subnet.get("node_count", 0)),
|
|
243
|
+
format_balance(subnet.get("total_stake", 0)),
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
console.print(table)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def format_subnet_info(subnet_info: dict[str, Any]):
|
|
250
|
+
"""Format and display comprehensive subnet information."""
|
|
251
|
+
if not subnet_info:
|
|
252
|
+
console.print("Subnet information not available.")
|
|
253
|
+
return
|
|
254
|
+
|
|
255
|
+
# Check data completeness
|
|
256
|
+
data_completeness = subnet_info.get("data_completeness", "unknown")
|
|
257
|
+
is_partial = data_completeness == "partial"
|
|
258
|
+
|
|
259
|
+
# Basic Information Section
|
|
260
|
+
basic_info = f"""[bold cyan]Basic Information:[/bold cyan]
|
|
261
|
+
Subnet ID: {subnet_info.get("subnet_id", "N/A")}
|
|
262
|
+
Name: {subnet_info.get("name", "N/A")}
|
|
263
|
+
Repository: {subnet_info.get("repo", "N/A") or "Not specified"}
|
|
264
|
+
Description: {subnet_info.get("description", "N/A") or "Not specified"}
|
|
265
|
+
State: {subnet_info.get("state", "N/A")}
|
|
266
|
+
Owner: {subnet_info.get("owner", "N/A") or "Not assigned"}
|
|
267
|
+
Start Epoch: {subnet_info.get("start_epoch", "N/A") if subnet_info.get("start_epoch", 0) != 4294967295 else "Subnet not active yet"}
|
|
268
|
+
Registration Epoch: {subnet_info.get("registration_epoch", "N/A")}"""
|
|
269
|
+
|
|
270
|
+
# Node Information Section
|
|
271
|
+
node_info = f"""[bold green]Node Information:[/bold green]
|
|
272
|
+
Total Nodes: {subnet_info.get("total_nodes", 0)}
|
|
273
|
+
Active Nodes: {subnet_info.get("total_active_nodes", 0)}
|
|
274
|
+
Max Registered Nodes: {subnet_info.get("max_registered_nodes", 0)}
|
|
275
|
+
Node Registration Epochs: {subnet_info.get("node_registration_epochs", 0)}
|
|
276
|
+
Node Activation Interval: {subnet_info.get("node_activation_interval", 0)}
|
|
277
|
+
Churn Limit: {subnet_info.get("churn_limit", 0)}"""
|
|
278
|
+
|
|
279
|
+
# Staking Information Section
|
|
280
|
+
min_stake = subnet_info.get("min_stake", 0)
|
|
281
|
+
max_stake = subnet_info.get("max_stake", 0)
|
|
282
|
+
delegate_stake_balance = subnet_info.get("total_delegate_stake_balance", 0)
|
|
283
|
+
delegate_stake_shares = subnet_info.get("total_delegate_stake_shares", 0)
|
|
284
|
+
|
|
285
|
+
staking_info = f"""[bold yellow]Staking Information:[/bold yellow]
|
|
286
|
+
Minimum Stake: {format_balance(min_stake)}
|
|
287
|
+
Maximum Stake: {format_balance(max_stake)}
|
|
288
|
+
Delegate Stake Percentage: {subnet_info.get("delegate_stake_percentage", 0) / 1000000:.1f}%
|
|
289
|
+
Total Delegate Stake Balance: {format_balance(delegate_stake_balance)}
|
|
290
|
+
Total Delegate Stake Shares: {delegate_stake_shares}"""
|
|
291
|
+
|
|
292
|
+
# System Information Section
|
|
293
|
+
system_info = f"""[bold red]System Information:[/bold red]
|
|
294
|
+
Reputation: {subnet_info.get("reputation", 0)}
|
|
295
|
+
Min Node Reputation: {subnet_info.get("min_subnet_node_reputation", 0)}
|
|
296
|
+
Data Completeness: {data_completeness.title()}"""
|
|
297
|
+
|
|
298
|
+
# Combine all sections
|
|
299
|
+
full_info = f"{basic_info}\n\n{node_info}\n\n{staking_info}\n\n{system_info}"
|
|
300
|
+
|
|
301
|
+
# Additional info section
|
|
302
|
+
misc = subnet_info.get("misc", "")
|
|
303
|
+
if misc:
|
|
304
|
+
full_info += f"\n\n[bold blue]Additional Information:[/bold blue]\n{misc}"
|
|
305
|
+
|
|
306
|
+
# Add note for partial data
|
|
307
|
+
if is_partial:
|
|
308
|
+
full_info += "\n\n[bold yellow]⚠️ Note:[/bold yellow] This subnet exists but has partial registration data.\nSome fields may show default values or be unavailable."
|
|
309
|
+
|
|
310
|
+
# Set title and border based on data completeness
|
|
311
|
+
title = "📊 Subnet Information"
|
|
312
|
+
if is_partial:
|
|
313
|
+
title += " (Partial Data)"
|
|
314
|
+
border_style = "yellow" if is_partial else "cyan"
|
|
315
|
+
|
|
316
|
+
panel = Panel(full_info, title=title, border_style=border_style)
|
|
317
|
+
console.print(panel)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def format_node_list(nodes: list[dict[str, Any]]):
|
|
321
|
+
"""Format and display node list."""
|
|
322
|
+
if not nodes:
|
|
323
|
+
console.print("No nodes found.")
|
|
324
|
+
return
|
|
325
|
+
|
|
326
|
+
table = Table(title="Subnet Nodes")
|
|
327
|
+
table.add_column("Node ID", style="cyan")
|
|
328
|
+
table.add_column("Peer ID", style="green")
|
|
329
|
+
table.add_column("Hotkey", style="yellow")
|
|
330
|
+
table.add_column("Stake", style="blue")
|
|
331
|
+
table.add_column("Status", style="magenta")
|
|
332
|
+
|
|
333
|
+
for node in nodes:
|
|
334
|
+
table.add_row(
|
|
335
|
+
str(node.get("node_id", "N/A")),
|
|
336
|
+
format_address(node.get("peer_id", "N/A")),
|
|
337
|
+
format_address(node.get("hotkey", "N/A")),
|
|
338
|
+
format_balance(node.get("stake", 0)),
|
|
339
|
+
node.get("status", "N/A"),
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
console.print(table)
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
def format_stake_info(stake_data: dict[str, Any]):
|
|
346
|
+
"""Format and display stake information."""
|
|
347
|
+
if not stake_data:
|
|
348
|
+
console.print("Stake information not available.")
|
|
349
|
+
return
|
|
350
|
+
|
|
351
|
+
info_text = f"""
|
|
352
|
+
Account: {format_address(stake_data.get("account", "N/A"))}
|
|
353
|
+
Subnet ID: {stake_data.get("subnet_id", "N/A")}
|
|
354
|
+
Current Stake: {format_balance(stake_data.get("stake", 0))}
|
|
355
|
+
Unbonding: {format_balance(stake_data.get("unbonding", 0))}
|
|
356
|
+
Total Stake: {format_balance(stake_data.get("total_stake", 0))}
|
|
357
|
+
"""
|
|
358
|
+
|
|
359
|
+
panel = Panel(info_text, title="Stake Information")
|
|
360
|
+
console.print(panel)
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def format_network_stats(stats: dict[str, Any]):
|
|
364
|
+
"""Format and display network statistics."""
|
|
365
|
+
if not stats:
|
|
366
|
+
console.print("Network statistics not available.")
|
|
367
|
+
return
|
|
368
|
+
|
|
369
|
+
info_text = f"""
|
|
370
|
+
Total Subnets: {stats.get("total_subnets", 0)}
|
|
371
|
+
Active Subnets: {stats.get("active_subnets", 0)}
|
|
372
|
+
Total Nodes: {stats.get("total_nodes", 0)}
|
|
373
|
+
Total Stake: {format_balance(stats.get("total_stake", 0))}
|
|
374
|
+
Current Epoch: {stats.get("current_epoch", 0)}
|
|
375
|
+
Total Validations: {stats.get("total_validations", 0)}
|
|
376
|
+
Total Attestations: {stats.get("total_attestations", 0)}
|
|
377
|
+
Network Uptime: {stats.get("network_uptime", 0)}%
|
|
378
|
+
Average Block Time: {stats.get("average_block_time", 0)}s
|
|
379
|
+
"""
|
|
380
|
+
|
|
381
|
+
panel = Panel(info_text, title="Network Statistics")
|
|
382
|
+
console.print(panel)
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def format_account_info(account_data: dict[str, Any]):
|
|
386
|
+
"""Format and display account information."""
|
|
387
|
+
if not account_data:
|
|
388
|
+
console.print("Account information not available.")
|
|
389
|
+
return
|
|
390
|
+
|
|
391
|
+
info_text = f"""
|
|
392
|
+
Account: {format_address(account_data.get("account", "N/A"))}
|
|
393
|
+
Balance: {format_balance(account_data.get("balance", 0))}
|
|
394
|
+
Nonce: {account_data.get("nonce", 0)}
|
|
395
|
+
Reserved: {format_balance(account_data.get("reserved", 0))}
|
|
396
|
+
Misc Frozen: {format_balance(account_data.get("misc_frozen", 0))}
|
|
397
|
+
Fee Frozen: {format_balance(account_data.get("fee_frozen", 0))}
|
|
398
|
+
"""
|
|
399
|
+
|
|
400
|
+
panel = Panel(info_text, title="Account Information")
|
|
401
|
+
console.print(panel)
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def format_epoch_info(epoch_data: dict[str, Any]):
|
|
405
|
+
"""Format and display epoch information."""
|
|
406
|
+
if not epoch_data:
|
|
407
|
+
console.print("Epoch information not available.")
|
|
408
|
+
return
|
|
409
|
+
|
|
410
|
+
info_text = f"""
|
|
411
|
+
Epoch: {epoch_data.get("epoch", "N/A")}
|
|
412
|
+
Start Block: {epoch_data.get("start_block", "N/A")}
|
|
413
|
+
End Block: {epoch_data.get("end_block", "N/A")}
|
|
414
|
+
Blocks Remaining: {epoch_data.get("blocks_remaining", "N/A")}
|
|
415
|
+
Epoch Duration: {epoch_data.get("epoch_duration", "N/A")} blocks
|
|
416
|
+
Timestamp: {epoch_data.get("timestamp", "N/A")}
|
|
417
|
+
"""
|
|
418
|
+
|
|
419
|
+
panel = Panel(info_text, title="Epoch Information")
|
|
420
|
+
console.print(panel)
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def show_progress(description: str):
|
|
424
|
+
"""Show a progress spinner."""
|
|
425
|
+
with Progress(
|
|
426
|
+
SpinnerColumn(),
|
|
427
|
+
TextColumn("[progress.description]{task.description}"),
|
|
428
|
+
console=console,
|
|
429
|
+
) as progress:
|
|
430
|
+
task = progress.add_task(description, total=None)
|
|
431
|
+
return progress, task
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
def output_data(data: Any, format_type: str = "table"):
|
|
435
|
+
"""Output data in the specified format."""
|
|
436
|
+
if format_type == "json":
|
|
437
|
+
console.print(format_json(data))
|
|
438
|
+
elif format_type == "csv":
|
|
439
|
+
console.print(format_csv(data))
|
|
440
|
+
else:
|
|
441
|
+
# Default to table format
|
|
442
|
+
if isinstance(data, list) and data:
|
|
443
|
+
# Try to create a table from list of dictionaries
|
|
444
|
+
if isinstance(data[0], dict):
|
|
445
|
+
headers = list(data[0].keys())
|
|
446
|
+
rows = [[row.get(header, "") for header in headers] for row in data]
|
|
447
|
+
table = format_table(headers, rows)
|
|
448
|
+
console.print(table)
|
|
449
|
+
else:
|
|
450
|
+
console.print(data)
|
|
451
|
+
else:
|
|
452
|
+
console.print(data)
|