diff --git a/docs.html b/docs.html index a33a0d2..5aa2d06 100644 --- a/docs.html +++ b/docs.html @@ -35,6 +35,10 @@

Contract source at github<

Market API

The API here is provided for convenience. This functionality is provided by reading data off the Ethereum blockchain. You can run this on your own machine against your own Ethereum node or against a public provider like Etherscan.
+ +
+ To run api, download from our github. When running api and you enter "0" ("npm start 0") the min eth for open orders will be zero instead of the standard 0.001. +

API source at github

$ git clone https://github.com/Ethex/api
 $ cd api
diff --git a/server.js b/server.js
index 21fdf8a..1165803 100644
--- a/server.js
+++ b/server.js
@@ -7,12 +7,23 @@ var abiDecode = require("abi-decoder");
 var BigNumber = require('bignumber.js');
 const fs = require('fs');
 var docs = fs.readFileSync("./docs.html");
+
 var tokenMap = {};
 for (var token of tokens) {
   tokenMap[token.address.toLowerCase()] = token;
 }
+var globalBlockNum; //global version for the latest block to ensure API only updates when a new block comes in
 abiDecode.addABI(ethexABI);
-var minWei = new BigNumber(0.001).times(Math.pow(10, 18));
+
+var minWei;
+let args = process.argv;
+if (args[2] === '0') {
+  minWei = new BigNumber(0);
+}
+else {
+  minWei = new BigNumber(0.001).times(Math.pow(10, 18));
+}
+
 
 var rpcCall = (method, params, result_callback) => {
   var optionspost = {
@@ -31,7 +42,7 @@ var rpcCall = (method, params, result_callback) => {
       result_callback(JSON.parse(data));
     });
   }).on('error', (e) => {
-    result_callback(null);
+    console.log('RPC Error: ', e);
   });
   var params_str = JSON.stringify(params);
   var post = '{"jsonrpc":"2.0","method":"' + method + '","params":' + params_str + ',"id":74}';
@@ -39,171 +50,228 @@ var rpcCall = (method, params, result_callback) => {
   req.end();
 }
 
-
 var getMarketData = (marketDataCallback) => {
   var marketData = {}; //last,lowestAsk,highestBid,volume,high24hr,low24hr
   var blocksPer24Hours = (60 * 60 * 24) / 14.5;  //14.5 seconds per blocks on average. might need to be adjusted depending on blocktime.
   rpcCall("eth_blockNumber", [], (results) => {
-    var lastBlock = parseInt(results.result);
-    var startBlock = 4078264;
-    var rangeBlock = lastBlock - blocksPer24Hours;
-    var startBlock = "0x" + startBlock.toString(16);
-    var lastBlock = "0x" + lastBlock.toString(16);
-    rpcCall("eth_getLogs", [{ "fromBlock": startBlock, "toBlock": lastBlock, "address": config.contract_address }], (results) => {
-      var logs = abiDecode.decodeLogs(results['result']);
-      var openOrders = {};
-      for (var log_idx in logs) {
-        //initialize
-        var log = logs[log_idx];
+    if (!globalBlockNum || globalBlockNum < parseInt(results.result)) {
+      var lastBlock = parseInt(results.result);
+      globalBlockNum = lastBlock;
+      var startBlock = 4078264;
+      var rangeBlock = lastBlock - blocksPer24Hours;
+      var startBlock = "0x" + startBlock.toString(16);
+      var lastBlock = "0x" + lastBlock.toString(16);
+      rpcCall("eth_getLogs", [{ "fromBlock": startBlock, "toBlock": lastBlock, "address": config.contract_address }], (results) => {
+        var logs = abiDecode.decodeLogs(results['result']);
+        var _openSells = {};
+        var _openBuys = {};
+        for (var log_idx in logs) {
+          //initialize
+          var log = logs[log_idx];
+
+          var tx = results['result'][log_idx];
+          var tokenData = { last: null, lowestAsk: null, highestBid: null, volume: new BigNumber(0), high24hr: null, low24hr: null };
+          var tokenAddress = null;
+          var token = null;
+          var tokAmount;
+          var weiAmount;
+          var orderHash;
+          var maker;
+          var orgTokAmount;
+          var orgWeiAmount;
 
-        var tx = results['result'][log_idx]
-        var tokenData = { last: null, lowestAsk: null, highestBid: null, volume: new BigNumber(0), high24hr: null, low24hr: null };
-        var tokenAddress = null;
-        var token = null;
-        var tokAmount;
-        var weiAmount;
-        var orderHash;
-        var maker;
-        if (log.name === "MakeSellOrder" ||
-          log.name === "MakeBuyOrder" ||
-          log.name === "TakeSellOrder" ||
-          log.name === "TakeBuyOrder" ||
-          log.name === "CancelBuyOrder" ||
-          log.name === "CancelSellOrder") {
-          tokenAddress = null;
-          for (var event of log.events) {
-            if (event.name === "token") {
-              tokenAddress = event.value;
+          if (log.name === "MakeSellOrder" ||
+            log.name === "MakeBuyOrder" ||
+            log.name === "TakeSellOrder" ||
+            log.name === "TakeBuyOrder" ||
+            log.name === "CancelBuyOrder" ||
+            log.name === "CancelSellOrder" ||
+            log.name === "ChangeBuyOrder" ||
+            log.name === "ChangeSellOrder") {
+            tokenAddress = null;
+            for (var event of log.events) {
+              if (event.name === "token") {
+                tokenAddress = event.value;
+              }
+              if (event.name === "tokenAmount") {
+                tokAmount = new BigNumber(event.value);
+              }
+              if (event.name === "weiAmount") {
+                weiAmount = new BigNumber(event.value);
+              }
+              if (event.name === "orderHash") {
+                orderHash = event.value;
+              }
+              if (event.name === "buyer") {
+                maker = event.value;
+              }
+              if (event.name === "seller") {
+                maker = event.value;
+              }
             }
-            if (event.name === "tokenAmount"){
-              tokAmount = new BigNumber(event.value);
+            if (!marketData[tokenAddress]) {
+              marketData[tokenAddress] = tokenData;
+            } else {
+              tokenData = marketData[tokenAddress];
             }
-            if (event.name === "weiAmount"){
-              weiAmount = new BigNumber(event.value);
+            token = tokenMap[tokenAddress];
+            var weiPerTok = weiAmount.dividedBy(tokAmount);
+            var price = null;
+            if (token)
+              price = weiPerTok.dividedBy(Math.pow(10, 18 - token.decimals));//ETH per full token
+            if (log.name === "MakeSellOrder") { //ask
+              
+              // for orders with the same hash, add the previously stored wei, tok amount, and original block number
+              if (_openSells[orderHash]) {
+                weiAmount = _openSells[orderHash].weiAmount.plus(weiAmount);
+                tokAmount = _openSells[orderHash].tokAmount.plus(tokAmount);
+              }
+              var order = { type: log.name, tokenAddress, price, weiPerTok, weiAmount, tokAmount, orgTokAmount: !orgTokAmount ? tokAmount : orgTokAmount, orgWeiAmount: !orgWeiAmount ? weiAmount : orgWeiAmount, maker, decimals: token.decimals, transactionHash: tx.transactionHash, blockNumber: parseInt(tx.blockNumber, 16) };
+
+              _openSells[orderHash] = order;
             }
-            if (event.name === "orderHash") {
-              orderHash = event.value;
+            if (log.name === "MakeBuyOrder") { //bid
+              // for orders with the same hash, add the previously stored wei and tok amount
+              if (_openBuys[orderHash]) {
+                weiAmount = _openBuys[orderHash].weiAmount.plus(weiAmount);
+                tokAmount = _openBuys[orderHash].tokAmount.plus(tokAmount);
+              }
+              var order = { type: log.name, tokenAddress, price, weiPerTok, weiAmount, tokAmount, orgTokAmount: !orgTokAmount ? tokAmount : orgTokAmount, orgWeiAmount: !orgWeiAmount ? weiAmount : orgWeiAmount, maker, decimals: token.decimals, transactionHash: tx.transactionHash, blockNumber: parseInt(tx.blockNumber, 16) };
+
+              _openBuys[orderHash] = order;
             }
-            if(event.name === 'buyer'){
-              maker = event.value;
+            if (log.name === "ChangeSellOrder") { //ask
+              var oldHash = log.events[0].value;
+              var order = { type: log.name, tokenAddress, price, weiPerTok, weiAmount, tokAmount, orgTokAmount: !orgTokAmount ? tokAmount : orgTokAmount, orgWeiAmount: !orgWeiAmount ? weiAmount : orgWeiAmount, maker };
+              _openSells[orderHash] = order;
+              delete _openSells[oldHash];
             }
-            if(event.name === 'seller'){
-              maker = event.value;
+            if (log.name === "ChangeBuyOrder") { //bid
+              var oldHash = log.events[0].value;
+              var order = { type: log.name, tokenAddress, price, weiPerTok, weiAmount, tokAmount, orgTokAmount: !orgTokAmount ? tokAmount : orgTokAmount, orgWeiAmount: !orgWeiAmount ? weiAmount : orgWeiAmount, maker };
+              _openBuys[orderHash] = order;
+              delete _openBuys[oldHash];
             }
-          }
-          if (!marketData[tokenAddress]) {
-            marketData[tokenAddress] = tokenData;
-          } else {
-            tokenData = marketData[tokenAddress];
-          }
-          token = tokenMap[tokenAddress];
-          var weiPerTok = weiAmount.dividedBy(tokAmount);
-          var price = null;
-          if (token)
-            price = weiPerTok.dividedBy(Math.pow(10, 18 - token.decimals));//ETH per full token
-          if (log.name === "MakeSellOrder") { //ask
-            var order = { type: log.name, tokenAddress, price, weiPerTok, weiAmount, tokAmount, maker };
-            openOrders[orderHash] = order;
-          }
-          if (log.name === "MakeBuyOrder") { //bid
-            var order = { type: log.name, tokenAddress, price, weiPerTok, weiAmount, tokAmount, maker };
-            openOrders[orderHash] = order;
-          }
-          if (log.name.startsWith("Take") && tx.blockNumber >= rangeBlock) {
-            tokenData.last = price;
-            if (tokenData.high24hr === null || price.isGreaterThan(tokenData.high24hr))
-              tokenData.high24hr = price;
-            if (tokenData.low24hr === null || price.isLessThan(tokenData.low24hr))
-              tokenData.low24hr = price;
-          }
-          if (log.name === "TakeSellOrder") { //last price,volume,high24hr,low24hr
+            if (log.name.startsWith("Take") && tx.blockNumber >= rangeBlock) {
+              tokenData.last = price;
+              if (tokenData.high24hr === null || price.gt(tokenData.high24hr))
+                tokenData.high24hr = price;
+              if (tokenData.low24hr === null || price.lt(tokenData.low24hr))
+                tokenData.low24hr = price;
+            }
+            if (log.name === "TakeSellOrder") { //last price,volume,high24hr,low24hr
 
-            for (var event of log.events) {
-              if (event.name === "totalTransactionWei") {
-                var totalTransactionWei = new BigNumber(event.value);
-                if (openOrders[orderHash]) {
-                  order = openOrders[orderHash];
-                  order.weiAmount = order.weiAmount.minus(totalTransactionWei);
-                  order.tokAmount = order.tokAmount.minus(totalTransactionWei.dividedBy(weiPerTok));
-                  if (tx.blockNumber >= rangeBlock)
-                    tokenData.volume = tokenData.volume.plus(totalTransactionWei.dividedBy(Math.pow(10, 18)));
+              for (var event of log.events) {
+                if (event.name === "totalTransactionWei") {
+                  var totalTransactionWei = new BigNumber(event.value);
+                  if (_openSells[orderHash]) {
+                    order = _openSells[orderHash];
+                    order.weiAmount = order.weiAmount.minus(totalTransactionWei);
+                    order.tokAmount = order.tokAmount.minus(totalTransactionWei.dividedBy(weiPerTok));
+                    if (tx.blockNumber >= rangeBlock)
+                      tokenData.volume = tokenData.volume.plus(totalTransactionWei.dividedBy(Math.pow(10, 18)));
+                  }
                 }
               }
             }
-          }
-          if (log.name === "TakeBuyOrder") { //last price,volume,high24hr,low24hr
-            for (var event of log.events) {
-              if (event.name === "totalTransactionTokens") {
-                var totalTransactionTokens = new BigNumber(event.value);
-                if (openOrders[orderHash]) {
-                  order = openOrders[orderHash];
-                  var totalTransactionWei = totalTransactionTokens.times(weiPerTok);
-                  order.tokAmount = order.tokAmount.minus(totalTransactionTokens);
-                  order.weiAmount = order.weiAmount.minus(totalTransactionTokens.times(weiPerTok));
-                  if (tx.blockNumber >= rangeBlock)
-                    tokenData.volume = tokenData.volume.plus(totalTransactionWei.dividedBy(Math.pow(10, 18)));
+            if (log.name === "TakeBuyOrder") { //last price,volume,high24hr,low24hr
+              for (var event of log.events) {
+                if (event.name === "totalTransactionTokens") {
+                  var totalTransactionTokens = new BigNumber(event.value);
+                  if (_openBuys[orderHash]) {
+                    order = _openBuys[orderHash];
+                    var totalTransactionWei = totalTransactionTokens.times(weiPerTok);
+                    order.tokAmount = order.tokAmount.minus(totalTransactionTokens);
+                    order.weiAmount = order.weiAmount.minus(totalTransactionTokens.times(weiPerTok));
+                    if (tx.blockNumber >= rangeBlock)
+                      tokenData.volume = tokenData.volume.plus(totalTransactionWei.dividedBy(Math.pow(10, 18)));
+                  }
                 }
               }
             }
+            if (log.name === "CancelBuyOrder") {
+              delete _openBuys[orderHash];
+            }
+            if (log.name === "CancelSellOrder") {
+              delete _openSells[orderHash];
+            }
+            //tokenData and tokenAddress are now initialized.
           }
-          if (log.name === "CancelBuyOrder") {
-            delete openOrders[orderHash];
-          }
-          if (log.name === "CancelSellOrder") {
-            delete openOrders[orderHash];
-          }
-          //tokenData and tokenAddress are now initialized.
-        }
 
-      }
-      //openOrders is the entire order book.
-      for (hash in openOrders) {
-        var openOrder = openOrders[hash];
-        if (openOrder.weiAmount.isLessThan(minWei)) {
-          delete openOrders[hash];
-          continue;
-        }
-        token = tokenMap[openOrder.tokenAddress];
-        tokenData = marketData[openOrder.tokenAddress];
-        if (openOrder.type === "MakeSellOrder" && (tokenData.lowestAsk === null || tokenData.lowestAsk.isGreaterThan(openOrder.price))) {
-          tokenData.lowestAsk = openOrder.price;
-        }
-        if (openOrder.type === "MakeBuyOrder" && (tokenData.highestBid === null || tokenData.highestBid.isLessThan(openOrder.price))) {
-          tokenData.highestBid = openOrder.price;
         }
-      }
-      //format for eth
-      var ethOpenOrders = {};
-      for (hash in openOrders) {
-        var openOrder = openOrders[hash];
-        var type = "Buy";
-        if (openOrder.type === "MakeBuyOrder") {
-          type = "Sell";
-        }
-        var token = tokenMap[openOrder.tokenAddress];
-        if (token) {
-          ethOpenOrders[hash] = {
-            type: type,
-            priceEth: openOrder.price,
-            tokenAmount: openOrder.tokAmount.dividedBy(Math.pow(10, token.decimals)),
-            ethAmount: openOrder.weiAmount.dividedBy(Math.pow(10, 18)),
-            tokenAddress: openOrder.tokenAddress,
-            symbol: token.symbol,
-            maker: openOrder.maker
-          };
-        } else {
-        }
-      }
-      marketDataCallback(marketData, ethOpenOrders);
-    });
+        clearEmptyOrders(_openBuys, marketData);
+        clearEmptyOrders(_openSells, marketData);
+        // reset global orders to make sure canceled and completed orders do not get picked up.
+        ethOpenSells = {};
+        ethOpenBuys = {};
+        formatOrder(_openBuys);
+        formatOrder(_openSells);
+        marketDataCallback(marketData, ethOpenBuys, ethOpenSells);
+      });
+    }
   });
 }
 var MarketData = {};
-var OpenOrders = {};
+var openBuys = {};
+var openSells = {};
+var ethOpenBuys = {};
+var ethOpenSells = {};
+var clearEmptyOrders = (openOrders, marketData) => {
+  for (hash in openOrders) {
+    var openOrder = openOrders[hash];
+    if (openOrder.weiAmount.lte(minWei)) {
+      delete openOrders[hash];
+      continue;
+    }
+    token = tokenMap[openOrder.tokenAddress];
+    tokenData = marketData[openOrder.tokenAddress];
+    if (openOrder.type === "MakeSellOrder" && (tokenData.lowestAsk === null || tokenData.lowestAsk.gt(openOrder.price))) {
+      tokenData.lowestAsk = openOrder.price;
+    }
+    if (openOrder.type === "MakeBuyOrder" && (tokenData.highestBid === null || tokenData.highestBid.lt(openOrder.price))) {
+      tokenData.highestBid = openOrder.price;
+    }
+  }
+}
+var formatOrder = (orders) => {
+
+  for (hash in orders) {
+    var openOrder = orders[hash];
+    var type = "Buy";
+    if (openOrder.type === "MakeBuyOrder" || openOrder.type === "ChangeBuyOrder") {
+      type = "Sell";
+    }
+    var specifiedOpenOrders;
+    if (type === "Buy") {
+      specifiedOpenOrders = ethOpenSells;
+    }
+    else {
+      specifiedOpenOrders = ethOpenBuys;
+    }
+    var token = tokenMap[openOrder.tokenAddress];
+    if (token) {
+      specifiedOpenOrders[hash] = {
+        type: type,
+        priceEth: openOrder.price,
+        tokenAmount: openOrder.tokAmount.dividedBy(Math.pow(10, token.decimals)),
+        ethAmount: openOrder.weiAmount.dividedBy(Math.pow(10, 18)),
+        tokenAddress: openOrder.tokenAddress,
+        symbol: token.symbol,
+        orgTokenAmount: openOrder.orgTokAmount.dividedBy(Math.pow(10, token.decimals)),
+        orgEthAmount: openOrder.orgWeiAmount.dividedBy(Math.pow(10, 18)),
+        maker: openOrder.maker,
+        decimals: openOrder.decimals,		
+        transactionHash: openOrder.transactionHash,		
+        blockNumber: openOrder.blockNumber
+      }
+    }
+  }
+}
 var refresh24Hour = () => {
-  getMarketData((marketData, openOrders) => {
+  getMarketData((marketData, _openBuys, _openSells) => {
     MarketData = marketData;
-    OpenOrders = openOrders;
+    openBuys = _openBuys;
+    openSells = _openSells;
   });
 }
 
@@ -232,15 +300,25 @@ var server = http.createServer(function (req, res) {
       return;
     }
     else if (req.url === "/openOrders") {
-      var results = {};
-      for (var hash in OpenOrders) {
-        var openOrder = OpenOrders[hash];
+      var results = [];
+      for (var hash in openBuys) {
+        var openOrder = openBuys[hash];
         var token = tokenMap[openOrder.tokenAddress];
         if (token) {
           var pair = "ETH_" + token.symbol;
           openOrder.pair = pair;
           openOrder.hash = hash;
-          results[hash] = OpenOrders[hash];
+          results.push(openOrder);
+        }
+      }
+      for (var hash in openSells) {
+        var openOrder = openSells[hash];
+        var token = tokenMap[openOrder.tokenAddress];
+        if (token) {
+          var pair = "ETH_" + token.symbol;
+          openOrder.pair = pair;
+          openOrder.hash = hash;
+          results.push(openOrder);
         }
       }
       res.write(JSON.stringify(results, undefined, 2));
@@ -255,12 +333,16 @@ var server = http.createServer(function (req, res) {
   }
 });
 console.log("starting api server");
-getMarketData((marketData, openOrders) => {
+getMarketData((marketData, _openBuys, _openSells) => {
   MarketData = marketData;
-  OpenOrders = openOrders;
+  openBuys = _openBuys;
+  openSells = _openSells;
+  server.once('error', function (err) {
+    if (err.code === 'EADDRINUSE') {
+      console.log('Port is already in use. Make sure you can see open orders on localhost:5055/openOrders.');
+    }
+  });
   server.listen(config.server_port);
   console.log("listening on", config.server_port);
 });
-setInterval(refresh24Hour, 30 * 1000);
-
-
+setInterval(refresh24Hour, 15 * 1000);
\ No newline at end of file