smartholdem-cli
Version:
Command Line Interface to SmartHoldem blockchain
1,177 lines (1,110 loc) • 38.9 kB
JavaScript
const sthjs = require("sthjs");
const crypto = require("crypto");
const figlet = require("figlet");
const colors = require("colors");
const request = require("request");
const requestPromise = require("request-promise-native");
///const asciichart = require ('asciichart');
///const chart = require ('chart');
///const cliSpinners = require('cli-spinners');
const Table = require('ascii-table');
const ora = require('ora');
const cowsay = require('cowsay');
const async = require('async');
const vorpal = require('vorpal')();
///const cluster = require('cluster');
const child_process = require('child_process');
const Path = require('path');
var ledgerSupported = true;
try {
var ledger = require('ledgerco');
var LedgerSth = require('./src/LedgerSth.js');
var ledgerWorker = child_process.fork(Path.resolve(__dirname, './ledger-worker'));
} catch (USBError) {
ledgerSupported = false;
vorpal.log(colors.yellow("Warning: SmartHoldem-Client is running on a server or virtual machine: No Ledger support available."));
}
var blessed = require('blessed');
var contrib = require('blessed-contrib');
var connected = false;
var server;
var network;
var sthticker = {};
const currencies = ["USD","AUD", "BRL", "CAD", "CHF", "CNY", "EUR", "GBP", "HKD", "IDR", "INR", "JPY", "KRW", "MXN", "RUB"];
var ledgerAccounts = [];
var ledgerBridge = null;
var ledgerComm = null;
const networks = {
devnet: {
nethash: "3a6d2bec6798dedea99a1e6c64120a3876781b85e46bb75908aba07ffda61360",
peers: [
"88.198.67.196:6101",
"213.239.207.170:6101",
"80.211.38.83:6101"
],
ledgerpath: "44'/1'/"
},
mainnet: {
nethash: "fc46bfaf9379121dd6b09f5014595c7b7bd52a0a6d57c5aff790b42a73c76da7",
peers: [
"95.216.142.160:6100",
"95.216.136.246:6100",
"138.201.94.238:6100",
"213.239.207.170:6100",
"88.198.67.196:6100",
"95.183.9.207:6100",
"95.183.9.191:6100",
"156.67.218.218:6100",
"31.220.52.1:6100",
"89.36.212.238:6100",
"94.177.255.148:6100",
"80.211.31.231:6100",
"80.211.31.162:6100",
"80.211.31.241:6100",
"80.211.31.222:6100",
"80.211.31.90:6100",
"80.211.31.13:6100",
"80.211.237.199:6100",
"94.177.213.55:6100",
"80.211.32.86:6100",
"188.213.168.227:6100",
"194.182.72.198:6100",
"194.182.68.74:6100",
"194.182.74.66:6100",
"194.182.74.87:6100",
"194.182.74.137:6100",
"194.182.74.218:6100",
"194.182.74.133:6100",
"194.182.74.133:6100",
"136.243.65.148:6100"
],
ledgerpath: "44'/255'/"
}
};
function isConnected() {
return server && connected;
}
function getNetworkFromNethash(nethash){
for(var n in networks){
if(networks[n].nethash == nethash){
return n;
}
}
return "unknown";
}
function findEnabledPeers(cb){
var peers=[];
getFromNode('http://'+server+'/peer/list', function(err, response, body){
if(err){
vorpal.log(colors.red("Can't get peers from network: " + err));
return cb(peers);
}
else {
var respeers = JSON.parse(body).peers.map(function(peer){
return peer.ip+":"+peer.port;
}).filter(function(peer){
return peer.status=="OK";
});
async.each(respeers, function(peer, cb){
getFromNode('http://'+peer+'/api/blocks/getHeight', function(err, response, body){
if(body != "Forbidden"){
peers.push(peer);
}
cb();
});
},function(err){
return cb(peers);
});
}
});
}
function postTransaction(container, transaction, cb){
var performPost = function() {
request({
url: 'http://'+server+'/peer/transactions',
headers: {
nethash: network.nethash,
version: '1.0.0',
port:1
},
method: 'POST',
json: true,
body: {transactions:[transaction]}
}, cb);
};
let senderAddress = sthjs.crypto.getAddress(transaction.senderPublicKey);
getFromNode('http://' + server + '/api/accounts?address=' + senderAddress, function(err, response, body){
if(!err && body) {
try {
body = JSON.parse(body);
if ( !body.hasOwnProperty('success') || body.success === false) {
// The account does not yet exist on the connected node.
throw "Failed: " + body.error;
}
if (body.hasOwnProperty('account') && body.account.secondSignature) {
container.prompt({
type: 'password',
name: 'passphrase',
message: 'Second passphrase: ',
}, function(result) {
if (result.passphrase) {
var secondKeys = sthjs.crypto.getKeys(result.passphrase);
sthjs.crypto.secondSign(transaction, secondKeys);
transaction.id = sthjs.crypto.getId(transaction);
} else {
vorpal.log('No second passphrase given. Trying without.');
}
});
}
} catch (error) {
vorpal.log(colors.red(error));
}
} // if(body)
performPost();
});
}
function getFromNode(url, cb){
let nethash=network?network.nethash:"";
request(
{
url: url,
headers: {
nethash: nethash,
version: '1.0.0',
port:1
},
timeout: 5000
},
cb
);
}
function getSTHTicker(currency){
request({url: "https://api.coinmarketcap.com/v1/ticker/sth/?convert="+currency}, function(err, response, body){
sthticker[currency]=JSON.parse(body)[0];
});
}
function getAccount(container, seriesCb) {
var getPassPhrase = function() {
container.prompt({
type: 'password',
name: 'passphrase',
message: 'passphrase: ',
}, function(result){
if (result.passphrase) {
return seriesCb(null, {
passphrase: result.passphrase,
});
} else{
return seriesCb("Aborted.");
}
});
};
if (ledgerSupported && ledgerAccounts.length) {
var message = 'We have found the following Ledgers: \n';
ledgerAccounts.forEach(function(ledger, index) {
var balance = network.config.symbol + (ledger.data.accountData.balance / 100000000);
message += (index + 1) + ') ' + ledger.data.address + ' (' + balance + ')' + '\n';
});
message += 'N) passphrase\n\n';
message += 'Please choose an option: ';
container.prompt({
type: 'input',
name: 'account',
message: message,
}, function(result){
if (result.account.toUpperCase() === 'N') {
getPassPhrase();
} else if (ledgerAccounts[result.account - 1]) {
var ledger = ledgerAccounts[result.account - 1];
return seriesCb(null, {
address: ledger.data.address,
publicKey: ledger.data.publicKey,
path: ledger.path,
});
} else {
return seriesCb("Failed to get Accounts");
}
});
} else {
getPassPhrase();
}
}
function resetLedger() {
ledgerAccounts = [];
ledgerBridge = null;
if (ledgerComm !== null) {
ledgerComm.close_async();
ledgerComm = null;
}
}
async function populateLedgerAccounts() {
if (!ledgerSupported || !ledgerBridge) {
return;
}
ledgerAccounts = [];
///var accounts = [];
var account_index = 0;
var path = network.hasOwnProperty('ledgerpath') ? network.ledgerpath : "44'/255'/";
var empty = false;
while (!empty) {
var localpath = path + account_index + "'/0/0";
var result = null;
try {
await ledgerBridge.getAddress_async(localpath).then(
(response) => { result = response }
).fail(
(response) => { result = response }
);
if (result.publicKey) {
result.address = sthjs.crypto.getAddress(result.publicKey);
var accountData = null;
await requestPromise({
uri: 'http://' + server + '/api/accounts?address=' + result.address,
headers: {
nethash: network.nethash,
version: '1.0.0',
port: 1
},
timeout: 5000,
json: true,
}).then(
(body) => { accountData = body }
);
if (!accountData || accountData.success === false) {
// Add an empty available account when 0 transactions have been made.
empty = true;
result.accountData = {
address: result.address,
unconfirmedBalance: "0",
balance: "0",
publicKey: result.publicKey,
unconfirmedSignature: 0,
secondSignature: 0,
secondPublicKey: null,
multisignatures: [],
u_multisignatures: []
};
} else {
result.accountData = accountData.account;
}
}
} catch (e) {
console.log('no request:', e);
break;
}
if (result && result.address) {
ledgerAccounts.push({
data: result,
path: localpath
});
account_index = account_index + 1;
} else {
empty = true;
}
}
if (ledgerAccounts.length) {
vorpal.log('Ledger App Connected');
}
}
async function ledgerSignTransaction(seriesCb, transaction, account, callback) {
if (!ledgerSupported || !account.publicKey || !account.path) {
return callback(transaction);
}
transaction.senderId = account.address;
if (transaction.type === 3) {
transaction.recipientId = account.address;
}
transaction.senderPublicKey = account.publicKey;
delete transaction.signature;
var transactionHex = sthjs.crypto.getBytes(transaction, true, true).toString("hex");
var result = null;
console.log('Please sign the transaction on your Ledger');
await ledgerBridge.signTransaction_async(account.path, transactionHex).then(
(response) => { result = response }
).fail(
(response) => { result = response }
);
if (result.signature && result.signature === '00000100') {
return seriesCb('We could not sign the transaction. Close everything using the Ledger and try again.');
} else if (result.signature) {
transaction.signature = result.signature;
transaction.id = sthjs.crypto.getId(transaction);
} else {
transaction = null;
}
callback(transaction);
}
if (ledgerSupported) {
ledgerWorker.on('message', function (message) {
if (message.connected && network && (!ledgerComm || !ledgerAccounts.length)) {
ledger.comm_node.create_async().then((comm) => {
ledgerComm = comm;
ledgerBridge = new LedgerSth(ledgerComm);
populateLedgerAccounts();
}).fail((error) => {
//vorpal.log(colors.red('ledger error: ' +error));
});
} else if (!message.connected && ledgerComm) {
vorpal.log('Ledger App Disconnected');
resetLedger();
}
});
}
vorpal
.command('connect <network>', 'Connect to network. Network is devnet or mainnet')
.action(function(args, callback) {
// reset an existing connection first
if(server) {
server=null;
network=null;
resetLedger();
}
var self = this;
network = networks[args.network];
if(!network){
self.log("Network not found");
return callback();
}
connect2network(network,function(){
getFromNode('http://'+server+'/peer/status', function(err, response, body){
self.log("Node: " + server + ", height: " + JSON.parse(body).height);
self.delimiter('sth '+args.network+'>');
sthjs.crypto.setNetworkVersion(network.config.version);
connected = true;
callback();
});
});
});
function connect2network(n, callback){
server = n.peers[Math.floor(Math.random()*1000)%n.peers.length];
findEnabledPeers(function(peers){
if(peers.length>0){
server=peers[0];
n.peers=peers;
}
});
getFromNode('http://'+server+'/api/loader/autoconfigure', function(err, response, body){
if(!body) connect2network(n, callback);
else{
n.config = JSON.parse(body).network;
vorpal.log(n.config);
callback();
}
});
}
vorpal
.command('connect node <url>', 'Connect to a server. For example "connect node 88.198.67.196:6100"')
.action(function(args, callback) {
// reset an existing connection first
if(server) {
server=null;
network=null;
resetLedger();
}
var self = this;
server=args.url;
getFromNode('http://'+server+'/api/blocks/getNethash', function(err, response, body){
if(err){
self.log(colors.red("Public API unreacheable on this server "+server+" - "+err));
server=null;
self.delimiter('sth>');
return callback();
}
try {
var nethash = JSON.parse(body).nethash;
}
catch (error){
self.log(colors.red("API is not returning expected result:"));
self.log(body);
server=null;
self.delimiter('sth>');
return callback();
}
var networkname = getNetworkFromNethash(nethash);
network = networks[networkname];
if(!network){
network = {
nethash: nethash,
peers:[server]
};
networks[nethash]=network;
}
getFromNode('http://'+server+'/api/loader/autoconfigure', function(err, response, body){
network.config = JSON.parse(body).network;
console.log(network.config);
});
self.log("Connected to network " + nethash + colors.green(" ("+networkname+")"));
self.delimiter('sth '+server+'>');
getFromNode('http://'+server+'/peer/status', function(err, response, body){
self.log("Node height ", JSON.parse(body).height);
});
connected = true;
callback();
});
});
vorpal
.command('disconnect', 'Disconnect from server or network')
.action(function(args, callback) {
var self = this;
self.log("Disconnected from "+server);
self.delimiter('sth>');
server=null;
network=null;
connected = false;
resetLedger();
callback();
});
vorpal
.command('network stats', 'Get stats from network')
.action(function(args, callback) {
var self = this;
if(!isConnected()){
self.log("Please connect to node or network before");
return callback();
}
getFromNode('http://'+server+'/peer/list', function(err, response, body){
if(err){
self.log(colors.red("Can't get peers from network: " + err));
return callback();
}
else {
var peers = JSON.parse(body).peers.map(function(peer){
return peer.ip+":"+peer.port;
});
self.log("Checking "+peers.length+" peers");
var spinner = ora({text:"0%",spinner:"shsth"}).start();
var heights={};
var delays={};
var count=0;
async.each(peers, function(peer, cb){
var delay=new Date().getTime();
getFromNode('http://'+peer+'/peer/status', function(err, response, hbody){
delay=new Date().getTime()-delay;
if(delays[10*Math.floor(delay/10)]){
delays[10*Math.floor(delay/10)]++;
}
else{
delays[10*Math.floor(delay/10)]=1;
}
count++;
spinner.text=Math.floor(100*count/peers.length)+"%";
if(err){
return cb();
}
else{
var height=JSON.parse(hbody).height;
if(!height){
return cb();
}
if(heights[height]){
heights[height]++;
}
else{
heights[height]=1;
}
return cb();
}
return cb();
});
},function(err){
spinner.stop();
var screen = blessed.screen();
var grid = new contrib.grid({rows: 12, cols: 12, screen: screen});
var line = grid.set(0, 0, 6, 6, contrib.line,
{ style:
{ line: "yellow"
, text: "green"
, baseline: "black"}
, xLabelPadding: 3
, xPadding: 5
, label: 'Delays'});
var data = {
x: Object.keys(delays).map(function(d){return d+"ms"}),
y: Object.values(delays)
};
screen.append(line); //must append before setting data
line.setData([data]);
var bar = grid.set(6, 0, 6, 12, contrib.bar, { label: 'Network Height', barWidth: 4, barSpacing: 6, xOffset: 0, maxHeight: 9});
screen.append(bar); //must append before setting data
bar.setData({titles: Object.keys(heights), data: Object.values(heights)});
screen.onceKey(['escape'], function(ch, key) {
screen.destroy();
});
screen.render();
});
}
});
});
vorpal
.command('account status <address>', 'Get account status')
.action(function(args, callback) {
var self = this;
if(!isConnected()){
self.log("please connect to node or network before");
return callback();
}
var address=args.address;
getFromNode('http://'+server+'/api/accounts?address='+address, function(err, response, body){
var a = JSON.parse(body).account;
if(!a){
self.log("Unknown on the blockchain");
return callback();
}
for(var i in a){
if(!a[i] || a[i].length === 0) delete a[i];
}
delete a.address;
var table = new Table();
table.setHeading(Object.keys(a));
var rowItems = [];
Object.keys(a).map(function (key) {
var value = a[key];
if (['unconfirmedBalance', 'balance'].includes(key)) {
value = value / 100000000;
}
rowItems.push(value);
});
table.addRow(rowItems);
self.log(table.toString());
getFromNode('http://'+server+'/api/delegates/get/?publicKey='+a.publicKey, function(err, response, body){
body = JSON.parse(body);
if(body.success){
var delegate=body.delegate;
delete delegate.address;
delete delegate.publicKey;
table = new Table("Delegate");
table.setHeading(Object.keys(delegate));
table.addRow(Object.values(delegate));
self.log(table.toString());
}
callback();
});
});
});
vorpal
.command('account vote <name>', 'Vote for delegate <name>. Remove previous vote if needed')
.action(function(args, callback) {
var self = this;
if(!isConnected()){
self.log("please connect to node or network before");
return callback();
}
async.waterfall([
function(seriesCb) {
getAccount(self, seriesCb);
},
function(account, seriesCb) {
var delegateName = args.name;
var address = null;
var publicKey = null;
var passphrase = '';
if (account.passphrase) {
passphrase = account.passphrase;
var keys = sthjs.crypto.getKeys(passphrase);
publicKey = keys.publicKey;
address = sthjs.crypto.getAddress(publicKey);
} else if (account.publicKey) {
address = account.address;
publicKey = account.publicKey;
} else {
return seriesCb('No public key for account');
}
getFromNode('http://'+server+'/api/accounts/delegates/?address='+address, function(err, response, body) {
body = JSON.parse(body);
if (!body.success) {
return seriesCb("Failed getting current vote: " + body.error);
}
var currentVote = null;
if (body.delegates.length) {
currentVote = body.delegates.pop();
if (currentVote.username === delegateName) {
return seriesCb('You have already voted for ' + delegateName);
}
}
getFromNode('http://'+server+'/api/delegates/get/?username='+delegateName, function(err, response, body){
body = JSON.parse(body);
if (!body.success) {
return seriesCb("Failed: " + body.error);
}
var newDelegate = body.delegate;
var confirmMessage = 'Vote for ' + delegateName + ' now';
if (currentVote) {
confirmMessage = 'Vote for ' + delegateName + ' and unvote ' + currentVote.username + ' now';
}
self.prompt({
type: 'confirm',
name: 'continue',
default: false,
message: confirmMessage,
}, function(result){
if (result.continue) {
if (currentVote) {
try {
var unvoteTransaction = sthjs.vote.createVote(passphrase, ['-'+currentVote.publicKey]);
} catch (error) {
return seriesCb('Failed: ' + error);
}
ledgerSignTransaction(seriesCb, unvoteTransaction, account, function(unvoteTransaction) {
if (!unvoteTransaction) {
return seriesCb('Failed to sign unvote transaction with ledger');
}
postTransaction(self, unvoteTransaction, function(err, response, body) {
if (err) {
return seriesCb('Failed to unvote previous delegate: ' + err);
} else if (!body.success){
return seriesCb("Failed to send unvote transaction: " + body.error);
}
var transactionId = body.transactionIds.pop();
console.log('Waiting for unvote transaction (' + transactionId + ') to confirm.');
var checkTransactionTimerId = setInterval(function() {
getFromNode('http://' + server + '/api/transactions/get?id=' + transactionId, function(err, response, body) {
body = JSON.parse(body);
if (!body.success && body.error !== 'Transaction not found') {
clearInterval(checkTransactionTimerId);
return seriesCb('Failed to fetch unconfirmed transaction: ' + body.error);
} else if (body.transaction) {
clearInterval(checkTransactionTimerId);
try {
var transaction = sthjs.vote.createVote(passphrase, ['+'+newDelegate.publicKey]);
} catch (error) {
return seriesCb('Failed: ' + error);
}
ledgerSignTransaction(seriesCb, transaction, account, function(transaction) {
if (!transaction) {
return seriesCb('Failed to sign vote transaction with ledger');
}
return seriesCb(null, transaction);
});
}
});
}, 2000);
});
});
} else {
try {
var transaction = sthjs.vote.createVote(passphrase, ['+'+newDelegate.publicKey]);
} catch (error) {
return seriesCb('Failed: ' + error);
}
ledgerSignTransaction(seriesCb, transaction, account, function(transaction) {
if (!transaction) {
return seriesCb('Failed to sign transaction with ledger');
}
return seriesCb(null, transaction);
});
}
} else {
return seriesCb("Aborted.");
}
});
});
});
},
function(transaction, seriesCb){
postTransaction(self, transaction, function(err, response, body){
if(err){
seriesCb("Failed to send transaction: " + err);
}
else if(body.success){
seriesCb(null, transaction);
}
else {
seriesCb("Failed to send transaction: " + body.error);
}
});
}
], function(err, transaction){
if(err){
self.log(colors.red(err));
}
else{
self.log(colors.green("Transaction sent successfully with id "+transaction.id));
}
return callback();
});
});
vorpal
.command('account unvote', 'Remove previous vote')
.action(function(args, callback) {
var self = this;
if(!isConnected()){
self.log("please connect to node or network before");
return callback();
}
async.waterfall([
function(seriesCb){
getAccount(self, seriesCb);
},
function(account, seriesCb){
var address = null;
var publicKey = null;
var passphrase = '';
if (account.passphrase) {
passphrase = account.passphrase;
var keys = sthjs.crypto.getKeys(passphrase);
publicKey = keys.publicKey;
address = sthjs.crypto.getAddress(publicKey);
} else if (account.publicKey) {
address = account.address;
publicKey = account.publicKey;
} else {
return seriesCb('No public key for account');
}
getFromNode('http://'+server+'/api/accounts/delegates/?address='+address, function(err, response, body) {
body = JSON.parse(body);
if (!body.success) {
return seriesCb("Failed: " + body.error);
}
if (!body.delegates.length) {
return seriesCb("You currently haven't voted for anyone.");
}
var lastDelegate = body.delegates.pop();
var delegates = ['-' + lastDelegate.publicKey];
self.prompt({
type: 'confirm',
name: 'continue',
default: false,
message: 'Removing last vote for ' + lastDelegate.username,
}, function(result){
if (result.continue) {
try {
var transaction = sthjs.vote.createVote(passphrase, delegates);
} catch (error) {
return seriesCb('Failed: ' + error);
}
ledgerSignTransaction(seriesCb, transaction, account, function(transaction) {
if (!transaction) {
return seriesCb('Failed to sign transaction with ledger');
}
return seriesCb(null, transaction);
});
} else {
return seriesCb("Aborted.");
}
});
});
},
function(transaction, seriesCb){
postTransaction(self, transaction, function(err, response, body){
if(err){
seriesCb("Failed to send transaction: " + err);
}
else if(body.success){
seriesCb(null, transaction);
}
else {
seriesCb("Failed to send transaction: " + body.error);
}
});
}
], function(err, transaction){
if(err){
self.log(colors.red(err));
}
else{
self.log(colors.green("Transaction sent successfully with id "+transaction.id));
}
return callback();
});
});
vorpal
.command('account send <amount> <address>', 'Send <amount> STH to <address>. <amount> format examples: 10, 10.25, 100')
.action(function(args, callback) {
var self = this;
if(!isConnected()){
self.log("please connect to node or network before");
return callback();
}
var currency;
var found = false;
if(typeof args.amount != "number")
{
for(var i in currencies)
{
if(args.amount.startsWith(currencies[i]))
{
currency=currencies[i];
args.amount = Number(args.amount.replace(currency,""));
getSTHTicker(currency);
found = true;
break;
}
}
if(!found)
{
self.log("Invalid Currency Format");
return callback();
}
}
async.waterfall([
function(seriesCb){
getAccount(self, seriesCb);
},
function(account, seriesCb){
var address = null;
var publicKey = null;
var passphrase = '';
if (account.passphrase) {
passphrase = account.passphrase;
var keys = sthjs.crypto.getKeys(passphrase);
publicKey = keys.publicKey;
address = sthjs.crypto.getAddress(publicKey);
} else if (account.publicKey) {
address = account.address;
publicKey = account.publicKey;
} else {
return seriesCb('No public key for account');
}
var sthamount = args.amount;
var sthAmountString = args.amount;
if(currency){
if(!sthticker[currency]){
return seriesCb("Can't get price from market. Aborted.");
}
sthamount = parseInt(args.amount * 100000000 / Number(sthticker[currency]["price_"+currency.toLowerCase()]),10);
sthAmountString = sthamount/100000000;
} else {
sthamount = parseInt(args.amount * 100000000, 10);
}
self.prompt({
type: 'confirm',
name: 'continue',
default: false,
message: 'Sending ' + sthAmountString + ' ' + network.config.token+' '+(currency?'('+currency+args.amount+') ':'')+'to '+args.address+' now',
}, function(result){
if (result.continue) {
try {
var transaction = sthjs.transaction.createTransaction(args.address, sthamount, null, passphrase);
} catch (error) {
return seriesCb('Failed: ' + error);
}
ledgerSignTransaction(seriesCb, transaction, account, function(transaction) {
if (!transaction) {
return seriesCb('Failed to sign transaction with ledger');
}
return seriesCb(null, transaction);
});
}
else {
return seriesCb("Aborted.");
}
});
},
function(transaction, seriesCb){
postTransaction(self, transaction, function(err, response, body){
if(err){
seriesCb("Failed to send transaction: " + err);
}
else if(body.success){
seriesCb(null, transaction);
}
else {
seriesCb("Failed to send transaction: " + body.error);
}
});
}
], function(err, transaction){
if(err){
self.log(colors.red(err));
}
else{
self.log(colors.green("Transaction sent successfully with id "+transaction.id));
}
return callback();
});
});
vorpal
.command('account delegate <username>', 'Register new delegate with <username> ')
.action(function(args, callback) {
var self = this;
if(!isConnected()){
self.log("please connect to node or network before");
return callback();
}
async.waterfall([
function(seriesCb) {
getAccount(self, seriesCb);
},
function(account, seriesCb) {
var address = null;
var publicKey = null;
var passphrase = '';
if (account.passphrase) {
passphrase = account.passphrase;
var keys = sthjs.crypto.getKeys(passphrase);
publicKey = keys.publicKey;
address = sthjs.crypto.getAddress(publicKey);
} else if (account.publicKey) {
address = account.address;
publicKey = account.publicKey;
} else {
return seriesCb('No public key for account');
}
try {
var transaction = sthjs.delegate.createDelegate(passphrase, args.username);
} catch (error) {
return seriesCb('Failed: ' + error);
}
ledgerSignTransaction(seriesCb, transaction, account, function(transaction) {
if (!transaction) {
return seriesCb('Failed to sign transaction with ledger');
}
return seriesCb(null, transaction);
});
},
function(transaction, seriesCb) {
postTransaction(self, transaction, function(err, response, body){
if(err){
seriesCb("Failed to send transaction: " + err);
}
else if(body.success){
seriesCb(null, transaction);
}
else {
seriesCb("Failed to send transaction: " + body.error);
}
});
}
], function(err, transaction){
if(err){
self.log(colors.red(err));
}
else{
self.log(colors.green("Transaction sent successfully with id "+transaction.id));
}
return callback();
});
});
vorpal
.command('account create', 'Generate a new random cold account')
.action(function(args, callback) {
var self = this;
if(!isConnected()){
self.log("please connect to node or network before, in order to retrieve necessery information about address prefixing");
return callback();
}
var passphrase = require("bip39").generateMnemonic();
self.log("Seed - private:",passphrase);
self.log("WIF - private:",sthjs.crypto.getKeys(passphrase).toWIF());
self.log("Address - public :",sthjs.crypto.getAddress(sthjs.crypto.getKeys(passphrase).publicKey));
callback();
});
vorpal
.command('account vanity <string>', 'Generate an address containing lowercased <string> (WARNING you could wait for long)')
.action(function(args, callback) {
var self=this;
if(!isConnected()){
self.log("please connect to node or network before, in order to retrieve necessery information about address prefixing");
return callback();
}
var count=0;
var numCPUs = require('os').cpus().length;
var cps=[];
self.log("Spawning process to "+numCPUs+" cpus");
var spinner = ora({text:"passphrases tested: 0",spinner:"shsth"}).start();
for (var i = 0; i < numCPUs; i++) {
var cp=child_process.fork(__dirname+"/vanity.js");
cps.push(cp);
cp.on('message', function(message){
if(message.passphrase){
spinner.stop();
var passphrase = message.passphrase;
self.log("Found after",count,"passphrases tested");
self.log("Seed - private:",passphrase);
self.log("WIF - private:",sthjs.crypto.getKeys(passphrase).toWIF());
self.log("Address - public :",sthjs.crypto.getAddress(sthjs.crypto.getKeys(passphrase).publicKey));
for(var killid in cps){
cps[killid].kill();
}
callback();
}
if(message.count){
count += message.count;
spinner.text="passphrases tested: "+count;
}
});
cp.send({string:args.string.toLowerCase(), version:network.config.version});
}
});
vorpal
.command('message sign <message>', 'Sign a message')
.action(function(args, callback) {
var self = this;
return this.prompt({
type: 'password',
name: 'passphrase',
message: 'passphrase: ',
}, function(result){
if (result.passphrase) {
var hash = crypto.createHash('sha256');
hash = hash.update(new Buffer(args.message,"utf-8")).digest();
self.log("public key: ",sthjs.crypto.getKeys(result.passphrase).publicKey);
self.log("address : ",sthjs.crypto.getAddress(sthjs.crypto.getKeys(result.passphrase).publicKey));
self.log("signature : ",sthjs.crypto.getKeys(result.passphrase).sign(hash).toDER().toString("hex"));
} else {
self.log('Aborted.');
callback();
}
});
});
vorpal
.command('message verify <message> <publickey>', 'Verify the <message> signed by the owner of <publickey> (you will be prompted to provide the signature)')
.action(function(args, callback) {
var self = this;
return this.prompt({
type: 'input',
name: 'signature',
message: 'signature: ',
}, function(result){
if (result.signature) {
try{
var hash = crypto.createHash('sha256');
hash = hash.update(new Buffer(args.message,"utf-8")).digest();
var signature = new Buffer(result.signature, "hex");
var publickey= new Buffer(args.publickey, "hex");
var ecpair = sthjs.ECPair.fromPublicKeyBuffer(publickey);
var ecsignature = sthjs.ECSignature.fromDER(signature);
var res = ecpair.verify(hash, ecsignature);
self.log(res);
}
catch(error){
self.log("Failed: ", error);
}
callback();
} else {
self.log('Aborted.');
callback();
}
});
});
var shsthspinner;
/*
vorpal
.command("shSTH", "No you don't want to use this command")
.action(function(args, callback) {
var self = this;
self.log(colors.red(figlet.textSync("shSTH")));
shsthspinner = ora({text:"Watch out, the shSTH attack!",spinner:"shsth"}).start();
callback();
});
*/
vorpal
.command("spSTHaaaaa!")
.hidden()
.action(function(args, callback) {
var time = 0;
var self=this;
shsthspinner && shsthspinner.stop();
["tux","meow","bunny","cower","dragon-and-cow"].forEach(function(spark){
setTimeout(function(){
self.log(cowsay.say({text:"SPAAAASTHKKAAAAAAA!", f:spark}));
}, time++*1000);
});
callback();
});
vorpal.history('smartholdem-client');
vorpal.log(colors.cyan(figlet.textSync("SmartHoldem Client","Slant")));
vorpal
.delimiter('smartholdem>')
.show();