bittensor-cli 9.8.7__py3-none-any.whl → 9.9.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bittensor_cli/cli.py +64 -1
- bittensor_cli/src/bittensor/extrinsics/transfer.py +34 -17
- bittensor_cli/src/bittensor/subtensor_interface.py +32 -14
- bittensor_cli/src/commands/stake/add.py +82 -27
- bittensor_cli/src/commands/stake/move.py +101 -81
- bittensor_cli/src/commands/stake/remove.py +114 -7
- bittensor_cli/src/commands/wallets.py +91 -3
- {bittensor_cli-9.8.7.dist-info → bittensor_cli-9.9.0.dist-info}/METADATA +2 -2
- {bittensor_cli-9.8.7.dist-info → bittensor_cli-9.9.0.dist-info}/RECORD +12 -12
- {bittensor_cli-9.8.7.dist-info → bittensor_cli-9.9.0.dist-info}/WHEEL +0 -0
- {bittensor_cli-9.8.7.dist-info → bittensor_cli-9.9.0.dist-info}/entry_points.txt +0 -0
- {bittensor_cli-9.8.7.dist-info → bittensor_cli-9.9.0.dist-info}/top_level.txt +0 -0
bittensor_cli/cli.py
CHANGED
@@ -817,6 +817,9 @@ class CLIManager:
|
|
817
817
|
self.wallet_app.command(
|
818
818
|
"sign", rich_help_panel=HELP_PANELS["WALLET"]["OPERATIONS"]
|
819
819
|
)(self.wallet_sign)
|
820
|
+
self.wallet_app.command(
|
821
|
+
"verify", rich_help_panel=HELP_PANELS["WALLET"]["OPERATIONS"]
|
822
|
+
)(self.wallet_verify)
|
820
823
|
|
821
824
|
# stake commands
|
822
825
|
self.stake_app.command(
|
@@ -1889,6 +1892,12 @@ class CLIManager:
|
|
1889
1892
|
transfer_all: bool = typer.Option(
|
1890
1893
|
False, "--all", prompt=False, help="Transfer all available balance."
|
1891
1894
|
),
|
1895
|
+
allow_death: bool = typer.Option(
|
1896
|
+
False,
|
1897
|
+
"--allow-death",
|
1898
|
+
prompt=False,
|
1899
|
+
help="Transfer balance even if the resulting balance falls below the existential deposit.",
|
1900
|
+
),
|
1892
1901
|
period: int = Options.period,
|
1893
1902
|
wallet_name: str = Options.wallet_name,
|
1894
1903
|
wallet_path: str = Options.wallet_path,
|
@@ -1932,7 +1941,7 @@ class CLIManager:
|
|
1932
1941
|
subtensor = self.initialize_chain(network)
|
1933
1942
|
if transfer_all and amount:
|
1934
1943
|
print_error("Cannot specify an amount and '--all' flag.")
|
1935
|
-
|
1944
|
+
return False
|
1936
1945
|
elif transfer_all:
|
1937
1946
|
amount = 0
|
1938
1947
|
elif not amount:
|
@@ -1944,6 +1953,7 @@ class CLIManager:
|
|
1944
1953
|
destination=destination_ss58_address,
|
1945
1954
|
amount=amount,
|
1946
1955
|
transfer_all=transfer_all,
|
1956
|
+
allow_death=allow_death,
|
1947
1957
|
era=period,
|
1948
1958
|
prompt=prompt,
|
1949
1959
|
json_output=json_output,
|
@@ -3091,6 +3101,59 @@ class CLIManager:
|
|
3091
3101
|
|
3092
3102
|
return self._run_command(wallets.sign(wallet, message, use_hotkey, json_output))
|
3093
3103
|
|
3104
|
+
def wallet_verify(
|
3105
|
+
self,
|
3106
|
+
message: Optional[str] = typer.Option(
|
3107
|
+
None, "--message", "-m", help="The message that was signed"
|
3108
|
+
),
|
3109
|
+
signature: Optional[str] = typer.Option(
|
3110
|
+
None, "--signature", "-s", help="The signature to verify (hex format)"
|
3111
|
+
),
|
3112
|
+
public_key_or_ss58: Optional[str] = typer.Option(
|
3113
|
+
None,
|
3114
|
+
"--address",
|
3115
|
+
"-a",
|
3116
|
+
"--public-key",
|
3117
|
+
"-p",
|
3118
|
+
help="SS58 address or public key (hex) of the signer",
|
3119
|
+
),
|
3120
|
+
quiet: bool = Options.quiet,
|
3121
|
+
verbose: bool = Options.verbose,
|
3122
|
+
json_output: bool = Options.json_output,
|
3123
|
+
):
|
3124
|
+
"""
|
3125
|
+
Verify a message signature using the signer's public key or SS58 address.
|
3126
|
+
|
3127
|
+
This command allows you to verify that a message was signed by the owner of a specific address.
|
3128
|
+
|
3129
|
+
USAGE
|
3130
|
+
|
3131
|
+
Provide the original message, the signature (in hex format), and either the SS58 address
|
3132
|
+
or public key of the signer to verify the signature.
|
3133
|
+
|
3134
|
+
EXAMPLES
|
3135
|
+
|
3136
|
+
[green]$[/green] btcli wallet verify --message "Hello world" --signature "0xabc123..." --address "5GrwvaEF..."
|
3137
|
+
|
3138
|
+
[green]$[/green] btcli wallet verify -m "Test message" -s "0xdef456..." -p "0x1234abcd..."
|
3139
|
+
"""
|
3140
|
+
self.verbosity_handler(quiet, verbose, json_output)
|
3141
|
+
|
3142
|
+
if not public_key_or_ss58:
|
3143
|
+
public_key_or_ss58 = Prompt.ask(
|
3144
|
+
"Enter the [blue]address[/blue] (SS58 or hex format)"
|
3145
|
+
)
|
3146
|
+
|
3147
|
+
if not message:
|
3148
|
+
message = Prompt.ask("Enter the [blue]message[/blue]")
|
3149
|
+
|
3150
|
+
if not signature:
|
3151
|
+
signature = Prompt.ask("Enter the [blue]signature[/blue]")
|
3152
|
+
|
3153
|
+
return self._run_command(
|
3154
|
+
wallets.verify(message, signature, public_key_or_ss58, json_output)
|
3155
|
+
)
|
3156
|
+
|
3094
3157
|
def wallet_swap_coldkey(
|
3095
3158
|
self,
|
3096
3159
|
wallet_name: Optional[str] = Options.wallet_name,
|
@@ -26,9 +26,9 @@ async def transfer_extrinsic(
|
|
26
26
|
amount: Balance,
|
27
27
|
era: int = 3,
|
28
28
|
transfer_all: bool = False,
|
29
|
+
allow_death: bool = False,
|
29
30
|
wait_for_inclusion: bool = True,
|
30
31
|
wait_for_finalization: bool = False,
|
31
|
-
keep_alive: bool = True,
|
32
32
|
prompt: bool = False,
|
33
33
|
) -> bool:
|
34
34
|
"""Transfers funds from this wallet to the destination public key address.
|
@@ -39,11 +39,11 @@ async def transfer_extrinsic(
|
|
39
39
|
:param amount: Amount to stake as Bittensor balance.
|
40
40
|
:param era: Length (in blocks) for which the transaction should be valid.
|
41
41
|
:param transfer_all: Whether to transfer all funds from this wallet to the destination address.
|
42
|
+
:param allow_death: Whether to allow for falling below the existential deposit when performing this transfer.
|
42
43
|
:param wait_for_inclusion: If set, waits for the extrinsic to enter a block before returning `True`,
|
43
44
|
or returns `False` if the extrinsic fails to enter the block within the timeout.
|
44
45
|
:param wait_for_finalization: If set, waits for the extrinsic to be finalized on the chain before returning
|
45
46
|
`True`, or returns `False` if the extrinsic fails to be finalized within the timeout.
|
46
|
-
:param keep_alive: If set, keeps the account alive by keeping the balance above the existential deposit.
|
47
47
|
:param prompt: If `True`, the call waits for confirmation from the user before proceeding.
|
48
48
|
:return: success: Flag is `True` if extrinsic was finalized or included in the block. If we did not wait for
|
49
49
|
finalization / inclusion, the response is `True`, regardless of its inclusion.
|
@@ -57,8 +57,8 @@ async def transfer_extrinsic(
|
|
57
57
|
"""
|
58
58
|
call = await subtensor.substrate.compose_call(
|
59
59
|
call_module="Balances",
|
60
|
-
call_function=
|
61
|
-
call_params=
|
60
|
+
call_function=call_function,
|
61
|
+
call_params=call_params,
|
62
62
|
)
|
63
63
|
|
64
64
|
try:
|
@@ -82,8 +82,8 @@ async def transfer_extrinsic(
|
|
82
82
|
"""
|
83
83
|
call = await subtensor.substrate.compose_call(
|
84
84
|
call_module="Balances",
|
85
|
-
call_function=
|
86
|
-
call_params=
|
85
|
+
call_function=call_function,
|
86
|
+
call_params=call_params,
|
87
87
|
)
|
88
88
|
extrinsic = await subtensor.substrate.create_signed_extrinsic(
|
89
89
|
call=call, keypair=wallet.coldkey, era={"period": era}
|
@@ -115,6 +115,20 @@ async def transfer_extrinsic(
|
|
115
115
|
if not unlock_key(wallet).success:
|
116
116
|
return False
|
117
117
|
|
118
|
+
call_params = {"dest": destination}
|
119
|
+
if transfer_all:
|
120
|
+
call_function = "transfer_all"
|
121
|
+
if allow_death:
|
122
|
+
call_params["keep_alive"] = False
|
123
|
+
else:
|
124
|
+
call_params["keep_alive"] = True
|
125
|
+
else:
|
126
|
+
call_params["value"] = amount.rao
|
127
|
+
if allow_death:
|
128
|
+
call_function = "transfer_allow_death"
|
129
|
+
else:
|
130
|
+
call_function = "transfer_keep_alive"
|
131
|
+
|
118
132
|
# Check balance.
|
119
133
|
with console.status(
|
120
134
|
f":satellite: Checking balance and fees on chain [white]{subtensor.network}[/white]",
|
@@ -131,23 +145,26 @@ async def transfer_extrinsic(
|
|
131
145
|
)
|
132
146
|
fee = await get_transfer_fee()
|
133
147
|
|
134
|
-
if
|
135
|
-
# Check if the transfer should
|
148
|
+
if allow_death:
|
149
|
+
# Check if the transfer should keep alive the account
|
136
150
|
existential_deposit = Balance(0)
|
137
151
|
|
138
|
-
|
139
|
-
if transfer_all is True:
|
140
|
-
amount = account_balance - fee - existential_deposit
|
141
|
-
if amount < Balance(0):
|
142
|
-
print_error("Not enough balance to transfer")
|
143
|
-
return False
|
144
|
-
|
145
|
-
if account_balance < (amount + fee + existential_deposit):
|
152
|
+
if account_balance < (amount + fee + existential_deposit) and not allow_death:
|
146
153
|
err_console.print(
|
147
154
|
":cross_mark: [bold red]Not enough balance[/bold red]:\n\n"
|
148
155
|
f" balance: [bright_cyan]{account_balance}[/bright_cyan]\n"
|
149
156
|
f" amount: [bright_cyan]{amount}[/bright_cyan]\n"
|
150
|
-
f" for fee: [bright_cyan]{fee}[/bright_cyan]"
|
157
|
+
f" for fee: [bright_cyan]{fee}[/bright_cyan]\n"
|
158
|
+
f" would bring you under the existential deposit: [bright_cyan]{existential_deposit}[/bright_cyan].\n"
|
159
|
+
f"You can try again with `--allow-death`."
|
160
|
+
)
|
161
|
+
return False
|
162
|
+
elif account_balance < (amount + fee) and allow_death:
|
163
|
+
print_error(
|
164
|
+
":cross_mark: [bold red]Not enough balance[/bold red]:\n\n"
|
165
|
+
f" balance: [bright_red]{account_balance}[/bright_red]\n"
|
166
|
+
f" amount: [bright_red]{amount}[/bright_red]\n"
|
167
|
+
f" for fee: [bright_red]{fee}[/bright_red]"
|
151
168
|
)
|
152
169
|
return False
|
153
170
|
|
@@ -3,7 +3,9 @@ import os
|
|
3
3
|
from typing import Optional, Any, Union, TypedDict, Iterable
|
4
4
|
|
5
5
|
import aiohttp
|
6
|
+
from async_substrate_interface.utils.storage import StorageKey
|
6
7
|
from bittensor_wallet import Wallet
|
8
|
+
from bittensor_wallet.bittensor_wallet import Keypair
|
7
9
|
from bittensor_wallet.utils import SS58_FORMAT
|
8
10
|
from scalecodec import GenericCall
|
9
11
|
from async_substrate_interface.errors import SubstrateRequestException
|
@@ -881,9 +883,10 @@ class SubtensorInterface:
|
|
881
883
|
storage_function="IdentitiesV2",
|
882
884
|
block_hash=block_hash,
|
883
885
|
reuse_block_hash=reuse_block,
|
886
|
+
fully_exhaust=True,
|
884
887
|
)
|
885
888
|
all_identities = {}
|
886
|
-
|
889
|
+
for ss58_address, identity in identities.records:
|
887
890
|
all_identities[decode_account_id(ss58_address[0])] = decode_hex_identity(
|
888
891
|
identity.value
|
889
892
|
)
|
@@ -939,22 +942,22 @@ class SubtensorInterface:
|
|
939
942
|
:param reuse_block: Whether to reuse the last-used blockchain block hash.
|
940
943
|
:return: Dict with 'coldkeys' and 'hotkeys' as keys.
|
941
944
|
"""
|
942
|
-
|
943
|
-
|
945
|
+
if block_hash is None:
|
946
|
+
block_hash = await self.substrate.get_chain_head()
|
947
|
+
coldkey_identities = await self.query_all_identities(block_hash=block_hash)
|
944
948
|
identities = {"coldkeys": {}, "hotkeys": {}}
|
945
|
-
|
946
|
-
|
947
|
-
|
948
|
-
|
949
|
-
|
950
|
-
|
951
|
-
|
952
|
-
reuse_block_hash=reuse_block,
|
953
|
-
)
|
949
|
+
sks = [
|
950
|
+
await self.substrate.create_storage_key(
|
951
|
+
"SubtensorModule", "OwnedHotkeys", [ck], block_hash=block_hash
|
952
|
+
)
|
953
|
+
for ck in coldkey_identities.keys()
|
954
|
+
]
|
955
|
+
query = await self.substrate.query_multi(sks, block_hash=block_hash)
|
954
956
|
|
955
|
-
|
957
|
+
storage_key: StorageKey
|
958
|
+
for storage_key, hotkeys in query:
|
959
|
+
coldkey_ss58 = storage_key.params[0]
|
956
960
|
coldkey_identity = coldkey_identities.get(coldkey_ss58)
|
957
|
-
hotkeys = [decode_account_id(hotkey[0]) for hotkey in hotkeys or []]
|
958
961
|
|
959
962
|
identities["coldkeys"][coldkey_ss58] = {
|
960
963
|
"identity": coldkey_identity,
|
@@ -1455,6 +1458,8 @@ class SubtensorInterface:
|
|
1455
1458
|
),
|
1456
1459
|
self.get_subnet_price(netuid=netuid, block_hash=block_hash),
|
1457
1460
|
)
|
1461
|
+
if not result:
|
1462
|
+
raise ValueError(f"Subnet {netuid} not found")
|
1458
1463
|
subnet_ = DynamicInfo.from_any(result)
|
1459
1464
|
subnet_.price = price
|
1460
1465
|
return subnet_
|
@@ -1484,6 +1489,19 @@ class SubtensorInterface:
|
|
1484
1489
|
|
1485
1490
|
return [decode_account_id(hotkey[0]) for hotkey in owned_hotkeys or []]
|
1486
1491
|
|
1492
|
+
async def get_extrinsic_fee(self, call: GenericCall, keypair: Keypair) -> Balance:
|
1493
|
+
"""
|
1494
|
+
Determines the fee for the extrinsic call.
|
1495
|
+
Args:
|
1496
|
+
call: Created extrinsic call
|
1497
|
+
keypair: The keypair that would sign the extrinsic (usually you would just want to use the *pub for this)
|
1498
|
+
|
1499
|
+
Returns:
|
1500
|
+
Balance object representing the fee for this extrinsic.
|
1501
|
+
"""
|
1502
|
+
fee_dict = await self.substrate.get_payment_info(call, keypair)
|
1503
|
+
return Balance.from_rao(fee_dict["partial_fee"])
|
1504
|
+
|
1487
1505
|
async def get_stake_fee(
|
1488
1506
|
self,
|
1489
1507
|
origin_hotkey_ss58: Optional[str],
|
@@ -65,6 +65,45 @@ async def stake_add(
|
|
65
65
|
bool: True if stake operation is successful, False otherwise
|
66
66
|
"""
|
67
67
|
|
68
|
+
async def get_stake_extrinsic_fee(
|
69
|
+
netuid_: int,
|
70
|
+
amount_: Balance,
|
71
|
+
staking_address_: str,
|
72
|
+
safe_staking_: bool,
|
73
|
+
price_limit: Optional[Balance] = None,
|
74
|
+
):
|
75
|
+
"""
|
76
|
+
Quick method to get the extrinsic fee for adding stake depending on the args supplied.
|
77
|
+
Args:
|
78
|
+
netuid_: The netuid where the stake will be added
|
79
|
+
amount_: the amount of stake to add
|
80
|
+
staking_address_: the hotkey ss58 to stake to
|
81
|
+
safe_staking_: whether to use safe staking
|
82
|
+
price_limit: rate with tolerance
|
83
|
+
|
84
|
+
Returns:
|
85
|
+
Balance object representing the extrinsic fee for adding stake.
|
86
|
+
"""
|
87
|
+
call_fn = "add_stake" if not safe_staking_ else "add_stake_limit"
|
88
|
+
call_params = {
|
89
|
+
"hotkey": staking_address_,
|
90
|
+
"netuid": netuid_,
|
91
|
+
"amount_staked": amount_.rao,
|
92
|
+
}
|
93
|
+
if safe_staking_:
|
94
|
+
call_params.update(
|
95
|
+
{
|
96
|
+
"limit_price": price_limit,
|
97
|
+
"allow_partial": allow_partial_stake,
|
98
|
+
}
|
99
|
+
)
|
100
|
+
call = await subtensor.substrate.compose_call(
|
101
|
+
call_module="SubtensorModule",
|
102
|
+
call_function=call_fn,
|
103
|
+
call_params=call_params,
|
104
|
+
)
|
105
|
+
return await subtensor.get_extrinsic_fee(call, wallet.coldkeypub)
|
106
|
+
|
68
107
|
async def safe_stake_extrinsic(
|
69
108
|
netuid_: int,
|
70
109
|
amount_: Balance,
|
@@ -87,7 +126,7 @@ async def stake_add(
|
|
87
126
|
"hotkey": hotkey_ss58_,
|
88
127
|
"netuid": netuid_,
|
89
128
|
"amount_staked": amount_.rao,
|
90
|
-
"limit_price": price_limit,
|
129
|
+
"limit_price": price_limit.rao,
|
91
130
|
"allow_partial": allow_partial_stake,
|
92
131
|
},
|
93
132
|
),
|
@@ -332,19 +371,6 @@ async def stake_add(
|
|
332
371
|
# Temporary workaround - calculations without slippage
|
333
372
|
current_price_float = float(subnet_info.price.tao)
|
334
373
|
rate = 1.0 / current_price_float
|
335
|
-
received_amount = rate * amount_to_stake
|
336
|
-
|
337
|
-
# Add rows for the table
|
338
|
-
base_row = [
|
339
|
-
str(netuid), # netuid
|
340
|
-
f"{hotkey[1]}", # hotkey
|
341
|
-
str(amount_to_stake), # amount
|
342
|
-
str(rate)
|
343
|
-
+ f" {Balance.get_unit(netuid)}/{Balance.get_unit(0)} ", # rate
|
344
|
-
str(received_amount.set_unit(netuid)), # received
|
345
|
-
str(stake_fee), # fee
|
346
|
-
# str(slippage_pct), # slippage
|
347
|
-
]
|
348
374
|
|
349
375
|
# If we are staking safe, add price tolerance
|
350
376
|
if safe_staking:
|
@@ -356,21 +382,45 @@ async def stake_add(
|
|
356
382
|
rate_with_tolerance = f"{_rate_with_tolerance:.4f}"
|
357
383
|
price_with_tolerance = Balance.from_tao(
|
358
384
|
price_with_tolerance
|
359
|
-
)
|
385
|
+
) # Actual price to pass to extrinsic
|
360
386
|
else:
|
361
387
|
rate_with_tolerance = "1"
|
362
388
|
price_with_tolerance = Balance.from_rao(1)
|
389
|
+
extrinsic_fee = await get_stake_extrinsic_fee(
|
390
|
+
netuid_=netuid,
|
391
|
+
amount_=amount_to_stake,
|
392
|
+
staking_address_=hotkey[1],
|
393
|
+
safe_staking_=safe_staking,
|
394
|
+
price_limit=price_with_tolerance,
|
395
|
+
)
|
363
396
|
prices_with_tolerance.append(price_with_tolerance)
|
364
|
-
|
365
|
-
|
366
|
-
[
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
397
|
+
row_extension = [
|
398
|
+
f"{rate_with_tolerance} {Balance.get_unit(netuid)}/{Balance.get_unit(0)} ",
|
399
|
+
f"[{'dark_sea_green3' if allow_partial_stake else 'red'}]"
|
400
|
+
# safe staking
|
401
|
+
f"{allow_partial_stake}[/{'dark_sea_green3' if allow_partial_stake else 'red'}]",
|
402
|
+
]
|
403
|
+
else:
|
404
|
+
extrinsic_fee = await get_stake_extrinsic_fee(
|
405
|
+
netuid_=netuid,
|
406
|
+
amount_=amount_to_stake,
|
407
|
+
staking_address_=hotkey[1],
|
408
|
+
safe_staking_=safe_staking,
|
372
409
|
)
|
373
|
-
|
410
|
+
row_extension = []
|
411
|
+
received_amount = rate * (amount_to_stake - stake_fee - extrinsic_fee)
|
412
|
+
# Add rows for the table
|
413
|
+
base_row = [
|
414
|
+
str(netuid), # netuid
|
415
|
+
f"{hotkey[1]}", # hotkey
|
416
|
+
str(amount_to_stake), # amount
|
417
|
+
str(rate)
|
418
|
+
+ f" {Balance.get_unit(netuid)}/{Balance.get_unit(0)} ", # rate
|
419
|
+
str(received_amount.set_unit(netuid)), # received
|
420
|
+
str(stake_fee), # fee
|
421
|
+
str(extrinsic_fee),
|
422
|
+
# str(slippage_pct), # slippage
|
423
|
+
] + row_extension
|
374
424
|
rows.append(tuple(base_row))
|
375
425
|
|
376
426
|
# Define and print stake table + slippage warning
|
@@ -569,17 +619,17 @@ def _define_stake_table(
|
|
569
619
|
"Hotkey", justify="center", style=COLOR_PALETTE["GENERAL"]["HOTKEY"]
|
570
620
|
)
|
571
621
|
table.add_column(
|
572
|
-
|
622
|
+
"Amount (τ)",
|
573
623
|
justify="center",
|
574
624
|
style=COLOR_PALETTE["POOLS"]["TAO"],
|
575
625
|
)
|
576
626
|
table.add_column(
|
577
|
-
|
627
|
+
"Rate (per τ)",
|
578
628
|
justify="center",
|
579
629
|
style=COLOR_PALETTE["POOLS"]["RATE"],
|
580
630
|
)
|
581
631
|
table.add_column(
|
582
|
-
"Received",
|
632
|
+
"Est. Received",
|
583
633
|
justify="center",
|
584
634
|
style=COLOR_PALETTE["POOLS"]["TAO_EQUIV"],
|
585
635
|
)
|
@@ -588,6 +638,11 @@ def _define_stake_table(
|
|
588
638
|
justify="center",
|
589
639
|
style=COLOR_PALETTE["STAKE"]["STAKE_AMOUNT"],
|
590
640
|
)
|
641
|
+
table.add_column(
|
642
|
+
"Extrinsic Fee (τ)",
|
643
|
+
justify="center",
|
644
|
+
style=COLOR_PALETTE.STAKE.TAO,
|
645
|
+
)
|
591
646
|
# TODO: Uncomment when slippage is reimplemented for v3
|
592
647
|
# table.add_column(
|
593
648
|
# "Slippage", justify="center", style=COLOR_PALETTE["STAKE"]["SLIPPAGE_PERCENT"]
|
@@ -33,12 +33,15 @@ async def display_stake_movement_cross_subnets(
|
|
33
33
|
destination_hotkey: str,
|
34
34
|
amount_to_move: Balance,
|
35
35
|
stake_fee: Balance,
|
36
|
+
extrinsic_fee: Balance,
|
36
37
|
) -> tuple[Balance, str]:
|
37
38
|
"""Calculate and display stake movement information"""
|
38
39
|
|
39
40
|
if origin_netuid == destination_netuid:
|
40
41
|
subnet = await subtensor.subnet(origin_netuid)
|
41
|
-
received_amount_tao =
|
42
|
+
received_amount_tao = (
|
43
|
+
subnet.alpha_to_tao(amount_to_move - stake_fee) - extrinsic_fee
|
44
|
+
)
|
42
45
|
received_amount = subnet.tao_to_alpha(received_amount_tao)
|
43
46
|
|
44
47
|
if received_amount < Balance.from_tao(0).set_unit(destination_netuid):
|
@@ -62,7 +65,9 @@ async def display_stake_movement_cross_subnets(
|
|
62
65
|
price_destination = dynamic_destination.price.tao
|
63
66
|
rate = price_origin / (price_destination or 1)
|
64
67
|
|
65
|
-
received_amount_tao =
|
68
|
+
received_amount_tao = (
|
69
|
+
dynamic_origin.alpha_to_tao(amount_to_move - stake_fee) - extrinsic_fee
|
70
|
+
)
|
66
71
|
received_amount = dynamic_destination.tao_to_alpha(received_amount_tao)
|
67
72
|
received_amount.set_unit(destination_netuid)
|
68
73
|
|
@@ -81,14 +86,14 @@ async def display_stake_movement_cross_subnets(
|
|
81
86
|
# Create and display table
|
82
87
|
table = Table(
|
83
88
|
title=(
|
84
|
-
f"\n[{COLOR_PALETTE
|
89
|
+
f"\n[{COLOR_PALETTE.G.HEADER}]"
|
85
90
|
f"Moving stake from: "
|
86
|
-
f"[{COLOR_PALETTE
|
87
|
-
f"[/{COLOR_PALETTE
|
91
|
+
f"[{COLOR_PALETTE.G.SUBHEAD}]{Balance.get_unit(origin_netuid)}(Netuid: {origin_netuid})"
|
92
|
+
f"[/{COLOR_PALETTE.G.SUBHEAD}] "
|
88
93
|
f"to: "
|
89
|
-
f"[{COLOR_PALETTE
|
90
|
-
f"[/{COLOR_PALETTE
|
91
|
-
f"[/{COLOR_PALETTE
|
94
|
+
f"[{COLOR_PALETTE.G.SUBHEAD}]{Balance.get_unit(destination_netuid)}(Netuid: {destination_netuid})"
|
95
|
+
f"[/{COLOR_PALETTE.G.SUBHEAD}]\nNetwork: {subtensor.network}\n"
|
96
|
+
f"[/{COLOR_PALETTE.G.HEADER}]"
|
92
97
|
),
|
93
98
|
show_footer=True,
|
94
99
|
show_edge=False,
|
@@ -132,6 +137,9 @@ async def display_stake_movement_cross_subnets(
|
|
132
137
|
justify="center",
|
133
138
|
style=COLOR_PALETTE["STAKE"]["STAKE_AMOUNT"],
|
134
139
|
)
|
140
|
+
table.add_column(
|
141
|
+
"Extrinsic Fee (τ)", justify="center", style=COLOR_PALETTE.STAKE.TAO
|
142
|
+
)
|
135
143
|
|
136
144
|
table.add_row(
|
137
145
|
f"{Balance.get_unit(origin_netuid)}({origin_netuid})",
|
@@ -142,6 +150,7 @@ async def display_stake_movement_cross_subnets(
|
|
142
150
|
price_str,
|
143
151
|
str(received_amount),
|
144
152
|
str(stake_fee.set_unit(origin_netuid)),
|
153
|
+
str(extrinsic_fee),
|
145
154
|
)
|
146
155
|
|
147
156
|
console.print(table)
|
@@ -165,10 +174,10 @@ def prompt_stake_amount(
|
|
165
174
|
while True:
|
166
175
|
amount_input = Prompt.ask(
|
167
176
|
f"\nEnter the amount to {action_name} "
|
168
|
-
f"[{COLOR_PALETTE
|
169
|
-
f"[{COLOR_PALETTE
|
177
|
+
f"[{COLOR_PALETTE.S.STAKE_AMOUNT}]{Balance.get_unit(netuid)}[/{COLOR_PALETTE.S.STAKE_AMOUNT}] "
|
178
|
+
f"[{COLOR_PALETTE.S.STAKE_AMOUNT}](max: {current_balance})[/{COLOR_PALETTE.S.STAKE_AMOUNT}] "
|
170
179
|
f"or "
|
171
|
-
f"[{COLOR_PALETTE
|
180
|
+
f"[{COLOR_PALETTE.S.STAKE_AMOUNT}]'all'[/{COLOR_PALETTE.S.STAKE_AMOUNT}] "
|
172
181
|
f"for entire balance"
|
173
182
|
)
|
174
183
|
|
@@ -183,7 +192,7 @@ def prompt_stake_amount(
|
|
183
192
|
if amount > current_balance.tao:
|
184
193
|
console.print(
|
185
194
|
f"[red]Amount exceeds available balance of "
|
186
|
-
f"[{COLOR_PALETTE
|
195
|
+
f"[{COLOR_PALETTE.S.STAKE_AMOUNT}]{current_balance}[/{COLOR_PALETTE.S.STAKE_AMOUNT}]"
|
187
196
|
f"[/red]"
|
188
197
|
)
|
189
198
|
continue
|
@@ -270,8 +279,8 @@ async def stake_move_transfer_selection(
|
|
270
279
|
|
271
280
|
# Display available netuids for selected hotkey
|
272
281
|
table = Table(
|
273
|
-
title=f"\n[{COLOR_PALETTE
|
274
|
-
f"[{COLOR_PALETTE
|
282
|
+
title=f"\n[{COLOR_PALETTE.G.HEADER}]Available Stakes for Hotkey\n[/{COLOR_PALETTE.G.HEADER}]"
|
283
|
+
f"[{COLOR_PALETTE.G.HK}]{origin_hotkey_ss58}[/{COLOR_PALETTE.G.HK}]\n",
|
275
284
|
show_edge=False,
|
276
285
|
header_style="bold white",
|
277
286
|
border_style="bright_black",
|
@@ -279,7 +288,7 @@ async def stake_move_transfer_selection(
|
|
279
288
|
width=len(origin_hotkey_ss58) + 20,
|
280
289
|
)
|
281
290
|
table.add_column("Netuid", style="cyan")
|
282
|
-
table.add_column("Stake Amount", style=COLOR_PALETTE
|
291
|
+
table.add_column("Stake Amount", style=COLOR_PALETTE.STAKE.STAKE_AMOUNT)
|
283
292
|
|
284
293
|
available_netuids = []
|
285
294
|
for netuid in origin_hotkey_info["netuids"]:
|
@@ -347,8 +356,8 @@ async def stake_swap_selection(
|
|
347
356
|
|
348
357
|
# Display available stakes
|
349
358
|
table = Table(
|
350
|
-
title=f"\n[{COLOR_PALETTE
|
351
|
-
f"[{COLOR_PALETTE
|
359
|
+
title=f"\n[{COLOR_PALETTE.G.HEADER}]Available Stakes for Hotkey\n[/{COLOR_PALETTE.G.HEADER}]"
|
360
|
+
f"[{COLOR_PALETTE.G.HK}]{wallet.hotkey_str}: {wallet.hotkey.ss58_address}[/{COLOR_PALETTE.G.HK}]\n",
|
352
361
|
show_edge=False,
|
353
362
|
header_style="bold white",
|
354
363
|
border_style="bright_black",
|
@@ -366,7 +375,7 @@ async def stake_swap_selection(
|
|
366
375
|
for idx, (netuid, stake_info) in enumerate(sorted(hotkey_stakes.items())):
|
367
376
|
subnet_info = subnet_dict[netuid]
|
368
377
|
subnet_name_cell = (
|
369
|
-
f"[{COLOR_PALETTE
|
378
|
+
f"[{COLOR_PALETTE.G.SYM}]{subnet_info.symbol if netuid != 0 else 'τ'}[/{COLOR_PALETTE.G.SYM}]"
|
370
379
|
f" {get_subnet_name(subnet_info)}"
|
371
380
|
)
|
372
381
|
|
@@ -498,14 +507,28 @@ async def move_stake(
|
|
498
507
|
)
|
499
508
|
return False
|
500
509
|
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
510
|
+
call = await subtensor.substrate.compose_call(
|
511
|
+
call_module="SubtensorModule",
|
512
|
+
call_function="move_stake",
|
513
|
+
call_params={
|
514
|
+
"origin_hotkey": origin_hotkey,
|
515
|
+
"origin_netuid": origin_netuid,
|
516
|
+
"destination_hotkey": destination_hotkey,
|
517
|
+
"destination_netuid": destination_netuid,
|
518
|
+
"alpha_amount": amount_to_move_as_balance.rao,
|
519
|
+
},
|
520
|
+
)
|
521
|
+
stake_fee, extrinsic_fee = await asyncio.gather(
|
522
|
+
subtensor.get_stake_fee(
|
523
|
+
origin_hotkey_ss58=origin_hotkey,
|
524
|
+
origin_netuid=origin_netuid,
|
525
|
+
origin_coldkey_ss58=wallet.coldkeypub.ss58_address,
|
526
|
+
destination_hotkey_ss58=destination_hotkey,
|
527
|
+
destination_netuid=destination_netuid,
|
528
|
+
destination_coldkey_ss58=wallet.coldkeypub.ss58_address,
|
529
|
+
amount=amount_to_move_as_balance.rao,
|
530
|
+
),
|
531
|
+
subtensor.get_extrinsic_fee(call, wallet.coldkeypub),
|
509
532
|
)
|
510
533
|
|
511
534
|
# Display stake movement details
|
@@ -519,6 +542,7 @@ async def move_stake(
|
|
519
542
|
destination_hotkey=destination_hotkey,
|
520
543
|
amount_to_move=amount_to_move_as_balance,
|
521
544
|
stake_fee=stake_fee,
|
545
|
+
extrinsic_fee=extrinsic_fee,
|
522
546
|
)
|
523
547
|
except ValueError:
|
524
548
|
return False
|
@@ -529,20 +553,10 @@ async def move_stake(
|
|
529
553
|
if not unlock_key(wallet).success:
|
530
554
|
return False
|
531
555
|
with console.status(
|
532
|
-
f"\n:satellite: Moving [blue]{amount_to_move_as_balance}[/blue] from [blue]{origin_hotkey}[/blue] on netuid:
|
556
|
+
f"\n:satellite: Moving [blue]{amount_to_move_as_balance}[/blue] from [blue]{origin_hotkey}[/blue] on netuid: "
|
557
|
+
f"[blue]{origin_netuid}[/blue] \nto "
|
533
558
|
f"[blue]{destination_hotkey}[/blue] on netuid: [blue]{destination_netuid}[/blue] ..."
|
534
559
|
):
|
535
|
-
call = await subtensor.substrate.compose_call(
|
536
|
-
call_module="SubtensorModule",
|
537
|
-
call_function="move_stake",
|
538
|
-
call_params={
|
539
|
-
"origin_hotkey": origin_hotkey,
|
540
|
-
"origin_netuid": origin_netuid,
|
541
|
-
"destination_hotkey": destination_hotkey,
|
542
|
-
"destination_netuid": destination_netuid,
|
543
|
-
"alpha_amount": amount_to_move_as_balance.rao,
|
544
|
-
},
|
545
|
-
)
|
546
560
|
extrinsic = await subtensor.substrate.create_signed_extrinsic(
|
547
561
|
call=call, keypair=wallet.coldkey, era={"period": era}
|
548
562
|
)
|
@@ -677,19 +691,33 @@ async def transfer_stake(
|
|
677
691
|
if amount_to_transfer > current_stake:
|
678
692
|
err_console.print(
|
679
693
|
f"[red]Not enough stake to transfer[/red]:\n"
|
680
|
-
f"Stake balance: [{COLOR_PALETTE
|
681
|
-
f"Transfer amount: [{COLOR_PALETTE
|
694
|
+
f"Stake balance: [{COLOR_PALETTE.S.STAKE_AMOUNT}]{current_stake}[/{COLOR_PALETTE.S.STAKE_AMOUNT}] < "
|
695
|
+
f"Transfer amount: [{COLOR_PALETTE.S.STAKE_AMOUNT}]{amount_to_transfer}[/{COLOR_PALETTE.S.STAKE_AMOUNT}]"
|
682
696
|
)
|
683
697
|
return False
|
684
698
|
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
699
|
+
call = await subtensor.substrate.compose_call(
|
700
|
+
call_module="SubtensorModule",
|
701
|
+
call_function="transfer_stake",
|
702
|
+
call_params={
|
703
|
+
"destination_coldkey": dest_coldkey_ss58,
|
704
|
+
"hotkey": origin_hotkey,
|
705
|
+
"origin_netuid": origin_netuid,
|
706
|
+
"destination_netuid": dest_netuid,
|
707
|
+
"alpha_amount": amount_to_transfer.rao,
|
708
|
+
},
|
709
|
+
)
|
710
|
+
stake_fee, extrinsic_fee = await asyncio.gather(
|
711
|
+
subtensor.get_stake_fee(
|
712
|
+
origin_hotkey_ss58=origin_hotkey,
|
713
|
+
origin_netuid=origin_netuid,
|
714
|
+
origin_coldkey_ss58=wallet.coldkeypub.ss58_address,
|
715
|
+
destination_hotkey_ss58=origin_hotkey,
|
716
|
+
destination_netuid=dest_netuid,
|
717
|
+
destination_coldkey_ss58=dest_coldkey_ss58,
|
718
|
+
amount=amount_to_transfer.rao,
|
719
|
+
),
|
720
|
+
subtensor.get_extrinsic_fee(call, wallet.coldkeypub),
|
693
721
|
)
|
694
722
|
|
695
723
|
# Display stake movement details
|
@@ -703,6 +731,7 @@ async def transfer_stake(
|
|
703
731
|
destination_hotkey=origin_hotkey,
|
704
732
|
amount_to_move=amount_to_transfer,
|
705
733
|
stake_fee=stake_fee,
|
734
|
+
extrinsic_fee=extrinsic_fee,
|
706
735
|
)
|
707
736
|
except ValueError:
|
708
737
|
return False
|
@@ -715,18 +744,6 @@ async def transfer_stake(
|
|
715
744
|
return False
|
716
745
|
|
717
746
|
with console.status("\n:satellite: Transferring stake ..."):
|
718
|
-
call = await subtensor.substrate.compose_call(
|
719
|
-
call_module="SubtensorModule",
|
720
|
-
call_function="transfer_stake",
|
721
|
-
call_params={
|
722
|
-
"destination_coldkey": dest_coldkey_ss58,
|
723
|
-
"hotkey": origin_hotkey,
|
724
|
-
"origin_netuid": origin_netuid,
|
725
|
-
"destination_netuid": dest_netuid,
|
726
|
-
"alpha_amount": amount_to_transfer.rao,
|
727
|
-
},
|
728
|
-
)
|
729
|
-
|
730
747
|
extrinsic = await subtensor.substrate.create_signed_extrinsic(
|
731
748
|
call=call, keypair=wallet.coldkey, era={"period": era}
|
732
749
|
)
|
@@ -846,19 +863,32 @@ async def swap_stake(
|
|
846
863
|
if amount_to_swap > current_stake:
|
847
864
|
err_console.print(
|
848
865
|
f"[red]Not enough stake to swap[/red]:\n"
|
849
|
-
f"Stake balance: [{COLOR_PALETTE
|
850
|
-
f"Swap amount: [{COLOR_PALETTE
|
866
|
+
f"Stake balance: [{COLOR_PALETTE.S.STAKE_AMOUNT}]{current_stake}[/{COLOR_PALETTE.S.STAKE_AMOUNT}] < "
|
867
|
+
f"Swap amount: [{COLOR_PALETTE.S.STAKE_AMOUNT}]{amount_to_swap}[/{COLOR_PALETTE.S.STAKE_AMOUNT}]"
|
851
868
|
)
|
852
869
|
return False
|
853
870
|
|
854
|
-
|
855
|
-
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
|
871
|
+
call = await subtensor.substrate.compose_call(
|
872
|
+
call_module="SubtensorModule",
|
873
|
+
call_function="swap_stake",
|
874
|
+
call_params={
|
875
|
+
"hotkey": hotkey_ss58,
|
876
|
+
"origin_netuid": origin_netuid,
|
877
|
+
"destination_netuid": destination_netuid,
|
878
|
+
"alpha_amount": amount_to_swap.rao,
|
879
|
+
},
|
880
|
+
)
|
881
|
+
stake_fee, extrinsic_fee = await asyncio.gather(
|
882
|
+
subtensor.get_stake_fee(
|
883
|
+
origin_hotkey_ss58=hotkey_ss58,
|
884
|
+
origin_netuid=origin_netuid,
|
885
|
+
origin_coldkey_ss58=wallet.coldkeypub.ss58_address,
|
886
|
+
destination_hotkey_ss58=hotkey_ss58,
|
887
|
+
destination_netuid=destination_netuid,
|
888
|
+
destination_coldkey_ss58=wallet.coldkeypub.ss58_address,
|
889
|
+
amount=amount_to_swap.rao,
|
890
|
+
),
|
891
|
+
subtensor.get_extrinsic_fee(call, wallet.coldkeypub),
|
862
892
|
)
|
863
893
|
|
864
894
|
# Display stake movement details
|
@@ -872,6 +902,7 @@ async def swap_stake(
|
|
872
902
|
destination_hotkey=hotkey_ss58,
|
873
903
|
amount_to_move=amount_to_swap,
|
874
904
|
stake_fee=stake_fee,
|
905
|
+
extrinsic_fee=extrinsic_fee,
|
875
906
|
)
|
876
907
|
except ValueError:
|
877
908
|
return False
|
@@ -887,17 +918,6 @@ async def swap_stake(
|
|
887
918
|
f"\n:satellite: Swapping stake from netuid [blue]{origin_netuid}[/blue] "
|
888
919
|
f"to netuid [blue]{destination_netuid}[/blue]..."
|
889
920
|
):
|
890
|
-
call = await subtensor.substrate.compose_call(
|
891
|
-
call_module="SubtensorModule",
|
892
|
-
call_function="swap_stake",
|
893
|
-
call_params={
|
894
|
-
"hotkey": hotkey_ss58,
|
895
|
-
"origin_netuid": origin_netuid,
|
896
|
-
"destination_netuid": destination_netuid,
|
897
|
-
"alpha_amount": amount_to_swap.rao,
|
898
|
-
},
|
899
|
-
)
|
900
|
-
|
901
921
|
extrinsic = await subtensor.substrate.create_signed_extrinsic(
|
902
922
|
call=call, keypair=wallet.coldkey, era={"period": era}
|
903
923
|
)
|
@@ -47,6 +47,7 @@ async def unstake(
|
|
47
47
|
era: int,
|
48
48
|
):
|
49
49
|
"""Unstake from hotkey(s)."""
|
50
|
+
|
50
51
|
with console.status(
|
51
52
|
f"Retrieving subnet data & identities from {subtensor.network}...",
|
52
53
|
spinner="earth",
|
@@ -82,7 +83,7 @@ async def unstake(
|
|
82
83
|
hotkey_to_unstake_all = hotkeys_to_unstake_from[0]
|
83
84
|
unstake_all_alpha = Confirm.ask(
|
84
85
|
"\nDo you want to:\n"
|
85
|
-
"[blue]Yes[/blue]: Unstake from all subnets and automatically
|
86
|
+
"[blue]Yes[/blue]: Unstake from all subnets and automatically re-stake to subnet 0 (root)\n"
|
86
87
|
"[blue]No[/blue]: Unstake everything (including subnet 0)",
|
87
88
|
default=True,
|
88
89
|
)
|
@@ -210,8 +211,38 @@ async def unstake(
|
|
210
211
|
|
211
212
|
try:
|
212
213
|
current_price = subnet_info.price.tao
|
214
|
+
if safe_staking:
|
215
|
+
if subnet_info.is_dynamic:
|
216
|
+
price_with_tolerance = current_price * (1 - rate_tolerance)
|
217
|
+
rate_with_tolerance = price_with_tolerance
|
218
|
+
price_limit = Balance.from_tao(
|
219
|
+
rate_with_tolerance
|
220
|
+
) # Actual price to pass to extrinsic
|
221
|
+
else:
|
222
|
+
price_limit = Balance.from_rao(1)
|
223
|
+
extrinsic_fee = await _get_extrinsic_fee(
|
224
|
+
"unstake_safe",
|
225
|
+
wallet,
|
226
|
+
subtensor,
|
227
|
+
hotkey_ss58=staking_address_ss58,
|
228
|
+
amount=amount_to_unstake_as_balance,
|
229
|
+
netuid=netuid,
|
230
|
+
price_limit=price_limit,
|
231
|
+
allow_partial_stake=allow_partial_stake,
|
232
|
+
)
|
233
|
+
else:
|
234
|
+
extrinsic_fee = await _get_extrinsic_fee(
|
235
|
+
"unstake",
|
236
|
+
wallet,
|
237
|
+
subtensor,
|
238
|
+
hotkey_ss58=staking_address_ss58,
|
239
|
+
netuid=netuid,
|
240
|
+
amount=amount_to_unstake_as_balance,
|
241
|
+
)
|
213
242
|
rate = current_price
|
214
|
-
received_amount =
|
243
|
+
received_amount = (
|
244
|
+
(amount_to_unstake_as_balance - stake_fee) * rate
|
245
|
+
) - extrinsic_fee
|
215
246
|
except ValueError:
|
216
247
|
continue
|
217
248
|
total_received_amount += received_amount
|
@@ -233,8 +264,9 @@ async def unstake(
|
|
233
264
|
staking_address_name, # Hotkey Name
|
234
265
|
str(amount_to_unstake_as_balance), # Amount to Unstake
|
235
266
|
f"{subnet_info.price.tao:.6f}"
|
236
|
-
+ f"({Balance.get_unit(
|
267
|
+
+ f"(τ/{Balance.get_unit(netuid)})", # Rate
|
237
268
|
str(stake_fee.set_unit(netuid)), # Fee
|
269
|
+
str(extrinsic_fee), # Extrinsic fee
|
238
270
|
str(received_amount), # Received Amount
|
239
271
|
# slippage_pct, # Slippage Percent
|
240
272
|
]
|
@@ -431,10 +463,15 @@ async def unstake_all(
|
|
431
463
|
style=COLOR_PALETTE["POOLS"]["RATE"],
|
432
464
|
)
|
433
465
|
table.add_column(
|
434
|
-
f"Fee ({Balance.
|
466
|
+
f"Fee ({Balance.get_unit(1)})",
|
435
467
|
justify="center",
|
436
468
|
style=COLOR_PALETTE["STAKE"]["STAKE_AMOUNT"],
|
437
469
|
)
|
470
|
+
table.add_column(
|
471
|
+
"Extrinsic Fee (τ)",
|
472
|
+
justify="center",
|
473
|
+
style=COLOR_PALETTE.STAKE.TAO,
|
474
|
+
)
|
438
475
|
table.add_column(
|
439
476
|
f"Received ({Balance.unit})",
|
440
477
|
justify="center",
|
@@ -467,8 +504,17 @@ async def unstake_all(
|
|
467
504
|
|
468
505
|
try:
|
469
506
|
current_price = subnet_info.price.tao
|
507
|
+
extrinsic_type = (
|
508
|
+
"unstake_all" if not unstake_all_alpha else "unstake_all_alpha"
|
509
|
+
)
|
510
|
+
extrinsic_fee = await _get_extrinsic_fee(
|
511
|
+
extrinsic_type,
|
512
|
+
wallet,
|
513
|
+
subtensor,
|
514
|
+
hotkey_ss58=stake.hotkey_ss58,
|
515
|
+
)
|
470
516
|
rate = current_price
|
471
|
-
received_amount = stake_amount * rate -
|
517
|
+
received_amount = ((stake_amount - stake_fee) * rate) - extrinsic_fee
|
472
518
|
|
473
519
|
if received_amount < Balance.from_tao(0):
|
474
520
|
print_error("Not enough Alpha to pay the transaction fee.")
|
@@ -485,6 +531,7 @@ async def unstake_all(
|
|
485
531
|
f"{float(subnet_info.price):.6f}"
|
486
532
|
+ f"({Balance.get_unit(0)}/{Balance.get_unit(stake.netuid)})",
|
487
533
|
str(stake_fee),
|
534
|
+
str(extrinsic_fee),
|
488
535
|
str(received_amount),
|
489
536
|
)
|
490
537
|
console.print(table)
|
@@ -830,6 +877,63 @@ async def _unstake_all_extrinsic(
|
|
830
877
|
err_out(f"{failure_prelude} with error: {str(e)}")
|
831
878
|
|
832
879
|
|
880
|
+
async def _get_extrinsic_fee(
|
881
|
+
_type: str,
|
882
|
+
wallet: Wallet,
|
883
|
+
subtensor: "SubtensorInterface",
|
884
|
+
hotkey_ss58: str,
|
885
|
+
netuid: Optional[int] = None,
|
886
|
+
amount: Optional[Balance] = None,
|
887
|
+
price_limit: Optional[Balance] = None,
|
888
|
+
allow_partial_stake: bool = False,
|
889
|
+
) -> Balance:
|
890
|
+
"""
|
891
|
+
Retrieves the extrinsic fee for a given unstaking call.
|
892
|
+
Args:
|
893
|
+
_type: 'unstake', 'unstake_safe', 'unstake_all', 'unstake_all_alpha' depending on the specific
|
894
|
+
extrinsic to be called
|
895
|
+
wallet: Wallet object
|
896
|
+
subtensor: SubtensorInterface object
|
897
|
+
hotkey_ss58: the hotkey ss58 to unstake from
|
898
|
+
netuid: the netuid from which to remove the stake
|
899
|
+
amount: the amount of stake to remove
|
900
|
+
price_limit: the price limit
|
901
|
+
allow_partial_stake: whether to allow partial unstaking
|
902
|
+
|
903
|
+
Returns:
|
904
|
+
Balance object representing the extrinsic fee.
|
905
|
+
"""
|
906
|
+
lookup_table = {
|
907
|
+
"unstake": lambda: (
|
908
|
+
"remove_stake",
|
909
|
+
{
|
910
|
+
"hotkey": hotkey_ss58,
|
911
|
+
"netuid": netuid,
|
912
|
+
"amount_unstaked": amount.rao,
|
913
|
+
},
|
914
|
+
),
|
915
|
+
"unstake_safe": lambda: (
|
916
|
+
"remove_stake_limit",
|
917
|
+
{
|
918
|
+
"hotkey": hotkey_ss58,
|
919
|
+
"netuid": netuid,
|
920
|
+
"amount_unstaked": amount.rao,
|
921
|
+
"limit_price": price_limit,
|
922
|
+
"allow_partial": allow_partial_stake,
|
923
|
+
},
|
924
|
+
),
|
925
|
+
"unstake_all": lambda: ("unstake_all", {"hotkey": hotkey_ss58}),
|
926
|
+
"unstake_all_alpha": lambda: ("unstake_all_alpha", {"hotkey": hotkey_ss58}),
|
927
|
+
}
|
928
|
+
call_fn, call_params = lookup_table[_type]()
|
929
|
+
call = await subtensor.substrate.compose_call(
|
930
|
+
call_module="SubtensorModule",
|
931
|
+
call_function=call_fn,
|
932
|
+
call_params=call_params,
|
933
|
+
)
|
934
|
+
return await subtensor.get_extrinsic_fee(call, wallet.coldkeypub)
|
935
|
+
|
936
|
+
|
833
937
|
# Helpers
|
834
938
|
async def _unstake_selection(
|
835
939
|
dynamic_info,
|
@@ -1184,7 +1288,7 @@ def _create_unstake_table(
|
|
1184
1288
|
style=COLOR_PALETTE["POOLS"]["TAO"],
|
1185
1289
|
)
|
1186
1290
|
table.add_column(
|
1187
|
-
f"Rate ({Balance.get_unit(
|
1291
|
+
f"Rate (τ/{Balance.get_unit(1)})",
|
1188
1292
|
justify="center",
|
1189
1293
|
style=COLOR_PALETTE["POOLS"]["RATE"],
|
1190
1294
|
)
|
@@ -1194,7 +1298,10 @@ def _create_unstake_table(
|
|
1194
1298
|
style=COLOR_PALETTE["STAKE"]["STAKE_AMOUNT"],
|
1195
1299
|
)
|
1196
1300
|
table.add_column(
|
1197
|
-
|
1301
|
+
"Extrinsic Fee (τ)", justify="center", style=COLOR_PALETTE.STAKE.TAO
|
1302
|
+
)
|
1303
|
+
table.add_column(
|
1304
|
+
"Received (τ)",
|
1198
1305
|
justify="center",
|
1199
1306
|
style=COLOR_PALETTE["POOLS"]["TAO_EQUIV"],
|
1200
1307
|
footer=str(total_received_amount),
|
@@ -1408,6 +1408,7 @@ async def transfer(
|
|
1408
1408
|
destination: str,
|
1409
1409
|
amount: float,
|
1410
1410
|
transfer_all: bool,
|
1411
|
+
allow_death: bool,
|
1411
1412
|
era: int,
|
1412
1413
|
prompt: bool,
|
1413
1414
|
json_output: bool,
|
@@ -1419,6 +1420,7 @@ async def transfer(
|
|
1419
1420
|
destination=destination,
|
1420
1421
|
amount=Balance.from_tao(amount),
|
1421
1422
|
transfer_all=transfer_all,
|
1423
|
+
allow_death=allow_death,
|
1422
1424
|
era=era,
|
1423
1425
|
prompt=prompt,
|
1424
1426
|
)
|
@@ -1800,10 +1802,96 @@ async def sign(
|
|
1800
1802
|
)
|
1801
1803
|
|
1802
1804
|
signed_message = keypair.sign(message.encode("utf-8")).hex()
|
1803
|
-
|
1805
|
+
signer_address = keypair.ss58_address
|
1806
|
+
console.print("[dark_sea_green3]Message signed successfully!\n")
|
1807
|
+
|
1804
1808
|
if json_output:
|
1805
|
-
json_console.print(
|
1806
|
-
|
1809
|
+
json_console.print(
|
1810
|
+
json.dumps(
|
1811
|
+
{"signed_message": signed_message, "signer_address": signer_address}
|
1812
|
+
)
|
1813
|
+
)
|
1814
|
+
else:
|
1815
|
+
console.print(f"[yellow]Signature:[/yellow]\n{signed_message}")
|
1816
|
+
console.print(f"[yellow]Signer address:[/yellow] {signer_address}")
|
1817
|
+
|
1818
|
+
|
1819
|
+
async def verify(
|
1820
|
+
message: str,
|
1821
|
+
signature: str,
|
1822
|
+
public_key_or_ss58: str,
|
1823
|
+
json_output: bool = False,
|
1824
|
+
):
|
1825
|
+
"""Verify a message signature using a public key or SS58 address."""
|
1826
|
+
|
1827
|
+
if is_valid_ss58_address(public_key_or_ss58):
|
1828
|
+
print_verbose(f"[blue]SS58 address detected:[/blue] {public_key_or_ss58}")
|
1829
|
+
keypair = Keypair(ss58_address=public_key_or_ss58)
|
1830
|
+
signer_address = public_key_or_ss58
|
1831
|
+
else:
|
1832
|
+
try:
|
1833
|
+
public_key_hex = public_key_or_ss58.strip().lower()
|
1834
|
+
if public_key_hex.startswith("0x"):
|
1835
|
+
public_key_hex = public_key_hex[2:]
|
1836
|
+
if len(public_key_hex) == 64:
|
1837
|
+
bytes.fromhex(public_key_hex)
|
1838
|
+
print_verbose("[blue]Hex public key detected[/blue] (64 characters)")
|
1839
|
+
keypair = Keypair(public_key=public_key_hex)
|
1840
|
+
signer_address = keypair.ss58_address
|
1841
|
+
print_verbose(
|
1842
|
+
f"[blue]Corresponding SS58 address:[/blue] {signer_address}"
|
1843
|
+
)
|
1844
|
+
else:
|
1845
|
+
raise ValueError("Public key must be 32 bytes (64 hex characters)")
|
1846
|
+
|
1847
|
+
except (ValueError, TypeError) as e:
|
1848
|
+
if json_output:
|
1849
|
+
json_console.print(
|
1850
|
+
json.dumps(
|
1851
|
+
{
|
1852
|
+
"verified": False,
|
1853
|
+
"error": f"Invalid public key or SS58 address: {str(e)}",
|
1854
|
+
}
|
1855
|
+
)
|
1856
|
+
)
|
1857
|
+
else:
|
1858
|
+
err_console.print(
|
1859
|
+
f":cross_mark: Invalid SS58 address or hex public key (64 chars, with or without 0x prefix)- {str(e)}"
|
1860
|
+
)
|
1861
|
+
return False
|
1862
|
+
|
1863
|
+
try:
|
1864
|
+
signature_bytes = bytes.fromhex(signature.strip().lower().replace("0x", ""))
|
1865
|
+
except ValueError as e:
|
1866
|
+
if json_output:
|
1867
|
+
json_console.print(
|
1868
|
+
json.dumps(
|
1869
|
+
{
|
1870
|
+
"verified": False,
|
1871
|
+
"error": f"Invalid signature format: {str(e)}",
|
1872
|
+
}
|
1873
|
+
)
|
1874
|
+
)
|
1875
|
+
else:
|
1876
|
+
err_console.print(f"[red]:cross_mark: Invalid signature format: {str(e)}")
|
1877
|
+
return False
|
1878
|
+
|
1879
|
+
is_valid = keypair.verify(message.encode("utf-8"), signature_bytes)
|
1880
|
+
|
1881
|
+
if json_output:
|
1882
|
+
json_console.print(
|
1883
|
+
json.dumps(
|
1884
|
+
{"verified": is_valid, "signer": signer_address, "message": message}
|
1885
|
+
)
|
1886
|
+
)
|
1887
|
+
else:
|
1888
|
+
if is_valid:
|
1889
|
+
console.print("[dark_sea_green3]Signature is valid!\n")
|
1890
|
+
console.print(f"[yellow]Signer:[/yellow] {signer_address}")
|
1891
|
+
else:
|
1892
|
+
err_console.print(":cross_mark: [red]Signature verification failed!")
|
1893
|
+
|
1894
|
+
return is_valid
|
1807
1895
|
|
1808
1896
|
|
1809
1897
|
async def schedule_coldkey_swap(
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: bittensor-cli
|
3
|
-
Version: 9.
|
3
|
+
Version: 9.9.0
|
4
4
|
Summary: Bittensor CLI
|
5
5
|
Author: bittensor.com
|
6
6
|
Project-URL: homepage, https://github.com/opentensor/btcli
|
@@ -8,7 +8,7 @@ Project-URL: Repository, https://github.com/opentensor/btcli
|
|
8
8
|
Requires-Python: <3.14,>=3.9
|
9
9
|
Description-Content-Type: text/markdown
|
10
10
|
Requires-Dist: wheel
|
11
|
-
Requires-Dist: async-substrate-interface>=1.
|
11
|
+
Requires-Dist: async-substrate-interface>=1.4.2
|
12
12
|
Requires-Dist: aiohttp~=3.10.2
|
13
13
|
Requires-Dist: backoff~=2.2.1
|
14
14
|
Requires-Dist: click<8.2.0
|
@@ -1,5 +1,5 @@
|
|
1
1
|
bittensor_cli/__init__.py,sha256=Lpv4NkbAQgwrfqFOnTMuR_S-fqGdaWCSLhxnFnGTHM0,1232
|
2
|
-
bittensor_cli/cli.py,sha256=
|
2
|
+
bittensor_cli/cli.py,sha256=lG3LyOXXiOOEIPgiW9qnqmKnH2wa598iJ6pf-eNCveg,234995
|
3
3
|
bittensor_cli/doc_generation_helper.py,sha256=GexqjEIKulWg84hpNBEchJ840oOgOi7DWpt447nsdNI,91
|
4
4
|
bittensor_cli/version.py,sha256=dU1xsa3mG5FPdhzvqlzDByNcCHmzcFQH3q1pQr4u76g,720
|
5
5
|
bittensor_cli/src/__init__.py,sha256=QCR6kt9rbUiLC4gkvczl47eg725nqSFK4Xr7_mz3e_s,33171
|
@@ -8,12 +8,12 @@ bittensor_cli/src/bittensor/balances.py,sha256=q5KkxF8wmUguWAFddEKstfDKTxPe5ISHp
|
|
8
8
|
bittensor_cli/src/bittensor/chain_data.py,sha256=_GATOCacWMN2jRurzn3hffJiq_l7AznGRkKePTAflwQ,44420
|
9
9
|
bittensor_cli/src/bittensor/minigraph.py,sha256=BIzmSVLfBYiRAeGD_i1LAC8Cw7zxp38a91SIFEPMqYc,10479
|
10
10
|
bittensor_cli/src/bittensor/networking.py,sha256=pZLMs8YXpZzDMLXWMBb_Bj6TVkm_q9khyY-lnbwVMuE,462
|
11
|
-
bittensor_cli/src/bittensor/subtensor_interface.py,sha256=
|
11
|
+
bittensor_cli/src/bittensor/subtensor_interface.py,sha256=37BB5dfcRE6QrAKwuYs9XlaLs36xHrhJ3wCsWd-rXek,62672
|
12
12
|
bittensor_cli/src/bittensor/utils.py,sha256=WfxxQnekVy7nbB2jsPrRC4E7SFIvAjDJJjq3glhKMSc,48270
|
13
13
|
bittensor_cli/src/bittensor/extrinsics/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
14
14
|
bittensor_cli/src/bittensor/extrinsics/registration.py,sha256=7ybqpJ7-Z_PTgj4boCOFxgfjMBCHv0JE7MNpJzJmyDc,66360
|
15
15
|
bittensor_cli/src/bittensor/extrinsics/root.py,sha256=C9WPssL2HpNK8u_IFPPr8TrdFgbLPTfkbAYzalfmbRM,19188
|
16
|
-
bittensor_cli/src/bittensor/extrinsics/transfer.py,sha256=
|
16
|
+
bittensor_cli/src/bittensor/extrinsics/transfer.py,sha256=S4HFvgwhUk-NQ26FOkg8m1ggAJ50jgPsW_isdAdS_rc,9235
|
17
17
|
bittensor_cli/src/bittensor/templates/main-filters.j2,sha256=ZM0l2exr6FKqw86GRyHHtw-h8Y5Ckgx-SPc-48gZoxI,914
|
18
18
|
bittensor_cli/src/bittensor/templates/main-header.j2,sha256=aZwB9NeEjNr0pM5mPYTYeky_031zAu6yErIMiwTPcwE,1684
|
19
19
|
bittensor_cli/src/bittensor/templates/neuron-details.j2,sha256=O_Mhg2E04BzW_cSGIldObhVTSRX29gDNqnimW4ty9rY,4848
|
@@ -30,22 +30,22 @@ bittensor_cli/src/bittensor/templates/view.js,sha256=QIPnPp9SuYS9wcl7cwL8nFzd8id
|
|
30
30
|
bittensor_cli/src/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
31
31
|
bittensor_cli/src/commands/sudo.py,sha256=zyv4sJM7sH73_c-mgskcnF2kQfJBJ8_xF74WrZFv7rY,33820
|
32
32
|
bittensor_cli/src/commands/view.py,sha256=9lx6vfOkel8KIefUhDNaBS_j5lNR2djcRFRbK4mbnDE,12535
|
33
|
-
bittensor_cli/src/commands/wallets.py,sha256=
|
33
|
+
bittensor_cli/src/commands/wallets.py,sha256=SIcEf7AkxonUAM3zYEaQUJTczCIY6iFtR3sTunhB1C8,74745
|
34
34
|
bittensor_cli/src/commands/weights.py,sha256=BCJm_mlw0pVK4YEZuEMqQBpvvOoB7B1rzdvMeN3uTfM,16503
|
35
35
|
bittensor_cli/src/commands/liquidity/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
36
36
|
bittensor_cli/src/commands/liquidity/liquidity.py,sha256=AXCjBvQb2gakP8n1z81npYkZ_QF5CQy7r82AMtQwXzk,21692
|
37
37
|
bittensor_cli/src/commands/liquidity/utils.py,sha256=egfZHnvBMc8ydntAHnU6V5Zyi-wLkomjNuucUj73aZQ,6361
|
38
38
|
bittensor_cli/src/commands/stake/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
39
|
-
bittensor_cli/src/commands/stake/add.py,sha256=
|
39
|
+
bittensor_cli/src/commands/stake/add.py,sha256=jdNOilpEsq8f7vJUQH1qFdiQ-HbSg6siYQl_CQdtlh0,29731
|
40
40
|
bittensor_cli/src/commands/stake/children_hotkeys.py,sha256=lMiV-Z3SGZUEapdy0LRthFLx0RlFK0KVxytE47ybdEc,31746
|
41
41
|
bittensor_cli/src/commands/stake/list.py,sha256=tzjhiJucXgOGaw7TGt420nGosH85AEjvOimP1XXV3Xs,29038
|
42
|
-
bittensor_cli/src/commands/stake/move.py,sha256=
|
43
|
-
bittensor_cli/src/commands/stake/remove.py,sha256=
|
42
|
+
bittensor_cli/src/commands/stake/move.py,sha256=RJiY_oSQUV-ZO04Y_PosrDqfz0JfdQX4ioB74f_7lU8,35144
|
43
|
+
bittensor_cli/src/commands/stake/remove.py,sha256=Xf_BWihl--4WGKPl6Wz4VkWus3JKEikHQKA0tbQTetM,51318
|
44
44
|
bittensor_cli/src/commands/subnets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
45
45
|
bittensor_cli/src/commands/subnets/price.py,sha256=akXkbilWjQYqTYvtOhXngX_cVkU0Mv-Gl3kjce6dtl0,21490
|
46
46
|
bittensor_cli/src/commands/subnets/subnets.py,sha256=ZeR7gxjtxi9eQdPLlIOmw3jDxDPnteppTPJ1-1y_TCs,94601
|
47
|
-
bittensor_cli-9.
|
48
|
-
bittensor_cli-9.
|
49
|
-
bittensor_cli-9.
|
50
|
-
bittensor_cli-9.
|
51
|
-
bittensor_cli-9.
|
47
|
+
bittensor_cli-9.9.0.dist-info/METADATA,sha256=3KHTWo7iaqwIrHfos259k3CbxZI9MBy_OwGxAZSf2rI,6601
|
48
|
+
bittensor_cli-9.9.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
49
|
+
bittensor_cli-9.9.0.dist-info/entry_points.txt,sha256=hBTLGLbVxmAKy69XSKaUZvjTCmyEzDGZKq4S8UOto8I,49
|
50
|
+
bittensor_cli-9.9.0.dist-info/top_level.txt,sha256=DvgvXpmTtI_Q1BbDZMlK90LFcGFCreN1daViEPV2iFw,14
|
51
|
+
bittensor_cli-9.9.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|