Skip to main content
The Sign Transaction screen is displayed when your application requests a wallet signature for a blockchain transaction. This screen shows users the transaction details and allows them to approve or reject the transaction before it’s signed.
Transaction signing creates a signed transaction that can be broadcast to the blockchain. This is different from Send Transaction, which both signs and broadcasts the transaction automatically.

How it works

When your application requests a transaction signature:
  1. Your app calls the sign transaction method from the MoonKey SDK
  2. The Sign Transaction screen appears, displaying transaction details
  3. User reviews the transaction information (recipient, amount, gas, etc.)
  4. User approves or rejects the signature request
  5. If approved, the signed transaction is returned to your application
  6. Your application can then broadcast the signed transaction to the blockchain

Triggering the Sign Transaction screen

Use the useSignTransaction hook from the MoonKey SDK to trigger the Sign Transaction screen:
  • Ethereum
  • Solana
import { useMoonKey } from '@moon-key/react-auth';
import { useSignTransaction } from '@moon-key/react-auth/ethereum';

export default function SignTransactionButton() {
  const { user } = useMoonKey();
  const { signTransaction } = useSignTransaction();
  
  const handleSign = async () => {
    if (!user?.wallet) {
      alert('No wallet found');
      return;
    }
    
    try {
      const result = await signTransaction({
        transaction: {
          to: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045',
          value: '0x38d7ea4c68000', // 0.001 ETH in hex
          data: '0x',
          gas: '0x5208', // 21000 in hex
          gasPrice: '0x4a817c800' // 20 gwei in hex
        },
        wallet: user.wallet,
        options: {
          uiOptions: {
            title: 'Sign Transaction',
            description: 'Review and sign this transaction',
            buttonText: 'Sign',
            showWalletUI: true,
            successHeader: 'Transaction Signed!',
            successDescription: 'Your transaction has been signed successfully',
            isCancellable: true
          }
        }
      });
      
      console.log('Signed transaction:', result.signature);
      
      // Now you can broadcast the signed transaction
      // await broadcastTransaction(result.signature);
    } catch (error) {
      console.error('User rejected transaction:', error);
    }
  };
  
  return <button onClick={handleSign}>Sign Transaction</button>;
}

Customizing the Sign Transaction screen

You can customize the Sign Transaction screen by passing UI options in the options.uiOptions object:
const result = await signTransaction({
  transaction: transactionData,
  wallet: user.wallet,
  options: {
    uiOptions: {
      title: 'Approve Transaction',
      description: 'Please review the transaction details before signing',
      buttonText: 'Approve & Sign',
      showWalletUI: true,
      successHeader: 'Success!',
      successDescription: 'Transaction signed and ready to broadcast',
      isCancellable: true,
      transactionInfo: {
        // Custom transaction display options
      }
    }
  }
});

Configuration options

Basic UI Options

title
string
Custom title text displayed at the top of the Sign Transaction screen.Example:
options: {
  uiOptions: {
    title: 'Approve Transaction'
  }
}
description
string
Custom description text displayed below the title, providing context for the transaction.Example:
options: {
  uiOptions: {
    description: 'Review the transaction details carefully before signing'
  }
}
buttonText
string
Custom text for the confirmation button.Default: 'Sign'Example:
options: {
  uiOptions: {
    buttonText: 'Approve & Sign'
  }
}
showWalletUI
boolean
Whether to show wallet information (address, balance) in the Sign Transaction screen.Default: trueExample:
options: {
  uiOptions: {
    showWalletUI: false // Hide wallet details for a cleaner UI
  }
}

Success Screen Options

successHeader
string
Header text displayed on the success screen after the transaction is signed.Example:
options: {
  uiOptions: {
    successHeader: 'Transaction Signed Successfully!'
  }
}
successDescription
string
Description text displayed on the success screen after the transaction is signed.Example:
options: {
  uiOptions: {
    successDescription: 'Your transaction is ready to be broadcast to the network'
  }
}

Behavior Options

