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
@@ -0,0 +1,992 @@
|
|
1
|
+
import asyncio
|
2
|
+
|
3
|
+
from typing import TYPE_CHECKING
|
4
|
+
import typer
|
5
|
+
|
6
|
+
from bittensor_wallet import Wallet
|
7
|
+
from bittensor_wallet.errors import KeyFileError
|
8
|
+
from rich.table import Table
|
9
|
+
from rich.prompt import Confirm, Prompt
|
10
|
+
|
11
|
+
from bittensor_cli.src import COLOR_PALETTE
|
12
|
+
from bittensor_cli.src.bittensor.balances import Balance
|
13
|
+
from bittensor_cli.src.bittensor.utils import (
|
14
|
+
console,
|
15
|
+
err_console,
|
16
|
+
print_error,
|
17
|
+
format_error_message,
|
18
|
+
group_subnets,
|
19
|
+
get_subnet_name,
|
20
|
+
)
|
21
|
+
|
22
|
+
if TYPE_CHECKING:
|
23
|
+
from bittensor_cli.src.bittensor.subtensor_interface import SubtensorInterface
|
24
|
+
|
25
|
+
MIN_STAKE_FEE = Balance.from_rao(500_000)
|
26
|
+
|
27
|
+
|
28
|
+
# Helpers
|
29
|
+
async def display_stake_movement_cross_subnets(
|
30
|
+
subtensor: "SubtensorInterface",
|
31
|
+
origin_netuid: int,
|
32
|
+
destination_netuid: int,
|
33
|
+
origin_hotkey: str,
|
34
|
+
destination_hotkey: str,
|
35
|
+
amount_to_move: Balance,
|
36
|
+
) -> tuple[Balance, float, str, str]:
|
37
|
+
"""Calculate and display slippage information"""
|
38
|
+
|
39
|
+
if origin_netuid == destination_netuid:
|
40
|
+
subnet = await subtensor.subnet(origin_netuid)
|
41
|
+
received_amount_tao = subnet.alpha_to_tao(amount_to_move)
|
42
|
+
received_amount_tao -= MIN_STAKE_FEE
|
43
|
+
received_amount = subnet.tao_to_alpha(received_amount_tao)
|
44
|
+
slippage_pct_float = (
|
45
|
+
100 * float(MIN_STAKE_FEE) / float(MIN_STAKE_FEE + received_amount_tao)
|
46
|
+
if received_amount_tao != 0
|
47
|
+
else 0
|
48
|
+
)
|
49
|
+
slippage_pct = f"{slippage_pct_float:.4f}%"
|
50
|
+
price = Balance.from_tao(1).set_unit(origin_netuid)
|
51
|
+
price_str = (
|
52
|
+
str(float(price.tao))
|
53
|
+
+ f"{Balance.get_unit(origin_netuid)}/{Balance.get_unit(origin_netuid)}"
|
54
|
+
)
|
55
|
+
else:
|
56
|
+
dynamic_origin, dynamic_destination = await asyncio.gather(
|
57
|
+
subtensor.subnet(origin_netuid),
|
58
|
+
subtensor.subnet(destination_netuid),
|
59
|
+
)
|
60
|
+
price = (
|
61
|
+
float(dynamic_origin.price) * 1 / (float(dynamic_destination.price) or 1)
|
62
|
+
)
|
63
|
+
received_amount_tao, _, slippage_pct_float = (
|
64
|
+
dynamic_origin.alpha_to_tao_with_slippage(amount_to_move)
|
65
|
+
)
|
66
|
+
received_amount_tao -= MIN_STAKE_FEE
|
67
|
+
received_amount, _, slippage_pct_float = (
|
68
|
+
dynamic_destination.tao_to_alpha_with_slippage(received_amount_tao)
|
69
|
+
)
|
70
|
+
received_amount.set_unit(destination_netuid)
|
71
|
+
slippage_pct = f"{slippage_pct_float:.4f} %"
|
72
|
+
price_str = (
|
73
|
+
str(float(price))
|
74
|
+
+ f"{Balance.get_unit(destination_netuid)}/{Balance.get_unit(origin_netuid)}"
|
75
|
+
)
|
76
|
+
|
77
|
+
# Create and display table
|
78
|
+
table = Table(
|
79
|
+
title=(
|
80
|
+
f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]"
|
81
|
+
f"Moving stake from: "
|
82
|
+
f"[{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{Balance.get_unit(origin_netuid)}(Netuid: {origin_netuid})"
|
83
|
+
f"[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}] "
|
84
|
+
f"to: "
|
85
|
+
f"[{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{Balance.get_unit(destination_netuid)}(Netuid: {destination_netuid})"
|
86
|
+
f"[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}]\nNetwork: {subtensor.network}\n"
|
87
|
+
f"[/{COLOR_PALETTE['GENERAL']['HEADER']}]"
|
88
|
+
),
|
89
|
+
show_footer=True,
|
90
|
+
show_edge=False,
|
91
|
+
header_style="bold white",
|
92
|
+
border_style="bright_black",
|
93
|
+
style="bold",
|
94
|
+
title_justify="center",
|
95
|
+
show_lines=False,
|
96
|
+
pad_edge=True,
|
97
|
+
)
|
98
|
+
|
99
|
+
table.add_column(
|
100
|
+
"origin netuid", justify="center", style=COLOR_PALETTE["GENERAL"]["SYMBOL"]
|
101
|
+
)
|
102
|
+
table.add_column(
|
103
|
+
"origin hotkey", justify="center", style=COLOR_PALETTE["GENERAL"]["HOTKEY"]
|
104
|
+
)
|
105
|
+
table.add_column(
|
106
|
+
"dest netuid", justify="center", style=COLOR_PALETTE["GENERAL"]["SYMBOL"]
|
107
|
+
)
|
108
|
+
table.add_column(
|
109
|
+
"dest hotkey", justify="center", style=COLOR_PALETTE["GENERAL"]["HOTKEY"]
|
110
|
+
)
|
111
|
+
table.add_column(
|
112
|
+
f"amount ({Balance.get_unit(origin_netuid)})",
|
113
|
+
justify="center",
|
114
|
+
style=COLOR_PALETTE["STAKE"]["TAO"],
|
115
|
+
)
|
116
|
+
table.add_column(
|
117
|
+
f"rate ({Balance.get_unit(destination_netuid)}/{Balance.get_unit(origin_netuid)})",
|
118
|
+
justify="center",
|
119
|
+
style=COLOR_PALETTE["POOLS"]["RATE"],
|
120
|
+
)
|
121
|
+
table.add_column(
|
122
|
+
f"received ({Balance.get_unit(destination_netuid)})",
|
123
|
+
justify="center",
|
124
|
+
style=COLOR_PALETTE["POOLS"]["TAO_EQUIV"],
|
125
|
+
)
|
126
|
+
table.add_column(
|
127
|
+
"slippage",
|
128
|
+
justify="center",
|
129
|
+
style=COLOR_PALETTE["STAKE"]["SLIPPAGE_PERCENT"],
|
130
|
+
)
|
131
|
+
|
132
|
+
table.add_row(
|
133
|
+
f"{Balance.get_unit(origin_netuid)}({origin_netuid})",
|
134
|
+
f"{origin_hotkey[:3]}...{origin_hotkey[-3:]}",
|
135
|
+
f"{Balance.get_unit(destination_netuid)}({destination_netuid})",
|
136
|
+
f"{destination_hotkey[:3]}...{destination_hotkey[-3:]}",
|
137
|
+
str(amount_to_move),
|
138
|
+
price_str,
|
139
|
+
str(received_amount),
|
140
|
+
str(slippage_pct),
|
141
|
+
)
|
142
|
+
|
143
|
+
console.print(table)
|
144
|
+
# console.print(
|
145
|
+
# f"[dim]A fee of {MIN_STAKE_FEE} applies.[/dim]"
|
146
|
+
# )
|
147
|
+
|
148
|
+
# Display slippage warning if necessary
|
149
|
+
if slippage_pct_float > 5:
|
150
|
+
message = f"[{COLOR_PALETTE['STAKE']['SLIPPAGE_TEXT']}]-------------------------------------------------------------------------------------------------------------------\n"
|
151
|
+
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"
|
152
|
+
message += "-------------------------------------------------------------------------------------------------------------------\n"
|
153
|
+
console.print(message)
|
154
|
+
|
155
|
+
return received_amount, slippage_pct_float, slippage_pct, price_str
|
156
|
+
|
157
|
+
|
158
|
+
def prompt_stake_amount(
|
159
|
+
current_balance: Balance, netuid: int, action_name: str
|
160
|
+
) -> tuple[Balance, bool]:
|
161
|
+
"""Prompts user to input a stake amount with validation.
|
162
|
+
|
163
|
+
Args:
|
164
|
+
current_balance (Balance): The maximum available balance
|
165
|
+
netuid (int): The subnet id to get the correct unit
|
166
|
+
action_name (str): The name of the action (e.g. "transfer", "move", "unstake")
|
167
|
+
|
168
|
+
Returns:
|
169
|
+
tuple[Balance, bool]: (The amount to use as Balance object, whether all balance was selected)
|
170
|
+
"""
|
171
|
+
while True:
|
172
|
+
amount_input = Prompt.ask(
|
173
|
+
f"\nEnter amount to {action_name} from "
|
174
|
+
f"[{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{Balance.get_unit(netuid)}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}] "
|
175
|
+
f"[{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}](max: {current_balance})[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}] "
|
176
|
+
f"or "
|
177
|
+
f"[{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]'all'[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}] "
|
178
|
+
f"for entire balance"
|
179
|
+
)
|
180
|
+
|
181
|
+
if amount_input.lower() == "all":
|
182
|
+
return current_balance, True
|
183
|
+
|
184
|
+
try:
|
185
|
+
amount = float(amount_input)
|
186
|
+
if amount <= 0:
|
187
|
+
console.print("[red]Amount must be greater than 0[/red]")
|
188
|
+
continue
|
189
|
+
if amount > current_balance.tao:
|
190
|
+
console.print(
|
191
|
+
f"[red]Amount exceeds available balance of "
|
192
|
+
f"[{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{current_balance}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]"
|
193
|
+
f"[/red]"
|
194
|
+
)
|
195
|
+
continue
|
196
|
+
return Balance.from_tao(amount), False
|
197
|
+
except ValueError:
|
198
|
+
console.print("[red]Please enter a valid number or 'all'[/red]")
|
199
|
+
|
200
|
+
|
201
|
+
async def stake_move_selection(
|
202
|
+
subtensor: "SubtensorInterface",
|
203
|
+
wallet: Wallet,
|
204
|
+
):
|
205
|
+
"""Selection interface for moving stakes between hotkeys and subnets."""
|
206
|
+
stakes, ck_hk_identities, old_identities = await asyncio.gather(
|
207
|
+
subtensor.get_stake_for_coldkey(coldkey_ss58=wallet.coldkeypub.ss58_address),
|
208
|
+
subtensor.fetch_coldkey_hotkey_identities(),
|
209
|
+
subtensor.get_delegate_identities(),
|
210
|
+
)
|
211
|
+
|
212
|
+
hotkey_stakes = {}
|
213
|
+
for stake in stakes:
|
214
|
+
if stake.stake.tao > 0:
|
215
|
+
hotkey = stake.hotkey_ss58
|
216
|
+
netuid = stake.netuid
|
217
|
+
stake_balance = stake.stake
|
218
|
+
hotkey_stakes.setdefault(hotkey, {})[netuid] = stake_balance
|
219
|
+
|
220
|
+
if not hotkey_stakes:
|
221
|
+
print_error("You have no stakes to move.")
|
222
|
+
raise typer.Exit()
|
223
|
+
|
224
|
+
# Display hotkeys with stakes
|
225
|
+
table = Table(
|
226
|
+
title=f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Hotkeys with Stakes\n",
|
227
|
+
show_footer=True,
|
228
|
+
show_edge=False,
|
229
|
+
header_style="bold white",
|
230
|
+
border_style="bright_black",
|
231
|
+
style="bold",
|
232
|
+
title_justify="center",
|
233
|
+
show_lines=False,
|
234
|
+
pad_edge=True,
|
235
|
+
)
|
236
|
+
table.add_column("Index", justify="right")
|
237
|
+
table.add_column("Identity", style=COLOR_PALETTE["GENERAL"]["SUBHEADING"])
|
238
|
+
table.add_column("Netuids", style=COLOR_PALETTE["GENERAL"]["NETUID"])
|
239
|
+
table.add_column("Hotkey Address", style=COLOR_PALETTE["GENERAL"]["HOTKEY"])
|
240
|
+
|
241
|
+
hotkeys_info = []
|
242
|
+
for idx, (hotkey_ss58, netuid_stakes) in enumerate(hotkey_stakes.items()):
|
243
|
+
if hk_identity := ck_hk_identities["hotkeys"].get(hotkey_ss58):
|
244
|
+
hotkey_name = hk_identity.get("identity", {}).get(
|
245
|
+
"name", ""
|
246
|
+
) or hk_identity.get("display", "~")
|
247
|
+
elif old_identity := old_identities.get(hotkey_ss58):
|
248
|
+
hotkey_name = old_identity.display
|
249
|
+
else:
|
250
|
+
hotkey_name = "~"
|
251
|
+
hotkeys_info.append(
|
252
|
+
{
|
253
|
+
"index": idx,
|
254
|
+
"identity": hotkey_name,
|
255
|
+
"hotkey_ss58": hotkey_ss58,
|
256
|
+
"netuids": list(netuid_stakes.keys()),
|
257
|
+
"stakes": netuid_stakes,
|
258
|
+
}
|
259
|
+
)
|
260
|
+
table.add_row(
|
261
|
+
str(idx),
|
262
|
+
hotkey_name,
|
263
|
+
group_subnets([n for n in netuid_stakes.keys()]),
|
264
|
+
hotkey_ss58,
|
265
|
+
)
|
266
|
+
|
267
|
+
console.print("\n", table)
|
268
|
+
|
269
|
+
# Select origin hotkey
|
270
|
+
origin_idx = Prompt.ask(
|
271
|
+
"\nEnter the index of the hotkey you want to move stake from",
|
272
|
+
choices=[str(i) for i in range(len(hotkeys_info))],
|
273
|
+
)
|
274
|
+
origin_hotkey_info = hotkeys_info[int(origin_idx)]
|
275
|
+
origin_hotkey_ss58 = origin_hotkey_info["hotkey_ss58"]
|
276
|
+
|
277
|
+
# Display available netuids for selected hotkey
|
278
|
+
table = Table(
|
279
|
+
title=f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Available Stakes for Hotkey\n[/{COLOR_PALETTE['GENERAL']['HEADER']}]"
|
280
|
+
f"[{COLOR_PALETTE['GENERAL']['HOTKEY']}]{origin_hotkey_ss58}[/{COLOR_PALETTE['GENERAL']['HOTKEY']}]\n",
|
281
|
+
show_edge=False,
|
282
|
+
header_style="bold white",
|
283
|
+
border_style="bright_black",
|
284
|
+
title_justify="center",
|
285
|
+
width=len(origin_hotkey_ss58) + 20,
|
286
|
+
)
|
287
|
+
table.add_column("Index", justify="right")
|
288
|
+
table.add_column("Netuid", style="cyan")
|
289
|
+
table.add_column("Stake Amount", style=COLOR_PALETTE["STAKE"]["STAKE_AMOUNT"])
|
290
|
+
|
291
|
+
available_netuids = []
|
292
|
+
for idx, netuid in enumerate(origin_hotkey_info["netuids"]):
|
293
|
+
stake = origin_hotkey_info["stakes"][netuid]
|
294
|
+
if stake.tao > 0:
|
295
|
+
available_netuids.append(netuid)
|
296
|
+
table.add_row(str(idx), str(netuid), str(stake))
|
297
|
+
|
298
|
+
console.print("\n", table)
|
299
|
+
|
300
|
+
# Select origin netuid
|
301
|
+
netuid_idx = Prompt.ask(
|
302
|
+
"\nEnter the index of the subnet you want to move stake from",
|
303
|
+
choices=[str(i) for i in range(len(available_netuids))],
|
304
|
+
)
|
305
|
+
origin_netuid = available_netuids[int(netuid_idx)]
|
306
|
+
origin_stake = origin_hotkey_info["stakes"][origin_netuid]
|
307
|
+
|
308
|
+
# Ask for amount to move
|
309
|
+
amount, stake_all = prompt_stake_amount(origin_stake, origin_netuid, "move")
|
310
|
+
|
311
|
+
all_subnets = sorted(await subtensor.get_all_subnet_netuids())
|
312
|
+
destination_netuid = Prompt.ask(
|
313
|
+
"\nEnter the netuid of the subnet you want to move stake to"
|
314
|
+
+ f" ([dim]{group_subnets(all_subnets)}[/dim])",
|
315
|
+
choices=[str(netuid) for netuid in all_subnets],
|
316
|
+
show_choices=False,
|
317
|
+
)
|
318
|
+
|
319
|
+
return {
|
320
|
+
"origin_hotkey": origin_hotkey_ss58,
|
321
|
+
"origin_netuid": origin_netuid,
|
322
|
+
"amount": amount.tao,
|
323
|
+
"stake_all": stake_all,
|
324
|
+
"destination_netuid": int(destination_netuid),
|
325
|
+
}
|
326
|
+
|
327
|
+
|
328
|
+
async def stake_transfer_selection(
|
329
|
+
wallet: Wallet,
|
330
|
+
subtensor: "SubtensorInterface",
|
331
|
+
):
|
332
|
+
"""Selection interface for transferring stakes."""
|
333
|
+
(
|
334
|
+
stakes,
|
335
|
+
all_netuids,
|
336
|
+
all_subnets,
|
337
|
+
) = await asyncio.gather(
|
338
|
+
subtensor.get_stake_for_coldkey(coldkey_ss58=wallet.coldkeypub.ss58_address),
|
339
|
+
subtensor.get_all_subnet_netuids(),
|
340
|
+
subtensor.all_subnets(),
|
341
|
+
)
|
342
|
+
all_netuids = sorted(all_netuids)
|
343
|
+
all_subnets = {di.netuid: di for di in all_subnets}
|
344
|
+
|
345
|
+
available_stakes = {}
|
346
|
+
for stake in stakes:
|
347
|
+
if stake.stake.tao > 0 and stake.hotkey_ss58 == wallet.hotkey.ss58_address:
|
348
|
+
available_stakes[stake.netuid] = {
|
349
|
+
"hotkey_ss58": stake.hotkey_ss58,
|
350
|
+
"stake": stake.stake,
|
351
|
+
"is_registered": stake.is_registered,
|
352
|
+
}
|
353
|
+
|
354
|
+
if not available_stakes:
|
355
|
+
console.print("[red]No stakes available to transfer.[/red]")
|
356
|
+
return None
|
357
|
+
|
358
|
+
table = Table(
|
359
|
+
title=(
|
360
|
+
f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]"
|
361
|
+
f"Available Stakes to Transfer\n"
|
362
|
+
f"for wallet hotkey:\n"
|
363
|
+
f"[{COLOR_PALETTE['GENERAL']['HOTKEY']}]{wallet.hotkey_str}: {wallet.hotkey.ss58_address}"
|
364
|
+
f"[/{COLOR_PALETTE['GENERAL']['HOTKEY']}]\n"
|
365
|
+
),
|
366
|
+
show_edge=False,
|
367
|
+
header_style="bold white",
|
368
|
+
border_style="bright_black",
|
369
|
+
title_justify="center",
|
370
|
+
width=len(wallet.hotkey_str + wallet.hotkey.ss58_address) + 10,
|
371
|
+
)
|
372
|
+
|
373
|
+
table.add_column("Index", justify="right", style="cyan")
|
374
|
+
table.add_column("Netuid")
|
375
|
+
table.add_column("Name", style="cyan", justify="left")
|
376
|
+
table.add_column("Stake Amount", style=COLOR_PALETTE["STAKE"]["STAKE_AMOUNT"])
|
377
|
+
table.add_column("Registered", justify="center")
|
378
|
+
|
379
|
+
for idx, (netuid, stake_info) in enumerate(available_stakes.items()):
|
380
|
+
subnet_name_cell = (
|
381
|
+
f"[{COLOR_PALETTE['GENERAL']['SYMBOL']}]{all_subnets[netuid].symbol if netuid != 0 else 'τ'}[/{COLOR_PALETTE['GENERAL']['SYMBOL']}]"
|
382
|
+
f" {get_subnet_name(all_subnets[netuid])}"
|
383
|
+
)
|
384
|
+
table.add_row(
|
385
|
+
str(idx),
|
386
|
+
str(netuid),
|
387
|
+
subnet_name_cell,
|
388
|
+
str(stake_info["stake"]),
|
389
|
+
"[dark_sea_green3]YES"
|
390
|
+
if stake_info["is_registered"]
|
391
|
+
else f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]NO",
|
392
|
+
)
|
393
|
+
|
394
|
+
console.print(table)
|
395
|
+
|
396
|
+
if not available_stakes:
|
397
|
+
console.print("[red]No stakes available to transfer.[/red]")
|
398
|
+
return None
|
399
|
+
|
400
|
+
# Prompt to select index of stake to transfer
|
401
|
+
selection = Prompt.ask(
|
402
|
+
"\nEnter the index of the stake you want to transfer",
|
403
|
+
choices=[str(i) for i in range(len(available_stakes))],
|
404
|
+
)
|
405
|
+
selected_netuid = list(available_stakes.keys())[int(selection)]
|
406
|
+
selected_stake = available_stakes[selected_netuid]
|
407
|
+
|
408
|
+
# Prompt for amount
|
409
|
+
stake_balance = selected_stake["stake"]
|
410
|
+
amount, _ = prompt_stake_amount(stake_balance, selected_netuid, "transfer")
|
411
|
+
|
412
|
+
# Prompt for destination subnet
|
413
|
+
destination_netuid = Prompt.ask(
|
414
|
+
"\nEnter the netuid of the subnet you want to move stake to"
|
415
|
+
+ f" ([dim]{group_subnets(all_netuids)}[/dim])",
|
416
|
+
choices=[str(netuid) for netuid in all_netuids],
|
417
|
+
show_choices=False,
|
418
|
+
)
|
419
|
+
|
420
|
+
return {
|
421
|
+
"origin_netuid": selected_netuid,
|
422
|
+
"amount": amount.tao,
|
423
|
+
"destination_netuid": int(destination_netuid),
|
424
|
+
}
|
425
|
+
|
426
|
+
|
427
|
+
async def stake_swap_selection(
|
428
|
+
subtensor: "SubtensorInterface",
|
429
|
+
wallet: Wallet,
|
430
|
+
) -> dict:
|
431
|
+
"""Selection interface for swapping stakes between subnets."""
|
432
|
+
block_hash = await subtensor.substrate.get_chain_head()
|
433
|
+
stakes, all_subnets = await asyncio.gather(
|
434
|
+
subtensor.get_stake_for_coldkey(
|
435
|
+
coldkey_ss58=wallet.coldkeypub.ss58_address, block_hash=block_hash
|
436
|
+
),
|
437
|
+
subtensor.all_subnets(block_hash=block_hash),
|
438
|
+
)
|
439
|
+
subnet_dict = {di.netuid: di for di in all_subnets}
|
440
|
+
|
441
|
+
# Filter stakes for this hotkey
|
442
|
+
hotkey_stakes = {}
|
443
|
+
for stake in stakes:
|
444
|
+
if stake.hotkey_ss58 == wallet.hotkey.ss58_address and stake.stake.tao > 0:
|
445
|
+
hotkey_stakes[stake.netuid] = {
|
446
|
+
"stake": stake.stake,
|
447
|
+
"is_registered": stake.is_registered,
|
448
|
+
}
|
449
|
+
|
450
|
+
if not hotkey_stakes:
|
451
|
+
print_error(f"No stakes found for hotkey: {wallet.hotkey_str}")
|
452
|
+
raise typer.Exit()
|
453
|
+
|
454
|
+
# Display available stakes
|
455
|
+
table = Table(
|
456
|
+
title=f"\n[{COLOR_PALETTE['GENERAL']['HEADER']}]Available Stakes for Hotkey\n[/{COLOR_PALETTE['GENERAL']['HEADER']}]"
|
457
|
+
f"[{COLOR_PALETTE['GENERAL']['HOTKEY']}]{wallet.hotkey_str}: {wallet.hotkey.ss58_address}[/{COLOR_PALETTE['GENERAL']['HOTKEY']}]\n",
|
458
|
+
show_edge=False,
|
459
|
+
header_style="bold white",
|
460
|
+
border_style="bright_black",
|
461
|
+
title_justify="center",
|
462
|
+
width=len(wallet.hotkey.ss58_address) + 20,
|
463
|
+
)
|
464
|
+
|
465
|
+
table.add_column("Index", justify="right", style="cyan")
|
466
|
+
table.add_column("Netuid", style=COLOR_PALETTE["GENERAL"]["NETUID"])
|
467
|
+
table.add_column("Name", style="cyan", justify="left")
|
468
|
+
table.add_column("Stake Amount", style=COLOR_PALETTE["STAKE"]["STAKE_AMOUNT"])
|
469
|
+
table.add_column("Registered", justify="center")
|
470
|
+
|
471
|
+
available_netuids = []
|
472
|
+
for idx, (netuid, stake_info) in enumerate(sorted(hotkey_stakes.items())):
|
473
|
+
subnet_info = subnet_dict[netuid]
|
474
|
+
subnet_name_cell = (
|
475
|
+
f"[{COLOR_PALETTE['GENERAL']['SYMBOL']}]{subnet_info.symbol if netuid != 0 else 'τ'}[/{COLOR_PALETTE['GENERAL']['SYMBOL']}]"
|
476
|
+
f" {get_subnet_name(subnet_info)}"
|
477
|
+
)
|
478
|
+
|
479
|
+
available_netuids.append(netuid)
|
480
|
+
table.add_row(
|
481
|
+
str(idx),
|
482
|
+
str(netuid),
|
483
|
+
subnet_name_cell,
|
484
|
+
str(stake_info["stake"]),
|
485
|
+
"[dark_sea_green3]YES"
|
486
|
+
if stake_info["is_registered"]
|
487
|
+
else f"[{COLOR_PALETTE['STAKE']['NOT_REGISTERED']}]NO",
|
488
|
+
)
|
489
|
+
|
490
|
+
console.print("\n", table)
|
491
|
+
|
492
|
+
# Select origin netuid
|
493
|
+
origin_idx = Prompt.ask(
|
494
|
+
"\nEnter the index of the subnet you want to swap stake from",
|
495
|
+
choices=[str(i) for i in range(len(available_netuids))],
|
496
|
+
)
|
497
|
+
origin_netuid = available_netuids[int(origin_idx)]
|
498
|
+
origin_stake = hotkey_stakes[origin_netuid]["stake"]
|
499
|
+
|
500
|
+
# Ask for amount to swap
|
501
|
+
amount, all_balance = prompt_stake_amount(origin_stake, origin_netuid, "swap")
|
502
|
+
|
503
|
+
all_netuids = sorted(await subtensor.get_all_subnet_netuids())
|
504
|
+
destination_choices = [
|
505
|
+
str(netuid) for netuid in all_netuids if netuid != origin_netuid
|
506
|
+
]
|
507
|
+
destination_netuid = Prompt.ask(
|
508
|
+
"\nEnter the netuid of the subnet you want to swap stake to"
|
509
|
+
+ f" ([dim]{group_subnets(all_netuids)}[/dim])",
|
510
|
+
choices=destination_choices,
|
511
|
+
show_choices=False,
|
512
|
+
)
|
513
|
+
|
514
|
+
return {
|
515
|
+
"origin_netuid": origin_netuid,
|
516
|
+
"amount": amount.tao,
|
517
|
+
"destination_netuid": int(destination_netuid),
|
518
|
+
}
|
519
|
+
|
520
|
+
|
521
|
+
# Commands
|
522
|
+
async def move_stake(
|
523
|
+
subtensor: "SubtensorInterface",
|
524
|
+
wallet: Wallet,
|
525
|
+
origin_netuid: int,
|
526
|
+
origin_hotkey: str,
|
527
|
+
destination_netuid: int,
|
528
|
+
destination_hotkey: str,
|
529
|
+
amount: float,
|
530
|
+
stake_all: bool,
|
531
|
+
interactive_selection: bool = False,
|
532
|
+
prompt: bool = True,
|
533
|
+
):
|
534
|
+
if interactive_selection:
|
535
|
+
selection = await stake_move_selection(subtensor, wallet)
|
536
|
+
origin_hotkey = selection["origin_hotkey"]
|
537
|
+
origin_netuid = selection["origin_netuid"]
|
538
|
+
amount = selection["amount"]
|
539
|
+
stake_all = selection["stake_all"]
|
540
|
+
destination_netuid = selection["destination_netuid"]
|
541
|
+
|
542
|
+
# Get the wallet stake balances.
|
543
|
+
block_hash = await subtensor.substrate.get_chain_head()
|
544
|
+
origin_stake_balance, destination_stake_balance = await asyncio.gather(
|
545
|
+
subtensor.get_stake(
|
546
|
+
coldkey_ss58=wallet.coldkeypub.ss58_address,
|
547
|
+
hotkey_ss58=origin_hotkey,
|
548
|
+
netuid=origin_netuid,
|
549
|
+
block_hash=block_hash,
|
550
|
+
),
|
551
|
+
subtensor.get_stake(
|
552
|
+
coldkey_ss58=wallet.coldkeypub.ss58_address,
|
553
|
+
hotkey_ss58=destination_hotkey,
|
554
|
+
netuid=destination_netuid,
|
555
|
+
block_hash=block_hash,
|
556
|
+
),
|
557
|
+
)
|
558
|
+
|
559
|
+
if origin_stake_balance.tao == 0:
|
560
|
+
print_error(
|
561
|
+
f"Your balance is "
|
562
|
+
f"[{COLOR_PALETTE['POOLS']['TAO']}]0[/{COLOR_PALETTE['POOLS']['TAO']}] "
|
563
|
+
f"in Netuid: "
|
564
|
+
f"[{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{origin_netuid}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}]"
|
565
|
+
)
|
566
|
+
raise typer.Exit()
|
567
|
+
|
568
|
+
console.print(
|
569
|
+
f"\nOrigin Netuid: "
|
570
|
+
f"[{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{origin_netuid}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}], "
|
571
|
+
f"Origin stake: "
|
572
|
+
f"[{COLOR_PALETTE['POOLS']['TAO']}]{origin_stake_balance}[/{COLOR_PALETTE['POOLS']['TAO']}]"
|
573
|
+
)
|
574
|
+
console.print(
|
575
|
+
f"Destination netuid: "
|
576
|
+
f"[{COLOR_PALETTE['GENERAL']['SUBHEADING']}]{destination_netuid}[/{COLOR_PALETTE['GENERAL']['SUBHEADING']}], "
|
577
|
+
f"Destination stake: "
|
578
|
+
f"[{COLOR_PALETTE['POOLS']['TAO']}]{destination_stake_balance}[/{COLOR_PALETTE['POOLS']['TAO']}]\n"
|
579
|
+
)
|
580
|
+
|
581
|
+
# Determine the amount we are moving.
|
582
|
+
amount_to_move_as_balance = None
|
583
|
+
if amount:
|
584
|
+
amount_to_move_as_balance = Balance.from_tao(amount)
|
585
|
+
elif stake_all:
|
586
|
+
amount_to_move_as_balance = origin_stake_balance
|
587
|
+
else:
|
588
|
+
amount_to_move_as_balance, _ = prompt_stake_amount(
|
589
|
+
origin_stake_balance, origin_netuid, "move"
|
590
|
+
)
|
591
|
+
|
592
|
+
# Check enough to move.
|
593
|
+
amount_to_move_as_balance.set_unit(origin_netuid)
|
594
|
+
if amount_to_move_as_balance > origin_stake_balance:
|
595
|
+
err_console.print(
|
596
|
+
f"[red]Not enough stake[/red]:\n"
|
597
|
+
f" Stake balance: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{origin_stake_balance}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]"
|
598
|
+
f" < Moving amount: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{amount_to_move_as_balance}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]"
|
599
|
+
)
|
600
|
+
return False
|
601
|
+
|
602
|
+
# Slippage warning
|
603
|
+
if prompt:
|
604
|
+
await display_stake_movement_cross_subnets(
|
605
|
+
subtensor=subtensor,
|
606
|
+
origin_netuid=origin_netuid,
|
607
|
+
destination_netuid=destination_netuid,
|
608
|
+
origin_hotkey=origin_hotkey,
|
609
|
+
destination_hotkey=destination_hotkey,
|
610
|
+
amount_to_move=amount_to_move_as_balance,
|
611
|
+
)
|
612
|
+
if not Confirm.ask("Would you like to continue?"):
|
613
|
+
raise typer.Exit()
|
614
|
+
|
615
|
+
# Perform moving operation.
|
616
|
+
try:
|
617
|
+
wallet.unlock_coldkey()
|
618
|
+
except KeyFileError:
|
619
|
+
err_console.print("Error decrypting coldkey (possibly incorrect password)")
|
620
|
+
return False
|
621
|
+
with console.status(
|
622
|
+
f"\n:satellite: Moving [blue]{amount_to_move_as_balance}[/blue] from [blue]{origin_hotkey}[/blue] on netuid: [blue]{origin_netuid}[/blue] \nto "
|
623
|
+
f"[blue]{destination_hotkey}[/blue] on netuid: [blue]{destination_netuid}[/blue] ..."
|
624
|
+
):
|
625
|
+
call = await subtensor.substrate.compose_call(
|
626
|
+
call_module="SubtensorModule",
|
627
|
+
call_function="move_stake",
|
628
|
+
call_params={
|
629
|
+
"origin_hotkey": origin_hotkey,
|
630
|
+
"origin_netuid": origin_netuid,
|
631
|
+
"destination_hotkey": destination_hotkey,
|
632
|
+
"destination_netuid": destination_netuid,
|
633
|
+
"alpha_amount": amount_to_move_as_balance.rao,
|
634
|
+
},
|
635
|
+
)
|
636
|
+
extrinsic = await subtensor.substrate.create_signed_extrinsic(
|
637
|
+
call=call, keypair=wallet.coldkey
|
638
|
+
)
|
639
|
+
response = await subtensor.substrate.submit_extrinsic(
|
640
|
+
extrinsic, wait_for_inclusion=True, wait_for_finalization=False
|
641
|
+
)
|
642
|
+
|
643
|
+
if not prompt:
|
644
|
+
console.print(":white_heavy_check_mark: [green]Sent[/green]")
|
645
|
+
return True
|
646
|
+
else:
|
647
|
+
await response.process_events()
|
648
|
+
if not await response.is_success:
|
649
|
+
err_console.print(
|
650
|
+
f"\n:cross_mark: [red]Failed[/red] with error:"
|
651
|
+
f" {format_error_message( await response.error_message, subtensor.substrate)}"
|
652
|
+
)
|
653
|
+
return
|
654
|
+
else:
|
655
|
+
console.print(
|
656
|
+
":white_heavy_check_mark: [dark_sea_green3]Stake moved.[/dark_sea_green3]"
|
657
|
+
)
|
658
|
+
block_hash = await subtensor.substrate.get_chain_head()
|
659
|
+
(
|
660
|
+
new_origin_stake_balance,
|
661
|
+
new_destination_stake_balance,
|
662
|
+
) = await asyncio.gather(
|
663
|
+
subtensor.get_stake(
|
664
|
+
coldkey_ss58=wallet.coldkeypub.ss58_address,
|
665
|
+
hotkey_ss58=origin_hotkey,
|
666
|
+
netuid=origin_netuid,
|
667
|
+
block_hash=block_hash,
|
668
|
+
),
|
669
|
+
subtensor.get_stake(
|
670
|
+
coldkey_ss58=wallet.coldkeypub.ss58_address,
|
671
|
+
hotkey_ss58=destination_hotkey,
|
672
|
+
netuid=destination_netuid,
|
673
|
+
block_hash=block_hash,
|
674
|
+
),
|
675
|
+
)
|
676
|
+
|
677
|
+
console.print(
|
678
|
+
f"Origin Stake:\n [blue]{origin_stake_balance}[/blue] :arrow_right: "
|
679
|
+
f"[{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_origin_stake_balance}"
|
680
|
+
)
|
681
|
+
console.print(
|
682
|
+
f"Destination Stake:\n [blue]{destination_stake_balance}[/blue] :arrow_right: "
|
683
|
+
f"[{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_destination_stake_balance}"
|
684
|
+
)
|
685
|
+
return
|
686
|
+
|
687
|
+
|
688
|
+
async def transfer_stake(
|
689
|
+
wallet: Wallet,
|
690
|
+
subtensor: "SubtensorInterface",
|
691
|
+
amount: float,
|
692
|
+
origin_netuid: int,
|
693
|
+
dest_netuid: int,
|
694
|
+
dest_coldkey_ss58: str,
|
695
|
+
interactive_selection: bool = False,
|
696
|
+
prompt: bool = True,
|
697
|
+
) -> bool:
|
698
|
+
"""Transfers stake from one network to another.
|
699
|
+
|
700
|
+
Args:
|
701
|
+
wallet (Wallet): Bittensor wallet object.
|
702
|
+
subtensor (SubtensorInterface): Subtensor interface instance.
|
703
|
+
amount (float): Amount to transfer.
|
704
|
+
origin_netuid (int): The netuid to transfer stake from.
|
705
|
+
dest_netuid (int): The netuid to transfer stake to.
|
706
|
+
dest_coldkey_ss58 (str): The destination coldkey to transfer stake to.
|
707
|
+
interactive_selection (bool): If true, prompts for selection of origin and destination subnets.
|
708
|
+
prompt (bool): If true, prompts for confirmation before executing transfer.
|
709
|
+
|
710
|
+
Returns:
|
711
|
+
bool: True if transfer was successful, False otherwise.
|
712
|
+
"""
|
713
|
+
if interactive_selection:
|
714
|
+
selection = await stake_transfer_selection(wallet, subtensor)
|
715
|
+
origin_netuid = selection["origin_netuid"]
|
716
|
+
amount = selection["amount"]
|
717
|
+
dest_netuid = selection["destination_netuid"]
|
718
|
+
|
719
|
+
# Check if both subnets exist
|
720
|
+
block_hash = await subtensor.substrate.get_chain_head()
|
721
|
+
dest_exists, origin_exists = await asyncio.gather(
|
722
|
+
subtensor.subnet_exists(netuid=dest_netuid, block_hash=block_hash),
|
723
|
+
subtensor.subnet_exists(netuid=origin_netuid, block_hash=block_hash),
|
724
|
+
)
|
725
|
+
if not dest_exists:
|
726
|
+
err_console.print(f"[red]Subnet {dest_netuid} does not exist[/red]")
|
727
|
+
return False
|
728
|
+
|
729
|
+
if not origin_exists:
|
730
|
+
err_console.print(f"[red]Subnet {origin_netuid} does not exist[/red]")
|
731
|
+
return False
|
732
|
+
|
733
|
+
# Get current stake balances
|
734
|
+
hotkey_ss58 = wallet.hotkey.ss58_address
|
735
|
+
with console.status(f"Retrieving stake data from {subtensor.network}..."):
|
736
|
+
current_stake = await subtensor.get_stake(
|
737
|
+
coldkey_ss58=wallet.coldkeypub.ss58_address,
|
738
|
+
hotkey_ss58=hotkey_ss58,
|
739
|
+
netuid=origin_netuid,
|
740
|
+
)
|
741
|
+
current_dest_stake = await subtensor.get_stake(
|
742
|
+
coldkey_ss58=dest_coldkey_ss58,
|
743
|
+
hotkey_ss58=hotkey_ss58,
|
744
|
+
netuid=dest_netuid,
|
745
|
+
)
|
746
|
+
amount_to_transfer = Balance.from_tao(amount).set_unit(origin_netuid)
|
747
|
+
|
748
|
+
# Check if enough stake to transfer
|
749
|
+
if amount_to_transfer > current_stake:
|
750
|
+
err_console.print(
|
751
|
+
f"[red]Not enough stake to transfer[/red]:\n"
|
752
|
+
f"Stake balance: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{current_stake}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}] < "
|
753
|
+
f"Transfer amount: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{amount_to_transfer}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]"
|
754
|
+
)
|
755
|
+
return False
|
756
|
+
|
757
|
+
# Slippage warning
|
758
|
+
if prompt:
|
759
|
+
await display_stake_movement_cross_subnets(
|
760
|
+
subtensor=subtensor,
|
761
|
+
origin_netuid=origin_netuid,
|
762
|
+
destination_netuid=dest_netuid,
|
763
|
+
origin_hotkey=hotkey_ss58,
|
764
|
+
destination_hotkey=hotkey_ss58,
|
765
|
+
amount_to_move=amount_to_transfer,
|
766
|
+
)
|
767
|
+
|
768
|
+
if not Confirm.ask("Would you like to continue?"):
|
769
|
+
raise typer.Exit()
|
770
|
+
|
771
|
+
# Perform transfer operation
|
772
|
+
try:
|
773
|
+
wallet.unlock_coldkey()
|
774
|
+
except KeyFileError:
|
775
|
+
err_console.print("Error decrypting coldkey (possibly incorrect password)")
|
776
|
+
return False
|
777
|
+
|
778
|
+
with console.status("\n:satellite: Transferring stake ..."):
|
779
|
+
call = await subtensor.substrate.compose_call(
|
780
|
+
call_module="SubtensorModule",
|
781
|
+
call_function="transfer_stake",
|
782
|
+
call_params={
|
783
|
+
"destination_coldkey": dest_coldkey_ss58,
|
784
|
+
"hotkey": hotkey_ss58,
|
785
|
+
"origin_netuid": origin_netuid,
|
786
|
+
"destination_netuid": dest_netuid,
|
787
|
+
"alpha_amount": amount_to_transfer.rao,
|
788
|
+
},
|
789
|
+
)
|
790
|
+
|
791
|
+
extrinsic = await subtensor.substrate.create_signed_extrinsic(
|
792
|
+
call=call, keypair=wallet.coldkey
|
793
|
+
)
|
794
|
+
|
795
|
+
response = await subtensor.substrate.submit_extrinsic(
|
796
|
+
extrinsic, wait_for_inclusion=True, wait_for_finalization=False
|
797
|
+
)
|
798
|
+
|
799
|
+
if not prompt:
|
800
|
+
console.print(":white_heavy_check_mark: [green]Sent[/green]")
|
801
|
+
return True
|
802
|
+
|
803
|
+
await response.process_events()
|
804
|
+
if not await response.is_success:
|
805
|
+
err_console.print(
|
806
|
+
f":cross_mark: [red]Failed[/red] with error: "
|
807
|
+
f"{format_error_message(await response.error_message, subtensor.substrate)}"
|
808
|
+
)
|
809
|
+
return False
|
810
|
+
|
811
|
+
# Get and display new stake balances
|
812
|
+
new_stake, new_dest_stake = await asyncio.gather(
|
813
|
+
subtensor.get_stake(
|
814
|
+
coldkey_ss58=wallet.coldkeypub.ss58_address,
|
815
|
+
hotkey_ss58=hotkey_ss58,
|
816
|
+
netuid=origin_netuid,
|
817
|
+
),
|
818
|
+
subtensor.get_stake(
|
819
|
+
coldkey_ss58=dest_coldkey_ss58,
|
820
|
+
hotkey_ss58=hotkey_ss58,
|
821
|
+
netuid=dest_netuid,
|
822
|
+
),
|
823
|
+
)
|
824
|
+
|
825
|
+
console.print(
|
826
|
+
f"Origin Stake:\n [blue]{current_stake}[/blue] :arrow_right: "
|
827
|
+
f"[{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_stake}"
|
828
|
+
)
|
829
|
+
console.print(
|
830
|
+
f"Destination Stake:\n [blue]{current_dest_stake}[/blue] :arrow_right: "
|
831
|
+
f"[{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_dest_stake}"
|
832
|
+
)
|
833
|
+
return True
|
834
|
+
|
835
|
+
|
836
|
+
async def swap_stake(
|
837
|
+
wallet: Wallet,
|
838
|
+
subtensor: "SubtensorInterface",
|
839
|
+
origin_netuid: int,
|
840
|
+
destination_netuid: int,
|
841
|
+
amount: float,
|
842
|
+
swap_all: bool = False,
|
843
|
+
interactive_selection: bool = False,
|
844
|
+
prompt: bool = True,
|
845
|
+
wait_for_inclusion: bool = True,
|
846
|
+
wait_for_finalization: bool = False,
|
847
|
+
) -> bool:
|
848
|
+
"""Swaps stake between subnets while keeping the same coldkey-hotkey pair ownership.
|
849
|
+
|
850
|
+
Args:
|
851
|
+
wallet (Wallet): The wallet to swap stake from.
|
852
|
+
subtensor (SubtensorInterface): Subtensor interface instance.
|
853
|
+
hotkey_ss58 (str): The SS58 address of the hotkey whose stake is being swapped.
|
854
|
+
origin_netuid (int): The netuid from which stake is removed.
|
855
|
+
destination_netuid (int): The netuid to which stake is added.
|
856
|
+
amount (float): The amount to swap.
|
857
|
+
interactive_selection (bool): If true, prompts for selection of origin and destination subnets.
|
858
|
+
prompt (bool): If true, prompts for confirmation before executing swap.
|
859
|
+
wait_for_inclusion (bool): If true, waits for the transaction to be included in a block.
|
860
|
+
wait_for_finalization (bool): If true, waits for the transaction to be finalized.
|
861
|
+
|
862
|
+
Returns:
|
863
|
+
bool: True if the swap was successful, False otherwise.
|
864
|
+
"""
|
865
|
+
hotkey_ss58 = wallet.hotkey.ss58_address
|
866
|
+
if interactive_selection:
|
867
|
+
selection = await stake_swap_selection(subtensor, wallet)
|
868
|
+
origin_netuid = selection["origin_netuid"]
|
869
|
+
amount = selection["amount"]
|
870
|
+
destination_netuid = selection["destination_netuid"]
|
871
|
+
|
872
|
+
# Check if both subnets exist
|
873
|
+
block_hash = await subtensor.substrate.get_chain_head()
|
874
|
+
dest_exists, origin_exists = await asyncio.gather(
|
875
|
+
subtensor.subnet_exists(netuid=destination_netuid, block_hash=block_hash),
|
876
|
+
subtensor.subnet_exists(netuid=origin_netuid, block_hash=block_hash),
|
877
|
+
)
|
878
|
+
if not dest_exists:
|
879
|
+
err_console.print(f"[red]Subnet {destination_netuid} does not exist[/red]")
|
880
|
+
return False
|
881
|
+
|
882
|
+
if not origin_exists:
|
883
|
+
err_console.print(f"[red]Subnet {origin_netuid} does not exist[/red]")
|
884
|
+
return False
|
885
|
+
|
886
|
+
# Get current stake balances
|
887
|
+
with console.status(f"Retrieving stake data from {subtensor.network}..."):
|
888
|
+
current_stake = await subtensor.get_stake(
|
889
|
+
coldkey_ss58=wallet.coldkeypub.ss58_address,
|
890
|
+
hotkey_ss58=hotkey_ss58,
|
891
|
+
netuid=origin_netuid,
|
892
|
+
)
|
893
|
+
current_dest_stake = await subtensor.get_stake(
|
894
|
+
coldkey_ss58=wallet.coldkeypub.ss58_address,
|
895
|
+
hotkey_ss58=hotkey_ss58,
|
896
|
+
netuid=destination_netuid,
|
897
|
+
)
|
898
|
+
|
899
|
+
if swap_all:
|
900
|
+
amount_to_swap = Balance.from_tao(current_stake).set_unit(origin_netuid)
|
901
|
+
else:
|
902
|
+
amount_to_swap = Balance.from_tao(amount).set_unit(origin_netuid)
|
903
|
+
|
904
|
+
# Check if enough stake to swap
|
905
|
+
if amount_to_swap > current_stake:
|
906
|
+
err_console.print(
|
907
|
+
f"[red]Not enough stake to swap[/red]:\n"
|
908
|
+
f"Stake balance: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{current_stake}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}] < "
|
909
|
+
f"Swap amount: [{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{amount_to_swap}[/{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]"
|
910
|
+
)
|
911
|
+
return False
|
912
|
+
|
913
|
+
# Slippage warning
|
914
|
+
if prompt:
|
915
|
+
await display_stake_movement_cross_subnets(
|
916
|
+
subtensor=subtensor,
|
917
|
+
origin_netuid=origin_netuid,
|
918
|
+
destination_netuid=destination_netuid,
|
919
|
+
origin_hotkey=hotkey_ss58,
|
920
|
+
destination_hotkey=hotkey_ss58,
|
921
|
+
amount_to_move=amount_to_swap,
|
922
|
+
)
|
923
|
+
|
924
|
+
if not Confirm.ask("Would you like to continue?"):
|
925
|
+
raise typer.Exit()
|
926
|
+
|
927
|
+
# Perform swap operation
|
928
|
+
try:
|
929
|
+
wallet.unlock_coldkey()
|
930
|
+
except KeyFileError:
|
931
|
+
err_console.print("Error decrypting coldkey (possibly incorrect password)")
|
932
|
+
return False
|
933
|
+
|
934
|
+
with console.status(
|
935
|
+
f"\n:satellite: Swapping stake from netuid [blue]{origin_netuid}[/blue] to netuid [blue]{destination_netuid}[/blue]..."
|
936
|
+
):
|
937
|
+
call = await subtensor.substrate.compose_call(
|
938
|
+
call_module="SubtensorModule",
|
939
|
+
call_function="swap_stake",
|
940
|
+
call_params={
|
941
|
+
"hotkey": hotkey_ss58,
|
942
|
+
"origin_netuid": origin_netuid,
|
943
|
+
"destination_netuid": destination_netuid,
|
944
|
+
"alpha_amount": amount_to_swap.rao,
|
945
|
+
},
|
946
|
+
)
|
947
|
+
|
948
|
+
extrinsic = await subtensor.substrate.create_signed_extrinsic(
|
949
|
+
call=call, keypair=wallet.coldkey
|
950
|
+
)
|
951
|
+
|
952
|
+
response = await subtensor.substrate.submit_extrinsic(
|
953
|
+
extrinsic,
|
954
|
+
wait_for_inclusion=wait_for_inclusion,
|
955
|
+
wait_for_finalization=wait_for_finalization,
|
956
|
+
)
|
957
|
+
|
958
|
+
if not prompt:
|
959
|
+
console.print(":white_heavy_check_mark: [green]Sent[/green]")
|
960
|
+
return True
|
961
|
+
|
962
|
+
await response.process_events()
|
963
|
+
if not await response.is_success:
|
964
|
+
err_console.print(
|
965
|
+
f":cross_mark: [red]Failed[/red] with error: "
|
966
|
+
f"{format_error_message(await response.error_message, subtensor.substrate)}"
|
967
|
+
)
|
968
|
+
return False
|
969
|
+
|
970
|
+
# Get and display new stake balances
|
971
|
+
new_stake, new_dest_stake = await asyncio.gather(
|
972
|
+
subtensor.get_stake(
|
973
|
+
coldkey_ss58=wallet.coldkeypub.ss58_address,
|
974
|
+
hotkey_ss58=hotkey_ss58,
|
975
|
+
netuid=origin_netuid,
|
976
|
+
),
|
977
|
+
subtensor.get_stake(
|
978
|
+
coldkey_ss58=wallet.coldkeypub.ss58_address,
|
979
|
+
hotkey_ss58=hotkey_ss58,
|
980
|
+
netuid=destination_netuid,
|
981
|
+
),
|
982
|
+
)
|
983
|
+
|
984
|
+
console.print(
|
985
|
+
f"Origin Stake:\n [blue]{current_stake}[/blue] :arrow_right: "
|
986
|
+
f"[{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_stake}"
|
987
|
+
)
|
988
|
+
console.print(
|
989
|
+
f"Destination Stake:\n [blue]{current_dest_stake}[/blue] :arrow_right: "
|
990
|
+
f"[{COLOR_PALETTE['STAKE']['STAKE_AMOUNT']}]{new_dest_stake}"
|
991
|
+
)
|
992
|
+
return True
|