bittensor-cli 8.2.0rc13__py3-none-any.whl → 8.2.0rc14__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.
- bittensor_cli/__init__.py +1 -1
- bittensor_cli/cli.py +406 -26
- bittensor_cli/src/__init__.py +1 -0
- bittensor_cli/src/bittensor/balances.py +29 -1
- bittensor_cli/src/bittensor/chain_data.py +13 -2
- bittensor_cli/src/bittensor/subtensor_interface.py +63 -77
- bittensor_cli/src/bittensor/utils.py +25 -5
- bittensor_cli/src/commands/stake/move.py +992 -0
- bittensor_cli/src/commands/stake/stake.py +20 -1134
- bittensor_cli/src/commands/subnets/__init__.py +0 -0
- bittensor_cli/src/commands/subnets/price.py +867 -0
- bittensor_cli/src/commands/{subnets.py → subnets/subnets.py} +13 -130
- bittensor_cli/src/commands/sudo.py +1 -2
- bittensor_cli/src/commands/wallets.py +9 -59
- {bittensor_cli-8.2.0rc13.dist-info → bittensor_cli-8.2.0rc14.dist-info}/METADATA +3 -1
- bittensor_cli-8.2.0rc14.dist-info/RECORD +33 -0
- bittensor_cli-8.2.0rc13.dist-info/RECORD +0 -30
- {bittensor_cli-8.2.0rc13.dist-info → bittensor_cli-8.2.0rc14.dist-info}/WHEEL +0 -0
- {bittensor_cli-8.2.0rc13.dist-info → bittensor_cli-8.2.0rc14.dist-info}/entry_points.txt +0 -0
- {bittensor_cli-8.2.0rc13.dist-info → bittensor_cli-8.2.0rc14.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,7 @@
|
|
1
1
|
import asyncio
|
2
2
|
from functools import partial
|
3
3
|
|
4
|
-
from typing import TYPE_CHECKING, Optional
|
4
|
+
from typing import TYPE_CHECKING, Optional
|
5
5
|
import typer
|
6
6
|
|
7
7
|
from bittensor_wallet import Wallet
|
@@ -25,7 +25,6 @@ from bittensor_cli.src.bittensor.utils import (
|
|
25
25
|
print_error,
|
26
26
|
get_hotkey_wallets_for_wallet,
|
27
27
|
is_valid_ss58_address,
|
28
|
-
u16_normalized_float,
|
29
28
|
format_error_message,
|
30
29
|
group_subnets,
|
31
30
|
millify_tao,
|
@@ -36,797 +35,6 @@ if TYPE_CHECKING:
|
|
36
35
|
from bittensor_cli.src.bittensor.subtensor_interface import SubtensorInterface
|
37
36
|
|
38
37
|
|
39
|
-
# Helpers and Extrinsics
|
40
|
-
|
41
|
-
|
42
|
-
async def _get_threshold_amount(
|
43
|
-
subtensor: "SubtensorInterface", block_hash: str
|
44
|
-
) -> Balance:
|
45
|
-
mrs = await subtensor.substrate.query(
|
46
|
-
module="SubtensorModule",
|
47
|
-
storage_function="NominatorMinRequiredStake",
|
48
|
-
block_hash=block_hash,
|
49
|
-
)
|
50
|
-
min_req_stake: Balance = Balance.from_rao(mrs)
|
51
|
-
return min_req_stake
|
52
|
-
|
53
|
-
|
54
|
-
async def _check_threshold_amount(
|
55
|
-
subtensor: "SubtensorInterface",
|
56
|
-
sb: Balance,
|
57
|
-
block_hash: str,
|
58
|
-
min_req_stake: Optional[Balance] = None,
|
59
|
-
) -> tuple[bool, Balance]:
|
60
|
-
"""
|
61
|
-
Checks if the new stake balance will be above the minimum required stake threshold.
|
62
|
-
|
63
|
-
:param sb: the balance to check for threshold limits.
|
64
|
-
|
65
|
-
:return: (success, threshold)
|
66
|
-
`True` if the staking balance is above the threshold, or `False` if the staking balance is below the
|
67
|
-
threshold.
|
68
|
-
The threshold balance required to stake.
|
69
|
-
"""
|
70
|
-
if not min_req_stake:
|
71
|
-
min_req_stake = await _get_threshold_amount(subtensor, block_hash)
|
72
|
-
|
73
|
-
if min_req_stake > sb:
|
74
|
-
return False, min_req_stake
|
75
|
-
else:
|
76
|
-
return True, min_req_stake
|
77
|
-
|
78
|
-
|
79
|
-
async def add_stake_extrinsic(
|
80
|
-
subtensor: "SubtensorInterface",
|
81
|
-
wallet: Wallet,
|
82
|
-
old_balance: Balance,
|
83
|
-
hotkey_ss58: Optional[str] = None,
|
84
|
-
amount: Optional[Balance] = None,
|
85
|
-
wait_for_inclusion: bool = True,
|
86
|
-
wait_for_finalization: bool = False,
|
87
|
-
prompt: bool = False,
|
88
|
-
) -> bool:
|
89
|
-
"""
|
90
|
-
Adds the specified amount of stake to passed hotkey `uid`.
|
91
|
-
|
92
|
-
:param subtensor: the initialized SubtensorInterface object to use
|
93
|
-
:param wallet: Bittensor wallet object.
|
94
|
-
:param old_balance: the balance prior to the staking
|
95
|
-
:param hotkey_ss58: The `ss58` address of the hotkey account to stake to defaults to the wallet's hotkey.
|
96
|
-
:param amount: Amount to stake as Bittensor balance, `None` if staking all.
|
97
|
-
:param wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning `True`, or returns
|
98
|
-
`False` if the extrinsic fails to enter the block within the timeout.
|
99
|
-
:param wait_for_finalization: If set, waits for the extrinsic to be finalized on the chain before returning `True`,
|
100
|
-
or returns `False` if the extrinsic fails to be finalized within the timeout.
|
101
|
-
:param prompt: If `True`, the call waits for confirmation from the user before proceeding.
|
102
|
-
|
103
|
-
:return: success: Flag is `True` if extrinsic was finalized or included in the block. If we did not wait for
|
104
|
-
finalization/inclusion, the response is `True`.
|
105
|
-
"""
|
106
|
-
|
107
|
-
# Decrypt keys,
|
108
|
-
try:
|
109
|
-
wallet.unlock_coldkey()
|
110
|
-
except KeyFileError:
|
111
|
-
err_console.print("Error decrypting coldkey (possibly incorrect password)")
|
112
|
-
return False
|
113
|
-
|
114
|
-
# Default to wallet's own hotkey if the value is not passed.
|
115
|
-
if hotkey_ss58 is None:
|
116
|
-
hotkey_ss58 = wallet.hotkey.ss58_address
|
117
|
-
|
118
|
-
# Flag to indicate if we are using the wallet's own hotkey.
|
119
|
-
own_hotkey: bool
|
120
|
-
|
121
|
-
with console.status(
|
122
|
-
f":satellite: Syncing with chain: [white]{subtensor}[/white] ...",
|
123
|
-
spinner="aesthetic",
|
124
|
-
) as status:
|
125
|
-
block_hash = await subtensor.substrate.get_chain_head()
|
126
|
-
# Get hotkey owner
|
127
|
-
print_verbose("Confirming hotkey owner", status)
|
128
|
-
hotkey_owner = await subtensor.get_hotkey_owner(
|
129
|
-
hotkey_ss58=hotkey_ss58, block_hash=block_hash
|
130
|
-
)
|
131
|
-
own_hotkey = wallet.coldkeypub.ss58_address == hotkey_owner
|
132
|
-
if not own_hotkey:
|
133
|
-
# This is not the wallet's own hotkey, so we are delegating.
|
134
|
-
if not await subtensor.is_hotkey_delegate(
|
135
|
-
hotkey_ss58, block_hash=block_hash
|
136
|
-
):
|
137
|
-
err_console.print(
|
138
|
-
f"Hotkey {hotkey_ss58} is not a delegate on the chain."
|
139
|
-
)
|
140
|
-
return False
|
141
|
-
|
142
|
-
# Get hotkey take
|
143
|
-
hk_result = await subtensor.substrate.query(
|
144
|
-
module="SubtensorModule",
|
145
|
-
storage_function="Delegates",
|
146
|
-
params=[hotkey_ss58],
|
147
|
-
block_hash=block_hash,
|
148
|
-
)
|
149
|
-
hotkey_take = u16_normalized_float(hk_result or 0)
|
150
|
-
else:
|
151
|
-
hotkey_take = None
|
152
|
-
|
153
|
-
# Get current stake
|
154
|
-
print_verbose("Fetching current stake", status)
|
155
|
-
old_stake = await subtensor.get_stake_for_coldkey_and_hotkey(
|
156
|
-
coldkey_ss58=wallet.coldkeypub.ss58_address,
|
157
|
-
hotkey_ss58=hotkey_ss58,
|
158
|
-
block_hash=block_hash,
|
159
|
-
)
|
160
|
-
|
161
|
-
print_verbose("Fetching existential deposit", status)
|
162
|
-
# Grab the existential deposit.
|
163
|
-
existential_deposit = await subtensor.get_existential_deposit()
|
164
|
-
|
165
|
-
# Convert to bittensor.Balance
|
166
|
-
if amount is None:
|
167
|
-
# Stake it all.
|
168
|
-
staking_balance = Balance.from_tao(old_balance.tao)
|
169
|
-
else:
|
170
|
-
staking_balance = Balance.from_tao(amount)
|
171
|
-
|
172
|
-
# Leave existential balance to keep key alive.
|
173
|
-
if staking_balance > old_balance - existential_deposit:
|
174
|
-
# If we are staking all, we need to leave at least the existential deposit.
|
175
|
-
staking_balance = old_balance - existential_deposit
|
176
|
-
else:
|
177
|
-
staking_balance = staking_balance
|
178
|
-
|
179
|
-
# Check enough to stake.
|
180
|
-
if staking_balance > old_balance:
|
181
|
-
err_console.print(
|
182
|
-
f":cross_mark: [red]Not enough stake[/red]:[bold white]\n"
|
183
|
-
f"\tbalance:\t{old_balance}\n"
|
184
|
-
f"\tamount:\t{staking_balance}\n"
|
185
|
-
f"\tcoldkey:\t{wallet.name}[/bold white]"
|
186
|
-
)
|
187
|
-
return False
|
188
|
-
|
189
|
-
# If nominating, we need to check if the new stake balance will be above the minimum required stake threshold.
|
190
|
-
if not own_hotkey:
|
191
|
-
new_stake_balance = old_stake + staking_balance
|
192
|
-
print_verbose("Fetching threshold amount")
|
193
|
-
is_above_threshold, threshold = await _check_threshold_amount(
|
194
|
-
subtensor, new_stake_balance, block_hash
|
195
|
-
)
|
196
|
-
if not is_above_threshold:
|
197
|
-
err_console.print(
|
198
|
-
f":cross_mark: [red]New stake balance of {new_stake_balance} is below the minimum required nomination"
|
199
|
-
f" stake threshold {threshold}.[/red]"
|
200
|
-
)
|
201
|
-
return False
|
202
|
-
|
203
|
-
# Ask before moving on.
|
204
|
-
if prompt:
|
205
|
-
if not own_hotkey:
|
206
|
-
# We are delegating.
|
207
|
-
if not Confirm.ask(
|
208
|
-
f"Do you want to delegate:[bold white]\n"
|
209
|
-
f"\tamount: {staking_balance}\n"
|
210
|
-
f"\tto: {hotkey_ss58}\n"
|
211
|
-
f"\ttake: {hotkey_take}\n[/bold white]"
|
212
|
-
f"\towner: {hotkey_owner}\n"
|
213
|
-
):
|
214
|
-
return False
|
215
|
-
else:
|
216
|
-
if not Confirm.ask(
|
217
|
-
f"Do you want to stake:[bold white]\n"
|
218
|
-
f"\tamount: {staking_balance}\n"
|
219
|
-
f"\tto: {wallet.hotkey_str}\n"
|
220
|
-
f"\taddress: {hotkey_ss58}[/bold white]\n"
|
221
|
-
):
|
222
|
-
return False
|
223
|
-
|
224
|
-
with console.status(
|
225
|
-
f":satellite: Staking to: [bold white]{subtensor}[/bold white] ...",
|
226
|
-
spinner="earth",
|
227
|
-
):
|
228
|
-
call = await subtensor.substrate.compose_call(
|
229
|
-
call_module="SubtensorModule",
|
230
|
-
call_function="add_stake",
|
231
|
-
call_params={"hotkey": hotkey_ss58, "amount_staked": staking_balance.rao},
|
232
|
-
)
|
233
|
-
staking_response, err_msg = await subtensor.sign_and_send_extrinsic(
|
234
|
-
call, wallet, wait_for_inclusion, wait_for_finalization
|
235
|
-
)
|
236
|
-
if staking_response is True: # If we successfully staked.
|
237
|
-
# We only wait here if we expect finalization.
|
238
|
-
if not wait_for_finalization and not wait_for_inclusion:
|
239
|
-
return True
|
240
|
-
|
241
|
-
console.print(":white_heavy_check_mark: [green]Finalized[/green]")
|
242
|
-
with console.status(
|
243
|
-
f":satellite: Checking Balance on: [white]{subtensor}[/white] ..."
|
244
|
-
):
|
245
|
-
new_block_hash = await subtensor.substrate.get_chain_head()
|
246
|
-
new_balance, new_stake = await asyncio.gather(
|
247
|
-
subtensor.get_balance(
|
248
|
-
wallet.coldkeypub.ss58_address, block_hash=new_block_hash
|
249
|
-
),
|
250
|
-
subtensor.get_stake_for_coldkey_and_hotkey(
|
251
|
-
coldkey_ss58=wallet.coldkeypub.ss58_address,
|
252
|
-
hotkey_ss58=hotkey_ss58,
|
253
|
-
block_hash=new_block_hash,
|
254
|
-
),
|
255
|
-
)
|
256
|
-
|
257
|
-
console.print(
|
258
|
-
f"Balance:\n"
|
259
|
-
f"\t[blue]{old_balance}[/blue] :arrow_right: "
|
260
|
-
f"[green]{new_balance[wallet.coldkeypub.ss58_address]}[/green]"
|
261
|
-
)
|
262
|
-
console.print(
|
263
|
-
f"Stake:\n"
|
264
|
-
f"\t[blue]{old_stake}[/blue] :arrow_right: [green]{new_stake}[/green]"
|
265
|
-
)
|
266
|
-
return True
|
267
|
-
else:
|
268
|
-
err_console.print(f":cross_mark: [red]Failed[/red]: {err_msg}")
|
269
|
-
return False
|
270
|
-
|
271
|
-
|
272
|
-
async def add_stake_multiple_extrinsic(
|
273
|
-
subtensor: "SubtensorInterface",
|
274
|
-
wallet: Wallet,
|
275
|
-
old_balance: Balance,
|
276
|
-
hotkey_ss58s: list[str],
|
277
|
-
amounts: Optional[list[Balance]] = None,
|
278
|
-
wait_for_inclusion: bool = True,
|
279
|
-
wait_for_finalization: bool = False,
|
280
|
-
prompt: bool = False,
|
281
|
-
) -> bool:
|
282
|
-
"""Adds stake to each ``hotkey_ss58`` in the list, using each amount, from a common coldkey.
|
283
|
-
|
284
|
-
:param subtensor: The initialized SubtensorInterface object.
|
285
|
-
:param wallet: Bittensor wallet object for the coldkey.
|
286
|
-
:param old_balance: The balance of the wallet prior to staking.
|
287
|
-
:param hotkey_ss58s: List of hotkeys to stake to.
|
288
|
-
:param amounts: List of amounts to stake. If `None`, stake all to the first hotkey.
|
289
|
-
:param wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning `True`, or returns
|
290
|
-
`False` if the extrinsic fails to enter the block within the timeout.
|
291
|
-
:param wait_for_finalization: If set, waits for the extrinsic to be finalized on the chain before returning `True`,
|
292
|
-
or returns `False` if the extrinsic fails to be finalized within the timeout.
|
293
|
-
:param prompt: If `True`, the call waits for confirmation from the user before proceeding.
|
294
|
-
|
295
|
-
:return: success: `True` if extrinsic was finalized or included in the block. `True` if any wallet was staked. If
|
296
|
-
we did not wait for finalization/inclusion, the response is `True`.
|
297
|
-
"""
|
298
|
-
|
299
|
-
if len(hotkey_ss58s) == 0:
|
300
|
-
return True
|
301
|
-
|
302
|
-
if amounts is not None and len(amounts) != len(hotkey_ss58s):
|
303
|
-
raise ValueError("amounts must be a list of the same length as hotkey_ss58s")
|
304
|
-
|
305
|
-
new_amounts: Sequence[Optional[Balance]]
|
306
|
-
if amounts is None:
|
307
|
-
new_amounts = [None] * len(hotkey_ss58s)
|
308
|
-
else:
|
309
|
-
new_amounts = [Balance.from_tao(amount) for amount in amounts]
|
310
|
-
if sum(amount.tao for amount in new_amounts) == 0:
|
311
|
-
# Staking 0 tao
|
312
|
-
return True
|
313
|
-
|
314
|
-
# Decrypt coldkey.
|
315
|
-
try:
|
316
|
-
wallet.unlock_coldkey()
|
317
|
-
except KeyFileError:
|
318
|
-
err_console.print("Error decrypting coldkey (possibly incorrect password)")
|
319
|
-
return False
|
320
|
-
|
321
|
-
with console.status(
|
322
|
-
f":satellite: Syncing with chain: [white]{subtensor}[/white] ..."
|
323
|
-
):
|
324
|
-
block_hash = await subtensor.substrate.get_chain_head()
|
325
|
-
old_stakes = await asyncio.gather(
|
326
|
-
*[
|
327
|
-
subtensor.get_stake_for_coldkey_and_hotkey(
|
328
|
-
hk, wallet.coldkeypub.ss58_address, block_hash=block_hash
|
329
|
-
)
|
330
|
-
for hk in hotkey_ss58s
|
331
|
-
]
|
332
|
-
)
|
333
|
-
|
334
|
-
# Remove existential balance to keep key alive.
|
335
|
-
## Keys must maintain a balance of at least 1000 rao to stay alive.
|
336
|
-
total_staking_rao = sum(
|
337
|
-
[amount.rao if amount is not None else 0 for amount in new_amounts]
|
338
|
-
)
|
339
|
-
if total_staking_rao == 0:
|
340
|
-
# Staking all to the first wallet.
|
341
|
-
if old_balance.rao > 1000:
|
342
|
-
old_balance -= Balance.from_rao(1000)
|
343
|
-
|
344
|
-
elif total_staking_rao < 1000:
|
345
|
-
# Staking less than 1000 rao to the wallets.
|
346
|
-
pass
|
347
|
-
else:
|
348
|
-
# Staking more than 1000 rao to the wallets.
|
349
|
-
## Reduce the amount to stake to each wallet to keep the balance above 1000 rao.
|
350
|
-
percent_reduction = 1 - (1000 / total_staking_rao)
|
351
|
-
new_amounts = [
|
352
|
-
Balance.from_tao(amount.tao * percent_reduction)
|
353
|
-
for amount in cast(Sequence[Balance], new_amounts)
|
354
|
-
]
|
355
|
-
|
356
|
-
successful_stakes = 0
|
357
|
-
for idx, (hotkey_ss58, amount, old_stake) in enumerate(
|
358
|
-
zip(hotkey_ss58s, new_amounts, old_stakes)
|
359
|
-
):
|
360
|
-
staking_all = False
|
361
|
-
# Convert to bittensor.Balance
|
362
|
-
if amount is None:
|
363
|
-
# Stake it all.
|
364
|
-
staking_balance = Balance.from_tao(old_balance.tao)
|
365
|
-
staking_all = True
|
366
|
-
else:
|
367
|
-
# Amounts are cast to balance earlier in the function
|
368
|
-
assert isinstance(amount, Balance)
|
369
|
-
staking_balance = amount
|
370
|
-
|
371
|
-
# Check enough to stake
|
372
|
-
if staking_balance > old_balance:
|
373
|
-
err_console.print(
|
374
|
-
f":cross_mark: [red]Not enough balance[/red]:"
|
375
|
-
f" [green]{old_balance}[/green] to stake: [blue]{staking_balance}[/blue]"
|
376
|
-
f" from coldkey: [white]{wallet.name}[/white]"
|
377
|
-
)
|
378
|
-
continue
|
379
|
-
|
380
|
-
# Ask before moving on.
|
381
|
-
if prompt:
|
382
|
-
if not Confirm.ask(
|
383
|
-
f"Do you want to stake:\n"
|
384
|
-
f"\t[bold white]amount: {staking_balance}\n"
|
385
|
-
f"\thotkey: {wallet.hotkey_str}[/bold white ]?"
|
386
|
-
):
|
387
|
-
continue
|
388
|
-
|
389
|
-
call = await subtensor.substrate.compose_call(
|
390
|
-
call_module="SubtensorModule",
|
391
|
-
call_function="add_stake",
|
392
|
-
call_params={"hotkey": hotkey_ss58, "amount_staked": staking_balance.rao},
|
393
|
-
)
|
394
|
-
staking_response, err_msg = await subtensor.sign_and_send_extrinsic(
|
395
|
-
call, wallet, wait_for_inclusion, wait_for_finalization
|
396
|
-
)
|
397
|
-
|
398
|
-
if staking_response is True: # If we successfully staked.
|
399
|
-
# We only wait here if we expect finalization.
|
400
|
-
|
401
|
-
if idx < len(hotkey_ss58s) - 1:
|
402
|
-
# Wait for tx rate limit.
|
403
|
-
tx_query = await subtensor.substrate.query(
|
404
|
-
module="SubtensorModule",
|
405
|
-
storage_function="TxRateLimit",
|
406
|
-
block_hash=block_hash,
|
407
|
-
)
|
408
|
-
tx_rate_limit_blocks: int = tx_query
|
409
|
-
if tx_rate_limit_blocks > 0:
|
410
|
-
with console.status(
|
411
|
-
f":hourglass: [yellow]Waiting for tx rate limit:"
|
412
|
-
f" [white]{tx_rate_limit_blocks}[/white] blocks[/yellow]"
|
413
|
-
):
|
414
|
-
await asyncio.sleep(
|
415
|
-
tx_rate_limit_blocks * 12
|
416
|
-
) # 12 seconds per block
|
417
|
-
|
418
|
-
if not wait_for_finalization and not wait_for_inclusion:
|
419
|
-
old_balance -= staking_balance
|
420
|
-
successful_stakes += 1
|
421
|
-
if staking_all:
|
422
|
-
# If staked all, no need to continue
|
423
|
-
break
|
424
|
-
|
425
|
-
continue
|
426
|
-
|
427
|
-
console.print(":white_heavy_check_mark: [green]Finalized[/green]")
|
428
|
-
|
429
|
-
new_block_hash = await subtensor.substrate.get_chain_head()
|
430
|
-
new_stake, new_balance_ = await asyncio.gather(
|
431
|
-
subtensor.get_stake_for_coldkey_and_hotkey(
|
432
|
-
coldkey_ss58=wallet.coldkeypub.ss58_address,
|
433
|
-
hotkey_ss58=hotkey_ss58,
|
434
|
-
block_hash=new_block_hash,
|
435
|
-
),
|
436
|
-
subtensor.get_balance(
|
437
|
-
wallet.coldkeypub.ss58_address, block_hash=new_block_hash
|
438
|
-
),
|
439
|
-
)
|
440
|
-
new_balance = new_balance_[wallet.coldkeypub.ss58_address]
|
441
|
-
console.print(
|
442
|
-
"Stake ({}): [blue]{}[/blue] :arrow_right: [green]{}[/green]".format(
|
443
|
-
hotkey_ss58, old_stake, new_stake
|
444
|
-
)
|
445
|
-
)
|
446
|
-
old_balance = new_balance
|
447
|
-
successful_stakes += 1
|
448
|
-
if staking_all:
|
449
|
-
# If staked all, no need to continue
|
450
|
-
break
|
451
|
-
|
452
|
-
else:
|
453
|
-
err_console.print(f":cross_mark: [red]Failed[/red]: {err_msg}")
|
454
|
-
continue
|
455
|
-
|
456
|
-
if successful_stakes != 0:
|
457
|
-
with console.status(
|
458
|
-
f":satellite: Checking Balance on: ([white]{subtensor}[/white] ..."
|
459
|
-
):
|
460
|
-
new_balance_ = await subtensor.get_balance(
|
461
|
-
wallet.coldkeypub.ss58_address, reuse_block=False
|
462
|
-
)
|
463
|
-
new_balance = new_balance_[wallet.coldkeypub.ss58_address]
|
464
|
-
console.print(
|
465
|
-
f"Balance: [blue]{old_balance}[/blue] :arrow_right: [green]{new_balance}[/green]"
|
466
|
-
)
|
467
|
-
return True
|
468
|
-
|
469
|
-
return False
|
470
|
-
|
471
|
-
|
472
|
-
async def unstake_extrinsic(
|
473
|
-
subtensor: "SubtensorInterface",
|
474
|
-
wallet: Wallet,
|
475
|
-
hotkey_ss58: Optional[str] = None,
|
476
|
-
amount: Optional[Balance] = None,
|
477
|
-
wait_for_inclusion: bool = True,
|
478
|
-
wait_for_finalization: bool = False,
|
479
|
-
prompt: bool = False,
|
480
|
-
) -> bool:
|
481
|
-
"""Removes stake into the wallet coldkey from the specified hotkey ``uid``.
|
482
|
-
|
483
|
-
:param subtensor: the initialized SubtensorInterface object to use
|
484
|
-
:param wallet: Bittensor wallet object.
|
485
|
-
:param hotkey_ss58: The `ss58` address of the hotkey to unstake from. By default, the wallet hotkey is used.
|
486
|
-
:param amount: Amount to stake as Bittensor balance, or `None` is unstaking all
|
487
|
-
:param wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning `True`, or returns
|
488
|
-
`False` if the extrinsic fails to enter the block within the timeout.
|
489
|
-
:param wait_for_finalization: If set, waits for the extrinsic to be finalized on the chain before returning `True`,
|
490
|
-
or returns `False` if the extrinsic fails to be finalized within the timeout.
|
491
|
-
:param prompt: If `True`, the call waits for confirmation from the user before proceeding.
|
492
|
-
|
493
|
-
:return: success: `True` if extrinsic was finalized or included in the block. If we did not wait for
|
494
|
-
finalization/inclusion, the response is `True`.
|
495
|
-
"""
|
496
|
-
# Decrypt keys,
|
497
|
-
try:
|
498
|
-
wallet.unlock_coldkey()
|
499
|
-
except KeyFileError:
|
500
|
-
err_console.print("Error decrypting coldkey (possibly incorrect password)")
|
501
|
-
return False
|
502
|
-
|
503
|
-
if hotkey_ss58 is None:
|
504
|
-
hotkey_ss58 = wallet.hotkey.ss58_address # Default to wallet's own hotkey.
|
505
|
-
|
506
|
-
with console.status(
|
507
|
-
f":satellite: Syncing with chain: [white]{subtensor}[/white] ...",
|
508
|
-
spinner="aesthetic",
|
509
|
-
) as status:
|
510
|
-
print_verbose("Fetching balance and stake", status)
|
511
|
-
block_hash = await subtensor.substrate.get_chain_head()
|
512
|
-
old_balance, old_stake, hotkey_owner = await asyncio.gather(
|
513
|
-
subtensor.get_balance(
|
514
|
-
wallet.coldkeypub.ss58_address, block_hash=block_hash
|
515
|
-
),
|
516
|
-
subtensor.get_stake_for_coldkey_and_hotkey(
|
517
|
-
coldkey_ss58=wallet.coldkeypub.ss58_address,
|
518
|
-
hotkey_ss58=hotkey_ss58,
|
519
|
-
block_hash=block_hash,
|
520
|
-
),
|
521
|
-
subtensor.get_hotkey_owner(hotkey_ss58, block_hash),
|
522
|
-
)
|
523
|
-
|
524
|
-
own_hotkey: bool = wallet.coldkeypub.ss58_address == hotkey_owner
|
525
|
-
|
526
|
-
# Convert to bittensor.Balance
|
527
|
-
if amount is None:
|
528
|
-
# Unstake it all.
|
529
|
-
unstaking_balance = old_stake
|
530
|
-
else:
|
531
|
-
unstaking_balance = Balance.from_tao(amount)
|
532
|
-
|
533
|
-
# Check enough to unstake.
|
534
|
-
stake_on_uid = old_stake
|
535
|
-
if unstaking_balance > stake_on_uid:
|
536
|
-
err_console.print(
|
537
|
-
f":cross_mark: [red]Not enough stake[/red]: "
|
538
|
-
f"[green]{stake_on_uid}[/green] to unstake: "
|
539
|
-
f"[blue]{unstaking_balance}[/blue] from hotkey:"
|
540
|
-
f" [white]{wallet.hotkey_str}[/white]"
|
541
|
-
)
|
542
|
-
return False
|
543
|
-
|
544
|
-
print_verbose("Fetching threshold amount")
|
545
|
-
# If nomination stake, check threshold.
|
546
|
-
if not own_hotkey and not await _check_threshold_amount(
|
547
|
-
subtensor=subtensor,
|
548
|
-
sb=(stake_on_uid - unstaking_balance),
|
549
|
-
block_hash=block_hash,
|
550
|
-
):
|
551
|
-
console.print(
|
552
|
-
":warning: [yellow]This action will unstake the entire staked balance![/yellow]"
|
553
|
-
)
|
554
|
-
unstaking_balance = stake_on_uid
|
555
|
-
|
556
|
-
# Ask before moving on.
|
557
|
-
if prompt:
|
558
|
-
if not Confirm.ask(
|
559
|
-
f"Do you want to unstake:\n"
|
560
|
-
f"[bold white]\tamount: {unstaking_balance}\n"
|
561
|
-
f"\thotkey: {wallet.hotkey_str}[/bold white ]?"
|
562
|
-
):
|
563
|
-
return False
|
564
|
-
|
565
|
-
with console.status(
|
566
|
-
f":satellite: Unstaking from chain: [white]{subtensor}[/white] ...",
|
567
|
-
spinner="earth",
|
568
|
-
):
|
569
|
-
call = await subtensor.substrate.compose_call(
|
570
|
-
call_module="SubtensorModule",
|
571
|
-
call_function="remove_stake",
|
572
|
-
call_params={
|
573
|
-
"hotkey": hotkey_ss58,
|
574
|
-
"amount_unstaked": unstaking_balance.rao,
|
575
|
-
},
|
576
|
-
)
|
577
|
-
staking_response, err_msg = await subtensor.sign_and_send_extrinsic(
|
578
|
-
call, wallet, wait_for_inclusion, wait_for_finalization
|
579
|
-
)
|
580
|
-
|
581
|
-
if staking_response is True: # If we successfully unstaked.
|
582
|
-
# We only wait here if we expect finalization.
|
583
|
-
if not wait_for_finalization and not wait_for_inclusion:
|
584
|
-
return True
|
585
|
-
|
586
|
-
console.print(":white_heavy_check_mark: [green]Finalized[/green]")
|
587
|
-
with console.status(
|
588
|
-
f":satellite: Checking Balance on: [white]{subtensor}[/white] ..."
|
589
|
-
):
|
590
|
-
new_block_hash = await subtensor.substrate.get_chain_head()
|
591
|
-
new_balance, new_stake = await asyncio.gather(
|
592
|
-
subtensor.get_balance(
|
593
|
-
wallet.coldkeypub.ss58_address, block_hash=new_block_hash
|
594
|
-
),
|
595
|
-
subtensor.get_stake_for_coldkey_and_hotkey(
|
596
|
-
hotkey_ss58, wallet.coldkeypub.ss58_address, new_block_hash
|
597
|
-
),
|
598
|
-
)
|
599
|
-
console.print(
|
600
|
-
f"Balance:\n"
|
601
|
-
f" [blue]{old_balance[wallet.coldkeypub.ss58_address]}[/blue] :arrow_right:"
|
602
|
-
f" [green]{new_balance[wallet.coldkeypub.ss58_address]}[/green]"
|
603
|
-
)
|
604
|
-
console.print(
|
605
|
-
f"Stake:\n [blue]{old_stake}[/blue] :arrow_right: [green]{new_stake}[/green]"
|
606
|
-
)
|
607
|
-
return True
|
608
|
-
else:
|
609
|
-
err_console.print(f":cross_mark: [red]Failed[/red]: {err_msg}")
|
610
|
-
return False
|
611
|
-
|
612
|
-
|
613
|
-
async def unstake_multiple_extrinsic(
|
614
|
-
subtensor: "SubtensorInterface",
|
615
|
-
wallet: Wallet,
|
616
|
-
hotkey_ss58s: list[str],
|
617
|
-
amounts: Optional[list[Union[Balance, float]]] = None,
|
618
|
-
wait_for_inclusion: bool = True,
|
619
|
-
wait_for_finalization: bool = False,
|
620
|
-
prompt: bool = False,
|
621
|
-
) -> bool:
|
622
|
-
"""
|
623
|
-
Removes stake from each `hotkey_ss58` in the list, using each amount, to a common coldkey.
|
624
|
-
|
625
|
-
:param subtensor: the initialized SubtensorInterface object to use
|
626
|
-
:param wallet: The wallet with the coldkey to unstake to.
|
627
|
-
:param hotkey_ss58s: List of hotkeys to unstake from.
|
628
|
-
:param amounts: List of amounts to unstake. If ``None``, unstake all.
|
629
|
-
:param wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning `True`, or returns
|
630
|
-
`False` if the extrinsic fails to enter the block within the timeout.
|
631
|
-
:param wait_for_finalization: If set, waits for the extrinsic to be finalized on the chain before returning `True`,
|
632
|
-
or returns `False` if the extrinsic fails to be finalized within the timeout.
|
633
|
-
:param prompt: If `True`, the call waits for confirmation from the user before proceeding.
|
634
|
-
|
635
|
-
:return: success: `True` if extrinsic was finalized or included in the block. Flag is `True` if any wallet was
|
636
|
-
unstaked. If we did not wait for finalization/inclusion, the response is `True`.
|
637
|
-
"""
|
638
|
-
if not isinstance(hotkey_ss58s, list) or not all(
|
639
|
-
isinstance(hotkey_ss58, str) for hotkey_ss58 in hotkey_ss58s
|
640
|
-
):
|
641
|
-
raise TypeError("hotkey_ss58s must be a list of str")
|
642
|
-
|
643
|
-
if len(hotkey_ss58s) == 0:
|
644
|
-
return True
|
645
|
-
|
646
|
-
if amounts is not None and len(amounts) != len(hotkey_ss58s):
|
647
|
-
raise ValueError("amounts must be a list of the same length as hotkey_ss58s")
|
648
|
-
|
649
|
-
if amounts is not None and not all(
|
650
|
-
isinstance(amount, (Balance, float)) for amount in amounts
|
651
|
-
):
|
652
|
-
raise TypeError(
|
653
|
-
"amounts must be a [list of bittensor.Balance or float] or None"
|
654
|
-
)
|
655
|
-
|
656
|
-
new_amounts: Sequence[Optional[Balance]]
|
657
|
-
if amounts is None:
|
658
|
-
new_amounts = [None] * len(hotkey_ss58s)
|
659
|
-
else:
|
660
|
-
new_amounts = [
|
661
|
-
Balance(amount) if not isinstance(amount, Balance) else amount
|
662
|
-
for amount in (amounts or [None] * len(hotkey_ss58s))
|
663
|
-
]
|
664
|
-
if sum(amount.tao for amount in new_amounts if amount is not None) == 0:
|
665
|
-
return True
|
666
|
-
|
667
|
-
# Unlock coldkey.
|
668
|
-
try:
|
669
|
-
wallet.unlock_coldkey()
|
670
|
-
except KeyFileError:
|
671
|
-
err_console.print("Error decrypting coldkey (possibly incorrect password)")
|
672
|
-
return False
|
673
|
-
|
674
|
-
with console.status(
|
675
|
-
f":satellite: Syncing with chain: [white]{subtensor}[/white] ..."
|
676
|
-
):
|
677
|
-
block_hash = await subtensor.substrate.get_chain_head()
|
678
|
-
|
679
|
-
old_balance_ = subtensor.get_balance(
|
680
|
-
wallet.coldkeypub.ss58_address, block_hash=block_hash
|
681
|
-
)
|
682
|
-
old_stakes_ = asyncio.gather(
|
683
|
-
*[
|
684
|
-
subtensor.get_stake_for_coldkey_and_hotkey(
|
685
|
-
h, wallet.coldkeypub.ss58_address, block_hash
|
686
|
-
)
|
687
|
-
for h in hotkey_ss58s
|
688
|
-
]
|
689
|
-
)
|
690
|
-
hotkey_owners_ = asyncio.gather(
|
691
|
-
*[subtensor.get_hotkey_owner(h, block_hash) for h in hotkey_ss58s]
|
692
|
-
)
|
693
|
-
|
694
|
-
old_balance, old_stakes, hotkey_owners, threshold = await asyncio.gather(
|
695
|
-
old_balance_,
|
696
|
-
old_stakes_,
|
697
|
-
hotkey_owners_,
|
698
|
-
_get_threshold_amount(subtensor, block_hash),
|
699
|
-
)
|
700
|
-
own_hotkeys = [
|
701
|
-
wallet.coldkeypub.ss58_address == hotkey_owner
|
702
|
-
for hotkey_owner in hotkey_owners
|
703
|
-
]
|
704
|
-
|
705
|
-
successful_unstakes = 0
|
706
|
-
for idx, (hotkey_ss58, amount, old_stake, own_hotkey) in enumerate(
|
707
|
-
zip(hotkey_ss58s, new_amounts, old_stakes, own_hotkeys)
|
708
|
-
):
|
709
|
-
# Covert to bittensor.Balance
|
710
|
-
if amount is None:
|
711
|
-
# Unstake it all.
|
712
|
-
unstaking_balance = old_stake
|
713
|
-
else:
|
714
|
-
unstaking_balance = amount
|
715
|
-
|
716
|
-
# Check enough to unstake.
|
717
|
-
stake_on_uid = old_stake
|
718
|
-
if unstaking_balance > stake_on_uid:
|
719
|
-
err_console.print(
|
720
|
-
f":cross_mark: [red]Not enough stake[/red]:"
|
721
|
-
f" [green]{stake_on_uid}[/green] to unstake:"
|
722
|
-
f" [blue]{unstaking_balance}[/blue] from hotkey:"
|
723
|
-
f" [white]{wallet.hotkey_str}[/white]"
|
724
|
-
)
|
725
|
-
continue
|
726
|
-
|
727
|
-
# If nomination stake, check threshold.
|
728
|
-
if (
|
729
|
-
not own_hotkey
|
730
|
-
and (
|
731
|
-
await _check_threshold_amount(
|
732
|
-
subtensor=subtensor,
|
733
|
-
sb=(stake_on_uid - unstaking_balance),
|
734
|
-
block_hash=block_hash,
|
735
|
-
min_req_stake=threshold,
|
736
|
-
)
|
737
|
-
)[0]
|
738
|
-
is False
|
739
|
-
):
|
740
|
-
console.print(
|
741
|
-
":warning: [yellow]This action will unstake the entire staked balance![/yellow]"
|
742
|
-
)
|
743
|
-
unstaking_balance = stake_on_uid
|
744
|
-
|
745
|
-
# Ask before moving on.
|
746
|
-
if prompt:
|
747
|
-
if not Confirm.ask(
|
748
|
-
f"Do you want to unstake:\n"
|
749
|
-
f"[bold white]\tamount: {unstaking_balance}\n"
|
750
|
-
f"ss58: {hotkey_ss58}[/bold white ]?"
|
751
|
-
):
|
752
|
-
continue
|
753
|
-
|
754
|
-
with console.status(
|
755
|
-
f":satellite: Unstaking from chain: [white]{subtensor}[/white] ..."
|
756
|
-
):
|
757
|
-
call = await subtensor.substrate.compose_call(
|
758
|
-
call_module="SubtensorModule",
|
759
|
-
call_function="remove_stake",
|
760
|
-
call_params={
|
761
|
-
"hotkey": hotkey_ss58,
|
762
|
-
"amount_unstaked": unstaking_balance.rao,
|
763
|
-
},
|
764
|
-
)
|
765
|
-
staking_response, err_msg = await subtensor.sign_and_send_extrinsic(
|
766
|
-
call, wallet, wait_for_inclusion, wait_for_finalization
|
767
|
-
)
|
768
|
-
|
769
|
-
if staking_response is True: # If we successfully unstaked.
|
770
|
-
# We only wait here if we expect finalization.
|
771
|
-
|
772
|
-
if idx < len(hotkey_ss58s) - 1:
|
773
|
-
# Wait for tx rate limit.
|
774
|
-
tx_query = await subtensor.substrate.query(
|
775
|
-
module="SubtensorModule",
|
776
|
-
storage_function="TxRateLimit",
|
777
|
-
block_hash=block_hash,
|
778
|
-
)
|
779
|
-
tx_rate_limit_blocks: int = tx_query
|
780
|
-
|
781
|
-
# TODO: Handle in-case we have fast blocks
|
782
|
-
if tx_rate_limit_blocks > 0:
|
783
|
-
console.print(
|
784
|
-
":hourglass: [yellow]Waiting for tx rate limit:"
|
785
|
-
f" [white]{tx_rate_limit_blocks}[/white] blocks,"
|
786
|
-
f" estimated time: [white]{tx_rate_limit_blocks * 12} [/white] seconds[/yellow]"
|
787
|
-
)
|
788
|
-
await asyncio.sleep(
|
789
|
-
tx_rate_limit_blocks * 12
|
790
|
-
) # 12 seconds per block
|
791
|
-
|
792
|
-
if not wait_for_finalization and not wait_for_inclusion:
|
793
|
-
successful_unstakes += 1
|
794
|
-
continue
|
795
|
-
|
796
|
-
console.print(":white_heavy_check_mark: [green]Finalized[/green]")
|
797
|
-
with console.status(
|
798
|
-
f":satellite: Checking stake balance on: [white]{subtensor}[/white] ..."
|
799
|
-
):
|
800
|
-
new_stake = await subtensor.get_stake_for_coldkey_and_hotkey(
|
801
|
-
coldkey_ss58=wallet.coldkeypub.ss58_address,
|
802
|
-
hotkey_ss58=hotkey_ss58,
|
803
|
-
block_hash=(await subtensor.substrate.get_chain_head()),
|
804
|
-
)
|
805
|
-
console.print(
|
806
|
-
"Stake ({}): [blue]{}[/blue] :arrow_right: [green]{}[/green]".format(
|
807
|
-
hotkey_ss58, stake_on_uid, new_stake
|
808
|
-
)
|
809
|
-
)
|
810
|
-
successful_unstakes += 1
|
811
|
-
else:
|
812
|
-
err_console.print(":cross_mark: [red]Failed[/red]: Unknown Error.")
|
813
|
-
continue
|
814
|
-
|
815
|
-
if successful_unstakes != 0:
|
816
|
-
with console.status(
|
817
|
-
f":satellite: Checking balance on: ([white]{subtensor}[/white] ..."
|
818
|
-
):
|
819
|
-
new_balance = await subtensor.get_balance(wallet.coldkeypub.ss58_address)
|
820
|
-
console.print(
|
821
|
-
f"Balance: [blue]{old_balance[wallet.coldkeypub.ss58_address]}[/blue]"
|
822
|
-
f" :arrow_right: [green]{new_balance[wallet.coldkeypub.ss58_address]}[/green]"
|
823
|
-
)
|
824
|
-
return True
|
825
|
-
|
826
|
-
return False
|
827
|
-
|
828
|
-
|
829
|
-
# Commands
|
830
38
|
async def stake_add(
|
831
39
|
wallet: Wallet,
|
832
40
|
subtensor: "SubtensorInterface",
|
@@ -931,8 +139,8 @@ async def stake_add(
|
|
931
139
|
|
932
140
|
starting_chain_head = await subtensor.substrate.get_chain_head()
|
933
141
|
_all_dynamic_info, stake_info_dict = await asyncio.gather(
|
934
|
-
subtensor.
|
935
|
-
subtensor.
|
142
|
+
subtensor.all_subnets(),
|
143
|
+
subtensor.get_stake_for_coldkeys(
|
936
144
|
coldkey_ss58_list=[wallet.coldkeypub.ss58_address],
|
937
145
|
block_hash=starting_chain_head,
|
938
146
|
),
|
@@ -998,15 +206,10 @@ async def stake_add(
|
|
998
206
|
remaining_wallet_balance -= amount_to_stake_as_balance
|
999
207
|
|
1000
208
|
# Slippage warning
|
1001
|
-
received_amount,
|
1002
|
-
amount_to_stake_as_balance
|
209
|
+
received_amount, _, slippage_pct_float = (
|
210
|
+
dynamic_info.tao_to_alpha_with_slippage(amount_to_stake_as_balance)
|
1003
211
|
)
|
1004
212
|
if dynamic_info.is_dynamic:
|
1005
|
-
slippage_pct_float = (
|
1006
|
-
100 * float(slippage) / float(slippage + received_amount)
|
1007
|
-
if slippage + received_amount != 0
|
1008
|
-
else 0
|
1009
|
-
)
|
1010
213
|
slippage_pct = f"{slippage_pct_float:.4f} %"
|
1011
214
|
rate = str(1 / (float(dynamic_info.price) or 1))
|
1012
215
|
else:
|
@@ -1114,7 +317,7 @@ The columns are as follows:
|
|
1114
317
|
else:
|
1115
318
|
new_balance_, stake_info_dict = await asyncio.gather(
|
1116
319
|
subtensor.get_balance(wallet.coldkeypub.ss58_address),
|
1117
|
-
subtensor.
|
320
|
+
subtensor.get_stake_for_coldkeys(
|
1118
321
|
coldkey_ss58_list=[wallet.coldkeypub.ss58_address],
|
1119
322
|
),
|
1120
323
|
)
|
@@ -1179,7 +382,7 @@ async def unstake_selection(
|
|
1179
382
|
old_identities,
|
1180
383
|
netuid: Optional[int] = None,
|
1181
384
|
):
|
1182
|
-
stake_infos = await subtensor.
|
385
|
+
stake_infos = await subtensor.get_stake_for_coldkey(
|
1183
386
|
coldkey_ss58=wallet.coldkeypub.ss58_address
|
1184
387
|
)
|
1185
388
|
|
@@ -1407,10 +610,10 @@ async def _unstake_all(
|
|
1407
610
|
all_sn_dynamic_info_,
|
1408
611
|
current_wallet_balance,
|
1409
612
|
) = await asyncio.gather(
|
1410
|
-
subtensor.
|
613
|
+
subtensor.get_stake_for_coldkey(wallet.coldkeypub.ss58_address),
|
1411
614
|
subtensor.fetch_coldkey_hotkey_identities(),
|
1412
615
|
subtensor.get_delegate_identities(),
|
1413
|
-
subtensor.
|
616
|
+
subtensor.all_subnets(),
|
1414
617
|
subtensor.get_balance(wallet.coldkeypub.ss58_address),
|
1415
618
|
)
|
1416
619
|
|
@@ -1472,8 +675,8 @@ async def _unstake_all(
|
|
1472
675
|
|
1473
676
|
dynamic_info = all_sn_dynamic_info.get(stake.netuid)
|
1474
677
|
stake_amount = stake.stake
|
1475
|
-
received_amount,
|
1476
|
-
stake_amount
|
678
|
+
received_amount, _, slippage_pct_float = (
|
679
|
+
dynamic_info.alpha_to_tao_with_slippage(stake_amount)
|
1477
680
|
)
|
1478
681
|
|
1479
682
|
total_received_value += received_amount
|
@@ -1491,11 +694,6 @@ async def _unstake_all(
|
|
1491
694
|
hotkey_display = stake.hotkey_ss58
|
1492
695
|
|
1493
696
|
if dynamic_info.is_dynamic:
|
1494
|
-
slippage_pct_float = (
|
1495
|
-
100 * float(slippage) / float(slippage + received_amount)
|
1496
|
-
if slippage + received_amount != 0
|
1497
|
-
else 0
|
1498
|
-
)
|
1499
697
|
slippage_pct = f"{slippage_pct_float:.4f} %"
|
1500
698
|
else:
|
1501
699
|
slippage_pct_float = 0
|
@@ -1600,7 +798,7 @@ async def unstake(
|
|
1600
798
|
spinner="earth",
|
1601
799
|
):
|
1602
800
|
all_sn_dynamic_info_, ck_hk_identities, old_identities = await asyncio.gather(
|
1603
|
-
subtensor.
|
801
|
+
subtensor.all_subnets(),
|
1604
802
|
subtensor.fetch_coldkey_hotkey_identities(),
|
1605
803
|
subtensor.get_delegate_identities(),
|
1606
804
|
)
|
@@ -1684,7 +882,7 @@ async def unstake(
|
|
1684
882
|
|
1685
883
|
# Fetch stake balances
|
1686
884
|
chain_head = await subtensor.substrate.get_chain_head()
|
1687
|
-
stake_info_dict = await subtensor.
|
885
|
+
stake_info_dict = await subtensor.get_stake_for_coldkeys(
|
1688
886
|
coldkey_ss58_list=[wallet.coldkeypub.ss58_address],
|
1689
887
|
block_hash=chain_head,
|
1690
888
|
)
|
@@ -1765,16 +963,11 @@ async def unstake(
|
|
1765
963
|
)
|
1766
964
|
continue # Skip to the next subnet - useful when single amount is specified for all subnets
|
1767
965
|
|
1768
|
-
received_amount,
|
1769
|
-
amount_to_unstake_as_balance
|
966
|
+
received_amount, _, slippage_pct_float = (
|
967
|
+
dynamic_info.alpha_to_tao_with_slippage(amount_to_unstake_as_balance)
|
1770
968
|
)
|
1771
969
|
total_received_amount += received_amount
|
1772
970
|
if dynamic_info.is_dynamic:
|
1773
|
-
slippage_pct_float = (
|
1774
|
-
100 * float(slippage) / float(slippage + received_amount)
|
1775
|
-
if slippage + received_amount != 0
|
1776
|
-
else 0
|
1777
|
-
)
|
1778
971
|
slippage_pct = f"{slippage_pct_float:.4f} %"
|
1779
972
|
else:
|
1780
973
|
slippage_pct_float = 0
|
@@ -1961,7 +1154,7 @@ The columns are as follows:
|
|
1961
1154
|
wallet.coldkeypub.ss58_address
|
1962
1155
|
)
|
1963
1156
|
new_balance = new_balance_[wallet.coldkeypub.ss58_address]
|
1964
|
-
new_stake_info = await subtensor.
|
1157
|
+
new_stake_info = await subtensor.get_stake_for_coldkeys(
|
1965
1158
|
coldkey_ss58_list=[wallet.coldkeypub.ss58_address],
|
1966
1159
|
)
|
1967
1160
|
new_stake = Balance.from_rao(0)
|
@@ -2001,11 +1194,11 @@ async def stake_list(
|
|
2001
1194
|
registered_delegate_info,
|
2002
1195
|
_dynamic_info,
|
2003
1196
|
) = await asyncio.gather(
|
2004
|
-
subtensor.
|
1197
|
+
subtensor.get_stake_for_coldkeys(
|
2005
1198
|
coldkey_ss58_list=[coldkey_address], block_hash=block_hash
|
2006
1199
|
),
|
2007
1200
|
subtensor.get_delegate_identities(block_hash=block_hash),
|
2008
|
-
subtensor.
|
1201
|
+
subtensor.all_subnets(),
|
2009
1202
|
)
|
2010
1203
|
sub_stakes = substakes[coldkey_address]
|
2011
1204
|
dynamic_info = {info.netuid: info for info in _dynamic_info}
|
@@ -2131,18 +1324,13 @@ async def stake_list(
|
|
2131
1324
|
total_tao_value += tao_value
|
2132
1325
|
|
2133
1326
|
# Swapped TAO value and slippage cell
|
2134
|
-
swapped_tao_value,
|
2135
|
-
substake_.stake
|
1327
|
+
swapped_tao_value, _, slippage_percentage_ = (
|
1328
|
+
pool.alpha_to_tao_with_slippage(substake_.stake)
|
2136
1329
|
)
|
2137
1330
|
total_swapped_tao_value += swapped_tao_value
|
2138
1331
|
|
2139
1332
|
# Slippage percentage cell
|
2140
1333
|
if pool.is_dynamic:
|
2141
|
-
slippage_percentage_ = (
|
2142
|
-
100 * float(slippage) / float(slippage + swapped_tao_value)
|
2143
|
-
if slippage + swapped_tao_value != 0
|
2144
|
-
else 0
|
2145
|
-
)
|
2146
1334
|
slippage_percentage = f"[{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}]{slippage_percentage_:.3f}%[/{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}]"
|
2147
1335
|
else:
|
2148
1336
|
slippage_percentage = f"[{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}]0.000%[/{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}]"
|
@@ -2648,305 +1836,3 @@ async def stake_list(
|
|
2648
1836
|
for field_name, description in fields:
|
2649
1837
|
description_table.add_row(field_name, description)
|
2650
1838
|
console.print(description_table)
|
2651
|
-
|
2652
|
-
|
2653
|
-
async def move_stake(
|
2654
|
-
subtensor: "SubtensorInterface",
|
2655
|
-
wallet: Wallet,
|
2656
|
-
origin_netuid: int,
|
2657
|
-
destination_netuid: int,
|
2658
|
-
destination_hotkey: str,
|
2659
|
-
amount: float,
|
2660
|
-
stake_all: bool,
|
2661
|
-
prompt: bool = True,
|
2662
|
-
):
|
2663
|
-
origin_hotkey_ss58 = wallet.hotkey.ss58_address
|
2664
|
-
# Get the wallet stake balances.
|
2665
|
-
origin_stake_balance = Balance.from_rao(0)
|
2666
|
-
destination_stake_balance = Balance.from_rao(0)
|
2667
|
-
|
2668
|
-
chain_head = await subtensor.substrate.get_chain_head()
|
2669
|
-
stake_info_dict = await subtensor.get_stake_info_for_coldkeys(
|
2670
|
-
coldkey_ss58_list=[wallet.coldkeypub.ss58_address],
|
2671
|
-
block_hash=chain_head,
|
2672
|
-
)
|
2673
|
-
|
2674
|
-
for stake_info in stake_info_dict[wallet.coldkeypub.ss58_address]:
|
2675
|
-
if (
|
2676
|
-
stake_info.hotkey_ss58 == origin_hotkey_ss58
|
2677
|
-
and stake_info.netuid == origin_netuid
|
2678
|
-
):
|
2679
|
-
origin_stake_balance = stake_info.stake
|
2680
|
-
elif (
|
2681
|
-
stake_info.hotkey_ss58 == destination_hotkey
|
2682
|
-
and stake_info.netuid == destination_netuid
|
2683
|
-
):
|
2684
|
-
destination_stake_balance = stake_info.stake
|
2685
|
-
|
2686
|
-
# Set appropriate units
|
2687
|
-
origin_stake_balance = origin_stake_balance.set_unit(origin_netuid)
|
2688
|
-
destination_stake_balance = destination_stake_balance.set_unit(destination_netuid)
|
2689
|
-
|
2690
|
-
if origin_stake_balance == Balance.from_tao(0).set_unit(origin_netuid):
|
2691
|
-
print_error(
|
2692
|
-
f"Your balance is [{COLOR_PALETTE['POOLS']['TAO']}]0[/{COLOR_PALETTE['POOLS']['TAO']}] in Netuid: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{origin_netuid}"
|
2693
|
-
)
|
2694
|
-
raise typer.Exit()
|
2695
|
-
|
2696
|
-
console.print(
|
2697
|
-
f"\nOrigin Netuid: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{origin_netuid}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}], Origin stake: [{COLOR_PALETTE['POOLS']['TAO']}]{origin_stake_balance}"
|
2698
|
-
)
|
2699
|
-
console.print(
|
2700
|
-
f"Destination netuid: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{destination_netuid}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}], Destination stake: [{COLOR_PALETTE['POOLS']['TAO']}]{destination_stake_balance}\n"
|
2701
|
-
)
|
2702
|
-
|
2703
|
-
# Determine the amount we are moving.
|
2704
|
-
amount_to_move_as_balance = None
|
2705
|
-
if amount:
|
2706
|
-
amount_to_move_as_balance = Balance.from_tao(amount)
|
2707
|
-
elif stake_all:
|
2708
|
-
amount_to_move_as_balance = origin_stake_balance
|
2709
|
-
else: # max_stake
|
2710
|
-
# TODO improve this
|
2711
|
-
if Confirm.ask(
|
2712
|
-
f"Move all: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{origin_stake_balance}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]?"
|
2713
|
-
):
|
2714
|
-
amount_to_move_as_balance = origin_stake_balance
|
2715
|
-
else:
|
2716
|
-
try:
|
2717
|
-
amount = float(
|
2718
|
-
Prompt.ask(
|
2719
|
-
f"Enter amount to move in [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{Balance.get_unit(origin_netuid)}"
|
2720
|
-
)
|
2721
|
-
)
|
2722
|
-
amount_to_move_as_balance = Balance.from_tao(amount)
|
2723
|
-
except ValueError:
|
2724
|
-
print_error(f":cross_mark: Invalid amount: {amount}")
|
2725
|
-
return False
|
2726
|
-
|
2727
|
-
# Check enough to move.
|
2728
|
-
amount_to_move_as_balance.set_unit(origin_netuid)
|
2729
|
-
if amount_to_move_as_balance > origin_stake_balance:
|
2730
|
-
err_console.print(
|
2731
|
-
f"[red]Not enough stake[/red]:\n Stake balance: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{origin_stake_balance}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}] < Moving amount: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{amount_to_move_as_balance}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]"
|
2732
|
-
)
|
2733
|
-
return False
|
2734
|
-
|
2735
|
-
# Slippage warning
|
2736
|
-
if prompt:
|
2737
|
-
if origin_netuid == destination_netuid:
|
2738
|
-
received_amount_destination = amount_to_move_as_balance
|
2739
|
-
slippage_pct_float = 0
|
2740
|
-
slippage_pct = f"{slippage_pct_float}%"
|
2741
|
-
price = Balance.from_tao(1).set_unit(origin_netuid)
|
2742
|
-
price_str = (
|
2743
|
-
str(float(price.tao))
|
2744
|
-
+ f"{Balance.get_unit(origin_netuid)}/{Balance.get_unit(origin_netuid)}"
|
2745
|
-
)
|
2746
|
-
else:
|
2747
|
-
dynamic_origin, dynamic_destination = await asyncio.gather(
|
2748
|
-
subtensor.get_subnet_dynamic_info(origin_netuid),
|
2749
|
-
subtensor.get_subnet_dynamic_info(destination_netuid),
|
2750
|
-
)
|
2751
|
-
price = (
|
2752
|
-
float(dynamic_origin.price)
|
2753
|
-
* 1
|
2754
|
-
/ (float(dynamic_destination.price) or 1)
|
2755
|
-
)
|
2756
|
-
received_amount_tao, slippage = dynamic_origin.alpha_to_tao_with_slippage(
|
2757
|
-
amount_to_move_as_balance
|
2758
|
-
)
|
2759
|
-
received_amount_destination, slippage = (
|
2760
|
-
dynamic_destination.tao_to_alpha_with_slippage(received_amount_tao)
|
2761
|
-
)
|
2762
|
-
received_amount_destination.set_unit(destination_netuid)
|
2763
|
-
slippage_pct_float = (
|
2764
|
-
100 * float(slippage) / float(slippage + received_amount_destination)
|
2765
|
-
if slippage + received_amount_destination != 0
|
2766
|
-
else 0
|
2767
|
-
)
|
2768
|
-
slippage_pct = f"{slippage_pct_float:.4f} %"
|
2769
|
-
price_str = (
|
2770
|
-
str(float(price))
|
2771
|
-
+ f"{Balance.get_unit(destination_netuid)}/{Balance.get_unit(origin_netuid)}"
|
2772
|
-
)
|
2773
|
-
|
2774
|
-
table = Table(
|
2775
|
-
title=f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Moving stake from: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{Balance.get_unit(origin_netuid)}(Netuid: {origin_netuid})[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] to: [{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{Balance.get_unit(destination_netuid)}(Netuid: {destination_netuid})[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}]\nNetwork: {subtensor.network}\n",
|
2776
|
-
show_footer=True,
|
2777
|
-
show_edge=False,
|
2778
|
-
header_style="bold white",
|
2779
|
-
border_style="bright_black",
|
2780
|
-
style="bold",
|
2781
|
-
title_justify="center",
|
2782
|
-
show_lines=False,
|
2783
|
-
pad_edge=True,
|
2784
|
-
)
|
2785
|
-
table.add_column(
|
2786
|
-
"origin netuid", justify="center", style=COLOR_PALETTE["GENERAL"]["SYMBOL"]
|
2787
|
-
)
|
2788
|
-
table.add_column(
|
2789
|
-
"origin hotkey", justify="center", style=COLOR_PALETTE["GENERAL"]["HOTKEY"]
|
2790
|
-
)
|
2791
|
-
table.add_column(
|
2792
|
-
"dest netuid", justify="center", style=COLOR_PALETTE["GENERAL"]["SYMBOL"]
|
2793
|
-
)
|
2794
|
-
table.add_column(
|
2795
|
-
"dest hotkey", justify="center", style=COLOR_PALETTE["GENERAL"]["HOTKEY"]
|
2796
|
-
)
|
2797
|
-
table.add_column(
|
2798
|
-
f"amount ({Balance.get_unit(origin_netuid)})",
|
2799
|
-
justify="center",
|
2800
|
-
style=COLOR_PALETTE["STAKE"]["TAO"],
|
2801
|
-
)
|
2802
|
-
table.add_column(
|
2803
|
-
f"rate ({Balance.get_unit(destination_netuid)}/{Balance.get_unit(origin_netuid)})",
|
2804
|
-
justify="center",
|
2805
|
-
style=COLOR_PALETTE["POOLS"]["RATE"],
|
2806
|
-
)
|
2807
|
-
table.add_column(
|
2808
|
-
f"received ({Balance.get_unit(destination_netuid)})",
|
2809
|
-
justify="center",
|
2810
|
-
style=COLOR_PALETTE["POOLS"]["TAO_EQUIV"],
|
2811
|
-
)
|
2812
|
-
table.add_column(
|
2813
|
-
"slippage",
|
2814
|
-
justify="center",
|
2815
|
-
style=COLOR_PALETTE["STAKE"]["SLIPPAGE_PERCENT"],
|
2816
|
-
)
|
2817
|
-
|
2818
|
-
table.add_row(
|
2819
|
-
f"{Balance.get_unit(origin_netuid)}({origin_netuid})",
|
2820
|
-
f"{origin_hotkey_ss58[:3]}...{origin_hotkey_ss58[-3:]}",
|
2821
|
-
# TODO f-strings
|
2822
|
-
Balance.get_unit(destination_netuid) + "(" + str(destination_netuid) + ")",
|
2823
|
-
f"{destination_hotkey[:3]}...{destination_hotkey[-3:]}",
|
2824
|
-
str(amount_to_move_as_balance),
|
2825
|
-
price_str,
|
2826
|
-
str(received_amount_destination.set_unit(destination_netuid)),
|
2827
|
-
str(slippage_pct),
|
2828
|
-
)
|
2829
|
-
|
2830
|
-
console.print(table)
|
2831
|
-
message = ""
|
2832
|
-
if slippage_pct_float > 5:
|
2833
|
-
message += f"[{COLOR_PALETTE['STAKE']['SLIPPAGE_TEXT']}]-------------------------------------------------------------------------------------------------------------------\n"
|
2834
|
-
message += f"[bold]WARNING:\tSlippage is high: [{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}]{slippage_pct}[/{COLOR_PALETTE['STAKE']['SLIPPAGE_PERCENT']}], this may result in a loss of funds.[/bold] \n"
|
2835
|
-
message += "-------------------------------------------------------------------------------------------------------------------\n"
|
2836
|
-
console.print(message)
|
2837
|
-
if not Confirm.ask("Would you like to continue?"):
|
2838
|
-
return True
|
2839
|
-
|
2840
|
-
# Perform staking operation.
|
2841
|
-
try:
|
2842
|
-
wallet.unlock_coldkey()
|
2843
|
-
except KeyFileError:
|
2844
|
-
err_console.print("Error decrypting coldkey (possibly incorrect password)")
|
2845
|
-
return False
|
2846
|
-
with console.status(
|
2847
|
-
f"\n:satellite: Moving {amount_to_move_as_balance} from {origin_hotkey_ss58} on netuid: {origin_netuid} to "
|
2848
|
-
f"{destination_hotkey} on netuid: {destination_netuid} ..."
|
2849
|
-
):
|
2850
|
-
call = await subtensor.substrate.compose_call(
|
2851
|
-
call_module="SubtensorModule",
|
2852
|
-
call_function="move_stake",
|
2853
|
-
call_params={
|
2854
|
-
"origin_hotkey": origin_hotkey_ss58,
|
2855
|
-
"origin_netuid": origin_netuid,
|
2856
|
-
"destination_hotkey": destination_hotkey,
|
2857
|
-
"destination_netuid": destination_netuid,
|
2858
|
-
"alpha_amount": amount_to_move_as_balance.rao,
|
2859
|
-
},
|
2860
|
-
)
|
2861
|
-
extrinsic = await subtensor.substrate.create_signed_extrinsic(
|
2862
|
-
call=call, keypair=wallet.coldkey
|
2863
|
-
)
|
2864
|
-
response = await subtensor.substrate.submit_extrinsic(
|
2865
|
-
extrinsic, wait_for_inclusion=True, wait_for_finalization=False
|
2866
|
-
)
|
2867
|
-
if not prompt:
|
2868
|
-
console.print(":white_heavy_check_mark: [green]Sent[/green]")
|
2869
|
-
return True
|
2870
|
-
else:
|
2871
|
-
await response.process_events()
|
2872
|
-
if not await response.is_success:
|
2873
|
-
err_console.print(
|
2874
|
-
f":cross_mark: [red]Failed[/red] with error:"
|
2875
|
-
f" {format_error_message(response.error_message, subtensor.substrate)}"
|
2876
|
-
)
|
2877
|
-
return
|
2878
|
-
else:
|
2879
|
-
new_stake_info_dict = await subtensor.get_stake_info_for_coldkeys(
|
2880
|
-
coldkey_ss58_list=[wallet.coldkeypub.ss58_address],
|
2881
|
-
)
|
2882
|
-
|
2883
|
-
new_origin_stake_balance = Balance.from_rao(0)
|
2884
|
-
new_destination_stake_balance = Balance.from_rao(0)
|
2885
|
-
|
2886
|
-
for stake_info in new_stake_info_dict[wallet.coldkeypub.ss58_address]:
|
2887
|
-
if (
|
2888
|
-
stake_info.hotkey_ss58 == origin_hotkey_ss58
|
2889
|
-
and stake_info.netuid == origin_netuid
|
2890
|
-
):
|
2891
|
-
new_origin_stake_balance = stake_info.stake.set_unit(
|
2892
|
-
origin_netuid
|
2893
|
-
)
|
2894
|
-
elif (
|
2895
|
-
stake_info.hotkey_ss58 == destination_hotkey
|
2896
|
-
and stake_info.netuid == destination_netuid
|
2897
|
-
):
|
2898
|
-
new_destination_stake_balance = stake_info.stake.set_unit(
|
2899
|
-
destination_netuid
|
2900
|
-
)
|
2901
|
-
|
2902
|
-
console.print(
|
2903
|
-
f"Origin Stake:\n [blue]{origin_stake_balance}[/blue] :arrow_right: "
|
2904
|
-
f"[{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_origin_stake_balance}"
|
2905
|
-
)
|
2906
|
-
console.print(
|
2907
|
-
f"Destination Stake:\n [blue]{destination_stake_balance}[/blue] :arrow_right: "
|
2908
|
-
f"[{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_destination_stake_balance}"
|
2909
|
-
)
|
2910
|
-
return
|
2911
|
-
|
2912
|
-
|
2913
|
-
async def fetch_coldkey_stake(subtensor: "SubtensorInterface", wallet: Wallet):
|
2914
|
-
sub_stakes = await subtensor.get_stake_info_for_coldkey(
|
2915
|
-
coldkey_ss58=wallet.coldkeypub.ss58_address
|
2916
|
-
)
|
2917
|
-
return sub_stakes
|
2918
|
-
|
2919
|
-
|
2920
|
-
# TODO: Use this in all subnet commands.
|
2921
|
-
async def get_stake_info_for_coldkey_and_hotkey(
|
2922
|
-
subtensor: "SubtensorInterface",
|
2923
|
-
coldkey_ss58: str,
|
2924
|
-
hotkey_ss58: Optional[str] = None,
|
2925
|
-
netuid: Optional[int] = None,
|
2926
|
-
block_hash: Optional[str] = None,
|
2927
|
-
) -> dict[tuple[str, int], Balance]:
|
2928
|
-
"""Helper function to get stake info for a coldkey and optionally filter by hotkey and netuid.
|
2929
|
-
|
2930
|
-
Args:
|
2931
|
-
subtensor: SubtensorInterface instance
|
2932
|
-
coldkey_ss58: Coldkey SS58 address
|
2933
|
-
hotkey_ss58: Optional hotkey SS58 address to filter by
|
2934
|
-
netuid: Optional netuid to filter by
|
2935
|
-
block_hash: Optional block hash to query at
|
2936
|
-
|
2937
|
-
Returns:
|
2938
|
-
Dictionary mapping (hotkey, netuid) tuple to stake balance
|
2939
|
-
"""
|
2940
|
-
stake_info_dict = await subtensor.get_stake_info_for_coldkeys(
|
2941
|
-
coldkey_ss58_list=[coldkey_ss58], block_hash=block_hash
|
2942
|
-
)
|
2943
|
-
|
2944
|
-
stakes = {}
|
2945
|
-
for stake_info in stake_info_dict[coldkey_ss58]:
|
2946
|
-
if hotkey_ss58 and stake_info.hotkey_ss58 != hotkey_ss58:
|
2947
|
-
continue
|
2948
|
-
if netuid is not None and stake_info.netuid != netuid:
|
2949
|
-
continue
|
2950
|
-
stakes[(stake_info.hotkey_ss58, stake_info.netuid)] = stake_info.stake
|
2951
|
-
|
2952
|
-
return stakes
|