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,1504 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Staking extrinsics for HTCLI.
|
|
3
|
+
|
|
4
|
+
Provides all staking-related transaction operations for the Hypertensor blockchain.
|
|
5
|
+
Reference: mesh-template/mesh/substrate/chain_functions.py:685-911
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
from substrateinterface import Keypair, SubstrateInterface
|
|
11
|
+
|
|
12
|
+
from ...models.requests.staking import (
|
|
13
|
+
DelegateStakeAddRequest,
|
|
14
|
+
DelegateStakeDonateRequest,
|
|
15
|
+
DelegateStakeRemoveRequest,
|
|
16
|
+
DelegateStakeSwapRequest,
|
|
17
|
+
DelegateStakeTransferRequest,
|
|
18
|
+
NodeDelegateStakeAddRequest,
|
|
19
|
+
NodeDelegateStakeDonateRequest,
|
|
20
|
+
NodeDelegateStakeRemoveRequest,
|
|
21
|
+
NodeDelegateStakeSwapRequest,
|
|
22
|
+
NodeDelegateStakeTransferRequest,
|
|
23
|
+
StakeAddRequest,
|
|
24
|
+
StakeRemoveRequest,
|
|
25
|
+
StakeSwapFromNodeToSubnetRequest,
|
|
26
|
+
StakeSwapFromSubnetToNodeRequest,
|
|
27
|
+
StakeSwapFromSubnetToValidatorRequest,
|
|
28
|
+
StakeSwapFromValidatorToSubnetRequest,
|
|
29
|
+
StakeSwapQueueUpdateRequest,
|
|
30
|
+
ValidatorDelegateStakeAddRequest,
|
|
31
|
+
ValidatorDelegateStakeDonateRequest,
|
|
32
|
+
ValidatorDelegateStakeRemoveRequest,
|
|
33
|
+
ValidatorDelegateStakeSwapRequest,
|
|
34
|
+
ValidatorDelegateStakeTransferRequest,
|
|
35
|
+
)
|
|
36
|
+
from ...models.responses.staking import (
|
|
37
|
+
ClaimUnbondingsResponse,
|
|
38
|
+
DelegateStakeAddResponse,
|
|
39
|
+
DelegateStakeDonateResponse,
|
|
40
|
+
DelegateStakeRemoveResponse,
|
|
41
|
+
DelegateStakeSwapResponse,
|
|
42
|
+
DelegateStakeTransferResponse,
|
|
43
|
+
NodeDelegateStakeAddResponse,
|
|
44
|
+
NodeDelegateStakeDonateResponse,
|
|
45
|
+
NodeDelegateStakeRemoveResponse,
|
|
46
|
+
NodeDelegateStakeSwapResponse,
|
|
47
|
+
NodeDelegateStakeTransferResponse,
|
|
48
|
+
StakeAddResponse,
|
|
49
|
+
StakeRemoveResponse,
|
|
50
|
+
StakeSwapFromNodeToSubnetResponse,
|
|
51
|
+
StakeSwapFromSubnetToNodeResponse,
|
|
52
|
+
StakeSwapFromSubnetToValidatorResponse,
|
|
53
|
+
StakeSwapFromValidatorToSubnetResponse,
|
|
54
|
+
StakeSwapQueueUpdateResponse,
|
|
55
|
+
ValidatorDelegateStakeAddResponse,
|
|
56
|
+
ValidatorDelegateStakeDonateResponse,
|
|
57
|
+
ValidatorDelegateStakeRemoveResponse,
|
|
58
|
+
ValidatorDelegateStakeSwapResponse,
|
|
59
|
+
ValidatorDelegateStakeTransferResponse,
|
|
60
|
+
)
|
|
61
|
+
from ...utils.logging import get_logger
|
|
62
|
+
from .base import BaseExtrinsicClient, get_user_friendly_error
|
|
63
|
+
|
|
64
|
+
logger = get_logger(__name__)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class StakingExtrinsics(BaseExtrinsicClient):
|
|
68
|
+
"""Client for staking-related extrinsics."""
|
|
69
|
+
|
|
70
|
+
def __init__(self, substrate: Optional[SubstrateInterface] = None):
|
|
71
|
+
"""Initialize the staking client."""
|
|
72
|
+
super().__init__(substrate)
|
|
73
|
+
|
|
74
|
+
# ============================================================================
|
|
75
|
+
# DIRECT STAKING
|
|
76
|
+
# ============================================================================
|
|
77
|
+
|
|
78
|
+
def add_stake(self, request: StakeAddRequest, keypair: Keypair) -> StakeAddResponse:
|
|
79
|
+
"""
|
|
80
|
+
Add direct stake to a subnet node.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
request: Stake addition request
|
|
84
|
+
keypair: Keypair for signing (coldkey)
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
StakeAddResponse with transaction details
|
|
88
|
+
|
|
89
|
+
Reference: mesh-template lines 685-731
|
|
90
|
+
"""
|
|
91
|
+
try:
|
|
92
|
+
logger.info(
|
|
93
|
+
f"Adding {request.stake_amount} wei stake to subnet {request.subnet_id} "
|
|
94
|
+
f"node {request.subnet_node_id}"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
# Compose call
|
|
98
|
+
call = self.substrate.compose_call(
|
|
99
|
+
call_module="Network",
|
|
100
|
+
call_function="add_stake",
|
|
101
|
+
call_params={
|
|
102
|
+
"subnet_id": request.subnet_id,
|
|
103
|
+
"subnet_node_id": request.subnet_node_id,
|
|
104
|
+
"hotkey": request.hotkey,
|
|
105
|
+
"stake_to_be_added": request.stake_amount,
|
|
106
|
+
},
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Submit with retry
|
|
110
|
+
result = self._submit_extrinsic(call, keypair)
|
|
111
|
+
|
|
112
|
+
if result["success"]:
|
|
113
|
+
# Extract stake added event
|
|
114
|
+
stake_event = self._extract_event_by_type(
|
|
115
|
+
result.get("events", []), "Network", "StakeAdded"
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
response_data = {
|
|
119
|
+
"success": True,
|
|
120
|
+
"transaction_hash": result.get("extrinsic_hash"),
|
|
121
|
+
"block_hash": result.get("block_hash"),
|
|
122
|
+
"block_number": result.get("block_number"),
|
|
123
|
+
"subnet_id": request.subnet_id,
|
|
124
|
+
"subnet_node_id": request.subnet_node_id,
|
|
125
|
+
"hotkey": request.hotkey,
|
|
126
|
+
"stake_added": request.stake_amount,
|
|
127
|
+
"message": f"Successfully staked {request.stake_amount / 1e18:.4f} TENSOR",
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
# Try to get total stake from event if available
|
|
131
|
+
if stake_event:
|
|
132
|
+
# Event attributes: (coldkey, hotkey, subnet_id, amount)
|
|
133
|
+
response_data["stake_added"] = self._extract_attribute_value(
|
|
134
|
+
stake_event, 3, request.stake_amount
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
return StakeAddResponse(**response_data)
|
|
138
|
+
else:
|
|
139
|
+
error_msg = get_user_friendly_error(
|
|
140
|
+
result.get("error", "Unknown error")
|
|
141
|
+
)
|
|
142
|
+
return StakeAddResponse(
|
|
143
|
+
success=False,
|
|
144
|
+
error=error_msg,
|
|
145
|
+
message=f"Failed to add stake: {error_msg}",
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
except Exception as e:
|
|
149
|
+
logger.error(f"Error adding stake: {e}")
|
|
150
|
+
return StakeAddResponse(
|
|
151
|
+
success=False, error=str(e), message=f"Error adding stake: {str(e)}"
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
def remove_stake(
|
|
155
|
+
self, request: StakeRemoveRequest, keypair: Keypair
|
|
156
|
+
) -> StakeRemoveResponse:
|
|
157
|
+
"""
|
|
158
|
+
Remove stake from a subnet node (creates unbonding entry).
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
request: Stake removal request
|
|
162
|
+
keypair: Keypair for signing (coldkey)
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
StakeRemoveResponse with transaction details
|
|
166
|
+
|
|
167
|
+
Reference: mesh-template lines 733-777
|
|
168
|
+
"""
|
|
169
|
+
try:
|
|
170
|
+
logger.info(
|
|
171
|
+
f"Removing {request.stake_amount} wei stake from subnet {request.subnet_id}"
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
# Compose call
|
|
175
|
+
call = self.substrate.compose_call(
|
|
176
|
+
call_module="Network",
|
|
177
|
+
call_function="remove_stake",
|
|
178
|
+
call_params={
|
|
179
|
+
"subnet_id": request.subnet_id,
|
|
180
|
+
"hotkey": request.hotkey,
|
|
181
|
+
"stake_to_be_removed": request.stake_amount,
|
|
182
|
+
},
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
# Submit with retry
|
|
186
|
+
result = self._submit_extrinsic(call, keypair)
|
|
187
|
+
|
|
188
|
+
if result["success"]:
|
|
189
|
+
response_data = {
|
|
190
|
+
"success": True,
|
|
191
|
+
"transaction_hash": result.get("extrinsic_hash"),
|
|
192
|
+
"block_hash": result.get("block_hash"),
|
|
193
|
+
"block_number": result.get("block_number"),
|
|
194
|
+
"subnet_id": request.subnet_id,
|
|
195
|
+
"stake_removed": request.stake_amount,
|
|
196
|
+
"message": f"Successfully removed {request.stake_amount / 1e18:.4f} TENSOR (will unbond in 7 epochs)",
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return StakeRemoveResponse(**response_data)
|
|
200
|
+
else:
|
|
201
|
+
error_msg = get_user_friendly_error(
|
|
202
|
+
result.get("error", "Unknown error")
|
|
203
|
+
)
|
|
204
|
+
return StakeRemoveResponse(
|
|
205
|
+
success=False,
|
|
206
|
+
error=error_msg,
|
|
207
|
+
message=f"Failed to remove stake: {error_msg}",
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
except Exception as e:
|
|
211
|
+
logger.error(f"Error removing stake: {e}")
|
|
212
|
+
return StakeRemoveResponse(
|
|
213
|
+
success=False, error=str(e), message=f"Error removing stake: {str(e)}"
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
def claim_unbondings(self, keypair: Keypair) -> ClaimUnbondingsResponse:
|
|
217
|
+
"""
|
|
218
|
+
Claim all unbonded stake that has completed the unbonding period.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
keypair: Keypair for signing (coldkey)
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
ClaimUnbondingsResponse with claim details
|
|
225
|
+
|
|
226
|
+
Reference: mesh-template lines 779-805
|
|
227
|
+
"""
|
|
228
|
+
try:
|
|
229
|
+
logger.info("Claiming unbonded stake")
|
|
230
|
+
|
|
231
|
+
# Compose call
|
|
232
|
+
call = self.substrate.compose_call(
|
|
233
|
+
call_module="Network", call_function="claim_unbondings", call_params={}
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
# Submit with retry
|
|
237
|
+
result = self._submit_extrinsic(call, keypair)
|
|
238
|
+
|
|
239
|
+
if result["success"]:
|
|
240
|
+
# Try to extract amount from event
|
|
241
|
+
claim_event = self._extract_event_by_type(
|
|
242
|
+
result.get("events", []), "Network", "UnbondingsClaimed"
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
response_data = {
|
|
246
|
+
"success": True,
|
|
247
|
+
"transaction_hash": result.get("extrinsic_hash"),
|
|
248
|
+
"block_hash": result.get("block_hash"),
|
|
249
|
+
"block_number": result.get("block_number"),
|
|
250
|
+
"message": "Successfully claimed unbonded stake",
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if claim_event:
|
|
254
|
+
total_claimed = self._extract_attribute_value(claim_event, 1, 0)
|
|
255
|
+
response_data["total_claimed"] = total_claimed
|
|
256
|
+
response_data["message"] = (
|
|
257
|
+
f"Claimed {total_claimed / 1e18:.4f} TENSOR"
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
return ClaimUnbondingsResponse(**response_data)
|
|
261
|
+
else:
|
|
262
|
+
error_msg = get_user_friendly_error(
|
|
263
|
+
result.get("error", "Unknown error")
|
|
264
|
+
)
|
|
265
|
+
return ClaimUnbondingsResponse(
|
|
266
|
+
success=False,
|
|
267
|
+
error=error_msg,
|
|
268
|
+
message=f"Failed to claim unbondings: {error_msg}",
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
except Exception as e:
|
|
272
|
+
logger.error(f"Error claiming unbondings: {e}")
|
|
273
|
+
return ClaimUnbondingsResponse(
|
|
274
|
+
success=False,
|
|
275
|
+
error=str(e),
|
|
276
|
+
message=f"Error claiming unbondings: {str(e)}",
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
# ============================================================================
|
|
280
|
+
# DELEGATE STAKING (SUBNET LEVEL)
|
|
281
|
+
# ============================================================================
|
|
282
|
+
|
|
283
|
+
def add_to_delegate_stake(
|
|
284
|
+
self, request: DelegateStakeAddRequest, keypair: Keypair
|
|
285
|
+
) -> DelegateStakeAddResponse:
|
|
286
|
+
"""
|
|
287
|
+
Add delegate stake to a subnet.
|
|
288
|
+
|
|
289
|
+
Args:
|
|
290
|
+
request: Delegate stake addition request
|
|
291
|
+
keypair: Keypair for signing (coldkey)
|
|
292
|
+
|
|
293
|
+
Returns:
|
|
294
|
+
DelegateStakeAddResponse with share details
|
|
295
|
+
|
|
296
|
+
Reference: mesh-template lines 807-849
|
|
297
|
+
"""
|
|
298
|
+
try:
|
|
299
|
+
logger.info(
|
|
300
|
+
f"Adding {request.stake_amount} wei delegate stake to subnet {request.subnet_id}"
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
# Compose call
|
|
304
|
+
call = self.substrate.compose_call(
|
|
305
|
+
call_module="Network",
|
|
306
|
+
call_function="add_to_delegate_stake",
|
|
307
|
+
call_params={
|
|
308
|
+
"subnet_id": request.subnet_id,
|
|
309
|
+
"stake_to_be_added": request.stake_amount,
|
|
310
|
+
},
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
# Submit with retry
|
|
314
|
+
result = self._submit_extrinsic(call, keypair)
|
|
315
|
+
|
|
316
|
+
if result["success"]:
|
|
317
|
+
# Extract amount from events
|
|
318
|
+
amount_added = request.stake_amount
|
|
319
|
+
shares_received = 0
|
|
320
|
+
|
|
321
|
+
for event in result.get("events", []):
|
|
322
|
+
if hasattr(event, "value"):
|
|
323
|
+
event_id = event.value.get("event_id", "")
|
|
324
|
+
if event_id == "SubnetDelegateStakeAdded":
|
|
325
|
+
attributes = event.value.get("attributes", {})
|
|
326
|
+
if isinstance(attributes, dict):
|
|
327
|
+
amount_added = attributes.get("amount", amount_added)
|
|
328
|
+
elif (
|
|
329
|
+
isinstance(attributes, (list, tuple))
|
|
330
|
+
and len(attributes) >= 3
|
|
331
|
+
):
|
|
332
|
+
# [subnet_id, account_id, amount]
|
|
333
|
+
amount_added = attributes[2]
|
|
334
|
+
|
|
335
|
+
response_data = {
|
|
336
|
+
"success": True,
|
|
337
|
+
"transaction_hash": result.get("extrinsic_hash"),
|
|
338
|
+
"block_hash": result.get("block_hash"),
|
|
339
|
+
"block_number": result.get("block_number"),
|
|
340
|
+
"subnet_id": request.subnet_id,
|
|
341
|
+
"stake_added": amount_added,
|
|
342
|
+
"shares_received": shares_received,
|
|
343
|
+
"message": f"Successfully delegated {amount_added / 1e18:.4f} TENSOR",
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return DelegateStakeAddResponse(**response_data)
|
|
347
|
+
else:
|
|
348
|
+
error_msg = get_user_friendly_error(
|
|
349
|
+
result.get("error", "Unknown error")
|
|
350
|
+
)
|
|
351
|
+
return DelegateStakeAddResponse(
|
|
352
|
+
success=False,
|
|
353
|
+
error=error_msg,
|
|
354
|
+
message=f"Failed to add delegate stake: {error_msg}",
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
except Exception as e:
|
|
358
|
+
logger.error(f"Error adding delegate stake: {e}")
|
|
359
|
+
return DelegateStakeAddResponse(
|
|
360
|
+
success=False,
|
|
361
|
+
error=str(e),
|
|
362
|
+
message=f"Error adding delegate stake: {str(e)}",
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
def remove_delegate_stake(
|
|
366
|
+
self, request: DelegateStakeRemoveRequest, keypair: Keypair
|
|
367
|
+
) -> DelegateStakeRemoveResponse:
|
|
368
|
+
"""
|
|
369
|
+
Remove delegate stake from a subnet.
|
|
370
|
+
|
|
371
|
+
Args:
|
|
372
|
+
request: Delegate stake removal request
|
|
373
|
+
keypair: Keypair for signing (coldkey)
|
|
374
|
+
|
|
375
|
+
Returns:
|
|
376
|
+
DelegateStakeRemoveResponse with balance details
|
|
377
|
+
"""
|
|
378
|
+
try:
|
|
379
|
+
logger.info(
|
|
380
|
+
f"Removing {request.shares_to_be_removed} shares from subnet {request.subnet_id}"
|
|
381
|
+
)
|
|
382
|
+
|
|
383
|
+
# Compose call
|
|
384
|
+
call = self.substrate.compose_call(
|
|
385
|
+
call_module="Network",
|
|
386
|
+
call_function="remove_delegate_stake",
|
|
387
|
+
call_params={
|
|
388
|
+
"subnet_id": request.subnet_id,
|
|
389
|
+
"shares_to_be_removed": request.shares_to_be_removed, # Blockchain uses shares
|
|
390
|
+
},
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
# Submit with retry
|
|
394
|
+
result = self._submit_extrinsic(call, keypair)
|
|
395
|
+
|
|
396
|
+
if result["success"]:
|
|
397
|
+
response_data = {
|
|
398
|
+
"success": True,
|
|
399
|
+
"transaction_hash": result.get("extrinsic_hash"),
|
|
400
|
+
"block_hash": result.get("block_hash"),
|
|
401
|
+
"block_number": result.get("block_number"),
|
|
402
|
+
"subnet_id": request.subnet_id,
|
|
403
|
+
"message": f"Successfully removed {request.shares_to_be_removed} shares from delegate stake",
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return DelegateStakeRemoveResponse(**response_data)
|
|
407
|
+
else:
|
|
408
|
+
error_msg = get_user_friendly_error(
|
|
409
|
+
result.get("error", "Unknown error")
|
|
410
|
+
)
|
|
411
|
+
return DelegateStakeRemoveResponse(
|
|
412
|
+
success=False,
|
|
413
|
+
error=error_msg,
|
|
414
|
+
message=f"Failed to remove delegate stake: {error_msg}",
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
except Exception as e:
|
|
418
|
+
logger.error(f"Error removing delegate stake: {e}")
|
|
419
|
+
return DelegateStakeRemoveResponse(
|
|
420
|
+
success=False,
|
|
421
|
+
error=str(e),
|
|
422
|
+
message=f"Error removing delegate stake: {str(e)}",
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
def transfer_delegate_stake(
|
|
426
|
+
self, request: DelegateStakeTransferRequest, keypair: Keypair
|
|
427
|
+
) -> DelegateStakeTransferResponse:
|
|
428
|
+
"""
|
|
429
|
+
Transfer delegate stake between accounts.
|
|
430
|
+
|
|
431
|
+
Args:
|
|
432
|
+
request: Delegate stake transfer request
|
|
433
|
+
keypair: Keypair for signing
|
|
434
|
+
|
|
435
|
+
Returns:
|
|
436
|
+
DelegateStakeTransferResponse
|
|
437
|
+
"""
|
|
438
|
+
try:
|
|
439
|
+
logger.info(
|
|
440
|
+
f"Transferring {request.delegate_stake_shares_to_transfer} shares delegate stake "
|
|
441
|
+
f"from {request.from_account} to {request.to_account}"
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
# Compose call
|
|
445
|
+
call = self.substrate.compose_call(
|
|
446
|
+
call_module="Network",
|
|
447
|
+
call_function="transfer_delegate_stake",
|
|
448
|
+
call_params={
|
|
449
|
+
"subnet_id": request.subnet_id,
|
|
450
|
+
"from": request.from_account,
|
|
451
|
+
"to_account_id": request.to_account,
|
|
452
|
+
"delegate_stake_shares_to_transfer": request.delegate_stake_shares_to_transfer,
|
|
453
|
+
},
|
|
454
|
+
)
|
|
455
|
+
|
|
456
|
+
# Submit with retry
|
|
457
|
+
result = self._submit_extrinsic(call, keypair)
|
|
458
|
+
|
|
459
|
+
if result["success"]:
|
|
460
|
+
return DelegateStakeTransferResponse(
|
|
461
|
+
success=True,
|
|
462
|
+
transaction_hash=result.get("extrinsic_hash"),
|
|
463
|
+
block_number=result.get("block_number"),
|
|
464
|
+
subnet_id=request.subnet_id,
|
|
465
|
+
from_account=request.from_account,
|
|
466
|
+
to_account=request.to_account,
|
|
467
|
+
message="Delegate stake transferred successfully",
|
|
468
|
+
)
|
|
469
|
+
else:
|
|
470
|
+
error_msg = get_user_friendly_error(
|
|
471
|
+
result.get("error", "Unknown error")
|
|
472
|
+
)
|
|
473
|
+
return DelegateStakeTransferResponse(
|
|
474
|
+
success=False,
|
|
475
|
+
error=error_msg,
|
|
476
|
+
message=f"Failed to transfer delegate stake: {error_msg}",
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
except Exception as e:
|
|
480
|
+
logger.error(f"Error transferring delegate stake: {e}")
|
|
481
|
+
return DelegateStakeTransferResponse(
|
|
482
|
+
success=False,
|
|
483
|
+
error=str(e),
|
|
484
|
+
message=f"Error transferring delegate stake: {str(e)}",
|
|
485
|
+
)
|
|
486
|
+
|
|
487
|
+
def swap_delegate_stake(
|
|
488
|
+
self, request: DelegateStakeSwapRequest, keypair: Keypair
|
|
489
|
+
) -> DelegateStakeSwapResponse:
|
|
490
|
+
"""
|
|
491
|
+
Swap delegate stake between subnets.
|
|
492
|
+
|
|
493
|
+
Args:
|
|
494
|
+
request: Delegate stake swap request
|
|
495
|
+
keypair: Keypair for signing
|
|
496
|
+
|
|
497
|
+
Returns:
|
|
498
|
+
DelegateStakeSwapResponse
|
|
499
|
+
"""
|
|
500
|
+
try:
|
|
501
|
+
logger.info(
|
|
502
|
+
f"Swapping {request.delegate_stake_shares_to_swap} shares delegate stake "
|
|
503
|
+
f"from subnet {request.from_subnet_id} to {request.to_subnet_id}"
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
# Compose call
|
|
507
|
+
call = self.substrate.compose_call(
|
|
508
|
+
call_module="Network",
|
|
509
|
+
call_function="swap_delegate_stake",
|
|
510
|
+
call_params={
|
|
511
|
+
"from_subnet_id": request.from_subnet_id,
|
|
512
|
+
"to_subnet_id": request.to_subnet_id,
|
|
513
|
+
"delegate_stake_shares_to_swap": request.delegate_stake_shares_to_swap,
|
|
514
|
+
},
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
# Submit with retry
|
|
518
|
+
result = self._submit_extrinsic(call, keypair)
|
|
519
|
+
|
|
520
|
+
if result["success"]:
|
|
521
|
+
return DelegateStakeSwapResponse(
|
|
522
|
+
success=True,
|
|
523
|
+
transaction_hash=result.get("extrinsic_hash"),
|
|
524
|
+
block_hash=result.get("block_hash"),
|
|
525
|
+
block_number=result.get("block_number"),
|
|
526
|
+
from_subnet_id=request.from_subnet_id,
|
|
527
|
+
to_subnet_id=request.to_subnet_id,
|
|
528
|
+
message="Delegate stake swapped successfully",
|
|
529
|
+
)
|
|
530
|
+
else:
|
|
531
|
+
error_msg = get_user_friendly_error(
|
|
532
|
+
result.get("error", "Unknown error")
|
|
533
|
+
)
|
|
534
|
+
return DelegateStakeSwapResponse(
|
|
535
|
+
success=False,
|
|
536
|
+
error=error_msg,
|
|
537
|
+
message=f"Failed to swap delegate stake: {error_msg}",
|
|
538
|
+
)
|
|
539
|
+
|
|
540
|
+
except Exception as e:
|
|
541
|
+
logger.error(f"Error swapping delegate stake: {e}")
|
|
542
|
+
return DelegateStakeSwapResponse(
|
|
543
|
+
success=False,
|
|
544
|
+
error=str(e),
|
|
545
|
+
message=f"Error swapping delegate stake: {str(e)}",
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
def donate_delegate_stake(
|
|
549
|
+
self, request: DelegateStakeDonateRequest, keypair: Keypair
|
|
550
|
+
) -> DelegateStakeDonateResponse:
|
|
551
|
+
"""
|
|
552
|
+
Donate delegate stake to subnet treasury.
|
|
553
|
+
|
|
554
|
+
Args:
|
|
555
|
+
request: Delegate stake donation request
|
|
556
|
+
keypair: Keypair for signing
|
|
557
|
+
|
|
558
|
+
Returns:
|
|
559
|
+
DelegateStakeDonateResponse
|
|
560
|
+
"""
|
|
561
|
+
try:
|
|
562
|
+
logger.info(
|
|
563
|
+
f"Donating {request.stake_amount} wei delegate stake to subnet {request.subnet_id}"
|
|
564
|
+
)
|
|
565
|
+
|
|
566
|
+
# Compose call
|
|
567
|
+
call = self.substrate.compose_call(
|
|
568
|
+
call_module="Network",
|
|
569
|
+
call_function="donate_delegate_stake",
|
|
570
|
+
call_params={
|
|
571
|
+
"subnet_id": request.subnet_id,
|
|
572
|
+
"amount": request.stake_amount,
|
|
573
|
+
},
|
|
574
|
+
)
|
|
575
|
+
|
|
576
|
+
# Submit with retry
|
|
577
|
+
result = self._submit_extrinsic(call, keypair)
|
|
578
|
+
|
|
579
|
+
if result["success"]:
|
|
580
|
+
return DelegateStakeDonateResponse(
|
|
581
|
+
success=True,
|
|
582
|
+
transaction_hash=result.get("extrinsic_hash"),
|
|
583
|
+
block_hash=result.get("block_hash"),
|
|
584
|
+
block_number=result.get("block_number"),
|
|
585
|
+
subnet_id=request.subnet_id,
|
|
586
|
+
message=f"Donated {request.stake_amount / 1e18:.4f} TENSOR to subnet treasury",
|
|
587
|
+
)
|
|
588
|
+
else:
|
|
589
|
+
error_msg = get_user_friendly_error(
|
|
590
|
+
result.get("error", "Unknown error")
|
|
591
|
+
)
|
|
592
|
+
return DelegateStakeDonateResponse(
|
|
593
|
+
success=False,
|
|
594
|
+
error=error_msg,
|
|
595
|
+
message=f"Failed to donate delegate stake: {error_msg}",
|
|
596
|
+
)
|
|
597
|
+
|
|
598
|
+
except Exception as e:
|
|
599
|
+
logger.error(f"Error donating delegate stake: {e}")
|
|
600
|
+
return DelegateStakeDonateResponse(
|
|
601
|
+
success=False,
|
|
602
|
+
error=str(e),
|
|
603
|
+
message=f"Error donating delegate stake: {str(e)}",
|
|
604
|
+
)
|
|
605
|
+
|
|
606
|
+
# ============================================================================
|
|
607
|
+
# VALIDATOR DELEGATE STAKING
|
|
608
|
+
# ============================================================================
|
|
609
|
+
|
|
610
|
+
def add_to_validator_delegate_stake(
|
|
611
|
+
self, request: ValidatorDelegateStakeAddRequest, keypair: Keypair
|
|
612
|
+
) -> ValidatorDelegateStakeAddResponse:
|
|
613
|
+
"""Add delegate stake to a validator."""
|
|
614
|
+
try:
|
|
615
|
+
logger.info(
|
|
616
|
+
f"Adding {request.delegate_stake_to_be_added} wei validator delegate stake "
|
|
617
|
+
f"to validator {request.validator_id}"
|
|
618
|
+
)
|
|
619
|
+
|
|
620
|
+
call = self.substrate.compose_call(
|
|
621
|
+
call_module="Network",
|
|
622
|
+
call_function="add_validator_delegate_stake",
|
|
623
|
+
call_params={
|
|
624
|
+
"validator_id": request.validator_id,
|
|
625
|
+
"delegate_stake_to_be_added": request.delegate_stake_to_be_added,
|
|
626
|
+
},
|
|
627
|
+
)
|
|
628
|
+
|
|
629
|
+
result = self._submit_extrinsic(call, keypair)
|
|
630
|
+
|
|
631
|
+
if result["success"]:
|
|
632
|
+
return ValidatorDelegateStakeAddResponse(
|
|
633
|
+
success=True,
|
|
634
|
+
transaction_hash=result.get("extrinsic_hash"),
|
|
635
|
+
block_hash=result.get("block_hash"),
|
|
636
|
+
block_number=result.get("block_number"),
|
|
637
|
+
validator_id=request.validator_id,
|
|
638
|
+
stake_added=request.delegate_stake_to_be_added,
|
|
639
|
+
shares_received=0,
|
|
640
|
+
message=f"Successfully delegated {request.delegate_stake_to_be_added / 1e18:.4f} TENSOR to validator",
|
|
641
|
+
)
|
|
642
|
+
error_msg = get_user_friendly_error(result.get("error", "Unknown error"))
|
|
643
|
+
return ValidatorDelegateStakeAddResponse(
|
|
644
|
+
success=False,
|
|
645
|
+
error=error_msg,
|
|
646
|
+
message=f"Failed to add validator delegate stake: {error_msg}",
|
|
647
|
+
)
|
|
648
|
+
|
|
649
|
+
except Exception as e:
|
|
650
|
+
logger.error(f"Error adding validator delegate stake: {e}")
|
|
651
|
+
return ValidatorDelegateStakeAddResponse(
|
|
652
|
+
success=False,
|
|
653
|
+
error=str(e),
|
|
654
|
+
message=f"Error adding validator delegate stake: {str(e)}",
|
|
655
|
+
)
|
|
656
|
+
|
|
657
|
+
def remove_validator_delegate_stake(
|
|
658
|
+
self, request: ValidatorDelegateStakeRemoveRequest, keypair: Keypair
|
|
659
|
+
) -> ValidatorDelegateStakeRemoveResponse:
|
|
660
|
+
"""Remove delegate stake from a validator."""
|
|
661
|
+
try:
|
|
662
|
+
logger.info(
|
|
663
|
+
f"Removing {request.validator_delegate_stake_shares_to_be_removed} "
|
|
664
|
+
f"validator delegate shares from validator {request.validator_id}"
|
|
665
|
+
)
|
|
666
|
+
|
|
667
|
+
call = self.substrate.compose_call(
|
|
668
|
+
call_module="Network",
|
|
669
|
+
call_function="remove_validator_delegate_stake",
|
|
670
|
+
call_params={
|
|
671
|
+
"validator_id": request.validator_id,
|
|
672
|
+
"validator_delegate_stake_shares_to_be_removed": request.validator_delegate_stake_shares_to_be_removed,
|
|
673
|
+
},
|
|
674
|
+
)
|
|
675
|
+
|
|
676
|
+
result = self._submit_extrinsic(call, keypair)
|
|
677
|
+
|
|
678
|
+
if result["success"]:
|
|
679
|
+
return ValidatorDelegateStakeRemoveResponse(
|
|
680
|
+
success=True,
|
|
681
|
+
transaction_hash=result.get("extrinsic_hash"),
|
|
682
|
+
block_hash=result.get("block_hash"),
|
|
683
|
+
block_number=result.get("block_number"),
|
|
684
|
+
validator_id=request.validator_id,
|
|
685
|
+
shares_removed=request.validator_delegate_stake_shares_to_be_removed,
|
|
686
|
+
message=f"Successfully removed {request.validator_delegate_stake_shares_to_be_removed} shares",
|
|
687
|
+
)
|
|
688
|
+
error_msg = get_user_friendly_error(result.get("error", "Unknown error"))
|
|
689
|
+
return ValidatorDelegateStakeRemoveResponse(
|
|
690
|
+
success=False,
|
|
691
|
+
error=error_msg,
|
|
692
|
+
message=f"Failed to remove validator delegate stake: {error_msg}",
|
|
693
|
+
)
|
|
694
|
+
|
|
695
|
+
except Exception as e:
|
|
696
|
+
logger.error(f"Error removing validator delegate stake: {e}")
|
|
697
|
+
return ValidatorDelegateStakeRemoveResponse(
|
|
698
|
+
success=False,
|
|
699
|
+
error=str(e),
|
|
700
|
+
message=f"Error removing validator delegate stake: {str(e)}",
|
|
701
|
+
)
|
|
702
|
+
|
|
703
|
+
def transfer_validator_delegate_stake(
|
|
704
|
+
self, request: ValidatorDelegateStakeTransferRequest, keypair: Keypair
|
|
705
|
+
) -> ValidatorDelegateStakeTransferResponse:
|
|
706
|
+
"""Transfer validator delegate stake shares to another account."""
|
|
707
|
+
try:
|
|
708
|
+
logger.info(
|
|
709
|
+
f"Transferring {request.validator_delegate_stake_shares_to_transfer} "
|
|
710
|
+
f"validator delegate shares from validator {request.validator_id} "
|
|
711
|
+
f"to account {request.to_account_id}"
|
|
712
|
+
)
|
|
713
|
+
|
|
714
|
+
call = self.substrate.compose_call(
|
|
715
|
+
call_module="Network",
|
|
716
|
+
call_function="transfer_validator_delegate_stake",
|
|
717
|
+
call_params={
|
|
718
|
+
"validator_id": request.validator_id,
|
|
719
|
+
"to_account_id": request.to_account_id,
|
|
720
|
+
"validator_delegate_stake_shares_to_transfer": request.validator_delegate_stake_shares_to_transfer,
|
|
721
|
+
},
|
|
722
|
+
)
|
|
723
|
+
|
|
724
|
+
result = self._submit_extrinsic(call, keypair)
|
|
725
|
+
|
|
726
|
+
if result["success"]:
|
|
727
|
+
return ValidatorDelegateStakeTransferResponse(
|
|
728
|
+
success=True,
|
|
729
|
+
transaction_hash=result.get("extrinsic_hash"),
|
|
730
|
+
block_hash=result.get("block_hash"),
|
|
731
|
+
block_number=result.get("block_number"),
|
|
732
|
+
validator_id=request.validator_id,
|
|
733
|
+
to_account=request.to_account_id,
|
|
734
|
+
shares_transferred=request.validator_delegate_stake_shares_to_transfer,
|
|
735
|
+
message="Validator delegate stake transferred successfully",
|
|
736
|
+
)
|
|
737
|
+
error_msg = get_user_friendly_error(result.get("error", "Unknown error"))
|
|
738
|
+
return ValidatorDelegateStakeTransferResponse(
|
|
739
|
+
success=False,
|
|
740
|
+
error=error_msg,
|
|
741
|
+
message=f"Failed to transfer validator delegate stake: {error_msg}",
|
|
742
|
+
)
|
|
743
|
+
|
|
744
|
+
except Exception as e:
|
|
745
|
+
logger.error(f"Error transferring validator delegate stake: {e}")
|
|
746
|
+
return ValidatorDelegateStakeTransferResponse(
|
|
747
|
+
success=False,
|
|
748
|
+
error=str(e),
|
|
749
|
+
message=f"Error transferring validator delegate stake: {str(e)}",
|
|
750
|
+
)
|
|
751
|
+
|
|
752
|
+
def swap_validator_delegate_stake(
|
|
753
|
+
self, request: ValidatorDelegateStakeSwapRequest, keypair: Keypair
|
|
754
|
+
) -> ValidatorDelegateStakeSwapResponse:
|
|
755
|
+
"""Swap validator delegate stake shares between validators."""
|
|
756
|
+
try:
|
|
757
|
+
logger.info(
|
|
758
|
+
"Swapping validator delegate shares from validator %s to validator %s",
|
|
759
|
+
request.from_validator_id,
|
|
760
|
+
request.to_validator_id,
|
|
761
|
+
)
|
|
762
|
+
|
|
763
|
+
call = self.substrate.compose_call(
|
|
764
|
+
call_module="Network",
|
|
765
|
+
call_function="swap_validator_delegate_stake",
|
|
766
|
+
call_params={
|
|
767
|
+
"from_validator_id": request.from_validator_id,
|
|
768
|
+
"to_validator_id": request.to_validator_id,
|
|
769
|
+
"stake_to_be_removed": request.stake_to_be_removed,
|
|
770
|
+
},
|
|
771
|
+
)
|
|
772
|
+
|
|
773
|
+
result = self._submit_extrinsic(call, keypair)
|
|
774
|
+
|
|
775
|
+
if result["success"]:
|
|
776
|
+
return ValidatorDelegateStakeSwapResponse(
|
|
777
|
+
success=True,
|
|
778
|
+
transaction_hash=result.get("extrinsic_hash"),
|
|
779
|
+
block_hash=result.get("block_hash"),
|
|
780
|
+
block_number=result.get("block_number"),
|
|
781
|
+
from_validator_id=request.from_validator_id,
|
|
782
|
+
to_validator_id=request.to_validator_id,
|
|
783
|
+
shares_swapped=request.stake_to_be_removed,
|
|
784
|
+
message="Validator delegate stake swap submitted",
|
|
785
|
+
)
|
|
786
|
+
error_msg = get_user_friendly_error(result.get("error", "Unknown error"))
|
|
787
|
+
return ValidatorDelegateStakeSwapResponse(
|
|
788
|
+
success=False,
|
|
789
|
+
error=error_msg,
|
|
790
|
+
message=f"Failed to swap validator delegate stake: {error_msg}",
|
|
791
|
+
)
|
|
792
|
+
|
|
793
|
+
except Exception as e:
|
|
794
|
+
logger.error(f"Error swapping validator delegate stake: {e}")
|
|
795
|
+
return ValidatorDelegateStakeSwapResponse(
|
|
796
|
+
success=False,
|
|
797
|
+
error=str(e),
|
|
798
|
+
message=f"Error swapping validator delegate stake: {str(e)}",
|
|
799
|
+
)
|
|
800
|
+
|
|
801
|
+
def donate_validator_delegate_stake(
|
|
802
|
+
self, request: ValidatorDelegateStakeDonateRequest, keypair: Keypair
|
|
803
|
+
) -> ValidatorDelegateStakeDonateResponse:
|
|
804
|
+
"""Donate stake to a validator delegate pool."""
|
|
805
|
+
try:
|
|
806
|
+
logger.info(
|
|
807
|
+
f"Donating {request.amount} wei to validator {request.validator_id}"
|
|
808
|
+
)
|
|
809
|
+
|
|
810
|
+
call = self.substrate.compose_call(
|
|
811
|
+
call_module="Network",
|
|
812
|
+
call_function="donate_validator_delegate_stake",
|
|
813
|
+
call_params={
|
|
814
|
+
"validator_id": request.validator_id,
|
|
815
|
+
"amount": request.amount,
|
|
816
|
+
},
|
|
817
|
+
)
|
|
818
|
+
|
|
819
|
+
result = self._submit_extrinsic(call, keypair)
|
|
820
|
+
|
|
821
|
+
if result["success"]:
|
|
822
|
+
return ValidatorDelegateStakeDonateResponse(
|
|
823
|
+
success=True,
|
|
824
|
+
transaction_hash=result.get("extrinsic_hash"),
|
|
825
|
+
block_hash=result.get("block_hash"),
|
|
826
|
+
block_number=result.get("block_number"),
|
|
827
|
+
validator_id=request.validator_id,
|
|
828
|
+
balance_donated=request.amount,
|
|
829
|
+
message=f"Donated {request.amount / 1e18:.4f} TENSOR to validator delegate pool",
|
|
830
|
+
)
|
|
831
|
+
error_msg = get_user_friendly_error(result.get("error", "Unknown error"))
|
|
832
|
+
return ValidatorDelegateStakeDonateResponse(
|
|
833
|
+
success=False,
|
|
834
|
+
error=error_msg,
|
|
835
|
+
message=f"Failed to donate validator delegate stake: {error_msg}",
|
|
836
|
+
)
|
|
837
|
+
|
|
838
|
+
except Exception as e:
|
|
839
|
+
logger.error(f"Error donating validator delegate stake: {e}")
|
|
840
|
+
return ValidatorDelegateStakeDonateResponse(
|
|
841
|
+
success=False,
|
|
842
|
+
error=str(e),
|
|
843
|
+
message=f"Error donating validator delegate stake: {str(e)}",
|
|
844
|
+
)
|
|
845
|
+
|
|
846
|
+
# ============================================================================
|
|
847
|
+
# NODE DELEGATE STAKING
|
|
848
|
+
# ============================================================================
|
|
849
|
+
|
|
850
|
+
def add_to_node_delegate_stake(
|
|
851
|
+
self, request: NodeDelegateStakeAddRequest, keypair: Keypair
|
|
852
|
+
) -> NodeDelegateStakeAddResponse:
|
|
853
|
+
"""
|
|
854
|
+
Add delegate stake to a specific node.
|
|
855
|
+
|
|
856
|
+
Args:
|
|
857
|
+
request: Node delegate stake addition request
|
|
858
|
+
keypair: Keypair for signing (coldkey)
|
|
859
|
+
|
|
860
|
+
Returns:
|
|
861
|
+
NodeDelegateStakeAddResponse with share details
|
|
862
|
+
|
|
863
|
+
Reference: mesh-template lines 851-894
|
|
864
|
+
"""
|
|
865
|
+
try:
|
|
866
|
+
logger.info(
|
|
867
|
+
f"Adding {request.node_delegate_stake_to_be_added} wei node delegate stake to "
|
|
868
|
+
f"subnet {request.subnet_id} node {request.subnet_node_id}"
|
|
869
|
+
)
|
|
870
|
+
|
|
871
|
+
# Compose call
|
|
872
|
+
call = self.substrate.compose_call(
|
|
873
|
+
call_module="Network",
|
|
874
|
+
call_function="add_to_node_delegate_stake",
|
|
875
|
+
call_params={
|
|
876
|
+
"subnet_id": request.subnet_id,
|
|
877
|
+
"subnet_node_id": request.subnet_node_id,
|
|
878
|
+
"node_delegate_stake_to_be_added": request.node_delegate_stake_to_be_added, # Blockchain parameter name
|
|
879
|
+
},
|
|
880
|
+
)
|
|
881
|
+
|
|
882
|
+
# Submit with retry
|
|
883
|
+
result = self._submit_extrinsic(call, keypair)
|
|
884
|
+
|
|
885
|
+
if result["success"]:
|
|
886
|
+
# Extract amount from events
|
|
887
|
+
amount_added = request.node_delegate_stake_to_be_added
|
|
888
|
+
shares_received = 0
|
|
889
|
+
|
|
890
|
+
for event in result.get("events", []):
|
|
891
|
+
if hasattr(event, "value"):
|
|
892
|
+
event_id = event.value.get("event_id", "")
|
|
893
|
+
if event_id == "DelegateNodeStakeAdded":
|
|
894
|
+
attributes = event.value.get("attributes", {})
|
|
895
|
+
if isinstance(attributes, dict):
|
|
896
|
+
amount_added = attributes.get("amount", amount_added)
|
|
897
|
+
elif (
|
|
898
|
+
isinstance(attributes, (list, tuple))
|
|
899
|
+
and len(attributes) >= 4
|
|
900
|
+
):
|
|
901
|
+
# [account_id, subnet_id, subnet_node_id, amount]
|
|
902
|
+
amount_added = attributes[3]
|
|
903
|
+
|
|
904
|
+
response_data = {
|
|
905
|
+
"success": True,
|
|
906
|
+
"transaction_hash": result.get("extrinsic_hash"),
|
|
907
|
+
"block_hash": result.get("block_hash"),
|
|
908
|
+
"block_number": result.get("block_number"),
|
|
909
|
+
"subnet_id": request.subnet_id,
|
|
910
|
+
"subnet_node_id": request.subnet_node_id,
|
|
911
|
+
"stake_added": amount_added, # Use stake_added to match model
|
|
912
|
+
"shares_received": shares_received,
|
|
913
|
+
"message": f"Successfully delegated {amount_added / 1e18:.4f} TENSOR to node",
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
return NodeDelegateStakeAddResponse(**response_data)
|
|
917
|
+
else:
|
|
918
|
+
error_msg = get_user_friendly_error(
|
|
919
|
+
result.get("error", "Unknown error")
|
|
920
|
+
)
|
|
921
|
+
return NodeDelegateStakeAddResponse(
|
|
922
|
+
success=False,
|
|
923
|
+
error=error_msg,
|
|
924
|
+
message=f"Failed to add node delegate stake: {error_msg}",
|
|
925
|
+
)
|
|
926
|
+
|
|
927
|
+
except Exception as e:
|
|
928
|
+
logger.error(f"Error adding node delegate stake: {e}")
|
|
929
|
+
return NodeDelegateStakeAddResponse(
|
|
930
|
+
success=False,
|
|
931
|
+
error=str(e),
|
|
932
|
+
message=f"Error adding node delegate stake: {str(e)}",
|
|
933
|
+
)
|
|
934
|
+
|
|
935
|
+
def remove_node_delegate_stake(
|
|
936
|
+
self, request: NodeDelegateStakeRemoveRequest, keypair: Keypair
|
|
937
|
+
) -> NodeDelegateStakeRemoveResponse:
|
|
938
|
+
"""
|
|
939
|
+
Remove delegate stake from a specific node.
|
|
940
|
+
|
|
941
|
+
Args:
|
|
942
|
+
request: Node delegate stake removal request
|
|
943
|
+
keypair: Keypair for signing (coldkey)
|
|
944
|
+
|
|
945
|
+
Returns:
|
|
946
|
+
NodeDelegateStakeRemoveResponse
|
|
947
|
+
"""
|
|
948
|
+
try:
|
|
949
|
+
logger.info(
|
|
950
|
+
f"Removing {request.node_delegate_stake_shares_to_be_removed} shares from "
|
|
951
|
+
f"subnet {request.subnet_id} node {request.subnet_node_id}"
|
|
952
|
+
)
|
|
953
|
+
|
|
954
|
+
# Compose call
|
|
955
|
+
call = self.substrate.compose_call(
|
|
956
|
+
call_module="Network",
|
|
957
|
+
call_function="remove_node_delegate_stake",
|
|
958
|
+
call_params={
|
|
959
|
+
"subnet_id": request.subnet_id,
|
|
960
|
+
"subnet_node_id": request.subnet_node_id,
|
|
961
|
+
"node_delegate_stake_shares_to_be_removed": request.node_delegate_stake_shares_to_be_removed, # Blockchain parameter
|
|
962
|
+
},
|
|
963
|
+
)
|
|
964
|
+
|
|
965
|
+
# Submit with retry
|
|
966
|
+
result = self._submit_extrinsic(call, keypair)
|
|
967
|
+
|
|
968
|
+
if result["success"]:
|
|
969
|
+
return NodeDelegateStakeRemoveResponse(
|
|
970
|
+
success=True,
|
|
971
|
+
transaction_hash=result.get("extrinsic_hash"),
|
|
972
|
+
block_hash=result.get("block_hash"),
|
|
973
|
+
block_number=result.get("block_number"),
|
|
974
|
+
subnet_id=request.subnet_id,
|
|
975
|
+
subnet_node_id=request.subnet_node_id,
|
|
976
|
+
shares_removed=request.node_delegate_stake_shares_to_be_removed,
|
|
977
|
+
message=f"Successfully removed {request.node_delegate_stake_shares_to_be_removed} shares",
|
|
978
|
+
)
|
|
979
|
+
else:
|
|
980
|
+
error_msg = get_user_friendly_error(
|
|
981
|
+
result.get("error", "Unknown error")
|
|
982
|
+
)
|
|
983
|
+
return NodeDelegateStakeRemoveResponse(
|
|
984
|
+
success=False,
|
|
985
|
+
error=error_msg,
|
|
986
|
+
message=f"Failed to remove node delegate stake: {error_msg}",
|
|
987
|
+
)
|
|
988
|
+
|
|
989
|
+
except Exception as e:
|
|
990
|
+
logger.error(f"Error removing node delegate stake: {e}")
|
|
991
|
+
return NodeDelegateStakeRemoveResponse(
|
|
992
|
+
success=False,
|
|
993
|
+
error=str(e),
|
|
994
|
+
message=f"Error removing node delegate stake: {str(e)}",
|
|
995
|
+
)
|
|
996
|
+
|
|
997
|
+
def transfer_node_delegate_stake(
|
|
998
|
+
self, request: NodeDelegateStakeTransferRequest, keypair: Keypair
|
|
999
|
+
) -> NodeDelegateStakeTransferResponse:
|
|
1000
|
+
"""
|
|
1001
|
+
Transfer node delegate stake shares to another account.
|
|
1002
|
+
"""
|
|
1003
|
+
try:
|
|
1004
|
+
logger.info(
|
|
1005
|
+
f"Transferring {request.node_delegate_stake_shares_to_transfer} shares "
|
|
1006
|
+
f"from subnet {request.subnet_id} node {request.subnet_node_id} "
|
|
1007
|
+
f"to account {request.to_account_id}"
|
|
1008
|
+
)
|
|
1009
|
+
|
|
1010
|
+
call = self.substrate.compose_call(
|
|
1011
|
+
call_module="Network",
|
|
1012
|
+
call_function="transfer_node_delegate_stake",
|
|
1013
|
+
call_params={
|
|
1014
|
+
"subnet_id": request.subnet_id,
|
|
1015
|
+
"subnet_node_id": request.subnet_node_id,
|
|
1016
|
+
"to_account_id": request.to_account_id,
|
|
1017
|
+
"node_delegate_stake_shares_to_transfer": request.node_delegate_stake_shares_to_transfer,
|
|
1018
|
+
},
|
|
1019
|
+
)
|
|
1020
|
+
|
|
1021
|
+
result = self._submit_extrinsic(call, keypair)
|
|
1022
|
+
|
|
1023
|
+
if result["success"]:
|
|
1024
|
+
return NodeDelegateStakeTransferResponse(
|
|
1025
|
+
success=True,
|
|
1026
|
+
transaction_hash=result.get("extrinsic_hash"),
|
|
1027
|
+
block_number=result.get("block_number"),
|
|
1028
|
+
subnet_id=request.subnet_id,
|
|
1029
|
+
subnet_node_id=request.subnet_node_id,
|
|
1030
|
+
to_account=request.to_account_id,
|
|
1031
|
+
shares_transferred=request.node_delegate_stake_shares_to_transfer,
|
|
1032
|
+
message="Node delegate stake transferred successfully",
|
|
1033
|
+
)
|
|
1034
|
+
error_msg = get_user_friendly_error(result.get("error", "Unknown error"))
|
|
1035
|
+
return NodeDelegateStakeTransferResponse(
|
|
1036
|
+
success=False,
|
|
1037
|
+
error=error_msg,
|
|
1038
|
+
message=f"Failed to transfer node delegate stake: {error_msg}",
|
|
1039
|
+
)
|
|
1040
|
+
|
|
1041
|
+
except Exception as e:
|
|
1042
|
+
logger.error(f"Error transferring node delegate stake: {e}")
|
|
1043
|
+
return NodeDelegateStakeTransferResponse(
|
|
1044
|
+
success=False,
|
|
1045
|
+
error=str(e),
|
|
1046
|
+
message=f"Error transferring node delegate stake: {str(e)}",
|
|
1047
|
+
)
|
|
1048
|
+
|
|
1049
|
+
def swap_node_delegate_stake(
|
|
1050
|
+
self, request: NodeDelegateStakeSwapRequest, keypair: Keypair
|
|
1051
|
+
) -> NodeDelegateStakeSwapResponse:
|
|
1052
|
+
"""
|
|
1053
|
+
Swap node delegate stake shares between nodes.
|
|
1054
|
+
"""
|
|
1055
|
+
try:
|
|
1056
|
+
logger.info(
|
|
1057
|
+
"Swapping node delegate shares from subnet %s node %s to subnet %s node %s",
|
|
1058
|
+
request.from_subnet_id,
|
|
1059
|
+
request.from_subnet_node_id,
|
|
1060
|
+
request.to_subnet_id,
|
|
1061
|
+
request.to_subnet_node_id,
|
|
1062
|
+
)
|
|
1063
|
+
|
|
1064
|
+
call = self.substrate.compose_call(
|
|
1065
|
+
call_module="Network",
|
|
1066
|
+
call_function="swap_node_delegate_stake",
|
|
1067
|
+
call_params={
|
|
1068
|
+
"from_subnet_id": request.from_subnet_id,
|
|
1069
|
+
"from_subnet_node_id": request.from_subnet_node_id,
|
|
1070
|
+
"to_subnet_id": request.to_subnet_id,
|
|
1071
|
+
"to_subnet_node_id": request.to_subnet_node_id,
|
|
1072
|
+
"node_delegate_stake_shares_to_swap": request.node_delegate_stake_shares_to_swap,
|
|
1073
|
+
},
|
|
1074
|
+
)
|
|
1075
|
+
|
|
1076
|
+
result = self._submit_extrinsic(call, keypair)
|
|
1077
|
+
|
|
1078
|
+
if result["success"]:
|
|
1079
|
+
return NodeDelegateStakeSwapResponse(
|
|
1080
|
+
success=True,
|
|
1081
|
+
transaction_hash=result.get("extrinsic_hash"),
|
|
1082
|
+
block_hash=result.get("block_hash"),
|
|
1083
|
+
block_number=result.get("block_number"),
|
|
1084
|
+
from_subnet_id=request.from_subnet_id,
|
|
1085
|
+
from_subnet_node_id=request.from_subnet_node_id,
|
|
1086
|
+
to_subnet_id=request.to_subnet_id,
|
|
1087
|
+
to_subnet_node_id=request.to_subnet_node_id,
|
|
1088
|
+
shares_swapped=request.node_delegate_stake_shares_to_swap,
|
|
1089
|
+
message="Node delegate stake swap submitted (queued)",
|
|
1090
|
+
)
|
|
1091
|
+
error_msg = get_user_friendly_error(result.get("error", "Unknown error"))
|
|
1092
|
+
return NodeDelegateStakeSwapResponse(
|
|
1093
|
+
success=False,
|
|
1094
|
+
error=error_msg,
|
|
1095
|
+
message=f"Failed to swap node delegate stake: {error_msg}",
|
|
1096
|
+
)
|
|
1097
|
+
|
|
1098
|
+
except Exception as e:
|
|
1099
|
+
logger.error(f"Error swapping node delegate stake: {e}")
|
|
1100
|
+
return NodeDelegateStakeSwapResponse(
|
|
1101
|
+
success=False,
|
|
1102
|
+
error=str(e),
|
|
1103
|
+
message=f"Error swapping node delegate stake: {str(e)}",
|
|
1104
|
+
)
|
|
1105
|
+
|
|
1106
|
+
def donate_node_delegate_stake(
|
|
1107
|
+
self, request: NodeDelegateStakeDonateRequest, keypair: Keypair
|
|
1108
|
+
) -> NodeDelegateStakeDonateResponse:
|
|
1109
|
+
"""
|
|
1110
|
+
Donate node delegate stake (in wei) to a node's delegate pool.
|
|
1111
|
+
"""
|
|
1112
|
+
try:
|
|
1113
|
+
logger.info(
|
|
1114
|
+
f"Donating {request.amount} wei to subnet {request.subnet_id} node {request.subnet_node_id}"
|
|
1115
|
+
)
|
|
1116
|
+
|
|
1117
|
+
call = self.substrate.compose_call(
|
|
1118
|
+
call_module="Network",
|
|
1119
|
+
call_function="donate_node_delegate_stake",
|
|
1120
|
+
call_params={
|
|
1121
|
+
"subnet_id": request.subnet_id,
|
|
1122
|
+
"subnet_node_id": request.subnet_node_id,
|
|
1123
|
+
"amount": request.amount,
|
|
1124
|
+
},
|
|
1125
|
+
)
|
|
1126
|
+
|
|
1127
|
+
result = self._submit_extrinsic(call, keypair)
|
|
1128
|
+
|
|
1129
|
+
if result["success"]:
|
|
1130
|
+
return NodeDelegateStakeDonateResponse(
|
|
1131
|
+
success=True,
|
|
1132
|
+
transaction_hash=result.get("extrinsic_hash"),
|
|
1133
|
+
block_hash=result.get("block_hash"),
|
|
1134
|
+
block_number=result.get("block_number"),
|
|
1135
|
+
subnet_id=request.subnet_id,
|
|
1136
|
+
subnet_node_id=request.subnet_node_id,
|
|
1137
|
+
balance_donated=request.amount,
|
|
1138
|
+
message=f"Donated {request.amount / 1e18:.4f} TENSOR to node delegate pool",
|
|
1139
|
+
)
|
|
1140
|
+
error_msg = get_user_friendly_error(result.get("error", "Unknown error"))
|
|
1141
|
+
return NodeDelegateStakeDonateResponse(
|
|
1142
|
+
success=False,
|
|
1143
|
+
error=error_msg,
|
|
1144
|
+
message=f"Failed to donate node delegate stake: {error_msg}",
|
|
1145
|
+
)
|
|
1146
|
+
|
|
1147
|
+
except Exception as e:
|
|
1148
|
+
logger.error(f"Error donating node delegate stake: {e}")
|
|
1149
|
+
return NodeDelegateStakeDonateResponse(
|
|
1150
|
+
success=False,
|
|
1151
|
+
error=str(e),
|
|
1152
|
+
message=f"Error donating node delegate stake: {str(e)}",
|
|
1153
|
+
)
|
|
1154
|
+
|
|
1155
|
+
def swap_from_node_to_subnet(
|
|
1156
|
+
self, request: StakeSwapFromNodeToSubnetRequest, keypair: Keypair
|
|
1157
|
+
) -> StakeSwapFromNodeToSubnetResponse:
|
|
1158
|
+
"""
|
|
1159
|
+
Swap stake from a node delegate position to a subnet delegate position.
|
|
1160
|
+
"""
|
|
1161
|
+
try:
|
|
1162
|
+
logger.info(
|
|
1163
|
+
"Swapping node delegate shares (%s) from subnet %s node %s to subnet %s",
|
|
1164
|
+
request.node_delegate_stake_shares_to_swap,
|
|
1165
|
+
request.from_subnet_id,
|
|
1166
|
+
request.from_subnet_node_id,
|
|
1167
|
+
request.to_subnet_id,
|
|
1168
|
+
)
|
|
1169
|
+
|
|
1170
|
+
call = self.substrate.compose_call(
|
|
1171
|
+
call_module="Network",
|
|
1172
|
+
call_function="swap_from_node_to_subnet",
|
|
1173
|
+
call_params={
|
|
1174
|
+
"from_subnet_id": request.from_subnet_id,
|
|
1175
|
+
"from_subnet_node_id": request.from_subnet_node_id,
|
|
1176
|
+
"to_subnet_id": request.to_subnet_id,
|
|
1177
|
+
"node_delegate_stake_shares_to_swap": request.node_delegate_stake_shares_to_swap,
|
|
1178
|
+
},
|
|
1179
|
+
)
|
|
1180
|
+
|
|
1181
|
+
result = self._submit_extrinsic(call, keypair)
|
|
1182
|
+
|
|
1183
|
+
if result["success"]:
|
|
1184
|
+
return StakeSwapFromNodeToSubnetResponse(
|
|
1185
|
+
success=True,
|
|
1186
|
+
transaction_hash=result.get("extrinsic_hash"),
|
|
1187
|
+
block_hash=result.get("block_hash"),
|
|
1188
|
+
block_number=result.get("block_number"),
|
|
1189
|
+
subnet_id=request.from_subnet_id,
|
|
1190
|
+
subnet_node_id=request.from_subnet_node_id,
|
|
1191
|
+
stake_swapped=request.node_delegate_stake_shares_to_swap,
|
|
1192
|
+
message="Swap from node to subnet queued successfully",
|
|
1193
|
+
)
|
|
1194
|
+
error_msg = get_user_friendly_error(result.get("error", "Unknown error"))
|
|
1195
|
+
return StakeSwapFromNodeToSubnetResponse(
|
|
1196
|
+
success=False,
|
|
1197
|
+
error=error_msg,
|
|
1198
|
+
message=f"Failed to swap from node to subnet: {error_msg}",
|
|
1199
|
+
)
|
|
1200
|
+
|
|
1201
|
+
except Exception as e:
|
|
1202
|
+
logger.error(f"Error swapping node -> subnet delegate stake: {e}")
|
|
1203
|
+
return StakeSwapFromNodeToSubnetResponse(
|
|
1204
|
+
success=False,
|
|
1205
|
+
error=str(e),
|
|
1206
|
+
message=f"Error swapping node -> subnet delegate stake: {str(e)}",
|
|
1207
|
+
)
|
|
1208
|
+
|
|
1209
|
+
def swap_from_subnet_to_node(
|
|
1210
|
+
self, request: StakeSwapFromSubnetToNodeRequest, keypair: Keypair
|
|
1211
|
+
) -> StakeSwapFromSubnetToNodeResponse:
|
|
1212
|
+
"""
|
|
1213
|
+
Swap stake from a subnet delegate position to a node delegate position.
|
|
1214
|
+
"""
|
|
1215
|
+
try:
|
|
1216
|
+
logger.info(
|
|
1217
|
+
"Swapping subnet delegate shares (%s) from subnet %s to subnet %s node %s",
|
|
1218
|
+
request.delegate_stake_shares_to_swap,
|
|
1219
|
+
request.from_subnet_id,
|
|
1220
|
+
request.to_subnet_id,
|
|
1221
|
+
request.to_subnet_node_id,
|
|
1222
|
+
)
|
|
1223
|
+
|
|
1224
|
+
call = self.substrate.compose_call(
|
|
1225
|
+
call_module="Network",
|
|
1226
|
+
call_function="swap_from_subnet_to_node",
|
|
1227
|
+
call_params={
|
|
1228
|
+
"from_subnet_id": request.from_subnet_id,
|
|
1229
|
+
"to_subnet_id": request.to_subnet_id,
|
|
1230
|
+
"to_subnet_node_id": request.to_subnet_node_id,
|
|
1231
|
+
"node_delegate_stake_shares_to_swap": request.delegate_stake_shares_to_swap,
|
|
1232
|
+
},
|
|
1233
|
+
)
|
|
1234
|
+
|
|
1235
|
+
result = self._submit_extrinsic(call, keypair)
|
|
1236
|
+
|
|
1237
|
+
if result["success"]:
|
|
1238
|
+
return StakeSwapFromSubnetToNodeResponse(
|
|
1239
|
+
success=True,
|
|
1240
|
+
transaction_hash=result.get("extrinsic_hash"),
|
|
1241
|
+
block_number=result.get("block_number"),
|
|
1242
|
+
subnet_id=request.to_subnet_id,
|
|
1243
|
+
subnet_node_id=request.to_subnet_node_id,
|
|
1244
|
+
stake_swapped=request.delegate_stake_shares_to_swap,
|
|
1245
|
+
message="Swap from subnet to node queued successfully",
|
|
1246
|
+
)
|
|
1247
|
+
error_msg = get_user_friendly_error(result.get("error", "Unknown error"))
|
|
1248
|
+
return StakeSwapFromSubnetToNodeResponse(
|
|
1249
|
+
success=False,
|
|
1250
|
+
error=error_msg,
|
|
1251
|
+
message=f"Failed to swap from subnet to node: {error_msg}",
|
|
1252
|
+
)
|
|
1253
|
+
|
|
1254
|
+
except Exception as e:
|
|
1255
|
+
logger.error(f"Error swapping subnet -> node delegate stake: {e}")
|
|
1256
|
+
return StakeSwapFromSubnetToNodeResponse(
|
|
1257
|
+
success=False,
|
|
1258
|
+
error=str(e),
|
|
1259
|
+
message=f"Error swapping subnet -> node delegate stake: {str(e)}",
|
|
1260
|
+
)
|
|
1261
|
+
|
|
1262
|
+
def swap_from_validator_to_subnet(
|
|
1263
|
+
self, request: StakeSwapFromValidatorToSubnetRequest, keypair: Keypair
|
|
1264
|
+
) -> StakeSwapFromValidatorToSubnetResponse:
|
|
1265
|
+
"""Swap stake from a validator delegate position to a subnet delegate position."""
|
|
1266
|
+
try:
|
|
1267
|
+
logger.info(
|
|
1268
|
+
"Swapping validator delegate shares (%s) from validator %s to subnet %s",
|
|
1269
|
+
request.node_delegate_stake_shares_to_swap,
|
|
1270
|
+
request.from_validator_id,
|
|
1271
|
+
request.to_subnet_id,
|
|
1272
|
+
)
|
|
1273
|
+
|
|
1274
|
+
call = self.substrate.compose_call(
|
|
1275
|
+
call_module="Network",
|
|
1276
|
+
call_function="swap_from_validator_to_subnet",
|
|
1277
|
+
call_params={
|
|
1278
|
+
"from_validator_id": request.from_validator_id,
|
|
1279
|
+
"to_subnet_id": request.to_subnet_id,
|
|
1280
|
+
"node_delegate_stake_shares_to_swap": request.node_delegate_stake_shares_to_swap,
|
|
1281
|
+
},
|
|
1282
|
+
)
|
|
1283
|
+
|
|
1284
|
+
result = self._submit_extrinsic(call, keypair)
|
|
1285
|
+
|
|
1286
|
+
if result["success"]:
|
|
1287
|
+
return StakeSwapFromValidatorToSubnetResponse(
|
|
1288
|
+
success=True,
|
|
1289
|
+
transaction_hash=result.get("extrinsic_hash"),
|
|
1290
|
+
block_hash=result.get("block_hash"),
|
|
1291
|
+
block_number=result.get("block_number"),
|
|
1292
|
+
validator_id=request.from_validator_id,
|
|
1293
|
+
subnet_id=request.to_subnet_id,
|
|
1294
|
+
stake_swapped=request.node_delegate_stake_shares_to_swap,
|
|
1295
|
+
validator_shares_removed=request.node_delegate_stake_shares_to_swap,
|
|
1296
|
+
message="Swap from validator to subnet queued successfully",
|
|
1297
|
+
)
|
|
1298
|
+
error_msg = get_user_friendly_error(result.get("error", "Unknown error"))
|
|
1299
|
+
return StakeSwapFromValidatorToSubnetResponse(
|
|
1300
|
+
success=False,
|
|
1301
|
+
error=error_msg,
|
|
1302
|
+
message=f"Failed to swap from validator to subnet: {error_msg}",
|
|
1303
|
+
)
|
|
1304
|
+
|
|
1305
|
+
except Exception as e:
|
|
1306
|
+
logger.error(f"Error swapping validator -> subnet delegate stake: {e}")
|
|
1307
|
+
return StakeSwapFromValidatorToSubnetResponse(
|
|
1308
|
+
success=False,
|
|
1309
|
+
error=str(e),
|
|
1310
|
+
message=f"Error swapping validator -> subnet delegate stake: {str(e)}",
|
|
1311
|
+
)
|
|
1312
|
+
|
|
1313
|
+
def swap_from_subnet_to_validator(
|
|
1314
|
+
self, request: StakeSwapFromSubnetToValidatorRequest, keypair: Keypair
|
|
1315
|
+
) -> StakeSwapFromSubnetToValidatorResponse:
|
|
1316
|
+
"""Swap stake from a subnet delegate position to a validator delegate position."""
|
|
1317
|
+
try:
|
|
1318
|
+
logger.info(
|
|
1319
|
+
"Swapping subnet delegate shares (%s) from subnet %s to validator %s",
|
|
1320
|
+
request.subnet_delegate_stake_shares_to_swap,
|
|
1321
|
+
request.from_subnet_id,
|
|
1322
|
+
request.to_validator_id,
|
|
1323
|
+
)
|
|
1324
|
+
|
|
1325
|
+
call = self.substrate.compose_call(
|
|
1326
|
+
call_module="Network",
|
|
1327
|
+
call_function="swap_from_subnet_to_validator",
|
|
1328
|
+
call_params={
|
|
1329
|
+
"from_subnet_id": request.from_subnet_id,
|
|
1330
|
+
"to_validator_id": request.to_validator_id,
|
|
1331
|
+
"subnet_delegate_stake_shares_to_swap": request.subnet_delegate_stake_shares_to_swap,
|
|
1332
|
+
},
|
|
1333
|
+
)
|
|
1334
|
+
|
|
1335
|
+
result = self._submit_extrinsic(call, keypair)
|
|
1336
|
+
|
|
1337
|
+
if result["success"]:
|
|
1338
|
+
return StakeSwapFromSubnetToValidatorResponse(
|
|
1339
|
+
success=True,
|
|
1340
|
+
transaction_hash=result.get("extrinsic_hash"),
|
|
1341
|
+
block_hash=result.get("block_hash"),
|
|
1342
|
+
block_number=result.get("block_number"),
|
|
1343
|
+
subnet_id=request.from_subnet_id,
|
|
1344
|
+
validator_id=request.to_validator_id,
|
|
1345
|
+
stake_swapped=request.subnet_delegate_stake_shares_to_swap,
|
|
1346
|
+
subnet_shares_removed=request.subnet_delegate_stake_shares_to_swap,
|
|
1347
|
+
message="Swap from subnet to validator queued successfully",
|
|
1348
|
+
)
|
|
1349
|
+
error_msg = get_user_friendly_error(result.get("error", "Unknown error"))
|
|
1350
|
+
return StakeSwapFromSubnetToValidatorResponse(
|
|
1351
|
+
success=False,
|
|
1352
|
+
error=error_msg,
|
|
1353
|
+
message=f"Failed to swap from subnet to validator: {error_msg}",
|
|
1354
|
+
)
|
|
1355
|
+
|
|
1356
|
+
except Exception as e:
|
|
1357
|
+
logger.error(f"Error swapping subnet -> validator delegate stake: {e}")
|
|
1358
|
+
return StakeSwapFromSubnetToValidatorResponse(
|
|
1359
|
+
success=False,
|
|
1360
|
+
error=str(e),
|
|
1361
|
+
message=f"Error swapping subnet -> validator delegate stake: {str(e)}",
|
|
1362
|
+
)
|
|
1363
|
+
|
|
1364
|
+
def update_swap_queue(
|
|
1365
|
+
self, request: StakeSwapQueueUpdateRequest, keypair: Keypair
|
|
1366
|
+
) -> StakeSwapQueueUpdateResponse:
|
|
1367
|
+
"""
|
|
1368
|
+
Update a queued swap entry to point to a new target.
|
|
1369
|
+
"""
|
|
1370
|
+
try:
|
|
1371
|
+
logger.info(f"Updating swap queue entry {request.queue_id}")
|
|
1372
|
+
|
|
1373
|
+
signer_account = keypair.ss58_address
|
|
1374
|
+
new_call_variant = self._build_swap_queue_call(
|
|
1375
|
+
request.new_call, signer_account
|
|
1376
|
+
)
|
|
1377
|
+
|
|
1378
|
+
call = self.substrate.compose_call(
|
|
1379
|
+
call_module="Network",
|
|
1380
|
+
call_function="update_swap_queue",
|
|
1381
|
+
call_params={
|
|
1382
|
+
"id": request.queue_id,
|
|
1383
|
+
"new_call": new_call_variant,
|
|
1384
|
+
},
|
|
1385
|
+
)
|
|
1386
|
+
|
|
1387
|
+
result = self._submit_extrinsic(call, keypair)
|
|
1388
|
+
|
|
1389
|
+
if result["success"]:
|
|
1390
|
+
return StakeSwapQueueUpdateResponse(
|
|
1391
|
+
success=True,
|
|
1392
|
+
transaction_hash=result.get("extrinsic_hash"),
|
|
1393
|
+
block_number=result.get("block_number"),
|
|
1394
|
+
queue_updated=True,
|
|
1395
|
+
queue_position=request.queue_id,
|
|
1396
|
+
message="Swap queue entry updated successfully",
|
|
1397
|
+
)
|
|
1398
|
+
error_msg = get_user_friendly_error(result.get("error", "Unknown error"))
|
|
1399
|
+
return StakeSwapQueueUpdateResponse(
|
|
1400
|
+
success=False,
|
|
1401
|
+
error=error_msg,
|
|
1402
|
+
queue_updated=False,
|
|
1403
|
+
queue_position=request.queue_id,
|
|
1404
|
+
message=f"Failed to update swap queue: {error_msg}",
|
|
1405
|
+
)
|
|
1406
|
+
|
|
1407
|
+
except ValueError as ve:
|
|
1408
|
+
logger.error(f"Invalid swap queue payload: {ve}")
|
|
1409
|
+
return StakeSwapQueueUpdateResponse(
|
|
1410
|
+
success=False,
|
|
1411
|
+
error=str(ve),
|
|
1412
|
+
queue_updated=False,
|
|
1413
|
+
queue_position=request.queue_id,
|
|
1414
|
+
message=str(ve),
|
|
1415
|
+
)
|
|
1416
|
+
except Exception as e:
|
|
1417
|
+
logger.error(f"Error updating swap queue: {e}")
|
|
1418
|
+
return StakeSwapQueueUpdateResponse(
|
|
1419
|
+
success=False,
|
|
1420
|
+
error=str(e),
|
|
1421
|
+
queue_updated=False,
|
|
1422
|
+
queue_position=request.queue_id,
|
|
1423
|
+
message=f"Error updating swap queue: {str(e)}",
|
|
1424
|
+
)
|
|
1425
|
+
|
|
1426
|
+
def _build_swap_queue_call(self, new_call: dict, signer_account: str) -> dict:
|
|
1427
|
+
"""
|
|
1428
|
+
Convert user-provided call payload into the enum expected by Substrate.
|
|
1429
|
+
"""
|
|
1430
|
+
if not isinstance(new_call, dict):
|
|
1431
|
+
raise ValueError("new_call must be a JSON object")
|
|
1432
|
+
|
|
1433
|
+
if "SwapToSubnetDelegateStake" in new_call:
|
|
1434
|
+
payload = new_call["SwapToSubnetDelegateStake"]
|
|
1435
|
+
if "to_subnet_id" not in payload:
|
|
1436
|
+
raise ValueError(
|
|
1437
|
+
"to_subnet_id is required for SwapToSubnetDelegateStake"
|
|
1438
|
+
)
|
|
1439
|
+
to_subnet_id = int(payload["to_subnet_id"])
|
|
1440
|
+
return {
|
|
1441
|
+
"SwapToSubnetDelegateStake": {
|
|
1442
|
+
"account_id": payload.get("account_id", signer_account),
|
|
1443
|
+
"to_subnet_id": to_subnet_id,
|
|
1444
|
+
"balance": payload.get("balance", 0),
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
if "SwapToNodeDelegateStake" in new_call:
|
|
1449
|
+
payload = new_call["SwapToNodeDelegateStake"]
|
|
1450
|
+
if "to_subnet_id" not in payload or "to_subnet_node_id" not in payload:
|
|
1451
|
+
raise ValueError(
|
|
1452
|
+
"to_subnet_id and to_subnet_node_id are required for SwapToNodeDelegateStake"
|
|
1453
|
+
)
|
|
1454
|
+
to_subnet_id = int(payload["to_subnet_id"])
|
|
1455
|
+
to_subnet_node_id = int(payload["to_subnet_node_id"])
|
|
1456
|
+
return {
|
|
1457
|
+
"SwapToNodeDelegateStake": {
|
|
1458
|
+
"account_id": payload.get("account_id", signer_account),
|
|
1459
|
+
"to_subnet_id": to_subnet_id,
|
|
1460
|
+
"to_subnet_node_id": to_subnet_node_id,
|
|
1461
|
+
"balance": payload.get("balance", 0),
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
call_type = new_call.get("type") or new_call.get("call_type")
|
|
1466
|
+
if not call_type:
|
|
1467
|
+
raise ValueError("new_call must include 'type'")
|
|
1468
|
+
normalized = call_type.strip()
|
|
1469
|
+
if normalized not in (
|
|
1470
|
+
"SwapToSubnetDelegateStake",
|
|
1471
|
+
"SwapToNodeDelegateStake",
|
|
1472
|
+
):
|
|
1473
|
+
raise ValueError(
|
|
1474
|
+
"Unsupported call type. Use SwapToSubnetDelegateStake or SwapToNodeDelegateStake"
|
|
1475
|
+
)
|
|
1476
|
+
|
|
1477
|
+
if normalized == "SwapToSubnetDelegateStake":
|
|
1478
|
+
if "to_subnet_id" not in new_call:
|
|
1479
|
+
raise ValueError(
|
|
1480
|
+
"to_subnet_id is required for SwapToSubnetDelegateStake"
|
|
1481
|
+
)
|
|
1482
|
+
to_subnet_id = int(new_call["to_subnet_id"])
|
|
1483
|
+
return {
|
|
1484
|
+
"SwapToSubnetDelegateStake": {
|
|
1485
|
+
"account_id": signer_account,
|
|
1486
|
+
"to_subnet_id": to_subnet_id,
|
|
1487
|
+
"balance": new_call.get("balance", 0),
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
|
|
1491
|
+
if "to_subnet_id" not in new_call or "to_subnet_node_id" not in new_call:
|
|
1492
|
+
raise ValueError(
|
|
1493
|
+
"to_subnet_id and to_subnet_node_id are required for SwapToNodeDelegateStake"
|
|
1494
|
+
)
|
|
1495
|
+
to_subnet_id = int(new_call["to_subnet_id"])
|
|
1496
|
+
to_subnet_node_id = int(new_call["to_subnet_node_id"])
|
|
1497
|
+
return {
|
|
1498
|
+
"SwapToNodeDelegateStake": {
|
|
1499
|
+
"account_id": signer_account,
|
|
1500
|
+
"to_subnet_id": to_subnet_id,
|
|
1501
|
+
"to_subnet_node_id": to_subnet_node_id,
|
|
1502
|
+
"balance": new_call.get("balance", 0),
|
|
1503
|
+
}
|
|
1504
|
+
}
|