Browse Source

Merge pull request #1 from KomodoPlatform/v0.25

V0.25
v0.25
siulynot 7 years ago
committed by GitHub
parent
commit
6867763d1b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      LICENSE
  2. 2
      gui/EasyDEX-GUI
  3. 504
      main.js
  4. 22
      package.json
  5. 126
      private/kmdcli.js
  6. 36
      private/mainmenu.js
  7. 24
      routes/appConfig.js
  8. 289
      routes/electrumjs/electrumServers.js
  9. 461
      routes/electrumjs/electrumjs.networks.js
  10. 120
      routes/electrumjs/electrumjs.txdecoder-2bytes.js
  11. 123
      routes/electrumjs/electrumjs.txdecoder-pos.js
  12. 40
      routes/ports.js
  13. 12
      routes/shepherd.js
  14. 152
      routes/shepherd/addCoinShortcuts.js
  15. 27
      routes/shepherd/appInfo.js
  16. 48
      routes/shepherd/auth.js
  17. 239
      routes/shepherd/binsTestUtil.js
  18. 141
      routes/shepherd/coindWalletKeys.js
  19. 41
      routes/shepherd/coins.js
  20. 94
      routes/shepherd/coinsList.js
  21. 65
      routes/shepherd/config.js
  22. 280
      routes/shepherd/daemonControl.js
  23. 518
      routes/shepherd/dashboardUpdate.js
  24. 122
      routes/shepherd/debugLog.js
  25. 208
      routes/shepherd/downloadBins.js
  26. 147
      routes/shepherd/downloadPatch.js
  27. 107
      routes/shepherd/downloadZcparams.js
  28. 372
      routes/shepherd/elections.js
  29. 139
      routes/shepherd/electrum/auth.js
  30. 203
      routes/shepherd/electrum/balance.js
  31. 46
      routes/shepherd/electrum/block.js
  32. 81
      routes/shepherd/electrum/coins.js
  33. 500
      routes/shepherd/electrum/createtx-multi.js
  34. 86
      routes/shepherd/electrum/createtx-split.js
  35. 792
      routes/shepherd/electrum/createtx.js
  36. 31
      routes/shepherd/electrum/estimate.js
  37. 2
      routes/shepherd/electrum/interest.js
  38. 353
      routes/shepherd/electrum/keys.js
  39. 89
      routes/shepherd/electrum/listunspent.js
  40. 48
      routes/shepherd/electrum/merkle.js
  41. 199
      routes/shepherd/electrum/network.js
  42. 573
      routes/shepherd/electrum/transactions.js
  43. 59
      routes/shepherd/kickstart.js
  44. 127
      routes/shepherd/log.js
  45. 209
      routes/shepherd/pin.js
  46. 132
      routes/shepherd/quitDaemon.js
  47. 373
      routes/shepherd/rpc.js
  48. 6
      version
  49. 2
      version_build

2
LICENSE

@ -1,6 +1,6 @@
MIT License
Copyright (c) 2017 SuperNET
Copyright (c) 2017 - 2018 SuperNET
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

2
gui/EasyDEX-GUI

@ -1 +1 @@
Subproject commit 0092ac50871b6964e6462f3ba68ac7d1cd8c1421
Subproject commit 54f9dfc6e7b519a5a2295b49813cfd99be131cae

504
main.js

