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.
Закажите консультацию по интеграции нашей системы. Свяжитесь с нами, чтобы обсудить ваш проект. Получите демо-доступ к системе и оцените её эффективность.







