Building a Token-Gated Interface with Wagmi
Step-by-step guide to implementing token-gated content in your Next.js application using Wagmi, including handling dynamic gated content and access states.
Introduction
Token gating is one of the most powerful patterns in Web3. Instead of email/password, you gate access based on NFT ownership or token balances. In this guide, we'll build a complete token-gated interface using Wagmi.
The Core Hook
First, we need a hook that checks if the connected address holds a specific token. Wagmi makes this straightforward with their readContract functionality.
import { useAccount, useReadContract } from 'wagmi'
import { useMemo } from 'react'
const ERC20_ABI = [
{
name: 'balanceOf',
type: 'function',
inputs: [{ name: 'account', type: 'address' }],
outputs: [{ type: 'uint256' }],
stateMutability: 'view',
},
] as const
export function useTokenGate(tokenAddress: `0x${string}`, minBalance: bigint) {
const { address } = useAccount()
const { data: balance } = useReadContract({
address: tokenAddress,
abi: ERC20_ABI,
functionName: 'balanceOf',
args: [address!],
query: { enabled: !!address },
})
return useMemo(() => ({
hasAccess: !!balance && balance >= minBalance,
balance,
isLoading: !address || balance === undefined,
}), [balance, minBalance, address])
}The Gate Component
Now we wrap this in a component that shows different content based on access.
"use client"
import { useTokenGate } from '@/hooks/useTokenGate'
import { ConnectButton } from './ConnectButton'
interface TokenGateProps {
tokenAddress: `0x${string}`
minBalance: bigint
children: React.ReactNode
fallback?: React.ReactNode
}
export function TokenGate({
tokenAddress,
minBalance,
children,
fallback,
}: TokenGateProps) {
const { hasAccess, isLoading } = useTokenGate(tokenAddress, minBalance)
if (isLoading) {
return <div className="animate-pulse h-32 bg-white/5 rounded-lg" />
}
if (!hasAccess) {
return fallback ?? (
<div className="p-8 bg-white/5 rounded-xl text-center">
<p className="text-white/60">You need more tokens to access this content.</p>
</div>
)
}
return <>{children}</>
}Usage Example
Here's how you'd use it in a page. Notice how the premium content is only rendered when the user has sufficient tokens.
// app/premium/page.tsx
import { TokenGate } from '@/components/TokenGate'
const PREMIUM_TOKEN = '0x1234...' as const
const ONE_TOKEN = 1_000_000_000_000_000_000n // 1 token with 18 decimals
export default function PremiumPage() {
return (
<div>
<h1>Premium Content</h1>
<TokenGate
tokenAddress={PREMIUM_TOKEN}
minBalance={ONE_TOKEN}
fallback={<UpgradePrompt />}
>
<ExclusiveContent />
</TokenGate>
</div>
)
}Handling Multiple Tokens
For DAO governance or multi-token gates, you can compose multiple hooks:
export function useMultiTokenGate(requirements: TokenRequirement[]) {
const results = requirements.map(req => ({
...req,
...useTokenGate(req.tokenAddress, req.minBalance),
}))
return {
hasAccess: results.every(r => r.hasAccess),
requirements: results,
isLoading: results.some(r => r.isLoading),
}
}Conclusion
Token gating with Wagmi is straightforward once you understand the pattern: read the balance on-chain, compare against a threshold, conditionally render content. The key is keeping the gate component small and composable so you can reuse it across your application.
Need token gating for your product? Let's build it together →