Send Transaction
Send and broadcast Solana transactions using a user’s embedded wallet. MoonKey handles transaction signing and submission to the Solana network.
Overview
Sending transactions on Solana enables:
SOL transfers : Send native SOL tokens between addresses
SPL token transfers : Transfer fungible tokens (SPL tokens)
Program interactions : Execute instructions on Solana programs
NFT operations : Mint, transfer, or burn NFTs
MoonKey uses @solana/web3.js for transaction construction. Transactions are serialized, signed, and broadcast to the network automatically.
React SDK
Basic Usage
With UI Customization
With Callbacks
To send a transaction, use the sendTransaction method from the useSendTransaction hook: import { useMoonKey } from '@moon-key/react-auth' ;
import { useSendTransaction } from '@moon-key/react-auth/solana' ;
import {
Transaction ,
SystemProgram ,
PublicKey ,
Connection ,
LAMPORTS_PER_SOL
} from '@solana/web3.js' ;
function SendSolButton () {
const { user } = useMoonKey ();
const { sendTransaction } = useSendTransaction ();
const handleSend = async () => {
if ( ! user ?. wallet ) {
alert ( 'No wallet found' );
return ;
}
try {
// Create connection
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 // 0.001 SOL
})
);
// Get recent blockhash
const { blockhash } = await connection . getLatestBlockhash ();
transaction . recentBlockhash = blockhash ;
transaction . feePayer = new PublicKey ( user . wallet . address );
// Send transaction
const signature = await sendTransaction ({
transaction ,
connection
});
console . log ( 'Transaction sent:' , signature );
alert ( `Transaction successful! Signature: ${ signature } ` );
} catch ( error ) {
console . error ( 'Failed to send transaction:' , error );
}
};
return (
< button onClick = { handleSend } >
Send 0.001 SOL
</ button >
);
}
Parameters
The Solana transaction to send. Must be a Transaction object from @solana/web3.js. Example: const transaction = new Transaction (). add (
SystemProgram . transfer ({
fromPubkey: new PublicKey ( fromAddress ),
toPubkey: new PublicKey ( toAddress ),
lamports: amount
})
);
The Solana RPC connection to use for broadcasting the transaction. Example: const connection = new Connection ( 'https://api.devnet.solana.com' );
Options Object
The second parameter is an optional configuration object:
Configuration for the transaction confirmation modal. Custom title for the confirmation modal.
Description text shown in the modal.
Text for the confirm button.
Text for the cancel button.
Additional transaction information to display. Show transactionInfo properties
Description of the action being performed.
Estimated cost of the transaction.
Whether to show the confirmation modal. Set to false to send without confirmation.
Hook Callbacks
onSuccess
(result: { signature: string }) => void
Callback executed after successful transaction. Receives the transaction signature.
Callback executed if transaction fails or user cancels.
Returns
The transaction signature (base58 encoded string).
Complete Examples
SOL Transfer
'use client' ;
import { useMoonKey } from '@moon-key/react-auth' ;
import { useSendTransaction } 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 SolTransfer () {
const { user , isAuthenticated } = useMoonKey ();
const [ recipient , setRecipient ] = useState ( '' );
const [ amount , setAmount ] = useState ( '' );
const [ isSending , setIsSending ] = useState ( false );
const { sendTransaction } = useSendTransaction ({
onSuccess : ({ signature }) => {
console . log ( 'Transfer successful:' , signature );
alert ( `Transfer successful! Signature: ${ signature } ` );
setRecipient ( '' );
setAmount ( '' );
setIsSending ( false );
},
onError : ( error ) => {
console . error ( 'Transfer failed:' , error );
alert ( 'Transfer failed. Please try again.' );
setIsSending ( false );
}
});
const handleTransfer = async () => {
if ( ! user ?. wallet ) {
alert ( 'Please connect your wallet' );
return ;
}
if ( ! recipient || ! amount ) {
alert ( 'Please enter recipient and amount' );
return ;
}
setIsSending ( true );
try {
// Validate recipient address
const recipientPubkey = new PublicKey ( recipient );
// Create connection (use devnet for testing)
const connection = new Connection ( 'https://api.devnet.solana.com' );
// Check balance
const balance = await connection . getBalance ( new PublicKey ( user . wallet . address ));
const transferAmount = parseFloat ( amount ) * LAMPORTS_PER_SOL ;
if ( balance < transferAmount ) {
alert ( 'Insufficient balance' );
setIsSending ( false );
return ;
}
// Create transaction
const transaction = new Transaction (). add (
SystemProgram . transfer ({
fromPubkey: new PublicKey ( user . wallet . address ),
toPubkey: recipientPubkey ,
lamports: transferAmount
})
);
// Get recent blockhash
const { blockhash } = await connection . getLatestBlockhash ();
transaction . recentBlockhash = blockhash ;
transaction . feePayer = new PublicKey ( user . wallet . address );
// Send transaction
await sendTransaction ({
transaction ,
connection
}, {
uiConfig: {
title: 'Confirm Transfer' ,
description: `Send ${ amount } SOL to ${ recipient . slice ( 0 , 8 ) } ... ${ recipient . slice ( - 8 ) } ` ,
confirmButtonText: 'Send SOL' ,
transactionInfo: {
action: 'SOL Transfer' ,
cost: ` ${ amount } SOL + network fees`
}
}
});
} catch ( error : any ) {
console . error ( 'Transfer error:' , error );
if ( error . message ?. includes ( 'Invalid public key' )) {
alert ( 'Invalid recipient address' );
} else {
alert ( 'Transfer failed: ' + error . message );
}
setIsSending ( false );
}
};
if ( ! isAuthenticated ) {
return < p > Please sign in to transfer SOL </ p > ;
}
return (
< div className = "sol-transfer" >
< h2 > Transfer SOL </ h2 >
< div className = "form" >
< div className = "field" >
< label > Recipient Address: </ label >
< input
type = "text"
placeholder = "Solana address"
value = { recipient }
onChange = { ( e ) => setRecipient ( e . target . value ) }
/>
</ div >
< div className = "field" >
< label > Amount (SOL): </ label >
< input
type = "number"
placeholder = "0.001"
step = "0.001"
value = { amount }
onChange = { ( e ) => setAmount ( e . target . value ) }
/>
</ div >
< button
onClick = { handleTransfer }
disabled = { isSending || ! recipient || ! amount }
>
{ isSending ? 'Sending...' : 'Send SOL' }
</ button >
</ div >
</ div >
);
}
SPL Token Transfer
'use client' ;
import { useMoonKey } from '@moon-key/react-auth' ;
import { useSendTransaction } from '@moon-key/react-auth/solana' ;
import {
Transaction ,
PublicKey ,
Connection
} from '@solana/web3.js' ;
import {
getAssociatedTokenAddress ,
createTransferInstruction ,
TOKEN_PROGRAM_ID
} from '@solana/spl-token' ;
import { useState } from 'react' ;
export default function SPLTokenTransfer () {
const { user } = useMoonKey ();
const { sendTransaction } = useSendTransaction ();
const [ recipient , setRecipient ] = useState ( '' );
const [ amount , setAmount ] = useState ( '' );
const handleTokenTransfer = async () => {
if ( ! user ?. wallet ) return ;
try {
const connection = new Connection ( 'https://api.devnet.solana.com' );
// Token mint address (example: USDC on devnet)
const tokenMintAddress = new PublicKey ( 'TokenMintAddressHere...' );
// Get source and destination token accounts
const sourceAccount = await getAssociatedTokenAddress (
tokenMintAddress ,
new PublicKey ( user . wallet . address )
);
const destinationAccount = await getAssociatedTokenAddress (
tokenMintAddress ,
new PublicKey ( recipient )
);
// Create transfer instruction
const transferInstruction = createTransferInstruction (
sourceAccount ,
destinationAccount ,
new PublicKey ( user . wallet . address ),
parseFloat ( amount ) * Math . pow ( 10 , 6 ), // Assuming 6 decimals
[],
TOKEN_PROGRAM_ID
);
// Create transaction
const transaction = new Transaction (). add ( transferInstruction );
const { blockhash } = await connection . getLatestBlockhash ();
transaction . recentBlockhash = blockhash ;
transaction . feePayer = new PublicKey ( user . wallet . address );
// Send transaction
const signature = await sendTransaction ({
transaction ,
connection
}, {
uiConfig: {
title: 'Transfer Tokens' ,
description: `Send ${ amount } tokens` ,
confirmButtonText: 'Confirm Transfer'
}
});
console . log ( 'Token transfer successful:' , signature );
alert ( 'Tokens sent successfully!' );
} catch ( error ) {
console . error ( 'Token transfer failed:' , error );
alert ( 'Failed to transfer tokens' );
}
};
return (
< div className = "token-transfer" >
< h2 > Transfer SPL Tokens </ h2 >
< input
type = "text"
placeholder = "Recipient address"
value = { recipient }
onChange = { ( e ) => setRecipient ( e . target . value ) }
/>
< input
type = "number"
placeholder = "Amount"
value = { amount }
onChange = { ( e ) => setAmount ( e . target . value ) }
/>
< button onClick = { handleTokenTransfer } >
Transfer Tokens
</ button >
</ div >
);
}
Program Interaction
'use client' ;
import { useMoonKey } from '@moon-key/react-auth' ;
import { useSendTransaction } from '@moon-key/react-auth/solana' ;
import {
Transaction ,
TransactionInstruction ,
PublicKey ,
Connection
} from '@solana/web3.js' ;
export default function ProgramInteraction () {
const { user } = useMoonKey ();
const { sendTransaction } = useSendTransaction ();
const handleProgramCall = async () => {
if ( ! user ?. wallet ) return ;
try {
const connection = new Connection ( 'https://api.devnet.solana.com' );
// Your program ID
const programId = new PublicKey ( 'YourProgramIdHere...' );
// Create instruction data (depends on your program)
const instructionData = Buffer . from ([
/* your instruction data */
]);
// Create instruction
const instruction = new TransactionInstruction ({
keys: [
{
pubkey: new PublicKey ( user . wallet . address ),
isSigner: true ,
isWritable: true
}
// Add more account keys as needed
],
programId ,
data: instructionData
});
// Create transaction
const transaction = new Transaction (). add ( instruction );
const { blockhash } = await connection . getLatestBlockhash ();
transaction . recentBlockhash = blockhash ;
transaction . feePayer = new PublicKey ( user . wallet . address );
// Send transaction
const signature = await sendTransaction ({
transaction ,
connection
}, {
uiConfig: {
title: 'Execute Program' ,
description: 'Interact with Solana program' ,
confirmButtonText: 'Execute'
}
});
console . log ( 'Program execution successful:' , signature );
} catch ( error ) {
console . error ( 'Program execution failed:' , error );
}
};
return (
< button onClick = { handleProgramCall } >
Execute Program
</ button >
);
}
Transaction Construction
Basic Transaction Setup
import {
Transaction ,
SystemProgram ,
PublicKey ,
Connection ,
LAMPORTS_PER_SOL
} from '@solana/web3.js' ;
// Create connection
const connection = new Connection ( 'https://api.devnet.solana.com' );
// Create transaction
const transaction = new Transaction ();
// Add instruction
transaction . add (
SystemProgram . transfer ({
fromPubkey: new PublicKey ( fromAddress ),
toPubkey: new PublicKey ( toAddress ),
lamports: 0.001 * LAMPORTS_PER_SOL
})
);
// Set recent blockhash and fee payer
const { blockhash } = await connection . getLatestBlockhash ();
transaction . recentBlockhash = blockhash ;
transaction . feePayer = new PublicKey ( fromAddress );
Multiple Instructions
const transaction = new Transaction ();
// Add multiple instructions
transaction . add (
SystemProgram . transfer ({
fromPubkey: senderPubkey ,
toPubkey: recipient1Pubkey ,
lamports: 0.001 * LAMPORTS_PER_SOL
}),
SystemProgram . transfer ({
fromPubkey: senderPubkey ,
toPubkey: recipient2Pubkey ,
lamports: 0.002 * LAMPORTS_PER_SOL
})
);
const { blockhash } = await connection . getLatestBlockhash ();
transaction . recentBlockhash = blockhash ;
transaction . feePayer = senderPubkey ;
RPC Endpoints
Use appropriate RPC endpoints for different networks:
// Devnet (for testing)
const devnetConnection = new Connection ( 'https://api.devnet.solana.com' );
// Testnet
const testnetConnection = new Connection ( 'https://api.testnet.solana.com' );
// Mainnet Beta
const mainnetConnection = new Connection ( 'https://api.mainnet-beta.solana.com' );
// Custom RPC (recommended for production)
const customConnection = new Connection ( 'https://your-rpc-provider.com' );
For production applications, use a dedicated RPC provider like Helius, QuickNode, or Alchemy for better reliability and rate limits.
Install Dependencies
npm install @solana/web3.js @solana/spl-token
UI Customization
Customize the transaction confirmation modal:
await sendTransaction ({
transaction ,
connection
}, {
uiConfig: {
title: 'Confirm Transaction' ,
description: 'Review and confirm your transaction' ,
confirmButtonText: 'Confirm' ,
cancelButtonText: 'Cancel' ,
transactionInfo: {
action: 'Transfer SOL' ,
cost: '0.001 SOL + fees'
}
}
});
Hide Modal
await sendTransaction ({
transaction ,
connection
}, {
uiConfig: {
showWalletUI: false
}
});
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.
Error Handling
Handle common transaction errors:
const { sendTransaction } = useSendTransaction ({
onError : ( error : any ) => {
if ( error . message ?. includes ( 'insufficient funds' )) {
alert ( 'Insufficient balance to complete transaction' );
} else if ( error . message ?. includes ( 'blockhash' )) {
alert ( 'Transaction expired. Please try again.' );
} else if ( error . message ?. includes ( 'simulation failed' )) {
alert ( 'Transaction simulation failed. Check transaction parameters.' );
} else if ( error . code === 'USER_REJECTED' ) {
console . log ( 'User cancelled transaction' );
} else {
console . error ( 'Transaction error:' , error );
alert ( 'Transaction failed: ' + error . message );
}
}
});
Best Practices
Check balance before sending
Always verify sufficient balance: const connection = new Connection ( 'https://api.devnet.solana.com' );
const balance = await connection . getBalance ( new PublicKey ( user . wallet . address ));
const transferAmount = 0.001 * LAMPORTS_PER_SOL ;
if ( balance < transferAmount ) {
alert ( 'Insufficient balance' );
return ;
}
// Proceed with transaction
Always fetch a recent blockhash before sending: // ✅ Good - Fresh blockhash
const { blockhash } = await connection . getLatestBlockhash ();
transaction . recentBlockhash = blockhash ;
// ❌ Bad - Reusing old blockhash
// Blockhashes expire after ~60 seconds
Validate recipient addresses before sending: import { PublicKey } from '@solana/web3.js' ;
function isValidSolanaAddress ( address : string ) : boolean {
try {
new PublicKey ( address );
return true ;
} catch {
return false ;
}
}
if ( ! isValidSolanaAddress ( recipient )) {
alert ( 'Invalid recipient address' );
return ;
}
Wait for transaction confirmation: const signature = await sendTransaction ({
transaction ,
connection
});
// Wait for confirmation
const confirmation = await connection . confirmTransaction ( signature , 'confirmed' );
if ( confirmation . value . err ) {
console . error ( 'Transaction failed:' , confirmation . value . err );
} else {
console . log ( 'Transaction confirmed!' );
}
Handle network errors gracefully
Implement retry logic for network issues: async function sendWithRetry (
transaction : Transaction ,
connection : Connection ,
maxRetries = 3
) {
let attempts = 0 ;
while ( attempts < maxRetries ) {
try {
const signature = await sendTransaction ({
transaction ,
connection
});
return signature ;
} catch ( error : any ) {
attempts ++ ;
if ( attempts >= maxRetries ) throw error ;
console . log ( `Retry ${ attempts } / ${ maxRetries } ` );
await new Promise ( resolve => setTimeout ( resolve , 1000 * attempts ));
}
}
}
Use appropriate RPC endpoints
Choose the right network for your use case: // Development and testing
const connection = new Connection ( 'https://api.devnet.solana.com' );
// Production
const connection = new Connection (
process . env . NEXT_PUBLIC_SOLANA_RPC_URL || 'https://api.mainnet-beta.solana.com'
);
Troubleshooting
Transaction fails with blockhash error
Blockhashes expire quickly on Solana (~60 seconds): // Always get fresh blockhash right before sending
const { blockhash } = await connection . getLatestBlockhash ();
transaction . recentBlockhash = blockhash ;
transaction . feePayer = new PublicKey ( user . wallet . address );
// Send immediately
await sendTransaction ({ transaction , connection });
Check both SOL balance and required amount: const balance = await connection . getBalance (
new PublicKey ( user . wallet . address )
);
// Account for transfer amount + transaction fees (~0.000005 SOL)
const requiredAmount = transferAmount + ( 5000 ); // 5000 lamports for fees
if ( balance < requiredAmount ) {
alert ( `Insufficient balance. You need ${ requiredAmount / LAMPORTS_PER_SOL } SOL` );
return ;
}
Transaction simulation can fail for various reasons: // Check if accounts exist
const accountInfo = await connection . getAccountInfo (
new PublicKey ( recipientAddress )
);
// For SPL tokens, check if token account exists
const tokenAccount = await getAssociatedTokenAddress (
tokenMintAddress ,
new PublicKey ( recipientAddress )
);
const tokenAccountInfo = await connection . getAccountInfo ( tokenAccount );
if ( ! tokenAccountInfo ) {
// May need to create associated token account first
console . log ( 'Token account does not exist' );
}
Use appropriate timeouts and retry logic: const connection = new Connection (
'https://api.devnet.solana.com' ,
{
commitment: 'confirmed' ,
confirmTransactionInitialTimeout: 60000 // 60 seconds
}
);
Next Steps