Skip to content

Latest commit

 

History

History
408 lines (331 loc) · 10.7 KB

File metadata and controls

408 lines (331 loc) · 10.7 KB
title Send EVM Transactions - MetaMask Connect Guide
sidebar_label Send transactions
description Send ETH and token transactions, estimate gas, track receipts, and handle errors using MetaMask Connect EVM with viem, ethers.js, or web3.js.
keywords
SDK
JavaScript
wagmi
send
transaction
transactions
status
estimate
gas
dapp
eth_sendTransaction
gas estimation
transaction receipt
viem
ethers.js
web3.js
send ETH
toc_max_heading_level 2

import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem';

Send transactions

Use MetaMask Connect EVM to send Ethereum transactions from your JavaScript dapp. MetaMask Connect EVM supports eth_sendTransaction through viem, ethers.js, web3.js, the raw Ethereum API, and Wagmi, with built-in gas estimation, transaction receipt tracking, and error handling for common failure cases like user rejection and insufficient funds.

With MetaMask Connect EVM:

  • Send transactions.
  • Track transaction status in real time.
  • Estimate gas costs accurately.
  • Handle transaction errors gracefully.
  • Manage complex transaction patterns.

The following examples demonstrate how to use MetaMask Connect EVM with viem, web3.js, ethers.js, Ethereum APIs, or Wagmi to send a basic transaction and an advanced transaction with gas estimation.

Prerequisites

Follow the JavaScript quickstart or Wagmi quickstart to install, initialize, and connect the EVM client.

Send a basic transaction

import { createEVMClient } from '@metamask/connect-evm'
import { createPublicClient, createWalletClient, custom } from 'viem'
import { mainnet } from 'viem/chains'

const evmClient = await createEVMClient({
  dapp: {
    name: 'MetaMask Connect EVM Example',
    url: window.location.href,
    iconUrl: 'https://mydapp.com/icon.png', // Optional
  },
  api: {
    supportedNetworks: {
      '0x1': 'https://mainnet.infura.io/v3/YOUR_INFURA_API_KEY',
      '0xaa36a7': 'https://sepolia.infura.io/v3/YOUR_INFURA_API_KEY',
    },
  },
})
const provider = evmClient.getProvider()

const publicClient = createPublicClient({ chain: mainnet, transport: custom(provider) })
const walletClient = createWalletClient({ chain: mainnet, transport: custom(provider) })

// data for the transaction
const destination = '0xRECIPIENT_ADDRESS'
const amount = parseEther('0.0001')
const address = await walletClient.getAddresses()

// Submit transaction to the blockchain
const hash = await walletClient.sendTransaction({
  account: address[0],
  to: destination,
  value: amount,
})

const receipt = await publicClient.waitForTransactionReceipt({ hash })
import { createEVMClient } from '@metamask/connect-evm'
import { Web3 } from 'web3'

const evmClient = await createEVMClient({
  dapp: {
    name: 'MetaMask Connect EVM Example',
    url: window.location.href,
    iconUrl: 'https://mydapp.com/icon.png', // Optional
  },
  api: {
    supportedNetworks: {
      '0x1': 'https://mainnet.infura.io/v3/YOUR_INFURA_API_KEY',
      '0xaa36a7': 'https://sepolia.infura.io/v3/YOUR_INFURA_API_KEY',
    },
  },
})
const provider = evmClient.getProvider()

const web3 = new Web3(provider)

// Get user's Ethereum public address
const fromAddress = (await web3.eth.getAccounts())[0]

const destination = '0xRECIPIENT_ADDRESS'
const amount = web3.utils.toWei('0.0001') // Convert 0.0001 ether to wei

// Submit transaction to the blockchain and wait for it to be mined
const receipt = await web3.eth.sendTransaction({
  from: fromAddress,
  to: destination,
  value: amount,
})
import { createEVMClient } from '@metamask/connect-evm'
import { ethers } from 'ethers'
import { BrowserProvider, parseUnits } from 'ethers'

