Реализация Token-Gated страниц и разделов сайта
Token-Gated страницы закрывают целые разделы сайта для пользователей без нужных токенов. Реализация охватывает как серверную защиту роутов, так и клиентский UI — блюр контента, оверлей с призывом подключить кошелёк.
Стратегии реализации
Hard gate — контент не загружается вообще без токена. Сервер возвращает 403 или редирект.
Soft gate (blur gate) — контент видно размытым, поверх наложен оверлей с призывом получить доступ. Используется как маркетинговый инструмент.
Progressive disclosure — часть контента открыта, остальное — за токеном.
Next.js: серверная защита страниц
// app/members/page.tsx (Next.js App Router)
import { cookies } from 'next/headers';
import { redirect } from 'next/navigation';
import { verifyTokenGate } from '@/lib/token-gate';
export default async function MembersPage() {
const cookieStore = cookies();
const token = cookieStore.get('auth_token')?.value;
if (!token) {
redirect('/connect-wallet?redirect=/members');
}
const { walletAddress } = verifyJwt(token);
const hasAccess = await verifyTokenGate(walletAddress, {
contractAddress: process.env.NFT_CONTRACT,
type: 'ERC721',
minBalance: 1
});
if (!hasAccess) {
redirect('/token-required?contract=' + process.env.NFT_CONTRACT);
}
return <MembersContent />;
}
React: UI Token Gate компонент
// components/TokenGate.tsx
import { useAccount, useReadContract } from 'wagmi';
import { erc721Abi } from 'viem';
interface TokenGateProps {
contractAddress: `0x${string}`;
tokenType: 'ERC721' | 'ERC20';
minBalance?: bigint;
lockedContent: React.ReactNode; // отображается при отсутствии токена
children: React.ReactNode;
}
export function TokenGate({
contractAddress, tokenType, minBalance = 1n, lockedContent, children
}: TokenGateProps) {
const { address, isConnected } = useAccount();
const { data: balance, isLoading } = useReadContract({
address: contractAddress,
abi: erc721Abi,
functionName: 'balanceOf',
args: [address!],
query: { enabled: isConnected && !!address }
});
if (!isConnected) {
return <WalletConnectPrompt redirectAfter={window.location.pathname} />;
}
if (isLoading) {
return <div className="token-gate-loading">Проверка доступа...</div>;
}
const hasAccess = (balance ?? 0n) >= minBalance;
if (!hasAccess) {
return <>{lockedContent}</>;
}
return <>{children}</>;
}
// Использование
function PremiumSection() {
return (
<TokenGate
contractAddress="0xYourNFTContract"
tokenType="ERC721"
lockedContent={
<div className="token-gate-overlay">
<h3>Только для держателей NFT</h3>
<p>Купите NFT для получения доступа к эксклюзивному контенту</p>
<a href="https://opensea.io/collection/your-nft">Купить на OpenSea</a>
</div>
}
>
<ExclusiveContent />
</TokenGate>
);
}
Blur-gate эффект
// Размытый preview с оверлеем
function BlurGate({ hasAccess, children, contractAddress }) {
return (
<div className="relative">
<div className={hasAccess ? '' : 'blur-sm select-none pointer-events-none'}>
{children}
</div>
{!hasAccess && (
<div className="absolute inset-0 flex items-center justify-center bg-black/30 backdrop-blur-sm">
<div className="bg-white rounded-xl p-8 text-center shadow-xl max-w-sm">
<LockIcon className="w-12 h-12 mx-auto mb-4 text-gray-400" />
<h3 className="text-xl font-bold mb-2">Контент для членов клуба</h3>
<p className="text-gray-600 mb-4">
Получите NFT для доступа к этому разделу
</p>
<BuyNFTButton contractAddress={contractAddress} />
</div>
</div>
)}
</div>
);
}
Мульти-токенный доступ
// Доступ если есть хотя бы один из нескольких токенов
async function checkMultiTokenAccess(walletAddress: string): Promise<{
hasAccess: boolean;
grantedBy?: string;
}> {
const gates = [
{ contract: PREMIUM_NFT, name: 'Premium NFT', type: 'ERC721' as const },
{ contract: GOVERNANCE_TOKEN, name: 'Governance Token', type: 'ERC20' as const, min: 1000n * 10n**18n }
];
for (const gate of gates) {
const has = gate.type === 'ERC721'
? await checkNFTOwnership(walletAddress, gate.contract)
: await checkERC20Balance(walletAddress, gate.contract, gate.min ?? 1n);
if (has) return { hasAccess: true, grantedBy: gate.name };
}
return { hasAccess: false };
}
Сроки
Token-gated страницы с серверной защитой + React компонент + blur-gate — 4–6 дней.







