> ## Documentation Index
> Fetch the complete documentation index at: https://docs.streambird.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Sign Transaction

# Sign Transaction

Sign Solana transactions without broadcasting them to the network. This is useful for advanced use cases like off-chain transaction batching, meta-transactions, or custom transaction submission workflows.

## Overview

Signing transactions without sending allows you to:

* **Batch transactions**: Collect multiple signed transactions before submitting
* **Off-chain processing**: Sign now, broadcast later
* **Custom submission**: Use custom RPC logic or transaction relayers
* **Transaction inspection**: Review signed transaction data before broadcasting

<Info>
  Unlike `sendTransaction` which signs and broadcasts immediately, `signTransaction` only signs the transaction and returns it for you to broadcast separately.
</Info>

## Signing vs Sending

| Feature               | Sign Transaction         | Send Transaction      |
| --------------------- | ------------------------ | --------------------- |
| Signs transaction     | ✅                        | ✅                     |
| Broadcasts to network | ❌                        | ✅                     |
| Returns               | Signed transaction bytes | Transaction signature |
| Use case              | Advanced workflows       | Standard transfers    |

## React SDK

<Tabs>
  <Tab title="Basic Usage">
    To sign a transaction, use the `signTransaction` method from the `useSignTransaction` hook:

    ```tsx theme={null}
    import { useMoonKey } from '@moon-key/react-auth';
    import { useSignTransaction } from '@moon-key/react-auth/solana';
    import {
      Transaction,
      SystemProgram,
      PublicKey,
      Connection,
      LAMPORTS_PER_SOL
    } from '@solana/web3.js';

    function SignTransactionButton() {
      const { user } = useMoonKey();
      const { signTransaction } = useSignTransaction();

      const handleSign = async () => {
        if (!user?.wallet) {
          alert('No wallet found');
          return;
        }

        try {
          const connection = new Connection('https://api.devnet.solana.com');

          // Create transaction
          const transaction = new Transaction().add(
            SystemProgram.transfer({
              fromPubkey: new PublicKey(user.wallet.address),
              toPubkey: new PublicKey('RecipientAddressHere...'),
              lamports: 0.001 * LAMPORTS_PER_SOL
            })
          );

          // Get recent blockhash
          const { blockhash } = await connection.getLatestBlockhash();
          transaction.recentBlockhash = blockhash;
          transaction.feePayer = new PublicKey(user.wallet.address);

          // Sign transaction (does not broadcast)
          const { signedTransaction } = await signTransaction({
            transaction
          });

          console.log('Transaction signed:', signedTransaction);
          alert('Transaction signed successfully!');

          // You can now broadcast it later or store it
        } catch (error) {
          console.error('Failed to sign transaction:', error);
        }
      };

      return (
        <button onClick={handleSign}>
          Sign Transaction
        </button>
      );
    }
    ```
  </Tab>

  <Tab title="With UI Customization">
    Customize the signing confirmation modal:

    ```tsx theme={null}
    import { useMoonKey } from '@moon-key/react-auth';
    import { useSignTransaction } from '@moon-key/react-auth/solana';
    import {
      Transaction,
      SystemProgram,
      PublicKey,
      Connection,
      LAMPORTS_PER_SOL
    } from '@solana/web3.js';

    function CustomSignButton() {
      const { user } = useMoonKey();
      const { signTransaction } = useSignTransaction();

      const handleSign = async () => {
        if (!user?.wallet) return;

        const connection = new Connection('https://api.devnet.solana.com');

        const transaction = new Transaction().add(
          SystemProgram.transfer({
            fromPubkey: new PublicKey(user.wallet.address),
            toPubkey: new PublicKey('RecipientAddressHere...'),
            lamports: 0.001 * LAMPORTS_PER_SOL
          })
        );

        const { blockhash } = await connection.getLatestBlockhash();
        transaction.recentBlockhash = blockhash;
        transaction.feePayer = new PublicKey(user.wallet.address);

        try {
          const { signedTransaction } = await signTransaction({
            transaction
          }, {
            uiConfig: {
              title: 'Sign Transaction',
              description: 'Review and sign this Solana transaction',
              confirmButtonText: 'Sign',
              cancelButtonText: 'Cancel',
              transactionInfo: {
                action: 'SOL Transfer',
                cost: '0.001 SOL'
              }
            }
          });

          console.log('Transaction signed:', signedTransaction);
        } catch (error) {
          console.error('Signing failed:', error);
        }
      };

      return (
        <button onClick={handleSign}>
          Sign Transaction (Custom UI)
        </button>
      );
    }
    ```
  </Tab>

  <Tab title="With Callbacks">
    Use callbacks to handle success and error cases:

    ```tsx theme={null}
    import { useMoonKey } from '@moon-key/react-auth';
    import { useSignTransaction } from '@moon-key/react-auth/solana';
    import {
      Transaction,
      SystemProgram,
      PublicKey,
      Connection,
      LAMPORTS_PER_SOL
    } from '@solana/web3.js';

    function SignWithCallbacks() {
      const { user } = useMoonKey();
      
      const { signTransaction } = useSignTransaction({
        onSuccess: ({ signedTransaction }) => {
          console.log('Signing successful');
          alert('Transaction signed!');
          
          // Store or process signed transaction
          storeSignedTransaction(signedTransaction);
        },
        onError: (error) => {
          console.error('Signing failed:', error);
          alert('Failed to sign transaction');
        }
      });

      const handleSign = async () => {
        if (!user?.wallet) return;

        const connection = new Connection('https://api.devnet.solana.com');

        const transaction = new Transaction().add(
          SystemProgram.transfer({
            fromPubkey: new PublicKey(user.wallet.address),
            toPubkey: new PublicKey('RecipientAddressHere...'),
            lamports: 0.001 * LAMPORTS_PER_SOL
          })
        );

        const { blockhash } = await connection.getLatestBlockhash();
        transaction.recentBlockhash = blockhash;
        transaction.feePayer = new PublicKey(user.wallet.address);

        await signTransaction({ transaction });
      };

      return (
        <button onClick={handleSign}>
          Sign Transaction
        </button>
      );
    }
    ```
  </Tab>
