Shower Thought: Can I Resurrect and Old Failed Decentralized Exchange Arbitrage Bot I’d built, sell it as SaaS?

(real) Jarett Dunn
Feb 22 · 6 min read

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

Want to join? Check this new dexArb Telegram group: https://t.me/dexArb

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


Originally published at https://jare.cloud on February 22, 2020.

(real) Jarett Dunn

Written by

I’m just a poor boy trying to get heard — for fun & profit! Mental health consumer and advocate, extra hacker. https://jare.cloud Write jarettrsdunn@gmail.com!

More From Medium

Related reads

Related reads

Related reads

Related reads

Rock Creek Again

644

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade