Разработка системы управления апрувалами токенов (revoke)

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1 услугВсе 1306 услуг
Разработка системы управления апрувалами токенов (revoke)
Средняя
~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

Разработка системы управления одобрениями токенов (revoke)

approve(spender, type(uint256).max) — строка в транзакции, которую большинство пользователей подписывают не глядя, потому что без неё dApp не работает. Результат: сотни контрактов с неограниченным доступом к токенам кошелька. Когда один из них взламывают, атакующий дренирует всё — не только ту транзакцию, для которой выдавалось разрешение. Revoke.cash и Etherscan Token Approvals решают эту проблему для end users, но если нужна кастомная система для конкретного протокола, white-label продукта или корпоративного кошелька — это отдельная разработка.

Технические основы: как работают approvals

ERC-20 allowance

Стандарт ERC-20 определяет allowance(owner, spender) — сколько токенов spender может тратить от имени owner. Устанавливается через approve(spender, amount). Значение type(uint256).max (2^256-1) означает «бесконечно» — большинство протоколов требуют именно это для удобства.

Проблема: allowance не имеет срока истечения. Нет механизма автоматической отмены. Если протокол скомпрометирован через год после вашего approve — allowance всё ещё активна.

ERC-721 и ERC-1155 approvals

Для NFT два типа approvals:

  • approve(operator, tokenId) — разрешение на конкретный токен
  • setApprovalForAll(operator, true) — полный доступ ко всей коллекции

setApprovalForAll используется OpenSea, blur.io и другими маркетплейсами. Это наиболее опасный тип — один взломанный маркетплейс с активным setApprovalForAll = вся коллекция потеряна.

EIP-2612: Permit

permit(owner, spender, value, deadline, v, r, s) — подпись вместо транзакции. Не создаёт постоянного allowance, работает разово с конкретным deadline. Правильно спроектированные dApps используют permit вместо approve.

Но permit имеет нюанс: если DAI, USDC или другой токен поддерживает permit — allowance через permit тоже можно посмотреть через allowance(). Они неотличимы от обычных approve.

Чтение данных об approvals

Через событие Approval

Прямой запрос allowance(owner, spender) требует знать адрес spender. Чтобы получить все активные approvals кошелька — нужно читать события:

import { createPublicClient, http, parseAbi } from 'viem';

const ERC20_ABI = parseAbi([
  'event Approval(address indexed owner, address indexed spender, uint256 value)',
  'function allowance(address owner, address spender) view returns (uint256)',
  'function symbol() view returns (string)',
  'function decimals() view returns (uint8)',
]);

async function getTokenApprovals(ownerAddress: `0x${string}`) {
  const client = createPublicClient({ chain: mainnet, transport: http(RPC_URL) });

  // Получаем все Approval события где owner = наш адрес
  const approvalLogs = await client.getLogs({
    event: ERC20_ABI[0], // Approval event
    args: { owner: ownerAddress },
    fromBlock: 0n,
    toBlock: 'latest'
  });

  // Дедупликация: оставляем только последний Approval для каждой пары token+spender
  const latestApprovals = new Map<string, typeof approvalLogs[0]>();
  for (const log of approvalLogs) {
    const key = `${log.address}-${log.args.spender}`;
    latestApprovals.set(key, log); // более поздние перезаписывают более ранние
  }

  // Проверяем текущий allowance для каждой пары
  const results = await Promise.all(
    Array.from(latestApprovals.values()).map(async (log) => {
      const [allowance, symbol, decimals] = await Promise.all([
        client.readContract({
          address: log.address,
          abi: ERC20_ABI,
          functionName: 'allowance',
          args: [ownerAddress, log.args.spender!]
        }),
        client.readContract({ address: log.address, abi: ERC20_ABI, functionName: 'symbol' }),
        client.readContract({ address: log.address, abi: ERC20_ABI, functionName: 'decimals' }),
      ]);

      return {
        tokenAddress: log.address,
        spenderAddress: log.args.spender!,
        allowance,
        symbol,
        decimals,
        isUnlimited: allowance === BigInt('0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'),
      };
    })
  );

  // Фильтруем нулевые allowances (уже отозванные)
  return results.filter(r => r.allowance > 0n);
}

