3
Suivre
1444
Abonnés

Comment mettre en œuvre le placement d'ordres dans les échanges décentralisés – Prenons l'exemple de Curve

Créé le: 2025-03-13 14:28:33, Mis à jour le: 2025-03-14 17:27:53
comments   1
hits   933

Pourquoi devrais-je passer une commande ?

Sur le marché des crypto-monnaies, les opportunités de trading sont souvent éphémères, en particulier les opportunités d’arbitrage, qui peuvent ne durer que quelques minutes. Si vous vous fiez aux opérations manuelles, les utilisateurs risquent de ne pas être en mesure de saisir la meilleure opportunité à temps ou de manquer le meilleur prix. Par exemple, il y a quelque temps, en raison de l’attaque de pirate informatique sur Bybit, l’USDe a été découplé, ce qui a provoqué une forte fluctuation de son prix. À cette époque, le taux de rendement annualisé obtenu en achetant et en rachetant des sUSDe dépassait 300 %. Cette fenêtre de rendement élevé dure généralement très peu de temps et il est difficile pour le trading manuel de suivre le rythme du marché sans définir d’ordres à l’avance. Par conséquent, la fonction de placement d’ordres est devenue un outil important pour les utilisateurs d’échanges décentralisés (DEX), améliorant considérablement l’efficacité des transactions.

Les principes et les inconvénients des ordres DEX

Plusieurs DEX offrent une fonctionnalité d’ordre limité, chacun avec des implémentations et des structures de frais différentes. Prenons Cow Swap et Odos comme exemples. Le principe de base est d’utiliser sa fonction d’agrégation pour surveiller la liquidité et les prix de plusieurs DEX en temps réel. Lorsque le prix du marché atteint les conditions limites, l’ordre sera déclenché par le preneur et le contrat intelligent exécutera automatiquement la transaction et paiera les frais de gaz. Cow stocke également les commandes des utilisateurs hors chaîne, qui sont ensuite comparées de manière compétitive par un groupe de nœuds décentralisés appelés « Solvers », mais sont finalement exécutées sur la chaîne. En bref, bien que le GAS soit exonéré, les bénéfices supplémentaires générés par les transactions effectuées par ces DEX pour vous peuvent couvrir les frais de GAS.

Mais cela pose un problème : si l’un de vos ordres est en attente à un prix de 90 U et que l’agrégateur constate que le prix d’un DEX donné est tombé à 80 U, il exécutera la transaction pour vous. Votre ordre est finalement exécuté à un prix de 90 U, générant un bénéfice de 10 U. Comment ce bénéfice est-il réparti ? Dans la pratique, les différentes plateformes gèrent la situation différemment. Prenons l’exemple de Cow Swap, son mécanisme stipule clairement que lorsque le prix d’exécution est meilleur que le prix limite, le surplus généré (c’est-à-dire les 10U) sera divisé entre la plateforme et l’utilisateur, Cow Swap prenant 50 % et l’utilisateur obtenant les 50 % restants. Odos déposera tout surplus dans le coffre-fort. En substance, votre ordre est arbitré sans risque par la bourse DEX.

De plus, afin d’économiser les frais de transaction, les ordres DEX sont des transactions agrégées, c’est-à-dire que de nombreuses transactions sont regroupées de temps en temps, et ETH a un bloc de 12 secondes, ce qui entraînera la perte de certaines opportunités possibles. Bien sûr, DEX présente encore de nombreux avantages, tels que la recherche de plus de chemins, la correspondance hors ligne, l’économie de GAS, etc., qui sont suffisants pour la plupart des utilisateurs.

Avantages de passer des commandes avec votre propre programme

Par rapport aux ordres agrégés du DEX, le trading direct via des contrats intelligents présente des avantages uniques. Tout d’abord, les utilisateurs ont un contrôle total sur la logique d’exécution des commandes et sur tous les gains. Deuxièmement, passer vos propres commandes évite le délai de conditionnement des transactions agrégées et permet de réagir plus rapidement aux changements du marché, en particulier en saisissant les opportunités dans les 12 secondes en cas de forte volatilité. De plus, les ordres personnalisés en attente peuvent définir de manière flexible des conditions complexes (telles que le trading de portefeuille multi-actifs ou le stop profit et le stop loss) sans être limités par les fonctions prédéfinies de la plateforme. Cependant, cela nécessite certaines compétences en programmation, il faut payer les frais de gaz soi-même et il peut y avoir des risques de sécurité sur la chaîne. Par conséquent, passer vos propres commandes convient aux utilisateurs avancés qui ont de solides capacités techniques et recherchent des rendements maximaux.

Protection des clés privées