</Tabs>

## Parameters

<ParamField path="transaction" type="Transaction | VersionedTransaction" required>
  The Solana transaction to sign. Can be either a legacy `Transaction` or `VersionedTransaction` from `@solana/web3.js`.

  **Example:**

  ```tsx theme={null}
  const transaction = new Transaction().add(
    SystemProgram.transfer({
      fromPubkey: new PublicKey(fromAddress),
      toPubkey: new PublicKey(toAddress),
      lamports: amount
    })
  );
  ```
</ParamField>

### Options Object

The second parameter is an optional configuration object:

<ParamField path="uiConfig" type="object">
  Configuration for the signing confirmation modal.

  <Expandable title="uiConfig properties">
    <ParamField path="title" type="string">
      Custom title for the confirmation modal.
    </ParamField>

    <ParamField path="description" type="string">
      Description text shown in the modal.
    </ParamField>

    <ParamField path="confirmButtonText" type="string">
      Text for the confirm button.
    </ParamField>

    <ParamField path="cancelButtonText" type="string">
      Text for the cancel button.
    </ParamField>

    <ParamField path="transactionInfo" type="object">
      Additional transaction information to display.

      <Expandable title="transactionInfo properties">
        <ParamField path="action" type="string">
          Description of the action being performed.
        </ParamField>

        <ParamField path="cost" type="string">
          Estimated cost of the transaction.
        </ParamField>
      </Expandable>
    </ParamField>

    <ParamField path="showWalletUI" type="boolean">
      Whether to show the confirmation modal. Set to `false` to sign without confirmation.
    </ParamField>
  </Expandable>
</ParamField>

### Hook Callbacks

<ParamField path="onSuccess" type="(result: { signedTransaction: Uint8Array }) => void">
  Callback executed after successful signing. Receives the signed transaction as Uint8Array.
</ParamField>

<ParamField path="onError" type="(error: Error) => void">
  Callback executed if signing fails or user cancels.
</ParamField>

## Returns

<ResponseField name="signedTransaction" type="Uint8Array">
  The signed transaction as a Uint8Array. This can be serialized and broadcast to the network.
</ResponseField>

## Complete Examples

### Sign and Broadcast Later

