Crypto wallet connection to web application: turnkey development

Connecting a crypto wallet is the first entry point to any Web3 application. The user clicks "Connect Wallet", the browser opens MetaMask or a WalletConnect QR, and the application receives an address and a signature. It sounds simple, but under the hood there are three different protocols, a dozen

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:

Frequently Asked Questions

Latest works

  • image_website-b2b-advance_0.webp
    B2B ADVANCE company website development
    1422
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1288
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    984
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1250
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    988
  • image_bitrix-bitrix-24-1c_fixper_448_0.webp
    Website development for FIXPER company
    1001

Connecting a crypto wallet is the first entry point to any Web3 application. The user clicks "Connect Wallet", the browser opens MetaMask or a WalletConnect QR, and the application receives an address and a signature. It sounds simple, but under the hood there are three different protocols, a dozen wallet providers, and a number of state-related issues that need to be solved correctly from the start. For example, a typical mistake is calling eth_requestAccounts on every component mount, which leads to constant popup requests and degrades UX. We have been working with this for years: we have 30+ projects with wallet integration on React, Next.js, and Vue under our belt.

Which wallets do we support?

We implement connections for MetaMask (injected EIP-1193), WalletConnect v2 (QR code/WebSocket for mobile wallets), Coinbase Wallet (both methods), Rabby, and Brave Wallet. The table below shows the specifics:

Wallet Connection type Peculiarities
MetaMask EIP-1193 injection Popular, browser extension, test network
WalletConnect QR code / WebSocket Any mobile wallet (Trust, Rainbow, OKX)
Coinbase Wallet Injection + WalletConnect Integration with Coinbase, dapp browser support
Rabby EIP-1193 injection Multi-chain, user-friendly interface, open source

For unification we use adapter libraries: @web3-onboard or wagmi with viem. Compared to a manual implementation via ethers.js, these solutions reduce code volume by 40% and lower the probability of errors when switching wallets. Which stack to choose depends on the requirements for bundle size and flexibility. We will evaluate your project and offer the optimal solution.

Minimal implementation via ethers.js

Below is a basic connection implementation using ethers.js (v6). This code works for custom interfaces when full control is needed.

// 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)}` }], }); } 

How to handle wallet events without memory leaks?

The wallet can change the account or network without notifying the application – you need to subscribe to events. Proper subscription with cleanup on unmount is key to stability.

// 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 the 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]); } 

Why is SIWE the passwordless authentication standard?

A wallet address is not a user identifier – it can easily be spoofed in an HTTP request. SIWE (Sign-In with Ethereum) solves this: the user signs a message, the backend verifies the signature via ecrecover. A nonce with a 5-minute TTL protects against replay attacks. This approach provides a secure session without storing passwords.

// 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 }; } 

The backend verifies the signature via the siwe package (Node.js) or any ecrecover implementation. More about the standard can be read in EIP-4361 or on Wikipedia.

How to unify connection of multiple wallets?

To support multiple providers without custom logic, we use adapter libraries. Manual implementation via ethers.js gives full control but requires writing an abstraction for each wallet. wagmi with viem offers React hooks and a type-safe API, reducing code by 30%, but is not suitable for Vue. @web3-onboard is the most universal: built-in UI and support for 20+ wallets, but adds about 50 KB to the bundle. The choice depends on priorities: lightness vs. development speed.

Example configuration 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', }, }); 

Session restoration after reload

Without persistence, the user would have to go through the entire flow every time. We use eth_accounts – it returns addresses without a popup if the wallet is already authorized.

// Check on initialization async function restoreConnection(): Promise<void> { if (!window.ethereum) return; const accounts: string[] = await window.ethereum.request({ method: 'eth_accounts', }); if (accounts.length > 0) { const provider = new BrowserProvider(window.ethereum); const network = await provider.getNetwork(); walletStore.set({ address: accounts[0], chainId: Number(network.chainId) }); } } 

Typical mistakes and how to avoid them

  • Using eth_requestAccounts on every mount – causes a popup even if the wallet is already connected. Solution: check eth_accounts first.
  • Lack of handling disconnect – can lead to memory leaks. Always subscribe and clean up listeners.
  • Incorrect nonce calculation in SIWE – if the nonce is not saved on the server, the signature cannot be verified. Use a unique nonce with TTL.

What's included in the work

We deliver a complete package:

  • Integration of 3-4 wallets (MetaMask, WalletConnect, Coinbase, Rabby) via an adapter
  • SIWE authentication with backend part (Node.js/Python)
  • Event handling (accountsChanged, chainChanged, disconnect)
  • Session restoration and error handling (rejection, no wallet)
  • Network switching and balance checking
  • Integration documentation and Readme
  • Consultation on library selection for your stack

Contact us for a free assessment of your project – we will respond within a day. Order a wallet integration right now and get clean code with full case coverage.

Timelines and cost

Basic integration of one wallet with SIWE – from 2 to 3 days. A full turnkey solution with support for 4-5 wallets, handling all states, and documentation – from 5 to 7 days. The cost is calculated individually depending on backend complexity and the number of wallets. Get a consultation – we will respond within a day.