Browse Source

Merge pull request #206 from SuperNETorg/electrum

Electrum
pkg_automation_electrum
pbca26 7 years ago
committed by GitHub
parent
commit
cb1cdef777
  1. 5
      gui/startup/app-closing.html
  2. 6
      main.js
  3. 158
      routes/electrumjs/electrumServers.js
  4. 197
      routes/fetchparams.js
  5. 5552
      routes/shepherd.js
  6. 103
      routes/shepherd/addCoinShortcuts.js
  7. 78
      routes/shepherd/appInfo.js
  8. 34
      routes/shepherd/auth.js
  9. 239
      routes/shepherd/binsTestUtil.js
  10. 70
      routes/shepherd/binsUtils.js
  11. 31
      routes/shepherd/coins.js
  12. 71
      routes/shepherd/coinsList.js
  13. 55
      routes/shepherd/confMaxconnections.js
  14. 152
      routes/shepherd/config.js
  15. 868
      routes/shepherd/daemonControl.js
  16. 294
      routes/shepherd/dashboardUpdate.js
  17. 92
      routes/shepherd/debugLog.js
  18. 44
      routes/shepherd/dex/coind.js
  19. 165
      routes/shepherd/downloadBins.js
  20. 154
      routes/shepherd/downloadPatch.js
  21. 49
      routes/shepherd/downloadUtil.js
  22. 127
      routes/shepherd/downloadZcparams.js
  23. 38
      routes/shepherd/electrum/auth.js
  24. 145
      routes/shepherd/electrum/balance.js
  25. 59
      routes/shepherd/electrum/block.js
  26. 69
      routes/shepherd/electrum/coins.js
  27. 411
      routes/shepherd/electrum/createtx.js
  28. 26
      routes/shepherd/electrum/estimate.js
  29. 37
      routes/shepherd/electrum/interest.js
  30. 128
      routes/shepherd/electrum/keys.js
  31. 194
      routes/shepherd/electrum/listunspent.js
  32. 145
      routes/shepherd/electrum/merkle.js
  33. 141
      routes/shepherd/electrum/network.js
  34. 397
      routes/shepherd/electrum/transactions.js
  35. 52
      routes/shepherd/init.js
  36. 150
      routes/shepherd/kickstart.js
  37. 141
      routes/shepherd/log.js
  38. 74
      routes/shepherd/paths.js
  39. 146
      routes/shepherd/pin.js
  40. 82
      routes/shepherd/quitDaemon.js
  41. 246
      routes/shepherd/rpc.js

5
gui/startup/app-closing.html

@ -22,7 +22,10 @@
alt="Agama Wallet"
width="80"
height="100" />
<div id="agamaModeStatus">App is closing. Please wait...</div>
<div id="agamaModeStatus">
App is closing. Please wait...<br/><br/>
<small>This may take a while depending on your system resources and current state of daemon applications.</small>
</div>
</div>
</div>
</body>

6
main.js

@ -30,6 +30,9 @@ if (osPlatform === 'linux') {
// GUI APP settings and starting gui on address http://120.0.0.1:17777
var shepherd = require('./routes/shepherd');
var guiapp = express();
shepherd.createAgamaDirs();
var appConfig = shepherd.loadLocalConfig(); // load app config
const nativeCoindList = shepherd.scanNativeCoindBins();
@ -52,7 +55,6 @@ const appBasicInfo = {
app.setName(appBasicInfo.name);
app.setVersion(appBasicInfo.version);
shepherd.binFixRights();
shepherd.createAgamaDirs();
const appSessionHash = md5(Date.now().toString());
@ -281,7 +283,7 @@ function createAppCloseWindow() {
// initialise window
appCloseWindow = new BrowserWindow({ // dirty hack to prevent main window flash on quit
width: 500,
height: 355,
height: 320,
frame: false,
icon: agamaIcon,
show: false,

158
routes/electrumjs/electrumServers.js

@ -0,0 +1,158 @@
let electrumServers = {
/*zcash: {
address: '173.212.225.176',
port: 50032,
proto: 'tcp',
},*/
revs: { // !estimatefee
address: '173.212.225.176',
port: 50050,
proto: 'tcp',
txfee: 10000,
abbr: 'REVS',
serverList: [
'173.212.225.176:50050',
'136.243.45.140:50050'
],
},
mnz: { // !estimatefee
address: '173.212.225.176',
port: 50053,
proto: 'tcp',
txfee: 10000,
abbr: 'MNZ',
serverList: [
'173.212.225.176:50053',
'136.243.45.140:50053'
],
},
wlc: { // !estimatefee
address: '173.212.225.176',
port: 50052,
proto: 'tcp',
txfee: 10000,
abbr: 'WLC',
serverList: [
'173.212.225.176:50052',
'136.243.45.140:50052'
],
},
jumblr: { // !estimatefee
address: '173.212.225.176',
port: 50051,
proto: 'tcp',
txfee: 10000,
abbr: 'JUMBLR',
serverList: [
'173.212.225.176:50051',
'136.243.45.140:50051'
],
},
komodo: { // !estimatefee
address: '173.212.225.176',
port: 50011,
proto: 'tcp',
txfee: 10000,
abbr: 'KMD',
serverList: [
'173.212.225.176:50011',
'136.243.45.140:50011'
],
},
dogecoin: { // !estimatefee
address: '173.212.225.176',
port: 50015,
proto: 'tcp',
txfee: 100000000,
abbr: 'DOGE',
},
viacoin: { // !estimatefee
address: 'vialectrum.bitops.me',
port: 50002,
proto: 'ssl',
txfee: 100000,
abbr: 'VIA',
},
vertcoin: {
address: '173.212.225.176',
port: 50088,
proto: 'tcp',
txfee: 100000,
abbr: 'VTC',
},
namecoin: {
address: '173.212.225.176',
port: 50036,
proto: 'tcp',
txfee: 100000,
abbr: 'NMC',
},
monacoin: { // !estimatefee
address: '173.212.225.176',
port: 50002,
proto: 'tcp',
txfee: 100000,
abbr: 'MONA',
},
litecoin: {
address: '173.212.225.176',
port: 50012,
proto: 'tcp',
txfee: 10000,
abbr: 'LTC',
},
faircoin: {
address: '173.212.225.176',
port: 50005,
proto: 'tcp',
txfee: 1000000,
abbr: 'FAIR',
},
digibyte: {
address: '173.212.225.176',
port: 50022,
proto: 'tcp',
txfee: 100000,
abbr: 'DGB',
},
dash: {
address: '173.212.225.176',
port: 50098,
proto: 'tcp',
txfee: 10000,
abbr: 'DASH',
},
crown: {
address: '173.212.225.176',
port: 50041,
proto: 'tcp',
txfee: 10000,
abbr: 'CRW',
},
bitcoin: {
address: '173.212.225.176',
port: 50001,
proto: 'tcp',
abbr: 'BTC',
},
argentum: { // !estimatefee
address: '173.212.225.176',
port: 50081,
proto: 'tcp',
txfee: 50000,
abbr: 'ARG',
},
chips: { // !estimatefee
address: '173.212.225.176',
port: 50076,
proto: 'tcp',
txfee: 10000,
abbr: 'CHIPS',
serverList: [
'173.212.225.176:50076',
'136.243.45.140:50076'
],
},
};
module.exports = electrumServers;

197
routes/fetchparams.js

@ -1,197 +0,0 @@
var app = require('http').createServer(handler),
io = require('socket.io')(app),
fs = require('fs'),
request = require('request'),
progress = require('request-progress');
const path = require('path'),
url = require('url'),
os = require('os'),
sha256 = require('sha256'),
crypto = require('crypto');
Promise = require('bluebird');
app.listen(3000);
function handler (req, res) {
fs.readFile(__dirname + '/index.html',
function (err, data) {
if (err) {
res.writeHead(500);
return res.end('Error loading index.html');
}
res.writeHead(200);
res.end(data);
});
}
if (os.platform() === 'darwin') {
var PARAMS_DIR = process.env.HOME + '/Library/Application Support/ZcashParams';
}
if (os.platform() === 'linux') {
var PARAMS_DIR = process.env.HOME + '/.zcash-params';
}
var SPROUT_FILES_DATA = [
{
'file': 'sprout-proving.key',
'hash': '8bc20a7f013b2b58970cddd2e7ea028975c88ae7ceb9259a5344a16bc2c0eef7'
}, {
'file': 'sprout-verifying.key',
'hash': '4bd498dae0aacfd8e98dc306338d017d9c08dd0918ead18172bd0aec2fc5df82'
}
];
var SPROUT_DL_URL = 'https://z.cash/downloads/';
SPROUT_FILES_DATA.forEach(function(value, index) {
fs.exists(value.file, function(exists) {
if (exists) {
console.log(value.file + ' already exists at location.');
var tmphas,
fd = fs.createReadStream(value.file),
hash = crypto.createHash('sha256');
hash.setEncoding('hex');
fd.on('end', function() {
hash.end();
console.log('hash is: ');
console.log(hash.read()); // the desired sha1sum
console.log(value.hash);
tmphash = hash.read();
if (hash.read() === value.hash) {
console.log('File SHA256 sum matches.');
} else {
console.log('File SHA256 sum does not match.');
}
});
// read all file and pipe it (write it) to the hash object
fd.pipe(hash);
} else {
var DLFile = function() {
return new Promise(function(resolve, reject) {
console.log('file not there.');
progress(request(SPROUT_DL_URL + value.file), {})
.on('progress', function (state) {
console.log('progress', state);
})
.on('error', function (err) {
console.log(err);
})
.on('end', function () {
// Do something after request finishes
console.log('download finished.');
var result = 'File ==> ' + value.file + ': DOWNLOADED';
})
.pipe(fs.createWriteStream(value.file));
console.log(result);
resolve(result);
})
}
var CheckFileSHA = function() {
return new Promise(function(resolve, reject) {
var fd = fs.createReadStream(value.file),
hash = crypto.createHash('sha256');
hash.setEncoding('hex');
fd.on('end', function() {
hash.end();
console.log('hash is: ');
console.log(hash.read()); // the desired sha1sum
console.log(value.hash);
if (hash.read() === value.hash) {
console.log('File SHA256 sum matches.');
} else {
console.log('File SHA256 sum does not match.');
}
});
// read all file and pipe it (write it) to the hash object
fd.pipe(hash);
var result = 'SHA256 SUM Check: DONE';
console.log(result);
resolve(result);
});
}
DLFile()
.then(function(result) {
return CheckFileSHA();
});
}
});
});
function CheckSHASum(file, hashstr) {
console.log(hashstr);
var shasum;
// the file you want to get the hash
if (shasum === hashstr ) {
return true;
} else {
return false;
}
}
/*var CheckFileExists = function() {
return new Promise(function(resolve, reject) {
if (path.existsSync('foo.txt')) {}
var result = 'Connecting To Pm2: done'
console.log(result)
resolve(result);
})
}
var DLFile = function() {
return new Promise(function(resolve, reject) {
var result = 'Killing Pm2: done'
setTimeout(function() {
console.log(result)
resolve(result);
}, 2000)
})
}
var CheckSHASum = function() {
return new Promise(function(resolve, reject) {
var result = 'Hiding Main Window: done'
console.log(result)
resolve(result);
})
}
var MoveFile = function() {
return new Promise(function(resolve, reject) {
var result = 'Quiting App: done'
console.log(result)
resolve(result);
})
}
ConnectToPm2()
.then(function(result) {
return KillPm2();
})
.then(HideMainWindow)
.then(QuitApp)
*/

5552
routes/shepherd.js

File diff suppressed because it is too large

103
routes/shepherd/addCoinShortcuts.js

@ -0,0 +1,103 @@
module.exports = (shepherd) => {
shepherd.startSPV = (coin) => {
if (coin === 'KMD+REVS+JUMBLR') {
shepherd.addElectrumCoin('KMD');
shepherd.addElectrumCoin('REVS');
shepherd.addElectrumCoin('JUMBLR');
} else {
shepherd.addElectrumCoin(coin);
}
}
shepherd.startKMDNative = (selection, isManual) => {
if (isManual) {
shepherd.kmdMainPassiveMode = true;
}
if (selection === 'KMD') {
const herdData = {
'ac_name': 'komodod',
'ac_options': [
'-daemon=0',
'-addnode=78.47.196.146',
],
};
const options = {
url: `http://127.0.0.1:${shepherd.appConfig.agamaPort}/shepherd/herd`,
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
herd: 'komodod',
options: herdData,
})
};
shepherd.request(options, (error, response, body) => {
if (response &&
response.statusCode &&
response.statusCode === 200) {
//resolve(body);
} else {
//resolve(body);
}
});
} else {
const herdData = [{
'ac_name': 'komodod',
'ac_options': [
'-daemon=0',
'-addnode=78.47.196.146',
]
}, {
'ac_name': 'REVS',
'ac_options': [
'-daemon=0',
'-server',
`-ac_name=REVS`,
'-addnode=78.47.196.146',
'-ac_supply=1300000'
]
}, {
'ac_name': 'JUMBLR',
'ac_options': [
'-daemon=0',
'-server',
`-ac_name=JUMBLR`,
'-addnode=78.47.196.146',
'-ac_supply=999999'
]
}];
for (let i = 0; i < herdData.length; i++) {
setTimeout(() => {
const options = {
url: `http://127.0.0.1:${shepherd.appConfig.agamaPort}/shepherd/herd`,
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
herd: 'komodod',
options: herdData[i],
})
};
shepherd.request(options, (error, response, body) => {
if (response &&
response.statusCode &&
response.statusCode === 200) {
//resolve(body);
} else {
//resolve(body);
}
});
}, 100);
}
}
};
return shepherd;
};

78
routes/shepherd/appInfo.js

@ -0,0 +1,78 @@
const formatBytes = (bytes, decimals) => {
if (bytes === 0) {
return '0 Bytes';
}
const k = 1000;
const dm = (decimals + 1) || 3;
const sizes = [
'Bytes',
'KB',
'MB',
'GB',
'TB',
'PB',
'EB',
'ZB',
'YB'
];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
}
module.exports = (shepherd) => {
shepherd.SystemInfo = () => {
const os_data = {
'totalmem_bytes': shepherd.os.totalmem(),
'totalmem_readable': formatBytes(shepherd.os.totalmem()),
'arch': shepherd.os.arch(),
'cpu': shepherd.os.cpus()[0].model,
'cpu_cores': shepherd.os.cpus().length,
'platform': shepherd.os.platform(),
'os_release': shepherd.os.release(),
'os_type': shepherd.os.type(),
};
return os_data;
}
shepherd.appInfo = () => {
const sysInfo = shepherd.SystemInfo();
const releaseInfo = shepherd.appBasicInfo;
const dirs = {
agamaDir: shepherd.agamaDir,
komodoDir: shepherd.komodoDir,
komododBin: shepherd.komododBin,
configLocation: `${shepherd.agamaDir}/config.json`,
cacheLocation: `${shepherd.agamaDir}/shepherd`,
};
return {
sysInfo,
releaseInfo,
dirs,
appSession: shepherd.appSessionHash,
};
}
/*
* type: GET
*
*/
shepherd.get('/sysinfo', (req, res, next) => {
const obj = shepherd.SystemInfo();
res.send(obj);
});
/*
* type: GET
*
*/
shepherd.get('/appinfo', (req, res, next) => {
const obj = shepherd.appInfo();
res.send(obj);
});
return shepherd;
};

34
routes/shepherd/auth.js

@ -0,0 +1,34 @@
module.exports = (shepherd) => {
/*
* type: GET
*
*/
shepherd.get('/auth/status', (req, res, next) => { // not finished
let successObj;
let _status = false;
if (Object.keys(shepherd.coindInstanceRegistry).length) {
if (Object.keys(shepherd.electrumCoins).length > 1 && shepherd.electrumCoins.auth) {
_status = true;
} else if (Object.keys(shepherd.electrumCoins).length === 1 && !shepherd.electrumCoins.auth) {
_status = true;
}
} else if (Object.keys(shepherd.electrumCoins).length > 1 && shepherd.electrumCoins.auth) {
_status = true;
} else if (Object.keys(shepherd.electrumCoins).length === 1 && !Object.keys(shepherd.coindInstanceRegistry).length) {
_status = true;
}
successObj = {
pubkey: 'nativeonly',
result: 'success',
handle: '',
status: _status ? 'unlocked' : 'locked',
duration: 2507830,
};
res.end(JSON.stringify(successObj));
});
return shepherd;
};

239
routes/shepherd/binsTestUtil.js

@ -0,0 +1,239 @@
module.exports = (shepherd) => {
shepherd.testClearAll = () => {
return new shepherd.Promise((resolve, reject) => {
shepherd.fs.removeSync(`${iguanaTestDir}`);
resolve('done');
});
}
shepherd.testBins = (daemonName) => {
return new shepherd.Promise((resolve, reject) => {
const _bins = {
komodod: shepherd.komododBin,
komodoCli: shepherd.komodocliBin,
};
const _arg = null;
let _pid;
shepherd.log('testBins exec ' + _bins[daemonName]);
if (!shepherd.fs.existsSync(shepherd.agamaTestDir)) {
shepherd.fs.mkdirSync(shepherd.agamaTestDir);
}
try {
shepherd._fs.access(`${shepherd.agamaTestDir}/${daemonName}Test.log`, shepherd.fs.constants.R_OK, (err) => {
if (!err) {
try {
shepherd._fs.unlinkSync(`${shepherd.agamaTestDir}/${daemonName}Test.log`);
} catch (e) {}
} else {
shepherd.log(`path ${shepherd.agamaTestDir}/${daemonName}Test.log doesnt exist`);
}
});
} catch (e) {}
if (daemonName === 'komodod') {
try {
shepherd._fs.access(`${iguanaTestDir}/debug.log`, shepherd.fs.constants.R_OK, (err) => {
if (!err) {
shepherd._fs.unlinkSync(`${iguanaTestDir}/db.log`);
shepherd._fs.unlinkSync(`${iguanaTestDir}/debug.log`);
shepherd._fs.unlinkSync(`${iguanaTestDir}/komodo.conf`);
shepherd._fs.unlinkSync(`${iguanaTestDir}/komodod.pid`);
shepherd._fs.unlinkSync(`${iguanaTestDir}/komodostate`);
shepherd._fs.unlinkSync(`${iguanaTestDir}/realtime`);
shepherd._fs.unlinkSync(`${iguanaTestDir}/wallet.dat`);
shepherd._fs.unlinkSync(`${iguanaTestDir}/.lock`);
shepherd.fs.removeSync(`${iguanaTestDir}/blocks`);
shepherd.fs.removeSync(`${iguanaTestDir}/chainstate`);
shepherd.fs.removeSync(`${iguanaTestDir}/database`);
execKomodod();
} else {
shepherd.log(`test: nothing to remove in ${iguanaTestDir}`);
execKomodod();
}
});
} catch (e) {}
const execKomodod = () => {
let _komododTest = {
port: 'unknown',
start: 'unknown',
getinfo: 'unknown',
errors: {
assertFailed: false,
zcashParams: false,
},
};
const _komodoConf = 'rpcuser=user83f3afba8d714993\n' +
'rpcpassword=0d4430ca1543833e35bce5a0cc9e16b3\n' +
'server=1\n' +
'addnode=78.47.196.146\n' +
'addnode=5.9.102.210\n' +
'addnode=178.63.69.164\n' +
'addnode=88.198.65.74\n' +
'addnode=5.9.122.241\n' +
'addnode=144.76.94.3\n' +
'addnode=144.76.94.38\n' +
'addnode=89.248.166.91\n' +
'addnode=148.251.57.148\n' +
'addnode=149.56.28.84\n' +
'addnode=176.9.26.39\n' +
'addnode=94.102.63.199\n' +
'addnode=94.102.63.200\n' +
'addnode=104.255.64.3\n' +
'addnode=221.121.144.140\n' +
'addnode=103.18.58.150\n' +
'addnode=103.18.58.146\n' +
'addnode=213.202.253.10\n' +
'addnode=185.106.121.32\n' +
'addnode=27.100.36.201\n';
shepherd.fs.writeFile(`${iguanaTestDir}/komodo.conf`, _komodoConf, (err) => {
if (err) {
shepherd.log(`test: error writing komodo conf in ${iguanaTestDir}`);
}
});
shepherd.portscanner.checkPortStatus('7771', '127.0.0.1', (error, status) => {
// Status is 'open' if currently in use or 'closed' if available
if (status === 'closed') {
_komododTest.port = 'passed';
} else {
_komododTest.port = 'failed';
}
});
/*pm2.connect(true,function(err) { //start up pm2 god
if (err) {
shepherd.error(err);
process.exit(2);
}
pm2.start({
script: shepherd.komododBin, // path to binary
name: 'komodod',
exec_mode : 'fork',
args: [
'-daemon=0',
'-addnode=78.47.196.146',
`-datadir=${iguanaTestDir}/`
],
output: `${iguanaTestDir}/komododTest.log`,
mergeLogs: true,
}, function(err, apps) {
if (apps[0] &&
apps[0].process &&
apps[0].process.pid) {
_komododTest.start = 'success';
shepherd.log(`test: got komodod instance pid = ${apps[0].process.pid}`);
shepherd.writeLog(`test: komodod started with pid ${apps[0].process.pid}`);
} else {
_komododTest.start = 'failed';
shepherd.log(`unable to start komodod`);
}
pm2.disconnect(); // Disconnect from PM2
if (err) {
shepherd.writeLog(`test: error starting komodod`);
shepherd.log(`komodod fork err: ${err}`);
// throw err;
}
});
});*/
setTimeout(() => {
const options = {
url: `http://localhost:7771`,
method: 'POST',
auth: {
user: 'user83f3afba8d714993',
pass: '0d4430ca1543833e35bce5a0cc9e16b3',
},
body: JSON.stringify({
agent: 'bitcoinrpc',
method: 'getinfo',
}),
};
shepherd.request(options, (error, response, body) => {
if (response &&
response.statusCode &&
response.statusCode === 200) {
// res.end(body);
shepherd.log(JSON.stringify(body, null, '\t'));
} else {
// res.end(body);
shepherd.log(JSON.stringify(body, null, '\t'));
}
});
}, 10000);
setTimeout(() => {
pm2.delete('komodod');
resolve(_komododTest);
}, 20000);
}
// komodod debug.log hooks
//"{\"result\":{\"version\":1000850,\"protocolversion\":170002,\"KMDversion\":\"0.1.1\",\"notarized\":0,\"notarizedhash\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"notarizedtxid\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"notarizedtxid_height\":\"mempool\",\"notarized_confirms\":0,\"walletversion\":60000,\"balance\":0.00000000,\"interest\":0.00000000,\"blocks\":128,\"longestchain\":472331,\"timeoffset\":0,\"tiptime\":1473827710,\"connections\":1,\"proxy\":\"\",\"difficulty\":1,\"testnet\":false,\"keypoololdest\":1504118047,\"keypoolsize\":101,\"paytxfee\":0.00000000,\"relayfee\":0.00000100,\"errors\":\"\"},\"error\":null,\"id\":null}\n"
//2017-08-30 17:51:33 Error: Cannot find the Zcash network parameters in the following directory:
//"/home/pbca/.zcash-params"
//Please run 'zcash-fetch-params' or './zcutil/fetch-params.sh' and then restart.
//EXCEPTION: St13runtime_error
//Assertion failed.
//2017-08-30 17:51:14 Using config file /home/pbca/.iguana/test/komodo.conf
//2017-08-30 18:23:43 UpdateTip: new best=0a47c1323f393650f7221c217d19d149d002d35444f47fde61be2dd90fbde8e6 height=1 log2_work=5.0874628 tx=2 date=2016-09-13 19:04:01 progress=0.000001 cache=0.0MiB(1tx)
//2017-08-30 18:23:43 UpdateTip: new best=05076a4e1fc9af0f5fda690257b17ae20c12d4796dfba1624804d012c9ec00be height=2 log2_work=5.6724253 tx=3 date=2016-09-13 19:05:28 progress=0.000001 cache=0.0MiB(2tx)
/*shepherd.execFile(`${shepherd.komododBin}`, _arg, {
maxBuffer: 1024 * 10000 // 10 mb
}, function(error, stdout, stderr) {
shepherd.writeLog(`stdout: ${stdout}`);
shepherd.writeLog(`stderr: ${stderr}`);
if (error !== null) {
console.log(`exec error: ${error}`);
shepherd.writeLog(`exec error: ${error}`);
if (error.toString().indexOf('using -reindex') > -1) {
shepherd.io.emit('service', {
komodod: {
error: 'run -reindex',
}
});
}
}
});*/
}
});
}
// komodod datadir location test
shepherd.testLocation = (path) => {
return new shepherd.Promise((resolve, reject) => {
if (shepherd.path.indexOf(' ') > -1) {
shepherd.log(`error testing path ${path}`);
resolve(-1);
} else {
shepherd.fs.lstat(path, (err, stats) => {
if (err) {
shepherd.log(`error testing path ${path}`);
resolve(-1);
} else {
if (stats.isDirectory()) {
resolve(true);
} else {
shepherd.log(`error testing path ${path} not a folder`);
resolve(false);
}
}
});
}
});
}
return shepherd;
};

70
routes/shepherd/binsUtils.js

@ -0,0 +1,70 @@
module.exports = (shepherd) => {
// osx and linux
shepherd.binFixRights = () => {
const osPlatform = shepherd.os.platform();
const _bins = [
shepherd.komododBin,
shepherd.komodocliBin
];
if (osPlatform === 'darwin' ||
osPlatform === 'linux') {
for (let i = 0; i < _bins.length; i++) {
shepherd._fs.stat(_bins[i], (err, stat) => {
if (!err) {
if (parseInt(stat.mode.toString(8), 10) !== 100775) {
shepherd.log(`${_bins[i]} fix permissions`);
shepherd.fsnode.chmodSync(_bins[i], '0775');
}
} else {
shepherd.log(`error: ${_bins[i]} not found`);
}
});
}
}
}
shepherd.killRogueProcess = (processName) => {
// kill rogue process copies on start
let processGrep;
const osPlatform = shepherd.os.platform();
switch (osPlatform) {
case 'darwin':
processGrep = "ps -p $(ps -A | grep -m1 " + processName + " | awk '{print $1}') | grep -i " + processName;
break;
case 'linux':
processGrep = 'ps -p $(pidof ' + processName + ') | grep -i ' + processName;
break;
case 'win32':
processGrep = 'tasklist';
break;
}
shepherd.exec(processGrep, (error, stdout, stderr) => {
if (stdout.indexOf(processName) > -1) {
const pkillCmd = osPlatform === 'win32' ? `taskkill /f /im ${processName}.exe` : `pkill -15 ${processName}`;
shepherd.log(`found another ${processName} process(es)`);
shepherd.writeLog(`found another ${processName} process(es)`);
shepherd.exec(pkillCmd, (error, stdout, stderr) => {
shepherd.log(`${pkillCmd} is issued`);
shepherd.writeLog(`${pkillCmd} is issued`);
if (error !== null) {
shepherd.log(`${pkillCmd} exec error: ${error}`);
shepherd.writeLog(`${pkillCmd} exec error: ${error}`);
};
});
}
if (error !== null) {
shepherd.log(`${processGrep} exec error: ${error}`);
shepherd.writeLog(`${processGrep} exec error: ${error}`);
};
});
}
return shepherd;
};

31
routes/shepherd/coins.js

@ -0,0 +1,31 @@
module.exports = (shepherd) => {
/*
* type: GET
*
*/
shepherd.get('/InstantDEX/allcoins', (req, res, next) => {
let successObj;
let nativeCoindList = [];
let electrumCoinsList = [];
for (let key in shepherd.electrumCoins) {
if (key !== 'auth') {
electrumCoinsList.push(shepherd.electrumCoins[key].abbr);
}
}
for (let key in shepherd.coindInstanceRegistry) {
nativeCoindList.push(key === 'komodod' ? 'KMD' : key);
}
successObj = {
native: nativeCoindList,
spv: electrumCoinsList,
total: Object.keys(shepherd.electrumCoins).length - 1 + Object.keys(nativeCoindList).length,
};
res.end(JSON.stringify(successObj));
});
return shepherd;
};

71
routes/shepherd/coinsList.js

@ -0,0 +1,71 @@
module.exports = (shepherd) => {
/*
* type: GET
*
*/
shepherd.get('/coinslist', (req, res, next) => {
if (shepherd.fs.existsSync(`${shepherd.agamaDir}/shepherd/coinslist.json`)) {
shepherd.fs.readFile(`${shepherd.agamaDir}/shepherd/coinslist.json`, 'utf8', (err, data) => {
if (err) {
const errorObj = {
msg: 'error',
result: err,
};
res.end(JSON.stringify(errorObj));
} else {
const successObj = {
msg: 'success',
result: data ? JSON.parse(data) : '',
};
res.end(JSON.stringify(successObj));
}
});
} else {
const errorObj = {
msg: 'error',
result: 'coin list doesn\'t exist',
};
res.end(JSON.stringify(errorObj));
}
});
/*
* type: POST
* params: payload
*/
shepherd.post('/coinslist', (req, res, next) => {
const _payload = req.body.payload;
if (!_payload) {
const errorObj = {
msg: 'error',
result: 'no payload provided',
};
res.end(JSON.stringify(errorObj));
} else {
shepherd.fs.writeFile(`${shepherd.agamaDir}/shepherd/coinslist.json`, JSON.stringify(_payload), (err) => {
if (err) {
const errorObj = {
msg: 'error',
result: err,
};
res.end(JSON.stringify(errorObj));
} else {
const successObj = {
msg: 'success',
result: 'done',
};
res.end(JSON.stringify(successObj));
}
});
}
});
return shepherd;
};

55
routes/shepherd/confMaxconnections.js

@ -0,0 +1,55 @@
module.exports = (shepherd) => {
shepherd.getMaxconKMDConf = () => {
return new shepherd.Promise((resolve, reject) => {
shepherd.fs.readFile(`${shepherd.komodoDir}/komodo.conf`, 'utf8', (err, data) => {
if (err) {
shepherd.log(`kmd conf maxconnections param read failed`);
resolve('unset');
} else {
const _maxcon = data.match(/maxconnections=\s*(.*)/);
if (!_maxcon) {
shepherd.log(`kmd conf maxconnections param is unset`);
resolve(false);
} else {
shepherd.log(`kmd conf maxconnections param is already set to ${_maxcon[1]}`);
resolve(_maxcon[1]);
}
}
});
});
}
shepherd.setMaxconKMDConf = (limit) => {
return new shepherd.Promise((resolve, reject) => {
shepherd.fs.readFile(`${shepherd.komodoDir}/komodo.conf`, 'utf8', (err, data) => {
const _maxconVal = limit ? 1 : 10;
if (err) {
shepherd.log(`error reading ${shepherd.komodoDir}/komodo.conf`);
resolve(false);
} else {
if (data.indexOf('maxconnections=') > -1) {
const _maxcon = data.match(/maxconnections=\s*(.*)/);
data = data.replace(`maxconnections=${_maxcon[1]}`, `maxconnections=${_maxconVal}`);
} else {
data = `${data}\nmaxconnections=${_maxconVal}\n`;
}
shepherd.fs.writeFile(`${shepherd.komodoDir}/komodo.conf`, data, (err) => {
if (err) {
shepherd.log(`error writing ${shepherd.komodoDir}/komodo.conf maxconnections=${_maxconVal}`);
resolve(false);
} else {
shepherd.log(`kmd conf maxconnections is set to ${_maxconVal}`);
resolve(true);
}
});
}
});
});
}
return shepherd;
};

152
routes/shepherd/config.js

@ -0,0 +1,152 @@
module.exports = (shepherd) => {
shepherd.loadLocalConfig = () => {
if (shepherd.fs.existsSync(`${shepherd.agamaDir}/config.json`)) {
let localAppConfig = shepherd.fs.readFileSync(`${shepherd.agamaDir}/config.json`, 'utf8');
shepherd.log('app config set from local file');
shepherd.writeLog('app config set from local file');
// find diff between local and hardcoded configs
// append diff to local config
const compareJSON = (obj1, obj2) => {
let result = {};
for (let i in obj1) {
if (!obj2.hasOwnProperty(i)) {
result[i] = obj1[i];
}
}
return result;
};
if (localAppConfig) {
const compareConfigs = compareJSON(shepherd.appConfig, JSON.parse(localAppConfig));
if (Object.keys(compareConfigs).length) {
const newConfig = Object.assign(JSON.parse(localAppConfig), compareConfigs);
shepherd.log('config diff is found, updating local config');
shepherd.log('config diff:');
shepherd.log(compareConfigs);
shepherd.writeLog('aconfig diff is found, updating local config');
shepherd.writeLog('config diff:');
shepherd.writeLog(compareConfigs);
shepherd.saveLocalAppConf(newConfig);
return newConfig;
} else {
return JSON.parse(localAppConfig);
}
} else {
return shepherd.appConfig;
}
} else {
shepherd.log('local config file is not found!');
shepherd.writeLog('local config file is not found!');
shepherd.saveLocalAppConf(shepherd.appConfig);
return shepherd.appConfig;
}
};
shepherd.saveLocalAppConf = (appSettings) => {
let appConfFileName = `${shepherd.agamaDir}/config.json`;
shepherd._fs.access(shepherd.agamaDir, shepherd.fs.constants.R_OK, (err) => {
if (!err) {
const FixFilePermissions = () => {
return new shepherd.Promise((resolve, reject) => {
const result = 'config.json file permissions updated to Read/Write';
shepherd.fsnode.chmodSync(appConfFileName, '0666');
setTimeout(() => {
shepherd.log(result);
shepherd.writeLog(result);
resolve(result);
}, 1000);
});
}
const FsWrite = () => {
return new shepherd.Promise((resolve, reject) => {
const result = 'config.json write file is done';
shepherd.fs.writeFile(appConfFileName,
JSON.stringify(appSettings)
.replace(/,/g, ',\n') // format json in human readable form
.replace(/":/g, '": ')
.replace(/{/g, '{\n')
.replace(/}/g, '\n}'), 'utf8', (err) => {
if (err)
return shepherd.log(err);
});
shepherd.fsnode.chmodSync(appConfFileName, '0666');
setTimeout(() => {
shepherd.log(result);
shepherd.log(`app conf.json file is created successfully at: ${shepherd.agamaDir}`);
shepherd.writeLog(`app conf.json file is created successfully at: ${shepherd.agamaDir}`);
resolve(result);
}, 2000);
});
}
FsWrite()
.then(FixFilePermissions());
}
});
}
/*
* type: POST
* params: payload
*/
shepherd.post('/appconf', (req, res, next) => {
if (!req.body.payload) {
const errorObj = {
msg: 'error',
result: 'no payload provided',
};
res.end(JSON.stringify(errorObj));
} else {
shepherd.saveLocalAppConf(req.body.payload);
const successObj = {
msg: 'success',
result: 'config saved',
};
res.end(JSON.stringify(successObj));
}
});
/*
* type: POST
* params: none
*/
shepherd.post('/appconf/reset', (req, res, next) => {
shepherd.saveLocalAppConf(shepherd.defaultAppConfig);
const successObj = {
msg: 'success',
result: 'config saved',
};
res.end(JSON.stringify(successObj));
});
/*
* type: GET
*
*/
shepherd.get('/appconf', (req, res, next) => {
const obj = shepherd.loadLocalConfig();
res.send(obj);
});
return shepherd;
};

868
routes/shepherd/daemonControl.js

@ -0,0 +1,868 @@
module.exports = (shepherd) => {
const getConf = (flock, coind) => {
let DaemonConfPath = '';
let nativeCoindDir;
if (flock === 'CHIPS') {
flock = 'chipsd';
}
shepherd.log(flock);
shepherd.log(`getconf coind ${coind}`);
shepherd.writeLog(`getconf flock: ${flock}`);
switch (shepherd.os.platform()) {
case 'darwin':
nativeCoindDir = `${process.env.HOME}/Library/Application Support/${shepherd.nativeCoindList[coind.toLowerCase()].bin}`;
break;
case 'linux':
nativeCoindDir = coind ? `${process.env.HOME}/.${shepherd.nativeCoindList[coind.toLowerCase()].bin.toLowerCase()}` : null;
break;
case 'win32':
nativeCoindDir = coind ? `${process.env.APPDATA}/${shepherd.nativeCoindList[coind.toLowerCase()].bin}` : null;
break;
}
switch (flock) {
case 'komodod':
DaemonConfPath = shepherd.komodoDir;
if (shepherd.os.platform() === 'win32') {
DaemonConfPath = shepherd.path.normalize(DaemonConfPath);
shepherd.log('===>>> SHEPHERD API OUTPUT ===>>>');
}
break;
case 'zcashd':
DaemonConfPath = shepherd.ZcashDir;
if (shepherd.os.platform() === 'win32') {
DaemonConfPath = shepherd.path.normalize(DaemonConfPath);
}
break;
case 'chipsd':
DaemonConfPath = shepherd.chipsDir;
if (shepherd.os.platform() === 'win32') {
DaemonConfPath = shepherd.path.normalize(DaemonConfPath);
}
break;
case 'coind':
DaemonConfPath = shepherd.os.platform() === 'win32' ? shepherd.path.normalize(`${shepherd.coindRootDir}/${coind.toLowerCase()}`) : `${shepherd.coindRootDir}/${coind.toLowerCase()}`;
break;
default:
DaemonConfPath = `${shepherd.komodoDir}/${flock}`;
if (shepherd.os.platform() === 'win32') {
DaemonConfPath = shepherd.path.normalize(DaemonConfPath);
}
}
shepherd.writeLog(`getconf path: ${DaemonConfPath}`);
shepherd.log(`daemon path: ${DaemonConfPath}`);
return DaemonConfPath;
}
// TODO: json.stringify wrapper
const herder = (flock, data, coind) => {
if (data === undefined) {
data = 'none';
shepherd.log('it is undefined');
}
shepherd.log(`herder flock: ${flock} coind: ${coind}`);
shepherd.log(`selected data: ${JSON.stringify(data, null, '\t')}`);
// TODO: notify gui that reindex/rescan param is used to reflect on the screen
// asset chain debug.log unlink
if (flock === 'komodod') {
let kmdDebugLogLocation = (data.ac_name !== 'komodod' ? `${shepherd.komodoDir}/${data.ac_name}` : shepherd.komodoDir) + '/debug.log';
shepherd.log('komodod flock selected...');
shepherd.log(`selected data: ${JSON.stringify(data, null, '\t')}`);
shepherd.writeLog('komodod flock selected...');
shepherd.writeLog(`selected data: ${data}`);
// datadir case, check if komodo/chain folder exists
if (shepherd.appConfig.dataDir.length &&
data.ac_name !== 'komodod') {
const _dir = data.ac_name !== 'komodod' ? `${shepherd.komodoDir}/${data.ac_name}` : shepherd.komodoDir;
try {
shepherd._fs.accessSync(_dir, shepherd.fs.R_OK | shepherd.fs.W_OK);
shepherd.log(`komodod datadir ${_dir} exists`);
} catch (e) {
shepherd.log(`komodod datadir ${_dir} access err: ${e}`);
shepherd.log(`attempting to create komodod datadir ${_dir}`);
shepherd.fs.mkdirSync(_dir);
if (shepherd.fs.existsSync(_dir)) {
shepherd.log(`created komodod datadir folder at ${_dir}`);
} else {
shepherd.log(`unable to create komodod datadir folder at ${_dir}`);
}
}
}
// truncate debug.log
if (!shepherd.kmdMainPassiveMode) {
try {
const _confFileAccess = shepherd._fs.accessSync(kmdDebugLogLocation, shepherd.fs.R_OK | shepherd.fs.W_OK);
if (_confFileAccess) {
shepherd.log(`error accessing ${kmdDebugLogLocation}`);
shepherd.writeLog(`error accessing ${kmdDebugLogLocation}`);
} else {
try {
shepherd.fs.unlinkSync(kmdDebugLogLocation);
shepherd.log(`truncate ${kmdDebugLogLocation}`);
shepherd.writeLog(`truncate ${kmdDebugLogLocation}`);
} catch (e) {
shepherd.log('cant unlink debug.log');
}
}
} catch (e) {
shepherd.log(`komodod debug.log access err: ${e}`);
shepherd.writeLog(`komodod debug.log access err: ${e}`);
}
}
// get komodod instance port
const _port = shepherd.assetChainPorts[data.ac_name];
try {
// check if komodod instance is already running
shepherd.portscanner.checkPortStatus(_port, '127.0.0.1', (error, status) => {
// Status is 'open' if currently in use or 'closed' if available
if (status === 'closed') {
// start komodod via exec
const _customParamDict = {
silent: '&',
reindex: '-reindex',
change: '-pubkey=',
datadir: '-datadir=',
rescan: '-rescan',
};
let _customParam = '';
if (data.ac_custom_param === 'silent' ||
data.ac_custom_param === 'reindex' ||
data.ac_custom_param === 'rescan') {
_customParam = ` ${_customParamDict[data.ac_custom_param]}`;
} else if (data.ac_custom_param === 'change' && data.ac_custom_param_value) {
_customParam = ` ${_customParamDict[data.ac_custom_param]}${data.ac_custom_param_value}`;
}
if (shepherd.appConfig.dataDir.length) {
_customParam = _customParam + ' -datadir=' + shepherd.appConfig.dataDir + (data.ac_name !== 'komodod' ? '/' + data.ac_name : '');
}
shepherd.log(`exec ${shepherd.komododBin} ${data.ac_options.join(' ')}${_customParam}`);
shepherd.writeLog(`exec ${shepherd.komododBin} ${data.ac_options.join(' ')}${_customParam}`);
const isChain = data.ac_name.match(/^[A-Z]*$/);
const coindACParam = isChain ? ` -ac_name=${data.ac_name} ` : '';
shepherd.log(`daemon param ${data.ac_custom_param}`);
shepherd.coindInstanceRegistry[data.ac_name] = true;
if (!shepherd.kmdMainPassiveMode) {
let _arg = `${coindACParam}${data.ac_options.join(' ')}${_customParam}`;
_arg = _arg.trim().split(' ');
shepherd.execFile(`${shepherd.komododBin}`, _arg, {
maxBuffer: 1024 * 1000000 // 1000 mb
}, (error, stdout, stderr) => {
shepherd.writeLog(`stdout: ${stdout}`);
shepherd.writeLog(`stderr: ${stderr}`);
if (error !== null) {
shepherd.log(`exec error: ${error}`);
shepherd.writeLog(`exec error: ${error}`);
if (error.toString().indexOf('using -reindex') > -1) {
shepherd.io.emit('service', {
komodod: {
error: 'run -reindex',
},
});
}
}
});
}
} else {
if (shepherd.kmdMainPassiveMode) {
shepherd.coindInstanceRegistry[data.ac_name] = true;
}
shepherd.log(`port ${_port} (${data.ac_name}) is already in use`);
shepherd.writeLog(`port ${_port} (${data.ac_name}) is already in use`);
}
});
} catch(e) {
shepherd.log(`failed to start komodod err: ${e}`);
shepherd.writeLog(`failed to start komodod err: ${e}`);
}
}
// TODO: refactor
if (flock === 'chipsd') {
let kmdDebugLogLocation = `${shepherd.chipsDir}/debug.log`;
shepherd.log('chipsd flock selected...');
shepherd.log(`selected data: ${JSON.stringify(data, null, '\t')}`);
shepherd.writeLog('chipsd flock selected...');
shepherd.writeLog(`selected data: ${data}`);
// truncate debug.log
try {
const _confFileAccess = shepherd._fs.accessSync(kmdDebugLogLocation, shepherd.fs.R_OK | shepherd.fs.W_OK);
if (_confFileAccess) {
shepherd.log(`error accessing ${kmdDebugLogLocation}`);
shepherd.writeLog(`error accessing ${kmdDebugLogLocation}`);
} else {
try {
shepherd.fs.unlinkSync(kmdDebugLogLocation);
shepherd.log(`truncate ${kmdDebugLogLocation}`);
shepherd.writeLog(`truncate ${kmdDebugLogLocation}`);
} catch (e) {
shepherd.log('cant unlink debug.log');
}
}
} catch(e) {
shepherd.log(`chipsd debug.log access err: ${e}`);
shepherd.writeLog(`chipsd debug.log access err: ${e}`);
}
// get komodod instance port
const _port = shepherd.assetChainPorts.chipsd;
try {
// check if komodod instance is already running
shepherd.portscanner.checkPortStatus(_port, '127.0.0.1', (error, status) => {
// Status is 'open' if currently in use or 'closed' if available
if (status === 'closed') {
// start komodod via exec
const _customParamDict = {
silent: '&',
reindex: '-reindex',
change: '-pubkey=',
rescan: '-rescan',
};
let _customParam = '';
if (data.ac_custom_param === 'silent' ||
data.ac_custom_param === 'reindex' ||
data.ac_custom_param === 'rescan') {
_customParam = ` ${_customParamDict[data.ac_custom_param]}`;
} else if (data.ac_custom_param === 'change' && data.ac_custom_param_value) {
_customParam = ` ${_customParamDict[data.ac_custom_param]}${data.ac_custom_param_value}`;
}
shepherd.log(`exec ${shepherd.chipsBin} ${_customParam}`);
shepherd.writeLog(`exec ${shepherd.chipsBin} ${_customParam}`);
shepherd.log(`daemon param ${data.ac_custom_param}`);
shepherd.coindInstanceRegistry['CHIPS'] = true;
let _arg = `${_customParam}`;
_arg = _arg.trim().split(' ');
if (_arg &&
_arg.length > 1) {
shepherd.execFile(`${shepherd.chipsBin}`, _arg, {
maxBuffer: 1024 * 1000000 // 1000 mb
}, (error, stdout, stderr) => {
shepherd.writeLog(`stdout: ${stdout}`);
shepherd.writeLog(`stderr: ${stderr}`);
if (error !== null) {
shepherd.log(`exec error: ${error}`);
shepherd.writeLog(`exec error: ${error}`);
if (error.toString().indexOf('using -reindex') > -1) {
shepherd.io.emit('service', {
komodod: {
error: 'run -reindex',
},
});
}
}
});
} else {
shepherd.execFile(`${shepherd.chipsBin}`, {
maxBuffer: 1024 * 1000000 // 1000 mb
}, (error, stdout, stderr) => {
shepherd.writeLog(`stdout: ${stdout}`);
shepherd.writeLog(`stderr: ${stderr}`);
if (error !== null) {
shepherd.log(`exec error: ${error}`);
shepherd.writeLog(`exec error: ${error}`);
if (error.toString().indexOf('using -reindex') > -1) {
shepherd.io.emit('service', {
komodod: {
error: 'run -reindex',
},
});
}
}
});
}
}
});
} catch(e) {
shepherd.log(`failed to start chipsd err: ${e}`);
shepherd.writeLog(`failed to start chipsd err: ${e}`);
}
}
if (flock === 'zcashd') { // TODO: fix(?)
let kmdDebugLogLocation = `${shepherd.zcashDir}/debug.log`;
shepherd.log('zcashd flock selected...');
shepherd.log(`selected data: ${data}`);
shepherd.writeLog('zcashd flock selected...');
shepherd.writeLog(`selected data: ${data}`);
/*pm2.connect(true, function(err) { // start up pm2 god
if (err) {
shepherd.error(err);
process.exit(2);
}
pm2.start({
script: shepherd.zcashdBin, // path to binary
name: data.ac_name, // REVS, USD, EUR etc.
exec_mode: 'fork',
cwd: shepherd.zcashDir,
args: data.ac_options
}, function(err, apps) {
shepherd.writeLog(`zcashd fork started ${data.ac_name} ${JSON.stringify(data.ac_options)}`);
pm2.disconnect(); // Disconnect from PM2
if (err) {
shepherd.writeLog(`pm2.disconnect err: ${err}`);
shepherd.log(`pm2.disconnect err: ${err}`);
}
// throw err;
});
});*/
}
if (flock === 'coind') {
const _osHome = shepherd.os.platform === 'win32' ? process.env.APPDATA : process.env.HOME;
let coindDebugLogLocation = `${_osHome}/.${shepherd.nativeCoindList[coind.toLowerCase()].bin.toLowerCase()}/debug.log`;
shepherd.log(`coind ${coind} flock selected...`);
shepherd.log(`selected data: ${JSON.stringify(data, null, '\t')}`);
shepherd.writeLog(`coind ${coind} flock selected...`);
shepherd.writeLog(`selected data: ${data}`);
// truncate debug.log
try {
shepherd._fs.access(coindDebugLogLocation, shepherd.fs.constants.R_OK, (err) => {
if (err) {
shepherd.log(`error accessing ${coindDebugLogLocation}`);
shepherd.writeLog(`error accessing ${coindDebugLogLocation}`);
} else {
shepherd.log(`truncate ${coindDebugLogLocation}`);
shepherd.writeLog(`truncate ${coindDebugLogLocation}`);
shepherd.fs.unlink(coindDebugLogLocation);
}
});
} catch(e) {
shepherd.log(`coind ${coind} debug.log access err: ${e}`);
shepherd.writeLog(`coind ${coind} debug.log access err: ${e}`);
}
// get komodod instance port
const _port = shepherd.nativeCoindList[coind.toLowerCase()].port;
const coindBin = `${shepherd.coindRootDir}/${coind.toLowerCase()}/${shepherd.nativeCoindList[coind.toLowerCase()].bin.toLowerCase()}d`;
try {
// check if coind instance is already running
shepherd.portscanner.checkPortStatus(_port, '127.0.0.1', (error, status) => {
// Status is 'open' if currently in use or 'closed' if available
if (status === 'closed') {
shepherd.log(`exec ${coindBin} ${data.ac_options.join(' ')}`);
shepherd.writeLog(`exec ${coindBin} ${data.ac_options.join(' ')}`);
shepherd.coindInstanceRegistry[coind] = true;
let _arg = `${data.ac_options.join(' ')}`;
_arg = _arg.trim().split(' ');
shepherd.execFile(`${coindBin}`, _arg, {
maxBuffer: 1024 * 1000000 // 1000 mb
}, (error, stdout, stderr) => {
shepherd.writeLog(`stdout: ${stdout}`);
shepherd.writeLog(`stderr: ${stderr}`);
if (error !== null) {
shepherd.log(`exec error: ${error}`);
shepherd.writeLog(`exec error: ${error}`);
}
});
} else {
shepherd.log(`port ${_port} (${coind}) is already in use`);
shepherd.writeLog(`port ${_port} (${coind}) is already in use`);
}
});
} catch(e) {
shepherd.log(`failed to start ${coind} err: ${e}`);
shepherd.writeLog(`failed to start ${coind} err: ${e}`);
}
}
}
const setConf = (flock, coind) => {
let nativeCoindDir;
let DaemonConfPath;
shepherd.log(flock);
shepherd.writeLog(`setconf ${flock}`);
if (shepherd.os.platform() === 'darwin') {
nativeCoindDir = coind ? `${process.env.HOME}/Library/Application Support/${shepherd.nativeCoindList[coind.toLowerCase()].bin}` : null;
}
if (shepherd.os.platform() === 'linux') {
nativeCoindDir = coind ? `${process.env.HOME}/.${shepherd.nativeCoindList[coind.toLowerCase()].bin.toLowerCase()}` : null;
}
if (shepherd.os.platform() === 'win32') {
nativeCoindDir = coind ? `${process.env.APPDATA}/${shepherd.nativeCoindList[coind.toLowerCase()].bin}` : null;
}
switch (flock) {
case 'komodod':
DaemonConfPath = `${shepherd.komodoDir}/komodo.conf`;
if (shepherd.os.platform() === 'win32') {
DaemonConfPath = shepherd.path.normalize(DaemonConfPath);
}
break;
case 'zcashd':
DaemonConfPath = `${shepherd.ZcashDir}/zcash.conf`;
if (shepherd.os.platform() === 'win32') {
DaemonConfPath = shepherd.path.normalize(DaemonConfPath);
}
break;
case 'chipsd':
DaemonConfPath = `${shepherd.chipsDir}/chips.conf`;
if (shepherd.os.platform() === 'win32') {
DaemonConfPath = shepherd.path.normalize(DaemonConfPath);
}
break;
case 'coind':
DaemonConfPath = `${nativeCoindDir}/${shepherd.nativeCoindList[coind.toLowerCase()].bin.toLowerCase()}.conf`;
if (shepherd.os.platform() === 'win32') {
DaemonConfPath = shepherd.path.normalize(DaemonConfPath);
}
break;
default:
DaemonConfPath = `${shepherd.komodoDir}/${flock}/${flock}.conf`;
if (shepherd.os.platform() === 'win32') {
DaemonConfPath = shepherd.path.normalize(DaemonConfPath);
}
}
shepherd.log(DaemonConfPath);
shepherd.writeLog(`setconf ${DaemonConfPath}`);
const CheckFileExists = () => {
return new shepherd.Promise((resolve, reject) => {
const result = 'Check Conf file exists is done';
const confFileExist = shepherd.fs.ensureFileSync(DaemonConfPath);
if (confFileExist) {
shepherd.log(result);
shepherd.writeLog(`setconf ${result}`);
resolve(result);
} else {
shepherd.log('conf file doesnt exist');
resolve('conf file doesnt exist');
}
});
}
const FixFilePermissions = () => {
return new shepherd.Promise((resolve, reject) => {
const result = 'Conf file permissions updated to Read/Write';
shepherd.fsnode.chmodSync(DaemonConfPath, '0666');
shepherd.log(result);
shepherd.writeLog(`setconf ${result}`);
resolve(result);
});
}
const RemoveLines = () => {
return new shepherd.Promise((resolve, reject) => {
const result = 'RemoveLines is done';
shepherd.fs.readFile(DaemonConfPath, 'utf8', (err, data) => {
if (err) {
shepherd.writeLog(`setconf error ${err}`);
return shepherd.log(err);
}
const rmlines = data.replace(/(?:(?:\r\n|\r|\n)\s*){2}/gm, '\n');
shepherd.fs.writeFile(DaemonConfPath, rmlines, 'utf8', (err) => {
if (err)
return shepherd.log(err);
shepherd.fsnode.chmodSync(DaemonConfPath, '0666');
shepherd.writeLog(`setconf ${result}`);
shepherd.log(result);
resolve(result);
});
});
});
}
const CheckConf = () => {
return new shepherd.Promise((resolve, reject) => {
const result = 'CheckConf is done';
shepherd.setconf.status(DaemonConfPath, (err, status) => {
const rpcuser = () => {
return new shepherd.Promise((resolve, reject) => {
const result = 'checking rpcuser...';
if (status[0].hasOwnProperty('rpcuser')) {
shepherd.log('rpcuser: OK');
shepherd.writeLog('rpcuser: OK');
} else {
const randomstring = shepherd.md5((Math.random() * Math.random() * 999).toString());
shepherd.log('rpcuser: NOT FOUND');
shepherd.writeLog('rpcuser: NOT FOUND');
shepherd.fs.appendFile(DaemonConfPath, `\nrpcuser=user${randomstring.substring(0, 16)}`, (err) => {
if (err) {
shepherd.writeLog(`append daemon conf err: ${err}`);
shepherd.log(`append daemon conf err: ${err}`);
}
// throw err;
shepherd.log('rpcuser: ADDED');
shepherd.writeLog('rpcuser: ADDED');
});
}
resolve(result);
});
}
const rpcpass = () => {
return new shepherd.Promise((resolve, reject) => {
const result = 'checking rpcpassword...';
if (status[0].hasOwnProperty('rpcpassword')) {
shepherd.log('rpcpassword: OK');
shepherd.writeLog('rpcpassword: OK');
} else {
const randomstring = shepherd.md5((Math.random() * Math.random() * 999).toString());
shepherd.log('rpcpassword: NOT FOUND');
shepherd.writeLog('rpcpassword: NOT FOUND');
shepherd.fs.appendFile(DaemonConfPath, `\nrpcpassword=${randomstring}`, (err) => {
if (err) {
shepherd.writeLog(`append daemon conf err: ${err}`);
shepherd.log(`append daemon conf err: ${err}`);
}
// throw err;
shepherd.log('rpcpassword: ADDED');
shepherd.writeLog('rpcpassword: ADDED');
});
}
resolve(result);
});
}
const rpcbind = () => {
return new shepherd.Promise((resolve, reject) => {
const result = 'checking rpcbind...';
if (status[0].hasOwnProperty('rpcbind')) {
shepherd.log('rpcbind: OK');
shepherd.writeLog('rpcbind: OK');
} else {
shepherd.log('rpcbind: NOT FOUND');
shepherd.writeLog('rpcbind: NOT FOUND');
shepherd.fs.appendFile(DaemonConfPath, '\nrpcbind=127.0.0.1', (err) => {
if (err) {
shepherd.writeLog(`append daemon conf err: ${err}`);
shepherd.log(`append daemon conf err: ${err}`);
}
// throw err;
shepherd.log('rpcbind: ADDED');
shepherd.writeLog('rpcbind: ADDED');
});
}
resolve(result);
});
}
const server = () => {
return new shepherd.Promise((resolve, reject) => {
const result = 'checking server...';
if (status[0].hasOwnProperty('server')) {
shepherd.log('server: OK');
shepherd.writeLog('server: OK');
} else {
shepherd.log('server: NOT FOUND');
shepherd.writeLog('server: NOT FOUND');
shepherd.fs.appendFile(DaemonConfPath, '\nserver=1', (err) => {
if (err) {
shepherd.writeLog(`append daemon conf err: ${err}`);
shepherd.log(`append daemon conf err: ${err}`);
}
// throw err;
shepherd.log('server: ADDED');
shepherd.writeLog('server: ADDED');
});
}
resolve(result);
});
}
const addnode = () => {
return new shepherd.Promise((resolve, reject) => {
const result = 'checking addnode...';
if (flock === 'chipsd' ||
flock === 'komodod') {
if (status[0].hasOwnProperty('addnode')) {
shepherd.log('addnode: OK');
shepherd.writeLog('addnode: OK');
} else {
let nodesList;
if (flock === 'chipsd') {
nodesList = '\naddnode=95.110.191.193' +
'\naddnode=144.76.167.66' +
'\naddnode=158.69.248.93' +
'\naddnode=149.202.49.218' +
'\naddnode=95.213.205.222' +
'\naddnode=5.9.253.198' +
'\naddnode=164.132.224.253' +
'\naddnode=163.172.4.66' +
'\naddnode=217.182.194.216' +
'\naddnode=94.130.96.114' +
'\naddnode=5.9.253.195';
} else if (flock === 'komodod') {
nodesList = '\naddnode=78.47.196.146' +
'\naddnode=5.9.102.210' +
'\naddnode=178.63.69.164' +
'\naddnode=88.198.65.74' +
'\naddnode=5.9.122.241' +
'\naddnode=144.76.94.3';
}
shepherd.log('addnode: NOT FOUND');
shepherd.fs.appendFile(DaemonConfPath, nodesList, (err) => {
if (err) {
shepherd.writeLog(`append daemon conf err: ${err}`);
shepherd.log(`append daemon conf err: ${err}`);
}
// throw err;
shepherd.log('addnode: ADDED');
shepherd.writeLog('addnode: ADDED');
});
}
} else {
result = 'skip addnode';
}
resolve(result);
});
}
rpcuser()
.then((result) => {
return rpcpass();
})
.then(server)
.then(rpcbind)
.then(addnode);
});
shepherd.log(result);
shepherd.writeLog(`checkconf addnode ${result}`);
resolve(result);
});
}
CheckFileExists()
.then((result) => {
return FixFilePermissions();
})
.then(RemoveLines)
.then(CheckConf);
}
/*
* type: POST
* params: herd
*/
shepherd.post('/herd', (req, res) => {
shepherd.log('======= req.body =======');
shepherd.log(req.body);
if (req.body.options &&
!shepherd.kmdMainPassiveMode) {
const testCoindPort = (skipError) => {
if (!shepherd.lockDownAddCoin) {
const _port = shepherd.assetChainPorts[req.body.options.ac_name];
shepherd.portscanner.checkPortStatus(_port, '127.0.0.1', (error, status) => {
// Status is 'open' if currently in use or 'closed' if available
if (status === 'open') {
if (!skipError) {
shepherd.log(`komodod service start error at port ${_port}, reason: port is closed`);
shepherd.writeLog(`komodod service start error at port ${_port}, reason: port is closed`);
shepherd.io.emit('service', {
komodod: {
error: `error starting ${req.body.herd} ${req.body.options.ac_name} daemon. Port ${_port} is already taken!`,
},
});
const obj = {
msg: 'error',
result: `error starting ${req.body.herd} ${req.body.options.ac_name} daemon. Port ${_port} is already taken!`,
};
res.status(500);
res.end(JSON.stringify(obj));
} else {
shepherd.log(`komodod service start success at port ${_port}`);
shepherd.writeLog(`komodod service start success at port ${_port}`);
}
} else {
if (!skipError) {
herder(req.body.herd, req.body.options);
const obj = {
msg: 'success',
result: 'result',
};
res.end(JSON.stringify(obj));
} else {
shepherd.log(`komodod service start error at port ${_port}, reason: unknown`);
shepherd.writeLog(`komodod service start error at port ${_port}, reason: unknown`);
}
}
});
}
}
if (req.body.herd === 'komodod') {
// check if komodod instance is already running
testCoindPort();
setTimeout(() => {
testCoindPort(true);
}, 10000);
} else {
herder(req.body.herd, req.body.options, req.body.coind);
const obj = {
msg: 'success',
result: 'result',
};
res.end(JSON.stringify(obj));
}
} else {
// (?)
herder(req.body.herd, req.body.options);
const obj = {
msg: 'success',
result: 'result',
};
res.end(JSON.stringify(obj));
}
});
/*
* type: POST
*/
shepherd.post('/setconf', (req, res) => {
shepherd.log('======= req.body =======');
shepherd.log(req.body);
if (shepherd.os.platform() === 'win32' &&
req.body.chain == 'komodod') {
setkomodoconf = spawn(shepherd.path.join(__dirname, '../assets/bin/win64/genkmdconf.bat'));
} else {
shepherd.setConf(req.body.chain);
}
const obj = {
msg: 'success',
result: 'result',
};
res.end(JSON.stringify(obj));
});
/*
* type: POST
*/
shepherd.post('/getconf', (req, res) => {
shepherd.log('======= req.body =======');
shepherd.log(req.body);
const confpath = getConf(req.body.chain, req.body.coind);
shepherd.log('got conf path is:');
shepherd.log(confpath);
shepherd.writeLog('got conf path is:');
shepherd.writeLog(confpath);
const obj = {
msg: 'success',
result: confpath,
};
res.end(JSON.stringify(obj));
});
shepherd.setConfKMD = (isChips) => {
// check if kmd conf exists
shepherd._fs.access(isChips ? `${shepherd.chipsDir}/chips.conf` : `${shepherd.komodoDir}/komodo.conf`, shepherd.fs.constants.R_OK, (err) => {
if (err) {
shepherd.log(isChips ? 'creating chips conf' : 'creating komodo conf');
shepherd.writeLog(isChips ? `creating chips conf in ${shepherd.chipsDir}/chips.conf` : `creating komodo conf in ${shepherd.komodoDir}/komodo.conf`);
setConf(isChips ? 'chipsd' : 'komodod');
} else {
const _confSize = shepherd.fs.lstatSync(isChips ? `${shepherd.chipsDir}/chips.conf` : `${shepherd.komodoDir}/komodo.conf`);
if (_confSize.size === 0) {
shepherd.log(isChips ? 'err: chips conf file is empty, creating chips conf' : 'err: komodo conf file is empty, creating komodo conf');
shepherd.writeLog(isChips ? `creating chips conf in ${shepherd.chipsDir}/chips.conf` : `creating komodo conf in ${shepherd.komodoDir}/komodo.conf`);
setConf(isChips ? 'chipsd' : 'komodod');
} else {
shepherd.writeLog(isChips ? 'chips conf exists' : 'komodo conf exists');
shepherd.log(isChips ? 'chips conf exists' : 'komodo conf exists');
}
}
});
}
return shepherd;
};

294
routes/shepherd/dashboardUpdate.js

@ -0,0 +1,294 @@
module.exports = (shepherd) => {
/*
* Combined native dashboard update same as in gui
* type: GET
* params: coin
*/
shepherd.post('/native/dashboard/update', (req, res, next) => {
let _returnObj;
let _promiseStack;
const _coin = req.body.coin;
if (_coin === 'CHIPS') {
_returnObj = {
getinfo: {},
listtransactions: [],
getbalance: {},
listunspent: {},
addresses: {},
};
_promiseStack = [
'getinfo',
'listtransactions',
'getbalance',
];
} else {
_returnObj = {
getinfo: {},
listtransactions: [],
z_gettotalbalance: {},
z_getoperationstatus: {},
listunspent: {},
addresses: {},
};
_promiseStack = [
'getinfo',
'listtransactions',
'z_gettotalbalance',
'z_getoperationstatus'
];
}
const getAddressesNative = (coin) => {
const type = [
'public',
'private'
];
if (coin === 'CHIPS') {
type.pop();
}
shepherd.Promise.all(type.map((_type, index) => {
return new shepherd.Promise((resolve, reject) => {
_bitcoinRPC(
coin,
_type === 'public' ? 'getaddressesbyaccount' : 'z_listaddresses',
['']
).then((_json) => {
if (_json === 'Work queue depth exceeded' ||
!_json) {
resolve({ error: 'daemon is busy' });
} else {
resolve(JSON.parse(_json).result);
}
});
});
}))
.then(result => {
if (result[0] &&
result[0].length) {
const calcBalance = (result, json) => {
if (json &&
json.length) {
const allAddrArray = json.map(res => res.address).filter((x, i, a) => a.indexOf(x) == i);
for (let a = 0; a < allAddrArray.length; a++) {
const filteredArray = json.filter(res => res.address === allAddrArray[a]).map(res => res.amount);
let isNewAddr = true;
for (let x = 0; x < result.length && isNewAddr; x++) {
for (let y = 0; y < result[x].length && isNewAddr; y++) {
if (allAddrArray[a] === result[x][y]) {
isNewAddr = false;
}
}
}
if (isNewAddr &&
(allAddrArray[a].substring(0, 2) === 'zc' ||
allAddrArray[a].substring(0, 2) === 'zt')) {
result[1][result[1].length] = allAddrArray[a];
} else {
result[0][result[0].length] = allAddrArray[a];
}
}
}
// remove addr duplicates
if (result[0] &&
result[0].length) {
result[0] = result[0].filter((elem, pos) => {
return result[0].indexOf(elem) === pos;
});
}
if (result[1] &&
result[1].length) {
result[1] = result[1].filter((elem, pos) => {
return result[1].indexOf(elem) === pos;
});
}
let newAddressArray = [];
for (let a = 0; a < result.length; a++) {
newAddressArray[a] = [];
if (result[a]) {
for (let b = 0; b < result[a].length; b++) {
let filteredArray;
filteredArray = json.filter(res => res.address === result[a][b]).map(res => res.amount);
let sum = 0;
for (let i = 0; i < filteredArray.length; i++) {
sum += filteredArray[i];
}
newAddressArray[a][b] = {
address: result[a][b],
amount: sum,
type: a === 0 ? 'public': 'private',
};
}
}
}
// get zaddr balance
if (result[1] &&
result[1].length) {
shepherd.Promise.all(result[1].map((_address, index) => {
return new shepherd.Promise((resolve, reject) => {
_bitcoinRPC(coin, 'z_getbalance', [_address])
.then((__json) => {
__json = JSON.parse(__json);
if (__json &&
__json.error) {
resolve(0);
} else {
resolve(__json.result)
newAddressArray[1][index] = {
address: _address,
amount: __json.result,
type: 'private',
};
}
});
});
}))
.then(zresult => {
_returnObj.addresses = {
public: newAddressArray[0],
private: newAddressArray[1],
};
const returnObj = {
msg: 'success',
result: _returnObj,
};
res.end(JSON.stringify(returnObj));
});
} else {
_returnObj.addresses = {
public: newAddressArray[0],
private: newAddressArray[1],
};
const returnObj = {
msg: 'success',
result: _returnObj,
};
res.end(JSON.stringify(returnObj));
}
}
_bitcoinRPC(coin, 'listunspent')
.then((__json) => {
if (__json === 'Work queue depth exceeded' ||
!__json) {
const returnObj = {
msg: 'success',
result: _returnObj,
};
res.end(JSON.stringify(returnObj));
} else {
_returnObj.listunspent = JSON.parse(__json);
calcBalance(
result,
JSON.parse(__json).result
);
}
});
} else {
_returnObj.addresses = {
public: {},
private: {},
};
const returnObj = {
msg: 'success',
result: _returnObj,
};
res.end(JSON.stringify(returnObj));
}
})
}
const _bitcoinRPC = (coin, cmd, params) => {
return new shepherd.Promise((resolve, reject) => {
let _payload;
if (params) {
_payload = {
mode: null,
chain: coin,
cmd: cmd,
params: params,
};
} else {
_payload = {
mode: null,
chain: coin,
cmd: cmd,
};
}
const options = {
url: `http://127.0.0.1:${shepherd.appConfig.agamaPort}/shepherd/cli`,
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ payload: _payload }),
timeout: 120000,
};
shepherd.request(options, (error, response, body) => {
if (response &&
response.statusCode &&
response.statusCode === 200) {
resolve(body);
} else {
resolve(body);
}
});
});
}
shepherd.Promise.all(_promiseStack.map((_call, index) => {
let _params;
if (_call === 'listtransactions') {
_params = [
'',
300,
0
];
}
return new shepherd.Promise((resolve, reject) => {
_bitcoinRPC(
_coin,
_call,
_params
)
.then((json) => {
if (json === 'Work queue depth exceeded' ||
!json) {
_returnObj[_call] = { error: 'daemon is busy' };
} else {
_returnObj[_call] = JSON.parse(json);
}
resolve(json);
});
});
}))
.then(result => {
getAddressesNative(_coin);
});
});
return shepherd;
};

