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,1080 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Stake command prompting logic.
|
|
3
|
+
|
|
4
|
+
Handles user interaction and input validation for stake operations.
|
|
5
|
+
Uses HTCLI UI components and Pydantic models for proper validation.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
from typing import Optional
|
|
10
|
+
|
|
11
|
+
from ...dependencies import get_client
|
|
12
|
+
from ...models.requests import (
|
|
13
|
+
ClaimUnbondingsRequest,
|
|
14
|
+
DelegateStakeAddRequest,
|
|
15
|
+
DelegateStakeDonateRequest,
|
|
16
|
+
DelegateStakeRemoveRequest,
|
|
17
|
+
DelegateStakeSwapRequest,
|
|
18
|
+
DelegateStakeTransferRequest,
|
|
19
|
+
NodeDelegateStakeAddRequest,
|
|
20
|
+
NodeDelegateStakeDonateRequest,
|
|
21
|
+
NodeDelegateStakeRemoveRequest,
|
|
22
|
+
NodeDelegateStakeSwapRequest,
|
|
23
|
+
NodeDelegateStakeTransferRequest,
|
|
24
|
+
StakeAddRequest,
|
|
25
|
+
StakeRemoveRequest,
|
|
26
|
+
StakeSwapFromNodeToSubnetRequest,
|
|
27
|
+
StakeSwapFromSubnetToNodeRequest,
|
|
28
|
+
StakeSwapFromSubnetToValidatorRequest,
|
|
29
|
+
StakeSwapFromValidatorToSubnetRequest,
|
|
30
|
+
StakeSwapQueueUpdateRequest,
|
|
31
|
+
ValidatorDelegateStakeAddRequest,
|
|
32
|
+
ValidatorDelegateStakeDonateRequest,
|
|
33
|
+
ValidatorDelegateStakeRemoveRequest,
|
|
34
|
+
ValidatorDelegateStakeSwapRequest,
|
|
35
|
+
ValidatorDelegateStakeTransferRequest,
|
|
36
|
+
)
|
|
37
|
+
from ...ui.colors import info, warning
|
|
38
|
+
from ...ui.display import HTCLIConsole, print_error, print_info
|
|
39
|
+
from ...ui.prompts import HTCLIPrompt, amount_prompt, confirm_prompt
|
|
40
|
+
from ...utils.blockchain.validation import validate_address
|
|
41
|
+
from ...utils.validation import (
|
|
42
|
+
validate_hotkey_address,
|
|
43
|
+
validate_node_id_prompt,
|
|
44
|
+
validate_stake_amount_prompt,
|
|
45
|
+
validate_subnet_id_prompt,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
console = HTCLIConsole()
|
|
49
|
+
prompt = HTCLIPrompt()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def prompt_stake_add(
|
|
53
|
+
subnet_id: Optional[int] = None,
|
|
54
|
+
node_id: Optional[int] = None,
|
|
55
|
+
hotkey: Optional[str] = None,
|
|
56
|
+
amount: Optional[int] = None,
|
|
57
|
+
) -> StakeAddRequest:
|
|
58
|
+
"""Prompt for stake addition parameters."""
|
|
59
|
+
# Validate subnet_id if provided
|
|
60
|
+
if subnet_id is not None:
|
|
61
|
+
is_valid, error_msg = validate_subnet_id_prompt(subnet_id)
|
|
62
|
+
if not is_valid:
|
|
63
|
+
print_error(f"Invalid subnet ID: {error_msg}")
|
|
64
|
+
subnet_id = None
|
|
65
|
+
|
|
66
|
+
if subnet_id is None:
|
|
67
|
+
subnet_id = prompt.integer_prompt(
|
|
68
|
+
"Enter the Subnet ID to stake to",
|
|
69
|
+
min_value=0,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# Validate node_id if provided
|
|
73
|
+
if node_id is not None:
|
|
74
|
+
is_valid, error_msg = validate_node_id_prompt(node_id)
|
|
75
|
+
if not is_valid:
|
|
76
|
+
print_error(f"Invalid node ID: {error_msg}")
|
|
77
|
+
node_id = None
|
|
78
|
+
|
|
79
|
+
# If node_id is provided, fetch hotkey from node_id automatically
|
|
80
|
+
if node_id is not None:
|
|
81
|
+
client = get_client()
|
|
82
|
+
node_info = client.rpc.node.get_subnet_node_info(subnet_id, node_id)
|
|
83
|
+
|
|
84
|
+
if node_info is None:
|
|
85
|
+
raise ValueError(f"Node {node_id} not found in subnet {subnet_id}")
|
|
86
|
+
|
|
87
|
+
hotkey = node_info.hotkey
|
|
88
|
+
print_info(f"Found hotkey {hotkey} for node {node_id} in subnet {subnet_id}")
|
|
89
|
+
else:
|
|
90
|
+
# No node_id provided - user must provide hotkey manually
|
|
91
|
+
# Validate hotkey if provided
|
|
92
|
+
if hotkey is not None:
|
|
93
|
+
is_valid, error_msg = validate_hotkey_address(hotkey)
|
|
94
|
+
if not is_valid:
|
|
95
|
+
print_error(f"Invalid hotkey address: {error_msg}")
|
|
96
|
+
hotkey = None
|
|
97
|
+
|
|
98
|
+
if hotkey is None:
|
|
99
|
+
hotkey = prompt.text_prompt(
|
|
100
|
+
"Enter the hotkey address to stake with",
|
|
101
|
+
)
|
|
102
|
+
# Validate address format
|
|
103
|
+
if not validate_address(hotkey):
|
|
104
|
+
raise ValueError(f"Invalid hotkey address format: {hotkey}")
|
|
105
|
+
|
|
106
|
+
# Try to get node_id from hotkey (optional - stake works even if hotkey not registered as node yet)
|
|
107
|
+
# Note: subnet_node_id is required by extrinsic but not used in staking logic
|
|
108
|
+
# We use 0 as default if hotkey is not registered
|
|
109
|
+
node_id = 0
|
|
110
|
+
client = get_client()
|
|
111
|
+
try:
|
|
112
|
+
result = client.substrate.query(
|
|
113
|
+
module="Network",
|
|
114
|
+
storage_function="HotkeySubnetNodeId",
|
|
115
|
+
params=[subnet_id, hotkey],
|
|
116
|
+
)
|
|
117
|
+
if result and result.value is not None:
|
|
118
|
+
node_id = result.value
|
|
119
|
+
except Exception:
|
|
120
|
+
# Hotkey might not be registered yet - that's OK, stake still works
|
|
121
|
+
# The blockchain will validate that coldkey owns the hotkey
|
|
122
|
+
pass
|
|
123
|
+
|
|
124
|
+
# Validate amount if provided (amount is in TENSOR)
|
|
125
|
+
if amount is not None:
|
|
126
|
+
is_valid, error_msg = validate_stake_amount_prompt(amount)
|
|
127
|
+
if not is_valid:
|
|
128
|
+
print_error(f"Invalid stake amount: {error_msg}")
|
|
129
|
+
amount = None
|
|
130
|
+
|
|
131
|
+
if amount is None:
|
|
132
|
+
amount = prompt.integer_prompt(
|
|
133
|
+
"Enter the stake amount to add",
|
|
134
|
+
min_value=1,
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
# Convert TENSOR to WEI for the request model (which expects WEI)
|
|
138
|
+
# amount is in TENSOR (integer), convert to WEI
|
|
139
|
+
amount_wei = int(amount * 1e18)
|
|
140
|
+
|
|
141
|
+
# subnet_node_id is required by extrinsic signature but not used in staking logic
|
|
142
|
+
# Staking is stored per (hotkey, subnet_id), not per node_id
|
|
143
|
+
# If node_id is 0, that's fine - the blockchain only validates coldkey owns hotkey
|
|
144
|
+
return StakeAddRequest(
|
|
145
|
+
subnet_id=subnet_id,
|
|
146
|
+
subnet_node_id=node_id or 0, # Use 0 as default if hotkey not registered yet
|
|
147
|
+
hotkey=hotkey,
|
|
148
|
+
stake_amount=amount_wei,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def prompt_stake_remove(
|
|
153
|
+
subnet_id: Optional[int] = None,
|
|
154
|
+
node_id: Optional[int] = None,
|
|
155
|
+
hotkey: Optional[str] = None,
|
|
156
|
+
amount: Optional[int] = None,
|
|
157
|
+
coldkey_name: Optional[str] = None,
|
|
158
|
+
) -> StakeRemoveRequest:
|
|
159
|
+
"""Prompt for stake removal parameters."""
|
|
160
|
+
client = get_client()
|
|
161
|
+
|
|
162
|
+
# Get coldkey address if provided (for hotkey disambiguation)
|
|
163
|
+
coldkey_address = None
|
|
164
|
+
if coldkey_name:
|
|
165
|
+
try:
|
|
166
|
+
from ...utils.wallet.crypto import get_wallet_info_by_name
|
|
167
|
+
|
|
168
|
+
coldkey_info = get_wallet_info_by_name(coldkey_name, is_hotkey=False)
|
|
169
|
+
coldkey_address = (
|
|
170
|
+
coldkey_info.get("evm_address")
|
|
171
|
+
or coldkey_info.get("ss58_address")
|
|
172
|
+
or coldkey_info.get("address")
|
|
173
|
+
)
|
|
174
|
+
except Exception:
|
|
175
|
+
# Coldkey not found yet, will be resolved later - that's okay
|
|
176
|
+
pass
|
|
177
|
+
# Validate subnet_id if provided
|
|
178
|
+
if subnet_id is not None:
|
|
179
|
+
is_valid, error_msg = validate_subnet_id_prompt(subnet_id)
|
|
180
|
+
if not is_valid:
|
|
181
|
+
print_error(f"Invalid subnet ID: {error_msg}")
|
|
182
|
+
subnet_id = None
|
|
183
|
+
|
|
184
|
+
if subnet_id is None:
|
|
185
|
+
subnet_id = prompt.integer_prompt(
|
|
186
|
+
"Enter the Subnet ID to remove stake from",
|
|
187
|
+
min_value=0,
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
# Validate node_id if provided
|
|
191
|
+
if node_id is not None:
|
|
192
|
+
is_valid, error_msg = validate_node_id_prompt(node_id)
|
|
193
|
+
if not is_valid:
|
|
194
|
+
print_error(f"Invalid node ID: {error_msg}")
|
|
195
|
+
node_id = None
|
|
196
|
+
|
|
197
|
+
if node_id is None:
|
|
198
|
+
# Ask if user wants to remove stake from subnet or specific node
|
|
199
|
+
console.print(info("Remove stake from:"))
|
|
200
|
+
console.print(" 1. Subnet (general subnet staking)")
|
|
201
|
+
console.print(" 2. Specific node in subnet")
|
|
202
|
+
|
|
203
|
+
choice = prompt.integer_prompt(
|
|
204
|
+
"Select option (1-2)",
|
|
205
|
+
min_value=1,
|
|
206
|
+
max_value=2,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
if choice == 2:
|
|
210
|
+
node_id = prompt.integer_prompt(
|
|
211
|
+
"Enter the Node ID within the subnet",
|
|
212
|
+
min_value=0,
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
# Resolve hotkey if provided (handles both wallet names and addresses)
|
|
216
|
+
if hotkey is not None:
|
|
217
|
+
hotkey = hotkey.strip()
|
|
218
|
+
# Check if it's already a valid address
|
|
219
|
+
if not client.offchain.wallet.validate_ethereum_address(hotkey):
|
|
220
|
+
# Try to resolve as wallet name with coldkey context
|
|
221
|
+
try:
|
|
222
|
+
resolved_hotkey = client.offchain.wallet.resolve_hotkey_address(
|
|
223
|
+
hotkey,
|
|
224
|
+
owner_coldkey_name=coldkey_name,
|
|
225
|
+
owner_address=coldkey_address,
|
|
226
|
+
)
|
|
227
|
+
# Verify the resolved address is valid
|
|
228
|
+
if not client.offchain.wallet.validate_ethereum_address(
|
|
229
|
+
resolved_hotkey
|
|
230
|
+
):
|
|
231
|
+
raise ValueError(
|
|
232
|
+
f"Resolved hotkey address is invalid: {resolved_hotkey}"
|
|
233
|
+
)
|
|
234
|
+
hotkey = resolved_hotkey
|
|
235
|
+
except ValueError as e:
|
|
236
|
+
# Resolution failed - provide clear error message
|
|
237
|
+
print_error(f"Failed to resolve hotkey '{hotkey}': {str(e)}")
|
|
238
|
+
print_error(
|
|
239
|
+
"Please provide a valid hotkey wallet name or Ethereum address (0x...)"
|
|
240
|
+
)
|
|
241
|
+
hotkey = None
|
|
242
|
+
except Exception as e:
|
|
243
|
+
# Unexpected error - re-raise with context
|
|
244
|
+
raise RuntimeError(
|
|
245
|
+
f"Unexpected error resolving hotkey '{hotkey}': {str(e)}"
|
|
246
|
+
) from e
|
|
247
|
+
|
|
248
|
+
if hotkey is None:
|
|
249
|
+
hotkey = prompt.text_prompt(
|
|
250
|
+
"Enter the hotkey address or wallet name to remove stake from",
|
|
251
|
+
)
|
|
252
|
+
hotkey = hotkey.strip()
|
|
253
|
+
# Check if it's already a valid address
|
|
254
|
+
if not client.offchain.wallet.validate_ethereum_address(hotkey):
|
|
255
|
+
# Try to resolve as wallet name with coldkey context
|
|
256
|
+
try:
|
|
257
|
+
resolved_hotkey = client.offchain.wallet.resolve_hotkey_address(
|
|
258
|
+
hotkey,
|
|
259
|
+
owner_coldkey_name=coldkey_name,
|
|
260
|
+
owner_address=coldkey_address,
|
|
261
|
+
)
|
|
262
|
+
# Verify the resolved address is valid
|
|
263
|
+
if not client.offchain.wallet.validate_ethereum_address(
|
|
264
|
+
resolved_hotkey
|
|
265
|
+
):
|
|
266
|
+
raise ValueError(
|
|
267
|
+
f"Resolved hotkey address is invalid: {resolved_hotkey}"
|
|
268
|
+
)
|
|
269
|
+
hotkey = resolved_hotkey
|
|
270
|
+
except ValueError as e:
|
|
271
|
+
# Resolution failed - validate as address format
|
|
272
|
+
if not validate_address(hotkey):
|
|
273
|
+
raise ValueError(
|
|
274
|
+
f"Invalid hotkey: '{hotkey}' is neither a valid wallet name nor a valid Ethereum address. "
|
|
275
|
+
f"Error: {str(e)}"
|
|
276
|
+
) from e
|
|
277
|
+
# If it passes validate_address but not validate_ethereum_address, something is wrong
|
|
278
|
+
raise ValueError(f"Invalid hotkey address format: {hotkey}") from e
|
|
279
|
+
except Exception as e:
|
|
280
|
+
# Unexpected error - re-raise with context
|
|
281
|
+
raise RuntimeError(
|
|
282
|
+
f"Unexpected error resolving hotkey '{hotkey}': {str(e)}"
|
|
283
|
+
) from e
|
|
284
|
+
|
|
285
|
+
# Final validation - ensure we have a valid Ethereum address
|
|
286
|
+
if not client.offchain.wallet.validate_ethereum_address(hotkey):
|
|
287
|
+
raise ValueError(f"Invalid hotkey address format: {hotkey}")
|
|
288
|
+
|
|
289
|
+
# Validate amount if provided (amount is in TENSOR)
|
|
290
|
+
if amount is not None:
|
|
291
|
+
is_valid, error_msg = validate_stake_amount_prompt(amount)
|
|
292
|
+
if not is_valid:
|
|
293
|
+
print_error(f"Invalid stake amount: {error_msg}")
|
|
294
|
+
amount = None
|
|
295
|
+
|
|
296
|
+
if amount is None:
|
|
297
|
+
amount = prompt.integer_prompt(
|
|
298
|
+
"Enter the stake amount to remove",
|
|
299
|
+
min_value=1,
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
# Convert TENSOR to WEI for the request model (which expects WEI)
|
|
303
|
+
amount_wei = int(amount * 1e18)
|
|
304
|
+
|
|
305
|
+
return StakeRemoveRequest(
|
|
306
|
+
subnet_id=subnet_id,
|
|
307
|
+
hotkey=hotkey,
|
|
308
|
+
stake_amount=amount_wei,
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def prompt_stake_claim() -> ClaimUnbondingsRequest:
|
|
313
|
+
"""Prompt for stake claiming parameters."""
|
|
314
|
+
console.print(info("Claim Unbonded Tokens"))
|
|
315
|
+
console.print(
|
|
316
|
+
"This will claim all tokens that have completed the unbonding period.\n"
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
# Confirm action
|
|
320
|
+
confirmed = confirm_prompt("Proceed with claiming unbonded tokens?", default=True)
|
|
321
|
+
if not confirmed:
|
|
322
|
+
raise KeyboardInterrupt("Operation cancelled")
|
|
323
|
+
|
|
324
|
+
return ClaimUnbondingsRequest()
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def prompt_delegate_stake_add(
|
|
328
|
+
subnet_id: Optional[int] = None,
|
|
329
|
+
stake_amount: Optional[float] = None,
|
|
330
|
+
coldkey: Optional[str] = None,
|
|
331
|
+
) -> DelegateStakeAddRequest:
|
|
332
|
+
"""Prompt for adding delegate stake to a subnet."""
|
|
333
|
+
# Validate subnet_id if provided
|
|
334
|
+
if subnet_id is not None:
|
|
335
|
+
is_valid, error_msg = validate_subnet_id_prompt(subnet_id)
|
|
336
|
+
if not is_valid:
|
|
337
|
+
print_error(f"Invalid subnet ID: {error_msg}")
|
|
338
|
+
subnet_id = None
|
|
339
|
+
|
|
340
|
+
if subnet_id is None:
|
|
341
|
+
subnet_id = prompt.integer_prompt(
|
|
342
|
+
"Enter the Subnet ID to delegate stake to",
|
|
343
|
+
min_value=0,
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
# Validate stake_amount if provided (stake_amount is in TENSOR)
|
|
347
|
+
if stake_amount is not None:
|
|
348
|
+
# Convert to float if it's an integer (treat integers as TENSOR)
|
|
349
|
+
if isinstance(stake_amount, int):
|
|
350
|
+
stake_amount = float(stake_amount)
|
|
351
|
+
is_valid, error_msg = validate_stake_amount_prompt(stake_amount)
|
|
352
|
+
if not is_valid:
|
|
353
|
+
print_error(f"Invalid stake amount: {error_msg}")
|
|
354
|
+
stake_amount = None
|
|
355
|
+
|
|
356
|
+
if stake_amount is None:
|
|
357
|
+
from ...ui.prompts import amount_prompt
|
|
358
|
+
|
|
359
|
+
stake_amount = amount_prompt(
|
|
360
|
+
"Enter the amount to delegate",
|
|
361
|
+
currency="TENSOR",
|
|
362
|
+
min_amount=0.000001,
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
# Convert TENSOR to WEI for the request model (which expects WEI)
|
|
366
|
+
stake_amount_wei = int(stake_amount * 1e18)
|
|
367
|
+
|
|
368
|
+
return DelegateStakeAddRequest(
|
|
369
|
+
subnet_id=subnet_id,
|
|
370
|
+
stake_amount=stake_amount_wei,
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def prompt_delegate_stake_remove(
|
|
375
|
+
subnet_id: Optional[int] = None,
|
|
376
|
+
shares_to_remove: Optional[int] = None,
|
|
377
|
+
) -> DelegateStakeRemoveRequest:
|
|
378
|
+
"""Prompt for removing delegate stake from a subnet."""
|
|
379
|
+
if subnet_id is None:
|
|
380
|
+
subnet_id = prompt.integer_prompt(
|
|
381
|
+
"Enter the Subnet ID to remove delegate stake from",
|
|
382
|
+
min_value=0,
|
|
383
|
+
)
|
|
384
|
+
|
|
385
|
+
if shares_to_remove is None:
|
|
386
|
+
console.print(warning("Note: Enter shares as displayed in 'htcli stake list'."))
|
|
387
|
+
console.print(
|
|
388
|
+
info("Check your current shares with: htcli stake list --coldkey <wallet>")
|
|
389
|
+
)
|
|
390
|
+
shares_to_remove = prompt.integer_prompt(
|
|
391
|
+
"Enter the number of shares to remove",
|
|
392
|
+
min_value=1,
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
# Convert human-readable shares to wei format (multiply by 10^18)
|
|
396
|
+
# Blockchain stores shares with 18 decimals
|
|
397
|
+
shares_in_wei = int(shares_to_remove * 10**18)
|
|
398
|
+
|
|
399
|
+
return DelegateStakeRemoveRequest(
|
|
400
|
+
subnet_id=subnet_id,
|
|
401
|
+
shares_to_be_removed=shares_in_wei,
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def prompt_validator_delegate_stake_add(
|
|
406
|
+
validator_id: Optional[int] = None,
|
|
407
|
+
stake_amount: Optional[float] = None,
|
|
408
|
+
coldkey: Optional[str] = None,
|
|
409
|
+
) -> ValidatorDelegateStakeAddRequest:
|
|
410
|
+
"""Prompt for adding delegate stake to a validator."""
|
|
411
|
+
if validator_id is not None and validator_id < 0:
|
|
412
|
+
print_error("Invalid validator ID: must be non-negative")
|
|
413
|
+
validator_id = None
|
|
414
|
+
|
|
415
|
+
if validator_id is None:
|
|
416
|
+
validator_id = prompt.integer_prompt(
|
|
417
|
+
"Enter the Validator ID to delegate to",
|
|
418
|
+
min_value=0,
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
if stake_amount is not None:
|
|
422
|
+
if isinstance(stake_amount, int):
|
|
423
|
+
stake_amount = float(stake_amount)
|
|
424
|
+
is_valid, error_msg = validate_stake_amount_prompt(stake_amount)
|
|
425
|
+
if not is_valid:
|
|
426
|
+
print_error(f"Invalid stake amount: {error_msg}")
|
|
427
|
+
stake_amount = None
|
|
428
|
+
|
|
429
|
+
if stake_amount is None:
|
|
430
|
+
stake_amount = amount_prompt(
|
|
431
|
+
"Enter the amount to delegate",
|
|
432
|
+
currency="TENSOR",
|
|
433
|
+
min_amount=0.000001,
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
return ValidatorDelegateStakeAddRequest(
|
|
437
|
+
validator_id=validator_id,
|
|
438
|
+
delegate_stake_to_be_added=int(stake_amount * 1e18),
|
|
439
|
+
)
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def prompt_validator_delegate_stake_remove(
|
|
443
|
+
validator_id: Optional[int] = None,
|
|
444
|
+
shares_to_remove: Optional[int] = None,
|
|
445
|
+
) -> ValidatorDelegateStakeRemoveRequest:
|
|
446
|
+
"""Prompt for removing delegate stake from a validator."""
|
|
447
|
+
if validator_id is not None and validator_id < 0:
|
|
448
|
+
print_error("Invalid validator ID: must be non-negative")
|
|
449
|
+
validator_id = None
|
|
450
|
+
|
|
451
|
+
if validator_id is None:
|
|
452
|
+
validator_id = prompt.integer_prompt(
|
|
453
|
+
"Enter the Validator ID to remove delegate stake from",
|
|
454
|
+
min_value=0,
|
|
455
|
+
)
|
|
456
|
+
|
|
457
|
+
if shares_to_remove is None:
|
|
458
|
+
console.print(warning("Note: You must specify shares to remove, not amount."))
|
|
459
|
+
shares_to_remove = prompt.integer_prompt(
|
|
460
|
+
"Enter the number of shares to remove",
|
|
461
|
+
min_value=1,
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
return ValidatorDelegateStakeRemoveRequest(
|
|
465
|
+
validator_id=validator_id,
|
|
466
|
+
validator_delegate_stake_shares_to_be_removed=shares_to_remove,
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
|
|
470
|
+
def prompt_validator_delegate_swap(
|
|
471
|
+
from_validator_id: Optional[int] = None,
|
|
472
|
+
to_validator_id: Optional[int] = None,
|
|
473
|
+
shares_to_swap: Optional[int] = None,
|
|
474
|
+
) -> ValidatorDelegateStakeSwapRequest:
|
|
475
|
+
"""Prompt for swapping validator delegate shares between validators."""
|
|
476
|
+
if from_validator_id is not None and from_validator_id < 0:
|
|
477
|
+
print_error("Invalid source validator ID: must be non-negative")
|
|
478
|
+
from_validator_id = None
|
|
479
|
+
if to_validator_id is not None and to_validator_id < 0:
|
|
480
|
+
print_error("Invalid destination validator ID: must be non-negative")
|
|
481
|
+
to_validator_id = None
|
|
482
|
+
|
|
483
|
+
if from_validator_id is None:
|
|
484
|
+
from_validator_id = prompt.integer_prompt(
|
|
485
|
+
"Enter source validator ID",
|
|
486
|
+
min_value=0,
|
|
487
|
+
)
|
|
488
|
+
if to_validator_id is None:
|
|
489
|
+
to_validator_id = prompt.integer_prompt(
|
|
490
|
+
"Enter destination validator ID",
|
|
491
|
+
min_value=0,
|
|
492
|
+
)
|
|
493
|
+
if shares_to_swap is None:
|
|
494
|
+
shares_to_swap = prompt.integer_prompt(
|
|
495
|
+
"Enter number of shares to swap",
|
|
496
|
+
min_value=1,
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
return ValidatorDelegateStakeSwapRequest(
|
|
500
|
+
from_validator_id=from_validator_id,
|
|
501
|
+
to_validator_id=to_validator_id,
|
|
502
|
+
stake_to_be_removed=shares_to_swap,
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
def prompt_validator_delegate_transfer(
|
|
507
|
+
validator_id: Optional[int] = None,
|
|
508
|
+
to_account: Optional[str] = None,
|
|
509
|
+
shares_to_transfer: Optional[int] = None,
|
|
510
|
+
) -> ValidatorDelegateStakeTransferRequest:
|
|
511
|
+
"""Prompt for transferring validator delegate shares."""
|
|
512
|
+
if validator_id is not None and validator_id < 0:
|
|
513
|
+
print_error("Invalid validator ID: must be non-negative")
|
|
514
|
+
validator_id = None
|
|
515
|
+
|
|
516
|
+
if validator_id is None:
|
|
517
|
+
validator_id = prompt.integer_prompt(
|
|
518
|
+
"Enter validator ID",
|
|
519
|
+
min_value=0,
|
|
520
|
+
)
|
|
521
|
+
if to_account is None:
|
|
522
|
+
to_account = prompt.text_prompt("Enter destination account (0x...)")
|
|
523
|
+
if not validate_address(to_account):
|
|
524
|
+
raise ValueError("Destination account must be a valid Ethereum address")
|
|
525
|
+
|
|
526
|
+
if shares_to_transfer is None:
|
|
527
|
+
shares_to_transfer = prompt.integer_prompt(
|
|
528
|
+
"Enter number of shares to transfer",
|
|
529
|
+
min_value=1,
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
return ValidatorDelegateStakeTransferRequest(
|
|
533
|
+
validator_id=validator_id,
|
|
534
|
+
to_account_id=to_account,
|
|
535
|
+
validator_delegate_stake_shares_to_transfer=shares_to_transfer,
|
|
536
|
+
)
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
def prompt_validator_delegate_donate(
|
|
540
|
+
validator_id: Optional[int] = None,
|
|
541
|
+
amount: Optional[float] = None,
|
|
542
|
+
) -> ValidatorDelegateStakeDonateRequest:
|
|
543
|
+
"""Prompt for donating validator delegate stake."""
|
|
544
|
+
if validator_id is not None and validator_id < 0:
|
|
545
|
+
print_error("Invalid validator ID: must be non-negative")
|
|
546
|
+
validator_id = None
|
|
547
|
+
|
|
548
|
+
if validator_id is None:
|
|
549
|
+
validator_id = prompt.integer_prompt(
|
|
550
|
+
"Enter validator ID",
|
|
551
|
+
min_value=0,
|
|
552
|
+
)
|
|
553
|
+
|
|
554
|
+
if amount is not None:
|
|
555
|
+
if isinstance(amount, int):
|
|
556
|
+
amount = float(amount)
|
|
557
|
+
is_valid, error_msg = validate_stake_amount_prompt(amount)
|
|
558
|
+
if not is_valid:
|
|
559
|
+
print_error(f"Invalid amount: {error_msg}")
|
|
560
|
+
amount = None
|
|
561
|
+
|
|
562
|
+
if amount is None:
|
|
563
|
+
amount = amount_prompt(
|
|
564
|
+
"Enter donation amount",
|
|
565
|
+
currency="TENSOR",
|
|
566
|
+
min_amount=0.000001,
|
|
567
|
+
)
|
|
568
|
+
|
|
569
|
+
return ValidatorDelegateStakeDonateRequest(
|
|
570
|
+
validator_id=validator_id,
|
|
571
|
+
amount=int(amount * 1e18),
|
|
572
|
+
)
|
|
573
|
+
|
|
574
|
+
|
|
575
|
+
def prompt_node_delegate_stake_add(
|
|
576
|
+
subnet_id: Optional[int] = None,
|
|
577
|
+
subnet_node_id: Optional[int] = None,
|
|
578
|
+
stake_amount: Optional[float] = None,
|
|
579
|
+
coldkey: Optional[str] = None,
|
|
580
|
+
) -> NodeDelegateStakeAddRequest:
|
|
581
|
+
"""Prompt for adding delegate stake to a specific node."""
|
|
582
|
+
# Validate subnet_id if provided
|
|
583
|
+
if subnet_id is not None:
|
|
584
|
+
is_valid, error_msg = validate_subnet_id_prompt(subnet_id)
|
|
585
|
+
if not is_valid:
|
|
586
|
+
print_error(f"Invalid subnet ID: {error_msg}")
|
|
587
|
+
subnet_id = None
|
|
588
|
+
|
|
589
|
+
if subnet_id is None:
|
|
590
|
+
subnet_id = prompt.integer_prompt(
|
|
591
|
+
"Enter the Subnet ID",
|
|
592
|
+
min_value=0,
|
|
593
|
+
)
|
|
594
|
+
|
|
595
|
+
# Validate subnet_node_id if provided
|
|
596
|
+
if subnet_node_id is not None:
|
|
597
|
+
is_valid, error_msg = validate_node_id_prompt(subnet_node_id)
|
|
598
|
+
if not is_valid:
|
|
599
|
+
print_error(f"Invalid node ID: {error_msg}")
|
|
600
|
+
subnet_node_id = None
|
|
601
|
+
|
|
602
|
+
if subnet_node_id is None:
|
|
603
|
+
subnet_node_id = prompt.integer_prompt(
|
|
604
|
+
"Enter the Node ID to delegate to",
|
|
605
|
+
min_value=0,
|
|
606
|
+
)
|
|
607
|
+
|
|
608
|
+
# Validate stake_amount if provided (stake_amount is in TENSOR)
|
|
609
|
+
if stake_amount is not None:
|
|
610
|
+
# Convert to float if it's an integer (treat integers as TENSOR)
|
|
611
|
+
if isinstance(stake_amount, int):
|
|
612
|
+
stake_amount = float(stake_amount)
|
|
613
|
+
is_valid, error_msg = validate_stake_amount_prompt(stake_amount)
|
|
614
|
+
if not is_valid:
|
|
615
|
+
print_error(f"Invalid stake amount: {error_msg}")
|
|
616
|
+
stake_amount = None
|
|
617
|
+
|
|
618
|
+
if stake_amount is None:
|
|
619
|
+
from ...ui.prompts import amount_prompt
|
|
620
|
+
|
|
621
|
+
stake_amount = amount_prompt(
|
|
622
|
+
"Enter the amount to delegate",
|
|
623
|
+
currency="TENSOR",
|
|
624
|
+
min_amount=0.000001,
|
|
625
|
+
)
|
|
626
|
+
|
|
627
|
+
# Convert TENSOR to WEI for the request model (which expects WEI)
|
|
628
|
+
stake_amount_wei = int(stake_amount * 1e18)
|
|
629
|
+
|
|
630
|
+
return NodeDelegateStakeAddRequest(
|
|
631
|
+
subnet_id=subnet_id,
|
|
632
|
+
subnet_node_id=subnet_node_id,
|
|
633
|
+
node_delegate_stake_to_be_added=stake_amount_wei,
|
|
634
|
+
)
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
def prompt_node_delegate_stake_remove(
|
|
638
|
+
subnet_id: Optional[int] = None,
|
|
639
|
+
subnet_node_id: Optional[int] = None,
|
|
640
|
+
shares_to_remove: Optional[int] = None,
|
|
641
|
+
) -> NodeDelegateStakeRemoveRequest:
|
|
642
|
+
"""Prompt for removing delegate stake from a specific node."""
|
|
643
|
+
if subnet_id is None:
|
|
644
|
+
subnet_id = prompt.integer_prompt(
|
|
645
|
+
"Enter the Subnet ID",
|
|
646
|
+
min_value=0,
|
|
647
|
+
)
|
|
648
|
+
|
|
649
|
+
if subnet_node_id is None:
|
|
650
|
+
subnet_node_id = prompt.integer_prompt(
|
|
651
|
+
"Enter the Node ID to remove delegate stake from",
|
|
652
|
+
min_value=0,
|
|
653
|
+
)
|
|
654
|
+
|
|
655
|
+
if shares_to_remove is None:
|
|
656
|
+
console.print(warning("Note: You must specify shares to remove, not amount."))
|
|
657
|
+
console.print(
|
|
658
|
+
info(
|
|
659
|
+
"Check your current shares with: htcli stake list --coldkey <your coldkey address>"
|
|
660
|
+
)
|
|
661
|
+
)
|
|
662
|
+
shares_to_remove = prompt.integer_prompt(
|
|
663
|
+
"Enter the number of shares to remove",
|
|
664
|
+
min_value=1,
|
|
665
|
+
)
|
|
666
|
+
|
|
667
|
+
return NodeDelegateStakeRemoveRequest(
|
|
668
|
+
subnet_id=subnet_id,
|
|
669
|
+
subnet_node_id=subnet_node_id,
|
|
670
|
+
node_delegate_stake_shares_to_be_removed=shares_to_remove,
|
|
671
|
+
)
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
def prompt_node_delegate_swap(
|
|
675
|
+
from_subnet_id: Optional[int] = None,
|
|
676
|
+
from_subnet_node_id: Optional[int] = None,
|
|
677
|
+
to_subnet_id: Optional[int] = None,
|
|
678
|
+
to_subnet_node_id: Optional[int] = None,
|
|
679
|
+
shares_to_swap: Optional[int] = None,
|
|
680
|
+
) -> NodeDelegateStakeSwapRequest:
|
|
681
|
+
"""Prompt for swapping node delegate shares between nodes."""
|
|
682
|
+
if from_subnet_id is None:
|
|
683
|
+
from_subnet_id = prompt.integer_prompt(
|
|
684
|
+
"Enter source subnet ID",
|
|
685
|
+
min_value=0,
|
|
686
|
+
)
|
|
687
|
+
if from_subnet_node_id is None:
|
|
688
|
+
from_subnet_node_id = prompt.integer_prompt(
|
|
689
|
+
"Enter source node ID",
|
|
690
|
+
min_value=0,
|
|
691
|
+
)
|
|
692
|
+
if to_subnet_id is None:
|
|
693
|
+
to_subnet_id = prompt.integer_prompt(
|
|
694
|
+
"Enter destination subnet ID",
|
|
695
|
+
min_value=0,
|
|
696
|
+
)
|
|
697
|
+
if to_subnet_node_id is None:
|
|
698
|
+
to_subnet_node_id = prompt.integer_prompt(
|
|
699
|
+
"Enter destination node ID",
|
|
700
|
+
min_value=0,
|
|
701
|
+
)
|
|
702
|
+
if shares_to_swap is None:
|
|
703
|
+
shares_to_swap = prompt.integer_prompt(
|
|
704
|
+
"Enter number of shares to swap",
|
|
705
|
+
min_value=1,
|
|
706
|
+
)
|
|
707
|
+
|
|
708
|
+
return NodeDelegateStakeSwapRequest(
|
|
709
|
+
from_subnet_id=from_subnet_id,
|
|
710
|
+
from_subnet_node_id=from_subnet_node_id,
|
|
711
|
+
to_subnet_id=to_subnet_id,
|
|
712
|
+
to_subnet_node_id=to_subnet_node_id,
|
|
713
|
+
node_delegate_stake_shares_to_swap=shares_to_swap,
|
|
714
|
+
)
|
|
715
|
+
|
|
716
|
+
|
|
717
|
+
def prompt_node_delegate_transfer(
|
|
718
|
+
subnet_id: Optional[int] = None,
|
|
719
|
+
subnet_node_id: Optional[int] = None,
|
|
720
|
+
to_account: Optional[str] = None,
|
|
721
|
+
shares_to_transfer: Optional[int] = None,
|
|
722
|
+
) -> NodeDelegateStakeTransferRequest:
|
|
723
|
+
"""Prompt for transferring node delegate shares."""
|
|
724
|
+
if subnet_id is None:
|
|
725
|
+
subnet_id = prompt.integer_prompt(
|
|
726
|
+
"Enter subnet ID",
|
|
727
|
+
min_value=0,
|
|
728
|
+
)
|
|
729
|
+
if subnet_node_id is None:
|
|
730
|
+
subnet_node_id = prompt.integer_prompt(
|
|
731
|
+
"Enter node ID",
|
|
732
|
+
min_value=0,
|
|
733
|
+
)
|
|
734
|
+
if to_account is None:
|
|
735
|
+
to_account = prompt.text_prompt("Enter destination account (0x...)")
|
|
736
|
+
if not validate_address(to_account):
|
|
737
|
+
raise ValueError("Destination account must be a valid Ethereum address")
|
|
738
|
+
|
|
739
|
+
if shares_to_transfer is None:
|
|
740
|
+
shares_to_transfer = prompt.integer_prompt(
|
|
741
|
+
"Enter number of shares to transfer",
|
|
742
|
+
min_value=1,
|
|
743
|
+
)
|
|
744
|
+
|
|
745
|
+
return NodeDelegateStakeTransferRequest(
|
|
746
|
+
subnet_id=subnet_id,
|
|
747
|
+
subnet_node_id=subnet_node_id,
|
|
748
|
+
to_account_id=to_account,
|
|
749
|
+
node_delegate_stake_shares_to_transfer=shares_to_transfer,
|
|
750
|
+
)
|
|
751
|
+
|
|
752
|
+
|
|
753
|
+
def prompt_node_delegate_donate(
|
|
754
|
+
subnet_id: Optional[int] = None,
|
|
755
|
+
subnet_node_id: Optional[int] = None,
|
|
756
|
+
amount: Optional[float] = None,
|
|
757
|
+
) -> NodeDelegateStakeDonateRequest:
|
|
758
|
+
"""Prompt for donating node delegate stake."""
|
|
759
|
+
if subnet_id is None:
|
|
760
|
+
subnet_id = prompt.integer_prompt(
|
|
761
|
+
"Enter subnet ID",
|
|
762
|
+
min_value=0,
|
|
763
|
+
)
|
|
764
|
+
if subnet_node_id is None:
|
|
765
|
+
subnet_node_id = prompt.integer_prompt(
|
|
766
|
+
"Enter node ID",
|
|
767
|
+
min_value=0,
|
|
768
|
+
)
|
|
769
|
+
# Validate amount if provided (amount is in TENSOR)
|
|
770
|
+
if amount is not None:
|
|
771
|
+
# Convert to float if it's an integer (treat integers as TENSOR)
|
|
772
|
+
if isinstance(amount, int):
|
|
773
|
+
amount = float(amount)
|
|
774
|
+
is_valid, error_msg = validate_stake_amount_prompt(amount)
|
|
775
|
+
if not is_valid:
|
|
776
|
+
print_error(f"Invalid amount: {error_msg}")
|
|
777
|
+
amount = None
|
|
778
|
+
|
|
779
|
+
if amount is None:
|
|
780
|
+
amount = amount_prompt(
|
|
781
|
+
"Enter donation amount",
|
|
782
|
+
currency="TENSOR",
|
|
783
|
+
min_amount=0.000001,
|
|
784
|
+
)
|
|
785
|
+
|
|
786
|
+
# Convert TENSOR to WEI for the request model (which expects WEI)
|
|
787
|
+
amount_wei = int(amount * 1e18)
|
|
788
|
+
|
|
789
|
+
return NodeDelegateStakeDonateRequest(
|
|
790
|
+
subnet_id=subnet_id,
|
|
791
|
+
subnet_node_id=subnet_node_id,
|
|
792
|
+
amount=amount_wei,
|
|
793
|
+
)
|
|
794
|
+
|
|
795
|
+
|
|
796
|
+
def prompt_swap_node_to_subnet(
|
|
797
|
+
from_subnet_id: Optional[int] = None,
|
|
798
|
+
from_subnet_node_id: Optional[int] = None,
|
|
799
|
+
to_subnet_id: Optional[int] = None,
|
|
800
|
+
shares_to_swap: Optional[int] = None,
|
|
801
|
+
) -> StakeSwapFromNodeToSubnetRequest:
|
|
802
|
+
"""Prompt for swapping node delegate shares into subnet shares."""
|
|
803
|
+
if from_subnet_id is None:
|
|
804
|
+
from_subnet_id = prompt.integer_prompt(
|
|
805
|
+
"Enter source subnet ID",
|
|
806
|
+
min_value=0,
|
|
807
|
+
)
|
|
808
|
+
if from_subnet_node_id is None:
|
|
809
|
+
from_subnet_node_id = prompt.integer_prompt(
|
|
810
|
+
"Enter source node ID",
|
|
811
|
+
min_value=0,
|
|
812
|
+
)
|
|
813
|
+
if to_subnet_id is None:
|
|
814
|
+
to_subnet_id = prompt.integer_prompt(
|
|
815
|
+
"Enter destination subnet ID",
|
|
816
|
+
min_value=0,
|
|
817
|
+
)
|
|
818
|
+
if shares_to_swap is None:
|
|
819
|
+
shares_to_swap = prompt.integer_prompt(
|
|
820
|
+
"Enter number of shares to swap",
|
|
821
|
+
min_value=1,
|
|
822
|
+
)
|
|
823
|
+
|
|
824
|
+
return StakeSwapFromNodeToSubnetRequest(
|
|
825
|
+
from_subnet_id=from_subnet_id,
|
|
826
|
+
from_subnet_node_id=from_subnet_node_id,
|
|
827
|
+
to_subnet_id=to_subnet_id,
|
|
828
|
+
node_delegate_stake_shares_to_swap=shares_to_swap,
|
|
829
|
+
)
|
|
830
|
+
|
|
831
|
+
|
|
832
|
+
def prompt_swap_subnet_to_node(
|
|
833
|
+
from_subnet_id: Optional[int] = None,
|
|
834
|
+
to_subnet_id: Optional[int] = None,
|
|
835
|
+
to_subnet_node_id: Optional[int] = None,
|
|
836
|
+
shares_to_swap: Optional[int] = None,
|
|
837
|
+
) -> StakeSwapFromSubnetToNodeRequest:
|
|
838
|
+
"""Prompt for swapping subnet delegate shares into node delegate shares."""
|
|
839
|
+
if from_subnet_id is None:
|
|
840
|
+
from_subnet_id = prompt.integer_prompt(
|
|
841
|
+
"Enter source subnet ID",
|
|
842
|
+
min_value=0,
|
|
843
|
+
)
|
|
844
|
+
if to_subnet_id is None:
|
|
845
|
+
to_subnet_id = prompt.integer_prompt(
|
|
846
|
+
"Enter destination subnet ID",
|
|
847
|
+
min_value=0,
|
|
848
|
+
)
|
|
849
|
+
if to_subnet_node_id is None:
|
|
850
|
+
to_subnet_node_id = prompt.integer_prompt(
|
|
851
|
+
"Enter destination node ID",
|
|
852
|
+
min_value=0,
|
|
853
|
+
)
|
|
854
|
+
if shares_to_swap is None:
|
|
855
|
+
shares_to_swap = prompt.integer_prompt(
|
|
856
|
+
"Enter number of shares to swap",
|
|
857
|
+
min_value=1,
|
|
858
|
+
)
|
|
859
|
+
|
|
860
|
+
return StakeSwapFromSubnetToNodeRequest(
|
|
861
|
+
from_subnet_id=from_subnet_id,
|
|
862
|
+
to_subnet_id=to_subnet_id,
|
|
863
|
+
to_subnet_node_id=to_subnet_node_id,
|
|
864
|
+
delegate_stake_shares_to_swap=shares_to_swap,
|
|
865
|
+
)
|
|
866
|
+
|
|
867
|
+
|
|
868
|
+
def prompt_swap_validator_to_subnet(
|
|
869
|
+
from_validator_id: Optional[int] = None,
|
|
870
|
+
to_subnet_id: Optional[int] = None,
|
|
871
|
+
shares_to_swap: Optional[int] = None,
|
|
872
|
+
) -> StakeSwapFromValidatorToSubnetRequest:
|
|
873
|
+
"""Prompt for swapping validator delegate shares into subnet shares."""
|
|
874
|
+
if from_validator_id is not None and from_validator_id < 0:
|
|
875
|
+
print_error("Invalid source validator ID: must be non-negative")
|
|
876
|
+
from_validator_id = None
|
|
877
|
+
if from_validator_id is None:
|
|
878
|
+
from_validator_id = prompt.integer_prompt(
|
|
879
|
+
"Enter source validator ID",
|
|
880
|
+
min_value=0,
|
|
881
|
+
)
|
|
882
|
+
if to_subnet_id is None:
|
|
883
|
+
to_subnet_id = prompt.integer_prompt(
|
|
884
|
+
"Enter destination subnet ID",
|
|
885
|
+
min_value=0,
|
|
886
|
+
)
|
|
887
|
+
if shares_to_swap is None:
|
|
888
|
+
shares_to_swap = prompt.integer_prompt(
|
|
889
|
+
"Enter number of shares to swap",
|
|
890
|
+
min_value=1,
|
|
891
|
+
)
|
|
892
|
+
|
|
893
|
+
return StakeSwapFromValidatorToSubnetRequest(
|
|
894
|
+
from_validator_id=from_validator_id,
|
|
895
|
+
to_subnet_id=to_subnet_id,
|
|
896
|
+
node_delegate_stake_shares_to_swap=shares_to_swap,
|
|
897
|
+
)
|
|
898
|
+
|
|
899
|
+
|
|
900
|
+
def prompt_swap_subnet_to_validator(
|
|
901
|
+
from_subnet_id: Optional[int] = None,
|
|
902
|
+
to_validator_id: Optional[int] = None,
|
|
903
|
+
shares_to_swap: Optional[int] = None,
|
|
904
|
+
) -> StakeSwapFromSubnetToValidatorRequest:
|
|
905
|
+
"""Prompt for swapping subnet delegate shares into validator shares."""
|
|
906
|
+
if from_subnet_id is None:
|
|
907
|
+
from_subnet_id = prompt.integer_prompt(
|
|
908
|
+
"Enter source subnet ID",
|
|
909
|
+
min_value=0,
|
|
910
|
+
)
|
|
911
|
+
if to_validator_id is not None and to_validator_id < 0:
|
|
912
|
+
print_error("Invalid destination validator ID: must be non-negative")
|
|
913
|
+
to_validator_id = None
|
|
914
|
+
if to_validator_id is None:
|
|
915
|
+
to_validator_id = prompt.integer_prompt(
|
|
916
|
+
"Enter destination validator ID",
|
|
917
|
+
min_value=0,
|
|
918
|
+
)
|
|
919
|
+
if shares_to_swap is None:
|
|
920
|
+
shares_to_swap = prompt.integer_prompt(
|
|
921
|
+
"Enter number of shares to swap",
|
|
922
|
+
min_value=1,
|
|
923
|
+
)
|
|
924
|
+
|
|
925
|
+
return StakeSwapFromSubnetToValidatorRequest(
|
|
926
|
+
from_subnet_id=from_subnet_id,
|
|
927
|
+
to_validator_id=to_validator_id,
|
|
928
|
+
subnet_delegate_stake_shares_to_swap=shares_to_swap,
|
|
929
|
+
)
|
|
930
|
+
|
|
931
|
+
|
|
932
|
+
def prompt_swap_queue_update(
|
|
933
|
+
queue_id: Optional[int] = None,
|
|
934
|
+
new_call_json: Optional[str] = None,
|
|
935
|
+
) -> StakeSwapQueueUpdateRequest:
|
|
936
|
+
"""Prompt for updating a swap queue entry."""
|
|
937
|
+
if queue_id is None:
|
|
938
|
+
queue_id = prompt.integer_prompt(
|
|
939
|
+
"Enter queue entry ID",
|
|
940
|
+
min_value=0,
|
|
941
|
+
)
|
|
942
|
+
|
|
943
|
+
if not new_call_json:
|
|
944
|
+
console.print(
|
|
945
|
+
info(
|
|
946
|
+
"Provide the new call payload as JSON. Example:\n"
|
|
947
|
+
'{"type": "SwapToNodeDelegateStake", "to_subnet_id": 1, "to_subnet_node_id": 3}'
|
|
948
|
+
)
|
|
949
|
+
)
|
|
950
|
+
new_call_json = prompt.text_prompt("Enter new call JSON")
|
|
951
|
+
|
|
952
|
+
try:
|
|
953
|
+
parsed = json.loads(new_call_json)
|
|
954
|
+
except json.JSONDecodeError as exc:
|
|
955
|
+
raise ValueError(f"Invalid JSON: {exc.msg}") from exc
|
|
956
|
+
|
|
957
|
+
return StakeSwapQueueUpdateRequest(
|
|
958
|
+
queue_id=queue_id,
|
|
959
|
+
new_call=parsed,
|
|
960
|
+
)
|
|
961
|
+
|
|
962
|
+
|
|
963
|
+
def prompt_delegate_swap(
|
|
964
|
+
from_subnet_id: Optional[int] = None,
|
|
965
|
+
to_subnet_id: Optional[int] = None,
|
|
966
|
+
shares: Optional[int] = None,
|
|
967
|
+
) -> DelegateStakeSwapRequest:
|
|
968
|
+
"""Prompt for swapping delegate stake between subnets."""
|
|
969
|
+
console.print(info("Swap Delegate Stake Between Subnets"))
|
|
970
|
+
console.print(
|
|
971
|
+
"Move your delegate stake from one subnet to another. Note: Swapping uses SHARES, not TENSOR amount.\\n"
|
|
972
|
+
)
|
|
973
|
+
|
|
974
|
+
if from_subnet_id is None:
|
|
975
|
+
from_subnet_id = prompt.integer_prompt(
|
|
976
|
+
"Enter source subnet ID",
|
|
977
|
+
min_value=0,
|
|
978
|
+
)
|
|
979
|
+
if to_subnet_id is None:
|
|
980
|
+
to_subnet_id = prompt.integer_prompt(
|
|
981
|
+
"Enter destination subnet ID",
|
|
982
|
+
min_value=0,
|
|
983
|
+
)
|
|
984
|
+
|
|
985
|
+
if shares is None:
|
|
986
|
+
shares = prompt.integer_prompt(
|
|
987
|
+
"Enter shares to swap",
|
|
988
|
+
min_value=1,
|
|
989
|
+
)
|
|
990
|
+
|
|
991
|
+
return DelegateStakeSwapRequest(
|
|
992
|
+
from_subnet_id=from_subnet_id,
|
|
993
|
+
to_subnet_id=to_subnet_id,
|
|
994
|
+
delegate_stake_shares_to_swap=shares,
|
|
995
|
+
)
|
|
996
|
+
|
|
997
|
+
|
|
998
|
+
def prompt_delegate_transfer(
|
|
999
|
+
subnet_id: Optional[int] = None,
|
|
1000
|
+
to_account: Optional[str] = None,
|
|
1001
|
+
shares: Optional[int] = None,
|
|
1002
|
+
) -> DelegateStakeTransferRequest:
|
|
1003
|
+
"""Prompt for transferring delegate stake to another account."""
|
|
1004
|
+
console.print(info("Transfer Delegate Stake"))
|
|
1005
|
+
console.print("Transfer your delegate stake to another account.\n")
|
|
1006
|
+
|
|
1007
|
+
if subnet_id is None:
|
|
1008
|
+
subnet_id = prompt.integer_prompt(
|
|
1009
|
+
"Enter subnet ID",
|
|
1010
|
+
min_value=0,
|
|
1011
|
+
)
|
|
1012
|
+
|
|
1013
|
+
if to_account is None:
|
|
1014
|
+
to_account = prompt.text_prompt("Enter destination account (0x...)")
|
|
1015
|
+
if not validate_address(to_account):
|
|
1016
|
+
raise ValueError("Destination account must be a valid Ethereum address")
|
|
1017
|
+
|
|
1018
|
+
if shares is None:
|
|
1019
|
+
shares = prompt.integer_prompt(
|
|
1020
|
+
"Enter shares to transfer",
|
|
1021
|
+
min_value=1,
|
|
1022
|
+
)
|
|
1023
|
+
|
|
1024
|
+
# The from_account will be the signer's address
|
|
1025
|
+
# We set a placeholder here that will be replaced during submission
|
|
1026
|
+
return DelegateStakeTransferRequest(
|
|
1027
|
+
subnet_id=subnet_id,
|
|
1028
|
+
from_account="0x0000000000000000000000000000000000000000", # Placeholder
|
|
1029
|
+
to_account=to_account,
|
|
1030
|
+
delegate_stake_shares_to_transfer=shares,
|
|
1031
|
+
)
|
|
1032
|
+
|
|
1033
|
+
|
|
1034
|
+
def prompt_delegate_donate(
|
|
1035
|
+
subnet_id: Optional[int] = None,
|
|
1036
|
+
amount: Optional[float] = None,
|
|
1037
|
+
) -> DelegateStakeDonateRequest:
|
|
1038
|
+
"""Prompt for donating delegate stake to subnet treasury."""
|
|
1039
|
+
console.print(info("Donate Delegate Stake"))
|
|
1040
|
+
console.print("Donate tokens to the subnet treasury.\n")
|
|
1041
|
+
console.print(
|
|
1042
|
+
warning("⚠️ This is a one-way transfer. Donated funds cannot be recovered.\n")
|
|
1043
|
+
)
|
|
1044
|
+
|
|
1045
|
+
if subnet_id is None:
|
|
1046
|
+
subnet_id = prompt.integer_prompt(
|
|
1047
|
+
"Enter subnet ID",
|
|
1048
|
+
min_value=0,
|
|
1049
|
+
)
|
|
1050
|
+
|
|
1051
|
+
# Validate amount if provided (amount is in TENSOR)
|
|
1052
|
+
if amount is not None:
|
|
1053
|
+
if isinstance(amount, int):
|
|
1054
|
+
amount = float(amount)
|
|
1055
|
+
is_valid, error_msg = validate_stake_amount_prompt(amount)
|
|
1056
|
+
if not is_valid:
|
|
1057
|
+
print_error(f"Invalid amount: {error_msg}")
|
|
1058
|
+
amount = None
|
|
1059
|
+
|
|
1060
|
+
if amount is None:
|
|
1061
|
+
amount = amount_prompt(
|
|
1062
|
+
"Enter donation amount",
|
|
1063
|
+
currency="TENSOR",
|
|
1064
|
+
min_amount=0.000001,
|
|
1065
|
+
)
|
|
1066
|
+
|
|
1067
|
+
# Confirm donation
|
|
1068
|
+
confirmed = confirm_prompt(
|
|
1069
|
+
f"Confirm donation of {amount:.4f} TENSOR to subnet {subnet_id}?", default=False
|
|
1070
|
+
)
|
|
1071
|
+
if not confirmed:
|
|
1072
|
+
raise KeyboardInterrupt("Operation cancelled")
|
|
1073
|
+
|
|
1074
|
+
# Convert TENSOR to WEI
|
|
1075
|
+
amount_wei = int(amount * 1e18)
|
|
1076
|
+
|
|
1077
|
+
return DelegateStakeDonateRequest(
|
|
1078
|
+
subnet_id=subnet_id,
|
|
1079
|
+
stake_amount=amount_wei,
|
|
1080
|
+
)
|