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,749 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Node display functions for RPC-based operations and extrinsic operations.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Any, Optional
|
|
6
|
+
|
|
7
|
+
from ...ui.colors import amount, info, success
|
|
8
|
+
from ...ui.components import HTCLIPanel, create_node_table
|
|
9
|
+
from ...ui.display import HTCLIConsole
|
|
10
|
+
|
|
11
|
+
console = HTCLIConsole()
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def display_all_nodes_rpc(nodes_data: list[Any]):
|
|
15
|
+
"""Display all nodes from RPC response."""
|
|
16
|
+
if not nodes_data:
|
|
17
|
+
console.print(info("No nodes found across all subnets"))
|
|
18
|
+
return
|
|
19
|
+
|
|
20
|
+
# Create table for all nodes
|
|
21
|
+
table = create_node_table(nodes_data)
|
|
22
|
+
console.print(table)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def display_node_info_rpc(node_data: Any, subnet_id: int, node_id: int):
|
|
26
|
+
"""Display specific node information from RPC response."""
|
|
27
|
+
if not node_data:
|
|
28
|
+
console.print(info(f"Node {node_id} not found in subnet {subnet_id}"))
|
|
29
|
+
return
|
|
30
|
+
|
|
31
|
+
from ...ui.components import HTCLIPanel
|
|
32
|
+
|
|
33
|
+
# Helper functions for formatting
|
|
34
|
+
def format_balance(balance: int) -> str:
|
|
35
|
+
"""Convert raw balance to TENSOR."""
|
|
36
|
+
return f"{balance / 1e18:,.4f} TENSOR"
|
|
37
|
+
|
|
38
|
+
def format_peer_id(peer_id) -> str:
|
|
39
|
+
"""Format peer ID - show only if not empty."""
|
|
40
|
+
# Handle None, empty bytes, or empty list
|
|
41
|
+
if not peer_id:
|
|
42
|
+
return "[dim]Not set[/dim]"
|
|
43
|
+
if isinstance(peer_id, bytes):
|
|
44
|
+
if len(peer_id) == 0:
|
|
45
|
+
return "[dim]Not set[/dim]"
|
|
46
|
+
# Try to decode using the peer_id utility function
|
|
47
|
+
try:
|
|
48
|
+
from ...utils.blockchain.peer_id import decode_peer_id
|
|
49
|
+
decoded = decode_peer_id(peer_id)
|
|
50
|
+
return decoded if decoded else "[dim]Empty[/dim]"
|
|
51
|
+
except Exception:
|
|
52
|
+
# Fallback to UTF-8 decode if decode_peer_id fails
|
|
53
|
+
try:
|
|
54
|
+
decoded = peer_id.decode('utf-8', errors='ignore')
|
|
55
|
+
return decoded if decoded else "[dim]Empty[/dim]"
|
|
56
|
+
except Exception:
|
|
57
|
+
return "[dim]Invalid[/dim]"
|
|
58
|
+
# Handle string peer IDs directly
|
|
59
|
+
if isinstance(peer_id, str):
|
|
60
|
+
return peer_id if peer_id else "[dim]Not set[/dim]"
|
|
61
|
+
# Handle list (shouldn't happen if to_bytes is working, but just in case)
|
|
62
|
+
if isinstance(peer_id, list):
|
|
63
|
+
if len(peer_id) == 0:
|
|
64
|
+
return "[dim]Not set[/dim]"
|
|
65
|
+
try:
|
|
66
|
+
return format_peer_id(bytes(peer_id))
|
|
67
|
+
except Exception:
|
|
68
|
+
return "[dim]Invalid[/dim]"
|
|
69
|
+
return str(peer_id)
|
|
70
|
+
|
|
71
|
+
def format_identity(identity: dict) -> str:
|
|
72
|
+
"""Format identity dict - show only non-empty fields."""
|
|
73
|
+
if not identity:
|
|
74
|
+
return "[dim]No identity set[/dim]"
|
|
75
|
+
|
|
76
|
+
# Filter out empty values
|
|
77
|
+
non_empty = {k: v for k, v in identity.items() if v and v.strip()}
|
|
78
|
+
|
|
79
|
+
if not non_empty:
|
|
80
|
+
return "[dim]No identity information[/dim]"
|
|
81
|
+
|
|
82
|
+
lines = []
|
|
83
|
+
for key, value in non_empty.items():
|
|
84
|
+
formatted_key = key.replace('_', ' ').title()
|
|
85
|
+
lines.append(f" • {formatted_key}: {value}")
|
|
86
|
+
|
|
87
|
+
return "\n".join(lines)
|
|
88
|
+
|
|
89
|
+
def format_classification(classification: dict) -> str:
|
|
90
|
+
"""Format classification information."""
|
|
91
|
+
if not classification:
|
|
92
|
+
return "[dim]Unknown[/dim]"
|
|
93
|
+
|
|
94
|
+
node_class = classification.get('node_class', 'Unknown')
|
|
95
|
+
start_epoch = classification.get('start_epoch', 'N/A')
|
|
96
|
+
|
|
97
|
+
# Color code based on class
|
|
98
|
+
if node_class == 'Active':
|
|
99
|
+
return f"[green]✓ Active[/green] (since epoch {start_epoch})"
|
|
100
|
+
elif node_class == 'Deactivated':
|
|
101
|
+
return f"[yellow]⚠ Deactivated[/yellow] (since epoch {start_epoch})"
|
|
102
|
+
elif node_class == 'Included':
|
|
103
|
+
return f"[cyan]◉ Included[/cyan] (since epoch {start_epoch})"
|
|
104
|
+
elif node_class == 'Queue':
|
|
105
|
+
return f"[blue]⋯ Queued[/blue] (since epoch {start_epoch})"
|
|
106
|
+
else:
|
|
107
|
+
return f"{node_class} (epoch {start_epoch})"
|
|
108
|
+
|
|
109
|
+
def format_reputation(reputation: dict) -> str:
|
|
110
|
+
"""Format reputation information."""
|
|
111
|
+
if not reputation:
|
|
112
|
+
return "[dim]No reputation data[/dim]"
|
|
113
|
+
|
|
114
|
+
score = reputation.get('score', 0) / 1e12 # Assuming score is scaled
|
|
115
|
+
lifetime_nodes = reputation.get('lifetime_node_count', 0)
|
|
116
|
+
total_active = reputation.get('total_active_nodes', 0)
|
|
117
|
+
increases = reputation.get('total_increases', 0)
|
|
118
|
+
decreases = reputation.get('total_decreases', 0)
|
|
119
|
+
avg_attestation = reputation.get('average_attestation', 0)
|
|
120
|
+
|
|
121
|
+
return f""" • Score: {score:,.2f}
|
|
122
|
+
• Lifetime Nodes: {lifetime_nodes}
|
|
123
|
+
• Active Nodes: {total_active}
|
|
124
|
+
• Increases: {increases} | Decreases: {decreases}
|
|
125
|
+
• Avg Attestation: {avg_attestation}"""
|
|
126
|
+
|
|
127
|
+
def format_bytes_field(value) -> str:
|
|
128
|
+
"""Best-effort formatting for optional bytes/opaque fields."""
|
|
129
|
+
if value is None:
|
|
130
|
+
return "[dim]Not set[/dim]"
|
|
131
|
+
if isinstance(value, (bytes, bytearray)):
|
|
132
|
+
try:
|
|
133
|
+
decoded = value.decode("utf-8", errors="ignore")
|
|
134
|
+
return decoded if decoded else f"0x{value.hex()}"
|
|
135
|
+
except Exception:
|
|
136
|
+
return f"0x{value.hex()}"
|
|
137
|
+
return str(value)
|
|
138
|
+
|
|
139
|
+
# Format balances
|
|
140
|
+
stake_balance = format_balance(node_data.stake_balance)
|
|
141
|
+
delegate_stake = format_balance(node_data.node_delegate_stake_balance)
|
|
142
|
+
|
|
143
|
+
# Format classification
|
|
144
|
+
classification_display = format_classification(node_data.classification)
|
|
145
|
+
|
|
146
|
+
# Format identity
|
|
147
|
+
identity_display = format_identity(node_data.identity)
|
|
148
|
+
|
|
149
|
+
# Format reputation (prefer coldkey_reputation if available)
|
|
150
|
+
raw_reputation = getattr(node_data, "coldkey_reputation", None)
|
|
151
|
+
if not raw_reputation:
|
|
152
|
+
raw_reputation = getattr(node_data, "reputation", None)
|
|
153
|
+
reputation_display = format_reputation(raw_reputation)
|
|
154
|
+
|
|
155
|
+
# Build the display - show all relevant fields
|
|
156
|
+
node_info = f"""[htcli.accent]Basic Information[/htcli.accent]
|
|
157
|
+
[htcli.value]Subnet ID:[/htcli.value] {subnet_id}
|
|
158
|
+
[htcli.value]Node ID:[/htcli.value] {node_id}
|
|
159
|
+
[htcli.value]Status:[/htcli.value] {classification_display}
|
|
160
|
+
|
|
161
|
+
[htcli.accent]Wallet Addresses[/htcli.accent]
|
|
162
|
+
[htcli.value]Coldkey:[/htcli.value] {node_data.coldkey}
|
|
163
|
+
[htcli.value]Hotkey:[/htcli.value] {node_data.hotkey}
|
|
164
|
+
|
|
165
|
+
[htcli.accent]Network Configuration[/htcli.accent]
|
|
166
|
+
[htcli.value]Peer ID:[/htcli.value] {format_peer_id(node_data.peer_id)}
|
|
167
|
+
[htcli.value]Bootnode Peer ID:[/htcli.value] {format_peer_id(node_data.bootnode_peer_id)}
|
|
168
|
+
[htcli.value]Client Peer ID:[/htcli.value] {format_peer_id(node_data.client_peer_id)}
|
|
169
|
+
[htcli.value]Is Bootnode:[/htcli.value] {"Yes" if node_data.bootnode else "No"}
|
|
170
|
+
|
|
171
|
+
[htcli.accent]Economics[/htcli.accent]
|
|
172
|
+
[htcli.value]Node Stake:[/htcli.value] {amount(stake_balance)}
|
|
173
|
+
[htcli.value]Total Delegate Stake:[/htcli.value] {amount(delegate_stake)}
|
|
174
|
+
[htcli.value]Delegate Reward Rate:[/htcli.value] {node_data.delegate_reward_rate}%
|
|
175
|
+
[htcli.value]Penalties:[/htcli.value] {getattr(node_data, 'penalties', 0)}
|
|
176
|
+
[htcli.value]Total Node Delegate Stake Shares:[/htcli.value] {getattr(node_data, 'total_node_delegate_stake_shares', 0)}
|
|
177
|
+
[htcli.value]Last Delegate Reward Rate Update Epoch:[/htcli.value] {getattr(node_data, 'last_delegate_reward_rate_update', 0)}
|
|
178
|
+
|
|
179
|
+
[htcli.accent]Activity & Scheduling[/htcli.accent]
|
|
180
|
+
[htcli.value]Node Slot Index:[/htcli.value] {getattr(node_data, 'node_slot_index', '[dim]N/A[/dim]')}
|
|
181
|
+
[htcli.value]Consecutive Idle Epochs:[/htcli.value] {getattr(node_data, 'consecutive_idle_epochs', 0)}
|
|
182
|
+
[htcli.value]Consecutive Included Epochs:[/htcli.value] {getattr(node_data, 'consecutive_included_epochs', 0)}
|
|
183
|
+
|
|
184
|
+
[htcli.accent]Custom Parameters[/htcli.accent]
|
|
185
|
+
[htcli.value]Unique:[/htcli.value] {format_bytes_field(getattr(node_data, 'unique', None))}
|
|
186
|
+
[htcli.value]Non-Unique:[/htcli.value] {format_bytes_field(getattr(node_data, 'non_unique', None))}
|
|
187
|
+
|
|
188
|
+
[htcli.accent]Identity[/htcli.accent]
|
|
189
|
+
{identity_display}
|
|
190
|
+
|
|
191
|
+
[htcli.accent]Reputation[/htcli.accent]
|
|
192
|
+
{reputation_display}
|
|
193
|
+
[htcli.value]Subnet Node Reputation:[/htcli.value] {getattr(node_data, 'subnet_node_reputation', 0)}
|
|
194
|
+
"""
|
|
195
|
+
|
|
196
|
+
panel = HTCLIPanel(
|
|
197
|
+
node_info,
|
|
198
|
+
title=f"🔍 Node {node_id} Details",
|
|
199
|
+
border_style="htcli.info",
|
|
200
|
+
highlight=True,
|
|
201
|
+
)
|
|
202
|
+
panel.render(console.console)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def display_overwatch_commits_rpc(
|
|
206
|
+
commits_data: list[Any], epoch: int, overwatch_node_id: int
|
|
207
|
+
):
|
|
208
|
+
"""Display overwatch commits from RPC response."""
|
|
209
|
+
if not commits_data:
|
|
210
|
+
console.print(
|
|
211
|
+
info(
|
|
212
|
+
f"No overwatch commits found for epoch {epoch}, node {overwatch_node_id}"
|
|
213
|
+
)
|
|
214
|
+
)
|
|
215
|
+
return
|
|
216
|
+
|
|
217
|
+
from ...ui.components import HTCLIPanel
|
|
218
|
+
|
|
219
|
+
# Display overwatch commits
|
|
220
|
+
commits_info = f"""
|
|
221
|
+
[htcli.accent]Overwatch Commits[/htcli.accent]
|
|
222
|
+
|
|
223
|
+
[htcli.value]Epoch:[/htcli.value] {epoch}
|
|
224
|
+
[htcli.value]Overwatch Node ID:[/htcli.value] {overwatch_node_id}
|
|
225
|
+
[htcli.value]Total Commits:[/htcli.value] {len(commits_data)}
|
|
226
|
+
|
|
227
|
+
[htcli.accent]Commits:[/htcli.accent]
|
|
228
|
+
{chr(10).join(f"• Subnet {commit['subnet_id']}: {commit['commit_hash']}" for commit in commits_data)}
|
|
229
|
+
"""
|
|
230
|
+
|
|
231
|
+
panel = HTCLIPanel(
|
|
232
|
+
commits_info,
|
|
233
|
+
title="🔐 Overwatch Commits",
|
|
234
|
+
border_style="htcli.info",
|
|
235
|
+
highlight=True,
|
|
236
|
+
)
|
|
237
|
+
panel.render(console.console)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def display_overwatch_reveals_rpc(
|
|
241
|
+
reveals_data: list[Any], epoch: int, overwatch_node_id: int
|
|
242
|
+
):
|
|
243
|
+
"""Display overwatch reveals from RPC response."""
|
|
244
|
+
if not reveals_data:
|
|
245
|
+
console.print(
|
|
246
|
+
info(
|
|
247
|
+
f"No overwatch reveals found for epoch {epoch}, node {overwatch_node_id}"
|
|
248
|
+
)
|
|
249
|
+
)
|
|
250
|
+
return
|
|
251
|
+
|
|
252
|
+
from ...ui.components import HTCLIPanel
|
|
253
|
+
|
|
254
|
+
# Display overwatch reveals
|
|
255
|
+
reveals_info = f"""
|
|
256
|
+
[htcli.accent]Overwatch Reveals[/htcli.accent]
|
|
257
|
+
|
|
258
|
+
[htcli.value]Epoch:[/htcli.value] {epoch}
|
|
259
|
+
[htcli.value]Overwatch Node ID:[/htcli.value] {overwatch_node_id}
|
|
260
|
+
[htcli.value]Total Reveals:[/htcli.value] {len(reveals_data)}
|
|
261
|
+
|
|
262
|
+
[htcli.accent]Reveals:[/htcli.accent]
|
|
263
|
+
{chr(10).join(f"• Subnet {reveal['subnet_id']}: Weight {reveal['weight']}" for reveal in reveals_data)}
|
|
264
|
+
"""
|
|
265
|
+
|
|
266
|
+
panel = HTCLIPanel(
|
|
267
|
+
reveals_info,
|
|
268
|
+
title="🔓 Overwatch Reveals",
|
|
269
|
+
border_style="htcli.info",
|
|
270
|
+
highlight=True,
|
|
271
|
+
)
|
|
272
|
+
panel.render(console.console)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
# ============================================================================
|
|
276
|
+
# EXTRINSIC DISPLAY FUNCTIONS - Write Operations
|
|
277
|
+
# ============================================================================
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def display_node_register_result(response):
|
|
281
|
+
"""Display node registration result."""
|
|
282
|
+
if hasattr(response, "success") and response.success:
|
|
283
|
+
stake_balance_tensor = (
|
|
284
|
+
(response.stake_balance / 1e18)
|
|
285
|
+
if hasattr(response, "stake_balance") and response.stake_balance
|
|
286
|
+
else 0
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
# Try to get block number if it's missing but we have block_hash
|
|
290
|
+
block_number = response.block_number if hasattr(response, "block_number") else None
|
|
291
|
+
if not block_number and hasattr(response, "block_hash") and response.block_hash:
|
|
292
|
+
try:
|
|
293
|
+
from ...dependencies import get_client
|
|
294
|
+
client = get_client()
|
|
295
|
+
if client and client.rpc and client.rpc.chain:
|
|
296
|
+
block_number = client.rpc.chain.get_block_number(response.block_hash)
|
|
297
|
+
except Exception:
|
|
298
|
+
pass
|
|
299
|
+
|
|
300
|
+
block_hash = getattr(response, "block_hash", None)
|
|
301
|
+
content = f"""[htcli.success]✅ Node Registered Successfully![/htcli.success]
|
|
302
|
+
|
|
303
|
+
[htcli.value]Subnet ID:[/htcli.value] {response.subnet_id if hasattr(response, "subnet_id") else "N/A"}
|
|
304
|
+
[htcli.value]Validator ID:[/htcli.value] {response.validator_id if hasattr(response, "validator_id") else "N/A"}
|
|
305
|
+
[htcli.value]Node ID:[/htcli.value] {response.subnet_node_id if hasattr(response, "subnet_node_id") else "N/A"}
|
|
306
|
+
[htcli.value]Hotkey:[/htcli.value] {response.hotkey if hasattr(response, "hotkey") else "N/A"}
|
|
307
|
+
[htcli.value]Initial Stake:[/htcli.value] {amount(f"{stake_balance_tensor:,.4f} TENSOR")}
|
|
308
|
+
[htcli.value]Transaction Hash:[/htcli.value] {response.transaction_hash or "N/A"}
|
|
309
|
+
[htcli.value]Block Number:[/htcli.value] {block_number or "N/A"}
|
|
310
|
+
"""
|
|
311
|
+
if block_hash:
|
|
312
|
+
content += f"[htcli.value]Block Hash:[/htcli.value] {block_hash}\n"
|
|
313
|
+
content += "\n[htcli.info]💡 Your node is now registered! Subnet may need additional nodes to activate.[/htcli.info]\n"
|
|
314
|
+
|
|
315
|
+
panel = HTCLIPanel(
|
|
316
|
+
content,
|
|
317
|
+
title="🚀 Node Registration Complete",
|
|
318
|
+
border_style="htcli.success",
|
|
319
|
+
highlight=True,
|
|
320
|
+
)
|
|
321
|
+
panel.render(console.console)
|
|
322
|
+
else:
|
|
323
|
+
error_msg = response.error if hasattr(response, "error") else "Unknown error"
|
|
324
|
+
|
|
325
|
+
if "ColdkeyRegistrationWhitelist" in error_msg:
|
|
326
|
+
panel = HTCLIPanel(
|
|
327
|
+
"""Only whitelisted coldkeys can register nodes while the subnet is still in its registration period.
|
|
328
|
+
|
|
329
|
+
💡 What you can do:
|
|
330
|
+
• Use one of the coldkeys supplied in --initial-coldkeys when the subnet was created
|
|
331
|
+
• Check each coldkey's remaining slots via `htcli subnet info --subnet-id <id>`
|
|
332
|
+
• Wait until the subnet activates—once active, the whitelist is removed
|
|
333
|
+
""",
|
|
334
|
+
title="⚠️ Coldkey Not On Registration Whitelist",
|
|
335
|
+
border_style="htcli.error",
|
|
336
|
+
highlight=True,
|
|
337
|
+
)
|
|
338
|
+
panel.render(console.console)
|
|
339
|
+
return
|
|
340
|
+
|
|
341
|
+
# Parse error message - it might be a dict or string
|
|
342
|
+
error_text = str(error_msg)
|
|
343
|
+
if isinstance(error_msg, dict):
|
|
344
|
+
# Extract message and data from error dict
|
|
345
|
+
error_text = error_msg.get("message", "Transaction failed")
|
|
346
|
+
error_data = error_msg.get("data", "")
|
|
347
|
+
if error_data:
|
|
348
|
+
error_text = f"{error_text}: {error_data}"
|
|
349
|
+
|
|
350
|
+
# Handle specific error types with helpful messages
|
|
351
|
+
error_lower = error_text.lower()
|
|
352
|
+
|
|
353
|
+
if "maxregisterednodes" in error_lower or "max registered nodes" in error_lower:
|
|
354
|
+
panel = HTCLIPanel(
|
|
355
|
+
"""The subnet has reached its maximum number of registered nodes.
|
|
356
|
+
|
|
357
|
+
💡 What this means:
|
|
358
|
+
• Each subnet has a MaxRegisteredNodes limit controlling how many nodes can be registered
|
|
359
|
+
• This limit may also include per-coldkey registration slots from the subnet's initial coldkeys
|
|
360
|
+
|
|
361
|
+
💡 What you can do:
|
|
362
|
+
• Check current node count and limits: htcli subnet info --subnet-id <id>
|
|
363
|
+
• If you are the subnet owner, increase the max registered nodes via subnet configuration/governance
|
|
364
|
+
• Or remove an existing node first: htcli node remove --subnet-id <id> --node-id <node-id>
|
|
365
|
+
""",
|
|
366
|
+
title="⚠️ Subnet Node Capacity Reached",
|
|
367
|
+
border_style="htcli.error",
|
|
368
|
+
highlight=True,
|
|
369
|
+
)
|
|
370
|
+
panel.render(console.console)
|
|
371
|
+
return
|
|
372
|
+
|
|
373
|
+
if "inability to pay" in error_lower or "account balance too low" in error_lower or "insufficient" in error_lower:
|
|
374
|
+
panel = HTCLIPanel(
|
|
375
|
+
"""Your account balance is too low to complete the transaction.
|
|
376
|
+
|
|
377
|
+
💡 How to fix this:
|
|
378
|
+
• Check your balance: htcli wallet balance --wallet <wallet-name>
|
|
379
|
+
• Ensure you have enough funds for:
|
|
380
|
+
- The stake amount you specified
|
|
381
|
+
- Transaction fees (typically 0.001-0.01 TENSOR)
|
|
382
|
+
• Add more funds to your wallet if needed
|
|
383
|
+
""",
|
|
384
|
+
title="⚠️ Insufficient Balance",
|
|
385
|
+
border_style="htcli.error",
|
|
386
|
+
highlight=True,
|
|
387
|
+
)
|
|
388
|
+
panel.render(console.console)
|
|
389
|
+
return
|
|
390
|
+
|
|
391
|
+
if "maxstakereached" in error_lower or "max stake reached" in error_lower:
|
|
392
|
+
panel = HTCLIPanel(
|
|
393
|
+
"""The stake amount you specified exceeds the subnet's maximum stake limit.
|
|
394
|
+
|
|
395
|
+
💡 How to fix this:
|
|
396
|
+
• Check the subnet's maximum stake: htcli subnet info --subnet-id <id>
|
|
397
|
+
• Reduce your stake amount to be within the subnet's limits
|
|
398
|
+
• The stake amount must be less than or equal to the subnet's max_stake value
|
|
399
|
+
""",
|
|
400
|
+
title="⚠️ Maximum Stake Limit Reached",
|
|
401
|
+
border_style="htcli.error",
|
|
402
|
+
highlight=True,
|
|
403
|
+
)
|
|
404
|
+
panel.render(console.console)
|
|
405
|
+
return
|
|
406
|
+
|
|
407
|
+
if "hotkeyhasowner" in error_lower or "hotkey has owner" in error_lower:
|
|
408
|
+
panel = HTCLIPanel(
|
|
409
|
+
"""This hotkey is already registered to a node.
|
|
410
|
+
|
|
411
|
+
💡 What this means:
|
|
412
|
+
• Hotkey needs to be unique across the network, not within that subnet
|
|
413
|
+
• The hotkey you're trying to use is already associated with an existing node somewhere on the network
|
|
414
|
+
|
|
415
|
+
💡 What you can do:
|
|
416
|
+
• Use a different hotkey wallet for this registration
|
|
417
|
+
• Check existing nodes: htcli node list --subnet-id <id>
|
|
418
|
+
• If you need to use this hotkey, you may need to remove the existing node first
|
|
419
|
+
""",
|
|
420
|
+
title="⚠️ Hotkey Already Registered",
|
|
421
|
+
border_style="htcli.error",
|
|
422
|
+
highlight=True,
|
|
423
|
+
)
|
|
424
|
+
panel.render(console.console)
|
|
425
|
+
return
|
|
426
|
+
|
|
427
|
+
if "invalid transaction" in error_lower:
|
|
428
|
+
panel = HTCLIPanel(
|
|
429
|
+
f"""{error_text}
|
|
430
|
+
|
|
431
|
+
💡 What you can do:
|
|
432
|
+
• Verify all parameters are correct
|
|
433
|
+
• Check your wallet balance
|
|
434
|
+
• Ensure the subnet is accepting registrations
|
|
435
|
+
• Review command help: htcli node register --help
|
|
436
|
+
""",
|
|
437
|
+
title="⚠️ Transaction Failed",
|
|
438
|
+
border_style="htcli.error",
|
|
439
|
+
highlight=True,
|
|
440
|
+
)
|
|
441
|
+
panel.render(console.console)
|
|
442
|
+
return
|
|
443
|
+
|
|
444
|
+
# Generic error display
|
|
445
|
+
panel = HTCLIPanel(
|
|
446
|
+
f"""{error_text}
|
|
447
|
+
|
|
448
|
+
💡 Troubleshooting:
|
|
449
|
+
• Verify node parameters are valid
|
|
450
|
+
• Check that the subnet exists and accepts registrations
|
|
451
|
+
• Ensure you have sufficient balance for stake and fees
|
|
452
|
+
• Review command help: htcli node register --help
|
|
453
|
+
""",
|
|
454
|
+
title="⚠️ Node Registration Failed",
|
|
455
|
+
border_style="htcli.error",
|
|
456
|
+
highlight=True,
|
|
457
|
+
)
|
|
458
|
+
panel.render(console.console)
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def display_node_bootnode_peer_update(
|
|
462
|
+
response, subnet_id: int, node_id: int, old_peer_id: Optional[str], new_peer_id: str
|
|
463
|
+
):
|
|
464
|
+
"""Display bootnode peer ID update result."""
|
|
465
|
+
if hasattr(response, "success") and response.success:
|
|
466
|
+
old_display = old_peer_id[:50] + "..." if old_peer_id and len(old_peer_id) > 50 else (old_peer_id or "N/A")
|
|
467
|
+
new_display = new_peer_id[:50] + "..." if len(new_peer_id) > 50 else new_peer_id
|
|
468
|
+
|
|
469
|
+
tx_hash = getattr(response, "transaction_hash", None) or getattr(response, "extrinsic_hash", None)
|
|
470
|
+
block_number = getattr(response, "block_number", None)
|
|
471
|
+
block_hash = getattr(response, "block_hash", None)
|
|
472
|
+
|
|
473
|
+
content = f"""[htcli.success]✅ Bootnode Peer ID Updated Successfully![/htcli.success]
|
|
474
|
+
|
|
475
|
+
[htcli.value]Subnet ID:[/htcli.value] {subnet_id}
|
|
476
|
+
[htcli.value]Node ID:[/htcli.value] {node_id}
|
|
477
|
+
[htcli.value]Old Bootnode Peer ID:[/htcli.value] {old_display}
|
|
478
|
+
[htcli.value]New Bootnode Peer ID:[/htcli.value] {new_display}
|
|
479
|
+
[htcli.value]Transaction Hash:[/htcli.value] {tx_hash or "N/A"}
|
|
480
|
+
[htcli.value]Block Number:[/htcli.value] {block_number or "N/A"}
|
|
481
|
+
"""
|
|
482
|
+
if block_hash:
|
|
483
|
+
content += f"[htcli.value]Block Hash:[/htcli.value] {block_hash}\n"
|
|
484
|
+
content += "\n[htcli.info]💡 The node's bootnode peer ID has been updated on-chain.[/htcli.info]\n"
|
|
485
|
+
|
|
486
|
+
panel = HTCLIPanel(
|
|
487
|
+
content,
|
|
488
|
+
title="🔄 Bootnode Peer ID Update Complete",
|
|
489
|
+
border_style="htcli.success",
|
|
490
|
+
highlight=True,
|
|
491
|
+
)
|
|
492
|
+
panel.render(console.console)
|
|
493
|
+
else:
|
|
494
|
+
error_msg = response.error if hasattr(response, "error") else "Unknown error"
|
|
495
|
+
console.print(f"[htcli.error]❌ Failed to update bootnode peer ID: {error_msg}[/]")
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
def display_node_client_peer_update(
|
|
499
|
+
response, subnet_id: int, node_id: int, old_peer_id: Optional[str], new_peer_id: str
|
|
500
|
+
):
|
|
501
|
+
"""Display client peer ID update result."""
|
|
502
|
+
if hasattr(response, "success") and response.success:
|
|
503
|
+
old_display = old_peer_id[:50] + "..." if old_peer_id and len(old_peer_id) > 50 else (old_peer_id or "N/A")
|
|
504
|
+
new_display = new_peer_id[:50] + "..." if len(new_peer_id) > 50 else new_peer_id
|
|
505
|
+
|
|
506
|
+
tx_hash = getattr(response, "transaction_hash", None) or getattr(response, "extrinsic_hash", None)
|
|
507
|
+
block_number = getattr(response, "block_number", None)
|
|
508
|
+
block_hash = getattr(response, "block_hash", None)
|
|
509
|
+
|
|
510
|
+
content = f"""[htcli.success]✅ Client Peer ID Updated Successfully![/htcli.success]
|
|
511
|
+
|
|
512
|
+
[htcli.value]Subnet ID:[/htcli.value] {subnet_id}
|
|
513
|
+
[htcli.value]Node ID:[/htcli.value] {node_id}
|
|
514
|
+
[htcli.value]Old Client Peer ID:[/htcli.value] {old_display}
|
|
515
|
+
[htcli.value]New Client Peer ID:[/htcli.value] {new_display}
|
|
516
|
+
[htcli.value]Transaction Hash:[/htcli.value] {tx_hash or "N/A"}
|
|
517
|
+
[htcli.value]Block Number:[/htcli.value] {block_number or "N/A"}
|
|
518
|
+
"""
|
|
519
|
+
if block_hash:
|
|
520
|
+
content += f"[htcli.value]Block Hash:[/htcli.value] {block_hash}\n"
|
|
521
|
+
content += "\n[htcli.info]💡 The node's client peer ID has been updated on-chain.[/htcli.info]\n"
|
|
522
|
+
|
|
523
|
+
panel = HTCLIPanel(
|
|
524
|
+
content,
|
|
525
|
+
title="🔄 Client Peer ID Update Complete",
|
|
526
|
+
border_style="htcli.success",
|
|
527
|
+
highlight=True,
|
|
528
|
+
)
|
|
529
|
+
panel.render(console.console)
|
|
530
|
+
else:
|
|
531
|
+
error_msg = response.error if hasattr(response, "error") else "Unknown error"
|
|
532
|
+
console.print(f"[htcli.error]❌ Failed to update client peer ID: {error_msg}[/]")
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
def display_node_remove_result(
|
|
536
|
+
result: Any,
|
|
537
|
+
subnet_id: int,
|
|
538
|
+
node_id: int,
|
|
539
|
+
):
|
|
540
|
+
"""Display subnet node removal result."""
|
|
541
|
+
success_flag = (
|
|
542
|
+
result.get("success", False) if isinstance(result, dict) else getattr(result, "success", False)
|
|
543
|
+
)
|
|
544
|
+
|
|
545
|
+
if success_flag:
|
|
546
|
+
tx_hash = (
|
|
547
|
+
result.get("transaction_hash")
|
|
548
|
+
or result.get("extrinsic_hash")
|
|
549
|
+
if isinstance(result, dict)
|
|
550
|
+
else getattr(result, "transaction_hash", None)
|
|
551
|
+
or getattr(result, "extrinsic_hash", None)
|
|
552
|
+
)
|
|
553
|
+
block_number = (
|
|
554
|
+
result.get("block_number") if isinstance(result, dict) else getattr(result, "block_number", None)
|
|
555
|
+
)
|
|
556
|
+
block_hash = (
|
|
557
|
+
result.get("block_hash") if isinstance(result, dict) else getattr(result, "block_hash", None)
|
|
558
|
+
)
|
|
559
|
+
|
|
560
|
+
content = f"""[htcli.success]✅ Node Removed Successfully[/htcli.success]
|
|
561
|
+
|
|
562
|
+
[htcli.value]Subnet ID:[/htcli.value] {subnet_id}
|
|
563
|
+
[htcli.value]Node ID:[/htcli.value] {node_id}
|
|
564
|
+
[htcli.value]Transaction Hash:[/htcli.value] {tx_hash or "N/A"}
|
|
565
|
+
[htcli.value]Block Number:[/htcli.value] {block_number or "N/A"}
|
|
566
|
+
"""
|
|
567
|
+
if block_hash:
|
|
568
|
+
content += f"[htcli.value]Block Hash:[/htcli.value] {block_hash}\n"
|
|
569
|
+
|
|
570
|
+
panel = HTCLIPanel(
|
|
571
|
+
content,
|
|
572
|
+
title="🗑️ Node Removal Complete",
|
|
573
|
+
border_style="htcli.success",
|
|
574
|
+
highlight=True,
|
|
575
|
+
)
|
|
576
|
+
panel.render(console.console)
|
|
577
|
+
else:
|
|
578
|
+
error_msg = result.get("error", "Unknown error") if isinstance(result, dict) else getattr(
|
|
579
|
+
result, "error", "Unknown error"
|
|
580
|
+
)
|
|
581
|
+
console.print(f"[htcli.error]❌ Failed to remove node: {error_msg}[/]")
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
def display_node_lifecycle_result(
|
|
585
|
+
result: Any,
|
|
586
|
+
subnet_id: int,
|
|
587
|
+
node_id: int,
|
|
588
|
+
action: str,
|
|
589
|
+
):
|
|
590
|
+
"""Display subnet node activation/deactivation result."""
|
|
591
|
+
success_flag = (
|
|
592
|
+
result.get("success", False)
|
|
593
|
+
if isinstance(result, dict)
|
|
594
|
+
else getattr(result, "success", False)
|
|
595
|
+
)
|
|
596
|
+
action_title = action.title()
|
|
597
|
+
|
|
598
|
+
if success_flag:
|
|
599
|
+
tx_hash = (
|
|
600
|
+
result.get("transaction_hash") or result.get("extrinsic_hash")
|
|
601
|
+
if isinstance(result, dict)
|
|
602
|
+
else getattr(result, "transaction_hash", None)
|
|
603
|
+
or getattr(result, "extrinsic_hash", None)
|
|
604
|
+
)
|
|
605
|
+
block_number = (
|
|
606
|
+
result.get("block_number")
|
|
607
|
+
if isinstance(result, dict)
|
|
608
|
+
else getattr(result, "block_number", None)
|
|
609
|
+
)
|
|
610
|
+
block_hash = (
|
|
611
|
+
result.get("block_hash")
|
|
612
|
+
if isinstance(result, dict)
|
|
613
|
+
else getattr(result, "block_hash", None)
|
|
614
|
+
)
|
|
615
|
+
|
|
616
|
+
content = f"""[htcli.success]✅ Node {action_title}d Successfully[/htcli.success]
|
|
617
|
+
|
|
618
|
+
[htcli.value]Subnet ID:[/htcli.value] {subnet_id}
|
|
619
|
+
[htcli.value]Node ID:[/htcli.value] {node_id}
|
|
620
|
+
[htcli.value]Transaction Hash:[/htcli.value] {tx_hash or "N/A"}
|
|
621
|
+
[htcli.value]Block Number:[/htcli.value] {block_number or "N/A"}
|
|
622
|
+
"""
|
|
623
|
+
if block_hash:
|
|
624
|
+
content += f"[htcli.value]Block Hash:[/htcli.value] {block_hash}\n"
|
|
625
|
+
|
|
626
|
+
panel = HTCLIPanel(
|
|
627
|
+
content,
|
|
628
|
+
title=f"Node {action_title} Complete",
|
|
629
|
+
border_style="htcli.success",
|
|
630
|
+
highlight=True,
|
|
631
|
+
)
|
|
632
|
+
panel.render(console.console)
|
|
633
|
+
else:
|
|
634
|
+
error_msg = (
|
|
635
|
+
result.get("error", "Unknown error")
|
|
636
|
+
if isinstance(result, dict)
|
|
637
|
+
else getattr(result, "error", "Unknown error")
|
|
638
|
+
)
|
|
639
|
+
console.print(f"[htcli.error]❌ Failed to {action} node: {error_msg}[/]")
|
|
640
|
+
|
|
641
|
+
|
|
642
|
+
def display_node_update_summary(
|
|
643
|
+
subnet_id: int,
|
|
644
|
+
node_id: int,
|
|
645
|
+
updates: list[dict[str, Any]],
|
|
646
|
+
client: Optional[Any] = None,
|
|
647
|
+
):
|
|
648
|
+
"""
|
|
649
|
+
Display a consolidated summary of multiple node updates in a single panel.
|
|
650
|
+
|
|
651
|
+
Each entry in `updates` should be a dict with:
|
|
652
|
+
- field: str
|
|
653
|
+
- old: Any
|
|
654
|
+
- new: Any
|
|
655
|
+
- result: dict or response object
|
|
656
|
+
"""
|
|
657
|
+
if not updates:
|
|
658
|
+
return
|
|
659
|
+
|
|
660
|
+
def format_field_name(field: str) -> str:
|
|
661
|
+
return field.replace("_", " ").title()
|
|
662
|
+
|
|
663
|
+
def get_transaction_hash(result: Any) -> Optional[str]:
|
|
664
|
+
if isinstance(result, dict):
|
|
665
|
+
return result.get("transaction_hash") or result.get("extrinsic_hash")
|
|
666
|
+
return (
|
|
667
|
+
getattr(result, "transaction_hash", None)
|
|
668
|
+
or getattr(result, "extrinsic_hash", None)
|
|
669
|
+
)
|
|
670
|
+
|
|
671
|
+
def get_block_hash(result: Any) -> Optional[str]:
|
|
672
|
+
if isinstance(result, dict):
|
|
673
|
+
return result.get("block_hash")
|
|
674
|
+
return getattr(result, "block_hash", None)
|
|
675
|
+
|
|
676
|
+
def get_block_number(result: Any) -> Optional[int]:
|
|
677
|
+
if isinstance(result, dict):
|
|
678
|
+
return result.get("block_number")
|
|
679
|
+
return getattr(result, "block_number", None)
|
|
680
|
+
|
|
681
|
+
def is_success(result: Any) -> bool:
|
|
682
|
+
if isinstance(result, dict):
|
|
683
|
+
return bool(result.get("success", False))
|
|
684
|
+
return bool(getattr(result, "success", False))
|
|
685
|
+
|
|
686
|
+
lines: list[str] = [
|
|
687
|
+
success(
|
|
688
|
+
f"✅ Node {node_id} on subnet {subnet_id} updated successfully!"
|
|
689
|
+
)
|
|
690
|
+
]
|
|
691
|
+
lines.append("") # blank line
|
|
692
|
+
|
|
693
|
+
for update in updates:
|
|
694
|
+
field = update.get("field")
|
|
695
|
+
old = update.get("old")
|
|
696
|
+
new = update.get("new")
|
|
697
|
+
result = update.get("result")
|
|
698
|
+
|
|
699
|
+
field_display = format_field_name(str(field))
|
|
700
|
+
status_text = "[green]Success[/green]" if is_success(result) else "[htcli.error]Failed[/htcli.error]"
|
|
701
|
+
lines.append(f"[htcli.value]{field_display}:[/] {status_text}")
|
|
702
|
+
|
|
703
|
+
if old is not None or new is not None:
|
|
704
|
+
old_disp = str(old) if old is not None else "[dim]N/A[/dim]"
|
|
705
|
+
new_disp = str(new) if new is not None else "[dim]N/A[/dim]"
|
|
706
|
+
lines.append(f" [htcli.subtitle]Old:[/] {old_disp}")
|
|
707
|
+
lines.append(f" [htcli.subtitle]New:[/] {new_disp}")
|
|
708
|
+
|
|
709
|
+
tx_hash = get_transaction_hash(result)
|
|
710
|
+
block_hash = get_block_hash(result)
|
|
711
|
+
block_number = get_block_number(result)
|
|
712
|
+
|
|
713
|
+
if not block_number and block_hash and client and getattr(client, "rpc", None) and getattr(
|
|
714
|
+
client.rpc, "chain", None
|
|
715
|
+
):
|
|
716
|
+
try:
|
|
717
|
+
block_number = client.rpc.chain.get_block_number(block_hash=block_hash)
|
|
718
|
+
except Exception:
|
|
719
|
+
pass
|
|
720
|
+
|
|
721
|
+
if tx_hash:
|
|
722
|
+
lines.append(
|
|
723
|
+
f" [htcli.subtitle]Transaction Hash:[/] [htcli.hash]{tx_hash}[/]"
|
|
724
|
+
)
|
|
725
|
+
if block_number:
|
|
726
|
+
lines.append(
|
|
727
|
+
f" [htcli.subtitle]Block Number:[/] [htcli.value]{block_number}[/]"
|
|
728
|
+
)
|
|
729
|
+
if block_hash:
|
|
730
|
+
lines.append(
|
|
731
|
+
f" [htcli.subtitle]Block Hash:[/] [htcli.hash]{block_hash}[/]"
|
|
732
|
+
)
|
|
733
|
+
lines.append(
|
|
734
|
+
)
|
|
735
|
+
|
|
736
|
+
lines.append("") # space between updates
|
|
737
|
+
|
|
738
|
+
if lines and lines[-1] == "":
|
|
739
|
+
lines.pop()
|
|
740
|
+
|
|
741
|
+
content = "\n".join(lines)
|
|
742
|
+
|
|
743
|
+
panel = HTCLIPanel(
|
|
744
|
+
content,
|
|
745
|
+
title="✅ Node Updates Complete",
|
|
746
|
+
border_style="htcli.success",
|
|
747
|
+
highlight=True,
|
|
748
|
+
)
|
|
749
|
+
panel.render(console.console)
|