Проблема с историческими данными

getLogs с fromBlock: 0n — медленный и дорогой запрос для публичных RPC. Решения:

  • The Graph: индексируем Approval события через субграф, GraphQL запросы мгновенные
  • Etherscan/Alchemy API: готовые endpoint для token approvals (alchemy_getTokenAllowances)
  • Incremental indexing: отслеживаем последний проиндексированный блок, при каждом обновлении запрашиваем только новые события

Для production системы The Graph субграф — оптимальное решение. Один запрос возвращает все активные approvals с метаданными.

Revoke операции

ERC-20 revoke

Revoke = approve(spender, 0). Одна транзакция на каждую пару token+spender.

async function revokeERC20Approval(
  tokenAddress: `0x${string}`,
  spenderAddress: `0x${string}`
) {
  const { writeContract } = useWriteContract();
  writeContract({
    address: tokenAddress,
    abi: erc20Abi,
    functionName: 'approve',
    args: [spenderAddress, 0n]
  });
}

Batch revoke через Multicall

Отзыв 10 approvals = 10 транзакций, 10 подписей пользователя. Это неприемлемо. Но! ERC-20 approve нельзя вызвать от имени пользователя без его подписи — нет multicall способа сделать batch approve/revoke за один клик без кастомного контракта или Permit2.

Permit2 batch revoke (если пользователь использует Permit2): Permit2 поддерживает lockdown(TokenSpenderPair[] calldata approvals) — отзывает несколько Permit2 allowances в одном вызове. Но не отзывает прямые ERC-20 approvals.

Practical решение: очередь revoke транзакций с автоматической отправкой следующей после подтверждения предыдущей. UI показывает прогресс Отзываю 3 из 8.... Пользователь подписывает каждую, но не ждёт вручную — pop-up следующей появляется автоматически.

async function batchRevoke(approvals: Approval[]) {
  for (const approval of approvals) {
    await writeContractAsync({
      address: approval.tokenAddress,
      abi: erc20Abi,
      functionName: 'approve',
      args: [approval.spenderAddress, 0n]
    });
    // Ждём подтверждение перед следующей
    await waitForTransaction({ hash: txHash });
  }
}

ERC-721 / ERC-1155 revoke

setApprovalForAll(operator, false) — отзыв полного доступа к коллекции. Более критично, поэтому в UI выделяем красным. approve(operator, tokenId) с последующим revoke менее критичен — доступ к конкретному токену.

UI дизайн системы

Таблица approvals

Основной компонент — таблица с сортировкой и фильтрацией:

Токен Spender Allowance Риск Действие
USDC Uniswap V3 Unlimited Средний Revoke
WETH Old Protocol (deprecated) Unlimited Высокий Revoke
DAI Aave V3 1,000 DAI Низкий Revoke

Risk scoring — важная часть UX. Spender адреса идентифицируются через:

  • Etherscan Labels API
  • DefiLlama протокол базы
  • Собственный whitelist известных протоколов

Verified протокол = средний риск (approve существует, но протокол надёжный). Неизвестный контракт = высокий риск. Deprecated/мёртвый контракт = критический риск.

Filters: по сети, по типу (ERC-20 / NFT), по уровню риска, только unlimited approvals.

Multi-chain

Пользователи имеют approvals на Ethereum, Base, Arbitrum, Polygon и других сетях. Система должна агрегировать данные со всех поддерживаемых chain. Параллельные запросы через Promise.all к RPC каждой сети, результаты объединяются в единый список с сетевой иконкой.

Стек разработки

React + Next.js, wagmi 2.x + viem для on-chain операций, TanStack Query для кэширования и фоновых обновлений, TanStack Table для таблицы approvals, The Graph для индексирования событий (или Alchemy Transfers API). TypeScript.

Для multi-chain: конфиг wagmi с поддерживаемыми цепями, отдельные viem publicClient для каждой сети.

Ориентиры по срокам

ERC-20 revoke система для одной сети с чтением через RPC Events, риск-скоринг по whitelist и batch revoke очередью — 2 дня. С multi-chain поддержкой (5+ сетей), ERC-721/ERC-1155 approvals, The Graph индексером и кастомным risk scoring — 3-5 дней.