Skip to main content

Basic Token Transfer

import { useWallet, useConnection } from '@hermis/solana-headless-react';
import { getAssociatedTokenAddress, createTransferInstruction, Transaction } from '@solana/spl-token';
import { PublicKey } from '@solana/web3.js';

function TransferTokens() {
  const { publicKey, sendTransaction } = useWallet();
  const { connection } = useConnection();

  const transfer = async (
    mintAddress: string,
    recipient: string,
    amount: number
  ) => {
    if (!publicKey) return;

    const mint = new PublicKey(mintAddress);
    const recipientPubKey = new PublicKey(recipient);

    // Get token accounts
    const fromTokenAccount = await getAssociatedTokenAddress(
      mint,
      publicKey
    );

    const toTokenAccount = await getAssociatedTokenAddress(
      mint,
      recipientPubKey
    );

    // Create transaction
    const transaction = new Transaction();

    transaction.add(
      createTransferInstruction(
        fromTokenAccount,
        toTokenAccount,
        publicKey,
        amount * Math.pow(10, 9) // With decimals
      )
    );

    const { blockhash } = await connection.getLatestBlockhash();
    transaction.recentBlockhash = blockhash;
    transaction.feePayer = publicKey;

    const signature = await sendTransaction(transaction, connection);
    await connection.confirmTransaction(signature);

    return signature;
  };

  return (
    <button onClick={() => transfer('mint...', 'recipient...', 100)}>
      Transfer 100 Tokens
    </button>
  );
}

With Associated Token Account Creation

import { useWallet, useConnection, useAnchorWallet } from '@hermis/solana-headless-react';
import { getOrCreateAssociatedTokenAccount, createTransferInstruction, Transaction } from '@solana/spl-token';
import { PublicKey } from '@solana/web3.js';

function TransferWithAccountCreation() {
  const { publicKey, sendTransaction } = useWallet();
  const { connection } = useConnection();
  const wallet = useAnchorWallet(); // Provides Signer interface for getOrCreateAssociatedTokenAccount

  const transferWithAccountCreation = async (
    mint: PublicKey,
    recipient: PublicKey,
    amount: number
  ) => {
    if (!publicKey || !wallet) return;

    // Get or create sender's token account
    const fromAccount = await getOrCreateAssociatedTokenAccount(
      connection,
      wallet, // AnchorWallet as fee payer
      mint,
      publicKey
    );

    // Get or create recipient's token account
    const toAccount = await getOrCreateAssociatedTokenAccount(
      connection,
      wallet, // AnchorWallet as fee payer
      mint,
      recipient
    );

    // Transfer
    const transaction = new Transaction();

    transaction.add(
      createTransferInstruction(
        fromAccount.address,
        toAccount.address,
        publicKey,
        amount
      )
    );

    const { blockhash } = await connection.getLatestBlockhash();
    transaction.recentBlockhash = blockhash;
    transaction.feePayer = publicKey;

    return await sendTransaction(transaction, connection);
  };

  return (
    <button
      onClick={() => transferWithAccountCreation(mintPubkey, recipientPubkey, 1000000)}
      disabled={!wallet}
    >
      Transfer Tokens
    </button>
  );
}

Complete Component

function TokenTransfer() {
  const [mint, setMint] = useState('');
  const [recipient, setRecipient] = useState('');
  const [amount, setAmount] = useState('');
  const [sending, setSending] = useState(false);
  const [signature, setSignature] = useState('');

  const { publicKey, sendTransaction } = useWallet();
  const { connection } = useConnection();

  const handleTransfer = async () => {
    setSending(true);
    try {
      const sig = await transfer(mint, recipient, parseFloat(amount));
      setSignature(sig);
      alert('Transfer successful!');
    } catch (error) {
      console.error('Transfer failed:', error);
      alert(`Error: ${error.message}`);
    } finally {
      setSending(false);
    }
  };

  return (
    <div>
      <input
        placeholder="Token Mint Address"
        value={mint}
        onChange={(e) => setMint(e.target.value)}
      />
      <input
        placeholder="Recipient Address"
        value={recipient}
        onChange={(e) => setRecipient(e.target.value)}
      />
      <input
        type="number"
        placeholder="Amount"
        value={amount}
        onChange={(e) => setAmount(e.target.value)}
      />
      <button onClick={handleTransfer} disabled={sending || !publicKey}>
        {sending ? 'Sending...' : 'Transfer'}
      </button>
      {signature && (
        <p>
          <a
            href={`https://explorer.solana.com/tx/${signature}`}
            target="_blank"
          >
            View Transaction
          </a>
        </p>
      )}
    </div>
  );
}

Token Transfer (@solana/kit)

import { useWallet, useConnection, createKitTransaction } from '@hermis/solana-headless-react';
import { createKitSignersFromAdapter } from '@hermis/solana-headless-adapter-base';
import { HermisError, HERMIS_ERROR__WALLET_INTERACTION__FEATURE_NOT_SUPPORTED } from '@hermis/errors';
import { address } from '@solana/web-sdk';
import { getTransferCheckedInstruction, getAssociatedTokenAddressSync } from '@solana/spl-token';
import { PublicKey } from '@solana/web3.js';

function TransferTokensKit() {
  const { wallet, publicKey, addressString } = useWallet();
  const { connection } = useConnection();

  const transfer = async (
    mintAddress: string,
    recipient: string,
    amount: number,
    decimals: number = 9
  ) => {
    if (!wallet || !publicKey || !addressString) return;

    // Create Kit signers from adapter
    const { transactionSigner } = createKitSignersFromAdapter(
      wallet.adapter,
      connection
    );

    if (!transactionSigner) {
      throw new HermisError(
        HERMIS_ERROR__WALLET_INTERACTION__FEATURE_NOT_SUPPORTED,
        { feature: 'transactions' }
      );
    }

    const mint = new PublicKey(mintAddress);
    const recipientPubKey = new PublicKey(recipient);

    // Get associated token accounts
    const fromTokenAccount = getAssociatedTokenAddressSync(mint, publicKey);
    const toTokenAccount = getAssociatedTokenAddressSync(mint, recipientPubKey);

    // Create transfer instruction
    const instruction = getTransferCheckedInstruction(
      fromTokenAccount,
      mint,
      toTokenAccount,
      publicKey,
      amount * Math.pow(10, decimals),
      decimals
    );

    // Create Kit transaction with automatic blockhash
    const transaction = await createKitTransaction(
      connection,
      address(addressString),
      [instruction]
    );

    // Sign and send
    const [signature] = await transactionSigner.signAndSendTransactions([transaction]);

    console.log('Transfer signature:', signature);
    return signature;
  };

  return (
    <button onClick={() => transfer('mint...', 'recipient...', 100)}>
      Transfer 100 Tokens (Kit)
    </button>
  );
}
When to use Kit for token operations:
  • Building with @solana/kit architecture
  • Need modern type-safe transaction building
  • Want to integrate with Kit-native programs
  • Prefer createKitTransaction for automatic blockhash handling
Note: This example still uses @solana/spl-token for token account addresses and instructions, but wraps them in a Kit transaction. For fully Kit-native token operations, watch for @solana/spl-token Kit equivalents as they become available.