3
Подписаться
1444
Подписчики

Как реализовать размещение заказов на децентрализованных биржах — на примере Curve

Создано: 2025-03-13 14:28:33, Обновлено: 2025-03-14 17:27:53
comments   1
hits   933

Почему мне следует сделать заказ?

На рынке криптовалют торговые возможности часто мимолетны, особенно возможности арбитража, которые могут существовать всего несколько минут. Если вы полагаетесь на ручные операции, пользователи могут не успеть воспользоваться лучшей возможностью или упустить лучшую цену. Например, некоторое время назад из-за хакерской атаки на Bybit произошел разрыв связи с USDe, что привело к значительным колебаниям его цены. На тот момент годовая норма прибыли от покупки и погашения sUSDe превысила 300%. Это окно высокой доходности обычно длится очень короткое время, и при ручной торговле сложно успевать за ритмом рынка, не выставляя ордера заранее. Таким образом, функция размещения заказов стала важным инструментом для пользователей децентрализованных бирж (DEX), значительно повышая эффективность транзакций.

Принципы и недостатки ордеров DEX

Несколько DEX предлагают функционал лимитных ордеров, каждый из которых имеет свою реализацию и структуру комиссий. Возьмем в качестве примера Cow Swap и Odos. Основной принцип заключается в использовании функции агрегатора для мониторинга ликвидности и цен нескольких DEX в режиме реального времени. Когда рыночная цена соответствует предельным условиям, тейкер активирует ордер, а смарт-контракт автоматически выполнит транзакцию и выплатит комиссию за газ. Cow также хранит заказы пользователей вне блокчейна, которые затем сопоставляются на конкурентной основе группой децентрализованных узлов, называемых «решателями», но в конечном итоге выполняются в блокчейне. Короче говоря, хотя GAS освобожден от уплаты, дополнительная прибыль, полученная в результате транзакций, совершенных для вас этими DEX, может покрыть сборы за GAS.

Но это поднимает проблему: если один из ваших ордеров ожидает исполнения по цене 90U, а агрегатор отслеживает, что цена определенного DEX упала до 80U, он выполнит транзакцию для вас. Ваш ордер в конечном итоге исполняется по цене 90U, генерируя прибыль в размере 10U. Как распределяется эта прибыль? В реальной работе разные платформы справляются с этим по-разному. Если взять в качестве примера Cow Swap, то его механизм четко предусматривает, что когда цена исполнения лучше предельной цены, сгенерированный излишек (то есть 10U) будет разделен между платформой и пользователем, при этом Cow Swap заберет себе 50%, а пользователь получит оставшиеся 50%. Одос внесет все излишки в казну. По сути, ваш заказ подвергается безрисковому арбитражу на бирже DEX.

Кроме того, в целях экономии комиссий за транзакции ордера DEX представляют собой агрегированные транзакции, то есть множество транзакций время от времени объединяются вместе, а у ETH блок длится 12 секунд, что приведет к упущению некоторых потенциальных возможностей. Конечно, DEX по-прежнему имеет множество преимуществ, таких как поиск большего количества путей, офлайн-сопоставление, экономия GAS и т. д., которых достаточно для большинства пользователей.

Преимущества размещения заказов с помощью собственной программы

По сравнению с использованием агрегированных ордеров DEX, торговля напрямую через смарт-контракты имеет уникальные преимущества. Во-первых, пользователи имеют полный контроль над логикой исполнения ордеров и всеми доходами. Во-вторых, размещение собственных ордеров позволяет избежать задержки упаковки агрегированных транзакций и быстрее реагировать на изменения рынка, особенно используя возможности в течение 12 секунд в периоды высокой волатильности. Кроме того, пользовательские отложенные ордера могут гибко задавать сложные условия (например, торговлю портфелем из нескольких активов или стоп-профит и стоп-лосс), не ограничиваясь предустановленными функциями платформы. Однако для этого требуются определенные навыки программирования, и вам придется самостоятельно платить за газ, а также могут возникнуть риски безопасности в цепочке. Поэтому размещение собственных заказов подходит для продвинутых пользователей, которые обладают высокими техническими возможностями и стремятся к максимальной прибыли.

Защита закрытых ключей

Если вы хотите управлять смарт-контрактами с помощью собственных программ, безопасность ваших закрытых ключей, несомненно, является вашей самой большой заботой. Решение, которое я придумал, заключается в использовании Python для шифрования моего собственного закрытого ключа в автономном режиме и сохранении зашифрованного шифротекста на сервере, на котором запущен хост. Расшифрованный пароль передается с использованием параметров робота платформы FMZ, а программа запускается на хосте, считывает и расшифровывает его (его можно удалить после запуска программы). Таким образом, даже если ваш сервер взломают, проблем не возникнет, но вам все равно нужно быть осторожным, чтобы не допустить утечки вашего аккаунта FMZ. Поскольку речь идет только об одном открытом тексте, находящемся вне сети, безопасность приемлема. Ниже приведен конкретный код, который можно запустить локально, без подключения к сети.

from web3 import Web3  # Web3.py 是与以太坊区块链交互的Python库
import json  # 用于处理JSON数据
import time  # 用于设置时间间隔
import requests  # 用于发送HTTP请求
from cryptography.fernet import Fernet
import base64
import hashlib
from datetime import datetime
from hexbytes import HexBytes

def generate_key(password: str) -> bytes:
    """通过用户密码生成 AES 密钥"""
    return base64.urlsafe_b64encode(hashlib.sha256(password.encode()).digest())

def encrypt_private_key(private_key: str, password: str) -> str:
    """使用密码加密私钥"""
    key = generate_key(password)
    cipher = Fernet(key)
    return cipher.encrypt(private_key.encode()).decode()

def decrypt_private_key(encrypted_key: str, password: str) -> str:
    """使用密码解密私钥"""
    key = generate_key(password)
    cipher = Fernet(key)
    return cipher.decrypt(encrypted_key.encode()).decode()

def save_key_to_file(key, file_path):
    # 将加密密钥保存到txt文件
    with open(file_path, 'w') as file:
        file.write(key) 

def load_key_from_file(file_path):
    # 从txt文件读取加密密钥
    with open(file_path, 'r') as file:
        return str(file.read())

def main():
    encrypt_key = encrypt_private_key('my_private_key', 'password') # my_private_key是自己的私钥,password是自己设置的密码
    save_key_to_file(encrypt_key,'encrypt_key.txt')
    print("加密后的私钥", encrypt_key)
    decrypt_key = decrypt_private_key(load_key_from_file('encrypt_key.txt'), 'password')
        print("解密的私钥", decrypt_key)

Подготовка перед привязкой ETH

Web3.py — мощная библиотека Python для взаимодействия с сетью Ethereum. Установите его с помощью следующей команды: pip install web3. Его функция — упростить взаимодействие между разработчиками и узлами Ethereum, а также поддерживать такие операции, как запрос балансов, вызов смарт-контрактов и отправка транзакций. Например, проверка баланса счета занимает всего несколько строк кода, что делает Web3.py идеальным инструментом для создания DApps или автоматизации транзакций.

python
from web3 import Web3
w3 = Web3(Web3.HTTPProvider('你的RPC地址'))
balance = w3.eth.get_balance('0x某个地址')
print(w3.from_wei(balance, 'ether'))

RPC (удаленный вызов процедур) — это интерфейс связи, предоставляемый узлом Ethereum, который взаимодействует с блокчейном, отправляя запросы JSON по протоколу HTTP или WebSocket. Например, eth_blockNumber может запросить последнюю высоту блока. Из-за высокой стоимости эксплуатации локального узла разработчики обычно полагаются на сторонних поставщиков RPC. Распространенные варианты включают в себя:

  • Infura: стандартный сервис MetaMask, простой в использовании, но с низкой бесплатной квотой (100 000 запросов в день).
  • Alchemy: превосходная производительность, большая бесплатная квота и поддержка большего количества функций.
  • QuickNode: подходит для случаев, когда требуются высокие показатели производительности, но ориентирован на платных пользователей.
from web3 import Web3
w3 = Web3(Web3.HTTPProvider('https://eth-mainnet.g.alchemy.com/v2/你的API密钥'))
print(w3.is_connected())

Рекомендуется использовать Alchemy. По сравнению с Infura от MetaMask, Alchemy предоставляет более высокую бесплатную квоту. После регистрации вы можете получить адрес RPC, например https://eth-mainnet.g.alchemy.com/v2/your API key, и настроить его на Web3.py для использования.

Адрес контракта Curve и ABI

Взяв в качестве примера пул Crve sDAI/sUSDe https://curve.fi/dex/ethereum/pools/factory-stable-ng-102/swap/, вы можете легко найти адреса двух токенов и адрес пула. Как реализовать размещение заказов на децентрализованных биржах — на примере Curve

ABI определяет, как взаимодействовать с контрактом, поэтому его также необходимо получить. Просмотреть контракт можно на ethscan https://etherscan.io/address/0x167478921b907422f8e88b43c4af2b8bea278d3a#code. Вы можете увидеть abi на странице контракта и скопировать его напрямую.

Ссылка на контракт, чтобы узнать цену

Сначала привяжите кошелек ETH. Если ваш адрес кошелька распечатался, значит, все прошло успешно.

def main():
    # 文件路径
    file_path = 'encrypted_key.txt'

    # 从文件中读取加密的私钥
    encrypted_private_key = load_key_from_file(file_path)
    private_key = decrypt_private_key(encrypted_private_key, Password) #Password为密码,定义为策略的参数
    web3 = Web3(Web3.HTTPProvider(HTTPProvider)) # HTTPProvider为RPC的链接,定义为参数
    account = web3.eth.account.from_key(private_key)
    address = account.address  # 获取账户的公开地址
    Log('链接账户', address)

Ниже представлен процесс получения цены. Наконец, без учета платы GAS, текущие инвестиции в размере 100 000 SDAI принесут прибыль в размере 335U через одну неделю. Это может показаться немного сложным, но на самом деле понять это несложно.

    # --------------- 合约设置 ---------------
    # Curve.fi sDAI/sUSDe 池合约地址和ABI
    pool_address = '0x167478921b907422F8E88B43C4Af2B8BEa278d3A'  # Curve池子的合约地址
    # 以下是简化的ABI,仅包含我们需要的函数
    pool_abi = json.loads('''[{"stateMutability":"view","type":"function","name":"get_dy","inputs":[{"name":"i","type":"int128"},{"name":"j","type":"int128"},{"name":"dx","type":"uint256"}],"outputs":[{"name":"","type":"uint256"}]}]''')
    # 创建池子合约对象
    pool_contract = web3.eth.contract(address=pool_address, abi=pool_abi)

    # ERC20 代币标准合约ABI
    erc20_abi = json.loads('''[
        {"constant":true,"inputs":[{"name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"balance","type":"uint256"}],"type":"function"},
        {"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"success","type":"bool"}],"type":"function"},
        {"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"}
    ]''')

    sdai_address = '0x83F20F44975D03b1b09e64809B757c47f942BEeA'  # sDAI代币的合约地址
    susde_address = '0x9D39A5DE30e57443BfF2A8307A4256c8797A3497'  # sUSDE代币的合约地址
    # 池子中代币的索引
    SDAI_INDEX = 0  # sDAI代币在池子中的索引
    SUSDE_INDEX = 1  # sUSDE代币在池子中的索引
    # 创建代币合约对象
    sdai_contract = web3.eth.contract(address=sdai_address, abi=erc20_abi)
    susde_contract = web3.eth.contract(address=susde_address, abi=erc20_abi)
    SUSDE_PRICE = 1.1623 #这个价格是ethena官网价格,1周后可赎回
    SDAI_PRICE = 1.15 #sDAI是收益代币,价值会累计,目前价格1.15

    try:
        SDAI_DECIMALS = sdai_contract.functions.decimals().call()
        SUSDE_DECIMALS = susde_contract.functions.decimals().call()
    except:
        # 如果无法获取,假设为标准的18位精度
        SDAI_DECIMALS = 18
        SUSDE_DECIMALS = 18
    amount_in = 100000
    amount_out = pool_contract.functions.get_dy(
                    SDAI_INDEX,  # 输入代币索引
                    SUSDE_INDEX,  # 输出代币索引
                    int(amount_in *  10**SDAI_DECIMALS)   # 输入数量(wei)
                ).call()
    profit =  SUSDE_PRICE * amount_out / 10**SUSDE_DECIMALS -  amount_in * SDAI_PRICE
    Log(round(profit, 2), round(amount_out / 10**SUSDE_DECIMALS, 2))

Полная программа

Наконец, мы используем опрос для постоянного получения цен и размещаем заказ, когда ожидаемая прибыль достигнута. Обратите внимание на этот кодЭто всего лишь пример кода, не используйте его напрямую.Читатели могут столкнуться с различными проблемами на практике, но ИИ в настоящее время очень мощный и может в основном отвечать на различные вопросы. Он также может напрямую помогать писать код. Редактор кода FMZ также интегрирует ChatGPT, который можно использовать чаще.

def execute_trade(amount_in, min_amount_out, direction):
    gas_price = web3.eth.gas_price
    index_in = SUSDE_INDEX
    index_out = SDAI_INDEX
    if direction == 'buy':
        index_in = SDAI_INDEX
        index_out = SUSDE_INDEX
    # 第二步:执行代币交换交易
    swap_tx = pool_contract.functions.exchange(
        index_in,  # 输入代币索引
        index_out,  # 输出代币索引
        int(amount_in*10**SDAI_DECIMALS),  # 输入数量
        int(min_amount_out*10**SUSDE_DECIMALS)  # 最小输出数量(考虑滑点)
    ).build_transaction({
        'from': address,  # 交易发送方
        'gas': 600000,  # gas限制
        'gasPrice': int(2*gas_price) ,
        'nonce': web3.eth.get_transaction_count(address),  # 获取新的nonce值
    })
    
    # 签名并发送交换交易
    signed_swap_tx = web3.eth.account.sign_transaction(swap_tx, private_key)
    swap_tx_hash = web3.eth.send_raw_transaction(signed_swap_tx.rawTransaction)
    
    Log(f"交换交易已提交,交易哈希: {swap_tx_hash.hex()}")

def get_buy_profit(amount_in):
    amount_out = pool_contract.functions.get_dy(
                    SDAI_INDEX,  # 输入代币索引
                    SUSDE_INDEX,  # 输出代币索引
                    int(amount_in *  10**SDAI_DECIMALS)   # 输入数量(wei)
                ).call()
    return  SUSDE_PRICE * amount_out / 10**SUSDE_DECIMALS -  amount_in * SDAI_PRICE, amount_out / 10**SUSDE_DECIMALS

def main():
    while True:
        try:
            sdai_balance = sdai_contract.functions.balanceOf(address).call() / 10**SDAI_DECIMALS
            susde_balance = susde_contract.functions.balanceOf(address).call() / 10**SUSDE_DECIMALS
            amount_in = 100000 #交易的DAI数量
            profit, amount_out = get_buy_profit(amount_in)
            LogStatus(f"SDAI数量:{sdai_balance}, SUSDE数量:{susde_balance}, 收益:{profit}")
            if profit > 1000 and sdai_balance > amount_in: #利润空间
                Log("\n开始执行SDAI->SUSDE交易...")
                execute_trade(amount_in, 0.999*amount_out, 'buy') #一定要设置滑点
            wait_time = 3  # 等待时间(秒)
            time.sleep(wait_time)
            
        except Exception as e:
            # 全局错误处理
            print(f"程序发生错误: {e}")
            print("60秒后重试...")
            time.sleep(60)  # 出错后等待更长时间

Напоминание о риске

Операции в цепочке относительно рискованны для новичков. Помимо риска утечки закрытого ключа, упомянутого выше, существуют и другие риски:

  • Роботы MEV должны устанавливать минимальный выход min_amount_out при выполнении транзакций, чтобы гарантировать прибыль даже в худшем случае, в противном случае они будут эксплуатироваться MEV. Сегодня человек использовал 220 000 USDC для обмена всего на 5272 USDT на Uniswap. Причина в том, что amountOutMinimum был установлен на 0.
  • Ошибки стратегии, как и API биржи, если в транзакции программы на блокчейне возникает ошибка, GAS будет часто расходоваться.

Новичкам в он-чейн-трейдинге необходимо изучить основы: понять такие понятия, как «Газ», «проскальзывание», «MEV» и т. д. Всегда начинайте с небольшой суммы и постепенно ее увеличивайте. Отслеживайте транзакции с помощью чего-то вроде Etherscan. Лучше упустить возможность, чем рисковать потерей капитала.