import asyncio
from typing import TYPE_CHECKING
from bittensor.core.settings import NETWORK_EXPLORER_MAP
from bittensor.utils import (
format_error_message,
get_explorer_url_for_network,
is_valid_bittensor_address_or_public_key,
unlock_key,
)
from bittensor.utils.balance import Balance
from bittensor.utils.btlogging import logging
if TYPE_CHECKING:
from bittensor.core.async_subtensor import AsyncSubtensor
from bittensor_wallet import Wallet
async def _do_transfer(
subtensor: "AsyncSubtensor",
wallet: "Wallet",
destination: str,
amount: "Balance",
wait_for_inclusion: bool = True,
wait_for_finalization: bool = False,
) -> tuple[bool, str, str]:
"""
Makes transfer from wallet to destination public key address.
Args:
subtensor (bittensor.core.async_subtensor.AsyncSubtensor): initialized AsyncSubtensor object used for transfer
wallet (bittensor_wallet.Wallet): Bittensor wallet object to make transfer from.
destination (str): Destination public key address (ss58_address or ed25519) of recipient.
amount (bittensor.utils.balance.Balance): Amount to stake as Bittensor balance.
wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning `True`, or returns
`False` if the extrinsic fails to enter the block within the timeout.
wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning
`True`, or returns `False` if the extrinsic fails to be finalized within the timeout.
Returns:
success, block hash, formatted error message
"""
call = await subtensor.substrate.compose_call(
call_module="Balances",
call_function="transfer_allow_death",
call_params={"dest": destination, "value": amount.rao},
)
extrinsic = await subtensor.substrate.create_signed_extrinsic(
call=call, keypair=wallet.coldkey
)
response = await subtensor.substrate.submit_extrinsic(
extrinsic=extrinsic,
wait_for_inclusion=wait_for_inclusion,
wait_for_finalization=wait_for_finalization,
)
# We only wait here if we expect finalization.
if not wait_for_finalization and not wait_for_inclusion:
return True, "", "Success, extrinsic submitted without waiting."
# Otherwise continue with finalization.
if await response.is_success:
block_hash_ = response.block_hash
return True, block_hash_, "Success with response."
return False, "", format_error_message(await response.error_message)
[docs]
async def transfer_extrinsic(
subtensor: "AsyncSubtensor",
wallet: "Wallet",
dest: str,
amount: "Balance",
transfer_all: bool = False,
wait_for_inclusion: bool = True,
wait_for_finalization: bool = False,
keep_alive: bool = True,
) -> bool:
"""Transfers funds from this wallet to the destination public key address.
Args:
subtensor (bittensor.core.async_subtensor.AsyncSubtensor): initialized AsyncSubtensor object used for transfer
wallet (bittensor_wallet.Wallet): Bittensor wallet object to make transfer from.
dest (str): Destination public key address (ss58_address or ed25519) of recipient.
amount (bittensor.utils.balance.Balance): Amount to stake as Bittensor balance.
transfer_all (bool): Whether to transfer all funds from this wallet to the destination address.
wait_for_inclusion (bool): If set, waits for the extrinsic to enter a block before returning `True`, or returns
`False` if the extrinsic fails to enter the block within the timeout.
wait_for_finalization (bool): If set, waits for the extrinsic to be finalized on the chain before returning
`True`, or returns `False` if the extrinsic fails to be finalized within the timeout.
keep_alive (bool): If set, keeps the account alive by keeping the balance above the existential deposit.
Returns:
success (bool): Flag is `True` if extrinsic was finalized or included in the block. If we did not wait for
finalization / inclusion, the response is `True`, regardless of its inclusion.
"""
destination = dest
# Validate destination address.
if not is_valid_bittensor_address_or_public_key(destination):
logging.error(
f":cross_mark: [red]Invalid destination SS58 address[/red]: {destination}"
)
return False
logging.info(f"Initiating transfer on network: {subtensor.network}")
# Unlock wallet coldkey.
if not (unlock := unlock_key(wallet)).success:
logging.error(unlock.message)
return False
# Check balance.
logging.info(
f":satellite: [magenta]Checking balance and fees on chain [/magenta] [blue]{subtensor.network}[/blue]"
)
# check existential deposit and fee
logging.debug("Fetching existential and fee")
block_hash = await subtensor.substrate.get_chain_head()
account_balance, existential_deposit = await asyncio.gather(
subtensor.get_balance(wallet.coldkeypub.ss58_address, block_hash=block_hash),
subtensor.get_existential_deposit(block_hash=block_hash),
)
fee = await subtensor.get_transfer_fee(
wallet=wallet, dest=destination, value=amount
)
if not keep_alive:
# Check if the transfer should keep_alive the account
existential_deposit = Balance(0)
# Check if we have enough balance.
if transfer_all is True:
amount = account_balance - fee - existential_deposit
if amount < Balance(0):
logging.error("Not enough balance to transfer")
return False
if account_balance < (amount + fee + existential_deposit):
logging.error(":cross_mark: [red]Not enough balance[/red]")
logging.error(f"\t\tBalance:\t[blue]{account_balance}[/blue]")
logging.error(f"\t\tAmount:\t[blue]{amount}[/blue]")
logging.error(f"\t\tFor fee:\t[blue]{fee}[/blue]")
return False
logging.info(":satellite: [magenta]Transferring...</magenta")
success, block_hash, err_msg = await _do_transfer(
subtensor=subtensor,
wallet=wallet,
destination=destination,
amount=amount,
wait_for_finalization=wait_for_finalization,
wait_for_inclusion=wait_for_inclusion,
)
if success:
logging.success(":white_heavy_check_mark: [green]Finalized[/green]")
logging.info(f"[green]Block Hash:[/green] [blue]{block_hash}[/blue]")
if subtensor.network == "finney":
logging.debug("Fetching explorer URLs")
explorer_urls = get_explorer_url_for_network(
subtensor.network, block_hash, NETWORK_EXPLORER_MAP
)
if explorer_urls:
logging.info(
f"[green]Opentensor Explorer Link: {explorer_urls.get('opentensor')}[/green]"
)
logging.info(
f"[green]Taostats Explorer Link: {explorer_urls.get('taostats')}[/green]"
)
logging.info(":satellite: [magenta]Checking Balance...[magenta]")
new_balance = await subtensor.get_balance(wallet.coldkeypub.ss58_address)
logging.info(
f"Balance: [blue]{account_balance}[/blue] :arrow_right: [green]{new_balance}[/green]"
)
return True
else:
logging.error(f":cross_mark: [red]Failed[/red]: {err_msg}")
return False