Si vous souhaitez exploiter des contrats intelligents avec vos propres programmes, la sécurité de vos clés privées est sans aucun doute votre plus grande préoccupation. La solution que j’ai trouvée consiste à utiliser Python pour chiffrer ma clé privée hors ligne et à stocker le texte chiffré sur le serveur hébergeant l’hôte. Le mot de passe déchiffré est transmis via les paramètres du robot de la plateforme FMZ, et le programme s’exécute sur l’hôte, le lit et le déchiffre (il peut être supprimé après exécution). De cette façon, même si votre serveur est piraté, il n’y aura pas de problème, mais vous devez toujours faire attention à ne pas divulguer votre compte FMZ. Comme il ne s’agit que d’un seul texte en clair hors réseau, la sécurité est acceptable. Le code spécifique est le suivant, qui peut être exécuté localement hors réseau

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)

Préparation avant de lier ETH

Web3.py est une puissante bibliothèque Python permettant d’interagir avec le réseau Ethereum. Installez-le avec la commande suivante : pip install web3. Sa fonction est de simplifier la communication entre les développeurs et les nœuds Ethereum et de prendre en charge des opérations telles que l’interrogation des soldes, l’appel de contrats intelligents et l’envoi de transactions. Par exemple, vérifier le solde d’un compte ne nécessite que quelques lignes de code, ce qui fait de Web3.py un outil idéal pour créer des DApps ou automatiser des transactions.

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) est une interface de communication fournie par le nœud Ethereum, qui interagit avec la blockchain en envoyant des requêtes JSON via le protocole HTTP ou WebSocket. Par exemple, eth_blockNumber peut interroger la hauteur du dernier bloc. En raison du coût élevé de l’exécution d’un nœud local, les développeurs s’appuient généralement sur des fournisseurs RPC tiers. Les choix courants incluent :

  • Infura : service par défaut MetaMask, facile à utiliser mais faible quota gratuit (100 000 requêtes par jour).
  • Alchemy : performances supérieures, quota gratuit élevé et prise en charge de davantage de fonctionnalités.
  • QuickNode : adapté aux exigences de haute performance, mais orienté vers les utilisateurs payants.
from web3 import Web3
w3 = Web3(Web3.HTTPProvider('https://eth-mainnet.g.alchemy.com/v2/你的API密钥'))
print(w3.is_connected())

Il est recommandé d’utiliser Alchemy. Comparé à Infura de MetaMask, Alchemy offre un quota gratuit plus élevé. Après inscription, vous pouvez obtenir l’adresse RPC, par exemple https://eth-mainnet.g.alchemy.com/v2/votre clé API, et la configurer sur Web3.py.

Adresse du contrat Curve et ABI

En prenant comme exemple le pool Crve de sDAI/sUSDe https://curve.fi/dex/ethereum/pools/factory-stable-ng-102/swap/, vous pouvez facilement trouver les adresses des deux jetons et l’adresse du pool. Comment mettre en œuvre le placement d’ordres dans les échanges décentralisés – Prenons l’exemple de Curve

L’ABI définit les modalités d’interaction avec le contrat ; il est donc nécessaire de l’obtenir. Consultez le contrat sur ethscan : https://etherscan.io/address/0x167478921b907422f8e88b43c4af2b8bea278d3a#code. Vous pouvez consulter l’ABI sur la page du contrat et le copier directement.

Lien de contrat pour obtenir le prix

Commencez par lier le portefeuille ETH. Si l’adresse de votre portefeuille est imprimée, cela signifie que l’opération a réussi.

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)

Voici le processus d’obtention du prix. Finalement, hors frais de GAS, l’investissement actuel de 100 000 SDAI générera un bénéfice de 335 U après une semaine. Cela peut paraître un peu compliqué, mais ce n’est en fait pas difficile à comprendre.

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

Programme complet

Enfin, nous utilisons des sondages pour obtenir des prix en continu et passer une commande lorsque le bénéfice attendu est atteint. Notez ce code.Ceci n’est qu’un exemple de code, ne l’utilisez pas directementLes lecteurs peuvent rencontrer divers problèmes dans la pratique, mais l’IA est actuellement très puissante et peut répondre à de nombreuses questions. Elle peut également contribuer directement à l’écriture de code. L’éditeur de code de FMZ intègre également ChatGPT, qui peut être utilisé plus fréquemment.

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

Rappel des risques

Les opérations on-chain sont relativement risquées pour les novices. Outre le risque de fuite de clé privée mentionné précédemment, il existe divers autres risques :

  • Les robots MEV doivent définir la sortie minimale min_amount_out lors de l’exécution des transactions pour garantir des bénéfices même dans le pire des cas, sinon ils seront exploités par MEV. Aujourd’hui, une personne a utilisé 220 000 USDC pour seulement 5 272 USDT sur Uniswap. La raison était que amountOutMinimum était fixé à 0.
  • Erreurs de stratégie, tout comme l’API d’échange, s’il y a un bug dans la transaction du programme en chaîne, le GAS sera fréquemment consommé.

Pour les novices en trading on-chain, ils doivent apprendre les bases : comprendre des concepts tels que le gaz, le glissement, le MEV, etc. Commencez toujours par une petite quantité et augmentez progressivement. Surveillez les transactions à l’aide d’un outil comme Etherscan. Il vaut mieux rater une opportunité que de risquer de perdre son capital.