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,502 @@
1
+ """
2
+ Staking response models for HTCLI.
3
+
4
+ These models define the response structure for all staking operations
5
+ on the Hypertensor blockchain.
6
+ """
7
+
8
+ from typing import Any, Optional
9
+
10
+ from pydantic import BaseModel, Field
11
+ from scalecodec import ScaleBytes
12
+
13
+ # ============================================================================
14
+ # BASE RESPONSE MODELS
15
+ # ============================================================================
16
+
17
+
18
+ class BaseStakingResponse(BaseModel):
19
+ """Base response model for staking operations."""
20
+
21
+ success: bool = Field(..., description="Whether the operation was successful")
22
+ transaction_hash: Optional[str] = Field(None, description="Transaction hash")
23
+ block_hash: Optional[str] = Field(
24
+ None, description="Block hash where transaction was included"
25
+ )
26
+ block_number: Optional[int] = Field(
27
+ None, description="Block number where transaction was included"
28
+ )
29
+ message: Optional[str] = Field(None, description="Human-readable message")
30
+ error: Optional[str] = Field(None, description="Error message if operation failed")
31
+
32
+
33
+ class UnbondingEntry(BaseModel):
34
+ """Represents an unbonding entry."""
35
+
36
+ amount: int = Field(..., description="Amount in wei")
37
+ unlock_block: int = Field(..., description="Block number when unbonding completes")
38
+
39
+ @classmethod
40
+ def from_scale_bytes(cls, scale_bytes: bytes) -> "UnbondingEntry":
41
+ """Create UnbondingEntry from SCALE encoded bytes."""
42
+ # Unbonding entry is (u128, u32) tuple
43
+
44
+ sb = ScaleBytes(scale_bytes)
45
+ amount = sb.get_next_u128()
46
+ unlock_block = sb.get_next_u32()
47
+
48
+ return cls(amount=amount, unlock_block=unlock_block)
49
+
50
+
51
+ # ============================================================================
52
+ # DIRECT STAKING RESPONSES
53
+ # ============================================================================
54
+
55
+
56
+ class StakeAddResponse(BaseStakingResponse):
57
+ """Response for adding stake."""
58
+
59
+ subnet_id: Optional[int] = Field(None, description="Subnet ID")
60
+ subnet_node_id: Optional[int] = Field(None, description="Subnet node ID")
61
+ hotkey: Optional[str] = Field(None, description="Hotkey address")
62
+ stake_added: Optional[int] = Field(None, description="Amount staked in wei")
63
+ total_stake: Optional[int] = Field(None, description="Total stake after operation")
64
+
65
+
66
+ class StakeRemoveResponse(BaseStakingResponse):
67
+ """Response for removing stake."""
68
+
69
+ subnet_id: Optional[int] = Field(None, description="Subnet ID")
70
+ stake_removed: Optional[int] = Field(None, description="Amount removed in wei")
71
+ total_stake: Optional[int] = Field(
72
+ None, description="Remaining stake after operation"
73
+ )
74
+ unbonding_entries: Optional[list[UnbondingEntry]] = Field(
75
+ None, description="New unbonding entries created"
76
+ )
77
+
78
+
79
+ class ClaimUnbondingsResponse(BaseStakingResponse):
80
+ """Response for claiming unbonded stake."""
81
+
82
+ total_claimed: Optional[int] = Field(
83
+ None, description="Total amount claimed in wei"
84
+ )
85
+ entries_claimed: Optional[int] = Field(
86
+ None, description="Number of unbonding entries claimed"
87
+ )
88
+ entries_remaining: Optional[int] = Field(
89
+ None, description="Number of unbonding entries remaining"
90
+ )
91
+
92
+
93
+ # ============================================================================
94
+ # DELEGATE STAKING RESPONSES
95
+ # ============================================================================
96
+
97
+
98
+ class DelegateStakeAddResponse(BaseStakingResponse):
99
+ """Response for adding delegate stake."""
100
+
101
+ subnet_id: Optional[int] = Field(None, description="Subnet ID")
102
+ stake_added: Optional[int] = Field(None, description="Amount delegated in wei")
103
+ shares_received: Optional[int] = Field(None, description="Shares received")
104
+ share_price: Optional[float] = Field(
105
+ None, description="Share price in TENSOR per share"
106
+ )
107
+ total_shares: Optional[int] = Field(
108
+ None, description="Total shares after operation"
109
+ )
110
+
111
+
112
+ class DelegateStakeRemoveResponse(BaseStakingResponse):
113
+ """Response for removing delegate stake."""
114
+
115
+ subnet_id: Optional[int] = Field(None, description="Subnet ID")
116
+ shares_removed: Optional[int] = Field(None, description="Shares removed")
117
+ balance_received: Optional[int] = Field(None, description="Balance received in wei")
118
+ share_price: Optional[float] = Field(None, description="Share price at removal")
119
+ remaining_shares: Optional[int] = Field(None, description="Remaining shares")
120
+
121
+
122
+ class DelegateStakeTransferResponse(BaseStakingResponse):
123
+ """Response for transferring delegate stake."""
124
+
125
+ subnet_id: Optional[int] = Field(None, description="Subnet ID")
126
+ from_account: Optional[str] = Field(None, description="Source account")
127
+ to_account: Optional[str] = Field(None, description="Destination account")
128
+ shares_transferred: Optional[int] = Field(None, description="Shares transferred")
129
+
130
+
131
+ class DelegateStakeSwapResponse(BaseStakingResponse):
132
+ """Response for swapping delegate stake."""
133
+
134
+ from_subnet_id: Optional[int] = Field(None, description="Source subnet ID")
135
+ to_subnet_id: Optional[int] = Field(None, description="Destination subnet ID")
136
+ shares_swapped: Optional[int] = Field(None, description="Shares swapped")
137
+ from_share_price: Optional[float] = Field(
138
+ None, description="Source subnet share price"
139
+ )
140
+ to_share_price: Optional[float] = Field(
141
+ None, description="Destination subnet share price"
142
+ )
143
+
144
+
145
+ class DelegateStakeDonateResponse(BaseStakingResponse):
146
+ """Response for donating delegate stake."""
147
+
148
+ subnet_id: Optional[int] = Field(None, description="Subnet ID")
149
+ shares_donated: Optional[int] = Field(None, description="Shares donated")
150
+ balance_donated: Optional[int] = Field(None, description="Balance donated in wei")
151
+
152
+
153
+ # ============================================================================
154
+ # VALIDATOR DELEGATE STAKING RESPONSES
155
+ # ============================================================================
156
+
157
+
158
+ class ValidatorDelegateStakeAddResponse(BaseStakingResponse):
159
+ """Response for adding validator delegate stake."""
160
+
161
+ validator_id: Optional[int] = Field(None, description="Validator ID")
162
+ stake_added: Optional[int] = Field(None, description="Amount delegated in wei")
163
+ shares_received: Optional[int] = Field(None, description="Shares received")
164
+
165
+
166
+ class ValidatorDelegateStakeRemoveResponse(BaseStakingResponse):
167
+ """Response for removing validator delegate stake."""
168
+
169
+ validator_id: Optional[int] = Field(None, description="Validator ID")
170
+ shares_removed: Optional[int] = Field(None, description="Shares removed")
171
+ balance_received: Optional[int] = Field(None, description="Balance received in wei")
172
+
173
+
174
+ class ValidatorDelegateStakeTransferResponse(BaseStakingResponse):
175
+ """Response for transferring validator delegate stake."""
176
+
177
+ validator_id: Optional[int] = Field(None, description="Validator ID")
178
+ to_account: Optional[str] = Field(None, description="Destination account")
179
+ shares_transferred: Optional[int] = Field(None, description="Shares transferred")
180
+
181
+
182
+ class ValidatorDelegateStakeSwapResponse(BaseStakingResponse):
183
+ """Response for swapping validator delegate stake."""
184
+
185
+ from_validator_id: Optional[int] = Field(None, description="Source validator ID")
186
+ to_validator_id: Optional[int] = Field(None, description="Destination validator ID")
187
+ shares_swapped: Optional[int] = Field(None, description="Shares swapped")
188
+
189
+
190
+ class ValidatorDelegateStakeDonateResponse(BaseStakingResponse):
191
+ """Response for donating validator delegate stake."""
192
+
193
+ validator_id: Optional[int] = Field(None, description="Validator ID")
194
+ balance_donated: Optional[int] = Field(None, description="Balance donated in wei")
195
+
196
+
197
+ # ============================================================================
198
+ # NODE DELEGATE STAKING RESPONSES
199
+ # ============================================================================
200
+
201
+
202
+ class NodeDelegateStakeAddResponse(BaseStakingResponse):
203
+ """Response for adding node delegate stake."""
204
+
205
+ subnet_id: Optional[int] = Field(None, description="Subnet ID")
206
+ subnet_node_id: Optional[int] = Field(None, description="Subnet node ID")
207
+ hotkey: Optional[str] = Field(None, description="Hotkey address")
208
+ stake_added: Optional[int] = Field(None, description="Amount delegated in wei")
209
+ shares_received: Optional[int] = Field(None, description="Shares received")
210
+
211
+
212
+ class NodeDelegateStakeRemoveResponse(BaseStakingResponse):
213
+ """Response for removing node delegate stake."""
214
+
215
+ subnet_id: Optional[int] = Field(None, description="Subnet ID")
216
+ subnet_node_id: Optional[int] = Field(None, description="Subnet node ID")
217
+ hotkey: Optional[str] = Field(None, description="Hotkey address")
218
+ shares_removed: Optional[int] = Field(None, description="Shares removed")
219
+ balance_received: Optional[int] = Field(None, description="Balance received in wei")
220
+
221
+
222
+ class NodeDelegateStakeTransferResponse(BaseStakingResponse):
223
+ """Response for transferring node delegate stake."""
224
+
225
+ subnet_id: Optional[int] = Field(None, description="Subnet ID")
226
+ subnet_node_id: Optional[int] = Field(None, description="Subnet node ID")
227
+ hotkey: Optional[str] = Field(None, description="Hotkey address")
228
+ from_account: Optional[str] = Field(None, description="Source account")
229
+ to_account: Optional[str] = Field(None, description="Destination account")
230
+ shares_transferred: Optional[int] = Field(None, description="Shares transferred")
231
+
232
+
233
+ class NodeDelegateStakeSwapResponse(BaseStakingResponse):
234
+ """Response for swapping node delegate stake."""
235
+
236
+ from_subnet_id: Optional[int] = Field(None, description="Source subnet ID")
237
+ from_subnet_node_id: Optional[int] = Field(
238
+ None, description="Source subnet node ID"
239
+ )
240
+ from_hotkey: Optional[str] = Field(None, description="Source hotkey")
241
+ to_subnet_id: Optional[int] = Field(None, description="Destination subnet ID")
242
+ to_subnet_node_id: Optional[int] = Field(
243
+ None, description="Destination subnet node ID"
244
+ )
245
+ to_hotkey: Optional[str] = Field(None, description="Destination hotkey")
246
+ shares_swapped: Optional[int] = Field(None, description="Shares swapped")
247
+
248
+
249
+ class NodeDelegateStakeDonateResponse(BaseStakingResponse):
250
+ """Response for donating node delegate stake."""
251
+
252
+ subnet_id: Optional[int] = Field(None, description="Subnet ID")
253
+ subnet_node_id: Optional[int] = Field(None, description="Subnet node ID")
254
+ hotkey: Optional[str] = Field(None, description="Hotkey address")
255
+ shares_donated: Optional[int] = Field(None, description="Shares donated")
256
+ balance_donated: Optional[int] = Field(None, description="Balance donated in wei")
257
+
258
+
259
+ # ============================================================================
260
+ # CROSS-STAKE SWAP RESPONSES
261
+ # ============================================================================
262
+
263
+
264
+ class StakeSwapFromNodeToSubnetResponse(BaseStakingResponse):
265
+ """Response for swapping stake from node delegate to subnet delegate."""
266
+
267
+ subnet_id: Optional[int] = Field(None, description="Subnet ID")
268
+ subnet_node_id: Optional[int] = Field(None, description="Subnet node ID")
269
+ hotkey: Optional[str] = Field(None, description="Hotkey address")
270
+ stake_swapped: Optional[int] = Field(None, description="Amount swapped in wei")
271
+ node_shares_removed: Optional[int] = Field(
272
+ None, description="Node delegate shares removed"
273
+ )
274
+ subnet_shares_received: Optional[int] = Field(
275
+ None, description="Subnet delegate shares received"
276
+ )
277
+
278
+
279
+ class StakeSwapFromSubnetToNodeResponse(BaseStakingResponse):
280
+ """Response for swapping stake from subnet delegate to node delegate."""
281
+
282
+ subnet_id: Optional[int] = Field(None, description="Subnet ID")
283
+ subnet_node_id: Optional[int] = Field(None, description="Subnet node ID")
284
+ hotkey: Optional[str] = Field(None, description="Hotkey address")
285
+ stake_swapped: Optional[int] = Field(None, description="Amount swapped in wei")
286
+ subnet_shares_removed: Optional[int] = Field(
287
+ None, description="Subnet delegate shares removed"
288
+ )
289
+ node_shares_received: Optional[int] = Field(
290
+ None, description="Node delegate shares received"
291
+ )
292
+
293
+
294
+ class StakeSwapFromValidatorToSubnetResponse(BaseStakingResponse):
295
+ """Response for swapping stake from validator delegate to subnet delegate."""
296
+
297
+ validator_id: Optional[int] = Field(None, description="Source validator ID")
298
+ subnet_id: Optional[int] = Field(None, description="Destination subnet ID")
299
+ stake_swapped: Optional[int] = Field(None, description="Shares swapped")
300
+ validator_shares_removed: Optional[int] = Field(
301
+ None, description="Validator delegate shares removed"
302
+ )
303
+ subnet_shares_received: Optional[int] = Field(
304
+ None, description="Subnet delegate shares received"
305
+ )
306
+
307
+
308
+ class StakeSwapFromSubnetToValidatorResponse(BaseStakingResponse):
309
+ """Response for swapping stake from subnet delegate to validator delegate."""
310
+
311
+ subnet_id: Optional[int] = Field(None, description="Source subnet ID")
312
+ validator_id: Optional[int] = Field(None, description="Destination validator ID")
313
+ stake_swapped: Optional[int] = Field(None, description="Shares swapped")
314
+ subnet_shares_removed: Optional[int] = Field(
315
+ None, description="Subnet delegate shares removed"
316
+ )
317
+ validator_shares_received: Optional[int] = Field(
318
+ None, description="Validator delegate shares received"
319
+ )
320
+
321
+
322
+ class StakeSwapQueueUpdateResponse(BaseStakingResponse):
323
+ """Response for updating stake swap queue."""
324
+
325
+ queue_updated: Optional[bool] = Field(None, description="Whether queue was updated")
326
+ queue_position: Optional[int] = Field(
327
+ None, description="Position in queue if applicable"
328
+ )
329
+
330
+
331
+ # ============================================================================
332
+ # OVERWATCH STAKING RESPONSES
333
+ # ============================================================================
334
+
335
+
336
+ class OverwatchStakeAddResponse(BaseStakingResponse):
337
+ """Response for adding overwatch stake."""
338
+
339
+ stake_added: Optional[int] = Field(None, description="Amount staked in wei")
340
+ total_overwatch_stake: Optional[int] = Field(
341
+ None, description="Total overwatch stake"
342
+ )
343
+
344
+
345
+ class OverwatchStakeRemoveResponse(BaseStakingResponse):
346
+ """Response for removing overwatch stake."""
347
+
348
+ stake_removed: Optional[int] = Field(None, description="Amount removed in wei")
349
+ remaining_overwatch_stake: Optional[int] = Field(
350
+ None, description="Remaining overwatch stake"
351
+ )
352
+
353
+
354
+ # ============================================================================
355
+ # QUERY RESPONSE MODELS
356
+ # ============================================================================
357
+
358
+
359
+ class StakeInfo(BaseModel):
360
+ """Comprehensive stake information for an account."""
361
+
362
+ account_id: str = Field(..., description="Account address")
363
+ subnet_id: int = Field(..., description="Subnet ID")
364
+
365
+ # Direct stake
366
+ direct_stake_balance: int = Field(0, description="Direct stake balance in wei")
367
+
368
+ # Delegate stake (subnet level)
369
+ delegate_stake_shares: int = Field(0, description="Delegate stake shares")
370
+ delegate_stake_balance: int = Field(0, description="Delegate stake balance in wei")
371
+
372
+ # Node delegate stakes
373
+ node_delegate_stakes: list[dict[str, Any]] = Field(
374
+ default_factory=list, description="Node delegate stakes"
375
+ )
376
+
377
+ # Unbonding information
378
+ unbonding_ledger: list[UnbondingEntry] = Field(
379
+ default_factory=list, description="Unbonding entries"
380
+ )
381
+
382
+ @classmethod
383
+ def from_scale_bytes(cls, scale_bytes: bytes) -> "StakeInfo":
384
+ """Create StakeInfo from SCALE encoded bytes."""
385
+
386
+ sb = ScaleBytes(scale_bytes)
387
+
388
+ # Parse the stake info structure
389
+ # This depends on the exact blockchain storage format
390
+ # For now, return a basic structure
391
+ return cls(
392
+ account_id="", # Parse from context
393
+ subnet_id=0, # Parse from context
394
+ direct_stake_balance=(
395
+ sb.get_next_u128() if sb.remaining_length() >= 16 else 0
396
+ ),
397
+ delegate_stake_shares=(
398
+ sb.get_next_u128() if sb.remaining_length() >= 16 else 0
399
+ ),
400
+ delegate_stake_balance=0, # Calculate from shares
401
+ node_delegate_stakes=[],
402
+ unbonding_ledger=[],
403
+ )
404
+
405
+
406
+ class DelegateStakeInfo(BaseModel):
407
+ """Delegate stake information for a subnet."""
408
+
409
+ subnet_id: int = Field(..., description="Subnet ID")
410
+ shares: int = Field(..., description="Number of shares")
411
+ balance: int = Field(..., description="Balance value in wei")
412
+ share_price: float = Field(..., description="Share price in TENSOR per share")
413
+
414
+ @classmethod
415
+ def from_scale_bytes(
416
+ cls, scale_bytes: bytes, subnet_id: int = 0
417
+ ) -> "DelegateStakeInfo":
418
+ """Create DelegateStakeInfo from SCALE encoded bytes."""
419
+
420
+ sb = ScaleBytes(scale_bytes)
421
+ shares = sb.get_next_u128()
422
+ balance = sb.get_next_u128()
423
+
424
+ share_price = balance / shares / 1e18 if shares > 0 else 1.0
425
+
426
+ return cls(
427
+ subnet_id=subnet_id, shares=shares, balance=balance, share_price=share_price
428
+ )
429
+
430
+
431
+ class NodeDelegateStakeInfo(BaseModel):
432
+ """Node delegate stake information."""
433
+
434
+ subnet_id: int = Field(..., description="Subnet ID")
435
+ subnet_node_id: int = Field(..., description="Subnet node ID")
436
+ hotkey: str = Field(..., description="Hotkey address")
437
+ shares: int = Field(..., description="Number of shares")
438
+ balance: int = Field(..., description="Balance value in wei")
439
+
440
+ @classmethod
441
+ def from_scale_bytes(
442
+ cls,
443
+ scale_bytes: bytes,
444
+ subnet_id: int = 0,
445
+ subnet_node_id: int = 0,
446
+ hotkey: str = "",
447
+ ) -> "NodeDelegateStakeInfo":
448
+ """Create NodeDelegateStakeInfo from SCALE encoded bytes."""
449
+
450
+ sb = ScaleBytes(scale_bytes)
451
+ shares = sb.get_next_u128()
452
+ balance = sb.get_next_u128()
453
+
454
+ return cls(
455
+ subnet_id=subnet_id,
456
+ subnet_node_id=subnet_node_id,
457
+ hotkey=hotkey,
458
+ shares=shares,
459
+ balance=balance,
460
+ )
461
+
462
+
463
+ class UnbondingLedger(BaseModel):
464
+ """Complete unbonding ledger for an account."""
465
+
466
+ account_id: str = Field(..., description="Account address")
467
+ total_unbonding: int = Field(0, description="Total unbonding amount in wei")
468
+ entries: list[UnbondingEntry] = Field(
469
+ default_factory=list, description="Unbonding entries"
470
+ )
471
+ claimable_amount: int = Field(0, description="Amount ready to claim in wei")
472
+ pending_amount: int = Field(0, description="Amount still unbonding in wei")
473
+
474
+ @classmethod
475
+ def from_scale_bytes(
476
+ cls, scale_bytes: bytes, account_id: str = "", current_block: int = 0
477
+ ) -> "UnbondingLedger":
478
+ """Create UnbondingLedger from SCALE encoded bytes."""
479
+
480
+ sb = ScaleBytes(scale_bytes)
481
+ entries = []
482
+
483
+ # Parse vector of (u128, u32) tuples
484
+ vector_length = sb.get_next_u32()
485
+ for _ in range(vector_length):
486
+ amount = sb.get_next_u128()
487
+ unlock_block = sb.get_next_u32()
488
+ entries.append(UnbondingEntry(amount=amount, unlock_block=unlock_block))
489
+
490
+ total_unbonding = sum(entry.amount for entry in entries)
491
+ claimable_amount = sum(
492
+ entry.amount for entry in entries if current_block >= entry.unlock_block
493
+ )
494
+ pending_amount = total_unbonding - claimable_amount
495
+
496
+ return cls(
497
+ account_id=account_id,
498
+ total_unbonding=total_unbonding,
499
+ entries=entries,
500
+ claimable_amount=claimable_amount,
501
+ pending_amount=pending_amount,
502
+ )