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,806 @@
1
+ """
2
+ Wallet command display logic.
3
+
4
+ Handles formatting and displaying results for wallet operations using HTCLI UI components.
5
+ """
6
+
7
+ from typing import Optional
8
+
9
+ from rich.padding import Padding
10
+
11
+ from ...models.responses import (
12
+ WalletCreateResponse,
13
+ WalletDeleteResponse,
14
+ WalletListResponse,
15
+ WalletStatusResponse,
16
+ WalletTransferResponse,
17
+ WalletUpdateResponse,
18
+ )
19
+ from ...ui.colors import address, address_full, error, info, primary, success, warning
20
+ from ...ui.components import (
21
+ HTCLIPanel,
22
+ HTCLITable,
23
+ create_htcli_balance_table,
24
+ create_wallet_hierarchy_tree,
25
+ create_wallet_minimal_table,
26
+ )
27
+ from ...ui.display import (
28
+ HTCLIConsole,
29
+ print_error,
30
+ print_info,
31
+ print_success,
32
+ print_warning,
33
+ )
34
+ from ...ui.prompts import confirm_prompt
35
+
36
+ console = HTCLIConsole()
37
+
38
+
39
+ def _copy_to_clipboard(text: str):
40
+ """Copy text to clipboard if pyperclip is installed."""
41
+ try:
42
+ import pyperclip
43
+
44
+ pyperclip.copy(text)
45
+ print_success("📋 Copied to clipboard!", emoji=False)
46
+ except ImportError:
47
+ print_warning("📋 Could not copy to clipboard.", emoji=False)
48
+ print_info(
49
+ "Install pyperclip for automatic copying: pip install pyperclip",
50
+ emoji=False,
51
+ )
52
+ console.print("Please copy the phrase manually.")
53
+
54
+
55
+ def display_wallet_creation_result(
56
+ response: WalletCreateResponse,
57
+ copy_mnemonic: bool = False,
58
+ owner_name: Optional[str] = None,
59
+ owner_address: Optional[str] = None,
60
+ preseeded_name: Optional[str] = None,
61
+ ):
62
+ """Display wallet creation results."""
63
+ print_success("Wallet created successfully!")
64
+
65
+ # Display key details in a table
66
+ table = HTCLITable(title="Wallet Details")
67
+ table.add_column("Property", style="white")
68
+ table.add_column("Value", style="dim")
69
+ table.add_row("Name", response.name)
70
+ table.add_row("Type", response.wallet_type.title())
71
+ table.add_row("Key Type", response.key_type.upper())
72
+ table.add_row("Address", address(response.address))
73
+ table.add_row("Public Key", response.public_key)
74
+ if owner_name and owner_address:
75
+ table.add_row("Owner", f"{owner_name} ({address(owner_address)})")
76
+ if preseeded_name:
77
+ table.add_row("Preseeded From", preseeded_name.title())
78
+ table.add_row("Encryption", "🔒 Enabled" if response.encrypted else "Disabled")
79
+ console.print(table.table)
80
+
81
+ # Mnemonic display in a panel
82
+ if response.mnemonic:
83
+ # Display mnemonic with separate subtitle
84
+ display_mnemonic_with_copy_option(
85
+ response.mnemonic,
86
+ response.name,
87
+ response.wallet_type.title(),
88
+ copy_mnemonic=copy_mnemonic,
89
+ )
90
+
91
+
92
+ def display_mnemonic_panel(
93
+ mnemonic: str, wallet_name: str, wallet_type: str = "Coldkey"
94
+ ):
95
+ """Display mnemonic in a beautiful panel."""
96
+ words = mnemonic.split()
97
+
98
+ # Create a table for evenly spaced words
99
+ mnemonic_table = HTCLITable(
100
+ show_header=False,
101
+ show_lines=False,
102
+ show_edge=False,
103
+ border_style="red",
104
+ padding=(0, 1),
105
+ )
106
+
107
+ # Add columns for 4 words per row
108
+ for _ in range(4):
109
+ mnemonic_table.add_column("", justify="center", width=20)
110
+
111
+ # Add rows of 4 words each
112
+ for i in range(0, 12, 4):
113
+ row_words = []
114
+ for j in range(4):
115
+ if i + j < len(words):
116
+ word_num = i + j + 1
117
+ row_words.append(f"{word_num:2d}. {words[i + j]}")
118
+ else:
119
+ row_words.append("")
120
+ mnemonic_table.add_row(*row_words)
121
+
122
+ panel = HTCLIPanel(
123
+ mnemonic_table.table,
124
+ title=f"[bold red]🔐 {wallet_type} Recovery Phrase[/bold red]",
125
+ border_style="red",
126
+ padding=(1, 2),
127
+ highlight=True,
128
+ )
129
+ console.print(panel.panel)
130
+
131
+
132
+ def display_mnemonic_with_copy_option(
133
+ mnemonic: str,
134
+ wallet_name: str,
135
+ wallet_type: str = "Coldkey",
136
+ copy_mnemonic: bool = False,
137
+ ):
138
+ """Display mnemonic and offer clipboard copy option.
139
+
140
+ Args:
141
+ mnemonic: The recovery phrase to display
142
+ wallet_name: Name of the wallet
143
+ wallet_type: Type of wallet (Coldkey/Hotkey)
144
+ copy_mnemonic: If True, automatically copy to clipboard without prompting
145
+ """
146
+ # Display the mnemonic in a beautiful panel only
147
+ display_mnemonic_panel(mnemonic, wallet_name, wallet_type)
148
+
149
+ # Display subtitle separately
150
+ console.print(
151
+ "[bold yellow]Store this securely. It is the only way to recover your wallet.[/bold yellow]"
152
+ )
153
+ console.print()
154
+
155
+ # Handle clipboard copy
156
+ if copy_mnemonic:
157
+ # Automatically copy if flag is set
158
+ _copy_to_clipboard(mnemonic)
159
+ else:
160
+ # Offer clipboard copy only if flag is not set
161
+ if confirm_prompt("Copy recovery phrase to clipboard?", default=False):
162
+ _copy_to_clipboard(mnemonic)
163
+
164
+ # Final security reminder
165
+ console.print("\n[bold red]🔒 Security Reminder:[/bold red]")
166
+ console.print("• Store this recovery phrase in a secure, offline location")
167
+ console.print("• Never share it with anyone")
168
+ console.print("• Consider using a hardware wallet for additional security")
169
+ console.print("• Test your recovery process in a safe environment")
170
+ console.print()
171
+
172
+
173
+ def display_wallet_generation_result(
174
+ coldkey_response: WalletCreateResponse,
175
+ hotkey_response: WalletCreateResponse,
176
+ copy_mnemonic: bool = False,
177
+ ):
178
+ """Display unified results for wallet generation (both coldkey and hotkey)."""
179
+ console.print()
180
+ print_success("Wallet created successfully!")
181
+ console.print(primary("Both coldkey and hotkey have been generated.\n"))
182
+
183
+ # Create a unified summary table
184
+ summary_table = HTCLITable(title="Wallet Summary")
185
+ summary_table.add_column("Property", style="bold white")
186
+ summary_table.add_column("Coldkey", style="cyan")
187
+ summary_table.add_column("Hotkey", style="yellow")
188
+
189
+ summary_table.add_row("Name", coldkey_response.name, hotkey_response.name)
190
+ summary_table.add_row(
191
+ "Address", address(coldkey_response.address), address(hotkey_response.address)
192
+ )
193
+ summary_table.add_row(
194
+ "Key Type", coldkey_response.key_type.upper(), hotkey_response.key_type.upper()
195
+ )
196
+ summary_table.add_row(
197
+ "Encryption",
198
+ "🔒 Enabled" if coldkey_response.encrypted else "Disabled",
199
+ "🔒 Enabled" if hotkey_response.encrypted else "Disabled",
200
+ )
201
+ summary_table.add_row(
202
+ "Owner", "—", f"{coldkey_response.name} ({address(coldkey_response.address)})"
203
+ )
204
+
205
+ console.print(summary_table.table)
206
+ console.print()
207
+
208
+ # Display both mnemonics in a unified format
209
+ console.print("[bold red]🔐 Recovery Phrases[/bold red]\n")
210
+ console.print(
211
+ "[bold yellow]⚠️ CRITICAL: Store these phrases securely. They are the only way to recover your wallets.[/bold yellow]\n"
212
+ )
213
+
214
+ # Coldkey mnemonic
215
+ console.print("[bold cyan]📘 Coldkey Recovery Phrase:[/bold cyan]")
216
+ display_mnemonic_panel(coldkey_response.mnemonic, coldkey_response.name, "Coldkey")
217
+ console.print()
218
+
219
+ # Hotkey mnemonic
220
+ console.print("[bold yellow]📗 Hotkey Recovery Phrase:[/bold yellow]")
221
+ display_mnemonic_panel(hotkey_response.mnemonic, hotkey_response.name, "Hotkey")
222
+ console.print()
223
+
224
+ # Handle clipboard copy
225
+ if copy_mnemonic:
226
+ # Copy both mnemonics (coldkey first, then hotkey)
227
+ combined = (
228
+ f"Coldkey: {coldkey_response.mnemonic}\nHotkey: {hotkey_response.mnemonic}"
229
+ )
230
+ _copy_to_clipboard(combined)
231
+ else:
232
+ # Offer to copy each mnemonic separately
233
+ if confirm_prompt("Copy coldkey recovery phrase to clipboard?", default=False):
234
+ _copy_to_clipboard(coldkey_response.mnemonic)
235
+ if confirm_prompt("Copy hotkey recovery phrase to clipboard?", default=False):
236
+ _copy_to_clipboard(hotkey_response.mnemonic)
237
+
238
+ # Final security reminder
239
+ console.print("\n[bold red]🔒 Security Reminder:[/bold red]")
240
+ console.print("• Store both recovery phrases in a secure, offline location")
241
+ console.print("• Never share them with anyone")
242
+ console.print("• Consider using a hardware wallet for additional security")
243
+ console.print("• Test your recovery process in a safe environment")
244
+ console.print("• Keep coldkey and hotkey phrases separate and clearly labeled")
245
+ console.print()
246
+
247
+
248
+ def display_wallet_restoration_result(
249
+ response: WalletCreateResponse,
250
+ import_method: str,
251
+ owner_name: Optional[str] = None,
252
+ owner_address: Optional[str] = None,
253
+ ):
254
+ """Display wallet restoration results."""
255
+ print_success(f"Wallet restored successfully from {import_method}!")
256
+ console.print()
257
+
258
+ # Display key details in a table
259
+ table = HTCLITable(title="Restored Wallet Details")
260
+ table.add_column("Property", style="bold #5EE7FF")
261
+ table.add_column("Value", style="#CBD5E0")
262
+ table.add_row("Name", response.name)
263
+ table.add_row("Type", response.wallet_type.title())
264
+ table.add_row("Key Type", response.key_type.upper())
265
+ table.add_row("Address", address(response.address))
266
+ table.add_row("Public Key", response.public_key)
267
+ if owner_name and owner_address:
268
+ table.add_row("Owner", f"{owner_name} ({address(owner_address)})")
269
+ table.add_row("Encryption", "🔒 Enabled" if response.encrypted else "Disabled")
270
+ table.render()
271
+
272
+
273
+ def display_wallet_list(response: WalletListResponse, format_type: str = "tree"):
274
+ """Display wallet list using existing patterns."""
275
+ if not response.wallets:
276
+ # Show friendly panel when no wallets exist
277
+ content = """[htcli.info]📭 No wallets found in your wallet directory.[/htcli.info]
278
+
279
+ [htcli.subtitle]💡 To create your first wallet, use one of these commands:[/htcli.subtitle]
280
+
281
+ [htcli.value]• Generate a new coldkey:[/htcli.value]
282
+ [bold cyan]htcli wallet generate-coldkey --name <wallet-name>[/bold cyan]
283
+
284
+ [htcli.value]• Generate a new hotkey:[/htcli.value]
285
+ [bold cyan]htcli wallet generate-hotkey --name <hotkey-name> --owner <coldkey-name>[/bold cyan]
286
+
287
+ [htcli.value]• Restore from mnemonic:[/htcli.value]
288
+ [bold cyan]htcli wallet restore-coldkey --name <wallet-name>[/bold cyan]
289
+
290
+ [htcli.info]For more information, run: [bold]htcli wallet --help[/bold][/htcli.info]"""
291
+
292
+ panel = HTCLIPanel(
293
+ content,
294
+ title="💼 No Wallets Found",
295
+ border_style="htcli.info",
296
+ highlight=True,
297
+ )
298
+ panel.render(console.console)
299
+ console.print()
300
+ return
301
+
302
+ if format_type == "json":
303
+ console.print_json(data=response.wallets)
304
+ elif format_type == "table":
305
+ table = create_wallet_minimal_table(response.wallets)
306
+ console.print(table.table)
307
+ # Show summary
308
+ console.print(
309
+ f"\n[bold]Summary:[/bold] {response.coldkeys} coldkeys, {response.hotkeys} hotkeys"
310
+ )
311
+ else: # tree format
312
+ _ = create_wallet_hierarchy_tree(response.wallets)
313
+
314
+
315
+ def display_wallet_status(response: WalletStatusResponse, format_type: str = "table"):
316
+ """Display wallet status using existing patterns."""
317
+ if not response.wallets:
318
+ print_warning("No wallets found.")
319
+ print_info("Create a wallet with: htcli wallet generate-coldkey")
320
+ return
321
+
322
+ console.print(primary("🔐 Your Blockchain Identity"))
323
+ console.print(f" Total Keys: {response.total_keys}")
324
+ console.print(f" Total Addresses: {response.total_addresses}")
325
+ console.print()
326
+
327
+ if format_type == "json":
328
+ console.print_json(data=response.wallets)
329
+ else:
330
+ table = create_wallet_minimal_table(response.wallets)
331
+ table.render()
332
+ # Show summary
333
+ console.print(
334
+ f"\n[bold]Summary:[/bold] {response.total_keys} keys, {response.total_addresses} addresses"
335
+ )
336
+
337
+
338
+ def display_balance_info(address: str, balance: float = 0):
339
+ """Display helpful information when balance is 0."""
340
+ if balance == 0:
341
+ console.print(
342
+ "\n[bold yellow]💡 Note:[/bold yellow] This wallet has no balance."
343
+ )
344
+ console.print(f"• To receive funds, share this address: [bold]{address}[/bold]")
345
+ console.print(
346
+ "• You can transfer funds from another wallet using: [bold]htcli wallet transfer[/bold]"
347
+ )
348
+ console.print("• Transaction fees are typically around 0.001-0.01 TENSOR")
349
+
350
+
351
+ def display_all_wallet_balances(
352
+ wallet_balances: list[dict],
353
+ totals: dict,
354
+ format_type: str,
355
+ network_name: Optional[str] = None,
356
+ ):
357
+ """Display balance for all wallets using the new standardized table."""
358
+ if not wallet_balances:
359
+ print_warning("No wallets found or balances retrieved.")
360
+ return
361
+
362
+ if format_type == "json":
363
+ json_data = {
364
+ "wallets": [
365
+ {
366
+ "name": wb.get("name"),
367
+ "address": wb.get("address"),
368
+ "free_balance": wb.get("free_balance", 0),
369
+ "direct_stake": wb.get("direct_stake", 0),
370
+ "delegate_stake": wb.get("delegate_stake", 0),
371
+ "node_delegate_stake": wb.get("node_delegate_stake", 0),
372
+ "overwatch_stake": wb.get("overwatch_stake", 0),
373
+ "unbonding": wb.get("unbonding", 0),
374
+ "staked_balance": wb.get("staked_balance", 0),
375
+ "total_balance": wb.get("total_balance", 0),
376
+ }
377
+ for wb in wallet_balances
378
+ ],
379
+ "totals": totals,
380
+ "network": network_name,
381
+ }
382
+ console.print_json(data=json_data)
383
+ else:
384
+ table = create_htcli_balance_table(
385
+ wallet_balances, totals, network_name=network_name, show_totals=True
386
+ )
387
+ console.print(Padding(table, (0, 0, 1, 0)))
388
+
389
+
390
+ def display_single_wallet_balance(
391
+ response,
392
+ wallet_name: Optional[str],
393
+ wallet_type: str,
394
+ format_type: str,
395
+ show_guidance: bool,
396
+ staking_balance: int = 0,
397
+ staking_breakdown: Optional[dict] = None,
398
+ network_name: Optional[str] = None,
399
+ ):
400
+ """Display the balance for a single wallet or address using the new standardized table."""
401
+ breakdown = _normalize_staking_breakdown(staking_breakdown, staking_balance)
402
+ staking_balance = breakdown["total"]
403
+ balance_wei = _response_balance(response)
404
+
405
+ if format_type == "json":
406
+ data = response.dict() if hasattr(response, "dict") else response
407
+ if isinstance(data, dict):
408
+ data["staking_balance"] = staking_balance
409
+ data["staking_breakdown"] = {
410
+ "direct_stake": breakdown["direct_stake"],
411
+ "delegate_stake": breakdown["delegate_stake"],
412
+ "node_delegate_stake": breakdown["node_delegate_stake"],
413
+ "overwatch_stake": breakdown["overwatch_stake"],
414
+ "unbonding": breakdown["unbonding"],
415
+ }
416
+ data["total_balance"] = balance_wei + staking_balance
417
+ console.print_json(data=data)
418
+ return
419
+
420
+ # BalanceResponse has fields directly - convert from wei to TENSOR for display
421
+ total_balance = balance_wei + staking_balance
422
+
423
+ table = create_htcli_balance_table(
424
+ [
425
+ {
426
+ "name": wallet_name or wallet_type,
427
+ "address": response.address or "Unknown",
428
+ "free_balance": balance_wei,
429
+ "direct_stake": breakdown["direct_stake"],
430
+ "delegate_stake": breakdown["delegate_stake"],
431
+ "node_delegate_stake": breakdown["node_delegate_stake"],
432
+ "overwatch_stake": breakdown["overwatch_stake"],
433
+ "unbonding": breakdown["unbonding"],
434
+ "staked_balance": staking_balance,
435
+ "total_balance": total_balance,
436
+ }
437
+ ],
438
+ totals={
439
+ "free": balance_wei,
440
+ "direct_stake": breakdown["direct_stake"],
441
+ "delegate_stake": breakdown["delegate_stake"],
442
+ "node_delegate_stake": breakdown["node_delegate_stake"],
443
+ "overwatch_stake": breakdown["overwatch_stake"],
444
+ "unbonding": breakdown["unbonding"],
445
+ "staked": staking_balance,
446
+ "total": total_balance,
447
+ },
448
+ network_name=network_name,
449
+ show_totals=False,
450
+ )
451
+ console.print(Padding(table, (0, 0, 1, 0)))
452
+
453
+ # Display additional details in a panel below the table
454
+ # TODO: Re-enable panel after fixing display issues
455
+ # from ...ui.components import HTCLIPanel
456
+
457
+ # balance_wei = response.balance or 0
458
+ # details_content = f"""[htcli.accent]Account Information[/htcli.accent]
459
+
460
+ # [htcli.value]Wallet Name:[/htcli.value] {wallet_name or "N/A"}
461
+ # [htcli.value]Wallet Type:[/htcli.value] {wallet_type}
462
+ # [htcli.value]Address:[/htcli.value] {response.address or "Unknown"}
463
+ # [htcli.value]Status:[/htcli.value] Active
464
+
465
+ # [htcli.accent]Balance Details[/htcli.accent]
466
+
467
+ # [htcli.value]Balance (TENSOR):[/htcli.value] {balance_tensor:,.2f} TENSOR
468
+ # [htcli.value]Balance (Wei):[/htcli.value] {balance_wei:,} wei
469
+ # [htcli.value]Available:[/htcli.value] {balance_tensor:,.2f} TENSOR
470
+ # """
471
+
472
+ # details_panel = HTCLIPanel(
473
+ # details_content,
474
+ # title="📊 Account Details",
475
+ # border_style="htcli.info",
476
+ # padding=(1, 2),
477
+ # )
478
+ # details_panel.render()
479
+
480
+ if show_guidance:
481
+ display_balance_info(response.address or "Unknown", response.balance or 0)
482
+
483
+
484
+ def _response_balance(response) -> int:
485
+ if isinstance(response, dict):
486
+ return int(response.get("balance", 0) or 0)
487
+ return int(getattr(response, "balance", 0) or 0)
488
+
489
+
490
+ def _normalize_staking_breakdown(
491
+ staking_breakdown: Optional[dict], staking_balance: int
492
+ ) -> dict:
493
+ component_keys = (
494
+ "direct_stake",
495
+ "delegate_stake",
496
+ "node_delegate_stake",
497
+ "overwatch_stake",
498
+ "unbonding",
499
+ )
500
+ breakdown = dict.fromkeys(component_keys, 0)
501
+
502
+ if staking_breakdown:
503
+ for key in component_keys:
504
+ breakdown[key] = int(staking_breakdown.get(key, 0) or 0)
505
+
506
+ component_total = sum(breakdown.values())
507
+ breakdown["total"] = int(
508
+ (staking_breakdown or {}).get("total", 0)
509
+ or component_total
510
+ or staking_balance
511
+ or 0
512
+ )
513
+ return breakdown
514
+
515
+
516
+ def display_wallet_transfer_result(
517
+ response: WalletTransferResponse, dry_run: bool = False
518
+ ):
519
+ """Display wallet transfer results with beautiful formatting."""
520
+ from ...ui.components import HTCLIPanel
521
+
522
+ # Format the amount nicely - handle string type
523
+ if isinstance(response.amount, str):
524
+ try:
525
+ amount_float = float(response.amount)
526
+ amount_str = f"{amount_float:,.4f} TENSOR"
527
+ except (ValueError, TypeError):
528
+ amount_str = f"{response.amount} TENSOR"
529
+ elif isinstance(response.amount, (int, float)):
530
+ amount_str = f"{response.amount:,.4f} TENSOR"
531
+ else:
532
+ amount_str = str(response.amount)
533
+
534
+ # Safely format addresses with checksum
535
+ from ...utils.wallet.crypto import format_address_display
536
+
537
+ from_addr = (
538
+ format_address_display(str(response.from_address), truncate=False)
539
+ if response.from_address
540
+ else "Unknown"
541
+ )
542
+ to_addr = (
543
+ format_address_display(str(response.to_address), truncate=False)
544
+ if response.to_address
545
+ else "Unknown"
546
+ )
547
+
548
+ # Build content string
549
+ content = f"""[htcli.success]✅ Transfer completed successfully![/htcli.success]
550
+
551
+ [htcli.value]From:[/htcli.value] [htcli.address]{from_addr}[/htcli.address]
552
+ [htcli.value]To:[/htcli.value] [htcli.address]{to_addr}[/htcli.address]
553
+ [htcli.value]Amount:[/htcli.value] [htcli.amount]{amount_str}[/htcli.amount]
554
+ """
555
+
556
+ # Add transaction details for real transfers
557
+ if not dry_run and response.transaction_hash:
558
+ content += f"\n[htcli.value]Transaction Hash:[/htcli.value] [htcli.subtitle]{response.transaction_hash}[/htcli.subtitle]\n"
559
+ # Add block hash if available
560
+ if response.block_hash:
561
+ content += f"[htcli.value]Block Hash:[/htcli.value] [htcli.subtitle]{response.block_hash}[/htcli.subtitle]\n"
562
+
563
+ # Display with HTCLIPanel
564
+ panel = HTCLIPanel(
565
+ content,
566
+ title="💸 Transfer Complete" if not dry_run else "🔍 Transfer Preview",
567
+ border_style="htcli.success" if not dry_run else "htcli.info",
568
+ highlight=True,
569
+ )
570
+ panel.render()
571
+
572
+
573
+ def display_wallet_update_result(response: WalletUpdateResponse):
574
+ """Display wallet update results."""
575
+ print_success("Wallet updated successfully!")
576
+ console.print()
577
+
578
+ console.print(f"[bold]Wallet:[/bold] {response.old_name} → {response.new_name}")
579
+ console.print(f"[bold]Address:[/bold] {address(response.address)}")
580
+ console.print(f"[bold]Key Type:[/bold] {response.key_type.upper()}")
581
+
582
+ console.print()
583
+ console.print("[bold]Updates:[/bold]")
584
+ console.print(
585
+ f" Name: {'✅ Updated' if response.name_updated else '❌ No change'}"
586
+ )
587
+ console.print(
588
+ f" Password: {'✅ Updated' if response.password_updated else '❌ No change'}"
589
+ )
590
+
591
+ if response.owner_updated is not None:
592
+ console.print(
593
+ f" Owner: {'✅ Updated' if response.owner_updated else '❌ No change'}"
594
+ )
595
+ if (
596
+ response.owner_updated
597
+ and response.old_owner_address
598
+ and response.new_owner_address
599
+ ):
600
+ console.print(
601
+ f" {address(response.old_owner_address)} → {address(response.new_owner_address)}"
602
+ )
603
+ # Show on-chain transaction details if available
604
+ if response.transaction_hash:
605
+ console.print()
606
+ console.print("[bold]On-Chain Update:[/bold]")
607
+ console.print(f" Transaction: {response.transaction_hash}")
608
+ if response.block_hash:
609
+ console.print(f" Block Hash: {response.block_hash}")
610
+ if response.block_number:
611
+ console.print(f" Block Number: {response.block_number}")
612
+ console.print()
613
+
614
+
615
+ def display_wallet_deletion_result(response: WalletDeleteResponse):
616
+ """Display wallet deletion results in a beautiful panel."""
617
+ import sys
618
+
619
+ from ...ui.components import HTCLIPanel
620
+
621
+ # Build content string
622
+ content = f"""[htcli.success]🎉 Successfully deleted {response.total_deleted} wallet(s)[/htcli.success]
623
+
624
+ [htcli.title]📊 Deletion Statistics:[/htcli.title]
625
+ • Coldkeys: [htcli.value]{response.coldkeys_deleted}[/htcli.value]
626
+ • Standalone Hotkeys: [htcli.value]{response.hotkeys_deleted}[/htcli.value] (directly deleted)
627
+ • Associated Hotkeys: [htcli.value]{response.associated_hotkeys_deleted}[/htcli.value] (deleted with owner coldkey)"""
628
+
629
+ content += "\n\n[htcli.error]🗑️ Deleted Wallets:[/htcli.error]"
630
+ for wallet_name in response.deleted_wallets:
631
+ content += f"\n • [htcli.value]{wallet_name}[/htcli.value]"
632
+
633
+ # Show associated hotkey details if any
634
+ if response.associated_hotkey_details:
635
+ content += "\n\n[htcli.title]🔑 Associated Hotkeys Deleted:[/htcli.title]"
636
+ for hotkey_info in response.associated_hotkey_details:
637
+ hotkey_name = hotkey_info.get("name", "Unknown")
638
+ owner_name = hotkey_info.get("owner", "Unknown")
639
+ content += f"\n • [htcli.value]{hotkey_name}[/htcli.value] (owned by [htcli.accent]{owner_name}[/htcli.accent])"
640
+
641
+ content += (
642
+ "\n\n[htcli.warning]💡 Note: This action cannot be undone.[/htcli.warning]"
643
+ )
644
+
645
+ # Create and display panel
646
+ panel = HTCLIPanel(
647
+ content,
648
+ title="✅ Wallet Deletion Complete",
649
+ border_style="htcli.success",
650
+ highlight=True,
651
+ )
652
+
653
+ console.print()
654
+ panel.render()
655
+ console.print()
656
+
657
+ # Ensure output is flushed
658
+ sys.stdout.flush()
659
+ sys.stderr.flush()
660
+
661
+
662
+ def display_wallet_describe_result(wallet_info: dict, balance_info: dict = None):
663
+ """Display comprehensive wallet information."""
664
+ from ...utils.blockchain.formatting import TensorAmount
665
+
666
+ print_success("Wallet Information")
667
+ console.print()
668
+
669
+ # Main wallet details table
670
+ table = HTCLITable(
671
+ title=f"[bold blue]📋 {wallet_info['name']} Details[/bold blue]",
672
+ border_style="blue",
673
+ header_style="bold cyan",
674
+ )
675
+ table.add_column("Property", style="bold white", width=20)
676
+ table.add_column("Value", style="bright_white", width=60)
677
+
678
+ # Basic wallet information
679
+ table.add_row("Name", wallet_info["name"])
680
+ table.add_row(
681
+ "Type", "Hotkey" if wallet_info.get("is_hotkey", False) else "Coldkey"
682
+ )
683
+ table.add_row("Key Type", wallet_info.get("key_type", "ECDSA").upper())
684
+ table.add_row("Address (Full)", address_full(wallet_info["address"]))
685
+ table.add_row("Public Key", wallet_info.get("public_key", "N/A"))
686
+ table.add_row(
687
+ "Encryption",
688
+ "🔒 Enabled" if wallet_info.get("encrypted", False) else "Disabled",
689
+ )
690
+
691
+ # Owner information for hotkeys
692
+ if wallet_info.get("is_hotkey", False) and wallet_info.get("owner_name"):
693
+ table.add_row(
694
+ "Owner",
695
+ f"{wallet_info['owner_name']} ({address(wallet_info.get('owner_address', 'N/A'))})",
696
+ )
697
+
698
+ console.print(table.table)
699
+ console.print()
700
+
701
+ # Balance information for coldkeys
702
+ if not wallet_info.get("is_hotkey", False) and balance_info:
703
+ if "error" in balance_info:
704
+ print_warning(f"Unable to fetch balance: {balance_info['error']}")
705
+ else:
706
+ balance_table = HTCLITable(
707
+ title="[bold green]💰 Balance Information[/bold green]",
708
+ border_style="green",
709
+ header_style="bold bright_green",
710
+ )
711
+ balance_table.add_column("Property", style="bold white", width=20)
712
+ balance_table.add_column("Value", style="bright_white", width=40)
713
+
714
+ try:
715
+ balance = balance_info.get("balance", 0)
716
+ if isinstance(balance, (int, float)) and balance > 0:
717
+ amount = TensorAmount.from_wei(balance)
718
+ balance_table.add_row(
719
+ "Free Balance", f"{amount.tensor:,.3f} TENSOR"
720
+ )
721
+ balance_table.add_row("Raw Balance", f"{balance:,}")
722
+ else:
723
+ balance_table.add_row("Free Balance", "0.000 TENSOR")
724
+ balance_table.add_row("Raw Balance", "0")
725
+ except Exception:
726
+ balance_table.add_row("Free Balance", "[red]Error calculating[/red]")
727
+ balance_table.add_row("Raw Balance", "[red]Error calculating[/red]")
728
+
729
+ console.print(balance_table.table)
730
+ console.print()
731
+
732
+ # Additional information for hotkeys
733
+ if wallet_info.get("is_hotkey", False):
734
+ hotkey_table = HTCLITable(
735
+ title="[bold yellow]Hotkey Information[/bold yellow]",
736
+ border_style="yellow",
737
+ header_style="bold bright_yellow",
738
+ )
739
+ hotkey_table.add_column("Property", style="bold white", width=20)
740
+ hotkey_table.add_column("Value", style="bright_white", width=40)
741
+
742
+ hotkey_table.add_row("Purpose", "Used for node operations and staking")
743
+ hotkey_table.add_row(
744
+ "Security", "Keep this key secure but accessible for node operations"
745
+ )
746
+ hotkey_table.add_row("Owner", wallet_info.get("owner_name", "Unknown"))
747
+
748
+ console.print(hotkey_table.table)
749
+ console.print()
750
+
751
+ # Security reminders
752
+ security_table = HTCLITable(
753
+ title="[bold red]🔒 Security Information[/bold red]",
754
+ border_style="red",
755
+ header_style="bold bright_red",
756
+ )
757
+ security_table.add_column("Property", style="bold white", width=20)
758
+ security_table.add_column("Value", style="bright_white", width=60)
759
+
760
+ if wallet_info.get("is_hotkey", False):
761
+ security_table.add_row("Backup", "Store hotkey securely for node operations")
762
+ security_table.add_row("Access", "Keep accessible for automated node functions")
763
+ security_table.add_row("Rotation", "Consider rotating hotkeys periodically")
764
+ else:
765
+ security_table.add_row("Backup", "Store coldkey in secure, offline location")
766
+ security_table.add_row("Access", "Limit access to essential operations only")
767
+ security_table.add_row(
768
+ "Recovery", "Keep recovery phrase in multiple secure locations"
769
+ )
770
+
771
+ console.print(security_table.table)
772
+
773
+
774
+ def display_identity_update_result(response: dict, hotkey_address: str):
775
+ """Display identity update results."""
776
+ print_success("Identity updated successfully!")
777
+ console.print()
778
+
779
+ # Display identity details in a table
780
+ table = HTCLITable(
781
+ title="[bold blue]🆔 Identity Information[/bold blue]",
782
+ border_style="blue",
783
+ header_style="bold cyan",
784
+ )
785
+ table.add_column("Property", style="bold white", width=20)
786
+ table.add_column("Value", style="bright_white", width=60)
787
+
788
+ # Hotkey Address
789
+ table.add_row("Hotkey", address(hotkey_address))
790
+
791
+ # Parse fields from the extrinsic call if possible, or display generic success
792
+ # Since we don't have the full parsed identity object here, we'll show what we can
793
+ # Ideally, we should fetch the updated identity to show here, but for now:
794
+
795
+ console.print(table.table)
796
+ console.print()
797
+
798
+ # Transaction details
799
+ if response.get("transaction_hash"):
800
+ console.print("[bold]Transaction Details:[/bold]")
801
+ console.print(f" Transaction Hash: {response.get('transaction_hash')}")
802
+ if response.get("block_hash"):
803
+ console.print(f" Block Hash: {response.get('block_hash')}")
804
+ if response.get("block_number"):
805
+ console.print(f" Block Number: {response.get('block_number')}")
806
+ console.print()