Skip to main content
The Send Transaction screen is displayed when your application requests to sign and broadcast a blockchain transaction. This screen shows users the transaction details, signs it with their wallet, and automatically broadcasts it to the blockchain network.
Send Transaction combines signing and broadcasting in one step. For more control over when a transaction is broadcast, use Sign Transaction instead.

How it works

When your application requests to send a transaction:
  1. Your app calls the send transaction method from the MoonKey SDK
  2. The Send Transaction screen appears, displaying transaction details
  3. User reviews the transaction information (recipient, amount, gas, etc.)
  4. User approves or rejects the transaction
  5. If approved, MoonKey signs the transaction with the user’s wallet
  6. MoonKey automatically broadcasts the signed transaction to the blockchain
  7. The transaction hash is returned to your application

Triggering the Send Transaction screen

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

export default function SendTransactionButton() {
  const { user } = useMoonKey();
  const { sendTransaction } = useSendTransaction();
  
  const handleSend = async () => {
    if (!user?.wallet) {
      alert('No wallet found');
      return;
    }
    
    try {
      const result = await sendTransaction({
        transaction: {
          to: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045',
          value: '0x38d7ea4c68000', // 0.001 ETH in hex
          data: '0x'
        },
        wallet: user.wallet,
        uiConfig: {
          title: 'Send 0.001 ETH',
          description: 'Transfer 0.001 ETH to the recipient',
          confirmButtonText: 'Send',
          showWalletUI: true,
          success: {
            title: 'Transaction Sent!',
            description: 'Your transaction is being processed'
          }
        }
      });
      
      console.log('Transaction hash:', result.transactionHash);
      alert(`Transaction sent! Hash: ${result.transactionHash}`);
    } catch (error) {
      console.error('Transaction failed:', error);
    }
  };
  
  return <button onClick={handleSend}>Send 0.001 ETH</button>;
}

Customizing the Send Transaction screen

You can customize the Send Transaction screen by passing UI configuration in the uiConfig object:
const result = await sendTransaction({
  transaction: transactionData,
  wallet: user.wallet,
  uiConfig: {
    logoUrl: 'https://your-app.com/logo.png',
    title: 'Send Payment',
    description: 'Review and send this payment',
    confirmButtonText: 'Send Now',
    cancelButtonText: 'Cancel',
    showWalletUI: true,
    waitBeforeCloseTime: 2000,
    advancedTransaction: {
      title: 'Advanced Settings',
      closeButtonText: 'Close'
    },
    success: {
      title: 'Payment Sent!',
      description: 'Your payment is on its way'
    },
    failed: {
      title: 'Transaction Failed',
      description: 'Your transaction could not be completed',
      errorTitle: 'Error Details',
      closeButtonText: 'Close'
    }
  }
});

Configuration options

Basic UI Options

logoUrl
string
URL to your company logo. Displayed at the top of the Send Transaction screen.Example:
uiConfig: {
  logoUrl: 'https://myapp.com/logo.png'
}
title
string
Custom title text displayed at the top of the Send Transaction screen.Example:
uiConfig: {
  title: 'Send Payment'
}
description
string
Custom description text displayed below the title, providing context for the transaction.Example:
uiConfig: {
  description: 'Review the payment details before sending'
}
confirmButtonText
string
Custom text for the confirm/send button.Default: 'Send'Example:
uiConfig: {
  confirmButtonText: 'Send Now'
}
cancelButtonText
string
Custom text for the cancel button.Default: 'Cancel'Example:
uiConfig: {
  cancelButtonText: 'Go Back'
}
showWalletUI
boolean
Whether to show wallet information (address, balance) in the Send Transaction screen.Default: trueExample:
uiConfig: {
  showWalletUI: true // Show balance to prevent insufficient funds errors
}
waitBeforeCloseTime
number
Time in milliseconds to wait before automatically closing the screen after a successful transaction.Default: 1500 (1.5 seconds)Example:
uiConfig: {
  waitBeforeCloseTime: 2000 // Wait 2 seconds before closing
}

Advanced Transaction Options

advancedTransaction
object
Configuration for the advanced transaction settings screen.

Success Screen Options

success
object
Configuration for the success screen displayed after the transaction is sent.

Failed Screen Options

failed
object
Configuration for the error screen displayed if the transaction fails.

Common use cases

Simple ETH/SOL transfer

Send native currency to a recipient:
import { useMoonKey } from '@moon-key/react-auth';
import { useSendTransaction } from '@moon-key/react-auth/ethereum';
import { parseEther } from 'viem';

export default function SendETH() {
  const { user } = useMoonKey();
  const { sendTransaction } = useSendTransaction();
  
  const sendETH = async (recipient: string, amount: string) => {
    if (!user?.wallet) return;
    
    try {
      const result = await sendTransaction({
        transaction: {
          to: recipient,
          value: parseEther(amount).toString(16) // Convert to hex
        },
        wallet: user.wallet,
        uiConfig: {
          title: `Send ${amount} ETH`,
          description: `Transfer ${amount} ETH to ${recipient.slice(0, 6)}...${recipient.slice(-4)}`,
          confirmButtonText: 'Send ETH',
          showWalletUI: true,
          success: {
            title: 'ETH Sent!',
            description: 'Your ETH transfer is being confirmed on the blockchain'
          }
        }
      });
      
      console.log('Transaction hash:', result.transactionHash);
      return result.transactionHash;
    } catch (error) {
      console.error('Failed to send ETH:', error);
      throw error;
    }
  };
  
  return (
    <button onClick={() => sendETH('0xRecipient...', '0.1')}>
      Send 0.1 ETH
    </button>
  );
}

Token transfer

Send ERC-20 or SPL tokens:
import { useMoonKey } from '@moon-key/react-auth';
import { useSendTransaction } from '@moon-key/react-auth/ethereum';
import { encodeFunctionData } from 'viem';

export default function SendTokens() {
  const { user } = useMoonKey();
  const { sendTransaction } = useSendTransaction();
  
  const sendTokens = async (tokenAddress: string, recipient: string, amount: bigint) => {
    if (!user?.wallet) return;
    
    try {
      // Encode ERC-20 transfer
      const data = encodeFunctionData({
        abi: [{
          name: 'transfer',
          type: 'function',
          inputs: [
            { name: 'to', type: 'address' },
            { name: 'amount', type: 'uint256' }
          ]
        }],
        functionName: 'transfer',
        args: [recipient, amount]
      });
      
      const result = await sendTransaction({
        transaction: {
          to: tokenAddress,
          data,
          value: '0x0'
        },
        wallet: user.wallet,
        uiConfig: {
          title: 'Send USDC',
          description: 'Transfer 100 USDC to recipient',
          confirmButtonText: 'Send Tokens',
          success: {
            title: 'Tokens Sent!',
            description: 'Your token transfer is being processed'
          }
        }
      });
      
      console.log('Transaction hash:', result.transactionHash);
      return result.transactionHash;
    } catch (error) {
      console.error('Failed to send tokens:', error);
      throw error;
    }
  };
  
  return (
    <button onClick={() => sendTokens('0xUSDC...', '0xRecipient...', BigInt(100_000000))}>
      Send 100 USDC
    </button>
  );
}

NFT transfer

Transfer an NFT to another wallet:
const transferNFT = async (nftContract: string, tokenId: string, recipient: string) => {
  if (!user?.wallet) return;
  
  // Encode ERC-721 transferFrom
  const data = encodeFunctionData({
    abi: [{
      name: 'transferFrom',
      type: 'function',
      inputs: [
        { name: 'from', type: 'address' },
        { name: 'to', type: 'address' },
        { name: 'tokenId', type: 'uint256' }
      ]
    }],
    functionName: 'transferFrom',
    args: [user.wallet.address, recipient, BigInt(tokenId)]
  });
  
  const result = await sendTransaction({
    transaction: {
      to: nftContract,
      data,
      value: '0x0'
    },
    wallet: user.wallet,
    uiConfig: {
      title: 'Transfer NFT',
      description: `Transfer NFT #${tokenId} to ${recipient.slice(0, 6)}...${recipient.slice(-4)}`,
      confirmButtonText: 'Transfer',
      showWalletUI: true,
      success: {
        title: 'NFT Transferred!',
        description: 'Your NFT has been sent to the recipient'
      }
    }
  });
  
  return result.transactionHash;
};

