Разработка системы token-gated доступа

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1 услугВсе 1306 услуг
Разработка системы token-gated доступа
Средняя
~2-3 рабочих дня
Часто задаваемые вопросы
Направления блокчейн-разработки
Этапы блокчейн-разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1258
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1170
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    873
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1092
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    563
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    830

Разработка системы token-gated доступа

Token-gating — ограничение доступа к контенту или функциям на основе владения определёнными токенами. Технически задача решается за 20 минут с помощью готовых решений типа Lit Protocol или Guild.xyz. Но в 9 из 10 случаев нужна кастомная реализация: нестандартные условия, интеграция с существующей auth системой, мультичейн проверки, performance требования.

Архитектурные варианты

Client-side (плохо) — проверяем баланс прямо в браузере и показываем/скрываем контент. Контент уже в DOM, любой может его увидеть через DevTools. Годится только для soft gates (nice-to-have, не критичный контент).

Server-side (правильно) — сервер проверяет владение токеном перед выдачей контента. Клиент получает только то, к чему у него есть доступ.

JWT с blockchain claims (оптимально) — при логине через SIWE проверяем токены, вписываем права в JWT. Последующие запросы не требуют обращения к ноде — только JWT верификация.

SIWE + token check при логине

// После успешной SIWE верификации
async function createSessionToken(address: string): Promise<string> {
  const [nftBalance, tokenBalance, ensName] = await Promise.all([
    checkNFTOwnership(address),
    checkTokenBalance(address),
    resolveENS(address),
  ]);
  
  const roles: string[] = [];
  if (nftBalance > 0) roles.push("nft_holder");
  if (tokenBalance >= parseEther("100")) roles.push("token_holder");
  if (ensName) roles.push("ens_user");
  
  return jwt.sign(
    {
      sub: address,
      roles,
      // Expires в 24 часа - баланс мог измениться
      exp: Math.floor(Date.now() / 1000) + 86400,
    },
    process.env.JWT_SECRET!
  );
}

Важно: expiry JWT. Токены продаются и покупаются. Сессия на неделю означает, что продавший NFT продолжает иметь доступ 7 дней. Для высоких stakes — короткий expiry (1-4 часа) + refresh через re-verification.

On-chain проверки через viem/wagmi

import { createPublicClient, http, erc721Abi, erc20Abi } from "viem";
import { mainnet } from "viem/chains";

const client = createPublicClient({
  chain: mainnet,
  transport: http(process.env.ETH_RPC),
});

// Проверка ERC-721 владения
async function checkNFTOwnership(
  address: `0x${string}`,
  collection: `0x${string}`
): Promise<number> {
  const balance = await client.readContract({
    address: collection,
    abi: erc721Abi,
    functionName: "balanceOf",
    args: [address],
  });
  return Number(balance);
}

// Проверка конкретного tokenId
async function checkSpecificToken(
  address: `0x${string}`,
  collection: `0x${string}`,
  tokenId: bigint
): Promise<boolean> {
  const owner = await client.readContract({
    address: collection,
    abi: erc721Abi,
    functionName: "ownerOf",
    args: [tokenId],
  });
  return owner.toLowerCase() === address.toLowerCase();
}

Мультичейн gates

Пользователь может держать NFT на Ethereum, а токены на Polygon. Проверяем параллельно:

const [ethBalance, polyBalance, arbBalance] = await Promise.all([
  checkBalanceOnChain(address, "ethereum"),
  checkBalanceOnChain(address, "polygon"),
  checkBalanceOnChain(address, "arbitrum"),
]);

const hasAccess = ethBalance > 0 || polyBalance > 0 || arbBalance > 0;

Кешируем результаты в Redis с TTL 5-15 минут — постоянные вызовы к нодам дороги и медленны.

Сложные условия доступа

Real-world token gates редко бывают простыми. Типичные случаи:

// AND условие: нужен И NFT, И достаточный баланс токена
const hasAccess = nftBalance > 0 && tokenBalance >= minTokenThreshold;

// OR: достаточно любого из условий  
const hasPremium = nftBalance > 0 || tokenBalance >= premiumThreshold || hasStaked;

// Trait-based: конкретные атрибуты NFT
// Требует off-chain metadata или on-chain traits
const isGoldMember = await checkTraitOwnership(address, "tier", "gold");

// Snapshot-based: баланс на конкретный блок (для airdrop eligibility)
const snapshotBalance = await client.readContract({
  ...tokenContract,
  functionName: "balanceOfAt",
  args: [address, snapshotBlockNumber],
  blockNumber: snapshotBlockNumber,
});

Middleware для API routes

// Express/Fastify middleware
async function tokenGateMiddleware(req, res, next) {
  const token = req.headers.authorization?.replace("Bearer ", "");
  
  if (!token) return res.status(401).json({ error: "Unauthorized" });
  
  try {
    const payload = jwt.verify(token, process.env.JWT_SECRET) as JWTPayload;
    
    // Проверяем роли из JWT (без обращения к блокчейну)
    if (!payload.roles.includes("nft_holder")) {
      return res.status(403).json({ error: "NFT required" });
    }
    
    req.user = payload;
    next();
  } catch {
    return res.status(401).json({ error: "Invalid token" });
  }
}

// Применяем к защищённым роутам
app.get("/api/premium/content", tokenGateMiddleware, getPremiumContent);

Frontend: UX для отказа в доступе

Хуже нет когда пользователь не понимает почему ему отказали и что нужно сделать:

function GatedContent({ requiredNFT, children }) {
  const { address } = useAccount();
  const { data: balance } = useReadContract({
    address: requiredNFT,
    abi: erc721Abi,
    functionName: "balanceOf",
    args: [address],
    query: { enabled: !!address },
  });
  
  if (!address) {
    return <ConnectWalletPrompt />;
  }
  
  if (!balance || balance === 0n) {
    return (
      <AccessDenied
        message="Для доступа необходим NFT коллекции XYZ"
        ctaText="Купить на OpenSea"
        ctaUrl={`https://opensea.io/collection/xyz`}
        currentFloorPrice={useFloorPrice(requiredNFT)}
      />
    );
  }
  
  return children;
}

Делегирование через delegate.cash

Пользователь не хочет подключать cold wallet с ценными NFT к сайту. delegate.cash (EIP-5639) позволяет владельцу cold wallet делегировать права на hot wallet:

// Проверяем не только прямое владение, но и делегирование
const { data: isDelegated } = useReadContract({
  address: DELEGATE_REGISTRY,
  abi: delegateRegistryAbi,
  functionName: "checkDelegateForContract",
  args: [hotWallet, coldWallet, nftCollection],
});

const hasAccess = directBalance > 0 || isDelegated;

Для production gates с ценным контентом — обязательно реализуем поддержку delegate.cash.