92
routes/shepherd/debugLog.js

@ -0,0 +1,92 @@
module.exports = (shepherd) => {
/*
* type: GET
* params: herd, lastLines
*/
shepherd.post('/debuglog', (req, res) => {
let _herd = req.body.herdname;
let _ac = req.body.ac;
let _lastNLines = req.body.lastLines;
let _location;
if (shepherd.os.platform() === 'darwin') {
shepherd.komodoDir = shepherd.appConfig.dataDir.length ? shepherd.appConfig.dataDir : `${process.env.HOME}/Library/Application Support/Komodo`;
}
if (shepherd.os.platform() === 'linux') {
shepherd.komodoDir = shepherd.appConfig.dataDir.length ? shepherd.appConfig.dataDir : `${process.env.HOME}/.komodo`;
}
if (shepherd.os.platform() === 'win32') {
shepherd.komodoDir = shepherd.appConfig.dataDir.length ? shepherd.appConfig.dataDir : `${process.env.APPDATA}/Komodo`;
shepherd.komodoDir = shepherd.path.normalize(shepherd.komodoDir);
}
if (_herd === 'komodo') {
_location = shepherd.komodoDir;
}
if (_ac) {
_location = `${shepherd.komodoDir}/${_ac}`;
if (_ac === 'CHIPS') {
_location = shepherd.chipsDir;
}
}
shepherd.readDebugLog(`${_location}/debug.log`, _lastNLines)
.then((result) => {
const _obj = {
msg: 'success',
result: result,
};
res.end(JSON.stringify(_obj));
}, (result) => {
const _obj = {
msg: 'error',
result: result,
};
res.end(JSON.stringify(_obj));
});
});
shepherd.readDebugLog = (fileLocation, lastNLines) => {
return new shepherd.Promise(
(resolve, reject) => {
if (lastNLines) {
try {
shepherd._fs.access(fileLocation, shepherd.fs.constants.R_OK, (err) => {
if (err) {
shepherd.log(`error reading ${fileLocation}`);
shepherd.writeLog(`error reading ${fileLocation}`);
reject(`readDebugLog error: ${err}`);
} else {
shepherd.log(`reading ${fileLocation}`);
shepherd._fs.readFile(fileLocation, 'utf-8', (err, data) => {
if (err) {
shepherd.writeLog(`readDebugLog err: ${err}`);
shepherd.log(`readDebugLog err: ${err}`);
}
const lines = data.trim().split('\n');
const lastLine = lines.slice(lines.length - lastNLines, lines.length).join('\n');
resolve(lastLine);
});
}
});
} catch (e) {
reject(`readDebugLog error: ${e}`);
}
} else {
reject('readDebugLog error: lastNLines param is not provided!');
}
}
);
};
return shepherd;
};

44
routes/shepherd/dex/coind.js

@ -0,0 +1,44 @@
module.exports = (shepherd) => {
/*
* list native coind
* type:
* params:
*/
shepherd.get('/coind/list', (req, res, next) => {
const successObj = {
msg: 'success',
result: shepherd.nativeCoindList,
};
res.end(JSON.stringify(successObj));
});
shepherd.scanNativeCoindBins = () => {
let nativeCoindList = {};
// check if coind bins are present in agama
for (let key in shepherd.nativeCoind) {
nativeCoindList[key] = {
name: shepherd.nativeCoind[key].name,
port: shepherd.nativeCoind[key].port,
bin: shepherd.nativeCoind[key].bin,
bins: {
daemon: false,
cli: false,
},
};
if (shepherd.fs.existsSync(`${shepherd.coindRootDir}/${key}/${shepherd.nativeCoind[key].bin}d${shepherd.os.platform() === 'win32' ? '.exe' : ''}`)) {
nativeCoindList[key].bins.daemon = true;
}
if (shepherd.fs.existsSync(`${shepherd.coindRootDir}/${key}/${shepherd.nativeCoind[key].bin}-cli${shepherd.os.platform() === 'win32' ? '.exe' : ''}`)) {
nativeCoindList[key].bins.cli = true;
}
}
return nativeCoindList;
}
return shepherd;
};

165
routes/shepherd/downloadBins.js

@ -0,0 +1,165 @@
const remoteBinLocation = {
win32: 'https://artifacts.supernet.org/latest/windows/',
darwin: 'https://artifacts.supernet.org/latest/osx/',
linux: 'https://artifacts.supernet.org/latest/linux/',
};
const localBinLocation = {
win32: 'assets/bin/win64/',
darwin: 'assets/bin/osx/',
linux: 'assets/bin/linux64/',
};
const latestBins = {
win32: [
'komodo-cli.exe',
'komodod.exe',
'libcrypto-1_1.dll',
'libcurl-4.dll',
'libcurl.dll',
'libgcc_s_sjlj-1.dll',
'libnanomsg.dll',
'libssl-1_1.dll',
'libwinpthread-1.dll',
'nanomsg.dll',
'pthreadvc2.dll',
],
darwin: [
'komodo-cli',
'komodod',
'libgcc_s.1.dylib',
'libgomp.1.dylib',
'libnanomsg.5.0.0.dylib',
'libstdc++.6.dylib', // encode %2B
],
linux: [
'komodo-cli',
'komodod',
],
};
let binsToUpdate = [];
module.exports = (shepherd) => {
/*
* Check bins file size
* type:
* params:
*/
// TODO: promises
shepherd.get('/update/bins/check', (req, res, next) => {
const rootLocation = shepherd.path.join(__dirname, '../../');
const successObj = {
msg: 'success',
result: 'bins',
};
res.end(JSON.stringify(successObj));
const _os = shepherd.os.platform();
shepherd.log(`checking bins: ${_os}`);
shepherd.io.emit('patch', {
patch: {
type: 'bins-check',
status: 'progress',
message: `checking bins: ${_os}`,
},
});
// get list of bins/dlls that can be updated to the latest
for (let i = 0; i < latestBins[_os].length; i++) {
shepherd.remoteFileSize(remoteBinLocation[_os] + latestBins[_os][i], (err, remoteBinSize) => {
const localBinSize = shepherd.fs.statSync(rootLocation + localBinLocation[_os] + latestBins[_os][i]).size;
shepherd.log('remote url: ' + (remoteBinLocation[_os] + latestBins[_os][i]) + ' (' + remoteBinSize + ')');
shepherd.log('local file: ' + (rootLocation + localBinLocation[_os] + latestBins[_os][i]) + ' (' + localBinSize + ')');
if (remoteBinSize !== localBinSize) {
shepherd.log(`${latestBins[_os][i]} can be updated`);
binsToUpdate.push({
name: latestBins[_os][i],
rSize: remoteBinSize,
lSize: localBinSize,
});
}
if (i === latestBins[_os].length - 1) {
shepherd.io.emit('patch', {
patch: {
type: 'bins-check',
status: 'done',
fileList: binsToUpdate,
},
});
}
});
}
});
/*
* Update bins
* type:
* params:
*/
shepherd.get('/update/bins', (req, res, next) => {
const rootLocation = shepherd.path.join(__dirname, '../../');
const _os = shepherd.os.platform();
const successObj = {
msg: 'success',
result: {
filesCount: binsToUpdate.length,
list: binsToUpdate,
},
};
res.end(JSON.stringify(successObj));
for (let i = 0; i < binsToUpdate.length; i++) {
shepherd.downloadFile({
remoteFile: remoteBinLocation[_os] + binsToUpdate[i].name,
localFile: `${rootLocation}${localBinLocation[_os]}patch/${binsToUpdate[i].name}`,
onProgress: (received, total) => {
const percentage = (received * 100) / total;
if (percentage.toString().indexOf('.10') > -1) {
shepherd.io.emit('patch', {
msg: {
type: 'bins-update',
status: 'progress',
file: binsToUpdate[i].name,
bytesTotal: total,
bytesReceived: received,
},
});
// shepherd.log(`${binsToUpdate[i].name} ${percentage}% | ${received} bytes out of ${total} bytes.`);
}
}
})
.then(() => {
// verify that remote file is matching to DL'ed file
const localBinSize = shepherd.fs.statSync(`${rootLocation}${localBinLocation[_os]}patch/${binsToUpdate[i].name}`).size;
shepherd.log('compare dl file size');
if (localBinSize === binsToUpdate[i].rSize) {
shepherd.io.emit('patch', {
msg: {
type: 'bins-update',
file: binsToUpdate[i].name,
status: 'done',
},
});
shepherd.log(`file ${binsToUpdate[i].name} succesfully downloaded`);
} else {
shepherd.io.emit('patch', {
msg: {
type: 'bins-update',
file: binsToUpdate[i].name,
message: 'size mismatch',
},
});
shepherd.log(`error: ${binsToUpdate[i].name} file size doesnt match remote!`);
}
});
}
});
return shepherd;
};

154
routes/shepherd/downloadPatch.js

@ -0,0 +1,154 @@
module.exports = (shepherd) => {
/*
* DL app patch
* type: GET
* params: patchList
*/
shepherd.get('/update/patch', (req, res, next) => {
const successObj = {
msg: 'success',
result: 'dl started'
};
res.end(JSON.stringify(successObj));
shepherd.updateAgama();
});
shepherd.updateAgama = () => {
const rootLocation = shepherd.path.join(__dirname, '../../');
shepherd.downloadFile({
remoteFile: 'https://github.com/pbca26/dl-test/raw/master/patch.zip',
localFile: `${rootLocation}patch.zip`,
onProgress: (received, total) => {
const percentage = (received * 100) / total;
if (percentage.toString().indexOf('.10') > -1) {
shepherd.io.emit('patch', {
msg: {
status: 'progress',
type: 'ui',
progress: percentage,
bytesTotal: total,
bytesReceived: received,
},
});
//shepherd.log(`patch ${percentage}% | ${received} bytes out of ${total} bytes.`);
}
}
})
.then(() => {
shepherd.remoteFileSize('https://github.com/pbca26/dl-test/raw/master/patch.zip', (err, remotePatchSize) => {
// verify that remote file is matching to DL'ed file
const localPatchSize = shepherd.fs.statSync(`${rootLocation}patch.zip`).size;
shepherd.log('compare dl file size');
if (localPatchSize === remotePatchSize) {
const zip = new shepherd.AdmZip(`${rootLocation}patch.zip`);
shepherd.log('patch succesfully downloaded');
shepherd.log('extracting contents');
if (shepherd.appConfig.dev) {
if (!shepherd.fs.existsSync(`${rootLocation}/patch`)) {
shepherd.fs.mkdirSync(`${rootLocation}/patch`);
}
}
zip.extractAllTo(/*target path*/rootLocation + (shepherd.appConfig.dev ? '/patch' : ''), /*overwrite*/true);
// TODO: extract files in chunks
shepherd.io.emit('patch', {
msg: {
type: 'ui',
status: 'done',
},
});
shepherd.fs.unlinkSync(`${rootLocation}patch.zip`);
} else {
shepherd.io.emit('patch', {
msg: {
type: 'ui',
status: 'error',
message: 'size mismatch',
},
});
shepherd.log('patch file size doesnt match remote!');
}
});
});
}
/*
* check latest version
* type:
* params:
*/
shepherd.get('/update/patch/check', (req, res, next) => {
const rootLocation = shepherd.path.join(__dirname, '../../');
const options = {
url: 'https://github.com/pbca26/dl-test/raw/master/version',
method: 'GET',
};
shepherd.request(options, (error, response, body) => {
if (response &&
response.statusCode &&
response.statusCode === 200) {
const remoteVersion = body.split('\n');
const localVersionFile = shepherd.fs.readFileSync(`${rootLocation}version`, 'utf8');
let localVersion;
if (localVersionFile.indexOf('\r\n') > -1) {
localVersion = localVersionFile.split('\r\n');
} else {
localVersion = localVersionFile.split('\n');
}
if (remoteVersion[0] === localVersion[0]) {
const successObj = {
msg: 'success',
result: 'latest',
};
res.end(JSON.stringify(successObj));
} else {
const successObj = {
msg: 'success',
result: 'update',
version: {
local: localVersion[0],
remote: remoteVersion[0],
},
};
res.end(JSON.stringify(successObj));
}
} else {
res.end({
err: 'error getting update',
});
}
});
});
/*
* unpack zip
* type:
* params:
*/
shepherd.get('/unpack', (req, res, next) => {
const dlLocation = shepherd.path.join(__dirname, '../../');
const zip = new shepherd.AdmZip(`${dlLocation}patch.zip`);
zip.extractAllTo(/*target path*/ `${dlLocation}/patch/unpack`, /*overwrite*/true);
const successObj = {
msg: 'success',
result: 'unpack started',
};
res.end(JSON.stringify(successObj));
});
return shepherd;
};

49
routes/shepherd/downloadUtil.js

@ -0,0 +1,49 @@
module.exports = (shepherd) => {
/**
* Promise based download file method
*/
shepherd.downloadFile = (configuration) => {
return new shepherd.Promise((resolve, reject) => {
// Save variable to know progress
let receivedBytes = 0;
let totalBytes = 0;
let req = shepherd.request({
method: 'GET',
uri: configuration.remoteFile,
agentOptions: {
keepAlive: true,
keepAliveMsecs: 15000,
},
});
let out = shepherd.fs.createWriteStream(configuration.localFile);
req.pipe(out);
req.on('response', (data) => {
// Change the total bytes value to get progress later.
totalBytes = parseInt(data.headers['content-length']);
});
// Get progress if callback exists
if (configuration.hasOwnProperty('onProgress')) {
req.on('data', (chunk) => {
// Update the received bytes
receivedBytes += chunk.length;
configuration.onProgress(receivedBytes, totalBytes);
});
} else {
req.on('data', (chunk) => {
// Update the received bytes
receivedBytes += chunk.length;
});
}
req.on('end', () => {
resolve();
});
});
}
return shepherd;
};

127
routes/shepherd/downloadZcparams.js

@ -0,0 +1,127 @@
module.exports = (shepherd) => {
shepherd.zcashParamsDownloadLinks = {
'agama.komodoplatform.com': {
proving: 'https://agama.komodoplatform.com/file/supernet/sprout-proving.key',
verifying: 'https://agama.komodoplatform.com/file/supernet/sprout-verifying.key',
},
'agama-yq0ysrdtr.stackpathdns.com': {
proving: 'http://agama-yq0ysrdtr.stackpathdns.com/file/supernet/sprout-proving.key',
verifying: 'http://agama-yq0ysrdtr.stackpathdns.com/file/supernet/sprout-verifying.key',
},
'zcash.dl.mercerweiss.com': {
proving: 'https://zcash.dl.mercerweiss.com/sprout-proving.key',
verifying: 'https://zcash.dl.mercerweiss.com/sprout-verifying.key',
},
};
shepherd.zcashParamsExist = () => {
let _checkList = {
rootDir: shepherd._fs.existsSync(shepherd.zcashParamsDir),
provingKey: shepherd._fs.existsSync(`${shepherd.zcashParamsDir}/sprout-proving.key`),
provingKeySize: false,
verifyingKey: shepherd._fs.existsSync(`${shepherd.zcashParamsDir}/sprout-verifying.key`),
verifyingKeySize: false,
errors: false,
};
if (_checkList.rootDir &&
_checkList.provingKey ||
_checkList.verifyingKey) {
// verify each key size
const _provingKeySize = _checkList.provingKey ? shepherd.fs.lstatSync(`${shepherd.zcashParamsDir}/sprout-proving.key`) : 0;
const _verifyingKeySize = _checkList.verifyingKey ? shepherd.fs.lstatSync(`${shepherd.zcashParamsDir}/sprout-verifying.key`) : 0;
if (Number(_provingKeySize.size) === 910173851) { // bytes
_checkList.provingKeySize = true;
}
if (Number(_verifyingKeySize.size) === 1449) {
_checkList.verifyingKeySize = true;
}
shepherd.log('zcashparams exist');
} else {
shepherd.log('zcashparams doesnt exist');
}
if (!_checkList.rootDir ||
!_checkList.provingKey ||
!_checkList.verifyingKey ||
!_checkList.provingKeySize ||
!_checkList.verifyingKeySize) {
_checkList.errors = true;
}
return _checkList;
}
/*
* Update bins
* type:
* params:
*/
shepherd.get('/zcparamsdl', (req, res, next) => {
// const dlLocation = shepherd.zcashParamsDir + '/test';
const dlLocation = shepherd.zcashParamsDir;
const dlOption = req.query.dloption;
const successObj = {
msg: 'success',
result: 'zcash params dl started',
};
res.end(JSON.stringify(successObj));
for (let key in shepherd.zcashParamsDownloadLinks[dlOption]) {
shepherd.downloadFile({
remoteFile: shepherd.zcashParamsDownloadLinks[dlOption][key],
localFile: `${dlLocation}/sprout-${key}.key`,
onProgress: (received, total) => {
const percentage = (received * 100) / total;
if (percentage.toString().indexOf('.10') > -1) {
shepherd.io.emit('zcparams', {
msg: {
type: 'zcpdownload',
status: 'progress',
file: key,
bytesTotal: total,
bytesReceived: received,
progress: percentage,
},
});
// shepherd.log(`${key} ${percentage}% | ${received} bytes out of ${total} bytes.`);
}
}
})
.then(() => {
const checkZcashParams = shepherd.zcashParamsExist();
shepherd.log(`${key} dl done`);
if (checkZcashParams.error) {
shepherd.io.emit('zcparams', {
msg: {
type: 'zcpdownload',
file: key,
status: 'error',
message: 'size mismatch',
progress: 100,
},
});
} else {
shepherd.io.emit('zcparams', {
msg: {
type: 'zcpdownload',
file: key,
progress: 100,
status: 'done',
},
});
shepherd.log(`file ${key} succesfully downloaded`);
}
});
}
});
return shepherd;
};

38
routes/shepherd/electrum/auth.js

@ -0,0 +1,38 @@
module.exports = (shepherd) => {
shepherd.get('/electrum/login', (req, res, next) => {
for (let key in shepherd.electrumServers) {
const _abbr = shepherd.electrumServers[key].abbr;
const { priv, pub } = shepherd.seedToWif(req.query.seed, shepherd.findNetworkObj(_abbr), req.query.iguana);
shepherd.electrumKeys[_abbr] = {
priv,
pub,
};
}
shepherd.electrumCoins.auth = true;
shepherd.log(JSON.stringify(shepherd.electrumKeys, null, '\t'), true);
const successObj = {
msg: 'success',
result: 'true',
};
res.end(JSON.stringify(successObj));
});
shepherd.get('/electrum/dev/logout', (req, res, next) => {
shepherd.electrumCoins.auth = false;
shepherd.electrumKeys = {};
const successObj = {
msg: 'success',
result: 'true',
};
res.end(JSON.stringify(successObj));
});
return shepherd;
};

145
routes/shepherd/electrum/balance.js

@ -0,0 +1,145 @@
module.exports = (shepherd) => {
shepherd.get('/electrum/getbalance', (req, res, next) => {
const network = req.query.network || shepherd.findNetworkObj(req.query.coin);
const ecl = new shepherd.electrumJSCore(shepherd.electrumServers[network].port, shepherd.electrumServers[network].address, shepherd.electrumServers[network].proto); // tcp or tls
shepherd.log('electrum getbalance =>', true);
ecl.connect();
ecl.blockchainAddressGetBalance(req.query.address)
.then((json) => {
if (json &&
json.hasOwnProperty('confirmed') &&
json.hasOwnProperty('unconfirmed')) {
if (network === 'komodo') {
ecl.connect();
ecl.blockchainAddressListunspent(req.query.address)
.then((utxoList) => {
if (utxoList &&
utxoList.length) {
// filter out < 10 KMD amounts
let _utxo = [];
for (let i = 0; i < utxoList.length; i++) {
shepherd.log(`utxo ${utxoList[i]['tx_hash']} sats ${utxoList[i].value} value ${Number(utxoList[i].value) * 0.00000001}`, true);
if (Number(utxoList[i].value) * 0.00000001 >= 10) {
_utxo.push(utxoList[i]);
}
}
shepherd.log('filtered utxo list =>', true);
shepherd.log(_utxo, true);
if (_utxo &&
_utxo.length) {
let interestTotal = 0;
shepherd.Promise.all(_utxo.map((_utxoItem, index) => {
return new shepherd.Promise((resolve, reject) => {
ecl.blockchainTransactionGet(_utxoItem['tx_hash'])
.then((_rawtxJSON) => {
shepherd.log('electrum gettransaction ==>', true);
shepherd.log((index + ' | ' + (_rawtxJSON.length - 1)), true);
shepherd.log(_rawtxJSON, true);
// decode tx
const _network = shepherd.getNetworkData(network);
const decodedTx = shepherd.electrumJSTxDecoder(_rawtxJSON, _network);
if (decodedTx &&
decodedTx.format &&
decodedTx.format.locktime > 0) {
interestTotal += shepherd.kmdCalcInterest(decodedTx.format.locktime, _utxoItem.value);
}
shepherd.log('decoded tx =>', true);
shepherd.log(decodedTx, true);
resolve(true);
});
});
}))
.then(promiseResult => {
ecl.close();
const successObj = {
msg: 'success',
result: {
balance: Number((0.00000001 * json.confirmed).toFixed(8)),
unconfirmed: Number((0.00000001 * json.unconfirmed).toFixed(8)),
unconfirmedSats: json.unconfirmed,
balanceSats: json.confirmed,
interest: Number(interestTotal.toFixed(8)),
interestSats: Math.floor(interestTotal * 100000000),
total: interestTotal > 0 ? Number((0.00000001 * json.confirmed + interestTotal).toFixed(8)) : 0,
totalSats: interestTotal > 0 ?json.confirmed + Math.floor(interestTotal * 100000000) : 0,
},
};
res.end(JSON.stringify(successObj));
});
} else {
const successObj = {
msg: 'success',
result: {
balance: Number((0.00000001 * json.confirmed).toFixed(8)),
unconfirmed: Number((0.00000001 * json.unconfirmed).toFixed(8)),
unconfirmedSats: json.unconfirmed,
balanceSats: json.confirmed,
interest: 0,
interestSats: 0,
total: 0,
totalSats: 0,
},
};
res.end(JSON.stringify(successObj));
}
} else {
const successObj = {
msg: 'success',
result: {
balance: Number((0.00000001 * json.confirmed).toFixed(8)),
unconfirmed: Number((0.00000001 * json.unconfirmed).toFixed(8)),
unconfirmedSats: json.unconfirmed,
balanceSats: json.confirmed,
interest: 0,
interestSats: 0,
total: 0,
totalSats: 0,
},
};
res.end(JSON.stringify(successObj));
}
});
} else {
ecl.close();
shepherd.log('electrum getbalance ==>', true);
shepherd.log(json, true);
const successObj = {
msg: 'success',
result: {
balance: Number((0.00000001 * json.confirmed).toFixed(8)),
unconfirmed: Number((0.00000001 * json.unconfirmed).toFixed(8)),
unconfirmedSats: json.unconfirmed,
balanceSats: json.confirmed,
},
};
res.end(JSON.stringify(successObj));
}
} else {
const successObj = {
msg: 'error',
result: shepherd.CONNECTION_ERROR_OR_INCOMPLETE_DATA,
};
res.end(JSON.stringify(successObj));
}
});
});
return shepherd;
};

59
routes/shepherd/electrum/block.js

@ -0,0 +1,59 @@
module.exports = (shepherd) => {
shepherd.get('/electrum/getblockinfo', (req, res, next) => {
shepherd.electrumGetBlockInfo(req.query.height, req.query.network)
.then((json) => {
const successObj = {
msg: 'success',
result: json,
};
res.end(JSON.stringify(successObj));
});
});
shepherd.electrumGetBlockInfo = (height, network) => {
return new shepherd.Promise((resolve, reject) => {
const ecl = new shepherd.electrumJSCore(shepherd.electrumServers[network].port, shepherd.electrumServers[network].address, shepherd.electrumServers[network].proto); // tcp or tls
ecl.connect();
ecl.blockchainBlockGetHeader(height)
.then((json) => {
ecl.close();
shepherd.log('electrum getblockinfo ==>', true);
shepherd.log(json, true);
resolve(json);
});
});
}
shepherd.get('/electrum/getcurrentblock', (req, res, next) => {
shepherd.electrumGetCurrentBlock(req.query.network)
.then((json) => {
const successObj = {
msg: 'success',
result: json,
};
res.end(JSON.stringify(successObj));
});
});
shepherd.electrumGetCurrentBlock = (network) => {
return new shepherd.Promise((resolve, reject) => {
const ecl = new shepherd.electrumJSCore(shepherd.electrumServers[network].port, shepherd.electrumServers[network].address, shepherd.electrumServers[network].proto); // tcp or tls
ecl.connect();
ecl.blockchainNumblocksSubscribe()
.then((json) => {
ecl.close();
shepherd.log('electrum currentblock ==>', true);
shepherd.log(json, true);
resolve(json);
});
});
}
return shepherd;
};

69
routes/shepherd/electrum/coins.js

@ -0,0 +1,69 @@
module.exports = (shepherd) => {
shepherd.findCoinName = (network) => {
for (let key in shepherd.electrumServers) {
if (key === network) {
return shepherd.electrumServers[key].abbr;
}
}
}
shepherd.addElectrumCoin = (coin) => {
for (let key in shepherd.electrumServers) {
if (shepherd.electrumServers[key].abbr === coin) {
shepherd.electrumCoins[coin] = {
name: key,
abbr: coin,
server: {
ip: shepherd.electrumServers[key].address,
port: shepherd.electrumServers[key].port,
},
serverList: shepherd.electrumServers[key].serverList ? shepherd.electrumServers[key].serverList : 'none',
txfee: 'calculated' /*shepherd.electrumServers[key].txfee*/,
};
return true;
}
}
}
shepherd.get('/electrum/coins/remove', (req, res, next) => {
delete shepherd.electrumCoins[req.query.coin];
const successObj = {
msg: 'success',
result,
};
res.end(JSON.stringify(successObj));
});
shepherd.get('/electrum/coins/add', (req, res, next) => {
const result = shepherd.addElectrumCoin(req.query.coin);
const successObj = {
msg: 'success',
result,
};
res.end(JSON.stringify(successObj));
});
shepherd.get('/electrum/coins', (req, res, next) => {
let _electrumCoins = JSON.parse(JSON.stringify(shepherd.electrumCoins)); // deep cloning
for (let key in _electrumCoins) {
if (shepherd.electrumKeys[key]) {
_electrumCoins[key].pub = shepherd.electrumKeys[key].pub;
}
}
const successObj = {
msg: 'success',
result: _electrumCoins,
};
res.end(JSON.stringify(successObj));
});
return shepherd;
};

411
routes/shepherd/electrum/createtx.js

@ -0,0 +1,411 @@
module.exports = (shepherd) => {
// single sig
shepherd.buildSignedTx = (sendTo, changeAddress, wif, network, utxo, changeValue, spendValue) => {
let key = shepherd.bitcoinJS.ECPair.fromWIF(wif, shepherd.getNetworkData(network));
let tx = new shepherd.bitcoinJS.TransactionBuilder(shepherd.getNetworkData(network));
shepherd.log('buildSignedTx');
// console.log(`buildSignedTx priv key ${wif}`);
shepherd.log(`buildSignedTx pub key ${key.getAddress().toString()}`, true);
// console.log('buildSignedTx std tx fee ' + shepherd.electrumServers[network].txfee);
for (let i = 0; i < utxo.length; i++) {
tx.addInput(utxo[i].txid, utxo[i].vout);
}
tx.addOutput(sendTo, Number(spendValue));
if (changeValue > 0) {
tx.addOutput(changeAddress, Number(changeValue));
}
if (network === 'komodo' ||
network === 'KMD') {
const _locktime = Math.floor(Date.now() / 1000) - 777;
tx.setLockTime(_locktime);
shepherd.log(`kmd tx locktime set to ${_locktime}`, true);
}
shepherd.log('buildSignedTx unsigned tx data vin', true);
shepherd.log(tx.tx.ins, true);
shepherd.log('buildSignedTx unsigned tx data vout', true);
shepherd.log(tx.tx.outs, true);
shepherd.log('buildSignedTx unsigned tx data', true);
shepherd.log(tx, true);
for (let i = 0; i < utxo.length; i++) {
tx.sign(i, key);
}
const rawtx = tx.build().toHex();
shepherd.log('buildSignedTx signed tx hex', true);
shepherd.log(rawtx, true);
return rawtx;
}
shepherd.maxSpendBalance = (utxoList, fee) => {
let maxSpendBalance = 0;
for (let i = 0; i < utxoList.length; i++) {
maxSpendBalance += Number(utxoList[i].value);
}
if (fee) {
return Number(maxSpendBalance) - Number(fee);
} else {
return maxSpendBalance;
}
}
shepherd.get('/electrum/createrawtx', (req, res, next) => {
// txid 64 char
const network = req.query.network || shepherd.findNetworkObj(req.query.coin);
const ecl = new shepherd.electrumJSCore(shepherd.electrumServers[network].port, shepherd.electrumServers[network].address, shepherd.electrumServers[network].proto); // tcp or tls
const outputAddress = req.query.address;
const changeAddress = req.query.change;
const value = Number(req.query.value);
const push = req.query.push;
const fee = shepherd.electrumServers[network].txfee;
let wif = req.query.wif;
if (req.query.gui) {
wif = shepherd.electrumKeys[req.query.coin].priv;
}
shepherd.log('electrum createrawtx =>', true);
ecl.connect();
shepherd.listunspent(ecl, changeAddress, network, true, true)
.then((utxoList) => {
ecl.close();
if (utxoList &&
utxoList.length) {
let utxoListFormatted = [];
let totalInterest = 0;
let totalInterestUTXOCount = 0;
let interestClaimThreshold = 200;
let utxoVerified = true;
for (let i = 0; i < utxoList.length; i++) {
if (network === 'komodo') {
utxoListFormatted.push({
txid: utxoList[i].txid,
vout: utxoList[i].vout,
value: Number(utxoList[i].amountSats),
interestSats: Number(utxoList[i].interestSats),
verified: utxoList[i].verified ? utxoList[i].verified : false,
});
} else {
utxoListFormatted.push({
txid: utxoList[i].txid,
vout: utxoList[i].vout,
value: Number(utxoList[i].amountSats),
verified: utxoList[i].verified ? utxoList[i].verified : false,
});
}
}
shepherd.log('electrum listunspent unformatted ==>', true);
shepherd.log(utxoList, true);
shepherd.log('electrum listunspent formatted ==>', true);
shepherd.log(utxoListFormatted, true);
const _maxSpendBalance = Number(shepherd.maxSpendBalance(utxoListFormatted));
let targets = [{
address: outputAddress,
value: value > _maxSpendBalance ? _maxSpendBalance : value,
}];
shepherd.log('targets =>', true);
shepherd.log(targets, true);
const feeRate = 20; // sats/byte
// default coin selection algo blackjack with fallback to accumulative
// make a first run, calc approx tx fee
// if ins and outs are empty reduce max spend by txfee
let { inputs, outputs, fee } = shepherd.coinSelect(utxoListFormatted, targets, feeRate);
shepherd.log('coinselect res =>', true);
shepherd.log('coinselect inputs =>', true);
shepherd.log(inputs, true);
shepherd.log('coinselect outputs =>', true);
shepherd.log(outputs, true);
shepherd.log('coinselect calculated fee =>', true);
shepherd.log(fee, true);
if (!inputs &&
!outputs) {
targets[0].value = targets[0].value - shepherd.electrumServers[network].txfee;
shepherd.log('second run', true);
shepherd.log('coinselect adjusted targets =>', true);
shepherd.log(targets, true);
const secondRun = shepherd.coinSelect(utxoListFormatted, targets, feeRate);
inputs = secondRun.inputs;
outputs = secondRun.outputs;
fee = secondRun.fee;
shepherd.log('coinselect inputs =>', true);
shepherd.log(inputs, true);
shepherd.log('coinselect outputs =>', true);
shepherd.log(outputs, true);
shepherd.log('coinselect fee =>', true);
shepherd.log(fee, true);
}
let _change = 0;
if (outputs &&
outputs.length === 2) {
_change = outputs[1].value;
}
// check if any outputs are unverified
if (inputs &&
inputs.length) {
for (let i = 0; i < inputs.length; i++) {
if (!inputs[i].verified) {
utxoVerified = false;
break;
}
}
for (let i = 0; i < inputs.length; i++) {
if (Number(inputs[i].interestSats) > interestClaimThreshold) {
totalInterest += Number(inputs[i].interestSats);
totalInterestUTXOCount++;
}
}
}
const _maxSpend = shepherd.maxSpendBalance(utxoListFormatted);
if (value > _maxSpend) {
const successObj = {
msg: 'error',
result: `Spend value is too large. Max available amount is ${Number((_maxSpend * 0.00000001.toFixed(8)))}`,
};
res.end(JSON.stringify(successObj));
} else {
shepherd.log(`maxspend ${_maxSpend} (${_maxSpend * 0.00000001})`, true);
shepherd.log(`value ${value}`, true);
shepherd.log(`sendto ${outputAddress} amount ${value} (${value * 0.00000001})`, true);
shepherd.log(`changeto ${changeAddress} amount ${_change} (${_change * 0.00000001})`, true);
// account for KMD interest
if (network === 'komodo' &&
totalInterest > 0) {
// account for extra vout
const _feeOverhead = outputs.length === 1 ? shepherd.estimateTxSize(0, 1) * feeRate : 0;
shepherd.log(`max interest to claim ${totalInterest} (${totalInterest * 0.00000001})`, true);
shepherd.log(`estimated fee overhead ${_feeOverhead}`, true);
shepherd.log(`current change amount ${_change} (${_change * 0.00000001}), boosted change amount ${_change + (totalInterest - _feeOverhead)} (${(_change + (totalInterest - _feeOverhead)) * 0.00000001})`, true);
_change = _change + (totalInterest - _feeOverhead);
}
if (!inputs &&
!outputs) {
const successObj = {
msg: 'error',
result: 'Can\'t find best fit utxo. Try lower amount.',
};
res.end(JSON.stringify(successObj));
} else {
let vinSum = 0;
for (let i = 0; i < inputs.length; i++) {
vinSum += inputs[i].value;
}
const _estimatedFee = vinSum - outputs[0].value - _change;
shepherd.log(`vin sum ${vinSum} (${vinSum * 0.00000001})`, true);
shepherd.log(`estimatedFee ${_estimatedFee} (${_estimatedFee * 0.00000001})`, true);
const _rawtx = shepherd.buildSignedTx(
outputAddress,
changeAddress,
wif,
network,
inputs,
_change,
value
);
if (!push ||
push === 'false') {
const successObj = {
msg: 'success',
result: {
utxoSet: inputs,
change: _change,
// wif,
fee,
value,
outputAddress,
changeAddress,
network,
rawtx: _rawtx,
utxoVerified,
},
};
res.end(JSON.stringify(successObj));
} else {
const ecl = new shepherd.electrumJSCore(shepherd.electrumServers[network].port, shepherd.electrumServers[network].address, shepherd.electrumServers[network].proto); // tcp or tls
ecl.connect();
ecl.blockchainTransactionBroadcast(_rawtx)
.then((txid) => {
ecl.close();
if (txid &&
txid.indexOf('bad-txns-inputs-spent') > -1) {
const successObj = {
msg: 'error',
result: 'Bad transaction inputs spent',
raw: {
utxoSet: inputs,
change: _change,
fee,
value,
outputAddress,
changeAddress,
network,
rawtx: _rawtx,
txid,
utxoVerified,
},
};
res.end(JSON.stringify(successObj));
} else {
if (txid &&
txid.length === 64) {
if (txid.indexOf('bad-txns-in-belowout') > -1) {
const successObj = {
msg: 'error',
result: 'Bad transaction inputs spent',
raw: {
utxoSet: inputs,
change: _change,
fee,
value,
outputAddress,
changeAddress,
network,
rawtx: _rawtx,
txid,
utxoVerified,
},
};
res.end(JSON.stringify(successObj));
} else {
const successObj = {
msg: 'success',
result: {
utxoSet: inputs,
change: _change,
fee,
// wif,
value,
outputAddress,
changeAddress,
network,
rawtx: _rawtx,
txid,
utxoVerified,
},
};
res.end(JSON.stringify(successObj));
}
} else {
if (txid &&
txid.indexOf('bad-txns-in-belowout') > -1) {
const successObj = {
msg: 'error',
result: 'Bad transaction inputs spent',
raw: {
utxoSet: inputs,
change: _change,
fee,
value,
outputAddress,
changeAddress,
network,
rawtx: _rawtx,
txid,
utxoVerified,
},
};
res.end(JSON.stringify(successObj));
} else {
const successObj = {
msg: 'error',
result: 'Can\'t broadcast transaction',
raw: {
utxoSet: inputs,
change: _change,
fee,
value,
outputAddress,
changeAddress,
network,
rawtx: _rawtx,
txid,
utxoVerified,
},
};
res.end(JSON.stringify(successObj));
}
}
}
});
}
}
}
} else {
const successObj = {
msg: 'error',
result: utxoList,
};
res.end(JSON.stringify(successObj));
}
});
});
shepherd.get('/electrum/pushtx', (req, res, next) => {
const rawtx = req.query.rawtx;
const ecl = new shepherd.electrumJSCore(shepherd.electrumServers[req.query.network].port, shepherd.electrumServers[req.query.network].address, shepherd.electrumServers[req.query.network].proto); // tcp or tls
ecl.connect();
ecl.blockchainTransactionBroadcast(rawtx)
.then((json) => {
ecl.close();
shepherd.log('electrum pushtx ==>', true);
shepherd.log(json, true);
const successObj = {
msg: 'success',
result: json,
};
res.end(JSON.stringify(successObj));
});
});
return shepherd;
};

26
routes/shepherd/electrum/estimate.js

@ -0,0 +1,26 @@
module.exports = (shepherd) => {
shepherd.get('/electrum/estimatefee', (req, res, next) => {
const ecl = new shepherd.electrumJSCore(shepherd.electrumServers[req.query.network].port, shepherd.electrumServers[req.query.network].address, shepherd.electrumServers[req.query.network].proto); // tcp or tls
ecl.connect();
ecl.blockchainEstimatefee(req.query.blocks)
.then((json) => {
ecl.close();
shepherd.log('electrum estimatefee ==>', true);
const successObj = {
msg: 'success',
result: json,
};
res.end(JSON.stringify(successObj));
});
});
shepherd.estimateTxSize = (numVins, numOuts) => {
// in x 180 + out x 34 + 10 plus or minus in
return numVins * 180 + numOuts * 34 + 11;
}
return shepherd;
};

37
routes/shepherd/electrum/interest.js

@ -0,0 +1,37 @@
module.exports = (shepherd) => {
shepherd.kmdCalcInterest = (locktime, value) => { // value in sats
const timestampDiff = Math.floor(Date.now() / 1000) - locktime - 777;
const hoursPassed = Math.floor(timestampDiff / 3600);
const minutesPassed = Math.floor((timestampDiff - (hoursPassed * 3600)) / 60);
const secondsPassed = timestampDiff - (hoursPassed * 3600) - (minutesPassed * 60);
let timestampDiffMinutes = timestampDiff / 60;
let interest = 0;
shepherd.log('kmdCalcInterest', true);
shepherd.log(`locktime ${locktime}`, true);
shepherd.log(`minutes converted ${timestampDiffMinutes}`, true);
shepherd.log(`passed ${hoursPassed}h ${minutesPassed}m ${secondsPassed}s`, true);
// calc interest
if (timestampDiffMinutes >= 60) {
if (timestampDiffMinutes > 365 * 24 * 60) {
timestampDiffMinutes = 365 * 24 * 60;
}
timestampDiffMinutes -= 59;
shepherd.log(`minutes if statement ${timestampDiffMinutes}`, true);
// TODO: check if interest is > 5% yr
// calc ytd and 5% for 1 yr
// const hoursInOneYear = 365 * 24;
// const hoursDiff = hoursInOneYear - hoursPassed;
interest = ((Number(value) * 0.00000001) / 10512000) * timestampDiffMinutes;
shepherd.log(`interest ${interest}`, true);
}
return interest;
}
return shepherd;
};

128
routes/shepherd/electrum/keys.js

@ -0,0 +1,128 @@
module.exports = (shepherd) => {
shepherd.seedToWif = (seed, network, iguana) => {
const bytes = shepherd.sha256(seed, { asBytes: true });
if (iguana) {
bytes[0] &= 248;
bytes[31] &= 127;
bytes[31] |= 64;
}
const toHexString = (byteArray) => {
return Array.from(byteArray, (byte) => {
return ('0' + (byte & 0xFF).toString(16)).slice(-2);
}).join('');
}
const hex = toHexString(bytes);
const key = new shepherd.CoinKey(new Buffer(hex, 'hex'), {
private: shepherd.getNetworkData(network).wif,
public: shepherd.getNetworkData(network).pubKeyHash,
});
key.compressed = true;
shepherd.log(`seedtowif priv key ${key.privateWif}`, true);
shepherd.log(`seedtowif pub key ${key.publicAddress}`, true);
return {
priv: key.privateWif,
pub: key.publicAddress,
};
}
shepherd.get('/electrum/seedtowif', (req, res, next) => {
const keys = shepherd.seedToWif(req.query.seed, req.query.network, req.query.iguana);
const successObj = {
msg: 'success',
result: {
keys,
},
};
res.end(JSON.stringify(successObj));
});
shepherd.get('/electrum/keys', (req, res, next) => {
let _matchingKeyPairs = 0;
let _electrumKeys = {};
for (let key in shepherd.electrumServers) {
const _abbr = shepherd.electrumServers[key].abbr;
const { priv, pub } = shepherd.seedToWif(req.query.seed, shepherd.findNetworkObj(_abbr), req.query.iguana);
if (shepherd.electrumKeys[_abbr].pub === pub &&
shepherd.electrumKeys[_abbr].priv === priv) {
_matchingKeyPairs++;
}
}
if (req.query.active) {
_electrumKeys = JSON.parse(JSON.stringify(shepherd.electrumKeys));
for (let key in _electrumKeys) {
if (!shepherd.electrumCoins[key]) {
delete _electrumKeys[key];
}
}
} else {
_electrumKeys = shepherd.electrumKeys;
}
shepherd.log(JSON.stringify(_electrumKeys, null, '\t'), true);
const successObj = {
msg: 'success',
result: _matchingKeyPairs === Object.keys(shepherd.electrumKeys).length ? _electrumKeys : false,
};
res.end(JSON.stringify(successObj));
});
// spv v2
/*shepherd.get('/electrum/bip39/seed', (req, res, next) => {
// TODO
const bip39 = require('bip39'); // npm i -S bip39
const crypto = require('crypto');
// what you describe as 'seed'
const randomBytes = crypto.randomBytes(16); // 128 bits is enough
// your 12 word phrase
const mnemonic = bip39.entropyToMnemonic(randomBytes.toString('hex'));
// what is accurately described as the wallet seed
// var seed = bip39.mnemonicToSeed(mnemonic) // you'll use this in #3 below
const seed = bip39.mnemonicToSeed(req.query.seed);
console.log(seed);
const successObj = {
msg: 'success',
result: {
servers: shepherd.electrumServers,
},
};
res.end(JSON.stringify(successObj));
console.log(shepherd.bitcoinJS.networks.komodo);
const hdMaster = shepherd.bitcoinJS.HDNode.fromSeedBuffer(seed, shepherd.electrumJSNetworks.komodo); // seed from above
const key1 = hdMaster.derivePath('m/0');
const key2 = hdMaster.derivePath('m/1');
console.log(hdMaster);
console.log(key1.keyPair.toWIF());
console.log(key1.keyPair.getAddress());
console.log(key2.keyPair.toWIF());
const hdnode = shepherd.bitcoinJS.HDNode.fromSeedBuffer(seed, shepherd.electrumJSNetworks.komodo).deriveHardened(0).derive(0).derive(1);
console.log(`address: ${hdnode.getAddress()}`);
console.log(`priv (WIF): ${hdnode.keyPair.toWIF()}`);
});*/
return shepherd;
};

194
routes/shepherd/electrum/listunspent.js

@ -0,0 +1,194 @@
module.exports = (shepherd) => {
shepherd.listunspent = (ecl, address, network, full, verify) => {
let _atLeastOneDecodeTxFailed = false;
if (full) {
return new shepherd.Promise((resolve, reject) => {
ecl.connect();
ecl.blockchainAddressListunspent(address)
.then((_utxoJSON) => {
if (_utxoJSON &&
_utxoJSON.length) {
let formattedUtxoList = [];
let _utxo = [];
ecl.blockchainNumblocksSubscribe()
.then((currentHeight) => {
if (currentHeight &&
Number(currentHeight) > 0) {
// filter out unconfirmed utxos
for (let i = 0; i < _utxoJSON.length; i++) {
if (Number(currentHeight) - Number(_utxoJSON[i].height) !== 0) {
_utxo.push(_utxoJSON[i]);
}
}
if (!_utxo.length) { // no confirmed utxo
resolve('no valid utxo');
} else {
shepherd.Promise.all(_utxo.map((_utxoItem, index) => {
return new shepherd.Promise((resolve, reject) => {
ecl.blockchainTransactionGet(_utxoItem['tx_hash'])
.then((_rawtxJSON) => {
shepherd.log('electrum gettransaction ==>', true);
shepherd.log(index + ' | ' + (_rawtxJSON.length - 1), true);
shepherd.log(_rawtxJSON, true);
// decode tx
const _network = shepherd.getNetworkData(network);
const decodedTx = shepherd.electrumJSTxDecoder(_rawtxJSON, _network);
shepherd.log('decoded tx =>', true);
shepherd.log(decodedTx, true);
if (!decodedTx) {
_atLeastOneDecodeTxFailed = true;
resolve('cant decode tx');
} else {
if (network === 'komodo') {
let interest = 0;
if (Number(_utxoItem.value) * 0.00000001 >= 10 &&
decodedTx.format.locktime > 0) {
interest = shepherd.kmdCalcInterest(decodedTx.format.locktime, _utxoItem.value);
}
let _resolveObj = {
txid: _utxoItem['tx_hash'],
vout: _utxoItem['tx_pos'],
address,
amount: Number(_utxoItem.value) * 0.00000001,
amountSats: _utxoItem.value,
interest: interest,
interestSats: Math.floor(interest * 100000000),
confirmations: currentHeight - _utxoItem.height,
spendable: true,
verified: false,
};
// merkle root verification agains another electrum server
if (verify) {
shepherd.verifyMerkleByCoin(shepherd.findCoinName(network), _utxoItem['tx_hash'], _utxoItem.height)
.then((verifyMerkleRes) => {
if (verifyMerkleRes && verifyMerkleRes === shepherd.CONNECTION_ERROR_OR_INCOMPLETE_DATA) {
verifyMerkleRes = false;
}
_resolveObj.verified = verifyMerkleRes;
resolve(_resolveObj);
});
} else {
resolve(_resolveObj);
}
} else {
let _resolveObj = {
txid: _utxoItem['tx_hash'],
vout: _utxoItem['tx_pos'],
address,
amount: Number(_utxoItem.value) * 0.00000001,
amountSats: _utxoItem.value,
confirmations: currentHeight - _utxoItem.height,
spendable: true,
verified: false,
};
// merkle root verification agains another electrum server
if (verify) {
shepherd.verifyMerkleByCoin(shepherd.findCoinName(network), _utxoItem['tx_hash'], _utxoItem.height)
.then((verifyMerkleRes) => {
if (verifyMerkleRes &&
verifyMerkleRes === shepherd.CONNECTION_ERROR_OR_INCOMPLETE_DATA) {
verifyMerkleRes = false;
}
_resolveObj.verified = verifyMerkleRes;
resolve(_resolveObj);
});
} else {
resolve(_resolveObj);
}
}
}
});
});
}))
.then(promiseResult => {
ecl.close();
if (!_atLeastOneDecodeTxFailed) {
shepherd.log(promiseResult, true);
resolve(promiseResult);
} else {
shepherd.log('listunspent error, cant decode tx(s)', true);
resolve('decode error');
}
});
}
} else {
resolve('cant get current height');
}
});
} else {
ecl.close();
resolve(shepherd.CONNECTION_ERROR_OR_INCOMPLETE_DATA);
}
});
});
} else {
return new shepherd.Promise((resolve, reject) => {
ecl.connect();
ecl.blockchainAddressListunspent(address)
.then((json) => {
ecl.close();
if (json &&
json.length) {
resolve(json);
} else {
resolve(shepherd.CONNECTION_ERROR_OR_INCOMPLETE_DATA);
}
});
});
}
}
shepherd.get('/electrum/listunspent', (req, res, next) => {
const network = req.query.network || shepherd.findNetworkObj(req.query.coin);
const ecl = new shepherd.electrumJSCore(shepherd.electrumServers[network].port, shepherd.electrumServers[network].address, shepherd.electrumServers[network].proto); // tcp or tls
if (req.query.full &&
req.query.full === 'true') {
shepherd.listunspent(
ecl,
req.query.address,
network,
true,
req.query.verify
).then((listunspent) => {
shepherd.log('electrum listunspent ==>', true);
const successObj = {
msg: 'success',
result: listunspent,
};
res.end(JSON.stringify(successObj));
});
} else {
shepherd.listunspent(ecl, req.query.address, network)
.then((listunspent) => {
ecl.close();
shepherd.log('electrum listunspent ==>', true);
const successObj = {
msg: 'success',
result: listunspent,
};
res.end(JSON.stringify(successObj));
});
}
});
return shepherd;
};

145
routes/shepherd/electrum/merkle.js

@ -0,0 +1,145 @@
module.exports = (shepherd) => {
// get merkle root
shepherd.getMerkleRoot = (txid, proof, pos) => {
const reverse = require('buffer-reverse');
let hash = txid;
let serialized;
const _sha256 = (data) => {
return shepherd.crypto.createHash('sha256').update(data).digest();
}
shepherd.log(`getMerkleRoot txid ${txid}`, true);
shepherd.log(`getMerkleRoot pos ${pos}`, true);
shepherd.log('getMerkleRoot proof', true);
shepherd.log(`getMerkleRoot ${proof}`, true);
for (i = 0; i < proof.length; i++) {
const _hashBuff = new Buffer(hash, 'hex');
const _proofBuff = new Buffer(proof[i], 'hex');
if ((pos & 1) == 0) {
serialized = Buffer.concat([reverse(_hashBuff), reverse(_proofBuff)]);
} else {
serialized = Buffer.concat([reverse(_proofBuff), reverse(_hashBuff)]);
}
hash = reverse(_sha256(_sha256(serialized))).toString('hex');
pos /= 2;
}
return hash;
}
shepherd.verifyMerkle = (txid, height, serverList, mainServer) => {
// select random server
const getRandomIntInclusive = (min, max) => {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min; // the maximum is inclusive and the minimum is inclusive
}
const _rnd = getRandomIntInclusive(0, serverList.length - 1);
const randomServer = serverList[_rnd];
const _randomServer = randomServer.split(':');
const _mainServer = mainServer.split(':');
let ecl = new shepherd.electrumJSCore(_mainServer[1], _mainServer[0], 'tcp'); // tcp or tls
return new shepherd.Promise((resolve, reject) => {
shepherd.log(`main server: ${mainServer}`, true);
shepherd.log(`verification server: ${randomServer}`, true);
ecl.connect();
ecl.blockchainTransactionGetMerkle(txid, height)
.then((merkleData) => {
if (merkleData &&
merkleData.merkle &&
merkleData.pos) {
shepherd.log('electrum getmerkle =>', true);
shepherd.log(merkleData, true);
ecl.close();
const _res = shepherd.getMerkleRoot(txid, merkleData.merkle, merkleData.pos);
shepherd.log(_res, true);
ecl = new shepherd.electrumJSCore(_randomServer[1], _randomServer[0], 'tcp');
ecl.connect();
ecl.blockchainBlockGetHeader(height)
.then((blockInfo) => {
if (blockInfo &&
blockInfo['merkle_root']) {
ecl.close();
shepherd.log('blockinfo =>', true);
shepherd.log(blockInfo, true);
shepherd.log(blockInfo['merkle_root'], true);
if (blockInfo &&
blockInfo['merkle_root']) {
if (_res === blockInfo['merkle_root']) {
resolve(true);
} else {
resolve(false);
}
} else {
resolve(shepherd.CONNECTION_ERROR_OR_INCOMPLETE_DATA);
}
} else {
resolve(shepherd.CONNECTION_ERROR_OR_INCOMPLETE_DATA);
}
});
} else {
resolve(shepherd.CONNECTION_ERROR_OR_INCOMPLETE_DATA);
}
});
});
}
shepherd.verifyMerkleByCoin = (coin, txid, height) => {
const _serverList = shepherd.electrumCoins[coin].serverList;
shepherd.log(`verifyMerkleByCoin`, true);
shepherd.log(shepherd.electrumCoins[coin].server, true);
shepherd.log(shepherd.electrumCoins[coin].serverList, true);
return new shepherd.Promise((resolve, reject) => {
if (_serverList !== 'none') {
let _filteredServerList = [];
for (let i = 0; i < _serverList.length; i++) {
if (_serverList[i] !== shepherd.electrumCoins[coin].server.ip + ':' + shepherd.electrumCoins[coin].server.port) {
_filteredServerList.push(_serverList[i]);
}
}
shepherd.verifyMerkle(
txid,
height,
_filteredServerList,
shepherd.electrumCoins[coin].server.ip + ':' + shepherd.electrumCoins[coin].server.port
).then((proof) => {
resolve(proof);
});
} else {
resolve(false);
}
});
}
shepherd.get('/electrum/merkle/verify', (req, res, next) => {
shepherd.verifyMerkleByCoin(req.query.coin, req.query.txid, req.query.height)
.then((verifyMerkleRes) => {
const successObj = {
msg: 'success',
result: {
merkleProof: verifyMerkleRes,
},
};
res.end(JSON.stringify(successObj));
});
});
return shepherd;
};

141
routes/shepherd/electrum/network.js

@ -0,0 +1,141 @@
module.exports = (shepherd) => {
shepherd.getNetworkData = (network) => {
const coin = shepherd.findNetworkObj(network) || shepherd.findNetworkObj(network.toUpperCase()) || shepherd.findNetworkObj(network.toLowerCase());
const coinUC = coin ? coin.toUpperCase() : null;
if (coin === 'SUPERNET' ||
coin === 'REVS' ||
coin === 'SUPERNET' ||
coin === 'PANGEA' ||
coin === 'DEX' ||
coin === 'JUMBLR' ||
coin === 'BET' ||
coin === 'CRYPTO' ||
coin === 'COQUI' ||
coin === 'HODL' ||
coin === 'SHARK' ||
coin === 'BOTS' ||
coin === 'MGW' ||
coin === 'MVP' ||
coin === 'KV' ||
coin === 'CEAL' ||
coin === 'MESH' ||
coin === 'WLC' ||
coin === 'MNZ' ||
coinUC === 'SUPERNET' ||
coinUC === 'REVS' ||
coinUC === 'SUPERNET' ||
coinUC === 'PANGEA' ||
coinUC === 'DEX' ||
coinUC === 'JUMBLR' ||
coinUC === 'BET' ||
coinUC === 'CRYPTO' ||
coinUC === 'COQUI' ||
coinUC === 'HODL' ||
coinUC === 'SHARK' ||
coinUC === 'BOTS' ||
coinUC === 'MGW' ||
coinUC === 'MVP' ||
coinUC === 'KV' ||
coinUC === 'CEAL' ||
coinUC === 'MESH' ||
coinUC === 'WLC' ||
coinUC === 'MNZ') {
return shepherd.electrumJSNetworks.komodo;
} else {
return shepherd.electrumJSNetworks[network];
}
}
shepherd.findNetworkObj = (coin) => {
for (let key in shepherd.electrumServers) {
if (shepherd.electrumServers[key].abbr === coin) {
return key;
}
}
}
shepherd.get('/electrum/servers', (req, res, next) => {
if (req.query.abbr) {
let _electrumServers = {};
for (let key in shepherd.electrumServers) {
_electrumServers[shepherd.electrumServers[key].abbr] = shepherd.electrumServers[key];
}
const successObj = {
msg: 'success',
result: {
servers: _electrumServers,
},
};
res.end(JSON.stringify(successObj));
} else {
const successObj = {
msg: 'success',
result: {
servers: shepherd.electrumServers,
},
};
res.end(JSON.stringify(successObj));
}
});
shepherd.get('/electrum/coins/server/set', (req, res, next) => {
shepherd.electrumCoins[req.query.coin].server = {
ip: req.query.address,
port: req.query.port,
};
for (let key in shepherd.electrumServers) {
if (shepherd.electrumServers[key].abbr === req.query.coin) { // a bit risky
shepherd.electrumServers[key].address = req.query.address;
shepherd.electrumServers[key].port = req.query.port;
break;
}
}
shepherd.log(JSON.stringify(shepherd.electrumCoins[req.query.coin], null, '\t'), true);
const successObj = {
msg: 'success',
result: true,
};
res.end(JSON.stringify(successObj));
});
shepherd.get('/electrum/servers/test', (req, res, next) => {
const ecl = new shepherd.electrumJSCore(req.query.port, req.query.address, 'tcp'); // tcp or tls
ecl.connect();
ecl.serverVersion()
.then((serverData) => {
ecl.close();
shepherd.log('serverData', true);
shepherd.log(serverData, true);
if (serverData &&
typeof serverData === 'string' &&
serverData.indexOf('Electrum') > -1) {
const successObj = {
msg: 'success',
result: true,
};
res.end(JSON.stringify(successObj));
} else {
const successObj = {
msg: 'error',
result: false,
};
res.end(JSON.stringify(successObj));
}
});
});
return shepherd;
};

