Publish Nerbus source snapshot and build documentation

This commit is contained in:
Nerbus Contributors committed 2026-10-17 18:56:29 +00:00
commit 76cccceca7
198 files changed
+338574

No files matched your search

+116
View File
@@ -0,0 +1,116 @@
const request = require('postman-request');
const base_url = 'https://api.coingecko.com/api/v3/';
const Decimal = require('decimal.js');
function get_coin_list(api_key, cb) {
request({ uri: base_url + 'coins/list?include_platform=false' + (api_key == null || api_key == '' ? '' : '&x_cg_demo_api_key=' + api_key), json: true}, function (error, response, body) {
if (error)
return cb(error, []);
else if (body == null || body == '' || typeof body !== 'object')
return cb('No data returned', []);
else if (body['status'] != null && body['status']['error_message'] != null && body['status']['error_message'] != '')
return cb(body['status']['error_message'], []);
else
return cb(null, body);
});
}
function get_simple_price(id, currency, market_array, api_key, cb) {
request({ uri: base_url + `simple/price?ids=${id.toLowerCase()}&vs_currencies=usd${currency == null || currency == '' ? '' : `,${currency}`}` + (api_key == null || api_key == '' ? '' : '&x_cg_demo_api_key=' + api_key), json: true}, function (error, response, body) {
if (error)
return cb(error, new Decimal('0'), new Decimal('0'));
else if (body == null || body == '' || typeof body !== 'object')
return cb('No data returned', new Decimal('0'), new Decimal('0'));
else if (body['status'] != null && body['status']['error_message'] != null && body['status']['error_message'] != '')
return cb(body['status']['error_message'], new Decimal('0'), new Decimal('0'));
else {
try {
if (market_array != null) {
// multiple currencies need to be combined before return
let last_price = new Decimal('0');
let last_usd_price = new Decimal('0');
let counter = 0;
let api_market = null;
// check if the currency variable is set
if (currency != null && currency != '') {
// find the market currency in the market_array
const base_index = market_array.findIndex(p => p.currency.toLowerCase() == currency.toLowerCase());
// check if the currency is found in the market_array
if (base_index > -1) {
// find the api market data row
api_market = body[market_array[base_index].coingecko_id.toLowerCase()];
}
}
// loop through all api object keys
Object.keys(body).forEach(function(key, index, map) {
const market_index = market_array.findIndex(p => p.coingecko_id.toLowerCase() == key.toLowerCase());
// check if the currency is found in the market_array
if (market_index > -1) {
// determine if the api already returned the market price
if (currency != null && currency != '' && body[key][currency.toLowerCase()] != null) {
// calculate the market price
last_price = last_price.add(new Decimal(market_array[market_index].last_price.toString()).mul(new Decimal((currency == null || currency == '' ? new Decimal('0') : new Decimal(body[key][currency.toLowerCase()].toString())))));
} else {
// check if the market currency exists in the api data
if (api_market != null) {
// calculate the currency ratio
const ratio = new Decimal(api_market['usd'].toString()).div(body[key]['usd'].toString());
// calculate the market price
last_price = last_price.add(new Decimal(market_array[market_index].last_price.toString()).div(ratio));
}
}
// calculate the usd price
last_usd_price = last_usd_price.add(new Decimal(market_array[market_index].last_price.toString()).mul(body[key]['usd'].toString()));
counter++;
}
});
// check if the counter is greater than 0
if (counter > 0) {
// average the market and usd prices
last_price = last_price.div(counter.toString());
last_usd_price = last_usd_price.div(counter.toString());
}
return cb(null, last_price, last_usd_price);
} else {
// single currency
return cb(null, (currency == null || currency == '' ? new Decimal('0') : new Decimal(body[id.toLowerCase()][currency.toLowerCase()].toString())), new Decimal(body[id.toLowerCase()]['usd'].toString()));
}
} catch(err) {
return cb('Received unexpected API data response', new Decimal('0'), new Decimal('0'));
}
}
});
}
module.exports = {
get_coin_data: function (api_key, cb) {
get_coin_list(api_key, function (err, coin_list) {
return cb(err, coin_list);
});
},
get_market_prices: function (id, currency, api_key, cb) {
get_simple_price(id, currency, null, api_key, function (err, last_price, last_usd) {
if (last_price == null)
console.log(`Error: "${currency}" is not a valid coingecko api currency`);
return cb(err, last_price, last_usd);
});
},
get_avg_market_prices: function (id, currency, market_array, api_key, cb) {
get_simple_price(id, currency, market_array, api_key, function (err, last_price, last_usd) {
if (last_price == null)
console.log(`Error: "${currency}" is not a valid coingecko api currency`);
return cb(err, last_price, last_usd);
});
}
};
+1063
View File
File diff suppressed because it is too large. Load diff
+2334
View File
File diff suppressed because it is too large. Load diff
+1451
View File
File diff suppressed because it is too large. Load diff
+171
View File
@@ -0,0 +1,171 @@
var http = require('http'),
https = require('https');
var Client = function(opts) {
this.opts = opts || {};
this.http = this.opts.ssl ? https : http;
};
Client.prototype.call = function(method, params, callback, errback, path) {
var time = Date.now();
var requestJSON;
if (Array.isArray(method)) {
// multiple rpc batch call
requestJSON = [];
method.forEach(function(batchCall, i) {
requestJSON.push({
id: time + '-' + i,
method: batchCall.method,
params: batchCall.params
});
});
} else {
// single rpc call
requestJSON = {
id: time,
method: method,
params: params
};
}
// first we encode the request into JSON
var requestJSON = JSON.stringify(requestJSON);
// prepare request options
var requestOptions = {
host: this.opts.host,
port: this.opts.port,
method: 'POST',
path: path || '/',
headers: {
'Host': this.opts.host,
'Content-Length': requestJSON.length
},
agent: false,
rejectUnauthorized: this.opts.ssl && this.opts.sslStrict !== false
};
if (this.opts.ssl && this.opts.sslCa) {
requestOptions.ca = this.opts.sslCa;
}
// use HTTP auth if user and password set
if (this.opts.username && this.opts.password)
requestOptions.auth = this.opts.username + ':' + this.opts.password;
// now make a request to the server
var cbCalled = false
var request = this.http.request(requestOptions);
// start request timeout timer
var reqTimeout = setTimeout(function() {
if (cbCalled)
return;
cbCalled = true;
request.abort();
var err = new Error('ETIMEDOUT');
err.code = 'ETIMEDOUT';
errback(err);
}, this.opts.timeout || 30000);
// set additional timeout on socket in case of remote freeze after sending headers
request.setTimeout(this.opts.timeout || 30000, function() {
if (cbCalled)
return;
cbCalled = true;
request.abort();
var err = new Error('ESOCKETTIMEDOUT');
err.code = 'ESOCKETTIMEDOUT';
errback(err);
});
request.on('error', function(err) {
if (cbCalled)
return;
cbCalled = true;
clearTimeout(reqTimeout);
errback(err);
});
request.on('response', function(response) {
clearTimeout(reqTimeout);
// we need to buffer the response chunks in a nonblocking way
var buffer = '';
response.on('data', function(chunk) {
buffer = buffer + chunk;
});
// when all the responses are finished, we decode the JSON and
// depending on whether it's got a result or an error, we call
// emitSuccess or emitError on the promise.
response.on('end', function() {
var err;
if (cbCalled)
return;
cbCalled = true;
try {
var decoded = JSON.parse(buffer);
} catch (e) {
if (response.statusCode !== 200) {
err = new Error('Invalid params, response status code: ' + response.statusCode);
err.code = -32602;
errback(err);
} else {
err = new Error('Problem parsing JSON response from server');
err.code = -32603;
errback(err);
}
return;
}
if (!Array.isArray(decoded))
decoded = [decoded];
// iterate over each response, normally there will be just one
// unless a batch rpc call response is being processed
decoded.forEach(function(decodedResponse, i) {
if (decodedResponse.hasOwnProperty('error') && decodedResponse.error != null) {
if (errback) {
err = new Error(decodedResponse.error.message || '');
if (decodedResponse.error.code)
err.code = decodedResponse.error.code;
errback(err);
}
} else if (decodedResponse.hasOwnProperty('result')) {
if (callback)
callback(decodedResponse.result, response.headers);
} else {
if (errback) {
err = new Error(decodedResponse.error.message || '');
if (decodedResponse.error.code)
err.code = decodedResponse.error.code;
errback(err);
}
}
});
});
});
request.end(requestJSON);
};
module.exports.Client = Client;
+301
View File
@@ -0,0 +1,301 @@
/**
* The Locale Module reads the locale settings and provides
* this information to the other modules
*/
exports.localization = {
// global
"difficulty": "Difficulty",
"network": "Network",
"masternodecount": "Masternodes",
"height": "Height",
"timestamp": "Timestamp",
"size": "Size",
"transactions": "Transactions",
"total_sent": "Total Sent",
"total_received": "Total Received",
"confirmations": "Confirmations",
"total": "Total",
"total_top_100": "Top 1-100 Total",
"bits": "Bits",
"nonce": "Nonce",
"new_coins": "New Coins",
"proof_of_stake": "PoS",
"hidden_address": "Hidden Address",
"hidden_sender": "Hidden Sender",
"hidden_recipient": "Hidden Recipient",
"unknown_address": "Unknown Address",
"unknown_sender": "Unknown Sender",
"unknown_recipient": "Unknown Recipient",
"last_updated": "Last Updated",
"initial_index_alert": "Blockchain data is currently being synchronized. You may browse the site during this time, but keep in mind that data may not yet be fully accurate and some functionality may not work until synchronization is complete.",
"please_wait": "Please wait",
"creating_initial_entry": "Creating initial {1} entry",
"entry_created_successfully": "Initial {1} entry for {2} created successfully",
"method_disabled": "This method is disabled",
"compiling_css": "Compiling CSS",
"installing_module": "Installing {1} module",
"restoring_backup": "Restoring backup",
"extracting_backup_files": "Extracting backup files",
"explorer_shutting_down": "Explorer shutting down",
"initializing_database": "Initializing database",
"downloading_newest_explorer_code": "Downloading newest explorer code",
"check_outdated_packages": "Checking for outdated packages",
"updating_explorer_packages": "Updating out-of-date explorer packages",
"checking_webserver_running": "Checking if webserver is running",
"reloading_explorer": "Reloading the explorer",
"email_sent_successfully": "Email sent successfully",
"check_console": "Check your console",
"process_aborted": "Process aborted",
"nothing_was_deleted": "Nothing was deleted",
"nothing_was_restored": "Nothing was restored",
// prompts
"are_you_sure": "Are you sure you want to do this? [y/n]",
"short_yes": "y",
"long_yes": "yes",
// menu items
"menu_explorer": "Explorer",
"menu_api": "API",
"menu_markets": "Markets",
"menu_richlist": "Rich List",
"menu_reward": "Reward",
"menu_movement": "Movement",
"menu_node": "Nodes",
"menu_network": "Network",
"menu_claim_address": "Claim Address",
"menu_orphans": "Orphaned Blocks",
// explorer view
"ex_title": "{1} Block Explorer",
"ex_description": "A listing of all verified {1} transactions",
"ex_search_title": "Search",
"ex_search_button": "Search",
"ex_search_message": "Search by block height, block hash, tx hash or address",
"ex_error": "Error",
"ex_warning": "Warning",
"ex_search_error": "Search found no results.",
"ex_latest_transactions": "Latest Transactions",
"ex_summary": "Block Summary",
"ex_supply": "Coin Supply",
"ex_block": "Block",
// transaction view
"tx_title": "{1} Transaction Details",
"tx_description": "Viewing tx data from {1} block # {2}",
"tx_block_hash": "Block Hash",
"tx_recipients": "Recipients",
"tx_contributors": "Contributor(s)",
"tx_hash": "Tx Hash",
"tx_address": "Address",
"tx_nonstandard": "NONSTANDARD TX",
"view_raw_tx_data": "View Raw Transaction Data",
"view_block": "View Block",
// block view
"block_title": "{1} Block Details",
"block_description": "Viewing block data from {1} block # {2}",
"block_previous": "Previous Block",
"block_next": "Next Block",
"block_genesis": "GENESIS",
"view_raw_block_data": "View Raw Block Data",
"view_tx": "View Transaction",
// error view
"error_title": "{1} Block Explorer Error",
"error_description": "The page you are looking for cannot be found",
"error_description_alt": "An error occurred which prevented the page from loading correctly",
"error_not_found": "Not Found",
// address view
"a_title": "{1} Wallet Address Details",
"a_description": "Viewing balance and transaction data from {1} address {2}",
"a_menu_showing": "Showing",
"a_menu_txs": "transactions",
"a_menu_all": "All",
"a_qr": "QR Code",
// masternode view
"mn_title": "{1} Masternodes",
"mn_description": "A listing of all masternodes known to be active on the {1} network",
"mn_masternode_list": "Masternode List",
// movement view
"move_title": "{1} Coin Movements",
"move_description": "A listing of larger movements where {1} or more {2} coins were sent in a single transaction",
// richlist view
"rl_title": "Top {1} Coin Holders",
"rl_description": "A listing of the richest {1} wallet addresses and breakdown of the current coin distribution",
"rl_received_coins": "Top 100 - Received Coins",
"rl_current_balance": "Top 100 - Current Balance",
"rl_received": "Received",
"rl_balance": "Balance",
"rl_wealth": "Wealth Distribution",
"rl_top25": "Top 1-25",
"rl_top50": "Top 26-50",
"rl_top75": "Top 51-75",
"rl_top100": "Top 76-100",
"rl_hundredplus": "101+",
// network view
"net_title": "{1} Network Peers",
"net_description": "A listing of {1} network peers that have connected to the explorer node in the last 24 hours",
"net_addnodes": "Add Nodes",
"net_connections": "Connections",
"net_address": "Address",
"net_protocol": "Protocol",
"net_subversion": "Sub-version",
"net_country": "Country",
// api view
"api_title": "{1} Public API",
"api_description": "A listing of public API endpoints for retrieving {1} coin data from the network without the need for a local wallet",
"api_documentation": "API Documentation",
"api_calls": "API Calls",
"api_getnetworkhashps": "Returns the current network hashrate. (hash/s)",
"api_getdifficulty": "Returns the current difficulty.",
"api_getconnectioncount": "Returns the number of connections the block explorer has to other nodes.",
"api_getmasternodelist": "Returns the complete list of masternodes on the network.",
"api_getmasternodecount": "Returns the total number of masternodes on the network.",
"api_getvotelist": "Returns the current vote list.",
"api_getblockcount": "Returns the number of blocks currently in the block chain.",
"api_getblockhash": "Returns the hash of the block at [index]; index 0 is the genesis block.",
"api_getblock": "Returns information about the block with the given hash.",
"api_getrawtransaction": "Returns raw transaction representation for given transaction id. decrypt can be set to 0(false) or 1(true).",
"api_getmaxmoney": 'Returns the maximum possible money supply.',
"api_getmaxvote": 'Returns the maximum allowed vote for the current phase of voting.',
"api_getvote": 'Returns the current block reward vote setting.',
"api_getphase": 'Returns the current voting phase (\'Mint\', \'Limit\' or \'Sustain\').',
"api_getreward": 'Returns the current block reward, which has been decided democratically in the previous round of block reward voting.',
"api_getsupply": 'Returns the current money supply.',
"api_getnextrewardestimate": 'Returns an estimate for the next block reward based on the current state of decentralized voting.',
"api_getnextrewardwhenstr": 'Returns a string describing how long until the votes are tallied and the next block reward is computed.',
// markets view
"mkt_title": "{1} Market Details",
"mkt_description": "Viewing {1} market data for the {2} exchange",
"mkt_hours": "24 hours",
"mkt_view_chart": "View 24 hour summary",
"mkt_view_summary": "View 24 hour chart",
"mkt_no_chart": "Chart data is not available via markets API",
"mkt_high": "High",
"mkt_low": "Low",
"mkt_volume": "Volume",
"mkt_top_bid": "Top Bid",
"mkt_top_ask": "Top Ask",
"mkt_last": "Last Price",
"mkt_yesterday": "Yesterday",
"mkt_change": "Change",
"mkt_sell_orders": "Sell Orders",
"mkt_buy_orders": "Buy Orders",
"mkt_price": "Price",
"mkt_amount": "Amount",
"mkt_total": "Total",
"mkt_trade_history": "Trade History",
"mkt_type": "Type",
"mkt_time_stamp": "Time Stamp",
"mkt_select": "Market Select",
"mkt_unexpected_api_data": "Received unexpected API data response",
// claim address view
"claim_title": "{1} Wallet Address Claim",
"claim_description": "Verify ownership of your {1} wallet address and set a custom display name in the explorer",
// orphans view
"orphan_title": "{1} Orphaned Blocks",
"orphan_description": "A listing of valid blocks that have been orphaned and do not belong to the main blockchain",
"orphan_block_list": "Orphaned Block List",
"orphan_block_hash": "Orphaned Block Hash",
"orphan_actual_block": "Actual Block",
"orphan_prev_block": "Previous Block",
"orphan_next_block": "Next Block",
"view_orphan": "View Orphaned Block",
// heavycoin rewards view
"heavy_title": "{1} Reward/Voting Details",
"heavy_description": "Viewing {1} voting data and coin reward change details",
"heavy_reward_voting_info": "Reward/voting information",
"heavy_vote": "Vote",
"heavy_cap": "Coin Cap",
"heavy_phase": "Phase",
"heavy_maxvote": "Max Vote",
"heavy_reward": "Reward",
"heavy_current": "Current Reward",
"heavy_estnext": "Est. Next",
"heavy_changein": "Reward change in approximately",
"heavy_key": "Key",
"heavy_lastxvotes": "Last 20 votes",
// sync
"script_launched": "Script launched with pid",
"stopping_sync_process": "Stopping sync process",
"finding_earliest_orphan": "Finding the earliest orphaned blockindex",
"calculating_market_price": "Calculating market price",
"syncing_blocks": "Syncing blocks",
"syncing_peers": "Syncing peers",
"syncing_masternodes": "Syncing masternodes",
"syncing_markets": "Syncing markets",
"checking_blocks": "Checking blocks",
"calculating_tx_count": "Calculating tx count",
"finding_last_blockindex": "Finding last blockindex",
"path_cannot_be_found": "'{1}' cannot be found",
// settings
"deprecated_setting": "Deprecated setting '{1}' has been mapped to the new location '{2}'. Be sure to make this change in settings.json as the old setting will be removed in a future release",
"unknown_setting": "Unknown setting '{1}' has been ignored",
"missing_setting": "Setting '{1}' is missing. Loading default value",
"continuing_using_defaults": "Continuing using defaults",
"missing_settings_file": "The {1} file is missing",
"error_processing_settings": "There was an error processing the {1} file",
// locale
"locale_lowercase_req": "Locale strings must start with a lowercase character",
"locale_unknown_string": "Unknown Locale String",
"locale_not_exists": "This setting doesn't exist or was removed"
};
exports.reloadLocale = function reloadLocale(locale) {
const fs = require('fs');
const jsonminify = require('jsonminify');
const localeFilename = './' + locale;
let localeStr;
try {
// read the settings sync
localeStr = fs.readFileSync(localeFilename).toString();
} catch(e) {
console.warn(`${exports.localization.missing_settings_file.replace('{1}', locale.split('/')[1])}. ${exports.localization.continuing_using_defaults}`);
}
var lsettings;
// try to parse the settings
try {
if (localeStr) {
localeStr = jsonminify(localeStr).replace(",]","]").replace(",}","}");
lsettings = JSON.parse(localeStr);
}
} catch(e) {
console.error(`${exports.localization.error_processing_settings.replace('{1}', locale.split('/')[1])}: ${e.message}`);
process.exit(1);
}
// loop through the settings
for (var i in lsettings) {
// test if the setting start with a low character
if (i.charAt(0).search("[a-z]") !== 0)
console.warn(`${lsettings.locale_lowercase_req}: '${i}'`);
if (exports.localization[i] !== undefined) {
// we know this setting, so we overwrite it
exports.localization[i] = lsettings[i];
} else {
// this setting is unknown. output a warning and discard it
console.warn(`${lsettings.locale_unknown_string}: '${i}'. ${lsettings.locale_not_exists}`);
}
}
return exports.localization;
};
+194
View File
@@ -0,0 +1,194 @@
const request = require('postman-request');
const base_url = 'https://dexomy.com/api/v2/';
const market_url_template = 'https://dexomy.com/exchange/dashboard?coin_pair={coin}_{base}';
// initialize the rate limiter to wait 2 seconds between requests to prevent abusing external apis
const rateLimitLib = require('../ratelimit');
const rateLimit = new rateLimitLib.RateLimit(1, 2000, false);
function get_summary(coin, exchange, api_error_msg, cb) {
const req_url = base_url + 'get-pair-list/';
// NOTE: no need to pause here because this is the first api call
request({uri: req_url, json: true}, function (error, response, body) {
if (error)
return cb(error, null, null);
else if (body == null || body == '' || typeof body !== 'object')
return cb(api_error_msg, null, null);
else if (!body.success)
return cb((body.message != null ? body.message : api_error_msg), null, null);
else {
try {
// find the correct coin pair record
const pairObject = body.data.find(item => item.coin_pair_name === `${coin}/${exchange}`);
if (pairObject) {
const coindata = {
parent_coin_id: pairObject.parent_coin_id,
child_coin_id: pairObject.child_coin_id
};
const summary = {
'high': parseFloat(pairObject.high) || 0,
'low': parseFloat(pairObject.low) || 0,
'volume': parseFloat(pairObject.volume) || 0,
'last': parseFloat(pairObject.last_price) || 0,
'change': parseFloat(pairObject.price_change) || 0
};
return cb(null, summary, coindata);
} else
return cb(api_error_msg, null, null);
} catch(err) {
return cb(api_error_msg, null, null);
}
}
});
}
function get_trades(parent_coin_id, child_coin_id, api_error_msg, cb) {
const req_url = `${base_url}get-exchange-market-trades?base_coin_id=${parent_coin_id}&trade_coin_id=${child_coin_id}`;
// pause for 2 seconds before continuing
rateLimit.schedule(function() {
request({uri: req_url, json: true}, function (error, response, body) {
if (error)
return cb(error, null);
else if (body == null || body == '' || typeof body !== 'object')
return cb(api_error_msg, null);
else if (!body.success)
return cb((body.message != null ? body.message : api_error_msg), null);
else {
try {
let trades = [];
for (let t = 0; t < body.data.transactions.data.length; t++) {
// convert the string timestamp to iso time and then to a unix timestamp
const isoTime = body.data.transactions.data[t].time.replace(' ', 'T') + 'Z';
const unixTimestamp = Math.floor(new Date(isoTime).getTime() / 1000);
trades.push({
ordertype: body.data.transactions.data[t].price_order_type.toString().toUpperCase(),
price: parseFloat(body.data.transactions.data[t].price) || 0,
quantity: parseFloat(body.data.transactions.data[t].amount) || 0,
timestamp: unixTimestamp
});
}
return cb(null, trades);
} catch(err) {
return cb(api_error_msg, null);
}
}
});
});
}
function get_orders(parent_coin_id, child_coin_id, api_error_msg, cb) {
const req_url = `${base_url}get-exchange-all-orders?base_coin_id=${parent_coin_id}&trade_coin_id=${child_coin_id}`;
// pause for 2 seconds before continuing
rateLimit.schedule(function() {
request({uri: req_url, json: true}, function (error, response, body) {
if (error)
return cb(error, null, null);
else if (body == null || body == '' || typeof body !== 'object')
return cb(api_error_msg, null, null);
else if (!body.success)
return cb((body.message != null ? body.message : api_error_msg), null, null);
else {
try {
let buys = [];
let sells = [];
for (let b = 0; b < body.data.buy_orders.data.length; b++) {
buys.push({
price: parseFloat(body.data.buy_orders.data[b].price) || 0,
quantity: parseFloat(body.data.buy_orders.data[b].amount) || 0
});
}
for (let s = 0; s < body.data.sell_orders.data.length; s++) {
sells.push({
price: parseFloat(body.data.sell_orders.data[s].price) || 0,
quantity: parseFloat(body.data.sell_orders.data[s].amount) || 0
});
}
return cb(null, buys, sells);
} catch(err) {
return cb(api_error_msg, null, null);
}
}
});
});
}
function get_chartdata(parent_coin_id, child_coin_id, api_error_msg, cb) {
const end = Date.now();
const start = end - 86400000;
const req_url = `${base_url}get-exchange-chart-data?base_coin_id=${parent_coin_id}&trade_coin_id=${child_coin_id}&interval=15`;
// pause for 2 seconds before continuing
rateLimit.schedule(function() {
request({uri: req_url, json: true}, function(error, response, body) {
if (error)
return cb(error, null);
else if (body == null || body == '' || typeof body !== 'object' || typeof body == 'string' || body instanceof String)
return cb(api_error_msg, null);
else if (!body.success)
return cb((body.message != null ? body.message : api_error_msg), null);
else {
try {
let chartdata = [];
for (let c = 0; c < body.data.length; c++) {
// only take values more recent than the last 24 hours
if ((body.data[c].time * 1000) > start)
chartdata.push([
parseInt(body.data[c].time * 1000),
parseFloat(body.data[c].open) || 0,
parseFloat(body.data[c].high) || 0,
parseFloat(body.data[c].low) || 0,
parseFloat(body.data[c].close) || 0
]);
}
return cb(null, chartdata);
} catch(err) {
return cb(api_error_msg, null);
}
}
});
});
}
module.exports = {
market_name: 'Dexomy',
market_logo: 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAACbVBMVEUAAAAoTTU2RzdBISEnAQQjIBkpGxolDg9bMi6sTD4nDhlTGyAVTy4QDwzl5uBbNjEUSCllcnIgJR0AFRcAEhYLNyK5KSA8KCgHPCU/TkkuJCUMHhy8q6eOlpUvNDMSMx1IVE1qXVJCWk8zRTGZeGSKk2/GnIFx0ZRQgl2Nonppl3geWy9WV1aOeXusSz4iOyFcOzMxRzI6KCWFiYmIMjKhMDCHeGuMZlYgAQM3OThXISe5pIC3wbVQZU5aOzNlZFdigH9sbGegMixKBAjEYlBBDQ1ZaE0dNiynSzhXGB5EFBaMkZJhX2EcERVkMDGhb2sdExgoQCJKJh9sGxtgPz+OEhM0Py2jamYKExNiNyo2AAdABwJjKiVZWVeOTj8bXzm4Tjk3STd3Oi0mKi0KOiosJScrMSQ3KiN2JyMANiJZHB9NKhobFBWOEBRqCxRVDxMKDxN9BhMBCgpKAAUuAAKrxY6DsXpkc3aAqnOTdHN/unHcgXBla255mWtjZmFLa19SjFokhFmPlFTCWVLNcU5HTEhHRUaNYENveUKTZEIjSUKfaEGQZkC4Uz01YjtkdzokZzo9YzpXOzrpSzk7VDg3NDjJPze0SjZMPjVvODJvSDEVPjFVNTGUNjBNMjCGNy+2Ly8pPi3XMS1RKi1eSiykSytAPisfMCvZJykkQCh+NCg+OCU4RSRKMSQ6MyMeICJYMCGsHiE5FyEAKB9AJh9SJB9dQh0AOx2gLB0vLB0PJR0zHR0hHRx8GhuJEBtuDxsCFhhsHReiDxefGxYgDhROABQALhMCIBMoFxIrGRBTBg0tBg13AAumAAZhAADJFxIcAAAAXXRSTlMA/hMI1UAcD/z6+vf29PLy8vHx8fHw8O/v7e3t7Onp5uXf3tzVzMrJx8XFxcG/v7+9vby7u7m4trW0tKupqKelnpyclZCPjoyBfHp5dnNubGVXV1FORUA+PTAvJgb/bIgPAAABGklEQVQY0w3HQ2IDABAAwI1r27Zt23Zss7Zt27Zt803N3AaS1bxdbR1tHJz8fXyjsgEAU1ez1kTYO+vt3DkR89MASgpMqllfjEf+WMsgQ5AIgMLZrf5JN4nsq4Wlka4gHED+RT/l2znTkzj99IzQzIH05pWq7XdkYVFcI/OXW58CWfH2HTfGSfrosLLxn9NFFwDwu1xXQqtPsmY5PAHTAlAxlQ9UqoIiiUNmf9wtG0KCcvn+aGu4R0Bg6dvL/YQV6HlF0NvMsEhscIXkVkwOhegty+s5o+Jct9rzqY0jRB5A7JBIRBdSGgZ6hl9p1igApClvhjDf3k06lB0jMgAgBP+5yz0QSmUSWl+q/JF4VS0dbV0NAxVzd4z8/2atXThWWOQQAAAAAElFTkSuQmCC',
market_url_template: market_url_template,
market_url_case: 'u',
get_data: function(settings, cb) {
get_summary(settings.coin, settings.exchange, settings.api_error_msg, function(summary_error, stats, coindata) {
if (summary_error == null) {
get_orders(coindata.parent_coin_id, coindata.child_coin_id, settings.api_error_msg, function(order_error, buys, sells) {
if (order_error == null) {
get_trades(coindata.parent_coin_id, coindata.child_coin_id, settings.api_error_msg, function(trade_error, trades) {
if (trade_error == null) {
get_chartdata(coindata.parent_coin_id, coindata.child_coin_id, settings.api_error_msg, function (chart_error, chartdata) {
if (chart_error == null)
return cb(null, {buys: buys, sells: sells, trades: trades, stats: stats, chartdata: chartdata});
else
return cb(chart_error, null);
});
} else
return cb(trade_error, null);
});
} else
return cb(order_error, null);
});
} else
return cb(summary_error, null);
});
}
};
+183
View File
@@ -0,0 +1,183 @@
const request = require('postman-request');
const base_url = 'https://api.dex-trade.com/v1/public/';
const base_chart_url = 'https://socket.dex-trade.com/graph/';
const market_url_template = 'https://dex-trade.com/spot/trading/{coin}{base}';
// initialize the rate limiter to wait 2 seconds between requests to prevent abusing external apis
const rateLimitLib = require('../ratelimit');
const rateLimit = new rateLimitLib.RateLimit(1, 2000, false);
function get_summary(coin, exchange, api_error_msg, bid, ask, cb) {
const req_url = base_url + 'ticker?pair=' + coin + exchange;
// pause for 2 seconds before continuing
rateLimit.schedule(function() {
request({uri: req_url, json: true}, function (error, response, body) {
if (error)
return cb(error, null);
else if (body == null || body == '' || typeof body !== 'object')
return cb(api_error_msg, null);
else if (body.status == null || body.status == false)
return cb((body.error != null ? body.error : api_error_msg), null);
else {
try {
const summary = {
'high': parseFloat(body.data.high) || 0,
'low': parseFloat(body.data.low) || 0,
'volume': parseFloat(body.data.volume_24H) || 0,
'bid': parseFloat(bid) || 0,
'ask': parseFloat(ask) || 0,
'last': parseFloat(body.data.last) || 0
};
return cb(null, summary);
} catch(err) {
return cb(api_error_msg, null);
}
}
});
});
}
function get_trades(coin, exchange, api_error_msg, cb) {
const req_url = base_url + 'trades?pair=' + coin + exchange;
// pause for 2 seconds before continuing
rateLimit.schedule(function() {
request({uri: req_url, json: true}, function (error, response, body) {
if (error)
return cb(error, null);
else if (body == null || body == '' || typeof body !== 'object')
return cb(api_error_msg, null);
else if (body.status == null || body.status == false)
return cb((body.error != null ? body.error : api_error_msg), null);
else {
try {
let trades = [];
for (let t = 0; t < body.data.length; t++) {
trades.push({
ordertype: body.data[t].type,
price: parseFloat(body.data[t].rate) || 0,
quantity: parseFloat(body.data[t].volume) || 0,
timestamp: parseInt(body.data[t].timestamp)
});
}
return cb(null, trades);
} catch(err) {
return cb(api_error_msg, null);
}
}
});
});
}
function get_orders(coin, exchange, api_error_msg, cb) {
const req_url = base_url + 'book?pair=' + coin + exchange;
// NOTE: no need to pause here because this is the first api call
request({uri: req_url, json: true}, function (error, response, body) {
if (error)
return cb(error, null, null);
else if (body == null || body == '' || typeof body !== 'object')
return cb(api_error_msg, null, null);
else if (body.status == null || body.status == false)
return cb((body.error != null ? body.error : api_error_msg), null, null);
else {
try {
let buys = [];
let sells = [];
for (let b = 0; b < body.data.buy.length; b++) {
buys.push({
price: parseFloat(body.data.buy[b].rate) || 0,
quantity: parseFloat(body.data.buy[b].volume) || 0
});
}
for (let s = 0; s < body.data.sell.length; s++) {
sells.push({
price: parseFloat(body.data.sell[s].rate) || 0,
quantity: parseFloat(body.data.sell[s].volume) || 0
});
}
return cb(null, buys, sells);
} catch(err) {
return cb(api_error_msg, null, null);
}
}
});
}
function get_chartdata(coin, exchange, api_error_msg, cb) {
const end = Date.now();
const start = end - 86400000;
const req_url = base_chart_url + 'hist?t=' + coin + exchange + '&r=15&limit=100';
// pause for 2 seconds before continuing
rateLimit.schedule(function() {
request({uri: req_url, json: true}, function (error, response, body) {
if (error)
return cb(error, null);
else if (body == null || body == '' || typeof body !== 'object' || typeof body == 'string' || body instanceof String)
return cb(api_error_msg, null);
else {
try {
let chartdata = [];
for (let c = 0; c < body.length; c++) {
// only take values more recent than the last 24 hours
if ((body[c].time * 1000) > start)
chartdata.push([
parseInt(body[c].time * 1000),
parseFloat(body[c].open) || 0,
parseFloat(body[c].high) || 0,
parseFloat(body[c].low) || 0,
parseFloat(body[c].close) || 0
]);
}
return cb(null, chartdata);
} catch(err) {
return cb(api_error_msg, null);
}
}
});
});
}
module.exports = {
market_name: 'Dex-Trade',
market_logo: 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAGTGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNi4wLWMwMDIgNzkuMTY0NDYwLCAyMDIwLzA1LzEyLTE2OjA0OjE3ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdEV2dD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIiB4bWxuczpwaG90b3Nob3A9Imh0dHA6Ly9ucy5hZG9iZS5jb20vcGhvdG9zaG9wLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgMjEuMiAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDIyLTA2LTI2VDIxOjEwOjEwLTA2OjAwIiB4bXA6TWV0YWRhdGFEYXRlPSIyMDIyLTA2LTI2VDIxOjEwOjEwLTA2OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAyMi0wNi0yNlQyMToxMDoxMC0wNjowMCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDozNDk5ZmMyYy02YjE0LWNhNDItOWNkYS05ODI4YTQ5MTBiNWMiIHhtcE1NOkRvY3VtZW50SUQ9ImFkb2JlOmRvY2lkOnBob3Rvc2hvcDpjMzIwOGQ5Yy05M2MyLTNkNDEtYTI0OC0yMjhkMjJkNjhmMmEiIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDoxYmVhYmUxYS1hY2FlLTQ1NGEtYThjMy1mMjlkMWNjOTBhZjkiIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIGRjOmZvcm1hdD0iaW1hZ2UvcG5nIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDoxYmVhYmUxYS1hY2FlLTQ1NGEtYThjMy1mMjlkMWNjOTBhZjkiIHN0RXZ0OndoZW49IjIwMjItMDYtMjZUMjE6MTA6MTAtMDY6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCAyMS4yIChXaW5kb3dzKSIvPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6MzQ5OWZjMmMtNmIxNC1jYTQyLTljZGEtOTgyOGE0OTEwYjVjIiBzdEV2dDp3aGVuPSIyMDIyLTA2LTI2VDIxOjEwOjEwLTA2OjAwIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgMjEuMiAoV2luZG93cykiIHN0RXZ0OmNoYW5nZWQ9Ii8iLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDxwaG90b3Nob3A6RG9jdW1lbnRBbmNlc3RvcnM+IDxyZGY6QmFnPiA8cmRmOmxpPjMyOTEwNDQzQzdFMjhCQTc2QTlCNTRFQkNFMjczMDkwPC9yZGY6bGk+IDwvcmRmOkJhZz4gPC9waG90b3Nob3A6RG9jdW1lbnRBbmNlc3RvcnM+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+3wqdeQAAA4xJREFUOI1NU2tMG1QUPrzpBlRs0U4efTAcK6UMQcu2zsdiZkZndKMjCyoUSCbskel8xB9zw2abmEDiwEQIREVd3TRubqL82SYT1gkUSmlpxyhtN1oeIzCpFST8+Ly96uJNvuSec/J955x7zqVx9x2a8EzSQL+Vhqw2amn7XLVHX96oVGlsaVJlUJKaFVRv0jpe2l3WZKw7XXjlSi/dHBimEecdGhrxEN0e91Hwj2Vyu31kqK41iiWZIEpgIFz9pRsXLlzm9zDWJKxD2StVjdYRJ03PLZFleILI5wvQ/MIilejLu4jiERUvRnyiBIaqWvx3tmp34HsmdLzuFBd6QVfS4/PPkcs9RTQzt0BHjr5nJIrGWmEqFI/n4zVDDTwe7wOBu34/Fu7fx++Li6DIRCYSAUN1zae+ST/RxUtdiqSH0yEUy1hQiJOnGljZlzixu/sGNm/RYnb2Hvr6+3BvbhbllbVMIAoxAhG+MX1bSIaqg00UIeTOQ0fehWPUha/PmtDS2obo+BResiq3COabfQgEAvjhcifzrQVFJGJvacV5Ktr6vJsoEhtVGsywTMM2G/YfeIMTk1PkWJ/9JH/UvCe0aGg4A7P5N+zeW8F8Aqjzt/hJ/KhiRbJOynsMBoP4sfNnaJ8tRlyCBPKsPEgz1UhX5CKW2U3NLTh3/jscOPwWkkUZSJfn/MUSJa7WHHqH92yxWNB9vYdNIgVpshxODiNNpgJFPQS7YxST/gB27ipBZnYBHnlswwrlFWi9QqYmX78JoVAInT918fIj40ScLM/KR6pMyX11H3zIE7V/9iW3c9Sbp6miuqYlbJTuq+TBcBums+dQrNNDkCTh/QtFMjyl2YZr164/GG1axgZoirZfpK86TMrY2CQ4nE78/9jtdpQb9kO3aw+am1vhdI3B4/PC6/Xh1q3bKC2rxPETp7fR0vIyvfn2+x/LMnOhe1GPg4ePwniyHq2t7VxkaelPhhACU/+MUCiS8vJfNbxuWlldZavsnyLvZICU6qLecCAiJhmFRdv5Kh87ZkRbeweustK9vrtsmQY4OTunwOp2eykwPUvUZxmhwMw8ucYnqLTM8EnsGjFfKqK4fz+VgCGGE6MFKXhZv69jcMgWOb8QIuuwk2jI5iSbY4wGbaP0a6+Z6usbnynWlXzBlmRMqlAHMxS5wY25GvfTz+00nTB+tKPHfIP6+gfJ5ZogO+P9DaehK5oI+uoTAAAAAElFTkSuQmCC',
market_url_template: market_url_template,
market_url_case: 'u',
get_data: function(settings, cb) {
// ensure coin info is uppercase
settings.coin = settings.coin.toUpperCase();
settings.exchange = settings.exchange.toUpperCase();
get_orders(settings.coin, settings.exchange, settings.api_error_msg, function(order_error, buys, sells) {
if (order_error == null) {
get_trades(settings.coin, settings.exchange, settings.api_error_msg, function(trade_error, trades) {
if (trade_error == null) {
get_summary(settings.coin, settings.exchange, settings.api_error_msg, (buys == null || buys.length == 0 ? null : buys[0].price), (sells == null || sells.length == 0 ? null : sells[0].price), function(summary_error, stats) {
if (summary_error == null) {
get_chartdata(settings.coin, settings.exchange, settings.api_error_msg, function (chart_error, chartdata) {
if (chart_error == null)
return cb(null, {buys: buys, sells: sells, trades: trades, stats: stats, chartdata: chartdata});
else
return cb(chart_error, null);
});
} else
return cb(summary_error, null);
});
} else
return cb(trade_error, null);
});
} else
return cb(order_error, null);
});
}
};
+166
View File
@@ -0,0 +1,166 @@
const request = require('postman-request');
const base_url = 'https://api.freiexchange.com/public/';
const market_url_template = '{url_prefix}/market/{coin}/{base}';
// initialize the rate limiter to wait 2 seconds between requests to prevent abusing external apis
const rateLimitLib = require('../ratelimit');
const rateLimit = new rateLimitLib.RateLimit(1, 2000, false);
function get_summary(coin, exchange, api_error_msg, cb) {
const req_url = base_url + 'ticker/' + coin;
// pause for 2 seconds before continuing
rateLimit.schedule(function() {
request({uri: req_url, json: true}, function (error, response, body) {
if (error)
return cb(error, null);
else if (body == null || body == '' || typeof body !== 'object') {
const summary = {
'high': 0,
'low': 0,
'volume': 0,
'volume_btc': 0,
'bid': 0,
'ask': 0,
'last': 0,
'change': 0
};
return cb(null, summary);
} else if (body.success != null && body.success == false)
return cb((body.result != null && body.result.message != null ? body.result.message : api_error_msg), null);
else {
try {
const prefix = body[coin + '_' + exchange];
const summary = {
'high': parseFloat(prefix[0].high) || 0,
'low': parseFloat(prefix[0].low) || 0,
'volume': parseFloat(prefix[0].volume24h) || 0,
'volume_btc': parseFloat(prefix[0][`volume24h_${exchange.toLowerCase()}`]) || 0,
'bid': parseFloat(prefix[0].highestBuy) || 0,
'ask': parseFloat(prefix[0].lowestSell) || 0,
'last': parseFloat(prefix[0].last) || 0,
'change': parseFloat(prefix[0].percent_change_24h) || 0
};
return cb(null, summary);
} catch(err) {
return cb(api_error_msg, null);
}
}
});
});
}
function get_trades(coin, exchange, api_error_msg, cb) {
// NOTE: Commenting out the trade data since the trades api seems to have been discontinued.
// It will be reimplemented again later if the trades api ever returns.
// const req_url = base_url + 'trades/' + coin + (exchange == 'LTC' ? '/LTC' : '');
// // pause for 2 seconds before continuing
// rateLimit.schedule(function() {
// request({uri: req_url, json: true}, function (error, response, body) {
// if (error)
// return cb(error, null);
// else if (body == null || body == '' || typeof body !== 'object')
// return cb(api_error_msg, null);
// else if (body.success != null && body.success == false)
// return cb((body.result != null && body.result.message != null ? body.result.message : api_error_msg), null);
// else {
// try {
// let trades = [];
// for (let t = 0; t < body.length; t++) {
// trades.push({
// ordertype: body[t].type,
// price: parseFloat(body[t].price) || 0,
// quantity: parseFloat(body[t].total_coin) || 0,
// timestamp: parseInt(new Date(body[t].time.toString().replace(' ', 'T') + 'Z').getTime() / 1000)
// });
// }
// return cb(null, trades);
// } catch(err) {
// return cb(api_error_msg, null);
// }
// }
// });
// });
return cb(null, []);
}
function get_orders(coin, exchange, api_error_msg, cb) {
const req_url = base_url + 'orderbook/' + coin + (exchange == 'LTC' ? '/LTC' : '');
// NOTE: no need to pause here because this is the first api call
request({uri: req_url, json: true}, function (error, response, body) {
if (error)
return cb(error, null, null);
else if (body == null || body == '' || typeof body !== 'object')
return cb(api_error_msg, null, null);
else if (body.success != null && body.success == false)
return cb((body.result != null && body.result.message != null ? body.result.message : api_error_msg), null, null);
else {
try {
let buys = [];
let sells = [];
for (let b = 0; b < body.BUY.length; b++) {
buys.push({
price: parseFloat(body.BUY[b].price) || 0,
quantity: parseFloat(body.BUY[b].amount) || 0
});
}
for (let s = 0; s < body.SELL.length; s++) {
sells.push({
price: parseFloat(body.SELL[s].price) || 0,
quantity: parseFloat(body.SELL[s].amount) || 0
});
}
return cb(null, buys, sells);
} catch(err) {
return cb(api_error_msg, null, null);
}
}
});
}
module.exports = {
market_name: 'FreiExchange',
market_name_alt: 'FreiXLite',
market_logo: 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAF8WlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNi4wLWMwMDIgNzkuMTY0NDYwLCAyMDIwLzA1LzEyLTE2OjA0OjE3ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdEV2dD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgMjEuMiAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDIyLTA2LTI2VDE2OjU2OjA2LTA2OjAwIiB4bXA6TWV0YWRhdGFEYXRlPSIyMDIyLTA2LTI2VDE2OjU2OjA2LTA2OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAyMi0wNi0yNlQxNjo1NjowNi0wNjowMCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpjM2IyNDVkYy1kZDI2LWYwNGEtYmIwYS03OWJjNDhmZTQxMzMiIHhtcE1NOkRvY3VtZW50SUQ9ImFkb2JlOmRvY2lkOnBob3Rvc2hvcDozZWIzNDAxZC00N2U4LTFmNGUtOGYwMi1kZmMzODg0MzFlZWYiIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo3NzY1YTNlYi1mMjRlLWVlNGQtOGM0My0xYTY3NjgzYTNkMTAiIGRjOmZvcm1hdD0iaW1hZ2UvcG5nIiBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIiBwaG90b3Nob3A6SUNDUHJvZmlsZT0ic1JHQiBJRUM2MTk2Ni0yLjEiPiA8eG1wTU06SGlzdG9yeT4gPHJkZjpTZXE+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJjcmVhdGVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjc3NjVhM2ViLWYyNGUtZWU0ZC04YzQzLTFhNjc2ODNhM2QxMCIgc3RFdnQ6d2hlbj0iMjAyMi0wNi0yNlQxNjo1NjowNi0wNjowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGhvdG9zaG9wIDIxLjIgKFdpbmRvd3MpIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJzYXZlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDpjM2IyNDVkYy1kZDI2LWYwNGEtYmIwYS03OWJjNDhmZTQxMzMiIHN0RXZ0OndoZW49IjIwMjItMDYtMjZUMTY6NTY6MDYtMDY6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCAyMS4yIChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz48i0LMAAAC2UlEQVQ4jV1T70tTURh+7z0/7tbcKjT7XdC3QIi0yBD6F/rQl8A+SIUlaISETSVGn6QocFaYWhARTpG0MLAvFQZFEbg+ZVAYVnO7cwuderubc2/vOS2VLjzsfe/O87zPOee5ABwABMAOwcBrGFBjikA7k7UhJm/3c+vhVc7vdjDZcIKJvfsYB9Og9XwDcbYTylXzV8AigSCTzZ+FjOelhSg9iELo3xXqvwmRvcNFWBqGALZegCkY0MPlMHKJK4zjLMEmJLhAW0icJbhUI+E159EtvKQcxE4AuQ30c5NbvYqcAsAZQnwdbMPUIromoJQ4Jj3vwfSBxhGTHUOT4YLXh/PhTsw+G0X3+RhmX77Apf5+jHu8GCNiJhzG+UgEberRNLDJMK/o6UNcDKOaFghg3nXw/8eurCRU6ToZDGJGuSD0ADzSAtNC2r+UPX8AsVBAJxrFaerVVHd8HJ2JKLqZDC6MjGCS3iXBcBp8/ub2+vrGo9XVFZDilhMvCuRTKczGZ3D+chDnzp/DdLBVT87ZNsZMU08m312N9+6fKhrMQkpYTqIosDz5adX6MsGJx3S9MPpUO1ICIYBbTX19tcVlDkzJtS0UXBezX7/gD58P0y1BXFHkkWHM5XJoVxzQt5Rk/Hejr+RSy+kzDYeqqvbDIJeP9SH6N2pJ9+MEft+0GfNUZwYG9HkU1Kg3b/En1YuUD7W+GyCiD/GwyWqQ9qeucXEwgnO073RdHbp0jTYJKetzoRBt4wnGGMMU5UJl4QLjrRQEJWHCNS56VWzT/8JDuUgUg5OkGCeK75MUKEUe5d53AF4KMAUJDEkaArqFHEJlj+I6qyKs4kwEhST1WfUfkV8J9qEUfGUAW4lcphyQAJfgFRYEubg4KWRsWU+yivCg6qeEWOri1g2hvx6LeOpDKl0T2EMCfsbgIOMlbcxzso2Jzg4mHrQzfuc6k2ePc7FrNzll+uQ8qwJ/AJ4ryGtYbOHVAAAAAElFTkSuQmCC',
market_logo_alt: 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAF8WlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNi4wLWMwMDIgNzkuMTY0NDYwLCAyMDIwLzA1LzEyLTE2OjA0OjE3ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdEV2dD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgMjEuMiAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDIyLTA2LTI2VDE5OjE0OjMzLTA2OjAwIiB4bXA6TWV0YWRhdGFEYXRlPSIyMDIyLTA2LTI2VDE5OjE0OjMzLTA2OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAyMi0wNi0yNlQxOToxNDozMy0wNjowMCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDozYjE4MmI1ZC0zZjU3LWVhNGMtYWFiYS01NmQ0YjVkZWVmNTUiIHhtcE1NOkRvY3VtZW50SUQ9ImFkb2JlOmRvY2lkOnBob3Rvc2hvcDpiNWRkYTk5MS02MGNlLTc5NGQtOWFlNy04MDcwY2I3NWVjYzkiIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDozMjM4NjRhMi0xNjk4LWU0NGUtODA5Yy1iYjc0ZjliM2UyMmEiIGRjOmZvcm1hdD0iaW1hZ2UvcG5nIiBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIiBwaG90b3Nob3A6SUNDUHJvZmlsZT0ic1JHQiBJRUM2MTk2Ni0yLjEiPiA8eG1wTU06SGlzdG9yeT4gPHJkZjpTZXE+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJjcmVhdGVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjMyMzg2NGEyLTE2OTgtZTQ0ZS04MDljLWJiNzRmOWIzZTIyYSIgc3RFdnQ6d2hlbj0iMjAyMi0wNi0yNlQxOToxNDozMy0wNjowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGhvdG9zaG9wIDIxLjIgKFdpbmRvd3MpIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJzYXZlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDozYjE4MmI1ZC0zZjU3LWVhNGMtYWFiYS01NmQ0YjVkZWVmNTUiIHN0RXZ0OndoZW49IjIwMjItMDYtMjZUMTk6MTQ6MzMtMDY6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCAyMS4yIChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4U//lqAAACLUlEQVQ4y42TQUiTYRzGf4GXITsowi4WjIRFXnYLPNRRXSB6M8kCD2J2cAgeJJFEIwRDqA42QmjDTiW2lGkGkRRBGkSktYrUNDZnmOg+9ZvfvqfDNjEx8oWH9/I+//eB/+8hDISBD0AcWIH8GFzagDtJGNyAm+tQOQ98BwzgFTACRMiaR4AZ4DecT8FHgQ5qE8Z/wul14PX+AY+BR8Ay9B1mPKgtqJ4EhoExgHFgHq79z7hZVKSk0ymBZuDsMPAMYA6O5x7Zbrfk9WbkdivtcEgDA9pubdWVzk6F6uslUApib+DYHEACAgJZeXmyUinlTjqRkBmJSJLiNTW63NWl5xUVe4liUHm/pARMWBLI9nhkS9rt79euzyfLspSWJL9fUVBDMKjPpaUSaA2in9rbb/dGImcQmALJ55MkWZK2q6tlR6Oyk0kJ9MLr1dVAIPf7zpfu7oe5pAh2BFJzcyZBU5PMlhZJkm0YEuhBebkuDg0p0NioREGBtVpWNpE0jIW4NIUJywIpGFRa0o7LJUkyp6eVlmT39mrU49GttjZd7+nRUnGxBOlFh6Mr3NEBcbgnkGZnZS0syKqtlR0KyXI6pbo62VVVh671K1yYIoPwCYHkckmFhToKTCbE3gHvASaBH9B+FGNOb+HcHkhPslwn4MZRzAZUjWfxf0qW5zDwDdiCSvMfZTJgYgVO/QJe7u/CGDAKLAIbmftkAvwW3N2FQRP6VqFhEfKXM439a8AfCVrpWXEdq70AAAAASUVORK5CYII=',
market_url_template: market_url_template,
market_url_case: 'u',
market_url: function(settings) {
return 'https://' + (settings.exchange.toUpperCase() == 'LTC' ? 'freixlite' : 'freiexchange') + '.com';
},
isAlt: function(settings) {
return settings.exchange.toUpperCase() == 'LTC';
},
get_data: function(settings, cb) {
// ensure coin info is uppercase
settings.coin = settings.coin.toUpperCase();
settings.exchange = settings.exchange.toUpperCase();
get_orders(settings.coin, settings.exchange, settings.api_error_msg, function(order_error, buys, sells) {
if (order_error == null) {
get_trades(settings.coin, settings.exchange, settings.api_error_msg, function(trade_error, trades) {
if (trade_error == null) {
get_summary(settings.coin, settings.exchange, settings.api_error_msg, function(summary_error, stats) {
if (summary_error == null)
return cb(null, {buys: buys, sells: sells, trades: trades, stats: stats, chartdata: null});
else
return cb(summary_error, null);
});
} else
return cb(trade_error, null);
});
} else
return cb(order_error, null);
});
}
};
+139
View File
@@ -0,0 +1,139 @@
const request = require('postman-request');
const base_url = 'https://trade.nestex.one/api/';
const market_url_template = 'https://trade.nestex.one/spot/{coin}';
// initialize the rate limiter to wait 2 seconds between requests to prevent abusing external apis
const rateLimitLib = require('../ratelimit');
const rateLimit = new rateLimitLib.RateLimit(1, 2000, false);
function get_summary(coin, exchange, api_error_msg, cb) {
const req_url = base_url + 'cg/tickers/' + coin + '_' + exchange;
// pause for 2 seconds before continuing
rateLimit.schedule(function() {
request({uri: req_url, json: true}, function (error, response, body) {
if (error)
return cb(error, null);
else if (body == null || body == '' || typeof body !== 'object')
return cb(api_error_msg, null);
else if (body.error != null)
return cb(body.error, null);
else {
try {
const summary = {
'high': parseFloat(body.high) || 0,
'low': parseFloat(body.low) || 0,
'volume': parseFloat(body.base_volume) || 0,
'volume_btc': parseFloat(body.target_volume) || 0,
'bid': parseFloat(body.bid) || 0,
'ask': parseFloat(body.ask) || 0,
'last': parseFloat(body.last_price) || 0
};
return cb(null, summary);
} catch(err) {
return cb(api_error_msg, null);
}
}
});
});
}
function get_trades(coin, exchange, api_error_msg, cb) {
const req_url = base_url + 'cg/tradebook/' + coin + '_' + exchange;
// pause for 2 seconds before continuing
rateLimit.schedule(function() {
request({uri: req_url, json: true}, function (error, response, body) {
if (error)
return cb(error, null);
else if (body == null || body == '' || typeof body !== 'object')
return cb(api_error_msg, null);
else if (body.error != null)
return cb(body.error, null);
else {
try {
let trades = [];
for (let t = 0; t < body.data.length; t++) {
trades.push({
ordertype: body.data[t].side.toString().toUpperCase(),
price: parseFloat(body.data[t].price) || 0,
quantity: parseFloat(body.data[t].quantity) || 0,
timestamp: parseInt(body.data[t].timestamp) / 1000
});
}
return cb(null, trades);
} catch(err) {
return cb(api_error_msg, null);
}
}
});
});
}
function get_orders(coin, exchange, api_error_msg, cb) {
const req_url = base_url + 'cg/orderbook/' + coin + '_' + exchange + '?depth=100';
// NOTE: no need to pause here because this is the first api call
request({uri: req_url, json: true}, function (error, response, body) {
if (error)
return cb(error, null, null);
else if (body == null || body == '' || typeof body !== 'object')
return cb(api_error_msg, null, null);
else if (body.error != null)
return cb(body.error, null, null);
else {
try {
let buys = [];
let sells = [];
const bids = Object.entries(body.bids);
const asks = Object.entries(body.asks);
for (let b = 0; b < bids.length; b++) {
buys.push({
price: parseFloat(bids[b][0]) || 0,
quantity: parseFloat(bids[b][1]) || 0
});
}
for (let a = 0; a < asks.length; a++) {
sells.push({
price: parseFloat(asks[a][0]) || 0,
quantity: parseFloat(asks[a][1]) || 0
});
}
return cb(null, buys, sells);
} catch(err) {
return cb(api_error_msg, null, null);
}
}
});
}
module.exports = {
market_name: 'NestEx',
market_logo: 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAe1BMVEUAAAD/yQX/yQX/yQX/yQX/yQX/yQX/yQX/yQX/yQX/yQX/yQX/yQX/yQX/yQX/yQX/yQX/yQX/yQX/yQX/yQX/yQX/yQX/yQX/yQX/yQX/yQX/yQX/yQX/yQX/yQX/yQX/yQX/yQX/yQX/yQX/yQX/yQX/yQX/yQX/yQVH+eRqAAAAKXRSTlMArGRHvXthTriLf3FsaFVBMhIGpZqRd1o5JBfRysiyj4RdKSDv4NiehahrJacAAACTSURBVBjTdc1XDsIwEEXRce92nN4rbf8rRApRAAnO37vzMfBHq9T2FfQs0efGFmxu3jvFIYtuSM+Q3HRUYXHHuQHHACXrhb+CQJDMul1MRQCXAdIoCHS8tXGCGosSCoSQIVg+TIUzeicAnubSs2ZjfsyR238UtRC0r0itCOw4kpSxkWYWDqHX3bUcPJwaPhV8hZ+exboH2W57FV0AAAAASUVORK5CYII=',
market_url_template: market_url_template,
market_url_case: 'u',
get_data: function(settings, cb) {
get_orders(settings.coin, settings.exchange, settings.api_error_msg, function(order_error, buys, sells) {
if (order_error == null) {
get_trades(settings.coin, settings.exchange, settings.api_error_msg, function(trade_error, trades) {
if (trade_error == null) {
get_summary(settings.coin, settings.exchange, settings.api_error_msg, function(summary_error, stats) {
if (summary_error == null)
return cb(null, {buys: buys, sells: sells, trades: trades, stats: stats, chartdata: null});
else
return cb(summary_error, null);
});
} else
return cb(trade_error, null);
});
} else
return cb(order_error, null);
});
}
};
+181
View File
@@ -0,0 +1,181 @@
const request = require('postman-request');
const base_url = 'https://api.nonkyc.io/api/v2';
const market_url_template = 'https://nonkyc.io/market/{coin}_{base}';
// initialize the rate limiter to wait 2 seconds between requests to prevent abusing external apis
const rateLimitLib = require('../ratelimit');
const rateLimit = new rateLimitLib.RateLimit(1, 2000, false);
function get_summary(coin, exchange, api_error_msg, cb) {
const req_url = base_url + '/market/getbysymbol/' + coin + '_' + exchange;
// pause for 2 seconds before continuing
rateLimit.schedule(function() {
request({uri: req_url, json: true}, function (error, response, body) {
if (error)
return cb(error, null);
else if (body == null || body == '' || typeof body !== 'object')
return cb(api_error_msg, null);
else if (body.error != null)
return cb((body.error.message != null ? body.error.message : api_error_msg), null);
else {
try {
const summary = {
'high': parseFloat(body.highPriceNumber) || 0,
'low': parseFloat(body.lowPriceNumber) || 0,
'volume': parseFloat(body.volumeNumber) || 0,
'volume_btc': parseFloat(body.volumeUsdNumber) || 0,
'bid': parseFloat(body.bestBidNumber) || 0,
'ask': parseFloat(body.bestAskNumber) || 0,
'last': parseFloat(body.lastPriceNumber) || 0,
'prev': parseFloat(body.yesterdayPriceNumber) || 0,
'change': parseFloat(body.changePercentNumber) || 0
};
return cb(null, summary);
} catch(err) {
return cb(api_error_msg, null);
}
}
});
});
}
function get_trades(coin, exchange, api_error_msg, cb) {
const req_url = base_url + '/historical_trades?ticker_id=' + coin + '_' + exchange + '&limit=300';
// pause for 2 seconds before continuing
rateLimit.schedule(function() {
request({uri: req_url, json: true}, function (error, response, body) {
if (error)
return cb(error, null);
else if (body == null || body == '' || typeof body !== 'object')
return cb(api_error_msg, null);
else if (body.error != null)
return cb((body.error.message != null ? body.error.message : api_error_msg), null);
else {
try {
let trades = [];
for (let t = 0; t < body.length; t++) {
trades.push({
ordertype: body[t].type,
price: parseFloat(body[t].price) || 0,
quantity: parseFloat(body[t].base_volume) || 0,
timestamp: parseInt(new Date(body[t].trade_timestamp).getTime() / 1000)
});
}
return cb(null, trades);
} catch(err) {
return cb(api_error_msg, null);
}
}
});
});
}
function get_orders(coin, exchange, api_error_msg, cb) {
const req_url = base_url + '/market/getorderbookbysymbol/' + coin + '_' + exchange;
// NOTE: no need to pause here because this is the first api call
request({uri: req_url, json: true}, function (error, response, body) {
if (error)
return cb(error, null, null);
else if (body == null || body == '' || typeof body !== 'object')
return cb(api_error_msg, null, null);
else if (body.error != null)
return cb((body.error.message != null ? body.error.message : api_error_msg), null, null);
else {
try {
let buys = [];
let sells = [];
for (let b = 0; b < body.bids.length; b++) {
buys.push({
price: parseFloat(body.bids[b].numberprice) || 0,
quantity: parseFloat(body.bids[b].quantity) || 0
});
}
for (let s = 0; s < body.asks.length; s++) {
sells.push({
price: parseFloat(body.asks[s].numberprice) || 0,
quantity: parseFloat(body.asks[s].quantity) || 0
});
}
return cb(null, buys, sells);
} catch(err) {
return cb(api_error_msg, null, null);
}
}
});
}
function get_chartdata(coin, exchange, api_error_msg, cb) {
const end = Date.now() / 1000;
const start = end - 86400;
const req_url = base_url + '/market/candles/?symbol=' + coin + '_' + exchange + '&from=' + parseInt(start).toString() + '&to=' + parseInt(end).toString() + '&resolution=15' ;
// pause for 2 seconds before continuing
rateLimit.schedule(function() {
request({uri: req_url, json: true}, function (error, response, body) {
if (error)
return cb(error, null);
else if (body == null || body == '' || typeof body !== 'object' || typeof body == 'string' || body instanceof String)
return cb(api_error_msg, null);
else if (body.error != null)
return cb((body.error.message != null ? body.error.message : api_error_msg), null);
else {
try {
let chartdata = [];
for (let c = 0; c < body.bars.length; c++) {
chartdata.push([
parseInt(body.bars[c].time),
parseFloat(body.bars[c].open) || 0,
parseFloat(body.bars[c].high) || 0,
parseFloat(body.bars[c].low) || 0,
parseFloat(body.bars[c].close) || 0
]);
}
return cb(null, chartdata);
} catch(err) {
return cb(api_error_msg, null);
}
}
});
});
}
module.exports = {
market_name: 'Nonkyc',
market_logo: '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 Line truncated
market_url_template: market_url_template,
market_url_case: 'u',
get_data: function(settings, cb) {
get_orders(settings.coin, settings.exchange, settings.api_error_msg, function(order_error, buys, sells) {
if (order_error == null) {
get_trades(settings.coin, settings.exchange, settings.api_error_msg, function(trade_error, trades) {
if (trade_error == null) {
get_summary(settings.coin, settings.exchange, settings.api_error_msg, function(summary_error, stats) {
if (summary_error == null) {
get_chartdata(settings.coin, settings.exchange, settings.api_error_msg, function (chart_error, chartdata) {
if (chart_error == null)
return cb(null, {buys: buys, sells: sells, trades: trades, stats: stats, chartdata: chartdata});
else
return cb(chart_error, null);
});
} else
return cb(summary_error, null);
});
} else
return cb(trade_error, null);
});
} else
return cb(order_error, null);
});
}
};
+182
View File
@@ -0,0 +1,182 @@
const request = require('postman-request');
const base_url = 'https://api.poloniex.com/markets/';
const market_url_template = 'https://poloniex.com/trade/{coin}_{base}?type=spot';
// initialize the rate limiter to wait 2 seconds between requests to prevent abusing external apis
const rateLimitLib = require('../ratelimit');
const rateLimit = new rateLimitLib.RateLimit(1, 2000, false);
function get_summary(coin, exchange, api_error_msg, cb) {
const req_url = base_url + coin + '_' + exchange + '/ticker24h';
// pause for 2 seconds before continuing
rateLimit.schedule(function() {
request({uri: req_url, json: true}, function (error, response, body) {
if (error)
return cb(error, null);
else if (body == null || body == '' || typeof body !== 'object')
return cb(api_error_msg, null);
else if (body.message != null)
return cb(body.message, null);
else {
try {
const summary = {
'high': parseFloat(body.high) || 0,
'low': parseFloat(body.low) || 0,
'volume': parseFloat(body.quantity) || 0,
'volume_btc': parseFloat(body.amount) || 0,
'bid': parseFloat(body.bid) || 0,
'ask': parseFloat(body.ask) || 0,
'change': parseFloat(body.dailyChange || 0) * 100
};
return cb(null, summary);
} catch(err) {
return cb(api_error_msg, null);
}
}
});
});
}
function get_trades(coin, exchange, api_error_msg, cb) {
const req_url = base_url + coin + '_' + exchange + '/trades';
// pause for 2 seconds before continuing
rateLimit.schedule(function() {
request({uri: req_url, json: true}, function (error, response, body) {
if (error)
return cb(error, null);
else if (body == null || body == '' || typeof body !== 'object')
return cb(api_error_msg, null);
else if (body.message != null)
return cb(body.message, null);
else {
try {
let trades = [];
for (let t = 0; t < body.length; t++) {
trades.push({
ordertype: body[t].takerSide,
price: parseFloat(body[t].price) || 0,
quantity: parseFloat(body[t].quantity) || 0,
timestamp: parseInt(new Date(body[t].ts).getTime() / 1000)
});
}
return cb(null, trades);
} catch(err) {
return cb(api_error_msg, null);
}
}
});
});
}
function get_orders(coin, exchange, api_error_msg, cb) {
const req_url = base_url + coin + '_' + exchange + '/orderBook?limit=50';
// NOTE: no need to pause here because this is the first api call
request({uri: req_url, json: true}, function (error, response, body) {
if (error)
return cb(error, null, null);
else if (body == null || body == '' || typeof body !== 'object')
return cb(api_error_msg, null, null);
else if (body.message != null)
return cb(body.message, null, null);
else {
try {
let buys = [];
let sells = [];
for (let b = 0; b < body.bids.length; b += 2) {
buys.push({
price: parseFloat(body.bids[b]) || 0,
quantity: parseFloat(body.bids[b + 1]) || 0
});
}
for (let s = 0; s < body.asks.length; s += 2) {
sells.push({
price: parseFloat(body.asks[s]) || 0,
quantity: parseFloat(body.asks[s + 1]) || 0
});
}
return cb(null, buys, sells);
} catch(err) {
return cb(api_error_msg, null, null);
}
}
});
}
function get_chartdata(coin, exchange, api_error_msg, cb) {
const end = Date.now();
const start = end - 86400000;
const req_url = base_url + coin + '_' + exchange + '/candles?interval=MINUTE_15&limit=96&startTime=' + start.toString() + '&endTime=' + end.toString();
// pause for 2 seconds before continuing
rateLimit.schedule(function() {
request({uri: req_url, json: true}, function (error, response, body) {
if (error)
return cb(error, null);
else if (body == null || body == '' || typeof body !== 'object' || typeof body == 'string' || body instanceof String)
return cb(api_error_msg, null);
else if (body.message != null)
return cb(body.message, null);
else {
try {
let chartdata = [];
for (let c = 0; c < body.length; c++)
chartdata.push([
parseInt(body[c][9]),
parseFloat(body[c][2]) || 0,
parseFloat(body[c][1]) || 0,
parseFloat(body[c][0]) || 0,
parseFloat(body[c][3]) || 0
]);
return cb(null, chartdata);
} catch(err) {
return cb(api_error_msg, null);
}
}
});
});
}
module.exports = {
market_name: 'Poloniex',
market_logo: 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAACXBIWXMAAAsTAAALEwEAmpwYAAAF8WlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNi4wLWMwMDIgNzkuMTY0NDYwLCAyMDIwLzA1LzEyLTE2OjA0OjE3ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdEV2dD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgMjEuMiAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDIwLTEyLTI1VDIwOjU1OjMzLTA3OjAwIiB4bXA6TWV0YWRhdGFEYXRlPSIyMDIwLTEyLTI1VDIwOjU1OjMzLTA3OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAyMC0xMi0yNVQyMDo1NTozMy0wNzowMCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDphMzU0YTQ5My02M2VlLTQ3NDUtOTAyMC1jZjQ5ZTZlODRlNDQiIHhtcE1NOkRvY3VtZW50SUQ9ImFkb2JlOmRvY2lkOnBob3Rvc2hvcDowZTZlOTkxZi05YTM4LTgxNDAtYTlmMS0yMGU3MThjOTU5YjMiIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDplNzcwYmQ5Zi1kMzM2LTliNGQtYmVhMC0wOTIwNjZkNDIwZDIiIGRjOmZvcm1hdD0iaW1hZ2UvcG5nIiBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIiBwaG90b3Nob3A6SUNDUHJvZmlsZT0ic1JHQiBJRUM2MTk2Ni0yLjEiPiA8eG1wTU06SGlzdG9yeT4gPHJkZjpTZXE+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJjcmVhdGVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOmU3NzBiZDlmLWQzMzYtOWI0ZC1iZWEwLTA5MjA2NmQ0MjBkMiIgc3RFdnQ6d2hlbj0iMjAyMC0xMi0yNVQyMDo1NTozMy0wNzowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGhvdG9zaG9wIDIxLjIgKFdpbmRvd3MpIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJzYXZlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDphMzU0YTQ5My02M2VlLTQ3NDUtOTAyMC1jZjQ5ZTZlODRlNDQiIHN0RXZ0OndoZW49IjIwMjAtMTItMjVUMjA6NTU6MzMtMDc6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCAyMS4yIChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4yAdy4AAACnUlEQVQ4y11TS09TQRid9raFloIY45/wHUNijEsXBjeKC40r3Wrig7b0AS229AVIotISATdiSohuVJDYEkXjxsSFCQlEDSASdvIwo4BiheOZK1x6bTLp3PnOOd/3nZlPOJKpOVs8IUWDXwqPT4p6rxTBkHSn0tKWSEqNq5J7EQgy5iGGcV+DVBw7ucIajRX33exAW6GA8PM8EoURnM/1QzQ2wZVMoZJLhBpxNpdDYmQE4Xwe7fw/QI6NXLGLGXY0hTH2eQalvzM9PbAEgrAFQ6jr7jbFxme+oCocgeIKF8uzU2B/uhW/VlYM0PzCAjR/AO5IM1alNM5//1zFoXQbLKzQmUxJobEXVSr7w6X+flOm7KvX2Hndg8WlJePs8sAAsV5UkWNV3pWxDJoFZ0tcD7wZHzeJnLvTiaPxhL4fm5qElYlcxNJAKK5wbApUsAUX+zrIwOL3H4bAV5Zf4fHB+/ARau9264a6iVUcx5YAFwSzVDMgvD5cuN9nquLdh48QV67CwgS2VHqLbBawlAhc7HtgEng7MQGNAlokAjsF7PH/BOz8cLe2wklnD7e149vyskH+ND+Psmv1SAwOoa73HkQgpLegbQnoJipDoi3QmH1scsqU/TiFT9y6re8nZ2dRRkx5LLZtInuXbnWNvC7/00ETOfD4CXaTgLU148w/NKRfeTVbsahrZDnSzgdzKtMFrK8bwPdT0xStRzkf2cRMySvd2MDpbBds5FSoGXFFY3IPK0BJ33/4ImtS9ISuq1WTTqO4srotQuxechRXWCPNxWOZLDpGRxEaHkacg3Kyp5dzEEI5y3TSMAvnoZZnsXwBgWfDaH/xEkc6M9CabxQFQXMi1vJvTL2b49zYJFV52uY466XyTB9nhVFYZlfcv+GHou2DaH9cAAAAAElFTkSuQmCC',
market_url_template: market_url_template,
market_url_case: 'u',
get_data: function(settings, cb) {
// ensure coin info is uppercase
settings.coin = settings.coin.toUpperCase();
settings.exchange = settings.exchange.toUpperCase();
get_orders(settings.coin, settings.exchange, settings.api_error_msg, function(order_error, buys, sells) {
if (order_error == null) {
get_trades(settings.coin, settings.exchange, settings.api_error_msg, function(trade_error, trades) {
if (trade_error == null) {
get_summary(settings.coin, settings.exchange, settings.api_error_msg, function(summary_error, stats) {
if (summary_error == null) {
get_chartdata(settings.coin, settings.exchange, settings.api_error_msg, function (chart_error, chartdata) {
if (chart_error == null)
return cb(null, {buys: buys, sells: sells, trades: trades, stats: stats, chartdata: chartdata});
else
return cb(chart_error, null);
});
} else
return cb(summary_error, null);
});
} else
return cb(trade_error, null);
});
} else
return cb(order_error, null);
});
}
};
+144
View File
@@ -0,0 +1,144 @@
const request = require('postman-request');
const base_url = 'https://qutrade.io/api/v1/';
const market_url_template = 'https://qutrade.io/en/?market={coin}_{base}';
// initialize the rate limiter to wait 2 seconds between requests to prevent abusing external apis
const rateLimitLib = require('../ratelimit');
const rateLimit = new rateLimitLib.RateLimit(1, 2000, false);
function get_summary(coin, exchange, api_error_msg, cb) {
const pair_key = coin.toLowerCase() + '_' + exchange.toLowerCase();
const req_url = base_url + 'market_data/?pair=' + pair_key;
// pause for 2 seconds before continuing
rateLimit.schedule(function() {
request({uri: req_url, json: true}, function (error, response, body) {
if (error)
return cb(error, null);
else if (body == null || body == '' || typeof body !== 'object' || body.result == null || body.list == null || body.list[pair_key] == null)
return cb(api_error_msg, null);
else if (body.result !== 'success')
return cb(body.info || api_error_msg, null);
else {
try {
const pair_data = body.list[pair_key];
const summary = {
'high': parseFloat(pair_data.high) || 0,
'low': parseFloat(pair_data.low) || 0,
'volume': parseFloat(pair_data.asset_1_volume) || 0,
'volume_btc': parseFloat(pair_data.asset_2_volume) || 0,
'bid': parseFloat(pair_data.bid) || 0,
'ask': parseFloat(pair_data.ask) || 0,
'last': parseFloat(pair_data.price) || 0,
'change': parseFloat(pair_data.trend) || 0
};
return cb(null, summary);
} catch(err) {
return cb(api_error_msg, null);
}
}
});
});
}
function get_trades(coin, exchange, api_error_msg, cb) {
const pair_key = coin.toLowerCase() + '_' + exchange.toLowerCase();
const req_url = base_url + 'market_trades/?pair=' + pair_key + '&limit=100';
// pause for 2 seconds before continuing
rateLimit.schedule(function() {
request({uri: req_url, json: true}, function (error, response, body) {
if (error)
return cb(error, null);
else if (body == null || body == '' || typeof body !== 'object' || body.result == null || body.list == null || body.list.length == 0)
return cb(api_error_msg, null);
else if (body.result !== 'success')
return cb(body.info || api_error_msg, null);
else {
try {
let trades = [];
for (let t = 0; t < body.list.length; t++) {
trades.push({
ordertype: body.list[t].side.toString().toUpperCase(),
price: parseFloat(body.list[t].price) || 0,
quantity: parseFloat(body.list[t].amount) || 0,
timestamp: parseInt(body.list[t].timestamp)
});
}
return cb(null, trades);
} catch(err) {
return cb(api_error_msg, null);
}
}
});
});
}
function get_orders(coin, exchange, api_error_msg, cb) {
const pair_key = coin.toLowerCase() + '_' + exchange.toLowerCase();
const req_url = base_url + 'market_depth/?pair=' + pair_key + '&limit=100';
// NOTE: no need to pause here because this is the first api call
request({uri: req_url, json: true}, function (error, response, body) {
if (error)
return cb(error, null, null);
else if (body == null || body == '' || typeof body !== 'object' || body.result == null || body.list == null || body.list[pair_key] == null)
return cb(api_error_msg, null, null);
else if (body.result !== 'success')
return cb(body.info || api_error_msg, null, null);
else {
try {
let buys = [];
let sells = [];
const pair_data = body.list[pair_key];
for (let b = 0; b < pair_data.bids.length; b++) {
buys.push({
price: parseFloat(pair_data.bids[b][0]) || 0,
quantity: parseFloat(pair_data.bids[b][1]) || 0
});
}
for (let s = 0; s < pair_data.asks.length; s++) {
sells.push({
price: parseFloat(pair_data.asks[s][0]) || 0,
quantity: parseFloat(pair_data.asks[s][1]) || 0
});
}
return cb(null, buys, sells);
} catch(err) {
return cb(api_error_msg, null, null);
}
}
});
}
module.exports = {
market_name: 'Qutrade',
market_logo: 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAhFBMVEUAAAAuSVwvR14vSFwwSF0wSF0wSF0wSF4xR14wR10wSF0vSF0uQ14xR1swSV0vSF0wSF0vSF0vRV4wR10vSF0vSF0xSVsvSV0wSlwxSF0wR1wuSFwwSF0vSV0wSl0vSF4uSFcyRmQuSVg6R2UsRGAxSFwwR1wySlswSF0xSlsyR18uR1sSei0+AAAAKHRSTlMAUg3n3KqdSC+jl0xCHgX89/LPvrOSiYR9d2VbWDwrJhfr2o1rUBQITdwwpgAAAJxJREFUGNNtj0kOgzAUQ02ANMxzmSmdHXr/+zWgZte3sfT99CVj59LkZNG8YDnT9X2X7HHQLW+151p9QhNOwdiadw7ASEaweEzhUCbAaQaWEREnBBTAvOUCLgGGqGh63lyv3ByAtTGQ6ivWbf8tKDHoGA9lLNMnISMIXcLikE+g1509nJhNJgLW4lgE1aa/KfSk9NjCkvgkM1/hH1/r4gv6hEKdhQAAAABJRU5ErkJggg==',
market_url_template: market_url_template,
market_url_case: 'l',
get_data: function(settings, cb) {
get_orders(settings.coin, settings.exchange, settings.api_error_msg, function(order_error, buys, sells) {
if (order_error == null) {
get_trades(settings.coin, settings.exchange, settings.api_error_msg, function(trade_error, trades) {
if (trade_error == null) {
get_summary(settings.coin, settings.exchange, settings.api_error_msg, function(summary_error, stats) {
if (summary_error == null)
return cb(null, {buys: buys, sells: sells, trades: trades, stats: stats, chartdata: null});
else
return cb(summary_error, null);
});
} else
return cb(trade_error, null);
});
} else
return cb(order_error, null);
});
}
};
+143
View File
@@ -0,0 +1,143 @@
const request = require('postman-request');
const base_url = 'https://yobit.net/api/3';
const market_url_template = 'https://yobit.net/en/trade/{coin}/{base}';
// initialize the rate limiter to wait 2 seconds between requests to prevent abusing external apis
const rateLimitLib = require('../ratelimit');
const rateLimit = new rateLimitLib.RateLimit(1, 2000, false);
function get_summary(coin, exchange, api_error_msg, cb) {
const req_url = base_url + '/ticker/' + coin + '_' + exchange;
// pause for 2 seconds before continuing
rateLimit.schedule(function() {
request({uri: req_url, json: true}, function (error, response, body) {
if (error)
return cb(error, null);
else if (body == null || body == '' || typeof body !== 'object')
return cb(api_error_msg, null);
else if (body.success == 0)
return cb((body.error != null ? body.error : api_error_msg), null);
else {
try {
const prefix = body[coin + '_' + exchange];
const summary = {
'high': parseFloat(prefix.high) || 0,
'low': parseFloat(prefix.low) || 0,
'volume': parseFloat(prefix.vol) || 0,
'bid': parseFloat(prefix.buy) || 0,
'ask': parseFloat(prefix.sell) || 0,
'last': parseFloat(prefix.last) || 0
};
return cb(null, summary);
} catch(err) {
return cb(api_error_msg, null);
}
}
});
});
}
function get_trades(coin, exchange, api_error_msg, cb) {
const req_url = base_url + '/trades/' + coin + '_' + exchange;
// pause for 2 seconds before continuing
rateLimit.schedule(function() {
request({uri: req_url, json: true}, function (error, response, body) {
if (error)
return cb(error, null);
else if (body == null || body == '' || typeof body !== 'object')
return cb(api_error_msg, null);
else if (body.success == 0)
return cb((body.error != null ? body.error : api_error_msg), null);
else {
try {
const prefix = body[coin + '_' + exchange];
let trades = [];
for (let t = 0; t < prefix.length; t++) {
trades.push({
ordertype: (prefix[t].type.toLowerCase() == 'bid' ? 'BUY' : 'SELL'),
price: parseFloat(prefix[t].price) || 0,
quantity: parseFloat(prefix[t].amount) || 0,
timestamp: parseInt(prefix[t].timestamp)
});
}
return cb(null, trades);
} catch(err) {
return cb(api_error_msg, null);
}
}
});
});
}
function get_orders(coin, exchange, api_error_msg, cb) {
const req_url = base_url + '/depth/' + coin + '_' + exchange;
// NOTE: no need to pause here because this is the first api call
request({uri: req_url, json: true}, function (error, response, body) {
if (error)
return cb(error, null, null);
else if (body == null || body == '' || typeof body !== 'object')
return cb(api_error_msg, null, null);
else if (body.success == 0)
return cb((body.error != null ? body.error : api_error_msg), null, null);
else {
try {
const prefix = body[coin + '_' + exchange];
let buys = [];
let sells = [];
for (let b = 0; b < prefix.bids.length; b++) {
buys.push({
price: parseFloat(prefix.bids[b][0]) || 0,
quantity: parseFloat(prefix.bids[b][1]) || 0
});
}
for (let s = 0; s < prefix.asks.length; s++) {
sells.push({
price: parseFloat(prefix.asks[s][0]) || 0,
quantity: parseFloat(prefix.asks[s][1]) || 0
});
}
return cb(null, buys, sells);
} catch(err) {
return cb(api_error_msg, null, null);
}
}
});
}
module.exports = {
market_name: 'Yobit',
market_logo: 'iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAEsUExURQCZ2ACV1gCY2ACO1ACR1QCT1gCP1AKa2AGZ2AOa2QCX1wOa2AGa2ACS1QCU1gCV1wSb2QCY1wGV16Pc8tLu+Q6c2QCa2ASa2Ruk3DKt4AKZ2Aud2nrL61y/5gGS1QOb2WLB6Cyr3wOW1wSZ2GrE6XrK6zKu4EC04lG65eH1+1C75bjk9Syq3s7t+HjK64DN7ACQ1eT2/AOY2Lnk9Fq+55PV70y45Kne8hCd2sXq98vs+IXN7IrQ7YvR7gOR1ZnX8ASa2FG35Aec2WDB5wmd2tXw+QCM0zuu4Aab2QOO1AKS1ZXW70a24wGX153Z8ard8giY2Aub2QWb2bvm9cTp9mbC6GXC6MPo9h6j3Cap3iKo3iGo3s3s+Cus34nR7d3z+n3M7ACQ1Nry+tjy+pKA2eoAAADASURBVBjTY2AgDLjZgQQnkObkFuAAMsX4+HkYGHiZ2Fl5WVmYgAL8MuqsnIJxUSxmwuZ6yoI8DKwahqLMCcGeoa6O7sLaSlIM8sx+3hHxogFJkcyMbFZavAwcRoEhyT7MsTEcnE52tjZsDAwCBmHhib7RIkwOLmym+kABTjkTf68gIUtnNzYLTTU+oACLgjGLh5COiL21rior0FEcXBKSXBw8jNKyKoogPhBwMQIJdiYWFkYIn4EDTHNwirMzEAMA6McVLdcDhkkAAAAASUVORK5CYII=',
market_url_template: market_url_template,
market_url_case: 'u',
get_data: function(settings, cb) {
// ensure coin info is lowercase
settings.coin = settings.coin.toLowerCase();
settings.exchange = settings.exchange.toLowerCase();
get_orders(settings.coin, settings.exchange, settings.api_error_msg, function(order_error, buys, sells) {
if (order_error == null) {
get_trades(settings.coin, settings.exchange, settings.api_error_msg, function(trade_error, trades) {
if (trade_error == null) {
get_summary(settings.coin, settings.exchange, settings.api_error_msg, function(summary_error, stats) {
if (summary_error == null)
return cb(null, {buys: buys, sells: sells, trades: trades, stats: stats, chartdata: null});
else
return cb(summary_error, null);
});
} else
return cb(trade_error, null);
});
} else
return cb(order_error, null);
});
}
};
+39
View File
@@ -0,0 +1,39 @@
const http = require('http');
const settings = require('./settings');
const expected = settings.block_page.genesis_block;
let state = {status:'offline',chain:'NRB',height:null};
function rpc(method, params) {
return new Promise((resolve,reject)=>{
const data=JSON.stringify({id:'nerbus-health',method,params});
const r=http.request({host:settings.wallet.host,port:settings.wallet.port,method:'POST',
auth:settings.wallet.username+':'+settings.wallet.password,
headers:{'Content-Type':'application/json','Content-Length':Buffer.byteLength(data)}}, res=>{
let raw='';res.on('data',d=>raw+=d);res.on('end',()=>{
try { const v=JSON.parse(raw);if(v.error) reject(Error('RPC error'));else resolve(v.result); }
catch(e){reject(e);}
});
});
r.setTimeout(3000,()=>r.destroy(Error('RPC timeout')));
r.on('error',reject);r.end(data);
});
}
async function probe() {
try {
if(await rpc('getblockhash',[0])!==expected) {
state={status:'wrong-network',chain:'NRB',height:null};return;
}
state={status:'online',chain:'NRB',height:await rpc('getblockcount',[])};
} catch (_) { state={status:'offline',chain:'NRB',height:null}; }
}
module.exports=function(app) {
probe();setInterval(probe,10000).unref();
app.get('/healthz',(req,res)=>res.json({explorer:'Nerbus',...state}));
app.use((req,res,next)=>{res.locals.nerbusState=state;
if(state.status!=='online' && req.path==='/ext/getsummary')
return res.json({status:state.status,difficulty:'-',difficultyHybrid:'',supply:'-',hashrate:'-',
connections:'-',blockcount:'-',lastPrice:'0',lastUSDPrice:'0',marketcap:'0.00',usdMarketcap:'0.00'});
if(state.status!=='online' && req.path.startsWith('/api/'))
return res.status(503).json({error:'Nerbus node is offline; network launch pending'});
next();
});
};
+33
View File
@@ -0,0 +1,33 @@
var rpc = require('./jsonrpc');
function Client(opts) {
this.rpc = new rpc.Client(opts);
};
Client.prototype.cmd = function() {
var args = [].slice.call(arguments);
var cmd = args.shift();
callRpc(cmd, args, this.rpc);
};
function callRpc (cmd, args, rpc) {
var fn = args[args.length - 1];
// if the last argument is a callback, pop it from the args list
if (typeof fn === 'function')
args.pop();
else
fn = function () {};
rpc.call(cmd, args, function () {
var args = [].slice.call(arguments);
args.unshift(null);
fn.apply(this, args);
}, function(err) {
fn(err);
});
};
module.exports.Client = Client;
+351
View File
@@ -0,0 +1,351 @@
var onode = require('./node'),
express = require('express'),
settings = require('./settings');
module.exports = function() {
function express_app() {
var app = express();
app.get('/{*splat}', hasAccess, function(req, res) {
var method = req.path.substring(1, req.path.length);
if ('undefined' != typeof requires_passphrase[method]) {
if (wallet_passphrase)
client.walletPassphrase(wallet_passphrase, 10);
else
res.send('A wallet passphrase is needed and has not been set.');
}
const query_parameters = req.query;
let params = [];
Object.keys(query_parameters).forEach(function(parameter) {
let param = query_parameters[parameter];
if (!isNaN(param))
param = parseFloat(param);
params.push(param);
});
var command = [];
switch (method) {
case 'getnetworkhashps':
case 'getmininginfo':
case 'getdifficulty':
case 'getconnectioncount':
case 'getblockcount':
case 'getblockhash':
case 'getblock':
case 'getrawtransaction':
case 'getsupply':
case 'getinfo':
case 'getblockchaininfo':
case 'getpeerinfo':
case 'gettxoutsetinfo':
case 'getmaxmoney':
case 'getmaxvote':
case 'getvote':
case 'getphase':
case 'getreward':
case 'getnextrewardestimate':
case 'getnextrewardwhenstr':
case 'getvotelist':
case 'getmasternodecount':
case 'getmasternodelist':
case 'verifymessage':
case 'sendmany':
command = specialApiCase(method, query_parameters);
break;
default:
command = [{
method: method,
params: params
}];
break;
}
client.cmd(command, function(err, response) {
if (err) {
console.log(err);
res.send(`${settings.localization.ex_error}: ${settings.localization.check_console}`);
} else {
if (typeof response === 'object')
res.json(response);
else {
res.setHeader('content-type', 'text/plain');
res.end(response.toString());
}
}
});
});
function hasAccess(req, res, next) {
var method = req.path.substring(1, req.path.length);
var method_enabled = false;
// check if this is a "normal" api method
if (settings.api_page.public_apis.rpc[method] != null) {
// check the enabled property of this normal method
method_enabled = settings.api_page.public_apis.rpc[method].enabled;
} else {
// look for this method in the list of blockchain specific features
Object.keys(settings.blockchain_specific).forEach(function(key, index, map) {
// check if this feature is enabled and has a definition for this api cmd
if (settings.blockchain_specific[key].enabled == true && settings.blockchain_specific[key]['public_apis'] != null && settings.blockchain_specific[key]['public_apis'][method] != null) {
// check the enabled properly of this blockchain specific method
method_enabled = settings.blockchain_specific[key]['public_apis'][method].enabled;
}
});
}
// only show disabled msg for outside calls. internal calls should always go through
if ((!settings.api_page.enabled || method_enabled == null || !method_enabled) && req.headers.host.indexOf('127.0.0.1') == -1)
res.end(settings.localization.method_disabled);
else if (method == 'getnetworkhashps' && !settings.shared_pages.show_hashrate) {
// getnetworkhashps requires show_hashrate to be enabled or else hashrate cannot be returned
res.end('-');
} else {
if (accesslist.type == 'all')
return next();
if ('undefined' == typeof accesslist[method]) {
if (accesslist.type == 'only')
res.end('This method is restricted');
else
return next();
} else {
if (accesslist[method] == true)
return next();
else
res.end('This method is restricted');
}
}
}
function prepareRpcCommand(cmd, addParams) {
var method_name = '';
var params = addParams || [];
// Check for null/blank string
if (cmd != null && cmd.trim() != '') {
// Split cmd by spaces
var split = cmd.split(' ');
for (i=0; i<split.length; i++) {
if (i==0)
method_name = split[i];
else
params.push(split[i]);
}
}
return { method: method_name, parameters: params };
}
function specialApiCase(method_name, query_parameters) {
var params = [];
switch (method_name) {
case 'getnetworkhashps':
case 'getmininginfo':
case 'getdifficulty':
case 'getconnectioncount':
case 'getblockcount':
case 'getinfo':
case 'getblockchaininfo':
case 'getpeerinfo':
case 'gettxoutsetinfo':
case 'getvotelist':
case 'getmasternodecount':
case 'getmasternodelist':
var cmd = prepareRpcCommand(settings.api_cmds[method_name]);
method_name = cmd.method;
params = cmd.parameters;
break;
case 'getmaxmoney':
case 'getmaxvote':
case 'getvote':
case 'getphase':
case 'getreward':
case 'getnextrewardestimate':
case 'getnextrewardwhenstr':
case 'getsupply':
var cmd = prepareRpcCommand(settings.blockchain_specific.heavycoin.api_cmds[method_name]);
method_name = cmd.method;
params = cmd.parameters;
break;
case 'getblockhash':
Object.keys(query_parameters).forEach(function(parameter) {
if (parameter == 'height' || parameter == 'index')
params.push(parseInt(query_parameters[parameter]));
});
var cmd = prepareRpcCommand(settings.api_cmds.getblockhash, params);
method_name = cmd.method;
params = cmd.parameters;
break;
case 'getblock':
Object.keys(query_parameters).forEach(function(parameter) {
if (parameter == 'hash')
params.push(query_parameters[parameter]);
});
var cmd = prepareRpcCommand(settings.api_cmds.getblock, params);
method_name = cmd.method;
params = cmd.parameters;
break;
case 'getrawtransaction':
Object.keys(query_parameters).forEach(function(parameter) {
if (parameter == 'txid')
params.push(query_parameters[parameter]);
else if (parameter == 'decrypt')
params.push(parseInt(query_parameters[parameter]));
});
var cmd = prepareRpcCommand(settings.api_cmds.getrawtransaction, params);
method_name = cmd.method;
params = cmd.parameters;
break;
case 'verifymessage':
Object.keys(query_parameters).forEach(function(parameter) {
if (parameter == 'address' || parameter == 'message')
params.push(query_parameters[parameter]);
if (parameter == 'signature') {
let param = decodeURIComponent(query_parameters[parameter]);
while (param.indexOf(' ') > -1)
param = param.replace(' ', '+');
params.push(param);
}
});
var cmd = prepareRpcCommand(settings.api_cmds.verifymessage, params);
method_name = cmd.method;
params = cmd.parameters;
break;
case 'sendmany':
var after_account = false;
var before_min_conf = true;
var address_info = {};
Object.keys(query_parameters).forEach(function(parameter) {
if (parameter == 'minconf') {
before_min_conf = false;
params.push(address_info);
}
let param = query_parameters[parameter];
if (!isNaN(param))
param = parseFloat(param);
if (after_account && before_min_conf)
address_info[parameter] = param;
else
params.push(param);
if (parameter == 'account')
after_account = true;
});
if (before_min_conf)
params.push(address_info);
break;
}
return [{
method: method_name,
params: params
}];
}
return app;
};
var accesslist = {};
var client = {};
var wallet_passphrase = null;
var requires_passphrase = {
'dumpprivkey': true,
'importprivkey': true,
'keypoolrefill': true,
'sendfrom': true,
'sendmany': true,
'sendtoaddress': true,
'signmessage': true,
'signrawtransaction': true
};
accesslist.type = 'all';
function setAccess(type, access_list) {
// reset
accesslist = {};
accesslist.type = type;
if (type == "only") {
for (i = 0; i < access_list.length; i++)
accesslist[access_list[i]] = true;
}
if (type == "restrict") {
for (i = 0; i < access_list.length; i++)
accesslist[access_list[i]] = false;
}
// default is for security reasons. Prevents accidental theft of coins/attack
if (type == 'default-safe') {
var restrict_list = ['dumpprivkey', 'walletpassphrasechange', 'stop'];
accesslist.type = 'restrict';
for (i = 0; i < restrict_list.length; i++)
accesslist[restrict_list[i]] = false;
}
if (type == 'read-only') {
var restrict_list = ['addmultisigaddress', 'addnode', 'backupwallet', 'createmultisig', 'createrawtransaction', 'encryptwallet', 'importprivkey', 'keypoolrefill', 'lockunspent', 'move', 'sendfrom', 'sendmany', 'sendrawtransaction', 'sendtoaddress', 'setaccount', 'setgenerate', 'settxfee', 'signmessage', 'signrawtransaction', 'stop', 'submitblock', 'walletlock', 'walletpassphrasechange'];
accesslist.type = 'restrict';
for (i = 0; i < restrict_list.length; i++)
accesslist[restrict_list[i]] = false;
}
};
function setWalletDetails(details) {
if ('undefined' == typeof details.rpc)
client = new onode.Client(details);
else
client = details;
};
function setWalletPassphrase(passphrase) {
wallet_passphrase = passphrase;
};
return {
app: express_app(),
setAccess: setAccess,
setWalletDetails: setWalletDetails,
setWalletPassphrase: setWalletPassphrase
}
}();
+52
View File
@@ -0,0 +1,52 @@
// Initialize the rate limiting class from Matteo Agosti via https://www.matteoagosti.com/blog/2013/01/22/rate-limiting-function-calls-in-javascript/
var RateLimit = (function() {
var RateLimit = function(maxOps, interval, allowBursts) {
this._maxRate = allowBursts ? maxOps : maxOps / interval;
this._interval = interval;
this._allowBursts = allowBursts;
this._numOps = 0;
this._start = new Date().getTime();
this._queue = [];
};
RateLimit.prototype.schedule = function(fn) {
var that = this,
rate = 0,
now = new Date().getTime(),
elapsed = now - this._start;
if (elapsed > this._interval) {
this._numOps = 0;
this._start = now;
}
rate = this._numOps / (this._allowBursts ? 1 : elapsed);
if (rate < this._maxRate) {
if (this._queue.length === 0) {
this._numOps++;
fn();
} else {
if (fn)
this._queue.push(fn);
this._numOps++;
this._queue.shift()();
}
} else {
if (fn)
this._queue.push(fn);
setTimeout(function() {
that.schedule();
}, 1 / this._maxRate);
}
};
return RateLimit;
})();
module.exports = {
RateLimit: RateLimit
};
+2363
View File
File diff suppressed because it is too large. Load diff