Интеграция фронтенда с Web3Modal
Если в dApp нужна поддержка больше одного кошелька — не пишите кастомный wallet selector с нуля. WalletConnect's Web3Modal v3 решает эту задачу: 300+ кошельков, WalletConnect QR, Mobile deeplinks, email/social login через Web3Auth — всё из коробки. Задача интеграции — правильно настроить конфиг и не наступить на типичные грабли с hydration и SSR.
Установка и базовая конфигурация
npm install @web3modal/wagmi wagmi viem @tanstack/react-query
Project ID получается на cloud.walletconnect.com. Без него Modal запустится, но WalletConnect соединения работать не будут.
// config/web3modal.ts
import { createWeb3Modal } from '@web3modal/wagmi/react'
import { defaultWagmiConfig } from '@web3modal/wagmi/react/config'
import { mainnet, arbitrum, base, polygon } from 'wagmi/chains'
const projectId = import.meta.env.VITE_WC_PROJECT_ID
const metadata = {
name: 'My dApp',
description: 'My dApp description',
url: 'https://mydapp.xyz', // должен совпадать с доменом в WC Cloud
icons: ['https://mydapp.xyz/icon.png'],
}
export const config = defaultWagmiConfig({
chains: [mainnet, arbitrum, base, polygon],
projectId,
metadata,
})
createWeb3Modal({
wagmiConfig: config,
projectId,
enableAnalytics: true,
enableOnramp: true, // встроенный fiat on-ramp
themeMode: 'dark',
themeVariables: {
'--w3m-accent': '#7c3aed',
'--w3m-border-radius-master': '4px',
},
})
Интеграция в React приложение
// main.tsx
import { WagmiProvider } from 'wagmi'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { config } from './config/web3modal'
const queryClient = new QueryClient()
export function App() {
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
<Router />
</QueryClientProvider>
</WagmiProvider>
)
}
Кнопка подключения — либо готовый компонент, либо кастомный через хук:
// Готовый компонент
<w3m-button />
// Кастомная кнопка
import { useWeb3Modal } from '@web3modal/wagmi/react'
import { useAccount } from 'wagmi'
function ConnectWallet() {
const { open } = useWeb3Modal()
const { address, isConnected } = useAccount()
return (
<button onClick={() => open()}>
{isConnected ? `${address?.slice(0, 6)}...${address?.slice(-4)}` : 'Connect Wallet'}
</button>
)
}
Кастомные сети
Добавление сетей, которых нет в wagmi/chains:
import { defineChain } from 'viem'
const sonic = defineChain({
id: 146,
name: 'Sonic',
nativeCurrency: { name: 'Sonic', symbol: 'S', decimals: 18 },
rpcUrls: { default: { http: ['https://rpc.soniclabs.com'] } },
blockExplorers: { default: { name: 'SonicScan', url: 'https://sonicscan.org' } },
})
export const config = defaultWagmiConfig({
chains: [mainnet, sonic],
// ...
})
Network switching
Web3Modal показывает network switcher автоматически. Для программного переключения:
import { useWeb3Modal } from '@web3modal/wagmi/react'
const { open } = useWeb3Modal()
// Открыть сразу на вкладке сетей
<button onClick={() => open({ view: 'Networks' })}>Switch Network</button>
SSR / Next.js
В Next.js App Router проблема с hydration: Web3Modal инициализируется client-side, но серверный рендер не знает о подключённом кошельке. Решение — 'use client' директива для провайдера и lazy initialization:
// providers/Web3Provider.tsx
'use client'
import { createWeb3Modal } from '@web3modal/wagmi/react'
import { useEffect, useState } from 'react'
// Инициализация вне компонента — выполняется один раз
createWeb3Modal({ wagmiConfig: config, projectId })
export function Web3Provider({ children }: { children: React.ReactNode }) {
const [mounted, setMounted] = useState(false)
useEffect(() => setMounted(true), [])
return (
<WagmiProvider config={config}>
<QueryClientProvider client={queryClient}>
{mounted ? children : null}
</QueryClientProvider>
</WagmiProvider>
)
}
mounted guard предотвращает hydration mismatch — без него React выбрасывает предупреждение о различии серверного и клиентского рендера адреса кошелька.
Email и social login
Web3Modal поддерживает embedded wallets через Web3Auth. Включается в конфиге:
createWeb3Modal({
// ...
featuredWalletIds: [], // убираем featured wallets если нужен clean UI
emailEnabled: true, // email OTP wallet
})
Пользователь входит через email или Google/GitHub — получает non-custodial кошелёк. Для широкой аудитории без крипто-опыта это значительно снижает барьер входа.