@ -17,7 +17,6 @@ const express = require('express');
const bodyParser = require('body-parser');
const fsnode = require('fs');
const fs = require('fs-extra');
const numCPUs = require('os').cpus().length;
const Promise = require('bluebird');
const arch = require('arch');
@ -56,8 +55,8 @@ app.setVersion(appBasicInfo.version);
shepherd.createAgamaDirs();
const appSessionHash = md5(Date.now().toString());
const _spvFees = shepherd.getSpvFees();
shepherd.writeLog(`app init ${appSessionHash}`);
shepherd.writeLog(`app info: ${appBasicInfo.name} ${appBasicInfo.version}`);
shepherd.writeLog('sys info:');
shepherd.writeLog(`totalmem_readable: ${formatBytes(os.totalmem())}`);
@ -68,7 +67,9 @@ shepherd.writeLog(`platform: ${osPlatform}`);
shepherd.writeLog(`os_release: ${os.release()}`);
shepherd.writeLog(`os_type: ${os.type()}`);
shepherd.log(`app init ${appSessionHash}`);
if (process.argv.indexOf('devmode') > -1) {
shepherd.log(`app init ${appSessionHash}`);
}
shepherd.log(`app info: ${appBasicInfo.name} ${appBasicInfo.version}`);
shepherd.log('sys info:');
shepherd.log(`totalmem_readable: ${formatBytes(os.totalmem())}`);
@ -89,7 +90,7 @@ shepherd.log(`app started in ${(appConfig.dev ? 'dev mode' : ' user mode')}`);
shepherd.writeLog(`app started in ${(appConfig.dev ? 'dev mode' : ' user mode')}`);
shepherd.setConfKMD();
shepherd.setConfKMD('CHIPS');
// shepherd.setConfKMD('CHIPS');
guiapp.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', appConfig.dev ? '*' : 'http://127.0.0.1:3000');
@ -114,9 +115,8 @@ process.once('loaded', () => {
applicationVersion: `${app.getVersion().replace('version=', '')}-beta`,
copyright: 'Released under the MIT license',
credits: 'SuperNET Team',
})
}
if (osPlatform === 'linux') {
});
} else if (osPlatform === 'linux') {
process.setFdLimit(appConfig.maxDescriptors.linux);
}
});
@ -149,9 +149,7 @@ const io = require('socket.io').listen(server);
const _zcashParamsExist = shepherd.zcashParamsExist();
let willQuitApp = false;
let mainWindow;
let loadingWindow;
let appCloseWindow;
let appSettingsWindow;
let closeAppAfterLoading = false;
let forceQuitApp = false;
@ -165,123 +163,13 @@ if (os.platform() === 'win32') {
agamaIcon = path.join(__dirname, '/assets/icons/agama_app_icon.ico');
}
function createLoadingWindow() {
mainWindow = null;
// initialise window
try {
loadingWindow = new BrowserWindow({
width: 500,
height: 355,
frame: false,
icon: agamaIcon,
show: false,
});
} catch(e) {}
loadingWindow.setResizable(false);
// check if agama is already running
portscanner.checkPortStatus(appConfig.agamaPort, '127.0.0.1', (error, status) => {
// Status is 'open' if currently in use or 'closed' if available
if (status === 'closed') {
server.listen(appConfig.agamaPort, () => {
shepherd.log(`guiapp and sockets.io are listening on port ${appConfig.agamaPort}`);
shepherd.writeLog(`guiapp and sockets.io are listening on port ${appConfig.agamaPort}`);
// start sockets.io
io.set('origins', appConfig.dev ? 'http://127.0.0.1:3000' : `http://127.0.0.1:${appConfig.agamaPort}`); // set origin
/*io.on('connection', function(client) {
shepherd.log('EDEX GUI is connected...');
shepherd.writeLog('EDEX GUI is connected...');
client.on('event', function(data) { // listen for client requests
shepherd.log(data);
});
client.on('disconnect', function(data) {
shepherd.log('EDEX GUI is disconnected');
});
client.on('join', function(data) {
shepherd.log(data);
client.emit('messages', 'Sockets server is listening');
});
});*/
});
} else {
willQuitApp = true;
server.listen(appConfig.agamaPort + 1, () => {
shepherd.log(`guiapp and sockets.io are listening on port ${appConfig.agamaPort + 1}`);
shepherd.writeLog(`guiapp and sockets.io are listening on port ${appConfig.agamaPort + 1}`);
});
loadingWindow.loadURL(`http://${appConfig.host}:${appConfig.agamaPort + 1}/gui/startup/agama-instance-error.html`);
shepherd.log('another agama app is already running');
}
});
shepherd.setIO(io); // pass sockets object to shepherd router
shepherd.setVar('appBasicInfo', appBasicInfo);
shepherd.setVar('appSessionHash', appSessionHash);
loadingWindow.createWindow = createWindow; // expose createWindow to front-end scripts
loadingWindow.appConfig = appConfig;
loadingWindow.forseCloseApp = forseCloseApp;
loadingWindow.createAppSettingsWindow = createAppSettingsWindow;
loadingWindow.startKMDNative = shepherd.startKMDNative;
loadingWindow.startSPV = shepherd.startSPV;
loadingWindow.arch = arch();
// load our index.html (i.e. easyDEX GUI)
loadingWindow.loadURL(`http://${appConfig.host}:${appConfig.agamaPort}/gui/startup`);
loadingWindow.webContents.on('did-finish-load', () => {
setTimeout(() => {
loadingWindow.show();
}, 40);
});
shepherd.writeLog('show loading window');
loadingWindow.on('hide', () => {
// our app does not have multiwindow - so we dereference the window object instead of
// putting them into an window_arr
loadingWindow = null;
});
loadingWindow.on('close', (e) => {
if (!forseCloseApp) {
if (willQuitApp) {
/* the user tried to quit the app */
loadingWindow = null;
} else {
/* the user only tried to close the window */
closeAppAfterLoading = true;
e.preventDefault();
}
}
});
}
// close app
function forseCloseApp() {
forceQuitApp = true;
app.quit();
}
function setDefaultAppSettings() {
shepherd.saveLocalAppConf(_defaultAppSettings);
}
function updateAppSettings(_settings) {
shepherd.saveLocalAppConf(_settings);
appConfig = _settings;
}
if (process.argv.indexOf('dexonly') > -1) {
app.on('ready', createLoadingWindow);
setTimeout(() => {
createWindow('open', true);
}, 500);
} else {
app.on('ready', createLoadingWindow);
}
app.on('ready', () => createWindow('open', process.argv.indexOf('dexonly') > -1 ? true : null));
function createAppCloseWindow() {
// initialise window
@ -304,66 +192,17 @@ function createAppCloseWindow() {
});
}
function reloadSettingsWindow() {
appSettingsWindow.loadURL(`http://${appConfig.host}:${appConfig.agamaPort}/gui/startup/app-settings.html`);
}
function createAppSettingsWindow() {
// initialise window
appSettingsWindow = new BrowserWindow({ // dirty hack to prevent main window flash on quit
width: 750,
height: 610,
frame: false,
icon: agamaIcon,
show: false,
});
appSettingsWindow.setResizable(false);
appSettingsWindow.appConfig = appConfig;
appSettingsWindow.appConfigSchema = shepherd.appConfigSchema;
appSettingsWindow.defaultAppSettings = _defaultAppSettings;
appSettingsWindow.destroyAppSettingsWindow = destroyAppSettingsWindow;
appSettingsWindow.reloadSettingsWindow = reloadSettingsWindow;
appSettingsWindow.testLocation = shepherd.testLocation;
appSettingsWindow.setDefaultAppSettings = setDefaultAppSettings;
appSettingsWindow.updateAppSettings = updateAppSettings;
appSettingsWindow.testBins = shepherd.testBins;
appSettingsWindow.zcashParamsExist = _zcashParamsExist;
appSettingsWindow.loadURL(`http://${appConfig.host}:${appConfig.agamaPort}/gui/startup/app-settings.html`);
appSettingsWindow.webContents.on('did-finish-load', () => {
setTimeout(() => {
appSettingsWindow.show();
}, 40);
});
}
function destroyAppSettingsWindow() {
appSettingsWindow.hide();
appSettingsWindow = null;
}
function createWindow(status, hideLoadingWindow) {
if (appSettingsWindow) {
destroyAppSettingsWindow();
if (process.argv.indexOf('spvcoins=all/add-all') > -1) {
shepherd.startSPV('kmd');
}
if (status === 'open') {
require(path.join(__dirname, 'private/mainmenu'));
// initialise window
mainWindow = new BrowserWindow({ // dirty hack to prevent main window flash on quit
width: closeAppAfterLoading ? 1 : 1280,
height: closeAppAfterLoading ? 1 : 800,
icon: agamaIcon,
show: false,
});
if (closeAppAfterLoading) {
mainWindow = null;
loadingWindow = null;
pm2Exit();
}
const staticMenu = Menu.buildFromTemplate([ // if static
@ -383,191 +222,242 @@ function createWindow(status, hideLoadingWindow) {
{ role: 'selectall' },
]);
// load our index.html (i.e. easyDEX GUI)
shepherd.writeLog('show edex gui');
mainWindow.appConfig = appConfig;
mainWindow.appConfigSchema = shepherd.appConfigSchema;
mainWindow.arch = arch();
mainWindow.appBasicInfo = appBasicInfo;
mainWindow.appSessionHash = appSessionHash;
mainWindow.assetChainPorts = require('./routes/ports.js');
mainWindow.agamaIcon = agamaIcon;
mainWindow.testLocation = shepherd.testLocation;
mainWindow.kmdMainPassiveMode = shepherd.kmdMainPassiveMode;
mainWindow.getAppRuntimeLog = shepherd.getAppRuntimeLog;
mainWindow.nativeCoindList = nativeCoindList;
mainWindow.zcashParamsExist = _zcashParamsExist;
mainWindow.zcashParamsExistPromise = shepherd.zcashParamsExistPromise;
mainWindow.zcashParamsDownloadLinks = shepherd.zcashParamsDownloadLinks;
mainWindow.isWindows = os.platform() === 'win32' ? true : false; // obsolete(?)
mainWindow.appExit = appExit;
mainWindow.getMaxconKMDConf = shepherd.getMaxconKMDConf;
mainWindow.setMaxconKMDConf = shepherd.setMaxconKMDConf;
mainWindow.getMMCacheData = shepherd.getMMCacheData;
mainWindow.activeSection = 'wallets';
mainWindow.argv = process.argv;
mainWindow.getAssetChainPorts = shepherd.getAssetChainPorts;
if (appConfig.dev) {
mainWindow.loadURL('http://127.0.0.1:3000');
} else {
mainWindow.loadURL(`http://${appConfig.host}:${appConfig.agamaPort}/gui/EasyDEX-GUI/react/build`);
}
// check if agama is already running
portscanner.checkPortStatus(appConfig.agamaPort, '127.0.0.1', (error, status) => {
// Status is 'open' if currently in use or 'closed' if available
if (status === 'closed') {
server.listen(appConfig.agamaPort, () => {
shepherd.log(`guiapp and sockets.io are listening on port ${appConfig.agamaPort}`);
shepherd.writeLog(`guiapp and sockets.io are listening on port ${appConfig.agamaPort}`);
// start sockets.io
io.set('origins', appConfig.dev ? 'http://127.0.0.1:3000' : `http://127.0.0.1:${appConfig.agamaPort}`); // set origin
});
mainWindow.webContents.on('did-finish-load', () => {
setTimeout(() => {
mainWindow.show();
// initialise window
mainWindow = new BrowserWindow({ // dirty hack to prevent main window flash on quit
width: closeAppAfterLoading ? 1 : 1280,
height: closeAppAfterLoading ? 1 : 850,
icon: agamaIcon,
show: false,
});
if (hideLoadingWindow &&
loadingWindow) {
loadingWindow.hide();
}
}, 40);
});
if (appConfig.dev) {
mainWindow.loadURL('http://127.0.0.1:3000');
} else {
mainWindow.loadURL(`http://${appConfig.host}:${appConfig.agamaPort}/gui/EasyDEX-GUI/react/build`);
}
shepherd.setIO(io); // pass sockets object to shepherd router
shepherd.setVar('appBasicInfo', appBasicInfo);
shepherd.setVar('appSessionHash', appSessionHash);
// load our index.html (i.e. easyDEX GUI)
shepherd.writeLog('show edex gui');
mainWindow.appConfig = appConfig;
mainWindow.appConfigSchema = shepherd.appConfigSchema;
mainWindow.arch = arch();
mainWindow.appBasicInfo = appBasicInfo;
mainWindow.appSessionHash = appSessionHash;
mainWindow.assetChainPorts = require('./routes/ports.js');
mainWindow.agamaIcon = agamaIcon;
mainWindow.testLocation = shepherd.testLocation;
mainWindow.kmdMainPassiveMode = shepherd.kmdMainPassiveMode;
mainWindow.getAppRuntimeLog = shepherd.getAppRuntimeLog;
mainWindow.nativeCoindList = nativeCoindList;
mainWindow.zcashParamsExist = _zcashParamsExist;
mainWindow.zcashParamsExistPromise = shepherd.zcashParamsExistPromise;
mainWindow.zcashParamsDownloadLinks = shepherd.zcashParamsDownloadLinks;
mainWindow.isWindows = os.platform() === 'win32' ? true : false; // obsolete(?)
mainWindow.appExit = appExit;
mainWindow.getMaxconKMDConf = shepherd.getMaxconKMDConf;
mainWindow.setMaxconKMDConf = shepherd.setMaxconKMDConf;
mainWindow.getMMCacheData = shepherd.getMMCacheData;
mainWindow.activeSection = 'wallets';
mainWindow.argv = process.argv;
mainWindow.getAssetChainPorts = shepherd.getAssetChainPorts;
mainWindow.spvFees = _spvFees;
mainWindow.startSPV = shepherd.startSPV;
mainWindow.startKMDNative = shepherd.startKMDNative;
mainWindow.addressVersionCheck = shepherd.addressVersionCheck;
mainWindow.getCoinByPub = shepherd.getCoinByPub;
mainWindow.resetSettings = function() { shepherd.saveLocalAppConf(__defaultAppSettings) };
mainWindow.createSeed = {
triggered: false,
firstLoginPH: null,
secondaryLoginPH: null,
};
for (let i = 0; i < process.argv.length; i++) {
if (process.argv[i].indexOf('nvote') > -1) {
console.log(`notary node elections chain ${process.argv[i].replace('nvote=', '')}`);
mainWindow.nnVoteChain = process.argv[i].replace('nvote=', '');
}
}
} else {
mainWindow = new BrowserWindow({
width: 500,
height: 355,
frame: false,
icon: agamaIcon,
show: false,
});
mainWindow.webContents.on('context-menu', (e, params) => { // context-menu returns params
const { selectionText, isEditable } = params; // params obj
mainWindow.setResizable(false);
mainWindow.forseCloseApp = forseCloseApp;
if (isEditable) {
editMenu.popup(mainWindow);
} else if (selectionText && selectionText.trim() !== '') {
staticMenu.popup(mainWindow);
willQuitApp = true;
server.listen(appConfig.agamaPort + 1, () => {
shepherd.log(`guiapp and sockets.io are listening on port ${appConfig.agamaPort + 1}`);
shepherd.writeLog(`guiapp and sockets.io are listening on port ${appConfig.agamaPort + 1}`);
});
mainWindow.loadURL(`http://${appConfig.host}:${appConfig.agamaPort + 1}/gui/startup/agama-instance-error.html`);
shepherd.log('another agama app is already running');
}
});
// DEVTOOLS - only for dev purposes - ca333
// mainWindow.webContents.openDevTools()
function appExit() {
const CloseDaemons = () => {
return new Promise((resolve, reject) => {
shepherd.log('Closing Main Window...');
shepherd.writeLog('exiting app...');
shepherd.quitKomodod(appConfig.cliStopTimeout);
mainWindow.webContents.on('did-finish-load', () => {
setTimeout(() => {
mainWindow.show();
}, 40);
});
/*loadingWindow.on('close', (e) => {
if (!forseCloseApp) {
if (willQuitApp) {
loadingWindow = null;
} else {
closeAppAfterLoading = true;
e.preventDefault();
}
}
});*/
mainWindow.webContents.on('context-menu', (e, params) => { // context-menu returns params
const { selectionText, isEditable } = params; // params obj
if (isEditable) {
editMenu.popup(mainWindow);
} else if (selectionText && selectionText.trim() !== '') {
staticMenu.popup(mainWindow);
}
});
const result = 'Closing daemons: done';
// DEVTOOLS - only for dev purposes - ca333
// mainWindow.webContents.openDevTools()
shepherd.log(result);
shepherd.writeLog(result);
resolve(result);
});
}
function appExit() {
const CloseDaemons = () => {
return new Promise((resolve, reject) => {
shepherd.log('Closing Main Window...');
shepherd.writeLog('exiting app...');
const HideMainWindow = () => {
return new Promise((resolve, reject) => {
const result = 'Hiding Main Window: done';
shepherd.quitKomodod(appConfig.cliStopTimeout);
shepherd.log('Exiting App...');
mainWindow = null;
shepherd.log(result);
resolve(result);
});
}
const result = 'Closing daemons: done';
const HideAppClosingWindow = () => {
return new Promise((resolve, reject) => {
appCloseWindow = null;
resolve(true);
});
}
shepherd.log(result);
shepherd.writeLog(result);
resolve(result);
});
}
const QuitApp = () => {
return new Promise((resolve, reject) => {
const result = 'Quiting App: done';
const HideMainWindow = () => {
return new Promise((resolve, reject) => {
const result = 'Hiding Main Window: done';
app.quit();
shepherd.log(result);
resolve(result);
});
}
shepherd.log('Exiting App...');
mainWindow = null;
shepherd.log(result);
resolve(result);
});
}
const closeApp = () => {
CloseDaemons()
.then(HideMainWindow)
.then(HideAppClosingWindow)
.then(QuitApp);
}
const HideAppClosingWindow = () => {
return new Promise((resolve, reject) => {
appCloseWindow = null;
resolve(true);
});
}
let _appClosingInterval;
const QuitApp = () => {
return new Promise((resolve, reject) => {
const result = 'Quiting App: done';
// shepherd.killRogueProcess('marketmaker');
if (!Object.keys(shepherd.coindInstanceRegistry).length ||
!appConfig.stopNativeDaemonsOnQuit) {
closeApp();
} else {
createAppCloseWindow();
shepherd.quitKomodod(appConfig.cliStopTimeout);
_appClosingInterval = setInterval(() => {
if (!Object.keys(shepherd.coindInstanceRegistry).length) {
closeApp();
}
}, 1000);
app.quit();
shepherd.log(result);
resolve(result);
});
}
const closeApp = () => {
CloseDaemons()
.then(HideMainWindow)
.then(HideAppClosingWindow)
.then(QuitApp);
}
let _appClosingInterval;
if (process.argv.indexOf('dexonly') > -1) {
shepherd.killRogueProcess('marketmaker');
}
if (!Object.keys(shepherd.coindInstanceRegistry).length ||
!appConfig.stopNativeDaemonsOnQuit) {
closeApp();
} else {
createAppCloseWindow();
shepherd.quitKomodod(appConfig.cliStopTimeout);
_appClosingInterval = setInterval(() => {
if (!Object.keys(shepherd.coindInstanceRegistry).length) {
closeApp();
}
}, 1000);
}
}
}
// if window closed we kill iguana proc
mainWindow.on('closed', () => {
appExit();
// close app
mainWindow.on('closed', () => {
appExit();
});
});
}
}
app.on('window-all-closed', () => {
//if (os.platform() !== 'win32') { ig.kill(); }
// if (os.platform() !== 'win32') { ig.kill(); }
// in osx apps stay active in menu bar until explictly closed or quitted by CMD Q
// so we do not kill the app --> for the case user clicks again on the iguana icon
// we open just a new window and respawn iguana proc
/*if (process.platform !== 'darwin' || process.platform !== 'linux' || process.platform !== 'win32') {
app.quit()
}*/
})
});
// Emitted before the application starts closing its windows.
// Calling event.preventDefault() will prevent the default behaviour, which is terminating the application.
app.on('before-quit', (event) => {
shepherd.log('before-quit');
// shepherd.killRogueProcess('marketmaker');
/*if (!forceQuitApp &&
mainWindow === null &&
loadingWindow != null) { // mainWindow not intitialised and loadingWindow not dereferenced
// loading window is still open
shepherd.log('before-quit prevented');
shepherd.writeLog('quit app after loading is done');
closeAppAfterLoading = true;
// obsolete(?)
let code = `$('#loading_status_text').html('Preparing to shutdown the wallet.<br/>Please wait while all daemons are closed...')`;
loadingWindow.webContents.executeJavaScript(code);
event.preventDefault();
}*/
if (process.argv.indexOf('dexonly') > -1) {
shepherd.killRogueProcess('marketmaker');
}
});
// Emitted when all windows have been closed and the application will quit.
// Calling event.preventDefault() will prevent the default behaviour, which is terminating the application.
app.on('will-quit', (event) => {
if (!forceQuitApp &&
mainWindow === null &&
loadingWindow != null) {
if (!forceQuitApp) {
// loading window is still open
shepherd.log('will-quit while loading window active');
event.preventDefault();
// event.preventDefault();
}
});
// Emitted when the application is quitting.
// Calling event.preventDefault() will prevent the default behaviour, which is terminating the application.
app.on('quit', (event) => {
if (!forceQuitApp &&
mainWindow === null &&
loadingWindow != null) {
if (!forceQuitApp) {
shepherd.log('quit while loading window active');
event.preventDefault();
// event.preventDefault();
}
})
app.on('activate', () => {
if (mainWindow === null) {}
});
app.commandLine.appendSwitch('ignore-certificate-errors'); // dirty hack

22
package.json

@ -1,7 +1,7 @@
{
"name": "agama-app",
"productName": "Agama",
"version": "0.2.24",
"version": "0.2.29",
"description": "Agama Wallet Desktop App",
"main": "main.js",
"scripts": {
@ -10,32 +10,38 @@
"make-rpm": "node make-rpm.js",
"make-deb": "node make-deb.js"
},
"repository": "https://github.com/SuperNETorg/Agama/",
"repository": "https://github.com/KomodoPlatform/Agama/",
"homepage": "http://supernet.org",
"keywords": [
"agama",
"SuperNET",
"komodo",
"pax"
"multicoin",
"wallet",
"spv"
],
"author": "SuperNET",
"author": "SuperNET Team",
"license": "MIT",
"devDependencies": {
"electron": "1.6.5",
"electron": "1.7.10",
"electron-installer-debian": "^0.6.0",
"electron-installer-redhat": "^0.5.0"
},
"dependencies": {
"adm-zip": "^0.4.7",
"arch": "^2.1.0",
"async": "^2.6.0",
"bigi": "^1.4.2",
"bip39": "^2.4.0",
"bitcoinjs-lib": "^3.2.0",
"bitcoinforksjs-lib": "git://github.com/bitcoinjs/bitcoinjs-lib#opt-in-bitcoincash-sighash",
"bitcoinjs-lib": "git://github.com/SuperNETorg/bitcoinjs-lib",
"bitcoinjs-lib-zcash": "git://github.com/pbca26/bitcoinjs-lib#zcash",
"bitcoinjs-lib-pos": "git://github.com/KomodoPlatform/bitcoinjs-lib-pos",
"bluebird": "^3.4.7",
"body-parser": "^1.15.2",
"buffer-reverse": "^1.0.1",
"coinkey": "^2.0.0",
"coinselect": "github:bitcoinjs/coinselect",
"electron": "1.6.5",
"electron": "1.8.2",
"express": "^4.14.0",
"fix-path": "^2.1.0",
"fs-extra": "^4.0.2",

126
private/kmdcli.js

@ -1,126 +0,0 @@
/*
* Copyright (c) 2015 Satinderjit Singh
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
*/
/*
* Agama komodo-cli paths
*
*/
var child_process = require('child_process'),
path = require('path'),
os = require('os');
if (os.platform() === 'darwin') {
var komodocliBin = path.join(__dirname, '../assets/bin/osx/komodo-cli'),
zcashcliBin = '/Applications/ZCashSwingWalletUI.app/Contents/MacOS/zcash-cli';
}
if (os.platform() === 'linux') {
var komodocliBin = path.join(__dirname, '../assets/bin/linux64/komodo-cli');
}
if (os.platform() === 'win32') {
var komodocliBin = path.join(__dirname, '../assets/bin/win64/komodo-cli.exe'),
komodocliBin = path.normalize(komodocliBin);
}
console.log(komodocliBin)
/**
* The **komodo-cli** command is used to get komodo api calls answer.
*
* @private
* @category kmdcli
*
*/
var kmdcli = module.exports = {
exec: child_process.exec,
command: command
};
/**
* Parses komodo-cli commands.
*
* @private
* @static
* @category kmdcli
* @param {function} callback The callback function.
*
*/
function parse_kmdcli_commands(callback) {
return function(error, stdout, stderr) {
if (error) callback(error, stderr);
else callback(error, stdout);
//console.log(stdout)
};
}
/**
* Parses komodo-cli commands.
*
* @private
* @static
* @category kmdcli
* @param {function} callback The callback function.
* @example
*
* var kmdcli = require('./kmdcli');
*
* kmdcli.command('getinfo', function(err, command) {
* console.log(command);
* });
*
* // =>
* {
* "version" : 1000550,
* "protocolversion" : 170002,
* "notarized" : 254740,
* "notarizedhash" : "01f4f1c46662ccca2e7fa9e7e38d4d2e4ced4402fa0f4fc116b8f004bb8cf272",
* "notarizedtxid" : "2b16e47a176f8c1886ca0268243f9b96f8b2db466ea26ae99873d5224bbf80b6",
* "walletversion" : 60000,
* "balance" : 32632.46167742,
* "interest" : 0.00478671,
* "blocks" : 254791,
* "longestchain" : 254791,
* "timeoffset" : 0,
* "tiptime" : 1490815616,
* "connections" : 8,
* "proxy" : "",
* "difficulty" : 707836.56791394,
* "testnet" : false,
* "keypoololdest" : 1482746526,
* "keypoolsize" : 101,
* "paytxfee" : 0.00000000,
* "relayfee" : 0.00001000,
* "errors" : "WARNING: check your network connection, 157 blocks received in the last 4 hours (240 expected)",
* "notaryid" : -1,
* "pubkey" : "000000000000000000000000000000000000000000000000000000000000000000"
* }
*
*/
function command(kmd_command, callback) {
if (callback) {
return this.exec(komodocliBin + " " + kmd_command,
parse_kmdcli_commands(callback));
}
}

36
private/mainmenu.js

@ -86,42 +86,18 @@ const template = [
}
]
},
/*{
{
role: 'help',
label: 'Support',
label: 'Debug',
submenu: [
{
label: 'Supernet.org',
click () {
if (process.platform === 'linux') {
require('child_process').exec('xdg-open http://support.supernet.org');
} else {
shell.openExternal('http://support.supernet.org');
}
}
},
{
label: 'Slack',
click () {
if (process.platform === 'linux') {
require('child_process').exec('xdg-open https://sprnt.slack.com/messages/support');
} else {
shell.openExternal('https://sprnt.slack.com/messages/support');
}
label: 'Reset settings',
click (item, focusedWindow) {
focusedWindow.resetSettings();
}
},
{
label: 'Github',
click () {
if (process.platform === 'linux') {
require('child_process').exec('xdg-open https://github.com/SuperNETorg/iguana/issues');
} else {
shell.openExternal('https://github.com/SuperNETorg/iguana/issues');
}
}
}
]
}*/
}
]
if (process.platform === 'darwin') {

24
routes/appConfig.js

@ -17,6 +17,9 @@ const appConfig = {
cliStopTimeout: 1000,
failedRPCAttemptsThreshold: 10,
stopNativeDaemonsOnQuit: true,
lang: 'EN',
rpc2cli: false,
fiatRates: false,
},
schema: {
host: {
@ -105,6 +108,27 @@ const appConfig = {
info: 'Number of allowed consequent RPC connect failures before the app marks native coin daemon as not running properly',
type: 'number',
},
lang: {
display: true,
displayName: 'Language',
type: 'select',
data: [
{ name: 'EN', label: 'English' },
{ name: 'DE', label: 'German' }
],
},
rpc2cli: {
display: true,
displayName: 'Disable RPC',
info: 'Use CLI instead of RPC JSON server in native mode',
type: 'boolean',
},
fiatRates: {
display: true,
displayName: 'Fetch fiat rates',
info: 'Get coin fiat rates from atomicexplorer.com',
type: 'boolean',
},
},
};

289
routes/electrumjs/electrumServers.js

@ -1,9 +1,4 @@
let electrumServers = {
/*zcash: {
address: '173.212.225.176',
port: 50032,
proto: 'tcp',
},*/
coqui: { // !estimatefee
address: 'electrum1.cipig.net',
port: 10011,
@ -141,6 +136,61 @@ let electrumServers = {
'electrum2.cipig.net:10014'
],
},
mgw: { // !estimatefee
address: 'electrum1.cipig.net',
port: 10015,
proto: 'tcp',
txfee: 10000,
abbr: 'MGW',
serverList: [
'electrum1.cipig.net:10015',
'electrum2.cipig.net:10015'
],
},
btch: { // !estimatefee
address: 'electrum1.cipig.net',
port: 10020,
proto: 'tcp',
txfee: 10000,
abbr: 'BTCH',
serverList: [
'electrum1.cipig.net:10020',
'electrum2.cipig.net:10020'
],
},
beer: { // !estimatefee
address: 'electrum1.cipig.net',
port: 10022,
proto: 'tcp',
txfee: 10000,
abbr: 'BEER',
serverList: [
'electrum1.cipig.net:10022',
'electrum2.cipig.net:10022'
],
},
pizza: { // !estimatefee
address: 'electrum1.cipig.net',
port: 10024,
proto: 'tcp',
txfee: 10000,
abbr: 'PIZZA',
serverList: [
'electrum1.cipig.net:10024',
'electrum2.cipig.net:10024'
],
},
vote: { // !estimatefee
address: 'electrum1.cipig.net',
port: 10021,
proto: 'tcp',
txfee: 10000,
abbr: 'VOTE',
serverList: [
'electrum1.cipig.net:10021',
'electrum2.cipig.net:10021'
],
},
jumblr: { // !estimatefee
address: 'electrum1.cipig.net',
port: 10004,
@ -169,13 +219,21 @@ let electrumServers = {
proto: 'tcp',
txfee: 100000000,
abbr: 'DOGE',
serverList: [
'173.212.225.176:50015',
'136.243.45.140:50015'
],
},
viacoin: { // !estimatefee
address: 'vialectrum.bitops.me',
port: 50002,
proto: 'ssl',
address: '173.212.225.176',
port: 50033,
proto: 'tcp',
txfee: 100000,
abbr: 'VIA',
serverList: [
'173.212.225.176:50033',
'136.243.45.140:50033'
],
},
vertcoin: {
address: '173.212.225.176',
@ -183,6 +241,10 @@ let electrumServers = {
proto: 'tcp',
txfee: 100000,
abbr: 'VTC',
serverList: [
'173.212.225.176:50088',
'136.243.45.140:50088'
],
},
namecoin: {
address: '173.212.225.176',
@ -190,6 +252,10 @@ let electrumServers = {
proto: 'tcp',
txfee: 100000,
abbr: 'NMC',
serverList: [
'173.212.225.176:50036',
'136.243.45.140:50036'
],
},
monacoin: { // !estimatefee
address: '173.212.225.176',
@ -197,13 +263,21 @@ let electrumServers = {
proto: 'tcp',
txfee: 100000,
abbr: 'MONA',
serverList: [
'173.212.225.176:50002',
'136.243.45.140:50002'
],
},
litecoin: {
address: '173.212.225.176',
port: 50012,
proto: 'tcp',
txfee: 10000,
txfee: 100000,
abbr: 'LTC',
serverList: [
'173.212.225.176:50012',
'136.243.45.140:50012'
],
},
faircoin: {
address: '173.212.225.176',
@ -211,13 +285,21 @@ let electrumServers = {
proto: 'tcp',
txfee: 1000000,
abbr: 'FAIR',
serverList: [
'173.212.225.176:50005',
'136.243.45.140:50005'
],
},
digibyte: {
dgb: {
address: '173.212.225.176',
port: 50022,
proto: 'tcp',
txfee: 100000,
abbr: 'DGB',
serverList: [
'173.212.225.176:50022',
'136.243.45.140:50022'
],
},
dash: {
address: '173.212.225.176',
@ -225,6 +307,10 @@ let electrumServers = {
proto: 'tcp',
txfee: 10000,
abbr: 'DASH',
serverList: [
'173.212.225.176:50098',
'136.243.45.140:50098'
],
},
crown: {
address: '173.212.225.176',
@ -232,12 +318,70 @@ let electrumServers = {
proto: 'tcp',
txfee: 10000,
abbr: 'CRW',
serverList: [
'173.212.225.176:50041',
'136.243.45.140:50041'
],
},
bitcoin: {
address: '173.212.225.176',
btc: {
address: 'e-x.not.fyi',
port: 50001,
proto: 'tcp',
abbr: 'BTC',
serverList: [
'mooo.not.fyi:50011',
'e-x.not.fyi:50001',
'vps.hsmiths.com:50001',
'us.electrum.be:50001',
'electrumx.bot.nu:50001',
'btc.asis.io:50001',
'electrum.backplanedns.org:50001',
'electrum.festivaldelhumor.org:50001'
],
},
btg: {
address: '173.212.225.176',
port: 10052,
proto: 'tcp',
abbr: 'BTG',
txfee: 10000,
serverList: [
'173.212.225.176:10052',
'94.130.224.11:10052'
],
},
blk: { // pos
address: 'electrum1.cipig.net',
port: 10054,
proto: 'tcp',
abbr: 'BLK',
txfee: 10000,
serverList: [
'electrum1.cipig.net:10054',
'electrum2.cipig.net:10054'
],
},
sib: {
address: 'electrum1.cipig.net',
port: 10050,
proto: 'tcp',
abbr: 'SIB',
txfee: 10000,
serverList: [
'electrum1.cipig.net:10050',
'electrum2.cipig.net:10050'
],
},
bch: {
address: 'electrum1.cipig.net',
port: 10051,
proto: 'tcp',
abbr: 'BCH',
txfee: 10000,
serverList: [
'electrum1.cipig.net:10051',
'electrum2.cipig.net:10051'
],
},
argentum: { // !estimatefee
address: '173.212.225.176',
@ -245,18 +389,131 @@ let electrumServers = {
proto: 'tcp',
txfee: 50000,
abbr: 'ARG',
serverList: [
'173.212.225.176:50081',
'136.243.45.140:50081'
],
},
chips: { // !estimatefee
address: '173.212.225.176',
port: 50076,
address: 'electrum1.cipig.net',
port: 10053,
proto: 'tcp',
txfee: 10000,
abbr: 'CHIPS',
serverList: [
'173.212.225.176:50076',
'136.243.45.140:50076'
'electrum1.cipig.net:10053',
'electrum2.cipig.net:10053'
],
},
zec: {
address: '173.212.225.176',
port: 50032,
proto: 'tcp',
txfee: 10000,
abbr: 'ZEC',
serverList: [
'173.212.225.176:50032',
'136.243.45.140:50032'
],
},
hush: {
address: '173.212.225.176',
port: 50013,
proto: 'tcp',
txfee: 10000,
abbr: 'HUSH',
serverList: [
'173.212.225.176:50013',
'136.243.45.140:50013'
],
},
xmy: {
address: 'cetus.cryptap.us',
port: 50004,
proto: 'ssl',
txfee: 5000,
abbr: 'XMY',
serverList: [
'cetus.cryptap.us:50004',
'kraken.cryptap.us:50004'
],
},
zcl: {
address: 'electrum1.cipig.net',
port: 50055,
proto: 'tcp',
txfee: 1000,
abbr: 'ZCL',
serverList: [
'electrum1.cipig.net:10055',
'electrum2.cipig.net:10055'
],
},
hodlc: {
address: 'hodl.amit177.cf',
port: 17989,
proto: 'tcp',
txfee: 5000,
abbr: 'HODLC',
serverList: [
'hodl.amit177.cf:17989',
'hodl2.amit177.cf:17898'
],
},
btx: {
address: 'electrum1.cipig.net',
port: 10057,
proto: 'tcp',
txfee: 50000,
abbr: 'BTX',
serverList: [
'electrum1.cipig.net:10057',
'electrum2.cipig.net:10057'
],
},
btcz: {
address: 'electrum1.cipig.net',
port: 10056,
proto: 'tcp',
txfee: 10000,
abbr: 'BTCZ',
serverList: [
'electrum1.cipig.net:10056',
'electrum2.cipig.net:10056'
],
},
grs: {
address: 'electrum10.groestlcoin.org',
port: 50001,
proto: 'tcp',
txfee: 50000,
abbr: 'GRS',
serverList: [
'electrum10.groestlcoin.org:50001',
'electrum11.groestlcoin.org:50001'
],
},
qtum: {
address: 's1.qtum.info',
port: 50001,
proto: 'tcp',
txfee: 400000,
abbr: 'QTUM',
serverList: [
's1.qtum.info:50001',
's2.qtum.info:50001'
],
},
};
electrumServers.crw = electrumServers.crown;
electrumServers.fair = electrumServers.faircoin;
electrumServers.arg = electrumServers.argentum;
electrumServers.ltc = electrumServers.litecoin;
electrumServers.mona = electrumServers.litecoin;
electrumServers.nmc = electrumServers.namecoin;
electrumServers.vtc = electrumServers.vertcoin;
electrumServers.via = electrumServers.viacoin;
electrumServers.doge = electrumServers.dogecoin;
module.exports = electrumServers;

461
routes/electrumjs/electrumjs.networks.js

@ -2,204 +2,363 @@
var bitcoin = require('bitcoinjs-lib');
var networks = exports;
Object.keys(bitcoin.networks).forEach(function(key){
networks[key] = bitcoin.networks[key]
Object.keys(bitcoin.networks).forEach((key) => {
networks[key] = bitcoin.networks[key];
});
networks.litecoin = {
messagePrefix: '\x19Litecoin Signed Message:\n',
bip32: {
public: 0x019da462,
private: 0x019d9cfe
},
pubKeyHash: 0x30,
scriptHash: 0x32,
wif: 0xb0,
dustThreshold: 0, // https://github.com/litecoin-project/litecoin/blob/v0.8.7.2/src/main.cpp#L360-L365
}
messagePrefix: '\x19Litecoin Signed Message:\n',
bip32: {
public: 0x019da462,
private: 0x019d9cfe
},
pubKeyHash: 0x30,
scriptHash: 0x32,
wif: 0xb0,
dustThreshold: 0 // https://github.com/litecoin-project/litecoin/blob/v0.8.7.2/src/main.cpp#L360-L365
};
networks.dogecoin = {
messagePrefix: '\x19Dogecoin Signed Message:\n',
bip32: {
public: 0x02facafd,
private: 0x02fac398,
},
pubKeyHash: 0x1e,
scriptHash: 0x16,
wif: 0x9e,
dustThreshold: 0 // https://github.com/dogecoin/dogecoin/blob/v1.7.1/src/core.h#L155-L160
messagePrefix: '\x19Dogecoin Signed Message:\n',
bip32: {
public: 0x02facafd,
private: 0x02fac398,
},
pubKeyHash: 0x1e,
scriptHash: 0x16,
wif: 0x9e,
dustThreshold: 0, // https://github.com/dogecoin/dogecoin/blob/v1.7.1/src/core.h#L155-L160
};
// https://github.com/monacoinproject/monacoin/blob/master-0.10/src/chainparams.cpp#L161
networks.monacoin = {
messagePrefix: '\x19Monacoin Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x32,
scriptHash: 0x05,
wif: 0xB2,
dustThreshold: 546, // https://github.com/bitcoin/bitcoin/blob/v0.9.2/src/core.h#L151-L162
messagePrefix: '\x19Monacoin Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x32,
scriptHash: 0x05,
wif: 0xB2,
dustThreshold: 546, // https://github.com/bitcoin/bitcoin/blob/v0.9.2/src/core.h#L151-L162
};
// https://github.com/gamecredits-project/GameCredits/blob/master/src/chainparams.cpp#L136
networks.game = {
messagePrefix: '\x19GameCredits Signed Message:\n',
bip32: {
public: 0x043587cf,
private: 0x04358394,
},
pubKeyHash: 0x6f,
scriptHash: 0xc4,
wif: 0xef,
dustThreshold: 546, // https://github.com/bitcoin/bitcoin/blob/v0.9.2/src/core.h#L151-L162
messagePrefix: '\x19GameCredits Signed Message:\n',
bip32: {
public: 0x043587cf,
private: 0x04358394,
},
pubKeyHash: 0x6f,
scriptHash: 0xc4,
wif: 0xef,
dustThreshold: 546, // https://github.com/bitcoin/bitcoin/blob/v0.9.2/src/core.h#L151-L162
};
// https://github.com/dashpay/dash/blob/master/src/chainparams.cpp#L171
networks.dash = {
messagePrefix: '\x19DarkCoin Signed Message:\n',
bip32: {
public: 0x02fe52f8,
private: 0x02fe52cc,
},
pubKeyHash: 0x4c,
scriptHash: 0x10,
wif: 0xcc,
dustThreshold: 5460, // https://github.com/dashpay/dash/blob/v0.12.0.x/src/primitives/transaction.h#L144-L155
messagePrefix: '\x19DarkCoin Signed Message:\n',
bip32: {
public: 0x02fe52f8,
private: 0x02fe52cc,
},
pubKeyHash: 0x4c,
scriptHash: 0x10,
wif: 0xcc,
dustThreshold: 5460, // https://github.com/dashpay/dash/blob/v0.12.0.x/src/primitives/transaction.h#L144-L155
};
// https://github.com/zcoinofficial/zcoin/blob/c93eccb39b07a6132cb3d787ac18be406b24c3fa/src/base58.h#L275
networks.zcoin = {
messagePrefix: '\x19ZCoin Signed Message:\n',
bip32: {
public: 0x0488b21e, // todo
private: 0x0488ade4, // todo
},
pubKeyHash: 0x52,
scriptHash: 0x07,
wif: 0x52 + 128,
dustThreshold: 1000, // https://github.com/zcoinofficial/zcoin/blob/f755f95a036eedfef7c96bcfb6769cb79278939f/src/main.h#L59
messagePrefix: '\x19ZCoin Signed Message:\n',
bip32: {
public: 0x0488b21e, // todo
private: 0x0488ade4, // todo
},
pubKeyHash: 0x52,
scriptHash: 0x07,
wif: 0x52 + 128,
dustThreshold: 1000, // https://github.com/zcoinofficial/zcoin/blob/f755f95a036eedfef7c96bcfb6769cb79278939f/src/main.h#L59
};
// https://raw.githubusercontent.com/jl777/komodo/beta/src/chainparams.cpp
networks.komodo = {
messagePrefix: '\x19Komodo Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x3c,
scriptHash: 0x55,
wif: 0xbc,
dustThreshold: 1000,
messagePrefix: '\x19Komodo Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x3c,
scriptHash: 0x55,
wif: 0xbc,
dustThreshold: 1000,
};
networks.viacoin = {
messagePrefix: '\x19Viacoin Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x47,
scriptHash: 0x21,
wif: 0xc7,
dustThreshold: 1000,
messagePrefix: '\x19Viacoin Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x47,
scriptHash: 0x21,
wif: 0xc7,
dustThreshold: 1000,
};
networks.vertcoin = {
messagePrefix: '\x19Vertcoin Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x47,
scriptHash: 0x5,
wif: 0x80,
dustThreshold: 1000,
messagePrefix: '\x19Vertcoin Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x47,
scriptHash: 0x5,
wif: 0x80,
dustThreshold: 1000,
};
networks.namecoin = {
messagePrefix: '\x19Namecoin Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x34,
scriptHash: 0xd,
wif: 0xb4,
dustThreshold: 1000,
messagePrefix: '\x19Namecoin Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x34,
scriptHash: 0xd,
wif: 0xb4,
dustThreshold: 1000,
};
networks.faircoin = {
messagePrefix: '\x19Faircoin Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x5f,
scriptHash: 0x24,
wif: 0xdf,
dustThreshold: 1000,
messagePrefix: '\x19Faircoin Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x5f,
scriptHash: 0x24,
wif: 0xdf,
dustThreshold: 1000,
};
networks.digibyte = {
messagePrefix: '\x19Digibyte Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x1e,
scriptHash: 0x5,
wif: 0x80,
dustThreshold: 1000,
messagePrefix: '\x19Digibyte Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x1e,
scriptHash: 0x5,
wif: 0x80,
dustThreshold: 1000,
};
networks.crown = {
messagePrefix: '\x19Crown Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x0,
scriptHash: 0x1c,
wif: 0x80,
dustThreshold: 1000,
messagePrefix: '\x19Crown Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x0,
scriptHash: 0x1c,
wif: 0x80,
dustThreshold: 1000,
};
networks.argentum = {
messagePrefix: '\x19Argentum Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x17,
scriptHash: 0x5,
wif: 0x97,
dustThreshold: 1000,
messagePrefix: '\x19Argentum Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x17,
scriptHash: 0x5,
wif: 0x97,
dustThreshold: 1000,
};
networks.chips = {
messagePrefix: '\x19Chips Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x3c,
scriptHash: 0x55,
wif: 0xbc,
dustThreshold: 1000,
};
/*networks.zcash = {
messagePrefix: '\x19Zcash Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x1cb8,
scriptHash: 0x1cbd,
wif: 0x80,
dustThreshold: 1000,
};*/
messagePrefix: '\x19Chips Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x3c,
scriptHash: 0x55,
wif: 0xbc,
dustThreshold: 1000,
};
networks.btg = {
messagePrefix: '\x19BitcoinGold Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x26,
scriptHash: 0x17,
wif: 0x80,
dustThreshold: 1000,
};
networks.bch = {
messagePrefix: '\x19BitcoinCash Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x0,
scriptHash: 0x5,
wif: 0x80,
dustThreshold: 1000,
};
networks.blk = {
messagePrefix: '\x19BlackCoin Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x19,
scriptHash: 0x55,
wif: 0x99,
dustThreshold: 1000,
isPoS: true,
};
networks.sib = {
messagePrefix: '\x19SibCoin Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x3f,
scriptHash: 0x28,
wif: 0x80,
dustThreshold: 1000,
};
networks.zcash = {
messagePrefix: '\x19Zcash Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x05358394,
},
pubKeyHash: 0x1cb8,
scriptHash: 0x1cbd,
wif: 0x80,
dustThreshold: 1000,
};
networks.hush = {
messagePrefix: '\x19Hush Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x1cb8,
scriptHash: 0x1cbd,
wif: 0x80,
dustThreshold: 1000,
};
networks.zcl = {
messagePrefix: '\x19Zclassic Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x1cb8,
scriptHash: 0x1cbd,
wif: 0x80,
dustThreshold: 1000,
};
networks.xmy = {
messagePrefix: '\x19Myriad Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x32,
scriptHash: 0x9,
wif: 0xB2,
dustThreshold: 1000,
};
networks.hodlc = {
messagePrefix: '\x19Hodlc Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x28,
scriptHash: 0x5,
wif: 0x28 + 128,
dustThreshold: 1000,
};
networks.qtum = {
messagePrefix: '\x19Qtum Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x3A,
scriptHash: 0x32,
wif: 0x80,
dustThreshold: 1000,
};
networks.btx = {
messagePrefix: '\x19Bitcore Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x0,
scriptHash: 0x5,
wif: 0x80,
dustThreshold: 1000,
};
networks.btcz = {
messagePrefix: '\x19BitcoinZ Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x1cb8,
scriptHash: 0x1cbd,
wif: 0x80,
dustThreshold: 1000,
};
networks.grs = { // fails to gen a proper addr
messagePrefix: '\x19Groestlcoin Signed Message:\n',
bip32: {
public: 0x0488b21e,
private: 0x0488ade4,
},
pubKeyHash: 0x24,
scriptHash: 0x5,
wif: 0x80,
dustThreshold: 1000,
};
networks.btc = networks.bitcoin;
networks.crw = networks.crown;
networks.dgb = networks.digibyte;
networks.arg = networks.argentum;
networks.zec = networks.zcash;
networks.nmc = networks.namecoin;
networks.ltc = networks.litecoin;
networks.vtc = networks.vertcoin;
networks.via = networks.viacoin;
networks.fair = networks.faircoin;
networks.doge = networks.dogecoin;
networks.kmd = networks.komodo;
networks.mona = networks.monacoin;

120
routes/electrumjs/electrumjs.txdecoder-2bytes.js

@ -0,0 +1,120 @@
/*
MIT License
Copyright (c) 2017 Yuki Akiyama, SuperNET
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
var bitcoin = require('bitcoinjs-lib-zcash');
var decodeFormat = function(tx) {
var result = {
txid: tx.getId(),
version: tx.version,
locktime: tx.locktime,
};
return result;
}
var decodeInput = function(tx) {
var result = [];
tx.ins.forEach(function(input, n) {
var vin = {
txid: input.hash.reverse().toString('hex'),
n: input.index,
script: bitcoin.script.toASM(input.script),
sequence: input.sequence,
};
result.push(vin);
});
return result;
}
var decodeOutput = function(tx, network) {
var format = function(out, n, network) {
var vout = {
satoshi: out.value,
value: (1e-8 * out.value).toFixed(8),
n: n,
scriptPubKey: {
asm: bitcoin.script.toASM(out.script),
hex: out.script.toString('hex'),
type: bitcoin.script.classifyOutput(out.script),
addresses: [],
},
};
switch(vout.scriptPubKey.type) {
case 'pubkeyhash':
vout.scriptPubKey.addresses.push(bitcoin.address.fromOutputScript(out.script, network));
break;
case 'pubkey':
const pubKeyBuffer = new Buffer(vout.scriptPubKey.asm.split(' ')[0], 'hex');
vout.scriptPubKey.addresses.push(bitcoin.ECPair.fromPublicKeyBuffer(pubKeyBuffer, network).getAddress());
break;
case 'scripthash':
vout.scriptPubKey.addresses.push(bitcoin.address.fromOutputScript(out.script, network));
break;
}
return vout;
}
var result = [];
tx.outs.forEach(function(out, n) {
result.push(format(out, n, network));
});
return result;
}
var TxDecoder = module.exports = function(rawtx, network) {
try {
const _tx = bitcoin.Transaction.fromHex(rawtx);
return {
tx: _tx,
network: network,
format: decodeFormat(_tx),
inputs: decodeInput(_tx),
outputs: decodeOutput(_tx, network),
};
} catch (e) {
return false;
}
}
TxDecoder.prototype.decode = function() {
var result = {};
var self = this;
Object.keys(self.format).forEach(function(key) {
result[key] = self.format[key];
});
result.outputs = self.outputs;
return result;
}

123
routes/electrumjs/electrumjs.txdecoder-pos.js

@ -0,0 +1,123 @@
/*
MIT License
Copyright (c) 2017 Yuki Akiyama, SuperNET
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
var bitcoin = require('bitcoinjs-lib-pos');
var script = require('bitcoinjs-lib-pos/src/script');
var address = require('bitcoinjs-lib-pos/src/address');
var bitcoinJS = require('bitcoinjs-lib');
var decodeFormat = function(tx) {
var result = {
txid: tx.getId(),
version: tx.version,
locktime: tx.locktime,
};
return result;
}
var decodeInput = function(tx) {
var result = [];
tx.ins.forEach(function(input, n) {
var vin = {
txid: input.hash.reverse().toString('hex'),
n: input.index,
script: script.fromHex(input.hash),
sequence: input.sequence,
};
result.push(vin);
});
return result;
}
var decodeOutput = function(tx, network) {
var format = function(out, n, network) {
var vout = {
satoshi: out.value,
value: (1e-8 * out.value).toFixed(8),
n: n,
scriptPubKey: {
asm: bitcoinJS.script.toASM(out.script.chunks),
hex: out.script.toHex(),
type: bitcoin.scripts.classifyOutput(out.script),
addresses: [],
},
};
switch(vout.scriptPubKey.type) {
case 'pubkeyhash':
vout.scriptPubKey.addresses.push(address.fromOutputScript(out.script, network));
break;
case 'pubkey':
const pubKeyBuffer = new Buffer(vout.scriptPubKey.asm.split(' ')[0], 'hex');
vout.scriptPubKey.addresses.push(bitcoin.ECPair.fromPublicKeyBuffer(pubKeyBuffer, network).getAddress());
break;
case 'scripthash':
vout.scriptPubKey.addresses.push(address.fromOutputScript(out.script, network));
break;
}
return vout;
}
var result = [];
tx.outs.forEach(function(out, n) {
result.push(format(out, n, network));
});
return result;
}
var TxDecoder = module.exports = function(rawtx, network) {
try {
const _tx = bitcoin.Transaction.fromHex(rawtx, network);
return {
tx: _tx,
network: network,
format: decodeFormat(_tx),
inputs: decodeInput(_tx),
outputs: decodeOutput(_tx, network),
};
} catch (e) {
return false;
}
}
TxDecoder.prototype.decode = function() {
var result = {};
var self = this;
Object.keys(self.format).forEach(function(key) {
result[key] = self.format[key];
});
result.outputs = self.outputs;
return result;
}

40
routes/ports.js

@ -1,6 +1,10 @@
// default daemon ports
const assetChainPorts = {
'komodod': '7771',
'markermaker': '7783',
'PIZZA': '11608',
'BEER': '8923',
'CHIPS': '57776',
'SUPERNET': '11341',
'REVS': '10196',
@ -20,38 +24,10 @@ const assetChainPorts = {
'KV': '8299',
'CEAL': '11116',
'MESH': '9455',
'USD': '13967',
'CHF': '15312',
'CAD': '8720',
'BRL': '9914',
'BGN': '9110',
'AUD': '8045',
'PLN': '13493',
'PHP': '11181',
'NZD': '10915',
'NOK': '11588',
'MYR': '10688',
'MXN': '13970',
'KRW': '14020',
'JPY': '13145',
'INR': '10536',
'ILS': '14638',
'IDR': '14459',
'HKD': '15409',
'HUF': '13699',
'HRK': '12617',
'GBP': '11505',
'EUR': '8065',
'DKK': '13830',
'CNY': '10384',
'ZAR': '15160',
'TRY': '13924',
'THB': '11847',
'SGD': '14475',
'SEK': '11447',
'RON': '8675',
'RUB': '8199',
'CZK': '9482'
'AXO': '12927',
'ETOMIC': '10271',
'VOTE': '8012',
'BTCH': '8800',
};
module.exports = assetChainPorts;

12
routes/shepherd.js

@ -18,7 +18,6 @@ shepherd.Promise = require('bluebird');
shepherd.exec = require('child_process').exec;
shepherd.execFile = require('child_process').execFile;
shepherd.sha256 = require('sha256');
shepherd.CoinKey = require('coinkey');
shepherd.bitcoinJS = require('bitcoinjs-lib');
shepherd.coinSelect = require('coinselect');
shepherd.fixPath = require('fix-path');
@ -38,6 +37,8 @@ shepherd.rpcConf = {};
shepherd.appRuntimeLog = [];
shepherd.appRuntimeSPVLog = [];
shepherd.lockDownAddCoin = false;
// dex cache
shepherd.mmupass = null;
shepherd.mmRatesInterval = null;
shepherd.mmPublic = {
@ -60,9 +61,10 @@ shepherd.electrumCoins = {
};
shepherd.electrumKeys = {};
shepherd.electrumCache = {};
shepherd.electrumJSCore = require('./electrumjs/electrumjs.core.js');
shepherd.electrumJSNetworks = require('./electrumjs/electrumjs.networks.js');
shepherd.electrumJSTxDecoder = require('./electrumjs/electrumjs.txdecoder.js');
shepherd.electrumServers = require('./electrumjs/electrumServers.js');
shepherd.CONNECTION_ERROR_OR_INCOMPLETE_DATA = 'connection error or incomplete data';
@ -96,6 +98,8 @@ shepherd = require('./shepherd/electrum/balance.js')(shepherd);
shepherd = require('./shepherd/electrum/transactions.js')(shepherd);
shepherd = require('./shepherd/electrum/block.js')(shepherd);
shepherd = require('./shepherd/electrum/createtx.js')(shepherd);
shepherd = require('./shepherd/electrum/createtx-split.js')(shepherd);
shepherd = require('./shepherd/electrum/createtx-multi.js')(shepherd);
shepherd = require('./shepherd/electrum/interest.js')(shepherd);
shepherd = require('./shepherd/electrum/listunspent.js')(shepherd);
shepherd = require('./shepherd/electrum/estimate.js')(shepherd);
@ -109,7 +113,6 @@ shepherd = require('./shepherd/dex/electrumServersList.js')(shepherd);
// core
shepherd = require('./shepherd/addCoinShortcuts.js')(shepherd);
shepherd = require('./shepherd/dashboardUpdate.js')(shepherd);
shepherd = require('./shepherd/binsTestUtil.js')(shepherd);
shepherd = require('./shepherd/binsUtils.js')(shepherd);
shepherd = require('./shepherd/downloadUtil.js')(shepherd);
shepherd = require('./shepherd/init.js')(shepherd);
@ -129,6 +132,9 @@ shepherd = require('./shepherd/auth.js')(shepherd);
shepherd = require('./shepherd/coins.js')(shepherd);
shepherd = require('./shepherd/coindWalletKeys.js')(shepherd);
// elections
shepherd = require('./shepherd/elections.js')(shepherd);
// explorer
// shepherd = require('./shepherd/explorer/overview.js')(shepherd);

152
routes/shepherd/addCoinShortcuts.js

@ -1,3 +1,5 @@
const electrumServers = require('../electrumjs/electrumServers');
module.exports = (shepherd) => {
shepherd.startSPV = (coin) => {
if (coin === 'KMD+REVS+JUMBLR') {
@ -5,7 +7,13 @@ module.exports = (shepherd) => {
shepherd.addElectrumCoin('REVS');
shepherd.addElectrumCoin('JUMBLR');
} else {
shepherd.addElectrumCoin(coin);
if (process.argv.indexOf('spvcoins=all/add-all') > -1) {
for (let key in electrumServers) {
shepherd.addElectrumCoin(electrumServers[key].abbr);
}
} else {
shepherd.addElectrumCoin(coin);
}
}
}
@ -32,7 +40,144 @@ module.exports = (shepherd) => {
body: JSON.stringify({
herd: 'komodod',
options: herdData,
})
token: shepherd.appSessionHash,
}),
};
shepherd.request(options, (error, response, body) => {
if (response &&
response.statusCode &&
response.statusCode === 200) {
//resolve(body);
} else {
//resolve(body);
}
});
} else if (selection === 'REVS') {
const herdData = {
'ac_name': 'REVS',
'ac_options': [
'-daemon=0',
'-server',
`-ac_name=REVS`,
'-addnode=78.47.196.146',
'-ac_supply=1300000'
]
};
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,
token: shepherd.appSessionHash,
}),
};
shepherd.request(options, (error, response, body) => {
if (response &&
response.statusCode &&
response.statusCode === 200) {
//resolve(body);
} else {
//resolve(body);
}
});
} else if (selection === 'JUMRLR') {
const herdData = {
'ac_name': 'JUMRLR',
'ac_options': [
'-daemon=0',
'-server',
`-ac_name=JUMRLR`,
'-addnode=78.47.196.146',
'-ac_supply=999999'
]
};
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,
token: shepherd.appSessionHash,
}),
};
shepherd.request(options, (error, response, body) => {
if (response &&
response.statusCode &&
response.statusCode === 200) {
//resolve(body);
} else {
//resolve(body);
}
});
} else if (selection === 'MNZ') {
const herdData = {
'ac_name': 'MNZ',
'ac_options': [
'-daemon=0',
'-server',
`-ac_name=MNZ`,
'-addnode=78.47.196.146',
'-ac_supply=257142858'
]
};
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,
token: shepherd.appSessionHash,
}),
};
shepherd.request(options, (error, response, body) => {
if (response &&
response.statusCode &&
response.statusCode === 200) {
//resolve(body);
} else {
//resolve(body);
}
});
} else if (selection === 'BTCH') {
const herdData = {
'ac_name': 'BTCH',
'ac_options': [
'-daemon=0',
'-server',
`-ac_name=BTCH`,
'-addnode=78.47.196.146',
'-ac_supply=20998641'
]
};
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,
token: shepherd.appSessionHash,
}),
};
shepherd.request(options, (error, response, body) => {
@ -82,7 +227,8 @@ module.exports = (shepherd) => {
body: JSON.stringify({
herd: 'komodod',
options: herdData[i],
})
token: shepherd.appSessionHash,
}),
};
shepherd.request(options, (error, response, body) => {

27
routes/shepherd/appInfo.js

@ -52,7 +52,6 @@ module.exports = (shepherd) => {
sysInfo,
releaseInfo,
dirs,
appSession: shepherd.appSessionHash,
};
}
@ -61,8 +60,17 @@ module.exports = (shepherd) => {
*
*/
shepherd.get('/sysinfo', (req, res, next) => {
const obj = shepherd.SystemInfo();
res.send(obj);
if (shepherd.checkToken(req.query.token)) {
const obj = shepherd.SystemInfo();
res.send(obj);
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(errorObj));
}
});
/*
@ -70,8 +78,17 @@ module.exports = (shepherd) => {
*
*/
shepherd.get('/appinfo', (req, res, next) => {
const obj = shepherd.appInfo();
res.send(obj);
if (shepherd.checkToken(req.query.token)) {
const obj = shepherd.appInfo();
res.send(obj);
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(errorObj));
}
});
return shepherd;

48
routes/shepherd/auth.js

@ -3,29 +3,45 @@ module.exports = (shepherd) => {
* type: GET
*
*/
shepherd.get('/auth/status', (req, res, next) => { // not finished
let successObj;
let _status = false;
shepherd.get('/auth/status', (req, res, next) => {
if (shepherd.checkToken(req.query.token)) {
let successObj;
let _status = false;
if (Object.keys(shepherd.coindInstanceRegistry).length) {
if (Object.keys(shepherd.electrumCoins).length > 1 &&
shepherd.electrumCoins.auth) {
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 && !shepherd.electrumCoins.auth) {
} else if (Object.keys(shepherd.electrumCoins).length === 1 && !Object.keys(shepherd.coindInstanceRegistry).length) {
_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 = {
status: _status ? 'unlocked' : 'locked',
};
successObj = {
status: _status ? 'unlocked' : 'locked',
};
res.end(JSON.stringify(successObj));
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(successObj));
res.end(JSON.stringify(errorObj));
}
});
shepherd.checkToken = (token) => {
if (token === shepherd.appSessionHash ||
process.argv.indexOf('devmode') > -1) {
return true;
}
};
return shepherd;
};

239
routes/shepherd/binsTestUtil.js

@ -1,239 +0,0 @@
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 (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;
};

141
routes/shepherd/coindWalletKeys.js

@ -4,94 +4,103 @@ module.exports = (shepherd) => {
*
*/
shepherd.get('/coindwalletkeys', (req, res, next) => {
const wif = require('wif');
const fs = require('fs');
const chain = req.query.chain;
if (shepherd.checkToken(req.query.token)) {
const wif = require('wif');
const fs = require('fs');
const chain = req.query.chain;
// ref: https://gist.github.com/kendricktan/1e62495150ad236b38616d733aac4eb9
let _walletDatLocation = chain === 'komodo' || chain === 'null' ? `${shepherd.komodoDir}/wallet.dat` : `${shepherd.komodoDir}/${chain}/wallet.dat`;
_walletDatLocation = chain === 'CHIPS' ? `${shepherd.chipsDir}/wallet.dat` : _walletDatLocation;
// ref: https://gist.github.com/kendricktan/1e62495150ad236b38616d733aac4eb9
let _walletDatLocation = chain === 'komodo' || chain === 'null' ? `${shepherd.komodoDir}/wallet.dat` : `${shepherd.komodoDir}/${chain}/wallet.dat`;
_walletDatLocation = chain === 'CHIPS' ? `${shepherd.chipsDir}/wallet.dat` : _walletDatLocation;
try {
shepherd._fs.access(_walletDatLocation, shepherd.fs.constants.R_OK, (err) => {
if (err) {
shepherd.log(`error reading ${_walletDatLocation}`);
successObj = {
msg: 'error',
result: `error reading ${_walletDatLocation}`,
};
res.end(JSON.stringify(successObj));
} else {
shepherd.log(`reading ${_walletDatLocation}`);
fs.readFile(_walletDatLocation, (err, data) => {
if (err) {
shepherd.log(`read wallet.dat err: ${err}`);
successObj = {
msg: 'error',
result: `error reading ${_walletDatLocation}`,
};
res.end(JSON.stringify(successObj));
} else {
const re = /\x30\x81\xD3\x02\x01\x01\x04\x20(.{32})/gm;
const dataHexStr = data.toString('latin1');
privateKeys = dataHexStr.match(re);
if (!privateKeys) {
shepherd.log('wallet is encrypted?');
try {
shepherd._fs.access(_walletDatLocation, shepherd.fs.constants.R_OK, (err) => {
if (err) {
shepherd.log(`error reading ${_walletDatLocation}`);
successObj = {
msg: 'error',
result: `error reading ${_walletDatLocation}`,
};
res.end(JSON.stringify(successObj));
} else {
shepherd.log(`reading ${_walletDatLocation}`);
fs.readFile(_walletDatLocation, (err, data) => {
if (err) {
shepherd.log(`read wallet.dat err: ${err}`);
successObj = {
msg: 'error',
result: 'wallet is encrypted?',
result: `error reading ${_walletDatLocation}`,
};
res.end(JSON.stringify(successObj));
} else {
let _keys = [];
privateKeys = privateKeys.map(x => x.replace('\x30\x81\xD3\x02\x01\x01\x04\x20', ''));
privateKeys = privateKeys.filter((v, i, a) => a.indexOf(v) === i);
shepherd.log(`found ${privateKeys.length} keys`);
const re = /\x30\x81\xD3\x02\x01\x01\x04\x20(.{32})/gm;
const dataHexStr = data.toString('latin1');
privateKeys = dataHexStr.match(re);
for (let i = 0; i < privateKeys.length; i++) {
const privateKey = new Buffer(Buffer.from(privateKeys[i], 'latin1').toString('hex'), 'hex');
const key = wif.encode(0xbc, privateKey, true);
const keyObj = wif.decode(key);
const wifKey = wif.encode(keyObj);
if (!privateKeys) {
shepherd.log('wallet is encrypted?');
const keyPair = shepherd.bitcoinJS.ECPair.fromWIF(wifKey, shepherd.electrumJSNetworks.komodo);
const _keyPair = {
priv: keyPair.toWIF(),
pub: keyPair.getAddress(),
successObj = {
msg: 'error',
result: 'wallet is encrypted?',
};
if (req.query.search) {
if (_keyPair.pub.indexOf(req.query.search) > -1) {
res.end(JSON.stringify(successObj));
} else {
let _keys = [];
privateKeys = privateKeys.map(x => x.replace('\x30\x81\xD3\x02\x01\x01\x04\x20', ''));
privateKeys = privateKeys.filter((v, i, a) => a.indexOf(v) === i);
shepherd.log(`found ${privateKeys.length} keys`);
for (let i = 0; i < privateKeys.length; i++) {
const privateKey = new Buffer(Buffer.from(privateKeys[i], 'latin1').toString('hex'), 'hex');
const key = wif.encode(0xbc, privateKey, true);
const keyObj = wif.decode(key);
const wifKey = wif.encode(keyObj);
const keyPair = shepherd.bitcoinJS.ECPair.fromWIF(wifKey, shepherd.electrumJSNetworks.komodo);
const _keyPair = {
priv: keyPair.toWIF(),
pub: keyPair.getAddress(),
};
if (req.query.search) {
if (_keyPair.pub.indexOf(req.query.search) > -1) {
_keys.push(_keyPair);
}
} else {
_keys.push(_keyPair);
}
} else {
_keys.push(_keyPair);
}
}
successObj = {
msg: 'success',
result: _keys,
};
successObj = {
msg: 'success',
result: _keys,
};
res.end(JSON.stringify(successObj));
res.end(JSON.stringify(successObj));
}
}
}
});
}
});
} catch (e) {
successObj = {
});
}
});
} catch (e) {
successObj = {
msg: 'error',
result: `error reading ${_walletDatLocation}`,
};
res.end(JSON.stringify(successObj));
}
} else {
const errorObj = {
msg: 'error',
result: `error reading ${_walletDatLocation}`,
result: 'unauthorized access',
};
res.end(JSON.stringify(successObj));
res.end(JSON.stringify(errorObj));
}
});

41
routes/shepherd/coins.js

@ -4,27 +4,36 @@ module.exports = (shepherd) => {
*
*/
shepherd.get('/InstantDEX/allcoins', (req, res, next) => {
let successObj;
let nativeCoindList = [];
let electrumCoinsList = [];
if (shepherd.checkToken(req.query.token)) {
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.electrumCoins) {
if (key !== 'auth') {
electrumCoinsList.push(shepherd.electrumCoins[key].abbr);
}
}
}
for (let key in shepherd.coindInstanceRegistry) {
nativeCoindList.push(key === 'komodod' ? 'KMD' : key);
}
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,
};
successObj = {
native: nativeCoindList,
spv: electrumCoinsList,
total: Object.keys(shepherd.electrumCoins).length - 1 + Object.keys(nativeCoindList).length,
};
res.end(JSON.stringify(successObj));
res.end(JSON.stringify(successObj));
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(errorObj));
}
});
return shepherd;

94
routes/shepherd/coinsList.js

@ -4,28 +4,37 @@ module.exports = (shepherd) => {
*
*/
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,
};
if (shepherd.checkToken(req.query.token)) {
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(errorObj));
} else {
const successObj = {
msg: 'success',
result: data ? JSON.parse(data) : '',
};
res.end(JSON.stringify(successObj));
}
});
res.end(JSON.stringify(successObj));
}
});
} else {
const errorObj = {
msg: 'error',
result: 'coin list doesn\'t exist',
};
res.end(JSON.stringify(errorObj));
}
} else {
const errorObj = {
msg: 'error',
result: 'coin list doesn\'t exist',
result: 'unauthorized access',
};
res.end(JSON.stringify(errorObj));
@ -37,33 +46,42 @@ module.exports = (shepherd) => {
* params: payload
*/
shepherd.post('/coinslist', (req, res, next) => {
const _payload = req.body.payload;
if (shepherd.checkToken(req.body.token)) {
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',
};
if (!_payload) {
res.end(JSON.stringify(successObj));
}
});
}
} else {
const errorObj = {
msg: 'error',
result: 'no payload provided',
result: 'unauthorized access',
};
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));
}
});
}
});

65
routes/shepherd/config.js

@ -105,22 +105,31 @@ module.exports = (shepherd) => {
* params: payload
*/
shepherd.post('/appconf', (req, res, next) => {
if (!req.body.payload) {
if (shepherd.checkToken(req.body.token)) {
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));
}
} else {
const errorObj = {
msg: 'error',
result: 'no payload provided',
result: 'unauthorized access',
};
res.end(JSON.stringify(errorObj));
} else {
shepherd.saveLocalAppConf(req.body.payload);
const successObj = {
msg: 'success',
result: 'config saved',
};
res.end(JSON.stringify(successObj));
}
});
@ -129,14 +138,23 @@ module.exports = (shepherd) => {
* params: none
*/
shepherd.post('/appconf/reset', (req, res, next) => {
shepherd.saveLocalAppConf(shepherd.defaultAppConfig);
if (shepherd.checkToken(req.body.token)) {
shepherd.saveLocalAppConf(shepherd.defaultAppConfig);
const successObj = {
msg: 'success',
result: 'config saved',
};
const successObj = {
msg: 'success',
result: 'config saved',
};
res.end(JSON.stringify(successObj));
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(successObj));
res.end(JSON.stringify(errorObj));
}
});
/*
@ -144,8 +162,17 @@ module.exports = (shepherd) => {
*
*/
shepherd.get('/appconf', (req, res, next) => {
const obj = shepherd.loadLocalConfig();
res.send(obj);
if (shepherd.checkToken(req.query.token)) {
const obj = shepherd.loadLocalConfig();
res.send(obj);
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(errorObj));
}
});
return shepherd;

280
routes/shepherd/daemonControl.js

@ -11,6 +11,7 @@ const md5 = require('../md5.js');
module.exports = (shepherd) => {
const getConf = (flock, coind) => {
const _platform = os.platform();
let DaemonConfPath = '';
let nativeCoindDir;
@ -23,7 +24,7 @@ module.exports = (shepherd) => {
shepherd.writeLog(`getconf flock: ${flock}`);
if (coind) {
switch (os.platform()) {
switch (_platform) {
case 'darwin':
nativeCoindDir = `${process.env.HOME}/Library/Application Support/${shepherd.nativeCoindList[coind.toLowerCase()].bin}`;
break;
@ -39,29 +40,29 @@ module.exports = (shepherd) => {
switch (flock) {
case 'komodod':
DaemonConfPath = shepherd.komodoDir;
if (os.platform() === 'win32') {
if (_platform === 'win32') {
DaemonConfPath = path.normalize(DaemonConfPath);
shepherd.log('===>>> SHEPHERD API OUTPUT ===>>>');
}
break;
case 'zcashd':
DaemonConfPath = shepherd.ZcashDir;
if (os.platform() === 'win32') {
if (_platform === 'win32') {
DaemonConfPath = path.normalize(DaemonConfPath);
}
break;
case 'chipsd':
DaemonConfPath = shepherd.chipsDir;
if (os.platform() === 'win32') {
if (_platform === 'win32') {
DaemonConfPath = path.normalize(DaemonConfPath);
}
break;
case 'coind':
DaemonConfPath = os.platform() === 'win32' ? shepherd.path.normalize(`${shepherd.coindRootDir}/${coind.toLowerCase()}`) : `${shepherd.coindRootDir}/${coind.toLowerCase()}`;
DaemonConfPath = _platform === 'win32' ? shepherd.path.normalize(`${shepherd.coindRootDir}/${coind.toLowerCase()}`) : `${shepherd.coindRootDir}/${coind.toLowerCase()}`;
break;
default:
DaemonConfPath = `${shepherd.komodoDir}/${flock}`;
if (os.platform() === 'win32') {
if (_platform === 'win32') {
DaemonConfPath = shepherd.path.normalize(DaemonConfPath);
}
}
@ -482,57 +483,58 @@ module.exports = (shepherd) => {
}
const setConf = (flock, coind) => {
const _platform = os.platform();
let nativeCoindDir;
let DaemonConfPath;
shepherd.log(flock);
shepherd.writeLog(`setconf ${flock}`);
if (os.platform() === 'darwin') {
nativeCoindDir = coind ? `${process.env.HOME}/Library/Application Support/${shepherd.nativeCoindList[coind.toLowerCase()].bin}` : null;
}
if (os.platform() === 'linux') {
nativeCoindDir = coind ? `${process.env.HOME}/.${shepherd.nativeCoindList[coind.toLowerCase()].bin.toLowerCase()}` : null;
}
if (os.platform() === 'win32') {
nativeCoindDir = coind ? `${process.env.APPDATA}/${shepherd.nativeCoindList[coind.toLowerCase()].bin}` : null;
switch (_platform) {
case 'darwin':
nativeCoindDir = coind ? `${process.env.HOME}/Library/Application Support/${shepherd.nativeCoindList[coind.toLowerCase()].bin}` : null;
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}/komodo.conf`;
if (os.platform() === 'win32') {
if (_platform === 'win32') {
DaemonConfPath = path.normalize(DaemonConfPath);
}
break;
case 'zcashd':
DaemonConfPath = `${shepherd.ZcashDir}/zcash.conf`;
if (os.platform() === 'win32') {
if (_platform === 'win32') {
DaemonConfPath = path.normalize(DaemonConfPath);
}
break;
case 'chipsd':
DaemonConfPath = `${shepherd.chipsDir}/chips.conf`;
if (os.platform() === 'win32') {
if (_platform === 'win32') {
DaemonConfPath = path.normalize(DaemonConfPath);
}
break;
case 'coind':
DaemonConfPath = `${nativeCoindDir}/${shepherd.nativeCoindList[coind.toLowerCase()].bin.toLowerCase()}.conf`;
if (os.platform() === 'win32') {
if (_platform === 'win32') {
DaemonConfPath = path.normalize(DaemonConfPath);
}
break;
default:
DaemonConfPath = `${shepherd.komodoDir}/${flock}/${flock}.conf`;
if (os.platform() === 'win32') {
if (_platform === 'win32') {
DaemonConfPath = path.normalize(DaemonConfPath);
}
}
@ -655,26 +657,28 @@ module.exports = (shepherd) => {
});
}
const rpcbind = () => {
const rpcport = () => {
return new Promise((resolve, reject) => {
const result = 'checking rpcbind...';
const result = 'checking rpcport...';
if (status[0].hasOwnProperty('rpcbind')) {
shepherd.log('rpcbind: OK');
shepherd.writeLog('rpcbind: OK');
} else {
shepherd.log('rpcbind: NOT FOUND');
shepherd.writeLog('rpcbind: NOT FOUND');
if (flock === 'komodod') {
if (status[0].hasOwnProperty('rpcport')) {
shepherd.log('rpcport: OK');
shepherd.writeLog('rpcport: OK');
} else {
shepherd.log('rpcport: NOT FOUND');
shepherd.writeLog('rpcport: NOT FOUND');
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');
});
fs.appendFile(DaemonConfPath, '\nrpcport=7771', (err) => {
if (err) {
shepherd.writeLog(`append daemon conf err: ${err}`);
shepherd.log(`append daemon conf err: ${err}`);
}
// throw err;
shepherd.log('rpcport: ADDED');
shepherd.writeLog('rpcport: ADDED');
});
}
}
resolve(result);
@ -764,7 +768,7 @@ module.exports = (shepherd) => {
return rpcpass();
})
.then(server)
.then(rpcbind)
.then(rpcport)
.then(addnode);
});
@ -788,66 +792,81 @@ module.exports = (shepherd) => {
* 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];
portscanner.checkPortStatus(_port, '127.0.0.1', (error, status) => {
// Status is 'open' if currently in use or 'closed' if available
if (status === 'open' &&
shepherd.appConfig.stopNativeDaemonsOnQuit) {
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!`,
},
});
if (shepherd.checkToken(req.body.token)) {
const _body = req.body;
shepherd.log('herd req.body =>');
shepherd.log(_body);
if (_body.options &&
!shepherd.kmdMainPassiveMode) {
const testCoindPort = (skipError) => {
const _acName = req.body.options.ac_name;
if (!shepherd.lockDownAddCoin) {
const _port = shepherd.assetChainPorts[_acName];
portscanner.checkPortStatus(_port, '127.0.0.1', (error, status) => {
// Status is 'open' if currently in use or 'closed' if available
if (status === 'open' &&
shepherd.appConfig.stopNativeDaemonsOnQuit) {
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 ${_body.herd} ${_acName} 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!`,
};
const obj = {
msg: 'error',
result: `error starting ${_body.herd} ${_acName} daemon. Port ${_port} is already taken!`,
};
res.status(500);
res.end(JSON.stringify(obj));
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 {
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);
if (!skipError) {
herder(_body.herd, _body.options);
const obj = {
msg: 'success',
result: 'result',
};
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`);
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);
if (_body.herd === 'komodod') {
// check if komodod instance is already running
testCoindPort();
setTimeout(() => {
testCoindPort(true);
}, 10000);
} else {
herder(_body.herd, _body.options, _body.coind);
const obj = {
msg: 'success',
result: 'result',
};
res.end(JSON.stringify(obj));
}
} else {
herder(req.body.herd, req.body.options, req.body.coind);
// (?)
herder(_body.herd, _body.options);
const obj = {
msg: 'success',
@ -857,15 +876,12 @@ module.exports = (shepherd) => {
res.end(JSON.stringify(obj));
}
} else {
// (?)
herder(req.body.herd, req.body.options);
const obj = {
msg: 'success',
result: 'result',
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(obj));
res.end(JSON.stringify(errorObj));
}
});
@ -873,44 +889,64 @@ module.exports = (shepherd) => {
* type: POST
*/
shepherd.post('/setconf', (req, res) => {
shepherd.log('======= req.body =======');
shepherd.log(req.body);
if (shepherd.checkToken(req.body.token)) {
const _body = req.body;
if (os.platform() === 'win32' &&
req.body.chain == 'komodod') {
setkomodoconf = spawn(path.join(__dirname, '../assets/bin/win64/genkmdconf.bat'));
} else {
shepherd.setConf(req.body.chain);
}
shepherd.log('setconf req.body =>');
shepherd.log(_body);
if (os.platform() === 'win32' &&
_body.chain == 'komodod') {
setkomodoconf = spawn(path.join(__dirname, '../assets/bin/win64/genkmdconf.bat'));
} else {
shepherd.setConf(_body.chain);
}
const obj = {
msg: 'success',
result: 'result',
};
const obj = {
msg: 'success',
result: 'result',
};
res.end(JSON.stringify(obj));
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(obj));
res.end(JSON.stringify(errorObj));
}
});
/*
* type: POST
*/
shepherd.post('/getconf', (req, res) => {
shepherd.log('======= req.body =======');
shepherd.log(req.body);
if (shepherd.checkToken(req.body.token)) {
const _body = req.body;
const confpath = getConf(req.body.chain, req.body.coind);
shepherd.log('getconf req.body =>');
shepherd.log(_body);
shepherd.log('got conf path is:');
shepherd.log(confpath);
shepherd.writeLog('got conf path is:');
shepherd.writeLog(confpath);
const confpath = getConf(_body.chain, _body.coind);
const obj = {
msg: 'success',
result: confpath,
};
shepherd.log(`getconf path is: ${confpath}`);
shepherd.writeLog(`getconf path is: ${confpath}`);
res.end(JSON.stringify(obj));
const obj = {
msg: 'success',
result: confpath,
};
res.end(JSON.stringify(obj));
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(errorObj));
}
});
shepherd.setConfKMD = (isChips) => {

518
routes/shepherd/dashboardUpdate.js

@ -7,171 +7,189 @@ module.exports = (shepherd) => {
* params: coin
*/
shepherd.post('/native/dashboard/update', (req, res, next) => {
const _coin = req.body.coin;
let _returnObj;
let _promiseStack;
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'
];
}
if (shepherd.checkToken(req.body.token)) {
const _coin = req.body.coin;
const _token = req.body.token;
let _returnObj;
let _promiseStack;
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'
];
const getAddressesNative = (coin) => {
const type = [
'public',
'private'
];
if (coin === 'CHIPS') {
type.pop();
}
if (coin === 'CHIPS') {
type.pop();
}
Promise.all(type.map((_type, index) => {
return new 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);
}
Promise.all(type.map((_type, index) => {
return new 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 &&
result[0][0].length &&
result[0][0].length > 10) {
const calcBalance = (result, json) => {
if (json &&
json.length &&
json[0] &&
json[0].address) {
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;
}))
.then(result => {
if (result[0] &&
result[0].length &&
result[0][0].length &&
result[0][0].length > 10) {
const calcBalance = (result, json) => {
if (json &&
json.length &&
json[0] &&
json[0].address) {
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];
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;
});
}
// 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] = [];
let newAddressArray = [];
if (result[a]) {
for (let b = 0; b < result[a].length; b++) {
const filteredArraySpends = json.filter(res => res.address === result[a][b]);
const filteredArray = json.filter(res => res.address === result[a][b]).map(res => res.amount);
for (let a = 0; a < result.length; a++) {
newAddressArray[a] = [];
let sum = 0;
let spendableSum = 0;
let canspend = true;
if (result[a]) {
for (let b = 0; b < result[a].length; b++) {
const filteredArraySpends = json.filter(res => res.address === result[a][b]);
const filteredArray = json.filter(res => res.address === result[a][b]).map(res => res.amount);
for (let i = 0; i < filteredArray.length; i++) {
sum += filteredArray[i];
let sum = 0;
let spendableSum = 0;
let canspend = true;
if (filteredArraySpends[i].spendable) {
spendableSum += filteredArray[i];
} else {
canspend = false;
for (let i = 0; i < filteredArray.length; i++) {
sum += filteredArray[i];
if (filteredArraySpends[i].spendable) {
spendableSum += filteredArray[i];
} else {
canspend = false;
}
}
}
newAddressArray[a][b] = {
address: result[a][b],
amount: sum,
spendable: spendableSum,
canspend,
type: a === 0 ? 'public': 'private',
};
newAddressArray[a][b] = {
address: result[a][b],
amount: sum,
spendable: spendableSum,
canspend,
type: a === 0 ? 'public': 'private',
};
}
}
}
}
// get zaddr balance
if (result[1] &&
result[1].length) {
Promise.all(result[1].map((_address, index) => {
return new 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',
};
}
// get zaddr balance
if (result[1] &&
result[1].length) {
Promise.all(result[1].map((_address, index) => {
return new 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));
});
}))
.then(zresult => {
} else {
_returnObj.addresses = {
public: newAddressArray[0],
private: newAddressArray[1],
@ -183,129 +201,129 @@ module.exports = (shepherd) => {
};
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,
};
_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);
res.end(JSON.stringify(returnObj));
} else {
_returnObj.listunspent = JSON.parse(__json);
calcBalance(
result,
JSON.parse(__json).result
);
}
});
} else {
_returnObj.addresses = {
public: {},
private: {},
};
calcBalance(
result,
JSON.parse(__json).result
);
}
});
} else {
_returnObj.addresses = {
public: {},
private: {},
};
const returnObj = {
msg: 'success',
result: _returnObj,
};
const returnObj = {
msg: 'success',
result: _returnObj,
};
res.end(JSON.stringify(returnObj));
}
})
}
res.end(JSON.stringify(returnObj));
}
})
}
const _bitcoinRPC = (coin, cmd, params) => {
return new Promise((resolve, reject) => {
let _payload;
const _bitcoinRPC = (coin, cmd, params) => {
return new Promise((resolve, reject) => {
let _payload;
if (params) {
_payload = {
mode: null,
chain: coin,
cmd: cmd,
params: params,
rpc2cli: req.body.rpc2cli,
token: _token,
};
} else {
_payload = {
mode: null,
chain: coin,
cmd: cmd,
rpc2cli: req.body.rpc2cli,
token: _token,
};
}
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,
};
}
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.request(options, (error, response, body) => {
if (response &&
response.statusCode &&
response.statusCode === 200) {
resolve(body);
} else {
resolve(body);
}
});
});
});
}
}
Promise.all(_promiseStack.map((_call, index) => {
let _params;
Promise.all(_promiseStack.map((_call, index) => {
let _params;
if (_call === 'listtransactions') {
_params = [
'*',
300,
0
];
}
if (_call === 'listtransactions') {
_params = [
'*',
300,
0
];
}
return new 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);
return new 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);
});
}))
.then(result => {
getAddressesNative(_coin);
});
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(errorObj));
}
});
return shepherd;

122
routes/shepherd/debugLog.js

@ -4,74 +4,92 @@ module.exports = (shepherd) => {
* 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.checkToken(req.body.token)) {
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() === '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 (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 (_herd === 'komodo') {
_location = shepherd.komodoDir;
}
if (_ac) {
_location = `${shepherd.komodoDir}/${_ac}`;
if (_ac) {
_location = `${shepherd.komodoDir}/${_ac}`;
if (_ac === 'CHIPS') {
_location = shepherd.chipsDir;
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 = {
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));
});
} else {
const errorObj = {
msg: 'error',
result: result,
result: 'unauthorized access',
};
res.end(JSON.stringify(_obj));
});
res.end(JSON.stringify(errorObj));
}
});
shepherd.get('/coind/stdout', (req, res) => {
const _daemonName = req.query.chain !== 'komodod' ? req.query.chain : 'komodod';
const _daemonLogName = `${shepherd.agamaDir}/${_daemonName}.log`;
shepherd.readDebugLog(_daemonLogName, 'all')
.then((result) => {
const _obj = {
msg: 'success',
result: result,
};
res.end(JSON.stringify(_obj));
}, (result) => {
const _obj = {
if (shepherd.checkToken(req.query.token)) {
const _daemonName = req.query.chain !== 'komodod' ? req.query.chain : 'komodod';
const _daemonLogName = `${shepherd.agamaDir}/${_daemonName}.log`;
shepherd.readDebugLog(_daemonLogName, 'all')
.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));
});
} else {
const errorObj = {
msg: 'error',
result: result,
result: 'unauthorized access',
};
res.end(JSON.stringify(_obj));
});
res.end(JSON.stringify(errorObj));
}
});
shepherd.readDebugLog = (fileLocation, lastNLines) => {

208
routes/shepherd/downloadBins.js

@ -46,51 +46,60 @@ module.exports = (shepherd) => {
*/
// 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,
},
});
}
if (shepherd.checkToken(req.query.token)) {
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,
},
});
}
});
}
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(errorObj));
}
});
@ -100,64 +109,73 @@ module.exports = (shepherd) => {
* 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) {
if (shepherd.checkToken(req.query.token)) {
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',
status: 'progress',
file: binsToUpdate[i].name,
bytesTotal: total,
bytesReceived: received,
status: 'done',
},
});
// shepherd.log(`${binsToUpdate[i].name} ${percentage}% | ${received} bytes out of ${total} bytes.`);
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!`);
}
}
})
.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!`);
}
});
});
}
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(errorObj));
}
});