const evmClient = await createEVMClient({
  dapp: {
    name: 'MetaMask Connect EVM Example',
    url: window.location.href,
    iconUrl: 'https://mydapp.com/icon.png', // Optional
  },
  api: {
    supportedNetworks: {
      '0x1': 'https://mainnet.infura.io/v3/YOUR_INFURA_API_KEY',
      '0xaa36a7': 'https://sepolia.infura.io/v3/YOUR_INFURA_API_KEY',
    },
  },
})
const provider = evmClient.getProvider()

const ethersProvider = new ethers.BrowserProvider(provider)
const signer = await ethersProvider.getSigner()

const destination = '0xRECIPIENT_ADDRESS'
const amount = parseUnits('0.0001', 'ether')

// Submit transaction to the blockchain
const tx = await signer.sendTransaction({
  to: destination,
  value: amount,
})

// Wait for the transaction to be mined
const receipt = await tx.wait()
import { createEVMClient } from '@metamask/connect-evm'

const evmClient = await createEVMClient({
  dapp: {
    name: 'MetaMask Connect EVM Example',
    url: window.location.href,
    iconUrl: 'https://mydapp.com/icon.png', // Optional
  },
  api: {
    supportedNetworks: {
      '0x1': 'https://mainnet.infura.io/v3/YOUR_INFURA_API_KEY',
      '0xaa36a7': 'https://sepolia.infura.io/v3/YOUR_INFURA_API_KEY',
    },
  },
})
const provider = evmClient.getProvider()

async function sendTransaction(recipientAddress, amount) {
  try {
    // Get current account
    const accounts = await provider.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 provider.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 provider.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()
  })
}
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'), // 0.1 ETH
    })
  }

  return (
    <div>
      <button onClick={handleSend} disabled={isPending}>
        {isPending ? 'Confirming...' : 'Send 0.1 ETH'}
      </button>

      {hash && (
        <div>
          Transaction Hash: {hash}
          {isConfirming && <div>Waiting for confirmation...</div>}
          {isConfirmed && <div>Transaction confirmed!</div>}
        </div>
      )}

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

Send an advanced transaction with gas estimation

import { createEVMClient } from '@metamask/connect-evm'

const evmClient = await createEVMClient({
  dapp: {
    name: 'MetaMask Connect EVM Example',
    url: window.location.href,
    iconUrl: 'https://mydapp.com/icon.png', // Optional
  },
  api: {
    supportedNetworks: {
      '0x1': 'https://mainnet.infura.io/v3/YOUR_INFURA_API_KEY',
      '0xaa36a7': 'https://sepolia.infura.io/v3/YOUR_INFURA_API_KEY',
    },
  },
})
const provider = evmClient.getProvider()

async function estimateGas(transaction) {
  try {
    const gasEstimate = await provider.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
  }
}
import { parseEther } from 'viem'
import { useSendTransaction, useWaitForTransactionReceipt, useEstimateGas } from 'wagmi'

function AdvancedTransaction() {
  const transaction = {
    to: '0x...',
    value: parseEther('0.1'),
    data: '0x...', // Optional contract interaction data
  }

  // Estimate gas
  const { data: gasEstimate } = useEstimateGas(transaction)

  const { sendTransaction } = useSendTransaction()

  function handleSend() {
    sendTransaction({
      ...transaction,
      gas: gasEstimate,
    })
  }

  return <button onClick={handleSend}>Send with Gas Estimate</button>
}

Best practices

Follow these best practices when handling transactions.

Transaction security

  • Always validate inputs before sending transactions.
  • Check wallet balances to ensure sufficient funds.
  • Verify addresses are valid.

Error handling

  • Handle common errors like user rejection and insufficient funds.
  • Provide clear error messages to users.
  • Implement proper error recovery flows.
  • Consider network congestion in gas estimates.

User experience

  • Display clear loading states during transactions.
  • Show transaction progress in real time.
  • Provide detailed transaction information.

Common errors

Error code Description Solution
4001 User rejected transaction Show a retry option and a clear error message.
-32603 Insufficient funds Check the balance before sending a transaction.
-32000 Gas too low Increase the gas limit or add a buffer to the estimation.
-32002 Request already pending Prevent multiple concurrent transactions.

Next steps

See the following guides to add more functionality to your dapp: