Transactions
Simple guide to handling Solana transactions with LazorKit.
How It Works
Smart Wallet Flow:
- User action → Create instruction → Sign with passkey → Execute transaction
Two patterns:
- Sign & Send: Complete handling (recommended)
- Sign Only: Manual control
Basic Patterns
Sign & Send (Recommended)
import { useWallet } from '@lazorkit/wallet';
import { SystemProgram, LAMPORTS_PER_SOL, PublicKey } from '@solana/web3.js';
function TransferComponent() {
const { signAndSendTransaction, smartWalletPubkey, isSigning } = useWallet();
const sendSOL = async () => {
const instruction = SystemProgram.transfer({
fromPubkey: smartWalletPubkey,
toPubkey: new PublicKey(process.env.REACT_APP_RECIPIENT_ADDRESS!),
lamports: LAMPORTS_PER_SOL * 0.1,
});
try {
const signature = await signAndSendTransaction(instruction);
console.log('Sent:', signature);
} catch (error) {
console.error('Failed:', error.code); // 'USER_CANCELLED' | 'INSUFFICIENT_FUNDS'
}
};
return (
<button onClick={sendSOL} disabled={isSigning}>
{isSigning ? 'Sending...' : 'Send 0.1 SOL'}
</button>
);
}
Sign Only Pattern
function SignOnlyExample() {
const { signTransaction, smartWalletPubkey } = useWallet();
const signOnly = async () => {
const instruction = SystemProgram.transfer({
fromPubkey: smartWalletPubkey,
toPubkey: new PublicKey('...'),
lamports: LAMPORTS_PER_SOL * 0.1,
});
const signedTx = await signTransaction(instruction);
// Manual sending with Connection if needed
// const connection = new Connection('...');
// await connection.sendTransaction(signedTx);
};
return <button onClick={signOnly}>Sign Only</button>;
}
Custom Transaction Hook
// hooks/useTransactionSender.ts
import { useState } from 'react';
import { useWallet } from '@lazorkit/wallet';
export function useTransactionSender() {
const { signAndSendTransaction } = useWallet();
const [state, setState] = useState({
isLoading: false,
signature: null,
error: null,
});
const send = async (instruction) => {
setState({ isLoading: true, signature: null, error: null });
try {
const signature = await signAndSendTransaction(instruction);
setState({ isLoading: false, signature, error: null });
return signature;
} catch (error) {
const message = error.code === 'USER_CANCELLED'
? 'Transaction cancelled'
: error.message || 'Transaction failed';
setState({ isLoading: false, signature: null, error: message });
throw error;
}
};
return { send, ...state, reset: () => setState({ isLoading: false, signature: null, error: null }) };
}
// Usage
function TransactionExample() {
const { send, isLoading, signature, error } = useTransactionSender();
const { smartWalletPubkey } = useWallet();
const handleSend = async () => {
const instruction = SystemProgram.transfer({
fromPubkey: smartWalletPubkey,
toPubkey: new PublicKey('...'),
lamports: LAMPORTS_PER_SOL * 0.1,
});
await send(instruction);
};
return (
<div>
<button onClick={handleSend} disabled={isLoading}>
{isLoading ? 'Sending...' : 'Send Transaction'}
</button>
{signature && <p>✅ Sent: {signature.slice(0, 8)}...</p>}
{error && <p className="text-red-500">❌ {error}</p>}
</div>
);
}
Common Use Cases
SPL Token Transfer
import { createTransferInstruction, getAssociatedTokenAddress } from '@solana/spl-token';
function TokenTransfer() {
const { signAndSendTransaction, smartWalletPubkey } = useWallet();
const transferTokens = async () => {
const mintAddress = new PublicKey('So11111111111111111111111111111111111111112'); // WSOL
const recipientAddress = new PublicKey('...');
const senderTokenAccount = await getAssociatedTokenAddress(mintAddress, smartWalletPubkey);
const recipientTokenAccount = await getAssociatedTokenAddress(mintAddress, recipientAddress);
const instruction = createTransferInstruction(
senderTokenAccount,
recipientTokenAccount,
smartWalletPubkey,
1000000, // Amount in token decimals
);
const signature = await signAndSendTransaction(instruction);
console.log('Token sent:', signature);
};
return <button onClick={transferTokens}>Transfer Tokens</button>;
}
Custom Program Call
function CustomProgramExample() {
const { signAndSendTransaction, smartWalletPubkey } = useWallet();
const callProgram = async () => {
const instruction = new TransactionInstruction({
keys: [
{ pubkey: smartWalletPubkey, isSigner: true, isWritable: true },
{ pubkey: new PublicKey('...'), isSigner: false, isWritable: false },
],
programId: new PublicKey('YourProgramId...'),
data: Buffer.from([1, 2, 3]), // Instruction data
});
const signature = await signAndSendTransaction(instruction);
console.log('Program called:', signature);
};
return <button onClick={callProgram}>Call Program</button>;
}
Transaction Monitoring
Simple Transaction History
function TransactionHistory() {
const [transactions, setTransactions] = useState([]);
const { signAndSendTransaction } = useWallet();
const sendAndTrack = async (instruction) => {
try {
const signature = await signAndSendTransaction(instruction);
// Add to history
setTransactions(prev => [...prev, {
signature,
status: 'confirmed', // LazorKit handles confirmation
timestamp: Date.now(),
}]);
} catch (error) {
console.error('Transaction failed:', error);
}
};
return (
<div>
<h3>Recent Transactions</h3>
{transactions.map(tx => (
<div key={tx.signature} className="p-2 border rounded mb-2">
<p className="font-mono text-sm">{tx.signature.slice(0, 8)}...</p>
<p className="text-green-600 text-sm">✅ {tx.status}</p>
</div>
))}
</div>
);
}
Error Handling
Transaction Error Codes
const handleError = (error) => {
switch (error.code) {
case 'USER_CANCELLED':
return 'User cancelled the transaction';
case 'INSUFFICIENT_FUNDS':
return 'Not enough balance for this transaction';
case 'TRANSACTION_FAILED':
return 'Transaction failed to execute';
case 'NOT_CONNECTED':
return 'Please connect your wallet first';
default:
return error.message || 'Transaction error occurred';
}
};
// Usage
try {
await signAndSendTransaction(instruction);
} catch (error) {
const userMessage = handleError(error);
setErrorMessage(userMessage);
}
Retry Pattern
function useRetryTransaction() {
const { signAndSendTransaction } = useWallet();
const sendWithRetry = async (instruction, maxAttempts = 2) => {
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
try {
return await signAndSendTransaction(instruction);
} catch (error) {
if (attempt === maxAttempts || error.code === 'USER_CANCELLED') {
throw error;
}
// Wait 1 second before retry
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
};
return { sendWithRetry };
}
Best Practices
1. User Feedback
Always show transaction status:
function TransactionButton() {
const { isSigning } = useWallet();
const [status, setStatus] = useState('idle');
const statusMessages = {
idle: 'Ready to send',
signing: 'Confirm with passkey...',
success: 'Transaction sent!',
error: 'Transaction failed'
};
return (
<div>
<button disabled={isSigning}>
{isSigning ? 'Signing...' : 'Send Transaction'}
</button>
<p>{statusMessages[status]}</p>
</div>
);
}
2. Error Recovery
Handle common errors gracefully:
const recoverableErrors = ['TRANSACTION_FAILED', 'BLOCKHASH_NOT_FOUND'];
if (recoverableErrors.includes(error.code)) {
// Retry automatically
await sendWithRetry(instruction);
} else if (error.code === 'USER_CANCELLED') {
// Don't retry, just inform user
setMessage('Transaction cancelled');
}
3. Validation
Basic validation before sending:
const validateTransaction = (instruction, wallet) => {
const isSigner = instruction.keys.some(
key => key.pubkey.equals(wallet) && key.isSigner
);
if (!isSigner) {
throw new Error('Wallet must sign this transaction');
}
return true;
};
Gasless Transactions
When paymaster is configured, transactions are automatically gasless:
// No SOL needed for fees - paymaster handles it
function GaslessTransfer() {
const { signAndSendTransaction, smartWalletPubkey } = useWallet();
const sendGasless = async () => {
const instruction = SystemProgram.transfer({
fromPubkey: smartWalletPubkey,
toPubkey: new PublicKey('...'),
lamports: LAMPORTS_PER_SOL * 0.1,
});
// Transaction fees paid by paymaster
const signature = await signAndSendTransaction(instruction);
console.log('Gasless transaction:', signature);
};
return <button onClick={sendGasless}>Send (No Fees)</button>;
}