isCancellable
boolean
Whether the user can cancel the transaction signing request.Default: trueExample:
options: {
  uiOptions: {
    isCancellable: false // Require user to sign or close the entire modal
  }
}
transactionInfo
TransactionUIOptions
Custom options for displaying transaction information in the UI.This allows you to customize how transaction details are presented to the user.Example:
options: {
  uiOptions: {
    transactionInfo: {
      // Custom transaction display configuration
    }
  }
}

Common use cases

Token transfer transaction

Sign a transaction to transfer ERC-20 tokens:
import { useMoonKey } from '@moon-key/react-auth';
import { useSignTransaction } from '@moon-key/react-auth/ethereum';
import { encodeFunctionData } from 'viem';

export default function SignTokenTransfer() {
  const { user } = useMoonKey();
  const { signTransaction } = useSignTransaction();
  
  const signTokenTransferTx = async () => {
    if (!user?.wallet) return;
    
    try {
      // Encode ERC-20 transfer function call
      const data = encodeFunctionData({
        abi: [{
          name: 'transfer',
          type: 'function',
          inputs: [
            { name: 'to', type: 'address' },
            { name: 'amount', type: 'uint256' }
          ]
        }],
        functionName: 'transfer',
        args: ['0xRecipientAddress', BigInt(1000000000000000000)] // 1 token with 18 decimals
      });
      
      const result = await signTransaction({
        transaction: {
          to: '0xTokenContractAddress',
          data,
          value: '0x0'
        },
        wallet: user.wallet,
        options: {
          uiOptions: {
            title: 'Sign Token Transfer',
            description: 'Transfer 1 TOKEN to recipient',
            buttonText: 'Sign Transfer',
            successHeader: 'Transfer Signed!',
            successDescription: 'Token transfer is ready to broadcast'
          }
        }
      });
      
      console.log('Signed transaction:', result.signature);
      // Broadcast the signed transaction
    } catch (error) {
      console.error('Failed to sign:', error);
    }
  };
  
  return <button onClick={signTokenTransferTx}>Sign Token Transfer</button>;
}

Smart contract interaction

Sign a transaction to interact with a smart contract:
const signContractInteraction = async () => {
  if (!user?.wallet) return;
  
  const result = await signTransaction({
    transaction: {
      to: '0xSmartContractAddress',
      data: encodedFunctionCall,
      value: '0x0',
      gas: '0x30d40' // 200,000 gas limit
    },
    wallet: user.wallet,
    options: {
      uiOptions: {
        title: 'Sign Contract Interaction',
        description: 'Approve this transaction to interact with the smart contract',
        buttonText: 'Approve',
        showWalletUI: true,
        isCancellable: true
      }
    }
  });
  
  return result.signature;
};

NFT minting transaction

Sign a transaction to mint an NFT:
const signNFTMint = async () => {
  if (!user?.wallet) return;
  
  const result = await signTransaction({
    transaction: {
      to: '0xNFTContractAddress',
      data: mintFunctionData,
      value: '0x16345785D8A0000' // 0.1 ETH mint price
    },
    wallet: user.wallet,
    options: {
      uiOptions: {
        title: 'Mint NFT',
        description: 'Sign to mint your NFT for 0.1 ETH',
        buttonText: 'Mint Now',
        successHeader: 'NFT Mint Signed!',
        successDescription: 'Your NFT mint transaction is ready to broadcast',
        showWalletUI: true
      }
    }
  });
  
  // Broadcast the signed transaction
  const txHash = await broadcastTransaction(result.signature);
  console.log('Transaction hash:', txHash);
};

Batch signing multiple transactions

