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,76 @@
1
+ """
2
+ HTCLI Utilities - Domain-based organization.
3
+
4
+ This module provides utilities organized by domain:
5
+ - wallet: Wallet creation, management, and authentication
6
+ - blockchain: EVM address validation, formatting, and ownership
7
+ - network: Subnet management and network operations
8
+ - legacy: Deprecated utilities (use UI system instead)
9
+ """
10
+
11
+ # Import from domain-specific modules
12
+ from .blockchain import *
13
+ from .network import *
14
+ from .wallet import *
15
+
16
+ # Legacy imports for backwards compatibility
17
+ # NOTE: These will be removed in a future version
18
+ # Use the new UI system instead:
19
+ # - colors -> ui.colors
20
+ # - formatting -> ui.display
21
+ # - interactive -> ui.prompts
22
+
23
+ __all__ = [
24
+ # Wallet utilities
25
+ "create_wallet",
26
+ "import_wallet",
27
+ "get_keypair_from_wallet",
28
+ "get_available_wallets",
29
+ "display_available_wallets",
30
+ "prompt_for_wallet_selection",
31
+ "create_wallet_if_needed",
32
+ "create_new_wallet",
33
+ "get_wallet_for_subnet_operation",
34
+ "generate_coldkey_pair",
35
+ "generate_hotkey_pair",
36
+ "get_wallet_info_by_name",
37
+ "get_wallet_with_retry",
38
+ "list_keys",
39
+ "wallet_name_exists",
40
+ "encrypt_private_key",
41
+ "decrypt_private_key",
42
+ "generate_mnemonic",
43
+ "create_keypair_from_mnemonic",
44
+ "get_secure_password",
45
+ "get_unlock_password",
46
+ "prompt_for_password",
47
+ "generate_password_hash",
48
+ "verify_password",
49
+ # Blockchain utilities
50
+ "validate_address",
51
+ "validate_ethereum_address",
52
+ "validate_amount",
53
+ "validate_subnet_id",
54
+ "validate_node_id",
55
+ "validate_peer_id",
56
+ "validate_key_type",
57
+ "validate_password",
58
+ "validate_file_path",
59
+ "validate_wallet_name",
60
+ "TensorAmount",
61
+ "format_tensor_balance",
62
+ "format_balance",
63
+ "format_address",
64
+ "format_hash",
65
+ "format_amount",
66
+ "get_user_addresses",
67
+ "user_owns_subnet",
68
+ "require_user_keys",
69
+ "show_mine_filter_info",
70
+ # Network utilities
71
+ "add_subnet_to_registry",
72
+ "get_subnet_from_registry",
73
+ "remove_subnet_from_registry",
74
+ "list_registered_subnets",
75
+ "validate_subnet_manifest",
76
+ ]
@@ -0,0 +1,75 @@
1
+ """Blockchain utilities for htcli."""
2
+
3
+ from .peer_id import (
4
+ TEST_PEER_IDS,
5
+ decode_peer_id,
6
+ encode_peer_id,
7
+ generate_test_peer_id,
8
+ get_test_peer_id,
9
+ validate_peer_id_format,
10
+ )
11
+ from .validation import (
12
+ validate_address,
13
+ validate_amount,
14
+ validate_ethereum_address,
15
+ validate_hotkey_address,
16
+ validate_key_type,
17
+ validate_network_url,
18
+ validate_node_id,
19
+ validate_peer_id,
20
+ validate_subnet_id,
21
+ )
22
+
23
+ # Conditional import of type_registry (requires scalecodec)
24
+ try:
25
+ from .type_registry import (
26
+ CUSTOM_RPC_TYPE_REGISTRY,
27
+ decode_option_subnet_info,
28
+ decode_option_subnet_node_info,
29
+ decode_vec_delegate_stake_info,
30
+ decode_vec_node_delegate_stake_info,
31
+ decode_vec_subnet_info,
32
+ decode_vec_subnet_node_info,
33
+ decode_vec_subnet_node_stake_info,
34
+ get_rpc_runtime_config,
35
+ )
36
+
37
+ _HAS_TYPE_REGISTRY = True
38
+ except ImportError:
39
+ _HAS_TYPE_REGISTRY = False
40
+
41
+ __all__ = [
42
+ # Peer ID utilities
43
+ "encode_peer_id",
44
+ "decode_peer_id",
45
+ "validate_peer_id_format",
46
+ "generate_test_peer_id",
47
+ "get_test_peer_id",
48
+ "TEST_PEER_IDS",
49
+ # Validation utilities
50
+ "validate_address",
51
+ "validate_ethereum_address",
52
+ "validate_hotkey_address",
53
+ "validate_amount",
54
+ "validate_subnet_id",
55
+ "validate_node_id",
56
+ "validate_peer_id",
57
+ "validate_key_type",
58
+ "validate_network_url",
59
+ ]
60
+
61
+ # Add type registry exports if available
62
+ if _HAS_TYPE_REGISTRY:
63
+ __all__.extend(
64
+ [
65
+ "get_rpc_runtime_config",
66
+ "CUSTOM_RPC_TYPE_REGISTRY",
67
+ "decode_option_subnet_info",
68
+ "decode_vec_subnet_info",
69
+ "decode_option_subnet_node_info",
70
+ "decode_vec_subnet_node_info",
71
+ "decode_vec_subnet_node_stake_info",
72
+ "decode_vec_delegate_stake_info",
73
+ "decode_vec_node_delegate_stake_info",
74
+ ]
75
+ )
@@ -0,0 +1,368 @@
1
+ """
2
+ Blockchain formatting utilities.
3
+ Handles TENSOR amounts, addresses, and other blockchain data formatting.
4
+ Uses Decimal for precise 18-decimal TENSOR calculations.
5
+ """
6
+
7
+ from decimal import Decimal, getcontext
8
+ from typing import Optional, Union
9
+
10
+ from web3 import Web3
11
+
12
+ # Configure decimal context for TENSOR precision (18 decimals + extra for calculations)
13
+ getcontext().prec = 28 # Extra precision for intermediate calculations
14
+
15
+ # TENSOR token precision constants
16
+ TENSOR_DECIMALS = 18
17
+ TENSOR_UNIT = Decimal("1e18")
18
+
19
+
20
+ def to_checksum_address(address: Optional[str]) -> Optional[str]:
21
+ """
22
+ Convert an Ethereum-style address to EIP-55 checksummed format.
23
+
24
+ This function ensures addresses returned from RPC calls are properly
25
+ checksummed for display, providing the mixed-case checksum encoding
26
+ that helps detect typos when copying addresses.
27
+
28
+ Args:
29
+ address: Hex address string (with or without 0x prefix), or None
30
+
31
+ Returns:
32
+ EIP-55 checksummed address string, or None if input is None/invalid
33
+
34
+ Example:
35
+ >>> to_checksum_address("0xbbcdfe56402109104dca757b0b644035ac3fd321")
36
+ '0xBbCdFE56402109104dcA757B0B644035Ac3Fd321'
37
+ """
38
+ if not address:
39
+ return None
40
+
41
+ try:
42
+ # Ensure address has 0x prefix
43
+ if not address.startswith("0x"):
44
+ address = "0x" + address
45
+
46
+ # Use web3 to convert to checksummed format
47
+ return Web3.to_checksum_address(address)
48
+ except Exception:
49
+ # If checksum conversion fails (e.g., invalid address), return original
50
+ return address
51
+
52
+
53
+ class TensorAmount:
54
+ """
55
+ Precise TENSOR amount handling using Decimal for 18-decimal precision.
56
+ Handles TENSOR calculations with full precision automatically.
57
+ """
58
+
59
+ def __init__(self, value: Union[str, int, float, Decimal]):
60
+ """Initialize with precise decimal representation."""
61
+ self.amount = Decimal(str(value))
62
+
63
+ @property
64
+ def tensor(self) -> Decimal:
65
+ """Get amount in TENSOR units."""
66
+ return self.amount
67
+
68
+ @property
69
+ def wei(self) -> int:
70
+ """Get amount in smallest unit (wei equivalent)."""
71
+ return int(self.amount * TENSOR_UNIT)
72
+
73
+ @classmethod
74
+ def from_wei(cls, wei_amount: int) -> "TensorAmount":
75
+ """Create from smallest unit amount."""
76
+ return cls(Decimal(wei_amount) / TENSOR_UNIT)
77
+
78
+ def __str__(self) -> str:
79
+ """String representation with appropriate precision."""
80
+ return str(self.amount)
81
+
82
+ def __float__(self) -> float:
83
+ """Float conversion."""
84
+ return float(self.amount)
85
+
86
+ def is_valid(self) -> bool:
87
+ """Validate TENSOR amount is within acceptable range."""
88
+ return (
89
+ Decimal("0") < self.amount <= Decimal("1000000000")
90
+ ) # 1 billion TENSOR max
91
+
92
+
93
+ def format_tensor_balance(balance: Union[str, int, float, TensorAmount]) -> str:
94
+ """
95
+ Format a TENSOR balance for display with proper precision.
96
+
97
+ Args:
98
+ balance: TENSOR balance amount
99
+
100
+ Returns:
101
+ Formatted TENSOR balance string (number only)
102
+ """
103
+ try:
104
+ if isinstance(balance, TensorAmount):
105
+ amount = balance.tensor
106
+ else:
107
+ amount = TensorAmount(balance).tensor
108
+
109
+ # Format with appropriate decimal places based on magnitude
110
+ if amount >= 1_000_000:
111
+ return f"{amount:,.6f}"
112
+ elif amount >= 1:
113
+ return f"{amount:.9f}"
114
+ else:
115
+ return f"{amount:.18f}".rstrip("0").rstrip(".") if amount > 0 else "0"
116
+
117
+ except (ValueError, TypeError):
118
+ return "Invalid"
119
+
120
+
121
+ def format_balance(balance: Union[str, int, float], currency: str = "TENSOR") -> str:
122
+ """
123
+ Format a balance amount for display.
124
+
125
+ Args:
126
+ balance: Balance amount
127
+ currency: Currency symbol
128
+
129
+ Returns:
130
+ Formatted balance string
131
+ """
132
+ if currency == "TENSOR":
133
+ return format_tensor_balance(balance)
134
+
135
+ try:
136
+ if isinstance(balance, str):
137
+ balance = float(balance)
138
+
139
+ # Format with appropriate decimal places
140
+ if balance >= 1_000_000:
141
+ return f"{balance:,.2f} {currency}"
142
+ elif balance >= 1:
143
+ return f"{balance:.6f} {currency}"
144
+ else:
145
+ return f"{balance:.9f} {currency}"
146
+
147
+ except (ValueError, TypeError):
148
+ return f"Invalid {currency}"
149
+
150
+
151
+ def format_address(address: str, truncate: bool = True) -> str:
152
+ """
153
+ Format an EVM address for display.
154
+
155
+ Args:
156
+ address: EVM address to format
157
+ truncate: Whether to truncate for display
158
+
159
+ Returns:
160
+ Formatted address string
161
+ """
162
+ if not address:
163
+ return "Unknown Address"
164
+
165
+ # Ensure proper EVM format
166
+ if not address.startswith("0x"):
167
+ address = f"0x{address}"
168
+
169
+ if truncate and len(address) > 10:
170
+ # Remove 0x prefix if present for calculation
171
+ clean_address = address[2:]
172
+
173
+ if len(clean_address) > 10:
174
+ return f"0x{clean_address[:5]}...{clean_address[-5:]}"
175
+
176
+ return address
177
+
178
+
179
+ def format_hash(hash_value: str, truncate: bool = True) -> str:
180
+ """
181
+ Format a transaction or block hash for display.
182
+
183
+ Args:
184
+ hash_value: Hash value to format
185
+ truncate: Whether to truncate for display
186
+
187
+ Returns:
188
+ Formatted hash string
189
+ """
190
+ if not hash_value:
191
+ return "Unknown Hash"
192
+
193
+ # Ensure proper hex format
194
+ if not hash_value.startswith("0x"):
195
+ hash_value = f"0x{hash_value}"
196
+
197
+ if truncate and len(hash_value) > 20:
198
+ return f"{hash_value[:10]}...{hash_value[-8:]}"
199
+
200
+ return hash_value
201
+
202
+
203
+ def format_amount(
204
+ amount: Union[str, int, float], currency: str = "TENSOR", precision: int = 6
205
+ ) -> str:
206
+ """
207
+ Format a token amount for display.
208
+
209
+ Args:
210
+ amount: Amount to format
211
+ currency: Currency symbol
212
+ precision: Decimal precision
213
+
214
+ Returns:
215
+ Formatted amount string
216
+ """
217
+ try:
218
+ if isinstance(amount, str):
219
+ amount = float(amount)
220
+
221
+ # Remove trailing zeros
222
+ formatted = f"{amount:.{precision}f}".rstrip("0").rstrip(".")
223
+
224
+ return f"{formatted} {currency}"
225
+
226
+ except (ValueError, TypeError):
227
+ return f"Invalid {currency}"
228
+
229
+
230
+ def format_percentage(value: Union[str, int, float], precision: int = 2) -> str:
231
+ """
232
+ Format a percentage value for display.
233
+
234
+ Args:
235
+ value: Percentage value
236
+ precision: Decimal precision
237
+
238
+ Returns:
239
+ Formatted percentage string
240
+ """
241
+ try:
242
+ if isinstance(value, str):
243
+ value = float(value)
244
+
245
+ return f"{value:.{precision}f}%"
246
+
247
+ except (ValueError, TypeError):
248
+ return "Invalid %"
249
+
250
+
251
+ def format_large_number(number: Union[str, int, float], precision: int = 2) -> str:
252
+ """
253
+ Format large numbers with K, M, B suffixes.
254
+
255
+ Args:
256
+ number: Number to format
257
+ precision: Decimal precision
258
+
259
+ Returns:
260
+ Formatted number string with suffix
261
+ """
262
+ try:
263
+ if isinstance(number, str):
264
+ number = float(number)
265
+
266
+ if number >= 1_000_000_000:
267
+ return f"{number / 1_000_000_000:.{precision}f}B"
268
+ elif number >= 1_000_000:
269
+ return f"{number / 1_000_000:.{precision}f}M"
270
+ elif number >= 1_000:
271
+ return f"{number / 1_000:.{precision}f}K"
272
+ else:
273
+ return f"{number:.{precision}f}"
274
+
275
+ except (ValueError, TypeError):
276
+ return "Invalid"
277
+
278
+
279
+ def format_duration(seconds: Union[str, int, float]) -> str:
280
+ """
281
+ Format duration in seconds to human-readable format.
282
+
283
+ Args:
284
+ seconds: Duration in seconds
285
+
286
+ Returns:
287
+ Formatted duration string
288
+ """
289
+ try:
290
+ if isinstance(seconds, str):
291
+ seconds = float(seconds)
292
+
293
+ seconds = int(seconds)
294
+
295
+ if seconds < 60:
296
+ return f"{seconds}s"
297
+ elif seconds < 3600:
298
+ minutes = seconds // 60
299
+ remaining_seconds = seconds % 60
300
+ return (
301
+ f"{minutes}m {remaining_seconds}s"
302
+ if remaining_seconds
303
+ else f"{minutes}m"
304
+ )
305
+ elif seconds < 86400:
306
+ hours = seconds // 3600
307
+ remaining_minutes = (seconds % 3600) // 60
308
+ return (
309
+ f"{hours}h {remaining_minutes}m" if remaining_minutes else f"{hours}h"
310
+ )
311
+ else:
312
+ days = seconds // 86400
313
+ remaining_hours = (seconds % 86400) // 3600
314
+ return f"{days}d {remaining_hours}h" if remaining_hours else f"{days}d"
315
+
316
+ except (ValueError, TypeError):
317
+ return "Invalid duration"
318
+
319
+
320
+ def format_gas_price(gas_price: Union[str, int, float], unit: str = "gwei") -> str:
321
+ """
322
+ Format gas price for display.
323
+
324
+ Args:
325
+ gas_price: Gas price value
326
+ unit: Unit (gwei, wei, etc.)
327
+
328
+ Returns:
329
+ Formatted gas price string
330
+ """
331
+ try:
332
+ if isinstance(gas_price, str):
333
+ gas_price = float(gas_price)
334
+
335
+ if unit.lower() == "gwei":
336
+ return f"{gas_price:.2f} gwei"
337
+ elif unit.lower() == "wei":
338
+ return f"{gas_price:,.0f} wei"
339
+ else:
340
+ return f"{gas_price} {unit}"
341
+
342
+ except (ValueError, TypeError):
343
+ return f"Invalid {unit}"
344
+
345
+
346
+ def format_network_status(status: str) -> str:
347
+ """
348
+ Format network status with appropriate styling.
349
+
350
+ Args:
351
+ status: Network status
352
+
353
+ Returns:
354
+ Formatted status string
355
+ """
356
+ status = status.lower()
357
+
358
+ status_map = {
359
+ "active": "🟢 Active",
360
+ "inactive": "🔴 Inactive",
361
+ "pending": "🟡 Pending",
362
+ "syncing": "🔄 Syncing",
363
+ "error": "❌ Error",
364
+ "connected": "🟢 Connected",
365
+ "disconnected": "🔴 Disconnected",
366
+ }
367
+
368
+ return status_map.get(status, f"⚪ {status.title()}")