3
focar em
1444
Seguidores

Como implementar a colocação de ordens em bolsas descentralizadas — Tomando a Curve como exemplo

Criado em: 2025-03-13 14:28:33, atualizado em: 2025-03-14 17:27:53
comments   1
hits   933

Por que devo fazer um pedido?

No mercado de criptomoedas, as oportunidades de negociação geralmente são passageiras, especialmente as oportunidades de arbitragem, que podem durar apenas alguns minutos. Se você depender de operações manuais, os usuários podem não conseguir aproveitar a melhor oportunidade a tempo ou perder o melhor preço. Por exemplo, há algum tempo, devido ao ataque hacker à Bybit, o USDe foi desacoplado, fazendo com que seu preço flutuasse muito. Naquela época, a taxa de retorno anualizada por meio da compra e resgate de sUSDe ultrapassava 300%. Essa janela de alto rendimento geralmente dura muito pouco tempo, e é difícil para a negociação manual acompanhar o ritmo do mercado sem definir ordens com antecedência. Portanto, a função de colocação de ordens se tornou uma ferramenta importante para usuários de exchanges descentralizadas (DEX), melhorando significativamente a eficiência das transações.

Os princípios e deficiências das ordens DEX

Várias DEXs oferecem funcionalidade de ordens limitadas, cada uma com diferentes implementações e estruturas de taxas. Tomemos como exemplos Cow Swap e Odos. O princípio básico é usar sua função agregadora para monitorar a liquidez e os preços de vários DEXs em tempo real. Quando o preço de mercado atingir as condições limite, a ordem será acionada pelo tomador, e o contrato inteligente executará automaticamente a transação e pagará a taxa de gás. A Cow também armazena os pedidos dos usuários fora da cadeia, que são então correspondidos competitivamente por um grupo de nós descentralizados chamados “Solvers”, mas são finalmente executados na cadeia. Resumindo, embora o GAS seja isento, os lucros extras gerados pelas transações concluídas por essas DEXs para você podem cobrir as taxas do GAS.

Mas isso traz um problema: se uma de suas ordens estiver pendente a um preço de 90U e o agregador monitorar que o preço de uma determinada DEX caiu para 80U, ele executará a transação para você. Sua ordem é finalmente executada a um preço de 90U, gerando um lucro de 10U. Como esse lucro é distribuído? Na operação real, diferentes plataformas lidam com isso de maneiras diferentes. Tomando o Cow Swap como exemplo, seu mecanismo estipula claramente que quando o preço de execução for melhor que o preço limite, o excedente gerado (ou seja, os 10U) será dividido entre a plataforma e o usuário, com o Cow Swap ficando com 50% e o usuário com os 50% restantes. Odos depositará qualquer excedente no tesouro. Em essência, sua ordem é arbitrada sem risco pela bolsa DEX.

Além disso, para economizar taxas de transação, as ordens DEX são transações agregadas, ou seja, muitas transações são agrupadas de vez em quando, e o ETH tem um bloco de 12 segundos, o que resultará na perda de algumas oportunidades possíveis. Claro, o DEX ainda tem muitas vantagens, como pesquisar mais caminhos, correspondência offline, economizar GÁS, etc., que são suficientes para a maioria dos usuários.

Vantagens de fazer pedidos com seu próprio programa

Comparado com a dependência de ordens agregadas da DEX, negociar diretamente por meio de contratos inteligentes tem vantagens únicas. Primeiro, os usuários têm controle total sobre a lógica de execução de ordens e todos os ganhos. Em segundo lugar, fazer seus próprios pedidos evita o atraso na embalagem de transações agregadas e pode responder às mudanças do mercado mais rapidamente, especialmente aproveitando oportunidades em 12 segundos durante alta volatilidade. Além disso, ordens pendentes personalizadas podem definir de forma flexível condições complexas (como negociação de portfólio de múltiplos ativos ou stop profit e stop loss) sem serem restringidas pelas funções predefinidas da plataforma. No entanto, isso requer certas habilidades de programação, e é preciso pagar a taxa de gás por conta própria, e pode haver riscos de segurança na cadeia. Portanto, fazer seus próprios pedidos é adequado para usuários avançados que têm fortes capacidades técnicas e buscam o máximo retorno.

Proteção de chaves privadas

