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는 또한 사용자의 주문을 오프체인으로 저장하고, 이 주문은 “Solvers”라고 불리는 분산 노드 그룹에 의해 경쟁적으로 매칭되지만 궁극적으로는 온체인에서 실행됩니다. 간단히 말해, 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는 Ethereum 네트워크와 상호작용하기 위한 강력한 Python 라이브러리입니다. 다음 명령어로 설치하세요: pip install web3. 이 기능의 목적은 개발자와 이더리움 노드 간의 커뮤니케이션을 간소화하고, 잔액 조회, 스마트 계약 호출, 거래 전송 등의 작업을 지원하는 것입니다. 예를 들어, 계좌 잔액을 확인하려면 몇 줄의 코드만 있으면 되므로 Web3.py는 DApp을 구축하거나 거래를 자동화하는 데 이상적인 도구입니다.

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)는 HTTP나 WebSocket 프로토콜을 통해 JSON 요청을 전송하여 블록체인과 상호 작용하는 Ethereum 노드가 제공하는 통신 인터페이스입니다. 예를 들어, eth_blockNumber는 최신 블록 높이를 쿼리할 수 있습니다. 로컬 노드를 운영하는 데 비용이 많이 들기 때문에 개발자는 일반적으로 타사 RPC 공급자에 의존합니다. 일반적인 선택 사항은 다음과 같습니다.

  • 인푸라: MetaMask 기본 서비스로 사용하기 쉽지만 무료 할당량이 낮습니다(하루 요청 10만 건).
  • Alchemy: 뛰어난 성능, 높은 무료 할당량, 더 많은 기능 지원.
  • QuickNode: 고성능 요구 사항에 적합하지만, 유료 사용자에게 치우쳐 있습니다.
from web3 import Web3
w3 = Web3(Web3.HTTPProvider('https://eth-mainnet.g.alchemy.com/v2/你的API密钥'))
print(w3.is_connected())

Alchemy를 사용하는 것이 좋습니다. MetaMask의 Infura와 비교했을 때 Alchemy는 더 높은 무료 할당량을 제공합니다. 등록 후 https://eth-mainnet.g.alchemy.com/v2/your API 키와 같은 RPC 주소를 얻고 Web3.py로 구성하여 사용할 수 있습니다.

Curve 계약 주소 및 ABI

sDAI/sUSDe의 Crve 풀을 예로 들면 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는 1주일 후에 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))

전체 프로그램

마지막으로, 우리는 폴링을 사용하여 가격을 지속적으로 얻고, 예상 이익에 도달하면 주문을 합니다. 이 코드를 참고하세요이것은 단지 샘플 코드이므로 직접 사용하지 마십시오.독자들은 실제로 다양한 문제에 직면할 수 있지만, AI는 현재 매우 강력하며 기본적으로 다양한 질문에 답할 수 있습니다. 또한 코드를 작성하는 데 직접적으로 도움을 줄 수도 있습니다. 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를 사용하여 Uniswap에서 5272 USDT로만 교환했습니다. 이유는 amountOutMinimum이 0으로 설정되었기 때문입니다.
  • 전략적 오류는 거래소 API와 마찬가지로 체인상 프로그램 거래에 버그가 있는 경우 GAS가 자주 소모됩니다.

온체인 거래 초보자는 기본 사항을 알아야 합니다. 가스, 슬리피지, MEV 등의 개념을 이해해야 합니다. 항상 적은 양부터 시작해서 점차 늘려가세요. Etherscan 등을 사용하여 거래를 모니터링합니다. 자본을 잃을 위험을 감수하는 것보다 기회를 놓치는 것이 낫습니다.