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,448 @@
1
+ """
2
+ Staking utilities for Hypertensor blockchain.
3
+
4
+ Provides helper functions for staking calculations, conversions,
5
+ and share calculations.
6
+ """
7
+
8
+ from typing import Optional, Tuple
9
+
10
+ # ============================================================================
11
+ # UNIT CONVERSIONS
12
+ # ============================================================================
13
+
14
+
15
+ def wei_to_tensor(wei_amount: int) -> float:
16
+ """
17
+ Convert wei amount to TENSOR.
18
+
19
+ Args:
20
+ wei_amount: Amount in wei
21
+
22
+ Returns:
23
+ Amount in TENSOR
24
+ """
25
+ return wei_amount / 1e18
26
+
27
+
28
+ def tensor_to_wei(tensor_amount: float) -> int:
29
+ """
30
+ Convert TENSOR amount to wei.
31
+
32
+ Args:
33
+ tensor_amount: Amount in TENSOR
34
+
35
+ Returns:
36
+ Amount in wei
37
+ """
38
+ return int(tensor_amount * 1e18)
39
+
40
+
41
+ def format_tensor_amount(wei_amount: int, decimals: int = 4) -> str:
42
+ """
43
+ Format wei amount as human-readable TENSOR string.
44
+
45
+ Args:
46
+ wei_amount: Amount in wei
47
+ decimals: Number of decimal places to show
48
+
49
+ Returns:
50
+ Formatted string (e.g., "100.5000 TENSOR")
51
+ """
52
+ tensor_amount = wei_to_tensor(wei_amount)
53
+ return f"{tensor_amount:.{decimals}f} TENSOR"
54
+
55
+
56
+ def parse_tensor_amount(tensor_str: str) -> int:
57
+ """
58
+ Parse human-readable TENSOR string to wei.
59
+
60
+ Args:
61
+ tensor_str: String like "100.5 TENSOR" or "100.5"
62
+
63
+ Returns:
64
+ Amount in wei
65
+
66
+ Raises:
67
+ ValueError: If string format is invalid
68
+ """
69
+ # Remove "TENSOR" suffix if present
70
+ amount_str = tensor_str.replace("TENSOR", "").strip()
71
+
72
+ try:
73
+ tensor_amount = float(amount_str)
74
+ return tensor_to_wei(tensor_amount)
75
+ except ValueError:
76
+ raise ValueError(f"Invalid tensor amount format: {tensor_str}")
77
+
78
+
79
+ # ============================================================================
80
+ # SHARE CALCULATIONS
81
+ # ============================================================================
82
+
83
+
84
+ def calculate_shares_from_balance(
85
+ balance: int, total_shares: int, total_balance: int
86
+ ) -> int:
87
+ """
88
+ Calculate shares from balance using current share price.
89
+
90
+ Args:
91
+ balance: Balance to convert to shares
92
+ total_shares: Current total shares outstanding
93
+ total_balance: Current total balance backing shares
94
+
95
+ Returns:
96
+ Number of shares
97
+ """
98
+ if total_shares == 0:
99
+ # If no shares exist, 1 share = 1 wei (initial price)
100
+ return balance
101
+
102
+ if total_balance == 0:
103
+ raise ValueError("Cannot calculate shares with zero total balance")
104
+
105
+ # Share price = total_balance / total_shares
106
+ # Shares = balance / share_price = balance * total_shares / total_balance
107
+ return (balance * total_shares) // total_balance
108
+
109
+
110
+ def calculate_balance_from_shares(
111
+ shares: int, total_shares: int, total_balance: int
112
+ ) -> int:
113
+ """
114
+ Calculate balance from shares using current share price.
115
+
116
+ Args:
117
+ shares: Number of shares
118
+ total_shares: Current total shares outstanding
119
+ total_balance: Current total balance backing shares
120
+
121
+ Returns:
122
+ Balance in wei
123
+ """
124
+ if total_shares == 0:
125
+ return 0
126
+
127
+ # Share price = total_balance / total_shares
128
+ # Balance = shares * share_price = shares * total_balance / total_shares
129
+ return (shares * total_balance) // total_shares
130
+
131
+
132
+ def calculate_share_price(total_shares: int, total_balance: int) -> float:
133
+ """
134
+ Calculate current share price.
135
+
136
+ Args:
137
+ total_shares: Current total shares outstanding
138
+ total_balance: Current total balance backing shares
139
+
140
+ Returns:
141
+ Share price in TENSOR per share
142
+ """
143
+ if total_shares == 0:
144
+ return 1.0 # Initial price
145
+
146
+ return wei_to_tensor(total_balance) / total_shares
147
+
148
+
149
+ def calculate_share_price_change(
150
+ old_total_shares: int,
151
+ old_total_balance: int,
152
+ new_total_shares: int,
153
+ new_total_balance: int,
154
+ ) -> Tuple[float, float]:
155
+ """
156
+ Calculate share price change between two states.
157
+
158
+ Args:
159
+ old_total_shares: Previous total shares
160
+ old_total_balance: Previous total balance
161
+ new_total_shares: New total shares
162
+ new_total_balance: New total balance
163
+
164
+ Returns:
165
+ Tuple of (old_price, new_price) in TENSOR per share
166
+ """
167
+ old_price = calculate_share_price(old_total_shares, old_total_balance)
168
+ new_price = calculate_share_price(new_total_shares, new_total_balance)
169
+
170
+ return old_price, new_price
171
+
172
+
173
+ # ============================================================================
174
+ # UNBONDING CALCULATIONS
175
+ # ============================================================================
176
+
177
+
178
+ def estimate_unbonding_time(blocks_per_epoch: int = 2100) -> int:
179
+ """
180
+ Estimate unbonding time in blocks.
181
+
182
+ Args:
183
+ blocks_per_epoch: Number of blocks per epoch
184
+
185
+ Returns:
186
+ Estimated unbonding time in blocks
187
+ """
188
+ # Typical unbonding period is 7 epochs
189
+ unbonding_epochs = 7
190
+ return unbonding_epochs * blocks_per_epoch
191
+
192
+
193
+ def estimate_unbonding_time_days(
194
+ blocks_per_epoch: int = 2100, block_time_seconds: int = 6
195
+ ) -> float:
196
+ """
197
+ Estimate unbonding time in days.
198
+
199
+ Args:
200
+ blocks_per_epoch: Number of blocks per epoch
201
+ block_time_seconds: Average block time in seconds
202
+
203
+ Returns:
204
+ Estimated unbonding time in days
205
+ """
206
+ unbonding_blocks = estimate_unbonding_time(blocks_per_epoch)
207
+ unbonding_seconds = unbonding_blocks * block_time_seconds
208
+ return unbonding_seconds / (24 * 60 * 60) # Convert to days
209
+
210
+
211
+ def calculate_unbonding_ledger_total(unbonding_ledger: list) -> int:
212
+ """
213
+ Calculate total amount in unbonding ledger.
214
+
215
+ Args:
216
+ unbonding_ledger: List of (amount, unlock_block) tuples
217
+
218
+ Returns:
219
+ Total unbonding amount in wei
220
+ """
221
+ return sum(entry[0] for entry in unbonding_ledger)
222
+
223
+
224
+ def find_claimable_unbondings(
225
+ unbonding_ledger: list, current_block: int
226
+ ) -> Tuple[list, list]:
227
+ """
228
+ Find claimable and pending unbondings.
229
+
230
+ Args:
231
+ unbonding_ledger: List of (amount, unlock_block) tuples
232
+ current_block: Current blockchain block number
233
+
234
+ Returns:
235
+ Tuple of (claimable_entries, pending_entries)
236
+ """
237
+ claimable = []
238
+ pending = []
239
+
240
+ for entry in unbonding_ledger:
241
+ amount, unlock_block = entry
242
+ if current_block >= unlock_block:
243
+ claimable.append(entry)
244
+ else:
245
+ pending.append(entry)
246
+
247
+ return claimable, pending
248
+
249
+
250
+ # ============================================================================
251
+ # STAKE VALIDATION
252
+ # ============================================================================
253
+
254
+
255
+ def validate_stake_amount(
256
+ amount: int, min_stake: int, max_stake: int, current_stake: int = 0
257
+ ) -> Tuple[bool, Optional[str]]:
258
+ """
259
+ Validate stake amount against limits.
260
+
261
+ Args:
262
+ amount: Amount to stake
263
+ min_stake: Minimum stake requirement
264
+ max_stake: Maximum stake limit
265
+ current_stake: Current stake amount
266
+
267
+ Returns:
268
+ Tuple of (is_valid, error_message)
269
+ """
270
+ if amount <= 0:
271
+ return False, "Stake amount must be positive"
272
+
273
+ new_total = current_stake + amount
274
+
275
+ if new_total < min_stake:
276
+ return (
277
+ False,
278
+ f"Total stake {format_tensor_amount(new_total)} below minimum {format_tensor_amount(min_stake)}",
279
+ )
280
+
281
+ if new_total > max_stake:
282
+ return (
283
+ False,
284
+ f"Total stake {format_tensor_amount(new_total)} exceeds maximum {format_tensor_amount(max_stake)}",
285
+ )
286
+
287
+ return True, None
288
+
289
+
290
+ def validate_unstake_amount(
291
+ amount: int, current_stake: int, min_stake: int = 0
292
+ ) -> Tuple[bool, Optional[str]]:
293
+ """
294
+ Validate unstake amount against current stake.
295
+
296
+ Args:
297
+ amount: Amount to unstake
298
+ current_stake: Current stake amount
299
+ min_stake: Minimum stake to maintain
300
+
301
+ Returns:
302
+ Tuple of (is_valid, error_message)
303
+ """
304
+ if amount <= 0:
305
+ return False, "Unstake amount must be positive"
306
+
307
+ if amount > current_stake:
308
+ return (
309
+ False,
310
+ f"Cannot unstake {format_tensor_amount(amount)} from {format_tensor_amount(current_stake)}",
311
+ )
312
+
313
+ remaining_stake = current_stake - amount
314
+
315
+ if remaining_stake < min_stake:
316
+ return (
317
+ False,
318
+ f"Remaining stake {format_tensor_amount(remaining_stake)} below minimum {format_tensor_amount(min_stake)}",
319
+ )
320
+
321
+ return True, None
322
+
323
+
324
+ # ============================================================================
325
+ # REWARD CALCULATIONS
326
+ # ============================================================================
327
+
328
+
329
+ def calculate_delegate_reward_rate(
330
+ node_stake: int,
331
+ total_subnet_stake: int,
332
+ base_reward_rate: float = 0.15, # 15% base rate
333
+ ) -> float:
334
+ """
335
+ Calculate delegate reward rate for a node.
336
+
337
+ Args:
338
+ node_stake: Node's stake amount
339
+ total_subnet_stake: Total stake in subnet
340
+ base_reward_rate: Base reward rate (default 15%)
341
+
342
+ Returns:
343
+ Calculated reward rate as decimal (0.15 = 15%)
344
+ """
345
+ if total_subnet_stake == 0:
346
+ return base_reward_rate
347
+
348
+ # Calculate stake ratio
349
+ stake_ratio = node_stake / total_subnet_stake
350
+
351
+ # Adjust reward rate based on stake ratio
352
+ # Higher stake ratio = higher reward rate
353
+ adjusted_rate = base_reward_rate * (1 + stake_ratio)
354
+
355
+ # Cap at maximum rate (e.g., 25%)
356
+ max_rate = 0.25
357
+ return min(adjusted_rate, max_rate)
358
+
359
+
360
+ def estimate_rewards(stake_amount: int, reward_rate: float, epochs: int = 1) -> int:
361
+ """
362
+ Estimate rewards for a given stake amount and rate.
363
+
364
+ Args:
365
+ stake_amount: Stake amount in wei
366
+ reward_rate: Reward rate as decimal (0.15 = 15%)
367
+ epochs: Number of epochs to calculate for
368
+
369
+ Returns:
370
+ Estimated rewards in wei
371
+ """
372
+ # Annual reward rate to epoch reward rate
373
+ # Assuming 365 days / 7 days per epoch = ~52 epochs per year
374
+ epochs_per_year = 52
375
+ epoch_reward_rate = reward_rate / epochs_per_year
376
+
377
+ # Calculate compound interest
378
+ rewards = stake_amount * epoch_reward_rate * epochs
379
+
380
+ return int(rewards)
381
+
382
+
383
+ # ============================================================================
384
+ # UTILITY FUNCTIONS
385
+ # ============================================================================
386
+
387
+
388
+ def format_stake_summary(
389
+ direct_stake: int,
390
+ delegate_stake_shares: int,
391
+ delegate_stake_balance: int,
392
+ node_delegate_stakes: list,
393
+ ) -> str:
394
+ """
395
+ Format a comprehensive stake summary.
396
+
397
+ Args:
398
+ direct_stake: Direct stake amount
399
+ delegate_stake_shares: Delegate stake shares
400
+ delegate_stake_balance: Delegate stake balance
401
+ node_delegate_stakes: List of node delegate stakes
402
+
403
+ Returns:
404
+ Formatted summary string
405
+ """
406
+ lines = []
407
+
408
+ if direct_stake > 0:
409
+ lines.append(f"Direct Stake: {format_tensor_amount(direct_stake)}")
410
+
411
+ if delegate_stake_shares > 0:
412
+ lines.append(
413
+ f"Delegate Stake: {format_tensor_amount(delegate_stake_balance)} ({delegate_stake_shares} shares)"
414
+ )
415
+
416
+ if node_delegate_stakes:
417
+ lines.append("Node Delegate Stakes:")
418
+ for stake in node_delegate_stakes:
419
+ lines.append(
420
+ f" - Node {stake['node_id']}: {format_tensor_amount(stake['balance'])}"
421
+ )
422
+
423
+ if not lines:
424
+ lines.append("No stakes found")
425
+
426
+ return "\n".join(lines)
427
+
428
+
429
+ def calculate_total_stake_value(
430
+ direct_stake: int, delegate_stake_balance: int, node_delegate_stakes: list
431
+ ) -> int:
432
+ """
433
+ Calculate total stake value across all types.
434
+
435
+ Args:
436
+ direct_stake: Direct stake amount
437
+ delegate_stake_balance: Delegate stake balance
438
+ node_delegate_stakes: List of node delegate stakes
439
+
440
+ Returns:
441
+ Total stake value in wei
442
+ """
443
+ total = direct_stake + delegate_stake_balance
444
+
445
+ for stake in node_delegate_stakes:
446
+ total += stake.get("balance", 0)
447
+
448
+ return total