Se você deseja operar contratos inteligentes com seus próprios programas, a segurança de suas chaves privadas é, sem dúvida, sua maior preocupação. A solução que eu encontrei é usar Python para criptografar minha própria chave privada offline e armazenar o texto cifrado criptografado no servidor que executa o host. A senha descriptografada é passada usando os parâmetros do robô da plataforma FMZ, e o programa é executado no host e a lê e descriptografa (ela pode ser excluída após a execução do programa). Dessa forma, mesmo que seu servidor seja hackeado, não haverá problemas, mas você ainda precisa ter cuidado para não vazar sua conta FMZ. Como envolve apenas um texto simples fora da rede, a segurança é aceitável. O código específico é o seguinte, que pode ser executado localmente fora da rede

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)

Preparação antes de vincular ETH

Web3.py é uma poderosa biblioteca Python para interagir com a rede Ethereum. Instale-o com o seguinte comando: pip install web3. Sua função é simplificar a comunicação entre desenvolvedores e nós Ethereum, além de dar suporte a operações como consulta de saldos, chamada de contratos inteligentes e envio de transações. Por exemplo, verificar o saldo de uma conta leva apenas algumas linhas de código, tornando o Web3.py uma ferramenta ideal para criar DApps ou automatizar transações.

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) é uma interface de comunicação fornecida pelo nó Ethereum, que interage com o blockchain enviando solicitações JSON por meio do protocolo HTTP ou WebSocket. Por exemplo, eth_blockNumber pode consultar a altura do bloco mais recente. Devido ao alto custo de execução de um nó local, os desenvolvedores geralmente dependem de provedores de RPC de terceiros. As escolhas comuns incluem:

  • Infura: serviço padrão MetaMask, fácil de usar, mas com baixa cota gratuita (100.000 solicitações por dia).
  • Alchemy: desempenho superior, alta cota gratuita e suporte para mais recursos.
  • QuickNode: adequado para requisitos de alto desempenho, mas voltado para usuários pagos.
from web3 import Web3
w3 = Web3(Web3.HTTPProvider('https://eth-mainnet.g.alchemy.com/v2/你的API密钥'))
print(w3.is_connected())

É recomendado usar o Alchemy. Comparado com o Infura da MetaMask, o Alchemy fornece uma cota gratuita maior. Após o registro, você pode obter o endereço RPC, como https://eth-mainnet.g.alchemy.com/v2/sua chave de API, e configurá-lo para Web3.py para uso.

Endereço do contrato de curva e ABI

Tomando o pool Crve de sDAI/sUSDe como exemplo https://curve.fi/dex/ethereum/pools/factory-stable-ng-102/swap/, você pode encontrar facilmente os endereços dos dois tokens e o endereço do pool. Como implementar a colocação de ordens em bolsas descentralizadas — Tomando a Curve como exemplo

ABI define como interagir com o contrato, então também é necessário obtê-lo. Veja o contrato no ethscan https://etherscan.io/address/0x167478921b907422f8e88b43c4af2b8bea278d3a#code. Você pode ver o abi na página do contrato e copiá-lo diretamente.

Contrato de link para obter preço

Primeiro, vincule a carteira ETH. Se o endereço da sua carteira estiver impresso, significa que foi bem-sucedido.

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)

O seguinte é o processo de obtenção do preço. Finalmente, sem considerar a taxa GAS, o investimento atual de 100.000 SDAI renderá um lucro de 335U após uma semana. Pode parecer um pouco complicado, mas na verdade não é 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

Por fim, usamos polling para obter preços continuamente e fazer um pedido quando o lucro esperado for atingido. Observe este códigoEste é apenas um código de exemplo, não o use diretamenteOs leitores podem encontrar vários problemas na prática, mas a IA é atualmente muito poderosa e pode basicamente responder a várias perguntas. Ela também pode ajudar diretamente a escrever código. O editor de código da FMZ também integra o ChatGPT, que pode ser usado com mais frequência.

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

Lembrete de risco

Operações on-chain são relativamente arriscadas para novatos. Além do risco de vazamento de chave privada mencionado acima, há vários outros riscos:

  • Os robôs MEV devem definir a saída mínima min_amount_out ao executar transações para garantir lucros mesmo no pior cenário, caso contrário, eles serão explorados pelo MEV. Hoje, uma pessoa usou 220.000 USDC para trocar por apenas 5272 USDT no Uniswap. O motivo foi que amountOutMinimum foi definido como 0.
  • Erros de estratégia, assim como a API de câmbio, se houver um bug na transação do programa on-chain, o GAS será consumido com frequência.

Para iniciantes em negociação on-chain, é preciso aprender o básico: entender conceitos como gás, deslizamento, MEV, etc. Comece sempre com uma quantia baixa e aumente gradativamente. Monitore transações usando algo como Etherscan. É melhor perder uma oportunidade do que correr o risco de perder seu capital.