Реализация Token Sniping на PancakeSwap через Mempool с Python

Реализация Token Sniping на PancakeSwap через Mempool с Python Python & API
Пошаговое руководство по созданию Python-бота для снайпинга новых токенов на PancakeSwap, отслеживая транзакции в Mempool и отправляя опережающие сделки.
Суть: Реализация алгоритма снайпинга токенов на PancakeSwap через Mempool с помощью Python включает в себя мониторинг Mempool сети BSC для обнаружения транзакций addLiquidityETH, отправляемых на роутер PancakeSwap. При обнаружении такой транзакции бот извлекает адрес нового токена и немедленно формирует, подписывает и отправляет свою собственную транзакцию swapExactETHForTokensSupportingFeeOnTransferTokens с более высокой ценой газа (gas price), чтобы опередить транзакцию добавления ликвидности и купить токен по начальной цене.

Исходный код

Представленный ниже Python-скрипт демонстрирует базовую реализацию алгоритма снайпинга токенов на PancakeSwap. Он подключается к WebSocket RPC для мониторинга Mempool сети Binance Smart Chain (BSC) в реальном времени. Когда обнаруживается транзакция addLiquidityETH, отправленная на роутер PancakeSwap, скрипт пытается декодировать ее, извлечь адрес нового токена и немедленно отправить свою собственную транзакцию на покупку этого токена с использованием BNB.

Важные замечания:

  • Риск amountOutMin: В целях демонстрации amountOutMin установлен в 0. Это означает, что вы готовы принять любое количество токенов, что крайне рискованно в реальных условиях и может привести к значительным потерям из-за проскальзывания (slippage) или MEV-атак. В продакшене необходимо реализовать сложную логику для точного расчета amountOutMin на основе ожидаемой цены и допустимого проскальзывания.
  • Надежность RPC: Для эффективного снайпинга критически важен доступ к надежному и быстрому RPC-узлу, который предоставляет полный и своевременный поток транзакций Mempool. Публичные RPC-узлы часто имеют ограничения по скорости, задержки или не показывают все pending-транзакции. Рекомендуется использовать выделенные узлы или специализированные сервисы.
  • Скорость: Скорость выполнения кода, сетевая задержка и скорость ответа RPC-узла являются ключевыми факторами успеха.
  • Безопасность: Никогда не храните приватные ключи непосредственно в коде. Используйте переменные окружения или другие безопасные методы хранения.

import asyncio
import os
import json
from web3 import Web3
from web3.middleware import geth_poa_middleware
from web3.exceptions import TransactionNotFound
from eth_abi import decode_abi

# --- Configuration ---
# Используйте переменные окружения для чувствительных данных
PRIVATE_KEY = os.getenv("PRIVATE_KEY")
if not PRIVATE_KEY:
    raise ValueError("PRIVATE_KEY environment variable not set.")

# BSC RPC URL (например, от QuickNode, Alchemy, или публичный узел)
# Для мониторинга Mempool рекомендуется выделенный узел или сервис.
# Публичные узлы могут иметь ограничения по скорости или ненадежно показывать pending-транзакции.
BSC_RPC_WSS = os.getenv("BSC_RPC_WSS", "wss://bsc-ws-node.nariox.org:443") # Пример публичного WSS
BSC_RPC_HTTP = os.getenv("BSC_RPC_HTTP", "https://bsc-dataseed.binance.org/") # Пример публичного HTTP

# Адрес PancakeSwap Router V2
ROUTER_ADDRESS = Web3.to_checksum_address("0x10ED43C718714eb63d5aA57B78B54704E256024E")
# Адрес WBNB
WBNB_ADDRESS = Web3.to_checksum_address("0xbb4CdB9eD5B8D4B0AD8e592a0d0Bf17346401d78")

# Ваш адрес кошелька
YOUR_ADDRESS = Web3.to_checksum_address(Web3.eth.account.from_key(PRIVATE_KEY).address)

# Параметры снайпинга
GAS_PRICE_MULTIPLIER = 1.5 # Умножить обнаруженную цену газа на этот фактор
GAS_LIMIT = 3000000 # Максимальный лимит газа для снайперской транзакции
SLIPPAGE = 0.05 # 5% проскальзывания (0.05) - НЕ ИСПОЛЬЗУЕТСЯ ПРИ amountOutMin=0
AMOUNT_BNB_TO_SNIPE = Web3.to_wei(0.01, 'ether') # Количество BNB для использования в снайперской транзакции

# --- ABIs (упрощенные для релевантных функций) ---
# Полный ABI для PancakeSwap Router V2 можно найти на BscScan
ROUTER_ABI = [
    {
        "inputs": [
            {"internalType": "address", "name": "token", "type": "address"},
            {"internalType": "uint256", "name": "amountTokenDesired", "type": "uint256"},
            {"internalType": "uint256", "name": "amountTokenMin", "type": "uint256"},
            {"internalType": "uint256", "name": "amountETHMin", "type": "uint256"},
            {"internalType": "address", "name": "to", "type": "address"},
            {"internalType": "uint256", "name": "deadline", "type": "uint256"},
        ],
        "name": "addLiquidityETH",
        "outputs": [
            {"internalType": "uint256", "name": "amountToken", "type": "uint256"},
            {"internalType": "uint256", "name": "amountETH", "type": "uint256"},
            {"internalType": "uint256", "name": "liquidity", "type": "uint256"},
        ],
        "stateMutability": "payable",
        "type": "function",
    },
    {
        "inputs": [
            {"internalType": "uint256", "name": "amountOutMin", "type": "uint256"},
            {"internalType": "address[]", "name": "path", "type": "address[]"},
            {"internalType": "address", "name": "to", "type": "address"},
            {"internalType": "uint256", "name": "deadline", "type": "uint256"},
        ],
        "name": "swapExactETHForTokensSupportingFeeOnTransferTokens",
        "outputs": [],
        "stateMutability": "payable",
        "type": "function",
    },
]

# --- Web3 Setup ---
w3_http = Web3(Web3.HTTPProvider(BSC_RPC_HTTP))
w3_http.middleware_onion.inject(geth_poa_middleware, layer=0) # Для совместимости с BSC

router_contract = w3_http.eth.contract(address=ROUTER_ADDRESS, abi=ROUTER_ABI)

# Селекторы функций для быстрого сравнения
ADD_LIQUIDITY_ETH_SELECTOR = router_contract.functions.addLiquidityETH.selector.hex()
SWAP_ETH_FOR_TOKENS_SELECTOR = router_contract.functions.swapExactETHForTokensSupportingFeeOnTransferTokens.selector.hex()

