This guide provides a comprehensive overview of how to manage Ethereum Virtual Machine (EVM) transactions within your decentralized applications (dapps). You'll learn to send transactions, monitor their status, estimate costs, and handle potential errors effectively using popular development tools.
Core Transaction Capabilities
Modern dapp development requires robust transaction handling. The primary functionalities you can implement include sending transactions, tracking their real-time status, accurately estimating gas costs, gracefully managing errors, and handling complex transaction patterns.
Two common approaches exist for developers: using the Wagmi React Hooks library for a more integrated experience or working directly with Vanilla JavaScript for greater control. Both methods provide the necessary tools to interact with the Ethereum blockchain efficiently.
Using Wagmi for Transaction Management
Wagmi offers a collection of React Hooks that simplify interacting with Ethereum, making it easier to send transactions and track their status within your application's components.
Basic Transaction Implementation
The foundation of transaction handling involves sending a simple transfer of value. Here's how you can implement a basic ETH transfer using Wagmi's hooks:
import { parseEther } from "viem"
import { useSendTransaction, useWaitForTransactionReceipt } from "wagmi"
function SendTransaction() {
const {
data: hash,
error,
isPending,
sendTransaction
} = useSendTransaction()
const {
isLoading: isConfirming,
isSuccess: isConfirmed
} = useWaitForTransactionReceipt({
hash
})
async function handleSend() {
sendTransaction({
to: "0x...", // Recipient address
value: parseEther("0.1") // 0.1 ETH
})
}
return (
<div>
<button disabled={isPending} onClick={handleSend}>
{isPending ? "Confirming..." : "Send 0.1 ETH"}
</button>
{hash && (
<div>
<div>Transaction Hash: {hash}</div>
{isConfirming && <div>Waiting for confirmation...</div>}
{isConfirmed && <div>Transaction confirmed!</div>}
</div>
)}
{error && <div>Error: {error.message}</div>}
</div>
)
}Advanced Transaction with Gas Estimation
For more complex transactions, especially those involving smart contract interactions, proper gas estimation becomes crucial:
import { parseEther } from "viem"
import {
useSendTransaction,
useWaitForTransactionReceipt,
useEstimateGas
} from "wagmi"
function AdvancedTransaction() {
const transaction = {
to: "0x...", // Recipient address
value: parseEther("0.1"), // Amount to send
data: "0x..." // Optional contract interaction data
}
// Estimate gas requirements
const { data: gasEstimate } = useEstimateGas(transaction)
const { sendTransaction } = useSendTransaction({
...transaction,
gas: gasEstimate,
onSuccess: (hash) => {
console.log("Transaction sent:", hash)
}
})
return <button onClick={() => sendTransaction()}>Send with Gas Estimate</button>
}Vanilla JavaScript Implementation
For developers preferring to work without additional frameworks, Vanilla JavaScript provides direct access to Ethereum JSON-RPC methods through the Ethereum provider API.
Basic Transaction Flow
The fundamental transaction process uses three core RPC methods: eth_requestAccounts for account access, eth_sendTransaction for submitting transactions, and eth_getTransactionReceipt for status monitoring.
async function sendTransaction(recipientAddress, amount) {
try {
// Get current account
const accounts = await ethereum.request({
method: "eth_requestAccounts"
});
const from = accounts[0];
// Convert ETH amount to wei (hex)
const value = `0x${(amount * 1e18).toString(16)}`;
// Prepare transaction
const transaction = {
from,
to: recipientAddress,
value,
// Gas fields are optional - MetaMask will estimate
};
// Send transaction
const txHash = await ethereum.request({
method: "eth_sendTransaction",
params: [transaction],
});
return txHash;
} catch (error) {
if (error.code === 4001) {
throw new Error("Transaction rejected by user");
}
throw error;
}
}
// Track transaction status
function watchTransaction(txHash) {
return new Promise((resolve, reject) => {
const checkTransaction = async () => {
try {
const tx = await ethereum.request({
method: "eth_getTransactionReceipt",
params: [txHash],
});
if (tx) {
if (tx.status === "0x1") {
resolve(tx);
} else {
reject(new Error("Transaction failed"));
}
} else {
setTimeout(checkTransaction, 2000); // Check every 2 seconds
}
} catch (error) {
reject(error);
}
};
checkTransaction();
});
}Implementing this in a simple user interface would look like:
<div>
<input type="text" id="recipient" placeholder="Recipient Address" />
<input type="number" id="amount" placeholder="Amount (ETH)" />
<button onclick="handleSend()">Send ETH</button>
<div id="status"></div>
</div>
<script>
async function handleSend() {
const recipient = document.getElementById("recipient").value;
const amount = document.getElementById("amount").value;
const status = document.getElementById("status");
try {
status.textContent = "Sending transaction...";
const txHash = await sendTransaction(recipient, amount);
status.textContent = `Transaction sent: ${txHash}`;
// Watch for confirmation
status.textContent = "Waiting for confirmation...";
await watchTransaction(txHash);
status.textContent = "Transaction confirmed!";
} catch (error) {
status.textContent = `Error: ${error.message}`;
}
}
</script>Advanced Gas Estimation
Adding gas estimation to your Vanilla JavaScript implementation improves reliability, especially during network congestion:
async function estimateGas(transaction) {
try {
const gasEstimate = await ethereum.request({
method: "eth_estimateGas",
params: [transaction]
});
// Add 20% buffer for safety
return BigInt(gasEstimate) * 120n / 100n;
} catch (error) {
console.error("Gas estimation failed:", error);
throw error;
}
}Best Practices for Transaction Handling
Implementing transactions effectively requires attention to security, error management, and user experience considerations.
Transaction Security Measures
Always validate inputs thoroughly before submitting transactions to the network. Check wallet balances to ensure sufficient funds are available for both the transaction value and gas fees. Verify that recipient addresses are valid Ethereum addresses to prevent irreversible errors. These precautions help maintain the integrity of your application and protect users from common mistakes.
Comprehensive Error Handling
Proper error management distinguishes professional dapps from amateur implementations. Handle common errors like user rejection (when a user cancels a transaction) and insufficient funds with specific error messages and recovery options. Provide clear, actionable error messages that help users understand what went wrong and how to proceed. Implement proper error recovery flows that allow users to retry failed transactions with appropriate adjustments. Consider network congestion in your gas estimates by adding buffers during periods of high activity.
Optimizing User Experience
The transaction experience significantly impacts user satisfaction with your dapp. Display clear loading states during transaction processing to keep users informed. Show transaction progress in real time whenever possible, providing visibility into each stage from submission to confirmation. Offer detailed transaction information, including gas costs and estimated confirmation times, to set appropriate expectations.
👉 Explore more strategies for enhancing your dapp's transaction experience.
Common Transaction Errors and Solutions
| Error Code | Description | Recommended Solution |
|---|---|---|
4001 | User rejected the transaction | Show a retry option with a clear error message explaining the rejection |
-32603 | Insufficient funds for transaction | Check the user's balance before sending and provide guidance on adding funds |
-32000 | Gas limit set too low | Increase the gas limit or add a buffer to your estimation calculations |
-32002 | Request already pending | Implement checks to prevent multiple concurrent transaction requests |
Frequently Asked Questions
What is the difference between basic and advanced transactions?
Basic transactions typically involve simple value transfers without complex data parameters, while advanced transactions often include smart contract interactions, custom gas settings, and additional data fields. Advanced transactions usually require gas estimation to ensure successful execution.
How can I improve transaction success rates during network congestion?
During periods of high network activity, increase gas price recommendations and add buffers to gas limit estimations. Implementing dynamic gas estimation that responds to current network conditions significantly improves transaction success rates.
What should I do when a transaction remains pending for too long?
Monitor the transaction status and consider implementing transaction replacement strategies if supported by the network. Provide users with clear information about network status and expected confirmation times based on current conditions.
Why is input validation important for transactions?
Input validation prevents common errors such as sending to invalid addresses or attempting transactions with incorrect parameters. It protects users from irreversible mistakes and enhances the overall security of your application.
How can I provide better user feedback during transaction processing?
Implement clear status indicators at each stage of the transaction lifecycle. Use progress indicators, descriptive messages, and visual feedback to keep users informed about transaction status, estimated confirmation times, and any required actions.
What are the key security considerations for transaction handling?
Always verify recipient addresses, validate input amounts, check for sufficient balances, and implement proper error handling for rejected transactions. Consider adding confirmation screens for significant transactions to prevent user errors.