147
routes/shepherd/downloadPatch.js

@ -5,14 +5,23 @@ module.exports = (shepherd) => {
* params: patchList
*/
shepherd.get('/update/patch', (req, res, next) => {
const successObj = {
msg: 'success',
result: 'dl started'
};
res.end(JSON.stringify(successObj));
shepherd.updateAgama();
if (shepherd.checkToken(req.query.token)) {
const successObj = {
msg: 'success',
result: 'dl started',
};
res.end(JSON.stringify(successObj));
shepherd.updateAgama();
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(errorObj));
}
});
shepherd.updateAgama = () => {
@ -85,51 +94,60 @@ module.exports = (shepherd) => {
* 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',
};
if (shepherd.checkToken(req.query.token)) {
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');
}
res.end(JSON.stringify(successObj));
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 {
const successObj = {
msg: 'success',
result: 'update',
version: {
local: localVersion[0],
remote: remoteVersion[0],
},
};
res.end(JSON.stringify(successObj));
res.end({
err: 'error getting update',
});
}
} else {
res.end({
err: 'error getting update',
});
}
});
});
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(errorObj));
}
});
/*
@ -138,16 +156,25 @@ module.exports = (shepherd) => {
* 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));
if (shepherd.checkToken(req.query.token)) {
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));
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(errorObj));
}
});
return shepherd;

107
routes/shepherd/downloadZcparams.js

@ -67,66 +67,75 @@ module.exports = (shepherd) => {
* params:
*/
shepherd.get('/zcparamsdl', (req, res, next) => {
// const dlLocation = shepherd.zcashParamsDir + '/test';
const dlLocation = shepherd.zcashParamsDir;
const dlOption = req.query.dloption;
if (shepherd.checkToken(req.query.token)) {
// const dlLocation = shepherd.zcashParamsDir + '/test';
const dlLocation = shepherd.zcashParamsDir;
const dlOption = req.query.dloption;
const successObj = {
msg: 'success',
result: 'zcash params dl started',
};
const successObj = {
msg: 'success',
result: 'zcash params dl started',
};
res.end(JSON.stringify(successObj));
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();
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;
shepherd.log(`${key} dl done`);
if (percentage.toString().indexOf('.10') > -1) {
if (checkZcashParams.error) {
shepherd.io.emit('zcparams', {
msg: {
type: 'zcpdownload',
status: 'progress',
file: key,
bytesTotal: total,
bytesReceived: received,
progress: percentage,
status: 'error',
message: 'size mismatch',
progress: 100,
},
});
// shepherd.log(`${key} ${percentage}% | ${received} bytes out of ${total} bytes.`);
} else {
shepherd.io.emit('zcparams', {
msg: {
type: 'zcpdownload',
file: key,
progress: 100,
status: 'done',
},
});
shepherd.log(`file ${key} succesfully downloaded`);
}
}
})
.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`);
}
});
});
}
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(errorObj));
}
});

372
routes/shepherd/elections.js

@ -0,0 +1,372 @@
const bs58check = require('bs58check');
const bitcoin = require('bitcoinjs-lib');
module.exports = (shepherd) => {
shepherd.elections = {};
shepherd.hex2str = (hexx) => {
const hex = hexx.toString(); // force conversion
let str = '';
for (let i = 0; i < hex.length; i += 2) {
str += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
}
return str;
};
shepherd.post('/elections/status', (req, res, next) => {
if (shepherd.checkToken(req.body.token)) {
const successObj = {
msg: 'success',
result: shepherd.elections.pub ? shepherd.elections.pub : 'unauth',
};
res.end(JSON.stringify(successObj));
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(errorObj));
}
});
shepherd.post('/elections/login', (req, res, next) => {
if (shepherd.checkToken(req.body.token)) {
const _seed = req.body.seed;
const _network = req.body.network;
let keys;
let isWif = false;
if (_seed.match('^[a-zA-Z0-9]{34}$')) {
shepherd.log('watchonly elections pub addr');
shepherd.elections = {
priv: _seed,
pub: _seed,
};
} else {
try {
bs58check.decode(_seed);
isWif = true;
} catch (e) {}
if (isWif) {
try {
let key = bitcoin.ECPair.fromWIF(_seed, shepherd.getNetworkData(_network.toLowerCase()), true);
keys = {
priv: key.toWIF(),
pub: key.getAddress(),
};
} catch (e) {
_wifError = true;
}
} else {
keys = shepherd.seedToWif(_seed, _network, req.body.iguana);
}
shepherd.elections = {
priv: keys.priv,
pub: keys.pub,
};
}
const successObj = {
msg: 'success',
result: shepherd.elections.pub,
};
res.end(JSON.stringify(successObj));
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(errorObj));
}
});
shepherd.post('/elections/logout', (req, res, next) => {
if (shepherd.checkToken(req.body.token)) {
shepherd.elections = {};
const successObj = {
msg: 'success',
result: true,
};
res.end(JSON.stringify(successObj));
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(errorObj));
}
});
shepherd.electionsDecodeTx = (decodedTx, ecl, network, _network, transaction, blockInfo, address) => {
let txInputs = [];
return new shepherd.Promise((resolve, reject) => {
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, _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: Number(transaction.height) === 0 ? Math.floor(Date.now() / 1000) : blockInfo.timestamp,
};
resolve(shepherd.parseTransactionAddresses(_parsedTx, address, network, true));
});
} else {
const _parsedTx = {
network: decodedTx.network,
format: 'cant parse',
inputs: 'cant parse',
outputs: 'cant parse',
height: transaction.height,
timestamp: Number(transaction.height) === 0 ? Math.floor(Date.now() / 1000) : blockInfo.timestamp,
};
resolve(shepherd.parseTransactionAddresses(_parsedTx, address, network));
}
});
};
shepherd.get('/elections/listtransactions', (req, res, next) => {
if (shepherd.checkToken(req.query.token)) {
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 type = req.query.type;
const _address = req.query.address;
shepherd.log('electrum elections listtransactions ==>', true);
const MAX_TX = req.query.maxlength || 10;
ecl.connect();
ecl.blockchainAddressGetHistory(_address)
.then((json) => {
if (json &&
json.length) {
let _rawtx = [];
json = shepherd.sortTransactions(json);
// json = json.length > MAX_TX ? json.slice(0, MAX_TX) : json;
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, _network);
let _res = {};
let _opreturnFound = false;
let _region;
if (decodedTx &&
decodedTx.outputs &&
decodedTx.outputs.length) {
for (let i = 0; i < decodedTx.outputs.length; i++) {
if (decodedTx.outputs[i].scriptPubKey.asm.indexOf('OP_RETURN') > -1) {
_opreturnFound = true;
_region = shepherd.hex2str(decodedTx.outputs[i].scriptPubKey.hex.substr(4, decodedTx.outputs[i].scriptPubKey.hex.length));
shepherd.log(`found opreturn tag ${_region}`);
break;
}
}
}
if (_opreturnFound) {
let _candidate = {};
for (let i = 0; i < decodedTx.outputs.length; i++) {
if (type === 'voter' &&
decodedTx.outputs[i].scriptPubKey.addresses &&
decodedTx.outputs[i].scriptPubKey.addresses[0] &&
decodedTx.outputs[i].scriptPubKey.addresses[0] !== _address) {
if (_region === 'ne2k18-na-1-eu-2-ae-3-sh-4') {
const _regionsLookup = [
'ne2k18-na',
'ne2k18-eu',
'ne2k18-ae',
'ne2k18-sh'
];
shepherd.log(`i voted ${decodedTx.outputs[i].value} for ${decodedTx.outputs[i].scriptPubKey.addresses[0]}`);
_rawtx.push({
address: decodedTx.outputs[i].scriptPubKey.addresses[0],
amount: decodedTx.outputs[i].value,
region: _regionsLookup[i],
timestamp: blockInfo.timestamp,
});
resolve(true);
} else {
shepherd.log(`i voted ${decodedTx.outputs[i].value} for ${decodedTx.outputs[i].scriptPubKey.addresses[0]}`);
_rawtx.push({
address: decodedTx.outputs[i].scriptPubKey.addresses[0],
amount: decodedTx.outputs[i].value,
region: _region,
timestamp: blockInfo.timestamp,
});
resolve(true);
}
}
if (type === 'candidate') {
if (_region === 'ne2k18-na-1-eu-2-ae-3-sh-4') {
if (decodedTx.outputs[i].scriptPubKey.addresses[0] === _address && decodedTx.outputs[i].scriptPubKey.asm.indexOf('OP_RETURN') === -1) {
const _regionsLookup = [
'ne2k18-na',
'ne2k18-eu',
'ne2k18-ae',
'ne2k18-sh'
];
shepherd.electionsDecodeTx(decodedTx, ecl, network, _network, transaction, blockInfo, _address)
.then((res) => {
shepherd.log(`i received ${decodedTx.outputs[i].value} from ${res.outputAddresses[0]} out ${i} region ${_regionsLookup[i]}`);
_rawtx.push({
address: res.outputAddresses[0],
timestamp: blockInfo.timestamp,
amount: decodedTx.outputs[i].value,
region: _regionsLookup[i],
});
resolve(true);
});
}
} else {
shepherd.electionsDecodeTx(decodedTx, ecl, network, _network, transaction, blockInfo, _address)
.then((res) => {
if (decodedTx.outputs[i].scriptPubKey.addresses[0] === _address) {
_candidate.amount = decodedTx.outputs[i].value;
} else if (decodedTx.outputs[i].scriptPubKey.addresses[0] !== _address && decodedTx.outputs[i].scriptPubKey.asm.indexOf('OP_RETURN') === -1) {
_candidate.address = decodedTx.outputs[i].scriptPubKey.addresses[0];
_candidate.region = _region;
_candidate.timestamp = blockInfo.timestamp;
}
if (i === decodedTx.outputs.length - 1) {
shepherd.log(`i received ${_candidate.amount} from ${_candidate.address} region ${_region}`);
_rawtx.push(_candidate);
resolve(true);
}
});
}
}
}
} else {
shepherd.log('elections regular tx', true);
shepherd.electionsDecodeTx(decodedTx, ecl, network, _network, transaction, blockInfo, _address)
.then((_regularTx) => {
if (_regularTx[0] &&
_regularTx[1]) {
_rawtx.push({
address: _regularTx[type === 'voter' ? 0 : 1].address || 'self',
timestamp: _regularTx[type === 'voter' ? 0 : 1].timestamp,
amount: _regularTx[type === 'voter' ? 0 : 1].amount,
region: 'unknown',
regularTx: true,
hash: transaction['tx_hash'],
});
} else {
if ((type === 'voter' && _regularTx.type !== 'received') && (type === 'candidate' && _regularTx.type !== 'sent')) {
_rawtx.push({
address: _regularTx.address || 'self',
timestamp: _regularTx.timestamp,
amount: _regularTx.amount,
region: 'unknown',
regularTx: true,
hash: transaction['tx_hash'],
});
}
}
resolve(true);
});
}
});
} else {
_rawtx.push({
address: 'unknown',
timestamp: 'cant get block info',
amount: 'unknown',
region: 'unknown',
regularTx: true,
});
resolve(false);
}
});
});
}))
.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 errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(errorObj));
}
});
return shepherd;
}

139
routes/shepherd/electrum/auth.js

@ -1,64 +1,117 @@
const bs58check = require('bs58check');
const bitcoinZcash = require('bitcoinjs-lib-zcash');
const bitcoin = require('bitcoinjs-lib');
module.exports = (shepherd) => {
shepherd.post('/electrum/login', (req, res, next) => {
for (let key in shepherd.electrumServers) {
const _abbr = shepherd.electrumServers[key].abbr;
const _seed = req.body.seed;
let keys;
if ((_seed.length === 51 || _seed.length === 52) &&
_seed.indexOf(' ') === -1 &&
_seed.match(/^[a-zA-Z0-9]*$/)) {
let key = shepherd.bitcoinJS.ECPair.fromWIF(_seed, shepherd.electrumJSNetworks.komodo);
keys = {
priv: key.toWIF(),
pub: key.getAddress(),
};
} else {
keys = shepherd.seedToWif(_seed, shepherd.findNetworkObj(_abbr), req.body.iguana);
if (shepherd.checkToken(req.body.token)) {
let _wifError = false;
for (let key in shepherd.electrumCoins) {
if (key !== 'auth') {
const _abbr = key;
const _seed = req.body.seed;
let keys;
let isWif = false;
if (req.body.seed.match('^[a-zA-Z0-9]{34}$') &&
shepherd.appConfig.experimentalFeatures) {
shepherd.log('watchonly pub addr');
shepherd.electrumKeys[_abbr] = {
priv: req.body.seed,
pub: req.body.seed,
};
} else {
try {
bs58check.decode(_seed);
isWif = true;
} catch (e) {}
if (isWif) {
try {
let key = shepherd.isZcash(_abbr.toLowerCase()) ? bitcoinZcash.ECPair.fromWIF(_seed, shepherd.getNetworkData(_abbr.toLowerCase()), true) : bitcoin.ECPair.fromWIF(_seed, shepherd.getNetworkData(_abbr.toLowerCase()), true);
keys = {
priv: key.toWIF(),
pub: key.getAddress(),
};
} catch (e) {
_wifError = true;
break;
}
} else {
keys = shepherd.seedToWif(_seed, shepherd.findNetworkObj(_abbr), req.body.iguana);
}
shepherd.electrumKeys[_abbr] = {
priv: keys.priv,
pub: keys.pub,
};
}
}
}
shepherd.electrumKeys[_abbr] = {
priv: keys.priv,
pub: keys.pub,
};
}
shepherd.electrumCoins.auth = true;
shepherd.electrumCoins.auth = true;
// shepherd.log(JSON.stringify(shepherd.electrumKeys, null, '\t'), true);
// shepherd.log(JSON.stringify(shepherd.electrumKeys, null, '\t'), true);
const successObj = {
msg: _wifError ? 'error' : 'success',
result: 'true',
};
const successObj = {
msg: 'success',
result: 'true',
};
res.end(JSON.stringify(successObj));
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(successObj));
res.end(JSON.stringify(errorObj));
}
});
shepherd.post('/electrum/lock', (req, res, next) => {
shepherd.electrumCoins.auth = false;
shepherd.electrumKeys = {};
if (shepherd.checkToken(req.body.token)) {
shepherd.electrumCoins.auth = false;
shepherd.electrumKeys = {};
const successObj = {
msg: 'success',
result: 'true',
};
const successObj = {
msg: 'success',
result: 'true',
};
res.end(JSON.stringify(successObj));
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(successObj));
res.end(JSON.stringify(errorObj));
}
});
shepherd.post('/electrum/logout', (req, res, next) => {
shepherd.electrumCoins = {
auth: false,
};
shepherd.electrumKeys = {};
if (shepherd.checkToken(req.body.token)) {
shepherd.electrumCoins = {
auth: false,
};
shepherd.electrumKeys = {};
const obj = {
msg: 'success',
result: 'result',
};
const obj = {
msg: 'success',
result: 'result',
};
res.end(JSON.stringify(obj));
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(obj));
res.end(JSON.stringify(errorObj));
}
});
return shepherd;

203
routes/shepherd/electrum/balance.js

@ -1,68 +1,87 @@
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]);
if (shepherd.checkToken(req.query.token)) {
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.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);
shepherd.log('filtered utxo list =>', true);
shepherd.log(_utxo, true);
if (_utxo &&
_utxo.length) {
let interestTotal = 0;
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);
shepherd.Promise.all(_utxo.map((_utxoItem, index) => {
return new shepherd.Promise((resolve, reject) => {
shepherd.getTransaction(_utxoItem['tx_hash'], network, ecl)
.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);
// decode tx
const _network = shepherd.getNetworkData(network);
const decodedTx = shepherd.electrumJSTxDecoder(_rawtxJSON, network, _network);
if (decodedTx &&
decodedTx.format &&
decodedTx.format.locktime > 0) {
interestTotal += shepherd.kmdCalcInterest(decodedTx.format.locktime, _utxoItem.value);
}
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);
shepherd.log('decoded tx =>', true);
shepherd.log(decodedTx, true);
resolve(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));
});
}))
.then(promiseResult => {
} else {
ecl.close();
const successObj = {
msg: 'success',
result: {
@ -70,16 +89,18 @@ module.exports = (shepherd) => {
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,
interest: 0,
interestSats: 0,
total: 0,
totalSats: 0,
},
};
res.end(JSON.stringify(successObj));
});
}
} else {
ecl.close();
const successObj = {
msg: 'success',
result: {
@ -96,50 +117,44 @@ module.exports = (shepherd) => {
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 {
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,
},
msg: 'error',
result: shepherd.CONNECTION_ERROR_OR_INCOMPLETE_DATA,
};
res.end(JSON.stringify(successObj));
}
} else {
const successObj = {
msg: 'error',
result: shepherd.CONNECTION_ERROR_OR_INCOMPLETE_DATA,
};
res.end(JSON.stringify(successObj));
}
});
});
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(errorObj));
}
});
return shepherd;
};

46
routes/shepherd/electrum/block.js

@ -1,14 +1,23 @@
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,
if (shepherd.checkToken(req.query.token)) {
shepherd.electrumGetBlockInfo(req.query.height, req.query.network)
.then((json) => {
const successObj = {
msg: 'success',
result: json,
};
res.end(JSON.stringify(successObj));
});
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(successObj));
});
res.end(JSON.stringify(errorObj));
}
});
shepherd.electrumGetBlockInfo = (height, network) => {
@ -28,15 +37,24 @@ module.exports = (shepherd) => {
}
shepherd.get('/electrum/getcurrentblock', (req, res, next) => {
shepherd.electrumGetCurrentBlock(req.query.network)
.then((json) => {
const successObj = {
msg: 'success',
result: json,
if (shepherd.checkToken(req.query.token)) {
shepherd.electrumGetCurrentBlock(req.query.network)
.then((json) => {
const successObj = {
msg: 'success',
result: json,
};
res.end(JSON.stringify(successObj));
});
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(successObj));
});
res.end(JSON.stringify(errorObj));
}
});
shepherd.electrumGetCurrentBlock = (network) => {

81
routes/shepherd/electrum/coins.js

@ -54,37 +54,84 @@ module.exports = (shepherd) => {
shepherd.log(`${coin} doesnt have any backup electrum servers`, true);
}
if (Object.keys(shepherd.electrumKeys).length > 0) {
const _keys = shepherd.wifToWif(shepherd.electrumKeys[Object.keys(shepherd.electrumKeys)[0]].priv, coin);
shepherd.electrumKeys[coin] = {
priv: _keys.priv,
pub: _keys.pub,
};
}
return true;
}
}
}
shepherd.get('/electrum/coins/add', (req, res, next) => {
const result = shepherd.addElectrumCoin(req.query.coin);
shepherd.get('/electrum/coin/changepub', (req, res, next) => {
if (shepherd.checkToken(req.query.token)) {
shepherd.electrumKeys[req.query.coin].pub = req.query.pub;
const successObj = {
msg: 'success',
result,
};
const successObj = {
msg: 'success',
result: 'true',
};
res.end(JSON.stringify(successObj));
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(errorObj));
}
});
res.end(JSON.stringify(successObj));
shepherd.get('/electrum/coins/add', (req, res, next) => {
if (shepherd.checkToken(req.query.token)) {
const result = shepherd.addElectrumCoin(req.query.coin);
const successObj = {
msg: 'success',
result,
};
res.end(JSON.stringify(successObj));
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(errorObj));
}
});
shepherd.get('/electrum/coins', (req, res, next) => {
let _electrumCoins = JSON.parse(JSON.stringify(shepherd.electrumCoins)); // deep cloning
if (shepherd.checkToken(req.query.token)) {
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;
for (let key in _electrumCoins) {
if (shepherd.electrumKeys[key]) {
_electrumCoins[key].pub = shepherd.electrumKeys[key].pub;
}
}
}
const successObj = {
msg: 'success',
result: _electrumCoins,
};
const successObj = {
msg: 'success',
result: _electrumCoins,
};
res.end(JSON.stringify(successObj));
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(successObj));
res.end(JSON.stringify(errorObj));
}
});
return shepherd;

500
routes/shepherd/electrum/createtx-multi.js

@ -0,0 +1,500 @@
const bitcoinJSForks = require('bitcoinforksjs-lib');
const bitcoinZcash = require('bitcoinjs-lib-zcash');
const bitcoinPos = require('bitcoinjs-lib-pos');
// not prod ready, only for voting!
// needs a fix
module.exports = (shepherd) => {
shepherd.post('/electrum/createrawtx-multiout', (req, res, next) => {
if (shepherd.checkToken(req.body.token)) {
// TODO: 1) unconf output(s) error message
// 2) check targets integrity
const network = req.body.network || shepherd.findNetworkObj(req.body.coin);
const ecl = new shepherd.electrumJSCore(shepherd.electrumServers[network].port, shepherd.electrumServers[network].address, shepherd.electrumServers[network].proto); // tcp or tls
const initTargets = JSON.parse(JSON.stringify(req.body.targets));
let targets = req.body.targets;
const changeAddress = req.body.change;
const push = req.body.push;
const opreturn = req.body.opreturn;
const btcFee = req.body.btcfee ? Number(req.body.btcfee) : null;
let fee = shepherd.electrumServers[network].txfee;
let wif = req.body.wif;
if (req.body.gui) {
wif = shepherd.electrumKeys[req.body.coin].priv;
}
if (req.body.vote) {
wif = shepherd.elections.priv;
}
if (btcFee) {
fee = 0;
}
shepherd.log('electrum createrawtx =>', true);
ecl.connect();
shepherd.listunspent(ecl, changeAddress, network, true, req.body.verify === 'true' ? true : null)
.then((utxoList) => {
ecl.close();
if (utxoList &&
utxoList.length &&
utxoList[0] &&
utxoList[0].txid) {
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);
targets[0].value = targets[0].value + fee;
shepherd.log(`default fee ${fee}`, true);
shepherd.log(`targets ==>`, true);
shepherd.log(targets, true);
// 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
const firstRun = shepherd.coinSelect(utxoListFormatted, targets, btcFee ? btcFee : 0);
let inputs = firstRun.inputs;
let outputs = firstRun.outputs;
if (btcFee) {
shepherd.log(`btc fee per byte ${btcFee}`, true);
fee = firstRun.fee;
}
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 (!outputs) {
targets[0].value = targets[0].value - fee;
shepherd.log('second run', true);
shepherd.log('coinselect adjusted targets =>', true);
shepherd.log(targets, true);
const secondRun = shepherd.coinSelect(utxoListFormatted, targets, 0);
inputs = secondRun.inputs;
outputs = secondRun.outputs;
fee = fee ? fee : secondRun.fee;
shepherd.log('second run coinselect inputs =>', true);
shepherd.log(inputs, true);
shepherd.log('second run coinselect outputs =>', true);
shepherd.log(outputs, true);
shepherd.log('second run coinselect fee =>', true);
shepherd.log(fee, true);
}
let _change = 0;
if (outputs &&
outputs.length > 1) {
_change = outputs[outputs.length - 1].value - fee;
}
if (!btcFee &&
_change === 0) {
outputs[0].value = outputs[0].value - fee;
}
shepherd.log('adjusted outputs');
shepherd.log(outputs, true);
shepherd.log('init targets', true);
shepherd.log(initTargets, true);
if (initTargets[0].value < targets[0].value) {
targets[0].value = initTargets[0].value;
}
let _targetsSum = 0;
for (let i = 0; i < targets.length; i++) {
_targetsSum += Number(targets[i].value);
}
shepherd.log(`total targets sum ${_targetsSum}`);
/*if (btcFee) {
value = _targetsSum;
} else {
if (_change > 0) {
value = _targetsSum - fee;
}
}*/
value = _targetsSum;
shepherd.log('adjusted outputs, value - default fee =>', true);
shepherd.log(outputs, true);
// 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;
const _feeOverhead = 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);
if (_maxSpend - fee === value) {
_change = totalInterest - _change - _feeOverhead;
if (outputAddress === changeAddress) {
value += _change;
_change = 0;
shepherd.log(`send to self ${outputAddress} = ${changeAddress}`, true);
shepherd.log(`send to self old val ${value}, new val ${value + _change}`, true);
}
} else {
_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;
}
let voutSum = 0;
for (let i = 0; i < outputs.length; i++) {
voutSum += outputs[i].value;
}
const _estimatedFee = vinSum - voutSum;
shepherd.log(`vin sum ${vinSum} (${vinSum * 0.00000001})`, true);
shepherd.log(`vout sum ${voutSum} (${voutSum * 0.00000001})`, true);
shepherd.log(`estimatedFee ${_estimatedFee} (${_estimatedFee * 0.00000001})`, true);
// double check no extra fee is applied
shepherd.log(`vin - vout - change ${vinSum - value - _change}`);
if ((vinSum - value - _change) > fee) {
_change += fee;
shepherd.log(`double fee, increase change by ${fee}`);
shepherd.log(`adjusted vin - vout - change ${vinSum - value - _change}`);
}
// TODO: use individual dust thresholds
if (_change > 0 &&
_change <= 1000) {
shepherd.log(`change is < 1000 sats, donate ${_change} sats to miners`, true);
_change = 0;
}
outputAddress = outputs;
if (outputAddress.length > 1) {
outputAddress.pop();
}
let _rawtx;
if (network === 'btg' ||
network === 'bch') {
/*_rawtx = shepherd.buildSignedTxForks(
outputAddress,
changeAddress,
wif,
network,
inputs,
_change,
value
);*/
} else {
_rawtx = shepherd.buildSignedTxMulti(
outputAddress,
changeAddress,
wif,
network,
inputs,
_change,
value,
opreturn
);
}
if (!push ||
push === 'false') {
const successObj = {
msg: 'success',
result: {
utxoSet: inputs,
change: _change,
changeAdjusted: _change,
totalInterest,
// 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();
const _rawObj = {
utxoSet: inputs,
change: _change,
changeAdjusted: _change,
totalInterest,
fee,
value,
outputAddress,
changeAddress,
network,
rawtx: _rawtx,
txid,
utxoVerified,
};
if (txid &&
txid.indexOf('bad-txns-inputs-spent') > -1) {
const successObj = {
msg: 'error',
result: 'Bad transaction inputs spent',
raw: _rawObj,
};
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: _rawObj,
};
res.end(JSON.stringify(successObj));
} else {
const successObj = {
msg: 'success',
result: _rawObj,
};
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: _rawObj,
};
res.end(JSON.stringify(successObj));
} else {
const successObj = {
msg: 'error',
result: 'Can\'t broadcast transaction',
raw: _rawObj,
};
res.end(JSON.stringify(successObj));
}
}
}
});
}
}
}
} else {
const successObj = {
msg: 'error',
result: utxoList,
};
res.end(JSON.stringify(successObj));
}
});
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(errorObj));
}
});
// single sig
shepherd.buildSignedTxMulti = (sendTo, changeAddress, wif, network, utxo, changeValue, spendValue, opreturn) => {
let key = shepherd.isZcash(network) ? bitcoinZcash.ECPair.fromWIF(wif, shepherd.getNetworkData(network)) : shepherd.bitcoinJS.ECPair.fromWIF(wif, shepherd.getNetworkData(network));
let tx;
if (shepherd.isZcash(network)) {
tx = new bitcoinZcash.TransactionBuilder(shepherd.getNetworkData(network));
} else if (shepherd.isPos(network)) {
tx = new bitcoinPos.TransactionBuilder(shepherd.getNetworkData(network));
} else {
tx = new shepherd.bitcoinJS.TransactionBuilder(shepherd.getNetworkData(network));
}
shepherd.log('buildSignedTx', true);
// 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);
}
for (let i = 0; i < sendTo.length; i++) {
if (shepherd.isPos(network)) {
tx.addOutput(sendTo[i].address, Number(sendTo[i].value), shepherd.getNetworkData(network));
} else {
tx.addOutput(sendTo[i].address, Number(sendTo[i].value));
}
}
if (changeValue > 0) {
if (shepherd.isPos(network)) {
tx.addOutput(changeAddress, Number(changeValue), shepherd.getNetworkData(network));
} else {
tx.addOutput(changeAddress, Number(changeValue));
}
}
if (opreturn &&
opreturn.length) {
for (let i = 0; i < opreturn.length; i++) {
shepherd.log(`opreturn ${i} ${opreturn[i]}`);
const data = Buffer.from(opreturn[i], 'utf8');
const dataScript = shepherd.bitcoinJS.script.nullData.output.encode(data);
tx.addOutput(dataScript, 1000);
}
}
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++) {
if (shepherd.isPos(network)) {
tx.sign(shepherd.getNetworkData(network), i, key);
} else {
tx.sign(i, key);
}
}
const rawtx = tx.build().toHex();
shepherd.log('buildSignedTx signed tx hex', true);
shepherd.log(rawtx, true);
return rawtx;
}
return shepherd;
};

86
routes/shepherd/electrum/createtx-split.js

@ -0,0 +1,86 @@
const bitcoinJSForks = require('bitcoinforksjs-lib');
const bitcoinZcash = require('bitcoinjs-lib-zcash');
const bitcoinPos = require('bitcoinjs-lib-pos');
module.exports = (shepherd) => {
// utxo split 1 -> 1, multiple outputs
shepherd.post('/electrum/createrawtx-split', (req, res, next) => {
if (shepherd.checkToken(req.body.token)) {
const wif = req.body.payload.wif;
const utxo = req.body.payload.utxo;
const targets = req.body.payload.targets;
const network = req.body.payload.network;
const change = req.body.payload.change;
const outputAddress = req.body.payload.outputAddress;
const changeAddress = req.body.payload.changeAddress;
let key = shepherd.isZcash(network) ? bitcoinZcash.ECPair.fromWIF(wif, shepherd.getNetworkData(network)) : shepherd.bitcoinJS.ECPair.fromWIF(wif, shepherd.getNetworkData(network));
let tx;
if (shepherd.isZcash(network)) {
tx = new bitcoinZcash.TransactionBuilder(shepherd.getNetworkData(network));
} else if (shepherd.isPos(network)) {
tx = new bitcoinPos.TransactionBuilder(shepherd.getNetworkData(network));
} else {
tx = new shepherd.bitcoinJS.TransactionBuilder(shepherd.getNetworkData(network));
}
shepherd.log('buildSignedTx', true);
shepherd.log(`buildSignedTx pub key ${key.getAddress().toString()}`, true);
for (let i = 0; i < utxo.length; i++) {
tx.addInput(utxo[i].txid, utxo[i].vout);
}
for (let i = 0; i < targets.length; i++) {
if (shepherd.isPos(network)) {
tx.addOutput(outputAddress, Number(targets[i]), shepherd.getNetworkData(network));
} else {
tx.addOutput(outputAddress, Number(targets[i]));
}
}
if (Number(change) > 0) {
if (shepherd.isPos(network)) {
tx.addOutput(changeAddress, Number(change), shepherd.getNetworkData(network));
} else {
shepherd.log(`change ${change}`, true);
tx.addOutput(changeAddress, Number(change));
}
}
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);
}
for (let i = 0; i < utxo.length; i++) {
if (shepherd.isPos(network)) {
tx.sign(shepherd.getNetworkData(network), i, key);
} else {
tx.sign(i, key);
}
}
const rawtx = tx.build().toHex();
const successObj = {
msg: 'success',
result: rawtx,
};
res.end(JSON.stringify(successObj));
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(errorObj));
}
});
return shepherd;
};

792
routes/shepherd/electrum/createtx.js

@ -1,9 +1,22 @@
const bitcoinJSForks = require('bitcoinforksjs-lib');
const bitcoinZcash = require('bitcoinjs-lib-zcash');
const bitcoinPos = require('bitcoinjs-lib-pos');
module.exports = (shepherd) => {
// unsigned tx
shepherd.buildUnsignedTx = (sendTo, changeAddress, network, utxo, changeValue, spendValue) => {
let tx = new shepherd.bitcoinJS.TransactionBuilder(shepherd.getNetworkData(network));
let tx;
shepherd.log('buildSignedTx');
// TODO: finish unsigned for zcash, btc forks and pos coins
if (network === 'btg') {
shepherd.log('enable btg', true);
tx = new bitcoinJSForks.TransactionBuilder(shepherd.getNetworkData(network));
tx.enableBitcoinGold(true);
} else {
tx = new shepherd.bitcoinJS.TransactionBuilder(shepherd.getNetworkData(network));
}
shepherd.log('buildSignedTx', true);
// console.log(`buildSignedTx priv key ${wif}`);
shepherd.log(`buildSignedTx pub key ${changeAddress}`, true);
// console.log('buildSignedTx std tx fee ' + shepherd.electrumServers[network].txfee);
@ -41,11 +54,19 @@ 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.buildSignedTx = (sendTo, changeAddress, wif, network, utxo, changeValue, spendValue, opreturn) => {
let key = shepherd.isZcash(network) ? bitcoinZcash.ECPair.fromWIF(wif, shepherd.getNetworkData(network)) : shepherd.bitcoinJS.ECPair.fromWIF(wif, shepherd.getNetworkData(network));
let tx;
if (shepherd.isZcash(network)) {
tx = new bitcoinZcash.TransactionBuilder(shepherd.getNetworkData(network));
} else if (shepherd.isPos(network)) {
tx = new bitcoinPos.TransactionBuilder(shepherd.getNetworkData(network));
} else {
tx = new shepherd.bitcoinJS.TransactionBuilder(shepherd.getNetworkData(network));
}
shepherd.log('buildSignedTx');
shepherd.log('buildSignedTx', true);
// 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);
@ -54,10 +75,25 @@ module.exports = (shepherd) => {
tx.addInput(utxo[i].txid, utxo[i].vout);
}
tx.addOutput(sendTo, Number(spendValue));
if (shepherd.isPos(network)) {
tx.addOutput(sendTo, Number(spendValue), shepherd.getNetworkData(network));
} else {
tx.addOutput(sendTo, Number(spendValue));
}
if (changeValue > 0) {
tx.addOutput(changeAddress, Number(changeValue));
if (shepherd.isPos(network)) {
tx.addOutput(changeAddress, Number(changeValue), shepherd.getNetworkData(network));
} else {
tx.addOutput(changeAddress, Number(changeValue));
}
}
if (opreturn) {
console.log(`opreturn ${opreturn}`);
const data = Buffer.from(opreturn, 'utf8');
const dataScript = shepherd.bitcoinJS.script.nullData.output.encode(data);
tx.addOutput(dataScript, 1000);
}
if (network === 'komodo' ||
@ -75,7 +111,65 @@ module.exports = (shepherd) => {
shepherd.log(tx, true);
for (let i = 0; i < utxo.length; i++) {
tx.sign(i, key);
if (shepherd.isPos(network)) {
tx.sign(shepherd.getNetworkData(network), i, key);
} else {
tx.sign(i, key);
}
}
const rawtx = tx.build().toHex();
shepherd.log('buildSignedTx signed tx hex', true);
shepherd.log(rawtx, true);
return rawtx;
}
// btg
shepherd.buildSignedTxForks = (sendTo, changeAddress, wif, network, utxo, changeValue, spendValue) => {
let tx;
if (network === 'btg' ||
network === 'bch') {
tx = new bitcoinJSForks.TransactionBuilder(shepherd.getNetworkData(network));
}
const keyPair = bitcoinJSForks.ECPair.fromWIF(wif, shepherd.getNetworkData(network));
const pk = bitcoinJSForks.crypto.hash160(keyPair.getPublicKeyBuffer());
const spk = bitcoinJSForks.script.pubKeyHash.output.encode(pk);
shepherd.log(`buildSignedTx${network.toUpperCase()}`, true);
for (let i = 0; i < utxo.length; i++) {
tx.addInput(utxo[i].txid, utxo[i].vout, bitcoinJSForks.Transaction.DEFAULT_SEQUENCE, spk);
}
tx.addOutput(sendTo, Number(spendValue));
if (changeValue > 0) {
tx.addOutput(changeAddress, Number(changeValue));
}
if (network === 'btg') {
tx.enableBitcoinGold(true);
} else if (network === 'bch') {
tx.enableBitcoinCash(true);
}
tx.setVersion(2);
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);
const hashType = bitcoinJSForks.Transaction.SIGHASH_ALL | bitcoinJSForks.Transaction.SIGHASH_BITCOINCASHBIP143;
for (let i = 0; i < utxo.length; i++) {
tx.sign(i, keyPair, null, hashType, utxo[i].value);
}
const rawtx = tx.build().toHex();
@ -101,386 +195,424 @@ module.exports = (shepherd) => {
}
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;
let 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;
}
if (shepherd.checkToken(req.query.token)) {
// TODO: unconf output(s) error message
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 push = req.query.push;
const opreturn = req.query.opreturn;
const btcFee = req.query.btcfee ? Number(req.query.btcfee) : null;
let fee = shepherd.electrumServers[network].txfee;
let value = Number(req.query.value);
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,
});
if (req.query.vote) {
wif = shepherd.elections.priv;
}
if (btcFee) {
fee = 0;
}
shepherd.log('electrum createrawtx =>', true);
ecl.connect();
shepherd.listunspent(ecl, changeAddress, network, true, req.query.verify === 'true' ? true : null)
.then((utxoList) => {
ecl.close();
if (utxoList &&
utxoList.length &&
utxoList[0] &&
utxoList[0].txid) {
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 (!outputs) {
targets[0].value = targets[0].value - fee;
shepherd.log('second run', true);
shepherd.log('coinselect adjusted targets =>', true);
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 secondRun = shepherd.coinSelect(utxoListFormatted, targets, feeRate);
inputs = secondRun.inputs;
outputs = secondRun.outputs;
fee = secondRun.fee;
targets[0].value = targets[0].value + fee;
shepherd.log('second run coinselect inputs =>', true);
shepherd.log(`default fee ${fee}`, true);
shepherd.log(`targets ==>`, true);
shepherd.log(targets, true);
// 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
const firstRun = shepherd.coinSelect(utxoListFormatted, targets, btcFee ? btcFee : 0);
let inputs = firstRun.inputs;
let outputs = firstRun.outputs;
if (btcFee) {
shepherd.log(`btc fee per byte ${btcFee}`, true);
fee = firstRun.fee;
}
shepherd.log('coinselect res =>', true);
shepherd.log('coinselect inputs =>', true);
shepherd.log(inputs, true);
shepherd.log('second run coinselect outputs =>', true);
shepherd.log('coinselect outputs =>', true);
shepherd.log(outputs, true);
shepherd.log('second run coinselect fee =>', true);
shepherd.log('coinselect calculated fee =>', true);
shepherd.log(fee, true);
}
let _change = 0;
if (!outputs) {
targets[0].value = targets[0].value - fee;
shepherd.log('second run', true);
shepherd.log('coinselect adjusted targets =>', true);
shepherd.log(targets, true);
const secondRun = shepherd.coinSelect(utxoListFormatted, targets, 0);
inputs = secondRun.inputs;
outputs = secondRun.outputs;
fee = fee ? fee : secondRun.fee;
shepherd.log('second run coinselect inputs =>', true);
shepherd.log(inputs, true);
shepherd.log('second run coinselect outputs =>', true);
shepherd.log(outputs, true);
shepherd.log('second run coinselect fee =>', true);
shepherd.log(fee, true);
}
if (outputs &&
outputs.length === 2) {
_change = outputs[1].value;
}
let _change = 0;
// 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;
}
if (outputs &&
outputs.length === 2) {
_change = outputs[1].value - fee;
}
for (let i = 0; i < inputs.length; i++) {
if (Number(inputs[i].interestSats) > interestClaimThreshold) {
totalInterest += Number(inputs[i].interestSats);
totalInterestUTXOCount++;
if (!btcFee &&
_change === 0) {
outputs[0].value = outputs[0].value - fee;
}
if (btcFee) {
value = outputs[0].value;
} else {
if (_change > 0) {
value = outputs[0].value - fee;
}
}
}
const _maxSpend = shepherd.maxSpendBalance(utxoListFormatted);
shepherd.log('adjusted outputs, value - default fee =>', true);
shepherd.log(outputs, true);
if (value > _maxSpend) {
const successObj = {
msg: 'error',
result: `Spend value is too large. Max available amount is ${Number((_maxSpend * 0.00000001.toFixed(8)))}`,
};
// 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;
}
}
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);
if (_maxSpend === value) {
_change = totalInterest -_change - _feeOverhead;
if (outputAddress === changeAddress) {
value += _change;
_change = 0;
shepherd.log(`send to self ${outputAddress} = ${changeAddress}`, true);
shepherd.log(`send to self old val ${value}, new val ${value + _change}`, true);
for (let i = 0; i < inputs.length; i++) {
if (Number(inputs[i].interestSats) > interestClaimThreshold) {
totalInterest += Number(inputs[i].interestSats);
totalInterestUTXOCount++;
}
} else {
_change = _change + (totalInterest - _feeOverhead);
}
}
if (!inputs &&
!outputs) {
const _maxSpend = shepherd.maxSpendBalance(utxoListFormatted);
if (value > _maxSpend) {
const successObj = {
msg: 'error',
result: 'Can\'t find best fit utxo. Try lower amount.',
result: `Spend value is too large. Max available amount is ${Number((_maxSpend * 0.00000001.toFixed(8)))}`,
};
res.end(JSON.stringify(successObj));
} else {
let vinSum = 0;
for (let i = 0; i < inputs.length; i++) {
vinSum += inputs[i].value;
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;
const _feeOverhead = 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);
if (_maxSpend - fee === value) {
_change = totalInterest - _change - _feeOverhead;
if (outputAddress === changeAddress) {
value += _change;
_change = 0;
shepherd.log(`send to self ${outputAddress} = ${changeAddress}`, true);
shepherd.log(`send to self old val ${value}, new val ${value + _change}`, true);
}
} else {
_change = _change + (totalInterest - _feeOverhead);
}
}
const _estimatedFee = vinSum - outputs[0].value - _change;
if (!inputs &&
!outputs) {
const successObj = {
msg: 'error',
result: 'Can\'t find best fit utxo. Try lower amount.',
};
shepherd.log(`vin sum ${vinSum} (${vinSum * 0.00000001})`, true);
shepherd.log(`estimatedFee ${_estimatedFee} (${_estimatedFee * 0.00000001})`, true);
res.end(JSON.stringify(successObj));
} else {
let vinSum = 0;
let _rawtx;
for (let i = 0; i < inputs.length; i++) {
vinSum += inputs[i].value;
}
if (req.query.unsigned) {
_rawtx = shepherd.buildUnsignedTx(
outputAddress,
changeAddress,
network,
inputs,
_change,
value
);
} else {
_rawtx = shepherd.buildSignedTx(
outputAddress,
changeAddress,
wif,
network,
inputs,
_change,
value
);
}
const _estimatedFee = vinSum - outputs[0].value - _change;
if (!push ||
push === 'false') {
const successObj = {
msg: 'success',
result: {
utxoSet: inputs,
change: _change,
changeAdjusted: _change,
totalInterest,
// wif,
fee,
value,
shepherd.log(`vin sum ${vinSum} (${vinSum * 0.00000001})`, true);
shepherd.log(`estimatedFee ${_estimatedFee} (${_estimatedFee * 0.00000001})`, true);
// double check no extra fee is applied
shepherd.log(`vin - vout ${vinSum - value - _change}`);
if ((vinSum - value - _change) > fee) {
_change += fee;
shepherd.log(`double fee, increase change by ${fee}`);
}
// TODO: use individual dust thresholds
if (_change > 0 &&
_change <= 1000) {
shepherd.log(`change is < 1000 sats, donate ${_change} sats to miners`, true);
_change = 0;
}
let _rawtx;
if (req.query.unsigned) {
_rawtx = shepherd.buildUnsignedTx(
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,
changeAdjusted: _change,
totalInterest,
fee,
value,
inputs,
_change,
value
);
} else {
if (!req.query.offline) {
if (network === 'btg' ||
network === 'bch') {
_rawtx = shepherd.buildSignedTxForks(
outputAddress,
changeAddress,
wif,
network,
inputs,
_change,
value
);
} else {
_rawtx = shepherd.buildSignedTx(
outputAddress,
changeAddress,
wif,
network,
rawtx: _rawtx,
txid,
utxoVerified,
},
inputs,
_change,
value,
opreturn
);
}
}
}
if (!push ||
push === 'false') {
const successObj = {
msg: 'success',
result: {
utxoSet: inputs,
change: _change,
changeAdjusted: _change,
totalInterest,
// 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();
const _rawObj = {
utxoSet: inputs,
change: _change,
changeAdjusted: _change,
totalInterest,
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,
changeAdjusted: _change,
totalInterest,
fee,
value,
outputAddress,
changeAddress,
network,
rawtx: _rawtx,
txid,
utxoVerified,
},
};
res.end(JSON.stringify(successObj));
} else {
const successObj = {
msg: 'success',
result: {
utxoSet: inputs,
change: _change,
changeAdjusted: _change,
totalInterest,
fee,
// wif,
value,
outputAddress,
changeAddress,
network,
rawtx: _rawtx,
txid,
utxoVerified,
},
};
res.end(JSON.stringify(successObj));
}
txid.indexOf('bad-txns-inputs-spent') > -1) {
const successObj = {
msg: 'error',
result: 'Bad transaction inputs spent',
raw: _rawObj,
};
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,
changeAdjusted: _change,
totalInterest,
fee,
value,
outputAddress,
changeAddress,
network,
rawtx: _rawtx,
txid,
utxoVerified,
},
};
res.end(JSON.stringify(successObj));
txid.length === 64) {
if (txid.indexOf('bad-txns-in-belowout') > -1) {
const successObj = {
msg: 'error',
result: 'Bad transaction inputs spent',
raw: _rawObj,
};
res.end(JSON.stringify(successObj));
} else {
const successObj = {
msg: 'success',
result: _rawObj,
};
res.end(JSON.stringify(successObj));
}
} else {
const successObj = {
msg: 'error',
result: 'Can\'t broadcast transaction',
raw: {
utxoSet: inputs,
change: _change,
changeAdjusted: _change,
totalInterest,
fee,
value,
outputAddress,
changeAddress,
network,
rawtx: _rawtx,
txid,
utxoVerified,
},
};
res.end(JSON.stringify(successObj));
if (txid &&
txid.indexOf('bad-txns-in-belowout') > -1) {
const successObj = {
msg: 'error',
result: 'Bad transaction inputs spent',
raw: _rawObj,
};
res.end(JSON.stringify(successObj));
} else {
const successObj = {
msg: 'error',
result: 'Can\'t broadcast transaction',
raw: _rawObj,
};
res.end(JSON.stringify(successObj));
}
}
}
}
});
});
}
}
}
} else {
const successObj = {
msg: 'error',
result: utxoList,
};
res.end(JSON.stringify(successObj));
}
} else {
const successObj = {
msg: 'error',
result: utxoList,
};
});
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(successObj));
}
});
res.end(JSON.stringify(errorObj));
}
});
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
shepherd.post('/electrum/pushtx', (req, res, next) => {
if (shepherd.checkToken(req.body.token)) {
const rawtx = req.body.rawtx;
const _network = req.body.network;
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((json) => {
ecl.close();
shepherd.log('electrum pushtx ==>', true);
shepherd.log(json, true);
ecl.connect();
ecl.blockchainTransactionBroadcast(rawtx)
.then((json) => {
ecl.close();
shepherd.log('electrum pushtx ==>', true);
shepherd.log(json, true);
const successObj = {
msg: 'success',
result: json,
const successObj = {
msg: 'success',
result: json,
};
res.end(JSON.stringify(successObj));
});
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(successObj));
});
res.end(JSON.stringify(errorObj));
}
});
return shepherd;

31
routes/shepherd/electrum/estimate.js

@ -1,20 +1,29 @@
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
if (shepherd.checkToken(req.query.token)) {
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);
ecl.connect();
ecl.blockchainEstimatefee(req.query.blocks)
.then((json) => {
ecl.close();
shepherd.log('electrum estimatefee ==>', true);
const successObj = {
msg: 'success',
result: json,
const successObj = {
msg: 'success',
result: json,
};
res.end(JSON.stringify(successObj));
});
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(successObj));
});
res.end(JSON.stringify(errorObj));
}
});
shepherd.estimateTxSize = (numVins, numOuts) => {

2
routes/shepherd/electrum/interest.js

@ -19,7 +19,7 @@ module.exports = (shepherd) => {
}
timestampDiffMinutes -= 59;
shepherd.log(`minutes if statement ${timestampDiffMinutes}`, true);
// shepherd.log(`minutes if statement ${timestampDiffMinutes}`, true);
// TODO: check if interest is > 5% yr
// calc ytd and 5% for 1 yr

353
routes/shepherd/electrum/keys.js

@ -1,11 +1,28 @@
const sha256 = require('js-sha256');
const buggySha256 = require('sha256');
const bip39 = require('bip39');
const crypto = require('crypto');
const bigi = require('bigi');
const bitcoinZcash = require('bitcoinjs-lib-zcash');
const bitcoin = require('bitcoinjs-lib');
const bs58check = require('bs58check');
module.exports = (shepherd) => {
shepherd.wifToWif = (wif, network) => {
network = network === 'KMD' ? 'komodo' : network.toLowerCase();
const key = shepherd.isZcash(network) ? new bitcoinZcash.ECPair.fromWIF(wif, shepherd.getNetworkData(network), true) : new bitcoin.ECPair.fromWIF(wif, shepherd.getNetworkData(network), true);
return {
pub: key.getAddress(),
priv: key.toWIF(),
};
}
shepherd.seedToWif = (seed, network, iguana) => {
let bytes;
if (process.argv.indexOf('spvold=true') > -1) {
bytes = shepherd.sha256(seed, { asBytes: true });
bytes = buggySha256(seed, { asBytes: true });
} else {
const hash = sha256.create().update(seed);
bytes = hash.array();
@ -17,162 +34,258 @@ module.exports = (shepherd) => {
bytes[31] |= 64;
}
const toHexString = (byteArray) => {
return Array.from(byteArray, (byte) => {
return ('0' + (byte & 0xFF).toString(16)).slice(-2);
}).join('');
}
const d = bigi.fromBuffer(bytes);
const keyPair = shepherd.isZcash(network) ? new bitcoinZcash.ECPair(d, null, { network: shepherd.getNetworkData(network) }) : new bitcoin.ECPair(d, null, { network: shepherd.getNetworkData(network) });
const keys = {
pub: keyPair.getAddress(),
priv: keyPair.toWIF(),
};
const hex = toHexString(bytes);
/*shepherd.log(`seed: ${seed}`, true);
shepherd.log(`network ${network}`, true);
shepherd.log(`seedtowif priv key ${keys.priv}`, true);
shepherd.log(`seedtowif pub key ${keys.pub}`, true);*/
const key = new shepherd.CoinKey(new Buffer(hex, 'hex'), {
private: shepherd.getNetworkData(network).wif,
public: shepherd.getNetworkData(network).pubKeyHash,
});
return keys;
}
key.compressed = true;
shepherd.get('/electrum/wiftopub', (req, res, next) => {
if (shepherd.checkToken(req.query.token)) {
let key = shepherd.isZcash(req.query.coin.toLowerCase()) ? bitcoinZcash.ECPair.fromWIF(req.query.wif, shepherd.electrumJSNetworks[req.query.coin], true) : bitcoin.ECPair.fromWIF(req.query.wif, shepherd.electrumJSNetworks[req.query.coin], true);
keys = {
priv: key.toWIF(),
pub: key.getAddress(),
};
const successObj = {
msg: 'success',
result: {
keys,
},
};
res.end(JSON.stringify(successObj));
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
// shepherd.log(`seed: ${seed}`, true);
// shepherd.log(`network ${network}`, true);
// shepherd.log(`seedtowif priv key ${key.privateWif}`, true);
// shepherd.log(`seedtowif pub key ${key.publicAddress}`, true);
res.end(JSON.stringify(errorObj));
}
});
return {
priv: key.privateWif,
pub: key.publicAddress,
};
}
shepherd.post('/electrum/seedtowif', (req, res, next) => {
if (shepherd.checkToken(req.body.token)) {
let keys = shepherd.seedToWif(req.body.seed, req.body.network.toLowerCase(), req.body.iguana);
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,
},
};
const successObj = {
msg: 'success',
result: {
keys,
},
};
res.end(JSON.stringify(successObj));
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(successObj));
res.end(JSON.stringify(errorObj));
}
});
shepherd.post('/electrum/keys', (req, res, next) => {
let _matchingKeyPairs = 0;
let _electrumKeys = {};
shepherd.post('/electrum/seedtowif', (req, res, next) => {
if (shepherd.checkToken(req.body.token)) {
let keys = shepherd.seedToWif(req.body.seed, req.body.network.toLowerCase(), req.body.iguana);
for (let key in shepherd.electrumServers) {
const _abbr = shepherd.electrumServers[key].abbr;
const { priv, pub } = shepherd.seedToWif(req.body.seed, shepherd.findNetworkObj(_abbr), req.body.iguana);
const successObj = {
msg: 'success',
result: {
keys,
},
};
if (shepherd.electrumKeys[_abbr].pub === pub &&
shepherd.electrumKeys[_abbr].priv === priv) {
_matchingKeyPairs++;
}
res.end(JSON.stringify(successObj));
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(errorObj));
}
});
if (req.body.active) {
_electrumKeys = JSON.parse(JSON.stringify(shepherd.electrumKeys));
shepherd.getCoinByPub = (address) => {
const _skipNetworks = ['btc', 'crw', 'dgb', 'arg', 'zec', 'nmc', 'ltc', 'vtc', 'via', 'fair', 'doge', 'kmd', 'mona'];
for (let key in _electrumKeys) {
if (!shepherd.electrumCoins[key]) {
delete _electrumKeys[key];
try {
const _b58check = shepherd.isZcash(network.toLowerCase()) ? bitcoinZcash.address.fromBase58Check(address) : bitcoin.address.fromBase58Check(address);
let _coin = [];
let returnObj;
for (let key in shepherd.electrumJSNetworks) {
if (_b58check.version === shepherd.electrumJSNetworks[key].pubKeyHash &&
!_skipNetworks.find((item) => { return item === key ? true : false })) {
_coin.push(key);
}
}
} else {
_electrumKeys = shepherd.electrumKeys;
if (_coin.length) {
return {
coin: _coin,
version: _b58check.version,
};
} else {
return 'Unable to find matching coin version';
}
} catch(e) {
return 'Invalid pub address';
}
};
// shepherd.log(JSON.stringify(_electrumKeys, null, '\t'), true);
shepherd.addressVersionCheck = (network, address) => {
const _network = shepherd.getNetworkData(network.toLowerCase());
const successObj = {
msg: 'success',
result: _matchingKeyPairs === Object.keys(shepherd.electrumKeys).length ? _electrumKeys : false,
};
try {
const _b58check = shepherd.isZcash(network.toLowerCase()) ? bitcoinZcash.address.fromBase58Check(address) : bitcoin.address.fromBase58Check(address);
res.end(JSON.stringify(successObj));
});
if (_b58check.version === _network.pubKeyHash) {
return true;
} else {
return false;
}
} catch(e) {
return 'Invalid pub address';
}
};
shepherd.post('/electrum/seed/bip39/match', (req, res, next) => {
const bip39 = require('bip39'); // npm i -S bip39
const crypto = require('crypto');
const seed = bip39.mnemonicToSeed(req.body.seed);
const hdMaster = shepherd.bitcoinJS.HDNode.fromSeedBuffer(seed, shepherd.electrumJSNetworks.komodo); // seed from above
const matchPattern = req.body.match;
const _defaultAddressDepth = req.body.addressdepth;
const _defaultAccountCount = req.body.accounts;
let _addresses = [];
let _matchingKey;
for (let i = 0; i < _defaultAccountCount; i++) {
for (let j = 0; j < 1; j++) {
for (let k = 0; k < _defaultAddressDepth; k++) {
const _key = hdMaster.derivePath(`m/44'/141'/${i}'/${j}/${k}`);
if (_key.keyPair.getAddress() === matchPattern) {
_matchingKey = {
pub: _key.keyPair.getAddress(),
priv: _key.keyPair.toWIF(),
};
shepherd.post('/electrum/keys', (req, res, next) => {
if (shepherd.checkToken(req.body.token)) {
let _matchingKeyPairs = 0;
let _totalKeys = 0;
let _electrumKeys = {};
let _seed = req.body.seed;
let _wifError = false;
for (let key in shepherd.electrumCoins) {
if (key !== 'auth') {
const _abbr = key;
let isWif = false;
let priv;
let pub;
try {
bs58check.decode(_seed);
isWif = true;
} catch (e) {}
if (isWif) {
try {
let key = shepherd.isZcash(_abbr.toLowerCase()) ? bitcoinZcash.ECPair.fromWIF(_seed, shepherd.getNetworkData(_abbr.toLowerCase()), true) : bitcoin.ECPair.fromWIF(_seed, shepherd.getNetworkData(_abbr.toLowerCase()), true);
priv = key.toWIF();
pub = key.getAddress();
} catch (e) {
_wifError = true;
break;
}
} else {
let _keys = shepherd.seedToWif(_seed, shepherd.findNetworkObj(_abbr), req.body.iguana);
priv = _keys.priv;
pub = _keys.pub;
}
if (shepherd.electrumKeys[_abbr].pub === pub &&
shepherd.electrumKeys[_abbr].priv === priv) {
_matchingKeyPairs++;
}
/*_addresses.push({
pub: _key.keyPair.getAddress(),
priv: _key.keyPair.toWIF(),
});*/
_totalKeys++;
}
}
}
const successObj = {
msg: 'success',
result: _matchingKey ? _matchingKey : 'address is not found',
};
res.end(JSON.stringify(successObj));
});
if (req.body.active) {
_electrumKeys = JSON.parse(JSON.stringify(shepherd.electrumKeys));
// spv v2
/*shepherd.get('/electrum/bip39/seed', (req, res, next) => {
const _seed = '';
// TODO
const bip39 = require('bip39'); // npm i -S bip39
const crypto = require('crypto');
for (let key in _electrumKeys) {
if (!shepherd.electrumCoins[key]) {
delete _electrumKeys[key];
}
}
} else {
_electrumKeys = shepherd.electrumKeys;
}
// what you describe as 'seed'
const randomBytes = crypto.randomBytes(16); // 128 bits is enough
const successObj = {
msg: _wifError ? 'error' : 'success',
result: _wifError ? false : (_matchingKeyPairs === _totalKeys ? _electrumKeys : false),
};
// your 12 word phrase
const mnemonic = bip39.entropyToMnemonic(randomBytes.toString('hex'));
res.end(JSON.stringify(successObj));
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
// what is accurately described as the wallet seed
// var seed = bip39.mnemonicToSeed(mnemonic) // you'll use this in #3 below
const seed = bip39.mnemonicToSeed(_seed);
res.end(JSON.stringify(errorObj));
}
});
console.log(seed);
shepherd.getSpvFees = () => {
let _fees = {};
const successObj = {
msg: 'success',
result: {
servers: shepherd.electrumServers,
},
};
for (let key in shepherd.electrumServers) {
if (shepherd.electrumServers[key].txfee) {
_fees[shepherd.electrumServers[key].abbr] = shepherd.electrumServers[key].txfee;
}
}
res.end(JSON.stringify(successObj));
return _fees;
};
console.log(shepherd.bitcoinJS.networks.komodo);
const hdMaster = shepherd.bitcoinJS.HDNode.fromSeedBuffer(seed, shepherd.electrumJSNetworks.komodo); // seed from above
shepherd.post('/electrum/seed/bip39/match', (req, res, next) => {
if (shepherd.checkToken(req.body.token)) {
const seed = bip39.mnemonicToSeed(req.body.seed);
const hdMaster = bitcoin.HDNode.fromSeedBuffer(seed, shepherd.electrumJSNetworks.komodo);
const matchPattern = req.body.match;
const _defaultAddressDepth = req.body.addressdepth;
const _defaultAccountCount = req.body.accounts;
let _addresses = [];
let _matchingKey;
for (let i = 0; i < _defaultAccountCount; i++) {
for (let j = 0; j < 1; j++) {
for (let k = 0; k < _defaultAddressDepth; k++) {
const _key = hdMaster.derivePath(`m/44'/141'/${i}'/${j}/${k}`);
if (_key.keyPair.getAddress() === matchPattern) {
_matchingKey = {
pub: _key.keyPair.getAddress(),
priv: _key.keyPair.toWIF(),
};
}
}
}
}
const key1 = hdMaster.derivePath("m/44'/141'/0'/0/0");
const key2 = hdMaster.derivePath('m/1');
console.log(hdMaster);
const successObj = {
msg: 'success',
result: _matchingKey ? _matchingKey : 'address is not found',
};
console.log(key1.keyPair.toWIF());
console.log(key1.keyPair.getAddress());
console.log(key2.keyPair.toWIF());
res.end(JSON.stringify(successObj));
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
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()}`);
});*/
res.end(JSON.stringify(errorObj));
}
});
return shepherd;
};

