UNPKG

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
#!/usr/local/bin/node // 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); }); }); }