Handle EVM Transactions in Your Dapp: A Comprehensive Guide

ยท

Introduction to EVM Transaction Handling

Building decentralized applications (dApps) requires robust transaction handling capabilities. Whether you're using Wagmi or Vanilla JavaScript, this guide will walk you through all aspects of EVM transaction management including:

๐Ÿ‘‰ Best practices for secure transaction handling

Using Wagmi for Transaction Management

Wagmi provides excellent React hooks for handling Ethereum transactions. Let's explore two common use cases.

Basic ETH Transfer

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...",
      value: parseEther("0.1")
    })
  }

  return (
    <div>
      <button onClick={handleSend} disabled={isPending}>
        {isPending ? "Confirming..." : "Send 0.1 ETH"}
      </button>
      
      {hash && (
        <div>
          Transaction Hash: {hash}
          {isConfirming && <p>Waiting for confirmation...</p>}
          {isConfirmed && <p>Transaction confirmed!</p>}
        </div>
      )}

      {error && <p>Error: {error.message}</p>}
    </div>
  )
}

Advanced Transaction With Gas Estimation

import { parseEther } from "viem"
import {
  useSendTransaction,
  useWaitForTransactionReceipt,
  useEstimateGas
} from "wagmi"

function AdvancedTransaction() {
  const transaction = {
    to: "0x...",
    value: parseEther("0.1"),
    data: "0x..."
  }

  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 those not using React frameworks, here's how to implement transaction handling directly.

Basic ETH Transfer

async function sendTransaction(recipientAddress, amount) {
  try {
    const accounts = await ethereum.request({
      method: "eth_requestAccounts"
    });
    const from = accounts[0];

    const value = `0x${(amount * 1e18).toString(16)}`;

    const transaction = {
      from,
      to: recipientAddress,
      value
    };

    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;
  }
}

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);
        }
      } catch (error) {
        reject(error);
      }
    };

    checkTransaction();
  });
}

UI Implementation Example

<div>
  <h2>Send ETH</h2>
  
  <form id="sendForm">
    <input type="text" id="recipient" placeholder="Recipient address">
    <input type="number" id="amount" placeholder="ETH amount">
    <button type="button" onclick="handleSend()">Send</button>
  </form>
  
  <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}`;

    status.textContent = "Waiting for confirmation...";
    await watchTransaction(txHash);
    status.textContent = "Transaction confirmed!";
  } catch (error) {
    status.textContent = `Error: ${error.message}`;
  }
}
</script>

Advanced Transaction With Gas Estimation

async function estimateGas(transaction) {
  try {
    const gasEstimate = await ethereum.request({
      method: "eth_estimateGas",
      params: [transaction]
    });

    return BigInt(gasEstimate) * 120n / 100n;
  } catch (error) {
    console.error("Gas estimation failed:", error);
    throw error;
  }
}

Best Practices for Transaction Handling

Transaction Security

๐Ÿ‘‰ Essential security measures for dApp developers

Error Handling Strategies

Error CodeDescriptionRecommended Solution
4001User rejected transactionShow friendly message with retry option
-32603Insufficient fundsCheck balance before transaction
-32000Gas too lowAdd 20% buffer to estimate
-32002Concurrent requestPrevent duplicate transactions

User Experience Considerations

FAQ Section

How do I estimate gas costs accurately?

Use the eth_estimateGas RPC method and add a 20% buffer to account for network fluctuations.

What should I do if a user rejects a transaction?

Provide a clear message explaining what the transaction was for and offer an easy way to retry.

How can I track transaction status?

Use eth_getTransactionReceipt to poll for status updates or WebSocket subscriptions for real-time updates.

What's the best way to handle network congestion?

Implement dynamic gas pricing that adjusts based on current network conditions.

How do I validate recipient addresses?

Use checksum validation and consider implementing address confirmation screens.

Expanding Dapp Functionality

To enhance your dapp's capabilities, consider adding: