Это руководство научит вас, как использовать 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_inETH в 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_inUNI, полученная функцией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».