Mint NFT

Mint an NFT with payment:
const mintNFT = async (nftContract: string, mintPrice: string) => {
  if (!user?.wallet) return;
  
  const result = await sendTransaction({
    transaction: {
      to: nftContract,
      data: encodedMintFunction,
      value: mintPrice // e.g., '0x16345785D8A0000' for 0.1 ETH
    },
    wallet: user.wallet,
    uiConfig: {
      title: 'Mint NFT',
      description: 'Mint your NFT for 0.1 ETH',
      confirmButtonText: 'Mint Now',
      showWalletUI: true,
      success: {
        title: 'NFT Minted!',
        description: 'Your NFT is being minted. Check your wallet in a few moments.'
      }
    }
  });
  
  console.log('Mint transaction hash:', result.transactionHash);
  return result.transactionHash;
};

Contract interaction with gas estimation

Send a transaction with custom gas settings:
const interactWithContract = async () => {
  if (!user?.wallet) return;
  
  const result = await sendTransaction({
    transaction: {
      to: '0xContractAddress',
      data: encodedFunctionCall,
      value: '0x0',
      gas: '0x30d40', // 200,000 gas limit
      gasPrice: '0x4a817c800' // 20 gwei
    },
    wallet: user.wallet,
    uiConfig: {
      title: 'Execute Contract Function',
      description: 'Call the contract function with the specified parameters',
      confirmButtonText: 'Execute',
      success: {
        title: 'Contract Called!',
        description: 'Your transaction is being confirmed'
      }
    }
  });
  
  return result.transactionHash;
};

Complete example

Here’s a complete example with transaction tracking:
'use client';
import { useMoonKey } from '@moon-key/react-auth';
import { useSendTransaction } from '@moon-key/react-auth/ethereum';
import { useState } from 'react';
import { parseEther } from 'viem';

export default function SendTransactionDemo() {
  const { user, isAuthenticated } = useMoonKey();
  const { sendTransaction } = useSendTransaction();
  const [txHash, setTxHash] = useState<string | null>(null);
  const [sending, setSending] = useState(false);
  const [recipient, setRecipient] = useState('0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045');
  const [amount, setAmount] = useState('0.001');
  
  if (!isAuthenticated) {
    return <div>Please sign in first</div>;
  }
  
  if (!user?.wallet) {
    return <div>No wallet found</div>;
  }
  
  const handleSendTransaction = async () => {
    setSending(true);
    try {
      const result = await sendTransaction({
        transaction: {
          to: recipient,
          value: `0x${parseEther(amount).toString(16)}`,
          data: '0x'
        },
        wallet: user.wallet,
        uiConfig: {
          title: `Send ${amount} ETH`,
          description: `Transfer ${amount} ETH to ${recipient.slice(0, 6)}...${recipient.slice(-4)}`,
          confirmButtonText: 'Send Transaction',
          cancelButtonText: 'Cancel',
          showWalletUI: true,
          success: {
            title: 'Transaction Sent!',
            description: 'Your transaction is being confirmed on the blockchain'
          },
          failed: {
            title: 'Transaction Failed',
            description: 'Please try again or check your balance',
            closeButtonText: 'Close'
          }
        }
      });
      
      setTxHash(result.transactionHash);
      console.log('Transaction sent:', result.transactionHash);
      alert('Transaction sent successfully!');
    } catch (error) {
      console.error('Failed to send transaction:', error);
      alert('Transaction failed or was cancelled');
    } finally {
      setSending(false);
    }
  };
  
  return (
    <div style={{ padding: '20px' }}>
      <h2>Send Transaction Example</h2>
      <p>Wallet: {user.wallet.address}</p>
      
      <div style={{ marginTop: '20px' }}>
        <div style={{ marginBottom: '10px' }}>
          <label>
            Recipient Address:
            <input
              type="text"
              value={recipient}
              onChange={(e) => setRecipient(e.target.value)}
              style={{
                width: '100%',
                padding: '8px',
                marginTop: '5px'
              }}
            />
          </label>
        </div>
        
        <div style={{ marginBottom: '10px' }}>
          <label>
            Amount (ETH):
            <input
              type="text"
              value={amount}
              onChange={(e) => setAmount(e.target.value)}
              style={{
                width: '100%',
                padding: '8px',
                marginTop: '5px'
              }}
            />
          </label>
        </div>
        
        <button 
          onClick={handleSendTransaction}
          disabled={sending || !recipient || !amount}
          style={{
            padding: '10px 20px',
            marginTop: '10px'
          }}
        >
          {sending ? 'Sending...' : 'Send Transaction'}
        </button>
      </div>
      
      {txHash && (
        <div style={{ 
          marginTop: '20px',
          padding: '15px',
          background: '#f0f9ff',
          borderRadius: '5px'
        }}>
          <h3>Transaction Sent!</h3>
          <p>Transaction Hash:</p>
          <pre style={{ 
            background: '#fff', 
            padding: '10px', 
            borderRadius: '5px',
            overflow: 'auto',
            fontSize: '12px'
          }}>
            {txHash}
          </pre>
          <a 
            href={`https://etherscan.io/tx/${txHash}`}
            target="_blank"
            rel="noopener noreferrer"
            style={{ color: '#0066cc' }}
          >
            View on Etherscan →
          </a>
        </div>
      )}
    </div>
  );
}

User experience flow

1

Trigger transaction

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

Send Transaction screen appears

The customized Send 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 send button to proceed, or cancels (if isCancellable: true) to reject.
5

Transaction signed and broadcast

If approved, MoonKey signs the transaction and automatically broadcasts it to the blockchain network.
6

Success screen

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

Return transaction hash

The transaction hash (or signature for Solana) is returned to your application for tracking.

Send Transaction vs. Sign Transaction

Understanding when to use each:
  • Signs and broadcasts in one step
  • Automatically submits to the blockchain
  • Returns transaction hash immediately
  • Simpler API for basic operations
  • Best for immediate transactions
  • User can’t control broadcast timing
  • Most common for standard transfers
  • Example: Send ETH, transfer tokens, mint NFTs
  • Only signs the transaction
  • Does not broadcast automatically
  • Returns signed transaction data
  • Requires manual broadcasting
  • More control over when to broadcast
  • Can sign offline
  • Good for batch operations or complex flows
  • Example: Multi-step flows, conditional broadcasting
Use Send Transaction for most use cases. Only use Sign Transaction when you need explicit control over the broadcasting step.

Tracking transaction status

After sending a transaction, you typically want to track its confirmation status:
  • Ethereum
  • Solana
import { createPublicClient, http } from 'viem';
import { mainnet } from 'viem/chains';

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

console.log('Transaction hash:', result.transactionHash);

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

// Wait for transaction receipt
const receipt = await publicClient.waitForTransactionReceipt({
  hash: result.transactionHash as `0x${string}`
});

console.log('Transaction confirmed!', receipt);
console.log('Block number:', receipt.blockNumber);
console.log('Gas used:', receipt.gasUsed);
console.log('Status:', receipt.status); // 'success' or 'reverted'

// Get transaction details
const transaction = await publicClient.getTransaction({
  hash: result.transactionHash as `0x${string}`
});

console.log('Transaction details:', transaction);

Best practices

Always provide clear information about what the transaction does:
uiConfig: {
  title: 'Send 100 USDC',
  description: 'Transfer 100 USDC to alice.eth. Estimated gas: $2.50'
}
Check transaction validity before showing the UI:
// Validate recipient address
if (!isAddress(recipient)) {
  alert('Invalid recipient address');
  return;
}

// Check sufficient balance
const balance = await getBalance(user.wallet.address);
if (balance < amount + estimatedGas) {
  alert('Insufficient funds');
  return;
}

