Это руководство научит вас, как использовать 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")
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».