```tsx theme={null}
'use client';
import { useMoonKey } from '@moon-key/react-auth';
import { useSignTransaction } from '@moon-key/react-auth/solana';
import {
  Transaction,
  SystemProgram,
  PublicKey,
  Connection,
  LAMPORTS_PER_SOL
} from '@solana/web3.js';
import { useState } from 'react';

export default function SignAndBroadcastLater() {
  const { user } = useMoonKey();
  const { signTransaction } = useSignTransaction();
  const [signedTx, setSignedTx] = useState<Uint8Array | null>(null);

  const handleSign = async () => {
    if (!user?.wallet) return;

    const connection = new Connection('https://api.devnet.solana.com');

    const transaction = new Transaction().add(
      SystemProgram.transfer({
        fromPubkey: new PublicKey(user.wallet.address),
        toPubkey: new PublicKey('RecipientAddressHere...'),
        lamports: 0.001 * LAMPORTS_PER_SOL
      })
    );

    const { blockhash } = await connection.getLatestBlockhash();
    transaction.recentBlockhash = blockhash;
    transaction.feePayer = new PublicKey(user.wallet.address);

    try {
      const { signedTransaction } = await signTransaction({
        transaction
      }, {
        uiConfig: {
          title: 'Sign Transaction',
          description: 'Sign transaction to broadcast later',
          confirmButtonText: 'Sign'
        }
      });

      setSignedTx(signedTransaction);
      alert('Transaction signed! Ready to broadcast.');
    } catch (error) {
      console.error('Signing failed:', error);
    }
  };

  const handleBroadcast = async () => {
    if (!signedTx) return;

    try {
      const connection = new Connection('https://api.devnet.solana.com');

      // Deserialize and send the signed transaction
      const transaction = Transaction.from(signedTx);
      const signature = await connection.sendRawTransaction(signedTx);

      console.log('Transaction broadcast:', signature);
      
      // Wait for confirmation
      await connection.confirmTransaction(signature, 'confirmed');
      
      alert(`Transaction confirmed! Signature: ${signature}`);
      setSignedTx(null);
    } catch (error) {
      console.error('Broadcast failed:', error);
      alert('Failed to broadcast transaction');
    }
  };

  return (
    <div className="sign-and-broadcast">
      <h2>Sign Now, Broadcast Later</h2>

      <button onClick={handleSign} disabled={signedTx !== null}>
        Sign Transaction
      </button>

      {signedTx && (
        <div className="signed-tx-actions">
          <p>✅ Transaction signed and ready</p>
          <button onClick={handleBroadcast}>
            Broadcast Transaction
          </button>
        </div>
      )}
    </div>
  );
}
```

### Batch Transaction Signing

```tsx theme={null}
'use client';
import { useMoonKey } from '@moon-key/react-auth';
import { useSignTransaction } from '@moon-key/react-auth/solana';
import {
  Transaction,
  SystemProgram,
  PublicKey,
  Connection,
  LAMPORTS_PER_SOL
} from '@solana/web3.js';
import { useState } from 'react';

export default function BatchTransactionSigning() {
  const { user } = useMoonKey();
  const { signTransaction } = useSignTransaction();
  const [signedTxs, setSignedTxs] = useState<Uint8Array[]>([]);

  const handleSignBatch = async () => {
    if (!user?.wallet) return;

    const connection = new Connection('https://api.devnet.solana.com');
    const { blockhash } = await connection.getLatestBlockhash();

    // Create multiple transactions
    const recipients = [
      'Recipient1AddressHere...',
      'Recipient2AddressHere...',
      'Recipient3AddressHere...'
    ];

    const signed: Uint8Array[] = [];

    try {
      for (const recipient of recipients) {
        const transaction = new Transaction().add(
          SystemProgram.transfer({
            fromPubkey: new PublicKey(user.wallet.address),
            toPubkey: new PublicKey(recipient),
            lamports: 0.001 * LAMPORTS_PER_SOL
          })
        );

        transaction.recentBlockhash = blockhash;
        transaction.feePayer = new PublicKey(user.wallet.address);

        const { signedTransaction } = await signTransaction({
          transaction
        }, {
          uiConfig: {
            title: `Sign Transaction ${signed.length + 1}/${recipients.length}`,
            description: `Signing batch transaction ${signed.length + 1} of ${recipients.length}`,
            confirmButtonText: 'Sign'
          }
        });

        signed.push(signedTransaction);
      }

      setSignedTxs(signed);
      alert(`All ${signed.length} transactions signed!`);
    } catch (error) {
      console.error('Batch signing failed:', error);
      alert('Failed to sign all transactions');
    }
  };

  const handleBroadcastBatch = async () => {
    if (signedTxs.length === 0) return;

    const connection = new Connection('https://api.devnet.solana.com');
    const signatures: string[] = [];

    try {
      for (const signedTx of signedTxs) {
        const signature = await connection.sendRawTransaction(signedTx);
        signatures.push(signature);
        console.log('Transaction sent:', signature);
      }

      alert(`All ${signatures.length} transactions broadcast!`);
      setSignedTxs([]);
    } catch (error) {
      console.error('Batch broadcast failed:', error);
    }
  };

  return (
    <div className="batch-signing">
      <h2>Batch Transaction Signing</h2>

      <button onClick={handleSignBatch} disabled={signedTxs.length > 0}>
        Sign Batch Transactions
      </button>

      {signedTxs.length > 0 && (
        <div className="batch-status">
          <p>✅ {signedTxs.length} transactions signed</p>
          <button onClick={handleBroadcastBatch}>
            Broadcast All
          </button>
        </div>
      )}
    </div>
  );
}
```

