blog:2022:0710_aurora_arb_monitoring

Arbitrage: Setting up arb monitoring on Aurora ?

Lately, I've seen a video on youtube on the EatTheBlocks channel concerning the arbitrage options on Aurora. I had already heard about that one, but never actually try to use that blockchain, and was not really sure to understand what it was in the first place. Time to change that in this session and check if I could successful run my Arb monitoring system there, let's rock it baby 😎!

  • First, as reference, the youtube video I mentioned above was the following: Dex Arbitrage Tutorial - Exchanges, Tokens & Routes, Trading Bot Controller, Trading on Aurora
  • At time 595s in this video we see a list of 3 routers:
    • trisolaris
    • wannaswap
    • auroraswap
  • ⇒ We should check if we can find those routers ourself with our data mining system.
  • But first let's setup the blockchain access.
  • Setup the corresponding blockchain:
        "aurora": {
          "chain_id": 1313161554,
          "provider_url": "https://mainnet.aurora.dev",
          "short_name": "aurora",
          "explorer_url": "https://aurorascan.dev/",
          "abi_provider_url": "https://api.aurorascan.dev/api?module=contract&action=getabi&address=",
          "default_account": "evm_default",
          "max_gas_price": 50,
          "chain_db_name": "aurora_chain",
          "default_gas_price": 27,
          "gas_price_weights": {
            "default": [20.0, 0.8, 0.2, 0.0],
            "fast": [15.0, 0.4, 0.6, 0.0],
            "high": [10.0, 0.0, 1.5, 0.0],
            "ultra": [8.0, 0.0, 0.9, 0.1]
          },
          "native_symbol": "AURORA"
      }
  • And now executing some initial tests to retrieve the blocks:
    from nvp.nvp_context import NVPContext
    import numpy as np
    from hexbytes import HexBytes
    import matplotlib.pyplot as plt
    
    if NVPContext.instance is None:
        NVPContext()
        
    ctx = NVPContext.get()
    chain = ctx.get_component('aurora_chain')
    
    bck = chain.get_block('latest', full_tx=True)
    bck
Interestingly we seem to get many blocks with 0 transactions in them 😅 ⇒ not much happening on that blockchain for the moment apparently.
  • Anyway, now that we have an initial config for the blockchain, we need to start collecting some blocks and gas prices:
  • $ nvp collect_aurora_blocks
    (...)
    File "D:\Projects\NervHome\nvh\crypto\blockchain\evm_sig_map.py", line 20, in __init__
    self.native_address = chain.get_wrapped_native_token().address()
    File "D:\Projects\NervHome\nvh\crypto\blockchain\evm_blockchain.py", line 807, in get_wrapped_native_token
    self.native_token = self.get_token(self.native_symbol)
    File "D:\Projects\NervHome\nvh\crypto\blockchain\evm_blockchain.py", line 828, in get_token
    self.check(desc is not None, "No token found with symbol %s", addr)
    File "D:\Projects\NervProj\nvp\nvp_object.py", line 73, in check
    raise NVPCheckError(fmt % args)
    nvp.nvp_object.NVPCheckError: No token found with symbol AURORA
  • Arrgh… not quite working here: it seems we need a direct access to the “wrapped native token” but we don't have that yet… and I'm not even sure it will really work that way 🤔. ⇒ So I think I should delay the creation of the sig_map.
  • Ohh crap: actually this is already delayed as much as possible: but here we need to know what is the native symbol…
  • Hmmm, and now i realize that the native symbol on that network is still “ETH”… wondering if these are actual ETH ? Anyway fixing that in the config.
And the answer is YES I think: when using the bridge we transfer ETH from ethereum and we get ETH on AURORA, so it seems these two are representing the same “ETH” coin/value, which is really interesting…
  • Next I found the address of the WETH token from this page: https://trisolaris-labs.github.io/docs/contracts/
  • Now I need a mechanism to manually register that token, let's see… This will do the trick:
            if cmd == "add-token":
                chain_name = self.get_param("chain")
                chain: EVMBlockchain = self.get_component(f"{chain_name}_chain")
    
                addr = self.get_param("address")
    
                # We check if we have a token for that address:
                token = chain.get_token(addr)
    
                if token is not None:
                    logger.info("Found token '%s'", token.symbol())
                else:
                    logger.info("Token not found.")
    
                return True
  • Then added WETH with:
    $ nvp bchain add-token -c aurora 0xC9BdeEd33CD01541e1eeD10f90519d2C06Fe3feB
  • So trying the block collection again now:
    $ nvp collect_aurora_blocks
    2022/07/06 21:06:29 [collect_evm_blocks] INFO: Collect 11 blocks and 19 transactions
  • ⇒ Cool! Working fine now. Let's add the cron script for it: done
  • Next we also need to collect the gas prices:
    $ nvp bchain collect-gas-price -c aurora
    2022/07/06 21:09:49 [nvh.crypto.blockchain.evm_blockchain] INFO: Ignoring block 69270259: not enough transactions.
  • ⇒ Collecting the gas price is not working so well on a network where we have generally 0 or 1 tx per block… 🤔 I think I should maybe chance that a bit.
  • OK, so accepting blocks with only one tx now and we get:
    $ nvp bchain collect-gas-price -c aurora
    2022/07/06 21:19:28 [nvh.crypto.blockchain.evm_blockchain] INFO: Block 69270699: num_tx=2, mode=0.070, mean=0.070, dev=0.000
  • Our collect_aurora_blocks script has been running for a few hours now, so we can use the collected blocks already to get some infos out of the transactions.
  • First, let's check if we have uniswap compatible routers:
    chain.handle("find_uniswap_routers")
  • Arrghh: doesn't work lol. Seems I broke something in there lately:
    File D:\Projects\NervHome\nvh\crypto\blockchain\handlers\find_uniswap_routers.py:68, in handle(chain)
         65 cdb: ChainDB = chain.get_db()
         67 sql = "SELECT to_id from transactions WHERE sig = ANY(%s);"
    ---> 68 cur = cdb.execute(sql, (sigs_i32,))
         69 rows = cur.fetchall()
         70 ids = [row[0] for row in rows]
    
    File D:\Projects\NervHome\nvh\crypto\blockchain\chain_db.py:191, in ChainDB.execute(self, *args, * *kaargs)
        189 def execute(self, *args, * *kaargs):
        190     """Forward execute code to the SQL DB"""
    --> 191     return self.sql_db.execute(*args, * *kaargs)
    
    File D:\Projects\NervHome\nvh\core\postgresql_db.py:62, in PostgreSQLDB.execute(self, code, data, many, commit)
         60         c.executemany(code, data)
         61     else:
    ---> 62         c.execute(code, data)
         63 if commit:
         64     self.conn.commit()
    
    UndefinedTable: relation "transactions" does not exist
    LINE 1: SELECT to_id from transactions WHERE sig = ANY(ARRAY[2146658...
  • ⇒ Yeah, makes sense: I don't have any “transactions” table anymore in the ChainDB: instead I'm storing that directly in the TransactionsDB, so fixing it.
  • OK: now finding some routers 👍! But next we try to get the factories, and the first router on the list doesn't seem to have a public ABI 😭
  • ⇒ hey! Let's try to use my new “automatic ABI” generation I described in my previous article!
  • Except that I need to extend a bit my ABIBuilder implementation to support retrieving outputs since what I need here is a method such as this:
      {
        "inputs": [],
        "name": "factory",
        "outputs": [
          {
            "internalType": "address",
            "name": "",
            "type": "address"
          }
        ],
        "stateMutability": "view",
        "type": "function"
      },
  • ⇒ Here is my updated method to parse my custom function declaration with support for output types and state mutability now:
        def add_function(self, fname):
            """Add a function to the abi"""
            # fname will be a full function declaration, so we parse it as such:
            full_sig = fname
            idx = fname.find("(")
            idx2 = fname.find(")")
            self.check(idx > 0 and idx2 > 0, "Invalid function declaration: %s", full_sig)
            args = fname[idx + 1 : idx2]
            fname = fname[:idx]
            mods = fname[:idx2]
    
            state = "nonpayable"
            for sname in ["view", "pure", "payable"]:
                if sname in mods:
                    state = sname
    
            # in the function name part we may still have the return value type first and
            # then the actual function name, so we check if we have a space in the name:
            outputs = []
            if " " in fname:
                parts = fname.split(" ")
                self.check(len(parts), "Unexpected function signature: '%s'", full_sig)
    
                # Update the function name:
                fname = parts[1]
    
                # We may still split the return types on commas here:
                rets = parts[0].split(",")
                for idx, rname in enumerate(rets):
                    desc = {"internalType": rname, "name": f"out{idx}", "type": rname}
                    outputs.append(desc)
    
            logger.debug("Adding function '%s' with args '%s'", fname, args)
            inputs = []
            args = [] if args == "" else args.split(",")
    
            for idx, aname in enumerate(args):
                inputs.append({"internalType": aname, "name": f"arg{idx}", "type": aname})
    
            desc = {"inputs": inputs, "name": fname, "outputs": outputs, "stateMutability": state, "type": "function"}
            self.abi.append(desc)
  • Now trying to use that to get the factory from our routers… In fact this is largely simplifying the code used to get the factories 😎:
        logger.info("Retrieving factory addresses...")
        
        funcs = ["address factory()"]
    
        for desc in descs:
            addr = desc["router_address"]
            # logger.info("Current router: %s", addr)
    
            contract = chain.get_contract(addr, funcs=funcs)
    
            try:
                factory_addr = contract.call_function("factory")
                logger.info("Factory for router %d: %s", desc["router_id"], factory_addr)
                desc["factory_address"] = factory_addr
            except NVPCheckError:
                logger.error("Cannot retrieve factory for router %s", addr)
                desc["factory_address"] = None
    
  • Whaaouu, that's working so well 😍, here are the outputs:
    2022/07/07 07:26:02 [find_uniswap_routers] INFO: Searching for routers in 2068 transactions...
    2022/07/07 07:26:02 [find_uniswap_routers] INFO: Found 7 routers:
    2022/07/07 07:26:02 [find_uniswap_routers] INFO:   - 0x2CB45Edb4517d5947aFdE3BEAbF95A582506858B: 1784
    2022/07/07 07:26:02 [find_uniswap_routers] INFO:   - 0xa3a1eF5Ae6561572023363862e238aFA84C72ef5: 242
    2022/07/07 07:26:02 [find_uniswap_routers] INFO:   - 0xcCC7B6CD8764E84Be19BD13b25850C4ac24aa2C0: 20
    2022/07/07 07:26:02 [find_uniswap_routers] INFO:   - 0xBaE0d7DFcd03C90EBCe003C58332c1346A72836A: 15
    2022/07/07 07:26:02 [find_uniswap_routers] INFO:   - 0xE52854C86fb64B04dd1D77AD542876dDC040c4f4: 3
    2022/07/07 07:26:02 [find_uniswap_routers] INFO:   - 0xA1B1742e9c32C7cAa9726d8204bD5715e3419861: 2
    2022/07/07 07:26:02 [find_uniswap_routers] INFO:   - 0x3d99B2F578d94f61adcD899DE55F2991522cefE1: 2
    2022/07/07 07:26:02 [find_uniswap_routers] INFO: Retrieving factory addresses...
    2022/07/07 07:26:03 [find_uniswap_routers] INFO: Factory for router 2: 0xc66F594268041dB60507F00703b152492fb176E7
    2022/07/07 07:26:03 [find_uniswap_routers] INFO: Factory for router 187: 0x7928D4FeA7b2c90C732c10aFF59cf403f0C38246
    2022/07/07 07:26:04 [find_uniswap_routers] INFO: Factory for router 648: 0x78f406B41C81eb4144C321ADa5902BBF5de28538
    2022/07/07 07:26:04 [find_uniswap_routers] INFO: Factory for router 548: 0x34484b4E416f5d4B45D4Add0B6eF6Ca08FcED8f1
    2022/07/07 07:26:05 [find_uniswap_routers] INFO: Factory for router 1042: 0x70dcc803784bd540d9f2A29b99C4df0130E348BB
    2022/07/07 07:26:05 [find_uniswap_routers] INFO: Factory for router 738: 0xC5E1DaeC2ad401eBEBdd3E32516d90Ab251A3aA3
    2022/07/07 07:26:06 [find_uniswap_routers] INFO: Factory for router 789: 0x34696b6cE48051048f07f4cAfa39e3381242c3eD
And we see that the 3 routers reported in the reference youtube video above are found, all right 👍
  • Setting up config for TriSolaris:
        "TriSolaris": {
          "chain": "aurora",
          "id": 14,
          "name": "TriSolaris",
          "router": "0x2CB45Edb4517d5947aFdE3BEAbF95A582506858B",
          "factory": "0xc66F594268041dB60507F00703b152492fb176E7",
          "flash_loan_supported": true,
          "swap_fee_points": 30,
          "default_slippage": 0.1,
          "swap_max_gas": 550000
        }
  • But then, naturally, failure 😆:
    $ nvp bchain update-pairs trisolaris
    Traceback (most recent call last):
    File "D:\Projects\NervHome\nvh\crypto\blockchain\uniswap_base.py", line 49, in __init__
    self.factory = self.chain.get_contract(self.config["factory"])
    File "D:\Projects\NervHome\nvh\crypto\blockchain\evm_blockchain.py", line 507, in get_contract
    contract = EVMSmartContract(self, address, abi_file, abi_fallback=abi_fallback, abi=abi)
    File "D:\Projects\NervHome\nvh\crypto\blockchain\evm_smart_contract.py", line 35, in __init__
    abi_file = self.download_abi(abi_fallback)
    File "D:\Projects\NervHome\nvh\crypto\blockchain\evm_smart_contract.py", line 75, in download_abi
    self.check(msg[0:2] == "OK", "Cannot retrieve contract ABI: %s (url=%s)", data, url)
    File "D:\Projects\NervProj\nvp\nvp_object.py", line 73, in check
    raise NVPCheckError(fmt % args)
    nvp.nvp_object.NVPCheckError: Cannot retrieve contract ABI: {'status': '0', 'message': 'NOTOK', 'result': 'Contract source code not verified'} (url=https://api.aurorascan.dev/api?module=contract&action=getabi&address=0xc66F594268041dB60507F00703b152492fb176E7)
  • The damn factory contract is not verified… So once more, I need my ABIBuilder here 👍!
  • Checking my current code, the only functions I need from the factory are:
    • uint256 getPairFees(address) view (⇒ that one may not be available ?)
    • uint256 allPairsLength() view
    • address allPairs(uint256) view
  • On the router we need the functions:
    • uint256[] swapExactTokensForETH(uint256,uint256,address[],address,uint256)
    • swapExactTokensForTokensSupportingFeeOnTransferTokens(uint256,uint256,address[],address,uint256)
  • So now using this init sequence in our Uniswap compatible dex classes:
            self.use_abi_builds = self.config.get("use_abi_builds", False)
    
            factory_funcs = None
            router_funcs = None
    
            if self.use_abi_builds:
                factory_funcs = [
                    "uint256 getPairFees(address) view",
                    "uint256 allPairsLength() view",
                    "address allPairs(uint256) view",
                ]
    
                router_funcs = [
                    "uint256[] swapExactTokensForETH(uint256,uint256,address[],address,uint256)",
                    "swapExactTokensForTokensSupportingFeeOnTransferTokens(uint256,uint256,address[],address,uint256)",
                ]
    
            self.factory = self.chain.get_contract(self.config["factory"], funcs=factory_funcs)
    
            # Load the pancake router:
            self.router = self.chain.get_contract(self.config["router"], funcs=router_funcs)
    
  • ⇒ This seems to do the trick as I'm now collecting about 1076 pairs on TriSolaris ✌️
  • Let's add a few more of those dexes now:
        "TriSolarisSwap": {
          "chain": "aurora",
          "id": 14,
          "name": "TriSolarisSwap",
          "router": "0x2CB45Edb4517d5947aFdE3BEAbF95A582506858B",
          "factory": "0xc66F594268041dB60507F00703b152492fb176E7",
          "flash_loan_supported": true,
          "use_abi_builder": true,
          "swap_fee_points": 30,
          "default_slippage": 0.1,
          "swap_max_gas": 550000
        },
        "WannaSwap": {
          "chain": "aurora",
          "id": 15,
          "name": "WannaSwap",
          "router": "0xa3a1eF5Ae6561572023363862e238aFA84C72ef5",
          "factory": "0x7928D4FeA7b2c90C732c10aFF59cf403f0C38246",
          "flash_loan_supported": true,
          "use_abi_builder": true,
          "swap_fee_points": 30,
          "default_slippage": 0.1,
          "swap_max_gas": 550000
        },
        "AuroraAnonSwap1": {
          "chain": "aurora",
          "id": 16,
          "name": "AuroraAnonSwap1",
          "router": "0xcCC7B6CD8764E84Be19BD13b25850C4ac24aa2C0",
          "factory": "0x78f406B41C81eb4144C321ADa5902BBF5de28538",
          "flash_loan_supported": true,
          "use_abi_builder": true,
          "swap_fee_points": 30,
          "default_slippage": 0.1,
          "swap_max_gas": 550000
        },
        "AuroraAnonSwap2": {
          "chain": "aurora",
          "id": 17,
          "name": "AuroraAnonSwap2",
          "router": "0xBaE0d7DFcd03C90EBCe003C58332c1346A72836A",
          "factory": "0x34484b4E416f5d4B45D4Add0B6eF6Ca08FcED8f1",
          "flash_loan_supported": true,
          "use_abi_builder": true,
          "swap_fee_points": 30,
          "default_slippage": 0.1,
          "swap_max_gas": 550000
        },
        "AuroraAnonSwap3": {
          "chain": "aurora",
          "id": 18,
          "name": "AuroraAnonSwap3",
          "router": "0xE52854C86fb64B04dd1D77AD542876dDC040c4f4",
          "factory": "0x70dcc803784bd540d9f2A29b99C4df0130E348BB",
          "flash_loan_supported": true,
          "use_abi_builder": true,
          "swap_fee_points": 30,
          "default_slippage": 0.1,
          "swap_max_gas": 550000
        },
        "AuroraSwap": {
          "chain": "aurora",
          "id": 19,
          "name": "AuroraSwap",
          "router": "0xA1B1742e9c32C7cAa9726d8204bD5715e3419861",
          "factory": "0xC5E1DaeC2ad401eBEBdd3E32516d90Ab251A3aA3",
          "flash_loan_supported": true,
          "use_abi_builder": true,
          "swap_fee_points": 30,
          "default_slippage": 0.1,
          "swap_max_gas": 550000
        },
        "AmaterasuSwap": {
          "chain": "aurora",
          "id": 20,
          "name": "AmaterasuSwap",
          "router": "0x3d99B2F578d94f61adcD899DE55F2991522cefE1",
          "factory": "0x34696b6cE48051048f07f4cAfa39e3381242c3eD",
          "flash_loan_supported": true,
          "use_abi_builder": true,
          "swap_fee_points": 25,
          "default_slippage": 0.1,
          "swap_max_gas": 550000
        }
  • Arrff, now I'm getting a lot of errors again with the gas-price retrieval on Aurora:
    lastest outputs:
    File "/mnt/data1/dev/projects/NervProj/.pyenvs/bsc_env/lib/python3.10/site-packages/web3/middleware/buffered_gas_estimate.py", line 40, in middleware
    return make_request(method, params)
    File "cytoolz/functoolz.pyx", line 250, in cytoolz.functoolz.curry.__call__
    File "/mnt/data1/dev/projects/NervProj/.pyenvs/bsc_env/lib/python3.10/site-packages/web3/middleware/formatting.py", line 76, in apply_formatters
    response = make_request(method, params)
    File "/mnt/data1/dev/projects/NervProj/.pyenvs/bsc_env/lib/python3.10/site-packages/web3/middleware/exception_retry_request.py", line 104, in middleware
    return make_request(method, params)
    File "/mnt/data1/dev/projects/NervProj/.pyenvs/bsc_env/lib/python3.10/site-packages/web3/providers/rpc.py", line 88, in make_request
    raw_response = make_post_request(
    File "/mnt/data1/dev/projects/NervProj/.pyenvs/bsc_env/lib/python3.10/site-packages/web3/_utils/request.py", line 48, in make_post_request
    response = session.post(endpoint_uri, data=data, *args, **kwargs)  # type: ignore
    File "/mnt/data1/dev/projects/NervProj/.pyenvs/bsc_env/lib/python3.10/site-packages/requests/sessions.py", line 577, in post
    return self.request('POST', url, data=data, json=json, **kwargs)
    File "/mnt/data1/dev/projects/NervProj/.pyenvs/bsc_env/lib/python3.10/site-packages/requests/sessions.py", line 529, in request
    resp = self.send(prep, **send_kwargs)
    File "/mnt/data1/dev/projects/NervProj/.pyenvs/bsc_env/lib/python3.10/site-packages/requests/sessions.py", line 645, in send
    r = adapter.send(request, **kwargs)
    File "/mnt/data1/dev/projects/NervProj/.pyenvs/bsc_env/lib/python3.10/site-packages/requests/adapters.py", line 532, in send
    raise ReadTimeout(e, request=request)
    requests.exceptions.ReadTimeout: HTTPSConnectionPool(host='mainnet.aurora.dev', port=443): Read timed out. (read timeout=10)
    2022/07/07 10:29:17 [nvh.crypto.blockchain.evm_blockchain] INFO: Block 69303942: num_tx=1, mode=0.070, mean=0.070, dev=0.000
  • ⇒ I need to handle these gracefully or my rocketchat messages every 3 minutes will drive me crazy lol (or more crazy than am I already at least 🤣).
  • OK, so here is the updated procedure to get a block in a “relatively safe” way (ie. catching some of those potential exceptions and retrying in that case):
        def get_block(self, block_num=None, full_tx=False):
            """Retrieve the current block"""
            if block_num is None:
                block_num = "latest"
    
            return self.safe_call(
                lambda: self.web3.eth.get_block(block_num, full_transactions=full_tx),
                [requests.exceptions.ReadTimeout, urllib3.exceptions.ReadTimeoutError, ValueError],
            )
  • Back to the aurora blockchain now, I need to deploy the required contracts to support the Arb Monitoring system.
  • But first, of course, I need some initial funds there, so let's see how the bridge works.
  • https://app.multichain.org/#/router also seems interesting but cannot find support for aurora there ? 🤔
  • Okay… so I just send 0.02 ETH to my Arb dedicated address, let's check if I received anything on the aurora explorer (https://aurorascan.dev/) ⇒ yp! it seems I received it.
  • Let's now check the balance from the command line:
    $ nvp bchain balance -a evm_arb -c aurora
    2022/07/08 15:58:29 [__main__] INFO: Current balance: 0.020000 ETH
  • Now compiling the existing contracts first to be sure we are good:
    $ nvp solc compile
    2022/07/08 15:59:36 [__main__] INFO: Auto selecting solidity compiler '0.6.6'
    2022/07/08 15:59:36 [__main__] INFO: Auto installing solidity compiler version '0.6.6'
    2022/07/08 15:59:36 [nvp.core.tools] INFO: Downloading file from https://solc-bin.ethereum.org/windows-amd64/solc-windows-amd64-v0.6.6+commit.6c089d02.zip...
    [==================================================] 7196296/7196296 100.000%
    2022/07/08 15:59:45 [__main__] INFO: Compiling D:\Projects\NervHome\contracts\sources\FlashArbV4.sol...
    2022/07/08 15:59:45 [__main__] INFO: Auto selecting solidity compiler '0.6.6'
    2022/07/08 15:59:45 [__main__] INFO: Compiling D:\Projects\NervHome\contracts\sources\FlashArbV5.sol...
    2022/07/08 15:59:45 [__main__] INFO: Auto selecting solidity compiler '0.6.6'
    2022/07/08 15:59:45 [__main__] INFO: Compiling D:\Projects\NervHome\contracts\sources\GetReservesV2.sol...
    2022/07/08 15:59:45 [__main__] INFO: Auto selecting solidity compiler '0.6.6'
    2022/07/08 15:59:45 [__main__] INFO: Compiling D:\Projects\NervHome\contracts\sources\PairCheckerV5.sol...
    2022/07/08 15:59:45 [__main__] INFO: Auto selecting solidity compiler '0.6.6'
    2022/07/08 15:59:45 [__main__] INFO: Compiling D:\Projects\NervHome\contracts\sources\PairCheckerV6.sol...
    2022/07/08 15:59:45 [__main__] INFO: Auto selecting solidity compiler '0.6.6'
    2022/07/08 15:59:45 [__main__] INFO: Compiling D:\Projects\NervHome\contracts\sources\TestPair.sol...
    2022/07/08 15:59:45 [__main__] INFO: Auto selecting solidity compiler '0.6.6'
    2022/07/08 15:59:45 [__main__] INFO: Compiling D:\Projects\NervHome\contracts\sources\TestToken.sol...
  • Next we deploy the needed contracts:
    • First the GetReservesV2:
      $ nvp bchain deploy -c aurora -a evm_arb GetReservesV2
    • Note: I actually got the exact same address as GetReservesV2 contract I deployed on AVAX before! That's surprising, I didn't expected it.
    • Then the PairCheckerV6:
      $ nvp bchain deploy -c aurora -a evm_arb PairCheckerV6
    • ⇒ Same address again.
    • And finally the FlashArb contract itself:
      $ nvp bchain deploy -c aurora -a evm_arb FlashArbV5
    • ⇒ Ahh, a different address this time 🙃 I really have no idea why lol.
  • OK, what's next 🤔 ? We need some funds for the PairChecker contract, but actually that part should be handled automatically now. So let's just have a first try and see what happens 😅:
    $ nvp arbman monitor-arbs -c aurora
    2022/07/08 17:22:11 [nvh.crypto.blockchain.evm_blockchain] INFO: Keeping 733/1076 quotable pairs.
    2022/07/08 17:22:11 [nvh.crypto.blockchain.evm_blockchain] INFO: Found 0 arb compatible pairs
    2022/07/08 17:22:11 [__main__] INFO: Collected 0 arb pairs
    2022/07/08 17:22:11 [__main__] INFO: Min profit value: 0.000100
    2022/07/08 17:22:11 [__main__] INFO: Wrapping some WETH for PairChecker setup...
    2022/07/08 17:22:12 [nvh.crypto.blockchain.evm_blockchain] INFO: Trial 1: Gas estimate for transaction: 6721975
    2022/07/08 17:22:12 [nvh.crypto.blockchain.evm_blockchain] INFO: Error while trying to perform operation: cannot convert float NaN to integer
    Traceback (most recent call last):
    File "D:\Projects\NervHome\nvh\crypto\blockchain\arbitrage_manager.py", line 929, in <module>
    comp.run()
    File "D:\Projects\NervProj\nvp\nvp_component.py", line 93, in run
    res = self.process_command(cmd)
    File "D:\Projects\NervHome\nvh\crypto\blockchain\arbitrage_manager.py", line 884, in process_command
    self.monitor_arbitrages(chain)
    File "D:\Projects\NervHome\nvh\crypto\blockchain\arbitrage_manager.py", line 130, in monitor_arbitrages
    self.update_quote_token_values()
    File "D:\Projects\NervHome\nvh\crypto\blockchain\arbitrage_manager.py", line 151, in update_quote_token_values
    ntoken.deposit(200000 * 20)
    File "D:\Projects\NervHome\nvh\crypto\blockchain\erc20_token.py", line 186, in deposit
    self.check(receipt is not None, "Cannot deposit %d units of %s ", amount, self.symbol())
    File "D:\Projects\NervProj\nvp\nvp_object.py", line 73, in check
    raise NVPCheckError(fmt % args)
    nvp.nvp_object.NVPCheckError: Cannot deposit 4000000 units of WETH
  • Hmmm, of course lol ⇒ Let's figure this out. I'm sure this is related to the gas price estimate process…
  • Yep, jsut calling this in jupyter:
    chain.compute_gas_price_estimate('default')
  • I get:
    ---------------------------------------------------------------------------
    ValueError                                Traceback (most recent call last)
    Input In [2], in <cell line: 1>()
    ----> 1 chain.compute_gas_price_estimate('default')
    
    File D:\Projects\NervHome\nvh\crypto\blockchain\evm_blockchain.py:736, in EVMBlockchain.compute_gas_price_estimate(self, mode)
        731 self.check(totw > 0.0, "Invalid total weights.")
        733 price = (
        734     emas["mode"] * weights[0] + emas["mean"] * weights[1] + (emas["mean"] + emas["std_dev"]) * weights[2]
        735 ) / totw
    --> 736 price = math.floor(price * 1000) / 1000
        738 # Clamp if bigger than max gas price:
        739 maxp = self.max_gas_price
    
    ValueError: cannot convert float NaN to integer
  • Ohh, okay, I think I get it now: this has to do with the fact that we request a given “period” to compute the gas price EMAs, but on this blockchain, we may actually not have the expected number of entries if we don't look far enough in the past (since some blocks have 0 txs and are not taken into account) ⇒ I think we ned to increase to timeoffset to search for blocks. and in the worst case also rectify the period used in the end for the EMA computation (with a warning message in that case)
  • Okay, so, the thing is, we really ned to bump the max gas value very high (to 10000000) on aurora to get the transactions to work but eventually, I got the arb monitor to run correctly, except that, I have no arb pair for the moment since I only collected the pairs from one dex lol let's update the other ones:
    $ nvp bchain update-pairs -c aurora
  • And now trying the arb monitoring again:
    $ nvp arbman monitor-arbs -c aurora
    2022/07/08 21:22:05 [nvh.crypto.blockchain.evm_blockchain] INFO: Keeping 906/1470 quotable pairs.
    2022/07/08 21:22:05 [nvh.crypto.blockchain.evm_blockchain] INFO: Found 62 arb compatible pairs
    2022/07/08 21:22:05 [__main__] INFO: Collected 62 arb pairs
    2022/07/08 21:22:05 [__main__] INFO: Min profit value: 0.000100
    2022/07/08 21:22:05 [__main__] INFO: Quote token WETH value: 1 WETH
    2022/07/08 21:22:06 [__main__] INFO: PairChecker balance: 200000 WETH
    2022/07/08 21:22:06 [__main__] INFO: Quote token NEAR value: 0.00292044 WETH
    2022/07/08 21:22:06 [__main__] INFO: PairChecker balance: 200000 NEAR
    2022/07/08 21:23:03 [__main__] INFO: 4 pair reserves collected in 0.1087 secs
  • So now this seems to be running fine, but there is not much happening there for the moment, I guess I need to wait and see.
  • Meanwhile, I would really like to get an idea of what are the list of the most significant quote tokens on that blockchain before deciding to include them in the arb monitoring session: should be fairly easy to add a function for that.
  • Added this helper command:
            if cmd == "list-quote-tokens":
                chain_name = self.get_param("chain")
                chain: EVMBlockchain = self.get_component(f"{chain_name}_chain")
                account = self.get_param("account")
                chain.set_account(account)
    
                num = self.get_param("count")
                _, qtokens = chain.collect_arb_compatible_pairs(num)
                for idx, addr in enumerate(qtokens):
                    token = chain.get_token(addr)
                    logger.info("Quote token %d: %s (%s)", idx + 1, token.symbol(), addr)
    
                return True
  • And this will produce this kind of result:
    $ nvp arbman list-quote-tokens -c aurora -n 10
    2022/07/08 21:42:53 [nvh.crypto.blockchain.evm_blockchain] INFO: Keeping 1295/1470 quotable pairs.
    2022/07/08 21:42:53 [nvh.crypto.blockchain.evm_blockchain] INFO: Found 103 arb compatible pairs
    2022/07/08 21:42:53 [__main__] INFO: Quote token 1: WETH (0xC9BdeEd33CD01541e1eeD10f90519d2C06Fe3feB)
    2022/07/08 21:42:53 [__main__] INFO: Quote token 2: NEAR (0xC42C30aC6Cc15faC9bD938618BcaA1a1FaE8501d)
    2022/07/08 21:42:53 [__main__] INFO: Quote token 3: AURORA (0x8BEc47865aDe3B172A928df8f990Bc7f2A3b9f79)
    2022/07/08 21:42:53 [__main__] INFO: Quote token 4: USDC (0xB12BFcA5A55806AaF64E99521918A4bf0fC40802)
    2022/07/08 21:42:53 [__main__] INFO: Quote token 5: USDT (0x4988a896b1227218e4A686fdE5EabdcAbd91571f)
    2022/07/08 21:42:53 [__main__] INFO: Quote token 6: TRI#2 (0xFa94348467f64D5A457F75F8bc40495D33c65aBB)
    2022/07/08 21:42:53 [__main__] INFO: Quote token 7: DAI (0xe3520349F477A5F6EB06107066048508498A291b)
    2022/07/08 21:42:53 [__main__] INFO: Quote token 8: WBTC (0xF4eB217Ba2454613b15dBdea6e5f22276410e89e)
    2022/07/08 21:42:53 [__main__] INFO: Quote token 9: atUST (0x5ce9F0B6AFb36135b5ddBF11705cEB65E634A9dC)
    2022/07/08 21:42:53 [__main__] INFO: Quote token 10: WANNA (0x7faA64Faf54750a2E3eE621166635fEAF406Ab22)
  • From there , I'd say I should quick the first 8 quote tokens from that list. Arrff crap: except that we have the same issue as on avalanche with USDC: not enough decimals for that one! So let's use one 3 quotes then for the moment.
  • Total unrelated topic from what we are discussing above, but anyway, I'm taking a few minutes to add support for that additionnal signature in my block processing system. Example transaction on Avalanche: 0xf32ca8b4e078f7105dbc7aa0a67a84c9aaeb2aab4e5a9686008a9f5d480913e6
  • ⇒ This method was already discussed before, so let's jsut add it now.
  • Here is the method that will be used to parse that type of operation:
        def parse_1inch_swap_op(self, regdata, _sig=None, txh=None):
            """Parse a 1inch swapoperation input data"""
            # regdata should contain the data for:
            # (uint256,uint256,address[],address,uint256)
    
            regs = self.parse_register_data(regdata, txh)
    
            if len(regs) < 7:
                logger.warning("Invalid num regs: %d (hash=%s)", len(regs), txh)
                return None
    
            # Extract the data:
            token_in = self.chain.to_checksum_address(regs[1][-40:])
            token_out = self.chain.to_checksum_address(regs[2][-40:])
            to_id = self.chain.to_checksum_address(regs[4][-40:])
            am_in = float(int(regs[5], 16))
            am_out = float(int(regs[6], 16))
    
            if am_in < 0.0 or am_out < 0.0 or am_in > 1e74 or am_out > 1e74:
                logger.warning("Invalid amounts %f or %f in tx (hash=%s)", am_in, am_out, txh)
                # utl.send_rocketchat_message(f"Invalid amounts in tx {txh}")
                return None
    
            return {
                "amount_in": am_in,
                "amount_out": am_out,
                "token_in": self.chain.get_address_id(token_in),
                "token_out": self.chain.get_address_id(token_out),
                "to_id": self.chain.get_address_id(to_id),
            }
  • I now have my ArbMonitoring system running on Aurora, but so far, I didn't get any Arb opportunity as there is really nothing happening on that network currently, so I think I just need to wait more and run the monitoring system a few more times now and then, until I get another issue reported 😆, We'll see. Let's call it a day for this session anyway.
  • blog/2022/0710_aurora_arb_monitoring.txt
  • Last modified: 2022/07/10 11:35
  • by 127.0.0.1