July 30, 2022

Быстрые запросы баланса кошелька

Всем привет!

В этой статье я расскажу, как узнать баланс кошелька пользователя, почему перебор не работает, и почему 1inch умнички???

Ссылка на гитхаб с исходным кодом будет в конце статьи

Я буду делать все на js

Если вы пользовались DEX обменником 1inch, то наверняка замечали, что баланс каждого ERC20 токена загружается мгновенно

Почему это важно?

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

Иными словами, если у вас есть список из 100 монет, и вы хотите узнать сколько монет держит пользователь, то необходимо сделать 100 запросов в блокчейн

А если их много?

Рассмотрим 3 метода получить количество токенов

  1. Простой перебор (brute force)
  2. Множественный запрос
  3. Запрос с оптимизацией газа

В дальнейших примерах я буду использовать пакет 1inch multicall

Установка:

npm install @1inch/multicall
yarn install @1inch/multicall

ПРОСТОЙ ПЕРЕБОР

Тут достаточно простая логика

Мы берем массив tokens и методом map пробегаем по массиву контрактов токенов

return provider.ethCall(tokenAddress, callData); - возврашает нам количество токенов в hex формате

тут я вывел баланс монет на кошельке xdefi, weth, ARMOR (хз че за токен)

Далее идем на сайт перевода систем счисления и видим, что все сходится (это все в wei, а в эфире это как раз 0,001025eth)

НО!

Если у нас не три токена, а 1009, как у 1inch, то получается вот что

Поясняю, 2,7 секунд на запрос - это очень много для веб приложения

А если вы используете провайдера Alchemy/Infurra, то получите такое

Ребята из 1inch решили эту проблему, и вот как...

МНОЖЕСТВЕННЫЙ ЗАПРОС

В чем суть решения

Мы не отправляем 1009 транзакций в блокчейн, а разбиваем их на группы (chunks)

const params = {
  chunkSize: 100,
  retriesLimit: 3,
  blockNumber: 'latest',
};

В объекте params мы указываем

  1. Количество запросов в одном чанке
  2. Количество попыток на запрос
  3. В каком блоке ищем ответ

Далее вызываем асинхронный метод класса multiCallService

multiCallService.callByChunks(callDatas, params).then((res)=>{//тут логика})

И радуемся огромному массиву с балансами токенов)

НО!

Если вызов этого метода достигнет лимита по газу (даже у функций чтения имеется газлимит) при вызове контракта, то запрос развернут, и вы не получите ответа

Для справки

Изначально в блоке 15 млн газа

Но если спрос высокий, то количество газа увеличивается до 30 млн газа

Транзакцию также развернут, если она занимает очень много времени

Поэтому есть еще более умный способ это сделать...

ЗАПРОС С ОПТИМИЗАЦИЕЙ ГАЗА

Чтобы не натыкаться на ограничения по газу на запрос, теперь мы будем узнавать у ноды лимиты на газ и разбивать запрос на чанки в соответсвии с лимитам

Отличие тут в том, что если какой-то чанк не поместится, весь запрос не отклонят, а отклонят только один чанк, и мы потом повторно попробуем получить данные

Вот объяснение на картиночках

У нас есть чанк с максимальным размером 6 запросов

Чтобы узнать лимит по газу на запрос мы берем минимум из максимального лимита на газ, который мы установили, и лимита на газ из ноды,

после этого вычитаем буффер (на схемке все видно)

Теперь мы знаем в какое количество газа нам нужно уложиться и добавляем в чанк запросы пока не достигнем лимита (это произошло на 5 и 11 запросе)

Допустим запросы 4, 9, 10, 12 не выполнились

Теперь мы уменьшаем максимальное количество запросов в чанке в 2 раза и еще раз пытаемся выполнить обращения к блокчейну

Ура! Все запросы выполнены, в конце просто их складываем и получаем ответ

PROFIT: Время запроса уменьшилось в 2 раза, и теперь мы не получаем ошибки из-за огромного количества обращений от Alchemy/Infurra

Как и обещал, вот ссылка на гитхаб https://github.com/chpotl/fastErc20Balance

Тут контракт 1inch - https://etherscan.io/address/0x8d035edd8e09c3283463dade67cc0d49d6868063#code

Тут репозиторий 1inch с этим пакетом - https://github.com/1inch/multicall

Надеюсь статья была полезной

Спасибо за прочтение

Мой телеграм - https://t.me/chpotldev