Skip to main content
Create and manage SPL token accounts for holding tokens on Solana. This includes creating Associated Token Accounts (ATAs) and closing empty accounts to reclaim SOL.
import { useParaSolanaSigner, useParaSolanaSignAndSend } from '@getpara/react-sdk';
import {
  createSolanaRpc,
  pipe,
  createTransactionMessage,
  setTransactionMessageFeePayer,
  setTransactionMessageLifetimeUsingBlockhash,
  appendTransactionMessageInstruction,
  address
} from '@solana/kit';
import {
  getCreateAssociatedTokenAccountInstruction,
  findAssociatedTokenPda,
  getCloseAccountInstruction
} from '@solana-program/token';

const rpc = createSolanaRpc("https://api.mainnet-beta.solana.com");

function ManageTokenAccounts() {
  const { solanaSigner, isLoading } = useParaSolanaSigner({ rpc });
  const { signAndSendAsync, isPending } = useParaSolanaSignAndSend(solanaSigner);

  const createTokenAccount = async (mint: string, owner?: string) => {
    if (!solanaSigner) return;

    const mintAddress = address(mint);
    const ownerAddress = owner ? address(owner) : solanaSigner.address;

    const [ata] = await findAssociatedTokenPda({
      mint: mintAddress,
      owner: ownerAddress,
    });

    const accountInfo = await rpc.getAccountInfo(ata).send();
    if (accountInfo.value) {
      console.log("Token account already exists:", ata);
      return ata;
    }

    const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();

    const transaction = pipe(
      createTransactionMessage({ version: 0 }),
      tx => setTransactionMessageFeePayer(solanaSigner.address, tx),
      tx => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
      tx => appendTransactionMessageInstruction(
        getCreateAssociatedTokenAccountInstruction({
          ata,
          mint: mintAddress,
          owner: ownerAddress,
          payer: solanaSigner,
        }),
        tx
      )
    );

    try {
      const result = await signAndSendAsync({ transactions: [transaction] });
      console.log("Created token account:", ata);
      console.log("Transaction signature:", result);
      return ata;
    } catch (error) {
      console.error("Failed to create token account:", error);
      throw error;
    }
  };

  const closeTokenAccount = async (tokenAccount: string) => {
    if (!solanaSigner) return;

    const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();

    const transaction = pipe(
      createTransactionMessage({ version: 0 }),
      tx => setTransactionMessageFeePayer(solanaSigner.address, tx),
      tx => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
      tx => appendTransactionMessageInstruction(
        getCloseAccountInstruction({
          account: address(tokenAccount),
          destination: solanaSigner.address,
          authority: solanaSigner,
        }),
        tx
      )
    );

    try {
      const result = await signAndSendAsync({ transactions: [transaction] });
      console.log("Closed token account:", tokenAccount);
      console.log("Transaction signature:", result);
      return result;
    } catch (error) {
      console.error("Failed to close token account:", error);
      throw error;
    }
  };

  return (
    <div>
      <button onClick={() => createTokenAccount("USDC_MINT_ADDRESS")}>
        Create USDC Account
      </button>
      <button onClick={() => closeTokenAccount("TOKEN_ACCOUNT_ADDRESS")}>
        Close Token Account
      </button>
    </div>
  );
}

Next Steps