89
routes/shepherd/electrum/listunspent.js

@ -1,3 +1,5 @@
// TODO: watchonly spendable switch
module.exports = (shepherd) => {
shepherd.listunspent = (ecl, address, network, full, verify) => {
let _atLeastOneDecodeTxFailed = false;
@ -24,11 +26,12 @@ module.exports = (shepherd) => {
}
if (!_utxo.length) { // no confirmed utxo
ecl.close();
resolve('no valid utxo');
} else {
shepherd.Promise.all(_utxo.map((_utxoItem, index) => {
return new shepherd.Promise((resolve, reject) => {
ecl.blockchainTransactionGet(_utxoItem['tx_hash'])
shepherd.getTransaction(_utxoItem['tx_hash'], network, ecl)
.then((_rawtxJSON) => {
shepherd.log('electrum gettransaction ==>', true);
shepherd.log(index + ' | ' + (_rawtxJSON.length - 1), true);
@ -36,7 +39,7 @@ module.exports = (shepherd) => {
// decode tx
const _network = shepherd.getNetworkData(network);
const decodedTx = shepherd.electrumJSTxDecoder(_rawtxJSON, _network);
const decodedTx = shepherd.electrumJSTxDecoder(_rawtxJSON, network, _network);
shepherd.log('decoded tx =>', true);
shepherd.log(decodedTx, true);
@ -67,11 +70,12 @@ module.exports = (shepherd) => {
verified: false,
};
// merkle root verification agains another electrum server
// merkle root verification against 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) {
if (verifyMerkleRes &&
verifyMerkleRes === shepherd.CONNECTION_ERROR_OR_INCOMPLETE_DATA) {
verifyMerkleRes = false;
}
@ -93,7 +97,7 @@ module.exports = (shepherd) => {
verified: false,
};
// merkle root verification agains another electrum server
// merkle root verification against another electrum server
if (verify) {
shepherd.verifyMerkleByCoin(shepherd.findCoinName(network), _utxoItem['tx_hash'], _utxoItem.height)
.then((verifyMerkleRes) => {
@ -126,6 +130,7 @@ module.exports = (shepherd) => {
});
}
} else {
ecl.close();
resolve('cant get current height');
}
});
@ -154,40 +159,50 @@ module.exports = (shepherd) => {
}
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);
if (shepherd.checkToken(req.query.token)) {
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,
};
const successObj = {
msg: 'success',
result: listunspent,
};
res.end(JSON.stringify(successObj));
});
res.end(JSON.stringify(successObj));
});
}
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(errorObj));
}
});