## Broadcasting Signed Transactions

After signing a transaction, you can broadcast it using Solana's connection:

```tsx theme={null}
import { Connection } from '@solana/web3.js';

// Broadcast signed transaction
const connection = new Connection('https://api.devnet.solana.com');
const signature = await connection.sendRawTransaction(signedTransaction);

console.log('Transaction signature:', signature);

// Wait for confirmation
const confirmation = await connection.confirmTransaction(signature, 'confirmed');

if (confirmation.value.err) {
  console.error('Transaction failed');
} else {
  console.log('Transaction confirmed!');
}
```

### With Custom Commitment

```tsx theme={null}
// Use different commitment levels
await connection.confirmTransaction(signature, 'processed'); // Fast, less secure
await connection.confirmTransaction(signature, 'confirmed'); // Balanced
await connection.confirmTransaction(signature, 'finalized'); // Slow, most secure
```

## Transaction Serialization

### Serialize to Base64

```tsx theme={null}
import { Transaction } from '@solana/web3.js';

// Convert signed transaction to base64
const transaction = Transaction.from(signedTransaction);
const base64Tx = Buffer.from(signedTransaction).toString('base64');

console.log('Base64 transaction:', base64Tx);
```

### Deserialize from Base64

```tsx theme={null}
// Convert base64 back to transaction
const txBuffer = Buffer.from(base64Tx, 'base64');
const transaction = Transaction.from(txBuffer);
```

## UI Customization

Customize the signing modal:

```tsx theme={null}
await signTransaction({
  transaction
}, {
  uiConfig: {
    title: 'Sign Transaction',
    description: 'Review and sign this transaction',
    confirmButtonText: 'Sign',
    cancelButtonText: 'Cancel',
    transactionInfo: {
      action: 'Transfer SOL',
      cost: '0.001 SOL'
    }
  }
});
```

### Hide Modal

```tsx theme={null}
await signTransaction({
  transaction
}, {
  uiConfig: {
    showWalletUI: false
  }
});
```

<Warning>
  Hiding the modal removes the user's ability to review the transaction before signing. Only use this for trusted operations or after obtaining explicit user consent.
</Warning>

## Best Practices

