#!/usr/bin/env pyston3 import argparse import logging import os import os.path import sys import traceback import typing from decimal import Decimal from solana.publickey import PublicKey from spl.token.client import Token from spl.token.constants import TOKEN_PROGRAM_ID sys.path.insert(0, os.path.abspath( os.path.join(os.path.dirname(__file__), ".."))) import mango # nopep8 # We explicitly want argument parsing to be outside the main try-except block because some arguments # (like --help) will cause an exit, which our except: block traps. parser = argparse.ArgumentParser(description="Sends an SPL tokens to a different address.") mango.Context.add_command_line_parameters(parser) mango.Wallet.add_command_line_parameters(parser) parser.add_argument("--symbol", type=str, required=True, help="token symbol to send (e.g. ETH)") parser.add_argument("--address", type=PublicKey, help="Destination address for the SPL token - can be either the actual token address or the address of the owner of the token address") parser.add_argument("--quantity", type=Decimal, required=True, help="quantity of token to send") parser.add_argument("--dry-run", action="store_true", default=False, help="runs as read-only and does not perform any transactions") args = parser.parse_args() logging.getLogger().setLevel(args.log_level) logging.warning(mango.WARNING_DISCLAIMER_TEXT) try: context = mango.Context.from_command_line_parameters(args) wallet = mango.Wallet.from_command_line_parameters_or_raise(args) logging.info(f"Context: {context}") logging.info(f"Wallet address: {wallet.address}") token = context.token_lookup.find_by_symbol(args.symbol.upper()) if token is None: raise Exception(f"Could not find details of token with symbol {args.symbol}.") spl_token = Token(context.client, token.mint, TOKEN_PROGRAM_ID, wallet.account) source_accounts = spl_token.get_accounts(wallet.address) source_account = source_accounts["result"]["value"][0] source = PublicKey(source_account["pubkey"]) # Is the address an actual token account? Or is it the SOL address of the owner? possible_dest: typing.Optional[mango.TokenAccount] = mango.TokenAccount.load(context, args.address) if (possible_dest is not None) and (possible_dest.value.token.mint == token.mint): # We successfully loaded the token account. destination: PublicKey = args.address else: destination_accounts = spl_token.get_accounts(args.address) if len(destination_accounts["result"]["value"]) == 0: raise Exception( f"Could not find destination account using {args.address} as either owner address or token address.") destination_account = destination_accounts["result"]["value"][0] destination = PublicKey(destination_account["pubkey"]) owner = wallet.account amount = int(args.quantity * Decimal(10 ** token.decimals)) print("Balance:", source_account["account"]["data"]["parsed"] ["info"]["tokenAmount"]["uiAmountString"], token.name) text_amount = f"{amount} {token.name} (@ {token.decimals} decimal places)" print(f"Sending {text_amount}") print(f" From: {source}") print(f" To: {destination}") if args.dry_run: print("Skipping actual transfer - dry run.") else: transfer_response = spl_token.transfer(source, destination, owner, amount) transaction_id = context.unwrap_transaction_id_or_raise_exception(transfer_response) print(f"Waiting on transaction ID: {transaction_id}") context.wait_for_confirmation(transaction_id) updated_balance = spl_token.get_balance(source) updated_balance_text = updated_balance["result"]["value"]["uiAmountString"] print(f"{text_amount} sent. Balance now: {updated_balance_text} {token.name}") except Exception as exception: logging.critical(f"send-token stopped because of exception: {exception} - {traceback.format_exc()}") except: logging.critical(f"send-token stopped because of uncatchable error: {traceback.format_exc()}")