Initial release

This commit is contained in:
Joe Uhren
2019-05-27 10:33:22 -07:00
commit 60fb8e29b4
506 changed files with 88789 additions and 0 deletions
+30
View File
@@ -0,0 +1,30 @@
# Logs
logs
*.log
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directory
# Deployed apps should consider commenting this line out:
# see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git
node_modules
settings.json
.idea
*~
.DS_Store
+30
View File
@@ -0,0 +1,30 @@
Copyright (c) 2019, The Exor Community
Copyright (c) 2017, The Chaincoin Community
Copyright (c) 2015, Iquidus Technology
Copyright (c) 2015, Luke Williams
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of Iquidus Technology nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+248
View File
@@ -0,0 +1,248 @@
eIquidus - 1.0.0
================
The Exor block explorer.
This project is a fork of [Ciquidus Explorer](https://github.com/suprnurd/ciquidus) which is a fork of [Iquidus Explorer](https://github.com/iquidus/explorer). Shoutouts to both Luke Williams for the original code and Alan Rudolf for all the additional bonus featues which saved me tons of time! I'm only standing on the shoulders of giants. Thank you both!!!
### See it in action
- https://explorer.exor.io/
### Requires
- node.js >= 10.15.3
- mongodb >= 4.0.9
- *coind
### Create database
Enter MongoDB cli:
$ mongo
Create database:
> use explorerdb
Create user with read/write access:
> db.createUser( { user: "eiquidus", pwd: "Nd^p2d77ceBX!L", roles: [ "readWrite" ] } )
### Get the source
git clone https://github.com/team-exor/eiquidus explorer
### Install node modules
cd explorer && npm install --production
### Configure
cp ./settings.json.template ./settings.json
*Make required changes in settings.json*
### Start Explorer
npm start
**NOTE:** mongod must be running to start the explorer
The explorer defaults to cluster mode, forking an instance of its process to each cpu core. This results in increased performance and stability. Load balancing gets automatically taken care of and any instances that for some reason die, will be restarted automatically. For testing/development (or if you just wish to) a single instance can be launched with
node --stack-size=10000 bin/instance
To stop the cluster you can use
npm stop
### Syncing databases with the blockchain
sync.js (located in scripts/) is used for updating the local databases. This script must be called from the explorers root directory.
Usage: node scripts/sync.js [database] [mode]
database: (required)
index [mode] Main index: coin info/stats, transactions & addresses
market Market data: summaries, orderbooks, trade history & chartdata
mode: (required for index database only)
update Updates index from last sync to current block
check checks index for (and adds) any missing transactions/addresses
reindex Clears index then resyncs from genesis to current block
notes:
* 'current block' is the latest created block when script is executed.
* The market database only supports (& defaults to) reindex mode.
* If check mode finds missing data(ignoring new data since last sync),
index_timeout in settings.json is set too low.
*It is recommended to have this script launched via a cronjob at 1+ min intervals.*
**crontab**
*Example crontab; update index every minute and market data every 2 minutes*
*/1 * * * * /path/to/explorer/scripts/index.sh /path/to/nodejs > /dev/null 2>&1
*/2 * * * * cd /path/to/explorer && /path/to/nodejs scripts/sync.js market > /dev/null 2>&1
*/5 * * * * cd /path/to/explorer && /path/to/nodejs scripts/peers.js > /dev/null 2>&1
### Wallet
The wallet connected to eIquidus must be running with the following flags:
-daemon -txindex
You may either call your coins daemon using this syntax:
```
coind -daemon -txindex
```
or else you can add the settings to your coins config file (recommended):
```
daemon=1
txindex=1
```
### CORS support
eIquidus has basic CORS support which is useful for taking advantage of the block explorer api in your other web projects.
#### What is CORS?
*CORS description taken from [MaxCDN One](https://www.maxcdn.com/one/visual-glossary/cors/)*
>To prevent websites from tampering with each other, web browsers implement a security measure known as the same-origin policy. The same-origin policy lets resources (such as JavaScript) interact with resources from the same domain, but not with resources from a different domain. This provides security for the user by preventing abuse, such as running a script that reads the password field on a secure website.
>In cases where cross-domain scripting is desired, CORS allows web developers to work around the same-origin policy. CORS adds HTTP headers which instruct web browsers on how to use and manage cross-domain content. The browser then allows or denies access to the content based on its security configuration.
#### How to benefit from using CORS?
You must first set up CORS in eIquidus by editing the settings.json file and setting the value for `usecors` to true.
```
"usecors": true,
```
The `corsorigin` setting defaults to "\*" which allows all requests from any origin. Keeping this setting at "\*" can lead to abuse and is not recommended. Therefore, you should change the `corsorigin` setting to an external origin that you control, as seen in the following example:
```
"corsorigin": "http://example.com",
```
The above example would allow sharing of resources from eIquidus for all data requests coming from the example.com domain while all requests coming from any other domain would be rejected as per normal.
Below is an example of a simple javascript call using [jQuery](https://jquery.com) that could be used on your example.com website to return the current block count from eIquidus:
```
jQuery(document).ready(function($) {
$.ajax({
type: "GET",
url: "http://your-eiquidus-url/api/getblockcount",
cache: false
}).done(function (data) {
alert(data);
});
});
```
### Backup/Restore Database Scripts
A pair of scripts to backup or restore the internal mongo database are included. There are a few different usage options:
**Backup Database (No filename specified)**
`sh create_backup.sh`: Backs up to the explorer/backups directory by default with the current date as the filename in the format yyyy-MMM-dd.tar.gz
**Backup Database (Partial filename specified)**
`sh create_backup.sh test`: Backs up the the explorer/backups directory by default with the filename test.tar.gz
**Backup Database (Full filename specified)**
`sh create_backup.sh today.tar.gz`: Backs up the the explorer/backups directory by default with the filename today.tar.gz
**Backup Database (Full path with partial filename specified)**
`sh create_backup.sh /usr/local/bin/abc`: Backs up the the /usr/local/bin directory with the filename abc.tar.gz
**Backup Database (Full path and filename specified)**
`sh create_backup.sh ~/new.tar.gz`: Backs up the the users home directory with the filename new.tar.gz
**Restore Database (Partial filename specified)**
`sh restore_backup.sh old`: Restores the explorer/scripts/backups/old.tar.gz file
**Restore Database (Full filename specified)**
`sh restore_backup.sh working.tar.gz`: Restores the explorer/scripts/backups/working.tar.gz file
**Restore Database (Full path with partial filename specified)**
`sh restore_backup.sh /home/explorer/backup`: Restores the /home/explorer/backup.tar.gz file
**Restore Database (Full path and filename specified)**
`sh restore_backup.sh ~/archive.tar.gz`: Restores the ~/archive.tar.gz file
### Donate
EXOR: EYYW8Nvz5aJz33M3JNHXG2FEHWUsntozrd
BTC: 15zQAQFB9KR35nPWEJEKvmytUF6fg2zvdP
### Known Issues
**exceeding stack size**
RangeError: Maximum call stack size exceeded
Nodes default stack size may be too small to index addresses with many tx's. If you experience the above error while running sync.js the stack size needs to be increased.
To determine the default setting run
node --v8-options | grep -B0 -A1 stack_size
To run sync.js with a larger stack size launch with
node --stack-size=[SIZE] scripts/sync.js index update
Where [SIZE] is an integer higher than the default.
*note: SIZE will depend on which blockchain you are using, you may need to play around a bit to find an optimal setting*
### License
Copyright (c) 2019, The Exor Community
Copyright (c) 2017, The Chaincoin Community
Copyright (c) 2015, Iquidus Technology
Copyright (c) 2015, Luke Williams
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of Iquidus Technology nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+4
View File
@@ -0,0 +1,4 @@
NOTE: All updates require the explorer to be restarted
1.0.0
-Initial release
+204
View File
@@ -0,0 +1,204 @@
var express = require('express')
, path = require('path')
, nodeapi = require('./lib/nodeapi')
, favicon = require('serve-favicon')
, logger = require('morgan')
, cookieParser = require('cookie-parser')
, bodyParser = require('body-parser')
, settings = require('./lib/settings')
, routes = require('./routes/index')
, lib = require('./lib/explorer')
, db = require('./lib/database')
, locale = require('./lib/locale')
, request = require('request');
var app = express();
// nodeapi
nodeapi.setWalletDetails(settings.wallet);
if (settings.heavy != true) {
nodeapi.setAccess('only', ['getinfo', 'getnetworkhashps', 'getmininginfo','getdifficulty', 'getconnectioncount',
'getmasternodecount', 'getmasternodelist', 'getvotelist', 'getblockcount', 'getblockhash', 'getblock', 'getrawtransaction',
'getpeerinfo', 'gettxoutsetinfo']);
} else {
// enable additional heavy api calls
/*
getvote - Returns the current block reward vote setting.
getmaxvote - Returns the maximum allowed vote for the current phase of voting.
getphase - Returns the current voting phase ('Mint', 'Limit' or 'Sustain').
getreward - Returns the current block reward, which has been decided democratically in the previous round of block reward voting.
getnextrewardestimate - Returns an estimate for the next block reward based on the current state of decentralized voting.
getnextrewardwhenstr - Returns string describing how long until the votes are tallied and the next block reward is computed.
getnextrewardwhensec - Same as above, but returns integer seconds.
getsupply - Returns the current money supply.
getmaxmoney - Returns the maximum possible money supply.
*/
nodeapi.setAccess('only', ['getinfo', 'getstakinginfo', 'getnetworkhashps', 'getdifficulty', 'getconnectioncount',
'getmasternodecount', 'getmasternodelist', 'getvotelist', 'getblockcount', 'getblockhash',
'getblock', 'getrawtransaction', 'getmaxmoney', 'getvote', 'getmaxvote', 'getphase', 'getreward', 'getpeerinfo',
'getnextrewardestimate', 'getnextrewardwhenstr', 'getnextrewardwhensec', 'getsupply', 'gettxoutsetinfo']);
}
// determine if cors should be enabled
if (settings.usecors == true) {
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", settings.corsorigin);
res.header('Access-Control-Allow-Methods', 'DELETE, PUT, GET, POST');
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
}
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');
app.use(favicon(path.join(__dirname, settings.favicon)));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
// routes
app.use('/api', nodeapi.app);
app.use('/', routes);
app.use('/ext/getmoneysupply', function(req,res){
lib.get_supply(function(supply){
res.send(' '+supply);
});
});
app.use('/ext/getaddress/:hash', function(req,res){
db.get_address(req.params.hash, function(address){
if (address) {
var a_ext = {
address: address.a_id,
sent: (address.sent / 100000000),
received: (address.received / 100000000),
balance: (address.balance / 100000000).toString().replace(/(^-+)/mg, ''),
last_txs: address.txs,
};
res.send(a_ext);
} else {
res.send({ error: 'address not found.', hash: req.params.hash})
}
});
});
app.use('/ext/getbalance/:hash', function(req,res){
db.get_address(req.params.hash, function(address){
if (address) {
res.send((address.balance / 100000000).toString().replace(/(^-+)/mg, ''));
} else {
res.send({ error: 'address not found.', hash: req.params.hash})
}
});
});
app.use('/ext/getdistribution', function(req,res){
db.get_richlist(settings.coin, function(richlist){
db.get_stats(settings.coin, function(stats){
db.get_distribution(richlist, stats, function(dist){
res.send(dist);
});
});
});
});
app.use('/ext/getlasttxs/:min', function(req,res){
db.get_last_txs(settings.index.last_txs, (req.params.min * 100000000), function(txs){
res.send({data: txs});
});
});
app.use('/ext/getcurrentprice', function(req,res){
db.get_stats(settings.coin, function (stats) {
eval('var p_ext = { "last_price_'+settings.markets.exchange.toLowerCase()+'": stats.last_price, "last_price_usd": stats.last_usd_price, }');
res.send(p_ext);
});
});
app.use('/ext/connections', function(req,res){
db.get_peers(function(peers){
res.send({data: peers});
});
});
// locals
app.set('title', settings.title);
app.set('symbol', settings.symbol);
app.set('coin', settings.coin);
app.set('locale', locale);
app.set('display', settings.display);
app.set('markets', settings.markets);
app.set('twitter', settings.twitter);
app.set('facebook', settings.facebook);
app.set('googleplus', settings.googleplus);
app.set('bitcointalk', settings.bitcointalk);
app.set('github', settings.github);
app.set('slack', settings.slack);
app.set('discord', settings.discord);
app.set('telegram', settings.telegram);
app.set('reddit', settings.reddit);
app.set('youtube', settings.youtube);
app.set('website', settings.website);
app.set('genesis_block', settings.genesis_block);
app.set('index', settings.index);
app.set('heavy', settings.heavy);
app.set('txcount', settings.txcount);
app.set('nethash', settings.nethash);
app.set('nethash_units', settings.nethash_units);
app.set('show_sent_received', settings.show_sent_received);
app.set('logo', settings.logo);
app.set('theme', settings.theme);
app.set('labels', settings.labels);
app.set('homelink', settings.homelink);
app.set('logoheight', settings.logoheight);
// determine panel offset based on which panels are enabled
var paneltotal=5;
var panelcount=(settings.display.networkpnl > 0 ? 1 : 0)+(settings.display.difficultypnl > 0 ? 1 : 0)+(settings.display.masternodespnl > 0 ? 1 : 0)+(settings.display.coinsupplypnl > 0 ? 1 : 0)+(settings.display.pricepnl > 0 ? 1 : 0);
app.set('paneloffset', paneltotal+1-panelcount);
// determine panel order
var panelorder = new Array();
if (settings.display.networkpnl > 0) panelorder.push({name: 'networkpnl', val: settings.display.networkpnl});
if (settings.display.difficultypnl > 0) panelorder.push({name: 'difficultypnl', val: settings.display.difficultypnl});
if (settings.display.masternodespnl > 0) panelorder.push({name: 'masternodespnl', val: settings.display.masternodespnl});
if (settings.display.coinsupplypnl > 0) panelorder.push({name: 'coinsupplypnl', val: settings.display.coinsupplypnl});
if (settings.display.pricepnl > 0) panelorder.push({name: 'pricepnl', val: settings.display.pricepnl});
panelorder.sort(function(a,b) { return a.val - b.val; });
for (var i=1; i<6; i++) {
app.set('panel'+i.toString(), ((panelorder.length >= i) ? panelorder[i-1].name : ''));
}
// catch 404 and forward to error handler
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});
// development error handler
// will print stacktrace
if (app.get('env') === 'development') {
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: err
});
});
}
// production error handler
// no stacktraces leaked to user
app.use(function(err, req, res, next) {
res.status(err.status || 500);
res.render('error', {
message: err.message,
error: {}
});
});
module.exports = app;
+37
View File
@@ -0,0 +1,37 @@
var cluster = require('cluster');
var fs = require('fs');
if (cluster.isMaster) {
fs.writeFile('./tmp/cluster.pid', process.pid, function (err) {
if (err) {
console.log('Error: unable to create cluster.pid');
process.exit(1);
} else {
console.log('Starting cluster with pid: ' + process.pid);
//ensure workers exit cleanly
process.on('SIGINT', function() {
console.log('Cluster shutting down..');
for (var id in cluster.workers) {
cluster.workers[id].kill();
}
// exit the master process
process.exit(0);
});
// Count the machine's CPUs
var cpuCount = require('os').cpus().length;
// Create a worker for each CPU
for (var i = 0; i < cpuCount; i += 1) {
cluster.fork();
}
// Listen for dying workers
cluster.on('exit', function () {
cluster.fork();
});
}
});
} else {
require('./instance');
}
+62
View File
@@ -0,0 +1,62 @@
#!/usr/bin/env node
var debug = require('debug')('explorer');
var settings = require('../lib/settings');
var db = require('../lib/database');
var app = require('../app');
app.set('port', process.env.PORT || settings.port);
var dbString = 'mongodb://' + settings.dbsettings.user;
dbString = dbString + ':' + settings.dbsettings.password;
dbString = dbString + '@' + settings.dbsettings.address;
dbString = dbString + ':' + settings.dbsettings.port;
dbString = dbString + '/' + settings.dbsettings.database;
db.connect(dbString, function() {
db.check_stats(settings.coin, function(exists) {
if (exists == false) {
console.log('no stats entry found, creating now..');
db.create_stats(settings.coin, function(){
//console.log('stats entry created successfully.');
});
} else {
db.get_stats(settings.coin, function (stats) {
app.locals.stats = stats;
});
}
});
// check markets
var markets = settings.markets.enabled;
for (var i = 0; i < markets.length; i++) {
db.check_market(markets[i], function(market, exists) {
if(exists == false) {
console.log('no %s entry found, creating now..', market);
db.create_market(settings.markets.coin, settings.markets.exchange, market, function(){
});
}
});
}
db.check_richlist(settings.coin, function(exists){
if (exists == false) {
console.log('no richlist entry found, creating now..');
db.create_richlist(settings.coin, function() {
});
}
});
if (settings.heavy == true) {
db.check_heavy(settings.coin, function(exists){
if (exists == false) {
console.log('no heavy entry found, creating now..');
db.create_heavy(settings.coin, function() {
});
}
});
}
var server = app.listen(app.get('port'), function() {
debug('Express server listening on port ' + server.address().port);
});
});
+21
View File
@@ -0,0 +1,21 @@
var request = require('request');
function get_usd_value(cb) {
request({ uri: 'https://api.coindesk.com/v1/bpi/currentprice/USD.json', json: true, headers: {'User-Agent': 'eiquidus'} }, function (error, response, body) {
if (error) {
return cb(error, 0);
} else {
return cb(null, body.bpi.USD['rate_float'].toFixed(4));
}
});
}
module.exports = {
get_data: function (cb) {
var error = null;
get_usd_value(function (err, last_usd) {
if (err) { error = err; }
return cb(error, last_usd);
});
}
};
+85
View File
@@ -0,0 +1,85 @@
module.exports = {
addMultiSigAddress: 'addmultisigaddress',
addNode: 'addnode', // bitcoind v0.8.0+
backupWallet: 'backupwallet',
createMultiSig: 'createmultisig',
createRawTransaction: 'createrawtransaction', // bitcoind v0.7.0+
decodeRawTransaction: 'decoderawtransaction', // bitcoind v0.7.0+
decodeScript: 'decodescript',
dumpPrivKey: 'dumpprivkey',
dumpWallet: 'dumpwallet', // bitcoind v0.9.0+
encryptWallet: 'encryptwallet',
estimateFee: 'estimatefee', // bitcoind v0.10.0x
estimatePriority: 'estimatepriority', // bitcoind v0.10.0+
generate: 'generate', // bitcoind v0.11.0+
getAccount: 'getaccount',
getAccountAddress: 'getaccountaddress',
getAddedNodeInfo: 'getaddednodeinfo', // bitcoind v0.8.0+
getAddressesByAccount: 'getaddressesbyaccount',
getBalance: 'getbalance',
getBestBlockHash: 'getbestblockhash', // bitcoind v0.9.0+
getBlock: 'getblock',
getBlockchainInfo: 'getblockchaininfo', // bitcoind v0.9.2+
getBlockCount: 'getblockcount',
getBlockHash: 'getblockhash',
getBlockTemplate: 'getblocktemplate', // bitcoind v0.7.0+
getChainTips: 'getchaintips', // bitcoind v0.10.0+
getConnectionCount: 'getconnectioncount',
getDifficulty: 'getdifficulty',
getGenerate: 'getgenerate',
getInfo: 'getinfo',
getMempoolInfo: 'getmempoolinfo', // bitcoind v0.10+
getMiningInfo: 'getmininginfo',
getNetTotals: 'getnettotals',
getNetworkInfo: 'getnetworkinfo', // bitcoind v0.9.2+
getNetworkHashPs: 'getnetworkhashps', // bitcoind v0.9.0+
getNewAddress: 'getnewaddress',
getPeerInfo: 'getpeerinfo', // bitcoind v0.7.0+
getRawChangeAddress: 'getrawchangeaddress', // bitcoin v0.9+
getRawMemPool: 'getrawmempool', // bitcoind v0.7.0+
getRawTransaction: 'getrawtransaction', // bitcoind v0.7.0+
getReceivedByAccount: 'getreceivedbyaccount',
getReceivedByAddress: 'getreceivedbyaddress',
getTransaction: 'gettransaction',
getTxOut: 'gettxout', // bitcoind v0.7.0+
getTxOutProof: 'gettxoutproof', // bitcoind v0.11.0+
getTxOutSetInfo: 'gettxoutsetinfo', // bitcoind v0.7.0+
getUnconfirmedBalance: 'getunconfirmedbalance', // bitcoind v0.9.0+
getWalletInfo: 'getwalletinfo', // bitcoind v0.9.2+
help: 'help',
importAddress: 'importaddress', // bitcoind v0.10.0+
importPrivKey: 'importprivkey',
importWallet: 'importwallet', // bitcoind v0.9.0+
keypoolRefill: 'keypoolrefill',
keyPoolRefill: 'keypoolrefill',
listAccounts: 'listaccounts',
listAddressGroupings: 'listaddressgroupings', // bitcoind v0.7.0+
listLockUnspent: 'listlockunspent', // bitcoind v0.8.0+
listReceivedByAccount: 'listreceivedbyaccount',
listReceivedByAddress: 'listreceivedbyaddress',
listSinceBlock: 'listsinceblock',
listTransactions: 'listtransactions',
listUnspent: 'listunspent', // bitcoind v0.7.0+
lockUnspent: 'lockunspent', // bitcoind v0.8.0+
move: 'move',
ping: 'ping', // bitcoind v0.9.0+
prioritiseTransaction: 'prioritisetransaction', // bitcoind v0.10.0+
sendFrom: 'sendfrom',
sendMany: 'sendmany',
sendRawTransaction: 'sendrawtransaction', // bitcoind v0.7.0+
sendToAddress: 'sendtoaddress',
setAccount: 'setaccount',
setGenerate: 'setgenerate',
setTxFee: 'settxfee',
signMessage: 'signmessage',
signRawTransaction: 'signrawtransaction', // bitcoind v0.7.0+
stop: 'stop',
submitBlock: 'submitblock', // bitcoind v0.7.0+
validateAddress: 'validateaddress',
verifyChain: 'verifychain', // bitcoind v0.9.0+
verifyMessage: 'verifymessage',
verifyTxOutProof: 'verifytxoutproof', // bitcoind v0.11.0+
walletLock: 'walletlock',
walletPassphrase: 'walletpassphrase',
walletPassphraseChange: 'walletpassphrasechange'
};
+758
View File
@@ -0,0 +1,758 @@
var mongoose = require('mongoose')
, Stats = require('../models/stats')
, Markets = require('../models/markets')
, Address = require('../models/address')
, Tx = require('../models/tx')
, Richlist = require('../models/richlist')
, Peers = require('../models/peers')
, Heavy = require('../models/heavy')
, lib = require('./explorer')
, settings = require('./settings')
, poloniex = require('./markets/poloniex')
, bittrex = require('./markets/bittrex')
, bleutrade = require('./markets/bleutrade')
, cryptsy = require('./markets/cryptsy')
, cryptopia = require('./markets/cryptopia')
, yobit = require('./markets/yobit')
, empoex = require('./markets/empoex')
, ccex = require('./markets/ccex')
, coinexchange = require('./markets/coinexchange')
, coindesk = require('./apis/coindesk');
function find_address(hash, cb) {
Address.findOne({a_id: hash}, function(err, address) {
if(address) {
return cb(address);
} else {
return cb();
}
});
}
function find_richlist(coin, cb) {
Richlist.findOne({coin: coin}, function(err, richlist) {
if(richlist) {
return cb(richlist);
} else {
return cb();
}
});
}
function update_address(hash, txid, amount, type, cb) {
// Check if address exists
find_address(hash, function(address) {
if (address) {
// if coinbase (new coins PoW), update sent only and return cb.
if ( hash == 'coinbase' ) {
Address.update({a_id:hash}, {
sent: address.sent + amount,
balance: 0,
}, function() {
return cb();
});
} else {
// ensure tx doesnt already exist in address.txs
lib.is_unique(address.txs, txid, function(unique, index) {
var tx_array = address.txs;
var received = address.received;
var sent = address.sent;
if (type == 'vin') {
sent = sent + amount;
} else {
received = received + amount;
}
if (unique == true) {
tx_array.push({addresses: txid, type: type});
if ( tx_array.length > settings.txcount ) {
tx_array.shift();
}
Address.update({a_id:hash}, {
txs: tx_array,
received: received,
sent: sent,
balance: received - sent
}, function() {
return cb();
});
} else {
if (type == tx_array[index].type) {
return cb(); //duplicate
} else {
Address.update({a_id:hash}, {
txs: tx_array,
received: received,
sent: sent,
balance: received - sent
}, function() {
return cb();
});
}
}
});
}
} else {
//new address
if (type == 'vin') {
var newAddress = new Address({
a_id: hash,
txs: [ {addresses: txid, type: 'vin'} ],
sent: amount,
balance: amount,
});
} else {
var newAddress = new Address({
a_id: hash,
txs: [ {addresses: txid, type: 'vout'} ],
received: amount,
balance: amount,
});
}
newAddress.save(function(err) {
if (err) {
return cb(err);
} else {
//console.log('address saved: %s', hash);
//console.log(newAddress);
return cb();
}
});
}
});
}
function find_tx(txid, cb) {
Tx.findOne({txid: txid}, function(err, tx) {
if(tx) {
return cb(tx);
} else {
return cb(null);
}
});
}
function save_tx(txid, cb) {
//var s_timer = new Date().getTime();
lib.get_rawtransaction(txid, function(tx){
if (tx != 'There was an error. Check your console.') {
lib.get_block(tx.blockhash, function(block){
if (block) {
lib.prepare_vin(tx, function(vin) {
lib.prepare_vout(tx.vout, txid, vin, function(vout, nvin) {
lib.syncLoop(vin.length, function (loop) {
var i = loop.iteration();
update_address(nvin[i].addresses, txid, nvin[i].amount, 'vin', function(){
loop.next();
});
}, function(){
lib.syncLoop(vout.length, function (subloop) {
var t = subloop.iteration();
if (vout[t].addresses) {
update_address(vout[t].addresses, txid, vout[t].amount, 'vout', function(){
subloop.next();
});
} else {
subloop.next();
}
}, function(){
lib.calculate_total(vout, function(total){
var newTx = new Tx({
txid: tx.txid,
vin: nvin,
vout: vout,
total: total.toFixed(8),
timestamp: tx.time,
blockhash: tx.blockhash,
blockindex: block.height,
});
newTx.save(function(err) {
if (err) {
return cb(err);
} else {
//console.log('txid: ');
return cb();
}
});
});
});
});
});
});
} else {
return cb('block not found: ' + tx.blockhash);
}
});
} else {
return cb('tx not found: ' + txid);
}
});
}
function get_market_data(market, cb) {
switch(market) {
case 'bittrex':
bittrex.get_data(settings.markets.coin, settings.markets.exchange, function(err, obj){
return cb(err, obj);
});
break;
case 'bleutrade':
bleutrade.get_data(settings.markets.coin, settings.markets.exchange, function(err, obj){
return cb(err, obj);
});
break;
case 'poloniex':
poloniex.get_data(settings.markets.coin, settings.markets.exchange, function(err, obj){
return cb(err, obj);
});
break;
case 'cryptsy':
cryptsy.get_data(settings.markets.coin, settings.markets.exchange, settings.markets.cryptsy_id, function(err, obj){
return cb(err, obj);
});
break;
case 'cryptopia':
cryptopia.get_data(settings.markets.coin, settings.markets.exchange, settings.markets.cryptopia_id, function (err, obj) {
return cb(err, obj);
});
break;
case 'ccex':
ccex.get_data(settings.markets.coin.toLowerCase(), settings.markets.exchange.toLowerCase(), settings.markets.ccex_key, function (err, obj) {
return cb(err, obj);
});
break;
case 'yobit':
yobit.get_data(settings.markets.coin.toLowerCase(), settings.markets.exchange.toLowerCase(), function(err, obj){
return cb(err, obj);
});
break;
case 'empoex':
empoex.get_data(settings.markets.coin, settings.markets.exchange, function(err, obj){
return cb(err, obj);
});
break;
case 'coinexchange':
coinexchange.get_data(settings.markets.coin, settings.markets.exchange, settings.markets.coinexchange_id, function(err, obj){
return cb(err, obj);
});
break;
default:
return cb(null);
}
}
module.exports = {
// initialize DB
connect: function(database, cb) {
mongoose.connect(database, { useNewUrlParser: true, useCreateIndex: true }, function(err) {
if (err) {
console.log('Unable to connect to database: %s', database);
console.log('Aborting');
process.exit(1);
}
//console.log('Successfully connected to MongoDB');
return cb();
});
},
check_stats: function(coin, cb) {
Stats.findOne({coin: coin}, function(err, stats) {
if(stats) {
// collection exists, now check if it is missing the last_usd_price column
Stats.findOne({last_usd_price: {$exists: false}}, function(err, stats) {
if (stats) {
// the last_usd_price needs to be added to the collection
Stats.update({coin: coin}, {
last_usd_price: 0,
}, function() { return cb(null); });
}
});
return cb(true);
} else {
return cb(false);
}
});
},
get_stats: function(coin, cb) {
Stats.findOne({coin: coin}, function(err, stats) {
if(stats) {
return cb(stats);
} else {
return cb(null);
}
});
},
create_stats: function(coin, cb) {
var newStats = new Stats({
coin: coin,
});
newStats.save(function(err) {
if (err) {
console.log(err);
return cb();
} else {
console.log("initial stats entry created for %s", coin);
//console.log(newStats);
return cb();
}
});
},
get_address: function(hash, cb) {
find_address(hash, function(address){
return cb(address);
});
},
get_richlist: function(coin, cb) {
find_richlist(coin, function(richlist){
return cb(richlist);
});
},
//property: 'received' or 'balance'
update_richlist: function(list, cb){
if(list == 'received') {
Address.find({}).sort({received: 'desc'}).limit(100).exec(function(err, addresses){
Richlist.update({coin: settings.coin}, {
received: addresses,
}, function() {
return cb();
});
});
} else { //balance
Address.find({}).sort({balance: 'desc'}).limit(100).exec(function(err, addresses){
Richlist.update({coin: settings.coin}, {
balance: addresses,
}, function() {
return cb();
});
});
}
},
get_tx: function(txid, cb) {
find_tx(txid, function(tx){
return cb(tx);
});
},
get_txs: function(block, cb) {
var txs = [];
lib.syncLoop(block.tx.length, function (loop) {
var i = loop.iteration();
find_tx(block.tx[i], function(tx){
if (tx) {
txs.push(tx);
loop.next();
} else {
loop.next();
}
})
}, function(){
return cb(txs);
});
},
create_tx: function(txid, cb) {
save_tx(txid, function(err){
if (err) {
return cb(err);
} else {
//console.log('tx stored: %s', txid);
return cb();
}
});
},
create_txs: function(block, cb) {
lib.syncLoop(block.tx.length, function (loop) {
var i = loop.iteration();
save_tx(block.tx[i], function(err){
if (err) {
loop.next();
} else {
//console.log('tx stored: %s', block.tx[i]);
loop.next();
}
});
}, function(){
return cb();
});
},
get_last_txs: function(count, min, cb) {
Tx.find({'total': {$gt: min}, $where: "this.vout.length > 0"}).sort({_id: 'desc'}).limit(count).exec(function(err, txs){
if (err) {
return cb(err);
} else {
return cb(txs);
}
});
},
create_market: function(coin, exchange, market, cb) {
var newMarkets = new Markets({
market: market,
coin: coin,
exchange: exchange,
});
newMarkets.save(function(err) {
if (err) {
console.log(err);
return cb();
} else {
console.log("initial markets entry created for %s", market);
//console.log(newMarkets);
return cb();
}
});
},
// checks market data exists for given market
check_market: function(market, cb) {
Markets.findOne({market: market}, function(err, exists) {
if(exists) {
return cb(market, true);
} else {
return cb(market, false);
}
});
},
// gets market data for given market
get_market: function(market, cb) {
Markets.findOne({market: market}, function(err, data) {
if(data) {
return cb(data);
} else {
return cb(null);
}
});
},
// creates initial richlist entry in database; called on first launch of explorer
create_richlist: function(coin, cb) {
var newRichlist = new Richlist({
coin: coin,
});
newRichlist.save(function(err) {
if (err) {
console.log(err);
return cb();
} else {
console.log("initial richlist entry created for %s", coin);
//console.log(newRichlist);
return cb();
}
});
},
// checks richlist data exists for given coin
check_richlist: function(coin, cb) {
Richlist.findOne({coin: coin}, function(err, exists) {
if(exists) {
return cb(true);
} else {
return cb(false);
}
});
},
create_heavy: function(coin, cb) {
var newHeavy = new Heavy({
coin: coin,
});
newHeavy.save(function(err) {
if (err) {
console.log(err);
return cb();
} else {
console.log("initial heavy entry created for %s", coin);
console.log(newHeavy);
return cb();
}
});
},
check_heavy: function(coin, cb) {
Heavy.findOne({coin: coin}, function(err, exists) {
if(exists) {
return cb(true);
} else {
return cb(false);
}
});
},
get_heavy: function(coin, cb) {
Heavy.findOne({coin: coin}, function(err, heavy) {
if(heavy) {
return cb(heavy);
} else {
return cb(null);
}
});
},
get_distribution: function(richlist, stats, cb){
var distribution = {
supply: stats.supply,
t_1_25: {percent: 0, total: 0 },
t_26_50: {percent: 0, total: 0 },
t_51_75: {percent: 0, total: 0 },
t_76_100: {percent: 0, total: 0 },
t_101plus: {percent: 0, total: 0 }
};
lib.syncLoop(richlist.balance.length, function (loop) {
var i = loop.iteration();
var count = i + 1;
var percentage = ((richlist.balance[i].balance / 100000000) / stats.supply) * 100;
if (count <= 25 ) {
distribution.t_1_25.percent = distribution.t_1_25.percent + percentage;
distribution.t_1_25.total = distribution.t_1_25.total + (richlist.balance[i].balance / 100000000);
}
if (count <= 50 && count > 25) {
distribution.t_26_50.percent = distribution.t_26_50.percent + percentage;
distribution.t_26_50.total = distribution.t_26_50.total + (richlist.balance[i].balance / 100000000);
}
if (count <= 75 && count > 50) {
distribution.t_51_75.percent = distribution.t_51_75.percent + percentage;
distribution.t_51_75.total = distribution.t_51_75.total + (richlist.balance[i].balance / 100000000);
}
if (count <= 100 && count > 75) {
distribution.t_76_100.percent = distribution.t_76_100.percent + percentage;
distribution.t_76_100.total = distribution.t_76_100.total + (richlist.balance[i].balance / 100000000);
}
loop.next();
}, function(){
distribution.t_101plus.percent = parseFloat(100 - distribution.t_76_100.percent - distribution.t_51_75.percent - distribution.t_26_50.percent - distribution.t_1_25.percent).toFixed(2);
distribution.t_101plus.total = parseFloat(distribution.supply - distribution.t_76_100.total - distribution.t_51_75.total - distribution.t_26_50.total - distribution.t_1_25.total).toFixed(8);
distribution.t_1_25.percent = parseFloat(distribution.t_1_25.percent).toFixed(2);
distribution.t_1_25.total = parseFloat(distribution.t_1_25.total).toFixed(8);
distribution.t_26_50.percent = parseFloat(distribution.t_26_50.percent).toFixed(2);
distribution.t_26_50.total = parseFloat(distribution.t_26_50.total).toFixed(8);
distribution.t_51_75.percent = parseFloat(distribution.t_51_75.percent).toFixed(2);
distribution.t_51_75.total = parseFloat(distribution.t_51_75.total).toFixed(8);
distribution.t_76_100.percent = parseFloat(distribution.t_76_100.percent).toFixed(2);
distribution.t_76_100.total = parseFloat(distribution.t_76_100.total).toFixed(8);
return cb(distribution);
});
},
// updates heavy stats for coin
// height: current block height, count: amount of votes to store
update_heavy: function(coin, height, count, cb) {
var newVotes = [];
lib.get_maxmoney( function (maxmoney) {
lib.get_maxvote( function (maxvote) {
lib.get_vote( function (vote) {
lib.get_phase( function (phase) {
lib.get_reward( function (reward) {
lib.get_supply( function (supply) {
lib.get_estnext( function (estnext) {
lib.get_nextin( function (nextin) {
lib.syncLoop(count, function (loop) {
var i = loop.iteration();
lib.get_blockhash(height-i, function (hash) {
lib.get_block(hash, function (block) {
newVotes.push({count:height-i,reward:block.reward,vote:block.vote});
loop.next();
});
});
}, function(){
console.log(newVotes);
Heavy.update({coin: coin}, {
lvote: vote,
reward: reward,
supply: supply,
cap: maxmoney,
estnext: estnext,
phase: phase,
maxvote: maxvote,
nextin: nextin,
votes: newVotes,
}, function() {
//console.log('address updated: %s', hash);
return cb();
});
});
});
});
});
});
});
});
});
});
},
// updates market data for given market; called by sync.js
update_markets_db: function(market, cb) {
get_market_data(market, function (err, obj) {
if (err == null) {
Markets.update({market:market}, {
chartdata: JSON.stringify(obj.chartdata),
buys: obj.buys,
sells: obj.sells,
history: obj.trades,
summary: obj.stats,
}, function() {
if ( market == settings.markets.default ) {
Stats.update({coin:settings.coin}, {
last_price: obj.stats.last,
}, function(){
return cb(null);
});
} else {
return cb(null);
}
});
} else {
return cb(err);
}
});
},
get_last_usd_price: function(cb) {
// Check if the market price is being recorded in BTC
if (settings.markets.enabled.length > 0 && settings.markets.exchange.toLowerCase() == "btc") {
// Convert btc to usd via coindesk api
coindesk.get_data(function (err, last_usd) {
// Get current stats
Stats.findOne({coin:settings.coin}, function(err, stats) {
// Update the last usd price
Stats.update({coin:settings.coin}, {
last_usd_price: (last_usd * stats.last_price),
}, function(){
return cb(null);
});
});
});
} else {
return cb(null);
}
},
// updates stats data for given coin; called by sync.js
update_db: function(coin, cb) {
lib.get_blockcount( function (count) {
if (!count){
console.log('Unable to connect to explorer API');
return cb(false);
}
lib.get_supply( function (supply){
lib.get_connectioncount(function (connections) {
Stats.update({coin: coin}, {
coin: coin,
count : count,
supply: supply,
connections: connections,
}, function() {
return cb(true);
});
});
});
});
},
// updates tx, address & richlist db's; called by sync.js
update_tx_db: function(coin, start, end, timeout, cb) {
var complete = false;
lib.syncLoop((end - start) + 1, function (loop) {
var x = loop.iteration();
if (x % 5000 === 0) {
Tx.find({}).where('blockindex').lt(start + x).sort({timestamp: 'desc'}).limit(settings.index.last_txs).exec(function(err, txs){
Stats.update({coin: coin}, {
last: start + x - 1,
last_txs: '' //not used anymore left to clear out existing objects
}, function() {});
});
}
lib.get_blockhash(start + x, function(blockhash){
if (blockhash) {
lib.get_block(blockhash, function(block) {
if (block) {
lib.syncLoop(block.tx.length, function (subloop) {
var i = subloop.iteration();
Tx.findOne({txid: block.tx[i]}, function(err, tx) {
if(tx) {
tx = null;
subloop.next();
} else {
save_tx(block.tx[i], function(err){
if (err) {
console.log(err);
} else {
console.log('%s: %s', block.height, block.tx[i]);
}
setTimeout( function(){
tx = null;
subloop.next();
}, timeout);
});
}
});
}, function(){
blockhash = null;
block = null;
loop.next();
});
} else {
console.log('block not found: %s', blockhash);
loop.next();
}
});
} else {
loop.next();
}
});
}, function(){
Tx.find({}).sort({timestamp: 'desc'}).limit(settings.index.last_txs).exec(function(err, txs){
Stats.update({coin: coin}, {
last: end,
last_txs: '' //not used anymore left to clear out existing objects
}, function() {
return cb();
});
});
});
},
create_peer: function(params, cb) {
var newPeer = new Peers(params);
newPeer.save(function(err) {
if (err) {
console.log(err);
return cb();
} else {
return cb();
}
});
},
find_peer: function(address, cb) {
Peers.findOne({address: address}, function(err, peer) {
if (err) {
return cb(null);
} else {
if (peer) {
return cb(peer);
} else {
return cb (null)
}
}
})
},
get_peers: function(cb) {
Peers.find({}, function(err, peers) {
if (err) {
return cb([]);
} else {
return cb(peers);
}
});
}
};
+409
View File
@@ -0,0 +1,409 @@
var request = require('request')
, settings = require('./settings')
, Address = require('../models/address');
var base_server = 'http://127.0.0.1:' + settings.port + "/";
var base_url = base_server + 'api/';
// returns coinbase total sent as current coin supply
function coinbase_supply(cb) {
Address.findOne({a_id: 'coinbase'}, function(err, address) {
if (address) {
return cb(address.sent);
} else {
return cb();
}
});
}
module.exports = {
convert_to_satoshi: function(amount, cb) {
// fix to 8dp & convert to string
var fixed = amount.toFixed(8).toString();
// remove decimal (.) and return integer
return cb(parseInt(fixed.replace('.', '')));
},
get_hashrate: function(cb) {
if (settings.index.show_hashrate == false) return cb('-');
if (settings.nethash == 'netmhashps') {
var uri = base_url + 'getmininginfo';
request({uri: uri, json: true, headers: {'User-Agent': 'eiquidus'}}, function (error, response, body) { //returned in mhash
if (body.netmhashps) {
if (settings.nethash_units == 'K') {
return cb((body.netmhashps * 1000).toFixed(4));
} else if (settings.nethash_units == 'G') {
return cb((body.netmhashps / 1000).toFixed(4));
} else if (settings.nethash_units == 'H') {
return cb((body.netmhashps * 1000000).toFixed(4));
} else if (settings.nethash_units == 'T') {
return cb((body.netmhashps / 1000000).toFixed(4));
} else if (settings.nethash_units == 'P') {
return cb((body.netmhashps / 1000000000).toFixed(4));
} else {
return cb(body.netmhashps.toFixed(4));
}
} else {
return cb('-');
}
});
} else {
var uri = base_url + 'getnetworkhashps';
request({uri: uri, json: true, headers: {'User-Agent': 'eiquidus'}}, function (error, response, body) {
if (body == 'There was an error. Check your console.') {
return cb('-');
} else {
if (settings.nethash_units == 'K') {
return cb((body / 1000).toFixed(4));
} else if (settings.nethash_units == 'M'){
return cb((body / 1000000).toFixed(4));
} else if (settings.nethash_units == 'G') {
return cb((body / 1000000000).toFixed(4));
} else if (settings.nethash_units == 'T') {
return cb((body / 1000000000000).toFixed(4));
} else if (settings.nethash_units == 'P') {
return cb((body / 1000000000000000).toFixed(4));
} else {
return cb((body).toFixed(4));
}
}
});
}
},
get_difficulty: function(cb) {
var uri = base_url + 'getdifficulty';
request({uri: uri, json: true, headers: {'User-Agent': 'eiquidus'}}, function (error, response, body) {
return cb(body);
});
},
get_connectioncount: function(cb) {
var uri = base_url + 'getconnectioncount';
request({uri: uri, json: true, headers: {'User-Agent': 'eiquidus'}}, function (error, response, body) {
return cb(body);
});
},
get_masternodecount: function(cb) {
var uri = base_url + 'getmasternodecount';
request({uri: uri, json: true, headers: {'User-Agent': 'eiquidus'}}, function (error, response, body) {
return cb(body);
});
},
get_blockcount: function(cb) {
var uri = base_url + 'getblockcount';
request({uri: uri, json: true, headers: {'User-Agent': 'eiquidus'}}, function (error, response, body) {
return cb(body);
});
},
get_blockhash: function(height, cb) {
var uri = base_url + 'getblockhash?height=' + height;
request({uri: uri, json: true, headers: {'User-Agent': 'eiquidus'}}, function (error, response, body) {
return cb(body);
});
},
get_block: function(hash, cb) {
var uri = base_url + 'getblock?hash=' + hash;
request({uri: uri, json: true, headers: {'User-Agent': 'eiquidus'}}, function (error, response, body) {
return cb(body);
});
},
get_rawtransaction: function(hash, cb) {
var uri = base_url + 'getrawtransaction?txid=' + hash + '&decrypt=1';
request({uri: uri, json: true, headers: {'User-Agent': 'eiquidus'}}, function (error, response, body) {
return cb(body);
});
},
get_maxmoney: function(cb) {
var uri = base_url + 'getmaxmoney';
request({uri: uri, json: true, headers: {'User-Agent': 'eiquidus'}}, function (error, response, body) {
return cb(body);
});
},
get_maxvote: function(cb) {
var uri = base_url + 'getmaxvote';
request({uri: uri, json: true, headers: {'User-Agent': 'eiquidus'}}, function (error, response, body) {
return cb(body);
});
},
get_vote: function(cb) {
var uri = base_url + 'getvote';
request({uri: uri, json: true, headers: {'User-Agent': 'eiquidus'}}, function (error, response, body) {
return cb(body);
});
},
get_phase: function(cb) {
var uri = base_url + 'getphase';
request({uri: uri, json: true, headers: {'User-Agent': 'eiquidus'}}, function (error, response, body) {
return cb(body);
});
},
get_reward: function(cb) {
var uri = base_url + 'getreward';
request({uri: uri, json: true, headers: {'User-Agent': 'eiquidus'}}, function (error, response, body) {
return cb(body);
});
},
get_estnext: function(cb) {
var uri = base_url + 'getnextrewardestimate';
request({uri: uri, json: true, headers: {'User-Agent': 'eiquidus'}}, function (error, response, body) {
return cb(body);
});
},
get_nextin: function(cb) {
var uri = base_url + 'getnextrewardwhenstr';
request({uri: uri, json: true, headers: {'User-Agent': 'eiquidus'}}, function (error, response, body) {
return cb(body);
});
},
// synchonous loop used to interate through an array,
// avoid use unless absolutely neccessary
syncLoop: function(iterations, process, exit){
var index = 0,
done = false,
shouldExit = false;
var loop = {
next:function(){
if(done){
if(shouldExit && exit){
exit(); // Exit if we're done
}
return; // Stop the loop if we're done
}
// If we're not finished
if(index < iterations){
index++; // Increment our index
if (index % 100 === 0) { //clear stack
setTimeout(function() {
process(loop); // Run our process, pass in the loop
}, 1);
} else {
process(loop); // Run our process, pass in the loop
}
// Otherwise we're done
} else {
done = true; // Make sure we say we're done
if(exit) exit(); // Call the callback on exit
}
},
iteration:function(){
return index - 1; // Return the loop number we're on
},
break:function(end){
done = true; // End the loop
shouldExit = end; // Passing end as true means we still call the exit callback
}
};
loop.next();
return loop;
},
balance_supply: function(cb) {
Address.find({}, 'balance').where('balance').gt(0).exec(function(err, docs) {
var count = 0;
module.exports.syncLoop(docs.length, function (loop) {
var i = loop.iteration();
count = count + docs[i].balance;
loop.next();
}, function(){
return cb(count);
});
});
},
get_supply: function(cb) {
if ( settings.supply == 'HEAVY' ) {
var uri = base_url + 'getsupply';
request({uri: uri, json: true, headers: {'User-Agent': 'eiquidus'}}, function (error, response, body) {
return cb(body);
});
} else if (settings.supply == 'GETINFO') {
var uri = base_url + 'getinfo';
request({uri: uri, json: true, headers: {'User-Agent': 'eiquidus'}}, function (error, response, body) {
return cb(body.moneysupply);
});
} else if (settings.supply == 'BALANCES') {
module.exports.balance_supply(function(supply) {
return cb(supply/100000000);
});
} else if (settings.supply == 'TXOUTSET') {
var uri = base_url + 'gettxoutsetinfo';
request({uri: uri, json: true, headers: {'User-Agent': 'eiquidus'}}, function (error, response, body) {
return cb(body.total_amount);
});
} else {
coinbase_supply(function(supply) {
return cb(supply/100000000);
});
}
},
is_unique: function(array, object, cb) {
var unique = true;
var index = null;
module.exports.syncLoop(array.length, function (loop) {
var i = loop.iteration();
if (array[i].addresses == object) {
unique = false;
index = i;
loop.break(true);
loop.next();
} else {
loop.next();
}
}, function(){
return cb(unique, index);
});
},
calculate_total: function(vout, cb) {
var total = 0;
module.exports.syncLoop(vout.length, function (loop) {
var i = loop.iteration();
//module.exports.convert_to_satoshi(parseFloat(vout[i].amount), function(amount_sat){
total = total + vout[i].amount;
loop.next();
//});
}, function(){
return cb(total);
});
},
prepare_vout: function(vout, txid, vin, cb) {
var arr_vout = [];
var arr_vin = [];
arr_vin = vin;
module.exports.syncLoop(vout.length, function (loop) {
var i = loop.iteration();
// make sure vout has an address
if (vout[i].scriptPubKey.type != 'nonstandard' && vout[i].scriptPubKey.type != 'nulldata') {
// check if tx is public or private (private = if no out address)
if (vout[i].scriptPubKey.type != 'zerocoinmint' && typeof vout[i].scriptPubKey.addresses != 'undefined') {
// check if vout address is unique, if so add it array, if not add its amount to existing index
module.exports.is_unique(arr_vout, vout[i].scriptPubKey.addresses[0], function(unique, index) {
if (unique == true) {
// unique vout
module.exports.convert_to_satoshi(parseFloat(vout[i].value), function(amount_sat){
arr_vout.push({addresses: vout[i].scriptPubKey.addresses[0], amount: amount_sat});
loop.next();
});
} else {
// already exists
module.exports.convert_to_satoshi(parseFloat(vout[i].value), function(amount_sat){
arr_vout[index].amount = arr_vout[index].amount + amount_sat;
loop.next();
});
}
});
} else {
// private tx
// TODO: save this data to be able to show an anon tx
loop.next();
}
} else {
// no address, move to next vout
loop.next();
}
}, function(){
if (vout[0].scriptPubKey.type == 'nonstandard') {
if ( arr_vin.length > 0 && arr_vout.length > 0 ) {
if (arr_vin[0].addresses == arr_vout[0].addresses) {
//PoS
arr_vout[0].amount = arr_vout[0].amount - arr_vin[0].amount;
arr_vin.shift();
return cb(arr_vout, arr_vin);
} else {
return cb(arr_vout, arr_vin);
}
} else {
return cb(arr_vout, arr_vin);
}
} else {
return cb(arr_vout, arr_vin);
}
});
},
get_input_addresses: function(input, vout, cb) {
var addresses = [];
if (input.coinbase) {
var amount = 0;
module.exports.syncLoop(vout.length, function (loop) {
var i = loop.iteration();
amount = amount + parseFloat(vout[i].value);
loop.next();
}, function(){
addresses.push({hash: 'coinbase', amount: amount});
return cb(addresses);
});
} else {
module.exports.get_rawtransaction(input.txid, function(tx){
if (tx) {
module.exports.syncLoop(tx.vout.length, function (loop) {
var i = loop.iteration();
if (tx.vout[i].n == input.vout) {
//module.exports.convert_to_satoshi(parseFloat(tx.vout[i].value), function(amount_sat){
if (tx.vout[i].scriptPubKey.addresses) {
addresses.push({hash: tx.vout[i].scriptPubKey.addresses[0], amount:tx.vout[i].value});
}
loop.break(true);
loop.next();
//});
} else {
loop.next();
}
}, function(){
return cb(addresses);
});
} else {
return cb();
}
});
}
},
prepare_vin: function(tx, cb) {
var arr_vin = [];
module.exports.syncLoop(tx.vin.length, function (loop) {
var i = loop.iteration();
module.exports.get_input_addresses(tx.vin[i], tx.vout, function(addresses){
if (addresses && addresses.length) {
//console.log('vin');
module.exports.is_unique(arr_vin, addresses[0].hash, function(unique, index) {
if (unique == true) {
module.exports.convert_to_satoshi(parseFloat(addresses[0].amount), function(amount_sat){
arr_vin.push({addresses:addresses[0].hash, amount:amount_sat});
loop.next();
});
} else {
module.exports.convert_to_satoshi(parseFloat(addresses[0].amount), function(amount_sat){
arr_vin[index].amount = arr_vin[index].amount + amount_sat;
loop.next();
});
}
});
} else {
loop.next();
}
});
}, function(){
return cb(arr_vin);
});
}
};
+156
View File
@@ -0,0 +1,156 @@
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.user && this.opts.pass) {
requestOptions.auth = this.opts.user + ':' + this.opts.pass;
}
// Now we'll 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.on('error', errback);
request.end(requestJSON);
};
module.exports.Client = Client;
+202
View File
@@ -0,0 +1,202 @@
/**
* The Locale Module reads the locale settings and provides
* this information to the other modules
*/
var fs = require("fs");
var jsonminify = require("jsonminify");
var settings = require("./settings");
exports.menu_explorer = "Explorer",
exports.menu_api = "API",
exports.menu_markets = "Markets",
exports.menu_richlist = "Rich List",
exports.menu_reward = "Reward",
exports.menu_movement = "Movement",
exports.menu_node = "Nodes",
exports.menu_network = "Network"
exports.ex_title = "Block Explorer",
exports.ex_search_title = "Search",
exports.ex_search_button = "Search",
exports.ex_search_message = "You may enter a block height, block hash ,tx hash or address.",
exports.ex_error = "Error!",
exports.ex_warning = "Warning:",
exports.ex_search_error = "Search found no results.",
exports.ex_latest_transactions = "Latest Transactions",
exports.ex_summary = "Block Summary",
exports.ex_supply = "Coin Supply",
exports.ex_block = "Block",
exports.tx_title = "Transaction Details",
exports.tx_block_hash = "Block Hash",
exports.tx_recipients = "Recipients",
exports.tx_contributors = "Contributor(s)",
exports.tx_hash = "Hash",
exports.tx_address = "Address",
exports.tx_nonstandard = "NONSTANDARD TX",
exports.block_title = "Block Details",
exports.block_previous = "Previous",
exports.block_next = "Next",
exports.block_genesis = "GENESIS",
exports.difficulty = "Difficulty",
exports.network = "Network",
exports.masternodecount = "Masternodes",
exports.height = "Height",
exports.timestamp = "Timestamp",
exports.size = "Size",
exports.transactions = "Transactions",
exports.total_sent = "Total Sent",
exports.total_received = "Total Received",
exports.confirmations = "Confirmations",
exports.total = "Total",
exports.bits = "Bits",
exports.nonce = "Nonce",
exports.new_coins = "New Coins",
exports.proof_of_stake = "PoS",
exports.initial_index_alert = "Indexing is currently incomplete, functionality is limited until index is up-to-date.",
exports.a_menu_showing = "Showing",
exports.a_menu_txs = "transactions",
exports.a_menu_all = "All",
exports.rl_received_coins = "Top 100 - Received Coins",
exports.rl_current_balance = "Top 100 - Current Balance",
exports.rl_received = "Received",
exports.rl_balance = "Balance",
exports.rl_wealth = "Wealth Distribution",
exports.rl_top25 = "Top 1-25",
exports.rl_top50 = "Top 26-50",
exports.rl_top75 = "Top 51-75",
exports.rl_top100 = "Top 76-100",
exports.rl_hundredplus = "101+",
exports.net_connections = "Connections",
exports.net_address = "Address",
exports.net_protocol = "Protocol",
exports.net_subversion = "Sub-version",
exports.net_country = "Country",
exports.net_warning = "This is simply a sub sample of the network based on wallets connected to this node.",
exports.api_title = "API Documentation",
exports.api_message = "The block explorer provides an API allowing users and/or applications to retrieve information from the network without the need for a local wallet.",
exports.api_calls = "API Calls",
exports.api_getnetworkhashps = "Returns the current network hashrate. (hash/s)",
exports.api_getdifficulty = "Returns the current difficulty.",
exports.api_getconnectioncount = "Returns the number of connections the block explorer has to other nodes.",
exports.api_getmasternodelist = "Returns the complete master node list.",
exports.api_getmasternodecount = "Returns the total number of masternodes on the network.",
exports.api_getmasternodecountonline = "Returns the number of masternodes on the network that are currently online.",
exports.api_getvotelist = "Returns the current vote list.",
exports.api_getblockcount = "Returns the number of blocks currently in the block chain.",
exports.api_getblockhash = "Returns the hash of the block at ; index 0 is the genesis block.",
exports.api_getblock = "Returns information about the block with the given hash.",
exports.api_getrawtransaction = "Returns raw transaction representation for given transaction id. decrypt can be set to 0(false) or 1(true).",
exports.api_getmaxmoney = 'Returns the maximum possible money supply.',
exports.api_getmaxvote = 'Returns the maximum allowed vote for the current phase of voting.',
exports.api_getvote = 'Returns the current block reward vote setting.',
exports.api_getphase = 'Returns the current voting phase (\'Mint\', \'Limit\' or \'Sustain\').',
exports.api_getreward = 'Returns the current block reward, which has been decided democratically in the previous round of block reward voting.',
exports.api_getsupply = 'Returns the current money supply.',
exports.api_getnextrewardestimate = 'Returns an estimate for the next block reward based on the current state of decentralized voting.',
exports.api_getnextrewardwhenstr = 'Returns string describing how long until the votes are tallied and the next block reward is computed.',
// Markets view
exports.mkt_hours = "24 hours",
exports.mkt_view_chart = "View 24 hour summary",
exports.mkt_view_summary = "View 24 hour chart",
exports.mkt_no_chart = "Chart data is not available via markets API.",
exports.mkt_high = "High",
exports.mkt_low = "Low",
exports.mkt_volume = "Volume",
exports.mkt_top_bid = "Top Bid",
exports.mkt_top_ask = "Top Ask",
exports.mkt_last = "Last Price",
exports.mkt_yesterday = "Yesterday",
exports.mkt_change = "Change",
exports.mkt_sell_orders = "Sell Orders",
exports.mkt_buy_orders = "Buy Orders",
exports.mkt_price = "Price",
exports.mkt_amount = "Amount",
exports.mkt_total = "Total",
exports.mkt_trade_history = "Trade History",
exports.mkt_type = "Type",
exports.mkt_time_stamp = "Time Stamp",
// Heavy
exports.heavy_vote = "Vote",
// Heavy rewards view
exports.heavy_title = "Reward/voting information",
exports.heavy_cap = "Coin Cap",
exports.heavy_phase = "Phase",
exports.heavy_maxvote = "Max Vote",
exports.heavy_reward = "Reward",
exports.heavy_current = "Current Reward",
exports.heavy_estnext = "Est. Next",
exports.heavy_changein = "Reward change in approximately",
exports.heavy_key = "Key",
exports.heavy_lastxvotes = "Last 20 votes",
exports.poloniex = "Poloniex",
exports.bittrex = "Bittrex",
exports.bleutrade = "Bleutrade",
exports.yobit = "Yobit",
exports.cryptsy = "Cryptsy",
exports.cryptopia = "Cryptopia",
exports.empoex = "Empoex",
exports.ccex = "C-Cex",
exports.coinexchange = "CoinExchange",
exports.reloadLocale = function reloadLocale(locale) {
// Discover where the locale file lives
var localeFilename = locale;
//console.log(localeFilename);
localeFilename = "./" + localeFilename;
//console.log('Loading locale: ' + localeFilename);
var localeStr;
try{
//read the settings sync
localeStr = fs.readFileSync(localeFilename).toString();
} catch(e){
console.warn('Locale file not found. Continuing using defaults!');
}
// try to parse the settings
var lsettings;
try {
if(localeStr) {
localeStr = jsonminify(localeStr).replace(",]","]").replace(",}","}");
lsettings = JSON.parse(localeStr);
}
}catch(e){
console.error('There was an error processing your locale file: '+e.message);
process.exit(1);
}
//loop trough 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("Settings should start with a low character: '" + i + "'");
}
//we know this setting, so we overwrite it
if(exports[i] !== undefined)
{
exports[i] = lsettings[i];
}
//this setting is unkown, output a warning and throw it away
else
{
console.warn("Unknown Setting: '" + i + "'. This setting doesn't exist or it was removed");
}
}
};
// initially load settings
exports.reloadLocale(settings.locale);
+84
View File
@@ -0,0 +1,84 @@
var request = require('request');
var base_url = 'https://bittrex.com/api/v1.1/public';
function get_summary(coin, exchange, cb) {
var req_url = base_url + '/getmarketsummary?market=' + exchange + '-' + coin;
request({uri: req_url, json: true, headers: {'User-Agent': 'eiquidus'}}, function (error, response, body) {
if (error) {
return cb(error, null);
} else {
if (body.message) {
return cb(body.message, null)
} else {
body.result[0]['last'] = body.result[0]['Last'];
return cb (null, body.result[0]);
}
}
});
}
function get_trades(coin, exchange, cb) {
var req_url = base_url + '/getmarkethistory?market=' + exchange + '-' + coin + '&count=50';
request({uri: req_url, json: true, headers: {'User-Agent': 'eiquidus'}}, function (error, response, body) {
if (body.success == true) {
return cb (null, body.result);
} else {
return cb(body.message, null);
}
});
}
function get_orders(coin, exchange, cb) {
var req_url = base_url + '/getorderbook?market=' + exchange + '-' + coin + '&type=both' + '&depth=50';
request({uri: req_url, json: true, headers: {'User-Agent': 'eiquidus'}}, function (error, response, body) {
if (body.success == true) {
var orders = body.result;
var buys = [];
var sells = [];
if (orders['buy'].length > 0){
for (var i = 0; i < orders['buy'].length; i++) {
var order = {
amount: parseFloat(orders.buy[i].Quantity).toFixed(8),
price: parseFloat(orders.buy[i].Rate).toFixed(8),
// total: parseFloat(orders.buy[i].Total).toFixed(8)
// Necessary because API will return 0.00 for small volume transactions
total: (parseFloat(orders.buy[i].Quantity).toFixed(8) * parseFloat(orders.buy[i].Rate)).toFixed(8)
}
buys.push(order);
}
}
if (orders['sell'].length > 0) {
for (var x = 0; x < orders['sell'].length; x++) {
var order = {
amount: parseFloat(orders.sell[x].Quantity).toFixed(8),
price: parseFloat(orders.sell[x].Rate).toFixed(8),
// total: parseFloat(orders.sell[x].Total).toFixed(8)
// Necessary because API will return 0.00 for small volume transactions
total: (parseFloat(orders.sell[x].Quantity).toFixed(8) * parseFloat(orders.sell[x].Rate)).toFixed(8)
}
sells.push(order);
}
}
return cb(null, buys, sells);
} else {
return cb(body.message, [], []);
}
});
}
module.exports = {
get_data: function(coin, exchange, cb) {
var error = null;
get_orders(coin, exchange, function(err, buys, sells) {
if (err) { error = err; }
get_trades(coin, exchange, function(err, trades) {
if (err) { error = err; }
get_summary(coin, exchange, function(err, stats) {
if (err) { error = err; }
return cb(error, {buys: buys, sells: sells, chartdata: [], trades: trades, stats: stats});
});
});
});
}
};
+84
View File
@@ -0,0 +1,84 @@
var request = require('request');
var base_url = 'https://bleutrade.com/api/v2/public';
function get_summary(coin, exchange, cb) {
var req_url = base_url + '/getmarketsummary?market=' + coin + '_' + exchange;
request({uri: req_url, json: true, headers: {'User-Agent': 'eiquidus'}}, function (error, response, body) {
if (error) {
return cb(error, null);
} else {
if (body.message) {
return cb(body.message, null)
} else {
body.result[0]['last'] = body.result[0].Last;
return cb (null, body.result[0]);
}
}
});
}
function get_trades(coin, exchange, cb) {
var req_url = base_url + '/getmarkethistory?market=' + coin + '_' + exchange + '&count=50';
request({uri: req_url, json: true, headers: {'User-Agent': 'eiquidus'}}, function (error, response, body) {
if (body.success == "true") {
return cb (null, body.result);
} else {
return cb(body.message, null);
}
});
}
function get_orders(coin, exchange, cb) {
var req_url = base_url + '/getorderbook?market=' + coin + '_' + exchange + '&type=all' + '&depth=50';
request({uri: req_url, json: true, headers: {'User-Agent': 'eiquidus'}}, function (error, response, body) {
if (body.success == "true") {
var orders = body.result;
var buys = [];
var sells = [];
if (orders['buy'].length > 0){
for (var i = 0; i < orders['buy'].length; i++) {
var order = {
amount: parseFloat(orders.buy[i].Quantity).toFixed(8),
price: parseFloat(orders.buy[i].Rate).toFixed(8),
// total: parseFloat(orders.buy[i].Total).toFixed(8)
// Necessary because API will return 0.00 for small volume transactions
total: (parseFloat(orders.buy[i].Quantity).toFixed(8) * parseFloat(orders.buy[i].Rate)).toFixed(8)
}
buys.push(order);
}
} else {}
if (orders['sell'].length > 0) {
for (var x = 0; x < orders['sell'].length; x++) {
var order = {
amount: parseFloat(orders.sell[x].Quantity).toFixed(8),
price: parseFloat(orders.sell[x].Rate).toFixed(8),
// total: parseFloat(orders.sell[x].Total).toFixed(8)
// Necessary because API will return 0.00 for small volume transactions
total: (parseFloat(orders.sell[x].Quantity).toFixed(8) * parseFloat(orders.sell[x].Rate)).toFixed(8)
}
sells.push(order);
}
} else {
}
return cb(null, buys, sells);
} else {
return cb(body.message, [], [])
}
});
}
module.exports = {
get_data: function(coin, exchange, cb) {
var error = null;
get_orders(coin, exchange, function(err, buys, sells) {
if (err) { error = err; }
get_trades(coin, exchange, function(err, trades) {
if (err) { error = err; }
get_summary(coin, exchange, function(err, stats) {
if (err) { error = err; }
return cb(error, {buys: buys, sells: sells, chartdata: [], trades: trades, stats: stats});
});
});
});
}
};
+136
View File
@@ -0,0 +1,136 @@
var request = require('request');
var base_url = 'https://c-cex.com/t/';
var d1 = new Date();
var d2 = new Date();
d1.setDate(d1.getDate() - 2);
function pad(x) {
if (x < 10) return "0" + x;
return x;
}
function toTimestamp(strDate) {
var datum = Date.parse(strDate);
return datum / 1000;
}
function formatdate(date1) {
var formatted = (date1.getUTCFullYear()) + '-' + pad((date1.getUTCMonth() + 1)) + '-' + pad(date1.getUTCDate());
return formatted;
}
function sleep9s() {
var start = new Date().getTime();
for (var i = 0; i < 1e9; i++) {
if ((new Date().getTime() - start) > 59000) {
break;
}
}
}
function get_summary(coin, exchange, cb) {
var summary = {};
sleep9s;
request({ uri: base_url + 's.html?a=volume&h=24&pair=' + coin + '-' + exchange, json: true, headers: {'User-Agent': 'eiquidus'} }, function (error, response, body) {
if (error) {
return cb(error, null);
} else if (body.return != undefined) {
var i = body.return.length - 1
summary['volume'] = body.return[i]['volume_' + coin].toFixed(8);
summary['volume_btc'] = body.return[i]['volume_' + exchange].toFixed(8);
sleep9s;
request({ uri: base_url + '/' + coin + '-' + exchange + '.json', json: true, headers: {'User-Agent': 'eiquidus'} }, function (error, response, body) {
if (error) {
return cb(error, null);
} else if (body != undefined) {
summary['bid'] = body.ticker['buy'].toFixed(8);
summary['ask'] = body.ticker['sell'].toFixed(8);
summary['high'] = body.ticker['high'].toFixed(8);
summary['low'] = body.ticker['low'].toFixed(8);
summary['last'] = body.ticker['lastprice'].toFixed(8);
return cb(null, summary);
} else {
return cb(error, null);
}
});
} else {
return cb(error, null);
}
});
}
function get_trades(coin, exchange, cb) {
var req_url = base_url + 's.html?a=tradehistory&d1=' + formatdate(d1) + '&d2=' + formatdate(d2) + '&pair=' + coin + '-' + exchange;
sleep9s;
request({ uri: req_url, json: true, headers: {'User-Agent': 'eiquidus'} }, function (error, response, body) {
if (body.return != undefined) {
var tTrades = body.return;
var trades = [];
if (tTrades == "No trade history for this period") {
return cb(tTrades, null);
} else {
for (var i = 0; i < tTrades.length; i++) {
var Trade = {
ordertype: tTrades[i].type,
amount: parseFloat(tTrades[i].amount).toFixed(8),
price: parseFloat(tTrades[i].rate).toFixed(8),
total: (parseFloat(tTrades[i].amount).toFixed(8) * parseFloat(tTrades[i].rate)).toFixed(8),
datetime: tTrades[i].datetime,
timestamp: toTimestamp(tTrades[i].datetime + 'Z'),
backrate: tTrades[i].backrate
}
trades.push(Trade);
}
}
return cb(null, trades);
} else {
return cb(body.message, null);
}
});
}
function get_orders(coin, exchange, ccex_key, cb) {
var req_url = base_url + 'r.html?key=' + ccex_key + '&a=orderlist&self=0&pair=' + coin + '-' + exchange;
sleep9s;
request({ uri: req_url, json: true, headers: {'User-Agent': 'eiquidus'} }, function (error, response, body) {
if (body != undefined) {
var orders = body;
orders.Data = body['return'];
var buys = [];
var sells = [];
for (Data in orders.Data) {
var order = {
otype: orders.Data[Data].type,
amount: parseFloat(orders.Data[Data].amount.toFixed(8)),
price: parseFloat(orders.Data[Data].price).toFixed(8),
total: (parseFloat(orders.Data[Data].amount) * parseFloat(orders.Data[Data].price)).toFixed(8)
}
if (order.otype == 'buy') {
buys.push(order);
} else {
sells.push(order);
}
}
return cb(null, buys, sells);
} else {
return cb(body.message, [], [])
}
});
}
module.exports = {
get_data: function (coin, exchange, ccex_key, cb) {
var error = null;
get_orders(coin, exchange, ccex_key, function (err, buys, sells) {
if (err) { error = err; }
get_trades(coin, exchange, function (err, trades) {
if (err) { error = err; }
get_summary(coin, exchange, function (err, stats) {
if (err) { error = err; }
return cb(error, { buys: buys, sells: sells, chartdata: [], trades: trades, stats: stats });
});
});
});
}
};
+109
View File
@@ -0,0 +1,109 @@
var request = require('request');
var base_url = 'https://www.coinexchange.io/api/v1';
function get_summary(coin, exchange, coinexchange_id, cb) {
var summary = {};
request({ uri: base_url + '/getmarketsummary?market_id=' + coinexchange_id, json: true, headers: {'User-Agent': 'eiquidus'} }, function (error, response, body) {
if (error) {
return cb(error, null);
} else if (body.success === "1") {
summary['bid'] = parseFloat(body.result['BidPrice']).toFixed(8);
summary['ask'] = parseFloat(body.result['AskPrice']).toFixed(8);
summary['volume'] = body.result['Volume'];
summary['high'] = parseFloat(body.result['HighPrice']).toFixed(8);
summary['low'] = parseFloat(body.result['LowPrice']).toFixed(8);
summary['last'] = parseFloat(body.result['LastPrice']).toFixed(8);
summary['change'] = body.result['Change'];
return cb(null, summary);
} else {
return cb(error, null);
}
});
}
function get_trades(coin, exchange, coinexchange_id, cb) {
return cb(null, []);
// trades endpoint doesn't exist yet
// var req_url = base_url + '/GetMarketHistory/' + coinexchange_id;
// request({ uri: req_url, json: true, headers: {'User-Agent': 'eiquidus'} }, function (error, response, body) {
// if (body.Success == true) {
// var tTrades = body.Data;
// var trades = [];
// for (var i = 0; i < tTrades.length; i++) {
// var Trade = {
// orderpair: tTrades[i].Label,
// ordertype: tTrades[i].Type,
// amount: parseFloat(tTrades[i].Amount).toFixed(8),
// price: parseFloat(tTrades[i].Price).toFixed(8),
// // total: parseFloat(tTrades[i].Total).toFixed(8)
// // Necessary because API will return 0.00 for small volume transactions
// total: (parseFloat(tTrades[i].Amount).toFixed(8) * parseFloat(tTrades[i].Price)).toFixed(8),
// timestamp: tTrades[i].Timestamp
// }
// trades.push(Trade);
// }
// return cb(null, trades);
// } else {
// return cb(body.Message, null);
// }
// });
}
function get_orders(coin, exchange, coinexchange_id, cb) {
var req_url = base_url + '/getorderbook?market_id=' + coinexchange_id;
request({ uri: req_url, json: true, headers: {'User-Agent': 'eiquidus'} }, function (error, response, body) {
if (body.success == "1") {
var orders = body.result;
var buys = [];
var sells = [];
if (orders['BuyOrders'].length > 0){
for (var i = 0; i < orders['BuyOrders'].length; i++) {
var order = {
amount: parseFloat(orders.BuyOrders[i].Quantity).toFixed(8),
price: parseFloat(orders.BuyOrders[i].Price).toFixed(8),
// total: parseFloat(orders.BuyOrders[i].Total).toFixed(8)
// Necessary because API will return 0.00 for small volume transactions
total: (parseFloat(orders.BuyOrders[i].Quantity).toFixed(8) * parseFloat(orders.BuyOrders[i].Price)).toFixed(8)
}
buys.push(order);
}
} else {}
if (orders['SellOrders'].length > 0) {
for (var x = 0; x < orders['SellOrders'].length; x++) {
var order = {
amount: parseFloat(orders.SellOrders[x].Quantity).toFixed(8),
price: parseFloat(orders.SellOrders[x].Price).toFixed(8),
// total: parseFloat(orders.SellOrders[x].Total).toFixed(8)
// Necessary because API will return 0.00 for small volume transactions
total: (parseFloat(orders.SellOrders[x].Quantity).toFixed(8) * parseFloat(orders.SellOrders[x].Price)).toFixed(8)
}
sells.push(order);
}
} else {
}
return cb(null, buys, sells);
} else {
return cb(body.Message, [], [])
}
});
}
module.exports = {
get_data: function (coin, exchange, coinexchange_id, cb) {
var error = null;
get_orders(coin, exchange, coinexchange_id, function (err, buys, sells) {
if (err) { error = err; }
get_trades(coin, exchange, coinexchange_id, function (err, trades) {
if (err) { error = err; }
get_summary(coin, exchange, coinexchange_id, function (err, stats) {
if (err) { error = err; }
return cb(error, { buys: buys, sells: sells, chartdata: [], trades: trades, stats: stats });
});
});
});
}
};
+105
View File
@@ -0,0 +1,105 @@
var request = require('request');
var base_url = 'https://www.cryptopia.co.nz/api';
function get_summary(coin, exchange, cryptopia_id, cb) {
var summary = {};
request({ uri: base_url + '/GetMarket/' + cryptopia_id + '/24', json: true, headers: {'User-Agent': 'eiquidus'} }, function (error, response, body) {
if (error) {
return cb(error, null);
} else if (body.Success === true) {
summary['bid'] = body.Data['BidPrice'].toFixed(8);
summary['ask'] = body.Data['AskPrice'].toFixed(8);
summary['volume'] = body.Data['Volume'];
summary['high'] = body.Data['High'].toFixed(8);
summary['low'] = body.Data['Low'].toFixed(8);
summary['last'] = body.Data['LastPrice'].toFixed(8);
summary['change'] = body.Data['Change'];
return cb(null, summary);
} else {
return cb(error, null);
}
});
}
function get_trades(coin, exchange, crytopia_id, cb) {
var req_url = base_url + '/GetMarketHistory/' + crytopia_id;
request({ uri: req_url, json: true, headers: {'User-Agent': 'eiquidus'} }, function (error, response, body) {
if (body.Success == true) {
var tTrades = body.Data;
var trades = [];
for (var i = 0; i < tTrades.length; i++) {
var Trade = {
orderpair: tTrades[i].Label,
ordertype: tTrades[i].Type,
amount: parseFloat(tTrades[i].Amount).toFixed(8),
price: parseFloat(tTrades[i].Price).toFixed(8),
// total: parseFloat(tTrades[i].Total).toFixed(8)
// Necessary because API will return 0.00 for small volume transactions
total: (parseFloat(tTrades[i].Amount).toFixed(8) * parseFloat(tTrades[i].Price)).toFixed(8),
timestamp: tTrades[i].Timestamp
}
trades.push(Trade);
}
return cb(null, trades);
} else {
return cb(body.Message, null);
}
});
}
function get_orders(coin, exchange, cryptopia_id, cb) {
var req_url = base_url + '/GetMarketOrders/' + cryptopia_id + '/50';
request({ uri: req_url, json: true, headers: {'User-Agent': 'eiquidus'} }, function (error, response, body) {
if (body.Success == true) {
var orders = body.Data;
var buys = [];
var sells = [];
if (orders['Buy'].length > 0){
for (var i = 0; i < orders['Buy'].length; i++) {
var order = {
amount: parseFloat(orders.Buy[i].Volume).toFixed(8),
price: parseFloat(orders.Buy[i].Price).toFixed(8),
// total: parseFloat(orders.Buy[i].Total).toFixed(8)
// Necessary because API will return 0.00 for small volume transactions
total: (parseFloat(orders.Buy[i].Volume).toFixed(8) * parseFloat(orders.Buy[i].Price)).toFixed(8)
}
buys.push(order);
}
} else {}
if (orders['Sell'].length > 0) {
for (var x = 0; x < orders['Sell'].length; x++) {
var order = {
amount: parseFloat(orders.Sell[x].Volume).toFixed(8),
price: parseFloat(orders.Sell[x].Price).toFixed(8),
// total: parseFloat(orders.Sell[x].Total).toFixed(8)
// Necessary because API will return 0.00 for small volume transactions
total: (parseFloat(orders.Sell[x].Volume).toFixed(8) * parseFloat(orders.Sell[x].Price)).toFixed(8)
}
sells.push(order);
}
} else {
}
return cb(null, buys, sells);
} else {
return cb(body.Message, [], [])
}
});
}
module.exports = {
get_data: function (coin, exchange, cryptopia_id, cb) {
var error = null;
get_orders(coin, exchange, cryptopia_id, function (err, buys, sells) {
if (err) { error = err; }
get_trades(coin, exchange, cryptopia_id, function (err, trades) {
if (err) { error = err; }
get_summary(coin, exchange, cryptopia_id, function (err, stats) {
if (err) { error = err; }
return cb(error, { buys: buys, sells: sells, chartdata: [], trades: trades, stats: stats });
});
});
});
}
};
+95
View File
@@ -0,0 +1,95 @@
var request = require('request');
var base_url = 'https://api.cryptsy.com/api/v2/markets';
function get_summary(coin, exchange, Crymktid, cb) {
var summary = {};
request({uri: base_url + '/' + Crymktid + '/ticker', json: true, headers: {'User-Agent': 'eiquidus'}}, function (error, response, body) {
if (error) {
return cb(error, null);
} else if (body.success === true) {
summary['bid'] = body.data['bid'].toFixed(8);
summary['ask'] = body.data['ask'].toFixed(8);
request({uri: base_url + '/' + Crymktid, json: true, headers: {'User-Agent': 'eiquidus'}}, function (error, response, body) {
if (error) {
return cb(error, null);
} else if (body.success === true) {
summary['volume'] = body.data['24hr']['volume'];
summary['volume_btc'] = body.data['24hr']['volume_btc'];
summary['high'] = body.data['24hr']['price_high'];
summary['low'] = body.data['24hr']['price_low'];
summary['last'] = body.data['last_trade']['price'];
return cb(null, summary);
} else {
return cb(error, null);
}
});
} else {
return cb(error, null);
}
});
}
function get_trades(coin, exchange, Crymktid, cb) {
var req_url = base_url + '/' + Crymktid + '/tradehistory?limit=100';
request({uri: req_url, json: true, headers: {'User-Agent': 'eiquidus'}}, function (error, response, body) {
if (body.success == true) {
return cb (null, body.data);
} else {
return cb(body.message, null);
}
});
}
function get_orders(coin, exchange, Crymktid, cb) {
var req_url = base_url + '/' + Crymktid + '/orderbook?type=both?limit=50';
request({uri: req_url, json: true, headers: {'User-Agent': 'eiquidus'}}, function (error, response, body) {
if (body.success == true) {
var orders = body.data;
var buys = [];
var sells = [];
if (orders['buyorders'].length > 0){
for (var i = 0; i < orders['buyorders'].length; i++) {
var order = {
amount: parseFloat(orders.buyorders[i].quantity).toFixed(8),
price: parseFloat(orders.buyorders[i].price).toFixed(8),
// total: parseFloat(orders.buyorders[i].total).toFixed(8)
// Necessary because API will return 0.00 for small volume transactions
total: (parseFloat(orders.buyorders[i].quantity).toFixed(8) * parseFloat(orders.buyorders[i].price)).toFixed(8)
}
buys.push(order);
}
} else {}
if (orders['sellorders'].length > 0) {
for (var x = 0; x < orders['sellorders'].length; x++) {
var order = {
amount: parseFloat(orders.sellorders[x].quantity).toFixed(8),
price: parseFloat(orders.sellorders[x].price).toFixed(8),
// total: parseFloat(orders.sellorders[x].total).toFixed(8)
// Necessary because API will return 0.00 for small volume transactions
total: (parseFloat(orders.sellorders[x].quantity).toFixed(8) * parseFloat(orders.sellorders[x].price)).toFixed(8)
}
sells.push(order);
}
} else {
}
return cb(null, buys, sells);
} else {
return cb(body.message, [], [])
}
});
}
module.exports = {
get_data: function(coin, exchange, Crymktid, cb) {
var error = null;
get_orders(coin, exchange, Crymktid, function(err, buys, sells) {
if (err) { error = err; }
get_trades(coin, exchange, Crymktid, function(err, trades) {
if (err) { error = err; }
get_summary(coin, exchange, Crymktid, function(err, stats) {
if (err) { error = err; }
return cb(error, {buys: buys, sells: sells, chartdata: [], trades: trades, stats: stats});
});
});
});
}
};
+758
View File
@@ -0,0 +1,758 @@
var mongoose = require('mongoose')
, Stats = require('../models/stats')
, Markets = require('../models/markets')
, Address = require('../models/address')
, Tx = require('../models/tx')
, Richlist = require('../models/richlist')
, Peers = require('../models/peers')
, Heavy = require('../models/heavy')
, lib = require('./explorer')
, settings = require('./settings')
, poloniex = require('./markets/poloniex')
, bittrex = require('./markets/bittrex')
, bleutrade = require('./markets/bleutrade')
, cryptsy = require('./markets/cryptsy')
, cryptopia = require('./markets/cryptopia')
, yobit = require('./markets/yobit')
, empoex = require('./markets/empoex')
, ccex = require('./markets/ccex')
, coinexchange = require('./markets/coinexchange')
, coindesk = require('./apis/coindesk');
function find_address(hash, cb) {
Address.findOne({a_id: hash}, function(err, address) {
if(address) {
return cb(address);
} else {
return cb();
}
});
}
function find_richlist(coin, cb) {
Richlist.findOne({coin: coin}, function(err, richlist) {
if(richlist) {
return cb(richlist);
} else {
return cb();
}
});
}
function update_address(hash, txid, amount, type, cb) {
// Check if address exists
find_address(hash, function(address) {
if (address) {
// if coinbase (new coins PoW), update sent only and return cb.
if ( hash == 'coinbase' ) {
Address.update({a_id:hash}, {
sent: address.sent + amount,
balance: 0,
}, function() {
return cb();
});
} else {
// ensure tx doesnt already exist in address.txs
lib.is_unique(address.txs, txid, function(unique, index) {
var tx_array = address.txs;
var received = address.received;
var sent = address.sent;
if (type == 'vin') {
sent = sent + amount;
} else {
received = received + amount;
}
if (unique == true) {
tx_array.push({addresses: txid, type: type});
if ( tx_array.length > settings.txcount ) {
tx_array.shift();
}
Address.update({a_id:hash}, {
txs: tx_array,
received: received,
sent: sent,
balance: received - sent
}, function() {
return cb();
});
} else {
if (type == tx_array[index].type) {
return cb(); //duplicate
} else {
Address.update({a_id:hash}, {
txs: tx_array,
received: received,
sent: sent,
balance: received - sent
}, function() {
return cb();
});
}
}
});
}
} else {
//new address
if (type == 'vin') {
var newAddress = new Address({
a_id: hash,
txs: [ {addresses: txid, type: 'vin'} ],
sent: amount,
balance: amount,
});
} else {
var newAddress = new Address({
a_id: hash,
txs: [ {addresses: txid, type: 'vout'} ],
received: amount,
balance: amount,
});
}
newAddress.save(function(err) {
if (err) {
return cb(err);
} else {
//console.log('address saved: %s', hash);
//console.log(newAddress);
return cb();
}
});
}
});
}
function find_tx(txid, cb) {
Tx.findOne({txid: txid}, function(err, tx) {
if(tx) {
return cb(tx);
} else {
return cb(null);
}
});
}
function save_tx(txid, cb) {
//var s_timer = new Date().getTime();
lib.get_rawtransaction(txid, function(tx){
if (tx != 'There was an error. Check your console.') {
lib.get_block(tx.blockhash, function(block){
if (block) {
lib.prepare_vin(tx, function(vin) {
lib.prepare_vout(tx.vout, txid, vin, function(vout, nvin) {
lib.syncLoop(vin.length, function (loop) {
var i = loop.iteration();
update_address(nvin[i].addresses, txid, nvin[i].amount, 'vin', function(){
loop.next();
});
}, function(){
lib.syncLoop(vout.length, function (subloop) {
var t = subloop.iteration();
if (vout[t].addresses) {
update_address(vout[t].addresses, txid, vout[t].amount, 'vout', function(){
subloop.next();
});
} else {
subloop.next();
}
}, function(){
lib.calculate_total(vout, function(total){
var newTx = new Tx({
txid: tx.txid,
vin: nvin,
vout: vout,
total: total.toFixed(8),
timestamp: tx.time,
blockhash: tx.blockhash,
blockindex: block.height,
});
newTx.save(function(err) {
if (err) {
return cb(err);
} else {
//console.log('txid: ');
return cb();
}
});
});
});
});
});
});
} else {
return cb('block not found: ' + tx.blockhash);
}
});
} else {
return cb('tx not found: ' + txid);
}
});
}
function get_market_data(market, cb) {
switch(market) {
case 'bittrex':
bittrex.get_data(settings.markets.coin, settings.markets.exchange, function(err, obj){
return cb(err, obj);
});
break;
case 'bleutrade':
bleutrade.get_data(settings.markets.coin, settings.markets.exchange, function(err, obj){
return cb(err, obj);
});
break;
case 'poloniex':
poloniex.get_data(settings.markets.coin, settings.markets.exchange, function(err, obj){
return cb(err, obj);
});
break;
case 'cryptsy':
cryptsy.get_data(settings.markets.coin, settings.markets.exchange, settings.markets.cryptsy_id, function(err, obj){
return cb(err, obj);
});
break;
case 'cryptopia':
cryptopia.get_data(settings.markets.coin, settings.markets.exchange, settings.markets.cryptopia_id, function (err, obj) {
return cb(err, obj);
});
break;
case 'ccex':
ccex.get_data(settings.markets.coin.toLowerCase(), settings.markets.exchange.toLowerCase(), settings.markets.ccex_key, function (err, obj) {
return cb(err, obj);
});
break;
case 'yobit':
yobit.get_data(settings.markets.coin.toLowerCase(), settings.markets.exchange.toLowerCase(), function(err, obj){
return cb(err, obj);
});
break;
case 'empoex':
empoex.get_data(settings.markets.coin, settings.markets.exchange, function(err, obj){
return cb(err, obj);
});
break;
case 'coinexchange':
coinexchange.get_data(settings.markets.coin, settings.markets.exchange, settings.markets.coinexchange_id, function(err, obj){
return cb(err, obj);
});
break;
default:
return cb(null);
}
}
module.exports = {
// initialize DB
connect: function(database, cb) {
mongoose.connect(database, { useNewUrlParser: true, useCreateIndex: true }, function(err) {
if (err) {
console.log('Unable to connect to database: %s', database);
console.log('Aborting');
process.exit(1);
}
//console.log('Successfully connected to MongoDB');
return cb();
});
},
check_stats: function(coin, cb) {
Stats.findOne({coin: coin}, function(err, stats) {
if(stats) {
// collection exists, now check if it is missing the last_usd_price column
Stats.findOne({last_usd_price: {$exists: false}}, function(err, stats) {
if (stats) {
// the last_usd_price needs to be added to the collection
Stats.update({coin: coin}, {
last_usd_price: 0,
}, function() { return cb(null); });
}
});
return cb(true);
} else {
return cb(false);
}
});
},
get_stats: function(coin, cb) {
Stats.findOne({coin: coin}, function(err, stats) {
if(stats) {
return cb(stats);
} else {
return cb(null);
}
});
},
create_stats: function(coin, cb) {
var newStats = new Stats({
coin: coin,
});
newStats.save(function(err) {
if (err) {
console.log(err);
return cb();
} else {
console.log("initial stats entry created for %s", coin);
//console.log(newStats);
return cb();
}
});
},
get_address: function(hash, cb) {
find_address(hash, function(address){
return cb(address);
});
},
get_richlist: function(coin, cb) {
find_richlist(coin, function(richlist){
return cb(richlist);
});
},
//property: 'received' or 'balance'
update_richlist: function(list, cb){
if(list == 'received') {
Address.find({}).sort({received: 'desc'}).limit(100).exec(function(err, addresses){
Richlist.update({coin: settings.coin}, {
received: addresses,
}, function() {
return cb();
});
});
} else { //balance
Address.find({}).sort({balance: 'desc'}).limit(100).exec(function(err, addresses){
Richlist.update({coin: settings.coin}, {
balance: addresses,
}, function() {
return cb();
});
});
}
},
get_tx: function(txid, cb) {
find_tx(txid, function(tx){
return cb(tx);
});
},
get_txs: function(block, cb) {
var txs = [];
lib.syncLoop(block.tx.length, function (loop) {
var i = loop.iteration();
find_tx(block.tx[i], function(tx){
if (tx) {
txs.push(tx);
loop.next();
} else {
loop.next();
}
})
}, function(){
return cb(txs);
});
},
create_tx: function(txid, cb) {
save_tx(txid, function(err){
if (err) {
return cb(err);
} else {
//console.log('tx stored: %s', txid);
return cb();
}
});
},
create_txs: function(block, cb) {
lib.syncLoop(block.tx.length, function (loop) {
var i = loop.iteration();
save_tx(block.tx[i], function(err){
if (err) {
loop.next();
} else {
//console.log('tx stored: %s', block.tx[i]);
loop.next();
}
});
}, function(){
return cb();
});
},
get_last_txs: function(count, min, cb) {
Tx.find({'total': {$gt: min}, $where: "this.vout.length > 0"}).sort({_id: 'desc'}).limit(count).exec(function(err, txs){
if (err) {
return cb(err);
} else {
return cb(txs);
}
});
},
create_market: function(coin, exchange, market, cb) {
var newMarkets = new Markets({
market: market,
coin: coin,
exchange: exchange,
});
newMarkets.save(function(err) {
if (err) {
console.log(err);
return cb();
} else {
console.log("initial markets entry created for %s", market);
//console.log(newMarkets);
return cb();
}
});
},
// checks market data exists for given market
check_market: function(market, cb) {
Markets.findOne({market: market}, function(err, exists) {
if(exists) {
return cb(market, true);
} else {
return cb(market, false);
}
});
},
// gets market data for given market
get_market: function(market, cb) {
Markets.findOne({market: market}, function(err, data) {
if(data) {
return cb(data);
} else {
return cb(null);
}
});
},
// creates initial richlist entry in database; called on first launch of explorer
create_richlist: function(coin, cb) {
var newRichlist = new Richlist({
coin: coin,
});
newRichlist.save(function(err) {
if (err) {
console.log(err);
return cb();
} else {
console.log("initial richlist entry created for %s", coin);
//console.log(newRichlist);
return cb();
}
});
},
// checks richlist data exists for given coin
check_richlist: function(coin, cb) {
Richlist.findOne({coin: coin}, function(err, exists) {
if(exists) {
return cb(true);
} else {
return cb(false);
}
});
},
create_heavy: function(coin, cb) {
var newHeavy = new Heavy({
coin: coin,
});
newHeavy.save(function(err) {
if (err) {
console.log(err);
return cb();
} else {
console.log("initial heavy entry created for %s", coin);
console.log(newHeavy);
return cb();
}
});
},
check_heavy: function(coin, cb) {
Heavy.findOne({coin: coin}, function(err, exists) {
if(exists) {
return cb(true);
} else {
return cb(false);
}
});
},
get_heavy: function(coin, cb) {
Heavy.findOne({coin: coin}, function(err, heavy) {
if(heavy) {
return cb(heavy);
} else {
return cb(null);
}
});
},
get_distribution: function(richlist, stats, cb){
var distribution = {
supply: stats.supply,
t_1_25: {percent: 0, total: 0 },
t_26_50: {percent: 0, total: 0 },
t_51_75: {percent: 0, total: 0 },
t_76_100: {percent: 0, total: 0 },
t_101plus: {percent: 0, total: 0 }
};
lib.syncLoop(richlist.balance.length, function (loop) {
var i = loop.iteration();
var count = i + 1;
var percentage = ((richlist.balance[i].balance / 100000000) / stats.supply) * 100;
if (count <= 25 ) {
distribution.t_1_25.percent = distribution.t_1_25.percent + percentage;
distribution.t_1_25.total = distribution.t_1_25.total + (richlist.balance[i].balance / 100000000);
}
if (count <= 50 && count > 25) {
distribution.t_26_50.percent = distribution.t_26_50.percent + percentage;
distribution.t_26_50.total = distribution.t_26_50.total + (richlist.balance[i].balance / 100000000);
}
if (count <= 75 && count > 50) {
distribution.t_51_75.percent = distribution.t_51_75.percent + percentage;
distribution.t_51_75.total = distribution.t_51_75.total + (richlist.balance[i].balance / 100000000);
}
if (count <= 100 && count > 75) {
distribution.t_76_100.percent = distribution.t_76_100.percent + percentage;
distribution.t_76_100.total = distribution.t_76_100.total + (richlist.balance[i].balance / 100000000);
}
loop.next();
}, function(){
distribution.t_101plus.percent = parseFloat(100 - distribution.t_76_100.percent - distribution.t_51_75.percent - distribution.t_26_50.percent - distribution.t_1_25.percent).toFixed(2);
distribution.t_101plus.total = parseFloat(distribution.supply - distribution.t_76_100.total - distribution.t_51_75.total - distribution.t_26_50.total - distribution.t_1_25.total).toFixed(8);
distribution.t_1_25.percent = parseFloat(distribution.t_1_25.percent).toFixed(2);
distribution.t_1_25.total = parseFloat(distribution.t_1_25.total).toFixed(8);
distribution.t_26_50.percent = parseFloat(distribution.t_26_50.percent).toFixed(2);
distribution.t_26_50.total = parseFloat(distribution.t_26_50.total).toFixed(8);
distribution.t_51_75.percent = parseFloat(distribution.t_51_75.percent).toFixed(2);
distribution.t_51_75.total = parseFloat(distribution.t_51_75.total).toFixed(8);
distribution.t_76_100.percent = parseFloat(distribution.t_76_100.percent).toFixed(2);
distribution.t_76_100.total = parseFloat(distribution.t_76_100.total).toFixed(8);
return cb(distribution);
});
},
// updates heavy stats for coin
// height: current block height, count: amount of votes to store
update_heavy: function(coin, height, count, cb) {
var newVotes = [];
lib.get_maxmoney( function (maxmoney) {
lib.get_maxvote( function (maxvote) {
lib.get_vote( function (vote) {
lib.get_phase( function (phase) {
lib.get_reward( function (reward) {
lib.get_supply( function (supply) {
lib.get_estnext( function (estnext) {
lib.get_nextin( function (nextin) {
lib.syncLoop(count, function (loop) {
var i = loop.iteration();
lib.get_blockhash(height-i, function (hash) {
lib.get_block(hash, function (block) {
newVotes.push({count:height-i,reward:block.reward,vote:block.vote});
loop.next();
});
});
}, function(){
console.log(newVotes);
Heavy.update({coin: coin}, {
lvote: vote,
reward: reward,
supply: supply,
cap: maxmoney,
estnext: estnext,
phase: phase,
maxvote: maxvote,
nextin: nextin,
votes: newVotes,
}, function() {
//console.log('address updated: %s', hash);
return cb();
});
});
});
});
});
});
});
});
});
});
},
// updates market data for given market; called by sync.js
update_markets_db: function(market, cb) {
get_market_data(market, function (err, obj) {
if (err == null) {
Markets.update({market:market}, {
chartdata: JSON.stringify(obj.chartdata),
buys: obj.buys,
sells: obj.sells,
history: obj.trades,
summary: obj.stats,
}, function() {
if ( market == settings.markets.default ) {
Stats.update({coin:settings.coin}, {
last_price: obj.stats.last,
}, function(){
return cb(null);
});
} else {
return cb(null);
}
});
} else {
return cb(err);
}
});
},
get_last_usd_price: function(cb) {
// Check if the market price is being recorded in BTC
if (settings.markets.enabled.length > 0 && settings.markets.exchange.toLowerCase() == "btc") {
// Convert btc to usd via coindesk api
coindesk.get_data(function (err, last_usd) {
// Get current stats
Stats.findOne({coin:settings.coin}, function(err, stats) {
// Update the last usd price
Stats.update({coin:settings.coin}, {
last_usd_price: (last_usd * stats.last_price),
}, function(){
return cb(null);
});
});
});
} else {
return cb(null);
}
},
// updates stats data for given coin; called by sync.js
update_db: function(coin, cb) {
lib.get_blockcount( function (count) {
if (!count){
console.log('Unable to connect to explorer API');
return cb(false);
}
lib.get_supply( function (supply){
lib.get_connectioncount(function (connections) {
Stats.update({coin: coin}, {
coin: coin,
count : count,
supply: supply,
connections: connections,
}, function() {
return cb(true);
});
});
});
});
},
// updates tx, address & richlist db's; called by sync.js
update_tx_db: function(coin, start, end, timeout, cb) {
var complete = false;
lib.syncLoop((end - start) + 1, function (loop) {
var x = loop.iteration();
if (x % 5000 === 0) {
Tx.find({}).where('blockindex').lt(start + x).sort({timestamp: 'desc'}).limit(settings.index.last_txs).exec(function(err, txs){
Stats.update({coin: coin}, {
last: start + x - 1,
last_txs: '' //not used anymore left to clear out existing objects
}, function() {});
});
}
lib.get_blockhash(start + x, function(blockhash){
if (blockhash) {
lib.get_block(blockhash, function(block) {
if (block) {
lib.syncLoop(block.tx.length, function (subloop) {
var i = subloop.iteration();
Tx.findOne({txid: block.tx[i]}, function(err, tx) {
if(tx) {
tx = null;
subloop.next();
} else {
save_tx(block.tx[i], function(err){
if (err) {
console.log(err);
} else {
console.log('%s: %s', block.height, block.tx[i]);
}
setTimeout( function(){
tx = null;
subloop.next();
}, timeout);
});
}
});
}, function(){
blockhash = null;
block = null;
loop.next();
});
} else {
console.log('block not found: %s', blockhash);
loop.next();
}
});
} else {
loop.next();
}
});
}, function(){
Tx.find({}).sort({timestamp: 'desc'}).limit(settings.index.last_txs).exec(function(err, txs){
Stats.update({coin: coin}, {
last: end,
last_txs: '' //not used anymore left to clear out existing objects
}, function() {
return cb();
});
});
});
},
create_peer: function(params, cb) {
var newPeer = new Peers(params);
newPeer.save(function(err) {
if (err) {
console.log(err);
return cb();
} else {
return cb();
}
});
},
find_peer: function(address, cb) {
Peers.findOne({address: address}, function(err, peer) {
if (err) {
return cb(null);
} else {
if (peer) {
return cb(peer);
} else {
return cb (null)
}
}
})
},
get_peers: function(cb) {
Peers.find({}, function(err, peers) {
if (err) {
return cb([]);
} else {
return cb(peers);
}
});
}
};
+53
View File
@@ -0,0 +1,53 @@
var request = require('request');
var base_url = 'https://api.empoex.com';
function get_summary(coin, exchange, cb) {
var req_url = base_url + '/marketinfo/' + coin + '-' + exchange;
request({uri: req_url, json: true, headers: {'User-Agent': 'eiquidus'}}, function (error, response, body) {
if (body.length < 1) {
return cb('Pair not found ' + coin + '-' + exchange, null)
} else {
return cb (null, body[0]);
}
});
}
function get_trades(coin, exchange, cb) {
var req_url = base_url + '/markethistory/' + coin + '-' + exchange;
request({uri: req_url, json: true, headers: {'User-Agent': 'eiquidus'}}, function (error, response, body) {
if (body.length < 1) {
return cb('Pair not found ' + coin + '-' + exchange, null)
} else {
return cb (null, body[coin + '-' + exchange]);
}
});
}
function get_orders(coin, exchange, cb) {
var req_url = base_url + '/orderbook/' + coin + '-' + exchange;
request({uri: req_url, json: true, headers: {'User-Agent': 'eiquidus'}}, function (error, response, body) {
if (body[coin + '-' + exchange]) {
var obj = body[coin + '-' + exchange];
return cb(null, obj.buy, obj.sell);
} else {
return cb('Pair not found ' + coin + '-' + exchange, [], []);
}
});
}
module.exports = {
get_data: function(coin, exchange, cb) {
var error = null;
get_orders(coin, exchange, function(err, buys, sells) {
if (err) { error = err; }
get_trades(coin, exchange, function(err, trades) {
if (err) { error = err; }
get_summary(coin, exchange, function(err, stats) {
if (err) { error = err; }
return cb(error, {buys: buys, sells: sells, chartdata: [], trades: trades, stats: stats});
});
});
});
}
};
+90
View File
@@ -0,0 +1,90 @@
var request = require('request');
var base_url = 'https://poloniex.com/public?command=';
function get_summary(coin, exchange, cb) {
var req_url = base_url + 'returnTicker';
var ticker = exchange + '_' + coin;
request({uri: req_url, json: true, headers: {'User-Agent': 'eiquidus'}}, function (error, response, body) {
if (body.error) {
return cb(body.error, null);
} else {
return cb(null, body[ticker]);
}
});
}
function get_trades(coin, exchange, cb) {
var req_url = base_url + 'returnTradeHistory&currencyPair=' + exchange + '_' + coin;
request({uri: req_url, json: true, headers: {'User-Agent': 'eiquidus'}}, function (error, response, body) {
if (body.error) {
return cb(body.error, []);
} else {
return cb(null, body);
}
});
}
function get_orders(coin, exchange, cb) {
var req_url = base_url + 'returnOrderBook&currencyPair=' + exchange + '_' + coin + '&depth=50';
request({uri: req_url, json: true, headers: {'User-Agent': 'eiquidus'}}, function (error, response, body) {
if (body.error) {
return cb(body.error, []);
} else {
return cb(null, body);
}
});
}
function get_chartdata(coin, exchange, cb) {
var end = Date.now();
end = end / 1000;
start = end - 86400;
var req_url = base_url + 'returnChartData&currencyPair=' + exchange + '_' + coin + '&start=' + start + '&end=' + end + '&period=1800';
request({uri: req_url, json: true, headers: {'User-Agent': 'eiquidus'}}, function (error, response, chartdata) {
if (error) {
return cb(error, []);
} else {
if (chartdata.error == null) {
var processed = [];
for (var i = 0; i < chartdata.length; i++) {
processed.push([chartdata[i].date * 1000, parseFloat(chartdata[i].open), parseFloat(chartdata[i].high), parseFloat(chartdata[i].low), parseFloat(chartdata[i].close)]);
if (i == chartdata.length - 1) {
return cb(null, processed);
}
}
} else {
return cb(chartdata.error, []);
}
}
});
}
module.exports = {
get_data: function(coin, exchange, cb) {
var error = null;
get_chartdata(coin, exchange, function (err, chartdata){
if (err) {
chartdata = [];
error = err;
}
get_orders(coin, exchange, function (err, orders){
var buys = [];
var sells = [];
if (orders.bids) {
buys = orders.bids;
sells = orders.asks;
} else {
error = err;
}
get_trades(coin, exchange, function (err, trades){
if (err) { error = err; }
get_summary(coin, exchange, function (err, stats){
if (err) { error = err; }
return cb(error, {buys: buys, sells: sells, chartdata: chartdata, trades: trades, stats: stats});
});
});
});
});
}
};
+60
View File
@@ -0,0 +1,60 @@
var request = require('request');
var base_url = 'https://yobit.net/api/3';
function get_summary(coin, exchange, cb) {
var req_url = base_url + '/ticker/' + coin + '_' + exchange;
request({uri: req_url, json: true, headers: {'User-Agent': 'eiquidus'}}, function (error, response, body) {
if (error) {
return cb(error, null);
} else {
if (body.message) {
return cb(body.message, null)
} else {
return cb (null, body[coin + '_' + exchange]);
}
}
});
}
function get_trades(coin, exchange, cb) {
var req_url = base_url + '/trades/' + coin + '_' + exchange;
request({uri: req_url, json: true, headers: {'User-Agent': 'eiquidus'}}, function (error, response, body) {
if (error) {
return cb(error, null);
} else {
if (body.message) {
return cb(body.message, null)
} else {
return cb (null, body[coin + '_' + exchange]);
}
}
});
}
function get_orders(coin, exchange, cb) {
var req_url = base_url + '/depth/' + coin + '_' + exchange;
request({uri: req_url, json: true, headers: {'User-Agent': 'eiquidus'}}, function (error, response, body) {
if (body.success == 0) {
return cb(body.error, null, null);
} else {
return cb(null, body[coin + '_' + exchange]['bids'], body[coin + '_' + exchange]['asks']);
}
});
}
module.exports = {
get_data: function(coin, exchange, cb) {
var error = null;
get_orders(coin, exchange, function(err, buys, sells) {
if (err) { error = err; }
get_trades(coin, exchange, function(err, trades) {
if (err) { error = err; }
get_summary(coin, exchange, function(err, stats) {
if (err) { error = err; }
return cb(error, {buys: buys, sells: sells, chartdata: [], trades: trades, stats: stats});
});
});
});
}
};
+45
View File
@@ -0,0 +1,45 @@
var commands = require('./commands');
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);
});
};
(function() {
for (var protoFn in commands) {
(function(protoFn) {
Client.prototype[protoFn] = function() {
var args = [].slice.call(arguments);
callRpc(commands[protoFn], args, this.rpc);
};
})(protoFn);
}
})();
module.exports.Client = Client;
+205
View File
@@ -0,0 +1,205 @@
var onode = require('./node');
var express = require('express');
module.exports = function(){
function express_app(){
var app = express();
app.get('*', 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.');
}
var query_parameters = req.query;
var params = [];
for(var parameter in query_parameters){
if(query_parameters.hasOwnProperty(parameter)){
var param = query_parameters[parameter];
if(!isNaN(param)){
param = parseFloat(param);
}
params.push(param);
}
}
var command = [];
if (method == 'sendmany' ||
method == 'getmasternodecountonline' ||
method == 'getmasternodelist' ||
method == 'getvotelist') {
command = specialApiCase(method);
} else {
command = [{
method: method,
params: params
}];
}
client.cmd(command, function(err, response){
if(err){console.log(err); res.send("There was an error. Check your console.");}
else{
if(typeof response === 'object'){
res.json(response);
}
else{
res.send(""+response);
}
}
});
});
function hasAccess(req, res, next){
if(accesslist.type == 'all'){
return next();
}
var method = req.path.substring(1,req.path.length);
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 specialApiCase(method_name){
var params = [];
if(method_name == 'sendmany'){
var after_account = false;
var before_min_conf = true;
var address_info = {};
for(var parameter in query_parameters){
if(query_parameters.hasOwnProperty(parameter)){
if(parameter == 'minconf'){
before_min_conf = false;
params.push(address_info);
}
var 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);
}
}
//not liking this
if(method_name == 'getvotelist'){
method_name = 'masternodelist'
params.push('votes');
}
if(method_name == 'getmasternodelist'){
method_name = 'masternodelist'
params.push('full');
}
if(method_name == 'getmasternodecountonline'){
method_name = 'masternode';
params.push('count');
params.push('enabled');
}
return [{
method: method_name,
params: params
}];
}
return app;
};
var accesslist = {};
accesslist.type = 'all';
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
};
function setAccess(type, access_list){
//Reset//
accesslist = {};
accesslist.type = type;
if(type == "only"){
var i=0;
for(; i<access_list.length; i++){
accesslist[access_list[i]] = true;
}
}
if(type == "restrict"){
var i=0;
for(; 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'){
accesslist.type = 'restrict';
var restrict_list = ['dumpprivkey', 'walletpassphrasechange', 'stop'];
var i=0;
for(;i<restrict_list.length;i++){
accesslist[restrict_list[i]] = false;
}
}
if(type == 'read-only'){
accesslist.type = 'restrict';
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'];
var i=0;
for(;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
}
}();
+210
View File
@@ -0,0 +1,210 @@
/**
* The Settings Module reads the settings out of settings.json and provides
* this information to the other modules
*/
var fs = require("fs");
var jsonminify = require("jsonminify");
//The app title, visible e.g. in the browser window
exports.title = "eIquidus";
//The url it will be accessed from
exports.address = "explorer.example.com";
//logo
exports.logo = "/images/logo.png";
//The app favicon fully specified url, visible e.g. in the browser window
exports.favicon = "public/favicon.ico";
//What is displayed for the home button in the top-left corner (valid options are: title, coin, logo)
exports.homelink = "coin";
// home link logo height (value in px, only valid if using homelink = 'logo')
exports.logoheight = 50;
//Theme
exports.theme = "Exor";
//The Port ep-lite should listen to
exports.port = process.env.PORT || 3001;
//coin symbol, visible e.g. MAX, LTC, HVC
exports.symbol = "EXOR";
//coin name, visible e.g. in the browser window
exports.coin = "Exor";
//This setting is passed to MongoDB to set up the database
exports.dbsettings = {
"user": "eiquidus",
"password": "Nd^p2d77ceBX!L",
"database": "blockchaindb",
"address" : "localhost",
"port" : 27017
};
//This setting is passed to the wallet
exports.wallet = { "host" : "127.0.0.1",
"port" : 51573,
"user" : "exorrpc",
"pass" : "sSTLyCkrD94Y8&9mr^m6W^Mk367Vr!!K"
};
//Locale file
exports.locale = "locale/en.json",
//Menu and panel items to display
// set a number to pnl variables to change the panel display order. lowest # = far left panel, highest # = far right panel, 0 = do not show panel
exports.display = {
"api": true,
"market": true,
"twitter": false,
"facebook": false,
"googleplus": false,
"bitcointalk": false,
"website": false,
"slack": false,
"github": false,
"discord": false,
"telegram": false,
"reddit": false,
"youtube": false,
"search": true,
"richlist": true,
"movement": true,
"network": true,
"networkpnl": 1,
"difficultypnl": 2,
"masternodespnl": 3,
"coinsupplypnl": 4,
"pricepnl": 5
};
//API view
exports.api = {
"blockindex": 6415,
"blockhash": "dd17105f9e3d79c553b3670001e0243dd21378f4f90a340d87c0e5eb0b44dfd4",
"txhash": "2af5cc842d18814b45db44b62411c8a47987fc3c56294af38572989de5c1f7d5",
"address": "EaqHssmmgEPCxaeczbZnoqM6vutv9xmhrZ",
};
// markets
exports.markets = {
"coin": "EXOR",
"exchange": "BTC",
"enabled": [],
"cryptopia_id": "",
"ccex_key" : "Get-Your-Own-Key",
"coinexchange_id": "",
"default": ""
};
// richlist/top100 settings
exports.richlist = {
"distribution": true,
"received": true,
"balance": true
};
exports.movement = {
"min_amount": 100,
"low_flag": 1000,
"high_flag": 10000
},
//index
exports.index = {
"show_hashrate": false,
"difficulty": "POS",
"last_txs": 100
};
// twitter, facebook, googleplus, bitcointalk, github, slack, discord, telegram, reddit, youtube, website
exports.twitter = "your-twitter-username";
exports.facebook = "your-facebook-username";
exports.googleplus = "your-google-plus-username";
exports.bitcointalk = "your-bitcointalk-topic-value";
exports.github = "your-github-username/your-github-repo";
exports.slack = "your-full-slack-invite-url";
exports.discord = "your-full-discord-invite-url";
exports.telegram = "your-telegram-group-or-channel-name";
exports.reddit = "your-subreddit-name";
exports.youtube = "your-full-youtube-url";
exports.website = "your-full-website-url";
exports.confirmations = 6;
//timeouts
exports.update_timeout = 125;
exports.check_timeout = 250;
//genesis
exports.genesis_tx = "dd1d332ad2d8d8f49195056d482ae3c96fd2d16e9d166413b27ca7f19775644c";
exports.genesis_block = "0000860fcf946b44df0e7d85d6757d45f8de6f4c9aacc5c7b6abc13db1f68819";
exports.heavy = false;
exports.txcount = 100;
exports.show_sent_received = true;
exports.supply = "TXOUTSET";
exports.nethash = "getnetworkhashps";
exports.nethash_units = "G";
// simple Cross-Origin Resource Sharing (CORS) support
// enabling this feature will add a new output header to all requests like this: Access-Control-Allow-Origin: <corsorigin>
// corsorigin "*" will allow any origin to access the requested resource while specifying any other value for corsorigin will allow cross-origin requests only when the request is made from a source that matches the corsorigin filter
exports.usecors = false;
exports.corsorigin = "*";
exports.labels = {};
exports.reloadSettings = function reloadSettings() {
// Discover where the settings file lives
var settingsFilename = "settings.json";
settingsFilename = "./" + settingsFilename;
var settingsStr;
try{
//read the settings sync
settingsStr = fs.readFileSync(settingsFilename).toString();
} catch(e){
console.warn('No settings file found. Continuing using defaults!');
}
// try to parse the settings
var settings;
try {
if(settingsStr) {
settingsStr = jsonminify(settingsStr).replace(",]","]").replace(",}","}");
settings = JSON.parse(settingsStr);
}
}catch(e){
console.error('There was an error processing your settings.json file: '+e.message);
process.exit(1);
}
//loop trough the settings
for(var i in settings)
{
//test if the setting start with a low character
if(i.charAt(0).search("[a-z]") !== 0)
{
console.warn("Settings should start with a low character: '" + i + "'");
}
//we know this setting, so we overwrite it
if(exports[i] !== undefined)
{
exports[i] = settings[i];
}
//this setting is unkown, output a warning and throw it away
else
{
console.warn("Unknown Setting: '" + i + "'. This setting doesn't exist or it was removed");
}
}
};
// initially load settings
exports.reloadSettings();
+154
View File
@@ -0,0 +1,154 @@
/*
This file must be valid JSON. But comments are allowed
Please edit en.json, not en.json.template
*/
{
// menu items
"menu_explorer": "Explorer",
"menu_api": "API",
"menu_markets": "Markets",
"menu_richlist": "Top 100",
"menu_reward": "Reward",
"menu_movement": "Movement",
"menu_node": "Nodes",
"menu_network": "Network",
// explorer view
"ex_title": "Block Explorer",
"ex_search_title": "Search",
"ex_search_button": "Search",
"ex_search_message": "You may enter a block height, block hash, tx hash or address.",
"ex_error": "Error!",
"ex_search_error": "Search found no results for: ",
"ex_latest_transactions": "Latest Transactions",
"ex_summary": "Block Summary",
"ex_supply": "Coin Supply",
"ex_block": "Block",
// transaction view
"tx_title": "Transaction Details",
"tx_block_hash": "Block Hash",
"tx_recipients": "Recipients",
"tx_contributors": "Input Addresses",
"tx_hash": "Hash",
"tx_address": "Address",
"tx_nonstandard": "NONSTANDARD TX",
// block view
"block_previous": "Previous",
"block_next": "Next",
"block_title": "Block Details",
"block_genesis": "GENESIS",
// global
"difficulty": "Difficulty",
"masternodecount": "Masternodes",
"network": "Network",
"height": "Height",
"timestamp": "Timestamp",
"size": "Size",
"transactions": "Transactions",
"total_sent": "Total Sent",
"total_received": "Total Received",
"confirmations": "Confirmations",
"total": "Total",
"bits": "Bits",
"nonce": "Nonce",
"new_coins": "New Coins",
"proof_of_stake": "PoS",
"initial_index_alert": "Indexing is currently incomplete, functionality is limited until index is up-to-date.",
//address menu
"a_menu_showing": "Showing last",
"a_menu_txs": "transactions",
"a_menu_all": "All",
//richlist
"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+",
"net_connections": "Connections",
"net_address": "Address",
"net_protocol": "Protocol",
"net_subversion": "Sub-version",
"net_country": "Country",
"net_warning": "This is sub sample of the network based on wallets that have connected to this node in the last 24hours.",
// api view
"api_title": "API Documentation",
"api_message": "The block explorer provides an API allowing users and/or applications to retrieve information from the network without the need for a local wallet.",
"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 master node list.",
"api_getmasternodecount": "Returns the total number of masternodes on the network.",
"api_getvotelist": "Returns the current vote list.",
"api_getblockcount": "Returns the current block index.",
"api_getblockhash": "Returns the hash of the block at ; 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 string describing how long until the votes are tallied and the next block reward is computed.",
// Markets view
"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",
// Markets
"poloniex": "Poloniex",
"bittrex": "Bittrex",
"bleutrade": "Bleutrade",
"yobit": "Yobit",
"empoex": "Empoex",
"cryptsy": "Cryptsy",
"cryptopia": "Cryptopia",
"ccex": "C-Cex",
"coinexchange": "CoinExchange",
// Heavy rewards view
"heavy_title": "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",
}
+13
View File
@@ -0,0 +1,13 @@
var mongoose = require('mongoose')
, Schema = mongoose.Schema;
var AddressSchema = new Schema({
a_id: { type: String, unique: true, index: true},
txs: { type: Array, default: [] },
received: { type: Number, default: 0 },
sent: { type: Number, default: 0 },
balance: {type: Number, default: 0},
}, {id: false});
module.exports = mongoose.model('Address', AddressSchema);
+21
View File
@@ -0,0 +1,21 @@
var mongoose = require('mongoose')
, Schema = mongoose.Schema;
var HeavySchema = new Schema({
coin: { type: String },
lvote: { type: Number, default: 0 },
reward: { type: Number, default: 0 },
supply: { type: Number, default: 0 },
cap: { type: Number, default: 0 },
estnext: { type: Number, default: 0 },
phase: { type: String, default: 'N/A'},
maxvote: { type: Number, default: 0 },
nextin: { type: String, default: 'N/A'},
votes: { type: Array, default: [] },
});
module.exports = mongoose.model('Heavy', HeavySchema);
/*
votes : [{ count: 0, reward: 0, vote: 0}]
*/
+13
View File
@@ -0,0 +1,13 @@
var mongoose = require('mongoose')
, Schema = mongoose.Schema;
var MarketsSchema = new Schema({
market: { type: String },
summary: { type: Object, default: {} },
chartdata: { type: Array, default: [] },
buys: { type: Array, default: [] },
sells: { type: Array, default: [] },
history: { type: Array, default: [] },
});
module.exports = mongoose.model('Markets', MarketsSchema);
+12
View File
@@ -0,0 +1,12 @@
var mongoose = require('mongoose')
, Schema = mongoose.Schema;
var PeersSchema = new Schema({
createdAt: { type: Date, expires: 86400, default: Date.now()},
address: { type: String, default: "" },
protocol: { type: String, default: "" },
version: { type: String, default: "" },
country: { type: String, default: "" }
});
module.exports = mongoose.model('Peers', PeersSchema);
+10
View File
@@ -0,0 +1,10 @@
var mongoose = require('mongoose')
, Schema = mongoose.Schema;
var RichlistSchema = new Schema({
coin: { type: String },
received: { type: Array, default: []},
balance: { type: Array, default: [] },
});
module.exports = mongoose.model('Richlist', RichlistSchema);
+17
View File
@@ -0,0 +1,17 @@
var mongoose = require('mongoose')
, Schema = mongoose.Schema;
var StatsSchema = new Schema({
coin: { type: String },
count: { type: Number, default: 1 },
last: { type: Number, default: 1 },
//difficulty: { type: Object, default: {} },
//hashrate: { type: String, default: 'N/A' },
supply: { type: Number, default: 0 },
//last_txs: { type: Array, default: [] },
connections: { type: Number, default: 0 },
last_price: { type: Number, default: 0 },
last_usd_price: { type: Number, default: 0 },
});
module.exports = mongoose.model('coinstats', StatsSchema);
+14
View File
@@ -0,0 +1,14 @@
var mongoose = require('mongoose')
, Schema = mongoose.Schema;
var TxSchema = new Schema({
txid: { type: String, lowercase: true, unique: true, index: true},
vin: { type: Array, default: [] },
vout: { type: Array, default: [] },
total: { type: Number, default: 0 },
timestamp: { type: Number, default: 0 },
blockhash: { type: String },
blockindex: {type: Number, default: 0},
}, {id: false});
module.exports = mongoose.model('Tx', TxSchema);
+27
View File
@@ -0,0 +1,27 @@
{
"name": "explorer",
"version": "1.0.0",
"private": true,
"scripts": {
"start": "node --stack-size=10000 ./bin/cluster",
"stop": "kill -2 $(cat tmp/cluster.pid)",
"test": "node ./node_modules/jasmine/bin/jasmine.js"
},
"dependencies": {
"express": ">=4.17.1",
"serve-favicon": "^2.5.0",
"morgan": ">=1.9.1",
"cookie-parser": "~1.4.4",
"body-parser": "~1.19.0",
"debug": ">=4.1.1",
"pug": "~2.0.3",
"request": "2.88.0",
"jsonminify": "0.4.1",
"mongodb": "3.2.6",
"mongoose": "5.5.11",
"qr-image": "~3.2.0"
},
"devDependencies": {
"jasmine": "~3.4.0"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

File diff suppressed because it is too large Load Diff
+205
View File
@@ -0,0 +1,205 @@
body {
padding: 80px 40px;
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
/* background-image: url('/images/background2.png');*/
}
#index-search {
width: 80%;
}
.nav-search input {
width: 400px !important;
margin-right: 5px;
}
.view_tx {
text-align: center;
}
.block-last {
margin: 0;
margin-right: 10px;
}
.block-next {
margin: 0;
margin-left: 10px;
}
#chart3 .jqplot-xaxis {
display: none;
}
.data {
display: block;
overflow: hidden;
width: 100%;
}
.txid {
width: 100%;
display:block;
overflow: hidden;
}
.panel-address-summary {
margin-bottom: 5px;
}
/* datatable tweaks */
table {
width: 100% !important;
}
.dataTables_info, .dataTables_length {
padding-left: 10px;
}
.dataTables_info {
display: hidden !important;
}
.dataTables_length {
padding-top: 10px;
}
.dataTables_paginate {
padding-right: 5px;
}
.tab-pane {
margin-top: 5px;
}
table a:not(.btn),.table a:not(.btn){
text-decoration:none
}
tr {
width: 100%;
}
.footer-padding {
height: 50px;
width: 100%;
}
.summary-table {
margin: 0px !important;
}
#loading-icon {
width: 30px;
margin: 10px 10px;
}
.menu-text {
margin-left:5px;
}
#twitter-icon,
#facebook-icon,
#googleplus-icon,
#bitcointalk-icon,
#website-icon,
#github-icon,
#slack-icon,
#discord-icon,
#telegram-icon,
#reddit-icon,
#youtube-icon {
font-size: 20px;
}
.connections {
position: absolute;
bottom: 15px;
right: 15px;
}
#lblBlockcount {
margin-right: 10px;
}
#twitter-icon img,
#facebook-icon img,
#googleplus-icon img,
#bitcointalk-icon img,
#website-icon img,
#github-icon img,
#slack-icon img,
#discord-icon img,
#telegram-icon img,
#reddit-icon img,
#youtube-icon img{
width: 40px;
margin: 5px 5px;
}
#market_menu {
margin-bottom: 5px;
}
#hashratepanel span,
#difficultypanel span,
#supplypanel span,
#masternodepanel span,
#pricepanel span{
margin: 0 5px 0 5px;
top: 0;
}
.label a:link {
color: #ffffff;
}
.label a:visited {
color: #ffffff;
}
.label a:hover {
color: #ffffff;
}
.label a:active {
color: #ffffff;
}
@media(max-width:767px){
body {
padding: 0px;
padding-top:60px;
}
}
@media(max-width:1096px){
.nav-search input {
width: 300px !important;
}
}
@media(max-width:865px){
.nav-search input {
width: 150px !important;
}
}
.qrcode {
position: absolute;
top: 65px;
right: 15px;
}
.footer-logo {
height: 40px;
position: absolute;
bottom: 0px;
}
.logo-main {
padding: 0 15px;
}
@media(max-width:767px){
.logo-main {
padding: 0 15px 0 30px;
}
}
File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.
+21
View File
@@ -0,0 +1,21 @@
Title: MIT License
Copyright (c) 2009-2013 Chris Leonello
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+77
View File
@@ -0,0 +1,77 @@
Title: jqPlot Readme
Pure JavaScript plotting plugin for jQuery.
To learn how to use jqPlot, start with the Basic Usage Instructions below. Then read the
usage.txt and jqPlotOptions.txt files included with the distribution.
The jqPlot home page is at <http://www.jqplot.com/>.
Downloads can be found at <http://bitbucket.org/cleonello/jqplot/downloads/>.
The mailing list is at <http://groups.google.com/group/jqplot-users>.
Examples and unit tests are at <http://www.jqplot.com/tests/>.
Documentation is at <http://www.jqplot.com/docs/>.
The project page and source code are at <http://www.bitbucket.org/cleonello/jqplot/>.
Bugs, issues, feature requests: <http://www.bitbucket.org/cleonello/jqplot/issues/>.
Basic Usage Instructions:
jqPlot requires jQuery (1.4+ required for certain features). jQuery 1.9.1 is included in
the distribution. To use jqPlot include jQuery, the jqPlot jQuery plugin, the jqPlot css file and
optionally the excanvas script to support IE version prior to IE 9 in your web page:
> <!--[if lt IE 9]><script language="javascript" type="text/javascript" src="excanvas.js"></script><![endif]-->
> <script language="javascript" type="text/javascript" src="jquery-1.4.4.min.js"></script>
> <script language="javascript" type="text/javascript" src="jquery.jqplot.min.js"></script>
> <link rel="stylesheet" type="text/css" href="jquery.jqplot.css" />
For usage instructions, see <jqPlot Usage> in usage.txt. For available options, see
<jqPlot Options> in jqPlotOptions.txt.
Building from source:
If you've cloned the repository, you can build a distribution from source.
You need to have ant <http://ant.apache.org> installed. You can simply
type "ant" from the jqplot directory to build the default "all" target.
There are 6 pertinent targets: clean, dist, min, docs, compress and all. Use:
> ant -p
to get a description of the various build targets.
Legal Notices:
Copyright (c) 2009-2013 Chris Leonello
jqPlot is currently available for use in all personal or commercial projects
under both the MIT and GPL version 2.0 licenses. This means that you can
choose the license that best suits your project and use it accordingly.
Although not required, the author would appreciate an email letting him
know of any substantial use of jqPlot. You can reach the author at:
chris at jqplot or see http://www.jqplot.com/info.php .
If you are feeling kind and generous, consider supporting the project by
making a donation at: http://www.jqplot.com/donate.php .
jqPlot includes date instance methods and printf/sprintf functions by other authors:
Date instance methods:
author Ken Snyder (ken d snyder at gmail dot com)
date 2008-09-10
version 2.0.2 (http://kendsnyder.com/sandbox/date/)
license Creative Commons Attribution License 3.0 (http://creativecommons.org/licenses/by/3.0/)
JavaScript printf/sprintf functions.
version 2007.04.27
author Ash Searle
http://hexmen.com/blog/2007/03/printf-sprintf/
http://hexmen.com/js/sprintf.js
The author (Ash Searle) has placed this code in the public domain:
"This code is unrestricted: you are free to use it however you like."
+458
View File
@@ -0,0 +1,458 @@
Title: Change Log
1.0.8:
* Issue #375: sortMergedLabels does not sort string labels
* Issue #279: Groups > 3 Causes Alignment Issues
* Issue #439: IE can't display a customized legend in Quirks mode
* Issue #482: "Undefined" error message when plotting a chart with no data
* Issue #116: Don't mix spaces and tabs for indentation
* Issue #564: Metergauge renderer not resizable when replotting
* Issue #409: MeterGaugeRenderer replot/redraw offsets center
* Issue #523: Adding rectangles to Canvas Overlay plugin
* Issue #756: jqplot.min files contain non-UTF-8 characters
* Issue #223: fillToZero does not color negative values when crossover point is 0
* Pull Request #23: Adding rectangles to Canvas Overlay plugin
* Pull Request #28: Cross-over points of 0 will actually change colors
* Pull Request #35: Don't highlight hidden bars or show tooltips for them
* Pull Request #41: Add dutch(nl) and svenska(sv) translations for dates
* Add tooltip support for Pie Charts
* Update to latest YUI compressor
1.0.7:
* Issue #726: Bug in sprintf %p, sometimes it outputs exponential form rather than decimal
* Issue #717: Plot's preDrawHooks not called
* Issue #707: Browser hangs with LogAxisRenderer when value is 0
* Issue #695: Horizontal Bar Chart Negative Series Colors Not Working
* Issue #670: Examples IE7, IE8 and IE9 multipleBarColors.html failure and fix
* Issue #636: X Axis Date Renderer Single Day Not plotting
* Issue #607: Integration issue
* Issue #571: Decimal numbers not properly formatted
* Issue #552: jqPlot crashes when interval too small
* Issue #536: DateAxisRenderer invalid scaling
* Issue #534: "decimalMark" in the "jqplot.sprintf.js"
* Issue #529: Scientific notation on label values ending in 0
* Issue #521: invalid JS in meterGaugeRenderer.js
* Issue #516: Including BezierCurveRenderer plugin and initializing jqplot with no options give error
* Issue #500: DateAxisRenderer has timezone related issues
* Issue #452: Including ALL jqPlot plugins causes an Error
* Issue #494: No point when use LogAxisRenderer and a point has a zero value
* Issue #430: getIsoWeek: invalid method call
* Issue #280: jqplot Options
* Issue #179: Spelling/grammar
* Pull Request #18: Implement getTop in CanvasAxisTickRenderer
* Pull Request #21: Performance issue when drawing pointlabels with zeros/null values
* Pull Request #24: Added suggested fix in comment #8 for issue #536
* Pull Request #29: Removed unbalanced addition of UTC offset
* Pull Request #33: Documentation fixes (issue #179, other changes)
* Pull Request #34: Start of updating jqPlotOptions.txt
* Pull Request #37: Example and suggested fix for issues #552 and issue #536
* Pull Request #39: Fixed trailing comma which caused issues with IE7
1.0.6:
* Add left sidebar navigation to examples
* Update examples for jquery 1.9.1 and jquery ui 1.10.0
* Add colorpicker.js to distribution
* Fix some problems with examples when viewing with local file system
* Add "minified" copyright notice for minified files, similar to jquery's notice.
* Pull Request #25: jqplot.sprintf.js is no longer the last file in the concatenated jquery.jqplot.js
* Pull Request #17: Fixed bug causing custom pointLabels passed with plot data to be ignored for horizontal bar graphs.
* Pull Request #10: Build error by invalid encoding.
* Issue #714: handle tickColor in meterGaugeRenderer
* Issue #519: jsDate Polish Localization
1.0.5:
* Updated to jQuery 1.9
1.0.0b2:
* Major improvements in memory usage:
** Merged in changes from Timo Besenruether to reuse canvas elements and improve
memory performance.
** Fixed all identifiable DOM leaks.
** Mergged in changes from cguillot for memory improvements in IE < 9.
* Added vertical and dashed vertical line support for canvas overlay.
* Fixed bug where initially hidden plots would not display.
* Fixed bug with point labels and null data points.
* Updated to jQuery 1.6.1.
* Improved pie slice margin calculation and fixed slice margin and pie positioning
with small slices.
* Improved bar renderer so bars always start at 0 if:
** The axis is a linear axis (not log/date).
** There are no other line types besides bars attached to the axis.
** The data on the axis is all >= 0.
** The user has not specified a pad, padMin or forceTickAt0 = true option.
* Modified tick prefix behavious so prefix no added to all ticks, even if format
string is specified.
* Fix to ensure original tick formats are applied when zooming and resetting
zoom.
* Updated auto tick format string so format adjusted when zooming.
* Modified auto tick computation to put less ticks on small plots and more
ticks on large plots.
* Update bubble render to support gradients in IE 9.
1.0.0b1:
* Much improved tick generation algorithm to get precise rounded
tick values (Thanks Scott Prahl!).
* Auto compute tick format string if none is provided.
* Much better "slicing" of pie charts when using "sliceMargin" option to set
a gap between the slices.
* Expanded canvasOverlay plugin to create arbitrary dashed and solid
horizontal and vertical lines on top of plot.
* Added defaultColors and defaultNegativeColors options to $.jqplot.config.
* Fixed issue #318, highlighter & bar renderer incompatability.
* Improve highlighter tooltip positioning with negative bars.
* Fixed #305, mispelling of jqlotDragStart and jqlotDragStop. MUST NOW BIND
TO jqplotDragStart and jqplotDragStop.
* Fixed #290, some variables left in global scope.
* Fixed #289, OHLC line widths hard coded at 1.5. Now set by lineWidth option.
* Fixed #296 for determining databounds on log axes.
* Updated to jQuery 1.5.1
* Fixed waterfall plot to ensure first and last bars always fill to zero.
* Added lineJoin and lineCap option to series lines.
* Bar widths now based on width of grid, not plot target for better scaling.
* Added looseZoom option to cursor so zooming can produce well rounded ticks.
* Added forceTickAt0 and forceTickAt100 options to ensure there will always
be a tick at 0 or 100 in the plot.
* Fixed bug where cursor legend didn't honor series showLabel option.
1.0.0a:
* Series can now be moved forward or backward in stack to e.g. bring a line
forward when mousing over a point.
* Can now move outside of grid area while zooming. Can have zoom
constrained to grid area or allow zooming outside.
* Fixed issue #142 with tooltip drawn on top of event canvas, hiding
mouse events.
* Fixed #147 where pie slices with 0 value not rendering properly in IE.
* Fixed #130 where stack data not sorted properly.
* Fixed bug with null values not handled properly in category axes.
* Fixed #156 where pie charts not rendering on QTWebKit.
* Now using feature detection for canvas and canvas text capability
rather than browser version.
* Added enahncedLegendRenderer plugin to allow multi row/column legends
and clickable labels to show/hide series.
* Added fillToValue option to allow filled line plot to fill to an
arbitrary value.
* Added block plot plugin.
* Added funnel type charts.
* Added meter gauge type charts.
* Added plot theming support.
* $.jqplot.config.enablePlugins now false by default.
* Implemented highlighting on bar, pie, donut, funnel, etc. charts.
* Fix to pointlabels plugin to align labels properly on multi series plots.
* Added custom error handling to display error message in plot area.
* Fixed issue where would call to draw grid border of 0 width would
result in a default border being drawn.
* Added options to place legend outside of grid and shrink grid so everything
stays within plot div.
* Fixed bug in color generator so now calls to get() continually cycle
through colors just like next().
* Added defaultAxisStart option.
* Added gradient fills to bubbles.
* Added bubble charts.
* Added showLabels option to bubble charts.
* Pass bubble radius to event callback in bubble charts.
* Fixed #207, typo in docs.
* Fixed #206 where "value" pie slice data labels were displaying wrong
value.
* Fixed #147 with 0 value slices in IE6.
* Fixed issue #241, disabled varyBarColor option in stacked charts.
* Added dataRenderer option to allow custom processors for JSON, AJAX
and anywhere else you might want to get data.
* Fixed null value handling so plot now properly skip or join over nulls.
* Fixed showTicks and showTickMarks option conflicts.
* Fixed issue #185 where pointLabels plugin incompatibility could crash
pie, donut and other plots.
* Fixed #23 and #143 to obey gridPadding option.
* Fixed #233 with highlighter tooltip separator.
* Fixed #224 where type checking failing on GWT.
* Fixed #272 with pie highlighting not working on replot.
* Memory performance improvements.
* Changes to build script so everything should build when pulled from repo.
* Fixed issue #275, IE 6/7 don't support array indexing of strings.
* Added event listener hooks for mouseUp, mouseDown, etc. to all line plots.
* Fixed bug with highlighter not working when null in data.
* Updated to jQuery 1.4.4
* Fixed bug where donut plots showed value of radians of slice instead
of actual data.
* Reverted to excanvas r3 so IE8 no longer has to emulate IE7.
* Added tooltipContentEditor option to highlighter, allowing callback
to manipulate tooltip content at run time (thanks Tim Bunce!).
* Fixed bug where axes scale not resetting.
* Fixed bug with date axes where data bounds not properly set.
* Fixed issue where tick marks disappear if grid lines turned off.
* Updated replot method to allow passing in axes options for more control.
* Added experimental support for "broken" axes.
* Fixed bug with pies where pies with 0 valued slices did not draw correctly.
* Added canvasOverlay plugin to allow drawing of arbitrary shapes on a canvas
over the plot.
* Added option to display arbitrary text/html (message, animated gif, etc.) if
plot is constructed without data. Allow a "data loading" indicator to be shown.
* Added resetAxisValues method to manually update axis ticks without
redrawing the plot.
* Fix to labels on negative bars so label postiion of 'n' will be below a negative bar,
just as it is above a positive bar (thanks guigod!).
* Added thousands separator character (') to sprintf formatting (thanks yuichi1004!).
* Re-factored date parsing/formatting to use new jsDate module which does not
extend the Date prototype.
0.9.7:
* Added Mekko chart plot type with enhanced legend and axes support.
* Implemented vertical waterfall charts. Can create waterfall plot as
option to bar chart. See examples folder of distribution.
* Enhanced plot labels for waterfall style.
* Enhanced bar plots so you can now color each bar of a series
independently with the "varyBarColor" option.
* Re-factored series drawing so that each series and series shadow drawn
on its own canvas. Allows series to be redrawn independently of each other.
* Added additional default series colors.
* Added useNegativeColors option to turn off negative color array and use
only seriesColors array to define all bar/filled line colors.
* Fix css for cursor legend.
* Modified shape renderer so rectangles can be stroked and filled.
* Re-factored date methods out of dateAxisRenderer so that date formatter
and methods can be accesses outside of dateAxisRenderer plugin.
* Fixed #132, now trigger series change event on plot target instead of drag canvas.
* Fixes issue #116 where some source files had mix of tabs and spaces
for indentation. Should have been all spaces.
* Fixed issue #126, some links broken in docs section of web site.
* Fixed issue #90, trendline plugin incompatibility with pie renderer.
* Updated samples in examples folder of distribution to include navigation
links if web server is set up to process .html files with php.
0.9.6:
* New, easier to use, replot() method for placing plots in tabs, accordions,
resizable containers or for changing plot parameters programmatically.
* Updated legend renderer for pie charts to draw swatches which will
print correctly.
* Fixed issue #118 with patch from taum so autoscale option will
honor tickInterval and numberTicks options
* Fix to plot diameter calculation for initially hidden plots.
* Added examples for making plots in jQuery UI tabs and accordions.
* Fixed issue #120 where pie chart with single slice not displaying
correctly in IE and Chrome
0.9.5.2:
* Fixed #102 where double clicking on plot that has zoom enabled, but
has not been zoomed resulted in error.
* Fixed bug where candlestick coloring options not working.
* Added option to turn individual series labels off in the legend.
0.9.5.1:
* Fixed bug where tooltip not working with OHLC and candlestick charts.
* Added additional marker styles: plus, X and dash.
0.9.5:
* Implemented "zoomProxy". zoomProxy allows zooming one plot from another
such as an overview plot.
* Zooming can now be constrained to just x or y axis.
* Enhanced cursor plugin with vertical "dataTracking" line. This is a line
at the cursor location with a readout of data points at the line location
which are displayed in the chart legend.
* Changed cursor tooltip format string. Now one format string is used for
entire tooltip.
* Added mechanisms to specify plot size when plot target is hidden or plot
height/width otherwise cannot be determined from markup.
* Added $.jqplot.config object to specify jqplot wide configuration options.
These include enablePlugins to globally set the default plugin state on/off
and defaultHeight/defaultWidth to specify default plot height/width.
* Added fillToZero option which forces filled charts to fill to zero as opposed
to axis minimum. Thus negative filled bar/line values will fill upwards to
zero axis value.
* Added option to disable stacking on individual lines.
* Changed targetId property of the plot object so it now includes a "#" before
the id string.
* Improved tick and body sizing of Open Hi Low Close and candlestick charts.
* Removed lots of web site related files from the repository. This means that,
if working from the sources, user's won't be able to build the jqplot web
site and the docs/tests that are hosted on that site. The minified and
compressed distribution packages will build fine.
* Lots of examples were added to a separate examples directory to better show
functionality of jqPlot for local testing with the distribution.
* Many various bug fixes and other minor enhancements.
0.9.4:
* Implemented axis labels. Labels can be rendered in div tags or as canvas
elements supporting rotated text.
* Improved rotated axis label positioning so labels will start or end at a
tick position.
* Fixed bug where an empty data series would hang plot rendering.
* completed issue #66 for misc. improvements to documentation.
* Fixed issue #64 where the same ID's were assigned to cursor and highlighter
elements.
* Added option to legend to encode special HTML characters.
* Fixed undesirable behavior where point labels for points off the plot
were being rendered.
* Added edgeTolerance option to point label renderer to control rendering of
labels near plot edges.
0.9.3:
* Preliminary support for axis labels. Currently rendered into DIV tags,
so no rotated label support. This feature is currently experimental.
* Fixed bug #52, needed space in tick div tag between style and class declarations
or plot failed in certain application doctypes.
* Fixed issue #54, miter style line join for chart lines causing spikes at steep
changes in slope. Changed miter style to round.
* Added examples for new autoscaling algorithm.
* Fixed bug #57, category axis labels disappear on redraw()
* Improved algorithm which controlled maximum number of labels that would display
on a category axis.
* Fixed bug #45 where null values causing errors in plotData and gridData.
* Fixed issue #60 where seriesColors option was not working.
0.9.2:
* Fixed bug #45 where a plot could crash if series had different numbers of points.
* Fixed issue #50, added option to turn off sorting of series data.
* Fixed issue #31, implemented a better axis autoscaling algorithm and added an autoscale option.
0.9.1:
* Fixed bug #40, when axis pad, padMax, padMin set to 0, graph would fail to render.
* Fixed bug #41 where pie and bar charts not rendered correctly on redraw().
* Fixed bug #11, filled stacked line plots not rendering correctly in IE.
* Fixed bug #42 where stacked charts not rendering with string date axis ticks.
* Fixed bug in redraw() method where axes ticks were not reset.
* Fixed "jqplotPreRedrawEvent" that should have been named "jqplotPostRedraw" event.
0.9.0:
* Added Open Hi Low Close charts, Candlestick charts and Hi Low Close charts.
* Added support for arbitrary labels on the data points.
* Enhanced highlighter plugin to allow custom formatting control of entire tooltip.
* Enhanced highlighter to support multiple y values in a data point.
* Fixed bug #38 where series with a single point with a negative value would fail.
* Improvements to examples to show what plugins to include.
* Expanded documentation for some of the plugins.
0.8.5:
* Added zooming ability with double click or single click options to reset zoom.
* Modified default tick spacing algorithm for date axes to give more space to ticks.
* Fixed bug #2 where tickInterval wasn't working properly.
* Added neighborThreshold option to control how close mouse must be to
point to trigger neighbor detection.
* Added double click event handler on plot.
0.8.0:
* Support for up to 9 y axes.
* Added option to control padding at max/min bounds of axes separately.
* Closed issue #21, added options to control grid line color and width.
* Closed issue #20, added options to filled line charts to stoke above
fill and customize fill color and transparency.
* Improved structure of on line documentation to make usage and options
docs default.
* Added much documentation on options and css styling.
0.7.1:
* Bug fix release
* Fixed bug #6, missing semi-colons messing up some javascript compressors.
* Fixed bug #13 where 2D ticks array of [values, labels] would fail to
renderer with DateAxisRenderer.
* Fixes bug #16 where pie renderer overwriting options for all plot types
and crashing non pie plots.
* Fixes bug #17 constrainTo dragable option mispelled as "contstrainTo".
Fixed dragable color issue when used with trend lines.
0.7.0:
* Pie chart support
* Enabled tooltipLocation option in highlighter.
* Highlighter Tooltip will account for mark size and highlight size when
positioning itself.
* Added ability to show just x, y or both axes in highlighter tooltip.
* Added customization of separator between axes values in highlighter tooltip.
* Modified how shadows are drawn for lines, bars and markers. Now drawn first,
so they are always behind the object.
* Adjustments to shadow parameters on lines to account for new shadow positioning.
* Added a ColorGenerator class to robustly return next available color
for a plot with wrap around to first color at end.
* Udates to docs about css file.
* Fixed bug with String x values in series and IE error on sorting (Category Axis).
* Added cursor changes in dragable plugin when cursor near dragable point.
0.6.6b:
* Added excanvas.js and excanvas.min.js to compressed distributions.
* Added example/test html pages I had locally into repository and to
compressed distributions.
0.6.6a:
* Removed absolute positioning from dom element and put back into css file.
* Duplicate of 0.6.6 with a suffix to unambiguously differentiate between
previously posted 0.6.6 release.
0.6.6:
* Fixed bug #5, trend line plugin failing when no trend line options specified.
* Added absolute position css spec to axis tick dom element.
* Enhancement to category axes, more intuitive handling of series with
missing data values.
0.6.5:
* Fixed bug #4, series of unequal data length not rendering correctly.
This is a bugfix release only.
0.6.4:
* Fixed bug (issue #1 in tracker) where flat line data series (all x and/or y
values are euqal) or single value data series would crash.
0.6.3:
* Support for stacked line (a.k.a. area) and stacked bar (horizontal and
vertical) charts.
* Refactored barRenderer to use default shape and shadow renderers.
* Added info (contacts & support information) page to web site.
0.6.2:
* This is a minor upgrade to docs and build only. No functionality has changed.
* Ant build script generates entire site, examples, tests and distribution.
* Improvements to documentation.
0.6.1:
* New sprintf implementation from Ash Searle that implements %g.
* Fix to sprintf e/f formats.
* Created new format specifier, %p and %P to preserve significance.
* Modified p/P format to better display larger numbers.
* Fixed and simplified significant digits calculation for sprintf.
* Added option to have cursor tooltip follow the mouse or not.
* Added options to change size of highlight.
* Updates to handle dates like '6-May-09'.
* Mods to improve look of web site.
* Updates to documentation.
* Added license and copyright statement to source files.
0.6.0:
* Added rotated text support. Uses native canvas text functionality in
browsers that support it or draws text on canvas with Hershey font
* metrics for non-supporting browsers.
* Removed lots of lint in js code.
* Moved tick css from js code into css file.
* Fix to tick positioning css. y axis ticks were positioned to wrong side of axis div.
* Re-factored axis tick renderer instantiation into the axes renderers themselves.
For changes prior to 0.6.0 release, please see change log at http://bitbucket.org/cleonello/jqplot/changesets/
+56
View File
@@ -0,0 +1,56 @@
/**
* jqPlot
* Pure JavaScript plotting plugin using jQuery
*
* Version: @VERSION
*
* Copyright (c) 2009-2013 Chris Leonello
* jqPlot is currently available for use in all personal or commercial projects
* under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
* version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
* choose the license that best suits your project and use it accordingly.
*
* Although not required, the author would appreciate an email letting him
* know of any substantial use of jqPlot. You can reach the author at:
* chris at jqplot dot com or see http://www.jqplot.com/info.php .
*
* If you are feeling kind and generous, consider supporting the project by
* making a donation at: http://www.jqplot.com/donate.php .
*
* sprintf functions contained in jqplot.sprintf.js by Ash Searle:
*
* version 2007.04.27
* author Ash Searle
* http://hexmen.com/blog/2007/03/printf-sprintf/
* http://hexmen.com/js/sprintf.js
* The author (Ash Searle) has placed this code in the public domain:
* "This code is unrestricted: you are free to use it however you like."
*
* included jsDate library by Chris Leonello:
*
* Copyright (c) 2010-2013 Chris Leonello
*
* jsDate is currently available for use in all personal or commercial projects
* under both the MIT and GPL version 2.0 licenses. This means that you can
* choose the license that best suits your project and use it accordingly.
*
* jsDate borrows many concepts and ideas from the Date Instance
* Methods by Ken Snyder along with some parts of Ken's actual code.
*
* Ken's origianl Date Instance Methods and copyright notice:
*
* Ken Snyder (ken d snyder at gmail dot com)
* 2008-09-10
* version 2.0.2 (http://kendsnyder.com/sandbox/date/)
* Creative Commons Attribution License 3.0 (http://creativecommons.org/licenses/by/3.0/)
*
* jqplotToImage function based on Larry Siden's export-jqplot-to-png.js.
* Larry has generously given permission to adapt his code for inclusion
* into jqPlot.
*
* Larry's original code can be found here:
*
* https://github.com/lsiden/export-jqplot-to-png
*
*
*/
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 996 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1006 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 858 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 886 B

Some files were not shown because too many files have changed in this diff Show More