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:
Your app calls the sign transaction method from the MoonKey SDK
The Sign Transaction screen appears, displaying transaction details
User reviews the transaction information (recipient, amount, gas, etc.)
User approves or rejects the signature request
If approved, the signed transaction is returned to your application
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:
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
Custom title text displayed at the top of the Sign Transaction screen. Example: options : {
uiOptions : {
title : 'Approve Transaction'
}
}
Custom description text displayed below the title, providing context for the transaction. Example: options : {
uiOptions : {
description : 'Review the transaction details carefully before signing'
}
}
Custom text for the confirmation button. Default: 'Sign'Example: options : {
uiOptions : {
buttonText : 'Approve & Sign'
}
}
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
Header text displayed on the success screen after the transaction is signed. Example: options : {
uiOptions : {
successHeader : 'Transaction Signed Successfully!'
}
}
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
Whether the user can cancel the transaction signing request. Default: trueExample: options : {
uiOptions : {
isCancellable : false // Require user to sign or close the entire modal
}
}
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
Trigger transaction signing
Your application calls signTransaction() with transaction data, wallet, and optional UI options.
Sign Transaction screen appears
The customized Sign Transaction screen is displayed with transaction details.
Review transaction details
User reviews the transaction information including recipient, amount, gas fees, and any contract interactions.
Approve or reject
User clicks the confirm button to sign, or cancels (if isCancellable: true) to reject.
Transaction signed
If approved, MoonKey signs the transaction with the userβs wallet private key.
Success screen
The success screen appears with the configured successHeader and successDescription.
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:
Sign Transaction (this screen)
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:
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
Provide clear transaction context
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.'
}
}
Show wallet UI for transactions
Enable showWalletUI so users can verify they have sufficient balance: options : {
uiOptions : {
showWalletUI : true // Show balance to prevent insufficient funds errors
}
}
Use descriptive success messages
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 ({ ... });
Allow cancellation for non-critical transactions
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.
Store and verify transaction data
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:
Display full transaction details - Show recipient, amount, gas fees, and any contract interactions
Validate transaction data - Verify the transaction is well-formed before requesting signature
Check balances - Ensure users have sufficient funds before signing
Rate limit signing - Prevent abuse by limiting how many transactions can be signed
Log signed transactions - Keep records for debugging and user support
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