<AccordionGroup>
  <Accordion title="Use fresh blockhashes">
    Always fetch a recent blockhash before signing:

    ```tsx theme={null}
    // ✅ Good - Fresh blockhash
    const { blockhash } = await connection.getLatestBlockhash();
    transaction.recentBlockhash = blockhash;

    // ❌ Bad - Reusing old blockhash
    // Blockhashes expire after ~60 seconds
    ```
  </Accordion>

  <Accordion title="Set fee payer">
    Always specify the fee payer:

    ```tsx theme={null}
    transaction.feePayer = new PublicKey(user.wallet.address);
    ```
  </Accordion>

  <Accordion title="Store signed transactions securely">
    If storing signed transactions, use secure storage:

    ```tsx theme={null}
    // Encrypt before storing
    const encrypted = encryptData(signedTransaction);
    localStorage.setItem('signedTx', encrypted);

    // Decrypt when broadcasting
    const decrypted = decryptData(localStorage.getItem('signedTx'));
    await connection.sendRawTransaction(decrypted);
    ```
  </Accordion>

  <Accordion title="Handle expiration">
    Signed transactions can expire if blockhash is old:

    ```tsx theme={null}
    try {
      const signature = await connection.sendRawTransaction(signedTransaction);
    } catch (error: any) {
      if (error.message?.includes('blockhash')) {
        alert('Transaction expired. Please sign again.');
        // Re-sign with fresh blockhash
      }
    }
    ```
  </Accordion>

  <Accordion title="Validate before signing">
    Check transaction validity before signing:

    ```tsx theme={null}
    // Validate recipient address
    if (!isValidSolanaAddress(recipient)) {
      alert('Invalid recipient address');
      return;
    }

    // Check balance
    const balance = await connection.getBalance(
      new PublicKey(user.wallet.address)
    );

    if (balance < transferAmount) {
      alert('Insufficient balance');
      return;
    }

    // Now sign
    await signTransaction({ transaction });
    ```
  </Accordion>

  <Accordion title="Confirm after broadcasting">
    Always wait for confirmation after broadcasting:

    ```tsx theme={null}
    const signature = await connection.sendRawTransaction(signedTransaction);

    // Wait for confirmation with timeout
    const confirmation = await connection.confirmTransaction(
      signature,
      'confirmed'
    );

    if (confirmation.value.err) {
      throw new Error('Transaction failed');
    }
    ```
  </Accordion>
</AccordionGroup>

## Troubleshooting

<AccordionGroup>
  <Accordion title="Blockhash expired error">
    Signed transactions expire if blockhash is too old:

    ```tsx theme={null}
    // Ensure fresh blockhash when signing
    const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash();
    transaction.recentBlockhash = blockhash;

    // Check if still valid before broadcasting
    const currentBlockHeight = await connection.getBlockHeight();

    if (currentBlockHeight > lastValidBlockHeight) {
      alert('Transaction expired, please sign again');
      return;
    }

    await connection.sendRawTransaction(signedTransaction);
    ```
  </Accordion>

  <Accordion title="Signature verification failed">
    Ensure transaction is properly constructed:

    ```tsx theme={null}
    // ✅ Correct - Set all required fields
    transaction.recentBlockhash = blockhash;
    transaction.feePayer = new PublicKey(user.wallet.address);

    // All signers must be specified
    transaction.sign(); // If additional signers needed
    ```
  </Accordion>

  <Accordion title="Cannot deserialize signed transaction">
    Use proper serialization/deserialization:

    ```tsx theme={null}
    import { Transaction } from '@solana/web3.js';

    // Serialize
    const serialized = signedTransaction;

    // Deserialize
    const transaction = Transaction.from(serialized);
    ```
  </Accordion>

  <Accordion title="User cancelled signing">
    Handle cancellations gracefully:

    ```tsx theme={null}
    const { signTransaction } = useSignTransaction({
      onError: (error: any) => {
        if (error.code === 'USER_REJECTED') {
          console.log('User cancelled signing');
          return;
        }
        console.error('Signing error:', error);
      }
    });
    ```
  </Accordion>
</AccordionGroup>

## Related Documentation

<CardGroup cols={2}>
  <Card title="Send Transaction" icon="paper-plane" href="/wallet-as-a-service/using-wallets/solana/send-transaction">
    Sign and broadcast transactions
  </Card>

  <Card title="Sign Message" icon="signature" href="/wallet-as-a-service/using-wallets/solana/sign-message">
    Sign messages with Solana wallet
  </Card>

  <Card title="Solana Web3.js" icon="code" href="https://solana-labs.github.io/solana-web3.js/">
    Official Solana JavaScript library
  </Card>

  <Card title="Transaction Format" icon="book" href="https://docs.solana.com/developing/programming-model/transactions">
    Solana transaction structure
  </Card>
</CardGroup>

## Next Steps

* Learn about [sending transactions](/wallet-as-a-service/using-wallets/solana/send-transaction)
* Understand [Solana's transaction lifecycle](https://docs.solana.com/developing/programming-model/transactions)
* Explore [transaction confirmation strategies](https://docs.solana.com/developing/clients/jsonrpc-api#confirming-transactions)
* Implement [transaction retry logic](https://docs.solana.com/developing/transaction_confirmation) for reliability
