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,195 @@
|
|
|
1
|
+
from pydantic import BaseModel, Field, ValidationInfo, field_validator
|
|
2
|
+
|
|
3
|
+
from src.htcli.models.enums.enum_types import KeyType
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SubnetRegisterRequest(BaseModel):
|
|
7
|
+
"""
|
|
8
|
+
Request model for subnet registration.
|
|
9
|
+
|
|
10
|
+
Note: Only fields that are part of RegistrationSubnetData are included here.
|
|
11
|
+
Other configuration fields (churn_limit, queue_epochs, etc.) are set separately
|
|
12
|
+
after registration using owner update calls.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
# Core parameters
|
|
16
|
+
max_cost: int = Field(
|
|
17
|
+
..., description="Maximum cost willing to pay for registration"
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
# Subnet metadata (part of RegistrationSubnetData)
|
|
21
|
+
name: str = Field(..., description="Unique subnet name", max_length=1024)
|
|
22
|
+
repo: str = Field(
|
|
23
|
+
..., description="Repository URL for subnet code", max_length=1024
|
|
24
|
+
)
|
|
25
|
+
description: str = Field(
|
|
26
|
+
default="", description="Subnet description", max_length=1024
|
|
27
|
+
)
|
|
28
|
+
misc: str = Field(
|
|
29
|
+
default="", description="Miscellaneous information", max_length=1024
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
# Subnet configuration (part of RegistrationSubnetData)
|
|
33
|
+
min_stake: int = Field(
|
|
34
|
+
default=100_000_000_000_000_000_000, # default 100 TENSOR
|
|
35
|
+
description="Minimum stake balance for subnet nodes (TENSOR)",
|
|
36
|
+
)
|
|
37
|
+
max_stake: int = Field(
|
|
38
|
+
default=1_000_000_000_000_000_000_000, # default 1000 TENSOR
|
|
39
|
+
description="Maximum stake balance for subnet nodes (TENSOR)",
|
|
40
|
+
)
|
|
41
|
+
delegate_stake_percentage: int = Field(
|
|
42
|
+
default=100_000_000_000_000_000, # 10%
|
|
43
|
+
description="Percentage of emissions allocated to delegate stakers",
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
# Access control (part of RegistrationSubnetData)
|
|
47
|
+
initial_coldkeys: dict[str, int] = Field(
|
|
48
|
+
default_factory=dict,
|
|
49
|
+
description="Initial coldkeys allowed to register (address -> max_registrations). Value must be >= 1.",
|
|
50
|
+
)
|
|
51
|
+
initial_validators: dict[int, int] = Field(
|
|
52
|
+
default_factory=dict,
|
|
53
|
+
description="Initial validators allowed to register (validator_id -> max_registrations). Value must be >= 1.",
|
|
54
|
+
)
|
|
55
|
+
key_types: set[KeyType] = Field(
|
|
56
|
+
default_factory=lambda: {KeyType.RSA}, description="Supported key types"
|
|
57
|
+
)
|
|
58
|
+
bootnodes: set[str] = Field(
|
|
59
|
+
default_factory=set, description="Bootnode addresses (can be empty)"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
@field_validator("name")
|
|
63
|
+
def validate_name(cls, v):
|
|
64
|
+
if v is None:
|
|
65
|
+
raise ValueError("Subnet name cannot be None")
|
|
66
|
+
if not isinstance(v, str) or not v.strip():
|
|
67
|
+
raise ValueError("Subnet name cannot be empty")
|
|
68
|
+
return v.strip()
|
|
69
|
+
|
|
70
|
+
@field_validator("repo")
|
|
71
|
+
def validate_repo(cls, v):
|
|
72
|
+
if v is None:
|
|
73
|
+
raise ValueError("Repository URL cannot be None")
|
|
74
|
+
if not isinstance(v, str) or not v.strip():
|
|
75
|
+
raise ValueError("Repository URL cannot be empty")
|
|
76
|
+
return v.strip()
|
|
77
|
+
|
|
78
|
+
@field_validator("min_stake")
|
|
79
|
+
def validate_min_stake(cls, v):
|
|
80
|
+
MIN_TENSOR = 100
|
|
81
|
+
MIN_WEI = int(MIN_TENSOR * 1e18) # 100 TENSOR
|
|
82
|
+
|
|
83
|
+
if v is None:
|
|
84
|
+
raise ValueError("min_stake cannot be None")
|
|
85
|
+
if v < 0:
|
|
86
|
+
raise ValueError("min_stake cannot be negative")
|
|
87
|
+
if v < MIN_WEI:
|
|
88
|
+
raise ValueError(
|
|
89
|
+
f"min_stake must be at least {MIN_TENSOR} TENSOR (got {v / 1e18:.4f} TENSOR)"
|
|
90
|
+
)
|
|
91
|
+
return v
|
|
92
|
+
|
|
93
|
+
@field_validator("max_stake")
|
|
94
|
+
def validate_max_stake(cls, v, info: ValidationInfo):
|
|
95
|
+
MAX_TENSOR = 1000
|
|
96
|
+
MAX_WEI = int(MAX_TENSOR * 1e18) # 1000 TENSOR
|
|
97
|
+
|
|
98
|
+
if v is None:
|
|
99
|
+
raise ValueError("max_stake cannot be None")
|
|
100
|
+
if v < 0:
|
|
101
|
+
raise ValueError("max_stake cannot be negative")
|
|
102
|
+
if v > MAX_WEI:
|
|
103
|
+
raise ValueError(
|
|
104
|
+
f"max_stake must be at most {MAX_TENSOR} TENSOR (got {v / 1e18:.4f} TENSOR)"
|
|
105
|
+
)
|
|
106
|
+
min_stake = info.data.get("min_stake") if info and info.data else None
|
|
107
|
+
if min_stake is not None and v < min_stake:
|
|
108
|
+
raise ValueError(
|
|
109
|
+
f"max_stake must be greater than or equal to min_stake (min: {min_stake / 1e18:.4f} TENSOR, max: {v / 1e18:.4f} TENSOR)"
|
|
110
|
+
)
|
|
111
|
+
return v
|
|
112
|
+
|
|
113
|
+
@field_validator("delegate_stake_percentage")
|
|
114
|
+
def validate_delegate_stake_percentage(cls, v):
|
|
115
|
+
# Blockchain requirement: MinDelegateStakePercentage (5%) to MaxDelegateStakePercentage (95%)
|
|
116
|
+
MIN_ALLOWED = 50_000_000_000_000_000 # 5%
|
|
117
|
+
MAX_ALLOWED = 950_000_000_000_000_000 # 95%
|
|
118
|
+
if v < MIN_ALLOWED or v > MAX_ALLOWED:
|
|
119
|
+
raise ValueError(
|
|
120
|
+
f"delegate_stake_percentage must be between {MIN_ALLOWED / 1e16:.0f}% and {MAX_ALLOWED / 1e16:.0f}%"
|
|
121
|
+
)
|
|
122
|
+
return v
|
|
123
|
+
|
|
124
|
+
@field_validator("bootnodes")
|
|
125
|
+
def validate_bootnodes(cls, v):
|
|
126
|
+
# Bootnodes can be empty, but if provided, must not exceed max
|
|
127
|
+
if v and len(v) > 32: # MaxBootnodes::get()
|
|
128
|
+
raise ValueError("Too many bootnodes (maximum 32)")
|
|
129
|
+
return v
|
|
130
|
+
|
|
131
|
+
@field_validator("initial_coldkeys")
|
|
132
|
+
def validate_initial_coldkeys(cls, v):
|
|
133
|
+
# Initial coldkeys can be empty for permissionless subnets
|
|
134
|
+
# When provided, should have at least 3 for meaningful consensus
|
|
135
|
+
if v and len(v) > 0 and len(v) < 3: # MinSubnetNodes::get()
|
|
136
|
+
raise ValueError(
|
|
137
|
+
"If providing initial coldkeys, provide at least 3 for meaningful consensus"
|
|
138
|
+
)
|
|
139
|
+
# Validate that all max_registrations values are >= 1
|
|
140
|
+
if v:
|
|
141
|
+
for address, max_regs in v.items():
|
|
142
|
+
if max_regs < 1:
|
|
143
|
+
raise ValueError(
|
|
144
|
+
f"max_registrations for {address} must be >= 1, got {max_regs}"
|
|
145
|
+
)
|
|
146
|
+
return v
|
|
147
|
+
|
|
148
|
+
@field_validator("initial_validators")
|
|
149
|
+
def validate_initial_validators(cls, v):
|
|
150
|
+
if v:
|
|
151
|
+
for address, max_regs in v.items():
|
|
152
|
+
if max_regs < 1:
|
|
153
|
+
raise ValueError(
|
|
154
|
+
f"max_registrations for {address} must be >= 1, got {max_regs}"
|
|
155
|
+
)
|
|
156
|
+
return v
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class SubnetActivateRequest(BaseModel):
|
|
160
|
+
"""Request model for subnet activation."""
|
|
161
|
+
|
|
162
|
+
subnet_id: int = Field(..., description="Subnet ID to activate")
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class SubnetRemoveRequest(BaseModel):
|
|
166
|
+
"""Request model for subnet removal."""
|
|
167
|
+
|
|
168
|
+
subnet_id: int = Field(..., description="Subnet ID to remove")
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class SubnetUpdateRequest(BaseModel):
|
|
172
|
+
"""Request model for subnet updates."""
|
|
173
|
+
|
|
174
|
+
subnet_id: int = Field(..., description="Subnet ID to update")
|
|
175
|
+
value: str = Field(..., description="New value")
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class SubnetConfigUpdateRequest(BaseModel):
|
|
179
|
+
"""Request model for subnet configuration updates."""
|
|
180
|
+
|
|
181
|
+
subnet_id: int = Field(..., description="Subnet ID to update")
|
|
182
|
+
value: int = Field(..., description="New configuration value")
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class SubnetOwnershipTransferRequest(BaseModel):
|
|
186
|
+
"""Request model for subnet ownership transfer."""
|
|
187
|
+
|
|
188
|
+
subnet_id: int = Field(..., description="Subnet ID to transfer")
|
|
189
|
+
new_owner: str = Field(..., description="New owner account ID")
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class SubnetOwnershipAcceptRequest(BaseModel):
|
|
193
|
+
"""Request model for accepting subnet ownership."""
|
|
194
|
+
|
|
195
|
+
subnet_id: int = Field(..., description="Subnet ID to accept ownership")
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
from typing import Any, Optional
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Field, field_validator, model_validator
|
|
4
|
+
|
|
5
|
+
from ...utils.blockchain import validate_ethereum_address
|
|
6
|
+
|
|
7
|
+
MAX_RATE = 10**18
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def _validate_account_id(value: Optional[str], field_name: str) -> Optional[str]:
|
|
11
|
+
if value is None:
|
|
12
|
+
return value
|
|
13
|
+
if not validate_ethereum_address(value):
|
|
14
|
+
raise ValueError(f"{field_name} must be a valid Ethereum address")
|
|
15
|
+
return value
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class ValidatorRegisterRequest(BaseModel):
|
|
19
|
+
"""Request model for validator registration."""
|
|
20
|
+
|
|
21
|
+
hotkey: str = Field(..., description="Validator hotkey account ID")
|
|
22
|
+
delegate_reward_rate: int = Field(..., description="Delegate reward rate")
|
|
23
|
+
delegate_account_id: Optional[str] = Field(
|
|
24
|
+
None, description="Optional delegate account ID"
|
|
25
|
+
)
|
|
26
|
+
delegate_account_rate: Optional[int] = Field(
|
|
27
|
+
None, description="Optional delegate account rate"
|
|
28
|
+
)
|
|
29
|
+
identity: Optional[dict[str, Any]] = Field(
|
|
30
|
+
None, description="Optional validator identity payload"
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
@field_validator("hotkey")
|
|
34
|
+
@classmethod
|
|
35
|
+
def validate_hotkey(cls, value: str) -> str:
|
|
36
|
+
return _validate_account_id(value, "hotkey")
|
|
37
|
+
|
|
38
|
+
@field_validator("delegate_account_id")
|
|
39
|
+
@classmethod
|
|
40
|
+
def validate_delegate_account_id(cls, value: Optional[str]) -> Optional[str]:
|
|
41
|
+
return _validate_account_id(value, "delegate_account_id")
|
|
42
|
+
|
|
43
|
+
@field_validator("delegate_reward_rate", "delegate_account_rate")
|
|
44
|
+
@classmethod
|
|
45
|
+
def validate_rate(cls, value: Optional[int]) -> Optional[int]:
|
|
46
|
+
if value is None:
|
|
47
|
+
return value
|
|
48
|
+
if value < 0 or value > MAX_RATE:
|
|
49
|
+
raise ValueError("rate must be between 0 and 100, or a 1e18-format value")
|
|
50
|
+
return value
|
|
51
|
+
|
|
52
|
+
@model_validator(mode="after")
|
|
53
|
+
def validate_delegate_account_pair(self) -> "ValidatorRegisterRequest":
|
|
54
|
+
if self.delegate_account_rate is not None and self.delegate_account_id is None:
|
|
55
|
+
raise ValueError(
|
|
56
|
+
"delegate_account_id is required when delegate_account_rate is set"
|
|
57
|
+
)
|
|
58
|
+
return self
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class ValidatorDelegateRewardRateUpdateRequest(BaseModel):
|
|
62
|
+
"""Request model for updating validator delegate reward rate."""
|
|
63
|
+
|
|
64
|
+
validator_id: int = Field(..., ge=0, description="Validator ID to update")
|
|
65
|
+
new_delegate_reward_rate: int = Field(..., description="New delegate reward rate")
|
|
66
|
+
|
|
67
|
+
@field_validator("new_delegate_reward_rate")
|
|
68
|
+
@classmethod
|
|
69
|
+
def validate_rate(cls, value: int) -> int:
|
|
70
|
+
if value < 0 or value > MAX_RATE:
|
|
71
|
+
raise ValueError("rate must be between 0 and 100, or a 1e18-format value")
|
|
72
|
+
return value
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class ValidatorDelegateAccountUpdateRequest(BaseModel):
|
|
76
|
+
"""Request model for updating validator delegate account."""
|
|
77
|
+
|
|
78
|
+
validator_id: int = Field(..., ge=0, description="Validator ID to update")
|
|
79
|
+
delegate_account_id: Optional[str] = Field(
|
|
80
|
+
None, description="Optional delegate account ID"
|
|
81
|
+
)
|
|
82
|
+
delegate_rate: Optional[int] = Field(
|
|
83
|
+
None, description="Optional delegate account rate"
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
@field_validator("delegate_account_id")
|
|
87
|
+
@classmethod
|
|
88
|
+
def validate_delegate_account_id(cls, value: Optional[str]) -> Optional[str]:
|
|
89
|
+
return _validate_account_id(value, "delegate_account_id")
|
|
90
|
+
|
|
91
|
+
@field_validator("delegate_rate")
|
|
92
|
+
@classmethod
|
|
93
|
+
def validate_delegate_rate(cls, value: Optional[int]) -> Optional[int]:
|
|
94
|
+
if value is None:
|
|
95
|
+
return value
|
|
96
|
+
if value < 0 or value > MAX_RATE:
|
|
97
|
+
raise ValueError("rate must be between 0 and 100, or a 1e18-format value")
|
|
98
|
+
return value
|
|
99
|
+
|
|
100
|
+
@model_validator(mode="after")
|
|
101
|
+
def validate_delegate_account_pair(self) -> "ValidatorDelegateAccountUpdateRequest":
|
|
102
|
+
if self.delegate_rate is not None and self.delegate_account_id is None:
|
|
103
|
+
raise ValueError(
|
|
104
|
+
"delegate_account_id is required when delegate_rate is set"
|
|
105
|
+
)
|
|
106
|
+
return self
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class ValidatorHotkeyUpdateRequest(BaseModel):
|
|
110
|
+
"""Request model for updating validator hotkey."""
|
|
111
|
+
|
|
112
|
+
validator_id: int = Field(..., ge=0, description="Validator ID to update")
|
|
113
|
+
new_hotkey: str = Field(..., description="New validator hotkey")
|
|
114
|
+
|
|
115
|
+
@field_validator("new_hotkey")
|
|
116
|
+
@classmethod
|
|
117
|
+
def validate_new_hotkey(cls, value: str) -> str:
|
|
118
|
+
return _validate_account_id(value, "new_hotkey")
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class ValidatorColdkeyUpdateRequest(BaseModel):
|
|
122
|
+
"""Request model for updating validator coldkey."""
|
|
123
|
+
|
|
124
|
+
validator_id: int = Field(..., ge=0, description="Validator ID to update")
|
|
125
|
+
new_coldkey: str = Field(..., description="New validator coldkey")
|
|
126
|
+
|
|
127
|
+
@field_validator("new_coldkey")
|
|
128
|
+
@classmethod
|
|
129
|
+
def validate_new_coldkey(cls, value: str) -> str:
|
|
130
|
+
return _validate_account_id(value, "new_coldkey")
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class ValidatorIdentityUpdateRequest(BaseModel):
|
|
134
|
+
"""Request model for updating validator identity."""
|
|
135
|
+
|
|
136
|
+
validator_id: int = Field(..., ge=0, description="Validator ID to update")
|
|
137
|
+
identity: Optional[dict[str, Any]] = Field(
|
|
138
|
+
None, description="Optional validator identity payload"
|
|
139
|
+
)
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Wallet-related request models.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, Field, field_validator
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class WalletCreateRequest(BaseModel):
|
|
11
|
+
"""Request model for wallet creation."""
|
|
12
|
+
|
|
13
|
+
name: str = Field(..., description="Wallet name")
|
|
14
|
+
key_type: str = Field(
|
|
15
|
+
default="ecdsa", description="Key type (ecdsa/sr25519/ed25519)"
|
|
16
|
+
)
|
|
17
|
+
wallet_type: str = Field(
|
|
18
|
+
default="coldkey", description="Wallet type (coldkey/hotkey)"
|
|
19
|
+
)
|
|
20
|
+
owner_address: Optional[str] = Field(None, description="Owner address for hotkeys")
|
|
21
|
+
password: Optional[str] = Field(None, description="Wallet password")
|
|
22
|
+
|
|
23
|
+
@field_validator("key_type")
|
|
24
|
+
def validate_key_type(cls, v):
|
|
25
|
+
if v not in ["ecdsa", "sr25519", "ed25519"]:
|
|
26
|
+
raise ValueError("Key type must be ecdsa, sr25519, or ed25519")
|
|
27
|
+
return v
|
|
28
|
+
|
|
29
|
+
@field_validator("wallet_type")
|
|
30
|
+
def validate_wallet_type(cls, v):
|
|
31
|
+
if v not in ["coldkey", "hotkey"]:
|
|
32
|
+
raise ValueError("Wallet type must be coldkey or hotkey")
|
|
33
|
+
return v
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class WalletRestoreRequest(BaseModel):
|
|
37
|
+
"""Request model for wallet restoration."""
|
|
38
|
+
|
|
39
|
+
name: str = Field(..., description="Wallet name")
|
|
40
|
+
private_key: Optional[str] = Field(None, description="Private key (hex)")
|
|
41
|
+
mnemonic: Optional[str] = Field(None, description="Mnemonic phrase")
|
|
42
|
+
key_type: str = Field(default="ecdsa", description="Key type")
|
|
43
|
+
wallet_type: str = Field(default="coldkey", description="Wallet type")
|
|
44
|
+
owner_address: Optional[str] = Field(None, description="Owner address for hotkeys")
|
|
45
|
+
password: Optional[str] = Field(None, description="Wallet password")
|
|
46
|
+
|
|
47
|
+
@field_validator("private_key")
|
|
48
|
+
def validate_private_key(cls, v):
|
|
49
|
+
if v and (len(v) != 64 or not all(c in "0123456789abcdefABCDEF" for c in v)):
|
|
50
|
+
raise ValueError("Private key must be 64 character hex string")
|
|
51
|
+
return v
|
|
52
|
+
|
|
53
|
+
@field_validator("mnemonic")
|
|
54
|
+
def validate_mnemonic(cls, v):
|
|
55
|
+
if v and len(v.split()) not in [12, 15, 18, 21, 24]:
|
|
56
|
+
raise ValueError("Mnemonic must be 12, 15, 18, 21, or 24 words")
|
|
57
|
+
return v
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class WalletUpdateRequest(BaseModel):
|
|
61
|
+
"""Request model for wallet updates."""
|
|
62
|
+
|
|
63
|
+
current_name: str = Field(..., description="Current wallet name")
|
|
64
|
+
new_name: Optional[str] = Field(None, description="New wallet name")
|
|
65
|
+
new_password: Optional[str] = Field(None, description="New password")
|
|
66
|
+
remove_password: bool = Field(False, description="Remove password protection")
|
|
67
|
+
current_password: Optional[str] = Field(
|
|
68
|
+
None, description="Current password (required when changing/removing password)"
|
|
69
|
+
)
|
|
70
|
+
new_owner: Optional[str] = Field(
|
|
71
|
+
None, description="New owner coldkey name (for hotkeys only)"
|
|
72
|
+
)
|
|
73
|
+
owner_name: Optional[str] = Field(
|
|
74
|
+
None,
|
|
75
|
+
description="Current owner coldkey name (for hotkeys only, required to identify the hotkey)",
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class WalletDeleteRequest(BaseModel):
|
|
80
|
+
"""Request model for wallet deletion."""
|
|
81
|
+
|
|
82
|
+
names: list[str] = Field(..., description="Wallet names to delete")
|
|
83
|
+
force: bool = Field(False, description="Skip confirmation")
|
|
84
|
+
deletion_plan: Optional[dict] = Field(None, description="Detailed deletion plan")
|
|
85
|
+
wallet_owners: Optional[dict[str, Optional[str]]] = Field(
|
|
86
|
+
None,
|
|
87
|
+
description="Mapping of wallet name to owner_address for disambiguation (hotkeys only)",
|
|
88
|
+
)
|
|
89
|
+
wallet_owner_context: Optional[
|
|
90
|
+
dict[str, dict[str, Optional[str]]]
|
|
91
|
+
] = Field(
|
|
92
|
+
None,
|
|
93
|
+
description="Mapping of wallet name to extra context (address/coldkey) used during deletion",
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class WalletTransferRequest(BaseModel):
|
|
98
|
+
"""Request model for wallet transfers."""
|
|
99
|
+
|
|
100
|
+
from_wallet: str = Field(..., description="Source wallet name")
|
|
101
|
+
to_address: str = Field(..., description="Destination address")
|
|
102
|
+
amount: float = Field(..., description="Transfer amount")
|
|
103
|
+
unit: str = Field(default="TENSOR", description="Amount unit")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class WalletBalanceRequest(BaseModel):
|
|
107
|
+
"""Request model for wallet balance queries."""
|
|
108
|
+
|
|
109
|
+
wallet_name: Optional[str] = Field(None, description="Wallet name")
|
|
110
|
+
address: Optional[str] = Field(None, description="Wallet address")
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class PreseededWalletRequest(BaseModel):
|
|
114
|
+
"""Request model for preseeded wallet operations."""
|
|
115
|
+
|
|
116
|
+
wallet_name: str = Field(..., description="Wallet name")
|
|
117
|
+
operation: str = Field(..., description="Operation type")
|
|
118
|
+
data: Optional[str] = Field(None, description="Operation data")
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Response models for HTCLI.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
# Base response
|
|
6
|
+
from .base import BaseResponse
|
|
7
|
+
|
|
8
|
+
# Chain-related responses
|
|
9
|
+
from .chain import BalanceResponse, EpochInfoResponse, NetworkStatsResponse
|
|
10
|
+
|
|
11
|
+
# Config-related responses
|
|
12
|
+
from .config import (
|
|
13
|
+
ConfigGetResponse,
|
|
14
|
+
ConfigInitResponse,
|
|
15
|
+
ConfigPathResponse,
|
|
16
|
+
ConfigSetResponse,
|
|
17
|
+
ConfigShowResponse,
|
|
18
|
+
ConfigValidateResponse,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
# Identity-related responses
|
|
22
|
+
from .identity import (
|
|
23
|
+
IdentityAcceptResponse,
|
|
24
|
+
IdentityInfo,
|
|
25
|
+
IdentityRegisterResponse,
|
|
26
|
+
IdentityRemoveResponse,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# Overwatch-related responses
|
|
30
|
+
from .overwatch import OverwatchNodeInfo
|
|
31
|
+
|
|
32
|
+
# Subnet-related responses
|
|
33
|
+
from .subnet import (
|
|
34
|
+
AllSubnetBootnodes,
|
|
35
|
+
BootnodesResponse,
|
|
36
|
+
ColdkeyStakesResponse,
|
|
37
|
+
ColdkeySubnetNodesResponse,
|
|
38
|
+
DelegateStakeIncreaseResponse,
|
|
39
|
+
DelegateStakeInfo,
|
|
40
|
+
DelegateStakesResponse,
|
|
41
|
+
NodeDelegateStakeInfo,
|
|
42
|
+
NodeDelegateStakesResponse,
|
|
43
|
+
NodeStakeInfo,
|
|
44
|
+
OverwatchCommitInfo,
|
|
45
|
+
OverwatchCommitsResponse,
|
|
46
|
+
OverwatchRevealInfo,
|
|
47
|
+
OverwatchRevealsResponse,
|
|
48
|
+
ProofOfStakeResponse,
|
|
49
|
+
StakeInfo,
|
|
50
|
+
SubnetData,
|
|
51
|
+
SubnetInfo,
|
|
52
|
+
SubnetInfoResponse,
|
|
53
|
+
SubnetNodeInfo,
|
|
54
|
+
SubnetNodeInfoResponse,
|
|
55
|
+
SubnetNodeStakeInfo,
|
|
56
|
+
SubnetOwnershipTransferResponse,
|
|
57
|
+
SubnetOwnerUpdateResponse,
|
|
58
|
+
SubnetPauseResponse,
|
|
59
|
+
SubnetRegisterResponse,
|
|
60
|
+
SubnetUnpauseResponse,
|
|
61
|
+
ValidatorStakesResponse,
|
|
62
|
+
ValidatorSubnetNodesResponse,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
# Wallet-related responses
|
|
66
|
+
from .wallet import (
|
|
67
|
+
DelegateStakeAddResponse,
|
|
68
|
+
DelegateStakeRemoveResponse,
|
|
69
|
+
StakeAddResponse,
|
|
70
|
+
StakeInfoResponse,
|
|
71
|
+
StakeRemoveResponse,
|
|
72
|
+
UnbondingClaimResponse,
|
|
73
|
+
WalletBalanceResponse,
|
|
74
|
+
WalletCreateResponse,
|
|
75
|
+
WalletDeleteResponse,
|
|
76
|
+
WalletListResponse,
|
|
77
|
+
WalletStatusResponse,
|
|
78
|
+
WalletTransferResponse,
|
|
79
|
+
WalletUpdateResponse,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
__all__ = [
|
|
83
|
+
# Base response
|
|
84
|
+
"BaseResponse",
|
|
85
|
+
# Chain responses
|
|
86
|
+
"BalanceResponse",
|
|
87
|
+
"EpochInfoResponse",
|
|
88
|
+
"NetworkStatsResponse",
|
|
89
|
+
# Overwatch responses
|
|
90
|
+
"OverwatchNodeInfo",
|
|
91
|
+
# Identity responses
|
|
92
|
+
"IdentityInfo",
|
|
93
|
+
"IdentityRegisterResponse",
|
|
94
|
+
"IdentityAcceptResponse",
|
|
95
|
+
"IdentityRemoveResponse",
|
|
96
|
+
# Wallet responses
|
|
97
|
+
"WalletCreateResponse",
|
|
98
|
+
"WalletListResponse",
|
|
99
|
+
"WalletStatusResponse",
|
|
100
|
+
"WalletBalanceResponse",
|
|
101
|
+
"WalletTransferResponse",
|
|
102
|
+
"WalletUpdateResponse",
|
|
103
|
+
"WalletDeleteResponse",
|
|
104
|
+
"DelegateStakeAddResponse",
|
|
105
|
+
"DelegateStakeRemoveResponse",
|
|
106
|
+
"StakeAddResponse",
|
|
107
|
+
"StakeInfoResponse",
|
|
108
|
+
"StakeRemoveResponse",
|
|
109
|
+
"UnbondingClaimResponse",
|
|
110
|
+
# Subnet responses
|
|
111
|
+
"AllSubnetBootnodes",
|
|
112
|
+
"BootnodesResponse",
|
|
113
|
+
"ColdkeyStakesResponse",
|
|
114
|
+
"ColdkeySubnetNodesResponse",
|
|
115
|
+
"DelegateStakeIncreaseResponse",
|
|
116
|
+
"DelegateStakeInfo",
|
|
117
|
+
"DelegateStakesResponse",
|
|
118
|
+
"NodeDelegateStakeInfo",
|
|
119
|
+
"NodeDelegateStakesResponse",
|
|
120
|
+
"NodeStakeInfo",
|
|
121
|
+
"OverwatchCommitInfo",
|
|
122
|
+
"OverwatchCommitsResponse",
|
|
123
|
+
"OverwatchRevealInfo",
|
|
124
|
+
"OverwatchRevealsResponse",
|
|
125
|
+
"ProofOfStakeResponse",
|
|
126
|
+
"SubnetData",
|
|
127
|
+
"SubnetInfo",
|
|
128
|
+
"SubnetInfoResponse",
|
|
129
|
+
"SubnetNodeInfo",
|
|
130
|
+
"SubnetNodeInfoResponse",
|
|
131
|
+
"SubnetNodeStakeInfo",
|
|
132
|
+
"SubnetOwnershipTransferResponse",
|
|
133
|
+
"SubnetOwnerUpdateResponse",
|
|
134
|
+
"SubnetPauseResponse",
|
|
135
|
+
"SubnetRegisterResponse",
|
|
136
|
+
"SubnetUnpauseResponse",
|
|
137
|
+
"ValidatorStakesResponse",
|
|
138
|
+
"ValidatorSubnetNodesResponse",
|
|
139
|
+
"StakeInfo",
|
|
140
|
+
# Config responses
|
|
141
|
+
"ConfigGetResponse",
|
|
142
|
+
"ConfigInitResponse",
|
|
143
|
+
"ConfigPathResponse",
|
|
144
|
+
"ConfigSetResponse",
|
|
145
|
+
"ConfigShowResponse",
|
|
146
|
+
"ConfigValidateResponse",
|
|
147
|
+
]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base response models.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BaseResponse(BaseModel):
|
|
11
|
+
"""Base response model."""
|
|
12
|
+
|
|
13
|
+
success: bool = Field(..., description="Whether the operation was successful")
|
|
14
|
+
transaction_hash: Optional[str] = Field(None, description="Transaction hash")
|
|
15
|
+
block_hash: Optional[str] = Field(None, description="Block hash for the transaction")
|
|
16
|
+
error: Optional[str] = Field(None, description="Error message if operation failed")
|
|
17
|
+
block_number: Optional[int] = Field(None, description="Block number of transaction")
|
|
18
|
+
epoch: Optional[int] = Field(None, description="Epoch of transaction")
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Chain-related response models.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
from pydantic import Field
|
|
8
|
+
|
|
9
|
+
from .base import BaseResponse
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class NetworkStatsResponse(BaseResponse):
|
|
13
|
+
"""Response for network statistics."""
|
|
14
|
+
|
|
15
|
+
total_subnets: Optional[int] = Field(None, description="Total number of subnets")
|
|
16
|
+
total_nodes: Optional[int] = Field(None, description="Total number of nodes")
|
|
17
|
+
total_stake: Optional[int] = Field(None, description="Total network stake")
|
|
18
|
+
current_epoch: Optional[int] = Field(None, description="Current epoch")
|
|
19
|
+
network_load: Optional[float] = Field(None, description="Network load percentage")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class EpochInfoResponse(BaseResponse):
|
|
23
|
+
"""Response for epoch information."""
|
|
24
|
+
|
|
25
|
+
current_epoch: Optional[int] = Field(None, description="Current epoch number")
|
|
26
|
+
epoch_start: Optional[int] = Field(None, description="Epoch start block")
|
|
27
|
+
epoch_end: Optional[int] = Field(None, description="Epoch end block")
|
|
28
|
+
blocks_per_epoch: Optional[int] = Field(None, description="Blocks per epoch")
|
|
29
|
+
epoch_duration: Optional[int] = Field(None, description="Epoch duration in seconds")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class BalanceResponse(BaseResponse):
|
|
33
|
+
"""Response for balance information."""
|
|
34
|
+
|
|
35
|
+
address: Optional[str] = Field(None, description="Account address")
|
|
36
|
+
balance: Optional[int] = Field(None, description="Account balance")
|
|
37
|
+
locked_balance: Optional[int] = Field(None, description="Locked balance")
|
|
38
|
+
available_balance: Optional[int] = Field(None, description="Available balance")
|
|
39
|
+
reserved_balance: Optional[int] = Field(None, description="Reserved balance")
|