setApprovalForAll — функция стандартов ERC-721 и ERC-1155, которая даёт контракту или адресу общее разрешение переводить любой NFT или токен определённой коллекции с вашего адреса — и те, что есть сейчас, и те, что появятся потом. Одна подпись, бессрочно, без ограничения по отдельным токенам. На этом держится схема OpenSea «одобрил один раз — торгуешь сколько угодно», и этим же пользуются наборы-дрейнеры Inferno Drainer и Pink Drainer. По сумме потерь это один из самых дорогих вызовов функции в истории Ethereum.

Зачем нужен setApprovalForAll

NFT-маркетплейсам нужно переводить NFT от имени продавцов, не требуя отдельной транзакции одобрения на каждое выставление. OpenSea, LooksRare, X2Y2, Blur — все они через setApprovalForAll получают для своего контракта право перемещать NFT в момент продажи.

Для честного использования это удобно. Но если одобренный контракт окажется вредоносным, одна подпись оборачивается катастрофой. В отличие от одобрений ERC-20, у которых есть лимит суммы, setApprovalForAll работает по принципу «да или нет»: всё или ничего.

Как работает дрейнер

Поддельная страница минта → пользователь подключает кошелёк → кнопка «Mint» запрашивает setApprovalForAll для самой ценной NFT-коллекции в кошельке → пользователь подписывает не читая → злоумышленник получает право переводить все NFT этой коллекции с этого адреса.

Обычно злоумышленник не спешит. Иногда ждёт, пока пользователь докупит NFT в тот же кошелёк (добыча будет больше). Иногда — какого-нибудь рыночного события, когда внимание пользователя рассеяно. А иногда выводит всё сразу, если в кошельке есть активы, которые вот-вот изменятся, например застейканные NFT накануне разблокировки.

Как setApprovalForAll выглядит в кошельке

В поле функции транзакции написано «setApprovalForAll», параметров два: адрес operator (контракт, который получает разрешение) и логическое значение (true — выдать, false — отозвать). Современные кошельки — Rabby и MetaMask начиная с 2024 года — показывают это понятно и часто предупреждают, если адрес operator незнаком.

Старые прошивки аппаратных кошельков показывают «сырую» транзакцию с данными в шестнадцатеричном виде. Прошивка Ledger Nano S Plus до 2024 года не расшифровывала setApprovalForAll в понятном виде; Trezor Safe 3 и Safe 5 расшифровывают. Если аппаратный кошелёк показывает шестнадцатеричные данные незнакомого вызова, это тревожный сигнал: откажитесь от подписи, обновите прошивку и попробуйте снова.

Как защититься

Три правила, которые работают на практике.

Первое: одобряйте только те маркетплейсы, которыми действительно пользуетесь. Если вы минтите только на OpenSea, не выдавайте одобрение контракту LooksRare. Каждое неиспользуемое одобрение — будущая лазейка.

Второе: после первого же обращения к незнакомому контракту отзывайте разрешение. Если вы один раз подключились к новому dApp, чтобы получить аирдроп, сразу после этого зайдите на revoke.cash и удалите одобрение.

Третье: разделите кошельки на «торговый» и «хранилище». Ценные NFT держите на адресе, который никому и ничего не одобрял. Когда захотите выставить NFT на продажу, переведите его на торговый адрес, выставьте, а непроданное верните обратно. Это лишние действия, зато весь класс катастроф с setApprovalForAll исчезает.

Ежеквартальная проверка

Раз в три месяца заходите на revoke.cash, подключайте по очереди все свои кошельки и отзывайте каждое одобрение, которое не можете опознать или не помните, когда выдали. Эта привычка окупится в тот единственный раз, когда она остановит кражу.

Дополнительное чтение: EIP-2612 Permit, revoke.cash, Фишинг.