htcli 1.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. htcli-1.1.0.dist-info/METADATA +509 -0
  2. htcli-1.1.0.dist-info/RECORD +140 -0
  3. htcli-1.1.0.dist-info/WHEEL +4 -0
  4. htcli-1.1.0.dist-info/entry_points.txt +2 -0
  5. htcli-1.1.0.dist-info/licenses/LICENSE +21 -0
  6. src/__init__.py +0 -0
  7. src/htcli/__init__.py +5 -0
  8. src/htcli/client/__init__.py +338 -0
  9. src/htcli/client/extrinsics/__init__.py +26 -0
  10. src/htcli/client/extrinsics/base.py +487 -0
  11. src/htcli/client/extrinsics/consensus.py +79 -0
  12. src/htcli/client/extrinsics/governance.py +714 -0
  13. src/htcli/client/extrinsics/identity.py +490 -0
  14. src/htcli/client/extrinsics/node.py +1054 -0
  15. src/htcli/client/extrinsics/overwatch.py +401 -0
  16. src/htcli/client/extrinsics/staking.py +1504 -0
  17. src/htcli/client/extrinsics/subnet.py +2218 -0
  18. src/htcli/client/extrinsics/validator.py +203 -0
  19. src/htcli/client/extrinsics/wallet.py +323 -0
  20. src/htcli/client/offchain/__init__.py +10 -0
  21. src/htcli/client/offchain/backup.py +385 -0
  22. src/htcli/client/offchain/config.py +541 -0
  23. src/htcli/client/offchain/wallet.py +839 -0
  24. src/htcli/client/rpc/__init__.py +20 -0
  25. src/htcli/client/rpc/chain.py +568 -0
  26. src/htcli/client/rpc/node.py +783 -0
  27. src/htcli/client/rpc/overwatch.py +680 -0
  28. src/htcli/client/rpc/staking.py +216 -0
  29. src/htcli/client/rpc/subnet.py +2104 -0
  30. src/htcli/client/rpc/wallet.py +912 -0
  31. src/htcli/commands/__init__.py +31 -0
  32. src/htcli/commands/chain/__init__.py +66 -0
  33. src/htcli/commands/chain/display.py +204 -0
  34. src/htcli/commands/chain/handlers.py +260 -0
  35. src/htcli/commands/config/__init__.py +158 -0
  36. src/htcli/commands/config/display.py +353 -0
  37. src/htcli/commands/config/handlers.py +347 -0
  38. src/htcli/commands/config/prompts.py +357 -0
  39. src/htcli/commands/consensus/__init__.py +61 -0
  40. src/htcli/commands/consensus/handlers.py +100 -0
  41. src/htcli/commands/governance/__init__.py +49 -0
  42. src/htcli/commands/governance/handlers.py +81 -0
  43. src/htcli/commands/node/__init__.py +304 -0
  44. src/htcli/commands/node/display.py +749 -0
  45. src/htcli/commands/node/error_handling.py +470 -0
  46. src/htcli/commands/node/handlers.py +844 -0
  47. src/htcli/commands/node/prompts.py +346 -0
  48. src/htcli/commands/overwatch/__init__.py +219 -0
  49. src/htcli/commands/overwatch/display.py +396 -0
  50. src/htcli/commands/overwatch/error_handling.py +276 -0
  51. src/htcli/commands/overwatch/handlers.py +443 -0
  52. src/htcli/commands/overwatch/prompts.py +359 -0
  53. src/htcli/commands/stake/__init__.py +736 -0
  54. src/htcli/commands/stake/display.py +1103 -0
  55. src/htcli/commands/stake/error_handling.py +425 -0
  56. src/htcli/commands/stake/handlers.py +1902 -0
  57. src/htcli/commands/stake/prompts.py +1080 -0
  58. src/htcli/commands/subnet/__init__.py +639 -0
  59. src/htcli/commands/subnet/display.py +801 -0
  60. src/htcli/commands/subnet/error_handling.py +524 -0
  61. src/htcli/commands/subnet/handlers.py +2855 -0
  62. src/htcli/commands/subnet/prompts.py +1225 -0
  63. src/htcli/commands/validator/__init__.py +192 -0
  64. src/htcli/commands/validator/display.py +54 -0
  65. src/htcli/commands/validator/handlers.py +340 -0
  66. src/htcli/commands/wallet/__init__.py +546 -0
  67. src/htcli/commands/wallet/display.py +806 -0
  68. src/htcli/commands/wallet/error_handling.py +210 -0
  69. src/htcli/commands/wallet/handlers.py +3040 -0
  70. src/htcli/commands/wallet/prompts.py +1518 -0
  71. src/htcli/config.py +184 -0
  72. src/htcli/dependencies.py +186 -0
  73. src/htcli/errors/__init__.py +63 -0
  74. src/htcli/errors/base.py +141 -0
  75. src/htcli/errors/display.py +20 -0
  76. src/htcli/errors/handlers.py +710 -0
  77. src/htcli/main.py +343 -0
  78. src/htcli/models/__init__.py +21 -0
  79. src/htcli/models/enums/enum_types.py +35 -0
  80. src/htcli/models/errors.py +103 -0
  81. src/htcli/models/requests/__init__.py +197 -0
  82. src/htcli/models/requests/config.py +70 -0
  83. src/htcli/models/requests/consensus.py +19 -0
  84. src/htcli/models/requests/governance.py +38 -0
  85. src/htcli/models/requests/identity.py +51 -0
  86. src/htcli/models/requests/key.py +22 -0
  87. src/htcli/models/requests/node.py +91 -0
  88. src/htcli/models/requests/overwatch.py +64 -0
  89. src/htcli/models/requests/staking.py +580 -0
  90. src/htcli/models/requests/subnet.py +195 -0
  91. src/htcli/models/requests/validator.py +139 -0
  92. src/htcli/models/requests/wallet.py +118 -0
  93. src/htcli/models/responses/__init__.py +147 -0
  94. src/htcli/models/responses/base.py +18 -0
  95. src/htcli/models/responses/chain.py +39 -0
  96. src/htcli/models/responses/config.py +58 -0
  97. src/htcli/models/responses/identity.py +102 -0
  98. src/htcli/models/responses/overwatch.py +51 -0
  99. src/htcli/models/responses/staking.py +502 -0
  100. src/htcli/models/responses/subnet.py +856 -0
  101. src/htcli/models/responses/wallet.py +185 -0
  102. src/htcli/ui/__init__.py +87 -0
  103. src/htcli/ui/colors.py +309 -0
  104. src/htcli/ui/components/__init__.py +60 -0
  105. src/htcli/ui/components/panels.py +174 -0
  106. src/htcli/ui/components/progress.py +166 -0
  107. src/htcli/ui/components/spinners.py +92 -0
  108. src/htcli/ui/components/tables.py +809 -0
  109. src/htcli/ui/components/trees.py +721 -0
  110. src/htcli/ui/display.py +336 -0
  111. src/htcli/ui/prompts.py +870 -0
  112. src/htcli/utils/__init__.py +76 -0
  113. src/htcli/utils/blockchain/__init__.py +75 -0
  114. src/htcli/utils/blockchain/formatting.py +368 -0
  115. src/htcli/utils/blockchain/patches.py +286 -0
  116. src/htcli/utils/blockchain/peer_id.py +186 -0
  117. src/htcli/utils/blockchain/staking.py +448 -0
  118. src/htcli/utils/blockchain/type_registry.py +1373 -0
  119. src/htcli/utils/blockchain/validation.py +179 -0
  120. src/htcli/utils/cache.py +613 -0
  121. src/htcli/utils/constants.py +38 -0
  122. src/htcli/utils/legacy/__init__.py +12 -0
  123. src/htcli/utils/legacy/colors.py +311 -0
  124. src/htcli/utils/legacy/crypto.py +1176 -0
  125. src/htcli/utils/legacy/formatting.py +452 -0
  126. src/htcli/utils/legacy/interactive.py +306 -0
  127. src/htcli/utils/legacy/subnet_manifest.py +265 -0
  128. src/htcli/utils/legacy/validation.py +488 -0
  129. src/htcli/utils/logging.py +183 -0
  130. src/htcli/utils/network/__init__.py +20 -0
  131. src/htcli/utils/network/subnet.py +344 -0
  132. src/htcli/utils/prompts.py +27 -0
  133. src/htcli/utils/scale_codec.py +155 -0
  134. src/htcli/utils/validation/__init__.py +57 -0
  135. src/htcli/utils/validation/prompt_validators.py +267 -0
  136. src/htcli/utils/wallet/__init__.py +65 -0
  137. src/htcli/utils/wallet/auth.py +151 -0
  138. src/htcli/utils/wallet/core.py +1069 -0
  139. src/htcli/utils/wallet/crypto.py +1615 -0
  140. src/htcli/utils/wallet/migration.py +159 -0
@@ -0,0 +1,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)