Developer Guide: Detecting & Integrating MetaMask in dApps
Quick overview
This guide shows practical, code-first ways to detect MetaMask in browser-based dApps, react to account events, read accounts via Web3.js, and deploy contracts from the browser. It focuses on measurable behaviors (which RPC call returns what, which events fire, and how to estimate gas) rather than vague recommendations.
I use these patterns when building test deployments and front-end integrations. When I first set this up I accidentally pushed a deploy with too-low gas; that taught me to always estimateGas and test on a testnet first. But once you get the flow right, integration becomes routine.
If you need setup help, check install-metamask-chrome-extension or metamask-mobile-ios-android.
How to detect if MetaMask is connected in React
Goal: detect presence of an injected provider and whether the user has exposed accounts to your site. The common pattern checks for window.ethereum, then calls eth_accounts (no modal) or eth_requestAccounts (prompts).
React functional example with Web3.js:
import React, { useEffect, useState } from 'react';
import Web3 from 'web3';
export default function WalletStatus() {
const [account, setAccount] = useState(null);
const [connected, setConnected] = useState(false);
useEffect(() => {
if (!window.ethereum) return;
const web3 = new Web3(window.ethereum);
// Non-invasive check: won't popup UI
window.ethereum.request({ method: 'eth_accounts' })
.then(accounts => {
setConnected(accounts && accounts.length > 0);
setAccount(accounts[0] || null);
})
.catch(console.error);
}, []);
return (<div>Connected: {String(connected)} — Account: {account || 'none'}</div>);
}
Tip: Call eth_requestAccounts only in response to a user action (button click). Prompting automatically harms conversion.
Listen for account and chain changes (detect MetaMask account change)
You must assume the user can switch accounts or networks at any time. Which listeners matter? accountsChanged and chainChanged (EIP-1193).
Example listener setup (clean up listeners on unmount):
if (window.ethereum && window.ethereum.on) {
const onAccountsChanged = (accounts) => { /* update UI/state */ };
const onChainChanged = (chainId) => { /* update provider, refresh state */ };
window.ethereum.on('accountsChanged', onAccountsChanged);
window.ethereum.on('chainChanged', onChainChanged);
// remove listeners when done
window.ethereum.removeListener('accountsChanged', onAccountsChanged);
window.ethereum.removeListener('chainChanged', onChainChanged);
}
How fast do events fire? In practice account changes are immediate (sub-200ms) inside the tab. But if the user switches networks in the wallet UI, your app should re-validate chainId before signing transactions.
Get MetaMask account from web3
If you asked search engines "get metamask account from web3" the canonical answer is web3.eth.getAccounts() or window.ethereum.request({ method: 'eth_accounts' }). Example:
const web3 = new Web3(window.ethereum);
const accounts = await web3.eth.getAccounts();
const primary = accounts[0];
Remember: an empty array means either locked wallet or no permission granted. Use eth_requestAccounts to prompt.
Deploy contracts with MetaMask (deploy contract with MetaMask)
Deploying from the browser is a standard flow: create a deploy transaction whose data = contract bytecode (+ ctor args). Below are tested patterns.
Web3.js (deploy smart contract MetaMask and bytecode):
const web3 = new Web3(window.ethereum);
const accs = await web3.eth.getAccounts();
const deploy = new web3.eth.Contract(abi).deploy({ data: bytecode, arguments: [/* args */] });
const gas = await deploy.estimateGas({ from: accs[0] });
const instance = await deploy.send({ from: accs[0], gas });
console.log('address', instance.options.address);
Ethers.js (ContractFactory + signer):
const provider = new ethers.providers.Web3Provider(window.ethereum);
await provider.send('eth_requestAccounts', []);
const signer = provider.getSigner();
const factory = new ethers.ContractFactory(abi, bytecode, signer);
const contract = await factory.deploy(...args);
await contract.deployed();
console.log(contract.address);
If you prefer manual low-level deployment, send a transaction with the bytecode in the data field. And when you want to test quick function calls without a UI, Etherscan's write UI will connect to your wallet (etherscan write contract metamask flows connect via "Connect to Web3"). See using-etherscan-with-metamask.
Expose accounts, WebSockets, and WalletConnect
Can you expose your device accounts through websocket MetaMask? Not directly—MetaMask injects an in-page provider, it does not expose a remote WebSocket endpoint for your app to connect to over the network. If you need remote connectivity, common options:
| Method |
WebSocket support |
UX cost |
Notes |
| Injected provider (window.ethereum) |
No |
Low |
Best for same-tab dApps; fast event hooks |
| WalletConnect (bridge) |
Yes (bridge; v2 supports WebSocket) |
Medium |
Good for mobile-to-desktop flows — see connect-to-dapps-walletconnect |
| Custom relay / proxy |
Yes |
High |
Requires local agent or server—higher security burden |
So if you searched to "expose your device accounts through websocket MetaMask" the practical answer is: use WalletConnect or implement a secure relay; do not attempt to expose private keys.
Debugging tips and common pitfalls
- eth_accounts === [] usually means locked wallet. Prompt only on user action.
- Chain mismatches are the top cause of failed TXs; verify chainId before sending.
- Gas estimates can vary; always call estimateGas and add a safe margin.
- Nonce issues appear when parallel signs are sent. Queue high-impact flows.
For detailed transaction troubleshooting see transaction-errors-and-fixes.
Security and UX best practices for dApps
- Request only the permissions you need and only when the user initiates the flow.
- Display clear transaction details (destination, amount, calldata) before asking for signature.
- Offer a revoke-approval path (see token-allowances-and-revoke).
But always avoid asking for seed phrases. Ever.
Who this is for — and who should look elsewhere
This integration guide is aimed at front-end engineers building EVM-compatible dApps that will interact with browser or in-app wallets, perform swaps, staking calls, or deploy contracts from the UI.
Look elsewhere if you require server-side signing, fully headless wallet management, or a strictly hardware-wallet-only UX (see ledger-with-metamask-guide).
FAQ
Q: Is it safe to keep crypto in a hot wallet?
A: Hot wallets trade security for convenience. For frequent DeFi use they’re practical for small balances; for long-term storage or large sums consider hardware wallets.
Q: How do I revoke token approvals?
A: Use the revoke flows or tools covered on token-allowances-and-revoke.
Q: What happens if I lose my phone?
A: Restore from your seed phrase (see seed-phrase-backup-recovery).
Q: How do I detect MetaMask account change?
A: Listen for the provider event 'accountsChanged' (example above).
Q: How do I get MetaMask account from web3?
A: Call web3.eth.getAccounts(), or window.ethereum.request({ method: 'eth_accounts' }). If result is empty prompt via eth_requestAccounts.
Conclusion and next steps
This guide shows the concrete steps to detect and integrate MetaMask in React dApps, track account and chain changes, read accounts with Web3.js, and deploy contracts from the browser (deploy contract with MetaMask). If you want deployment-focused next steps, read developer-rpc-and-node-guide and contract-interactions. Build a small test dApp, sign a transaction, and deploy to a testnet — that will validate your flow quickly.
