Cover photo

空投埋伏-quantumswap

前言

这是我mirror上的第一篇文章,而且也将转化为nft(属于是rabbithole任务的一部分)。2022年以来行情一直不是很好,看着手上的加密货币的缩水也深感持币观望不是个事,总要做点什么去尝试。最近听闻区块链里有神秘的组织叫区块链科学家,按照我目前的理解区块链科学家就是通过程序(pyhton、javascript等)与智能合约交互从而自动化地完成各种项目的交互任务。自己也是一名程序员,进入币圈时间虽然不长但认为自己还是可以通过摸索去当一个“区块链科学家的”,最近也学习了通过selenium去批量生成钱包,再通过类似爬虫的方式与项目方网站交互,另外趁着quantumswap测试的机会也学会了如何与合约交互。总之,通过项目的方式驱动学习一定是最好的一个方式,这也是我接下来在区块链里写文章的一个方向。

Quantumswap介绍

在twitter上一直关注几个撸空投很厉害的人,他们应该是有一个组织会在一个表格里定期更新比较不错的空投项目,我也是通过他们接触到这个领域,如果想了解表格的具体细节可以看他的twitter。

https://twitter.com/zlexdl

QuantumSwap是EVMOS链上的一个类似PancakeSwap的项目,作为一个技术人员,从目前可以公开的信息发现这个项目基本是从PancakeSwap clone来的,具体大家可以看他们项目的github看出来。

https://github.com/quantum-swap

如何手动交互

大家可以参考这篇文章去了解如何手动交互。

https://mirror.xyz/0x5B8c65ffa85fF42695B2f96A3B6eB6E45BBB4AdD/AZaRBYcFPFSQ-FZ5AaBR8OWzKGCHbi_YC6Eh2purQeM

如何通过程序自动化交互

自动化交互其实就是把上一章的操作变成程序语言。根据上一篇文章,交互的流程如下。

  • 领取tevmos测试代币

  • 在QuantumSwap上claim测试代币(USDT, USDC等),即claim操作

  • 进行简单的swap,比如USDT换成USDC

  • 添加流动性,比如添加USDT-USDC流动性

  • farm LP

  • 删除流动性

下面咱们一一介绍如何自动化这个流程。

领取tevmos测试代币

这个流程没法自动化,原因是万恶的google验证码。

claim

Claim操作其实就是一个智能合约上的函数,那么我们只要和这个函数进行交互就可以领取代币了。但是想与合约交互还需要知道两个信息。

  • 合约的地址

  • 对应函数的ABI

关于合约地址我们可以通区块链浏览器看到,举个例子,我们有一个账号先去尝试进行一次claim操作,那么通过区块链浏览器去查看这一操作的记录就知道了当时是与哪个合约地址进行的交互。

https://evm.evmos.dev

关于ABI这个稍微有点复杂,我也是参考了一个科学家的twitter才有所进展,twitter如下。简单来说,就是审查项目方的网站前端源码,就可以找到ABI对应的json结构,因为项目方网站也需要使用ABI才能与合约交互,所以理论上是没法完全隐藏掉ABI的。

https://twitter.com/gm365/status/1521058983838380032

关于这一部分我的代码如下,其实就是最简单的合约交互与调用,后面的代码逻辑基本也是一致的。

def claim_quantumswap_test_token(w3, wallet):
    public_key, private_key = wallet
    contract_address = "0x4c9e786f2bc5a128baf5faf39c34b42bd07af671" # claim token address    
    claim_realted_abi = None
    with open("./abis/abi_claim_realted.json") as f:
        claim_realted_abi = json.load(f)
    contract_id = web3.Web3.toChecksumAddress(contract_address)
    quantumswap_swap = w3.eth.contract(address=contract_id, abi=claim_realted_abi)
    nonce = w3.eth.get_transaction_count(public_key)
    txn = quantumswap_swap.functions.claim().buildTransaction({
        'from': public_key,
        'value': 0,
        'maxPriorityFeePerGas': w3.toWei(2, 'gwei'),
        'nonce': nonce
    })
    signed_txn = w3.eth.account.sign_transaction(txn, private_key)
    txn_hash = w3.eth.send_raw_transaction(signed_txn.rawTransaction)
    tx_token = w3.eth.wait_for_transaction_receipt(txn_hash)
    print(tx_token)
    isClaimed = quantumswap_swap.functions.hasClaimed(public_key).call()
    print("The status of claim: {}", isClaimed)

swap

swap的难点也依旧是找到合约地址与ABI,至于代码细节只是用了一个新的函数swapExactTokensForTokens,这个函数都是标准的接口网上有很多例子与教程。我的代码如下。

def swap_token_for_token(w3, wallet, token_a_address, token_b_address, token_a_amounts, tradeSlippage):
    contract_address = QTM_SWAP_CONTRACT
    abi = None
    public_key, private_key = wallet
    with open("./abis/swap_related_abi.json") as f:
        abi = json.load(f)
    contract_id = web3.Web3.toChecksumAddress(contract_address)
    quantumswap_swap = w3.eth.contract(address=contract_id, abi=abi)
    nonce = w3.eth.get_transaction_count(public_key)
    
    # Convert token a amouts to wei
    token_a_amounts_wei = w3.toWei(Decimal(token_a_amounts), 'ether')
    print("token_a_amounts_wei: {}".format(token_a_amounts_wei))
    
    # Fetch the token b amounts could be got by specified token a
    token_a_b_pair = quantumswap_swap.functions.getAmountsOut(token_a_amounts_wei, [token_a_address, token_b_address]).call()
    token_b_amounts_wei = token_a_b_pair[1]
    
    # Calculate the amount out min value for token b
    amountOutMin = int(token_b_amounts_wei - token_b_amounts_wei*(tradeSlippage/100.0))
    print("amountOutMin: {}".format(amountOutMin))
    eta = int(time.time()) + 1000 * 60 * 10
    txn = quantumswap_swap.functions.swapExactTokensForTokens(token_a_amounts_wei, amountOutMin,\
        [token_a_address, token_b_address],
        public_key, eta).buildTransaction({
            'from': public_key,
            'value': 0,
            # 'maxFeePerGas': w3.toWei(3, 'gwei'),
            'maxPriorityFeePerGas': w3.toWei(2, 'gwei'),
            'nonce': nonce
        })
    signed_txn = w3.eth.account.sign_transaction(txn, private_key)
    txn_hash = w3.eth.send_raw_transaction(signed_txn.rawTransaction)
    tx_token = w3.eth.wait_for_transaction_receipt(txn_hash)
    print(tx_token)

增加流动性

def addLiquidity(w3, wallet, token_a_address, token_b_address, token_a_amount, tradeSlippage):
    contract_address = QTM_SWAP_CONTRACT
    abi = None
    public_key, private_key = wallect
    with open("./abis/lp_related_abi.json") as f:
        abi = json.load(f)
    contract_id = web3.Web3.toChecksumAddress(contract_address)
    quantumswap_swap = w3.eth.contract(address=contract_id, abi=abi)
    nonce = w3.eth.get_transaction_count(public_key)
    token_a_amount_wei = w3.toWei(Decimal(token_a_amount), 'ether')
    token_a_b_pair = quantumswap_swap.functions.getAmountsOut(token_a_amount_wei, [token_a_address, token_b_address]).call()
    token_b_amount_wei = token_a_b_pair[1]
    
    token_a_amount_min = int(token_a_amount_wei - token_a_amount_wei*(tradeSlippage/100.0))
    token_b_amount_min = int(token_b_amount_wei - token_b_amount_wei*(tradeSlippage/100.0))
    eta = int(time.time()) + 1000 * 60 * 10
    txn = quantumswap_swap.functions.addLiquidity(token_a_address, token_b_address, token_a_amount_wei, token_b_amount_wei, token_a_amount_min, token_b_amount_min, public_key, eta).buildTransaction({
        'from': public_key,
        'value': 0,
        'maxPriorityFeePerGas': w3.toWei(2, 'gwei'),
        'nonce': nonce
    })
    signed_txn = w3.eth.account.sign_transaction(txn, private_key)
    txn_hash = w3.eth.send_raw_transaction(signed_txn.rawTransaction)
    tx_token = w3.eth.wait_for_transaction_receipt(txn_hash)
    print(tx_token)

farm lp

def earnFarm(lp_amounts, wallet):
    abi = None
    public_key, private_key = wallet
    with open("./abis/farm_related_abi.json") as f:
        abi = json.load(f)
    contract_id = web3.Web3.toChecksumAddress(LP_FARM_CONTRACT)
    farm_contract = w3.eth.contract(address=contract_id, abi=abi)
    nonce = w3.eth.get_transaction_count(public_key)
    txn = farm_contract.functions.deposit(0, lp_amounts).buildTransaction({
        'from': public_key,
        'value': 0,
        'maxPriorityFeePerGas': w3.toWei(2, 'gwei'),
        'nonce': nonce
    })
    signed_txn = w3.eth.account.sign_transaction(txn, private_key)
    txn_hash = w3.eth.send_raw_transaction(signed_txn.rawTransaction)
    tx_token = w3.eth.wait_for_transaction_receipt(txn_hash)
    print(tx_token)

删除流动性

到了删除流动性这里,我遇到了点难题,就是项目方是通过removeLiquidityETHWithPermit这个方法去实现的,但这个方法中有三个与签名(v, s, r)相关的参数我还没找到什么办法去生成,但通过查阅资料,这种带permit的方式与eip-712相关。大致意思是需要用户对一个trans进行签名,并将签名后生成的数据放到合约的参数中。所以v,s,r应该就是这个交易的一种加密形式(初步推测),还要具体实践后才能得出结论。

{
      "inputs": [
        {
          "internalType": "address",
          "name": "token",
          "type": "address"
        },
        {
          "internalType": "uint256",
          "name": "liquidity",
          "type": "uint256"
        },
        {
          "internalType": "uint256",
          "name": "amountTokenMin",
          "type": "uint256"
        },
        {
          "internalType": "uint256",
          "name": "amountETHMin",
          "type": "uint256"
        },
        {
          "internalType": "address",
          "name": "to",
          "type": "address"
        },
        {
          "internalType": "uint256",
          "name": "deadline",
          "type": "uint256"
        },
        {
          "internalType": "bool",
          "name": "approveMax",
          "type": "bool"
        },
        {
          "internalType": "uint8",
          "name": "v",
          "type": "uint8"
        },
        {
          "internalType": "bytes32",
          "name": "r",
          "type": "bytes32"
        },
        {
          "internalType": "bytes32",
          "name": "s",
          "type": "bytes32"
        }
      ],
      "name": "removeLiquidityETHWithPermit",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "amountToken",
          "type": "uint256"
        },
        {
          "internalType": "uint256",
          "name": "amountETH",
          "type": "uint256"
        }
      ],
      "stateMutability": "nonpayable",
      "type": "function"
    }

结语

第一篇文章略显仓促,有兴趣的朋友在我twitter留言,如果大家真的很感兴趣,我后面后发出更多细节。另外就是今日实在牙疼,也并无耐心继续写了,出去转一转陶冶下情操,缓解一下。