397
routes/shepherd/electrum/transactions.js

@ -0,0 +1,397 @@
module.exports = (shepherd) => {
shepherd.sortTransactions = (transactions) => {
return transactions.sort((b, a) => {
if (a.height < b.height) {
return -1;
}
if (a.height > b.height) {
return 1;
}
return 0;
});
}
shepherd.get('/electrum/listtransactions', (req, res, next) => {
const network = req.query.network || shepherd.findNetworkObj(req.query.coin);
const ecl = new shepherd.electrumJSCore(shepherd.electrumServers[network].port, shepherd.electrumServers[network].address, shepherd.electrumServers[network].proto); // tcp or tls
shepherd.log('electrum listtransactions ==>', true);
if (!req.query.full) {
ecl.connect();
ecl.blockchainAddressGetHistory(req.query.address)
.then((json) => {
ecl.close();
shepherd.log(json, true);
json = shepherd.sortTransactions(json);
const successObj = {
msg: 'success',
result: json,
};
res.end(JSON.stringify(successObj));
});
} else {
// !expensive call!
// TODO: limit e.g. 1-10, 10-20 etc
const MAX_TX = req.query.maxlength || 10;
ecl.connect();
ecl.blockchainNumblocksSubscribe()
.then((currentHeight) => {
if (currentHeight &&
Number(currentHeight) > 0) {
ecl.blockchainAddressGetHistory(req.query.address)
.then((json) => {
if (json &&
json.length) {
json = shepherd.sortTransactions(json);
json = json.slice(0, MAX_TX);
let _rawtx = [];
shepherd.log(json.length, true);
shepherd.Promise.all(json.map((transaction, index) => {
return new shepherd.Promise((resolve, reject) => {
ecl.blockchainBlockGetHeader(transaction.height)
.then((blockInfo) => {
if (blockInfo &&
blockInfo.timestamp) {
ecl.blockchainTransactionGet(transaction['tx_hash'])
.then((_rawtxJSON) => {
shepherd.log('electrum gettransaction ==>', true);
shepherd.log((index + ' | ' + (_rawtxJSON.length - 1)), true);
shepherd.log(_rawtxJSON, true);
// decode tx
const _network = shepherd.getNetworkData(network);
const decodedTx = shepherd.electrumJSTxDecoder(_rawtxJSON, _network);
let txInputs = [];
shepherd.log('decodedtx =>', true);
shepherd.log(decodedTx.outputs, true);
if (decodedTx &&
decodedTx.inputs) {
shepherd.Promise.all(decodedTx.inputs.map((_decodedInput, index) => {
return new shepherd.Promise((_resolve, _reject) => {
if (_decodedInput.txid !== '0000000000000000000000000000000000000000000000000000000000000000') {
ecl.blockchainTransactionGet(_decodedInput.txid)
.then((rawInput) => {
const decodedVinVout = shepherd.electrumJSTxDecoder(rawInput, _network);
shepherd.log('electrum raw input tx ==>', true);
if (decodedVinVout) {
shepherd.log(decodedVinVout.outputs[_decodedInput.n], true);
txInputs.push(decodedVinVout.outputs[_decodedInput.n]);
_resolve(true);
} else {
_resolve(true);
}
});
} else {
_resolve(true);
}
});
}))
.then(promiseResult => {
const _parsedTx = {
network: decodedTx.network,
format: decodedTx.format,
inputs: txInputs,
outputs: decodedTx.outputs,
height: transaction.height,
timestamp: blockInfo.timestamp,
confirmations: currentHeight - transaction.height,
};
const formattedTx = shepherd.parseTransactionAddresses(_parsedTx, req.query.address, network);
if (formattedTx.type) {
formattedTx.height = transaction.height;
formattedTx.blocktime = blockInfo.timestamp;
formattedTx.timereceived = blockInfo.timereceived;
formattedTx.hex = _rawtxJSON;
formattedTx.inputs = decodedTx.inputs;
formattedTx.outputs = decodedTx.outputs;
formattedTx.locktime = decodedTx.format.locktime;
_rawtx.push(formattedTx);
} else {
formattedTx[0].height = transaction.height;
formattedTx[0].blocktime = blockInfo.timestamp;
formattedTx[0].timereceived = blockInfo.timereceived;
formattedTx[0].hex = _rawtxJSON;
formattedTx[0].inputs = decodedTx.inputs;
formattedTx[0].outputs = decodedTx.outputs;
formattedTx[0].locktime = decodedTx.format.locktime;
formattedTx[1].height = transaction.height;
formattedTx[1].blocktime = blockInfo.timestamp;
formattedTx[1].timereceived = blockInfo.timereceived;
formattedTx[1].hex = _rawtxJSON;
formattedTx[1].inputs = decodedTx.inputs;
formattedTx[1].outputs = decodedTx.outputs;
formattedTx[1].locktime = decodedTx.format.locktime;
_rawtx.push(formattedTx[0]);
_rawtx.push(formattedTx[1]);
}
resolve(true);
});
} else {
const _parsedTx = {
network: decodedTx.network,
format: 'cant parse',
inputs: 'cant parse',
outputs: 'cant parse',
height: transaction.height,
timestamp: blockInfo.timestamp,
confirmations: currentHeight - transaction.height,
};
const formattedTx = shepherd.parseTransactionAddresses(_parsedTx, req.query.address, network);
_rawtx.push(formattedTx);
resolve(true);
}
});
} else {
const _parsedTx = {
network: 'cant parse',
format: 'cant parse',
inputs: 'cant parse',
outputs: 'cant parse',
height: transaction.height,
timestamp: 'cant get block info',
confirmations: currentHeight - transaction.height,
};
const formattedTx = shepherd.parseTransactionAddresses(_parsedTx, req.query.address, network);
_rawtx.push(formattedTx);
resolve(true);
}
});
});
}))
.then(promiseResult => {
ecl.close();
const successObj = {
msg: 'success',
result: _rawtx,
};
res.end(JSON.stringify(successObj));
});
} else {
const successObj = {
msg: 'success',
result: [],
};
res.end(JSON.stringify(successObj));
}
});
} else {
const successObj = {
msg: 'error',
result: 'cant get current height',
};
res.end(JSON.stringify(successObj));
}
});
}
});
shepherd.get('/electrum/gettransaction', (req, res, next) => {
const network = req.query.network || shepherd.findNetworkObj(req.query.coin);
const ecl = new shepherd.electrumJSCore(shepherd.electrumServers[network].port, shepherd.electrumServers[network].address, shepherd.electrumServers[network].proto); // tcp or tls
shepherd.log('electrum gettransaction =>', true);
ecl.connect();
ecl.blockchainTransactionGet(req.query.txid)
.then((json) => {
ecl.close();
shepherd.log(json, true);
const successObj = {
msg: 'success',
result: json,
};
res.end(JSON.stringify(successObj));
});
});
shepherd.parseTransactionAddresses = (tx, targetAddress, network) => {
// TODO: - sum vins / sum vouts to the same address
// - multi vin multi vout
// - detect change address
let result = [];
let _parse = {
inputs: {},
outputs: {},
};
let _sum = {
inputs: 0,
outputs: 0,
};
let _total = {
inputs: 0,
outputs: 0,
};
shepherd.log('parseTransactionAddresses result ==>', true);
if (tx.format === 'cant parse') {
return {
type: 'unknown',
amount: 'unknown',
address: targetAddress,
timestamp: tx.timestamp,
txid: tx.format.txid,
confirmations: tx.confirmations,
}
}
for (let key in _parse) {
if (!tx[key].length) {
_parse[key] = [];
_parse[key].push(tx[key]);
} else {
_parse[key] = tx[key];
}
for (let i = 0; i < _parse[key].length; i++) {
shepherd.log(`key ==>`, true);
shepherd.log(_parse[key][i], true);
shepherd.log(Number(_parse[key][i].value), true);
_total[key] += Number(_parse[key][i].value);
if (_parse[key][i].scriptPubKey &&
_parse[key][i].scriptPubKey.addresses &&
_parse[key][i].scriptPubKey.addresses[0] === targetAddress &&
_parse[key][i].value) {
_sum[key] += Number(_parse[key][i].value);
}
}
}
if (_sum.inputs > 0 &&
_sum.outputs > 0) {
// vin + change, break into two tx
result = [{ // reorder since tx sort by default is from newest to oldest
type: 'sent',
amount: Number(_sum.inputs.toFixed(8)),
address: targetAddress,
timestamp: tx.timestamp,
txid: tx.format.txid,
confirmations: tx.confirmations,
}, {
type: 'received',
amount: Number(_sum.outputs.toFixed(8)),
address: targetAddress,
timestamp: tx.timestamp,
txid: tx.format.txid,
confirmations: tx.confirmations,
}];
if (network === 'komodo') { // calc claimed interest amount
const vinVoutDiff = _total.inputs - _total.outputs;
if (vinVoutDiff < 0) {
result[1].interest = Number(vinVoutDiff.toFixed(8));
}
}
} else if (_sum.inputs === 0 && _sum.outputs > 0) {
result = {
type: 'received',
amount: Number(_sum.outputs.toFixed(8)),
address: targetAddress,
timestamp: tx.timestamp,
txid: tx.format.txid,
confirmations: tx.confirmations,
};
} else if (_sum.inputs > 0 && _sum.outputs === 0) {
result = {
type: 'sent',
amount: Number(_sum.inputs.toFixed(8)),
address: targetAddress,
timestamp: tx.timestamp,
txid: tx.format.txid,
confirmations: tx.confirmations,
};
} else {
// (?)
result = {
type: 'other',
amount: 'unknown',
address: targetAddress,
timestamp: tx.timestamp,
txid: tx.format.txid,
confirmations: tx.confirmations,
};
}
shepherd.log(_sum, true);
shepherd.log(result, true);
return result;
}
shepherd.get('/electrum/decoderawtx', (req, res, next) => {
const _network = shepherd.getNetworkData(req.query.network);
const _rawtx = req.query.rawtx;
// const _rawtx = '0100000001dd6d064f5665f8454293ecaa9dbb55accf4f7e443d35f3b5ab7760f54b6c15fe000000006a473044022056355585a4a501ec9afc96aa5df124cf29ad3ac6454b47cd07cd7d89ec95ec2b022074c4604ee349d30e5336f210598e4dc576bf16ebeb67eeac3f4e82f56e930fee012103b90ba01af308757054e0484bb578765d5df59c4a57adbb94e2419df5e7232a63feffffff0289fc923b000000001976a91424af38fcb13bbc171b0b42bb017244a53b6bb2fa88ac20a10700000000001976a9142f4c0f91fc06ac228c120aee41741d0d3909683288ac49258b58';
const decodedTx = shepherd.electrumJSTxDecoder(_rawtx, _network);
shepherd.log('electrum decoderawtx input tx ==>', true);
if (req.query.parseonly ||
decodedTx.inputs[0].txid === '0000000000000000000000000000000000000000000000000000000000000000') {
const successObj = {
msg: 'success',
result: {
network: decodedTx.network,
format: decodedTx.format,
inputs: decodedTx.inputs,
outputs: decodedTx.outputs,
},
};
shepherd.log(successObj.result, true);
res.end(JSON.stringify(successObj));
} else {
const ecl = new shepherd.electrumJSCore(shepherd.electrumServers[req.query.network].port, shepherd.electrumServers[req.query.network].address, shepherd.electrumServers[req.query.network].proto); // tcp or tls
ecl.connect();
ecl.blockchainTransactionGet(decodedTx.inputs[0].txid)
.then((json) => {
ecl.close();
shepherd.log(json, true);
const decodedVin = shepherd.electrumJSTxDecoder(json, _network);
const successObj = {
msg: 'success',
result: {
network: decodedTx.network,
format: decodedTx.format,
inputs: decodedVin.outputs[decodedTx.inputs[0].n],
outputs: decodedTx.outputs,
},
};
res.end(JSON.stringify(successObj));
});
}
});
return shepherd;
};

52
routes/shepherd/init.js

@ -0,0 +1,52 @@
module.exports = (shepherd) => {
shepherd.readVersionFile = () => {
// read app version
const rootLocation = shepherd.path.join(__dirname, '../../');
const localVersionFile = shepherd.fs.readFileSync(`${rootLocation}version`, 'utf8');
return localVersionFile;
}
shepherd.createAgamaDirs = () => {
if (!shepherd.fs.existsSync(shepherd.agamaDir)) {
shepherd.fs.mkdirSync(shepherd.agamaDir);
if (shepherd.fs.existsSync(shepherd.agamaDir)) {
shepherd.log(`created agama folder at ${shepherd.agamaDir}`);
shepherd.writeLog(`created agama folder at ${shepherd.agamaDir}`);
}
} else {
shepherd.log('agama folder already exists');
}
if (!shepherd.fs.existsSync(`${shepherd.agamaDir}/shepherd`)) {
shepherd.fs.mkdirSync(`${shepherd.agamaDir}/shepherd`);
if (shepherd.fs.existsSync(`${shepherd.agamaDir}/shepherd`)) {
shepherd.log(`created shepherd folder at ${shepherd.agamaDir}/shepherd`);
shepherd.writeLog(`create shepherd folder at ${shepherd.agamaDir}/shepherd`);
}
} else {
shepherd.log('agama/shepherd folder already exists');
}
if (!shepherd.fs.existsSync(`${shepherd.agamaDir}/shepherd/pin`)) {
shepherd.fs.mkdirSync(`${shepherd.agamaDir}/shepherd/pin`);
if (shepherd.fs.existsSync(`${shepherd.agamaDir}/shepherd/pin`)) {
shepherd.log(`created pin folder at ${shepherd.agamaDir}/shepherd/pin`);
shepherd.writeLog(`create pin folder at ${shepherd.agamaDir}/shepherd/pin`);
}
} else {
shepherd.log('shepherd/pin folder already exists');
}
if (!shepherd.fs.existsSync(shepherd.zcashParamsDir)) {
shepherd.fs.mkdirSync(shepherd.zcashParamsDir);
} else {
shepherd.log('zcashparams folder already exists');
}
}
return shepherd;
};

150
routes/shepherd/kickstart.js

@ -0,0 +1,150 @@
module.exports = (shepherd) => {
/*
* type: GET
* params: coin, type
* TODO: reorganize to work with coind
*/
shepherd.get('/kick', (req, res, next) => {
const _coin = req.query.coin;
const _type = req.query.type;
if (!_coin) {
const errorObj = {
msg: 'error',
result: 'no coin name provided',
};
res.end(JSON.stringify(errorObj));
}
if (!_type) {
const errorObj = {
msg: 'error',
result: 'no type provided',
};
res.end(JSON.stringify(errorObj));
}
const kickStartDirs = {
soft: [
{
name: 'DB/[coin]',
type: 'pattern',
match: 'balancecrc.',
},
{
name: 'DB/[coin]/utxoaddrs',
type: 'file',
},
{
name: 'DB/[coin]/accounts',
type: 'folder',
},
{
name: 'DB/[coin]/fastfind',
type: 'folder',
},
{
name: 'tmp/[coin]',
type: 'folder',
}
],
hard: [
{
name: 'DB/[coin]',
type: 'pattern',
match: 'balancecrc.',
},
{
name: 'DB/[coin]/utxoaddrs',
type: 'file',
},
{
name: 'DB/[coin]',
type: 'pattern',
match: 'utxoaddrs.',
},
{
name: 'DB/[coin]/accounts',
type: 'folder',
},
{
name: 'DB/[coin]/fastfind',
type: 'folder',
},
{
name: 'DB/[coin]/spends',
type: 'folder',
},
{
name: 'tmp/[coin]',
type: 'folder',
}
],
brutal: [ // delete all coin related data
{
name: 'DB/[coin]',
type: 'folder',
},
{
name: 'DB/purgeable/[coin]',
type: 'folder',
},
{
name: 'DB/ro/[coin]',
type: 'folder',
},
{
name: 'tmp/[coin]',
type: 'folder',
}
]
};
if (_coin &&
_type) {
for (let i = 0; i < kickStartDirs[_type].length; i++) {
let currentKickItem = kickStartDirs[_type][i];
shepherd.log('deleting ' + currentKickItem.type + (currentKickItem.match ? ' ' + currentKickItem.match : '') + ' ' + iguanaDir + '/' + currentKickItem.name.replace('[coin]', _coin));
if (currentKickItem.type === 'folder' ||
currentKickItem.type === 'file') {
/*rimraf(`${iguanaDir}/${currentKickItem.name.replace('[coin]', _coin)}`, function(err) {
if (err) {
shepherd.writeLog(`kickstart err: ${err}`);
shepherd.log(`kickstart err: ${err}`);
}
});*/
} else if (currentKickItem.type === 'pattern') {
let dirItems = shepherd.fs.readdirSync(`${iguanaDir}/currentKickItem.name.replace('[coin]', _coin)`);
if (dirItems &&
dirItems.length) {
for (let j = 0; j < dirItems.length; j++) {
if (dirItems[j].indexOf(currentKickItem.match) > -1) {
/*rimraf(`${iguanaDir}/${currentKickItem.name.replace('[coin]', _coin)}/${dirItems[j]}`, function(err) {
if (err) {
shepherd.writeLog(`kickstart err: ${err}`);
shepherd.log(`kickstart err: ${err}`);
}
});*/
shepherd.log(`deleting ${dirItems[j]}`);
}
}
}
}
}
const successObj = {
msg: 'success',
result: 'kickstart: brutal is executed',
};
res.end(JSON.stringify(successObj));
}
});
return shepherd;
};

141
routes/shepherd/log.js

@ -0,0 +1,141 @@
module.exports = (shepherd) => {
shepherd.log = (msg, isSpvOut) => {
if (shepherd.appConfig.dev ||
shepherd.appConfig.debug) {
console.log(msg);
}
if (!isSpvOut) {
shepherd.appRuntimeLog.push({
time: Date.now(),
msg: msg,
});
} else {
shepherd.appRuntimeSPVLog.push({
time: Date.now(),
msg: msg,
});
}
}
shepherd.writeLog = (data) => {
const logLocation = `${shepherd.agamaDir}/shepherd`;
const timeFormatted = new Date(Date.now()).toLocaleString('en-US', { hour12: false });
if (shepherd.appConfig.debug) {
if (shepherd.fs.existsSync(`${logLocation}/agamalog.txt`)) {
shepherd.fs.appendFile(`${logLocation}/agamalog.txt`, `${timeFormatted} ${data}\r\n`, (err) => {
if (err) {
shepherd.log('error writing log file');
}
});
} else {
shepherd.fs.writeFile(`${logLocation}/agamalog.txt`, `${timeFormatted} ${data}\r\n`, (err) => {
if (err) {
shepherd.log('error writing log file');
}
});
}
}
}
shepherd.get('/log/runtime', (req, res, next) => {
const successObj = {
msg: 'success',
result: req.query.spv && req.query.spv === 'true' ? shepherd.appRuntimeSPVLog : shepherd.appRuntimeLog,
};
res.end(JSON.stringify(successObj));
});
shepherd.getAppRuntimeLog = () => {
return new shepherd.Promise((resolve, reject) => {
resolve(shepherd.appRuntimeLog);
});
};
/*
* type: POST
* params: payload
*/
shepherd.post('/guilog', (req, res, next) => {
const logLocation = `${shepherd.agamaDir}/shepherd`;
if (!shepherd.guiLog[shepherd.appSessionHash]) {
shepherd.guiLog[shepherd.appSessionHash] = {};
}
if (shepherd.guiLog[shepherd.appSessionHash][req.body.timestamp]) {
shepherd.guiLog[shepherd.appSessionHash][req.body.timestamp].status = req.body.status;
shepherd.guiLog[shepherd.appSessionHash][req.body.timestamp].response = req.body.response;
} else {
shepherd.guiLog[shepherd.appSessionHash][req.body.timestamp] = {
function: req.body.function,
type: req.body.type,
url: req.body.url,
payload: req.body.payload,
status: req.body.status,
};
}
shepherd.fs.writeFile(`${logLocation}/agamalog.json`, JSON.stringify(shepherd.guiLog), (err) => {
if (err) {
shepherd.writeLog('error writing gui log file');
}
const returnObj = {
msg: 'success',
result: 'gui log entry is added',
};
res.end(JSON.stringify(returnObj));
});
});
/*
* type: GET
* params: type
*/
shepherd.get('/getlog', (req, res, next) => {
const logExt = req.query.type === 'txt' ? 'txt' : 'json';
if (shepherd.fs.existsSync(`${shepherd.agamaDir}/shepherd/agamalog.${logExt}`)) {
shepherd.fs.readFile(`${shepherd.agamaDir}/shepherd/agamalog.${logExt}`, 'utf8', (err, data) => {
if (err) {
const errorObj = {
msg: 'error',
result: err,
};
res.end(JSON.stringify(errorObj));
} else {
const successObj = {
msg: 'success',
result: data ? JSON.parse(data) : '',
};
res.end(JSON.stringify(successObj));
}
});
} else {
const errorObj = {
msg: 'error',
result: `agama.${logExt} doesnt exist`,
};
res.end(JSON.stringify(errorObj));
}
});
shepherd.printDirs = () => {
shepherd.log(`agama dir: ${shepherd.agamaDir}`);
shepherd.log('--------------------------')
shepherd.log(`komodo dir: ${shepherd.komododBin}`);
shepherd.log(`komodo bin: ${shepherd.komodoDir}`);
shepherd.writeLog(`agama dir: ${shepherd.agamaDir}`);
shepherd.writeLog(`komodo dir: ${shepherd.komododBin}`);
shepherd.writeLog(`komodo bin: ${shepherd.komodoDir}`);
}
return shepherd;
};

74
routes/shepherd/paths.js

@ -0,0 +1,74 @@
module.exports = (shepherd) => {
shepherd.pathsAgama = () => {
switch (shepherd.os.platform()) {
case 'darwin':
shepherd.fixPath();
shepherd.agamaDir = `${process.env.HOME}/Library/Application Support/Agama`;
break;
case 'linux':
shepherd.agamaDir = `${process.env.HOME}/.agama`;
break;
case 'win32':
shepherd.agamaDir = `${process.env.APPDATA}/Agama`;
shepherd.agamaDir = shepherd.path.normalize(shepherd.agamaDir);
break;
}
}
shepherd.pathsDaemons = () => {
switch (shepherd.os.platform()) {
case 'darwin':
shepherd.fixPath();
shepherd.agamaTestDir = `${process.env.HOME}/Library/Application Support/Agama/test`,
shepherd.komododBin = shepherd.path.join(__dirname, '../../assets/bin/osx/komodod'),
shepherd.komodocliBin = shepherd.path.join(__dirname, '../../assets/bin/osx/komodo-cli'),
shepherd.komodoDir = shepherd.appConfig.dataDir.length ? shepherd.appConfig.dataDir : `${process.env.HOME}/Library/Application Support/Komodo`,
shepherd.zcashdBin = '/Applications/ZCashSwingWalletUI.app/Contents/MacOS/zcashd',
shepherd.zcashcliBin = '/Applications/ZCashSwingWalletUI.app/Contents/MacOS/zcash-cli',
shepherd.zcashDir = `${process.env.HOME}/Library/Application Support/Zcash`,
shepherd.zcashParamsDir = `${process.env.HOME}/Library/Application Support/ZcashParams`,
shepherd.chipsBin = shepherd.path.join(__dirname, '../../assets/bin/osx/chipsd'),
shepherd.chipscliBin = shepherd.path.join(__dirname, '../../assets/bin/osx/chips-cli'),
shepherd.chipsDir = `${process.env.HOME}/Library/Application Support/Chips`,
shepherd.coindRootDir = shepherd.path.join(__dirname, '../../assets/bin/osx/dex/coind');
break;
case 'linux':
shepherd.agamaTestDir = `${process.env.HOME}/.agama/test`,
shepherd.komododBin = shepherd.path.join(__dirname, '../../assets/bin/linux64/komodod'),
shepherd.komodocliBin = shepherd.path.join(__dirname, '../../assets/bin/linux64/komodo-cli'),
shepherd.komodoDir = shepherd.appConfig.dataDir.length ? shepherd.appConfig.dataDir : `${process.env.HOME}/.komodo`,
shepherd.zcashParamsDir = `${process.env.HOME}/.zcash-params`,
shepherd.chipsBin = shepherd.path.join(__dirname, '../../assets/bin/linux64/chipsd'),
shepherd.chipscliBin = shepherd.path.join(__dirname, '../../assets/bin/linux64/chips-cli'),
shepherd.chipsDir = `${process.env.HOME}/.chips`,
shepherd.coindRootDir = shepherd.path.join(__dirname, '../../assets/bin/linux64/dex/coind');
break;
case 'win32':
shepherd.agamaTestDir = `${process.env.APPDATA}/Agama/test`;
shepherd.agamaTestDir = shepherd.path.normalize(shepherd.agamaTestDir);
shepherd.komododBin = shepherd.path.join(__dirname, '../../assets/bin/win64/komodod.exe'),
shepherd.komododBin = shepherd.path.normalize(shepherd.komododBin),
shepherd.komodocliBin = shepherd.path.join(__dirname, '../../assets/bin/win64/komodo-cli.exe'),
shepherd.komodocliBin = shepherd.path.normalize(shepherd.komodocliBin),
shepherd.komodoDir = shepherd.appConfig.dataDir.length ? shepherd.appConfig.dataDir : `${process.env.APPDATA}/Komodo`,
shepherd.komodoDir = shepherd.path.normalize(shepherd.komodoDir);
shepherd.chipsBin = shepherd.path.join(__dirname, '../../assets/bin/win64/chipsd.exe'),
shepherd.chipsBin = shepherd.path.normalize(shepherd.chipsBin),
shepherd.chipscliBin = shepherd.path.join(__dirname, '../../assets/bin/win64/chips-cli.exe'),
shepherd.chipscliBin = shepherd.path.normalize(shepherd.chipscliBin),
shepherd.chipsDir = `${process.env.APPDATA}/Chips`,
shepherd.chipsDir = shepherd.path.normalize(shepherd.chipsDir);
shepherd.zcashParamsDir = `${process.env.APPDATA}/ZcashParams`;
shepherd.zcashParamsDir = shepherd.path.normalize(shepherd.zcashParamsDir);
shepherd.coindRootDir = shepherd.path.join(__dirname, '../../assets/bin/osx/dex/coind');
shepherd.coindRootDir = shepherd.path.normalize(shepherd.coindRootDir);
break;
}
}
return shepherd;
};

146
routes/shepherd/pin.js

@ -0,0 +1,146 @@
module.exports = (shepherd) => {
/*
* type: POST
* params: none
*/
shepherd.post('/encryptkey', (req, res, next) => {
if (req.body.key &&
req.body.string &&
req.body.pubkey) {
const encryptedString = shepherd.aes256.encrypt(req.body.key, req.body.string);
// test pin security
// - at least 1 char in upper case
// - at least 1 digit
// - at least one special character
// - min length 8
const _pin = req.body.key;
const _pinTest = _pin.match('^(?=.*[A-Z])(?=.*[^<>{}\"/|;:.,~!?@#$%^=&*\\]\\\\()\\[_+]*$)(?=.*[0-9])(?=.*[a-z]).{8}$');
shepherd.fs.writeFile(`${shepherd.agamaDir}/shepherd/pin/${req.body.pubkey}.pin`, encryptedString, (err) => {
if (err) {
shepherd.log('error writing pin file');
}
const returnObj = {
msg: 'success',
result: encryptedString,
};
res.end(JSON.stringify(returnObj));
});
} else {
let errorObj = {
msg: 'error',
result: '',
};
const _paramsList = [
'key',
'string',
'pubkey'
];
let _errorParamsList = [];
for (let i = 0; i < _paramsList.length; i++) {
if (!req.query[_paramsList[i]]) {
_errorParamsList.push(_paramsList[i]);
}
}
errorObj.result = `missing param ${_errorParamsList.join(', ')}`;
res.end(JSON.stringify(errorObj));
}
});
shepherd.post('/decryptkey', (req, res, next) => {
if (req.body.key &&
req.body.pubkey) {
if (shepherd.fs.existsSync(`${shepherd.agamaDir}/shepherd/pin/${req.body.pubkey}.pin`)) {
shepherd.fs.readFile(`${shepherd.agamaDir}/shepherd/pin/${req.body.pubkey}.pin`, 'utf8', (err, data) => {
if (err) {
const errorObj = {
msg: 'error',
result: err,
};
res.end(JSON.stringify(errorObj));
} else {
const encryptedKey = shepherd.aes256.decrypt(req.body.key, data);
// test if stored encrypted passphrase is decrypted correctly
// if not then the key is wrong
const _regexTest = encryptedKey.match(/^[0-9a-zA-Z ]+$/g);
let returnObj;
if (!_regexTest) {
returnObj = {
msg: 'error',
result: 'wrong key',
};
} else {
returnObj = {
msg: 'success',
result: encryptedKey,
};
}
res.end(JSON.stringify(returnObj));
}
});
} else {
const errorObj = {
msg: 'error',
result: `file ${req.query.pubkey}.pin doesnt exist`,
};
res.end(JSON.stringify(errorObj));
}
} else {
const errorObj = {
msg: 'error',
result: 'missing key or pubkey param',
};
res.end(JSON.stringify(errorObj));
}
});
shepherd.get('/getpinlist', (req, res, next) => {
if (shepherd.fs.existsSync(`${shepherd.agamaDir}/shepherd/pin`)) {
shepherd.fs.readdir(`${shepherd.agamaDir}/shepherd/pin`, (err, items) => {
let _pins = [];
for (let i = 0; i < items.length; i++) {
if (items[i].substr(items[i].length - 4, 4) === '.pin') {
_pins.push(items[i].substr(0, items[i].length - 4));
}
}
if (!items.length) {
const errorObj = {
msg: 'error',
result: 'no pins',
};
res.end(JSON.stringify(errorObj));
} else {
const successObj = {
msg: 'success',
result: _pins,
};
res.end(JSON.stringify(successObj));
}
});
} else {
const errorObj = {
msg: 'error',
result: 'pin folder doesnt exist',
};
res.end(JSON.stringify(errorObj));
}
});
return shepherd;
};

82
routes/shepherd/quitDaemon.js

@ -0,0 +1,82 @@
module.exports = (shepherd) => {
shepherd.quitKomodod = (timeout = 100) => {
// if komodod is under heavy load it may not respond to cli stop the first time
// exit komodod gracefully
let coindExitInterval = {};
shepherd.lockDownAddCoin = true;
for (let key in shepherd.coindInstanceRegistry) {
const chain = key !== 'komodod' ? key : null;
let _coindQuitCmd = shepherd.komodocliBin;
// any coind
if (shepherd.nativeCoindList[key.toLowerCase()]) {
_coindQuitCmd = `${shepherd.coindRootDir}/${key.toLowerCase()}/${shepherd.nativeCoindList[key.toLowerCase()].bin.toLowerCase()}-cli`;
}
if (key === 'CHIPS') {
_coindQuitCmd = shepherd.chipscliBin;
}
const execCliStop = () => {
let _arg = [];
if (chain && !shepherd.nativeCoindList[key.toLowerCase()] && key !== 'CHIPS') {
_arg.push(`-ac_name=${chain}`);
if (shepherd.appConfig.dataDir.length) {
_arg.push(`-datadir=${shepherd.appConfig.dataDir + (key !== 'komodod' ? '/' + key : '')}`);
}
} else if (key === 'komodod' && shepherd.appConfig.dataDir.length) {
_arg.push(`-datadir=${shepherd.appConfig.dataDir}`);
}
_arg.push('stop');
shepherd.execFile(`${_coindQuitCmd}`, _arg, (error, stdout, stderr) => {
shepherd.log(`stdout: ${stdout}`);
shepherd.log(`stderr: ${stderr}`);
if (stdout.indexOf('EOF reached') > -1 ||
stderr.indexOf('EOF reached') > -1 ||
(error && error.toString().indexOf('Command failed') > -1 && !stderr) || // win "special snowflake" case
stdout.indexOf('connect to server: unknown (code -1)') > -1 ||
stderr.indexOf('connect to server: unknown (code -1)') > -1) {
delete shepherd.coindInstanceRegistry[key];
clearInterval(coindExitInterval[key]);
}
// workaround for AGT-65
const _port = shepherd.assetChainPorts[key];
setTimeout(() => {
shepherd.portscanner.checkPortStatus(_port, '127.0.0.1', (error, status) => {
// Status is 'open' if currently in use or 'closed' if available
if (status === 'closed') {
delete shepherd.coindInstanceRegistry[key];
clearInterval(coindExitInterval[key]);
}
});
}, 100);
if (error !== null) {
shepherd.log(`exec error: ${error}`);
}
if (key === 'CHIPS') {
setTimeout(() => {
shepherd.killRogueProcess('chips-cli');
}, 100);
} else {
setTimeout(() => {
shepherd.killRogueProcess('komodo-cli');
}, 100);
}
});
}
execCliStop();
coindExitInterval[key] = setInterval(() => {
execCliStop();
}, timeout);
}
}
return shepherd;
};

246
routes/shepherd/rpc.js

@ -0,0 +1,246 @@
module.exports = (shepherd) => {
shepherd.getConf = (chain) => {
let _confLocation = chain === 'komodod' ? `${shepherd.komodoDir}/komodo.conf` : `${shepherd.komodoDir}/${chain}/${chain}.conf`;
_confLocation = chain === 'CHIPS' ? `${shepherd.chipsDir}/chips.conf` : _confLocation;
// any coind
if (chain) {
if (shepherd.nativeCoindList[chain.toLowerCase()]) {
const _osHome = shepherd.os.platform === 'win32' ? process.env.APPDATA : process.env.HOME;
let coindDebugLogLocation = `${_osHome}/.${shepherd.nativeCoindList[chain.toLowerCase()].bin.toLowerCase()}/debug.log`;
_confLocation = `${_osHome}/.${shepherd.nativeCoindList[chain.toLowerCase()].bin.toLowerCase()}/${shepherd.nativeCoindList[chain.toLowerCase()].bin.toLowerCase()}.conf`;
}
if (shepherd.fs.existsSync(_confLocation)) {
let _port = shepherd.assetChainPorts[chain];
const _rpcConf = shepherd.fs.readFileSync(_confLocation, 'utf8');
// any coind
if (shepherd.nativeCoindList[chain.toLowerCase()]) {
_port = shepherd.nativeCoindList[chain.toLowerCase()].port;
}
if (_rpcConf.length) {
let _match;
let parsedRpcConfig = {
user: '',
pass: '',
port: _port,
};
if (_match = _rpcConf.match(/rpcuser=\s*(.*)/)) {
parsedRpcConfig.user = _match[1];
}
if ((_match = _rpcConf.match(/rpcpass=\s*(.*)/)) ||
(_match = _rpcConf.match(/rpcpassword=\s*(.*)/))) {
parsedRpcConfig.pass = _match[1];
}
if (shepherd.nativeCoindList[chain.toLowerCase()]) {
shepherd.rpcConf[chain] = parsedRpcConfig;
} else {
shepherd.rpcConf[chain === 'komodod' ? 'KMD' : chain] = parsedRpcConfig;
}
} else {
shepherd.log(`${_confLocation} is empty`);
}
} else {
shepherd.log(`${_confLocation} doesnt exist`);
}
}
}
/*
* type: POST
* params: payload
*/
shepherd.post('/cli', (req, res, next) => {
if (!req.body.payload) {
const errorObj = {
msg: 'error',
result: 'no payload provided',
};
res.end(JSON.stringify(errorObj));
} else if (!req.body.payload.cmd.match(/^[0-9a-zA-Z _\,\.\[\]"'/\\]+$/g)) {
const errorObj = {
msg: 'error',
result: 'wrong cli string format',
};
res.end(JSON.stringify(errorObj));
} else {
const _mode = req.body.payload.mode === 'passthru' ? 'passthru' : 'default';
const _chain = req.body.payload.chain === 'KMD' ? null : req.body.payload.chain;
let _cmd = req.body.payload.cmd;
const _params = req.body.payload.params ? ` ${req.body.payload.params}` : '';
if (!shepherd.rpcConf[_chain]) {
shepherd.getConf(req.body.payload.chain === 'KMD' || !req.body.payload.chain && shepherd.kmdMainPassiveMode ? 'komodod' : req.body.payload.chain);
}
if (_mode === 'default') {
/*let _body = {
agent: 'bitcoinrpc',
method: _cmd,
};
if (req.body.payload.params) {
_body = {
agent: 'bitcoinrpc',
method: _cmd,
params: req.body.payload.params === ' ' ? [''] : req.body.payload.params,
};
}
const options = {
url: `http://localhost:${rpcConf[req.body.payload.chain].port}`,
method: 'POST',
auth: {
user: rpcConf[req.body.payload.chain].user,
pass: rpcConf[req.body.payload.chain].pass,
},
body: JSON.stringify(_body)
};
// send back body on both success and error
// this bit replicates iguana core's behaviour
shepherd.request(options, function(error, response, body) {
if (response &&
response.statusCode &&
response.statusCode === 200) {
res.end(body);
} else {
res.end(body);
}
});*/
if (_cmd === 'debug' &&
_chain !== 'CHIPS') {
if (shepherd.nativeCoindList[_chain.toLowerCase()]) {
const _osHome = shepherd.os.platform === 'win32' ? process.env.APPDATA : process.env.HOME;
let coindDebugLogLocation;
if (_chain === 'CHIPS') {
coindDebugLogLocation = `${shepherd.chipsDir}/debug.log`;
} else {
coindDebugLogLocation = `${_osHome}/.${shepherd.nativeCoindList[_chain.toLowerCase()].bin.toLowerCase()}/debug.log`;
}
shepherd.readDebugLog(coindDebugLogLocation, 1)
.then((result) => {
const _obj = {
'msg': 'success',
'result': result,
};
// shepherd.log('bitcoinrpc debug ====>');
// console.log(result);
res.end(JSON.stringify(_obj));
}, (result) => {
const _obj = {
error: result,
result: 'error',
};
res.end(JSON.stringify(_obj));
});
} else {
res.end({
error: 'bitcoinrpc debug error',
result: 'error',
});
// console.log('bitcoinrpc debug error');
}
} else {
if (_chain === 'CHIPS' &&
_cmd === 'debug') {
_cmd = 'getblockchaininfo';
}
let _body = {
'agent': 'bitcoinrpc',
'method': _cmd,
};
if (req.body.payload.params) {
_body = {
'agent': 'bitcoinrpc',
'method': _cmd,
'params': req.body.payload.params === ' ' ? [''] : req.body.payload.params,
};
}
if (req.body.payload.chain) {
const options = {
url: `http://localhost:${shepherd.rpcConf[req.body.payload.chain].port}`,
method: 'POST',
auth: {
'user': shepherd.rpcConf[req.body.payload.chain].user,
'pass': shepherd.rpcConf[req.body.payload.chain].pass
},
body: JSON.stringify(_body)
};
// send back body on both success and error
// this bit replicates iguana core's behaviour
shepherd.request(options, (error, response, body) => {
if (response &&
response.statusCode &&
response.statusCode === 200) {
res.end(body);
} else {
res.end(body);
}
});
}
}
} else {
let _coindCliBin = shepherd.komodocliBin;
if (shepherd.nativeCoindList &&
_chain &&
shepherd.nativeCoindList[_chain.toLowerCase()]) {
_coindCliBin = `${shepherd.coindRootDir}/${_chain.toLowerCase()}/${shepherd.nativeCoindList[_chain.toLowerCase()].bin.toLowerCase()}-cli`;
}
let _arg = (_chain ? ' -ac_name=' + _chain : '') + ' ' + _cmd + _params;
if (shepherd.appConfig.dataDir.length) {
_arg = `${_arg} -datadir=${shepherd.appConfig.dataDir + (_chain ? '/' + key : '')}`;
}
_arg = _arg.trim().split(' ');
shepherd.execFile(_coindCliBin, _arg, (error, stdout, stderr) => {
shepherd.log(`stdout: ${stdout}`);
shepherd.log(`stderr: ${stderr}`);
if (error !== null) {
shepherd.log(`exec error: ${error}`);
}
let responseObj;
if (stderr) {
responseObj = {
msg: 'error',
result: stderr,
};
} else {
responseObj = {
msg: 'success',
result: stdout,
};
}
res.end(JSON.stringify(responseObj));
shepherd.killRogueProcess('komodo-cli');
});
}
}
});
return shepherd;
};
Loading…
Cancel
Save