48
routes/shepherd/electrum/merkle.js

@ -2,11 +2,11 @@ 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();
}
let hash = txid;
let serialized;
shepherd.log(`getMerkleRoot txid ${txid}`, true);
shepherd.log(`getMerkleRoot pos ${pos}`, true);
@ -30,7 +30,7 @@ module.exports = (shepherd) => {
return hash;
}
shepherd.verifyMerkle = (txid, height, serverList, mainServer) => {
shepherd.verifyMerkle = (txid, height, serverList, mainServer, network) => {
// select random server
const getRandomIntInclusive = (min, max) => {
min = Math.ceil(min);
@ -44,7 +44,7 @@ module.exports = (shepherd) => {
const _randomServer = randomServer.split(':');
const _mainServer = mainServer.split(':');
let ecl = new shepherd.electrumJSCore(_mainServer[1], _mainServer[0], 'tcp'); // tcp or tls
let ecl = new shepherd.electrumJSCore(_mainServer[1], _mainServer[0], _mainServer[2]); // tcp or tls
return new shepherd.Promise((resolve, reject) => {
shepherd.log(`main server: ${mainServer}`, true);
@ -63,10 +63,10 @@ module.exports = (shepherd) => {
const _res = shepherd.getMerkleRoot(txid, merkleData.merkle, merkleData.pos);
shepherd.log(_res, true);
ecl = new shepherd.electrumJSCore(_randomServer[1], _randomServer[0], 'tcp');
ecl = new shepherd.electrumJSCore(_randomServer[1], _randomServer[0], _mainServer[2]);
ecl.connect();
ecl.blockchainBlockGetHeader(height)
shepherd.getBlockHeader(height, network, ecl)
.then((blockInfo) => {
if (blockInfo &&
blockInfo['merkle_root']) {
@ -83,13 +83,16 @@ module.exports = (shepherd) => {
resolve(false);
}
} else {
ecl.close();
resolve(shepherd.CONNECTION_ERROR_OR_INCOMPLETE_DATA);
}
} else {
ecl.close();
resolve(shepherd.CONNECTION_ERROR_OR_INCOMPLETE_DATA);
}
});
} else {
ecl.close();
resolve(shepherd.CONNECTION_ERROR_OR_INCOMPLETE_DATA);
}
});
@ -117,8 +120,10 @@ module.exports = (shepherd) => {
txid,
height,
_filteredServerList,
shepherd.electrumCoins[coin].server.ip + ':' + shepherd.electrumCoins[coin].server.port
).then((proof) => {
shepherd.electrumCoins[coin].server.ip + ':' + shepherd.electrumCoins[coin].server.port + ':' + shepherd.electrumServers[coin === 'KMD' || coin === 'komodo' ? 'komodo' : coin.toLowerCase()].proto,
coin
)
.then((proof) => {
resolve(proof);
});
} else {
@ -128,17 +133,26 @@ module.exports = (shepherd) => {
}
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,
},
if (shepherd.checkToken(req.query.token)) {
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));
});
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(successObj));
});
res.end(JSON.stringify(errorObj));
}
});
return shepherd;

