Покупка токенов Uniswap с помощью Python

Это руководство научит вас, как использовать Python для создания Ethereum-транзакции, которую нужно отправить на Uniswap Universal Router (UR) для покупки или обмена токенов. Поскольку нет лучшего способа обучения, чем практика на реальном проекте, я проведу вас через простой пример: покупка некоторого количества токенов UNI за 1 ETH.

К концу этого урока вы сможете создать транзакцию для покупки токенов на Uniswap router с помощью Python.

Скачивайте книги ТОЛЬКО на русском языке у нас в телеграм канале: PythonBooksRU

Пререквизиты

Это руководство предназначено для разработчиков, хорошо владеющих языком Python и основами Ethereum (контракт, ERC20, транзакции, газ, …). Знание библиотеки web3.py рекомендуется, но не является обязательным для данного учебника. Знакомство с предыдущими Uniswap роутерами или хотя бы с концепцией автоматического маркет-мейкера поможет, но не является обязательным.

Вам понадобится адрес RPC эндпоинта для запроса блокчейна. Если у вас нет прямого доступа к узлу, вы можете получить его бесплатно у нескольких провайдеров: Infura, QuickNode или, например, Alchemy.

На момент написания статьи библиотеки, которые мы будем использовать, поддерживают Python 3.8-3.11, поэтому убедитесь, что на вашем компьютере установлена одна из этих версий, а также стандартный менеджер пакетов pip.

Библиотеки и инструменты

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

  • Web3.py – основная библиотека для разработчиков на Python, работающих с блокчейнами, совместимыми с Ethereum
  • UR Codec – библиотека с открытым исходным кодом, которая кодирует и декодирует данные, отправляемые на Universal Router.
  • Ganache CLI. Если вы вы не хотите учиться обменивать токены на реальных данных, вы можете использовать либо тестовую сеть, например Sepolia, либо локальный форк, созданный с помощью Ganache, который я использую в этом руководстве. Мы создадим локальный форк Ethereum с заданным номером блока, чтобы вы могли получить точно такие же результаты, как и я.
  • Node.js и npm. Среда выполнения JavaScript, используемая для запуска Ganache, и менеджер пакетов.

Подготовка

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

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

1. Установка Node.js и npm

Перейдите на официальную страницу загрузки и установите Node.js в соответствии с вашей ОС. Установка Node.js должна быть достаточно простой, но вот отличное руководство по установке для пользователей Linux.

На этом этапе вы должны получить что-то похожее на:

$ node -v
v18.16.0

$ npm version
{
  npm: '9.6.2',
  node: '18.16.0',
  ...
}

2. Установка Ganache

После установки Node.js установить Ganache очень просто:

$ npm install ganache --global

3. Запуск Ganache

Мы запустим Ganache и дадим ему команду локально форкнуть Ethereum на блоке с номером “17365005”. Вы можете использовать любой майнинговый блок или самый последний (в этом случае убираем опцию), но использование того же номера блока позволит вам получить те же результаты, что и я.

Вам также потребуется адрес эндпоинта RPC. Если вы используете Infura, то это будет что-то вроде https://mainnet.infura.io/v3/xxx, где xxx – ваш API-ключ.

Мой хранится в переменной окружения $RPC_ENDPOINT, поэтому команда для запуска Ganache будет такой:

ganache --fork.url=$RPC_ENDPOINT --fork.blockNumber=17365005

4. Получение закрытого ключа из Ganache

При запуске Ganache выводит несколько полезных сведений, которые должны начинаться примерно так:

Как видите, он сгенерировал для вас 10 счетов (= 10 пар открытых/закрытых ключей) и пополнил их на 1000 ETH! Но не нужно спешить на Etherscan, чтобы созерцать свое новое богатство! 😂 Эти эфиры находятся только на вашем локальном форке!

Сохраните один из приватных ключей для дальнейшего использования. Я сохраню этот:

private_key = "0x6c9803151aa5cf420e98dd1afb9db96f0510918cb2249e9858519d47777125c9"

Не используйте эти приватные ключи/аккаунты в Mainnet! Иначе каждый может получить доступ к вашему счету!

Кроме того, если вы увидите токены на этих счетах в Mainnet (или на любом другом счете, чей закрытый ключ находится в сети), никогда не пытайтесь их получить: бот-похититель украдет ваш газ!

5. Идентификатор локальной цепочки и RPC эндпоинт

Последняя часть того, что отображает Ganache при запуске, касается идентификатора локальной цепочки и локального rpc-адреса.

Мы будем использовать их в нашем Python-коде.

chain_id = 1337 
rpc_endpoint = "http://127.0.0.1:8545"

3. Установка библиотек Python

Теперь, когда Ganache установлен и работает, давайте займемся тем, чем мы, разработчики Python, любим заниматься: написанием кода!

Виртуальная среда Python

И начнем мы с хорошей практики – использования виртуального окружения Python. Окружение отлично подходит для предотвращения конфликтов версий зависимостей между всеми вашими проектами, а также между ними и вашей ОС, если она использует какой-либо Python.

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

Сначала создайте папку для своего проекта и перейдите в нее. Затем мы создадим новую виртуальную среду, выполнив следующую команду:

$ python3.9 -m venv venv_ur_tutorial

Используя Python 3.9, мы с помощью встроенного модуля venv (можно также использовать virtualenv) создали виртуальную среду с именем venv_ur_tutorial, которую мы можем активировать следующим образом для пользователей Linux или Mac:

$ source venv_ur_tutorial/bin/activate

или для пользователей Windows:

venv_ur_tutorial/Scripts/activate.bat

На этом этапе мы активировали виртуальную среду. Это означает, что любая зависимость, которую вы установите из этого терминала/среды, будет доступна только в ней.

Установка зависимостей

Теперь мы установим последнюю версию pip и библиотеки для работы с блокчейном:

$ pip install -U pip

$ pip install web3 uniswap-universal-router-decoder

Чтобы проверить наличие правильных версий зависимостей, выполните следующую команду:

$ pip freeze

Эта команда выведет все установленные пакеты в виртуальной среде. Просто проверьте что он выводит эти пакеты:

uniswap-universal-router-decoder==0.8.0
web3==6.4.0

Теперь у вас есть все, чтобы перейти к самому интересному: сбору информации, необходимой для совершения транзакции!

Сбор необходимой информации

К концу этого раздела у вас будет вся необходимая информация для создания обменной (swap) транзакции.

1. CoinGecko

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

Беглый взгляд на страницу CoinGecko для токена Uniswap дает нам следующую информацию:

  • Стоимость токена составляет 0,002715 ETH, таким образом, 1 ETH будет равен ~368 UNI.
  • Его адрес в Ethereum – 0x1f9840a85d5af5bf1d1762f925bdaddc4201f984.
  • Ссылка на Etherscan: https://etherscan.io/token/0x1f9840a85d5af5bf1d1762f925bdaddc4201f984
from web3 import Web3

uni_address = Web3.to_checksum_address('0x1f9840a85d5af5bf1d1762f925bdaddc4201f984')

Web3 работает только с адресом контрольной суммы, поэтому мы преобразуем строку в правильный формат благодаря функции Web3.to_checksum_address().

2. Etherscan

Перейдите по ссылке Etherscan.

Это покажет вам количество десятичных знаков токена: 18.

Теперь перейдите на вкладку “Contract”, прокрутите вниз и скопируйте ABI контракта.

uni_abi = '[{"inputs":[{"internalType":"address", ... ,"type":"function"}]'

Мы будем использовать его для запроса контракта UNI. Для удобства чтения я сократил большую часть кода. Если у вас есть сомнения, вы можете посмотреть полный код учебника.

3. Суммы

Используя полученную выше информацию, рассчитаем суммы, которые будут обмениваться.

ETH

В данном проекте мы хотим купить за 1 ETH валюту UNI. Родная монета Ethereum определяется с помощью 18 десятичных знаков. Таким образом, сумма ETH, которую мы хотим заплатить (ввести в своп), составляет:

amount_in = 1 * 10**18

UNI

Мы видели, что текущая стоимость 1 ETH составляет около 368 UNI.

Эта цена собрана из нескольких источников, и, учитывая колебания рынка и возможное проскальзывание (нашел не на локальном форке, а в Mainnet), давайте дадим немного свободы действий для суммы, которую мы готовы получить, чтобы быть уверенными в успешности свопа. Допустим, мы не хотим получить меньше 365 UNI за 1 ETH.

Токен UNI также имеет 18 десятичных знаков. Таким образом, минимальное количество UNI, которое мы готовы получить в результате свопа, составляет:

min_amount_out = 365 * 10**18

Путь

Для автоматического поиска путей более продвинутые разработчики могут использовать библиотеку с открытым исходным кодом Smart Path

Нам необходимо указать протоколу Uniswap, какими токенами обмениваться. Родной монетой является ETH, но обмениваться можно только токенами, поэтому мы будем использовать обернутую версиюWETH.

В качестве упражнения я предлагаю вам найти его адрес:

weth_address = Web3.to_checksum_address('0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2')

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

path = [weth_address, uni_address]

Этот путь предназначен для пула Uniswap V2. Пулы V3 несколько сложнее и не входят за рамки данного руководства.

В V2 путях можно использовать 3 адреса токенов, когда нет пула для обмена 2 токенов, но в данном случае это не так.

4. Универсальный адрес маршрутизатора и ABI

Наконец, адрес UR:

ur_address = Web3.to_checksum_address("0xEf1c6E67703c7BD7107eed8303Fbe6EC2554BF6B")

И ABI контракта UR:

ur_abi = '[{"inputs":[{"components":[{"internalType": ... ,"type":"receive"}]'

Для удобства чтения я сократил большую часть кода. Если у вас есть сомнения, вы можете посмотреть полный код учебника.

Кодирование транзакции для покупки UNI у универсального маршрутизатора Uniswap

К концу этого раздела вы будете знать, как создать и отправить транзакцию на UR с помощью Python для покупки токенов.

1. Создание Python-скрипта

Теперь, когда у нас есть необходимая информация, давайте соберем ее в Python-файл, который назовем buy_token.py. Начнем с импорта библиотек, как показано ниже:

from uniswap_universal_router_decoder import FunctionRecipient, RouterCodec
from web3 import Account, Web3

Здесь:

  • RouterCodec – класс, используемый для кодирования и декодирования данных, передаваемых в UR.
  • FunctionRecipient – это Enum, который будет использоваться для указания функциям UR, кто является получателем. Часто это либо сам маршрутизатор, либо отправитель транзакции.
  • Account – класс, используемый для создания учетной записи и подписания транзакций.
  • Web3 – класс, используемый для взаимодействия с блокчейном.

Затем добавляем константы, которые мы определили в предыдущих разделах:

from uniswap_universal_router_decoder import FunctionRecipient, RouterCodec
from web3 import Account, Web3

private_key = "0x6c9803151aa5cf420e98dd1afb9db96f0510918cb2249e9858519d47777125c9"

chain_id = 1337
rpc_endpoint = "http://127.0.0.1:8545"

uni_address = Web3.to_checksum_address('0x1f9840a85d5af5bf1d1762f925bdaddc4201f984')
uni_abi = '[{"inputs":[{"internalType":"address", ... ,"type":"function"}]'

amount_in = 1 * 10**18
min_amount_out = 365 * 10**18

weth_address = Web3.to_checksum_address('0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2')

path = [weth_address, uni_address]

ur_address = Web3.to_checksum_address("0xEf1c6E67703c7BD7107eed8303Fbe6EC2554BF6B")
ur_abi = '[{"inputs":[{"components":[{"internalType": ... ,"type":"receive"}]'

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

2. Кодирование входных данных транзакции

Входные данные – это та часть транзакции, которая будет выполнена смарт-контрактом UR и приведет к фактическому свопу. Мы будем использовать кодек UR следующим образом.

Сначала мы его инициализируем:

codec = RouterCodec()

Затем мы используем его для кодирования входных данных:

encoded_input = (
        codec
        .encode
        .chain()
        .wrap_eth(FunctionRecipient.ROUTER, amount_in)
        .v2_swap_exact_in(FunctionRecipient.SENDER, amount_in, min_amount_out, path, payer_is_sender=False)
        .build(codec.get_default_deadline())
)

Хорошо, но что это значит?! Что этот код делает?!

3. Некоторые пояснения

Давайте разберем команды:

  • encode сообщает кодеку, что мы хотим закодировать входные данные (в отличие от декодирования).
  • chain(). UR поддерживает несколько команд, объединенных в одну транзакцию. chain() инициализирует цепочку для одной или нескольких команд.
  • wrap_eth() – первая команда. Она запрашивает у маршрутизатора конвертацию amount_in ETH в WETH.
  • FunctionRecipient.ROUTER. Маршрутизатор будет получать WETH, преобразованный функцией wrap_eth().
  • v2_swap_exact_in(). Указание маршрутизатору использовать пул V2 с известным входным количеством (в данном случае 1 WETH).
  • FunctionRecipient.SENDER. Отправитель транзакции получит результат работы функции
  • v2_swap_exact_in() (то есть токены UNI).
  • payer_is_sender=False. amount_in UNI, полученная функцией v2_swap_exact_in(), не будет поступать от отправителя. Это связано с тем, что соответствующая функция UR получает WETH, сгенерированный ранее и хранящийся в маршрутизаторе.
  • min_amount_out. Если в результате свопа будет получено меньше данного количества UNI, транзакция будет отменена.
  • path. Токены, участвующие в обмене.
  • codec.get_default_deadline(). Временная метка, после которой транзакция перестанет быть действительной.
  • build(). Этот метод формирует и кодирует входные данные транзакции.

4. Построение словаря транзакций

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

w3 = Web3(Web3.HTTPProvider(rpc_endpoint))

и кошелек/аккаунт, использующий закрытый ключ:

account = Account.from_key(private_key)

Теперь мы готовы к созданию словаря транзакций:

trx_params = {
        "from": account.address,
        "to": ur_address,
        "gas": 500_000,
        "maxPriorityFeePerGas": w3.eth.max_priority_fee,
        "maxFeePerGas": 100 * 10**9,
        "type": '0x2',
        "chainId": chain_id,
        "value": amount_in,
        "nonce": w3.eth.get_transaction_count(account.address),
        "data": encoded_input,
}

Код здесь практически не требует пояснений, но все же:

  • gas – максимальное количество газа, которое вы готовы заплатить. Все неиспользованное будет возвращено. Здесь 500_000 – совершенно произвольное значение.
  • maxFeePerGas – максимальная цена газа, которую вы готовы заплатить. Здесь 100 Gwei – совершенно произвольно. Примечание: это выходит за рамки данного контекста, но возможно, вам стоит рассмотреть более тонкое вычисление газа / maxPriorityFeePerGas / maxFeePerGas!
  • nonce – счетчик, используемый для различения транзакций, отправленных с одного и того же счета.
  • data содержит encoded_input, полученный ранее.

5. Подписание и отправка транзакции

Подписать и отправить транзакцию очень просто:

raw_transaction = w3.eth.account.sign_transaction(trx_params, account.key).rawTransaction
trx_hash = w3.eth.send_raw_transaction(raw_transaction)

Вот и все! Вы купили токены UNI за 1 ETH!

Бонус: давайте проверим, сколько UNI мы получили

Сначала нам нужно создать Web3-экземпляр контракта UNI из его ABI и адреса:

uni_contract = w3.eth.contract(address=uni_address, abi=uni_abi)

Дальше мы можем получить наш баланс UNI:

uni_balance = 
uni_contract.functions.balanceOf(account.address).call()
print(uni_balance / 10**18)

Мой результат таков:

368.41333521045374

Если вы дошли до этого места, поздравляю!!!

Вы узнали:

  • как настроить среду Ganache
  • какие библиотеки Python использовать для работы с блокчейном
  • как кодировать, собирать и отправлять своп на универсальный маршрутизатор Uniswap

Полный код этого руководства можно найти здесь.

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

Перевод статьи «How to Buy a Token on the Uniswap Universal Router with Python».

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *