Сэкономьте миллионы на оплате газа, проверяя, не будет ли транзакция неудачной, перед ее отправкой.

Несколько месяцев назад я получил вышеуказанное сообщение об ошибке от Metamask при попытке подтвердить транзакцию Ethereum. Metamask только что добавил эту функцию в то время, и я твитнул о том, что это отличное улучшение UX.

По-видимому, я ввел недопустимые входные переменные в своем dApp при тестировании взаимодействия со смарт-контрактом Uniswap. Из любопытства я все равно отправил транзакцию, чтобы посмотреть, не произойдет ли она на самом деле — и да, это произошло.

Но как Metamask узнал, что транзакция провалится? Это был вопрос, который задержался в моей голове на некоторое время, пока я не нашел на него ответ.

Если вы такой же любопытный человек, как и я, следуйте этой статье, и я отвечу на вопрос «как», показав вам, как использовать ethers.js для проверки действительности транзакции перед ее отправкой.

Блокчейн-транзакции

Транзакции блокчейна по умолчанию являются неизменяемыми, а это означает, что невозможно отменить уже подтвержденную транзакцию. Например, если вы отправите ETH на неправильный адрес Ethereum, вы не сможете его восстановить. Если только владелец адреса не будет достаточно любезен, чтобы отправить вам возмещение.

Способ предотвратить это — перепроверить адрес перед подтверждением транзакции или использовать легко читаемые имена ENS. Но как насчет других видов транзакций (например, Minting NFT)?

Согласно Анализу продаж Tubbycat NFT от Buycoins Research, около 776 ETH (~ 2 миллиона долларов США на тот момент) было потрачено на комиссию за неудачную транзакцию. Это лишь малая часть того, сколько теряет вся экосистема из-за неудачных транзакций. Некоторые из этих комиссий за транзакции сжигаются, а другие идут майнерам.

Хотя существуют и другие факторы, такие как атаки с опережением, которые могут привести к сбою ожидающей транзакции, некоторые сбои транзакций можно предотвратить, предварительно проверив ее достоверность.

Статические вызовы

Вместо выполнения изменения состояния транзакции можно попросить узел притвориться, что вызов не изменяет состояние, и вернуть результат.

На самом деле это не меняет никакого состояния, но является бесплатным. В некоторых случаях это можно использовать для определения того, будет ли транзакция неудачной или успешной.

Рассмотрим этот пример:

Алиса хочет заказать гамбургер в ресторане быстрого питания Боба, но ей придется ехать туда, чтобы забрать его. Боб готовит лучшие гамбургеры в округе, и их очень быстро раскупают.

Если Алиса поедет в ресторан после того, как гамбургеры распроданы, она потратит впустую свое время, энергию и бензин. Поэтому ей нужно знать, есть ли еще гамбургеры, прежде чем она отправится в ресторан.

У ресторана Боба есть веб-сайт, на котором он обновляет информацию о продажах, чтобы люди могли видеть, сколько гамбургеров осталось. Если Алиса проверит веб-сайт и увидит, что гамбургеры распроданы, она останется и не будет тратить свои ресурсы на то, чтобы спуститься вниз.

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

Выполнение статического вызова с помощью NodeJS

В этом разделе мы сделаем статический вызов контракта Uniswap V3 и попытаемся перевести позицию ликвидности, которой мы не владеем.

Прежде чем приступить к коду, убедитесь, что у вас установлен ethers. Если нет, установите его с помощью следующей команды:

npm install --save ethers

После успешной установки откройте код контракта Uniswap на Etherscan. Прокрутите вниз до ABI контракта и скопируйте его в буфер обмена.

  • Создайте файл abi.json и вставьте в него ABI.
  • Затем создайте новый файл, который будет использоваться для статического вызова.
  • Импортируйте библиотеку ethers и abi в свой код.
const { ethers, providers } = require('ethers');
require('dotenv').config()
const abi = require('./abi.json')
  • Определите адрес контракта, подписывающую сторону и инициализируйте контракт.
const contractAddress = "0xC36442b4a4522E871399CD717aBDD847Ab11FE88"
const signer = new ethers.Wallet(
   process.env.PRIVATE_KEY,
   providers.getDefaultProvider('mainnet')
);
const contract = new ethers.Contract(contractAddress, abi, signer);

В этом примере используется поставщик по умолчанию из ethers. Однако при создании приложения обязательно зарегистрируйтесь у своего провайдера на Infura, чтобы увеличить скорость/лимит запросов и получить доступ к показателям вызовов.

  • Определите переменные транзакции.
const from = "0x66fe4806cD41BcD308c9d2f6815AEf6b2e38f9a3"
const to = "0xC41672E349C3F6dAdf8e4031b6D2d3d09De276f9"
const tokenId = 100

Адрес from не является ни владельцем, ни утвержденным покупателем токена 100 и, следовательно, не может перевести его на другой адрес. Но давайте попробуем передать его методом callStatic, чтобы увидеть результат.

const transaction = async () => {
   const a = await contract.callStatic.transferFrom(from, to, tokenId)
   console.log(a)
}
transaction()

Когда вы попробуете это, вы должны получить сообщение об ошибке, подобное приведенному ниже.

reason: 'ERC721: transfer caller is not owner nor approved',
  code: 'CALL_EXCEPTION',
  method: 'transferFrom(address,address,uint256)',
  errorArgs: [ 'ERC721: transfer caller is not owner nor approved' ],
  errorName: 'Error',
  errorSignature: 'Error(string)',
  address: '0xC36442b4a4522E871399CD717aBDD847Ab11FE88',
  args: [
    '0x66fe4806cD41BcD308c9d2f6815AEf6b2e38f9a3',
    '0xC41672E349C3F6dAdf8e4031b6D2d3d09De276f9',
    100
  ],
  transaction: {
    data: '0x23b872dd00000000000000000000000066fe4806cd41bcd308c9d2f6815aef6b2e38f9a3000000000000000000000000c41672e349c3f6dadf8e4031b6d2d3d09de276f90000000000000000000000000000000000000000000000000000000000000064',
    to: '0xC36442b4a4522E871399CD717aBDD847Ab11FE88',
    from: '0xC41672E349C3F6dAdf8e4031b6D2d3d09De276f9'
  }
}

Вы можете видеть reason: ‘ERC721: transfer caller is not owner nor approved’ как причину, по которой эта транзакция не будет выполнена. Самое главное, что статический вызов является read-only функцией и не содержит газа.

Интегрируя статический вызов в свое dApp, ботов и т. д., вы сэкономите много денег, которые можно было бы потратить на оплату газа за неудачные транзакции.

Если вам удалось пройти до этого момента, значит, вы успешно отправили статический вызов в блокчейн. Отличная работа!

Вы можете проверить полный код ниже: