#!/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 one of the Group's SPL tokens to a different address.") mango.Context.add_context_command_line_parameters(parser) parser.add_argument("--id-file", type=str, default="id.json", help="file containing the JSON-formatted wallet private key") parser.add_argument("--log-level", default=logging.INFO, type=lambda level: getattr(logging, level), help="level of verbosity to log (possible values: DEBUG, INFO, WARNING, ERROR, CRITICAL)") parser.add_argument("--token-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: id_filename = args.id_file if not os.path.isfile(id_filename): logging.error(f"Wallet file '{id_filename}' is not present.") sys.exit(1) wallet = mango.Wallet.load(id_filename) context = mango.Context.from_context_command_line_parameters(args) logging.info(f"Context: {context}") logging.info(f"Wallet address: {wallet.address}") group = mango.Group.load(context) group_basket_token = mango.BasketToken.find_by_symbol(group.basket_tokens, args.token_symbol) group_token = group_basket_token.token spl_token = Token(context.client, group_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 == group_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 ** group_token.decimals)) print("Balance:", source_account["account"]["data"]["parsed"] ["info"]["tokenAmount"]["uiAmountString"], group_token.name) text_amount = f"{amount} {group_token.name} (@ {group_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} {group_token.name}") except Exception as exception: logging.critical(f"group-send-token stopped because of exception: {exception} - {traceback.format_exc()}") except: logging.critical(f"group-send-token stopped because of uncatchable error: {traceback.format_exc()}")