Sign multiple transactions in sequence:
const signMultipleTransactions = async (transactions: any[]) => {
  if (!user?.wallet) return;
  
  const signedTransactions = [];
  
  for (let i = 0; i < transactions.length; i++) {
    try {
      const result = await signTransaction({
        transaction: transactions[i],
        wallet: user.wallet,
        options: {
          uiOptions: {
            title: `Sign Transaction ${i + 1} of ${transactions.length}`,
            description: 'Please sign each transaction to proceed',
            buttonText: 'Sign',
            isCancellable: true
          }
        }
      });
      
      signedTransactions.push(result.signature);
    } catch (error) {
      console.error(`Failed to sign transaction ${i + 1}:`, error);
      break; // Stop if user cancels
    }
  }
  
  return signedTransactions;
};

Complete example

Here’s a complete example with full transaction signing flow:
'use client';
import { useMoonKey } from '@moon-key/react-auth';
import { useSignTransaction } from '@moon-key/react-auth/ethereum';
import { useState } from 'react';

export default function SignTransactionDemo() {
  const { user, isAuthenticated } = useMoonKey();
  const { signTransaction } = useSignTransaction();
  const [signedTx, setSignedTx] = useState<string | null>(null);
  const [broadcasting, setBroadcasting] = useState(false);
  
  if (!isAuthenticated) {
    return <div>Please sign in first</div>;
  }
  
  if (!user?.wallet) {
    return <div>No wallet found</div>;
  }
  
  const handleSignTransaction = async () => {
    try {
      const result = await signTransaction({
        transaction: {
          to: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045',
          value: '0x38d7ea4c68000', // 0.001 ETH
          data: '0x'
        },
        wallet: user.wallet,
        options: {
          uiOptions: {
            title: 'Send 0.001 ETH',
            description: 'Sign this transaction to send 0.001 ETH',
            buttonText: 'Sign Transaction',
            showWalletUI: true,
            successHeader: 'Transaction Signed!',
            successDescription: 'Ready to broadcast to the network',
            isCancellable: true
          }
        }
      });
      
      setSignedTx(result.signature);
      console.log('Transaction signed:', result.signature);
    } catch (error) {
      console.error('Failed to sign transaction:', error);
      alert('Transaction signing failed or was cancelled');
    }
  };
  
  const handleBroadcast = async () => {
    if (!signedTx) return;
    
    setBroadcasting(true);
    try {
      // Broadcast the signed transaction using your preferred method
      const response = await fetch('/api/broadcast-transaction', {
        method: 'POST',
        body: JSON.stringify({ signedTransaction: signedTx })
      });
      
      const data = await response.json();
      alert(`Transaction broadcast! Hash: ${data.txHash}`);
    } catch (error) {
      console.error('Failed to broadcast:', error);
      alert('Failed to broadcast transaction');
    } finally {
      setBroadcasting(false);
    }
  };
  
  return (
    <div>
      <h2>Sign Transaction Example</h2>
      <p>Wallet: {user.wallet.address}</p>
      
      <button onClick={handleSignTransaction} disabled={!!signedTx}>
        Sign Transaction
      </button>
      
      {signedTx && (
        <div style={{ marginTop: '20px' }}>
          <h3>Signed Transaction:</h3>
          <pre style={{ 
            background: '#f5f5f5', 
            padding: '10px', 
            borderRadius: '5px',
            overflow: 'auto',
            fontSize: '12px'
          }}>
            {signedTx}
          </pre>
          
          <button 
            onClick={handleBroadcast} 
            disabled={broadcasting}
            style={{ marginTop: '10px' }}
          >
            {broadcasting ? 'Broadcasting...' : 'Broadcast Transaction'}
          </button>
        </div>
      )}
    </div>
  );
}

User experience flow

1

Trigger transaction signing

Your application calls signTransaction() with transaction data, wallet, and optional UI options.
2

Sign Transaction screen appears

The customized Sign Transaction screen is displayed with transaction details.
3

Review transaction details

User reviews the transaction information including recipient, amount, gas fees, and any contract interactions.
4

Approve or reject

User clicks the confirm button to sign, or cancels (if isCancellable: true) to reject.
5

Transaction signed

If approved, MoonKey signs the transaction with the user’s wallet private key.
6

Success screen

