All Insights
Dec 20259 min read

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.

Gabriel Njoabozia
Gabriel NjoaboziaFounder & Lead Engineer

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 →