Разработка системы управления одобрениями токенов (revoke)
Мы сталкивались с ситуацией: у пользователя сотни неиспользуемых апрувов на токены, один из протоколов взломали — и кошелёк опустел. Без системы управления апрувалами вы рискуете тем, что любой скомпрометированный протокол может дренировать ваши средства. Наша команда разрабатывает кастомные решения для просмотра и отзыва одобрений токенов — от простого интерфейса для одной сети до multi-chain системы с риск-скорингом и batch-отзывом. Такая разработка занимает от 2 до 5 дней в зависимости от сложности, и мы выполняем её под ключ: от анализа данных до деплоя. Свяжитесь с нами для оценки вашего проекта.
Технические основы: как работают 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) }); const approvalLogs = await client.getLogs({ event: ERC20_ABI[0], args: { owner: ownerAddress }, fromBlock: 0n, toBlock: 'latest' }); const latestApprovals = new Map<string, typeof approvalLogs[0]>(); for (const log of approvalLogs) { const key = `${log.address}-${log.args.spender}`; latestApprovals.set(key, log); } 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'), }; }) ); 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 с метаданными.
Почему batch revoke — сложная задача?
Отзыв 10 approvals через ERC-20 требует 10 отдельных транзакций, каждая с подписью пользователя. Это неприемлемо для UX. Multicall не помогает, потому что approve — это операция от имени пользователя, требующая подписи. Решение — очередь транзакций с автоматическим продолжением после подтверждения. Пользователь подписывает каждую, но видит прогресс «Отзываю 3 из 8…». Также можно использовать Permit2 batch revoke, если пользователь перевёл свои апрувы на Permit2 — это снижает газовые затраты до 70%.
Как выполняется отзыв токенов?
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] }); } ERC-721 / ERC-1155 revoke
setApprovalForAll(operator, false) — отзыв полного доступа к коллекции. Более критично, поэтому в UI выделяем красным. approve(operator, tokenId) с последующим revoke менее критичен — доступ к конкретному токену.
UI дизайн системы
Основной компонент — таблица с сортировкой и фильтрацией:
| Токен | 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.
Сравнение подходов к получению данных
| Подход | Скорость | Сложность | Газ для запросов |
|---|---|---|---|
| RPC Events | Медленно | Низкая | Высокий (много вызовов) |
| The Graph | Быстро в 10× | Средняя (нужен субграф) | Нулевой (GraphQL) |
| Alchemy API | Быстро | Низкая (готовый endpoint) | По подписке |
Что входит в нашу работу
- Разработка UI-интерфейса (React + wagmi + TanStack Table) с таблицей approvals
- Интеграция с выбранным источником данных (RPC, The Graph, Alchemy)
- Реализация batch-отзыва через очередь транзакций
- Поддержка мультисетей (Ethereum, Arbitrum, Polygon, Base, BNB Chain)
- Риск-скоринг на основе whitelist и внешних API
- Документация по API и инструкция для пользователей
- Развёртывание на вашем домене или white-label
Компания в цифрах
Более 5 лет опыта в блокчейн-разработке, 30+ реализованных проектов, включая DeFi-протоколы и NFT-маркетплейсы. Наша команда состоит из senior-инженеров с глубоким знанием Solidity, Rust (Solana) и TypeScript.
Ориентировочные сроки
Базовая система для одной сети (ERC-20, RPC Events) — от 2 дней. Multi-chain с NFT и The Graph — от 5 дней. Точную стоимость рассчитываем индивидуально — свяжитесь с нами для консультации.







