Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
gobitfly committed May 11, 2017
0 parents commit a7c2c23
Show file tree
Hide file tree
Showing 27 changed files with 1,514 additions and 0 deletions.
64 changes: 64 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# nyc test coverage
.nyc_output

# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# Typescript v1 declaration files
typings/

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
.env

# Data directory
data.db

# Configuration file
config.js
674 changes: 674 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# ERC20-Exporter
### Lightweight explorer for ERC20 based Ethereum tokens

ERC20-Exporter is an explorer built with NodeJS, Express and Parity. It does not require an external database and retrieves all information on the fly from a backend Ethereum node.


A demo instance connected to the Golem Network Token is available at [gnt.etherchain.org](http://gnt.etherchain.org).

## Current Features
* Browse transactions and accounts
* Named accounts
* Event log browser
* Supports Transfer and Approval events
* Live Backend Node status display
* Support for all [Bootswatch](https://bootswatch.com/) skins
* Accounts enumeration
* Supports IPC and HTTP backend connections
* Responsive layout

Missing a feature? Please request it by creating a new [Issue](https://github.com/gobitfly/erc20-exporter/issues).

## Getting started

Supported OS: Ubuntu 16.04

Supported Ethereum backend nodes: Parity, Geth (untested)

1. Setup a nodejs & npm environment
2. Install the latest version of the Parity Ethereum client
3. Start parity using the following options: `parity --warp`
4. Clone this repository to your local machine: `git clone https://github.com/gobitfly/erc20-exporter`
5. Install all dependencies: `npm install`
6. Rename `config.js.example` into `config.js` and adjust the file to your local environment & token
7. Start the explorer: `npm start`
8. Browse to `http://localhost:3000`

Please note that for large tokens the initial data export can take up to 30 minutes. Once completed it is recommended to change the exportStartBlock parameter in the config file to a block number that is around 30.000 blocks behind the current tip of the chain and restart the exporter.
94 changes: 94 additions & 0 deletions app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');

var index = require('./routes/index');
var account = require('./routes/account');
var accounts = require('./routes/accounts');
var event = require('./routes/event');
var events = require('./routes/events');
var search = require('./routes/search');

var config = new(require('./config.js'))();
var Datastore = require('nedb-core')
var db = new Datastore({ filename: './data.db', autoload: true });

db.ensureIndex({ fieldName: 'balance' }, function (err) {
if (err) {
console.log("Error creating balance db index:", err);
}
});

db.ensureIndex({ fieldName: 'timestamp' }, function (err) {
if (err) {
console.log("Error creating timestamp db index:", err);
}
});

db.ensureIndex({ fieldName: 'args._from' }, function (err) {
if (err) {
console.log("Error creating _from db index:", err);
}
});

db.ensureIndex({ fieldName: 'args._to' }, function (err) {
if (err) {
console.log("Error creating _to db index:", err);
}
});

var exporterService = require('./services/exporter.js');
var exporter = new exporterService(config, db);

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');
app.set('config', config);
app.set('db', db);
app.set('trust proxy', true);

// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(logger(config.logFormat));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/', index);
app.use('/account', account);
app.use('/accounts', accounts);
app.use('/event', event);
app.use('/events', events);
app.use('/search', search);

app.locals.moment = require('moment');
app.locals.nodeStatus = new(require('./utils/nodeStatus.js'))(config);
app.locals.nameformatter = new(require('./utils/nameformatter.js'))(config);
app.locals.tokenformatter = new(require('./utils/tokenformatter.js'))(config);
app.locals.config = config;

// catch 404 and forward to error handler
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});

// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};

// render the error page
res.status(err.status || 500);
res.render('error');
});

module.exports = app;
90 changes: 90 additions & 0 deletions bin/www
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#!/usr/bin/env node

/**
* Module dependencies.
*/

var app = require('../app');
var debug = require('debug')('erc20-explorer:server');
var http = require('http');

/**
* Get port from environment and store in Express.
*/

var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);

/**
* Create HTTP server.
*/

var server = http.createServer(app);

/**
* Listen on provided port, on all network interfaces.
*/

server.listen(port);
server.on('error', onError);
server.on('listening', onListening);

/**
* Normalize a port into a number, string, or false.
*/

function normalizePort(val) {
var port = parseInt(val, 10);

if (isNaN(port)) {
// named pipe
return val;
}

if (port >= 0) {
// port number
return port;
}

return false;
}

/**
* Event listener for HTTP server "error" event.
*/

function onError(error) {
if (error.syscall !== 'listen') {
throw error;
}

var bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;

// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}

/**
* Event listener for HTTP server "listening" event.
*/

function onListening() {
var addr = server.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
}
26 changes: 26 additions & 0 deletions config.js.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
var web3 = require('web3');
var net = require('net');

var config = function () {

this.logFormat = "combined";
this.ipcPath = process.env["HOME"] + "/.local/share/io.parity.ethereum/jsonrpc.ipc";
this.provider = new web3.providers.IpcProvider(this.ipcPath, net);

this.bootstrapUrl = "https://maxcdn.bootstrapcdn.com/bootswatch/3.3.7/yeti/bootstrap.min.css";

this.names = {
"0x007733a1fe69cf3f2cf989f81c7b4cac1693387a": "POA-Digix",
"0x00e4a10650e5a6d6001c38ff8e64f97016a1645c": "POA-Aurel",
"0x00e6d2b931f55a3f1701c7389d592a7778897879": "POA-Maker",
"0x0010f94b296a852aaac52ea6c5ac72e03afd032d": "POA-Paritytech",
"0x0020ee4be0e2027d76603cb751ee069519ba81a1": "POA-Melonport",
"0x4ed9b08e6354c70fe6f8cb0411b0d3246b424d6c": "POA-OneBit",
"0x00d6cc1ba9cf89bd2e58009741f4f7325badc0ed": "POA-Etherscan",
"0x00a0a24b9f0e5ec7aa4c7389b8302fd0123194de": "POA-GridS",
"0x00427feae2419c15b89d1c21af10d1b6650a4d3d": "POA-Attores"
}

}

module.exports = config;
21 changes: 21 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "erc20-explorer",
"version": "0.0.0",
"private": true,
"scripts": {
"start": "node ./bin/www"
},
"dependencies": {
"async": "^2.4.0",
"body-parser": "~1.16.0",
"cookie-parser": "~1.4.3",
"debug": "~2.6.0",
"express": "~4.14.1",
"moment": "^2.18.1",
"morgan": "~1.7.0",
"nedb-core": "^3.0.6",
"pug": "~2.0.0-beta10",
"serve-favicon": "~2.3.2",
"web3": "^0.19.0"
}
}
8 changes: 8 additions & 0 deletions public/stylesheets/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
body {
padding: 50px;
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
}

a {
color: #00B7FF;
}
29 changes: 29 additions & 0 deletions routes/account.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
var express = require('express');
var router = express.Router();

router.get('/:account/:offset?', function(req, res, next) {
var db = req.app.get('db');

if (!req.params.offset) {
req.params.offset = 0;
} else {
req.params.offset = parseInt(req.params.offset);
}
db.find({_id: req.params.account}).exec(function (err, balance) {

if (err) {
return next(err);
}

if (balance.length === 0 || !balance[0]._id) {
return next({message: "Account not found!"});
}

db.find( {$or: [{ "args._from": req.params.account }, { "args._to": req.params.account }] }).sort({ timestamp: -1 }).skip(req.params.offset).limit(50).exec(function(err, events) {
res.render('account', { balance: balance[0], events: events, offset: req.params.offset, stepSize: 50 });
});
});

});

module.exports = router;
19 changes: 19 additions & 0 deletions routes/accounts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
var express = require('express');
var router = express.Router();

router.get('/:offset?', function(req, res, next) {
var db = req.app.get('db');

if (!req.params.offset) {
req.params.offset = 0;
} else {
req.params.offset = parseInt(req.params.offset);
}

db.find({ balance: { $exists: true } }).sort({ _id: 1 }).skip(req.params.offset).limit(50).exec(function(err, accounts) {
res.render('accounts', {accounts: accounts, offset: req.params.offset, stepSize: 50 });
});

});

module.exports = router;
Loading

0 comments on commit a7c2c23

Please sign in to comment.