199
routes/shepherd/electrum/network.js

@ -1,4 +1,42 @@
const txDecoder = {
default: require('../../electrumjs/electrumjs.txdecoder.js'),
zcash: require('../../electrumjs/electrumjs.txdecoder-2bytes.js'),
pos: require('../../electrumjs/electrumjs.txdecoder-pos.js'),
};
module.exports = (shepherd) => {
shepherd.isZcash = (network) => {
if (network === 'ZEC' ||
network === 'zec' ||
network === 'zcash' ||
network === 'ZCASH' ||
network === 'HUSH' ||
network === 'hush' ||
network === 'ZCL' ||
network === 'zcl' ||
network === 'BTCZ' ||
network === 'btcz') {
return true;
}
};
shepherd.isPos = (network) => {
if (network === 'BLK' ||
network === 'blk') {
return true;
}
};
shepherd.electrumJSTxDecoder = (rawtx, networkName, network) => {
if (shepherd.isZcash(networkName)) {
return txDecoder.zcash(rawtx, network);
} else if (shepherd.isPos(networkName)) {
return txDecoder.pos(rawtx, network);
} else {
return txDecoder.default(rawtx, network);
}
};
shepherd.getNetworkData = (network) => {
const coin = shepherd.findNetworkObj(network) || shepherd.findNetworkObj(network.toUpperCase()) || shepherd.findNetworkObj(network.toLowerCase());
const coinUC = coin ? coin.toUpperCase() : null;
@ -23,6 +61,12 @@ module.exports = (shepherd) => {
coin === 'MESH' ||
coin === 'WLC' ||
coin === 'MNZ' ||
coin === 'BTCH' ||
coin === 'KMD' ||
coin === 'BEER' ||
coin === 'PIZZA' ||
coin === 'VOTE' ||
coin === 'KOMODO' ||
coinUC === 'SUPERNET' ||
coinUC === 'REVS' ||
coinUC === 'SUPERNET' ||
@ -42,7 +86,13 @@ module.exports = (shepherd) => {
coinUC === 'CEAL' ||
coinUC === 'MESH' ||
coinUC === 'WLC' ||
coinUC === 'MNZ') {
coinUC === 'MNZ' ||
coinUC === 'BTCH' ||
coinUC === 'BEER' ||
coinUC === 'PIZZA' ||
coinUC === 'VOTE' ||
coinUC === 'KMD' ||
coinUC === 'KOMODO') {
return shepherd.electrumJSNetworks.komodo;
} else {
return shepherd.electrumJSNetworks[network];
@ -58,85 +108,112 @@ module.exports = (shepherd) => {
}
shepherd.get('/electrum/servers', (req, res, next) => {
if (req.query.abbr) {
let _electrumServers = {};
if (shepherd.checkToken(req.query.token)) {
if (req.query.abbr) {
let _electrumServers = {};
for (let key in shepherd.electrumServers) {
_electrumServers[shepherd.electrumServers[key].abbr] = shepherd.electrumServers[key];
}
for (let key in shepherd.electrumServers) {
_electrumServers[shepherd.electrumServers[key].abbr] = shepherd.electrumServers[key];
}
const successObj = {
msg: 'success',
result: {
servers: _electrumServers,
},
};
const successObj = {
msg: 'success',
result: {
servers: _electrumServers,
},
};
res.end(JSON.stringify(successObj));
res.end(JSON.stringify(successObj));
} else {
const successObj = {
msg: 'success',
result: {
servers: shepherd.electrumServers,
},
};
res.end(JSON.stringify(successObj));
}
} else {
const successObj = {
msg: 'success',
result: {
servers: shepherd.electrumServers,
},
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(successObj));
res.end(JSON.stringify(errorObj));
}
});
shepherd.get('/electrum/coins/server/set', (req, res, next) => {
shepherd.electrumCoins[req.query.coin].server = {
ip: req.query.address,
port: req.query.port,
};
if (shepherd.checkToken(req.query.token)) {
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;
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);
// shepherd.log(JSON.stringify(shepherd.electrumCoins[req.query.coin], null, '\t'), true);
const successObj = {
msg: 'success',
result: true,
};
const successObj = {
msg: 'success',
result: true,
};
res.end(JSON.stringify(successObj));
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(successObj));
res.end(JSON.stringify(errorObj));
}
});
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,
};
if (shepherd.checkToken(req.query.token)) {
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));
}
});
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(successObj));
}
});
res.end(JSON.stringify(errorObj));
}
});
return shepherd;

