Skip to main content

Signing and Sending Transactions

The React SDK provides chain-specific hooks (useSolana and useEthereum) for signing and sending transactions with optimal blockchain-specific handling.
Embedded Wallet Limitations: The signTransaction and signAllTransactions methods are not supported for embedded wallets. For embedded wallets, use only signAndSendTransaction which signs and broadcasts the transaction in a single step.

Chain-Specific Transaction Hooks

Solana Transactions (useSolana)

import { useSolana } from "@phantom/react-sdk";

function SolanaTransactions() {
  const { solana } = useSolana();

  const sendTransaction = async () => {
    // Sign and send transaction
    const result = await solana.signAndSendTransaction(transaction);
    console.log("Transaction sent:", result.hash);
  };

  const signOnly = async () => {
    // Just sign (without sending) - Note: Not supported for embedded wallets
    const signedTx = await solana.signTransaction(transaction);
    console.log("Signed transaction:", signedTx);
  };

  return (
    <div>
      <button onClick={sendTransaction}>Send Transaction</button>
      <button onClick={signOnly}>Sign Only</button>
    </div>
  );
}

Ethereum Transactions (useEthereum)

import { useEthereum } from "@phantom/react-sdk";

function EthereumTransactions() {
  const { ethereum } = useEthereum();

  const sendTransaction = async () => {
    const result = await ethereum.sendTransaction({
      to: "0x742d35Cc6634C0532925a3b8D4C8db86fB5C4A7E",
      value: "1000000000000000000", // 1 ETH in wei
      gas: "21000",
    });
    console.log("Transaction sent:", result.hash);
  };

  return (
    <button onClick={sendTransaction}>Send ETH</button>
  );
}

Transaction Examples

Sequential Transactions for Embedded Wallets: When using embedded wallets with Phantom Connect, bundled or parallel transaction execution (such as Jito bundles) is not supported. Each transaction must be submitted individually and confirmed before the next can be sent. This is required for spending limit policy enforcement, as the backend must simulate each transaction independently to accurately calculate spending limits.

Solana with @solana/web3.js

import { VersionedTransaction, TransactionMessage, SystemProgram, PublicKey, Connection } from "@solana/web3.js";
import { useSolana } from "@phantom/react-sdk";

function SolanaExample() {
  const { solana } = useSolana();

  const sendTransaction = async () => {
    // Get recent blockhash
    const connection = new Connection("https://api.mainnet-beta.solana.com");
    const { blockhash } = await connection.getLatestBlockhash();

    // Create transfer instruction
    const fromAddress = await solana.getPublicKey();
    const transferInstruction = SystemProgram.transfer({
      fromPubkey: new PublicKey(fromAddress),
      toPubkey: new PublicKey(toAddress),
      lamports: 1000000, // 0.001 SOL
    });

    // Create VersionedTransaction
    const messageV0 = new TransactionMessage({
      payerKey: new PublicKey(fromAddress),
      recentBlockhash: blockhash,
      instructions: [transferInstruction],
    }).compileToV0Message();

    const transaction = new VersionedTransaction(messageV0);

    // Sign and send using chain-specific hook
    const result = await solana.signAndSendTransaction(transaction);
    console.log("Transaction sent:", result.hash);
  };

  return <button onClick={sendTransaction}>Send SOL</button>;
}

Solana with @solana/kit

import {
  createSolanaRpc,
  pipe,
  createTransactionMessage,
  setTransactionMessageFeePayer,
  setTransactionMessageLifetimeUsingBlockhash,
  address,
  compileTransaction,
} from "@solana/kit";
import { useSolana } from "@phantom/react-sdk";

function SolanaKitExample() {
  const { solana } = useSolana();

  const sendTransaction = async () => {
    const rpc = createSolanaRpc("https://api.mainnet-beta.solana.com");
    const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();

    const userPublicKey = await solana.getPublicKey();
    const transactionMessage = pipe(
      createTransactionMessage({ version: 0 }),
      tx => setTransactionMessageFeePayer(address(userPublicKey), tx),
      tx => setTransactionMessageLifetimeUsingBlockhash(latestBlockhash, tx),
    );

    const transaction = compileTransaction(transactionMessage);

    // Sign and send using chain-specific hook
    const result = await solana.signAndSendTransaction(transaction);
    console.log("Transaction sent:", result.hash);
  };

  return <button onClick={sendTransaction}>Send SOL</button>;
}

Ethereum with Viem

import { parseEther, parseGwei, encodeFunctionData } from "viem";
import { useEthereum } from "@phantom/react-sdk";

function EthereumExample() {
  const { ethereum } = useEthereum();

  const sendEth = async () => {
    const result = await ethereum.sendTransaction({
      to: "0x742d35Cc6634C0532925a3b8D4C8db86fB5C4A7E",
      value: parseEther("1").toString(), // 1 ETH
      gas: "21000",
      gasPrice: parseGwei("20").toString(), // 20 gwei
    });
    console.log("ETH sent:", result.hash);
  };

  const sendToken = async () => {
    const result = await ethereum.sendTransaction({
      to: tokenContractAddress,
      data: encodeFunctionData({
        abi: erc20Abi,
        functionName: "transfer",
        args: [recipientAddress, parseEther("100")],
      }),
      gas: "50000",
      maxFeePerGas: parseGwei("30").toString(),
      maxPriorityFeePerGas: parseGwei("2").toString(),
    });
    console.log("Token sent:", result.hash);
  };

  return (
    <div>
      <button onClick={sendEth}>Send ETH</button>
      <button onClick={sendToken}>Send Token</button>
    </div>
  );
}