
Basic connect flow — code samples
There are two common patterns: call provider.request('eth_requestAccounts') and use libraries that wrap it (web3.js / ethers.js / wagmi). web3.eth.getAccounts will return the currently unlocked accounts, but it does not prompt the user to connect. That distinction matters.
web3.eth.getAccounts metamask example:
// Using web3.js after injection
const accounts = await web3.eth.getAccounts();
// returns [] if locked/disconnected
Preferred connect (prompts user):
// EIP-1193 style
const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
// accounts[0] is the selected address
Or with ethers.js:
const provider = new ethers.providers.Web3Provider(window.ethereum);
await provider.send('eth_requestAccounts', []);
const signer = provider.getSigner();
Note: web3 metamask get account patterns are still common in legacy code. I recommend switching to provider.request for predictable permission prompts.
Handle these events from the provider: accountsChanged, chainChanged, connect, disconnect. Reactively updating UI on those events is essential for correct UX.
Adding and switching networks (wallet_addEthereumChain)
Want to add a custom RPC from your dApp? The RPC method is wallet_addEthereumChain (often written wallet_addethereumchain metamask in search queries). Use it carefully — the call asks the user to approve adding a network.
Example:
await window.ethereum.request({
method: 'wallet_addEthereumChain',
params: [{
chainId: '0x89', // hex for 137
chainName: 'Polygon Mainnet',
nativeCurrency: { name: 'MATIC', symbol: 'MATIC', decimals: 18 },
rpcUrls: ['https://rpc-mainnet.matic.network/'],
blockExplorerUrls: ['https://polygonscan.com']
}]
});
If the chain already exists, use wallet_switchEthereumChain with the chainId. Always handle user rejection and provider errors. See how to add custom networks for a step-by-step.
Adding tokens from your dApp (React example)
Users expect a one-click “Add token” flow. That’s wallet_watchAsset.
react add to wallet metamask example:
function AddTokenButton({ tokenAddress, symbol, decimals, image }) {
async function addToken() {
try {
const added = await window.ethereum.request({
method: 'wallet_watchAsset',
params: {
type: 'ERC20',
options: { address: tokenAddress, symbol, decimals, image },
},
});
console.log('Token added:', added);
} catch (err) {
console.error(err);
}
}
return <button onClick={addToken}>Add token to wallet</button>;
}
This only prompts the user; they must confirm. And yes, some mobile browsers do not expose the injected provider reliably — test both desktop and mobile.
Mobile: WalletConnect, SDKs, and wagmi
Mobile is different. Many users run MetaMask as a mobile app and expect deep links or WalletConnect sessions. WalletConnect creates a remote provider session (QR/deeplink) that works well when your web app opens a mobile browser or uses an in-app browser.
If you use wagmi, you get connectors that simplify both desktop and mobile flows. For example, use MetaMaskConnector (injected) on desktop and WalletConnectConnector for mobile to reach MetaMask Mobile. wagmi metamask mobile setups often use a WalletConnect bridge under the hood for mobile UIs.
If you need a programmatic provider for mobile without WalletConnect, consider the official SDK connection metamask option (an SDK that exposes a provider over a tunnel). Each method has trade-offs for UX and security.
See walletconnect-and-mobile-browser and walletconnect-mobile-browser for testing notes.
Security and dev best practices
- Never ask for a private key. The provider handles signing.
- Avoid requesting unlimited token allowance in your dApp flows. Offer fine-grained approvals or explain the risk.
- Verify chainId before performing transactions to avoid network mismatch losses (users sometimes pay fees on the wrong chain).
- Simulate transactions on testnets or a forked node when possible. I once sent a transaction to the wrong network because my frontend assumed the chain — don’t do that.
For approval revocation guidance, link users to revoke approvals and to general security best practices.
Common problems and debugging tips
- Empty accounts array: extension locked or user didn't connect. Prompt with eth_requestAccounts.
- wallet_addEthereumChain fails: check chainId hex format and RPC URL; handle user rejection gracefully.
- Mobile connect broken: test with both injected provider and WalletConnect; some iOS browsers kill injected providers.
If transactions appear stuck, consult stuck-pending-transactions and gas-fees-and-eip-1559.
Quick comparison: connection methods
| Method |
UX |
When to use |
| Injected provider (window.ethereum) |
Instant (desktop) |
dApps focused on desktop extension users |
| WalletConnect |
Mobile-friendly (QR/deeplink) |
Mobile-first dApps or wallets that don't inject |
| SDK connection (remote provider) |
Programmatic tunnel |
Apps that need a controlled mobile provider (advanced) |
Who should use this, who should look elsewhere
Who this is for: frontend engineers building EVM-compatible DeFi UIs, token dashboards, and dApp onboarding flows. If you expect users to switch networks, implement wallet_addEthereumChain flows and clear error handling.
Who should look elsewhere: teams needing on-chain custody or institutional custody solutions (hot wallet is for user keys). If users need hardware-level signing, add explicit Ledger/Trezor support (see connect-ledger).
FAQ
Q: Is it safe to keep crypto in a hot wallet?
A: Hot wallets balance convenience and risk. For daily DeFi, use a hot wallet for small balances and a hardware wallet for large holdings. See backup-and-recovery-options.
Q: How do I revoke token approvals?
A: Use on-chain revoke UIs or call smart contract functions that set allowance to zero. Follow the guide at revoke-approvals.
Q: What happens if I lose my phone?
A: If your seed phrase is backed up, restore to another device. If not, funds are likely lost. Read backup-and-recovery-options for secure workflows.
Conclusion & next steps
MetaMask provider integration is straightforward once you respect EIP-1193 patterns, handle user permissions, and test desktop and mobile flows. I’ve built flows that work across L2s and mobile, and the common failure points are predictable: missing provider, wrong chainId format, and unhandled user rejection. Want practical how-tos next? Read the mobile setup guide and the step-by-step on adding networks: setup-mobile and how-to-wallet-addethereumchain.