Skip to content

Commit 79e6639

Browse files
authored
Merge pull request #2993 from opentensor/fix/thewhaleking/transfers
Transfers improvements
2 parents a71cf44 + 10ab0d5 commit 79e6639

File tree

8 files changed

+263
-47
lines changed

8 files changed

+263
-47
lines changed

bittensor/core/async_subtensor.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@
9898
torch,
9999
u16_normalized_float,
100100
u64_normalized_float,
101+
get_transfer_fn_params,
101102
)
102103
from bittensor.core.extrinsics.asyncex.liquidity import (
103104
add_liquidity_extrinsic,
@@ -3134,7 +3135,7 @@ async def get_total_subnets(
31343135
return getattr(result, "value", None)
31353136

31363137
async def get_transfer_fee(
3137-
self, wallet: "Wallet", dest: str, value: Balance
3138+
self, wallet: "Wallet", dest: str, value: Balance, keep_alive: bool = True
31383139
) -> Balance:
31393140
"""
31403141
Calculates the transaction fee for transferring tokens from a wallet to a specified destination address. This
@@ -3146,6 +3147,8 @@ async def get_transfer_fee(
31463147
dest: The ``SS58`` address of the destination account.
31473148
value: The amount of tokens to be transferred, specified as a Balance object, or in Tao (float) or Rao
31483149
(int) units.
3150+
keep_alive: Whether the transfer fee should be calculated based on keeping the wallet alive (existential
3151+
deposit) or not.
31493152
31503153
Returns:
31513154
bittensor.utils.balance.Balance: The estimated transaction fee for the transfer, represented as a Balance
@@ -3155,12 +3158,15 @@ async def get_transfer_fee(
31553158
wallet has sufficient funds to cover both the transfer amount and the associated costs. This function provides
31563159
a crucial tool for managing financial operations within the Bittensor network.
31573160
"""
3158-
value = check_and_convert_to_balance(value)
3161+
if value is not None:
3162+
value = check_and_convert_to_balance(value)
3163+
call_params: dict[str, Union[int, str, bool]]
3164+
call_function, call_params = get_transfer_fn_params(value, dest, keep_alive)
31593165

31603166
call = await self.substrate.compose_call(
31613167
call_module="Balances",
3162-
call_function="transfer_keep_alive",
3163-
call_params={"dest": dest, "value": value.rao},
3168+
call_function=call_function,
3169+
call_params=call_params,
31643170
)
31653171

31663172
try:
@@ -5455,7 +5461,7 @@ async def transfer(
54555461
self,
54565462
wallet: "Wallet",
54575463
dest: str,
5458-
amount: Balance,
5464+
amount: Optional[Balance],
54595465
transfer_all: bool = False,
54605466
wait_for_inclusion: bool = True,
54615467
wait_for_finalization: bool = False,
@@ -5468,7 +5474,7 @@ async def transfer(
54685474
Arguments:
54695475
wallet: Source wallet for the transfer.
54705476
dest: Destination address for the transfer.
5471-
amount: Number of tokens to transfer.
5477+
amount: Number of tokens to transfer. `None` is transferring all.
54725478
transfer_all: Flag to transfer all tokens. Default is `False`.
54735479
wait_for_inclusion: Waits for the transaction to be included in a block. Defaults to `True`.
54745480
wait_for_finalization: Waits for the transaction to be finalized on the blockchain. Defaults to `False`.
@@ -5479,7 +5485,8 @@ async def transfer(
54795485
Returns:
54805486
`True` if the transferring was successful, otherwise `False`.
54815487
"""
5482-
amount = check_and_convert_to_balance(amount)
5488+
if amount is not None:
5489+
amount = check_and_convert_to_balance(amount)
54835490
return await transfer_extrinsic(
54845491
subtensor=self,
54855492
wallet=wallet,

bittensor/core/extrinsics/asyncex/transfer.py

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
get_explorer_url_for_network,
77
is_valid_bittensor_address_or_public_key,
88
unlock_key,
9+
get_transfer_fn_params,
910
)
1011
from bittensor.utils.balance import Balance
1112
from bittensor.utils.btlogging import logging
@@ -19,10 +20,11 @@ async def _do_transfer(
1920
subtensor: "AsyncSubtensor",
2021
wallet: "Wallet",
2122
destination: str,
22-
amount: "Balance",
23+
amount: Optional[Balance],
2324
wait_for_inclusion: bool = True,
2425
wait_for_finalization: bool = False,
2526
period: Optional[int] = None,
27+
keep_alive: bool = True,
2628
) -> tuple[bool, str, str]:
2729
"""
2830
Makes transfer from wallet to destination public key address.
@@ -39,14 +41,17 @@ async def _do_transfer(
3941
period (Optional[int]): The number of blocks during which the transaction will remain valid after it's submitted.
4042
If the transaction is not included in a block within that number of blocks, it will expire and be rejected.
4143
You can think of it as an expiration date for the transaction.
44+
keep_alive (bool): If `True`, will keep the existential deposit in the account.
4245
4346
Returns:
4447
success, block hash, formatted error message
4548
"""
49+
call_function, call_params = get_transfer_fn_params(amount, destination, keep_alive)
50+
4651
call = await subtensor.substrate.compose_call(
4752
call_module="Balances",
48-
call_function="transfer_keep_alive",
49-
call_params={"dest": destination, "value": amount.rao},
53+
call_function=call_function,
54+
call_params=call_params,
5055
)
5156

5257
success, message = await subtensor.sign_and_send_extrinsic(
@@ -73,7 +78,7 @@ async def transfer_extrinsic(
7378
subtensor: "AsyncSubtensor",
7479
wallet: "Wallet",
7580
dest: str,
76-
amount: "Balance",
81+
amount: Optional[Balance],
7782
transfer_all: bool = False,
7883
wait_for_inclusion: bool = True,
7984
wait_for_finalization: bool = False,
@@ -86,7 +91,8 @@ async def transfer_extrinsic(
8691
subtensor (bittensor.core.async_subtensor.AsyncSubtensor): initialized AsyncSubtensor object used for transfer
8792
wallet (bittensor_wallet.Wallet): Bittensor wallet object to make transfer from.
8893
dest (str): Destination public key address (ss58_address or ed25519) of recipient.
89-
amount (bittensor.utils.balance.Balance): Amount to stake as Bittensor balance.
94+
amount (Optional[bittensor.utils.balance.Balance]): Amount to stake as Bittensor balance. `None` if
95+
transferring all.
9096
transfer_all (bool): Whether to transfer all funds from this wallet to the destination address.
9197
wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning `True`, or returns
9298
`False` if the extrinsic fails to enter the block within the timeout.
@@ -102,6 +108,11 @@ async def transfer_extrinsic(
102108
finalization / inclusion, the response is `True`, regardless of its inclusion.
103109
"""
104110
destination = dest
111+
112+
if amount is None and not transfer_all:
113+
logging.error("If not transferring all, `amount` must be specified.")
114+
return False
115+
105116
# Validate destination address.
106117
if not is_valid_bittensor_address_or_public_key(destination):
107118
logging.error(
@@ -128,7 +139,7 @@ async def transfer_extrinsic(
128139
)
129140

130141
fee = await subtensor.get_transfer_fee(
131-
wallet=wallet, dest=destination, value=amount
142+
wallet=wallet, dest=destination, value=amount, keep_alive=keep_alive
132143
)
133144

134145
if not keep_alive:
@@ -137,12 +148,10 @@ async def transfer_extrinsic(
137148

138149
# Check if we have enough balance.
139150
if transfer_all is True:
140-
amount = account_balance - fee - existential_deposit
141-
if amount < Balance(0):
151+
if (account_balance - fee) < existential_deposit:
142152
logging.error("Not enough balance to transfer")
143153
return False
144-
145-
if account_balance < (amount + fee + existential_deposit):
154+
elif account_balance < (amount + fee + existential_deposit):
146155
logging.error(":cross_mark: [red]Not enough balance[/red]")
147156
logging.error(f"\t\tBalance:\t[blue]{account_balance}[/blue]")
148157
logging.error(f"\t\tAmount:\t[blue]{amount}[/blue]")
@@ -155,6 +164,7 @@ async def transfer_extrinsic(
155164
wallet=wallet,
156165
destination=destination,
157166
amount=amount,
167+
keep_alive=keep_alive,
158168
wait_for_finalization=wait_for_finalization,
159169
wait_for_inclusion=wait_for_inclusion,
160170
period=period,

bittensor/core/extrinsics/transfer.py

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
is_valid_bittensor_address_or_public_key,
66
unlock_key,
77
get_explorer_url_for_network,
8+
get_transfer_fn_params,
89
)
910
from bittensor.utils.balance import Balance
1011
from bittensor.utils.btlogging import logging
@@ -18,10 +19,11 @@ def _do_transfer(
1819
subtensor: "Subtensor",
1920
wallet: "Wallet",
2021
destination: str,
21-
amount: Balance,
22+
amount: Optional[Balance],
2223
wait_for_inclusion: bool = True,
2324
wait_for_finalization: bool = False,
2425
period: Optional[int] = None,
26+
keep_alive: bool = True,
2527
) -> tuple[bool, str, str]:
2628
"""
2729
Makes transfer from wallet to destination public key address.
@@ -30,22 +32,25 @@ def _do_transfer(
3032
subtensor (bittensor.core.subtensor.Subtensor): the Subtensor object used for transfer
3133
wallet (bittensor_wallet.Wallet): Bittensor wallet object to make transfer from.
3234
destination (str): Destination public key address (ss58_address or ed25519) of recipient.
33-
amount (bittensor.utils.balance.Balance): Amount to stake as Bittensor balance.
35+
amount (bittensor.utils.balance.Balance): Amount to stake as Bittensor balance. `None` if transferring all.
3436
wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning `True`, or returns
3537
`False` if the extrinsic fails to enter the block within the timeout.
3638
wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning
3739
`True`, or returns `False` if the extrinsic fails to be finalized within the timeout.
3840
period (Optional[int]): The number of blocks during which the transaction will remain valid after it's submitted.
3941
If the transaction is not included in a block within that number of blocks, it will expire and be rejected.
4042
You can think of it as an expiration date for the transaction.
43+
keep_alive (bool): If `True`, will keep the existential deposit in the account.
4144
4245
Returns:
4346
success, block hash, formatted error message
4447
"""
48+
call_function, call_params = get_transfer_fn_params(amount, destination, keep_alive)
49+
4550
call = subtensor.substrate.compose_call(
4651
call_module="Balances",
47-
call_function="transfer_keep_alive",
48-
call_params={"dest": destination, "value": amount.rao},
52+
call_function=call_function,
53+
call_params=call_params,
4954
)
5055

5156
success, message = subtensor.sign_and_send_extrinsic(
@@ -72,7 +77,7 @@ def transfer_extrinsic(
7277
subtensor: "Subtensor",
7378
wallet: "Wallet",
7479
dest: str,
75-
amount: Balance,
80+
amount: Optional[Balance],
7681
transfer_all: bool = False,
7782
wait_for_inclusion: bool = True,
7883
wait_for_finalization: bool = False,
@@ -85,7 +90,7 @@ def transfer_extrinsic(
8590
subtensor (bittensor.core.subtensor.Subtensor): the Subtensor object used for transfer
8691
wallet (bittensor_wallet.Wallet): Bittensor wallet object to make transfer from.
8792
dest (str): Destination public key address (ss58_address or ed25519) of recipient.
88-
amount (bittensor.utils.balance.Balance): Amount to stake as Bittensor balance.
93+
amount (bittensor.utils.balance.Balance): Amount to stake as Bittensor balance. `None` if transferring all.
8994
transfer_all (bool): Whether to transfer all funds from this wallet to the destination address.
9095
wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning `True`, or returns
9196
`False` if the extrinsic fails to enter the block within the timeout.
@@ -100,6 +105,10 @@ def transfer_extrinsic(
100105
success (bool): Flag is `True` if extrinsic was finalized or included in the block. If we did not wait for
101106
finalization / inclusion, the response is `True`, regardless of its inclusion.
102107
"""
108+
if amount is None and not transfer_all:
109+
logging.error("If not transferring all, `amount` must be specified.")
110+
return False
111+
103112
# Validate destination address.
104113
if not is_valid_bittensor_address_or_public_key(dest):
105114
logging.error(
@@ -127,16 +136,16 @@ def transfer_extrinsic(
127136
else:
128137
existential_deposit = subtensor.get_existential_deposit(block=block)
129138

130-
fee = subtensor.get_transfer_fee(wallet=wallet, dest=dest, value=amount)
139+
fee = subtensor.get_transfer_fee(
140+
wallet=wallet, dest=dest, value=amount, keep_alive=keep_alive
141+
)
131142

132143
# Check if we have enough balance.
133144
if transfer_all is True:
134-
amount = account_balance - fee - existential_deposit
135-
if amount < Balance(0):
145+
if (account_balance - fee) < existential_deposit:
136146
logging.error("Not enough balance to transfer")
137147
return False
138-
139-
if account_balance < (amount + fee + existential_deposit):
148+
elif account_balance < (amount + fee + existential_deposit):
140149
logging.error(":cross_mark: [red]Not enough balance[/red]")
141150
logging.error(f"\t\tBalance:\t[blue]{account_balance}[/blue]")
142151
logging.error(f"\t\tAmount:\t[blue]{amount}[/blue]")
@@ -149,6 +158,7 @@ def transfer_extrinsic(
149158
wallet=wallet,
150159
destination=dest,
151160
amount=amount,
161+
keep_alive=keep_alive,
152162
wait_for_finalization=wait_for_finalization,
153163
wait_for_inclusion=wait_for_inclusion,
154164
period=period,

bittensor/core/subtensor.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@
107107
u16_normalized_float,
108108
u64_normalized_float,
109109
deprecated_message,
110+
get_transfer_fn_params,
110111
)
111112
from bittensor.utils.balance import (
112113
Balance,
@@ -2243,7 +2244,13 @@ def get_total_subnets(self, block: Optional[int] = None) -> Optional[int]:
22432244
)
22442245
return getattr(result, "value", None)
22452246

2246-
def get_transfer_fee(self, wallet: "Wallet", dest: str, value: Balance) -> Balance:
2247+
def get_transfer_fee(
2248+
self,
2249+
wallet: "Wallet",
2250+
dest: str,
2251+
value: Optional[Balance],
2252+
keep_alive: bool = True,
2253+
) -> Balance:
22472254
"""
22482255
Calculates the transaction fee for transferring tokens from a wallet to a specified destination address. This
22492256
function simulates the transfer to estimate the associated cost, taking into account the current network
@@ -2254,6 +2261,8 @@ def get_transfer_fee(self, wallet: "Wallet", dest: str, value: Balance) -> Balan
22542261
dest (str): The ``SS58`` address of the destination account.
22552262
value (Union[bittensor.utils.balance.Balance, float, int]): The amount of tokens to be transferred,
22562263
specified as a Balance object, or in Tao (float) or Rao (int) units.
2264+
keep_alive: Whether the transfer fee should be calculated based on keeping the wallet alive (existential
2265+
deposit) or not.
22572266
22582267
Returns:
22592268
bittensor.utils.balance.Balance: The estimated transaction fee for the transfer, represented as a Balance
@@ -2263,11 +2272,15 @@ def get_transfer_fee(self, wallet: "Wallet", dest: str, value: Balance) -> Balan
22632272
has sufficient funds to cover both the transfer amount and the associated costs. This function provides a
22642273
crucial tool for managing financial operations within the Bittensor network.
22652274
"""
2266-
value = check_and_convert_to_balance(value)
2275+
if value is not None:
2276+
value = check_and_convert_to_balance(value)
2277+
call_params: dict[str, Union[int, str, bool]]
2278+
call_function, call_params = get_transfer_fn_params(value, dest, keep_alive)
2279+
22672280
call = self.substrate.compose_call(
22682281
call_module="Balances",
2269-
call_function="transfer_keep_alive",
2270-
call_params={"dest": dest, "value": value.rao},
2282+
call_function=call_function,
2283+
call_params=call_params,
22712284
)
22722285

22732286
try:
@@ -4266,7 +4279,7 @@ def transfer(
42664279
self,
42674280
wallet: "Wallet",
42684281
dest: str,
4269-
amount: Balance,
4282+
amount: Optional[Balance],
42704283
wait_for_inclusion: bool = True,
42714284
wait_for_finalization: bool = False,
42724285
transfer_all: bool = False,
@@ -4292,7 +4305,8 @@ def transfer(
42924305
Returns:
42934306
`True` if the transferring was successful, otherwise `False`.
42944307
"""
4295-
amount = check_and_convert_to_balance(amount)
4308+
if amount is not None:
4309+
amount = check_and_convert_to_balance(amount)
42964310
return transfer_extrinsic(
42974311
subtensor=self,
42984312
wallet=wallet,

bittensor/utils/__init__.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
if TYPE_CHECKING:
2424
from bittensor_wallet import Wallet
25+
from bittensor.utils.balance import Balance
2526

2627
BT_DOCS_LINK = "https://docs.bittensor.com"
2728

@@ -445,3 +446,34 @@ def deprecated_message(message: str) -> None:
445446
"""Shows a deprecation warning message with the given message."""
446447
warnings.simplefilter("default", DeprecationWarning)
447448
warnings.warn(message=message, category=DeprecationWarning, stacklevel=2)
449+
450+
451+
def get_transfer_fn_params(
452+
amount: Optional["Balance"], destination: str, keep_alive: bool
453+
) -> tuple[str, dict[str, Union[str, int, bool]]]:
454+
"""
455+
Helper function to get the transfer call function and call params, depending on the value and keep_alive flag
456+
provided
457+
458+
Args:
459+
amount: the amount of Tao to transfer. `None` if transferring all.
460+
destination: the destination SS58 of the transfer
461+
keep_alive: whether to enforce a retention of the existential deposit in the account after transfer.
462+
463+
Returns:
464+
tuple[call function, call params]
465+
"""
466+
call_params = {"dest": destination}
467+
if amount is None:
468+
call_function = "transfer_all"
469+
if keep_alive:
470+
call_params["keep_alive"] = True
471+
else:
472+
call_params["keep_alive"] = False
473+
else:
474+
call_params["value"] = amount.rao
475+
if keep_alive:
476+
call_function = "transfer_keep_alive"
477+
else:
478+
call_function = "transfer_allow_death"
479+
return call_function, call_params

0 commit comments

Comments
 (0)