Token approval — одна из наиболее опасных операций в Web3. Пользователь даёт контракту право тратить его токены без его участия в будущем. Approval scam — главная причина кражи активов: по данным Revoke.cash, потери превысили $2.8 млрд. Наша система предупреждений детектирует подозрительные approve до подписания транзакции. Она анализирует pending запросы и мониторит выданные approvals, используя симуляцию и базы данных адресов. С опытом более 5 лет в Web3-безопасности мы создаём защиту, которая предотвращает кражи до их совершения.
Почему approval scam — главная угроза для вашего кошелька?
ERC-20 approve: token.approve(spender, amount) разрешает spender переводить до amount токенов. ERC-721/ERC-1155 setApprovalForAll даёт право на все NFT — наиболее опасная форма. Permit (EIP-2612) позволяет gasless подпись approval без on-chain транзакции; злоумышленник может отправить permit() позже, скрытно. Наша система анализирует транзакции за 1-2 секунды, точность детектирования выше 99%.
Какие типы апрувалов требуют особого внимания?
| Тип | Права | Риск | Пример защиты |
|---|---|---|---|
| ERC-20 approve | До amount токенов | Средний | Симуляция + лимит |
| setApprovalForAll | Все NFT коллекции | Высокий | Принудительный ревью |
| Permit2 подпись | Бесконечные через оффчейн | Критический | Мониторинг signature-транзакций |
| Бесконечный approval | MaxUint256 | Высокий | Уведомление о pending апрувале |
Как работает система предупреждений о подозрительных апрувалах?
Система использует два уровня: pre-transaction screening (анализ pending транзакции до подписания) и post-approval monitoring (отслеживание выданных approvals). Риск-скор рассчитывается по алгоритму, учитывающему тип апрувала, данные спендера и возраст контракта. При скорре выше 70 система блокирует подписание.
Анализ pending транзакций — разработка системы предупреждений
Simulation через Tenderly или Alchemy
Перед подписанием симулируем транзакцию и анализируем state changes. Код функции:
interface ApprovalAnalysis { isSuspicious: boolean; riskScore: number; // 0-100 riskFactors: string[]; simulatedStateChanges: StateChange[]; spenderInfo: SpenderInfo; } interface SpenderInfo { address: string; isVerified: boolean; // есть ли в whitelist известных dApp isNewContract: boolean; // контракт < 30 дней hasSourceCode: boolean; // верифицирован на Etherscan blacklisted: boolean; // в списке известных scammers } async function analyzeApproval( txParams: TransactionParams, chainId: number ): Promise<ApprovalAnalysis> { const riskFactors: string[] = []; let riskScore = 0; // 1. Симуляция транзакции const simulation = await simulateTransaction(txParams, chainId); // Извлекаем approve вызовы из simulation const approvals = extractApprovals(simulation.stateChanges); for (const approval of approvals) { // 2. Проверка типа approval if (approval.type === "setApprovalForAll") { riskFactors.push("SET_APPROVAL_FOR_ALL — передаёт права на все NFT коллекции"); riskScore += 40; } if (approval.amount === MaxUint256) { riskFactors.push("UNLIMITED_APPROVAL — безлимитный апрувал токена"); riskScore += 20; } // 3. Анализ spender контракта const spenderInfo = await analyzeSpender(approval.spender, chainId); if (spenderInfo.blacklisted) { riskFactors.push("KNOWN_SCAMMER — адрес в чёрном списке"); riskScore += 60; } if (spenderInfo.isNewContract) { riskFactors.push("NEW_CONTRACT — контракт задеплоен менее 30 дней назад"); riskScore += 25; } if (!spenderInfo.hasSourceCode) { riskFactors.push("UNVERIFIED_CONTRACT — исходный код не верифицирован"); riskScore += 15; } if (!spenderInfo.isVerified && !spenderInfo.hasSourceCode) { riskScore += 20; // дополнительный штраф за полную непрозрачность } } return { isSuspicious: riskScore >= 50, riskScore: Math.min(100, riskScore), riskFactors, simulatedStateChanges: simulation.stateChanges, spenderInfo: approvals[0]?.spender ? await analyzeSpender(approvals[0].spender, chainId) : null }; } База данных известных spender адресов
// Whitelist известных dApp контрактов const KNOWN_SAFE_SPENDERS: Record<number, Set<string>> = { 1: new Set([ // Ethereum mainnet "0x000000000022d473030f116ddee9f6b43ac78ba3", // Permit2 (Uniswap) "0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45", // Uniswap Universal Router "0x7a250d5630b4cf539739df2c5dacb4c659f2488d", // Uniswap V2 Router "0xe592427a0aece92de3edee1f18e0157c05861564", // Uniswap V3 Router "0x1111111254eeb25477b68fb85ed929f73a960582", // 1inch V5 "0x00000000219ab540356cbb839cbe05303d7705fa", // ETH2 Deposit Contract ]), // Arbitrum, Base, Polygon... }; // Blacklist известных scam адресов // Источники: Forta, Chainabuse, Revoke.cash, MobyMask const KNOWN_SCAM_SPENDERS: Record<number, Set<string>> = { 1: new Set([ // Обновляется из Forta feeds и community reports ]) }; async function analyzeSpender( spenderAddress: string, chainId: number ): Promise<SpenderInfo> { const normalizedAddress = spenderAddress.toLowerCase(); // Проверка whitelist const isVerified = KNOWN_SAFE_SPENDERS[chainId]?.has(normalizedAddress) ?? false; // Проверка blacklist const blacklisted = KNOWN_SCAM_SPENDERS[chainId]?.has(normalizedAddress) ?? false; // Проверка возраста контракта const deployBlock = await getContractDeployBlock(spenderAddress, chainId); const currentBlock = await getCurrentBlock(chainId); const blockAge = currentBlock - deployBlock; const isNewContract = blockAge < 200_000; // ~30 дней на Ethereum // Проверка верифицированности на Etherscan const hasSourceCode = await checkEtherscanVerification(spenderAddress, chainId); return { address: spenderAddress, isVerified, blacklisted, isNewContract, hasSourceCode }; } Мониторинг выданных approvals
Indexer одобренных апрувалов
interface ApprovalRecord { owner: string; spender: string; tokenAddress: string; tokenType: "ERC20" | "ERC721" | "ERC1155"; amount: bigint | "unlimited" | "all"; // для ERC-20 / setApprovalForAll blockNumber: number; txHash: string; timestamp: number; revoked: boolean; } // Слушаем Approval и ApprovalForAll события async function indexApprovals( provider: ethers.Provider, userAddress: string ): Promise<ApprovalRecord[]> { // ERC-20 Approval(owner, spender, value) const erc20ApprovalFilter = { topics: [ ethers.id("Approval(address,address,uint256)"), ethers.zeroPadValue(userAddress, 32) // owner = userAddress ] }; // ERC-721/1155 ApprovalForAll(owner, operator, approved) const approvalForAllFilter = { topics: [ ethers.id("ApprovalForAll(address,address,bool)"), ethers.zeroPadValue(userAddress, 32) ] }; const [erc20Logs, nftLogs] = await Promise.all([ provider.getLogs({ ...erc20ApprovalFilter, fromBlock: 0, toBlock: "latest" }), provider.getLogs({ ...approvalForAllFilter, fromBlock: 0, toBlock: "latest" }) ]); return parseApprovalLogs([...erc20Logs, ...nftLogs]); } Алерт на использование approval
Отметим: когда выданный approval используется, необходимо отличать легитимные операции от аномальных. Алгоритм срабатывает, если перевод инициировал сам спендер (а не владелец).
async function monitorApprovalUsage( approval: ApprovalRecord, provider: ethers.Provider ): Promise<void> { const transferFilter = { address: approval.tokenAddress, topics: [ ethers.id("Transfer(address,address,uint256)"), ethers.zeroPadValue(approval.owner, 32) // from = owner ] }; // Подписываемся на Transfer события от owner через spender provider.on(transferFilter, async (log) => { const tx = await provider.getTransaction(log.transactionHash); // Транзакцию отправил spender (не owner) — это использование approval if (tx.from.toLowerCase() === approval.spender.toLowerCase()) { const transferAmount = BigInt(log.data); await sendAlert({ type: "APPROVAL_USED", severity: "HIGH", owner: approval.owner, spender: approval.spender, amount: transferAmount, txHash: log.transactionHash, message: `Ваш апрувал используется! Контракт ${approval.spender} ` + `переводит ${formatAmount(transferAmount)} токенов с вашего адреса.` }); } }); } Permit2 специфика
Uniswap Permit2 требует одного approve(permit2, unlimited) для контракта Permit2. Другие протоколы используют его с signature-based разрешениями. Система распознаёт этот паттерн:
const PERMIT2_TRANSFER_FROM_SELECTOR = "0x36c78516"; function isPermit2Transfer(calldata: string): boolean { return calldata.startsWith(PERMIT2_TRANSFER_FROM_SELECTOR); } function decodePermit2Transfer(calldata: string): { token: string; from: string; to: string; amount: bigint; } { const iface = new ethers.Interface(PERMIT2_ABI); const decoded = iface.decodeFunctionData("transferFrom", calldata); return { token: decoded.token, from: decoded.from, to: decoded.to, amount: decoded.amount }; } Интеграция в кошелёк или dApp
Система встраивается через wallet_watchAsset или browser extension. MetaMask Snaps даёт доступ к onTransaction хуку.
| Канал | Применение | Задержка |
|---|---|---|
| Wallet provider hook | Pre-sign screening в MetaMask Snaps | < 1 сек |
| Browser extension | Скрининг всех dApp транзакций | < 2 сек |
| dApp UI интеграция | SDK на стороне dApp | < 1 сек |
| Email/Telegram алерт | Post-approval monitoring | Real-time |
Пример: пользователь заходит на фишинговый сайт, который запрашивает approve для контракта, задеплоенного 2 часа назад, с неверифицированным кодом и суммой MaxUint256. Система оценивает риск в 95 баллов и блокирует подписание.
Как интеграция защищает от Permit2 атак?
Permit2 позволяет использовать одну подпись для неограниченного количества переводов. Злоумышленник может получить подпись через фишинг и отправить permit() позже. Система детектирует такие подписи на pre-screening и отслеживает их использование.
Как интегрировать систему: пошаговая инструкция
- Установите MetaMask Snap или browser extension.
- Подключите simulation API (Tenderly/Alchemy) — скрининг будет работать мгновенно.
- Настройте whitelist/blacklist — внесите известные dApp и scam-адреса.
- Запустите мониторинг выданных approvals через indexer событий.
- Настройте каналы оповещения (email, Telegram, webhook).
Чек-лист безопасного approve
- Всегда проверяйте адрес spender на Etherscan.\n- Ограничивайте сумму апрува, если это возможно.\n- Используйте нашу систему для автоматического пре-скрининга.Экономия от внедрения: предотвращение одной атаки может сохранить от $10,000 до $500,000 ваших активов.
Что входит в разработку?
- Анализ требований и threat modeling.
- Разработка смарт-контрактов и сценариев мониторинга.
- Интеграция с кошельками через Snaps и API.
- Развёртывание infrastructure (Tenderly, Alchemy).
- Документация и обучение пользователей.
- Техническая поддержка в течение 3 месяцев.
Сроки: от 4 до 8 недель. Стоимость рассчитывается индивидуально. Наш опыт в Web3 (30+ проектов, 10+ интеграций с кошельками) гарантирует надёжную защиту от approval scam.
Закажите консультацию по интеграции нашей системы. Свяжитесь с нами, чтобы обсудить ваш проект. Получите демо-доступ к системе и оцените её эффективность.







