View this blog in it’s original form on Jare.cloud!

Awhile ago I wrote this:

const io = require('socket.io-client');
const request = require('request');
var socket = io.connect("https://socket.etherdelta.com", {
    transports: ['websocket']
});

//starting bal 0.329945342678081181 Ether






socket.on("connect", function() {
    socket.emit("getMarket", {
        user: "0x8ebA329784974b96EC6293DD83bf462651BB75E6"
    })
});
const xpath = require('xpath');
const parse5 = require('parse5');
const xmlser = require('xmlserializer');
const dom = require('xmldom').DOMParser;

var Web3 = require('web3');
var web3 = new Web3();
web3.setProvider(new web3.providers.HttpProvider("http://localhost:8545"));
var totleabi = JSON.parse('[{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"handler","type":"address"},{"name":"allowed","type":"bool"}],"name":"setHandler","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"tokenAddresses","type":"address[]"},{"name":"buyOrSell","type":"bool[]"},{"name":"amountToObtain","type":"uint256[]"},{"name":"amountToGive","type":"uint256[]"},{"name":"tokenForOrder","type":"address[]"},{"name":"exchanges","type":"address[]"},{"name":"orderAddresses","type":"address[8][]"},{"name":"orderValues","type":"uint256[6][]"},{"name":"exchangeFees","type":"uint256[]"},{"name":"v","type":"uint8[]"},{"name":"r","type":"bytes32[]"},{"name":"s","type":"bytes32[]"}],"name":"executeOrders","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"handlerWhitelist","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"MAX_EXCHANGE_FEE_PERCENTAGE","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"proxy","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"previousOwner","type":"address"},{"indexed":true,"name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"}]')

var totle = web3.eth.contract(totleabi).at("0xd94c60e2793ad587400d86e4d6fd9c874f0f79ef");
web3.eth.defaultAccount = web3.eth.accounts[5];

socket.on("market", function(data) {

    setInterval(function() {
        console.log('lala');
        for (var d in data['returnTicker']) {
            fun(data['returnTicker'][d], data['returnTicker'].length)
        }
    }, 60000);
    setInterval(function() {
        console.log('lala arbbing');

        arbbing = false;
    }, 500000);
    console.log('lala');
    for (var d in data['returnTicker']) {
        fun(data['returnTicker'][d], data['returnTicker'].length)
    }
});
var arbbing = false;
var aTotal;
var bTotal;
var total = [];
var total2;
async function fun(d, t) {
    setTimeout(function() {
        var req = {
            "operationName": null,
            "variables": {
                "address": d.tokenAddr
            },
            "query": "query ($address: String) {\n  token(address: $address) {\n    address\n    name\n    price\n    priceChange {\n      change1h\n      change24h\n      change7d\n    }\n    bestPrice {\n      bid\n      ask\n    }\n    orders {\n      asks {\n        price\n        volume\n        exchangeId\n      }\n      bids {\n        price\n        volume\n        exchangeId\n      }\n    }\n    exchanges\n  }\n}\n"
        }

        request.post({
            url: 'https://services.totlesystem.com/graph',
            body: req,
            json: true
        }, function(error, response, body) {
            if (!error && response.statusCode == 200) {
                if (body.data.token != null) {
                    //console.log(body.data.token.bestPrice);
                    if (body.data.token.bestPrice.bid > (1.03 * body.data.token.bestPrice.ask)) {
                        //console.log(body.data.token.name + ' arb!');
                        //console.log(body.data.token.bestPrice.ask / body.data.token.bestPrice.bid);
                        aTotal = 0;
                        var aPrice;
                        var acount = 0;
                        for (var o in body.data.token.orders.asks) {

                            if (body.data.token.orders.asks[o].price <= body.data.token.bestPrice.bid) {
                                aTotal += parseFloat(body.data.token.orders.asks[o].volume)
                                aPrice = parseFloat(body.data.token.orders.asks[o].price)
                            }
                        }
                        bTotal = 0;
                        var bPrice;
                        var bcount = 0;
                        for (var o in body.data.token.orders.bids) {

                            if (body.data.token.orders.bids[o].price >= body.data.token.bestPrice.ask) {
                                bTotal += parseFloat(body.data.token.orders.bids[o].volume)
                                bPrice = parseFloat(body.data.token.orders.bids[o].price)
                            }

                        }
                        total2 = 0;
                        if (aTotal > bTotal) {
                            total2 = aTotal
                        } else {
                            total2 = bTotal
                        }
                        var token = body.data.token.address;
                        var name = body.data.token.name
                        total[token] = totaled(total2, bPrice);
                        if ((total[token] * bPrice) > (0.01 * Math.pow(10, 18))) { //100000000000000000
                            if (arbbing == false) {
                                console.log('arb!')

                                arbbing = true;
                                //console.log(body.data);;
                                console.log(token);
                                console.log(name);
                                request("https://etherscan.io/address/" + token + "#code", function(err, response, body) {
                                    if (err) {
                                        console.log('err');
                                        console.log(err);
                                    } else {
                                        // do stuff with body
                                        const html = body;
                                        //console.log(html);
                                        const document = parse5.parse(html.toString());
                                        const xhtml = xmlser.serializeToString(document);
                                        const doc = new dom().parseFromString(xhtml);
                                        const select = xpath.useNamespaces({
                                            "x": "http://www.w3.org/1999/xhtml"
                                        });
                                        const nodes = select('//*[@id="js-copytextarea2"]/text()', doc);
                                        //console.log(nodes);
                                        var contractABI = JSON.parse(nodes);
                                        var contract = web3.eth.contract(contractABI).at(token);
                                        if (contract.stopped) {
                                            var stopped = contract.stopped.call();
                                            if (stopped != true) {
                                                console.log(body.data.token.name + ' arb! ');

                                                buy(token, total[token], contract);
                                            } else {
                                                arbbing = false
                                                console.log('stopped! eos?')
                                            }
                                        } else {
                                            console.log(name + ' arb! ');

                                            buy(token, total[token], contract);
                                        }
                                    }
                                });

                            }
                        }
                    }
                }
            }
        })


    }, Math.random() * t * 200);
}
async function buy(token, total, contract) {

    var reqbuy = {
        "buys": [{
            "token": token,
            "amount": total
        }],
        "address": "0x8ebA329784974b96EC6293DD83bf462651BB75E6"
    }
    request.post({
        url: 'https://services.totlesystem.com/suggester',
        body: reqbuy,
        json: true
    }, function(error, response, body) {
        if (!error && response.statusCode == 200) {
            console.log(body);
            var tx = totle.executeOrders(
                body.response.orders[0],
                body.response.orders[1],
                body.response.orders[2],
                body.response.orders[3],
                body.response.orders[4],
                body.response.orders[5],
                body.response.orders[6],
                body.response.orders[7],
                body.response.orders[8],
                body.response.orders[9],
                body.response.orders[10],
                body.response.orders[11], {
                    value: (body.response.ethValue),
                    from: "0x8ebA329784974b96EC6293DD83bf462651BB75E6",
                    gas: 400000,
                    gasPrice: "6000000000"
                })
            console.log(tx);
            approve(tx, token, total, contract);
        }
    })


}

function totaled(total, price) {
    if ((parseFloat(total) * parseFloat(price)) > (1.05 * parseFloat(web3.eth.getBalance("0x8ebA329784974b96EC6293DD83bf462651BB75E6")))) {
        var atotal = total / 1.3
        totaled(parseFloat(atotal), parseFloat(price))
    }
    return parseFloat(total)

}
async function approve(tx, token, total, contract) {
    web3.eth.getTransaction(tx, function(err, receipt) {
        if (err) {
            console.log(err);
        }
        console.log(receipt.blockNumber);
        if (receipt.blockNumber == null) {
            setTimeout(function() {
                approve(tx, token, total, contract);
            }, 30000);
        } else {
            var approve2 = contract.approve("0xd94c60e2793ad587400d86e4d6fd9c874f0f79ef", (total), {
                from: "0x8ebA329784974b96EC6293DD83bf462651BB75E6",
                gas: 250000,
                gasPrice: "6000000000"
            })
            sell(approve2, token, total, contract);
        }
    })
}
async function sell(tx, token, total, contract) {
    console.log(tx);
    web3.eth.getTransaction(tx, function(err, receipt) {
        if (err) {
            console.log(err);
        }
        console.log(receipt.blockNumber);
        if (receipt.blockNumber == null) {
            setTimeout(function() {
                sell(tx, token, total, contract);
            }, 30000);
        } else {
            arbbing = false;
            var reqbuy2 = {
                "sells": [{
                    "token": token,
                    "amount": total
                }],
                "address": "0x8ebA329784974b96EC6293DD83bf462651BB75E6"
            }
            request.post({
                url: 'https://services.totlesystem.com/suggester',
                body: reqbuy2,
                json: true
            }, function(error, response, body) {
                if (!error && response.statusCode == 200) {

                    console.log(body);
                    totle.executeOrders(
                        body.response.orders[0],
                        body.response.orders[1],
                        body.response.orders[2],
                        body.response.orders[3],
                        body.response.orders[4],
                        body.response.orders[5],
                        body.response.orders[6],
                        body.response.orders[7],
                        body.response.orders[8],
                        body.response.orders[9],
                        body.response.orders[10],
                        body.response.orders[11], {
                            value: (body.response.ethValue),
                            from: "0x8ebA329784974b96EC6293DD83bf462651BB75E6",
                            gas: 4000000,
                            gasPrice: "6000000000"
                        })
                }
            });
        }
    })
}

This code is now defunct as Totle removed some of the functionality on their GraphQL endpoint, so gathering the best bid best ask out of all their constituent Dexes – they have dozens! – is not possible.

I tried to find a competitor dex aggregator to come up with this information, and a lo and behold stumbled on 0x’s API docs. Now, I can combine some of the Totle API docs and 0x Radar Relay API docs to built something genius. First, let’s grab all of the tokens from each of their API endpoints we want to check for arbitrage opportunities.

    r = requests.get('https://api.totle.com/tokens').json()
    print(len(r['tokens']))
    
    r2 = requests.get('https://api.0x.org/swap/v0/tokens').json()
    print(len(r2['records']))

We’re then going to start a new thread for each of these tokens, and run a multithreaded Python script so that it can check in on more then a few tokens in the timeframe it would have been slowly moving through the list for my original code in nodejs.

   for token in r2['records']:
        time.sleep(0.8)
        
        thread.start_new_thread( print_time2, (token['symbol'], token, ) )
    
    
    
    for token in r['tokens']:
        time.sleep(0.8)
        thread.start_new_thread( print_time, (token['name'], token, ) )

Next, we’ll check our 0x threaded function.

In a forever while loop, we sleep for a random amount of time. This helps from getting 502 or 503 errors from the API endpoints. We’ll calculate the Math.pow(10,18) in decimal notation for grabbing our token swap info, and then make these two requests:

        r1 = requests.get('https://api.0x.org/swap/v0/quote?sellToken=WETH&buyToken=' + token['address'] + '&buyAmount=' +str(one)).json()
        r2 = requests.get('https://api.0x.org/swap/v0/quote?sellToken=WETH&buyToken=' + token['address'] + '&sellAmount=100000000000000000').json()
        

we want to buy 1 worth of a token or sell 1 worth of an ETH for that token. That gets us two very different prices, like:

0.001045214514088444
956.69872684175248674

If we take the inverse of the first price, and compare, we’d see

956.7418729561602

956.6987268417524

If the 2nd price here were higher (after we figure in fees for the transactions), we’d be able buy for the lower price and sell for the higher price and earn a % in difference.

Alas, there are any number of arbitrage opportunities that exist almost all the time, when you don’t count the fees in every case – some of these are for whopping 20, 30, 1200% return on equity if you can be the ‘first in and first out’ on these arbitrage trades.

My claim to fame will be to see if I can index the coins that existed in the program’s first runtime. We can assume each of these opportunities have already been acted on, and that there’s no more volume for us to pick up as an arbitrage trade. When I tried running the above fully-built end-to-end decentralized exchanges arbitrage script, I was constantly running into errors as the orders in the books had already been executed against. Using my new python threaded application I can keep a close eye on what these trading pairs are doing and I might well be in first on an opportunity!

Another opportunity exists within Totle’s partner program. Should I be able to build an SaaS that allows others to arbitrage for a small fee or subscribe monthly for my cut of the fees reduced, I can offer this arbitraging experience at more people – #Build, and people will come for free-money sure-bets.

Given that I’m also looking for opportunities within 0x relays, there could be a few different offerings for an SaaS arbitrageur’s marketplace.

See how the big boys are making $100s $1000s with decentralized arbitrage: https://stat.bloxy.info/superset/dashboard/arbitrage/?standalone=true