Source code for bittensor.core.extrinsics.transfer

# The MIT License (MIT)
# Copyright © 2024 Opentensor Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
# documentation files (the “Software”), to deal in the Software without restriction, including without limitation
# the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all copies or substantial portions of
# the Software.
#
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
# THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.

from typing import Optional, Union, TYPE_CHECKING

from retry import retry
from rich.prompt import Confirm

from bittensor.core.extrinsics.utils import submit_extrinsic
from bittensor.core.settings import bt_console, NETWORK_EXPLORER_MAP
from bittensor.utils import (
    get_explorer_url_for_network,
    format_error_message,
    is_valid_bittensor_address_or_public_key,
)
from bittensor.utils.balance import Balance
from bittensor.utils.networking import ensure_connected

# For annotation purposes
if TYPE_CHECKING:
    from bittensor.core.subtensor import Subtensor
    from bittensor_wallet import Wallet


# Chain call for `transfer_extrinsic`
[docs] @ensure_connected def do_transfer( self: "Subtensor", wallet: "Wallet", dest: str, transfer_balance: "Balance", wait_for_inclusion: bool = True, wait_for_finalization: bool = False, ) -> tuple[bool, Optional[str], Optional[dict]]: """Sends a transfer extrinsic to the chain. Args: self (subtensor.core.subtensor.Subtensor): The Subtensor instance object. wallet (bittensor_wallet.Wallet): Wallet object. dest (str): Destination public key address. transfer_balance (bittensor.utils.balance.Balance): Amount to transfer. wait_for_inclusion (bool): If ``true``, waits for inclusion. wait_for_finalization (bool): If ``true``, waits for finalization. Returns: success (bool): ``True`` if transfer was successful. block_hash (str): Block hash of the transfer. On success and if wait_for_ finalization/inclusion is ``True``. error (dict): Error message from subtensor if transfer failed. """ @retry(delay=1, tries=3, backoff=2, max_delay=4) def make_substrate_call_with_retry(): call = self.substrate.compose_call( call_module="Balances", call_function="transfer_allow_death", call_params={"dest": dest, "value": transfer_balance.rao}, ) extrinsic = self.substrate.create_signed_extrinsic( call=call, keypair=wallet.coldkey ) response = submit_extrinsic( substrate=self.substrate, 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, None, None # Otherwise continue with finalization. response.process_events() if response.is_success: block_hash = response.block_hash return True, block_hash, None else: return False, None, response.error_message return make_substrate_call_with_retry()
# Community uses this extrinsic directly and via `subtensor.transfer`
[docs] def transfer_extrinsic( subtensor: "Subtensor", wallet: "Wallet", dest: str, amount: Union["Balance", float], wait_for_inclusion: bool = True, wait_for_finalization: bool = False, keep_alive: bool = True, prompt: bool = False, ) -> bool: """Transfers funds from this wallet to the destination public key address. Args: subtensor (subtensor.core.subtensor.Subtensor): The Subtensor instance object. wallet (bittensor_wallet.Wallet): Bittensor wallet object to make transfer from. dest (str, ss58_address or ed25519): Destination public key address of receiver. amount (Union[Balance, int]): Amount to stake as Bittensor balance, or ``float`` interpreted as Tao. 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. prompt (bool): If ``true``, the call waits for confirmation from the user before proceeding. Returns: success (bool): Flag is ``true`` if extrinsic was finalized or uncluded in the block. If we did not wait for finalization / inclusion, the response is ``true``. """ # Validate destination address. if not is_valid_bittensor_address_or_public_key(dest): bt_console.print( f":cross_mark: [red]Invalid destination address[/red]:[bold white]\n {dest}[/bold white]" ) return False if isinstance(dest, bytes): # Convert bytes to hex string. dest = "0x" + dest.hex() # Unlock wallet coldkey. wallet.unlock_coldkey() # Convert to bittensor.Balance if not isinstance(amount, Balance): transfer_balance = Balance.from_tao(amount) else: transfer_balance = amount # Check balance. with bt_console.status(":satellite: Checking Balance..."): account_balance = subtensor.get_balance(wallet.coldkey.ss58_address) # check existential deposit. existential_deposit = subtensor.get_existential_deposit() with bt_console.status(":satellite: Transferring..."): fee = subtensor.get_transfer_fee( wallet=wallet, dest=dest, value=transfer_balance.rao ) if not keep_alive: # Check if the transfer should keep_alive the account existential_deposit = Balance(0) # Check if we have enough balance. if account_balance < (transfer_balance + fee + existential_deposit): bt_console.print( ":cross_mark: [red]Not enough balance[/red]:[bold white]\n" f" balance: {account_balance}\n" f" amount: {transfer_balance}\n" f" for fee: {fee}[/bold white]" ) return False # Ask before moving on. if prompt: if not Confirm.ask( "Do you want to transfer:[bold white]\n" f" amount: {transfer_balance}\n" f" from: {wallet.name}:{wallet.coldkey.ss58_address}\n" f" to: {dest}\n" f" for fee: {fee}[/bold white]" ): return False with bt_console.status(":satellite: Transferring..."): success, block_hash, error_message = do_transfer( self=subtensor, wallet=wallet, dest=dest, transfer_balance=transfer_balance, wait_for_finalization=wait_for_finalization, wait_for_inclusion=wait_for_inclusion, ) if success: bt_console.print(":white_heavy_check_mark: [green]Finalized[/green]") bt_console.print(f"[green]Block Hash: {block_hash}[/green]") explorer_urls = get_explorer_url_for_network( subtensor.network, block_hash, NETWORK_EXPLORER_MAP ) if explorer_urls != {} and explorer_urls: bt_console.print( f"[green]Opentensor Explorer Link: {explorer_urls.get('opentensor')}[/green]" ) bt_console.print( f"[green]Taostats Explorer Link: {explorer_urls.get('taostats')}[/green]" ) else: bt_console.print( f":cross_mark: [red]Failed[/red]: {format_error_message(error_message)}" ) if success: with bt_console.status(":satellite: Checking Balance..."): new_balance = subtensor.get_balance(wallet.coldkey.ss58_address) bt_console.print( f"Balance:\n [blue]{account_balance}[/blue] :arrow_right: [green]{new_balance}[/green]" ) return True return False