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,580 @@
1
+ """
2
+ Staking request models for HTCLI.
3
+
4
+ These models define the request structure for all staking operations
5
+ on the Hypertensor blockchain.
6
+ """
7
+
8
+ from pydantic import BaseModel, Field, validator
9
+
10
+ # ============================================================================
11
+ # DIRECT STAKING MODELS
12
+ # ============================================================================
13
+
14
+
15
+ class StakeAddRequest(BaseModel):
16
+ """Request to add stake to a subnet node."""
17
+
18
+ subnet_id: int = Field(..., description="Subnet ID to stake on")
19
+ subnet_node_id: int = Field(..., description="Subnet node ID to stake on")
20
+ hotkey: str = Field(..., description="Hotkey address of the subnet node")
21
+ stake_amount: int = Field(
22
+ ..., description="Amount to stake in wei (1e18 = 1 TENSOR)"
23
+ )
24
+
25
+ @validator("subnet_id")
26
+ def validate_subnet_id(cls, v):
27
+ if v < 0:
28
+ raise ValueError("Subnet ID must be non-negative")
29
+ return v
30
+
31
+ @validator("subnet_node_id")
32
+ def validate_subnet_node_id(cls, v):
33
+ if v < 0:
34
+ raise ValueError("Subnet node ID must be non-negative")
35
+ return v
36
+
37
+ @validator("hotkey")
38
+ def validate_hotkey(cls, v):
39
+ if not v or len(v) != 42 or not v.startswith("0x"):
40
+ raise ValueError("Hotkey must be a valid Ethereum address")
41
+ return v.lower()
42
+
43
+ @validator("stake_amount")
44
+ def validate_stake_amount(cls, v):
45
+ if v <= 0:
46
+ raise ValueError("Stake amount must be positive")
47
+ if v < 1e15: # 0.001 TENSOR minimum
48
+ raise ValueError("Stake amount too small (minimum 0.001 TENSOR)")
49
+ return v
50
+
51
+
52
+ class StakeRemoveRequest(BaseModel):
53
+ """Request to remove stake from a subnet node."""
54
+
55
+ subnet_id: int = Field(..., description="Subnet ID to remove stake from")
56
+ hotkey: str = Field(..., description="Hotkey address of the node")
57
+ stake_amount: int = Field(
58
+ ..., description="Amount to remove in wei (1e18 = 1 TENSOR)"
59
+ )
60
+
61
+ @validator("subnet_id")
62
+ def validate_subnet_id(cls, v):
63
+ if v < 0:
64
+ raise ValueError("Subnet ID must be non-negative")
65
+ return v
66
+
67
+ @validator("stake_amount")
68
+ def validate_stake_amount(cls, v):
69
+ if v <= 0:
70
+ raise ValueError("Stake amount must be positive")
71
+ return v
72
+
73
+
74
+ class ClaimUnbondingsRequest(BaseModel):
75
+ """Request to claim unbonded stake."""
76
+
77
+ # No parameters needed - claims all available unbondings for the caller
78
+ pass
79
+
80
+
81
+ # ============================================================================
82
+ # DELEGATE STAKING MODELS
83
+ # ============================================================================
84
+
85
+
86
+ class DelegateStakeAddRequest(BaseModel):
87
+ """Request to add delegate stake to a subnet."""
88
+
89
+ subnet_id: int = Field(..., description="Subnet ID to delegate stake to")
90
+ stake_amount: int = Field(
91
+ ..., description="Amount to delegate in wei (1e18 = 1 TENSOR)"
92
+ )
93
+
94
+ @validator("subnet_id")
95
+ def validate_subnet_id(cls, v):
96
+ if v < 0:
97
+ raise ValueError("Subnet ID must be non-negative")
98
+ return v
99
+
100
+ @validator("stake_amount")
101
+ def validate_stake_amount(cls, v):
102
+ if v <= 0:
103
+ raise ValueError("Stake amount must be positive")
104
+ return v
105
+
106
+
107
+ class DelegateStakeRemoveRequest(BaseModel):
108
+ """Request to remove delegate stake from a subnet."""
109
+
110
+ subnet_id: int = Field(..., description="Subnet ID to remove delegate stake from")
111
+ shares_to_be_removed: int = Field(
112
+ ..., description="Shares to remove (blockchain uses shares, not amounts)"
113
+ )
114
+
115
+ @validator("subnet_id")
116
+ def validate_subnet_id(cls, v):
117
+ if v < 0:
118
+ raise ValueError("Subnet ID must be non-negative")
119
+ return v
120
+
121
+ @validator("shares_to_be_removed")
122
+ def validate_shares(cls, v):
123
+ if v <= 0:
124
+ raise ValueError("Shares must be positive")
125
+ return v
126
+
127
+
128
+ class DelegateStakeTransferRequest(BaseModel):
129
+ """Request to transfer delegate stake between accounts."""
130
+
131
+ subnet_id: int = Field(..., description="Subnet ID")
132
+ from_account: str = Field(..., description="Account to transfer from")
133
+ to_account: str = Field(..., description="Account to transfer to")
134
+ delegate_stake_shares_to_transfer: int = Field(
135
+ ..., description="Shares to transfer"
136
+ )
137
+
138
+ @validator("subnet_id")
139
+ def validate_subnet_id(cls, v):
140
+ if v < 0:
141
+ raise ValueError("Subnet ID must be non-negative")
142
+ return v
143
+
144
+ @validator("from_account", "to_account")
145
+ def validate_accounts(cls, v):
146
+ if not v or len(v) != 42 or not v.startswith("0x"):
147
+ raise ValueError("Account must be a valid Ethereum address")
148
+ return v.lower()
149
+
150
+ @validator("delegate_stake_shares_to_transfer")
151
+ def validate_shares(cls, v):
152
+ if v <= 0:
153
+ raise ValueError("Shares must be positive")
154
+ return v
155
+
156
+
157
+ class DelegateStakeSwapRequest(BaseModel):
158
+ """Request to swap delegate stake between subnets."""
159
+
160
+ from_subnet_id: int = Field(..., description="Source subnet ID")
161
+ to_subnet_id: int = Field(..., description="Destination subnet ID")
162
+ delegate_stake_shares_to_swap: int = Field(..., description="Shares to swap")
163
+
164
+ @validator("from_subnet_id", "to_subnet_id")
165
+ def validate_subnet_ids(cls, v):
166
+ if v < 0:
167
+ raise ValueError("Subnet ID must be non-negative")
168
+ return v
169
+
170
+ @validator("delegate_stake_shares_to_swap")
171
+ def validate_shares(cls, v):
172
+ if v <= 0:
173
+ raise ValueError("Shares must be positive")
174
+ return v
175
+
176
+
177
+ class DelegateStakeDonateRequest(BaseModel):
178
+ """Request to donate delegate stake to subnet treasury."""
179
+
180
+ subnet_id: int = Field(..., description="Subnet ID to donate to")
181
+ stake_amount: int = Field(..., description="Amount to donate in wei")
182
+
183
+ @validator("subnet_id")
184
+ def validate_subnet_id(cls, v):
185
+ if v < 0:
186
+ raise ValueError("Subnet ID must be non-negative")
187
+ return v
188
+
189
+ @validator("stake_amount")
190
+ def validate_stake_amount(cls, v):
191
+ if v <= 0:
192
+ raise ValueError("Stake amount must be positive")
193
+ return v
194
+
195
+
196
+ # ============================================================================
197
+ # VALIDATOR DELEGATE STAKING MODELS
198
+ # ============================================================================
199
+
200
+
201
+ class ValidatorDelegateStakeAddRequest(BaseModel):
202
+ """Request to add delegate stake to a validator."""
203
+
204
+ validator_id: int = Field(..., description="Validator ID to delegate to")
205
+ delegate_stake_to_be_added: int = Field(
206
+ ..., description="Amount to delegate in wei"
207
+ )
208
+
209
+ @validator("validator_id")
210
+ def validate_validator_id(cls, v):
211
+ if v < 0:
212
+ raise ValueError("Validator ID must be non-negative")
213
+ return v
214
+
215
+ @validator("delegate_stake_to_be_added")
216
+ def validate_stake_amount(cls, v):
217
+ if v <= 0:
218
+ raise ValueError("Stake amount must be positive")
219
+ return v
220
+
221
+
222
+ class ValidatorDelegateStakeRemoveRequest(BaseModel):
223
+ """Request to remove delegate stake from a validator."""
224
+
225
+ validator_id: int = Field(..., description="Validator ID")
226
+ validator_delegate_stake_shares_to_be_removed: int = Field(
227
+ ..., description="Shares to remove"
228
+ )
229
+
230
+ @validator("validator_id")
231
+ def validate_validator_id(cls, v):
232
+ if v < 0:
233
+ raise ValueError("Validator ID must be non-negative")
234
+ return v
235
+
236
+ @validator("validator_delegate_stake_shares_to_be_removed")
237
+ def validate_shares(cls, v):
238
+ if v <= 0:
239
+ raise ValueError("Shares must be positive")
240
+ return v
241
+
242
+
243
+ class ValidatorDelegateStakeTransferRequest(BaseModel):
244
+ """Request to transfer validator delegate stake shares to another account."""
245
+
246
+ validator_id: int = Field(..., description="Validator ID")
247
+ to_account_id: str = Field(..., description="Destination account")
248
+ validator_delegate_stake_shares_to_transfer: int = Field(
249
+ ..., description="Shares to transfer"
250
+ )
251
+
252
+ @validator("validator_id")
253
+ def validate_validator_id(cls, v):
254
+ if v < 0:
255
+ raise ValueError("Validator ID must be non-negative")
256
+ return v
257
+
258
+ @validator("to_account_id")
259
+ def validate_account(cls, v):
260
+ if not v or len(v) != 42 or not v.startswith("0x"):
261
+ raise ValueError("Account must be a valid Ethereum address")
262
+ return v.lower()
263
+
264
+ @validator("validator_delegate_stake_shares_to_transfer")
265
+ def validate_shares(cls, v):
266
+ if v <= 0:
267
+ raise ValueError("Shares must be positive")
268
+ return v
269
+
270
+
271
+ class ValidatorDelegateStakeSwapRequest(BaseModel):
272
+ """Request to swap validator delegate stake between validators."""
273
+
274
+ from_validator_id: int = Field(..., description="Source validator ID")
275
+ to_validator_id: int = Field(..., description="Destination validator ID")
276
+ stake_to_be_removed: int = Field(..., description="Shares to swap")
277
+
278
+ @validator("from_validator_id", "to_validator_id")
279
+ def validate_validator_ids(cls, v):
280
+ if v < 0:
281
+ raise ValueError("Validator ID must be non-negative")
282
+ return v
283
+
284
+ @validator("stake_to_be_removed")
285
+ def validate_shares(cls, v):
286
+ if v <= 0:
287
+ raise ValueError("Shares must be positive")
288
+ return v
289
+
290
+
291
+ class ValidatorDelegateStakeDonateRequest(BaseModel):
292
+ """Request to donate to a validator delegate stake pool."""
293
+
294
+ validator_id: int = Field(..., description="Validator ID")
295
+ amount: int = Field(..., description="Amount to donate in wei")
296
+
297
+ @validator("validator_id")
298
+ def validate_validator_id(cls, v):
299
+ if v < 0:
300
+ raise ValueError("Validator ID must be non-negative")
301
+ return v
302
+
303
+ @validator("amount")
304
+ def validate_stake_amount(cls, v):
305
+ if v <= 0:
306
+ raise ValueError("Stake amount must be positive")
307
+ return v
308
+
309
+
310
+ # ============================================================================
311
+ # NODE DELEGATE STAKING MODELS
312
+ # ============================================================================
313
+
314
+
315
+ class NodeDelegateStakeAddRequest(BaseModel):
316
+ """Request to add delegate stake to a specific node."""
317
+
318
+ subnet_id: int = Field(..., description="Subnet ID")
319
+ subnet_node_id: int = Field(..., description="Subnet node ID to delegate to")
320
+ node_delegate_stake_to_be_added: int = Field(
321
+ ..., description="Amount to delegate in wei (blockchain parameter name)"
322
+ )
323
+
324
+ @validator("subnet_id", "subnet_node_id")
325
+ def validate_ids(cls, v):
326
+ if v < 0:
327
+ raise ValueError("ID must be non-negative")
328
+ return v
329
+
330
+ @validator("node_delegate_stake_to_be_added")
331
+ def validate_stake_amount(cls, v):
332
+ if v <= 0:
333
+ raise ValueError("Stake amount must be positive")
334
+ return v
335
+
336
+
337
+ class NodeDelegateStakeRemoveRequest(BaseModel):
338
+ """Request to remove delegate stake from a specific node."""
339
+
340
+ subnet_id: int = Field(..., description="Subnet ID")
341
+ subnet_node_id: int = Field(
342
+ ..., description="Subnet node ID to remove delegate stake from"
343
+ )
344
+ node_delegate_stake_shares_to_be_removed: int = Field(
345
+ ..., description="Shares to remove (blockchain uses shares, not amounts)"
346
+ )
347
+
348
+ @validator("subnet_id", "subnet_node_id")
349
+ def validate_ids(cls, v):
350
+ if v < 0:
351
+ raise ValueError("ID must be non-negative")
352
+ return v
353
+
354
+ @validator("node_delegate_stake_shares_to_be_removed")
355
+ def validate_shares(cls, v):
356
+ if v <= 0:
357
+ raise ValueError("Shares must be positive")
358
+ return v
359
+
360
+
361
+ class NodeDelegateStakeTransferRequest(BaseModel):
362
+ """Request to transfer node delegate stake shares to another account."""
363
+
364
+ subnet_id: int = Field(..., description="Subnet ID")
365
+ subnet_node_id: int = Field(..., description="Subnet node ID")
366
+ to_account_id: str = Field(..., description="Destination account")
367
+ node_delegate_stake_shares_to_transfer: int = Field(
368
+ ..., description="Shares to transfer (not wei amount)"
369
+ )
370
+
371
+ @validator("subnet_id", "subnet_node_id")
372
+ def validate_ids(cls, v):
373
+ if v < 0:
374
+ raise ValueError("ID must be non-negative")
375
+ return v
376
+
377
+ @validator("to_account_id")
378
+ def validate_account(cls, v):
379
+ if not v or len(v) != 42 or not v.startswith("0x"):
380
+ raise ValueError("Account must be a valid Ethereum address")
381
+ return v.lower()
382
+
383
+ @validator("node_delegate_stake_shares_to_transfer")
384
+ def validate_shares(cls, v):
385
+ if v <= 0:
386
+ raise ValueError("Shares must be positive")
387
+ return v
388
+
389
+
390
+ class NodeDelegateStakeSwapRequest(BaseModel):
391
+ """Request to swap node delegate stake between nodes."""
392
+
393
+ from_subnet_id: int = Field(..., description="Source subnet ID")
394
+ from_subnet_node_id: int = Field(..., description="Source subnet node ID")
395
+ to_subnet_id: int = Field(..., description="Destination subnet ID")
396
+ to_subnet_node_id: int = Field(..., description="Destination subnet node ID")
397
+ node_delegate_stake_shares_to_swap: int = Field(
398
+ ..., description="Shares to swap (not amount)"
399
+ )
400
+
401
+ @validator(
402
+ "from_subnet_id", "from_subnet_node_id", "to_subnet_id", "to_subnet_node_id"
403
+ )
404
+ def validate_ids(cls, v):
405
+ if v < 0:
406
+ raise ValueError("ID must be non-negative")
407
+ return v
408
+
409
+ @validator("node_delegate_stake_shares_to_swap")
410
+ def validate_shares(cls, v):
411
+ if v <= 0:
412
+ raise ValueError("Shares must be positive")
413
+ return v
414
+
415
+
416
+ class NodeDelegateStakeDonateRequest(BaseModel):
417
+ """Request to donate node delegate stake to node treasury."""
418
+
419
+ subnet_id: int = Field(..., description="Subnet ID")
420
+ subnet_node_id: int = Field(..., description="Subnet node ID to donate to")
421
+ amount: int = Field(..., description="Amount to donate in wei")
422
+
423
+ @validator("subnet_id", "subnet_node_id")
424
+ def validate_ids(cls, v):
425
+ if v < 0:
426
+ raise ValueError("ID must be non-negative")
427
+ return v
428
+
429
+ @validator("amount")
430
+ def validate_stake_amount(cls, v):
431
+ if v <= 0:
432
+ raise ValueError("Stake amount must be positive")
433
+ return v
434
+
435
+
436
+ # ============================================================================
437
+ # CROSS-STAKE SWAP MODELS
438
+ # ============================================================================
439
+
440
+
441
+ class StakeSwapFromNodeToSubnetRequest(BaseModel):
442
+ """Request to swap stake from node delegate to subnet delegate."""
443
+
444
+ from_subnet_id: int = Field(..., description="Source subnet ID")
445
+ from_subnet_node_id: int = Field(..., description="Source node ID")
446
+ to_subnet_id: int = Field(..., description="Destination subnet ID")
447
+ node_delegate_stake_shares_to_swap: int = Field(
448
+ ..., description="Shares to convert from node to subnet"
449
+ )
450
+
451
+ @validator("from_subnet_id", "from_subnet_node_id", "to_subnet_id")
452
+ def validate_ids(cls, v):
453
+ if v < 0:
454
+ raise ValueError("ID must be non-negative")
455
+ return v
456
+
457
+ @validator("node_delegate_stake_shares_to_swap")
458
+ def validate_shares(cls, v):
459
+ if v <= 0:
460
+ raise ValueError("Shares must be positive")
461
+ return v
462
+
463
+
464
+ class StakeSwapFromSubnetToNodeRequest(BaseModel):
465
+ """Request to swap stake from subnet delegate to node delegate."""
466
+
467
+ from_subnet_id: int = Field(..., description="Source subnet ID")
468
+ to_subnet_id: int = Field(..., description="Destination subnet ID")
469
+ to_subnet_node_id: int = Field(..., description="Destination node ID")
470
+ delegate_stake_shares_to_swap: int = Field(
471
+ ..., description="Shares to convert from subnet to node"
472
+ )
473
+
474
+ @validator("from_subnet_id", "to_subnet_id", "to_subnet_node_id")
475
+ def validate_ids(cls, v):
476
+ if v < 0:
477
+ raise ValueError("ID must be non-negative")
478
+ return v
479
+
480
+ @validator("delegate_stake_shares_to_swap")
481
+ def validate_shares(cls, v):
482
+ if v <= 0:
483
+ raise ValueError("Shares must be positive")
484
+ return v
485
+
486
+
487
+ class StakeSwapFromValidatorToSubnetRequest(BaseModel):
488
+ """Request to swap stake from validator delegate to subnet delegate."""
489
+
490
+ from_validator_id: int = Field(..., description="Source validator ID")
491
+ to_subnet_id: int = Field(..., description="Destination subnet ID")
492
+ node_delegate_stake_shares_to_swap: int = Field(
493
+ ..., description="Validator delegate shares to convert to subnet delegate"
494
+ )
495
+
496
+ @validator("from_validator_id", "to_subnet_id")
497
+ def validate_ids(cls, v):
498
+ if v < 0:
499
+ raise ValueError("ID must be non-negative")
500
+ return v
501
+
502
+ @validator("node_delegate_stake_shares_to_swap")
503
+ def validate_shares(cls, v):
504
+ if v <= 0:
505
+ raise ValueError("Shares must be positive")
506
+ return v
507
+
508
+
509
+ class StakeSwapFromSubnetToValidatorRequest(BaseModel):
510
+ """Request to swap stake from subnet delegate to validator delegate."""
511
+
512
+ from_subnet_id: int = Field(..., description="Source subnet ID")
513
+ to_validator_id: int = Field(..., description="Destination validator ID")
514
+ subnet_delegate_stake_shares_to_swap: int = Field(
515
+ ..., description="Subnet delegate shares to convert to validator delegate"
516
+ )
517
+
518
+ @validator("from_subnet_id", "to_validator_id")
519
+ def validate_ids(cls, v):
520
+ if v < 0:
521
+ raise ValueError("ID must be non-negative")
522
+ return v
523
+
524
+ @validator("subnet_delegate_stake_shares_to_swap")
525
+ def validate_shares(cls, v):
526
+ if v <= 0:
527
+ raise ValueError("Shares must be positive")
528
+ return v
529
+
530
+
531
+ class StakeSwapQueueUpdateRequest(BaseModel):
532
+ """Request to update stake swap queue."""
533
+
534
+ queue_id: int = Field(..., description="Queue entry ID to update")
535
+ new_call: dict = Field(
536
+ ..., description="Replacement call payload (QueuedSwapCall JSON structure)"
537
+ )
538
+
539
+ @validator("queue_id")
540
+ def validate_queue_id(cls, v):
541
+ if v < 0:
542
+ raise ValueError("Queue ID must be non-negative")
543
+ return v
544
+
545
+ @validator("new_call")
546
+ def validate_new_call(cls, v):
547
+ if not isinstance(v, dict):
548
+ raise ValueError("new_call must be a JSON object")
549
+ if not v:
550
+ raise ValueError("new_call cannot be empty")
551
+ return v
552
+
553
+
554
+ # ============================================================================
555
+ # OVERWATCH STAKING MODELS
556
+ # ============================================================================
557
+
558
+
559
+ class OverwatchStakeAddRequest(BaseModel):
560
+ """Request to add stake to overwatch nodes."""
561
+
562
+ stake_amount: int = Field(..., description="Amount to stake in wei")
563
+
564
+ @validator("stake_amount")
565
+ def validate_stake_amount(cls, v):
566
+ if v <= 0:
567
+ raise ValueError("Stake amount must be positive")
568
+ return v
569
+
570
+
571
+ class OverwatchStakeRemoveRequest(BaseModel):
572
+ """Request to remove stake from overwatch nodes."""
573
+
574
+ stake_amount: int = Field(..., description="Amount to remove in wei")
575
+
576
+ @validator("stake_amount")
577
+ def validate_stake_amount(cls, v):
578
+ if v <= 0:
579
+ raise ValueError("Stake amount must be positive")
580
+ return v