The success screen appears with the configured successHeader and successDescription.
7

Return signed transaction

The signed transaction is returned to your application for broadcasting.

Sign Transaction vs. Send Transaction

Understanding the difference between these two operations:
  • Only signs the transaction
  • Does not broadcast to the blockchain
  • Returns a signed transaction that you must broadcast manually
  • Useful when you need control over broadcasting timing
  • Can sign transactions offline
  • Good for batch operations or complex flows
  • Example: Sign now, broadcast later when conditions are met
  • Signs and broadcasts in one step
  • Automatically submits the transaction to the blockchain
  • Returns the transaction hash after broadcasting
  • Simpler for basic send operations
  • Requires active network connection
  • Best for immediate transactions
  • Example: Send ETH/tokens directly to a recipient

Broadcasting signed transactions

After signing a transaction, you need to broadcast it to the blockchain:
  • Ethereum
  • Solana
import { createPublicClient, http } from 'viem';
import { mainnet } from 'viem/chains';

// After signing
const result = await signTransaction({
  transaction: txData,
  wallet: user.wallet
});

// Broadcast using viem
const publicClient = createPublicClient({
  chain: mainnet,
  transport: http()
});

const txHash = await publicClient.sendRawTransaction({
  serializedTransaction: result.signature as `0x${string}`
});

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

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

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

Best practices

Always explain what the transaction does in the description:
options: {
  uiOptions: {
    title: 'Transfer USDC',
    description: 'Send 100 USDC to 0x1234...5678. This will cost approximately $2 in gas fees.'
  }
}
Enable showWalletUI so users can verify they have sufficient balance:
options: {
  uiOptions: {
    showWalletUI: true // Show balance to prevent insufficient funds errors
  }
}
Inform users about next steps after signing:
options: {
  uiOptions: {
    successHeader: 'Transaction Signed!',
    successDescription: 'Broadcasting to the network. This may take a few seconds.'
  }
}
Users may reject transactions or encounter errors:
try {
  const result = await signTransaction({ 
    transaction: txData, 
    wallet: user.wallet 
  });
  await broadcastTransaction(result.signature);
} catch (error) {
  if (error.code === 4001) {
    // User rejected
    toast.info('Transaction cancelled');
  } else {
    // Other error
    toast.error('Failed to sign transaction');
  }
}
Check transaction validity before prompting the user:
// Check balance
if (balance < transactionAmount + estimatedGas) {
  alert('Insufficient funds for transaction and gas');
  return;
}

// Then proceed with signing
const result = await signTransaction({ ... });
Set isCancellable: true for most transactions:
options: {
  uiOptions: {
    isCancellable: true // Let users back out if needed
  }
}
Only set isCancellable: false for critical flows where cancellation would break your app state.
Before broadcasting, verify the signed transaction matches your intent:
const result = await signTransaction({ transaction: txData, wallet: user.wallet });

// Store signature for later
await savePendingTransaction({
  signature: result.signature,
  originalData: txData,
  timestamp: Date.now()
});

// Verify before broadcast
const verified = await verifyTransaction(result.signature);
if (verified) {
  await broadcastTransaction(result.signature);
}

Security considerations

Always review transaction details carefully before signing. Malicious applications could trick users into signing transactions that drain their wallets or approve unlimited token spending.
For developers:
  1. Display full transaction details - Show recipient, amount, gas fees, and any contract interactions
  2. Validate transaction data - Verify the transaction is well-formed before requesting signature
  3. Check balances - Ensure users have sufficient funds before signing
  4. Rate limit signing - Prevent abuse by limiting how many transactions can be signed
  5. Log signed transactions - Keep records for debugging and user support
  6. Use secure RPC endpoints - Broadcast to trusted nodes only
For users: The Sign Transaction screen helps users by:
  • Displaying transaction details (recipient, amount, gas)
  • Showing their wallet balance
  • Providing clear context via title and description
  • Allowing them to cancel if isCancellable is true
  • Showing success confirmation before closing

Next steps