3
Seguir
1444
Seguidores

Cómo implementar la colocación de órdenes en intercambios descentralizados: tomando Curve como ejemplo

Creado el: 2025-03-13 14:28:33, Actualizado el: 2025-03-14 17:27:53
comments   1
hits   933

¿Por qué realizar un pedido?

En el mercado de criptomonedas, las oportunidades comerciales suelen ser fugaces, especialmente las oportunidades de arbitraje, que pueden durar solo unos minutos. Si depende de operaciones manuales, es posible que los usuarios no puedan aprovechar la mejor oportunidad a tiempo o pierdan el mejor precio. Por ejemplo, hace algún tiempo, debido al ataque de hackers a Bybit, USDe se desacopló, lo que provocó que su precio fluctuara mucho. En ese momento, la tasa de rendimiento anualizada mediante la compra y el reembolso de sUSDe superó el 300%. Esta ventana de alto rendimiento suele durar muy poco tiempo y es difícil para el trading manual seguir el ritmo del mercado sin fijar órdenes con antelación. Por lo tanto, la función de colocación de órdenes se ha convertido en una herramienta importante para los usuarios de intercambios descentralizados (DEX), mejorando significativamente la eficiencia de las transacciones.

Los principios y las deficiencias de las órdenes DEX

Varios DEX ofrecen funcionalidad de órdenes limitadas, cada una con diferentes implementaciones y estructuras de tarifas. Tomemos como ejemplos Cow Swap y Odos. El principio básico es utilizar su función de agregador para monitorear la liquidez y los precios de múltiples DEX en tiempo real. Cuando el precio del mercado alcance las condiciones límite, el tomador activará la orden y el contrato inteligente ejecutará automáticamente la transacción y pagará la tarifa de gas. Cow también almacena los pedidos de los usuarios fuera de la cadena, que luego son comparados de manera competitiva por un grupo de nodos descentralizados llamados “Solvers”, pero que finalmente se ejecutan en la cadena. En resumen, aunque el GAS está exento, las ganancias adicionales generadas por las transacciones completadas por estos DEX para usted pueden cubrir las tarifas del GAS.

Pero esto plantea un problema: si una de tus órdenes está pendiente a un precio de 90U y el agregador detecta que el precio de un DEX determinado ha bajado a 80U, ejecutará la transacción por ti. Tu orden se ejecuta finalmente a un precio de 90U, generando una ganancia de 10U. ¿Cómo se distribuye esta ganancia? En la práctica, las distintas plataformas lo gestionan de forma distinta. Tomando como ejemplo Cow Swap, su mecanismo estipula claramente que cuando el precio de ejecución sea mejor que el precio límite, el excedente generado (es decir, los 10U) se dividirá entre la plataforma y el usuario, tomando Cow Swap el 50% y el usuario obteniendo el 50% restante. Odos depositará cualquier excedente en la bóveda. En esencia, su orden es arbitrada sin riesgos por el intercambio DEX.

Además, para ahorrar tarifas de transacción, las órdenes DEX son transacciones agregadas, es decir, muchas transacciones se empaquetan juntas de vez en cuando, y ETH tiene un bloque de 12 segundos, lo que hará que se pierdan algunas posibles oportunidades. Por supuesto, DEX todavía tiene muchas ventajas, como la búsqueda de más rutas, el emparejamiento sin conexión, el ahorro de GAS, etc., que son suficientes para la mayoría de los usuarios.

Ventajas de realizar pedidos con tu propio programa

En comparación con confiar en las órdenes agregadas de DEX, operar directamente a través de contratos inteligentes tiene ventajas únicas. En primer lugar, los usuarios tienen control total sobre la lógica de ejecución de órdenes y todas las ganancias. En segundo lugar, al colocar sus propios pedidos se evita el retraso en el empaquetado de transacciones agregadas y se puede responder a los cambios del mercado más rápidamente, especialmente aprovechando oportunidades en 12 segundos durante períodos de alta volatilidad. Además, las órdenes pendientes personalizadas pueden establecer de manera flexible condiciones complejas (como negociación de carteras de múltiples activos o stop profit y stop loss) sin estar restringidas por las funciones preestablecidas de la plataforma. Sin embargo, esto requiere ciertas habilidades de programación, y uno debe pagar la tarifa del gas usted mismo, y puede haber riesgos de seguridad en la cadena. Por lo tanto, realizar sus propios pedidos es adecuado para usuarios avanzados que tienen fuertes capacidades técnicas y buscan obtener el máximo rendimiento.

Protección de claves privadas

Si desea operar contratos inteligentes con sus propios programas, la seguridad de sus claves privadas es sin duda su mayor preocupación. La solución que he encontrado es usar Python para cifrar mi propia clave privada sin conexión y almacenar el texto cifrado en el servidor que ejecuta el host. La contraseña descifrada se pasa mediante los parámetros del robot de la plataforma FMZ, y el programa se ejecuta en el host, la lee y la descifra (puede eliminarse después de ejecutarse el programa). De esta manera, incluso si tu servidor es hackeado, no habrá ningún problema, pero aún así deberás tener cuidado de no filtrar tu cuenta FMZ. Dado que solo implica un texto simple fuera de la red, la seguridad es aceptable. El código específico es el siguiente, que se puede ejecutar localmente fuera de la red.

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)

Preparación antes de vincular ETH

Web3.py es una poderosa biblioteca de Python para interactuar con la red Ethereum. Instálelo con el siguiente comando: pip install web3. Su función es simplificar la comunicación entre los desarrolladores y los nodos de Ethereum, y soportar operaciones como consultar saldos, llamar contratos inteligentes y enviar transacciones. Por ejemplo, verificar el saldo de una cuenta solo requiere unas pocas líneas de código, lo que convierte a Web3.py en una herramienta ideal para crear DApps o automatizar transacciones.

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

RPC (Remote Procedure Call) es una interfaz de comunicación proporcionada por el nodo Ethereum, que interactúa con la cadena de bloques enviando solicitudes JSON a través del protocolo HTTP o WebSocket. Por ejemplo, eth_blockNumber puede consultar la última altura del bloque. Debido al alto costo de ejecutar un nodo local, los desarrolladores generalmente dependen de proveedores de RPC de terceros. Las opciones más comunes incluyen:

  • Infura: servicio predeterminado de MetaMask, fácil de usar pero con cuota libre baja (100.000 solicitudes por día).
  • Alchemy: rendimiento superior, alta cuota libre y soporte para más funciones.
  • QuickNode: adecuado para requisitos de alto rendimiento, pero orientado a los usuarios pagos.
from web3 import Web3
w3 = Web3(Web3.HTTPProvider('https://eth-mainnet.g.alchemy.com/v2/你的API密钥'))
print(w3.is_connected())

Se recomienda usar Alchemy. En comparación con Infura de MetaMask, Alchemy ofrece una mayor cuota gratuita. Tras registrarse, puede obtener la dirección RPC, como https://eth-mainnet.g.alchemy.com/v2/your API key, y configurarla en Web3.py para su uso.

Dirección del contrato de curva y ABI

Tomando el pool Crve de sDAI/sUSDe como ejemplo https://curve.fi/dex/ethereum/pools/factory-stable-ng-102/swap/, puedes encontrar fácilmente las direcciones de los dos tokens y la dirección del pool. Cómo implementar la colocación de órdenes en intercambios descentralizados: tomando Curve como ejemplo

La ABI define cómo interactuar con el contrato, por lo que también es necesario obtenerla. Consulte el contrato en ethscan https://etherscan.io/address/0x167478921b907422f8e88b43c4af2b8bea278d3a#code. Puede ver la ABI en la página del contrato y copiarla directamente.

Vincular contrato para obtener precio

Primero, vincula la billetera ETH. Si la dirección de tu billetera aparece impresa, significa que la vinculación se realizó correctamente.

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)

A continuación se muestra el proceso para obtener el precio. Finalmente, sin considerar la comisión de GAS, la inversión actual de 100.000 SDAI generará una ganancia de 335 U después de una semana. Puede parecer un poco complicado, pero en realidad no es difícil de entender.

    # --------------- 合约设置 ---------------
    # 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))

Programa completo

Finalmente, utilizamos el sondeo para obtener precios continuamente y realizamos un pedido cuando se alcanza la ganancia esperada. Observe este código.Este es solo un código de muestra, no lo use directamenteLos lectores pueden encontrarse con diversos problemas en la práctica, pero la IA es actualmente muy potente y puede responder a diversas preguntas. También puede ayudar directamente a escribir código. El editor de código de FMZ también integra ChatGPT, que puede usarse con mayor frecuencia.

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)  # 出错后等待更长时间

Recordatorio de riesgo

Las operaciones en cadena son relativamente arriesgadas para principiantes. Además del riesgo de fuga de claves privadas mencionado anteriormente, existen otros riesgos:

  • Los robots MEV deben establecer la salida mínima min_amount_out al ejecutar transacciones para garantizar ganancias incluso en el peor de los casos, de lo contrario serán explotados por MEV. Hoy, una persona usó 220,000 USDC para intercambiarlos por solo 5272 USDT en Uniswap. El motivo fue que amountOutMinimum estaba configurado a 0.
  • Errores de estrategia, al igual que la API de intercambio, si hay un error en la transacción del programa en cadena, el GAS se consumirá con frecuencia.

Para los novatos en el comercio en cadena, es necesario aprender los conceptos básicos: comprender conceptos como gas, deslizamiento, MEV, etc. Comience siempre con una cantidad baja y vaya aumentándola. Supervise las transacciones utilizando algo como Etherscan. Es mejor perder una oportunidad que arriesgarse a perder su capital.