htcli 1.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- htcli-1.1.0.dist-info/METADATA +509 -0
- htcli-1.1.0.dist-info/RECORD +140 -0
- htcli-1.1.0.dist-info/WHEEL +4 -0
- htcli-1.1.0.dist-info/entry_points.txt +2 -0
- htcli-1.1.0.dist-info/licenses/LICENSE +21 -0
- src/__init__.py +0 -0
- src/htcli/__init__.py +5 -0
- src/htcli/client/__init__.py +338 -0
- src/htcli/client/extrinsics/__init__.py +26 -0
- src/htcli/client/extrinsics/base.py +487 -0
- src/htcli/client/extrinsics/consensus.py +79 -0
- src/htcli/client/extrinsics/governance.py +714 -0
- src/htcli/client/extrinsics/identity.py +490 -0
- src/htcli/client/extrinsics/node.py +1054 -0
- src/htcli/client/extrinsics/overwatch.py +401 -0
- src/htcli/client/extrinsics/staking.py +1504 -0
- src/htcli/client/extrinsics/subnet.py +2218 -0
- src/htcli/client/extrinsics/validator.py +203 -0
- src/htcli/client/extrinsics/wallet.py +323 -0
- src/htcli/client/offchain/__init__.py +10 -0
- src/htcli/client/offchain/backup.py +385 -0
- src/htcli/client/offchain/config.py +541 -0
- src/htcli/client/offchain/wallet.py +839 -0
- src/htcli/client/rpc/__init__.py +20 -0
- src/htcli/client/rpc/chain.py +568 -0
- src/htcli/client/rpc/node.py +783 -0
- src/htcli/client/rpc/overwatch.py +680 -0
- src/htcli/client/rpc/staking.py +216 -0
- src/htcli/client/rpc/subnet.py +2104 -0
- src/htcli/client/rpc/wallet.py +912 -0
- src/htcli/commands/__init__.py +31 -0
- src/htcli/commands/chain/__init__.py +66 -0
- src/htcli/commands/chain/display.py +204 -0
- src/htcli/commands/chain/handlers.py +260 -0
- src/htcli/commands/config/__init__.py +158 -0
- src/htcli/commands/config/display.py +353 -0
- src/htcli/commands/config/handlers.py +347 -0
- src/htcli/commands/config/prompts.py +357 -0
- src/htcli/commands/consensus/__init__.py +61 -0
- src/htcli/commands/consensus/handlers.py +100 -0
- src/htcli/commands/governance/__init__.py +49 -0
- src/htcli/commands/governance/handlers.py +81 -0
- src/htcli/commands/node/__init__.py +304 -0
- src/htcli/commands/node/display.py +749 -0
- src/htcli/commands/node/error_handling.py +470 -0
- src/htcli/commands/node/handlers.py +844 -0
- src/htcli/commands/node/prompts.py +346 -0
- src/htcli/commands/overwatch/__init__.py +219 -0
- src/htcli/commands/overwatch/display.py +396 -0
- src/htcli/commands/overwatch/error_handling.py +276 -0
- src/htcli/commands/overwatch/handlers.py +443 -0
- src/htcli/commands/overwatch/prompts.py +359 -0
- src/htcli/commands/stake/__init__.py +736 -0
- src/htcli/commands/stake/display.py +1103 -0
- src/htcli/commands/stake/error_handling.py +425 -0
- src/htcli/commands/stake/handlers.py +1902 -0
- src/htcli/commands/stake/prompts.py +1080 -0
- src/htcli/commands/subnet/__init__.py +639 -0
- src/htcli/commands/subnet/display.py +801 -0
- src/htcli/commands/subnet/error_handling.py +524 -0
- src/htcli/commands/subnet/handlers.py +2855 -0
- src/htcli/commands/subnet/prompts.py +1225 -0
- src/htcli/commands/validator/__init__.py +192 -0
- src/htcli/commands/validator/display.py +54 -0
- src/htcli/commands/validator/handlers.py +340 -0
- src/htcli/commands/wallet/__init__.py +546 -0
- src/htcli/commands/wallet/display.py +806 -0
- src/htcli/commands/wallet/error_handling.py +210 -0
- src/htcli/commands/wallet/handlers.py +3040 -0
- src/htcli/commands/wallet/prompts.py +1518 -0
- src/htcli/config.py +184 -0
- src/htcli/dependencies.py +186 -0
- src/htcli/errors/__init__.py +63 -0
- src/htcli/errors/base.py +141 -0
- src/htcli/errors/display.py +20 -0
- src/htcli/errors/handlers.py +710 -0
- src/htcli/main.py +343 -0
- src/htcli/models/__init__.py +21 -0
- src/htcli/models/enums/enum_types.py +35 -0
- src/htcli/models/errors.py +103 -0
- src/htcli/models/requests/__init__.py +197 -0
- src/htcli/models/requests/config.py +70 -0
- src/htcli/models/requests/consensus.py +19 -0
- src/htcli/models/requests/governance.py +38 -0
- src/htcli/models/requests/identity.py +51 -0
- src/htcli/models/requests/key.py +22 -0
- src/htcli/models/requests/node.py +91 -0
- src/htcli/models/requests/overwatch.py +64 -0
- src/htcli/models/requests/staking.py +580 -0
- src/htcli/models/requests/subnet.py +195 -0
- src/htcli/models/requests/validator.py +139 -0
- src/htcli/models/requests/wallet.py +118 -0
- src/htcli/models/responses/__init__.py +147 -0
- src/htcli/models/responses/base.py +18 -0
- src/htcli/models/responses/chain.py +39 -0
- src/htcli/models/responses/config.py +58 -0
- src/htcli/models/responses/identity.py +102 -0
- src/htcli/models/responses/overwatch.py +51 -0
- src/htcli/models/responses/staking.py +502 -0
- src/htcli/models/responses/subnet.py +856 -0
- src/htcli/models/responses/wallet.py +185 -0
- src/htcli/ui/__init__.py +87 -0
- src/htcli/ui/colors.py +309 -0
- src/htcli/ui/components/__init__.py +60 -0
- src/htcli/ui/components/panels.py +174 -0
- src/htcli/ui/components/progress.py +166 -0
- src/htcli/ui/components/spinners.py +92 -0
- src/htcli/ui/components/tables.py +809 -0
- src/htcli/ui/components/trees.py +721 -0
- src/htcli/ui/display.py +336 -0
- src/htcli/ui/prompts.py +870 -0
- src/htcli/utils/__init__.py +76 -0
- src/htcli/utils/blockchain/__init__.py +75 -0
- src/htcli/utils/blockchain/formatting.py +368 -0
- src/htcli/utils/blockchain/patches.py +286 -0
- src/htcli/utils/blockchain/peer_id.py +186 -0
- src/htcli/utils/blockchain/staking.py +448 -0
- src/htcli/utils/blockchain/type_registry.py +1373 -0
- src/htcli/utils/blockchain/validation.py +179 -0
- src/htcli/utils/cache.py +613 -0
- src/htcli/utils/constants.py +38 -0
- src/htcli/utils/legacy/__init__.py +12 -0
- src/htcli/utils/legacy/colors.py +311 -0
- src/htcli/utils/legacy/crypto.py +1176 -0
- src/htcli/utils/legacy/formatting.py +452 -0
- src/htcli/utils/legacy/interactive.py +306 -0
- src/htcli/utils/legacy/subnet_manifest.py +265 -0
- src/htcli/utils/legacy/validation.py +488 -0
- src/htcli/utils/logging.py +183 -0
- src/htcli/utils/network/__init__.py +20 -0
- src/htcli/utils/network/subnet.py +344 -0
- src/htcli/utils/prompts.py +27 -0
- src/htcli/utils/scale_codec.py +155 -0
- src/htcli/utils/validation/__init__.py +57 -0
- src/htcli/utils/validation/prompt_validators.py +267 -0
- src/htcli/utils/wallet/__init__.py +65 -0
- src/htcli/utils/wallet/auth.py +151 -0
- src/htcli/utils/wallet/core.py +1069 -0
- src/htcli/utils/wallet/crypto.py +1615 -0
- src/htcli/utils/wallet/migration.py +159 -0
|
@@ -0,0 +1,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
|