MetaMask Wallet Integration

We design and develop full-cycle blockchain solutions: from smart contract architecture to launching DeFi protocols, NFT marketplaces and crypto exchanges. Security audits, tokenomics, integration with existing infrastructure.
Showing 1 of 1 servicesAll 1306 services
MetaMask Wallet Integration
Simple
~2-3 business days
FAQ
Blockchain Development Services
Blockchain Development Stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1214
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    852
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_logo-advance_0.png
    B2B Advance company logo design
    561
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    823

MetaMask Wallet Integration

MetaMask injects window.ethereum into the page — and direct work with the provider usually ends there. In 2024, writing window.ethereum.request({ method: 'eth_requestAccounts' }) directly — this is either a prototype or technical debt. Proper integration via wagmi + viem handles all edge cases: multiple wallets in browser, network switching, disconnection, mobile MetaMask via deeplink.

Stack and Connection

wagmi v2 + viem — the standard for modern dApps. Wagmi manages connection state, auto-reconnect, events. Viem — a typed low-level client instead of ethers.js.

import { createConfig, http } from 'wagmi'
import { mainnet, base } from 'wagmi/chains'
import { metaMask } from 'wagmi/connectors'

export const config = createConfig({
  chains: [mainnet, base],
  connectors: [metaMask()],
  transports: {
    [mainnet.id]: http(),
    [base.id]: http(),
  },
})

Connect button:

import { useConnect, useAccount, useDisconnect } from 'wagmi'

function ConnectButton() {
  const { address, isConnected } = useAccount()
  const { connect, connectors } = useConnect()
  const { disconnect } = useDisconnect()

  if (isConnected) {
    return (
      <button onClick={() => disconnect()}>
        {address?.slice(0, 6)}...{address?.slice(-4)}
      </button>
    )
  }

  const metamask = connectors.find(c => c.id === 'metaMaskSDK')
  return (
    <button onClick={() => connect({ connector: metamask! })}>
      Connect MetaMask
    </button>
  )
}

Network Switching and Adding Custom Network

Common problem: user on Ethereum mainnet, dApp works on Base. Need to request switch, and if network not added — add it.

import { useSwitchChain } from 'wagmi'

function NetworkGuard({ children, requiredChainId }: Props) {
  const { chainId } = useAccount()
  const { switchChain, isPending } = useSwitchChain()

  if (chainId !== requiredChainId) {
    return (
      <button
        onClick={() => switchChain({ chainId: requiredChainId })}
        disabled={isPending}
      >
        Switch to Base
      </button>
    )
  }
  return children
}

wagmi automatically calls wallet_addEthereumChain if wallet_switchEthereumChain returns error 4902 (chain not found).

Message Signing and Transactions

EIP-712 typed data signing — for structured signatures (permit, orders, auth tokens):

import { useSignTypedData } from 'wagmi'

const { signTypedData } = useSignTypedData()

signTypedData({
  domain: { name: 'MyApp', version: '1', chainId: 8453 },
  types: {
    Login: [
      { name: 'address', type: 'address' },
      { name: 'nonce', type: 'string' },
    ],
  },
  primaryType: 'Login',
  message: { address: userAddress, nonce: sessionNonce },
})

For SIWE (Sign-In with Ethereum) — siwe library generates standardized message, backend verifies via SiweMessage.verify().

Error Handling

MetaMask returns specific error codes:

  • 4001 — user rejected request
  • 4902 — network not found
  • -32002 — request already pending (second connect call while first hangs)
  • -32603 — internal JSON-RPC error (usually insufficient gas or revert)
const { error } = useConnect()

useEffect(() => {
  if (error?.name === 'UserRejectedRequestError') {
    toast('Connection cancelled')
  } else if (error?.name === 'ConnectorAlreadyConnectedError') {
    // already connected, ignore
  }
}, [error])

Mobile Deeplink

On mobile MetaMask opens via deeplink metamask://dapp/<your-url>. wagmi MetaMask connector automatically handles this via MetaMask SDK. For correct work on iOS need HTTPS (not HTTP) and properly configured WagmiProvider at top app level.