---
title: "Ethereum wallets (Wagmi v3)"
description: "EVM wallet stack for Ring clones — Wagmi v3 + viem, custom connector UI (no RainbowKit), RPC and RING token single sources of truth."
locale: "en"
---
# Ethereum wallets (Wagmi v3)

> **Info**
> Use the **Founder** / **Developer** tabs in the docs sidebar to filter this page. Founders get the operator view of what the EVM wallet layer enables; developers get verified module paths, the RPC/token single sources of truth, and connector white-labeling steps.

Ring's EVM (Ethereum / Polygon) wallet stack is **Wagmi v3 + viem 2.x** with a **custom connector picker and account menu** — Ring does **not** bundle `@rainbow-me/rainbowkit`. Wallet providers mount **only on crypto routes** through `Web3ScopeProvider`, and all RPC endpoints and the RING ERC-20 address resolve through config helpers instead of hardcoded strings.

| Concern | Single source of truth | Never do |
|---------|------------------------|----------|
| Connectors / UI | `lib/wagmi-config.ts` + custom picker | Add RainbowKit |
| EVM RPC URL | `getEvmRpcUrl()` (`lib/ring-config-chain.ts`) | Hardcode `polygon-rpc.com` |
| EVM chain id | `getEvmChainId()` (`lib/ring-config-chain.ts`) | Assume `137` inline |
| RING ERC-20 address | `getEvmTokenAddress()` / `getRingTokenAddress()` | Use `getNativeTokenAddress()` (Solana SPL mint) |
| App runtime library | wagmi + viem | `ethers` in `features/` / `app/` / `lib/` |

### For founders

## Why this matters for your clone

EVM wallets let members connect MetaMask, Coinbase Wallet, or any WalletConnect-compatible mobile wallet to your Ring clone — for RING token holdings, staking, and on-chain referral rewards on Polygon. The stack is **config-driven**: you enable it with environment variables, no code changes for the common case.

  
- **[Wallet feature (operator)](/docs/features/wallet.md)** — Custodial wallets, credits, top-up, and PIN security for members.

  
- **[Token economics](/docs/customization/token-economics.md)** — RING utility token, referral rails, and mainnet deploy checklist.

  
- **[Environment configuration](/docs/deployment/environment.md)** — WalletConnect, Polygon RPC, and RING token address env blocks.

  
- **[WalletConnect Project ID](/docs/configuration/walletconnect.md)** — Create a Reown Cloud ID and paste it on Order Lab Secrets.

  
- **[Staking feature](/docs/features/staking.md)** — APR pools and reward claims on the EVM adapter (viem).

### Typical scenarios

- **Members hold RING on Polygon** — the connector picker offers injected wallets, MetaMask, Coinbase Wallet, and (optionally) WalletConnect QR.
- **Mobile-first community** — set a WalletConnect project ID so phone wallets can scan a QR to connect.
- **No on-chain token yet** — leave the RING token address unset; wallet connect still works, token-balance panels degrade gracefully to zero.

### Go-live checklist

- [ ] Authenticated Polygon RPC set (`POLYGON_RPC_URL`, e.g. an Alchemy or Infura key) — never rely on anonymous public hosts.
- [ ] `NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID` set from [Reown Cloud](https://cloud.reown.com) if you want mobile QR connections.
- [ ] RING ERC-20 address (`RING_CONTRACT_ADDRESS`) set once your token is deployed.
- [ ] Branding (`appName`, logo) updated in the connector config for your domain.

### For developers

## Module map (verified)

| Layer | Path | Role |
|-------|------|------|
| Wagmi config | `lib/wagmi-config.ts` | `createConfig` singleton; connectors; Polygon-first chains; `ssr: true` + `cookieStorage` |
| Provider | `providers/web3-provider.tsx` | `WagmiProvider` + React Query `QueryClientProvider` |
| Scoped mount | `components/providers/web3-scope-provider.tsx` | Lazy `dynamic(..., { ssr:false })` mount gated by `pathNeedsWeb3()` + cookie hydration |
| Route gate | `lib/pathname-without-locale.ts` | `pathNeedsWeb3()`: `/login*`, `/auth/wallet-connect*`, `/wallet*`, `/store/checkout*`, `/nft*`, `/ai-web3*`, `/admin/nft*`, `/admin/web3*` |
| Account menu | `components/web3/connected-wallet-menu.tsx`, `components/web3/wallet-shell.tsx` | Custom picker + connected account UI |
| RPC / chain SSOT | `lib/ring-config-chain.ts` | `getEvmRpcUrl()`, `getEvmChainId()` |
| Low-level RPC | `lib/web3/polygon-rpc.ts` | `getPolygonRpcUrl()` (env → Ankr fallback) |
| Token SSOT | `lib/ring-config-chain.ts`, `constants/web3.ts` | `getEvmTokenAddress()`, `getRingTokenAddress()` |

### Provider mount flow

```mermaid
flowchart TD
  A[Root layout] --> B[Web3ScopeProvider]
  B --> C{pathNeedsWeb3?}
  C -- no --> D[Render children, no wagmi]
  C -- yes --> E[dynamic import Web3Provider ssr:false]
  E --> F[cookieToInitialState hydration]
  F --> G[WagmiProvider + QueryClientProvider]
```

### RPC and chain SSOT

Do **not** hardcode public Polygon RPC hosts (`polygon-rpc.com` blocks anonymous traffic). Resolution order:

| Helper | Location | Resolves |
|--------|----------|----------|
| `getEvmRpcUrl()` | `lib/ring-config-chain.ts` | `chains.evm.rpcUrlEnv` env → `getPolygonRpcUrl()` fallback |
| `getEvmChainId()` | `lib/ring-config-chain.ts` | `chains.evm.chainId` (default `137`) |
| `getPolygonRpcUrl()` | `lib/web3/polygon-rpc.ts` | `POLYGON_RPC_URL` → `NEXT_PUBLIC_POLYGON_RPC_URL` → Ankr fallback |

`lib/wagmi-config.ts` wires the Polygon transport as `http(getEvmRpcUrl())` and lists **polygon first** in `chains`. Server routes, NFT market, staking, and wallet transfer adapters resolve RPC through the same helpers — never ad-hoc strings.

### Token SSOT

> **Warning**
> For the RING **ERC-20** address use `getEvmTokenAddress()` (or `getRingTokenAddress()`, which wraps it). **Never** use `getNativeTokenAddress()` for EVM: when `chains.native === 'solana'` that helper returns the **Solana SPL mint**, which is not a valid EVM address.

{`import { getEvmTokenAddress } from '@/lib/ring-config-chain'
// or the constants wrapper (returns zero-address when unset):
import { getRingTokenAddress } from '@/constants/web3'

const ring = getEvmTokenAddress() // string | null (env → chains.evm.tokenAddress)`}

### Connector config

{`import { createConfig, http, cookieStorage, createStorage } from 'wagmi'
import { mainnet, polygon, arbitrum, optimism, base } from 'wagmi/chains'
import { injected, metaMask, coinbaseWallet } from 'wagmi/connectors'
import { walletConnect } from '@wagmi/connectors'
import { getEvmRpcUrl } from '@/lib/ring-config-chain'

export const wagmiConfig = createConfig({
  chains: [polygon, mainnet, arbitrum, optimism, base], // Polygon = Ring EVM default
  connectors: getConnectors(),                          // injected, metaMask, coinbaseWallet, + walletConnect if projectId set
  transports: { [polygon.id]: http(getEvmRpcUrl()) /* others: http() */ },
  ssr: true,
  storage: createStorage({ storage: cookieStorage }),
})`}

WalletConnect is only added when `NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID` is present and not the `demo-project-id` placeholder — this avoids **403** responses from WalletConnect Cloud.

### Hooks (Wagmi v3)

{`'use client'
import { useConnection, useConnect, useConnectors, useSwitchChain } from 'wagmi'

export function Example() {
  const { address, isConnected } = useConnection()   // NOT legacy useAccount()
  const connectors = useConnectors()                  // NOT useConnect().connectors
  const connect = useConnect()
  if (!isConnected) {
    return  connect.mutate({ connector: connectors[0] })}>Connect
  }
  return {address}
}`}

- Prefer **`useConnection()`** over legacy `useAccount()` for `address` / `chain` / `isConnected`.
- List connectors with **`useConnectors()`**; connect via `useConnect().mutate({ connector })` (or `mutateAsync`).
- Switch chains with **`useSwitchChain().mutate({ chainId })`**; read configured chains with **`useChains()`**.
- Migration reference: [Migrate from v2 to v3](https://wagmi.sh/react/guides/migrate-from-v2-to-v3).

### White-label: changing connectors

Edit `lib/wagmi-config.ts` — add or remove connectors from `wagmi/connectors` / `@wagmi/connectors`.

Install only the **peer dependencies** for connectors you enable (`@metamask/connect-evm`, `@coinbase/wallet-sdk`, `@walletconnect/ethereum-provider`).

Set branding (`appName`, `appLogoUrl`, WalletConnect `metadata`) for your domain and update the RPC/token env vars.

The picker lists `useConnectors()` output and dedupes EIP-6963 entries by `connector.id` — verify no duplicate wallet rows after adding a connector.

### Housekeeping (2026-07-21)

The EVM stack is consolidated to a single wagmi SSOT:

- **RPC / token:** `getEvmRpcUrl()` / `getEvmTokenAddress()` in `lib/ring-config-chain.ts` — never hardcode public Polygon hosts or confuse with Solana `getNativeTokenAddress()`.
- **Route gate:** `pathNeedsWeb3()` mounts wagmi only on `/login*`, `/auth/wallet-connect*`, `/wallet*`, `/store/checkout*`, `/nft*`, `/ai-web3*`, `/admin/nft*`, `/admin/web3*`.
- **Balance SSOT:** dead `app/_actions/get-wallet-balance.ts` is **deleted** — use `getWalletBalance` in `app/_actions/wallet.ts`.
- **No ethers in app runtime:** staking adapters use viem `WalletClient` (`features/staking/adapters/evm.ts`). Earlier removals: `WalletConnectPopup` stub, legacy `web3-context` wrappers, `lib/ring-config.ts` facade.

## Environment variables

| Variable | Purpose |
|----------|---------|
| `NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID` | WalletConnect / Reown Cloud project ID ([cloud.reown.com](https://cloud.reown.com)). Enables QR / deep-link flows; omit to disable WalletConnect. |
| `POLYGON_RPC_URL` / `NEXT_PUBLIC_POLYGON_RPC_URL` | Authenticated Polygon JSON-RPC endpoint (e.g. Alchemy/Infura). Also settable per-clone via `chains.evm.rpcUrlEnv`. |
| `RING_CONTRACT_ADDRESS` / `NEXT_PUBLIC_RING_TOKEN_ADDRESS` | RING **ERC-20** address on the configured EVM chain. |

> **Tip**
> Treat every `NEXT_PUBLIC_*` value as public — it ships in the browser bundle. Keep server-only secrets out of `NEXT_PUBLIC_` names.

## Related documentation

  
- [configuration/walletconnect](/docs/configuration/walletconnect.md) — Next-step: obtain Reown Cloud Project ID and set NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID on Secrets.

  
- [configuration/public-vars](/docs/configuration/public-vars.md) — Same-workflow: buyer-writable NEXT_PUBLIC_* catalog.

  
- [features/wallet](/docs/features/wallet.md) — Next-step: custodial wallets, credits, and getWalletBalance Server Action SSOT.

  
- [api/wallet](/docs/api/wallet.md) — Deep-dive: wallet HTTP routes and Server Actions.

  
- [architecture/wallet-conductor](/docs/architecture/wallet-conductor.md) — Same-workflow: custodial provisioning and native-token orchestration.

  
- [features/staking](/docs/features/staking.md) — See-also: EVM staking adapter on viem (not ethers).

  
- [examples/web3-integration](/docs/examples/web3-integration.md) — See-also: legacy ethers examples retired — Wagmi v3 pointer.

  
- [integrations](/docs/integrations.md) — Prerequisite: integrations hub for payments, storage, and wallets.