async def handle_pending_transaction(tx_hash_hex, w3_ws):
    """Обрабатывает обнаруженную pending-транзакцию."""
    try:
        # Получаем полную информацию о транзакции
        tx = await w3_ws.eth.get_transaction(tx_hash_hex)
        
        # Проверяем, является ли транзакция вызовом роутера PancakeSwap
        if tx and tx['to'] == ROUTER_ADDRESS:
            function_selector = tx['input'][:10]

            # Если это транзакция добавления ликвидности ETH
            if function_selector == ADD_LIQUIDITY_ETH_SELECTOR:
                print(f"Обнаружена транзакция addLiquidityETH: {tx_hash_hex}")
                
                try:
                    # Декодируем входные данные для получения адреса токена
                    # Типы для addLiquidityETH: (address, uint256, uint256, uint256, address, uint256)
                    # Нам нужен первый адрес (token)
                    decoded_data = decode_abi(
                        ['address', 'uint256', 'uint256', 'uint256', 'address', 'uint256'],
                        Web3.to_bytes(hexstr=tx['input'][10:])
                    )
                    token_address = Web3.to_checksum_address(decoded_data[0])
                    
                    print(f"  Токен: {token_address}, Отправитель LP: {tx['from']}")

                    # --- Логика снайпинга ---
                    # amountOutMin: Это критически важный параметр. Установка его в 0
                    # означает, что вы готовы принять любое количество токенов, что
                    # крайне рискованно и может привести к значительным потерям.
                    # В реальном снайпере здесь должна быть сложная логика для
                    # расчета минимального количества токенов, которое вы готовы получить,
                    # исходя из ожидаемой цены и допустимого проскальзывания.
                    # Для демонстрации мы устанавливаем 0, но это НЕ РЕКОМЕНДУЕТСЯ для продакшена.
                    amount_out_min = 0 # КРАЙНЕ РИСКОВАННО В ПРОДАКШЕНЕ!
                    print(f"  amountOutMin установлен в {amount_out_min} (КРАЙНЕ РИСКОВАННО).")

                    # Формируем снайперскую транзакцию
                    nonce = w3_http.eth.get_transaction_count(YOUR_ADDRESS)
                    
                    # Используем цену газа из pending-транзакции LP, умноженную на множитель
                    gas_price = int(tx['gasPrice'] * GAS_PRICE_MULTIPLIER)
                    
                    # Убедимся, что цена газа не слишком низкая (опционально)
                    current_avg_gas_price = w3_http.eth.gas_price
                    if gas_price < current_avg_gas_price:
                        gas_price = int(current_avg_gas_price * GAS_PRICE_MULTIPLIER)

                    sniping_tx = router_contract.functions.swapExactETHForTokensSupportingFeeOnTransferTokens(
                        amountOutMin=amount_out_min,
                        path=[WBNB_ADDRESS, token_address],
                        to=YOUR_ADDRESS,
                        deadline=int(asyncio.time() + 300) # Дедлайн через 5 минут
                    ).build_transaction({
                        'from': YOUR_ADDRESS,
                        'value': AMOUNT_BNB_TO_SNIPE,
                        'gas': GAS_LIMIT,
                        'gasPrice': gas_price,
                        'nonce': nonce,
                    })

                    # Подписываем и отправляем транзакцию
                    signed_tx = w3_http.eth.account.sign_transaction(sniping_tx, private_key=PRIVATE_KEY)
                    tx_hash_snipe = w3_http.eth.send_raw_transaction(signed_tx.rawTransaction)
                    print(f"  Снайперская транзакция отправлена! Хеш: {tx_hash_snipe.hex()}")

                    # Опционально: ожидание подтверждения транзакции
                    # try:
                    #     receipt = w3_http.eth.wait_for_transaction_receipt(tx_hash_snipe, timeout=300)
                    #     if receipt.status == 1:
                    #         print(f"  Снайперская транзакция {tx_hash_snipe.hex()} успешно выполнена!")
                    #     else:
                    #         print(f"  Снайперская транзакция {tx_hash_snipe.hex()} завершилась неудачей!")
                    # except TransactionNotFound:
                    #     print(f"  Снайперская транзакция {tx_hash_snipe.hex()} не найдена после таймаута.")
                    # except Exception as e:
                    #     print(f"  Ошибка при ожидании квитанции снайперской транзакции: {e}")

                except Exception as e:
                    print(f"  Ошибка при декодировании или обработке транзакции addLiquidityETH {tx_hash_hex}: {e}")

    except TransactionNotFound:
        # Транзакция могла быть уже добыта или отброшена до того, как мы успели ее получить
        pass
    except Exception as e:
        print(f"Ошибка при обработке транзакции {tx_hash_hex}: {e}")

async def listen_for_pending_transactions():
    """Подключается к WebSocket и слушает новые pending-транзакции."""
    print("Подключение к WebSocket...")
    w3_ws = Web3(Web3.WebsocketProvider(BSC_RPC_WSS))
    w3_ws.middleware_onion.inject(geth_poa_middleware, layer=0)

    if not w3_ws.is_connected():
        print("Не удалось подключиться к WebSocket RPC. Выход.")
        return

    print("Успешное подключение к WebSocket.")
    
    # Подписываемся на новые pending-транзакции
    # Подписка 'newPendingTransactions' возвращает только хеши транзакций.
    # Затем нам нужно получить полные детали транзакции с помощью eth_getTransaction.
    subscription_id = w3_ws.eth.subscribe('newPendingTransactions')
    print(f"Подписан на новые pending-транзакции. ID подписки: {subscription_id}")

    while True:
        try:
            message = w3_ws.ws.recv()
            response = json.loads(message)
            if 'params' in response and 'result' in response['params']:
                tx_hash_hex = response['params']['result']
                # Запускаем обработку транзакции как отдельную задачу, чтобы не блокировать слушатель
                asyncio.create_task(handle_pending_transaction(tx_hash_hex, w3_ws))
        except asyncio.CancelledError:
            print("Слушатель отменен.")
            break
        except Exception as e:
            print(f"Ошибка в слушателе WebSocket: {e}")
            # Попытка переподключиться или обработать ошибку
            await asyncio.sleep(5) # Ждем перед повторной попыткой

async def main():
    """Основная функция бота."""
    print("Запуск бота для снайпинга Mempool...")
    print(f"Мониторинг PancakeSwap Router V2 ({ROUTER_ADDRESS}) на предмет транзакций addLiquidityETH.")
    print(f"Ваш адрес: {YOUR_ADDRESS}")
    print(f"Количество BNB для снайпинга: {Web3.from_wei(AMOUNT_BNB_TO_SNIPE, 'ether')} BNB")

    await listen_for_pending_transactions()

if __name__ == "__main__":
    # Для Windows, ProactorEventLoop часто лучше для асинхронного ввода/вывода
    if os.name == 'nt':
        asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())
    try:
        asyncio.run(main())
    except KeyboardInterrupt:
        print("Бот остановлен вручную.")
    except Exception as e:
        print(f"Произошла необработанная ошибка: {e}")

Разбор параметров

  • PRIVATE_KEY: Ваш приватный ключ кошелька, с которого будут отправляться транзакции. Критически важно: храните его в переменных окружения или другом безопасном месте, никогда не вставляйте напрямую в код.
  • BSC_RPC_WSS: WebSocket URL для RPC-узла Binance Smart Chain. Используется для подписки на новые pending-транзакции в Mempool. Для продакшена рекомендуется использовать приватный или высокопроизводительный узел.
  • BSC_RPC_HTTP: HTTP URL для RPC-узла Binance Smart Chain. Используется для отправки транзакций и получения информации о состоянии сети (например, nonce, gas price).
  • ROUTER_ADDRESS: Адрес контракта PancakeSwap Router V2 на BSC. Это основной контракт, через который происходят обмены и добавление ликвидности.
  • WBNB_ADDRESS: Адрес обернутого BNB (Wrapped BNB) на BSC. Используется в качестве одной из сторон торговой пары.
  • YOUR_ADDRESS: Ваш публичный адрес кошелька, автоматически вычисляется из приватного ключа.
  • GAS_PRICE_MULTIPLIER: Множитель, применяемый к цене газа обнаруженной транзакции addLiquidityETH. Увеличение этого значения помогает вашей снайперской транзакции быть обработанной быстрее (front-run).
  • GAS_LIMIT: Максимальный лимит газа для вашей снайперской транзакции. Установите достаточно высокое значение, чтобы транзакция не провалилась из-за нехватки газа, но не чрезмерно высокое, чтобы избежать лишних трат.
  • SLIPPAGE: Допустимое проскальзывание в процентах (например, 0.05 для 5%). ВНИМАНИЕ: В текущей реализации с amountOutMin=0 этот параметр не используется для защиты от проскальзывания.
  • AMOUNT_BNB_TO_SNIPE: Количество BNB, которое вы хотите использовать для покупки нового токена.

Как запустить

Для запуска бота выполните следующие шаги:

  1. Установите Python: Убедитесь, что у вас установлен Python 3.8 или выше. Вы можете скачать его с официального сайта python.org.

  2. Установите необходимые библиотеки: Откройте терминал или командную строку и выполните команду:

    
    pip install web3 eth-abi
    
  3. Настройте переменные окружения: Создайте файл .env в корневой директории вашего проекта или установите переменные окружения напрямую в вашей системе. Это критически важно для безопасности вашего приватного ключа. Пример для .env:

    
    PRIVATE_KEY="ВАШ_ПРИВАТНЫЙ_КЛЮЧ_ЗДЕСЬ"
    BSC_RPC_WSS="wss://ваш_wss_rpc_url"
    BSC_RPC_HTTP="https://ваш_http_rpc_url"
    

    Замените заполнители на ваш реальный приватный ключ и URL-ы RPC. Для получения приватного ключа из MetaMask, например, вы можете следовать инструкциям в нашей статье Подпись API-запросов ECDSA для dYdX V4 на Python: Руководство, где объясняется работа с приватными ключами.

  4. Сохраните код: Скопируйте предоставленный выше Python-код и сохраните его в файл, например, sniper_bot.py.

  5. Запустите бота: Откройте терминал в директории, где вы сохранили файл sniper_bot.py, и выполните команду:

    
    python sniper_bot.py
    

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

Предупреждение: Снайпинг токенов — это высокорискованная деятельность, требующая глубокого понимания механики блокчейна, смарт-контрактов и рыночных условий. Этот скрипт является демонстрационным примером и не предназначен для использования в продакшене без значительных доработок и тестирования. Убедитесь, что вы понимаете все риски, включая возможность потери средств из-за проскальзывания, неудачных транзакций или MEV-атак. Также учтите, что комиссии за транзакции могут быть значительными, как обсуждается в нашей статье Расчет комиссий мейкера/тейкера (Fee Tiers) в CCXT Python.

Для изучения других продвинутых торговых стратегий, таких как арбитраж, вы можете ознакомиться с нашей статьей Треугольный арбитраж на OKX: Python скрипт для Triangular Arbitrage.

Оцените статью
FinFluct