diff --git a/docs.html b/docs.html index a33a0d2..5aa2d06 100644 --- a/docs.html +++ b/docs.html @@ -35,6 +35,10 @@
$ 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