htcli 1.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. htcli-1.1.0.dist-info/METADATA +509 -0
  2. htcli-1.1.0.dist-info/RECORD +140 -0
  3. htcli-1.1.0.dist-info/WHEEL +4 -0
  4. htcli-1.1.0.dist-info/entry_points.txt +2 -0
  5. htcli-1.1.0.dist-info/licenses/LICENSE +21 -0
  6. src/__init__.py +0 -0
  7. src/htcli/__init__.py +5 -0
  8. src/htcli/client/__init__.py +338 -0
  9. src/htcli/client/extrinsics/__init__.py +26 -0
  10. src/htcli/client/extrinsics/base.py +487 -0
  11. src/htcli/client/extrinsics/consensus.py +79 -0
  12. src/htcli/client/extrinsics/governance.py +714 -0
  13. src/htcli/client/extrinsics/identity.py +490 -0
  14. src/htcli/client/extrinsics/node.py +1054 -0
  15. src/htcli/client/extrinsics/overwatch.py +401 -0
  16. src/htcli/client/extrinsics/staking.py +1504 -0
  17. src/htcli/client/extrinsics/subnet.py +2218 -0
  18. src/htcli/client/extrinsics/validator.py +203 -0
  19. src/htcli/client/extrinsics/wallet.py +323 -0
  20. src/htcli/client/offchain/__init__.py +10 -0
  21. src/htcli/client/offchain/backup.py +385 -0
  22. src/htcli/client/offchain/config.py +541 -0
  23. src/htcli/client/offchain/wallet.py +839 -0
  24. src/htcli/client/rpc/__init__.py +20 -0
  25. src/htcli/client/rpc/chain.py +568 -0
  26. src/htcli/client/rpc/node.py +783 -0
  27. src/htcli/client/rpc/overwatch.py +680 -0
  28. src/htcli/client/rpc/staking.py +216 -0
  29. src/htcli/client/rpc/subnet.py +2104 -0
  30. src/htcli/client/rpc/wallet.py +912 -0
  31. src/htcli/commands/__init__.py +31 -0
  32. src/htcli/commands/chain/__init__.py +66 -0
  33. src/htcli/commands/chain/display.py +204 -0
  34. src/htcli/commands/chain/handlers.py +260 -0
  35. src/htcli/commands/config/__init__.py +158 -0
  36. src/htcli/commands/config/display.py +353 -0
  37. src/htcli/commands/config/handlers.py +347 -0
  38. src/htcli/commands/config/prompts.py +357 -0
  39. src/htcli/commands/consensus/__init__.py +61 -0
  40. src/htcli/commands/consensus/handlers.py +100 -0
  41. src/htcli/commands/governance/__init__.py +49 -0
  42. src/htcli/commands/governance/handlers.py +81 -0
  43. src/htcli/commands/node/__init__.py +304 -0
  44. src/htcli/commands/node/display.py +749 -0
  45. src/htcli/commands/node/error_handling.py +470 -0
  46. src/htcli/commands/node/handlers.py +844 -0
  47. src/htcli/commands/node/prompts.py +346 -0
  48. src/htcli/commands/overwatch/__init__.py +219 -0
  49. src/htcli/commands/overwatch/display.py +396 -0
  50. src/htcli/commands/overwatch/error_handling.py +276 -0
  51. src/htcli/commands/overwatch/handlers.py +443 -0
  52. src/htcli/commands/overwatch/prompts.py +359 -0
  53. src/htcli/commands/stake/__init__.py +736 -0
  54. src/htcli/commands/stake/display.py +1103 -0
  55. src/htcli/commands/stake/error_handling.py +425 -0
  56. src/htcli/commands/stake/handlers.py +1902 -0
  57. src/htcli/commands/stake/prompts.py +1080 -0
  58. src/htcli/commands/subnet/__init__.py +639 -0
  59. src/htcli/commands/subnet/display.py +801 -0
  60. src/htcli/commands/subnet/error_handling.py +524 -0
  61. src/htcli/commands/subnet/handlers.py +2855 -0
  62. src/htcli/commands/subnet/prompts.py +1225 -0
  63. src/htcli/commands/validator/__init__.py +192 -0
  64. src/htcli/commands/validator/display.py +54 -0
  65. src/htcli/commands/validator/handlers.py +340 -0
  66. src/htcli/commands/wallet/__init__.py +546 -0
  67. src/htcli/commands/wallet/display.py +806 -0
  68. src/htcli/commands/wallet/error_handling.py +210 -0
  69. src/htcli/commands/wallet/handlers.py +3040 -0
  70. src/htcli/commands/wallet/prompts.py +1518 -0
  71. src/htcli/config.py +184 -0
  72. src/htcli/dependencies.py +186 -0
  73. src/htcli/errors/__init__.py +63 -0
  74. src/htcli/errors/base.py +141 -0
  75. src/htcli/errors/display.py +20 -0
  76. src/htcli/errors/handlers.py +710 -0
  77. src/htcli/main.py +343 -0
  78. src/htcli/models/__init__.py +21 -0
  79. src/htcli/models/enums/enum_types.py +35 -0
  80. src/htcli/models/errors.py +103 -0
  81. src/htcli/models/requests/__init__.py +197 -0
  82. src/htcli/models/requests/config.py +70 -0
  83. src/htcli/models/requests/consensus.py +19 -0
  84. src/htcli/models/requests/governance.py +38 -0
  85. src/htcli/models/requests/identity.py +51 -0
  86. src/htcli/models/requests/key.py +22 -0
  87. src/htcli/models/requests/node.py +91 -0
  88. src/htcli/models/requests/overwatch.py +64 -0
  89. src/htcli/models/requests/staking.py +580 -0
  90. src/htcli/models/requests/subnet.py +195 -0
  91. src/htcli/models/requests/validator.py +139 -0
  92. src/htcli/models/requests/wallet.py +118 -0
  93. src/htcli/models/responses/__init__.py +147 -0
  94. src/htcli/models/responses/base.py +18 -0
  95. src/htcli/models/responses/chain.py +39 -0
  96. src/htcli/models/responses/config.py +58 -0
  97. src/htcli/models/responses/identity.py +102 -0
  98. src/htcli/models/responses/overwatch.py +51 -0
  99. src/htcli/models/responses/staking.py +502 -0
  100. src/htcli/models/responses/subnet.py +856 -0
  101. src/htcli/models/responses/wallet.py +185 -0
  102. src/htcli/ui/__init__.py +87 -0
  103. src/htcli/ui/colors.py +309 -0
  104. src/htcli/ui/components/__init__.py +60 -0
  105. src/htcli/ui/components/panels.py +174 -0
  106. src/htcli/ui/components/progress.py +166 -0
  107. src/htcli/ui/components/spinners.py +92 -0
  108. src/htcli/ui/components/tables.py +809 -0
  109. src/htcli/ui/components/trees.py +721 -0
  110. src/htcli/ui/display.py +336 -0
  111. src/htcli/ui/prompts.py +870 -0
  112. src/htcli/utils/__init__.py +76 -0
  113. src/htcli/utils/blockchain/__init__.py +75 -0
  114. src/htcli/utils/blockchain/formatting.py +368 -0
  115. src/htcli/utils/blockchain/patches.py +286 -0
  116. src/htcli/utils/blockchain/peer_id.py +186 -0
  117. src/htcli/utils/blockchain/staking.py +448 -0
  118. src/htcli/utils/blockchain/type_registry.py +1373 -0
  119. src/htcli/utils/blockchain/validation.py +179 -0
  120. src/htcli/utils/cache.py +613 -0
  121. src/htcli/utils/constants.py +38 -0
  122. src/htcli/utils/legacy/__init__.py +12 -0
  123. src/htcli/utils/legacy/colors.py +311 -0
  124. src/htcli/utils/legacy/crypto.py +1176 -0
  125. src/htcli/utils/legacy/formatting.py +452 -0
  126. src/htcli/utils/legacy/interactive.py +306 -0
  127. src/htcli/utils/legacy/subnet_manifest.py +265 -0
  128. src/htcli/utils/legacy/validation.py +488 -0
  129. src/htcli/utils/logging.py +183 -0
  130. src/htcli/utils/network/__init__.py +20 -0
  131. src/htcli/utils/network/subnet.py +344 -0
  132. src/htcli/utils/prompts.py +27 -0
  133. src/htcli/utils/scale_codec.py +155 -0
  134. src/htcli/utils/validation/__init__.py +57 -0
  135. src/htcli/utils/validation/prompt_validators.py +267 -0
  136. src/htcli/utils/wallet/__init__.py +65 -0
  137. src/htcli/utils/wallet/auth.py +151 -0
  138. src/htcli/utils/wallet/core.py +1069 -0
  139. src/htcli/utils/wallet/crypto.py +1615 -0
  140. src/htcli/utils/wallet/migration.py +159 -0
@@ -0,0 +1,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
+ }