Crypto Wallet Connection to Web Application

Our company is engaged in the development, support and maintenance of sites of any complexity. From simple one-page sites to large-scale cluster systems built on micro services. Experience of developers is confirmed by certificates from vendors.
Development and maintenance of all types of websites:
Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:
Development stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1215
  • 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
    1043
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    823
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Website development for FIXPER company
    815

Implementing Crypto Wallet Connection to Web Application

Wallet connection is the first entry point for any Web3 application. The user clicks "Connect Wallet," the browser opens MetaMask or WalletConnect QR, the application gets the address and signature. Sounds simple, but under the hood — three different protocols, a dozen wallet providers, and a number of state issues that need to be solved correctly from the start.

What Happens During Connection

  1. Browser checks for window.ethereum (injected wallets: MetaMask, Rabby, Brave Wallet)
  2. Application calls eth_requestAccounts — confirmation popup appears
  3. Wallet returns an array of addresses, the first is active
  4. Application signs SIWE (Sign-In with Ethereum) message for backend authentication
  5. Session is created on the server by signature

WalletConnect (mobile wallets) works differently: through relay server, WebSocket, and QR code. Coinbase Wallet supports both methods.

Minimal Implementation via ethers.js

// lib/wallet.ts
import { BrowserProvider, JsonRpcSigner } from 'ethers';

export interface WalletState {
  address: string | null;
  chainId: number | null;
  provider: BrowserProvider | null;
  signer: JsonRpcSigner | null;
}

export async function connectWallet(): Promise<WalletState> {
  if (!window.ethereum) {
    throw new Error('No injected wallet found. Install MetaMask.');
  }

  const provider = new BrowserProvider(window.ethereum);
  const accounts = await provider.send('eth_requestAccounts', []);
  const network = await provider.getNetwork();
  const signer = await provider.getSigner();

  return {
    address: accounts[0],
    chainId: Number(network.chainId),
    provider,
    signer,
  };
}

export async function switchChain(chainId: number): Promise<void> {
  await window.ethereum.request({
    method: 'wallet_switchEthereumChain',
    params: [{ chainId: `0x${chainId.toString(16)}` }],
  });
}

Handling Wallet Events

Wallet changes account or network without notifying the application — need to subscribe to events:

// hooks/useWalletEvents.ts
import { useEffect } from 'react';
import { useWalletStore } from '@/store/wallet';

export function useWalletEvents() {
  const { disconnect, setAddress, setChainId } = useWalletStore();

  useEffect(() => {
    if (!window.ethereum) return;

    const handleAccountsChanged = (accounts: string[]) => {
      if (accounts.length === 0) {
        disconnect();
      } else {
        setAddress(accounts[0]);
      }
    };

    const handleChainChanged = (chainIdHex: string) => {
      setChainId(parseInt(chainIdHex, 16));
      // Don't reload page — update state
    };

    window.ethereum.on('accountsChanged', handleAccountsChanged);
    window.ethereum.on('chainChanged', handleChainChanged);
    window.ethereum.on('disconnect', disconnect);

    return () => {
      window.ethereum.removeListener('accountsChanged', handleAccountsChanged);
      window.ethereum.removeListener('chainChanged', handleChainChanged);
      window.ethereum.removeListener('disconnect', disconnect);
    };
  }, [disconnect, setAddress, setChainId]);
}

SIWE Authentication

A wallet address is not a user identifier by itself. It can be forged in an HTTP request. For backend session, you need a signature:

// lib/siwe.ts
import { SiweMessage } from 'siwe';

export async function signInWithEthereum(
  address: string,
  chainId: number,
  signer: JsonRpcSigner,
): Promise<{ message: string; signature: string }> {
  const nonce = await fetch('/api/auth/nonce').then(r => r.text());

  const message = new SiweMessage({
    domain: window.location.host,
    address,
    statement: 'Sign in to MyApp',
    uri: window.location.origin,
    version: '1',
    chainId,
    nonce,
  });

  const messageStr = message.prepareMessage();
  const signature = await signer.signMessage(messageStr);

  return { message: messageStr, signature };
}

Backend verifies the signature via siwe package (Node.js) or any ecrecover implementation. Nonce in Redis with TTL 5 minutes — protection against replay attacks.

Multiple Wallets via Universal Provider

To support MetaMask, WalletConnect, Coinbase Wallet without custom logic for each — use @web3-onboard or the wagmi + viem stack. Minimal example with @web3-onboard:

import Onboard from '@web3-onboard/core';
import injectedModule from '@web3-onboard/injected-wallets';
import walletConnectModule from '@web3-onboard/walletconnect';

const injected = injectedModule();
const walletConnect = walletConnectModule({
  projectId: process.env.NEXT_PUBLIC_WC_PROJECT_ID!,
  requiredChains: [1, 137],
});

export const onboard = Onboard({
  wallets: [injected, walletConnect],
  chains: [
    { id: '0x1', token: 'ETH', label: 'Ethereum Mainnet', rpcUrl: process.env.ETH_RPC_URL! },
    { id: '0x89', token: 'MATIC', label: 'Polygon', rpcUrl: process.env.POLYGON_RPC_URL! },
  ],
  appMetadata: {
    name: 'MyApp',
    icon: '/logo.svg',
    description: 'DeFi platform',
  },
});

Connection Persistence

After page reload, wallet state needs to be restored without repeated popup:

// Check on initialization
async function restoreConnection(): Promise<void> {
  if (!window.ethereum) return;

  // eth_accounts (not eth_requestAccounts) — doesn't call popup
  const accounts: string[] = await window.ethereum.request({
    method: 'eth_accounts',
  });

  if (accounts.length > 0) {
    // Wallet already authorized — restore state
    const provider = new BrowserProvider(window.ethereum);
    const network = await provider.getNetwork();
    walletStore.set({ address: accounts[0], chainId: Number(network.chainId) });
  }
}

Timeline: basic MetaMask + WalletConnect connection with SIWE authentication — 2–3 days. Including event handling, persistence, and support for 3–4 wallets via @web3-onboard3–5 days.