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.
Send a basic transaction
- viem
- web3.js
- ethers.js
- Ethereum API
- Wagmi
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
- Ethereum API
- Wagmi
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: