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,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()}")
|