573
routes/shepherd/electrum/transactions.js

@ -1,3 +1,5 @@
const async = require('async');
module.exports = (shepherd) => {
shepherd.sortTransactions = (transactions) => {
return transactions.sort((b, a) => {
@ -12,135 +14,203 @@ module.exports = (shepherd) => {
return 0;
});
}
shepherd.getTransaction = (txid, network, ecl) => {
return new shepherd.Promise((resolve, reject) => {
if (!shepherd.electrumCache[network]) {
shepherd.electrumCache[network] = {};
}
if (!shepherd.electrumCache[network].tx) {
shepherd.electrumCache[network]['tx'] = {};
}
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
if (!shepherd.electrumCache[network].tx[txid]) {
shepherd.log(`electrum raw input tx ${txid}`, true);
ecl.blockchainTransactionGet(txid)
.then((_rawtxJSON) => {
shepherd.electrumCache[network].tx[txid] = _rawtxJSON;
resolve(_rawtxJSON);
});
} else {
shepherd.log(`electrum cached raw input tx ${txid}`, true);
resolve(shepherd.electrumCache[network].tx[txid]);
}
});
}
shepherd.log('electrum listtransactions ==>', true);
shepherd.getBlockHeader = (height, network, ecl) => {
return new shepherd.Promise((resolve, reject) => {
if (!shepherd.electrumCache[network]) {
shepherd.electrumCache[network] = {};
}
if (!shepherd.electrumCache[network].blockHeader) {
shepherd.electrumCache[network]['blockHeader'] = {};
}
if (!req.query.full) {
ecl.connect();
ecl.blockchainAddressGetHistory(req.query.address)
.then((json) => {
ecl.close();
shepherd.log(json, true);
if (!shepherd.electrumCache[network].blockHeader[height]) {
shepherd.log(`electrum raw block ${height}`, true);
ecl.blockchainBlockGetHeader(height)
.then((_rawtxJSON) => {
shepherd.electrumCache[network].blockHeader[height] = _rawtxJSON;
resolve(_rawtxJSON);
});
} else {
shepherd.log(`electrum cached raw block ${height}`, true);
resolve(shepherd.electrumCache[network].blockHeader[height]);
}
});
}
json = shepherd.sortTransactions(json);
shepherd.get('/electrum/listtransactions', (req, res, next) => {
if (shepherd.checkToken(req.query.token)) {
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 successObj = {
msg: 'success',
result: json,
};
shepherd.log('electrum listtransactions ==>', true);
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();
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,
};
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.length > MAX_TX ? json.slice(0, MAX_TX) : json;
let _rawtx = [];
shepherd.log(json.length, true);
shepherd.Promise.all(json.map((transaction, index) => {
return new shepherd.Promise((resolve, reject) => {
ecl.blockchainBlockGetHeader(transaction.height)
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) {
let _rawtx = [];
json = shepherd.sortTransactions(json);
json = json.length > MAX_TX ? json.slice(0, MAX_TX) : json;
shepherd.log(json.length, true);
let index = 0;
async.eachOfSeries(json, (transaction, ind, callback) => {
shepherd.getBlockHeader(transaction.height, network, ecl)
.then((blockInfo) => {
if (blockInfo &&
blockInfo.timestamp) {
ecl.blockchainTransactionGet(transaction['tx_hash'])
shepherd.getTransaction(transaction['tx_hash'], network, ecl)
.then((_rawtxJSON) => {
shepherd.log('electrum gettransaction ==>', true);
shepherd.log((index + ' | ' + (_rawtxJSON.length - 1)), true);
shepherd.log(_rawtxJSON, true);
// shepherd.log(_rawtxJSON, true);
// decode tx
const _network = shepherd.getNetworkData(network);
const decodedTx = shepherd.electrumJSTxDecoder(_rawtxJSON, _network);
const decodedTx = shepherd.electrumJSTxDecoder(_rawtxJSON, network, _network);
let txInputs = [];
shepherd.log(`decodedtx network ${network}`, true);
shepherd.log('decodedtx =>', true);
shepherd.log(decodedTx.outputs, true);
// shepherd.log(decodedTx.outputs, true);
let index2 = 0;
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);
decodedTx.inputs &&
decodedTx.inputs.length) {
async.eachOfSeries(decodedTx.inputs, (_decodedInput, ind2, callback2) => {
function checkLoop() {
index2++;
if (index2 === decodedTx.inputs.length) {
shepherd.log(`tx history decode inputs ${decodedTx.inputs.length} | ${index2} => main callback`, true);
const _parsedTx = {
network: decodedTx.network,
format: decodedTx.format,
inputs: txInputs,
outputs: decodedTx.outputs,
height: transaction.height,
timestamp: Number(transaction.height) === 0 ? Math.floor(Date.now() / 1000) : blockInfo.timestamp,
confirmations: Number(transaction.height) === 0 ? 0 : 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]);
}
index++;
if (index === json.length) {
ecl.close();
const successObj = {
msg: 'success',
result: _rawtx,
};
res.end(JSON.stringify(successObj));
}
callback();
shepherd.log(`tx history main loop ${json.length} | ${index}`, true);
}
});
}))
.then(promiseResult => {
const _parsedTx = {
network: decodedTx.network,
format: decodedTx.format,
inputs: txInputs,
outputs: decodedTx.outputs,
height: transaction.height,
timestamp: Number(transaction.height) === 0 ? Math.floor(Date.now() / 1000) : blockInfo.timestamp,
confirmations: Number(transaction.height) === 0 ? 0 : currentHeight - transaction.height,
};
callback2();
}
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);
if (_decodedInput.txid !== '0000000000000000000000000000000000000000000000000000000000000000') {
shepherd.getTransaction(_decodedInput.txid, network, ecl)
.then((rawInput) => {
const decodedVinVout = shepherd.electrumJSTxDecoder(rawInput, network, _network);
if (decodedVinVout) {
shepherd.log(decodedVinVout.outputs[_decodedInput.n], true);
txInputs.push(decodedVinVout.outputs[_decodedInput.n]);
}
checkLoop();
});
} 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]);
checkLoop();
}
resolve(true);
});
} else {
const _parsedTx = {
@ -155,7 +225,20 @@ module.exports = (shepherd) => {
const formattedTx = shepherd.parseTransactionAddresses(_parsedTx, req.query.address, network);
_rawtx.push(formattedTx);
resolve(true);
index++;
if (index === json.length) {
ecl.close();
const successObj = {
msg: 'success',
result: _rawtx,
};
res.end(JSON.stringify(successObj));
} else {
callback();
}
}
});
} else {
@ -170,67 +253,89 @@ module.exports = (shepherd) => {
};
const formattedTx = shepherd.parseTransactionAddresses(_parsedTx, req.query.address, network);
_rawtx.push(formattedTx);
resolve(true);
index++;
if (index === json.length) {
ecl.close();
const successObj = {
msg: 'success',
result: _rawtx,
};
res.end(JSON.stringify(successObj));
} else {
callback();
}
}
});
});
}))
.then(promiseResult => {
} else {
ecl.close();
const successObj = {
msg: 'success',
result: _rawtx,
result: [],
};
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',
};
}
});
} else {
const successObj = {
msg: 'error',
result: 'cant get current height',
};
res.end(JSON.stringify(successObj));
}
});
}
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(successObj));
}
});
res.end(JSON.stringify(errorObj));
}
});
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
if (shepherd.checkToken(req.query.token)) {
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);
shepherd.log('electrum gettransaction =>', true);
ecl.connect();
ecl.blockchainTransactionGet(req.query.txid)
.then((json) => {
ecl.close();
shepherd.log(json, true);
ecl.connect();
ecl.blockchainTransactionGet(req.query.txid)
.then((json) => {
ecl.close();
shepherd.log(json, true);
const successObj = {
msg: 'success',
result: json,
};
const successObj = {
msg: 'success',
result: json,
res.end(JSON.stringify(successObj));
});
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(successObj));
});
res.end(JSON.stringify(errorObj));
}
});
shepherd.parseTransactionAddresses = (tx, targetAddress, network) => {
shepherd.parseTransactionAddresses = (tx, targetAddress, network, skipTargetAddress) => {
// TODO: - sum vins / sum vouts to the same address
// - multi vin multi vout
// - detect change address
// - double check for exact sum input/output values
let result = [];
let _parse = {
inputs: {},
@ -244,6 +349,10 @@ module.exports = (shepherd) => {
inputs: 0,
outputs: 0,
};
let _addresses = {
inputs: [],
outputs: [],
};
shepherd.log('parseTransactionAddresses result ==>', true);
@ -273,39 +382,99 @@ module.exports = (shepherd) => {
_total[key] += Number(_parse[key][i].value);
// ignore op return outputs
if (_parse[key][i].scriptPubKey &&
_parse[key][i].scriptPubKey.addresses &&
_parse[key][i].scriptPubKey.addresses[0] &&
_parse[key][i].scriptPubKey.addresses[0] === targetAddress &&
_parse[key][i].value) {
_sum[key] += Number(_parse[key][i].value);
}
if (_parse[key][i].scriptPubKey &&
_parse[key][i].scriptPubKey.addresses &&
_parse[key][i].scriptPubKey.addresses[0]) {
_addresses[key].push(_parse[key][i].scriptPubKey.addresses[0]);
if (_parse[key][i].scriptPubKey.addresses[0] === targetAddress &&
skipTargetAddress) {
_addresses[key].pop();
}
}
}
}
_addresses.inputs = [ ...new Set(_addresses.inputs) ];
_addresses.outputs = [ ...new Set(_addresses.outputs) ];
shepherd.log('addresses in =>', true);
shepherd.log(_addresses.inputs, true);
shepherd.log('addresses out =>', true);
shepherd.log(_addresses.outputs, true);
let isSelfSend = {
inputs: false,
outputs: false,
};
for (let key in _parse) {
for (let i = 0; i < _addresses[key].length; i++) {
if (_addresses[key][i] === targetAddress &&
_addresses[key].length === 1) {
isSelfSend[key] = true;
}
}
}
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;
// send to self
if (isSelfSend.inputs && isSelfSend.outputs) {
result = {
type: 'self',
amount: Number(_sum.inputs - _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));
if (vinVoutDiff < 0) {
result.interest = Number(vinVoutDiff.toFixed(8));
}
}
} else {
result = [{ // reorder since tx sort by default is from newest to oldest
type: 'sent',
amount: Number(_sum.inputs.toFixed(8)),
address: _addresses.outputs[0],
timestamp: tx.timestamp,
txid: tx.format.txid,
confirmations: tx.confirmations,
from: _addresses.inputs,
to: _addresses.outputs,
}, {
type: 'received',
amount: Number(_sum.outputs.toFixed(8)),
address: targetAddress,
timestamp: tx.timestamp,
txid: tx.format.txid,
confirmations: tx.confirmations,
from: _addresses.inputs,
to: _addresses.outputs,
}];
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) {
@ -316,22 +485,26 @@ module.exports = (shepherd) => {
timestamp: tx.timestamp,
txid: tx.format.txid,
confirmations: tx.confirmations,
from: _addresses.inputs,
to: _addresses.outputs,
};
} else if (_sum.inputs > 0 && _sum.outputs === 0) {
result = {
type: 'sent',
amount: Number(_sum.inputs.toFixed(8)),
address: targetAddress,
address: isSelfSend.inputs && isSelfSend.outputs ? targetAddress : _addresses.outputs[0],
timestamp: tx.timestamp,
txid: tx.format.txid,
confirmations: tx.confirmations,
from: _addresses.inputs,
to: _addresses.outputs,
};
} else {
// (?)
result = {
type: 'other',
amount: 'unknown',
address: targetAddress,
address: 'unknown',
timestamp: tx.timestamp,
txid: tx.format.txid,
confirmations: tx.confirmations,
@ -345,51 +518,63 @@ module.exports = (shepherd) => {
}
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
if (shepherd.checkToken(req.query.token)) {
const _network = shepherd.getNetworkData(req.query.network);
const _rawtx = req.query.rawtx;
//const _rawtx = '010000006f2c395a02d81487fc7f9d1be3ea900316730133c044af70cd76d21e988e71de0e9e85918f010000006a47304402202097acd391e1d0eaaf91844bd596e918fb71320e3e0c51554acb71a39e4ee98b0220548fd61d4ae77a08d70b01bf5340983a1ba63f6b71ad71d478af77011f96fd510121031ffc010d8abc4180b4c1a13962bf9153a78082e7f2ac18f7d14cb6a6634ca218feffffff2b31f6c9a7916f7cf128cae94b3fc10e4c74ca3a740e1a7a6fd6624e4e9a5c8b010000006a473044022063f014c5fbaa7614732e0ae486179a854215fc32c02230e13f69b7e81fa000e50220236a2ba6373b1854aafc59c5391ab7505062067f3d293c016cbb5d252b35a56a012102f307f17d282fc0eabf99227c2e0f3122ae9ecd7da0de099f0c6007d4c941b57bfeffffff021b797ad7120000001976a914c7a7142d743b3e6eebe76923f43bae477d3ce31a88acff086d66000000001976a91463800ff36b9c52b2ffe5564af1c2a38df4f0126788ac16381d00';
const decodedTx = shepherd.electrumJSTxDecoder(_rawtx, req.query.network, _network);
ecl.connect();
ecl.blockchainTransactionGet(decodedTx.inputs[0].txid)
.then((json) => {
ecl.close();
shepherd.log(json, true);
const decodedVin = shepherd.electrumJSTxDecoder(json, _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: decodedVin.outputs[decodedTx.inputs[0].n],
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
shepherd.log(decodedTx.inputs[0]);
shepherd.log(decodedTx.inputs[0].txid);
ecl.connect();
ecl.blockchainTransactionGet(decodedTx.inputs[0].txid)
.then((json) => {
ecl.close();
shepherd.log(json, true);
const decodedVin = shepherd.electrumJSTxDecoder(json, req.query.network, _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));
});
}
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(errorObj));
}
});

59
routes/shepherd/kickstart.js

@ -7,37 +7,46 @@ module.exports = (shepherd) => {
* params: coin, type
*/
shepherd.get('/kick', (req, res, next) => {
const _coin = req.query.coin;
const _keepWallet = req.query.keepwallet;
if (shepherd.checkToken(req.query.token)) {
const _coin = req.query.coin;
const _keepWallet = req.query.keepwallet;
if (!_coin) {
const errorObj = {
msg: 'error',
result: 'no coin name provided',
};
res.end(JSON.stringify(errorObj));
} else {
const _location = path.join(_coin === 'KMD' ? shepherd.komodoDir : `${shepherd.komodoDir}/${_coin}`);
if (fs.existsSync(_location)) {
const items = fs.readdirSync(_location);
for (let i = 0; i < items.length; i++) {
if (items[i].indexOf('wallet.dat') === -1) {
fs.removeSync(`${_location}/${items[i]}`);
} else if (!_keepWallet) {
fs.removeSync(`${_location}/${items[i]}`);
}
}
}
if (!_coin) {
const successObj = {
msg: 'success',
result: `${_coin} native is kicked`,
};
res.end(JSON.stringify(successObj));
}
} else {
const errorObj = {
msg: 'error',
result: 'no coin name provided',
result: 'unauthorized access',
};
res.end(JSON.stringify(errorObj));
} else {
const _location = path.join(_coin === 'KMD' ? shepherd.komodoDir : `${shepherd.komodoDir}/${_coin}`);
if (fs.existsSync(_location)) {
const items = fs.readdirSync(_location);
for (let i = 0; i < items.length; i++) {
if (items[i].indexOf('wallet.dat') === -1) {
fs.removeSync(`${_location}/${items[i]}`);
} else if (!_keepWallet) {
fs.removeSync(`${_location}/${items[i]}`);
}
}
}
const successObj = {
msg: 'success',
result: `${_coin} native is kicked`,
};
res.end(JSON.stringify(successObj));
}
});

127
routes/shepherd/log.js

@ -40,12 +40,21 @@ module.exports = (shepherd) => {
}
shepherd.get('/log/runtime', (req, res, next) => {
const successObj = {
msg: 'success',
result: req.query.spv && req.query.spv === 'true' ? shepherd.appRuntimeSPVLog : shepherd.appRuntimeLog,
};
if (shepherd.checkToken(req.query.token)) {
const successObj = {
msg: 'success',
result: req.query.spv && req.query.spv === 'true' ? shepherd.appRuntimeSPVLog : shepherd.appRuntimeLog,
};
res.end(JSON.stringify(successObj));
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(successObj));
res.end(JSON.stringify(errorObj));
}
});
shepherd.getAppRuntimeLog = () => {
@ -59,37 +68,46 @@ module.exports = (shepherd) => {
* params: payload
*/
shepherd.post('/guilog', (req, res, next) => {
const logLocation = `${shepherd.agamaDir}/shepherd`;
if (shepherd.checkToken(req.body.token)) {
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,
};
}
if (!shepherd.guiLog[shepherd.appSessionHash]) {
shepherd.guiLog[shepherd.appSessionHash] = {};
}
shepherd.fs.writeFile(`${logLocation}/agamalog.json`, JSON.stringify(shepherd.guiLog), (err) => {
if (err) {
shepherd.writeLog('error writing gui log file');
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,
};
}
const returnObj = {
msg: 'success',
result: 'gui log entry is added',
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));
});
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(returnObj));
});
res.end(JSON.stringify(errorObj));
}
});
/*
@ -97,30 +115,39 @@ module.exports = (shepherd) => {
* params: type
*/
shepherd.get('/getlog', (req, res, next) => {
const logExt = req.query.type === 'txt' ? 'txt' : 'json';
if (shepherd.checkToken(req.query.token)) {
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));
}
});
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));
}
} else {
const errorObj = {
msg: 'error',
result: `agama.${logExt} doesnt exist`,
result: 'unauthorized access',
};
res.end(JSON.stringify(errorObj));

209
routes/shepherd/pin.js

@ -7,93 +7,111 @@ module.exports = (shepherd) => {
* params: none
*/
shepherd.post('/encryptkey', (req, res, next) => {
if (req.body.key &&
req.body.string &&
req.body.pubkey) {
const encryptedString = 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}$');
fs.writeFile(`${shepherd.agamaDir}/shepherd/pin/${req.body.pubkey}.pin`, encryptedString, (err) => {
if (err) {
shepherd.log('error writing pin file');
}
if (shepherd.checkToken(req.body.token)) {
if (req.body.key &&
req.body.string &&
req.body.pubkey) {
const encryptedString = 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}$');
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,
};
const returnObj = {
msg: 'success',
result: encryptedString,
res.end(JSON.stringify(returnObj));
});
} else {
const _paramsList = [
'key',
'string',
'pubkey'
];
let errorObj = {
msg: 'error',
result: '',
};
let _errorParamsList = [];
for (let i = 0; i < _paramsList.length; i++) {
if (!req.query[_paramsList[i]]) {
_errorParamsList.push(_paramsList[i]);
}
}
res.end(JSON.stringify(returnObj));
});
errorObj.result = `missing param ${_errorParamsList.join(', ')}`;
res.end(JSON.stringify(errorObj));
}
} else {
let errorObj = {
const errorObj = {
msg: 'error',
result: '',
result: 'unauthorized access',
};
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 (fs.existsSync(`${shepherd.agamaDir}/shepherd/pin/${req.body.pubkey}.pin`)) {
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 = 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 = {
if (shepherd.checkToken(req.body.token)) {
if (req.body.key &&
req.body.pubkey) {
if (fs.existsSync(`${shepherd.agamaDir}/shepherd/pin/${req.body.pubkey}.pin`)) {
fs.readFile(`${shepherd.agamaDir}/shepherd/pin/${req.body.pubkey}.pin`, 'utf8', (err, data) => {
if (err) {
const errorObj = {
msg: 'error',
result: 'wrong key',
result: err,
};
res.end(JSON.stringify(errorObj));
} else {
returnObj = {
msg: 'success',
result: encryptedKey,
};
const encryptedKey = 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(returnObj));
}
});
res.end(JSON.stringify(errorObj));
}
} else {
const errorObj = {
msg: 'error',
result: `file ${req.query.pubkey}.pin doesnt exist`,
result: 'missing key or pubkey param',
};
res.end(JSON.stringify(errorObj));
@ -101,7 +119,7 @@ module.exports = (shepherd) => {
} else {
const errorObj = {
msg: 'error',
result: 'missing key or pubkey param',
result: 'unauthorized access',
};
res.end(JSON.stringify(errorObj));
@ -109,36 +127,45 @@ module.exports = (shepherd) => {
});
shepherd.get('/getpinlist', (req, res, next) => {
if (fs.existsSync(`${shepherd.agamaDir}/shepherd/pin`)) {
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 (shepherd.checkToken(req.body.token)) {
if (fs.existsSync(`${shepherd.agamaDir}/shepherd/pin`)) {
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',
};
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(errorObj));
} else {
const successObj = {
msg: 'success',
result: _pins,
};
res.end(JSON.stringify(successObj));
}
});
res.end(JSON.stringify(successObj));
}
});
} else {
const errorObj = {
msg: 'error',
result: 'pin folder doesnt exist',
};
res.end(JSON.stringify(errorObj));
}
} else {
const errorObj = {
msg: 'error',
result: 'pin folder doesnt exist',
result: 'unauthorized access',
};
res.end(JSON.stringify(errorObj));

132
routes/shepherd/quitDaemon.js

@ -82,41 +82,32 @@ module.exports = (shepherd) => {
}
shepherd.post('/coind/stop', (req, res) => {
const _chain = req.body.chain;
let _coindQuitCmd = shepherd.komodocliBin;
let _arg = [];
if (shepherd.checkToken(req.body.token)) {
const _chain = req.body.chain;
let _coindQuitCmd = shepherd.komodocliBin;
let _arg = [];
if (_chain) {
_arg.push(`-ac_name=${_chain}`);
if (_chain) {
_arg.push(`-ac_name=${_chain}`);
if (shepherd.appConfig.dataDir.length) {
_arg.push(`-datadir=${shepherd.appConfig.dataDir + (_chain ? '/' + _chain : '')}`);
if (shepherd.appConfig.dataDir.length) {
_arg.push(`-datadir=${shepherd.appConfig.dataDir + (_chain ? '/' + _chain : '')}`);
}
} else if (!_chain && shepherd.appConfig.dataDir.length) {
_arg.push(`-datadir=${shepherd.appConfig.dataDir}`);
}
} else if (!_chain && shepherd.appConfig.dataDir.length) {
_arg.push(`-datadir=${shepherd.appConfig.dataDir}`);
}
_arg.push('stop');
execFile(`${_coindQuitCmd}`, _arg, (error, stdout, stderr) => {
shepherd.log(`stdout: ${stdout}`);
shepherd.log(`stderr: ${stderr}`);
shepherd.log(`send stop sig to ${_chain ? _chain : 'komodo'}`);
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[_chain ? _chain : 'komodod'];
const obj = {
msg: 'success',
result: 'result',
};
res.end(JSON.stringify(obj));
} else {
if (stdout.indexOf('Komodo server stopping') > -1) {
_arg.push('stop');
execFile(`${_coindQuitCmd}`, _arg, (error, stdout, stderr) => {
shepherd.log(`stdout: ${stdout}`);
shepherd.log(`stderr: ${stderr}`);
shepherd.log(`send stop sig to ${_chain ? _chain : 'komodo'}`);
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[_chain ? _chain : 'komodod'];
const obj = {
@ -126,43 +117,70 @@ module.exports = (shepherd) => {
res.end(JSON.stringify(obj));
} else {
const obj = {
msg: 'error',
result: 'result',
};
res.end(JSON.stringify(obj));
if (stdout.indexOf('Komodo server stopping') > -1) {
delete shepherd.coindInstanceRegistry[_chain ? _chain : 'komodod'];
const obj = {
msg: 'success',
result: 'result',
};
res.end(JSON.stringify(obj));
} else {
const obj = {
msg: 'error',
result: 'result',
};
res.end(JSON.stringify(obj));
}
}
}
});
});
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(errorObj));
}
});
shepherd.post('/coins/remove', (req, res) => {
const _chain = req.body.chain;
if (shepherd.checkToken(req.body.token)) {
const _chain = req.body.chain;
if (req.body.mode === 'native') {
delete shepherd.coindInstanceRegistry[_chain ? _chain : 'komodod'];
if (req.body.mode === 'native') {
delete shepherd.coindInstanceRegistry[_chain ? _chain : 'komodod'];
const obj = {
msg: 'success',
result: 'result',
};
const obj = {
msg: 'success',
result: 'result',
};
res.end(JSON.stringify(obj));
} else {
delete shepherd.electrumCoins[_chain === 'komodo' ? 'KMD' : _chain];
res.end(JSON.stringify(obj));
} else {
delete shepherd.electrumCoins[_chain === 'komodo' ? 'KMD' : _chain];
if (Object.keys(shepherd.electrumCoins).length - 1 === 0) {
shepherd.electrumCoins.auth = false;
shepherd.electrumKeys = {};
}
if (Object.keys(shepherd.electrumCoins).length - 1 === 0) {
shepherd.electrumCoins.auth = false;
shepherd.electrumKeys = {};
}
const obj = {
msg: 'success',
result: 'result',
};
const obj = {
msg: 'success',
result: 'result',
res.end(JSON.stringify(obj));
}
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(obj));
res.end(JSON.stringify(errorObj));
}
});

373
routes/shepherd/rpc.js

@ -60,160 +60,279 @@ module.exports = (shepherd) => {
* params: payload
*/
shepherd.post('/cli', (req, res, next) => {
if (!req.body.payload) {
const errorObj = {
msg: 'error',
result: 'no payload provided',
};
if (shepherd.checkToken(req.body.payload.token)) {
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 _params = req.body.payload.params ? ` ${req.body.payload.params}` : '';
let _cmd = req.body.payload.cmd;
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',
};
if (!shepherd.rpcConf[_chain]) {
shepherd.getConf(req.body.payload.chain === 'KMD' || !req.body.payload.chain && shepherd.kmdMainPassiveMode ? 'komodod' : req.body.payload.chain);
}
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;
const _params = req.body.payload.params ? ` ${req.body.payload.params}` : '';
let _cmd = req.body.payload.cmd;
if (_mode === 'default') {
if (req.body.payload.rpc2cli) {
let _coindCliBin = shepherd.komodocliBin;
if (!shepherd.rpcConf[_chain]) {
shepherd.getConf(req.body.payload.chain === 'KMD' || !req.body.payload.chain && shepherd.kmdMainPassiveMode ? 'komodod' : req.body.payload.chain);
}
if (shepherd.nativeCoindList &&
_chain &&
shepherd.nativeCoindList[_chain.toLowerCase()]) {
_coindCliBin = `${shepherd.coindRootDir}/${_chain.toLowerCase()}/${shepherd.nativeCoindList[_chain.toLowerCase()].bin.toLowerCase()}-cli`;
}
if (_params.indexOf('*')) {
_params = _params.replace('*', '"*"');
}
if (_params.indexOf(',') > -1) {
_params = _params.split(',');
}
if (_cmd.indexOf('getaddressesbyaccount') > -1) {
_cmd = 'getaddressesbyaccount ""';
}
if (_mode === 'default') {
if (_cmd === 'debug' &&
_chain !== 'CHIPS') {
if (shepherd.nativeCoindList[_chain.toLowerCase()]) {
const _osHome = os.platform === 'win32' ? process.env.APPDATA : process.env.HOME;
let coindDebugLogLocation;
let _arg = (_chain ? ' -ac_name=' + _chain : '') + ' ' + _cmd + (typeof _params === 'object' ? _params.join(' ') : _params);
if (_chain === 'CHIPS') {
coindDebugLogLocation = `${shepherd.chipsDir}/debug.log`;
} else {
coindDebugLogLocation = `${_osHome}/.${shepherd.nativeCoindList[_chain.toLowerCase()].bin.toLowerCase()}/debug.log`;
if (shepherd.appConfig.dataDir.length) {
_arg = `${_arg} -datadir=${shepherd.appConfig.dataDir + (_chain ? '/' + key : '')}`;
}
shepherd.readDebugLog(coindDebugLogLocation, 1)
.then((result) => {
const _obj = {
msg: 'success',
result: result,
};
shepherd.exec(`"${_coindCliBin}" ${_arg}`, (error, stdout, stderr) => {
//shepherd.log(`stdout: ${stdout}`);
//shepherd.log(`stderr: ${stderr}`);
// shepherd.log('bitcoinrpc debug ====>');
// console.log(result);
if (error !== null) {
shepherd.log(`exec error: ${error}`);
}
res.end(JSON.stringify(_obj));
}, (result) => {
const _obj = {
error: result,
result: 'error',
};
let responseObj;
if (stderr) {
let _res;
let _error;
if (_chain !== 'komodod' &&
stderr.indexOf(`error creating`) > -1) {
shepherd.log(`replace error creating (gen${_chain})`);
stderr = stderr.replace(`error creating (gen${_chain})`, '');
shepherd.log(stderr);
}
if ((stderr.indexOf('{') > -1 && stderr.indexOf('}') > -1) ||
(stderr.indexOf('[') > -1 && stderr.indexOf(']') > -1)) {
_res = JSON.parse(stderr);
} else {
_res = stderr.trim();
}
if (stderr.indexOf('error code:') > -1) {
_error = {
code: Number(stderr.substring(stderr.indexOf('error code:') + 11, stderr.indexOf('error message:') - stderr.indexOf('error code:')).trim()),
message: stderr.substring(stderr.indexOf('error message:') + 15, stderr.length).trim(),
};
}
if (_error) {
responseObj = {
error: _error,
};
} else {
responseObj = {
result: _res,
};
}
} else {
let _res;
let _error;
if (_chain !== 'komodod' &&
stdout.indexOf(`error creating`) > -1) {
shepherd.log(`replace error creating (gen${_chain})`);
stdout = stdout.replace(`error creating (gen${_chain})`, '');
shepherd.log(stdout);
}
if ((stdout.indexOf('{') > -1 && stdout.indexOf('}') > -1) ||
(stdout.indexOf('[') > -1 && stdout.indexOf(']') > -1)) {
_res = JSON.parse(stdout);
} else {
_res = stdout.trim();
}
if (stdout.indexOf('error code:') > -1) {
_error = {
code: Number(stdout.substring(stdout.indexOf('error code:') + 11, stdout.indexOf('error message:') - stdout.indexOf('error code:')).trim()),
message: stdout.substring(stdout.indexOf('error message:') + 15, stdout.length).trim(),
};
}
if (_error) {
responseObj = {
error: _error,
};
} else {
responseObj = {
result: _res,
};
}
}
res.end(JSON.stringify(_obj));
res.end(JSON.stringify(responseObj));
// shepherd.killRogueProcess('komodo-cli');
});
} else {
res.end({
error: 'bitcoinrpc debug error',
result: 'error',
});
// console.log('bitcoinrpc debug error');
}
} else {
if (_chain === 'CHIPS' &&
_cmd === 'debug') {
_cmd = 'getblockchaininfo';
}
if (_cmd === 'debug' &&
_chain !== 'CHIPS') {
if (shepherd.nativeCoindList[_chain.toLowerCase()]) {
const _osHome = 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,
};
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.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 ? body : JSON.stringify({
result: 'error',
error: {
code: -777,
message: `unable to call method ${_cmd} at port ${shepherd.rpcConf[req.body.payload.chain].port}`,
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 ? body : JSON.stringify({
result: 'error',
error: {
code: -777,
message: `unable to call method ${_cmd} at port ${shepherd.rpcConf[req.body.payload.chain].port}`,
},
}));
}
});
}
});
}
}
}
} else {
let _coindCliBin = shepherd.komodocliBin;
} 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`;
}
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;
let _arg = (_chain ? ' -ac_name=' + _chain : '') + ' ' + _cmd + _params;
if (shepherd.appConfig.dataDir.length) {
_arg = `${_arg} -datadir=${shepherd.appConfig.dataDir + (_chain ? '/' + key : '')}`;
}
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}`);
_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}`);
}
if (error !== null) {
shepherd.log(`exec error: ${error}`);
}
let responseObj;
let responseObj;
if (stderr) {
responseObj = {
msg: 'error',
result: stderr,
};
} else {
responseObj = {
msg: 'success',
result: stdout,
};
}
if (stderr) {
responseObj = {
msg: 'error',
result: stderr,
};
} else {
responseObj = {
msg: 'success',
result: stdout,
};
}
res.end(JSON.stringify(responseObj));
shepherd.killRogueProcess('komodo-cli');
});
res.end(JSON.stringify(responseObj));
shepherd.killRogueProcess('komodo-cli');
});
}
}
} else {
const errorObj = {
msg: 'error',
result: 'unauthorized access',
};
res.end(JSON.stringify(errorObj));
}
});

6
version

@ -1,3 +1,3 @@
version=0.2.0.25e
type=e-beta
minversion=0.2.0.2
version=0.2.0.29c
type=c-beta
minversion=0.2.0.29

2
version_build

@ -1 +1 @@
0.2.0.25e-beta
0.2.0.29c-beta
Loading…
Cancel
Save