// Then send
const result = await sendTransaction({ ... });
Different errors require different handling:
try {
  const result = await sendTransaction({ 
    transaction: txData, 
    wallet: user.wallet 
  });
  toast.success('Transaction sent!');
} catch (error) {
  if (error.code === 4001) {
    // User rejected
    toast.info('Transaction cancelled');
  } else if (error.message?.includes('insufficient funds')) {
    toast.error('Insufficient balance for transaction');
  } else if (error.message?.includes('gas')) {
    toast.error('Gas estimation failed. Transaction may fail.');
  } else {
    toast.error('Transaction failed. Please try again.');
  }
  console.error('Transaction error:', error);
}
Help users track their transaction:
const result = await sendTransaction({ ... });

// Show link to block explorer
const explorerUrl = `https://etherscan.io/tx/${result.transactionHash}`;

toast.success(
  <div>
    Transaction sent!
    <a href={explorerUrl} target="_blank">View on Etherscan</a>
  </div>
);

// Store for user's transaction history
await saveTransaction({
  hash: result.transactionHash,
  from: user.wallet.address,
  to: recipient,
  amount,
  timestamp: Date.now()
});
Always show wallet UI so users can verify they have sufficient funds:
uiConfig: {
  showWalletUI: true // Critical for preventing failed transactions
}
For complex transactions, set appropriate gas limits:
// Estimate gas first
const estimatedGas = await publicClient.estimateGas({
  to: recipient,
  data,
  value: amount
});

// Add 20% buffer
const gasLimit = (estimatedGas * 120n) / 100n;

const result = await sendTransaction({
  transaction: {
    to: recipient,
    data,
    value: amount,
    gas: `0x${gasLimit.toString(16)}`
  },
  wallet: user.wallet
});
Keep a record of sent transactions for user reference:
const result = await sendTransaction({ ... });

// Store in local storage or database
const txRecord = {
  hash: result.transactionHash,
  from: user.wallet.address,
  to: recipient,
  amount,
  timestamp: Date.now(),
  status: 'pending'
};

await saveTxHistory(txRecord);

// Update status when confirmed
const receipt = await waitForReceipt(result.transactionHash);
await updateTxStatus(result.transactionHash, receipt.status);
Inform users about next steps:
uiConfig: {
  success: {
    title: 'Payment Sent!',
    description: 'Your payment is being confirmed. This usually takes 15-30 seconds.'
  }
}

Error handling

Common errors and how to handle them:
try {
  const result = await sendTransaction({ ... });
  return result.transactionHash;
} catch (error) {
  // User rejected the transaction
  if (error.code === 4001) {
    console.log('User cancelled transaction');
    return null;
  }
  
  // Insufficient funds
  if (error.message?.includes('insufficient funds')) {
    alert('Not enough balance to complete transaction');
    return null;
  }
  
  // Gas estimation failed
  if (error.message?.includes('gas required exceeds allowance')) {
    alert('Transaction would fail. Please check parameters.');
    return null;
  }
  
  // Network error
  if (error.message?.includes('network')) {
    alert('Network error. Please check your connection.');
    return null;
  }
  
  // Generic error
  console.error('Transaction error:', error);
  alert('Transaction failed. Please try again.');
  return null;
}

Security considerations

Always validate transaction parameters before sending. Malicious applications could trick users into sending funds to unintended recipients or approving dangerous contract interactions.
For developers:
  1. Validate all inputs - Check addresses, amounts, and data before sending
  2. Show full details - Display recipient, amount, gas fees, and contract interactions
  3. Estimate gas - Provide accurate gas estimates to prevent failed transactions
  4. Check balances - Verify sufficient funds before attempting to send
  5. Rate limit - Prevent abuse by limiting transaction frequency
  6. Log transactions - Keep records for debugging and support
  7. Handle errors - Provide clear error messages for different failure scenarios
For users: The Send Transaction screen helps users by:
  • Displaying full transaction details before sending
  • Showing wallet balance to prevent insufficient funds errors
  • Providing clear context via title and description
  • Allowing cancellation if isCancellable is true
  • Showing success confirmation with transaction hash

Next steps