stampit-cli
Version:
Helper command line utility to publish and check data through the Ethereum stampit contract. Install with +npm install -g stampit-cli+ then run +stampit help+ and +stampit info+
425 lines (357 loc) • 11.8 kB
JavaScript
// usage:
// ./stampit --help
var Web3 = require('web3');
var web3 = new Web3();
var helpers = require('./lib/helpers')();
var lib = require('./lib/lib')();
var pkg = require('./package.json');
var config = require('./config.json');
var prompt = require('prompt');
var util = require('util');
var fs = require('fs-extra');
var crypto = require('crypto');
var homedir = require('homedir');
var path = require('path');
var userConfigPath = path.join(homedir(), '.stampit', 'config.json');
var Spinner = require('cli-spinner').Spinner;
Spinner.setDefaultSpinnerString(3);
web3.setProvider(new web3.providers.HttpProvider(config.web3provider));
function checkUserConfig() {
if (!fs.existsSync(userConfigPath)) {
console.log('Creating user config file. Using your default address. You can edit ' + userConfigPath);
fs.outputJsonSync(userConfigPath, config);
}
config = fs.readJsonSync(userConfigPath, { throws: false });
console.log('dbg', config.from[lib.getNetworkId()]);
if (config.from[lib.getNetworkId()] == '') {
config.from[lib.getNetworkId()] = web3.eth.accounts[0];
}
}
checkUserConfig();
config.getAddress = function() {
return config.address[lib.getNetworkId()];
}
config.getFromAccount = function() {
return config.from[lib.getNetworkId()];
}
var contractAddress = config.address[lib.getNetworkId()];
if (contractAddress == undefined)
console.log("No config found for network " + lib.getNetworkId(), config.address);
if (!contractAddress.match(/0x[0-9a-f]{40}/i))
helpers.exit(1, "Missing contract address");
var contract = web3.eth.contract(config.abi).at(contractAddress);
var yargs = require('yargs')
.usage('Usage: $0 <cmd> [options] [')
.example('$0 init|check|submit|list|info|verify|version [options]', '')
.command('init <yourfile.pdf>', 'Create a stampit.json from your file')
.command('check', 'Checks your stampit.json file')
.command('submit', 'Submit data to the blockchain')
// .option('local', {
// describe: 'List - all the local compilers',
// type: 'boolean'
// })
.command('info', 'Show some info about the contract')
.command('verify', 'Verify a local receipt against the data in the blockchain')
// .command(cmds.clean, 'Delete a/some compilers')
// .alias('clean', 'clean')
.global(['version', 'optimize'])
.showHelpOnFail(true, 'Specify --help for available options')
.help()
.demand(1, 'You need to provide at least a command.');
var argv = yargs.argv;
var cmd = argv._[0].toLowerCase();
var event;
// Check the command
// if (!(cmd in cmds))
// helpers.abort('cmd unknown');
function isJson(filename) {
return filename.indexOf('.json') > 0;
}
// return the fee amount
function getFee(contract) {
return contract.fee();
}
if (cmd === 'info') {
console.log('StampIt version: \t' + pkg.version);
console.log('Contract address: \t' + config.getAddress());
// console.log('Contract abi: \t\t' + JSON.stringify(config.abi));
console.log('Contract fee: \t\t' + web3.fromWei(getFee(contract), 'ether') + ' ETH');
console.log('User config: \t\t' + userConfigPath);
console.log('from: \t\t\t' + config.getFromAccount());
console.log('accounts\t\t' + web3.eth.accounts);
helpers.exit();
}
var data;
var file;
function eventHandler(err, res, tx, event) {
if (err) console.error('Error occured', err);
else {
// console.log('res=', res);
var eventData = res
if (eventData && eventData.transactionHash === tx) {
event.stopWatching()
confirmed();
} else {
console.log('Wrong event');
}
}
}
function onErr(err) {
console.log(err);
return 1;
}
function sha256(file, done) {
var hash = crypto.createHash('sha256'),
stream = fs.createReadStream(file);
stream.on('data', function(data) {
hash.update(data, 'utf8')
})
stream.on('end', function() {
done(hash.digest('hex')); // 34f7a3113803f8ed3b8fd7ce5656ebec
})
}
function getInitData(defval, file, done) {
prompt.start();
var targetFile = null;
if (file && fs.existsSync(file))
targetFile = file;
var properties = [{
name: 'file',
description: 'File',
default: targetFile ? targetFile : ''
}, {
name: 'author',
description: 'Author',
default: defval ? defval.author : process.env.USER ? process.env.USER : process.env.LOGNAME ? process.env.LOGNAME : ''
}, {
name: 'email',
description: 'E-Mail',
default: defval ? defval.email : ''
}, {
name: 'title',
description: 'Title',
default: defval ? defval.title : ''
}, {
name: 'summary',
description: 'Summary',
default: defval ? defval.summary : ''
}];
prompt.get(properties, function(err, result) {
if (err) {
return onErr(err);
}
if (!result.file)
done(result);
else
sha256(result.file, function(res) {
result.sha256 = res;
done(result);
});
});
}
function saveSpecs(specs, filename, done) {
fs.writeFile(filename, JSON.stringify(specs, null, 2), 'utf-8', done);
}
// create a new stampit.json
function init(file) {
var defval = null;
var defaultFile = process.cwd() + '/stampit.json';
if (fs.existsSync(defaultFile)) {
defval = require(defaultFile);
}
getInitData(defval, file, function(specs) {
saveSpecs(specs, './stampit.json', function(err) {
if (err) console.log(err);
console.log(specs);
console.log('File created, you can edit it and run stampit check');
});
});
}
if (cmd === 'init') {
if (argv._.length > 1) {
var file = argv._[1];
init(file);
} else {
init();
}
}
if (cmd === 'submit') {
var spec = null;
if (argv._.length > 1) {
spec = argv._[1];
if (!isJson(spec))
console.log('Try stampit init yourfile.pdf');
} else {
spec = 'stampit.json'
}
var specfile = process.cwd() + '/' + spec
data = require(specfile);
submit(data, specfile);
}
// check that the stampit.json file is ok
// for now, the file can be passed but only with ./file.json
if (cmd === 'check') {
var file = null;
if (argv._.length > 1) {
file = process.cwd() + '/' + argv._[1];
} else {
file = process.cwd() + '/stampit.json'
}
var spec = require(file);
var valid = validateSpecs(spec);
console.log(file + ' is ' + (valid ? 'VALID' : 'INVALID'));
if (valid)
console.log('You can now run stampit submit');
}
function validateSpecs(spec) {
return (spec.title != undefined && spec.title.length > 0)
}
// Call the getReceipt method and try to find the list of all receipt
// then display them
if (cmd === 'list') {
var submitter = null;
var i = 0;
var res = [];
do {
var rcp = contract.getReceipt(config.getFromAccount(), i++, { from: config.getFromAccount() });
res.push(rcp);
}
while (rcp !== '0x0000000000000000000000000000000000000000000000000000000000000000');
res.splice(-1, 1);
// console.log('Found ' + res.length + ' receipt(s):', res);
for (var j = 0; j < res.length; j++) {
var receipt = getSubmission(res[j]);
console.log(' - ' + (j + 1) + ': ' + res[j] + ' ' + receipt.timestamp);
}
}
function isValidTxHash(tx) {
return tx.match(/0x[0-9a-f]{64}/i);
}
function viewTx(tx) {
if (!isValidTxHash(tx)) {
helpers.exit(1);
}
const exec = require('child_process').exec;
exec('open ' + config.blockchainExplorer[lib.getNetworkId()] + tx, (error, stdout, stderr) => {
if (error) {
console.error(`exec error: ${error}`);
return;
}
if (stdout) console.log(`stdout: ${stdout}`);
if (stderr) console.log(`stderr: ${stderr}`);
});
}
if (cmd === 'verify') {
var file = null;
if (argv._.length > 1) {
file = argv._[1];
} else {
file = 'stampit.json';
}
var specs = require(process.cwd() + '/' + file);
console.log('Verifying submission based on ' + file);
var submission = getSubmission(specs.receipt.id);
var verif = verifySubmission(specs.data, submission);
var valid = isValid(verif);
displayVerif(specs.data, verif, submission, valid);
helpers.exit(0);
}
function displayVerif(specs, checks, values, valid) {
for (var prop in checks) {
var check = checks[prop] ? ' ✅ ' : ' ⛔ ';
console.log(check + prop + ': \t' + values[prop] + ' ' + check + ' ' + specs[prop]);
}
console.log('Global: ' + (valid ? ' ✅ ' : ' ⛔ '));
}
function isValid(checks) {
var res = true;
for (var prop in checks) {
res = res & checks[prop];
}
return res;
}
// sub is the original spec
// dat is what comes from the blockchain
function verifySubmission(sub, dat) {
var hashed = hash(sub);
// console.log('hashed',hashed);
var res = true;
var result = {};
var hasProperties = false;
for (var prop in hashed) {
hasProperties = true;
if (dat[prop] != undefined)
result[prop] = hashed[prop] === dat[prop]
}
return result;
}
function hash(obj) {
var res = {};
for (var prop in obj) {
res[prop] = web3.sha3(obj[prop]);
}
return res;
}
function confirmed(data, specfile, tx, receipt) {
console.log('Submission confirmed. Receipt: ' + receipt)
// console.log(data);
var specs = {};
specs.data = require(specfile);
specs.receipt = { id: receipt, contract: config.address, datetime: Date.now() };
var receiptFile = 'receipt_' + receipt + '.json';
saveSpecs(specs, receiptFile, function(err) {
if (err) console.log(err);
console.log(specs);
console.log('Receipt file created. Make sure to keep of copy of this receipt with your submitted (PDF) file.');
});
}
function getSubmission(receipt) {
var data = contract.getData(receipt, {
from: config.getFromAccount(),
});
return {
submitter: data[0],
sha256: data[1],
author: data[2],
email: data[3],
title: data[4],
summary: data[5],
timestamp: new Date(data[6].c[0] * 1000)
};
}
function submit(dat, specfile) {
console.log('Sending transaction... check your client, you may need to confirm the transaction.');
var spinner = new Spinner('Waiting for confirmation...');
spinner.start();
contract.submit(
web3.sha3(dat.sha256),
web3.sha3(dat.author),
web3.sha3(dat.email),
web3.sha3(dat.title),
web3.sha3(dat.summary), {
from: config.getFromAccount(),
value: getFee(contract)
},
function(err, tx) {
if (err) console.log(err);
console.log(config.blockchainExplorer[lib.getNetworkId()] + tx);
if (yargs.argv.showTx) viewTx(tx);
// console.log(contract);
event = contract.Submitted();
event.watch(function(err, res) {
if (err) console.error('Error occured', err);
else {
// console.log('res=', res);
var eventData = res
if (eventData && eventData.transactionHash === tx) {
event.stopWatching()
confirmed(dat, specfile, tx, res.args.receipt);
} else {
console.log('Wrong event');
}
}
spinner.stop(true);
});
});
}