Build Web3Modal Wallet Flow with Claude (2026)
Web3Modal is the standard library for connecting wallets to decentralized applications. Whether you’re building a DeFi protocol, NFT marketplace, or Web3 gaming platform, integrating wallet connections smoothly is crucial for user experience. This guide shows you how to use Claude Code to build solid Web3Modal wallet workflows that handle connection, disconnection, and account changes gracefully.
Understanding Web3Modal Architecture
Web3Modal (now part of the Reown ecosystem) provides a unified interface for connecting to dozens of wallet providers including MetaMask, Rainbow, Coinbase Wallet, and WalletConnect. The library handles the complexity of different wallet APIs so you can focus on your application logic.
Before diving into workflows, ensure you have the necessary dependencies:
npm install @web3modal/wagmi wagmi viem
The core components are the Web3Modal instance, Wagmi’s configuration, and the connection components that trigger the wallet modal.
Web3Modal vs Alternatives
Understanding when to choose Web3Modal over competing solutions helps you make the right architectural decision. Here is a quick comparison of the major wallet connection libraries:
| Library | Wallet Support | Bundle Size | SSR Support | WalletConnect | Best For |
|---|---|---|---|---|---|
| Web3Modal v3 (Reown) | 300+ wallets | ~150KB | Yes | Built-in | Production dApps needing broad reach |
| ConnectKit | 40+ wallets | ~80KB | Yes | Via WalletConnect | Polished UI with minimal config |
| RainbowKit | 30+ wallets | ~100KB | Yes | Via WalletConnect | React apps, high customizability |
| Web3-Onboard | 35+ wallets | ~120KB | Partial | Via WalletConnect | Framework-agnostic projects |
| Custom EIP-1193 | Any injected | Minimal | Manual | Manual | Lightweight internal tools |
Web3Modal stands out when you need the widest wallet compatibility and have WalletConnect QR code connectivity as a requirement. If you are building a consumer-facing dApp where users may connect from mobile with any wallet, Web3Modal’s breadth is hard to beat.
Setting Up Your Web3Modal Instance
The foundation of any wallet workflow starts with proper initialization. Here’s a practical setup using Wagmi v2:
import { createConfig, http } from 'wagmi'
import { mainnet, sepolia } from 'wagmi/chains'
import { web3ModalInstance } from './modal'
export const config = createConfig({
chains: [mainnet, sepolia],
transports: {
[mainnet.id]: http(),
[sepolia.id]: http(),
},
})
export const modalConfig = {
projectId: 'YOUR_PROJECT_ID',
chains: [mainnet, sepolia],
ssr: true,
}
This configuration connects your app to Ethereum mainnet and Sepolia testnet. Replace YOUR_PROJECT_ID with your Web3Modal project ID from the Reown dashboard.
Creating the Modal Instance
The modal itself lives in a separate module to avoid circular import issues and ensure it initializes exactly once:
// modal.js
import { createWeb3Modal } from '@web3modal/wagmi/react'
import { defaultWagmiConfig } from '@web3modal/wagmi/react/config'
import { mainnet, sepolia, polygon, arbitrum } from 'wagmi/chains'
import { QueryClient } from '@tanstack/react-query'
const projectId = process.env.NEXT_PUBLIC_WC_PROJECT_ID
if (!projectId) {
throw new Error('Missing NEXT_PUBLIC_WC_PROJECT_ID environment variable')
}
const metadata = {
name: 'My dApp',
description: 'My decentralized application',
url: 'https://mydapp.com',
icons: ['https://mydapp.com/favicon.ico'],
}
const chains = [mainnet, sepolia, polygon, arbitrum]
export const config = defaultWagmiConfig({
chains,
projectId,
metadata,
ssr: true,
storage: createStorage({
storage: cookieStorage,
}),
})
export const queryClient = new QueryClient()
createWeb3Modal({
wagmiConfig: config,
projectId,
enableAnalytics: true,
enableOnramp: true,
themeMode: 'dark',
})
Notice the metadata object, this is what appears to users inside the WalletConnect QR modal and in their wallet apps. Getting these values right improves trust and the overall connection experience.
Wrapping Your App in Providers
In a Next.js app, the provider setup goes into a context component so client-side rendering is isolated cleanly:
// providers.jsx
'use client'
import { WagmiProvider } from 'wagmi'
import { QueryClientProvider } from '@tanstack/react-query'
import { config, queryClient } from '@/lib/modal'
export function Providers({ children, initialState }) {
return (
<WagmiProvider config={config} initialState={initialState}>
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
</WagmiProvider>
)
}
And in layout.js (Next.js App Router):
// app/layout.js
import { headers, cookies } from 'next/headers'
import { cookieToInitialState } from 'wagmi'
import { config } from '@/lib/modal'
import { Providers } from '@/components/providers'
export default function RootLayout({ children }) {
const initialState = cookieToInitialState(config, cookies().toString())
return (
<html lang="en">
<body>
<Providers initialState={initialState}>
{children}
</Providers>
</body>
</html>
)
}
Passing initialState from cookies enables server-side hydration of the wallet connection state, which eliminates the flash of “disconnected” that plagues many dApps on page load.
The Connection Workflow
A complete wallet connection workflow involves several states your UI must handle. Let’s build a practical hook that manages these states:
import { useState, useCallback, useEffect } from 'react'
import { useConnect } from 'wagmi'
import { useWeb3Modal } from '@web3modal/wagmi/react'
export function useWalletConnection() {
const [isConnected, setIsConnected] = useState(false)
const [address, setAddress] = useState(null)
const { open } = useWeb3Modal()
const { connectors } = useConnect()
const connect = useCallback(async () => {
try {
await open({ view: 'Connect' })
} catch (error) {
console.error('Connection failed:', error)
}
}, [open])
const disconnect = useCallback(async () => {
setIsConnected(false)
setAddress(null)
}, [])
return {
isConnected,
address,
connect,
disconnect,
}
}
This hook abstracts away the complexity of the connection process. The open() function triggers Web3Modal’s built-in connection UI, which handles provider selection and wallet communication.
Building a Full Connection Button Component
A production-quality connect button needs to show the right UI for every connection state: disconnected, connecting, connected, and wrong network:
// ConnectButton.jsx
import { useWeb3Modal, useWeb3ModalState } from '@web3modal/wagmi/react'
import { useAccount, useBalance, useChainId } from 'wagmi'
import { mainnet } from 'wagmi/chains'
function truncateAddress(address) {
if (!address) return ''
return `${address.slice(0, 6)}...${address.slice(-4)}`
}
export function ConnectButton() {
const { open } = useWeb3Modal()
const { open: isOpen } = useWeb3ModalState()
const { address, isConnected, isConnecting } = useAccount()
const chainId = useChainId()
const { data: balance } = useBalance({ address })
const isWrongNetwork = isConnected && chainId !== mainnet.id
if (isConnecting || isOpen) {
return (
<button disabled className="btn btn-primary btn-loading">
Connecting...
</button>
)
}
if (isWrongNetwork) {
return (
<button
onClick={() => open({ view: 'Networks' })}
className="btn btn-warning"
>
Switch Network
</button>
)
}
if (isConnected && address) {
return (
<button
onClick={() => open({ view: 'Account' })}
className="btn btn-secondary"
>
{balance
? `${parseFloat(balance.formatted).toFixed(4)} ${balance.symbol}`
: truncateAddress(address)}
</button>
)
}
return (
<button
onClick={() => open({ view: 'Connect' })}
className="btn btn-primary"
>
Connect Wallet
</button>
)
}
The view parameter lets you deep-link directly to specific modal screens. 'Connect' shows the wallet selector, 'Account' shows account details, and 'Networks' shows the chain switcher.
Handling Account Changes
Wallet connections aren’t static, users can switch accounts, disconnect externally, or have their session expire. Your workflow must respond to these changes:
import { useWatchAccount } from '@web3modal/wagmi/react'
export function useAccountListener(onChange) {
const { address, isConnected, chainId } = useWatchAccount({
onChange(account) {
if (account.isConnected) {
onChange({
type: 'CONNECTED',
address: account.address,
chainId: account.chainId,
})
} else {
onChange({ type: 'DISCONNECTED' })
}
},
})
return { address, isConnected, chainId }
}
The useWatchAccount hook subscribes to account changes in real-time. This is essential for dApps that need to update their UI immediately when a user switches wallets or disconnects.
Responding to Chain Changes
Chain changes require special handling because they can invalidate cached contract calls and require different contract addresses:
import { useChainId, useSwitchChain } from 'wagmi'
import { mainnet, polygon } from 'wagmi/chains'
const CONTRACT_ADDRESSES = {
[mainnet.id]: '0xMainnetContractAddress',
[polygon.id]: '0xPolygonContractAddress',
}
export function useChainAwareContract() {
const chainId = useChainId()
const { switchChainAsync } = useSwitchChain()
const contractAddress = CONTRACT_ADDRESSES[chainId]
const isSupported = Boolean(contractAddress)
const ensureCorrectChain = async (targetChainId) => {
if (chainId !== targetChainId) {
await switchChainAsync({ chainId: targetChainId })
}
}
return { contractAddress, isSupported, ensureCorrectChain }
}
This pattern keeps contract addresses centralized and makes it easy to add new chain support without scattering magic addresses throughout your codebase.
Persisting Connection Preferences
Users expect their wallet to stay connected across page refreshes. Wagmi handles this automatically through its storage adapter, but you may want additional per-wallet preferences:
const STORAGE_KEY = 'wallet_preferences'
export function useWalletPreferences() {
const { address } = useAccount()
const savePreference = useCallback((key, value) => {
if (!address) return
const prefs = JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}')
prefs[address] = { ...prefs[address], [key]: value }
localStorage.setItem(STORAGE_KEY, JSON.stringify(prefs))
}, [address])
const getPreference = useCallback((key, defaultValue) => {
if (!address) return defaultValue
const prefs = JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}')
return prefs[address]?.[key] ?? defaultValue
}, [address])
const clearPreferences = useCallback(() => {
if (!address) return
const prefs = JSON.parse(localStorage.getItem(STORAGE_KEY) || '{}')
delete prefs[address]
localStorage.setItem(STORAGE_KEY, JSON.stringify(prefs))
}, [address])
return { savePreference, getPreference, clearPreferences }
}
This scopes preferences per wallet address, which means switching wallets automatically loads the correct set of preferences for that user.
Disconnection Best Practices
Proper disconnection clears all local state and ensures a clean slate for future connections:
export function useDisconnect() {
const { disconnectAsync } = useDisconnect()
const handleDisconnect = useCallback(async () => {
try {
// Clear any stored preferences
localStorage.removeItem('wallet_preference')
sessionStorage.clear()
// Execute the wagmi disconnect
await disconnectAsync()
// Emit custom event for other components
window.dispatchEvent(new CustomEvent('wallet:disconnected'))
} catch (error) {
console.error('Disconnect error:', error)
}
}, [disconnectAsync])
return handleDisconnect
}
Full Disconnect Flow with Cleanup
In a real application, disconnection often needs to touch multiple systems: clearing auth tokens, purging cached API data, and resetting UI state. Here is a more comprehensive disconnect implementation:
import { useDisconnect as useWagmiDisconnect } from 'wagmi'
import { useQueryClient } from '@tanstack/react-query'
import { useRouter } from 'next/navigation'
export function useFullDisconnect() {
const { disconnectAsync } = useWagmiDisconnect()
const queryClient = useQueryClient()
const router = useRouter()
const disconnect = useCallback(async (options = {}) => {
const { redirectTo = '/', clearCache = true } = options
try {
// 1. Clear backend session if you maintain one
await fetch('/api/auth/logout', {
method: 'POST',
credentials: 'include',
}).catch(() => {}) // Non-fatal
// 2. Clear wallet preferences
localStorage.removeItem('wallet_preferences')
localStorage.removeItem('wagmi.store')
// 3. Clear React Query cache for wallet-specific data
if (clearCache) {
queryClient.removeQueries({ queryKey: ['wallet'] })
queryClient.removeQueries({ queryKey: ['balance'] })
queryClient.removeQueries({ queryKey: ['nfts'] })
}
// 4. Execute wagmi disconnect (clears connector and storage)
await disconnectAsync()
// 5. Notify other browser tabs
const bc = new BroadcastChannel('wallet_events')
bc.postMessage({ type: 'DISCONNECTED' })
bc.close()
// 6. Redirect
router.push(redirectTo)
} catch (error) {
console.error('Disconnect sequence failed:', error)
// Still attempt wagmi disconnect even if cleanup fails
await disconnectAsync().catch(() => {})
}
}, [disconnectAsync, queryClient, router])
return disconnect
}
The BroadcastChannel call ensures that if your user has your dApp open in multiple tabs, all of them respond to the disconnect event simultaneously. This prevents the confusing scenario where one tab shows a connected state while another shows disconnected.
Integrating with Claude Code Workflows
Claude Code can accelerate your Web3Modal integration in several ways. First, use it to generate boilerplate code for common wallet patterns. Describe your requirements and let Claude generate the initial implementation.
For debugging wallet issues, Claude excels at analyzing connection problems. Share your error messages and configuration, and it can identify common issues like:
- Incorrect chain configuration
- Missing project ID
- Provider compatibility issues
- Event listener memory leaks
Here’s a practical Claude prompt for Web3Modal debugging:
I'm seeing "Connector not found" when trying to connect via WalletConnect.
My config includes mainnet and sepolia, using wagmi v2 and web3modal v3.
What could cause this and how do I fix it?
Effective Claude Code Prompts for Web3 Development
Getting the best results from Claude Code requires well-structured prompts. Here are prompt templates that consistently produce useful output for Web3Modal work:
For generating a new hook:
I need a React hook that:
- Reads an ERC-20 token balance for the connected wallet
- Supports wagmi v2 and viem
- Handles loading, error, and undefined states
- Refreshes every 10 seconds
- Takes a tokenAddress and decimals parameter
Here is my current wagmi config: [paste config]
For debugging a specific error:
I'm getting this error in my dApp:
[paste full error + stack trace]
This happens when [describe the user action].
My relevant code:
[paste relevant code]
Wagmi version: X.X.X, Web3Modal version: X.X.X
For reviewing connection architecture:
Review this wallet connection architecture for a DeFi protocol.
The app needs to support MetaMask, WalletConnect, and Coinbase Wallet.
Users connect once per session (no auto-reconnect).
Identify any issues, missing error handling, or state management problems.
[paste code]
Common Web3Modal Errors and Claude-Assisted Fixes
| Error | Likely Cause | Claude Prompt Focus |
|---|---|---|
Connector not found |
Missing connector in config, wallet not installed | Ask Claude to audit your defaultWagmiConfig connectors array |
Project ID not set |
Missing or invalid WalletConnect project ID | Ask Claude to review env var wiring and initialization order |
User rejected request |
User dismissed the modal | Ask Claude to add proper error boundary and retry UI |
Chain mismatch |
dApp and wallet on different networks | Ask Claude to implement chain switching guard hook |
Already processing request |
Duplicate connect call triggered | Ask Claude to add a lock mechanism to prevent concurrent open() calls |
Provider not found |
SSR rendering without 'use client' |
Ask Claude to audit provider tree for server/client boundary issues |
Handling Multiple Wallet Types
Different wallets have varying capabilities. Your workflow should accommodate this:
export function useWalletCapabilities(walletClient) {
const [capabilities, setCapabilities] = useState({
supportsSignTypedData: false,
supportsPersonalSign: true,
supportsChainSwitching: true,
})
useEffect(() => {
if (!walletClient) return
const provider = walletClient.provider
setCapabilities({
supportsSignTypedData: typeof provider?.signTypedData === 'function',
supportsPersonalSign: typeof provider?.personalSign === 'function',
supportsChainSwitching: typeof provider?.switchChain === 'function',
})
}, [walletClient])
return capabilities
}
This pattern lets you conditionally render features based on wallet capabilities, for example, hiding “Sign typed data” buttons for wallets that don’t support it.
Wallet Feature Matrix
Different wallets implement different parts of the EIP ecosystem. Here is what you can expect from the most common wallets your users will have:
| Feature | MetaMask | Rainbow | Coinbase Wallet | Ledger (WC) | Safe |
|---|---|---|---|---|---|
eth_sign |
Yes | Yes | Yes | Yes | No |
personal_sign |
Yes | Yes | Yes | Yes | Yes |
eth_signTypedData_v4 |
Yes | Yes | Yes | Partial | Yes |
| Chain switching | Yes | Yes | Yes | No | No |
| EIP-6963 (multi-inject) | Yes | Yes | Yes | No | No |
| Smart contract wallet | No | No | Optional | No | Yes |
| Hardware signing | No | No | No | Yes | No |
When building features that depend on typed data signing or chain switching, you should check capabilities before rendering those UI elements, or provide graceful fallbacks. Claude Code is helpful here for generating the conditional rendering logic once you describe the wallet feature matrix for your specific requirements.
Handling EIP-6963 Multi-Provider Injection
Modern browsers may have multiple wallets installed simultaneously. EIP-6963 provides a standard way to enumerate all installed wallets:
import { useState, useEffect } from 'react'
export function useInstalledWallets() {
const [wallets, setWallets] = useState([])
useEffect(() => {
const handleProvider = ({ detail }) => {
setWallets(prev => {
const exists = prev.find(w => w.info.uuid === detail.info.uuid)
if (exists) return prev
return [...prev, detail]
})
}
window.addEventListener('eip6963:announceProvider', handleProvider)
window.dispatchEvent(new Event('eip6963:requestProvider'))
return () => {
window.removeEventListener('eip6963:announceProvider', handleProvider)
}
}, [])
return wallets // Array of { info: { name, icon, uuid }, provider }
}
Web3Modal v3 handles EIP-6963 discovery automatically, but knowing how it works helps you debug cases where a user’s wallet isn’t appearing in the modal.
Error Handling Patterns
Solid wallet workflows require comprehensive error handling:
export async function withWalletErrorHandling(fn) {
try {
return await fn()
} catch (error) {
const errorMap = {
'User rejected request': 'Connection was rejected. Please try again.',
'Connector not found': 'Wallet not installed or not supported.',
'Chain changed': 'Network changed. Please reconnect.',
}
const message = errorMap[error.message] || 'Wallet error. Please try again.'
throw new WalletError(message, error.code)
}
}
class WalletError extends Error {
constructor(message, code) {
super(message)
this.code = code
}
}
Complete Error Classification System
EIP-1193 defines a set of numeric error codes that wallets return. Mapping these to user-friendly messages makes a significant difference in UX:
// errors.js
export const WALLET_ERROR_CODES = {
USER_REJECTED: 4001,
UNAUTHORIZED: 4100,
UNSUPPORTED_METHOD: 4200,
DISCONNECTED: 4900,
CHAIN_DISCONNECTED: 4901,
CHAIN_NOT_ADDED: 4902,
}
const ERROR_MESSAGES = {
[WALLET_ERROR_CODES.USER_REJECTED]: {
title: 'Request Rejected',
message: 'You rejected the connection request.',
recoverable: true,
action: 'Try Again',
},
[WALLET_ERROR_CODES.UNAUTHORIZED]: {
title: 'Unauthorized',
message: 'Your wallet is not authorized for this action.',
recoverable: false,
action: null,
},
[WALLET_ERROR_CODES.UNSUPPORTED_METHOD]: {
title: 'Unsupported',
message: 'Your wallet does not support this feature.',
recoverable: false,
action: 'Switch Wallet',
},
[WALLET_ERROR_CODES.DISCONNECTED]: {
title: 'Disconnected',
message: 'Wallet disconnected. Please reconnect.',
recoverable: true,
action: 'Reconnect',
},
[WALLET_ERROR_CODES.CHAIN_NOT_ADDED]: {
title: 'Network Not Found',
message: 'This network is not in your wallet. Add it to continue.',
recoverable: true,
action: 'Add Network',
},
}
export function classifyWalletError(error) {
const code = error.code ?? error.cause?.code
return ERROR_MESSAGES[code] ?? {
title: 'Wallet Error',
message: error.message || 'An unexpected wallet error occurred.',
recoverable: true,
action: 'Try Again',
}
}
With this classification system in place, you can build an error display component that renders contextually appropriate recovery actions:
function WalletErrorToast({ error, onRetry, onSwitchWallet }) {
const classified = classifyWalletError(error)
return (
<div className="toast toast-error">
<strong>{classified.title}</strong>
<p>{classified.message}</p>
{classified.action === 'Try Again' && (
<button onClick={onRetry}>Try Again</button>
)}
{classified.action === 'Switch Wallet' && (
<button onClick={onSwitchWallet}>Switch Wallet</button>
)}
{classified.action === 'Reconnect' && (
<button onClick={() => open({ view: 'Connect' })}>Reconnect</button>
)}
</div>
)
}
Transaction Error Handling
Beyond connection errors, you also need to handle transaction-level errors that occur during contract interactions:
import { parseEther } from 'viem'
import { useWriteContract, useWaitForTransactionReceipt } from 'wagmi'
export function useContractWrite(abi, address) {
const {
writeContractAsync,
isPending,
error: writeError,
} = useWriteContract()
const [txHash, setTxHash] = useState(null)
const {
isLoading: isConfirming,
isSuccess,
error: receiptError,
} = useWaitForTransactionReceipt({ hash: txHash })
const execute = async (functionName, args, valueInEth) => {
try {
const hash = await writeContractAsync({
abi,
address,
functionName,
args,
value: valueInEth ? parseEther(valueInEth.toString()) : undefined,
})
setTxHash(hash)
return hash
} catch (error) {
// Distinguish user rejection from actual errors
if (error.code === 4001) {
throw new Error('Transaction rejected by user')
}
// Gas estimation failure usually means the tx would revert
if (error.message?.includes('execution reverted')) {
throw new Error('Transaction would fail on-chain. Check your inputs.')
}
throw error
}
}
return {
execute,
isPending: isPending || isConfirming,
isSuccess,
txHash,
error: writeError || receiptError,
}
}
This hook gives you a unified state machine over the entire transaction lifecycle: idle, sending, confirming, success, and error. Claude Code is particularly good at extending this pattern to support retry logic, gas estimation, and EIP-1559 fee controls when you describe those requirements.
Testing Your Wallet Workflow
Testing wallet integrations is notoriously difficult because they depend on actual browser extensions. Use Claude Code to help you set up mocked providers for unit and integration tests:
// test-utils/mockProvider.js
export function createMockProvider(overrides = {}) {
return {
request: async ({ method, params }) => {
switch (method) {
case 'eth_requestAccounts':
return ['0x1234567890abcdef1234567890abcdef12345678']
case 'eth_chainId':
return '0x1' // mainnet
case 'eth_getBalance':
return '0x16345785D8A0000' // 0.1 ETH in wei hex
case 'personal_sign':
return '0xMockSignature'
case 'eth_sendTransaction':
return '0xMockTxHash'
default:
throw new Error(`Method not implemented in mock: ${method}`)
}
},
on: (event, handler) => {},
removeListener: (event, handler) => {},
...overrides,
}
}
Ask Claude to extend this mock with stateful behavior, for example, simulating a user rejecting after a delay, or simulating chain switching events during a test run.
Summary
Building a reliable Web3Modal wallet workflow requires handling initialization, connection, disconnection, account changes, and errors comprehensively. The patterns in this guide give you a solid foundation:
- Initialize Web3Modal with proper chain and project configuration
- Use React hooks to manage connection state reactively
- Listen for account changes to keep your UI in sync
- Implement clean disconnect flows that clear all state
- Handle errors gracefully with user-friendly messages
- Test with mocked providers to avoid relying on live wallet extensions
These workflows integrate smoothly with Claude Code’s development assistance, making your Web3 dApp development faster and more reliable. When you hit a wall, whether that is a cryptic EIP-1193 error code, a hydration mismatch from SSR wallet state, or a missing chain configuration, Claude Code can analyze your specific setup and suggest targeted fixes. Remember to test with multiple wallet providers during development to ensure broad compatibility.
Try it: Paste your error into our Error Diagnostic for an instant fix.
Related Reading
- Claude Code for Bolt.new Web App Workflow Guide
- Claude Code for Fast Web Components Workflow
- Claude Code for Fiber Go Web Framework Workflow
- Claude Code for Weights & Biases Workflow Guide
- Claude Code for Upstream Contribution Workflow Guide
- Claude Code for Apache Flink Workflow Tutorial
- Claude Code for Spring WebFlux Workflow Tutorial
- Claude Code for Wake Smart Contract Workflow
- Claude Code for TanStack Start Workflow Guide
- Claude Code For Opa Rego — Complete Developer Guide
- Claude Code for Kong Mesh Workflow Tutorial
Built by theluckystrike. More at zovo.one
Find the right skill → Browse 155+ skills in our Skill Finder.
Quick setup → Launch your project with our Project Starter.