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,809 @@
|
|
|
1
|
+
"""
|
|
2
|
+
HTCLI table components.
|
|
3
|
+
Provides enhanced tables for data display.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Any, Optional
|
|
7
|
+
|
|
8
|
+
from rich import box
|
|
9
|
+
from rich.console import Console
|
|
10
|
+
from rich.table import Table
|
|
11
|
+
from src.htcli.utils.wallet.crypto import format_address_display
|
|
12
|
+
|
|
13
|
+
from ..colors import HTCLIColors
|
|
14
|
+
from ..display import get_console
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class HTCLITable:
|
|
18
|
+
"""Enhanced table for displaying HTCLI data."""
|
|
19
|
+
|
|
20
|
+
def __init__(
|
|
21
|
+
self,
|
|
22
|
+
title: str = None,
|
|
23
|
+
caption: str = None,
|
|
24
|
+
show_header: bool = True,
|
|
25
|
+
show_lines: bool = False,
|
|
26
|
+
show_edge: bool = True,
|
|
27
|
+
show_footer: bool = False,
|
|
28
|
+
border_style: str = "htcli.table.border",
|
|
29
|
+
header_style: str = "htcli.table.header",
|
|
30
|
+
footer_style: str = "table.footer",
|
|
31
|
+
row_styles: list[str] = None,
|
|
32
|
+
box_style=box.HORIZONTALS,
|
|
33
|
+
expand: bool = True,
|
|
34
|
+
padding: tuple = (0, 0),
|
|
35
|
+
):
|
|
36
|
+
if row_styles is None:
|
|
37
|
+
row_styles = ["dim", ""]
|
|
38
|
+
self.table = Table(
|
|
39
|
+
title=title,
|
|
40
|
+
caption=caption,
|
|
41
|
+
show_header=show_header,
|
|
42
|
+
show_lines=show_lines,
|
|
43
|
+
show_edge=show_edge,
|
|
44
|
+
show_footer=show_footer,
|
|
45
|
+
border_style=border_style,
|
|
46
|
+
header_style=header_style,
|
|
47
|
+
footer_style=footer_style,
|
|
48
|
+
row_styles=row_styles or ["dim", ""],
|
|
49
|
+
box=box_style,
|
|
50
|
+
expand=expand,
|
|
51
|
+
padding=padding,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
@classmethod
|
|
55
|
+
def minimal_header_table(
|
|
56
|
+
cls,
|
|
57
|
+
title: str = None,
|
|
58
|
+
caption: str = None,
|
|
59
|
+
header_style: str = "bold blue",
|
|
60
|
+
):
|
|
61
|
+
"""
|
|
62
|
+
Create a table with minimal styling - only header underlines and bottom border.
|
|
63
|
+
No side borders or column separators.
|
|
64
|
+
"""
|
|
65
|
+
return cls(
|
|
66
|
+
title=title,
|
|
67
|
+
caption=caption,
|
|
68
|
+
show_header=True,
|
|
69
|
+
show_lines=False,
|
|
70
|
+
show_edge=False,
|
|
71
|
+
show_footer=False,
|
|
72
|
+
border_style="dim",
|
|
73
|
+
header_style=header_style,
|
|
74
|
+
box_style=box.HORIZONTALS,
|
|
75
|
+
expand=True,
|
|
76
|
+
padding=(1, 1),
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
def add_column(
|
|
80
|
+
self,
|
|
81
|
+
header: str,
|
|
82
|
+
footer: str = "",
|
|
83
|
+
style: str = None,
|
|
84
|
+
footer_style: str = None,
|
|
85
|
+
justify: str = "left",
|
|
86
|
+
min_width: int = None,
|
|
87
|
+
max_width: int = None,
|
|
88
|
+
width: int = None,
|
|
89
|
+
no_wrap: bool = False,
|
|
90
|
+
):
|
|
91
|
+
"""Add a column to the table."""
|
|
92
|
+
self.table.add_column(
|
|
93
|
+
header,
|
|
94
|
+
footer=footer,
|
|
95
|
+
style=style,
|
|
96
|
+
footer_style=footer_style,
|
|
97
|
+
justify=justify,
|
|
98
|
+
min_width=min_width,
|
|
99
|
+
max_width=max_width,
|
|
100
|
+
width=width,
|
|
101
|
+
no_wrap=no_wrap,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
def add_row(self, *values):
|
|
105
|
+
"""Add a row to the table."""
|
|
106
|
+
self.table.add_row(*[str(v) for v in values])
|
|
107
|
+
|
|
108
|
+
def add_blockchain_row(self, data: dict[str, Any]):
|
|
109
|
+
"""Add a row with blockchain-specific formatting."""
|
|
110
|
+
formatted_values = []
|
|
111
|
+
for key, value in data.items():
|
|
112
|
+
if key.lower() in ["address", "account", "validator"]:
|
|
113
|
+
formatted_values.append(f"[htcli.address]{value}[/]")
|
|
114
|
+
elif key.lower() in ["amount", "balance", "stake"]:
|
|
115
|
+
formatted_values.append(f"[htcli.amount]{value}[/]")
|
|
116
|
+
elif key.lower() in ["status", "state"]:
|
|
117
|
+
color = HTCLIColors.get_status_color(str(value))
|
|
118
|
+
formatted_values.append(f"[{color}]{value}[/]")
|
|
119
|
+
elif key.lower() in ["hash", "tx_hash", "block_hash"]:
|
|
120
|
+
formatted_values.append(f"[htcli.hash]{value}[/]")
|
|
121
|
+
else:
|
|
122
|
+
formatted_values.append(str(value))
|
|
123
|
+
self.table.add_row(*formatted_values)
|
|
124
|
+
|
|
125
|
+
def set_column_footers(self, footers: list[str]):
|
|
126
|
+
"""Set footers for all columns."""
|
|
127
|
+
if len(footers) != len(self.table.columns):
|
|
128
|
+
raise ValueError(
|
|
129
|
+
f"Number of footers ({len(footers)}) must match number of columns ({len(self.table.columns)})"
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
for i, footer in enumerate(footers):
|
|
133
|
+
self.table.columns[i].footer = footer
|
|
134
|
+
|
|
135
|
+
def render(self, console: Console = None, with_panel: bool = False):
|
|
136
|
+
"""
|
|
137
|
+
Render the table wrapped inside a full Layout.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
console (Console, optional): Rich console to print to.
|
|
141
|
+
with_panel (bool): If True, wraps the table in a Panel to fill vertical space.
|
|
142
|
+
"""
|
|
143
|
+
console = console or get_console().console
|
|
144
|
+
|
|
145
|
+
renderable = self.table
|
|
146
|
+
|
|
147
|
+
console.print(renderable)
|
|
148
|
+
|
|
149
|
+
def get_renderable(self):
|
|
150
|
+
"""Get the table as a renderable object."""
|
|
151
|
+
return self.table
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
# Specialized table factory functions
|
|
155
|
+
def create_subnet_table(subnets: list[dict]) -> HTCLITable:
|
|
156
|
+
"""Create a standardized subnet table with bright, bold colors for displaying subnet information."""
|
|
157
|
+
# Table styling:
|
|
158
|
+
# - show_edge=False, header_style="bold white", border_style="bright_black"
|
|
159
|
+
# - style="bold", title_justify="center", show_lines=False, pad_edge=True
|
|
160
|
+
# Color palette values:
|
|
161
|
+
# HEADER = "#4196D6" (Light Blue)
|
|
162
|
+
# SUBHEADING = "#AFEFFF" (Pale Blue)
|
|
163
|
+
# SUBHEADING_EXTRA_1 = "#96A3C5" (Grayish Blue)
|
|
164
|
+
# STAKE_ALPHA = "#53B5A0" (Teal)
|
|
165
|
+
# POOLS.EMISSION = "#F8D384" (Light Orange)
|
|
166
|
+
|
|
167
|
+
# Get network name if available for title display
|
|
168
|
+
try:
|
|
169
|
+
from ...dependencies import get_config
|
|
170
|
+
config = get_config()
|
|
171
|
+
network_name = getattr(config, 'network', {}).get('endpoint', 'Hypertensor') if config else 'Hypertensor'
|
|
172
|
+
# Extract network identifier from endpoint if it's a URL
|
|
173
|
+
if '://' in network_name:
|
|
174
|
+
network_name = network_name.split('://')[-1].split('.')[0] if '.' in network_name else 'Hypertensor'
|
|
175
|
+
except:
|
|
176
|
+
network_name = 'Hypertensor'
|
|
177
|
+
|
|
178
|
+
table = HTCLITable(
|
|
179
|
+
title=f"\n[#4196D6]Subnets[/#4196D6]"
|
|
180
|
+
f"\nNetwork: [#AFEFFF]{network_name}[/#AFEFFF]\n\n",
|
|
181
|
+
header_style="bold white",
|
|
182
|
+
border_style="bright_black",
|
|
183
|
+
box_style=box.MINIMAL,
|
|
184
|
+
show_footer=True, # Enable footer for totals
|
|
185
|
+
show_edge=False,
|
|
186
|
+
show_lines=False,
|
|
187
|
+
padding=(0, 1), # Match Rich Table default spacing (vertical, horizontal)
|
|
188
|
+
row_styles=["", ""], # No alternating dimness - all rows same brightness
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
# Set table style to bold
|
|
192
|
+
table.table.style = "bold"
|
|
193
|
+
# Set title justification to center
|
|
194
|
+
table.table.title_justify = "center"
|
|
195
|
+
# Enable pad_edge
|
|
196
|
+
table.table.pad_edge = True
|
|
197
|
+
|
|
198
|
+
# Column styles with bright colors:
|
|
199
|
+
# Subnetid: centered, grey89 style
|
|
200
|
+
table.add_column(
|
|
201
|
+
"[bold white]Subnetid[/bold white]",
|
|
202
|
+
style="grey89",
|
|
203
|
+
justify="center",
|
|
204
|
+
)
|
|
205
|
+
# Name: left, cyan style
|
|
206
|
+
table.add_column("[bold white]Name[/bold white]", style="cyan", justify="left")
|
|
207
|
+
# Owner: left, steel_blue3 style (full address)
|
|
208
|
+
table.add_column(
|
|
209
|
+
"[bold white]Coldkey[/bold white]",
|
|
210
|
+
style="htcli.address",
|
|
211
|
+
justify="left",
|
|
212
|
+
no_wrap=True,
|
|
213
|
+
)
|
|
214
|
+
# Status: center, using #96A3C5 (Grayish Blue)
|
|
215
|
+
table.add_column(
|
|
216
|
+
"[bold white]Status[/bold white]",
|
|
217
|
+
style="#96A3C5",
|
|
218
|
+
justify="center",
|
|
219
|
+
)
|
|
220
|
+
# Total Nodes: center, using dark_sea_green2
|
|
221
|
+
table.add_column(
|
|
222
|
+
"[bold white]Nodes[/bold white]",
|
|
223
|
+
style="dark_sea_green2",
|
|
224
|
+
justify="center",
|
|
225
|
+
)
|
|
226
|
+
# Active Nodes: center, using #F8D384 (Light Orange)
|
|
227
|
+
table.add_column(
|
|
228
|
+
"[bold white]Active[/bold white]",
|
|
229
|
+
style="#F8D384",
|
|
230
|
+
justify="center",
|
|
231
|
+
)
|
|
232
|
+
# Min Stake: right, using #53B5A0 (Teal)
|
|
233
|
+
table.add_column(
|
|
234
|
+
"[bold white]Min Stake[/bold white]",
|
|
235
|
+
style="#53B5A0",
|
|
236
|
+
justify="right",
|
|
237
|
+
)
|
|
238
|
+
# Max Stake: right, using steel_blue3
|
|
239
|
+
table.add_column(
|
|
240
|
+
"[bold white]Max Stake[/bold white]",
|
|
241
|
+
style="steel_blue3",
|
|
242
|
+
justify="right",
|
|
243
|
+
)
|
|
244
|
+
# Delegate Stake: right, using #F8D384 (Light Orange) - shows total delegated stake
|
|
245
|
+
table.add_column(
|
|
246
|
+
"[bold white]Del. Stake[/bold white]",
|
|
247
|
+
style="#F8D384",
|
|
248
|
+
justify="right",
|
|
249
|
+
)
|
|
250
|
+
# Stake: left, using #53B5A0 (Teal)
|
|
251
|
+
table.add_column(
|
|
252
|
+
"[bold white]Stake[/bold white]",
|
|
253
|
+
style="#53B5A0",
|
|
254
|
+
justify="left",
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
def get_subnet_field(subnet, field_name, default=None):
|
|
258
|
+
"""Get field from subnet whether it's a dict or Pydantic model."""
|
|
259
|
+
if isinstance(subnet, dict):
|
|
260
|
+
return subnet.get(field_name, default)
|
|
261
|
+
elif hasattr(subnet, field_name):
|
|
262
|
+
return getattr(subnet, field_name, default)
|
|
263
|
+
elif hasattr(subnet, "model_dump"):
|
|
264
|
+
# Pydantic model - convert to dict first
|
|
265
|
+
subnet_dict = subnet.model_dump()
|
|
266
|
+
return subnet_dict.get(field_name, default)
|
|
267
|
+
return default
|
|
268
|
+
|
|
269
|
+
# Initialize totals for footer
|
|
270
|
+
total_subnets = len(subnets)
|
|
271
|
+
total_nodes = 0
|
|
272
|
+
total_active_nodes = 0
|
|
273
|
+
total_min_stake = 0
|
|
274
|
+
total_max_stake = 0
|
|
275
|
+
total_delegate_stake = 0
|
|
276
|
+
total_stake = 0
|
|
277
|
+
|
|
278
|
+
for subnet in subnets:
|
|
279
|
+
# Convert to dict if it's a Pydantic model
|
|
280
|
+
if hasattr(subnet, "model_dump") and not isinstance(subnet, dict):
|
|
281
|
+
subnet_dict = subnet.model_dump()
|
|
282
|
+
else:
|
|
283
|
+
subnet_dict = subnet if isinstance(subnet, dict) else {}
|
|
284
|
+
|
|
285
|
+
# Get the actual state from blockchain (Registered, Active, Paused)
|
|
286
|
+
state = get_subnet_field(subnet, "state", "Unknown")
|
|
287
|
+
if state is None:
|
|
288
|
+
state = "Unknown"
|
|
289
|
+
|
|
290
|
+
# Format state with color based on actual state
|
|
291
|
+
if state == "Active" or state == "active":
|
|
292
|
+
status_display = "[bold green]Active[/]"
|
|
293
|
+
elif state == "Registered" or state == "registered":
|
|
294
|
+
status_display = "[bold yellow]Registered[/]"
|
|
295
|
+
elif state == "Paused" or state == "paused":
|
|
296
|
+
status_display = "[bold red]Paused[/]"
|
|
297
|
+
else:
|
|
298
|
+
# Handle lowercase or other variations
|
|
299
|
+
state_lower = str(state).lower()
|
|
300
|
+
if "active" in state_lower:
|
|
301
|
+
status_display = "[bold green]Active[/]"
|
|
302
|
+
elif "registered" in state_lower:
|
|
303
|
+
status_display = "[bold yellow]Registered[/]"
|
|
304
|
+
elif "paused" in state_lower:
|
|
305
|
+
status_display = "[bold red]Paused[/]"
|
|
306
|
+
else:
|
|
307
|
+
status_display = f"[dim]{state}[/]"
|
|
308
|
+
|
|
309
|
+
subnet_id = str(get_subnet_field(subnet, "id", "N/A"))
|
|
310
|
+
|
|
311
|
+
# Format friendly ID
|
|
312
|
+
friendly_id = get_subnet_field(subnet, "friendly_id")
|
|
313
|
+
friendly_id_display = str(friendly_id) if friendly_id is not None else "—"
|
|
314
|
+
|
|
315
|
+
# Format name - handle bytes/str
|
|
316
|
+
subnet_name = get_subnet_field(subnet, "name", "Unknown")
|
|
317
|
+
if isinstance(subnet_name, bytes):
|
|
318
|
+
try:
|
|
319
|
+
subnet_name = subnet_name.decode("utf-8")
|
|
320
|
+
except:
|
|
321
|
+
subnet_name = subnet_name.hex()[:20] + "..."
|
|
322
|
+
elif not isinstance(subnet_name, str):
|
|
323
|
+
subnet_name = str(subnet_name) if subnet_name else "Unknown"
|
|
324
|
+
|
|
325
|
+
# Format owner address - show full address
|
|
326
|
+
owner = get_subnet_field(subnet, "owner")
|
|
327
|
+
if owner and owner != "N/A" and owner is not None:
|
|
328
|
+
owner_str = str(owner)
|
|
329
|
+
owner_display = f"[htcli.address]{format_address_display(owner_str)}[/htcli.address]"
|
|
330
|
+
else:
|
|
331
|
+
owner_display = "[dim]N/A[/dim]"
|
|
332
|
+
|
|
333
|
+
# Format node counts
|
|
334
|
+
subnet_total_nodes = get_subnet_field(subnet, "total_nodes", 0) or 0
|
|
335
|
+
subnet_total_active = get_subnet_field(subnet, "total_active_nodes", 0) or 0
|
|
336
|
+
total_electable = get_subnet_field(subnet, "total_electable_nodes", 0) or 0
|
|
337
|
+
|
|
338
|
+
# Accumulate totals for footer
|
|
339
|
+
total_nodes += subnet_total_nodes
|
|
340
|
+
total_active_nodes += subnet_total_active
|
|
341
|
+
|
|
342
|
+
# Format stake values (convert from raw to TENSOR for display)
|
|
343
|
+
def format_stake(value, default=0):
|
|
344
|
+
"""Format stake value in TENSOR units."""
|
|
345
|
+
if value is None:
|
|
346
|
+
return "0"
|
|
347
|
+
# Convert from raw (smallest unit) to TENSOR
|
|
348
|
+
tensor_value = value / 1e18 if value > 0 else 0
|
|
349
|
+
if tensor_value == 0:
|
|
350
|
+
return "0"
|
|
351
|
+
if tensor_value < 0.01:
|
|
352
|
+
return f"{tensor_value:.6f}"
|
|
353
|
+
if tensor_value < 1000:
|
|
354
|
+
return f"{tensor_value:.2f}"
|
|
355
|
+
return f"{tensor_value:,.0f}"
|
|
356
|
+
|
|
357
|
+
def get_stake_value(subnet, field_name, default=0):
|
|
358
|
+
"""Get raw stake value in wei for calculations."""
|
|
359
|
+
value = get_subnet_field(subnet, field_name, default) or 0
|
|
360
|
+
return value if isinstance(value, (int, float)) else 0
|
|
361
|
+
|
|
362
|
+
# Get raw stake values for totals
|
|
363
|
+
subnet_min_stake_raw = get_stake_value(subnet, "min_stake", 0)
|
|
364
|
+
subnet_max_stake_raw = get_stake_value(subnet, "max_stake", 0)
|
|
365
|
+
# Prefer new field name from updated SubnetInfo, fall back to legacy key if present
|
|
366
|
+
subnet_total_stake_raw = get_stake_value(subnet, "total_subnet_stake", 0)
|
|
367
|
+
if subnet_total_stake_raw == 0:
|
|
368
|
+
subnet_total_stake_raw = get_stake_value(subnet, "total_stake", 0)
|
|
369
|
+
# Delegate stake (total delegated stake balance on subnet)
|
|
370
|
+
subnet_delegate_stake_raw = get_stake_value(subnet, "total_subnet_delegate_stake_balance", 0)
|
|
371
|
+
|
|
372
|
+
# Accumulate stake totals
|
|
373
|
+
total_min_stake += subnet_min_stake_raw
|
|
374
|
+
total_max_stake += subnet_max_stake_raw
|
|
375
|
+
total_delegate_stake += subnet_delegate_stake_raw
|
|
376
|
+
total_stake += subnet_total_stake_raw
|
|
377
|
+
|
|
378
|
+
# Format for display
|
|
379
|
+
min_stake = format_stake(subnet_min_stake_raw)
|
|
380
|
+
max_stake = format_stake(subnet_max_stake_raw)
|
|
381
|
+
delegate_stake = format_stake(subnet_delegate_stake_raw)
|
|
382
|
+
subnet_total_stake_formatted = format_stake(subnet_total_stake_raw)
|
|
383
|
+
|
|
384
|
+
# Format subnet name with cyan color
|
|
385
|
+
subnet_name_display = f"[cyan]{subnet_name}[/cyan]"
|
|
386
|
+
|
|
387
|
+
# Format stake values with TENSOR suffix if > 0
|
|
388
|
+
min_stake_display = f"{min_stake}" if min_stake != "0" else "0"
|
|
389
|
+
max_stake_display = f"{max_stake}" if max_stake != "0" else "0"
|
|
390
|
+
delegate_stake_display = f"{delegate_stake}" if delegate_stake != "0" else "0"
|
|
391
|
+
total_stake_display = f"{subnet_total_stake_formatted}" if subnet_total_stake_formatted != "0" else "0"
|
|
392
|
+
|
|
393
|
+
table.add_row(
|
|
394
|
+
subnet_id,
|
|
395
|
+
subnet_name_display,
|
|
396
|
+
owner_display,
|
|
397
|
+
status_display,
|
|
398
|
+
str(subnet_total_nodes),
|
|
399
|
+
str(subnet_total_active),
|
|
400
|
+
min_stake_display,
|
|
401
|
+
max_stake_display,
|
|
402
|
+
delegate_stake_display,
|
|
403
|
+
total_stake_display,
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
# Format totals for footer display
|
|
407
|
+
def format_total_stake(value):
|
|
408
|
+
"""Format total stake value in TENSOR units for footer."""
|
|
409
|
+
if value is None or value == 0:
|
|
410
|
+
return "0"
|
|
411
|
+
# Convert from raw (smallest unit) to TENSOR
|
|
412
|
+
tensor_value = value / 1e18 if value > 0 else 0
|
|
413
|
+
if tensor_value == 0:
|
|
414
|
+
return "0"
|
|
415
|
+
if tensor_value < 0.01:
|
|
416
|
+
return f"{tensor_value:.6f}"
|
|
417
|
+
if tensor_value < 1000:
|
|
418
|
+
return f"{tensor_value:,.2f}"
|
|
419
|
+
return f"{tensor_value:,.0f}"
|
|
420
|
+
|
|
421
|
+
# Set footer with totals
|
|
422
|
+
footer_values = [
|
|
423
|
+
f"[bold]{total_subnets}[/bold]", # Total number of subnets
|
|
424
|
+
"", # Name column
|
|
425
|
+
"", # Owner column
|
|
426
|
+
"", # Status column
|
|
427
|
+
f"[bold]{total_nodes}[/bold]", # Total nodes
|
|
428
|
+
f"[bold]{total_active_nodes}[/bold]", # Total active nodes
|
|
429
|
+
f"[bold]{format_total_stake(total_min_stake)}[/bold]", # Total min stake
|
|
430
|
+
f"[bold]{format_total_stake(total_max_stake)}[/bold]", # Total max stake
|
|
431
|
+
f"[bold]{format_total_stake(total_delegate_stake)}[/bold]", # Total delegate stake
|
|
432
|
+
f"[bold]{format_total_stake(total_stake)}[/bold]", # Total stake
|
|
433
|
+
]
|
|
434
|
+
table.set_column_footers(footer_values)
|
|
435
|
+
|
|
436
|
+
return table
|
|
437
|
+
|
|
438
|
+
|
|
439
|
+
def create_node_table(nodes: list) -> HTCLITable:
|
|
440
|
+
"""Create a standardized node table for displaying all nodes."""
|
|
441
|
+
table = HTCLITable(
|
|
442
|
+
title="🔗 Network Nodes",
|
|
443
|
+
header_style="bold cyan",
|
|
444
|
+
border_style="cyan",
|
|
445
|
+
)
|
|
446
|
+
table.add_column("Subnet", style="bold", width=8)
|
|
447
|
+
table.add_column("Node ID", style="bold blue", width=8)
|
|
448
|
+
table.add_column("Coldkey", style="htcli.address", min_width=25, max_width=42)
|
|
449
|
+
table.add_column("Hotkey", style="htcli.address", min_width=25, max_width=42)
|
|
450
|
+
table.add_column("Status", style="bold", justify="center", width=14)
|
|
451
|
+
table.add_column("Stake", style="green", justify="right", width=15)
|
|
452
|
+
table.add_column("Penalties", style="yellow", justify="center", width=10)
|
|
453
|
+
|
|
454
|
+
for node in nodes:
|
|
455
|
+
# Handle both dict and object formats
|
|
456
|
+
if hasattr(node, 'subnet_id'):
|
|
457
|
+
subnet_id = str(node.subnet_id)
|
|
458
|
+
node_id = str(node.subnet_node_id)
|
|
459
|
+
coldkey = node.coldkey[:10] + "..." if len(node.coldkey) > 13 else node.coldkey
|
|
460
|
+
hotkey = node.hotkey[:10] + "..." if len(node.hotkey) > 13 else node.hotkey
|
|
461
|
+
stake_balance = node.stake_balance / 1e18 # Convert from wei
|
|
462
|
+
penalties = str(node.penalties)
|
|
463
|
+
|
|
464
|
+
# Get classification status
|
|
465
|
+
classification = node.classification
|
|
466
|
+
if isinstance(classification, dict):
|
|
467
|
+
node_class = classification.get('node_class', 'Unknown')
|
|
468
|
+
else:
|
|
469
|
+
node_class = 'Unknown'
|
|
470
|
+
else:
|
|
471
|
+
subnet_id = str(node.get("subnet_id", "N/A"))
|
|
472
|
+
node_id = str(node.get("subnet_node_id", "N/A"))
|
|
473
|
+
coldkey_full = node.get("coldkey", "N/A")
|
|
474
|
+
coldkey = coldkey_full[:10] + "..." if len(coldkey_full) > 13 else coldkey_full
|
|
475
|
+
hotkey_full = node.get("hotkey", "N/A")
|
|
476
|
+
hotkey = hotkey_full[:10] + "..." if len(hotkey_full) > 13 else hotkey_full
|
|
477
|
+
stake_balance = node.get("stake_balance", 0) / 1e18
|
|
478
|
+
penalties = str(node.get("penalties", 0))
|
|
479
|
+
|
|
480
|
+
classification = node.get("classification", {})
|
|
481
|
+
node_class = classification.get('node_class', 'Unknown') if isinstance(classification, dict) else 'Unknown'
|
|
482
|
+
|
|
483
|
+
# Format status with color
|
|
484
|
+
if node_class == 'Active':
|
|
485
|
+
status_display = "[bold green]✓ Active[/]"
|
|
486
|
+
elif node_class == 'Deactivated':
|
|
487
|
+
status_display = "[yellow]⚠ Deactivated[/]"
|
|
488
|
+
elif node_class == 'Included':
|
|
489
|
+
status_display = "[cyan]◉ Included[/]"
|
|
490
|
+
elif node_class == 'Queue':
|
|
491
|
+
status_display = "[blue]⋯ Queued[/]"
|
|
492
|
+
else:
|
|
493
|
+
status_display = f"[dim]{node_class}[/]"
|
|
494
|
+
|
|
495
|
+
table.add_row(
|
|
496
|
+
subnet_id,
|
|
497
|
+
node_id,
|
|
498
|
+
coldkey,
|
|
499
|
+
hotkey,
|
|
500
|
+
status_display,
|
|
501
|
+
status_display,
|
|
502
|
+
f"{stake_balance:,.4f}",
|
|
503
|
+
penalties,
|
|
504
|
+
penalties,
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
return table
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
def create_wallet_table(wallets: list[dict]) -> HTCLITable:
|
|
511
|
+
"""Create a standardized wallet table."""
|
|
512
|
+
table = HTCLITable(
|
|
513
|
+
title="💰 Wallets",
|
|
514
|
+
header_style="bold green",
|
|
515
|
+
border_style="green",
|
|
516
|
+
)
|
|
517
|
+
table.add_column("Name", style="bold blue", min_width=15)
|
|
518
|
+
table.add_column("Type", style="bold", width=8)
|
|
519
|
+
table.add_column("Address", style="htcli.address", min_width=20)
|
|
520
|
+
table.add_column("Key Type", style="dim", width=8)
|
|
521
|
+
table.add_column("Balance", style="green", justify="right", width=12)
|
|
522
|
+
|
|
523
|
+
for wallet in wallets:
|
|
524
|
+
table.add_row(
|
|
525
|
+
wallet.get("name", "Unknown"),
|
|
526
|
+
wallet.get("wallet_type", "Unknown"),
|
|
527
|
+
wallet.get("address", "N/A"),
|
|
528
|
+
wallet.get("key_type", "Unknown"),
|
|
529
|
+
f"{wallet.get('balance', 0):,}",
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
return table
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
def create_balance_table(balances: list[dict], total_balance: float = 0) -> HTCLITable:
|
|
536
|
+
"""Create a balance table for wallet balances with footer showing total."""
|
|
537
|
+
table = HTCLITable(
|
|
538
|
+
title="💰 Balance Information",
|
|
539
|
+
header_style="bold green",
|
|
540
|
+
border_style="green",
|
|
541
|
+
show_footer=True,
|
|
542
|
+
)
|
|
543
|
+
table.add_column("Wallet", style="bold blue", min_width=15)
|
|
544
|
+
table.add_column("Address", style="htcli.address", min_width=20)
|
|
545
|
+
table.add_column("Balance", style="green", justify="right", width=12)
|
|
546
|
+
table.add_column("Status", style="bold", justify="center", width=8)
|
|
547
|
+
|
|
548
|
+
# Track total for footer
|
|
549
|
+
total_balance_tensor = 0
|
|
550
|
+
|
|
551
|
+
for balance in balances:
|
|
552
|
+
balance_amount = balance.get("balance_tensor", balance.get("balance", 0))
|
|
553
|
+
|
|
554
|
+
# Handle both string and numeric values
|
|
555
|
+
if isinstance(balance_amount, str):
|
|
556
|
+
balance_display = balance_amount
|
|
557
|
+
elif isinstance(balance_amount, (int, float)) and balance_amount > 0:
|
|
558
|
+
# Convert from wei to TENSOR
|
|
559
|
+
balance_tensor = balance_amount / 1e18
|
|
560
|
+
balance_display = f"{balance_tensor:,.2f}"
|
|
561
|
+
total_balance_tensor += balance_tensor
|
|
562
|
+
else:
|
|
563
|
+
balance_display = "0.00"
|
|
564
|
+
|
|
565
|
+
table.add_row(
|
|
566
|
+
balance.get("name", "Unknown"),
|
|
567
|
+
balance.get("address", "N/A"),
|
|
568
|
+
balance_display,
|
|
569
|
+
balance.get("status", "Active"),
|
|
570
|
+
)
|
|
571
|
+
|
|
572
|
+
# Add footer row with total balance
|
|
573
|
+
footer = [
|
|
574
|
+
"",
|
|
575
|
+
"",
|
|
576
|
+
"[bold]Total[/bold]",
|
|
577
|
+
f"[bold]{total_balance_tensor:,.2f}[/bold]",
|
|
578
|
+
"",
|
|
579
|
+
]
|
|
580
|
+
table.set_column_footers(footer)
|
|
581
|
+
|
|
582
|
+
return table
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
def create_htcli_balance_table(
|
|
586
|
+
balances: list[dict],
|
|
587
|
+
totals: dict,
|
|
588
|
+
network_name: Optional[str] = None,
|
|
589
|
+
show_totals: bool = True,
|
|
590
|
+
) -> Table:
|
|
591
|
+
"""Create an HTCLI balance table with staking breakdown and totals footer."""
|
|
592
|
+
from ...utils.wallet.crypto import format_address_display
|
|
593
|
+
|
|
594
|
+
title_lines = ["Wallet Coldkey Balance"]
|
|
595
|
+
if network_name:
|
|
596
|
+
title_lines.append(f"Network: {network_name}")
|
|
597
|
+
title_lines.append("[dim]All values in TENSOR[/dim]")
|
|
598
|
+
title = "\n".join(
|
|
599
|
+
[
|
|
600
|
+
f"[{HTCLIColors.PRIMARY}]{line}[/{HTCLIColors.PRIMARY}]"
|
|
601
|
+
if "dim" not in line else line
|
|
602
|
+
for line in title_lines
|
|
603
|
+
]
|
|
604
|
+
)
|
|
605
|
+
|
|
606
|
+
table = Table(
|
|
607
|
+
title=f"\n{title}\n",
|
|
608
|
+
show_footer=show_totals,
|
|
609
|
+
show_edge=False,
|
|
610
|
+
border_style=HTCLIColors.GRAY_500,
|
|
611
|
+
box=box.SIMPLE_HEAVY,
|
|
612
|
+
pad_edge=False,
|
|
613
|
+
expand=True,
|
|
614
|
+
)
|
|
615
|
+
|
|
616
|
+
table.add_column(
|
|
617
|
+
"[white]Wallet Name", style=HTCLIColors.PRIMARY_LIGHT, no_wrap=True
|
|
618
|
+
)
|
|
619
|
+
table.add_column(
|
|
620
|
+
"[white]Coldkey Address", style="htcli.address", no_wrap=True
|
|
621
|
+
)
|
|
622
|
+
table.add_column(
|
|
623
|
+
"[white]Free Balance",
|
|
624
|
+
justify="right",
|
|
625
|
+
style=HTCLIColors.BALANCE,
|
|
626
|
+
no_wrap=True,
|
|
627
|
+
)
|
|
628
|
+
table.add_column(
|
|
629
|
+
"[white]Direct Stake",
|
|
630
|
+
justify="right",
|
|
631
|
+
style="#53B5A0", # Teal for direct stake
|
|
632
|
+
no_wrap=True,
|
|
633
|
+
)
|
|
634
|
+
table.add_column(
|
|
635
|
+
"[white]Delegate",
|
|
636
|
+
justify="right",
|
|
637
|
+
style=HTCLIColors.STAKING_ORANGE,
|
|
638
|
+
no_wrap=True,
|
|
639
|
+
)
|
|
640
|
+
table.add_column(
|
|
641
|
+
"[white]Node Del.",
|
|
642
|
+
justify="right",
|
|
643
|
+
style="#96A3C5", # Grayish blue for node delegate
|
|
644
|
+
no_wrap=True,
|
|
645
|
+
)
|
|
646
|
+
table.add_column(
|
|
647
|
+
"[white]Overwatch",
|
|
648
|
+
justify="right",
|
|
649
|
+
style="#E066FF", # Purple for overwatch stake
|
|
650
|
+
no_wrap=True,
|
|
651
|
+
)
|
|
652
|
+
table.add_column(
|
|
653
|
+
"[white]Unbonding",
|
|
654
|
+
justify="right",
|
|
655
|
+
style="#FF6B6B", # Coral/Red for unbonding (in waiting period)
|
|
656
|
+
no_wrap=True,
|
|
657
|
+
)
|
|
658
|
+
table.add_column(
|
|
659
|
+
"[white]Total Balance",
|
|
660
|
+
justify="right",
|
|
661
|
+
style=HTCLIColors.CRYPTO_GOLD,
|
|
662
|
+
no_wrap=True,
|
|
663
|
+
)
|
|
664
|
+
|
|
665
|
+
def _format_tensor(value: Optional[int], precision: int = 2) -> str:
|
|
666
|
+
if value is None:
|
|
667
|
+
return "--"
|
|
668
|
+
try:
|
|
669
|
+
tensor_value = value / 1e18
|
|
670
|
+
except TypeError:
|
|
671
|
+
return str(value)
|
|
672
|
+
return f"{tensor_value:,.{precision}f}"
|
|
673
|
+
|
|
674
|
+
for row in balances:
|
|
675
|
+
address = row.get("address") or "N/A"
|
|
676
|
+
address_display = format_address_display(address) if address else "N/A"
|
|
677
|
+
|
|
678
|
+
free_display = row.get("display_free") or _format_tensor(
|
|
679
|
+
row.get("free_balance", 0)
|
|
680
|
+
)
|
|
681
|
+
direct_display = _format_tensor(row.get("direct_stake", 0))
|
|
682
|
+
delegate_display = _format_tensor(row.get("delegate_stake", 0))
|
|
683
|
+
node_delegate_display = _format_tensor(row.get("node_delegate_stake", 0))
|
|
684
|
+
overwatch_display = _format_tensor(row.get("overwatch_stake", 0))
|
|
685
|
+
unbonding_display = _format_tensor(row.get("unbonding", 0))
|
|
686
|
+
total_value = row.get("total_balance")
|
|
687
|
+
if total_value is None:
|
|
688
|
+
total_value = (row.get("free_balance", 0) or 0) + (
|
|
689
|
+
row.get("staked_balance", 0) or 0
|
|
690
|
+
)
|
|
691
|
+
total_display = row.get("display_total") or _format_tensor(total_value)
|
|
692
|
+
|
|
693
|
+
table.add_row(
|
|
694
|
+
row.get("name", "Unknown"),
|
|
695
|
+
address_display,
|
|
696
|
+
free_display,
|
|
697
|
+
direct_display,
|
|
698
|
+
delegate_display,
|
|
699
|
+
node_delegate_display,
|
|
700
|
+
overwatch_display,
|
|
701
|
+
unbonding_display,
|
|
702
|
+
total_display,
|
|
703
|
+
)
|
|
704
|
+
|
|
705
|
+
if show_totals and totals:
|
|
706
|
+
wallet_count = len(balances)
|
|
707
|
+
footers = [
|
|
708
|
+
f"[bold white]{wallet_count} wallet{'s' if wallet_count != 1 else ''}[/bold white]",
|
|
709
|
+
"",
|
|
710
|
+
f"[bold]{_format_tensor(totals.get('free'))}[/bold]",
|
|
711
|
+
f"[bold]{_format_tensor(totals.get('direct_stake'))}[/bold]",
|
|
712
|
+
f"[bold]{_format_tensor(totals.get('delegate_stake'))}[/bold]",
|
|
713
|
+
f"[bold]{_format_tensor(totals.get('node_delegate_stake'))}[/bold]",
|
|
714
|
+
f"[bold]{_format_tensor(totals.get('overwatch_stake'))}[/bold]",
|
|
715
|
+
f"[bold]{_format_tensor(totals.get('unbonding'))}[/bold]",
|
|
716
|
+
f"[bold]{_format_tensor(totals.get('total'))}[/bold]",
|
|
717
|
+
]
|
|
718
|
+
for column, footer in zip(table.columns, footers):
|
|
719
|
+
column.footer = footer
|
|
720
|
+
|
|
721
|
+
return table
|
|
722
|
+
|
|
723
|
+
|
|
724
|
+
def create_wallet_minimal_table(wallets: list[dict[str, Any]]) -> HTCLITable:
|
|
725
|
+
"""
|
|
726
|
+
Create a wallet table with the same structure as display_keys_table but using HTCLITable.
|
|
727
|
+
"""
|
|
728
|
+
from ...utils.wallet.crypto import format_address_display
|
|
729
|
+
|
|
730
|
+
table = HTCLITable(
|
|
731
|
+
title="Stored Wallets",
|
|
732
|
+
header_style="htcli.table.header",
|
|
733
|
+
border_style="htcli.table.border",
|
|
734
|
+
row_styles=["", "dim"],
|
|
735
|
+
box_style=box.SIMPLE_HEAD,
|
|
736
|
+
show_edge=False,
|
|
737
|
+
padding=(0, 1),
|
|
738
|
+
)
|
|
739
|
+
|
|
740
|
+
table.add_column("Wallet", style="bold white", min_width=18, no_wrap=True)
|
|
741
|
+
table.add_column("Type", style="htcli.secondary", justify="center", width=10)
|
|
742
|
+
table.add_column("Address", style="htcli.address", min_width=24)
|
|
743
|
+
table.add_column("Owner / Coldkey", style="htcli.address", min_width=24)
|
|
744
|
+
table.add_column("Key Type", style="htcli.value", justify="center", width=10)
|
|
745
|
+
table.add_column("Encryption", style="htcli.value", justify="center", width=12)
|
|
746
|
+
|
|
747
|
+
coldkeys = [k for k in wallets if not k.get("is_hotkey", False)]
|
|
748
|
+
hotkeys = [k for k in wallets if k.get("is_hotkey", False)]
|
|
749
|
+
|
|
750
|
+
address_to_name = {
|
|
751
|
+
key.get("ss58_address"): key.get("name", "Unknown") for key in coldkeys
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
def _build_row(key_info: dict, is_hotkey: bool):
|
|
755
|
+
name = key_info.get("name", "Unknown")
|
|
756
|
+
icon = "🔥" if is_hotkey else "❄️"
|
|
757
|
+
wallet_label = f"{icon} {name}"
|
|
758
|
+
|
|
759
|
+
wallet_type = (
|
|
760
|
+
"[htcli.secondary]HOTKEY[/htcli.secondary]"
|
|
761
|
+
if is_hotkey
|
|
762
|
+
else "[htcli.success]COLDKEY[/htcli.success]"
|
|
763
|
+
)
|
|
764
|
+
|
|
765
|
+
address = key_info.get("ss58_address") or key_info.get("address") or "N/A"
|
|
766
|
+
formatted_address = format_address_display(address, max_length=36)
|
|
767
|
+
|
|
768
|
+
if is_hotkey:
|
|
769
|
+
owner_address = key_info.get("owner_address")
|
|
770
|
+
if owner_address and owner_address in address_to_name:
|
|
771
|
+
owner_display = (
|
|
772
|
+
f"{format_address_display(owner_address, max_length=34)} "
|
|
773
|
+
f"({address_to_name[owner_address]})"
|
|
774
|
+
)
|
|
775
|
+
else:
|
|
776
|
+
owner_display = (
|
|
777
|
+
format_address_display(owner_address or "N/A", max_length=34)
|
|
778
|
+
)
|
|
779
|
+
else:
|
|
780
|
+
owner_display = formatted_address if formatted_address != "N/A" else "—"
|
|
781
|
+
|
|
782
|
+
encrypted_status = (
|
|
783
|
+
"[green]🔒 Encrypted[/green]"
|
|
784
|
+
if key_info.get("is_encrypted", True)
|
|
785
|
+
else "[yellow]Plain[/yellow]"
|
|
786
|
+
)
|
|
787
|
+
|
|
788
|
+
key_type = key_info.get("key_type", "—")
|
|
789
|
+
key_type_display = key_type.upper() if isinstance(key_type, str) else str(key_type)
|
|
790
|
+
|
|
791
|
+
table.add_row(
|
|
792
|
+
wallet_label,
|
|
793
|
+
wallet_type,
|
|
794
|
+
formatted_address,
|
|
795
|
+
owner_display,
|
|
796
|
+
key_type_display,
|
|
797
|
+
encrypted_status,
|
|
798
|
+
)
|
|
799
|
+
|
|
800
|
+
for key in coldkeys:
|
|
801
|
+
_build_row(key, is_hotkey=False)
|
|
802
|
+
|
|
803
|
+
if coldkeys and hotkeys:
|
|
804
|
+
table.table.add_section()
|
|
805
|
+
|
|
806
|
+
for key in hotkeys:
|
|
807
|
+
_build_row(key, is_hotkey=True)
|
|
808
|
+
|
|
809
|
+
return table
|