> ## 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 transactions from a user's embedded Ethereum wallet without broadcasting them to the network. This allows you to review, store, or broadcast transactions at a later time.

## Overview

The `useSignTransaction` hook from MoonKey's React SDK provides an interface for signing transactions without immediately broadcasting them. This is useful for scenarios where you need to:

* Batch multiple transactions for later submission
* Implement custom transaction broadcasting logic
* Store signed transactions for delayed execution
* Create transactions that will be submitted by a relayer

<Info>
  **Signing vs Sending**: `signTransaction` only signs the transaction and returns the signature, while `sendTransaction` both signs and broadcasts the transaction to the network.
</Info>

## 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/ethereum';

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

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

        try {
          const result = await signTransaction({
            to: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045',
            value: '0x38d7ea4c68000', // 0.001 ETH in wei (hex)
            chainId: 1 // Mainnet
          });

          console.log('Transaction signed:', result.signedTransaction);
          console.log('Signature:', result.signature);
        } catch (error) {
          console.error('Signing failed:', 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/ethereum';

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

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

        try {
          const result = await signTransaction({
            to: '0xRecipient...',
            value: '0x38d7ea4c68000',
            chainId: 1
          }, {
            uiConfig: {
              title: 'Sign Transaction',
              description: 'Sign this transaction for later submission',
              confirmButtonText: 'Sign',
              cancelButtonText: 'Cancel'
            }
          });

          console.log('Signed successfully:', result.signedTransaction);
        } catch (error) {
          console.error('Signing failed:', error);
        }
      };

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

## Parameters

### Transaction Object

The first parameter to `signTransaction` is the transaction object:

<ParamField path="to" type="string" required>
  The recipient address for the transaction.

  **Example:**

  ```tsx theme={null}
  to: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'
  ```
</ParamField>

<ParamField path="value" type="string">
  The amount of ETH to send in wei, as a hexadecimal string. Omit for contract interactions with no ETH transfer.

  **Example:**

  ```tsx theme={null}
  value: '0x38d7ea4c68000' // 0.001 ETH
  ```
</ParamField>

<ParamField path="data" type="string">
  The encoded function call data for contract interactions, as a hexadecimal string.

  **Example:**

  ```tsx theme={null}
  data: '0x095ea7b3000000000000000000000000...'
  ```
</ParamField>

<ParamField path="chainId" type="number" required>
  The chain ID for the transaction.

  **Example:**

  ```tsx theme={null}
  chainId: 1 // Ethereum Mainnet
  ```
</ParamField>

<ParamField path="gasLimit" type="string">
  Optional gas limit as a hexadecimal string. MoonKey estimates gas automatically if not provided.

  **Example:**

  ```tsx theme={null}
  gasLimit: '0x5208' // 21000 in decimal
  ```
</ParamField>

<ParamField path="maxFeePerGas" type="string">
  Optional maximum fee per gas for EIP-1559 transactions, as a hexadecimal string.

  **Example:**

  ```tsx theme={null}
  maxFeePerGas: '0x59682f00' // 1.5 gwei
  ```
</ParamField>

<ParamField path="maxPriorityFeePerGas" type="string">
  Optional maximum priority fee per gas for EIP-1559 transactions, as a hexadecimal string.

  **Example:**

  ```tsx theme={null}
  maxPriorityFeePerGas: '0x3b9aca00' // 1 gwei
  ```
</ParamField>

<ParamField path="nonce" type="number">
  Optional nonce for the transaction. MoonKey determines this automatically if not provided.

  **Example:**

  ```tsx theme={null}
  nonce: 5
  ```
</ParamField>

### Options Object

The second parameter is an optional configuration object:

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

  <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="showWalletUI" type="boolean">
      Whether to show the confirmation modal. Set to `false` to sign without confirmation.
    </ParamField>
  </Expandable>
</ParamField>

<ParamField path="wallet" type="Wallet">
  Specific wallet to use for signing. If not provided, uses the user's default wallet.

  **Example:**

  ```tsx theme={null}
  wallet: user.wallet
  ```
</ParamField>

## Returns

The `signTransaction` method returns a Promise that resolves with:

<ResponseField name="signedTransaction" type="string">
  The RLP-encoded signed transaction, ready to be broadcast.

  **Example:**

  ```tsx theme={null}
  "0x02f8730182012a8459682f00843b9aca0082520894d8da6bf26964af9d7eed9e03e53415d37aa9604587038d7ea4c6800080c080a0..."
  ```
</ResponseField>

<ResponseField name="signature" type="string">
  The transaction signature.

  **Example:**

  ```tsx theme={null}
  "0xa0cd31b38c3e3a48230bee2d5c687a0b2a5efcb298c58cfc3b43449eefd17857cf..."
  ```
</ResponseField>

<ResponseField name="encoding" type="'rlp'">
  The encoding format of the signed transaction. Currently always `'rlp'` for Ethereum.
</ResponseField>

## Complete Examples

### Contract Interaction Signing

```tsx theme={null}
'use client';
import { useMoonKey } from '@moon-key/react-auth';
import { useSignTransaction } from '@moon-key/react-auth/ethereum';
import { encodeFunctionData } from 'viem';

// ERC-20 ABI
const ERC20_ABI = [{
  name: 'transfer',
  type: 'function',
  inputs: [
    { name: 'to', type: 'address' },
    { name: 'amount', type: 'uint256' }
  ],
  outputs: [{ type: 'bool' }]
}];

export default function SignTokenTransfer() {
  const { user } = useMoonKey();
  const { signTransaction } = useSignTransaction();

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

    const tokenAddress = '0xTokenContractAddress...';
    const recipientAddress = '0xRecipientAddress...';
    const amount = BigInt('1000000000000000000'); // 1 token (18 decimals)

    try {
      // Encode the transfer function call
      const data = encodeFunctionData({
        abi: ERC20_ABI,
        functionName: 'transfer',
        args: [recipientAddress, amount]
      });

      // Sign the transaction
      const result = await signTransaction({
        to: tokenAddress,
        data,
        chainId: 1
      });

      console.log('Token transfer signed:', result.signedTransaction);

      // Store or submit the signed transaction
      await submitToBackend(result.signedTransaction);

      alert('Token transfer signed successfully!');
    } catch (error) {
      console.error('Signing failed:', error);
    }
  };

  const submitToBackend = async (signedTx: string) => {
    // Submit to your backend for processing
    await fetch('/api/store-signed-transaction', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ signedTransaction: signedTx })
    });
  };

  return (
    <button onClick={handleSignTransfer}>
      Sign Token Transfer
    </button>
  );
}
```

## Broadcasting Signed Transactions

After signing a transaction, you can broadcast it using `viem`'s public client:

```tsx theme={null}
import { createPublicClient, http } from 'viem';
import { mainnet } from 'viem/chains';

// Create a public client
const publicClient = createPublicClient({
  chain: mainnet,
  transport: http()
});

// Broadcast the signed transaction
const txHash = await publicClient.sendRawTransaction({
  serializedTransaction: signedTransaction as `0x${string}`
});

console.log('Transaction broadcasted:', txHash);

// Wait for confirmation
const receipt = await publicClient.waitForTransactionReceipt({
  hash: txHash
});

console.log('Transaction confirmed:', receipt.status);
```

## UI Customization

Customize the signing confirmation modal:

```tsx theme={null}
import { useSignTransaction } from '@moon-key/react-auth/ethereum';

const { signTransaction } = useSignTransaction();

await signTransaction({
  to: '0x...',
  value: '0x38d7ea4c68000',
  chainId: 1
}, {
  uiConfig: {
    title: 'Sign Transaction',
    description: 'Review and sign this transaction',
    confirmButtonText: 'Sign Transaction',
    cancelButtonText: 'Cancel'
  }
});
```

### Hide Modal

For automated flows, you can sign without showing the modal:

```tsx theme={null}
await signTransaction({
  to: '0x...',
  value: '0x38d7ea4c68000',
  chainId: 1
}, {
  uiConfig: {
    showWalletUI: false
  }
});
```

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

## Signing vs Sending

Understanding the difference between signing and sending transactions:

| Feature                   | `signTransaction`       | `sendTransaction`          |
| ------------------------- | ----------------------- | -------------------------- |
| **Signs transaction**     | ✅ Yes                   | ✅ Yes                      |
| **Broadcasts to network** | ❌ No                    | ✅ Yes                      |
| **Returns**               | Signed transaction      | Transaction hash           |
| **Use case**              | Deferred submission     | Immediate execution        |
| **Gas required**          | ❌ Not yet               | ✅ Yes                      |
| **Reversible**            | ✅ Yes (not broadcasted) | ❌ No (already broadcasted) |

### When to use `signTransaction`:

* **Batch operations**: Sign multiple transactions and submit together
* **Relayer services**: Submit through a third-party relayer for gasless transactions
* **Delayed execution**: Sign now, broadcast later
* **Custom broadcasting**: Implement your own transaction submission logic
* **Multi-sig workflows**: Collect signatures from multiple parties

### When to use `sendTransaction`:

* **Immediate execution**: Transaction should execute right away
* **Simple workflows**: Standard transaction flow without special handling
* **User-paid gas**: User pays gas fees directly
* **Real-time updates**: Need immediate confirmation

## Error Handling

Handle common signing errors:

```tsx theme={null}
import { useMoonKey } from '@moon-key/react-auth';
import { useSignTransaction } from '@moon-key/react-auth/ethereum';

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

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

    try {
      const result = await signTransaction({
        to: '0x...',
        value: '0x38d7ea4c68000',
        chainId: 1
      });

      console.log('Success:', result.signedTransaction);
    } catch (error: any) {
      // Handle specific error types
      if (error.code === 'USER_REJECTED') {
        console.log('User cancelled signing');
        alert('Signing cancelled');
      } else if (error.code === 'INVALID_PARAMS') {
        console.log('Invalid transaction parameters');
        alert('Invalid transaction data');
      } else if (error.code === 'NETWORK_ERROR') {
        console.log('Network error during signing');
        alert('Network error. Please try again');
      } else {
        console.error('Signing failed:', error);
        alert('Failed to sign transaction: ' + error.message);
      }
    }
  };

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

## Best Practices

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

    ```tsx theme={null}
    // ❌ Don't store in localStorage for sensitive transactions
    localStorage.setItem('signedTx', signedTransaction);

    // ✅ Store securely on your backend
    await fetch('/api/store-transaction', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${userToken}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        signedTransaction,
        userId: user.id,
        timestamp: Date.now()
      })
    });
    ```
  </Accordion>

  <Accordion title="Set appropriate nonce values">
    When batching transactions, manage nonces carefully:

    ```tsx theme={null}
    import { createPublicClient, http } from 'viem';
    import { mainnet } from 'viem/chains';

    const publicClient = createPublicClient({
      chain: mainnet,
      transport: http()
    });

    // Get current nonce
    const currentNonce = await publicClient.getTransactionCount({
      address: user.wallet.address as `0x${string}`
    });

    // Sign multiple transactions with incremental nonces
    const signedTxs = [];
    for (let i = 0; i < transactions.length; i++) {
      const result = await signTransaction({
        ...transactions[i],
        nonce: currentNonce + i,
        chainId: 1
      });
      signedTxs.push(result.signedTransaction);
    }
    ```
  </Accordion>

  <Accordion title="Validate before signing">
    Always validate transaction parameters before signing:

    ```tsx theme={null}
    import { isAddress } from 'viem';

    const handleSign = async () => {
      // Validate address
      if (!isAddress(recipient)) {
        alert('Invalid recipient address');
        return;
      }

      // Validate amount
      if (BigInt(value) <= 0n) {
        alert('Amount must be greater than 0');
        return;
      }

      // Validate chain ID
      const supportedChains = [1, 8453, 137];
      if (!supportedChains.includes(chainId)) {
        alert('Unsupported chain');
        return;
      }

      // Proceed with signing
      await signTransaction({ to: recipient, value, chainId });
    };
    ```
  </Accordion>

  <Accordion title="Handle transaction expiration">
    Signed transactions can become stale. Implement expiration logic:

    ```tsx theme={null}
    interface StoredTransaction {
      signedTransaction: string;
      createdAt: number;
      expiresAt: number;
    }

    const storeSignedTransaction = (signedTx: string) => {
      const now = Date.now();
      const expires = now + (5 * 60 * 1000); // 5 minutes

      const stored: StoredTransaction = {
        signedTransaction: signedTx,
        createdAt: now,
        expiresAt: expires
      };

      localStorage.setItem('signedTx', JSON.stringify(stored));
    };

    const getSignedTransaction = (): string | null => {
      const stored = localStorage.getItem('signedTx');
      if (!stored) return null;

      const data: StoredTransaction = JSON.parse(stored);

      // Check if expired
      if (Date.now() > data.expiresAt) {
        localStorage.removeItem('signedTx');
        return null;
      }

      return data.signedTransaction;
    };
    ```
  </Accordion>

  <Accordion title="Provide clear user feedback">
    Keep users informed about signing status:

    ```tsx theme={null}
    const [status, setStatus] = useState<'idle' | 'signing' | 'signed' | 'error'>('idle');

    const handleSign = async () => {
      setStatus('signing');

      try {
        const result = await signTransaction({ to: '0x...', value: '0x38d7ea4c68000', chainId: 1 });
        setStatus('signed');
        console.log('Transaction signed:', result.signedTransaction);
      } catch (error) {
        setStatus('error');
        console.error('Signing failed:', error);
      }
    };

    return (
      <div>
        <button onClick={handleSign} disabled={status === 'signing'}>
          {status === 'signing' && 'Signing...'}
          {status === 'signed' && 'Signed ✓'}
          {status === 'error' && 'Failed ✗'}
          {status === 'idle' && 'Sign Transaction'}
        </button>
        {status === 'signed' && <p>Transaction signed successfully. Ready to broadcast.</p>}
      </div>
    );
    ```
  </Accordion>
</AccordionGroup>

## Troubleshooting

<AccordionGroup>
  <Accordion title="Signed transaction won't broadcast">
    Common causes when broadcasting fails:

    * **Stale nonce**: The nonce used when signing is outdated
    * **Expired transaction**: Too much time passed since signing
    * **Insufficient gas**: Gas parameters were underestimated
    * **Invalid format**: Signed transaction is malformed

    ```tsx theme={null}
    try {
      const txHash = await publicClient.sendRawTransaction({
        serializedTransaction: signedTransaction as `0x${string}`
      });
    } catch (error: any) {
      if (error.message.includes('nonce')) {
        alert('Transaction nonce is invalid. Please sign again.');
      } else if (error.message.includes('underpriced')) {
        alert('Gas price is too low. Please sign with higher gas.');
      } else {
        console.error('Broadcast error:', error);
      }
    }
    ```
  </Accordion>

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

    ```tsx theme={null}
    try {
      const result = await signTransaction({ to: '0x...', value: '0x38d7ea4c68000', chainId: 1 });
    } catch (error: any) {
      if (error.code === 'USER_REJECTED') {
        // Don't show error - this is expected behavior
        console.log('User cancelled signing');
        return;
      }
      // Handle other errors
      alert('Signing failed: ' + error.message);
    }
    ```
  </Accordion>

  <Accordion title="Invalid chain ID">
    Ensure correct chain ID for the target network:

    ```tsx theme={null}
    const CHAIN_IDS = {
      mainnet: 1,
      base: 8453,
      polygon: 137,
      arbitrum: 42161,
      optimism: 10
    };

    const handleSign = async (network: keyof typeof CHAIN_IDS) => {
      const chainId = CHAIN_IDS[network];

      await signTransaction({
        to: '0x...',
        value: '0x38d7ea4c68000',
        chainId
      });
    };
    ```
  </Accordion>
</AccordionGroup>

## Related Documentation

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

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

  <Card title="Sign Transaction UI" icon="window" href="/wallet-as-a-service/ui-components/sign-transaction">
    Customize the signing confirmation UI
  </Card>

  <Card title="Interfacing Libraries" icon="book" href="/wallet-as-a-service/using-wallets/ethereum/interfacing-libraries">
    Use with viem and ethers
  </Card>
</CardGroup>

## Next Steps

* Learn about [sending transactions](/wallet-as-a-service/using-wallets/ethereum/send-transaction) for immediate execution
* Explore [message signing](/wallet-as-a-service/using-wallets/ethereum/sign-message) for authentication
* Understand [contract interactions](/wallet-as-a-service/using-wallets/ethereum/interfacing-libraries) with web3 libraries
* Customize the [signing UI](/wallet-as-a-service/ui-components/sign-transaction) to match your brand
