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