mirror of https://github.com/lukechilds/Agama.git
pbca26
7 years ago
committed by
GitHub
94 changed files with 7477 additions and 5096 deletions
@ -0,0 +1,12 @@ |
|||
# Agama application build summary is here: |
|||
|
|||
Platform: windows |
|||
|
|||
Version: 0.2.0.1a-beta |
|||
|
|||
Date: Mon Jun 12 11:57:21 CEST 2017 |
|||
|
|||
|
|||
Download link: |
|||
* [https://f001.backblazeb2.com/file/supernet/files/AgamaApp-0.2.0.1a-beta-windows-installer.zip](https://f001.backblazeb2.com/file/supernet/files/AgamaApp-0.2.0.1a-beta-windows-installer.zip) |
|||
* [checksum](https://f001.backblazeb2.com/file/supernet/files/AgamaApp-0.2.0.1a-beta-windows.checksum) |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,19 +0,0 @@ |
|||
mkdir %AppData%\Komodo |
|||
|
|||
@echo off |
|||
|
|||
IF NOT EXIST %AppData%\Komodo\komodo.conf ( |
|||
( |
|||
echo rpcuser=kmdusr%random%%random% |
|||
echo rpcpassword=kmdpass%random%%random% |
|||
echo rpcbind=127.0.0.1 |
|||
echo txindex=1 |
|||
echo server=1 |
|||
echo addnode=5.9.102.210 |
|||
echo addnode=78.47.196.146 |
|||
echo addnode=178.63.69.164 |
|||
echo addnode=88.198.65.74 |
|||
echo addnode=5.9.122.241 |
|||
echo addnode=144.76.94.38 |
|||
) > %AppData%\Komodo\komodo.conf |
|||
) |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,58 +1,54 @@ |
|||
echo Refreshing binaries from artifacts.supernet.org |
|||
echo ========================================= |
|||
echo Step: Removing old binaries |
|||
mkdir -p build |
|||
cd build |
|||
rm -rvf artifacts.supernet.org |
|||
pwd |
|||
[ ! -d assets ] && \ |
|||
mkdir -p assets |
|||
cd assets |
|||
[ -d artifacts.supernet.org ] && \ |
|||
echo Removing old artifacts. && \ |
|||
rm -rvf artifacts.supernet.org |
|||
echo |
|||
echo Step: Cloning latest binaries for build |
|||
wget --recursive --no-parent https://artifacts.supernet.org/latest/ |
|||
chmod -R +x artifacts.supernet.org/latest/ |
|||
cd .. |
|||
echo ========================================= |
|||
echo |
|||
echo ========================================= |
|||
echo Step: Moving osx binaries from artifacts to assets/bin/osx/ |
|||
echo |
|||
mv -fv build/artifacts.supernet.org/latest/osx/iguana assets/bin/osx/ |
|||
mv -fv build/artifacts.supernet.org/latest/osx/komodo-cli assets/bin/osx/ |
|||
mv -fv build/artifacts.supernet.org/latest/osx/komodod assets/bin/osx/ |
|||
mv -fv build/artifacts.supernet.org/latest/osx/libgcc_s.1.dylib assets/bin/osx/ |
|||
mv -fv build/artifacts.supernet.org/latest/osx/libgomp.1.dylib assets/bin/osx/ |
|||
mv -fv build/artifacts.supernet.org/latest/osx/libnanomsg.5.0.0.dylib assets/bin/osx/ |
|||
mv -fv build/artifacts.supernet.org/latest/osx/libstdc++.6.dylib assets/bin/osx/ |
|||
echo |
|||
echo |
|||
pwd |
|||
echo ========================================= |
|||
echo Step: Moving Win64 binaries from artifacts to assets/bin/win64/ |
|||
echo |
|||
mv -fv build/artifacts.supernet.org/latest/windows/genkmdconf.bat assets/bin/win64/ |
|||
mv -fv build/artifacts.supernet.org/latest/windows/iguana.exe assets/bin/win64/ |
|||
mv -fv build/artifacts.supernet.org/latest/windows/index.html assets/bin/win64/ |
|||
mv -fv build/artifacts.supernet.org/latest/windows/komodo-cli.exe assets/bin/win64/ |
|||
mv -fv build/artifacts.supernet.org/latest/windows/komodo-tx.exe assets/bin/win64/ |
|||
mv -fv build/artifacts.supernet.org/latest/windows/komodod.exe assets/bin/win64/ |
|||
mv -fv build/artifacts.supernet.org/latest/windows/libcrypto-1_1.dll assets/bin/win64/ |
|||
mv -fv build/artifacts.supernet.org/latest/windows/libcurl-4.dll assets/bin/win64/ |
|||
mv -fv build/artifacts.supernet.org/latest/windows/libcurl.dll assets/bin/win64/ |
|||
mv -fv build/artifacts.supernet.org/latest/windows/libgcc_s_sjlj-1.dll assets/bin/win64/ |
|||
mv -fv build/artifacts.supernet.org/latest/windows/libnanomsg.dll assets/bin/win64/ |
|||
mv -fv build/artifacts.supernet.org/latest/windows/libssl-1_1.dll assets/bin/win64/ |
|||
mv -fv build/artifacts.supernet.org/latest/windows/libwinpthread-1.dll assets/bin/win64/ |
|||
mv -fv build/artifacts.supernet.org/latest/windows/nanomsg.dll assets/bin/win64/ |
|||
mv -fv build/artifacts.supernet.org/latest/windows/pthreadvc2.dll assets/bin/win64/ |
|||
echo |
|||
echo Step: Permission +x for OSX binaries from artifacts to assets/bin/osx/ |
|||
echo |
|||
rm assets/artifacts.supernet.org/latest/osx/iguana |
|||
chmod +x assets/artifacts.supernet.org/latest/osx/komodo* |
|||
|
|||
mkdir assets/bin |
|||
mv assets/artifacts.supernet.org/latest/osx assets/bin/osx |
|||
|
|||
echo Moving legacy libs to assets/bin |
|||
wget https://supernetorg.bintray.com/misc/libs_legacy_osx.zip |
|||
checksum=`shasum -a 256 libs_legacy_osx.zip | awk '{ print $1 }'` |
|||
if [ "$checksum" = "e9474aa243694a2d4c87fccc443e4b16a9a5172a24da76af9e5ecddd006649bb" ]; then |
|||
echo "Checksum is correct." |
|||
unzip libs_legacy_osx.zip |
|||
cp -rvf libs_legacy_osx/* assets/bin/osx/. |
|||
else |
|||
echo "Checksum is incorrect!" |
|||
exit 0 |
|||
fi |
|||
echo ========================================= |
|||
echo Step: Moving linux64 binaries from artifacts to assets/bin/linux64 |
|||
echo |
|||
mv -fv build/artifacts.supernet.org/latest/linux/iguana assets/bin/linux64/ |
|||
mv -fv build/artifacts.supernet.org/latest/linux/komodo-cli assets/bin/linux64/ |
|||
mv -fv build/artifacts.supernet.org/latest/linux/komodod assets/bin/linux64/ |
|||
echo |
|||
echo Step: Moving Windows binaries from artifacts to assets/bin/win64/ |
|||
#echo |
|||
rm assets/artifacts.supernet.org/latest/windows/iguana |
|||
mv assets/artifacts.supernet.org/latest/windows assets/bin/win64 |
|||
echo |
|||
echo ========================================= |
|||
echo Step: Cleaning artifacts data |
|||
echo |
|||
rm -rf build/ |
|||
echo |
|||
echo Step: Permissions +x for linux64 binaries from artifacts to assets/bin/linux64 |
|||
echo |
|||
rm assets/artifacts.supernet.org/latest/linux/iguana |
|||
chmod +x assets/artifacts.supernet.org/latest/linux/komodo* |
|||
echo Moving Linux bins to assets/bin |
|||
mv assets/artifacts.supernet.org/latest/linux assets/bin/linux64/ |
|||
echo |
|||
echo ========================================= |
|||
echo Step: Finished Updating binaries from artifacts |
|||
echo |
|||
echo |
@ -1,119 +0,0 @@ |
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> |
|||
<html> |
|||
<head> |
|||
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> |
|||
<link rel="stylesheet" href="EasyDEX-GUI/assets/global/css/bootstrap.min.css"> |
|||
<script>if (typeof module === 'object') {window.module = module; module = undefined;}</script> |
|||
<script type="text/javascript" src="EasyDEX-GUI/assets/global/vendor/jquery/jquery.min.js"></script> |
|||
<script>if (window.module) module = window.module;</script> |
|||
<script type="text/javascript"> |
|||
function resizeMainWindow() { |
|||
/* set default map height */ |
|||
var mapH = $(window).height(); |
|||
|
|||
$(".page-main").outerHeight(mapH); |
|||
} |
|||
function StartIguana() { |
|||
var ajax_data = {"herd":"iguana"}; |
|||
console.log(ajax_data); |
|||
$.ajax({ |
|||
type: 'POST', |
|||
data: JSON.stringify(ajax_data), |
|||
url: 'http://127.0.0.1:17777/shepherd/herd', |
|||
dataType: "xml/html/script/json", // expected format for response |
|||
contentType: "application/json", // send as JSON |
|||
success: function(data, textStatus, jqXHR) { |
|||
var AjaxOutputData = JSON.parse(data); |
|||
console.log('== ActiveHandle Data OutPut =='); |
|||
console.log(AjaxOutputData); |
|||
}, |
|||
error: function(xhr, textStatus, error) { |
|||
console.log(xhr.statusText); |
|||
if ( xhr.readyState == 0 ) { |
|||
} |
|||
console.log(textStatus); |
|||
console.log(error); |
|||
} |
|||
}); |
|||
} |
|||
function StartCorsproxy() { |
|||
var ajax_data = {"herd":"corsproxy"}; |
|||
console.log(ajax_data); |
|||
$.ajax({ |
|||
type: 'POST', |
|||
data: JSON.stringify(ajax_data), |
|||
url: 'http://127.0.0.1:17777/shepherd/herd', |
|||
dataType: "xml/html/script/json", // expected format for response |
|||
contentType: "application/json", // send as JSON |
|||
success: function(data, textStatus, jqXHR) { |
|||
var AjaxOutputData = JSON.parse(data); |
|||
console.log('== ActiveHandle Data OutPut =='); |
|||
console.log(AjaxOutputData); |
|||
}, |
|||
error: function(xhr, textStatus, error) { |
|||
console.log(xhr.statusText); |
|||
if ( xhr.readyState == 0 ) { |
|||
} |
|||
console.log(textStatus); |
|||
console.log(error); |
|||
} |
|||
}); |
|||
} |
|||
function StartKomodod() { |
|||
var ajax_data = {"herd":"komodod"}; |
|||
console.log(ajax_data); |
|||
$.ajax({ |
|||
type: 'POST', |
|||
data: JSON.stringify(ajax_data), |
|||
url: 'http://127.0.0.1:17777/shepherd/herd', |
|||
dataType: "xml/html/script/json", // expected format for response |
|||
contentType: "application/json", // send as JSON |
|||
success: function(data, textStatus, jqXHR) { |
|||
var AjaxOutputData = JSON.parse(data); |
|||
console.log('== ActiveHandle Data OutPut =='); |
|||
console.log(AjaxOutputData); |
|||
}, |
|||
error: function(xhr, textStatus, error) { |
|||
console.log(xhr.statusText); |
|||
if ( xhr.readyState == 0 ) { |
|||
} |
|||
console.log(textStatus); |
|||
console.log(error); |
|||
} |
|||
}); |
|||
} |
|||
function StartKMDNativeIGUI() { |
|||
var secToLaunch = 60; |
|||
$('#kmdNativeBtn').text('Starting Komodo in ' + secToLaunch + 's'); |
|||
StartCorsproxy(); |
|||
StartKomodod(); |
|||
setInterval(function() { |
|||
$('#kmdNativeBtn').text('Starting Komodo in ' + secToLaunch + 's'); |
|||
secToLaunch--; |
|||
}, 1000); |
|||
setTimeout(function() { |
|||
document.location = 'Iguana-GUI/index.html'; |
|||
}, secToLaunch * 1000); |
|||
} |
|||
jQuery(document).ready(function() { |
|||
resizeMainWindow(); |
|||
window.onresize = function(event) { resizeMainWindow(); }; |
|||
}); |
|||
</script> |
|||
</head> |
|||
<body> |
|||
<div class="page-main"> |
|||
<div class="col-xs-6 text-center" style="height: 100%; background: url(bg.jpg) no-repeat fixed; background-color: #c7c7c7; vertical-align: middle;" id="iguanaGuiStart"> |
|||
<h1 style="color: white;">Iguana Wallet<h1> |
|||
<a type="button" class="btn btn-default btn-lg" href="Iguana-GUI/index.html">Open Iguana Wallet</a><br/><br/> |
|||
<a type="button" class="btn btn-default btn-lg" href="#" onclick="StartCorsproxy()">Launch proxy server</a><br/><br/> |
|||
<a type="button" class="btn btn-default btn-lg" href="#" onclick="StartKMDNativeIGUI()" id="kmdNativeBtn">Komodo Native</a><br/><br/> |
|||
<a type="button" class="btn btn-default btn-lg" href="#" onclick="StartIguana()">Start Iguana Core</a> |
|||
</div> |
|||
<div class="col-xs-6 text-center" style="height: 100%; background: url(bg2.jpg) no-repeat fixed; background-color: #d8d8d8; vertical-align: middle;" id="edexGuiStart"> |
|||
<h1 style="color: white;">EasyDEX</h1> |
|||
<a type="button" class="btn btn-default btn-lg" href="EasyDEX-GUI/index.html">Open EasyDEX</a> |
|||
</div> |
|||
</div> |
|||
</body> |
|||
</html> |
@ -1,18 +0,0 @@ |
|||
<!DOCTYPE html> |
|||
<html> |
|||
<head> |
|||
<meta charset="UTF-8"> |
|||
<title>iguanaElectronApp</title> |
|||
</head> |
|||
<body> |
|||
<h3>some version information:</h3> |
|||
node.js <script>document.write(process.versions.node)</script>, |
|||
chromium <script>document.write(process.versions.chrome)</script>, |
|||
electron <script>document.write(process.versions.electron)</script>. |
|||
</body> |
|||
|
|||
<script> |
|||
//bind other code refs |
|||
//require('./xxx.js') |
|||
</script> |
|||
</html> |
@ -0,0 +1,26 @@ |
|||
// prequsites: https://www.npmjs.com/package/electron-installer-debian
|
|||
|
|||
var installer = require('electron-installer-debian'); |
|||
|
|||
var options = { |
|||
src: 'build/Agama-linux-x64/', |
|||
dest: 'build/', |
|||
arch: 'amd64', |
|||
icon: 'assets/icons/agama_icons/64x64.png', |
|||
name: 'agama-app', |
|||
bin: 'Agama', |
|||
categories: ['Office', 'Internet'], |
|||
homepage: 'http://supernet.org', |
|||
maintainer: 'SuperNET <dev@supernet.org>', |
|||
} |
|||
|
|||
console.log('Creating package (this may take a while)'); |
|||
|
|||
installer(options, function (err) { |
|||
if (err) { |
|||
console.error(err, err.stack); |
|||
process.exit(1); |
|||
} |
|||
|
|||
console.log('Successfully created package at ' + options.dest); |
|||
}); |
@ -0,0 +1,26 @@ |
|||
// prequsites: https://www.npmjs.com/package/electron-installer-redhat
|
|||
|
|||
var installer = require('electron-installer-redhat'); |
|||
|
|||
var options = { |
|||
src: 'build/Agama-linux-x64/', |
|||
dest: 'build/', |
|||
arch: 'x86_64', |
|||
icon: 'assets/icons/agama_icons/64x64.png', |
|||
name: 'agama-app', |
|||
bin: 'Agama', |
|||
categories: ['Office', 'Internet'], |
|||
homepage: 'http://supernet.org', |
|||
maintainer: 'SuperNET <dev@supernet.org>', |
|||
}; |
|||
|
|||
console.log('Creating package (this may take a while)'); |
|||
|
|||
installer(options, function (err) { |
|||
if (err) { |
|||
console.error(err, err.stack); |
|||
process.exit(1); |
|||
} |
|||
|
|||
console.log('Successfully created package at ' + options.dest); |
|||
}); |
@ -1,43 +1,52 @@ |
|||
{ |
|||
"name": "agama_app", |
|||
"name": "agama-app", |
|||
"productName": "Agama", |
|||
"version": "0.2.22", |
|||
"description": "Agama Desktop App", |
|||
"version": "0.2.24", |
|||
"description": "Agama Wallet Desktop App", |
|||
"main": "main.js", |
|||
"scripts": { |
|||
"start": "electron .", |
|||
"make-patch": "./make-patch.sh" |
|||
"make-patch": "./make-patch.sh", |
|||
"make-rpm": "node make-rpm.js", |
|||
"make-deb": "node make-deb.js" |
|||
}, |
|||
"repository": "https://github.com/SuperNETorg/Agama/", |
|||
"homepage": "http://supernet.org", |
|||
"keywords": [ |
|||
"iguana", |
|||
"agama", |
|||
"superNET", |
|||
"SuperNET", |
|||
"komodo", |
|||
"easydex", |
|||
"instantdex", |
|||
"pax" |
|||
], |
|||
"author": "ca333, grewalsatinder", |
|||
"author": "SuperNET", |
|||
"license": "MIT", |
|||
"devDependencies": { |
|||
"electron": "^1.4.1" |
|||
"electron": "1.6.5", |
|||
"electron-installer-debian": "^0.6.0", |
|||
"electron-installer-redhat": "^0.5.0" |
|||
}, |
|||
"dependencies": { |
|||
"adm-zip": "^0.4.7", |
|||
"bitcoinjs-lib": "^3.2.0", |
|||
"bluebird": "^3.4.7", |
|||
"body-parser": "^1.15.2", |
|||
"buffer-reverse": "^1.0.1", |
|||
"coinkey": "^2.0.0", |
|||
"coinselect": "github:bitcoinjs/coinselect", |
|||
"marketmaker": "git://github.com/pbca26/marketmaker", |
|||
"electron": "1.6.5", |
|||
"express": "^4.14.0", |
|||
"fix-path": "^2.1.0", |
|||
"fs-extra": "^1.0.0", |
|||
"fs-extra": "^4.0.2", |
|||
"graceful-fs": "^4.1.11", |
|||
"js-sha256": "^0.7.1", |
|||
"nodejs-aes256": "^1.0.1", |
|||
"pm2": "^2.4.3", |
|||
"portscanner": "^2.1.1", |
|||
"ps-node": "^0.1.5", |
|||
"remote-file-size": "^3.0.3", |
|||
"request": "^2.80.0", |
|||
"socket.io": "^1.7.3" |
|||
"sha256": "^0.2.0", |
|||
"socket.io": "^1.7.3", |
|||
"wif": "^2.0.6" |
|||
} |
|||
} |
|||
|
@ -1,962 +0,0 @@ |
|||
const fs = require('fs-extra'); |
|||
const request = require('request'); |
|||
const async = require('async'); |
|||
|
|||
var cache = {}; |
|||
var inMemCache; |
|||
var inMemPubkey; |
|||
|
|||
cache.setVar = function(variable, value) { |
|||
cache[variable] = value; |
|||
} |
|||
|
|||
/* |
|||
* cache data is dumped to disk before app quit or after cache.one call is finished |
|||
*/ |
|||
cache.dumpCacheBeforeExit = function() { |
|||
if (inMemCache) { |
|||
cache.shepherd.log('dumping cache before exit'); |
|||
fs.writeFileSync(`${cache.iguanaDir}/shepherd/cache-${inMemPubkey}.json`, JSON.stringify(inMemCache), 'utf8'); |
|||
} |
|||
} |
|||
|
|||
cache.get = function(req, res, next) { |
|||
const pubkey = req.query.pubkey; |
|||
|
|||
if (pubkey) { |
|||
inMemPubkey = pubkey; |
|||
|
|||
if (!inMemCache) { |
|||
cache.shepherd.log('serving cache from disk'); |
|||
|
|||
if (fs.existsSync(`${cache.iguanaDir}/shepherd/cache-${pubkey}.json`)) { |
|||
fs.readFile(`${cache.iguanaDir}/shepherd/cache-${pubkey}.json`, 'utf8', function(err, data) { |
|||
if (err) { |
|||
const errorObj = { |
|||
msg: 'error', |
|||
result: err, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(errorObj)); |
|||
} else { // deprecated
|
|||
try { |
|||
const parsedJSON = JSON.parse(data); |
|||
const successObj = { |
|||
msg: 'success', |
|||
result: parsedJSON, |
|||
}; |
|||
|
|||
inMemCache = parsedJSON; |
|||
res.end(JSON.stringify(successObj)); |
|||
} catch (e) { |
|||
cache.shepherd.log('JSON parse error while reading cache data from disk:'); |
|||
cache.shepherd.log(e); |
|||
if (e.toString().indexOf('at position') > -1) { |
|||
const errorPos = e.toString().split(' '); |
|||
|
|||
cache.shepherd.log(`JSON error ---> ${data.substring(errorPos[errorPos.length - 1] - 20, errorPos[errorPos.length - 1] + 20)} | error sequence: ${data.substring(errorPos[errorPos.length - 1], errorPos[errorPos.length - 1] + 1)}`); |
|||
cache.shepherd.log('attempting to recover JSON data'); |
|||
|
|||
fs.writeFile(`${cache.iguanaDir}/shepherd/cache-${pubkey}.json`, data.substring(0, errorPos[errorPos.length - 1]), function(err) { |
|||
const successObj = { |
|||
msg: 'success', |
|||
result: data.substring(0, errorPos[errorPos.length - 1]), |
|||
}; |
|||
|
|||
inMemCache = JSON.parse(data.substring(0, errorPos[errorPos.length - 1])); |
|||
res.end(JSON.stringify(successObj)); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
}); |
|||
} else { |
|||
const errorObj = { |
|||
msg: 'error', |
|||
result: `no file with handle ${pubkey}`, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(errorObj)); |
|||
} |
|||
} else { |
|||
const successObj = { |
|||
msg: 'success', |
|||
result: inMemCache, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
} |
|||
} else { |
|||
const errorObj = { |
|||
msg: 'error', |
|||
result: 'no pubkey provided', |
|||
}; |
|||
|
|||
res.end(JSON.stringify(errorObj)); |
|||
} |
|||
} |
|||
|
|||
cache.groomGet = function(req, res, next) { |
|||
const _filename = req.query.filename; |
|||
|
|||
if (_filename) { |
|||
if (fs.existsSync(`${cache.iguanaDir}/shepherd/cache-${_filename}.json`)) { |
|||
fs.readFile(`${cache.iguanaDir}/shepherd/cache-${_filename}.json`, 'utf8', function(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: `no file with name ${_filename}`, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(errorObj)); |
|||
} |
|||
} else { |
|||
const errorObj = { |
|||
msg: 'error', |
|||
result: 'no file name provided', |
|||
}; |
|||
|
|||
res.end(JSON.stringify(errorObj)); |
|||
} |
|||
} |
|||
|
|||
cache.groomDelete = function(req, res, next) { |
|||
const _filename = req.body.filename; |
|||
|
|||
if (_filename) { |
|||
if (fs.existsSync(`${cache.iguanaDir}/shepherd/cache-${_filename}.json`)) { |
|||
inMemCache = null; |
|||
|
|||
fs.unlink(`${cache.iguanaDir}/shepherd/cache-${_filename}.json`, function(err) { |
|||
if (err) { |
|||
const errorObj = { |
|||
msg: 'error', |
|||
result: err, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(errorObj)); |
|||
} else { |
|||
const successObj = { |
|||
msg: 'success', |
|||
result: 'deleted', |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
} |
|||
}); |
|||
} else { |
|||
const errorObj = { |
|||
msg: 'error', |
|||
result: `no file with name ${_filename}`, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(errorObj)); |
|||
} |
|||
} else { |
|||
const errorObj = { |
|||
msg: 'error', |
|||
result: 'no file name provided', |
|||
}; |
|||
|
|||
res.end(JSON.stringify(errorObj)); |
|||
} |
|||
} |
|||
|
|||
cache.groomPost = function(req, res) { |
|||
const _filename = req.body.filename; |
|||
const _payload = req.body.payload; |
|||
|
|||
if (!cacheCallInProgress) { |
|||
cacheCallInProgress = true; |
|||
|
|||
if (_filename) { |
|||
if (!_payload) { |
|||
const errorObj = { |
|||
msg: 'error', |
|||
result: 'no payload provided', |
|||
}; |
|||
|
|||
res.end(JSON.stringify(errorObj)); |
|||
} else { |
|||
inMemCache = JSON.parse(_payload); |
|||
cache.shepherd.log('appending groom post to in mem cache'); |
|||
cache.shepherd.log('appending groom post to on disk cache'); |
|||
|
|||
fs.writeFile(`${cache.iguanaDir}/shepherd/cache-${_filename}.json`, _payload, function(err) { |
|||
if (err) { |
|||
const errorObj = { |
|||
msg: 'error', |
|||
result: err, |
|||
}; |
|||
|
|||
cacheCallInProgress = false; |
|||
res.end(JSON.stringify(errorObj)); |
|||
} else { |
|||
const successObj = { |
|||
msg: 'success', |
|||
result: 'done', |
|||
}; |
|||
|
|||
cacheCallInProgress = false; |
|||
res.end(JSON.stringify(successObj)); |
|||
} |
|||
}); |
|||
} |
|||
} else { |
|||
const errorObj = { |
|||
msg: 'error', |
|||
result: 'no file name provided', |
|||
}; |
|||
|
|||
res.end(JSON.stringify(errorObj)); |
|||
} |
|||
} else { |
|||
const errorObj = { |
|||
msg: 'error', |
|||
result: 'another job is in progress', |
|||
}; |
|||
|
|||
res.end(JSON.stringify(errorObj)); |
|||
} |
|||
} |
|||
|
|||
var cacheCallInProgress = false; |
|||
const cacheGlobLifetime = 600; // sec
|
|||
|
|||
// TODO: reset calls' states on new /cache call start
|
|||
var mock = require('./mock'); |
|||
|
|||
var callStack = {}; |
|||
const checkCallStack = function() { |
|||
let total = 0; |
|||
|
|||
for (let coin in callStack) { |
|||
total =+ callStack[coin]; |
|||
} |
|||
|
|||
if (total / Object.keys(callStack).length === 1) { |
|||
cache.dumpCacheBeforeExit(); |
|||
cacheCallInProgress = false; |
|||
cache.io.emit('messages', { |
|||
message: { |
|||
shepherd: { |
|||
method: 'cache-one', |
|||
status: 'done', |
|||
resp: 'success', |
|||
}, |
|||
}, |
|||
}); |
|||
} |
|||
}; |
|||
|
|||
/* |
|||
* type: GET |
|||
* params: userpass, pubkey, coin, address, skip |
|||
*/ |
|||
cache.one = function(req, res, next) { |
|||
if (req.query.pubkey && |
|||
!fs.existsSync(`${cache.iguanaDir}/shepherd/cache-${req.query.pubkey}.json`)) { |
|||
cacheCallInProgress = false; |
|||
} |
|||
|
|||
if (cacheCallInProgress) { |
|||
checkCallStack(); |
|||
} |
|||
|
|||
if (!cacheCallInProgress) { |
|||
cache.dumpCacheBeforeExit(); |
|||
|
|||
if (fs.existsSync(`${cache.iguanaDir}/shepherd/cache-${req.query.pubkey}.json`)) { |
|||
let _data = fs.readFileSync(`${cache.iguanaDir}/shepherd/cache-${req.query.pubkey}.json`, 'utf8'); |
|||
|
|||
if (_data) { |
|||
inMemCache = JSON.parse(_data); |
|||
_data = _data.replace('waiting', 'failed'); |
|||
cache.dumpCacheBeforeExit(); |
|||
} |
|||
} |
|||
|
|||
// TODO: add check to allow only one cache call/sequence in progress
|
|||
cacheCallInProgress = true; |
|||
|
|||
let sessionKey = req.query.userpass; |
|||
let coin = req.query.coin; |
|||
let address = req.query.address; |
|||
let addresses = req.query.addresses && req.query.addresses.indexOf(':') > -1 ? req.query.addresses.split(':') : null; |
|||
let pubkey = req.query.pubkey; |
|||
let mock = req.query.mock; |
|||
let skipTimeout = req.query.skip; |
|||
let callsArray = req.query.calls.split(':'); |
|||
let iguanaCorePort = req.query.port ? req.query.port : cache.appConfig.iguanaCorePort; |
|||
let errorObj = { |
|||
msg: 'error', |
|||
result: 'error', |
|||
}; |
|||
let outObj = {}; |
|||
const writeCache = function(timeStamp) { |
|||
if (timeStamp) { |
|||
outObj.timestamp = timeStamp; |
|||
} |
|||
|
|||
inMemCache = outObj; |
|||
}; |
|||
const checkTimestamp = function(dateToCheck) { |
|||
const currentEpochTime = new Date(Date.now()) / 1000; |
|||
const secondsElapsed = Number(currentEpochTime) - Number(dateToCheck / 1000); |
|||
|
|||
return Math.floor(secondsElapsed); |
|||
}; |
|||
let internalError = false; |
|||
|
|||
inMemPubkey = pubkey; |
|||
callStack[coin] = 1; |
|||
cache.shepherd.log(callsArray); |
|||
cache.shepherd.log(`iguana core port ${iguanaCorePort}`); |
|||
|
|||
if (!sessionKey) { |
|||
const errorObj = { |
|||
msg: 'error', |
|||
result: 'no session key provided', |
|||
}; |
|||
|
|||
res.end(JSON.stringify(errorObj)); |
|||
internalError = true; |
|||
} |
|||
|
|||
if (!pubkey) { |
|||
const errorObj = { |
|||
msg: 'error', |
|||
result: 'no pubkey provided', |
|||
}; |
|||
|
|||
res.end(JSON.stringify(errorObj)); |
|||
internalError = true; |
|||
} |
|||
|
|||
cache.shepherd.log('cache-one call started'); |
|||
|
|||
function fixJSON(data) { |
|||
if (data && |
|||
data.length) { |
|||
try { |
|||
const parsedJSON = JSON.parse(data); |
|||
|
|||
return parsedJSON; |
|||
} catch (e) { |
|||
cache.shepherd.log(e); |
|||
if (e.toString().indexOf('at position') > -1) { |
|||
const errorPos = e.toString().split(' '); |
|||
|
|||
cache.shepherd.log(`JSON error ---> ${data.substring(errorPos[errorPos.length - 1] - 20, errorPos[errorPos.length - 1] + 20)} | error sequence: ${data.substring(errorPos[errorPos.length - 1], errorPos[errorPos.length - 1] + 1)}`); |
|||
cache.shepherd.log('attempting to recover JSON data'); |
|||
return JSON.parse(data.substring(0, errorPos[errorPos.length - 1])); |
|||
} |
|||
if (e.toString().indexOf('Unexpected end of JSON input')) { |
|||
return {}; |
|||
} |
|||
} |
|||
} else { |
|||
return {}; |
|||
} |
|||
} |
|||
|
|||
if (fs.existsSync(`${cache.iguanaDir}/shepherd/cache-${pubkey}.json`) && |
|||
coin !== 'all') { |
|||
if (inMemCache) { |
|||
cache.shepherd.log('cache one from mem'); |
|||
outObj = inMemCache; |
|||
} else { |
|||
const _file = fs.readFileSync(`${cache.iguanaDir}/shepherd/cache-${pubkey}.json`, 'utf8'); |
|||
|
|||
cache.shepherd.log('cache one from disk'); |
|||
outObj = fixJSON(_file); |
|||
} |
|||
|
|||
if (!outObj || |
|||
!outObj.basilisk) { |
|||
cache.shepherd.log('no local basilisk info'); |
|||
outObj['basilisk'] = {}; |
|||
outObj['basilisk'][coin] = {}; |
|||
} else { |
|||
if (!outObj['basilisk'][coin]) { |
|||
cache.shepherd.log('no local coin info'); |
|||
outObj['basilisk'][coin] = {}; |
|||
} |
|||
} |
|||
} else { |
|||
outObj['basilisk'] = {}; |
|||
outObj['basilisk'][coin] = {}; |
|||
} |
|||
|
|||
res.end(JSON.stringify({ |
|||
msg: 'success', |
|||
result: 'call is initiated', |
|||
})); |
|||
|
|||
if (!internalError) { |
|||
cache.io.emit('messages', { |
|||
message: { |
|||
shepherd: { |
|||
method: 'cache-one', |
|||
status: 'in progress', |
|||
}, |
|||
}, |
|||
}); |
|||
|
|||
function execDEXRequests(coin, address) { |
|||
let dexUrls = { |
|||
listunspent: `http://${cache.appConfig.host}:${iguanaCorePort}/api/dex/listunspent?userpass=${sessionKey}&symbol=${coin}&address=${address}`, |
|||
listtransactions: `http://${cache.appConfig.host}:${iguanaCorePort}/api/dex/listtransactions?userpass=${sessionKey}&count=100&skip=0&symbol=${coin}&address=${address}`, |
|||
getbalance: `http://${cache.appConfig.host}:${iguanaCorePort}/api/dex/getbalance?userpass=${sessionKey}&symbol=${coin}&address=${address}`, |
|||
refresh: `http://${cache.appConfig.host}:${iguanaCorePort}/api/basilisk/refresh?userpass=${sessionKey}&symbol=${coin}&address=${address}` |
|||
}; |
|||
let _dexUrls = {}; |
|||
|
|||
for (let a = 0; a < callsArray.length; a++) { |
|||
_dexUrls[callsArray[a]] = dexUrls[callsArray[a]]; |
|||
} |
|||
|
|||
if (coin === 'BTC' || |
|||
coin === 'SYS') { |
|||
delete _dexUrls.refresh; |
|||
delete _dexUrls.getbalance; |
|||
} |
|||
|
|||
cache.shepherd.log(`${coin} address ${address}`); |
|||
|
|||
if (!outObj.basilisk[coin][address]) { |
|||
outObj.basilisk[coin][address] = {}; |
|||
writeCache(); |
|||
} |
|||
|
|||
// set current call status
|
|||
async.forEachOf(_dexUrls, function(dexUrl, key) { |
|||
if (!outObj.basilisk[coin][address][key]) { |
|||
outObj.basilisk[coin][address][key] = {}; |
|||
outObj.basilisk[coin][address][key].status = 'waiting'; |
|||
} else { |
|||
outObj.basilisk[coin][address][key].status = 'waiting'; |
|||
} |
|||
}); |
|||
writeCache(); |
|||
|
|||
async.forEachOf(_dexUrls, function(dexUrl, key) { |
|||
var tooEarly = false; |
|||
|
|||
if (outObj.basilisk[coin][address][key] && |
|||
outObj.basilisk[coin][address][key].timestamp && |
|||
(!skipTimeout && checkTimestamp(outObj.basilisk[coin][address][key].timestamp) < cacheGlobLifetime)) { |
|||
tooEarly = true; |
|||
outObj.basilisk[coin][address][key].status = 'done'; |
|||
cache.io.emit('messages', { |
|||
message: { |
|||
shepherd: { |
|||
method: 'cache-one', |
|||
status: 'in progress', |
|||
iguanaAPI: { |
|||
method: key, |
|||
coin: coin, |
|||
address: address, |
|||
status: 'done', |
|||
resp: 'too early', |
|||
}, |
|||
}, |
|||
}, |
|||
}); |
|||
} |
|||
if (!tooEarly) { |
|||
cache.io.emit('messages', { |
|||
message: { |
|||
shepherd: { |
|||
method: 'cache-one', |
|||
status: 'in progress', |
|||
iguanaAPI: { |
|||
method: key, |
|||
coin: coin, |
|||
address: address, |
|||
status: 'in progress', |
|||
}, |
|||
}, |
|||
}, |
|||
}); |
|||
outObj.basilisk[coin][address][key].status = 'in progress'; |
|||
request({ |
|||
url: mock ? `http://localhost:17777/shepherd/mock?url=${dexUrl}` : dexUrl, |
|||
method: 'GET', |
|||
timeout: 30000, |
|||
}, function(error, response, body) { |
|||
if (response && |
|||
response.statusCode && |
|||
response.statusCode === 200) { |
|||
cache.io.emit('messages', { |
|||
message: { |
|||
shepherd: { |
|||
method: 'cache-one', |
|||
status: 'in progress', |
|||
iguanaAPI: { |
|||
method: key, |
|||
coin: coin, |
|||
address: address, |
|||
status: 'done', |
|||
resp: body, |
|||
}, |
|||
}, |
|||
}, |
|||
}); |
|||
|
|||
// basilisk fallback
|
|||
const _parsedJSON = JSON.parse(body); |
|||
_explorerURL = { |
|||
getbalance: `http://kmd.explorer.supernet.org/api/addr/${address}/?noTxList=1`, |
|||
listtransactions: `https://kmd.explorer.supernet.org/api/txs?address=${address}&pageNum=0` |
|||
}; |
|||
if ((key === 'getbalance' && |
|||
coin === 'KMD' && |
|||
((_parsedJSON && _parsedJSON.balance === 0) || body === [])) || |
|||
(key === 'listtransactions' && |
|||
coin === 'KMD' && |
|||
(_parsedJSON === [] || !_parsedJSON.length))) { |
|||
cache.shepherd.log('fallback to kmd explorer ======>'); |
|||
request({ |
|||
url: _explorerURL[key], |
|||
method: 'GET' |
|||
}, function(error, response, _body) { |
|||
if (response && |
|||
response.statusCode && |
|||
response.statusCode === 200) { |
|||
let _parsedExplorerJSON = JSON.parse(_body); |
|||
_parsedExplorerJSON['source'] = 'explorer'; |
|||
let _formattedTxs = []; |
|||
|
|||
if (key === 'listtransactions' && |
|||
_parsedExplorerJSON['txs'] && |
|||
_parsedExplorerJSON['txs'].length) { |
|||
const _txList = _parsedExplorerJSON['txs']; |
|||
|
|||
for (let i = 0; i < _txList.length; i++) { |
|||
_formattedTxs.push({ |
|||
type: 'unknown', |
|||
height: _txList[i].blockheight, |
|||
confirmations: _txList[i].confirmations, |
|||
timestamp: _txList[i].time, |
|||
amount: _txList[i].vout[1].value, |
|||
txid: _txList[i].txid, |
|||
source: 'explorer', |
|||
}); |
|||
} |
|||
|
|||
_parsedExplorerJSON = _formattedTxs; |
|||
} |
|||
if ((key === 'getbalance' && |
|||
Number(_parsedExplorerJSON.balance) !== Number(_parsedJSON.balance) && |
|||
Number(_parsedExplorerJSON.txApperances) < 500) || key === 'listtransactions') { |
|||
body = JSON.stringify(_parsedExplorerJSON); |
|||
} |
|||
cache.io.emit('messages', { |
|||
'message': { |
|||
'shepherd': { |
|||
'method': 'cache-one', |
|||
'status': 'in progress', |
|||
'iguanaAPI': { |
|||
'method': key, |
|||
'coin': coin, |
|||
'address': address, |
|||
'status': 'done', |
|||
'resp': _body |
|||
} |
|||
} |
|||
} |
|||
}); |
|||
|
|||
outObj.basilisk[coin][address][key] = {}; |
|||
outObj.basilisk[coin][address][key].data = JSON.parse(body); |
|||
outObj.basilisk[coin][address][key].timestamp = Date.now(); // add timestamp
|
|||
outObj.basilisk[coin][address][key].status = 'done'; |
|||
cache.shepherd.log(dexUrl); |
|||
cache.shepherd.log(body); |
|||
callStack[coin]--; |
|||
cache.shepherd.log(`${coin} _stack len ${callStack[coin]}`); |
|||
cache.io.emit('messages', { |
|||
message: { |
|||
shepherd: { |
|||
method: 'cache-one', |
|||
status: 'in progress', |
|||
iguanaAPI: { |
|||
currentStackLength: callStack[coin], |
|||
}, |
|||
}, |
|||
}, |
|||
}); |
|||
checkCallStack(); |
|||
writeCache(); |
|||
} else { |
|||
cache.shepherd.log(`explorer ${key} for address ${address} fallback failed`); |
|||
} |
|||
}); |
|||
} else { |
|||
outObj.basilisk[coin][address][key] = {}; |
|||
outObj.basilisk[coin][address][key].data = JSON.parse(body); |
|||
outObj.basilisk[coin][address][key].timestamp = Date.now(); // add timestamp
|
|||
outObj.basilisk[coin][address][key].status = 'done'; |
|||
cache.shepherd.log(dexUrl); |
|||
cache.shepherd.log(body); |
|||
callStack[coin]--; |
|||
cache.shepherd.log(`${coin} _stack len ${callStack[coin]}`); |
|||
cache.io.emit('messages', { |
|||
message: { |
|||
shepherd: { |
|||
method: 'cache-one', |
|||
status: 'in progress', |
|||
iguanaAPI: { |
|||
currentStackLength: callStack[coin], |
|||
}, |
|||
}, |
|||
}, |
|||
}); |
|||
checkCallStack(); |
|||
writeCache(); |
|||
} |
|||
} |
|||
if (error || |
|||
!body || |
|||
!response) { |
|||
// basilisk fallback
|
|||
_explorerURL = { |
|||
getbalance: `http://kmd.explorer.supernet.org/api/addr/${address}/?noTxList=1`, |
|||
listtransactions: `https://kmd.explorer.supernet.org/api/txs?address=${address}&pageNum=0` |
|||
}; |
|||
if ((key === 'getbalance' && |
|||
coin === 'KMD') || |
|||
(key === 'listtransactions' && |
|||
coin === 'KMD')) { |
|||
cache.shepherd.log('fallback to kmd explorer ======>'); |
|||
request({ |
|||
url: _explorerURL[key], |
|||
method: 'GET' |
|||
}, function(error, response, _body) { |
|||
if (response && |
|||
response.statusCode && |
|||
response.statusCode === 200) { |
|||
let _parsedExplorerJSON = JSON.parse(_body); |
|||
_parsedExplorerJSON['source'] = 'explorer'; |
|||
|
|||
if ((key === 'getbalance' && |
|||
Number(_parsedExplorerJSON.balance) !== Number(_parsedJSON.balance) && |
|||
Number(_parsedExplorerJSON.txApperances) < 500) || key === 'listtransactions') { |
|||
body = JSON.stringify(_parsedExplorerJSON); |
|||
} |
|||
cache.io.emit('messages', { |
|||
'message': { |
|||
'shepherd': { |
|||
'method': 'cache-one', |
|||
'status': 'in progress', |
|||
'iguanaAPI': { |
|||
'method': key, |
|||
'coin': coin, |
|||
'address': address, |
|||
'status': 'done', |
|||
'resp': _body |
|||
} |
|||
} |
|||
} |
|||
}); |
|||
|
|||
outObj.basilisk[coin][address][key] = {}; |
|||
outObj.basilisk[coin][address][key].data = JSON.parse(body); |
|||
outObj.basilisk[coin][address][key].timestamp = Date.now(); // add timestamp
|
|||
outObj.basilisk[coin][address][key].status = 'done'; |
|||
cache.shepherd.log(dexUrl); |
|||
cache.shepherd.log(body); |
|||
callStack[coin]--; |
|||
cache.shepherd.log(`${coin} _stack len ${callStack[coin]}`); |
|||
cache.io.emit('messages', { |
|||
message: { |
|||
shepherd: { |
|||
method: 'cache-one', |
|||
status: 'in progress', |
|||
iguanaAPI: { |
|||
currentStackLength: callStack[coin], |
|||
}, |
|||
}, |
|||
}, |
|||
}); |
|||
checkCallStack(); |
|||
writeCache(); |
|||
} else { |
|||
cache.shepherd.log(`explorer ${key} for address ${address} fallback failed`); |
|||
outObj.basilisk[coin][address][key] = {}; |
|||
outObj.basilisk[coin][address][key].data = { 'error': 'request failed' }; |
|||
outObj.basilisk[coin][address][key].timestamp = 1471620867 // add timestamp
|
|||
outObj.basilisk[coin][address][key].status = 'done'; |
|||
callStack[coin]--; |
|||
cache.shepherd.log(`${coin} request ${key} for address ${address} failed`); |
|||
cache.shepherd.log(`${coin} _stack len ${callStack[coin]}`); |
|||
cache.io.emit('messages', { |
|||
message: { |
|||
shepherd: { |
|||
method: 'cache-one', |
|||
status: 'in progress', |
|||
iguanaAPI: { |
|||
currentStackLength: callStack[coin], |
|||
}, |
|||
}, |
|||
}, |
|||
}); |
|||
checkCallStack(); |
|||
writeCache(); |
|||
} |
|||
}); |
|||
} else { |
|||
outObj.basilisk[coin][address][key] = {}; |
|||
outObj.basilisk[coin][address][key].data = { 'error': 'request failed' }; |
|||
outObj.basilisk[coin][address][key].timestamp = 1471620867 // add timestamp
|
|||
outObj.basilisk[coin][address][key].status = 'done'; |
|||
callStack[coin]--; |
|||
cache.shepherd.log(`${coin} request ${key} for address ${address} failed`); |
|||
cache.shepherd.log(`${coin} _stack len ${callStack[coin]}`); |
|||
cache.io.emit('messages', { |
|||
message: { |
|||
shepherd: { |
|||
method: 'cache-one', |
|||
status: 'in progress', |
|||
iguanaAPI: { |
|||
currentStackLength: callStack[coin], |
|||
}, |
|||
}, |
|||
}, |
|||
}); |
|||
checkCallStack(); |
|||
writeCache(); |
|||
} |
|||
} |
|||
}); |
|||
} else { |
|||
cache.shepherd.log(`${key} is fresh, check back in ${(cacheGlobLifetime - checkTimestamp(outObj.basilisk[coin][address][key].timestamp))}s`); |
|||
callStack[coin]--; |
|||
cache.shepherd.log(`${coin} _stack len ${callStack[coin]}`); |
|||
cache.io.emit('messages', { |
|||
message: { |
|||
shepherd: { |
|||
method: 'cache-one', |
|||
status: 'in progress', |
|||
iguanaAPI: { |
|||
currentStackLength: callStack[coin], |
|||
}, |
|||
}, |
|||
}, |
|||
}); |
|||
checkCallStack(); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
function parseAddresses(coin, addrArray) { |
|||
cache.io.emit('messages', { |
|||
message: { |
|||
shepherd: { |
|||
method: 'cache-one', |
|||
status: 'in progress', |
|||
iguanaAPI: { |
|||
method: 'getaddressesbyaccount', |
|||
coin: coin, |
|||
status: 'done', |
|||
resp: addrArray, |
|||
}, |
|||
}, |
|||
}, |
|||
}); |
|||
outObj.basilisk[coin].addresses = addrArray; |
|||
cache.shepherd.log(addrArray); |
|||
writeCache(); |
|||
|
|||
const addrCount = outObj.basilisk[coin].addresses ? outObj.basilisk[coin].addresses.length : 0; |
|||
let callsArrayBTC = callsArray.length; |
|||
|
|||
if (callsArray.indexOf('getbalance') > - 1) { |
|||
callsArrayBTC--; |
|||
} |
|||
if (callsArray.indexOf('refresh') > - 1) { |
|||
callsArrayBTC--; |
|||
} |
|||
callStack[coin] = callStack[coin] + addrCount * (coin === 'BTC' || coin === 'SYS' ? callsArrayBTC : callsArray.length); |
|||
cache.shepherd.log(`${coin} stack len ${callStack[coin]}`); |
|||
|
|||
cache.io.emit('messages', { |
|||
message: { |
|||
shepherd: { |
|||
method: 'cache-one', |
|||
status: 'in progress', |
|||
iguanaAPI: { |
|||
totalStackLength: callStack[coin], |
|||
}, |
|||
}, |
|||
}, |
|||
}); |
|||
async.each(outObj.basilisk[coin].addresses, function(address) { |
|||
execDEXRequests(coin, address); |
|||
}); |
|||
} |
|||
|
|||
function getAddresses(coin) { |
|||
if (addresses) { |
|||
parseAddresses(coin, addresses); |
|||
} else { |
|||
const tempUrl = `http://${cache.appConfig.host}:${cache.appConfig.iguanaCorePort}/api/bitcoinrpc/getaddressesbyaccount?userpass=${sessionKey}&coin=${coin}&account=*`; |
|||
request({ |
|||
url: mock ? `http://localhost:17777/shepherd/mock?url=${tempUrl}` : tempUrl, |
|||
method: 'GET' |
|||
}, function(error, response, body) { |
|||
if (response && |
|||
response.statusCode && |
|||
response.statusCode === 200) { |
|||
parseAddresses(coin, JSON.parse(body).result); |
|||
} else { |
|||
// TODO: error
|
|||
} |
|||
}); |
|||
} |
|||
} |
|||
|
|||
// update all available coin addresses
|
|||
if (!address) { |
|||
cache.io.emit('messages', { |
|||
message: { |
|||
shepherd: { |
|||
method: 'cache-one', |
|||
status: 'in progress', |
|||
iguanaAPI: { |
|||
method: 'getaddressesbyaccount', |
|||
coin: coin, |
|||
status: 'in progress', |
|||
}, |
|||
}, |
|||
}, |
|||
}); |
|||
|
|||
if (coin === 'all') { |
|||
const tempUrl = `http://${cache.appConfig.host}:${cache.appConfig.iguanaCorePort}/api/InstantDEX/allcoins?userpass=${sessionKey}`; |
|||
request({ |
|||
url: mock ? `http://localhost:17777/shepherd/mock?url=${tempUrl}` : tempUrl, |
|||
method: 'GET' |
|||
}, function(error, response, body) { |
|||
if (response && |
|||
response.statusCode && |
|||
response.statusCode === 200) { |
|||
cache.shepherd.log(JSON.parse(body).basilisk); |
|||
cache.io.emit('messages', { |
|||
message: { |
|||
shepherd: { |
|||
method: 'cache-one', |
|||
status: 'in progress', |
|||
iguanaAPI: { |
|||
method: 'allcoins', |
|||
status: 'done', |
|||
resp: body, |
|||
}, |
|||
}, |
|||
}, |
|||
}); |
|||
body = JSON.parse(body); |
|||
// basilisk coins
|
|||
if (body.basilisk && |
|||
body.basilisk.length) { |
|||
// get coin addresses
|
|||
async.each(body.basilisk, function(coin) { |
|||
callStack[coin] = 1; |
|||
}); |
|||
|
|||
async.each(body.basilisk, function(coin) { |
|||
outObj.basilisk[coin] = {}; |
|||
writeCache(); |
|||
|
|||
cache.io.emit('messages', { |
|||
message: { |
|||
shepherd: { |
|||
method: 'cache-one', |
|||
status: 'in progress', |
|||
iguanaAPI: { |
|||
method: 'getaddressesbyaccount', |
|||
coin: coin, |
|||
status: 'in progress', |
|||
}, |
|||
}, |
|||
}, |
|||
}); |
|||
|
|||
getAddresses(coin); |
|||
}); |
|||
} |
|||
} |
|||
if (error) { // stop further requests on failure, exit
|
|||
callStack[coin] = 1; |
|||
checkCallStack(); |
|||
} |
|||
}); |
|||
} else { |
|||
getAddresses(coin); |
|||
} |
|||
} else { |
|||
let callsArrayBTC = callsArray.length; // restrict BTC and SYS only to listunspent and listtransactions calls
|
|||
|
|||
if (callsArray.indexOf('getbalance') > - 1) { |
|||
callsArrayBTC--; |
|||
} |
|||
if (callsArray.indexOf('refresh') > - 1) { |
|||
callsArrayBTC--; |
|||
} |
|||
|
|||
callStack[coin] = callStack[coin] + (coin === 'BTC' || coin === 'SYS' ? callsArrayBTC : callsArray.length); |
|||
cache.shepherd.log(`${coin} stack len ${callStack[coin]}`); |
|||
|
|||
cache.io.emit('messages', { |
|||
message: { |
|||
shepherd: { |
|||
method: 'cache-one', |
|||
status: 'in progress', |
|||
iguanaAPI: { |
|||
totalStackLength: callStack[coin], |
|||
currentStackLength: callStack[coin], |
|||
}, |
|||
}, |
|||
}, |
|||
}); |
|||
|
|||
execDEXRequests(coin, address); |
|||
} |
|||
} else { |
|||
cache.io.emit('messages', { |
|||
message: { |
|||
shepherd: { |
|||
method: 'cache-all', |
|||
status: 'done', |
|||
resp: 'internal error', |
|||
}, |
|||
}, |
|||
}); |
|||
cacheCallInProgress = false; |
|||
} |
|||
} else { |
|||
res.end(JSON.stringify({ |
|||
msg: 'error', |
|||
result: 'another call is in progress already', |
|||
})); |
|||
} |
|||
}; |
|||
|
|||
module.exports = cache; |
@ -0,0 +1,262 @@ |
|||
let electrumServers = { |
|||
/*zcash: { |
|||
address: '173.212.225.176', |
|||
port: 50032, |
|||
proto: 'tcp', |
|||
},*/ |
|||
coqui: { // !estimatefee
|
|||
address: 'electrum1.cipig.net', |
|||
port: 10011, |
|||
proto: 'tcp', |
|||
txfee: 10000, |
|||
abbr: 'COQUI', |
|||
serverList: [ |
|||
'electrum1.cipig.net:10011', |
|||
'electrum2.cipig.net:10011' |
|||
], |
|||
}, |
|||
revs: { // !estimatefee
|
|||
address: 'electrum1.cipig.net', |
|||
port: 10003, |
|||
proto: 'tcp', |
|||
txfee: 10000, |
|||
abbr: 'REVS', |
|||
serverList: [ |
|||
'electrum1.cipig.net:10003', |
|||
'electrum2.cipig.net:10003' |
|||
], |
|||
}, |
|||
supernet: { // !estimatefee
|
|||
address: 'electrum1.cipig.net', |
|||
port: 10005, |
|||
proto: 'tcp', |
|||
txfee: 10000, |
|||
abbr: 'SUPERNET', |
|||
serverList: [ |
|||
'electrum1.cipig.net:10005', |
|||
'electrum2.cipig.net:10005' |
|||
], |
|||
}, |
|||
dex: { // !estimatefee
|
|||
address: 'electrum1.cipig.net', |
|||
port: 10006, |
|||
proto: 'tcp', |
|||
txfee: 10000, |
|||
abbr: 'DEX', |
|||
serverList: [ |
|||
'electrum1.cipig.net:10006', |
|||
'electrum2.cipig.net:10006' |
|||
], |
|||
}, |
|||
bots: { // !estimatefee
|
|||
address: 'electrum1.cipig.net', |
|||
port: 10007, |
|||
proto: 'tcp', |
|||
txfee: 10000, |
|||
abbr: 'BOTS', |
|||
serverList: [ |
|||
'electrum1.cipig.net:10007', |
|||
'electrum2.cipig.net:10007' |
|||
], |
|||
}, |
|||
crypto: { // !estimatefee
|
|||
address: 'electrum1.cipig.net', |
|||
port: 10008, |
|||
proto: 'tcp', |
|||
txfee: 10000, |
|||
abbr: 'CRYPTO', |
|||
serverList: [ |
|||
'electrum1.cipig.net:10008', |
|||
'electrum2.cipig.net:10008' |
|||
], |
|||
}, |
|||
hodl: { // !estimatefee
|
|||
address: 'electrum1.cipig.net', |
|||
port: 10009, |
|||
proto: 'tcp', |
|||
txfee: 10000, |
|||
abbr: 'HODL', |
|||
serverList: [ |
|||
'electrum1.cipig.net:10009', |
|||
'electrum2.cipig.net:10009' |
|||
], |
|||
}, |
|||
pangea: { // !estimatefee
|
|||
address: 'electrum1.cipig.net', |
|||
port: 10010, |
|||
proto: 'tcp', |
|||
txfee: 10000, |
|||
abbr: 'PANGEA', |
|||
serverList: [ |
|||
'electrum1.cipig.net:10010', |
|||
'electrum2.cipig.net:10010' |
|||
], |
|||
}, |
|||
bet: { // !estimatefee
|
|||
address: 'electrum1.cipig.net', |
|||
port: 10012, |
|||
proto: 'tcp', |
|||
txfee: 10000, |
|||
abbr: 'BET', |
|||
serverList: [ |
|||
'electrum1.cipig.net:10012', |
|||
'electrum2.cipig.net:10012' |
|||
], |
|||
}, |
|||
mshark: { // !estimatefee
|
|||
address: 'electrum1.cipig.net', |
|||
port: 10013, |
|||
proto: 'tcp', |
|||
txfee: 10000, |
|||
abbr: 'MSHARK', |
|||
serverList: [ |
|||
'electrum1.cipig.net:10013', |
|||
'electrum2.cipig.net:10013' |
|||
], |
|||
}, |
|||
mnz: { // !estimatefee
|
|||
address: 'electrum1.cipig.net', |
|||
port: 10002, |
|||
proto: 'tcp', |
|||
txfee: 10000, |
|||
abbr: 'MNZ', |
|||
serverList: [ |
|||
'electrum1.cipig.net:10002', |
|||
'electrum2.cipig.net:10002'/*, |
|||
'18.216.195.109:10002', |
|||
'52.41.58.116:10002', |
|||
'52.67.48.29:10002', |
|||
'13.124.87.194:10002', |
|||
'52.63.107.102:10002'*/ |
|||
], |
|||
}, |
|||
wlc: { // !estimatefee
|
|||
address: 'electrum1.cipig.net', |
|||
port: 10014, |
|||
proto: 'tcp', |
|||
txfee: 10000, |
|||
abbr: 'WLC', |
|||
serverList: [ |
|||
'electrum1.cipig.net:10014', |
|||
'electrum2.cipig.net:10014' |
|||
], |
|||
}, |
|||
jumblr: { // !estimatefee
|
|||
address: 'electrum1.cipig.net', |
|||
port: 10004, |
|||
proto: 'tcp', |
|||
txfee: 10000, |
|||
abbr: 'JUMBLR', |
|||
serverList: [ |
|||
'electrum1.cipig.net:10004', |
|||
'electrum2.cipig.net:10004' |
|||
], |
|||
}, |
|||
komodo: { // !estimatefee
|
|||
address: 'electrum1.cipig.net', |
|||
port: 10001, |
|||
proto: 'tcp', |
|||
txfee: 10000, |
|||
abbr: 'KMD', |
|||
serverList: [ |
|||
'electrum1.cipig.net:10001', |
|||
'electrum2.cipig.net:10001', |
|||
], |
|||
}, |
|||
dogecoin: { // !estimatefee
|
|||
address: '173.212.225.176', |
|||
port: 50015, |
|||
proto: 'tcp', |
|||
txfee: 100000000, |
|||
abbr: 'DOGE', |
|||
}, |
|||
viacoin: { // !estimatefee
|
|||
address: 'vialectrum.bitops.me', |
|||
port: 50002, |
|||
proto: 'ssl', |
|||
txfee: 100000, |
|||
abbr: 'VIA', |
|||
}, |
|||
vertcoin: { |
|||
address: '173.212.225.176', |
|||
port: 50088, |
|||
proto: 'tcp', |
|||
txfee: 100000, |
|||
abbr: 'VTC', |
|||
}, |
|||
namecoin: { |
|||
address: '173.212.225.176', |
|||
port: 50036, |
|||
proto: 'tcp', |
|||
txfee: 100000, |
|||
abbr: 'NMC', |
|||
}, |
|||
monacoin: { // !estimatefee
|
|||
address: '173.212.225.176', |
|||
port: 50002, |
|||
proto: 'tcp', |
|||
txfee: 100000, |
|||
abbr: 'MONA', |
|||
}, |
|||
litecoin: { |
|||
address: '173.212.225.176', |
|||
port: 50012, |
|||
proto: 'tcp', |
|||
txfee: 10000, |
|||
abbr: 'LTC', |
|||
}, |
|||
faircoin: { |
|||
address: '173.212.225.176', |
|||
port: 50005, |
|||
proto: 'tcp', |
|||
txfee: 1000000, |
|||
abbr: 'FAIR', |
|||
}, |
|||
digibyte: { |
|||
address: '173.212.225.176', |
|||
port: 50022, |
|||
proto: 'tcp', |
|||
txfee: 100000, |
|||
abbr: 'DGB', |
|||
}, |
|||
dash: { |
|||
address: '173.212.225.176', |
|||
port: 50098, |
|||
proto: 'tcp', |
|||
txfee: 10000, |
|||
abbr: 'DASH', |
|||
}, |
|||
crown: { |
|||
address: '173.212.225.176', |
|||
port: 50041, |
|||
proto: 'tcp', |
|||
txfee: 10000, |
|||
abbr: 'CRW', |
|||
}, |
|||
bitcoin: { |
|||
address: '173.212.225.176', |
|||
port: 50001, |
|||
proto: 'tcp', |
|||
abbr: 'BTC', |
|||
}, |
|||
argentum: { // !estimatefee
|
|||
address: '173.212.225.176', |
|||
port: 50081, |
|||
proto: 'tcp', |
|||
txfee: 50000, |
|||
abbr: 'ARG', |
|||
}, |
|||
chips: { // !estimatefee
|
|||
address: '173.212.225.176', |
|||
port: 50076, |
|||
proto: 'tcp', |
|||
txfee: 10000, |
|||
abbr: 'CHIPS', |
|||
serverList: [ |
|||
'173.212.225.176:50076', |
|||
'136.243.45.140:50076' |
|||
], |
|||
}, |
|||
}; |
|||
|
|||
module.exports = electrumServers; |
@ -0,0 +1,354 @@ |
|||
/* |
|||
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. |
|||
*/ |
|||
|
|||
const tls = require('tls'); |
|||
const net = require('net'); |
|||
const EventEmitter = require('events').EventEmitter; |
|||
const SOCKET_MAX_TIMEOUT = 10000; |
|||
|
|||
const makeRequest = function(method, params, id) { |
|||
return JSON.stringify({ |
|||
jsonrpc : '2.0', |
|||
method : method, |
|||
params : params, |
|||
id : id, |
|||
}); |
|||
} |
|||
|
|||
const createRecursiveParser = function(maxDepth, delimiter) { |
|||
const MAX_DEPTH = maxDepth; |
|||
const DELIMITER = delimiter; |
|||
const recursiveParser = function(n, buffer, callback) { |
|||
if (buffer.length === 0) { |
|||
return { |
|||
code: 0, |
|||
buffer: buffer, |
|||
}; |
|||
} |
|||
|
|||
if (n > MAX_DEPTH) { |
|||
return { |
|||
code: 1, |
|||
buffer: buffer, |
|||
}; |
|||
} |
|||
|
|||
const xs = buffer.split(DELIMITER); |
|||
|
|||
if (xs.length === 1) { |
|||
return { |
|||
code: 0, |
|||
buffer: buffer, |
|||
}; |
|||
} |
|||
|
|||
callback(xs.shift(), n); |
|||
|
|||
return recursiveParser(n + 1, xs.join(DELIMITER), callback); |
|||
} |
|||
|
|||
return recursiveParser; |
|||
} |
|||
|
|||
const createPromiseResult = function(resolve, reject) { |
|||
return (err, result) => { |
|||
if (err) { |
|||
console.log('electrum error:'); |
|||
console.log(err); |
|||
resolve(err); |
|||
// reject(err);
|
|||
} else { |
|||
resolve(result); |
|||
} |
|||
} |
|||
} |
|||
|
|||
class MessageParser { |
|||
constructor(callback) { |
|||
this.buffer = ''; |
|||
this.callback = callback; |
|||
this.recursiveParser = createRecursiveParser(20, '\n'); |
|||
} |
|||
|
|||
run(chunk) { |
|||
this.buffer += chunk; |
|||
|
|||
while (true) { |
|||
const res = this.recursiveParser(0, this.buffer, this.callback); |
|||
|
|||
this.buffer = res.buffer; |
|||
|
|||
if (res.code === 0) { |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
const util = { |
|||
makeRequest, |
|||
createRecursiveParser, |
|||
createPromiseResult, |
|||
MessageParser, |
|||
}; |
|||
|
|||
const getSocket = function(protocol, options) { |
|||
switch (protocol) { |
|||
case 'tcp': |
|||
return new net.Socket(); |
|||
case 'tls': |
|||
// todo
|
|||
case 'ssl': |
|||
return new tls.TLSSocket(options); |
|||
} |
|||
|
|||
throw new Error('unknown protocol'); |
|||
} |
|||
|
|||
const initSocket = function(self, protocol, options) { |
|||
const conn = getSocket(protocol, options); |
|||
|
|||
conn.setTimeout(SOCKET_MAX_TIMEOUT); |
|||
conn.on('timeout', () => { |
|||
console.log('socket timeout'); |
|||
self.onError(new Error('socket timeout')); |
|||
self.onClose(); |
|||
}); |
|||
conn.setEncoding('utf8'); |
|||
conn.setKeepAlive(true, 0); |
|||
conn.setNoDelay(true); |
|||
conn.on('connect', () => { |
|||
self.onConnect(); |
|||
}); |
|||
conn.on('close', (e) => { |
|||
self.onClose(e); |
|||
}); |
|||
conn.on('data', (chunk) => { |
|||
self.onReceive(chunk); |
|||
}); |
|||
conn.on('end', (e) => { |
|||
self.onEnd(e); |
|||
}); |
|||
conn.on('error', (e) => { |
|||
self.onError(e); |
|||
}); |
|||
|
|||
return conn; |
|||
} |
|||
|
|||
class Client { |
|||
constructor(port, host, protocol = 'tcp', options = void 0) { |
|||
this.id = 0; |
|||
this.port = port; |
|||
this.host = host; |
|||
this.callbackMessageQueue = {}; |
|||
this.subscribe = new EventEmitter(); |
|||
this.conn = initSocket(this, protocol, options); |
|||
this.mp = new util.MessageParser((body, n) => { |
|||
this.onMessage(body, n); |
|||
}); |
|||
this.status = 0; |
|||
} |
|||
|
|||
connect() { |
|||
if (this.status) { |
|||
return Promise.resolve(); |
|||
} |
|||
|
|||
this.status = 1; |
|||
|
|||
return new Promise((resolve, reject) => { |
|||
const errorHandler = (e) => reject(e) |
|||
|
|||
this.conn.connect(this.port, this.host, () => { |
|||
this.conn.removeListener('error', errorHandler); |
|||
resolve(); |
|||
}); |
|||
this.conn.on('error', errorHandler); |
|||
}); |
|||
} |
|||
|
|||
close() { |
|||
if (!this.status) { |
|||
return |
|||
} |
|||
|
|||
this.conn.end(); |
|||
this.conn.destroy(); |
|||
this.status = 0; |
|||
} |
|||
|
|||
request(method, params) { |
|||
if (!this.status) { |
|||
return Promise.reject(new Error('ESOCKET')); |
|||
} |
|||
|
|||
return new Promise((resolve, reject) => { |
|||
const id = ++this.id; |
|||
const content = util.makeRequest(method, params, id); |
|||
|
|||
this.callbackMessageQueue[id] = util.createPromiseResult(resolve, reject); |
|||
this.conn.write(`${content}\n`); |
|||
}); |
|||
} |
|||
|
|||
response(msg) { |
|||
const callback = this.callbackMessageQueue[msg.id]; |
|||
|
|||
if (callback) { |
|||
delete this.callbackMessageQueue[msg.id]; |
|||
|
|||
if (msg.error) { |
|||
callback(msg.error); |
|||
} else { |
|||
callback(null, msg.result); |
|||
} |
|||
} else { |
|||
// can't get callback
|
|||
} |
|||
} |
|||
|
|||
onMessage(body, n) { |
|||
const msg = JSON.parse(body); |
|||
|
|||
if (msg instanceof Array) { |
|||
// don't support batch request
|
|||
} else { |
|||
if (msg.id !== void 0) { |
|||
this.response(msg); |
|||
} else { |
|||
this.subscribe.emit(msg.method, msg.params); |
|||
} |
|||
} |
|||
} |
|||
|
|||
onConnect() { |
|||
} |
|||
|
|||
onClose() { |
|||
Object.keys(this.callbackMessageQueue).forEach((key) => { |
|||
this.callbackMessageQueue[key](new Error('close connect')); |
|||
delete this.callbackMessageQueue[key]; |
|||
}); |
|||
} |
|||
|
|||
onReceive(chunk) { |
|||
this.mp.run(chunk); |
|||
} |
|||
|
|||
onEnd() { |
|||
} |
|||
|
|||
onError(e) { |
|||
} |
|||
} |
|||
|
|||
class ElectrumJSCore extends Client { |
|||
constructor(protocol, port, host, options) { |
|||
super(protocol, port, host, options); |
|||
} |
|||
|
|||
onClose() { |
|||
super.onClose(); |
|||
const list = [ |
|||
'server.peers.subscribe', |
|||
'blockchain.numblocks.subscribe', |
|||
'blockchain.headers.subscribe', |
|||
'blockchain.address.subscribe' |
|||
]; |
|||
|
|||
list.forEach(event => this.subscribe.removeAllListeners(event)); |
|||
} |
|||
|
|||
// ref: http://docs.electrum.org/en/latest/protocol.html
|
|||
serverVersion(client_name, protocol_version) { |
|||
return this.request('server.version', [client_name, protocol_version]); |
|||
} |
|||
|
|||
serverBanner() { |
|||
return this.request('server.banner', []); |
|||
} |
|||
|
|||
serverDonationAddress() { |
|||
return this.request('server.donation_address', []); |
|||
} |
|||
|
|||
serverPeersSubscribe() { |
|||
return this.request('server.peers.subscribe', []); |
|||
} |
|||
|
|||
blockchainAddressGetBalance(address) { |
|||
return this.request('blockchain.address.get_balance', [address]); |
|||
} |
|||
|
|||
blockchainAddressGetHistory(address) { |
|||
return this.request('blockchain.address.get_history', [address]); |
|||
} |
|||
|
|||
blockchainAddressGetMempool(address) { |
|||
return this.request('blockchain.address.get_mempool', [address]); |
|||
} |
|||
|
|||
blockchainAddressListunspent(address) { |
|||
return this.request('blockchain.address.listunspent', [address]); |
|||
} |
|||
|
|||
blockchainBlockGetHeader(height) { |
|||
return this.request('blockchain.block.get_header', [height]); |
|||
} |
|||
|
|||
blockchainBlockGetChunk(index) { |
|||
return this.request('blockchain.block.get_chunk', [index]); |
|||
} |
|||
|
|||
blockchainEstimatefee(number) { |
|||
return this.request('blockchain.estimatefee', [number]); |
|||
} |
|||
|
|||
blockchainHeadersSubscribe() { |
|||
return this.request('blockchain.headers.subscribe', []); |
|||
} |
|||
|
|||
blockchainNumblocksSubscribe() { |
|||
return this.request('blockchain.numblocks.subscribe', []); |
|||
} |
|||
|
|||
blockchainRelayfee() { |
|||
return this.request('blockchain.relayfee', []); |
|||
} |
|||
|
|||
blockchainTransactionBroadcast(rawtx) { |
|||
return this.request('blockchain.transaction.broadcast', [rawtx]); |
|||
} |
|||
|
|||
blockchainTransactionGet(tx_hash, height) { |
|||
return this.request('blockchain.transaction.get', [tx_hash, height]); |
|||
} |
|||
|
|||
blockchainTransactionGetMerkle(tx_hash, height) { |
|||
return this.request('blockchain.transaction.get_merkle', [tx_hash, height]); |
|||
} |
|||
} |
|||
|
|||
module.exports = ElectrumJSCore; |
@ -0,0 +1,205 @@ |
|||
'use strict' |
|||
var bitcoin = require('bitcoinjs-lib'); |
|||
|
|||
var networks = exports; |
|||
Object.keys(bitcoin.networks).forEach(function(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
|
|||
} |
|||
|
|||
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
|
|||
}; |
|||
|
|||
// 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
|
|||
}; |
|||
|
|||
|
|||
// 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
|
|||
}; |
|||
|
|||
// 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
|
|||
}; |
|||
|
|||
// 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
|
|||
}; |
|||
|
|||
// 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, |
|||
}; |
|||
|
|||
networks.viacoin = { |
|||
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, |
|||
}; |
|||
|
|||
networks.namecoin = { |
|||
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, |
|||
}; |
|||
|
|||
networks.digibyte = { |
|||
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, |
|||
}; |
|||
|
|||
networks.argentum = { |
|||
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, |
|||
};*/ |
@ -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'); |
|||
|
|||
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; |
|||
} |
@ -1,197 +0,0 @@ |
|||
var app = require('http').createServer(handler), |
|||
io = require('socket.io')(app), |
|||
fs = require('fs'), |
|||
request = require('request'), |
|||
progress = require('request-progress'); |
|||
const path = require('path'), |
|||
url = require('url'), |
|||
os = require('os'), |
|||
sha256 = require('sha256'), |
|||
crypto = require('crypto'); |
|||
|
|||
Promise = require('bluebird'); |
|||
|
|||
app.listen(3000); |
|||
|
|||
function handler (req, res) { |
|||
fs.readFile(__dirname + '/index.html', |
|||
function (err, data) { |
|||
if (err) { |
|||
res.writeHead(500); |
|||
return res.end('Error loading index.html'); |
|||
} |
|||
|
|||
res.writeHead(200); |
|||
res.end(data); |
|||
}); |
|||
} |
|||
|
|||
if (os.platform() === 'darwin') { |
|||
var PARAMS_DIR = process.env.HOME + '/Library/Application Support/ZcashParams'; |
|||
} |
|||
if (os.platform() === 'linux') { |
|||
var PARAMS_DIR = process.env.HOME + '/.zcash-params'; |
|||
} |
|||
|
|||
var SPROUT_FILES_DATA = [ |
|||
{ |
|||
'file': 'sprout-proving.key', |
|||
'hash': '8bc20a7f013b2b58970cddd2e7ea028975c88ae7ceb9259a5344a16bc2c0eef7' |
|||
}, { |
|||
'file': 'sprout-verifying.key', |
|||
'hash': '4bd498dae0aacfd8e98dc306338d017d9c08dd0918ead18172bd0aec2fc5df82' |
|||
} |
|||
]; |
|||
var SPROUT_DL_URL = 'https://z.cash/downloads/'; |
|||
|
|||
SPROUT_FILES_DATA.forEach(function(value, index) { |
|||
fs.exists(value.file, function(exists) { |
|||
if (exists) { |
|||
console.log(value.file + ' already exists at location.'); |
|||
|
|||
var tmphas, |
|||
fd = fs.createReadStream(value.file), |
|||
hash = crypto.createHash('sha256'); |
|||
|
|||
hash.setEncoding('hex'); |
|||
|
|||
fd.on('end', function() { |
|||
hash.end(); |
|||
|
|||
console.log('hash is: '); |
|||
console.log(hash.read()); // the desired sha1sum
|
|||
console.log(value.hash); |
|||
|
|||
tmphash = hash.read(); |
|||
if (hash.read() === value.hash) { |
|||
console.log('File SHA256 sum matches.'); |
|||
} else { |
|||
console.log('File SHA256 sum does not match.'); |
|||
} |
|||
}); |
|||
// read all file and pipe it (write it) to the hash object
|
|||
fd.pipe(hash); |
|||
} else { |
|||
var DLFile = function() { |
|||
return new Promise(function(resolve, reject) { |
|||
console.log('file not there.'); |
|||
|
|||
progress(request(SPROUT_DL_URL + value.file), {}) |
|||
.on('progress', function (state) { |
|||
console.log('progress', state); |
|||
}) |
|||
.on('error', function (err) { |
|||
console.log(err); |
|||
}) |
|||
.on('end', function () { |
|||
// Do something after request finishes
|
|||
console.log('download finished.'); |
|||
var result = 'File ==> ' + value.file + ': DOWNLOADED'; |
|||
}) |
|||
.pipe(fs.createWriteStream(value.file)); |
|||
|
|||
console.log(result); |
|||
resolve(result); |
|||
}) |
|||
} |
|||
var CheckFileSHA = function() { |
|||
return new Promise(function(resolve, reject) { |
|||
var fd = fs.createReadStream(value.file), |
|||
hash = crypto.createHash('sha256'); |
|||
|
|||
hash.setEncoding('hex'); |
|||
|
|||
fd.on('end', function() { |
|||
hash.end(); |
|||
|
|||
console.log('hash is: '); |
|||
console.log(hash.read()); // the desired sha1sum
|
|||
console.log(value.hash); |
|||
|
|||
if (hash.read() === value.hash) { |
|||
console.log('File SHA256 sum matches.'); |
|||
} else { |
|||
console.log('File SHA256 sum does not match.'); |
|||
} |
|||
}); |
|||
// read all file and pipe it (write it) to the hash object
|
|||
fd.pipe(hash); |
|||
|
|||
var result = 'SHA256 SUM Check: DONE'; |
|||
|
|||
console.log(result); |
|||
resolve(result); |
|||
}); |
|||
} |
|||
|
|||
DLFile() |
|||
.then(function(result) { |
|||
return CheckFileSHA(); |
|||
}); |
|||
} |
|||
}); |
|||
}); |
|||
|
|||
function CheckSHASum(file, hashstr) { |
|||
console.log(hashstr); |
|||
var shasum; |
|||
|
|||
// the file you want to get the hash
|
|||
if (shasum === hashstr ) { |
|||
return true; |
|||
} else { |
|||
return false; |
|||
} |
|||
} |
|||
|
|||
/*var CheckFileExists = function() { |
|||
|
|||
return new Promise(function(resolve, reject) { |
|||
|
|||
if (path.existsSync('foo.txt')) {} |
|||
var result = 'Connecting To Pm2: done' |
|||
|
|||
console.log(result) |
|||
resolve(result); |
|||
}) |
|||
} |
|||
|
|||
var DLFile = function() { |
|||
|
|||
return new Promise(function(resolve, reject) { |
|||
var result = 'Killing Pm2: done' |
|||
|
|||
setTimeout(function() { |
|||
console.log(result) |
|||
resolve(result); |
|||
}, 2000) |
|||
}) |
|||
} |
|||
|
|||
var CheckSHASum = function() { |
|||
|
|||
return new Promise(function(resolve, reject) { |
|||
var result = 'Hiding Main Window: done' |
|||
|
|||
console.log(result) |
|||
resolve(result); |
|||
}) |
|||
} |
|||
|
|||
var MoveFile = function() { |
|||
|
|||
return new Promise(function(resolve, reject) { |
|||
var result = 'Quiting App: done' |
|||
|
|||
console.log(result) |
|||
resolve(result); |
|||
}) |
|||
} |
|||
|
|||
ConnectToPm2() |
|||
.then(function(result) { |
|||
return KillPm2(); |
|||
}) |
|||
.then(HideMainWindow) |
|||
.then(QuitApp) |
|||
*/ |
@ -1,45 +0,0 @@ |
|||
const fs = require('fs-extra'), |
|||
request = require('request'), |
|||
async = require('async'), |
|||
url = require('url'); |
|||
|
|||
let mock = {}; |
|||
|
|||
mock.setVar = function(variable, value) { |
|||
mock[variable] = value; |
|||
} |
|||
|
|||
mock.get = function(req, res, next) { |
|||
const _url = req.query.url; |
|||
|
|||
if (_url.indexOf('/InstantDEX/allcoins') > -1) { |
|||
res.end(JSON.stringify({ |
|||
'native': [], |
|||
'basilisk': [ 'KMD', 'BTC'], |
|||
'full':[], |
|||
'tag': '18430609759584422959' |
|||
})); |
|||
} |
|||
if (_url.indexOf('/bitcoinrpc/getaddressesbyaccount') > -1) { |
|||
console.log(_url.indexOf('/bitcoinrpc/getaddressesbyaccount')); |
|||
res.end(JSON.stringify({ |
|||
'result': [ |
|||
"RDbGxL8QYdEp8sMULaVZS2E6XThcTKT9Jd", |
|||
"RL4orv22Xch7PhM5w9jUHhVQhX6kF6GkfS", |
|||
"RUrxvPTEKGWEDTvAtgiqbUTTFE53Xdpj8a", |
|||
"RPJoLDa7RezvfUUBr7R3U8wrP16AgUsNw3", |
|||
"RQPTpRJEeafNx5hkDzgjcsPyU4E8RFVApT" |
|||
] |
|||
})); |
|||
} |
|||
if (_url.indexOf('/api/dex/listunspent') > -1 || |
|||
_url.indexOf('/api/dex/listtransactions') > -1 || |
|||
_url.indexOf('/api/ss/getbalance') > -1 || |
|||
_url.indexOf('/api/ww/refresh') > -1) { |
|||
res.end(JSON.stringify({ |
|||
'some key': 'some value' |
|||
})); |
|||
} |
|||
} |
|||
|
|||
module.exports = mock; |
@ -0,0 +1,98 @@ |
|||
const nativeCoind = { |
|||
'btc': { |
|||
name: 'Bitcoin', |
|||
bin: 'bitcoin', |
|||
fullMode: true, |
|||
port: 8332, |
|||
}, |
|||
'btcd': { |
|||
name: 'BitcoinDark', |
|||
bin: 'bitcoindarkd', |
|||
fullMode: true, |
|||
port: 14632, |
|||
}, |
|||
'ltc': { |
|||
name: 'Litecoin', |
|||
bin: 'litecoin', |
|||
fullMode: true, |
|||
port: 9332, |
|||
}, |
|||
'sys': { |
|||
name: 'Syscoin', |
|||
bin: 'syscoin', |
|||
fullMode: true, |
|||
port: 8368, |
|||
}, |
|||
'uno': { |
|||
name: 'Unobtanium', |
|||
bin: 'unobtanium', |
|||
fullMode: true, |
|||
port: 65535, |
|||
}, |
|||
'nmc': { |
|||
name: 'Namecoin', |
|||
bin: 'namecoin', |
|||
fullMode: true, |
|||
port: 8336, |
|||
}, |
|||
'game': { |
|||
name: 'GameCredits', |
|||
bin: 'gamecredits', |
|||
fullMode: true, |
|||
port: 40001, |
|||
}, |
|||
'mzc': { |
|||
name: 'MazaCoin', |
|||
bin: 'maza', |
|||
fullMode: true, |
|||
port: 12832, |
|||
}, |
|||
'frk': { |
|||
name: 'Franko', |
|||
bin: 'franko', |
|||
fullMode: true, |
|||
port: 7913, |
|||
}, |
|||
'doge': { |
|||
name: 'Dogecoin', |
|||
bin: 'dogecoin', |
|||
fullMode: true, |
|||
port: 22555, |
|||
}, |
|||
'dgb': { |
|||
name: 'Digibyte', |
|||
bin: 'digibyte', |
|||
port: 14022, |
|||
}, |
|||
'zet': { |
|||
name: 'Zetacoin', |
|||
bin: 'zetacoin', |
|||
fullMode: true, |
|||
port: 17335, |
|||
}, |
|||
'btm': { |
|||
name: 'Bitmark', |
|||
bin: 'bitmark', |
|||
fullMode: true, |
|||
port: 9266, |
|||
}, |
|||
'carb': { |
|||
name: 'Carboncoin', |
|||
bin: 'carboncoin', |
|||
fullMode: true, |
|||
port: 9351, |
|||
}, |
|||
'anc': { |
|||
name: 'Anoncoin', |
|||
bin: 'anoncoin', |
|||
fullMode: true, |
|||
port: 28332, |
|||
}, |
|||
'lbc': { |
|||
name: 'LBRY Credits', |
|||
bin: 'lbrycrd', |
|||
port: 9245, |
|||
} |
|||
}; |
|||
|
|||
module.exports = nativeCoind; |
File diff suppressed because it is too large
@ -0,0 +1,103 @@ |
|||
module.exports = (shepherd) => { |
|||
shepherd.startSPV = (coin) => { |
|||
if (coin === 'KMD+REVS+JUMBLR') { |
|||
shepherd.addElectrumCoin('KMD'); |
|||
shepherd.addElectrumCoin('REVS'); |
|||
shepherd.addElectrumCoin('JUMBLR'); |
|||
} else { |
|||
shepherd.addElectrumCoin(coin); |
|||
} |
|||
} |
|||
|
|||
shepherd.startKMDNative = (selection, isManual) => { |
|||
if (isManual) { |
|||
shepherd.kmdMainPassiveMode = true; |
|||
} |
|||
|
|||
if (selection === 'KMD') { |
|||
const herdData = { |
|||
'ac_name': 'komodod', |
|||
'ac_options': [ |
|||
'-daemon=0', |
|||
'-addnode=78.47.196.146', |
|||
], |
|||
}; |
|||
|
|||
const options = { |
|||
url: `http://127.0.0.1:${shepherd.appConfig.agamaPort}/shepherd/herd`, |
|||
method: 'POST', |
|||
headers: { |
|||
'Content-Type': 'application/json', |
|||
}, |
|||
body: JSON.stringify({ |
|||
herd: 'komodod', |
|||
options: herdData, |
|||
}) |
|||
}; |
|||
|
|||
shepherd.request(options, (error, response, body) => { |
|||
if (response && |
|||
response.statusCode && |
|||
response.statusCode === 200) { |
|||
//resolve(body);
|
|||
} else { |
|||
//resolve(body);
|
|||
} |
|||
}); |
|||
} else { |
|||
const herdData = [{ |
|||
'ac_name': 'komodod', |
|||
'ac_options': [ |
|||
'-daemon=0', |
|||
'-addnode=78.47.196.146', |
|||
] |
|||
}, { |
|||
'ac_name': 'REVS', |
|||
'ac_options': [ |
|||
'-daemon=0', |
|||
'-server', |
|||
`-ac_name=REVS`, |
|||
'-addnode=78.47.196.146', |
|||
'-ac_supply=1300000' |
|||
] |
|||
}, { |
|||
'ac_name': 'JUMBLR', |
|||
'ac_options': [ |
|||
'-daemon=0', |
|||
'-server', |
|||
`-ac_name=JUMBLR`, |
|||
'-addnode=78.47.196.146', |
|||
'-ac_supply=999999' |
|||
] |
|||
}]; |
|||
|
|||
for (let i = 0; i < herdData.length; i++) { |
|||
setTimeout(() => { |
|||
const options = { |
|||
url: `http://127.0.0.1:${shepherd.appConfig.agamaPort}/shepherd/herd`, |
|||
method: 'POST', |
|||
headers: { |
|||
'Content-Type': 'application/json', |
|||
}, |
|||
body: JSON.stringify({ |
|||
herd: 'komodod', |
|||
options: herdData[i], |
|||
}) |
|||
}; |
|||
|
|||
shepherd.request(options, (error, response, body) => { |
|||
if (response && |
|||
response.statusCode && |
|||
response.statusCode === 200) { |
|||
//resolve(body);
|
|||
} else { |
|||
//resolve(body);
|
|||
} |
|||
}); |
|||
}, 100); |
|||
} |
|||
} |
|||
}; |
|||
|
|||
return shepherd; |
|||
}; |
@ -0,0 +1,78 @@ |
|||
const formatBytes = (bytes, decimals) => { |
|||
if (bytes === 0) { |
|||
return '0 Bytes'; |
|||
} |
|||
|
|||
const k = 1000; |
|||
const dm = (decimals + 1) || 3; |
|||
const sizes = [ |
|||
'Bytes', |
|||
'KB', |
|||
'MB', |
|||
'GB', |
|||
'TB', |
|||
'PB', |
|||
'EB', |
|||
'ZB', |
|||
'YB' |
|||
]; |
|||
const i = Math.floor(Math.log(bytes) / Math.log(k)); |
|||
|
|||
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`; |
|||
} |
|||
|
|||
module.exports = (shepherd) => { |
|||
shepherd.SystemInfo = () => { |
|||
const os_data = { |
|||
'totalmem_bytes': shepherd.os.totalmem(), |
|||
'totalmem_readable': formatBytes(shepherd.os.totalmem()), |
|||
'arch': shepherd.os.arch(), |
|||
'cpu': shepherd.os.cpus()[0].model, |
|||
'cpu_cores': shepherd.os.cpus().length, |
|||
'platform': shepherd.os.platform(), |
|||
'os_release': shepherd.os.release(), |
|||
'os_type': shepherd.os.type(), |
|||
}; |
|||
|
|||
return os_data; |
|||
} |
|||
|
|||
shepherd.appInfo = () => { |
|||
const sysInfo = shepherd.SystemInfo(); |
|||
const releaseInfo = shepherd.appBasicInfo; |
|||
const dirs = { |
|||
agamaDir: shepherd.agamaDir, |
|||
komodoDir: shepherd.komodoDir, |
|||
komododBin: shepherd.komododBin, |
|||
configLocation: `${shepherd.agamaDir}/config.json`, |
|||
cacheLocation: `${shepherd.agamaDir}/shepherd`, |
|||
}; |
|||
|
|||
return { |
|||
sysInfo, |
|||
releaseInfo, |
|||
dirs, |
|||
appSession: shepherd.appSessionHash, |
|||
}; |
|||
} |
|||
|
|||
/* |
|||
* type: GET |
|||
* |
|||
*/ |
|||
shepherd.get('/sysinfo', (req, res, next) => { |
|||
const obj = shepherd.SystemInfo(); |
|||
res.send(obj); |
|||
}); |
|||
|
|||
/* |
|||
* type: GET |
|||
* |
|||
*/ |
|||
shepherd.get('/appinfo', (req, res, next) => { |
|||
const obj = shepherd.appInfo(); |
|||
res.send(obj); |
|||
}); |
|||
|
|||
return shepherd; |
|||
}; |
@ -0,0 +1,31 @@ |
|||
module.exports = (shepherd) => { |
|||
/* |
|||
* type: GET |
|||
* |
|||
*/ |
|||
shepherd.get('/auth/status', (req, res, next) => { // not finished
|
|||
let successObj; |
|||
let _status = false; |
|||
|
|||
if (Object.keys(shepherd.coindInstanceRegistry).length) { |
|||
if (Object.keys(shepherd.electrumCoins).length > 1 && |
|||
shepherd.electrumCoins.auth) { |
|||
_status = true; |
|||
} else if (Object.keys(shepherd.electrumCoins).length === 1 && !shepherd.electrumCoins.auth) { |
|||
_status = true; |
|||
} |
|||
} else if (Object.keys(shepherd.electrumCoins).length > 1 && shepherd.electrumCoins.auth) { |
|||
_status = true; |
|||
} else if (Object.keys(shepherd.electrumCoins).length === 1 && !Object.keys(shepherd.coindInstanceRegistry).length) { |
|||
_status = true; |
|||
} |
|||
|
|||
successObj = { |
|||
status: _status ? 'unlocked' : 'locked', |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
}); |
|||
|
|||
return shepherd; |
|||
}; |
@ -0,0 +1,239 @@ |
|||
module.exports = (shepherd) => { |
|||
shepherd.testClearAll = () => { |
|||
return new shepherd.Promise((resolve, reject) => { |
|||
shepherd.fs.removeSync(`${iguanaTestDir}`); |
|||
resolve('done'); |
|||
}); |
|||
} |
|||
|
|||
shepherd.testBins = (daemonName) => { |
|||
return new shepherd.Promise((resolve, reject) => { |
|||
const _bins = { |
|||
komodod: shepherd.komododBin, |
|||
komodoCli: shepherd.komodocliBin, |
|||
}; |
|||
const _arg = null; |
|||
let _pid; |
|||
|
|||
shepherd.log('testBins exec ' + _bins[daemonName]); |
|||
|
|||
if (!shepherd.fs.existsSync(shepherd.agamaTestDir)) { |
|||
shepherd.fs.mkdirSync(shepherd.agamaTestDir); |
|||
} |
|||
|
|||
try { |
|||
shepherd._fs.access(`${shepherd.agamaTestDir}/${daemonName}Test.log`, shepherd.fs.constants.R_OK, (err) => { |
|||
if (!err) { |
|||
try { |
|||
shepherd._fs.unlinkSync(`${shepherd.agamaTestDir}/${daemonName}Test.log`); |
|||
} catch (e) {} |
|||
} else { |
|||
shepherd.log(`path ${shepherd.agamaTestDir}/${daemonName}Test.log doesnt exist`); |
|||
} |
|||
}); |
|||
} catch (e) {} |
|||
|
|||
if (daemonName === 'komodod') { |
|||
try { |
|||
shepherd._fs.access(`${iguanaTestDir}/debug.log`, shepherd.fs.constants.R_OK, (err) => { |
|||
if (!err) { |
|||
shepherd._fs.unlinkSync(`${iguanaTestDir}/db.log`); |
|||
shepherd._fs.unlinkSync(`${iguanaTestDir}/debug.log`); |
|||
shepherd._fs.unlinkSync(`${iguanaTestDir}/komodo.conf`); |
|||
shepherd._fs.unlinkSync(`${iguanaTestDir}/komodod.pid`); |
|||
shepherd._fs.unlinkSync(`${iguanaTestDir}/komodostate`); |
|||
shepherd._fs.unlinkSync(`${iguanaTestDir}/realtime`); |
|||
shepherd._fs.unlinkSync(`${iguanaTestDir}/wallet.dat`); |
|||
shepherd._fs.unlinkSync(`${iguanaTestDir}/.lock`); |
|||
shepherd.fs.removeSync(`${iguanaTestDir}/blocks`); |
|||
shepherd.fs.removeSync(`${iguanaTestDir}/chainstate`); |
|||
shepherd.fs.removeSync(`${iguanaTestDir}/database`); |
|||
execKomodod(); |
|||
} else { |
|||
shepherd.log(`test: nothing to remove in ${iguanaTestDir}`); |
|||
execKomodod(); |
|||
} |
|||
}); |
|||
} catch (e) {} |
|||
|
|||
const execKomodod = () => { |
|||
let _komododTest = { |
|||
port: 'unknown', |
|||
start: 'unknown', |
|||
getinfo: 'unknown', |
|||
errors: { |
|||
assertFailed: false, |
|||
zcashParams: false, |
|||
}, |
|||
}; |
|||
const _komodoConf = 'rpcuser=user83f3afba8d714993\n' + |
|||
'rpcpassword=0d4430ca1543833e35bce5a0cc9e16b3\n' + |
|||
'server=1\n' + |
|||
'addnode=78.47.196.146\n' + |
|||
'addnode=5.9.102.210\n' + |
|||
'addnode=178.63.69.164\n' + |
|||
'addnode=88.198.65.74\n' + |
|||
'addnode=5.9.122.241\n' + |
|||
'addnode=144.76.94.3\n' + |
|||
'addnode=144.76.94.38\n' + |
|||
'addnode=89.248.166.91\n' + |
|||
'addnode=148.251.57.148\n' + |
|||
'addnode=149.56.28.84\n' + |
|||
'addnode=176.9.26.39\n' + |
|||
'addnode=94.102.63.199\n' + |
|||
'addnode=94.102.63.200\n' + |
|||
'addnode=104.255.64.3\n' + |
|||
'addnode=221.121.144.140\n' + |
|||
'addnode=103.18.58.150\n' + |
|||
'addnode=103.18.58.146\n' + |
|||
'addnode=213.202.253.10\n' + |
|||
'addnode=185.106.121.32\n' + |
|||
'addnode=27.100.36.201\n'; |
|||
|
|||
shepherd.fs.writeFile(`${iguanaTestDir}/komodo.conf`, _komodoConf, (err) => { |
|||
if (err) { |
|||
shepherd.log(`test: error writing komodo conf in ${iguanaTestDir}`); |
|||
} |
|||
}); |
|||
|
|||
shepherd.portscanner.checkPortStatus('7771', '127.0.0.1', (error, status) => { |
|||
// Status is 'open' if currently in use or 'closed' if available
|
|||
if (status === 'closed') { |
|||
_komododTest.port = 'passed'; |
|||
} else { |
|||
_komododTest.port = 'failed'; |
|||
} |
|||
}); |
|||
|
|||
/*pm2.connect(true,function(err) { //start up pm2 god |
|||
if (err) { |
|||
shepherd.error(err); |
|||
process.exit(2); |
|||
} |
|||
|
|||
pm2.start({ |
|||
script: shepherd.komododBin, // path to binary
|
|||
name: 'komodod', |
|||
exec_mode : 'fork', |
|||
args: [ |
|||
'-daemon=0', |
|||
'-addnode=78.47.196.146', |
|||
`-datadir=${iguanaTestDir}/` |
|||
], |
|||
output: `${iguanaTestDir}/komododTest.log`, |
|||
mergeLogs: true, |
|||
}, function(err, apps) { |
|||
if (apps[0] && |
|||
apps[0].process && |
|||
apps[0].process.pid) { |
|||
_komododTest.start = 'success'; |
|||
shepherd.log(`test: got komodod instance pid = ${apps[0].process.pid}`); |
|||
shepherd.writeLog(`test: komodod started with pid ${apps[0].process.pid}`); |
|||
} else { |
|||
_komododTest.start = 'failed'; |
|||
shepherd.log(`unable to start komodod`); |
|||
} |
|||
|
|||
pm2.disconnect(); // Disconnect from PM2
|
|||
if (err) { |
|||
shepherd.writeLog(`test: error starting komodod`); |
|||
shepherd.log(`komodod fork err: ${err}`); |
|||
// throw err;
|
|||
} |
|||
}); |
|||
});*/ |
|||
|
|||
setTimeout(() => { |
|||
const options = { |
|||
url: `http://localhost:7771`, |
|||
method: 'POST', |
|||
auth: { |
|||
user: 'user83f3afba8d714993', |
|||
pass: '0d4430ca1543833e35bce5a0cc9e16b3', |
|||
}, |
|||
body: JSON.stringify({ |
|||
agent: 'bitcoinrpc', |
|||
method: 'getinfo', |
|||
}), |
|||
}; |
|||
|
|||
shepherd.request(options, (error, response, body) => { |
|||
if (response && |
|||
response.statusCode && |
|||
response.statusCode === 200) { |
|||
// res.end(body);
|
|||
shepherd.log(JSON.stringify(body, null, '\t')); |
|||
} else { |
|||
// res.end(body);
|
|||
shepherd.log(JSON.stringify(body, null, '\t')); |
|||
} |
|||
}); |
|||
}, 10000); |
|||
|
|||
setTimeout(() => { |
|||
pm2.delete('komodod'); |
|||
resolve(_komododTest); |
|||
}, 20000); |
|||
} |
|||
// komodod debug.log hooks
|
|||
|
|||
//"{\"result\":{\"version\":1000850,\"protocolversion\":170002,\"KMDversion\":\"0.1.1\",\"notarized\":0,\"notarizedhash\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"notarizedtxid\":\"0000000000000000000000000000000000000000000000000000000000000000\",\"notarizedtxid_height\":\"mempool\",\"notarized_confirms\":0,\"walletversion\":60000,\"balance\":0.00000000,\"interest\":0.00000000,\"blocks\":128,\"longestchain\":472331,\"timeoffset\":0,\"tiptime\":1473827710,\"connections\":1,\"proxy\":\"\",\"difficulty\":1,\"testnet\":false,\"keypoololdest\":1504118047,\"keypoolsize\":101,\"paytxfee\":0.00000000,\"relayfee\":0.00000100,\"errors\":\"\"},\"error\":null,\"id\":null}\n"
|
|||
|
|||
//2017-08-30 17:51:33 Error: Cannot find the Zcash network parameters in the following directory:
|
|||
//"/home/pbca/.zcash-params"
|
|||
//Please run 'zcash-fetch-params' or './zcutil/fetch-params.sh' and then restart.
|
|||
//EXCEPTION: St13runtime_error
|
|||
//Assertion failed.
|
|||
//2017-08-30 17:51:14 Using config file /home/pbca/.iguana/test/komodo.conf
|
|||
//2017-08-30 18:23:43 UpdateTip: new best=0a47c1323f393650f7221c217d19d149d002d35444f47fde61be2dd90fbde8e6 height=1 log2_work=5.0874628 tx=2 date=2016-09-13 19:04:01 progress=0.000001 cache=0.0MiB(1tx)
|
|||
//2017-08-30 18:23:43 UpdateTip: new best=05076a4e1fc9af0f5fda690257b17ae20c12d4796dfba1624804d012c9ec00be height=2 log2_work=5.6724253 tx=3 date=2016-09-13 19:05:28 progress=0.000001 cache=0.0MiB(2tx)
|
|||
|
|||
/*shepherd.execFile(`${shepherd.komododBin}`, _arg, { |
|||
maxBuffer: 1024 * 10000 // 10 mb
|
|||
}, function(error, stdout, stderr) { |
|||
shepherd.writeLog(`stdout: ${stdout}`); |
|||
shepherd.writeLog(`stderr: ${stderr}`); |
|||
|
|||
if (error !== null) { |
|||
console.log(`exec error: ${error}`); |
|||
shepherd.writeLog(`exec error: ${error}`); |
|||
|
|||
if (error.toString().indexOf('using -reindex') > -1) { |
|||
shepherd.io.emit('service', { |
|||
komodod: { |
|||
error: 'run -reindex', |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
});*/ |
|||
} |
|||
}); |
|||
} |
|||
|
|||
// komodod datadir location test
|
|||
shepherd.testLocation = (path) => { |
|||
return new shepherd.Promise((resolve, reject) => { |
|||
if (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; |
|||
}; |
@ -0,0 +1,70 @@ |
|||
module.exports = (shepherd) => { |
|||
// osx and linux
|
|||
shepherd.binFixRights = () => { |
|||
const osPlatform = shepherd.os.platform(); |
|||
const _bins = [ |
|||
shepherd.komododBin, |
|||
shepherd.komodocliBin |
|||
]; |
|||
|
|||
if (osPlatform === 'darwin' || |
|||
osPlatform === 'linux') { |
|||
for (let i = 0; i < _bins.length; i++) { |
|||
shepherd._fs.stat(_bins[i], (err, stat) => { |
|||
if (!err) { |
|||
if (parseInt(stat.mode.toString(8), 10) !== 100775) { |
|||
shepherd.log(`${_bins[i]} fix permissions`); |
|||
shepherd.fsnode.chmodSync(_bins[i], '0775'); |
|||
} |
|||
} else { |
|||
shepherd.log(`error: ${_bins[i]} not found`); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
|
|||
shepherd.killRogueProcess = (processName) => { |
|||
// kill rogue process copies on start
|
|||
let processGrep; |
|||
const osPlatform = shepherd.os.platform(); |
|||
|
|||
switch (osPlatform) { |
|||
case 'darwin': |
|||
processGrep = "ps -p $(ps -A | grep -m1 " + processName + " | awk '{print $1}') | grep -i " + processName; |
|||
break; |
|||
case 'linux': |
|||
processGrep = 'ps -p $(pidof ' + processName + ') | grep -i ' + processName; |
|||
break; |
|||
case 'win32': |
|||
processGrep = 'tasklist'; |
|||
break; |
|||
} |
|||
|
|||
shepherd.exec(processGrep, (error, stdout, stderr) => { |
|||
if (stdout.indexOf(processName) > -1) { |
|||
const pkillCmd = osPlatform === 'win32' ? `taskkill /f /im ${processName}.exe` : `pkill -15 ${processName}`; |
|||
|
|||
shepherd.log(`found another ${processName} process(es)`); |
|||
shepherd.writeLog(`found another ${processName} process(es)`); |
|||
|
|||
shepherd.exec(pkillCmd, (error, stdout, stderr) => { |
|||
shepherd.log(`${pkillCmd} is issued`); |
|||
shepherd.writeLog(`${pkillCmd} is issued`); |
|||
|
|||
if (error !== null) { |
|||
shepherd.log(`${pkillCmd} exec error: ${error}`); |
|||
shepherd.writeLog(`${pkillCmd} exec error: ${error}`); |
|||
}; |
|||
}); |
|||
} |
|||
|
|||
if (error !== null) { |
|||
shepherd.log(`${processGrep} exec error: ${error}`); |
|||
shepherd.writeLog(`${processGrep} exec error: ${error}`); |
|||
}; |
|||
}); |
|||
} |
|||
|
|||
return shepherd; |
|||
}; |
@ -0,0 +1,99 @@ |
|||
module.exports = (shepherd) => { |
|||
/* |
|||
* type: GET |
|||
* |
|||
*/ |
|||
shepherd.get('/coindwalletkeys', (req, res, next) => { |
|||
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; |
|||
|
|||
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?'); |
|||
|
|||
successObj = { |
|||
msg: 'error', |
|||
result: 'wallet is encrypted?', |
|||
}; |
|||
|
|||
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); |
|||
} |
|||
} |
|||
|
|||
successObj = { |
|||
msg: 'success', |
|||
result: _keys, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
} |
|||
} |
|||
}); |
|||
} |
|||
}); |
|||
} catch (e) { |
|||
successObj = { |
|||
msg: 'error', |
|||
result: `error reading ${_walletDatLocation}`, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
} |
|||
}); |
|||
|
|||
return shepherd; |
|||
}; |
@ -0,0 +1,31 @@ |
|||
module.exports = (shepherd) => { |
|||
/* |
|||
* type: GET |
|||
* |
|||
*/ |
|||
shepherd.get('/InstantDEX/allcoins', (req, res, next) => { |
|||
let successObj; |
|||
let nativeCoindList = []; |
|||
let electrumCoinsList = []; |
|||
|
|||
for (let key in shepherd.electrumCoins) { |
|||
if (key !== 'auth') { |
|||
electrumCoinsList.push(shepherd.electrumCoins[key].abbr); |
|||
} |
|||
} |
|||
|
|||
for (let key in shepherd.coindInstanceRegistry) { |
|||
nativeCoindList.push(key === 'komodod' ? 'KMD' : key); |
|||
} |
|||
|
|||
successObj = { |
|||
native: nativeCoindList, |
|||
spv: electrumCoinsList, |
|||
total: Object.keys(shepherd.electrumCoins).length - 1 + Object.keys(nativeCoindList).length, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
}); |
|||
|
|||
return shepherd; |
|||
}; |
@ -0,0 +1,71 @@ |
|||
module.exports = (shepherd) => { |
|||
/* |
|||
* type: GET |
|||
* |
|||
*/ |
|||
shepherd.get('/coinslist', (req, res, next) => { |
|||
if (shepherd.fs.existsSync(`${shepherd.agamaDir}/shepherd/coinslist.json`)) { |
|||
shepherd.fs.readFile(`${shepherd.agamaDir}/shepherd/coinslist.json`, 'utf8', (err, data) => { |
|||
if (err) { |
|||
const errorObj = { |
|||
msg: 'error', |
|||
result: err, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(errorObj)); |
|||
} else { |
|||
const successObj = { |
|||
msg: 'success', |
|||
result: data ? JSON.parse(data) : '', |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
} |
|||
}); |
|||
} else { |
|||
const errorObj = { |
|||
msg: 'error', |
|||
result: 'coin list doesn\'t exist', |
|||
}; |
|||
|
|||
res.end(JSON.stringify(errorObj)); |
|||
} |
|||
}); |
|||
|
|||
/* |
|||
* type: POST |
|||
* params: payload |
|||
*/ |
|||
shepherd.post('/coinslist', (req, res, next) => { |
|||
const _payload = req.body.payload; |
|||
|
|||
if (!_payload) { |
|||
const errorObj = { |
|||
msg: 'error', |
|||
result: 'no payload provided', |
|||
}; |
|||
|
|||
res.end(JSON.stringify(errorObj)); |
|||
} else { |
|||
shepherd.fs.writeFile(`${shepherd.agamaDir}/shepherd/coinslist.json`, JSON.stringify(_payload), (err) => { |
|||
if (err) { |
|||
const errorObj = { |
|||
msg: 'error', |
|||
result: err, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(errorObj)); |
|||
} else { |
|||
const successObj = { |
|||
msg: 'success', |
|||
result: 'done', |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
} |
|||
}); |
|||
} |
|||
}); |
|||
|
|||
return shepherd; |
|||
}; |
@ -0,0 +1,57 @@ |
|||
const fs = require('fs-extra'); |
|||
|
|||
module.exports = (shepherd) => { |
|||
shepherd.getMaxconKMDConf = () => { |
|||
return new shepherd.Promise((resolve, reject) => { |
|||
fs.readFile(`${shepherd.komodoDir}/komodo.conf`, 'utf8', (err, data) => { |
|||
if (err) { |
|||
shepherd.log('kmd conf maxconnections param read failed'); |
|||
resolve('unset'); |
|||
} else { |
|||
const _maxcon = data.match(/maxconnections=\s*(.*)/); |
|||
|
|||
if (!_maxcon) { |
|||
shepherd.log('kmd conf maxconnections param is unset'); |
|||
resolve(false); |
|||
} else { |
|||
shepherd.log(`kmd conf maxconnections param is already set to ${_maxcon[1]}`); |
|||
resolve(_maxcon[1]); |
|||
} |
|||
} |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
shepherd.setMaxconKMDConf = (limit) => { |
|||
return new shepherd.Promise((resolve, reject) => { |
|||
fs.readFile(`${shepherd.komodoDir}/komodo.conf`, 'utf8', (err, data) => { |
|||
const _maxconVal = limit ? 1 : 10; |
|||
|
|||
if (err) { |
|||
shepherd.log(`error reading ${shepherd.komodoDir}/komodo.conf`); |
|||
resolve(false); |
|||
} else { |
|||
if (data.indexOf('maxconnections=') > -1) { |
|||
const _maxcon = data.match(/maxconnections=\s*(.*)/); |
|||
|
|||
data = data.replace(`maxconnections=${_maxcon[1]}`, `maxconnections=${_maxconVal}`); |
|||
} else { |
|||
data = `${data}\nmaxconnections=${_maxconVal}\n`; |
|||
} |
|||
|
|||
fs.writeFile(`${shepherd.komodoDir}/komodo.conf`, data, (err) => { |
|||
if (err) { |
|||
shepherd.log(`error writing ${shepherd.komodoDir}/komodo.conf maxconnections=${_maxconVal}`); |
|||
resolve(false); |
|||
} else { |
|||
shepherd.log(`kmd conf maxconnections is set to ${_maxconVal}`); |
|||
resolve(true); |
|||
} |
|||
}); |
|||
} |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
return shepherd; |
|||
}; |
@ -0,0 +1,152 @@ |
|||
module.exports = (shepherd) => { |
|||
shepherd.loadLocalConfig = () => { |
|||
if (shepherd.fs.existsSync(`${shepherd.agamaDir}/config.json`)) { |
|||
let localAppConfig = shepherd.fs.readFileSync(`${shepherd.agamaDir}/config.json`, 'utf8'); |
|||
|
|||
shepherd.log('app config set from local file'); |
|||
shepherd.writeLog('app config set from local file'); |
|||
|
|||
// find diff between local and hardcoded configs
|
|||
// append diff to local config
|
|||
const compareJSON = (obj1, obj2) => { |
|||
let result = {}; |
|||
|
|||
for (let i in obj1) { |
|||
if (!obj2.hasOwnProperty(i)) { |
|||
result[i] = obj1[i]; |
|||
} |
|||
} |
|||
|
|||
return result; |
|||
}; |
|||
|
|||
if (localAppConfig) { |
|||
const compareConfigs = compareJSON(shepherd.appConfig, JSON.parse(localAppConfig)); |
|||
|
|||
if (Object.keys(compareConfigs).length) { |
|||
const newConfig = Object.assign(JSON.parse(localAppConfig), compareConfigs); |
|||
|
|||
shepherd.log('config diff is found, updating local config'); |
|||
shepherd.log('config diff:'); |
|||
shepherd.log(compareConfigs); |
|||
shepherd.writeLog('aconfig diff is found, updating local config'); |
|||
shepherd.writeLog('config diff:'); |
|||
shepherd.writeLog(compareConfigs); |
|||
|
|||
shepherd.saveLocalAppConf(newConfig); |
|||
return newConfig; |
|||
} else { |
|||
return JSON.parse(localAppConfig); |
|||
} |
|||
} else { |
|||
return shepherd.appConfig; |
|||
} |
|||
} else { |
|||
shepherd.log('local config file is not found!'); |
|||
shepherd.writeLog('local config file is not found!'); |
|||
shepherd.saveLocalAppConf(shepherd.appConfig); |
|||
|
|||
return shepherd.appConfig; |
|||
} |
|||
}; |
|||
|
|||
shepherd.saveLocalAppConf = (appSettings) => { |
|||
let appConfFileName = `${shepherd.agamaDir}/config.json`; |
|||
|
|||
shepherd._fs.access(shepherd.agamaDir, shepherd.fs.constants.R_OK, (err) => { |
|||
if (!err) { |
|||
|
|||
const FixFilePermissions = () => { |
|||
return new shepherd.Promise((resolve, reject) => { |
|||
const result = 'config.json file permissions updated to Read/Write'; |
|||
|
|||
shepherd.fsnode.chmodSync(appConfFileName, '0666'); |
|||
|
|||
setTimeout(() => { |
|||
shepherd.log(result); |
|||
shepherd.writeLog(result); |
|||
resolve(result); |
|||
}, 1000); |
|||
}); |
|||
} |
|||
|
|||
const FsWrite = () => { |
|||
return new shepherd.Promise((resolve, reject) => { |
|||
const result = 'config.json write file is done'; |
|||
|
|||
shepherd.fs.writeFile(appConfFileName, |
|||
JSON.stringify(appSettings) |
|||
.replace(/,/g, ',\n') // format json in human readable form
|
|||
.replace(/":/g, '": ') |
|||
.replace(/{/g, '{\n') |
|||
.replace(/}/g, '\n}'), 'utf8', (err) => { |
|||
if (err) |
|||
return shepherd.log(err); |
|||
}); |
|||
|
|||
shepherd.fsnode.chmodSync(appConfFileName, '0666'); |
|||
setTimeout(() => { |
|||
shepherd.log(result); |
|||
shepherd.log(`app conf.json file is created successfully at: ${shepherd.agamaDir}`); |
|||
shepherd.writeLog(`app conf.json file is created successfully at: ${shepherd.agamaDir}`); |
|||
resolve(result); |
|||
}, 2000); |
|||
}); |
|||
} |
|||
|
|||
FsWrite() |
|||
.then(FixFilePermissions()); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
/* |
|||
* type: POST |
|||
* params: payload |
|||
*/ |
|||
shepherd.post('/appconf', (req, res, next) => { |
|||
if (!req.body.payload) { |
|||
const errorObj = { |
|||
msg: 'error', |
|||
result: 'no payload provided', |
|||
}; |
|||
|
|||
res.end(JSON.stringify(errorObj)); |
|||
} else { |
|||
shepherd.saveLocalAppConf(req.body.payload); |
|||
|
|||
const successObj = { |
|||
msg: 'success', |
|||
result: 'config saved', |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
} |
|||
}); |
|||
|
|||
/* |
|||
* type: POST |
|||
* params: none |
|||
*/ |
|||
shepherd.post('/appconf/reset', (req, res, next) => { |
|||
shepherd.saveLocalAppConf(shepherd.defaultAppConfig); |
|||
|
|||
const successObj = { |
|||
msg: 'success', |
|||
result: 'config saved', |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
}); |
|||
|
|||
/* |
|||
* type: GET |
|||
* |
|||
*/ |
|||
shepherd.get('/appconf', (req, res, next) => { |
|||
const obj = shepherd.loadLocalConfig(); |
|||
res.send(obj); |
|||
}); |
|||
|
|||
return shepherd; |
|||
}; |
@ -0,0 +1,908 @@ |
|||
const spawn = require('child_process').spawn; |
|||
const fs = require('fs-extra'); |
|||
const _fs = require('graceful-fs'); |
|||
const fsnode = require('fs'); |
|||
const path = require('path'); |
|||
const os = require('os'); |
|||
const portscanner = require('portscanner'); |
|||
const execFile = require('child_process').execFile; |
|||
const Promise = require('bluebird'); |
|||
const md5 = require('../md5.js'); |
|||
|
|||
module.exports = (shepherd) => { |
|||
const getConf = (flock, coind) => { |
|||
let DaemonConfPath = ''; |
|||
let nativeCoindDir; |
|||
|
|||
if (flock === 'CHIPS') { |
|||
flock = 'chipsd'; |
|||
} |
|||
|
|||
shepherd.log(flock); |
|||
shepherd.log(`getconf coind ${coind}`); |
|||
shepherd.writeLog(`getconf flock: ${flock}`); |
|||
|
|||
if (coind) { |
|||
switch (os.platform()) { |
|||
case 'darwin': |
|||
nativeCoindDir = `${process.env.HOME}/Library/Application Support/${shepherd.nativeCoindList[coind.toLowerCase()].bin}`; |
|||
break; |
|||
case 'linux': |
|||
nativeCoindDir = coind ? `${process.env.HOME}/.${shepherd.nativeCoindList[coind.toLowerCase()].bin.toLowerCase()}` : null; |
|||
break; |
|||
case 'win32': |
|||
nativeCoindDir = coind ? `${process.env.APPDATA}/${shepherd.nativeCoindList[coind.toLowerCase()].bin}` : null; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
switch (flock) { |
|||
case 'komodod': |
|||
DaemonConfPath = shepherd.komodoDir; |
|||
if (os.platform() === 'win32') { |
|||
DaemonConfPath = path.normalize(DaemonConfPath); |
|||
shepherd.log('===>>> SHEPHERD API OUTPUT ===>>>'); |
|||
} |
|||
break; |
|||
case 'zcashd': |
|||
DaemonConfPath = shepherd.ZcashDir; |
|||
if (os.platform() === 'win32') { |
|||
DaemonConfPath = path.normalize(DaemonConfPath); |
|||
} |
|||
break; |
|||
case 'chipsd': |
|||
DaemonConfPath = shepherd.chipsDir; |
|||
if (os.platform() === 'win32') { |
|||
DaemonConfPath = path.normalize(DaemonConfPath); |
|||
} |
|||
break; |
|||
case 'coind': |
|||
DaemonConfPath = os.platform() === 'win32' ? shepherd.path.normalize(`${shepherd.coindRootDir}/${coind.toLowerCase()}`) : `${shepherd.coindRootDir}/${coind.toLowerCase()}`; |
|||
break; |
|||
default: |
|||
DaemonConfPath = `${shepherd.komodoDir}/${flock}`; |
|||
if (os.platform() === 'win32') { |
|||
DaemonConfPath = shepherd.path.normalize(DaemonConfPath); |
|||
} |
|||
} |
|||
|
|||
shepherd.writeLog(`getconf path: ${DaemonConfPath}`); |
|||
shepherd.log(`daemon path: ${DaemonConfPath}`); |
|||
|
|||
return DaemonConfPath; |
|||
} |
|||
|
|||
// TODO: json.stringify wrapper
|
|||
|
|||
const herder = (flock, data, coind) => { |
|||
if (data === undefined) { |
|||
data = 'none'; |
|||
shepherd.log('it is undefined'); |
|||
} |
|||
|
|||
shepherd.log(`herder flock: ${flock} coind: ${coind}`); |
|||
shepherd.log(`selected data: ${JSON.stringify(data, null, '\t')}`); |
|||
|
|||
// TODO: notify gui that reindex/rescan param is used to reflect on the screen
|
|||
// asset chain debug.log unlink
|
|||
if (flock === 'komodod') { |
|||
let kmdDebugLogLocation = (data.ac_name !== 'komodod' ? `${shepherd.komodoDir}/${data.ac_name}` : shepherd.komodoDir) + '/debug.log'; |
|||
|
|||
shepherd.log('komodod flock selected...'); |
|||
shepherd.log(`selected data: ${JSON.stringify(data, null, '\t')}`); |
|||
shepherd.writeLog('komodod flock selected...'); |
|||
shepherd.writeLog(`selected data: ${data}`); |
|||
|
|||
// datadir case, check if komodo/chain folder exists
|
|||
if (shepherd.appConfig.dataDir.length && |
|||
data.ac_name !== 'komodod') { |
|||
const _dir = data.ac_name !== 'komodod' ? `${shepherd.komodoDir}/${data.ac_name}` : shepherd.komodoDir; |
|||
|
|||
try { |
|||
_fs.accessSync(_dir, fs.R_OK | fs.W_OK); |
|||
|
|||
shepherd.log(`komodod datadir ${_dir} exists`); |
|||
} catch (e) { |
|||
shepherd.log(`komodod datadir ${_dir} access err: ${e}`); |
|||
shepherd.log(`attempting to create komodod datadir ${_dir}`); |
|||
|
|||
fs.mkdirSync(_dir); |
|||
|
|||
if (fs.existsSync(_dir)) { |
|||
shepherd.log(`created komodod datadir folder at ${_dir}`); |
|||
} else { |
|||
shepherd.log(`unable to create komodod datadir folder at ${_dir}`); |
|||
} |
|||
} |
|||
} |
|||
|
|||
// truncate debug.log
|
|||
if (!shepherd.kmdMainPassiveMode) { |
|||
try { |
|||
const _confFileAccess = _fs.accessSync(kmdDebugLogLocation, fs.R_OK | fs.W_OK); |
|||
|
|||
if (_confFileAccess) { |
|||
shepherd.log(`error accessing ${kmdDebugLogLocation}`); |
|||
shepherd.writeLog(`error accessing ${kmdDebugLogLocation}`); |
|||
} else { |
|||
try { |
|||
fs.unlinkSync(kmdDebugLogLocation); |
|||
shepherd.log(`truncate ${kmdDebugLogLocation}`); |
|||
shepherd.writeLog(`truncate ${kmdDebugLogLocation}`); |
|||
} catch (e) { |
|||
shepherd.log('cant unlink debug.log'); |
|||
} |
|||
} |
|||
} catch (e) { |
|||
shepherd.log(`komodod debug.log access err: ${e}`); |
|||
shepherd.writeLog(`komodod debug.log access err: ${e}`); |
|||
} |
|||
} |
|||
|
|||
// get komodod instance port
|
|||
const _port = shepherd.assetChainPorts[data.ac_name]; |
|||
|
|||
try { |
|||
// check if komodod instance is already running
|
|||
portscanner.checkPortStatus(_port, '127.0.0.1', (error, status) => { |
|||
// Status is 'open' if currently in use or 'closed' if available
|
|||
if (status === 'closed' || |
|||
!shepherd.appConfig.stopNativeDaemonsOnQuit) { |
|||
// start komodod via exec
|
|||
const _customParamDict = { |
|||
silent: '&', |
|||
reindex: '-reindex', |
|||
change: '-pubkey=', |
|||
datadir: '-datadir=', |
|||
rescan: '-rescan', |
|||
}; |
|||
let _customParam = ''; |
|||
|
|||
if (data.ac_custom_param === 'silent' || |
|||
data.ac_custom_param === 'reindex' || |
|||
data.ac_custom_param === 'rescan') { |
|||
_customParam = ` ${_customParamDict[data.ac_custom_param]}`; |
|||
} else if (data.ac_custom_param === 'change' && data.ac_custom_param_value) { |
|||
_customParam = ` ${_customParamDict[data.ac_custom_param]}${data.ac_custom_param_value}`; |
|||
} |
|||
|
|||
if (shepherd.appConfig.dataDir.length) { |
|||
_customParam = _customParam + ' -datadir=' + shepherd.appConfig.dataDir + (data.ac_name !== 'komodod' ? '/' + data.ac_name : ''); |
|||
} |
|||
|
|||
shepherd.log(`exec ${shepherd.komododBin} ${data.ac_options.join(' ')}${_customParam}`); |
|||
shepherd.writeLog(`exec ${shepherd.komododBin} ${data.ac_options.join(' ')}${_customParam}`); |
|||
|
|||
const isChain = data.ac_name.match(/^[A-Z]*$/); |
|||
const coindACParam = isChain ? ` -ac_name=${data.ac_name} ` : ''; |
|||
shepherd.log(`daemon param ${data.ac_custom_param}`); |
|||
|
|||
shepherd.coindInstanceRegistry[data.ac_name] = true; |
|||
if (!shepherd.kmdMainPassiveMode) { |
|||
let _arg = `${coindACParam}${data.ac_options.join(' ')}${_customParam}`; |
|||
_arg = _arg.trim().split(' '); |
|||
|
|||
const _daemonName = data.ac_name !== 'komodod' ? data.ac_name : 'komodod'; |
|||
const _daemonLogName = `${shepherd.agamaDir}/${_daemonName}.log`; |
|||
|
|||
try { |
|||
fs.accessSync(_daemonLogName, fs.R_OK | fs.W_OK); |
|||
shepherd.log(`created ${_daemonLogName}`); |
|||
fs.unlinkSync(_daemonLogName); |
|||
} catch (e) { |
|||
shepherd.log(`error accessing ${_daemonLogName}, doesnt exist or another proc is already running`); |
|||
} |
|||
|
|||
if (!shepherd.appConfig.stopNativeDaemonsOnQuit) { |
|||
let spawnOut = fs.openSync(_daemonLogName, 'a'); |
|||
let spawnErr = fs.openSync(_daemonLogName, 'a'); |
|||
|
|||
spawn(shepherd.komododBin, _arg, { |
|||
stdio: ['ignore', spawnOut, spawnErr], |
|||
detached: true, |
|||
}).unref(); |
|||
} else { |
|||
let logStream = fs.createWriteStream(_daemonLogName, { flags: 'a' }); |
|||
|
|||
let _daemonChildProc = execFile(`${shepherd.komododBin}`, _arg, { |
|||
maxBuffer: 1024 * 1000000, // 1000 mb
|
|||
}, (error, stdout, stderr) => { |
|||
shepherd.writeLog(`stdout: ${stdout}`); |
|||
shepherd.writeLog(`stderr: ${stderr}`); |
|||
|
|||
if (error !== null) { |
|||
shepherd.log(`exec error: ${error}`); |
|||
shepherd.writeLog(`exec error: ${error}`); |
|||
|
|||
if (error.toString().indexOf('using -reindex') > -1) { |
|||
shepherd.io.emit('service', { |
|||
komodod: { |
|||
error: 'run -reindex', |
|||
}, |
|||
}); |
|||
} |
|||
} |
|||
}); |
|||
|
|||
_daemonChildProc.stdout.on('data', (data) => { |
|||
// shepherd.log(`${_daemonName} stdout: \n${data}`);
|
|||
}).pipe(logStream); |
|||
|
|||
_daemonChildProc.stdout.on('error', (data) => { |
|||
// shepherd.log(`${_daemonName} stdout: \n${data}`);
|
|||
}).pipe(logStream); |
|||
|
|||
_daemonChildProc.stderr.on('data', (data) => { |
|||
// shepherd.error(`${_daemonName} stderr:\n${data}`);
|
|||
}).pipe(logStream); |
|||
|
|||
_daemonChildProc.on('exit', (exitCode) => { |
|||
const _errMsg = exitCode === 0 ? `${_daemonName} exited with code ${exitCode}` : `${_daemonName} exited with code ${exitCode}, crashed?`; |
|||
|
|||
fs.appendFile(_daemonLogName, _errMsg, (err) => { |
|||
if (err) { |
|||
shepherd.writeLog(_errMsg); |
|||
shepherd.log(_errMsg); |
|||
} |
|||
shepherd.log(_errMsg); |
|||
}); |
|||
}); |
|||
} |
|||
} |
|||
} else { |
|||
if (shepherd.kmdMainPassiveMode) { |
|||
shepherd.coindInstanceRegistry[data.ac_name] = true; |
|||
} |
|||
shepherd.log(`port ${_port} (${data.ac_name}) is already in use`); |
|||
shepherd.writeLog(`port ${_port} (${data.ac_name}) is already in use`); |
|||
} |
|||
}); |
|||
} catch(e) { |
|||
shepherd.log(`failed to start komodod err: ${e}`); |
|||
shepherd.writeLog(`failed to start komodod err: ${e}`); |
|||
} |
|||
} |
|||
|
|||
// TODO: refactor
|
|||
if (flock === 'chipsd') { |
|||
let kmdDebugLogLocation = `${shepherd.chipsDir}/debug.log`; |
|||
|
|||
shepherd.log('chipsd flock selected...'); |
|||
shepherd.log(`selected data: ${JSON.stringify(data, null, '\t')}`); |
|||
shepherd.writeLog('chipsd flock selected...'); |
|||
shepherd.writeLog(`selected data: ${data}`); |
|||
|
|||
// truncate debug.log
|
|||
try { |
|||
const _confFileAccess = _fs.accessSync(kmdDebugLogLocation, fs.R_OK | fs.W_OK); |
|||
|
|||
if (_confFileAccess) { |
|||
shepherd.log(`error accessing ${kmdDebugLogLocation}`); |
|||
shepherd.writeLog(`error accessing ${kmdDebugLogLocation}`); |
|||
} else { |
|||
try { |
|||
fs.unlinkSync(kmdDebugLogLocation); |
|||
shepherd.log(`truncate ${kmdDebugLogLocation}`); |
|||
shepherd.writeLog(`truncate ${kmdDebugLogLocation}`); |
|||
} catch (e) { |
|||
shepherd.log('cant unlink debug.log'); |
|||
} |
|||
} |
|||
} catch(e) { |
|||
shepherd.log(`chipsd debug.log access err: ${e}`); |
|||
shepherd.writeLog(`chipsd debug.log access err: ${e}`); |
|||
} |
|||
|
|||
// get komodod instance port
|
|||
const _port = shepherd.assetChainPorts.chipsd; |
|||
|
|||
try { |
|||
// check if komodod instance is already running
|
|||
portscanner.checkPortStatus(_port, '127.0.0.1', (error, status) => { |
|||
// Status is 'open' if currently in use or 'closed' if available
|
|||
if (status === 'closed') { |
|||
// start komodod via exec
|
|||
const _customParamDict = { |
|||
silent: '&', |
|||
reindex: '-reindex', |
|||
change: '-pubkey=', |
|||
rescan: '-rescan', |
|||
}; |
|||
let _customParam = ''; |
|||
|
|||
if (data.ac_custom_param === 'silent' || |
|||
data.ac_custom_param === 'reindex' || |
|||
data.ac_custom_param === 'rescan') { |
|||
_customParam = ` ${_customParamDict[data.ac_custom_param]}`; |
|||
} else if (data.ac_custom_param === 'change' && data.ac_custom_param_value) { |
|||
_customParam = ` ${_customParamDict[data.ac_custom_param]}${data.ac_custom_param_value}`; |
|||
} |
|||
|
|||
shepherd.log(`exec ${shepherd.chipsBin} ${_customParam}`); |
|||
shepherd.writeLog(`exec ${shepherd.chipsBin} ${_customParam}`); |
|||
|
|||
shepherd.log(`daemon param ${data.ac_custom_param}`); |
|||
|
|||
shepherd.coindInstanceRegistry['CHIPS'] = true; |
|||
let _arg = `${_customParam}`; |
|||
_arg = _arg.trim().split(' '); |
|||
|
|||
if (_arg && |
|||
_arg.length > 1) { |
|||
execFile(`${shepherd.chipsBin}`, _arg, { |
|||
maxBuffer: 1024 * 1000000 // 1000 mb
|
|||
}, (error, stdout, stderr) => { |
|||
shepherd.writeLog(`stdout: ${stdout}`); |
|||
shepherd.writeLog(`stderr: ${stderr}`); |
|||
|
|||
if (error !== null) { |
|||
shepherd.log(`exec error: ${error}`); |
|||
shepherd.writeLog(`exec error: ${error}`); |
|||
|
|||
if (error.toString().indexOf('using -reindex') > -1) { |
|||
shepherd.io.emit('service', { |
|||
komodod: { |
|||
error: 'run -reindex', |
|||
}, |
|||
}); |
|||
} |
|||
} |
|||
}); |
|||
} else { |
|||
execFile(`${shepherd.chipsBin}`, { |
|||
maxBuffer: 1024 * 1000000 // 1000 mb
|
|||
}, (error, stdout, stderr) => { |
|||
shepherd.writeLog(`stdout: ${stdout}`); |
|||
shepherd.writeLog(`stderr: ${stderr}`); |
|||
|
|||
if (error !== null) { |
|||
shepherd.log(`exec error: ${error}`); |
|||
shepherd.writeLog(`exec error: ${error}`); |
|||
|
|||
if (error.toString().indexOf('using -reindex') > -1) { |
|||
shepherd.io.emit('service', { |
|||
komodod: { |
|||
error: 'run -reindex', |
|||
}, |
|||
}); |
|||
} |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
}); |
|||
} catch(e) { |
|||
shepherd.log(`failed to start chipsd err: ${e}`); |
|||
shepherd.writeLog(`failed to start chipsd err: ${e}`); |
|||
} |
|||
} |
|||
|
|||
if (flock === 'zcashd') { // TODO: fix(?)
|
|||
let kmdDebugLogLocation = `${shepherd.zcashDir}/debug.log`; |
|||
|
|||
shepherd.log('zcashd flock selected...'); |
|||
shepherd.log(`selected data: ${data}`); |
|||
shepherd.writeLog('zcashd flock selected...'); |
|||
shepherd.writeLog(`selected data: ${data}`); |
|||
} |
|||
|
|||
if (flock === 'coind') { |
|||
const _osHome = os.platform === 'win32' ? process.env.APPDATA : process.env.HOME; |
|||
let coindDebugLogLocation = `${_osHome}/.${shepherd.nativeCoindList[coind.toLowerCase()].bin.toLowerCase()}/debug.log`; |
|||
|
|||
shepherd.log(`coind ${coind} flock selected...`); |
|||
shepherd.log(`selected data: ${JSON.stringify(data, null, '\t')}`); |
|||
shepherd.writeLog(`coind ${coind} flock selected...`); |
|||
shepherd.writeLog(`selected data: ${data}`); |
|||
|
|||
// truncate debug.log
|
|||
try { |
|||
_fs.access(coindDebugLogLocation, fs.constants.R_OK, (err) => { |
|||
if (err) { |
|||
shepherd.log(`error accessing ${coindDebugLogLocation}`); |
|||
shepherd.writeLog(`error accessing ${coindDebugLogLocation}`); |
|||
} else { |
|||
shepherd.log(`truncate ${coindDebugLogLocation}`); |
|||
shepherd.writeLog(`truncate ${coindDebugLogLocation}`); |
|||
fs.unlink(coindDebugLogLocation); |
|||
} |
|||
}); |
|||
} catch(e) { |
|||
shepherd.log(`coind ${coind} debug.log access err: ${e}`); |
|||
shepherd.writeLog(`coind ${coind} debug.log access err: ${e}`); |
|||
} |
|||
|
|||
// get komodod instance port
|
|||
const _port = shepherd.nativeCoindList[coind.toLowerCase()].port; |
|||
const coindBin = `${shepherd.coindRootDir}/${coind.toLowerCase()}/${shepherd.nativeCoindList[coind.toLowerCase()].bin.toLowerCase()}d`; |
|||
|
|||
try { |
|||
// check if coind instance is already running
|
|||
portscanner.checkPortStatus(_port, '127.0.0.1', (error, status) => { |
|||
// Status is 'open' if currently in use or 'closed' if available
|
|||
if (status === 'closed') { |
|||
shepherd.log(`exec ${coindBin} ${data.ac_options.join(' ')}`); |
|||
shepherd.writeLog(`exec ${coindBin} ${data.ac_options.join(' ')}`); |
|||
|
|||
shepherd.coindInstanceRegistry[coind] = true; |
|||
let _arg = `${data.ac_options.join(' ')}`; |
|||
_arg = _arg.trim().split(' '); |
|||
execFile(`${coindBin}`, _arg, { |
|||
maxBuffer: 1024 * 1000000 // 1000 mb
|
|||
}, (error, stdout, stderr) => { |
|||
shepherd.writeLog(`stdout: ${stdout}`); |
|||
shepherd.writeLog(`stderr: ${stderr}`); |
|||
|
|||
if (error !== null) { |
|||
shepherd.log(`exec error: ${error}`); |
|||
shepherd.writeLog(`exec error: ${error}`); |
|||
} |
|||
}); |
|||
} else { |
|||
shepherd.log(`port ${_port} (${coind}) is already in use`); |
|||
shepherd.writeLog(`port ${_port} (${coind}) is already in use`); |
|||
} |
|||
}); |
|||
} catch(e) { |
|||
shepherd.log(`failed to start ${coind} err: ${e}`); |
|||
shepherd.writeLog(`failed to start ${coind} err: ${e}`); |
|||
} |
|||
} |
|||
} |
|||
|
|||
const setConf = (flock, coind) => { |
|||
let nativeCoindDir; |
|||
let DaemonConfPath; |
|||
|
|||
shepherd.log(flock); |
|||
shepherd.writeLog(`setconf ${flock}`); |
|||
|
|||
if (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 (flock) { |
|||
case 'komodod': |
|||
DaemonConfPath = `${shepherd.komodoDir}/komodo.conf`; |
|||
|
|||
if (os.platform() === 'win32') { |
|||
DaemonConfPath = path.normalize(DaemonConfPath); |
|||
} |
|||
break; |
|||
case 'zcashd': |
|||
DaemonConfPath = `${shepherd.ZcashDir}/zcash.conf`; |
|||
|
|||
if (os.platform() === 'win32') { |
|||
DaemonConfPath = path.normalize(DaemonConfPath); |
|||
} |
|||
break; |
|||
case 'chipsd': |
|||
DaemonConfPath = `${shepherd.chipsDir}/chips.conf`; |
|||
|
|||
if (os.platform() === 'win32') { |
|||
DaemonConfPath = path.normalize(DaemonConfPath); |
|||
} |
|||
break; |
|||
case 'coind': |
|||
DaemonConfPath = `${nativeCoindDir}/${shepherd.nativeCoindList[coind.toLowerCase()].bin.toLowerCase()}.conf`; |
|||
|
|||
if (os.platform() === 'win32') { |
|||
DaemonConfPath = path.normalize(DaemonConfPath); |
|||
} |
|||
break; |
|||
default: |
|||
DaemonConfPath = `${shepherd.komodoDir}/${flock}/${flock}.conf`; |
|||
|
|||
if (os.platform() === 'win32') { |
|||
DaemonConfPath = path.normalize(DaemonConfPath); |
|||
} |
|||
} |
|||
|
|||
shepherd.log(DaemonConfPath); |
|||
shepherd.writeLog(`setconf ${DaemonConfPath}`); |
|||
|
|||
const CheckFileExists = () => { |
|||
return new Promise((resolve, reject) => { |
|||
const result = 'Check Conf file exists is done'; |
|||
const confFileExist = fs.ensureFileSync(DaemonConfPath); |
|||
|
|||
if (confFileExist) { |
|||
shepherd.log(result); |
|||
shepherd.writeLog(`setconf ${result}`); |
|||
|
|||
resolve(result); |
|||
} else { |
|||
shepherd.log('conf file doesnt exist'); |
|||
resolve('conf file doesnt exist'); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
const FixFilePermissions = () => { |
|||
return new Promise((resolve, reject) => { |
|||
const result = 'Conf file permissions updated to Read/Write'; |
|||
|
|||
fsnode.chmodSync(DaemonConfPath, '0666'); |
|||
shepherd.log(result); |
|||
shepherd.writeLog(`setconf ${result}`); |
|||
|
|||
resolve(result); |
|||
}); |
|||
} |
|||
|
|||
const RemoveLines = () => { |
|||
return new Promise((resolve, reject) => { |
|||
const result = 'RemoveLines is done'; |
|||
|
|||
fs.readFile(DaemonConfPath, 'utf8', (err, data) => { |
|||
if (err) { |
|||
shepherd.writeLog(`setconf error ${err}`); |
|||
return shepherd.log(err); |
|||
} |
|||
|
|||
const rmlines = data.replace(/(?:(?:\r\n|\r|\n)\s*){2}/gm, '\n'); |
|||
|
|||
fs.writeFile(DaemonConfPath, rmlines, 'utf8', (err) => { |
|||
if (err) |
|||
return shepherd.log(err); |
|||
|
|||
fsnode.chmodSync(DaemonConfPath, '0666'); |
|||
shepherd.writeLog(`setconf ${result}`); |
|||
shepherd.log(result); |
|||
resolve(result); |
|||
}); |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
const CheckConf = () => { |
|||
return new Promise((resolve, reject) => { |
|||
const result = 'CheckConf is done'; |
|||
|
|||
shepherd.setconf.status(DaemonConfPath, (err, status) => { |
|||
const rpcuser = () => { |
|||
return new Promise((resolve, reject) => { |
|||
const result = 'checking rpcuser...'; |
|||
|
|||
if (status[0].hasOwnProperty('rpcuser')) { |
|||
shepherd.log('rpcuser: OK'); |
|||
shepherd.writeLog('rpcuser: OK'); |
|||
} else { |
|||
const randomstring = shepherd.md5((Math.random() * Math.random() * 999).toString()); |
|||
|
|||
shepherd.log('rpcuser: NOT FOUND'); |
|||
shepherd.writeLog('rpcuser: NOT FOUND'); |
|||
|
|||
fs.appendFile(DaemonConfPath, `\nrpcuser=user${randomstring.substring(0, 16)}`, (err) => { |
|||
if (err) { |
|||
shepherd.writeLog(`append daemon conf err: ${err}`); |
|||
shepherd.log(`append daemon conf err: ${err}`); |
|||
} |
|||
// throw err;
|
|||
shepherd.log('rpcuser: ADDED'); |
|||
shepherd.writeLog('rpcuser: ADDED'); |
|||
}); |
|||
} |
|||
|
|||
resolve(result); |
|||
}); |
|||
} |
|||
|
|||
const rpcpass = () => { |
|||
return new Promise((resolve, reject) => { |
|||
const result = 'checking rpcpassword...'; |
|||
|
|||
if (status[0].hasOwnProperty('rpcpassword')) { |
|||
shepherd.log('rpcpassword: OK'); |
|||
shepherd.writeLog('rpcpassword: OK'); |
|||
} else { |
|||
const randomstring = md5((Math.random() * Math.random() * 999).toString()); |
|||
|
|||
shepherd.log('rpcpassword: NOT FOUND'); |
|||
shepherd.writeLog('rpcpassword: NOT FOUND'); |
|||
|
|||
fs.appendFile(DaemonConfPath, `\nrpcpassword=${randomstring}`, (err) => { |
|||
if (err) { |
|||
shepherd.writeLog(`append daemon conf err: ${err}`); |
|||
shepherd.log(`append daemon conf err: ${err}`); |
|||
} |
|||
// throw err;
|
|||
shepherd.log('rpcpassword: ADDED'); |
|||
shepherd.writeLog('rpcpassword: ADDED'); |
|||
}); |
|||
} |
|||
|
|||
resolve(result); |
|||
}); |
|||
} |
|||
|
|||
const rpcbind = () => { |
|||
return new Promise((resolve, reject) => { |
|||
const result = 'checking rpcbind...'; |
|||
|
|||
if (status[0].hasOwnProperty('rpcbind')) { |
|||
shepherd.log('rpcbind: OK'); |
|||
shepherd.writeLog('rpcbind: OK'); |
|||
} else { |
|||
shepherd.log('rpcbind: NOT FOUND'); |
|||
shepherd.writeLog('rpcbind: NOT FOUND'); |
|||
|
|||
fs.appendFile(DaemonConfPath, '\nrpcbind=127.0.0.1', (err) => { |
|||
if (err) { |
|||
shepherd.writeLog(`append daemon conf err: ${err}`); |
|||
shepherd.log(`append daemon conf err: ${err}`); |
|||
} |
|||
// throw err;
|
|||
shepherd.log('rpcbind: ADDED'); |
|||
shepherd.writeLog('rpcbind: ADDED'); |
|||
}); |
|||
} |
|||
|
|||
resolve(result); |
|||
}); |
|||
} |
|||
|
|||
const server = () => { |
|||
return new Promise((resolve, reject) => { |
|||
const result = 'checking server...'; |
|||
|
|||
if (status[0].hasOwnProperty('server')) { |
|||
shepherd.log('server: OK'); |
|||
shepherd.writeLog('server: OK'); |
|||
} else { |
|||
shepherd.log('server: NOT FOUND'); |
|||
shepherd.writeLog('server: NOT FOUND'); |
|||
|
|||
fs.appendFile(DaemonConfPath, '\nserver=1', (err) => { |
|||
if (err) { |
|||
shepherd.writeLog(`append daemon conf err: ${err}`); |
|||
shepherd.log(`append daemon conf err: ${err}`); |
|||
} |
|||
// throw err;
|
|||
shepherd.log('server: ADDED'); |
|||
shepherd.writeLog('server: ADDED'); |
|||
}); |
|||
} |
|||
|
|||
resolve(result); |
|||
}); |
|||
} |
|||
|
|||
const addnode = () => { |
|||
return new Promise((resolve, reject) => { |
|||
const result = 'checking addnode...'; |
|||
|
|||
if (flock === 'chipsd' || |
|||
flock === 'komodod') { |
|||
if (status[0].hasOwnProperty('addnode')) { |
|||
shepherd.log('addnode: OK'); |
|||
shepherd.writeLog('addnode: OK'); |
|||
} else { |
|||
let nodesList; |
|||
|
|||
if (flock === 'chipsd') { |
|||
nodesList = '\naddnode=95.110.191.193' + |
|||
'\naddnode=144.76.167.66' + |
|||
'\naddnode=158.69.248.93' + |
|||
'\naddnode=149.202.49.218' + |
|||
'\naddnode=95.213.205.222' + |
|||
'\naddnode=5.9.253.198' + |
|||
'\naddnode=164.132.224.253' + |
|||
'\naddnode=163.172.4.66' + |
|||
'\naddnode=217.182.194.216' + |
|||
'\naddnode=94.130.96.114' + |
|||
'\naddnode=5.9.253.195'; |
|||
} else if (flock === 'komodod') { |
|||
nodesList = '\naddnode=78.47.196.146' + |
|||
'\naddnode=5.9.102.210' + |
|||
'\naddnode=178.63.69.164' + |
|||
'\naddnode=88.198.65.74' + |
|||
'\naddnode=5.9.122.241' + |
|||
'\naddnode=144.76.94.3'; |
|||
} |
|||
|
|||
shepherd.log('addnode: NOT FOUND'); |
|||
fs.appendFile(DaemonConfPath, nodesList, (err) => { |
|||
if (err) { |
|||
shepherd.writeLog(`append daemon conf err: ${err}`); |
|||
shepherd.log(`append daemon conf err: ${err}`); |
|||
} |
|||
// throw err;
|
|||
shepherd.log('addnode: ADDED'); |
|||
shepherd.writeLog('addnode: ADDED'); |
|||
}); |
|||
} |
|||
} else { |
|||
result = 'skip addnode'; |
|||
} |
|||
|
|||
resolve(result); |
|||
}); |
|||
} |
|||
|
|||
rpcuser() |
|||
.then((result) => { |
|||
return rpcpass(); |
|||
}) |
|||
.then(server) |
|||
.then(rpcbind) |
|||
.then(addnode); |
|||
}); |
|||
|
|||
shepherd.log(result); |
|||
shepherd.writeLog(`checkconf addnode ${result}`); |
|||
|
|||
resolve(result); |
|||
}); |
|||
} |
|||
|
|||
CheckFileExists() |
|||
.then((result) => { |
|||
return FixFilePermissions(); |
|||
}) |
|||
.then(RemoveLines) |
|||
.then(CheckConf); |
|||
} |
|||
|
|||
/* |
|||
* type: POST |
|||
* params: herd |
|||
*/ |
|||
shepherd.post('/herd', (req, res) => { |
|||
shepherd.log('======= req.body ======='); |
|||
shepherd.log(req.body); |
|||
|
|||
if (req.body.options && |
|||
!shepherd.kmdMainPassiveMode) { |
|||
const testCoindPort = (skipError) => { |
|||
if (!shepherd.lockDownAddCoin) { |
|||
const _port = shepherd.assetChainPorts[req.body.options.ac_name]; |
|||
|
|||
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!`, |
|||
}, |
|||
}); |
|||
|
|||
const obj = { |
|||
msg: 'error', |
|||
result: `error starting ${req.body.herd} ${req.body.options.ac_name} daemon. Port ${_port} is already taken!`, |
|||
}; |
|||
|
|||
res.status(500); |
|||
res.end(JSON.stringify(obj)); |
|||
} else { |
|||
shepherd.log(`komodod service start success at port ${_port}`); |
|||
shepherd.writeLog(`komodod service start success at port ${_port}`); |
|||
} |
|||
} else { |
|||
if (!skipError) { |
|||
herder(req.body.herd, req.body.options); |
|||
|
|||
const obj = { |
|||
msg: 'success', |
|||
result: 'result', |
|||
}; |
|||
|
|||
res.end(JSON.stringify(obj)); |
|||
} else { |
|||
shepherd.log(`komodod service start error at port ${_port}, reason: unknown`); |
|||
shepherd.writeLog(`komodod service start error at port ${_port}, reason: unknown`); |
|||
} |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
|
|||
if (req.body.herd === 'komodod') { |
|||
// check if komodod instance is already running
|
|||
testCoindPort(); |
|||
setTimeout(() => { |
|||
testCoindPort(true); |
|||
}, 10000); |
|||
} else { |
|||
herder(req.body.herd, req.body.options, req.body.coind); |
|||
|
|||
const obj = { |
|||
msg: 'success', |
|||
result: 'result', |
|||
}; |
|||
|
|||
res.end(JSON.stringify(obj)); |
|||
} |
|||
} else { |
|||
// (?)
|
|||
herder(req.body.herd, req.body.options); |
|||
|
|||
const obj = { |
|||
msg: 'success', |
|||
result: 'result', |
|||
}; |
|||
|
|||
res.end(JSON.stringify(obj)); |
|||
} |
|||
}); |
|||
|
|||
/* |
|||
* type: POST |
|||
*/ |
|||
shepherd.post('/setconf', (req, res) => { |
|||
shepherd.log('======= req.body ======='); |
|||
shepherd.log(req.body); |
|||
|
|||
if (os.platform() === 'win32' && |
|||
req.body.chain == 'komodod') { |
|||
setkomodoconf = spawn(path.join(__dirname, '../assets/bin/win64/genkmdconf.bat')); |
|||
} else { |
|||
shepherd.setConf(req.body.chain); |
|||
} |
|||
|
|||
const obj = { |
|||
msg: 'success', |
|||
result: 'result', |
|||
}; |
|||
|
|||
res.end(JSON.stringify(obj)); |
|||
}); |
|||
|
|||
/* |
|||
* type: POST |
|||
*/ |
|||
shepherd.post('/getconf', (req, res) => { |
|||
shepherd.log('======= req.body ======='); |
|||
shepherd.log(req.body); |
|||
|
|||
const confpath = getConf(req.body.chain, req.body.coind); |
|||
|
|||
shepherd.log('got conf path is:'); |
|||
shepherd.log(confpath); |
|||
shepherd.writeLog('got conf path is:'); |
|||
shepherd.writeLog(confpath); |
|||
|
|||
const obj = { |
|||
msg: 'success', |
|||
result: confpath, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(obj)); |
|||
}); |
|||
|
|||
shepherd.setConfKMD = (isChips) => { |
|||
// check if kmd conf exists
|
|||
_fs.access(isChips ? `${shepherd.chipsDir}/chips.conf` : `${shepherd.komodoDir}/komodo.conf`, shepherd.fs.constants.R_OK, (err) => { |
|||
if (err) { |
|||
shepherd.log(isChips ? 'creating chips conf' : 'creating komodo conf'); |
|||
shepherd.writeLog(isChips ? `creating chips conf in ${shepherd.chipsDir}/chips.conf` : `creating komodo conf in ${shepherd.komodoDir}/komodo.conf`); |
|||
setConf(isChips ? 'chipsd' : 'komodod'); |
|||
} else { |
|||
const _confSize = shepherd.fs.lstatSync(isChips ? `${shepherd.chipsDir}/chips.conf` : `${shepherd.komodoDir}/komodo.conf`); |
|||
|
|||
if (_confSize.size === 0) { |
|||
shepherd.log(isChips ? 'err: chips conf file is empty, creating chips conf' : 'err: komodo conf file is empty, creating komodo conf'); |
|||
shepherd.writeLog(isChips ? `creating chips conf in ${shepherd.chipsDir}/chips.conf` : `creating komodo conf in ${shepherd.komodoDir}/komodo.conf`); |
|||
setConf(isChips ? 'chipsd' : 'komodod'); |
|||
} else { |
|||
shepherd.writeLog(isChips ? 'chips conf exists' : 'komodo conf exists'); |
|||
shepherd.log(isChips ? 'chips conf exists' : 'komodo conf exists'); |
|||
} |
|||
} |
|||
}); |
|||
} |
|||
|
|||
return shepherd; |
|||
}; |
@ -0,0 +1,312 @@ |
|||
const Promise = require('bluebird'); |
|||
|
|||
module.exports = (shepherd) => { |
|||
/* |
|||
* Combined native dashboard update same as in gui |
|||
* type: GET |
|||
* 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' |
|||
]; |
|||
} |
|||
|
|||
const getAddressesNative = (coin) => { |
|||
const type = [ |
|||
'public', |
|||
'private' |
|||
]; |
|||
|
|||
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); |
|||
} |
|||
}); |
|||
}); |
|||
})) |
|||
.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]; |
|||
} |
|||
} |
|||
} |
|||
|
|||
// remove addr duplicates
|
|||
if (result[0] && |
|||
result[0].length) { |
|||
result[0] = result[0].filter((elem, pos) => { |
|||
return result[0].indexOf(elem) === pos; |
|||
}); |
|||
} |
|||
if (result[1] && |
|||
result[1].length) { |
|||
result[1] = result[1].filter((elem, pos) => { |
|||
return result[1].indexOf(elem) === pos; |
|||
}); |
|||
} |
|||
|
|||
let newAddressArray = []; |
|||
for (let a = 0; a < result.length; a++) { |
|||
newAddressArray[a] = []; |
|||
|
|||
if (result[a]) { |
|||
for (let b = 0; b < result[a].length; b++) { |
|||
const filteredArraySpends = json.filter(res => res.address === result[a][b]); |
|||
const filteredArray = json.filter(res => res.address === result[a][b]).map(res => res.amount); |
|||
|
|||
let sum = 0; |
|||
let spendableSum = 0; |
|||
let canspend = true; |
|||
|
|||
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', |
|||
}; |
|||
} |
|||
} |
|||
} |
|||
|
|||
// 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)); |
|||
}); |
|||
} else { |
|||
_returnObj.addresses = { |
|||
public: newAddressArray[0], |
|||
private: newAddressArray[1], |
|||
}; |
|||
|
|||
const returnObj = { |
|||
msg: 'success', |
|||
result: _returnObj, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(returnObj)); |
|||
} |
|||
} |
|||
|
|||
_bitcoinRPC(coin, 'listunspent') |
|||
.then((__json) => { |
|||
if (__json === 'Work queue depth exceeded' || |
|||
!__json) { |
|||
const returnObj = { |
|||
msg: 'success', |
|||
result: _returnObj, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(returnObj)); |
|||
} else { |
|||
_returnObj.listunspent = JSON.parse(__json); |
|||
|
|||
calcBalance( |
|||
result, |
|||
JSON.parse(__json).result |
|||
); |
|||
} |
|||
}); |
|||
} else { |
|||
_returnObj.addresses = { |
|||
public: {}, |
|||
private: {}, |
|||
}; |
|||
|
|||
const returnObj = { |
|||
msg: 'success', |
|||
result: _returnObj, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(returnObj)); |
|||
} |
|||
}) |
|||
} |
|||
|
|||
const _bitcoinRPC = (coin, cmd, params) => { |
|||
return new Promise((resolve, reject) => { |
|||
let _payload; |
|||
|
|||
if (params) { |
|||
_payload = { |
|||
mode: null, |
|||
chain: coin, |
|||
cmd: cmd, |
|||
params: params, |
|||
}; |
|||
} else { |
|||
_payload = { |
|||
mode: null, |
|||
chain: coin, |
|||
cmd: cmd, |
|||
}; |
|||
} |
|||
|
|||
const options = { |
|||
url: `http://127.0.0.1:${shepherd.appConfig.agamaPort}/shepherd/cli`, |
|||
method: 'POST', |
|||
headers: { |
|||
'Content-Type': 'application/json', |
|||
}, |
|||
body: JSON.stringify({ payload: _payload }), |
|||
timeout: 120000, |
|||
}; |
|||
|
|||
shepherd.request(options, (error, response, body) => { |
|||
if (response && |
|||
response.statusCode && |
|||
response.statusCode === 200) { |
|||
resolve(body); |
|||
} else { |
|||
resolve(body); |
|||
} |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
Promise.all(_promiseStack.map((_call, index) => { |
|||
let _params; |
|||
|
|||
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); |
|||
}); |
|||
}); |
|||
})) |
|||
.then(result => { |
|||
getAddressesNative(_coin); |
|||
}); |
|||
}); |
|||
|
|||
return shepherd; |
|||
}; |
@ -0,0 +1,119 @@ |
|||
module.exports = (shepherd) => { |
|||
/* |
|||
* type: POST |
|||
* params: herd, lastLines |
|||
*/ |
|||
shepherd.post('/debuglog', (req, res) => { |
|||
let _herd = req.body.herdname; |
|||
let _ac = req.body.ac; |
|||
let _lastNLines = req.body.lastLines; |
|||
let _location; |
|||
|
|||
if (shepherd.os.platform() === 'darwin') { |
|||
shepherd.komodoDir = shepherd.appConfig.dataDir.length ? shepherd.appConfig.dataDir : `${process.env.HOME}/Library/Application Support/Komodo`; |
|||
} |
|||
|
|||
if (shepherd.os.platform() === 'linux') { |
|||
shepherd.komodoDir = shepherd.appConfig.dataDir.length ? shepherd.appConfig.dataDir : `${process.env.HOME}/.komodo`; |
|||
} |
|||
|
|||
if (shepherd.os.platform() === 'win32') { |
|||
shepherd.komodoDir = shepherd.appConfig.dataDir.length ? shepherd.appConfig.dataDir : `${process.env.APPDATA}/Komodo`; |
|||
shepherd.komodoDir = shepherd.path.normalize(shepherd.komodoDir); |
|||
} |
|||
|
|||
if (_herd === 'komodo') { |
|||
_location = shepherd.komodoDir; |
|||
} |
|||
|
|||
if (_ac) { |
|||
_location = `${shepherd.komodoDir}/${_ac}`; |
|||
|
|||
if (_ac === 'CHIPS') { |
|||
_location = shepherd.chipsDir; |
|||
} |
|||
} |
|||
|
|||
shepherd.readDebugLog(`${_location}/debug.log`, _lastNLines) |
|||
.then((result) => { |
|||
const _obj = { |
|||
msg: 'success', |
|||
result: result, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(_obj)); |
|||
}, (result) => { |
|||
const _obj = { |
|||
msg: 'error', |
|||
result: result, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(_obj)); |
|||
}); |
|||
}); |
|||
|
|||
shepherd.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 = { |
|||
msg: 'error', |
|||
result: result, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(_obj)); |
|||
}); |
|||
}); |
|||
|
|||
shepherd.readDebugLog = (fileLocation, lastNLines) => { |
|||
return new shepherd.Promise( |
|||
(resolve, reject) => { |
|||
if (lastNLines) { |
|||
try { |
|||
shepherd._fs.access(fileLocation, shepherd.fs.constants.R_OK, (err) => { |
|||
if (err) { |
|||
shepherd.log(`error reading ${fileLocation}`); |
|||
shepherd.writeLog(`error reading ${fileLocation}`); |
|||
reject(`readDebugLog error: ${err}`); |
|||
} else { |
|||
shepherd.log(`reading ${fileLocation}`); |
|||
shepherd._fs.readFile(fileLocation, 'utf-8', (err, data) => { |
|||
if (err) { |
|||
shepherd.writeLog(`readDebugLog err: ${err}`); |
|||
shepherd.log(`readDebugLog err: ${err}`); |
|||
} |
|||
|
|||
const lines = data.trim().split('\n'); |
|||
let lastLine; |
|||
|
|||
if (lastNLines === 'all') { |
|||
lastLine = data.trim(); |
|||
} else { |
|||
lastLine = lines.slice(lines.length - lastNLines, lines.length).join('\n'); |
|||
} |
|||
|
|||
resolve(lastLine); |
|||
}); |
|||
} |
|||
}); |
|||
} catch (e) { |
|||
reject(`readDebugLog error: ${e}`); |
|||
} |
|||
} else { |
|||
reject('readDebugLog error: lastNLines param is not provided!'); |
|||
} |
|||
} |
|||
); |
|||
}; |
|||
|
|||
return shepherd; |
|||
}; |
@ -0,0 +1,44 @@ |
|||
module.exports = (shepherd) => { |
|||
/* |
|||
* list native coind |
|||
* type: |
|||
* params: |
|||
*/ |
|||
shepherd.get('/coind/list', (req, res, next) => { |
|||
const successObj = { |
|||
msg: 'success', |
|||
result: shepherd.nativeCoindList, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
}); |
|||
|
|||
shepherd.scanNativeCoindBins = () => { |
|||
let nativeCoindList = {}; |
|||
|
|||
// check if coind bins are present in agama
|
|||
for (let key in shepherd.nativeCoind) { |
|||
nativeCoindList[key] = { |
|||
name: shepherd.nativeCoind[key].name, |
|||
port: shepherd.nativeCoind[key].port, |
|||
bin: shepherd.nativeCoind[key].bin, |
|||
bins: { |
|||
daemon: false, |
|||
cli: false, |
|||
}, |
|||
}; |
|||
|
|||
if (shepherd.fs.existsSync(`${shepherd.coindRootDir}/${key}/${shepherd.nativeCoind[key].bin}d${shepherd.os.platform() === 'win32' ? '.exe' : ''}`)) { |
|||
nativeCoindList[key].bins.daemon = true; |
|||
} |
|||
|
|||
if (shepherd.fs.existsSync(`${shepherd.coindRootDir}/${key}/${shepherd.nativeCoind[key].bin}-cli${shepherd.os.platform() === 'win32' ? '.exe' : ''}`)) { |
|||
nativeCoindList[key].bins.cli = true; |
|||
} |
|||
} |
|||
|
|||
return nativeCoindList; |
|||
} |
|||
|
|||
return shepherd; |
|||
}; |
File diff suppressed because one or more lines are too long
@ -0,0 +1,242 @@ |
|||
const os = require('os'); |
|||
const fs = require('fs-extra'); |
|||
const portscanner = require('portscanner'); |
|||
const exec = require('child_process').exec; |
|||
const execFile = require('child_process').execFile; |
|||
const path = require('path'); |
|||
const request = require('request'); |
|||
const Promise = require('bluebird'); |
|||
|
|||
const RATES_UPDATE_INTERVAL = 60000; |
|||
|
|||
module.exports = (shepherd) => { |
|||
shepherd.get('/mm/start', (req, res, next) => { |
|||
shepherd.log('mm start is called'); |
|||
|
|||
shepherd.startMarketMaker({ passphrase: req.query.passphrase }); |
|||
shepherd.mmupass = null; |
|||
|
|||
shepherd.mmupass = setInterval(() => { |
|||
const options = { |
|||
url: `http://localhost:7783`, |
|||
method: 'POST', |
|||
headers: { |
|||
'Content-Type': 'application/json', |
|||
}, |
|||
body: JSON.stringify({ method: 'balance' }), |
|||
}; |
|||
|
|||
// send back body on both success and error
|
|||
// this bit replicates iguana core's behaviour
|
|||
request(options, (error, response, body) => { |
|||
if (response && |
|||
response.statusCode && |
|||
response.statusCode === 200) { |
|||
const _parsedBody = JSON.parse(body); |
|||
|
|||
if (_parsedBody.userpass) { |
|||
res.end(body); |
|||
clearInterval(shepherd.mmupass); |
|||
shepherd.mmupass = _parsedBody.userpass; |
|||
shepherd.mmPublic.mmupass = shepherd.mmupass; |
|||
shepherd.mmPublic.isAuth = true; |
|||
shepherd.mmPublic.coins = _parsedBody.coins; |
|||
shepherd.log(`mm start success`); |
|||
shepherd.log(`mm userpass ${_parsedBody.userpass}`); |
|||
shepherd.getCoinsHelper(); |
|||
shepherd.getRates(); |
|||
} |
|||
} else { |
|||
shepherd.log(`mm start responded with error ${error}`); |
|||
/*res.end(body ? body : JSON.stringify({ |
|||
result: 'error', |
|||
error: { |
|||
code: -777, |
|||
message: `unable to call method balance at port 7783`, |
|||
}, |
|||
}));*/ |
|||
} |
|||
}); |
|||
}, 500); |
|||
}); |
|||
|
|||
shepherd.getCoinsHelper = () => { |
|||
const defaultCoinsListFile = path.join(__dirname, '../dex/coins.json'); |
|||
shepherd.mmPublic.coinsHelper = fs.readJsonSync(defaultCoinsListFile, { throws: false }); |
|||
} |
|||
|
|||
shepherd.getRates = () => { |
|||
function _getRates() { |
|||
const options = { |
|||
url: `https://min-api.cryptocompare.com/data/price?fsym=KMD&tsyms=BTC,USD`, |
|||
method: 'GET', |
|||
}; |
|||
|
|||
// send back body on both success and error
|
|||
// this bit replicates iguana core's behaviour
|
|||
request(options, (error, response, body) => { |
|||
if (response && |
|||
response.statusCode && |
|||
response.statusCode === 200) { |
|||
const _parsedBody = JSON.parse(body); |
|||
shepherd.log(`rates ${body}`); |
|||
shepherd.mmPublic.rates = _parsedBody; |
|||
} else { |
|||
shepherd.log(`mm unable to retrieve KMD/BTC,USD rate`); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
_getRates(); |
|||
shepherd.mmRatesInterval = setInterval(() => { |
|||
_getRates(); |
|||
}, RATES_UPDATE_INTERVAL); |
|||
} |
|||
|
|||
shepherd.getMMCacheData = () => { |
|||
return new Promise((resolve, reject) => { |
|||
resolve(shepherd.mmPublic); |
|||
}); |
|||
} |
|||
|
|||
shepherd.get('/mm/stop', (req, res, next) => { |
|||
shepherd.log('mm stop is called'); |
|||
clearInterval(shepherd.mmRatesInterval); |
|||
shepherd.killRogueProcess('marketmaker'); |
|||
shepherd.mmPublic = { |
|||
coins: [], |
|||
mmupass: null, |
|||
swaps: [], |
|||
bids: [], |
|||
asks: [], |
|||
isAuth: false, |
|||
rates: {}, |
|||
}; |
|||
|
|||
const successObj = { |
|||
msg: 'success', |
|||
result: 'executed', |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
}); |
|||
|
|||
shepherd.get('/mm/restart', (req, res, next) => { |
|||
shepherd.log('mm restart is called'); |
|||
shepherd.killRogueProcess('marketmaker'); |
|||
shepherd.mmPublic = { |
|||
coins: {}, |
|||
mmupass: null, |
|||
swaps: [], |
|||
bids: [], |
|||
asks: [], |
|||
isAuth: false, |
|||
}; |
|||
|
|||
setTimeout(() => { |
|||
shepherd.startMarketMaker({ passphrase: req.query.passphrase }); |
|||
|
|||
const successObj = { |
|||
msg: 'success', |
|||
result: 'restarting', |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
}, 1000); |
|||
}); |
|||
|
|||
shepherd.startMarketMaker = (data) => { |
|||
const defaultCoinsListFile = path.join(__dirname, '../dex/coins.json'); |
|||
|
|||
try { |
|||
// check if marketmaker instance is already running
|
|||
portscanner.checkPortStatus(7783, '127.0.0.1', (error, status) => { |
|||
// Status is 'open' if currently in use or 'closed' if available
|
|||
if (status === 'closed') { |
|||
// add BarterDEX check
|
|||
const _coinsListFile = shepherd.agamaDir + '/coins.json'; |
|||
|
|||
fs.pathExists(_coinsListFile, (err, exists) => { |
|||
if (exists) { |
|||
shepherd.log('dex coins file exist'); |
|||
data.coinslist = fs.readJsonSync(_coinsListFile, { throws: false }); |
|||
shepherd.execMarketMaker(data); |
|||
} else if (!exists) { |
|||
shepherd.log(`dex coins file doesnt exist`); |
|||
fs.copy(defaultCoinsListFile, _coinsListFile) |
|||
.then(() => { |
|||
shepherd.log(`dex coins file copied to ${shepherd.agamaDir}`); |
|||
data.coinslist = fs.readJsonSync(_coinsListFile, { throws: false }); |
|||
shepherd.execMarketMaker(data); |
|||
}) |
|||
.catch(err => { |
|||
shepherd.log(`unable to copy dex coins file, ${err}`); |
|||
}); |
|||
} else if (err) { |
|||
shepherd.log(`dex coins file doesnt exist, ${err}`); |
|||
} |
|||
}); |
|||
} else { |
|||
shepherd.log(`port 7783 marketmaker is already in use`); |
|||
} |
|||
}); |
|||
} catch(e) { |
|||
shepherd.log(`failed to start marketmaker err: ${e}`); |
|||
} |
|||
} |
|||
|
|||
shepherd.execMarketMaker = (data) => { |
|||
const _customParam = { |
|||
gui: 'agama-buildog', |
|||
client: 1, |
|||
profitmargin: 0.01, // (?)
|
|||
userhome: `${process.env.HOME}`, |
|||
passphrase: data.passphrase, |
|||
coins: data.coinslist, |
|||
}; |
|||
|
|||
//console.log(JSON.stringify(_customParam))
|
|||
//console.log(`exec ${BarterDEXBin} ${JSON.stringify(_customParam)}`);
|
|||
|
|||
let params = _customParam; |
|||
if (os.platform() !== 'win32') { |
|||
params = `'${JSON.stringify(_customParam)}'`; |
|||
} else { |
|||
shepherd.mmBin = `"${shepherd.mmBin}"`; |
|||
params.userhome = process.env.APPDATA; |
|||
|
|||
if (!!params.coins) { // if not undefined and true
|
|||
delete params.coins; // for Windows we should use coins.json file, and don't pass coins in command line
|
|||
} |
|||
|
|||
params = JSON.stringify(_customParam); |
|||
params = params.replace(/"/g, '\\"'); |
|||
params = `"${params}"`; |
|||
} |
|||
|
|||
const logStream = fs.createWriteStream(`${shepherd.agamaDir}/logFile.log`, { flags: 'a' }); |
|||
|
|||
shepherd.log('starting mm'); |
|||
const mmid = exec(`${shepherd.mmBin} ${params}`, { |
|||
cwd: shepherd.agamaDir, |
|||
maxBuffer: 1024 * 50000 // 50 mb
|
|||
}, function(error, stdout, stderr) { |
|||
// console.log(`stdout: ${stdout}`);
|
|||
// console.log(`stderr: ${stderr}`);
|
|||
|
|||
if (error !== null) { |
|||
shepherd.log(`mm exec error: ${error}`); |
|||
} |
|||
}); |
|||
|
|||
mmid.stdout.on('data', (data) => { |
|||
// console.log(`child stdout:\n${data}`);
|
|||
}).pipe(logStream); |
|||
|
|||
mmid.stderr.on('data', (data) => { |
|||
// console.error(`child stderr:\n${data}`);
|
|||
}).pipe(logStream); |
|||
} |
|||
|
|||
return shepherd; |
|||
}; |
@ -0,0 +1,39 @@ |
|||
const request = require('request'); |
|||
|
|||
module.exports = (shepherd) => { |
|||
// payload
|
|||
// record all calls
|
|||
shepherd.post('/mm/request', (req, res, next) => { |
|||
let _payload = req.body.payload; |
|||
_payload.userpass = shepherd.mmupass; |
|||
const options = { |
|||
url: `http://localhost:7783`, |
|||
method: 'POST', |
|||
body: JSON.stringify(_payload), |
|||
}; |
|||
|
|||
shepherd.log(_payload); |
|||
|
|||
// send back body on both success and error
|
|||
// this bit replicates iguana core's behaviour
|
|||
request(options, (error, response, body) => { |
|||
if (response && |
|||
response.statusCode && |
|||
response.statusCode === 200) { |
|||
const _parsedBody = JSON.parse(body); |
|||
shepherd.mmPublic[_payload.mapToProp] = _parsedBody; |
|||
res.end(body); |
|||
} else { |
|||
res.end(body ? body : JSON.stringify({ |
|||
result: 'error', |
|||
error: { |
|||
code: -777, |
|||
message: `unable to call method ${_payload.method} at port 7783`, |
|||
}, |
|||
})); |
|||
} |
|||
}); |
|||
}); |
|||
|
|||
return shepherd; |
|||
}; |
@ -0,0 +1,165 @@ |
|||
const remoteBinLocation = { |
|||
win32: 'https://artifacts.supernet.org/latest/windows/', |
|||
darwin: 'https://artifacts.supernet.org/latest/osx/', |
|||
linux: 'https://artifacts.supernet.org/latest/linux/', |
|||
}; |
|||
const localBinLocation = { |
|||
win32: 'assets/bin/win64/', |
|||
darwin: 'assets/bin/osx/', |
|||
linux: 'assets/bin/linux64/', |
|||
}; |
|||
const latestBins = { |
|||
win32: [ |
|||
'komodo-cli.exe', |
|||
'komodod.exe', |
|||
'libcrypto-1_1.dll', |
|||
'libcurl-4.dll', |
|||
'libcurl.dll', |
|||
'libgcc_s_sjlj-1.dll', |
|||
'libnanomsg.dll', |
|||
'libssl-1_1.dll', |
|||
'libwinpthread-1.dll', |
|||
'nanomsg.dll', |
|||
'pthreadvc2.dll', |
|||
], |
|||
darwin: [ |
|||
'komodo-cli', |
|||
'komodod', |
|||
'libgcc_s.1.dylib', |
|||
'libgomp.1.dylib', |
|||
'libnanomsg.5.0.0.dylib', |
|||
'libstdc++.6.dylib', // encode %2B
|
|||
], |
|||
linux: [ |
|||
'komodo-cli', |
|||
'komodod', |
|||
], |
|||
}; |
|||
|
|||
let binsToUpdate = []; |
|||
|
|||
module.exports = (shepherd) => { |
|||
/* |
|||
* Check bins file size |
|||
* type: |
|||
* params: |
|||
*/ |
|||
// TODO: promises
|
|||
shepherd.get('/update/bins/check', (req, res, next) => { |
|||
const rootLocation = shepherd.path.join(__dirname, '../../'); |
|||
const successObj = { |
|||
msg: 'success', |
|||
result: 'bins', |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
|
|||
const _os = shepherd.os.platform(); |
|||
shepherd.log(`checking bins: ${_os}`); |
|||
|
|||
shepherd.io.emit('patch', { |
|||
patch: { |
|||
type: 'bins-check', |
|||
status: 'progress', |
|||
message: `checking bins: ${_os}`, |
|||
}, |
|||
}); |
|||
// get list of bins/dlls that can be updated to the latest
|
|||
for (let i = 0; i < latestBins[_os].length; i++) { |
|||
shepherd.remoteFileSize(remoteBinLocation[_os] + latestBins[_os][i], (err, remoteBinSize) => { |
|||
const localBinSize = shepherd.fs.statSync(rootLocation + localBinLocation[_os] + latestBins[_os][i]).size; |
|||
|
|||
shepherd.log('remote url: ' + (remoteBinLocation[_os] + latestBins[_os][i]) + ' (' + remoteBinSize + ')'); |
|||
shepherd.log('local file: ' + (rootLocation + localBinLocation[_os] + latestBins[_os][i]) + ' (' + localBinSize + ')'); |
|||
|
|||
if (remoteBinSize !== localBinSize) { |
|||
shepherd.log(`${latestBins[_os][i]} can be updated`); |
|||
binsToUpdate.push({ |
|||
name: latestBins[_os][i], |
|||
rSize: remoteBinSize, |
|||
lSize: localBinSize, |
|||
}); |
|||
} |
|||
|
|||
if (i === latestBins[_os].length - 1) { |
|||
shepherd.io.emit('patch', { |
|||
patch: { |
|||
type: 'bins-check', |
|||
status: 'done', |
|||
fileList: binsToUpdate, |
|||
}, |
|||
}); |
|||
} |
|||
}); |
|||
} |
|||
}); |
|||
|
|||
/* |
|||
* Update bins |
|||
* type: |
|||
* params: |
|||
*/ |
|||
shepherd.get('/update/bins', (req, res, next) => { |
|||
const rootLocation = shepherd.path.join(__dirname, '../../'); |
|||
const _os = shepherd.os.platform(); |
|||
const successObj = { |
|||
msg: 'success', |
|||
result: { |
|||
filesCount: binsToUpdate.length, |
|||
list: binsToUpdate, |
|||
}, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
|
|||
for (let i = 0; i < binsToUpdate.length; i++) { |
|||
shepherd.downloadFile({ |
|||
remoteFile: remoteBinLocation[_os] + binsToUpdate[i].name, |
|||
localFile: `${rootLocation}${localBinLocation[_os]}patch/${binsToUpdate[i].name}`, |
|||
onProgress: (received, total) => { |
|||
const percentage = (received * 100) / total; |
|||
|
|||
if (percentage.toString().indexOf('.10') > -1) { |
|||
shepherd.io.emit('patch', { |
|||
msg: { |
|||
type: 'bins-update', |
|||
status: 'progress', |
|||
file: binsToUpdate[i].name, |
|||
bytesTotal: total, |
|||
bytesReceived: received, |
|||
}, |
|||
}); |
|||
// shepherd.log(`${binsToUpdate[i].name} ${percentage}% | ${received} bytes out of ${total} bytes.`);
|
|||
} |
|||
} |
|||
}) |
|||
.then(() => { |
|||
// verify that remote file is matching to DL'ed file
|
|||
const localBinSize = shepherd.fs.statSync(`${rootLocation}${localBinLocation[_os]}patch/${binsToUpdate[i].name}`).size; |
|||
shepherd.log('compare dl file size'); |
|||
|
|||
if (localBinSize === binsToUpdate[i].rSize) { |
|||
shepherd.io.emit('patch', { |
|||
msg: { |
|||
type: 'bins-update', |
|||
file: binsToUpdate[i].name, |
|||
status: 'done', |
|||
}, |
|||
}); |
|||
shepherd.log(`file ${binsToUpdate[i].name} succesfully downloaded`); |
|||
} else { |
|||
shepherd.io.emit('patch', { |
|||
msg: { |
|||
type: 'bins-update', |
|||
file: binsToUpdate[i].name, |
|||
message: 'size mismatch', |
|||
}, |
|||
}); |
|||
shepherd.log(`error: ${binsToUpdate[i].name} file size doesnt match remote!`); |
|||
} |
|||
}); |
|||
} |
|||
}); |
|||
|
|||
return shepherd; |
|||
}; |
@ -0,0 +1,154 @@ |
|||
module.exports = (shepherd) => { |
|||
/* |
|||
* DL app patch |
|||
* type: GET |
|||
* params: patchList |
|||
*/ |
|||
shepherd.get('/update/patch', (req, res, next) => { |
|||
const successObj = { |
|||
msg: 'success', |
|||
result: 'dl started' |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
|
|||
shepherd.updateAgama(); |
|||
}); |
|||
|
|||
shepherd.updateAgama = () => { |
|||
const rootLocation = shepherd.path.join(__dirname, '../../'); |
|||
|
|||
shepherd.downloadFile({ |
|||
remoteFile: 'https://github.com/pbca26/dl-test/raw/master/patch.zip', |
|||
localFile: `${rootLocation}patch.zip`, |
|||
onProgress: (received, total) => { |
|||
const percentage = (received * 100) / total; |
|||
|
|||
if (percentage.toString().indexOf('.10') > -1) { |
|||
shepherd.io.emit('patch', { |
|||
msg: { |
|||
status: 'progress', |
|||
type: 'ui', |
|||
progress: percentage, |
|||
bytesTotal: total, |
|||
bytesReceived: received, |
|||
}, |
|||
}); |
|||
//shepherd.log(`patch ${percentage}% | ${received} bytes out of ${total} bytes.`);
|
|||
} |
|||
} |
|||
}) |
|||
.then(() => { |
|||
shepherd.remoteFileSize('https://github.com/pbca26/dl-test/raw/master/patch.zip', (err, remotePatchSize) => { |
|||
// verify that remote file is matching to DL'ed file
|
|||
const localPatchSize = shepherd.fs.statSync(`${rootLocation}patch.zip`).size; |
|||
shepherd.log('compare dl file size'); |
|||
|
|||
if (localPatchSize === remotePatchSize) { |
|||
const zip = new shepherd.AdmZip(`${rootLocation}patch.zip`); |
|||
|
|||
shepherd.log('patch succesfully downloaded'); |
|||
shepherd.log('extracting contents'); |
|||
|
|||
if (shepherd.appConfig.dev) { |
|||
if (!shepherd.fs.existsSync(`${rootLocation}/patch`)) { |
|||
shepherd.fs.mkdirSync(`${rootLocation}/patch`); |
|||
} |
|||
} |
|||
|
|||
zip.extractAllTo(/*target path*/rootLocation + (shepherd.appConfig.dev ? '/patch' : ''), /*overwrite*/true); |
|||
// TODO: extract files in chunks
|
|||
shepherd.io.emit('patch', { |
|||
msg: { |
|||
type: 'ui', |
|||
status: 'done', |
|||
}, |
|||
}); |
|||
shepherd.fs.unlinkSync(`${rootLocation}patch.zip`); |
|||
} else { |
|||
shepherd.io.emit('patch', { |
|||
msg: { |
|||
type: 'ui', |
|||
status: 'error', |
|||
message: 'size mismatch', |
|||
}, |
|||
}); |
|||
shepherd.log('patch file size doesnt match remote!'); |
|||
} |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
/* |
|||
* check latest version |
|||
* type: |
|||
* params: |
|||
*/ |
|||
shepherd.get('/update/patch/check', (req, res, next) => { |
|||
const rootLocation = shepherd.path.join(__dirname, '../../'); |
|||
const options = { |
|||
url: 'https://github.com/pbca26/dl-test/raw/master/version', |
|||
method: 'GET', |
|||
}; |
|||
|
|||
shepherd.request(options, (error, response, body) => { |
|||
if (response && |
|||
response.statusCode && |
|||
response.statusCode === 200) { |
|||
const remoteVersion = body.split('\n'); |
|||
const localVersionFile = shepherd.fs.readFileSync(`${rootLocation}version`, 'utf8'); |
|||
let localVersion; |
|||
|
|||
if (localVersionFile.indexOf('\r\n') > -1) { |
|||
localVersion = localVersionFile.split('\r\n'); |
|||
} else { |
|||
localVersion = localVersionFile.split('\n'); |
|||
} |
|||
|
|||
if (remoteVersion[0] === localVersion[0]) { |
|||
const successObj = { |
|||
msg: 'success', |
|||
result: 'latest', |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
} else { |
|||
const successObj = { |
|||
msg: 'success', |
|||
result: 'update', |
|||
version: { |
|||
local: localVersion[0], |
|||
remote: remoteVersion[0], |
|||
}, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
} |
|||
} else { |
|||
res.end({ |
|||
err: 'error getting update', |
|||
}); |
|||
} |
|||
}); |
|||
}); |
|||
|
|||
/* |
|||
* unpack zip |
|||
* type: |
|||
* params: |
|||
*/ |
|||
shepherd.get('/unpack', (req, res, next) => { |
|||
const dlLocation = shepherd.path.join(__dirname, '../../'); |
|||
const zip = new shepherd.AdmZip(`${dlLocation}patch.zip`); |
|||
zip.extractAllTo(/*target path*/ `${dlLocation}/patch/unpack`, /*overwrite*/true); |
|||
|
|||
const successObj = { |
|||
msg: 'success', |
|||
result: 'unpack started', |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
}); |
|||
|
|||
return shepherd; |
|||
}; |
@ -0,0 +1,49 @@ |
|||
module.exports = (shepherd) => { |
|||
/** |
|||
* Promise based download file method |
|||
*/ |
|||
shepherd.downloadFile = (configuration) => { |
|||
return new shepherd.Promise((resolve, reject) => { |
|||
// Save variable to know progress
|
|||
let receivedBytes = 0; |
|||
let totalBytes = 0; |
|||
|
|||
let req = shepherd.request({ |
|||
method: 'GET', |
|||
uri: configuration.remoteFile, |
|||
agentOptions: { |
|||
keepAlive: true, |
|||
keepAliveMsecs: 15000, |
|||
}, |
|||
}); |
|||
|
|||
let out = shepherd.fs.createWriteStream(configuration.localFile); |
|||
req.pipe(out); |
|||
|
|||
req.on('response', (data) => { |
|||
// Change the total bytes value to get progress later.
|
|||
totalBytes = parseInt(data.headers['content-length']); |
|||
}); |
|||
|
|||
// Get progress if callback exists
|
|||
if (configuration.hasOwnProperty('onProgress')) { |
|||
req.on('data', (chunk) => { |
|||
// Update the received bytes
|
|||
receivedBytes += chunk.length; |
|||
configuration.onProgress(receivedBytes, totalBytes); |
|||
}); |
|||
} else { |
|||
req.on('data', (chunk) => { |
|||
// Update the received bytes
|
|||
receivedBytes += chunk.length; |
|||
}); |
|||
} |
|||
|
|||
req.on('end', () => { |
|||
resolve(); |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
return shepherd; |
|||
}; |
@ -0,0 +1,134 @@ |
|||
module.exports = (shepherd) => { |
|||
shepherd.zcashParamsDownloadLinks = { |
|||
'agama.komodoplatform.com': { |
|||
proving: 'https://agama.komodoplatform.com/file/supernet/sprout-proving.key', |
|||
verifying: 'https://agama.komodoplatform.com/file/supernet/sprout-verifying.key', |
|||
}, |
|||
'agama-yq0ysrdtr.stackpathdns.com': { |
|||
proving: 'http://agama-yq0ysrdtr.stackpathdns.com/file/supernet/sprout-proving.key', |
|||
verifying: 'http://agama-yq0ysrdtr.stackpathdns.com/file/supernet/sprout-verifying.key', |
|||
}, |
|||
'zcash.dl.mercerweiss.com': { |
|||
proving: 'https://zcash.dl.mercerweiss.com/sprout-proving.key', |
|||
verifying: 'https://zcash.dl.mercerweiss.com/sprout-verifying.key', |
|||
}, |
|||
}; |
|||
|
|||
shepherd.zcashParamsExist = () => { |
|||
let _checkList = { |
|||
rootDir: shepherd._fs.existsSync(shepherd.zcashParamsDir), |
|||
provingKey: shepherd._fs.existsSync(`${shepherd.zcashParamsDir}/sprout-proving.key`), |
|||
provingKeySize: false, |
|||
verifyingKey: shepherd._fs.existsSync(`${shepherd.zcashParamsDir}/sprout-verifying.key`), |
|||
verifyingKeySize: false, |
|||
errors: false, |
|||
}; |
|||
|
|||
if (_checkList.rootDir && |
|||
_checkList.provingKey || |
|||
_checkList.verifyingKey) { |
|||
// verify each key size
|
|||
const _provingKeySize = _checkList.provingKey ? shepherd.fs.lstatSync(`${shepherd.zcashParamsDir}/sprout-proving.key`) : 0; |
|||
const _verifyingKeySize = _checkList.verifyingKey ? shepherd.fs.lstatSync(`${shepherd.zcashParamsDir}/sprout-verifying.key`) : 0; |
|||
|
|||
if (Number(_provingKeySize.size) === 910173851) { // bytes
|
|||
_checkList.provingKeySize = true; |
|||
} |
|||
if (Number(_verifyingKeySize.size) === 1449) { |
|||
_checkList.verifyingKeySize = true; |
|||
} |
|||
|
|||
shepherd.log('zcashparams exist'); |
|||
} else { |
|||
shepherd.log('zcashparams doesnt exist'); |
|||
} |
|||
|
|||
if (!_checkList.rootDir || |
|||
!_checkList.provingKey || |
|||
!_checkList.verifyingKey || |
|||
!_checkList.provingKeySize || |
|||
!_checkList.verifyingKeySize) { |
|||
_checkList.errors = true; |
|||
} |
|||
|
|||
return _checkList; |
|||
} |
|||
|
|||
shepherd.zcashParamsExistPromise = () => { |
|||
return new shepherd.Promise((resolve, reject) => { |
|||
const _verify = shepherd.zcashParamsExist(); |
|||
resolve(_verify); |
|||
}); |
|||
}; |
|||
|
|||
/* |
|||
* Update bins |
|||
* type: |
|||
* params: |
|||
*/ |
|||
shepherd.get('/zcparamsdl', (req, res, next) => { |
|||
// const dlLocation = shepherd.zcashParamsDir + '/test';
|
|||
const dlLocation = shepherd.zcashParamsDir; |
|||
const dlOption = req.query.dloption; |
|||
|
|||
const successObj = { |
|||
msg: 'success', |
|||
result: 'zcash params dl started', |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
|
|||
for (let key in shepherd.zcashParamsDownloadLinks[dlOption]) { |
|||
shepherd.downloadFile({ |
|||
remoteFile: shepherd.zcashParamsDownloadLinks[dlOption][key], |
|||
localFile: `${dlLocation}/sprout-${key}.key`, |
|||
onProgress: (received, total) => { |
|||
const percentage = (received * 100) / total; |
|||
|
|||
if (percentage.toString().indexOf('.10') > -1) { |
|||
shepherd.io.emit('zcparams', { |
|||
msg: { |
|||
type: 'zcpdownload', |
|||
status: 'progress', |
|||
file: key, |
|||
bytesTotal: total, |
|||
bytesReceived: received, |
|||
progress: percentage, |
|||
}, |
|||
}); |
|||
// shepherd.log(`${key} ${percentage}% | ${received} bytes out of ${total} bytes.`);
|
|||
} |
|||
} |
|||
}) |
|||
.then(() => { |
|||
const checkZcashParams = shepherd.zcashParamsExist(); |
|||
|
|||
shepherd.log(`${key} dl done`); |
|||
|
|||
if (checkZcashParams.error) { |
|||
shepherd.io.emit('zcparams', { |
|||
msg: { |
|||
type: 'zcpdownload', |
|||
file: key, |
|||
status: 'error', |
|||
message: 'size mismatch', |
|||
progress: 100, |
|||
}, |
|||
}); |
|||
} else { |
|||
shepherd.io.emit('zcparams', { |
|||
msg: { |
|||
type: 'zcpdownload', |
|||
file: key, |
|||
progress: 100, |
|||
status: 'done', |
|||
}, |
|||
}); |
|||
shepherd.log(`file ${key} succesfully downloaded`); |
|||
} |
|||
}); |
|||
} |
|||
}); |
|||
|
|||
return shepherd; |
|||
}; |
@ -0,0 +1,64 @@ |
|||
module.exports = (shepherd) => { |
|||
shepherd.post('/electrum/login', (req, res, next) => { |
|||
for (let key in shepherd.electrumServers) { |
|||
const _abbr = shepherd.electrumServers[key].abbr; |
|||
let keys; |
|||
|
|||
if (req.body.seed.length === 52 && |
|||
req.body.seed.indexOf(' ') === -1 && |
|||
req.body.seed.match(/^[a-zA-Z0-9]*$/)) { |
|||
let key = shepherd.bitcoinJS.ECPair.fromWIF(req.body.seed, shepherd.electrumJSNetworks.komodo); |
|||
keys = { |
|||
priv: key.toWIF(), |
|||
pub: key.getAddress(), |
|||
}; |
|||
} else { |
|||
keys = shepherd.seedToWif(req.body.seed, shepherd.findNetworkObj(_abbr), req.body.iguana, req.body.old); |
|||
} |
|||
|
|||
shepherd.electrumKeys[_abbr] = { |
|||
priv: keys.priv, |
|||
pub: keys.pub, |
|||
}; |
|||
} |
|||
|
|||
shepherd.electrumCoins.auth = true; |
|||
|
|||
// shepherd.log(JSON.stringify(shepherd.electrumKeys, null, '\t'), true);
|
|||
|
|||
const successObj = { |
|||
msg: 'success', |
|||
result: 'true', |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
}); |
|||
|
|||
shepherd.post('/electrum/lock', (req, res, next) => { |
|||
shepherd.electrumCoins.auth = false; |
|||
shepherd.electrumKeys = {}; |
|||
|
|||
const successObj = { |
|||
msg: 'success', |
|||
result: 'true', |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
}); |
|||
|
|||
shepherd.post('/electrum/logout', (req, res, next) => { |
|||
shepherd.electrumCoins = { |
|||
auth: false, |
|||
}; |
|||
shepherd.electrumKeys = {}; |
|||
|
|||
const obj = { |
|||
msg: 'success', |
|||
result: 'result', |
|||
}; |
|||
|
|||
res.end(JSON.stringify(obj)); |
|||
}); |
|||
|
|||
return shepherd; |
|||
}; |
@ -0,0 +1,145 @@ |
|||
module.exports = (shepherd) => { |
|||
shepherd.get('/electrum/getbalance', (req, res, next) => { |
|||
const network = req.query.network || shepherd.findNetworkObj(req.query.coin); |
|||
const ecl = new shepherd.electrumJSCore(shepherd.electrumServers[network].port, shepherd.electrumServers[network].address, shepherd.electrumServers[network].proto); // tcp or tls
|
|||
|
|||
shepherd.log('electrum getbalance =>', true); |
|||
|
|||
ecl.connect(); |
|||
ecl.blockchainAddressGetBalance(req.query.address) |
|||
.then((json) => { |
|||
if (json && |
|||
json.hasOwnProperty('confirmed') && |
|||
json.hasOwnProperty('unconfirmed')) { |
|||
if (network === 'komodo') { |
|||
ecl.connect(); |
|||
ecl.blockchainAddressListunspent(req.query.address) |
|||
.then((utxoList) => { |
|||
if (utxoList && |
|||
utxoList.length) { |
|||
// filter out < 10 KMD amounts
|
|||
let _utxo = []; |
|||
|
|||
for (let i = 0; i < utxoList.length; i++) { |
|||
shepherd.log(`utxo ${utxoList[i]['tx_hash']} sats ${utxoList[i].value} value ${Number(utxoList[i].value) * 0.00000001}`, true); |
|||
|
|||
if (Number(utxoList[i].value) * 0.00000001 >= 10) { |
|||
_utxo.push(utxoList[i]); |
|||
} |
|||
} |
|||
|
|||
shepherd.log('filtered utxo list =>', true); |
|||
shepherd.log(_utxo, true); |
|||
|
|||
if (_utxo && |
|||
_utxo.length) { |
|||
let interestTotal = 0; |
|||
|
|||
shepherd.Promise.all(_utxo.map((_utxoItem, index) => { |
|||
return new shepherd.Promise((resolve, reject) => { |
|||
ecl.blockchainTransactionGet(_utxoItem['tx_hash']) |
|||
.then((_rawtxJSON) => { |
|||
shepherd.log('electrum gettransaction ==>', true); |
|||
shepherd.log((index + ' | ' + (_rawtxJSON.length - 1)), true); |
|||
shepherd.log(_rawtxJSON, true); |
|||
|
|||
// decode tx
|
|||
const _network = shepherd.getNetworkData(network); |
|||
const decodedTx = shepherd.electrumJSTxDecoder(_rawtxJSON, _network); |
|||
|
|||
if (decodedTx && |
|||
decodedTx.format && |
|||
decodedTx.format.locktime > 0) { |
|||
interestTotal += shepherd.kmdCalcInterest(decodedTx.format.locktime, _utxoItem.value); |
|||
} |
|||
|
|||
shepherd.log('decoded tx =>', true); |
|||
shepherd.log(decodedTx, true); |
|||
|
|||
resolve(true); |
|||
}); |
|||
}); |
|||
})) |
|||
.then(promiseResult => { |
|||
ecl.close(); |
|||
|
|||
const successObj = { |
|||
msg: 'success', |
|||
result: { |
|||
balance: Number((0.00000001 * json.confirmed).toFixed(8)), |
|||
unconfirmed: Number((0.00000001 * json.unconfirmed).toFixed(8)), |
|||
unconfirmedSats: json.unconfirmed, |
|||
balanceSats: json.confirmed, |
|||
interest: Number(interestTotal.toFixed(8)), |
|||
interestSats: Math.floor(interestTotal * 100000000), |
|||
total: interestTotal > 0 ? Number((0.00000001 * json.confirmed + interestTotal).toFixed(8)) : 0, |
|||
totalSats: interestTotal > 0 ?json.confirmed + Math.floor(interestTotal * 100000000) : 0, |
|||
}, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
}); |
|||
} else { |
|||
const successObj = { |
|||
msg: 'success', |
|||
result: { |
|||
balance: Number((0.00000001 * json.confirmed).toFixed(8)), |
|||
unconfirmed: Number((0.00000001 * json.unconfirmed).toFixed(8)), |
|||
unconfirmedSats: json.unconfirmed, |
|||
balanceSats: json.confirmed, |
|||
interest: 0, |
|||
interestSats: 0, |
|||
total: 0, |
|||
totalSats: 0, |
|||
}, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
} |
|||
} else { |
|||
const successObj = { |
|||
msg: 'success', |
|||
result: { |
|||
balance: Number((0.00000001 * json.confirmed).toFixed(8)), |
|||
unconfirmed: Number((0.00000001 * json.unconfirmed).toFixed(8)), |
|||
unconfirmedSats: json.unconfirmed, |
|||
balanceSats: json.confirmed, |
|||
interest: 0, |
|||
interestSats: 0, |
|||
total: 0, |
|||
totalSats: 0, |
|||
}, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
} |
|||
}); |
|||
} else { |
|||
ecl.close(); |
|||
shepherd.log('electrum getbalance ==>', true); |
|||
shepherd.log(json, true); |
|||
|
|||
const successObj = { |
|||
msg: 'success', |
|||
result: { |
|||
balance: Number((0.00000001 * json.confirmed).toFixed(8)), |
|||
unconfirmed: Number((0.00000001 * json.unconfirmed).toFixed(8)), |
|||
unconfirmedSats: json.unconfirmed, |
|||
balanceSats: json.confirmed, |
|||
}, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
} |
|||
} else { |
|||
const successObj = { |
|||
msg: 'error', |
|||
result: shepherd.CONNECTION_ERROR_OR_INCOMPLETE_DATA, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
} |
|||
}); |
|||
}); |
|||
return shepherd; |
|||
}; |
@ -0,0 +1,59 @@ |
|||
module.exports = (shepherd) => { |
|||
shepherd.get('/electrum/getblockinfo', (req, res, next) => { |
|||
shepherd.electrumGetBlockInfo(req.query.height, req.query.network) |
|||
.then((json) => { |
|||
const successObj = { |
|||
msg: 'success', |
|||
result: json, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
}); |
|||
}); |
|||
|
|||
shepherd.electrumGetBlockInfo = (height, network) => { |
|||
return new shepherd.Promise((resolve, reject) => { |
|||
const ecl = new shepherd.electrumJSCore(shepherd.electrumServers[network].port, shepherd.electrumServers[network].address, shepherd.electrumServers[network].proto); // tcp or tls
|
|||
|
|||
ecl.connect(); |
|||
ecl.blockchainBlockGetHeader(height) |
|||
.then((json) => { |
|||
ecl.close(); |
|||
shepherd.log('electrum getblockinfo ==>', true); |
|||
shepherd.log(json, true); |
|||
|
|||
resolve(json); |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
shepherd.get('/electrum/getcurrentblock', (req, res, next) => { |
|||
shepherd.electrumGetCurrentBlock(req.query.network) |
|||
.then((json) => { |
|||
const successObj = { |
|||
msg: 'success', |
|||
result: json, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
}); |
|||
}); |
|||
|
|||
shepherd.electrumGetCurrentBlock = (network) => { |
|||
return new shepherd.Promise((resolve, reject) => { |
|||
const ecl = new shepherd.electrumJSCore(shepherd.electrumServers[network].port, shepherd.electrumServers[network].address, shepherd.electrumServers[network].proto); // tcp or tls
|
|||
|
|||
ecl.connect(); |
|||
ecl.blockchainNumblocksSubscribe() |
|||
.then((json) => { |
|||
ecl.close(); |
|||
shepherd.log('electrum currentblock ==>', true); |
|||
shepherd.log(json, true); |
|||
|
|||
resolve(json); |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
return shepherd; |
|||
}; |
@ -0,0 +1,91 @@ |
|||
module.exports = (shepherd) => { |
|||
shepherd.findCoinName = (network) => { |
|||
for (let key in shepherd.electrumServers) { |
|||
if (key === network) { |
|||
return shepherd.electrumServers[key].abbr; |
|||
} |
|||
} |
|||
} |
|||
|
|||
shepherd.addElectrumCoin = (coin) => { |
|||
const servers = shepherd.electrumServers[coin === 'KMD' ? 'komodo' : coin.toLowerCase()].serverList; |
|||
// select random server
|
|||
const getRandomIntInclusive = (min, max) => { |
|||
min = Math.ceil(min); |
|||
max = Math.floor(max); |
|||
|
|||
return Math.floor(Math.random() * (max - min + 1)) + min; // the maximum is inclusive and the minimum is inclusive
|
|||
} |
|||
let randomServer; |
|||
|
|||
// pick a random server to communicate with
|
|||
if (servers && |
|||
servers.length > 0) { |
|||
const _randomServerId = getRandomIntInclusive(0, servers.length - 1); |
|||
const _randomServer = servers[_randomServerId]; |
|||
const _serverDetails = _randomServer.split(':'); |
|||
|
|||
if (_serverDetails.length === 2) { |
|||
randomServer = { |
|||
ip: _serverDetails[0], |
|||
port: _serverDetails[1], |
|||
}; |
|||
} |
|||
} |
|||
|
|||
for (let key in shepherd.electrumServers) { |
|||
if (shepherd.electrumServers[key].abbr === coin) { |
|||
shepherd.electrumCoins[coin] = { |
|||
name: key, |
|||
abbr: coin, |
|||
server: { |
|||
ip: randomServer ? randomServer.ip : shepherd.electrumServers[key].address, |
|||
port: randomServer ? randomServer.port : shepherd.electrumServers[key].port, |
|||
}, |
|||
serverList: shepherd.electrumServers[key].serverList ? shepherd.electrumServers[key].serverList : 'none', |
|||
txfee: 'calculated' /*shepherd.electrumServers[key].txfee*/, |
|||
}; |
|||
|
|||
shepherd.log(`default ${coin} electrum server ${shepherd.electrumServers[key].address + ':' + shepherd.electrumServers[key].port}`, true); |
|||
|
|||
if (randomServer) { |
|||
shepherd.log(`random ${coin} electrum server ${randomServer.ip + ':' + randomServer.port}`, true); |
|||
} else { |
|||
shepherd.log(`${coin} doesnt have any backup electrum servers`, true); |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
} |
|||
|
|||
shepherd.get('/electrum/coins/add', (req, res, next) => { |
|||
const result = shepherd.addElectrumCoin(req.query.coin); |
|||
|
|||
const successObj = { |
|||
msg: 'success', |
|||
result, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
}); |
|||
|
|||
shepherd.get('/electrum/coins', (req, res, next) => { |
|||
let _electrumCoins = JSON.parse(JSON.stringify(shepherd.electrumCoins)); // deep cloning
|
|||
|
|||
for (let key in _electrumCoins) { |
|||
if (shepherd.electrumKeys[key]) { |
|||
_electrumCoins[key].pub = shepherd.electrumKeys[key].pub; |
|||
} |
|||
} |
|||
|
|||
const successObj = { |
|||
msg: 'success', |
|||
result: _electrumCoins, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
}); |
|||
|
|||
return shepherd; |
|||
}; |
@ -0,0 +1,487 @@ |
|||
module.exports = (shepherd) => { |
|||
// unsigned tx
|
|||
shepherd.buildUnsignedTx = (sendTo, changeAddress, network, utxo, changeValue, spendValue) => { |
|||
let tx = new shepherd.bitcoinJS.TransactionBuilder(shepherd.getNetworkData(network)); |
|||
|
|||
shepherd.log('buildSignedTx'); |
|||
// console.log(`buildSignedTx priv key ${wif}`);
|
|||
shepherd.log(`buildSignedTx pub key ${changeAddress}`, true); |
|||
// console.log('buildSignedTx std tx fee ' + shepherd.electrumServers[network].txfee);
|
|||
|
|||
for (let i = 0; i < utxo.length; i++) { |
|||
tx.addInput(utxo[i].txid, utxo[i].vout); |
|||
} |
|||
|
|||
tx.addOutput(sendTo, Number(spendValue)); |
|||
|
|||
if (changeValue > 0) { |
|||
tx.addOutput(changeAddress, Number(changeValue)); |
|||
} |
|||
|
|||
if (network === 'komodo' || |
|||
network === 'KMD') { |
|||
const _locktime = Math.floor(Date.now() / 1000) - 777; |
|||
tx.setLockTime(_locktime); |
|||
shepherd.log(`kmd tx locktime set to ${_locktime}`, true); |
|||
} |
|||
|
|||
shepherd.log('buildSignedTx unsigned tx data vin', true); |
|||
shepherd.log(tx.tx.ins, true); |
|||
shepherd.log('buildSignedTx unsigned tx data vout', true); |
|||
shepherd.log(tx.tx.outs, true); |
|||
shepherd.log('buildSignedTx unsigned tx data', true); |
|||
shepherd.log(tx, true); |
|||
|
|||
const rawtx = tx.buildIncomplete().toHex(); |
|||
|
|||
shepherd.log('buildUnsignedTx tx hex', true); |
|||
shepherd.log(rawtx, true); |
|||
|
|||
return rawtx; |
|||
} |
|||
|
|||
// single sig
|
|||
shepherd.buildSignedTx = (sendTo, changeAddress, wif, network, utxo, changeValue, spendValue) => { |
|||
let key = shepherd.bitcoinJS.ECPair.fromWIF(wif, shepherd.getNetworkData(network)); |
|||
let tx = new shepherd.bitcoinJS.TransactionBuilder(shepherd.getNetworkData(network)); |
|||
|
|||
shepherd.log('buildSignedTx'); |
|||
// console.log(`buildSignedTx priv key ${wif}`);
|
|||
shepherd.log(`buildSignedTx pub key ${key.getAddress().toString()}`, true); |
|||
// console.log('buildSignedTx std tx fee ' + shepherd.electrumServers[network].txfee);
|
|||
|
|||
for (let i = 0; i < utxo.length; i++) { |
|||
tx.addInput(utxo[i].txid, utxo[i].vout); |
|||
} |
|||
|
|||
tx.addOutput(sendTo, Number(spendValue)); |
|||
|
|||
if (changeValue > 0) { |
|||
tx.addOutput(changeAddress, Number(changeValue)); |
|||
} |
|||
|
|||
if (network === 'komodo' || |
|||
network === 'KMD') { |
|||
const _locktime = Math.floor(Date.now() / 1000) - 777; |
|||
tx.setLockTime(_locktime); |
|||
shepherd.log(`kmd tx locktime set to ${_locktime}`, true); |
|||
} |
|||
|
|||
shepherd.log('buildSignedTx unsigned tx data vin', true); |
|||
shepherd.log(tx.tx.ins, true); |
|||
shepherd.log('buildSignedTx unsigned tx data vout', true); |
|||
shepherd.log(tx.tx.outs, true); |
|||
shepherd.log('buildSignedTx unsigned tx data', true); |
|||
shepherd.log(tx, true); |
|||
|
|||
for (let i = 0; i < utxo.length; i++) { |
|||
tx.sign(i, key); |
|||
} |
|||
|
|||
const rawtx = tx.build().toHex(); |
|||
|
|||
shepherd.log('buildSignedTx signed tx hex', true); |
|||
shepherd.log(rawtx, true); |
|||
|
|||
return rawtx; |
|||
} |
|||
|
|||
shepherd.maxSpendBalance = (utxoList, fee) => { |
|||
let maxSpendBalance = 0; |
|||
|
|||
for (let i = 0; i < utxoList.length; i++) { |
|||
maxSpendBalance += Number(utxoList[i].value); |
|||
} |
|||
|
|||
if (fee) { |
|||
return Number(maxSpendBalance) - Number(fee); |
|||
} else { |
|||
return maxSpendBalance; |
|||
} |
|||
} |
|||
|
|||
shepherd.get('/electrum/createrawtx', (req, res, next) => { |
|||
// txid 64 char
|
|||
const network = req.query.network || shepherd.findNetworkObj(req.query.coin); |
|||
const ecl = new shepherd.electrumJSCore(shepherd.electrumServers[network].port, shepherd.electrumServers[network].address, shepherd.electrumServers[network].proto); // tcp or tls
|
|||
const outputAddress = req.query.address; |
|||
const changeAddress = req.query.change; |
|||
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; |
|||
} |
|||
|
|||
shepherd.log('electrum createrawtx =>', true); |
|||
|
|||
ecl.connect(); |
|||
shepherd.listunspent(ecl, changeAddress, network, true, true) |
|||
.then((utxoList) => { |
|||
ecl.close(); |
|||
|
|||
if (utxoList && |
|||
utxoList.length) { |
|||
let utxoListFormatted = []; |
|||
let totalInterest = 0; |
|||
let totalInterestUTXOCount = 0; |
|||
let interestClaimThreshold = 200; |
|||
let utxoVerified = true; |
|||
|
|||
for (let i = 0; i < utxoList.length; i++) { |
|||
if (network === 'komodo') { |
|||
utxoListFormatted.push({ |
|||
txid: utxoList[i].txid, |
|||
vout: utxoList[i].vout, |
|||
value: Number(utxoList[i].amountSats), |
|||
interestSats: Number(utxoList[i].interestSats), |
|||
verified: utxoList[i].verified ? utxoList[i].verified : false, |
|||
}); |
|||
} else { |
|||
utxoListFormatted.push({ |
|||
txid: utxoList[i].txid, |
|||
vout: utxoList[i].vout, |
|||
value: Number(utxoList[i].amountSats), |
|||
verified: utxoList[i].verified ? utxoList[i].verified : false, |
|||
}); |
|||
} |
|||
} |
|||
|
|||
shepherd.log('electrum listunspent unformatted ==>', true); |
|||
shepherd.log(utxoList, true); |
|||
|
|||
shepherd.log('electrum listunspent formatted ==>', true); |
|||
shepherd.log(utxoListFormatted, true); |
|||
|
|||
const _maxSpendBalance = Number(shepherd.maxSpendBalance(utxoListFormatted)); |
|||
let targets = [{ |
|||
address: outputAddress, |
|||
value: value > _maxSpendBalance ? _maxSpendBalance : value, |
|||
}]; |
|||
shepherd.log('targets =>', true); |
|||
shepherd.log(targets, true); |
|||
|
|||
const feeRate = 20; // sats/byte
|
|||
|
|||
// default coin selection algo blackjack with fallback to accumulative
|
|||
// make a first run, calc approx tx fee
|
|||
// if ins and outs are empty reduce max spend by txfee
|
|||
let { inputs, outputs, fee } = shepherd.coinSelect(utxoListFormatted, targets, feeRate); |
|||
|
|||
shepherd.log('coinselect res =>', true); |
|||
shepherd.log('coinselect inputs =>', true); |
|||
shepherd.log(inputs, true); |
|||
shepherd.log('coinselect outputs =>', true); |
|||
shepherd.log(outputs, true); |
|||
shepherd.log('coinselect calculated fee =>', true); |
|||
shepherd.log(fee, true); |
|||
|
|||
if (!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, feeRate); |
|||
inputs = secondRun.inputs; |
|||
outputs = secondRun.outputs; |
|||
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 === 2) { |
|||
_change = outputs[1].value; |
|||
} |
|||
|
|||
// check if any outputs are unverified
|
|||
if (inputs && |
|||
inputs.length) { |
|||
for (let i = 0; i < inputs.length; i++) { |
|||
if (!inputs[i].verified) { |
|||
utxoVerified = false; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
for (let i = 0; i < inputs.length; i++) { |
|||
if (Number(inputs[i].interestSats) > interestClaimThreshold) { |
|||
totalInterest += Number(inputs[i].interestSats); |
|||
totalInterestUTXOCount++; |
|||
} |
|||
} |
|||
} |
|||
|
|||
const _maxSpend = shepherd.maxSpendBalance(utxoListFormatted); |
|||
|
|||
if (value > _maxSpend) { |
|||
const successObj = { |
|||
msg: 'error', |
|||
result: `Spend value is too large. Max available amount is ${Number((_maxSpend * 0.00000001.toFixed(8)))}`, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
} else { |
|||
shepherd.log(`maxspend ${_maxSpend} (${_maxSpend * 0.00000001})`, true); |
|||
shepherd.log(`value ${value}`, true); |
|||
shepherd.log(`sendto ${outputAddress} amount ${value} (${value * 0.00000001})`, true); |
|||
shepherd.log(`changeto ${changeAddress} amount ${_change} (${_change * 0.00000001})`, true); |
|||
|
|||
// account for KMD interest
|
|||
if (network === 'komodo' && |
|||
totalInterest > 0) { |
|||
// account for extra vout
|
|||
const _feeOverhead = outputs.length === 1 ? shepherd.estimateTxSize(0, 1) * feeRate : 0; |
|||
|
|||
shepherd.log(`max interest to claim ${totalInterest} (${totalInterest * 0.00000001})`, true); |
|||
shepherd.log(`estimated fee overhead ${_feeOverhead}`, true); |
|||
shepherd.log(`current change amount ${_change} (${_change * 0.00000001}), boosted change amount ${_change + (totalInterest - _feeOverhead)} (${(_change + (totalInterest - _feeOverhead)) * 0.00000001})`, true); |
|||
|
|||
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); |
|||
} |
|||
} 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; |
|||
} |
|||
|
|||
const _estimatedFee = vinSum - outputs[0].value - _change; |
|||
|
|||
shepherd.log(`vin sum ${vinSum} (${vinSum * 0.00000001})`, true); |
|||
shepherd.log(`estimatedFee ${_estimatedFee} (${_estimatedFee * 0.00000001})`, true); |
|||
|
|||
let _rawtx; |
|||
|
|||
if (req.query.unsigned) { |
|||
_rawtx = shepherd.buildUnsignedTx( |
|||
outputAddress, |
|||
changeAddress, |
|||
network, |
|||
inputs, |
|||
_change, |
|||
value |
|||
); |
|||
} else { |
|||
_rawtx = shepherd.buildSignedTx( |
|||
outputAddress, |
|||
changeAddress, |
|||
wif, |
|||
network, |
|||
inputs, |
|||
_change, |
|||
value |
|||
); |
|||
} |
|||
|
|||
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(); |
|||
|
|||
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, |
|||
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)); |
|||
} |
|||
} 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)); |
|||
} 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)); |
|||
} |
|||
} |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
} else { |
|||
const successObj = { |
|||
msg: 'error', |
|||
result: utxoList, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
} |
|||
}); |
|||
}); |
|||
|
|||
shepherd.get('/electrum/pushtx', (req, res, next) => { |
|||
const rawtx = req.query.rawtx; |
|||
const ecl = new shepherd.electrumJSCore(shepherd.electrumServers[req.query.network].port, shepherd.electrumServers[req.query.network].address, shepherd.electrumServers[req.query.network].proto); // tcp or tls
|
|||
|
|||
ecl.connect(); |
|||
ecl.blockchainTransactionBroadcast(rawtx) |
|||
.then((json) => { |
|||
ecl.close(); |
|||
shepherd.log('electrum pushtx ==>', true); |
|||
shepherd.log(json, true); |
|||
|
|||
const successObj = { |
|||
msg: 'success', |
|||
result: json, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
}); |
|||
}); |
|||
|
|||
return shepherd; |
|||
}; |
@ -0,0 +1,26 @@ |
|||
module.exports = (shepherd) => { |
|||
shepherd.get('/electrum/estimatefee', (req, res, next) => { |
|||
const ecl = new shepherd.electrumJSCore(shepherd.electrumServers[req.query.network].port, shepherd.electrumServers[req.query.network].address, shepherd.electrumServers[req.query.network].proto); // tcp or tls
|
|||
|
|||
ecl.connect(); |
|||
ecl.blockchainEstimatefee(req.query.blocks) |
|||
.then((json) => { |
|||
ecl.close(); |
|||
shepherd.log('electrum estimatefee ==>', true); |
|||
|
|||
const successObj = { |
|||
msg: 'success', |
|||
result: json, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
}); |
|||
}); |
|||
|
|||
shepherd.estimateTxSize = (numVins, numOuts) => { |
|||
// in x 180 + out x 34 + 10 plus or minus in
|
|||
return numVins * 180 + numOuts * 34 + 11; |
|||
} |
|||
|
|||
return shepherd; |
|||
}; |
@ -0,0 +1,37 @@ |
|||
module.exports = (shepherd) => { |
|||
shepherd.kmdCalcInterest = (locktime, value) => { // value in sats
|
|||
const timestampDiff = Math.floor(Date.now() / 1000) - locktime - 777; |
|||
const hoursPassed = Math.floor(timestampDiff / 3600); |
|||
const minutesPassed = Math.floor((timestampDiff - (hoursPassed * 3600)) / 60); |
|||
const secondsPassed = timestampDiff - (hoursPassed * 3600) - (minutesPassed * 60); |
|||
let timestampDiffMinutes = timestampDiff / 60; |
|||
let interest = 0; |
|||
|
|||
shepherd.log('kmdCalcInterest', true); |
|||
shepherd.log(`locktime ${locktime}`, true); |
|||
shepherd.log(`minutes converted ${timestampDiffMinutes}`, true); |
|||
shepherd.log(`passed ${hoursPassed}h ${minutesPassed}m ${secondsPassed}s`, true); |
|||
|
|||
// calc interest
|
|||
if (timestampDiffMinutes >= 60) { |
|||
if (timestampDiffMinutes > 365 * 24 * 60) { |
|||
timestampDiffMinutes = 365 * 24 * 60; |
|||
} |
|||
timestampDiffMinutes -= 59; |
|||
|
|||
shepherd.log(`minutes if statement ${timestampDiffMinutes}`, true); |
|||
|
|||
// TODO: check if interest is > 5% yr
|
|||
// calc ytd and 5% for 1 yr
|
|||
// const hoursInOneYear = 365 * 24;
|
|||
// const hoursDiff = hoursInOneYear - hoursPassed;
|
|||
|
|||
interest = ((Number(value) * 0.00000001) / 10512000) * timestampDiffMinutes; |
|||
shepherd.log(`interest ${interest}`, true); |
|||
} |
|||
|
|||
return interest; |
|||
} |
|||
|
|||
return shepherd; |
|||
}; |
@ -0,0 +1,178 @@ |
|||
const sha256 = require('js-sha256'); |
|||
|
|||
module.exports = (shepherd) => { |
|||
shepherd.seedToWif = (seed, network, iguana) => { |
|||
let bytes; |
|||
|
|||
if (process.argv.indexOf('spvold=true') > -1) { |
|||
bytes = shepherd.sha256(seed, { asBytes: true }); |
|||
} else { |
|||
const hash = sha256.create().update(seed); |
|||
bytes = hash.array(); |
|||
} |
|||
|
|||
if (iguana) { |
|||
bytes[0] &= 248; |
|||
bytes[31] &= 127; |
|||
bytes[31] |= 64; |
|||
} |
|||
|
|||
const toHexString = (byteArray) => { |
|||
return Array.from(byteArray, (byte) => { |
|||
return ('0' + (byte & 0xFF).toString(16)).slice(-2); |
|||
}).join(''); |
|||
} |
|||
|
|||
const hex = toHexString(bytes); |
|||
|
|||
const key = new shepherd.CoinKey(new Buffer(hex, 'hex'), { |
|||
private: shepherd.getNetworkData(network).wif, |
|||
public: shepherd.getNetworkData(network).pubKeyHash, |
|||
}); |
|||
|
|||
key.compressed = true; |
|||
|
|||
// shepherd.log(`seed: ${seed}`, true);
|
|||
// shepherd.log(`network ${network}`, true);
|
|||
// shepherd.log(`seedtowif priv key ${key.privateWif}`, true);
|
|||
// shepherd.log(`seedtowif pub key ${key.publicAddress}`, true);
|
|||
|
|||
return { |
|||
priv: key.privateWif, |
|||
pub: key.publicAddress, |
|||
}; |
|||
} |
|||
|
|||
shepherd.get('/electrum/seedtowif', (req, res, next) => { |
|||
const keys = shepherd.seedToWif(req.query.seed, req.query.network, req.query.iguana); |
|||
|
|||
const successObj = { |
|||
msg: 'success', |
|||
result: { |
|||
keys, |
|||
}, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
}); |
|||
|
|||
shepherd.post('/electrum/keys', (req, res, next) => { |
|||
let _matchingKeyPairs = 0; |
|||
let _electrumKeys = {}; |
|||
|
|||
for (let key in shepherd.electrumServers) { |
|||
const _abbr = shepherd.electrumServers[key].abbr; |
|||
const { priv, pub } = shepherd.seedToWif(req.body.seed, shepherd.findNetworkObj(_abbr), req.body.iguana); |
|||
|
|||
if (shepherd.electrumKeys[_abbr].pub === pub && |
|||
shepherd.electrumKeys[_abbr].priv === priv) { |
|||
_matchingKeyPairs++; |
|||
} |
|||
} |
|||
|
|||
if (req.body.active) { |
|||
_electrumKeys = JSON.parse(JSON.stringify(shepherd.electrumKeys)); |
|||
|
|||
for (let key in _electrumKeys) { |
|||
if (!shepherd.electrumCoins[key]) { |
|||
delete _electrumKeys[key]; |
|||
} |
|||
} |
|||
} else { |
|||
_electrumKeys = shepherd.electrumKeys; |
|||
} |
|||
|
|||
// shepherd.log(JSON.stringify(_electrumKeys, null, '\t'), true);
|
|||
|
|||
const successObj = { |
|||
msg: 'success', |
|||
result: _matchingKeyPairs === Object.keys(shepherd.electrumKeys).length ? _electrumKeys : false, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
}); |
|||
|
|||
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(), |
|||
}; |
|||
} |
|||
/*_addresses.push({ |
|||
pub: _key.keyPair.getAddress(), |
|||
priv: _key.keyPair.toWIF(), |
|||
});*/ |
|||
} |
|||
} |
|||
} |
|||
|
|||
const successObj = { |
|||
msg: 'success', |
|||
result: _matchingKey ? _matchingKey : 'address is not found', |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
}); |
|||
|
|||
// 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'); |
|||
|
|||
// what you describe as 'seed'
|
|||
const randomBytes = crypto.randomBytes(16); // 128 bits is enough
|
|||
|
|||
// your 12 word phrase
|
|||
const mnemonic = bip39.entropyToMnemonic(randomBytes.toString('hex')); |
|||
|
|||
// what is accurately described as the wallet seed
|
|||
// var seed = bip39.mnemonicToSeed(mnemonic) // you'll use this in #3 below
|
|||
const seed = bip39.mnemonicToSeed(_seed); |
|||
|
|||
console.log(seed); |
|||
|
|||
const successObj = { |
|||
msg: 'success', |
|||
result: { |
|||
servers: shepherd.electrumServers, |
|||
}, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
|
|||
console.log(shepherd.bitcoinJS.networks.komodo); |
|||
const hdMaster = shepherd.bitcoinJS.HDNode.fromSeedBuffer(seed, shepherd.electrumJSNetworks.komodo); // seed from above
|
|||
|
|||
const key1 = hdMaster.derivePath("m/44'/141'/0'/0/0"); |
|||
const key2 = hdMaster.derivePath('m/1'); |
|||
console.log(hdMaster); |
|||
|
|||
console.log(key1.keyPair.toWIF()); |
|||
console.log(key1.keyPair.getAddress()); |
|||
console.log(key2.keyPair.toWIF()); |
|||
|
|||
const hdnode = shepherd.bitcoinJS.HDNode.fromSeedBuffer(seed, shepherd.electrumJSNetworks.komodo).deriveHardened(0).derive(0).derive(1); |
|||
console.log(`address: ${hdnode.getAddress()}`); |
|||
console.log(`priv (WIF): ${hdnode.keyPair.toWIF()}`); |
|||
});*/ |
|||
|
|||
return shepherd; |
|||
}; |
@ -0,0 +1,195 @@ |
|||
module.exports = (shepherd) => { |
|||
shepherd.listunspent = (ecl, address, network, full, verify) => { |
|||
let _atLeastOneDecodeTxFailed = false; |
|||
|
|||
if (full) { |
|||
return new shepherd.Promise((resolve, reject) => { |
|||
ecl.connect(); |
|||
ecl.blockchainAddressListunspent(address) |
|||
.then((_utxoJSON) => { |
|||
if (_utxoJSON && |
|||
_utxoJSON.length) { |
|||
let formattedUtxoList = []; |
|||
let _utxo = []; |
|||
|
|||
ecl.blockchainNumblocksSubscribe() |
|||
.then((currentHeight) => { |
|||
if (currentHeight && |
|||
Number(currentHeight) > 0) { |
|||
// filter out unconfirmed utxos
|
|||
for (let i = 0; i < _utxoJSON.length; i++) { |
|||
if (Number(currentHeight) - Number(_utxoJSON[i].height) !== 0) { |
|||
_utxo.push(_utxoJSON[i]); |
|||
} |
|||
} |
|||
|
|||
if (!_utxo.length) { // no confirmed utxo
|
|||
resolve('no valid utxo'); |
|||
} else { |
|||
shepherd.Promise.all(_utxo.map((_utxoItem, index) => { |
|||
return new shepherd.Promise((resolve, reject) => { |
|||
ecl.blockchainTransactionGet(_utxoItem['tx_hash']) |
|||
.then((_rawtxJSON) => { |
|||
shepherd.log('electrum gettransaction ==>', true); |
|||
shepherd.log(index + ' | ' + (_rawtxJSON.length - 1), true); |
|||
shepherd.log(_rawtxJSON, true); |
|||
|
|||
// decode tx
|
|||
const _network = shepherd.getNetworkData(network); |
|||
const decodedTx = shepherd.electrumJSTxDecoder(_rawtxJSON, _network); |
|||
|
|||
shepherd.log('decoded tx =>', true); |
|||
shepherd.log(decodedTx, true); |
|||
|
|||
if (!decodedTx) { |
|||
_atLeastOneDecodeTxFailed = true; |
|||
resolve('cant decode tx'); |
|||
} else { |
|||
if (network === 'komodo') { |
|||
let interest = 0; |
|||
|
|||
if (Number(_utxoItem.value) * 0.00000001 >= 10 && |
|||
decodedTx.format.locktime > 0) { |
|||
interest = shepherd.kmdCalcInterest(decodedTx.format.locktime, _utxoItem.value); |
|||
} |
|||
|
|||
let _resolveObj = { |
|||
txid: _utxoItem['tx_hash'], |
|||
vout: _utxoItem['tx_pos'], |
|||
address, |
|||
amount: Number(_utxoItem.value) * 0.00000001, |
|||
amountSats: _utxoItem.value, |
|||
locktime: decodedTx.format.locktime, |
|||
interest: Number(interest.toFixed(8)), |
|||
interestSats: Math.floor(interest * 100000000), |
|||
confirmations: Number(_utxoItem.height) === 0 ? 0 : currentHeight - _utxoItem.height, |
|||
spendable: true, |
|||
verified: false, |
|||
}; |
|||
|
|||
// merkle root verification agains another electrum server
|
|||
if (verify) { |
|||
shepherd.verifyMerkleByCoin(shepherd.findCoinName(network), _utxoItem['tx_hash'], _utxoItem.height) |
|||
.then((verifyMerkleRes) => { |
|||
if (verifyMerkleRes && verifyMerkleRes === shepherd.CONNECTION_ERROR_OR_INCOMPLETE_DATA) { |
|||
verifyMerkleRes = false; |
|||
} |
|||
|
|||
_resolveObj.verified = verifyMerkleRes; |
|||
resolve(_resolveObj); |
|||
}); |
|||
} else { |
|||
resolve(_resolveObj); |
|||
} |
|||
} else { |
|||
let _resolveObj = { |
|||
txid: _utxoItem['tx_hash'], |
|||
vout: _utxoItem['tx_pos'], |
|||
address, |
|||
amount: Number(_utxoItem.value) * 0.00000001, |
|||
amountSats: _utxoItem.value, |
|||
confirmations: Number(_utxoItem.height) === 0 ? 0 : currentHeight - _utxoItem.height, |
|||
spendable: true, |
|||
verified: false, |
|||
}; |
|||
|
|||
// merkle root verification agains another electrum server
|
|||
if (verify) { |
|||
shepherd.verifyMerkleByCoin(shepherd.findCoinName(network), _utxoItem['tx_hash'], _utxoItem.height) |
|||
.then((verifyMerkleRes) => { |
|||
if (verifyMerkleRes && |
|||
verifyMerkleRes === shepherd.CONNECTION_ERROR_OR_INCOMPLETE_DATA) { |
|||
verifyMerkleRes = false; |
|||
} |
|||
|
|||
_resolveObj.verified = verifyMerkleRes; |
|||
resolve(_resolveObj); |
|||
}); |
|||
} else { |
|||
resolve(_resolveObj); |
|||
} |
|||
} |
|||
} |
|||
}); |
|||
}); |
|||
})) |
|||
.then(promiseResult => { |
|||
ecl.close(); |
|||
|
|||
if (!_atLeastOneDecodeTxFailed) { |
|||
shepherd.log(promiseResult, true); |
|||
resolve(promiseResult); |
|||
} else { |
|||
shepherd.log('listunspent error, cant decode tx(s)', true); |
|||
resolve('decode error'); |
|||
} |
|||
}); |
|||
} |
|||
} else { |
|||
resolve('cant get current height'); |
|||
} |
|||
}); |
|||
} else { |
|||
ecl.close(); |
|||
resolve(shepherd.CONNECTION_ERROR_OR_INCOMPLETE_DATA); |
|||
} |
|||
}); |
|||
}); |
|||
} else { |
|||
return new shepherd.Promise((resolve, reject) => { |
|||
ecl.connect(); |
|||
ecl.blockchainAddressListunspent(address) |
|||
.then((json) => { |
|||
ecl.close(); |
|||
|
|||
if (json && |
|||
json.length) { |
|||
resolve(json); |
|||
} else { |
|||
resolve(shepherd.CONNECTION_ERROR_OR_INCOMPLETE_DATA); |
|||
} |
|||
}); |
|||
}); |
|||
} |
|||
} |
|||
|
|||
shepherd.get('/electrum/listunspent', (req, res, next) => { |
|||
const network = req.query.network || shepherd.findNetworkObj(req.query.coin); |
|||
const ecl = new shepherd.electrumJSCore(shepherd.electrumServers[network].port, shepherd.electrumServers[network].address, shepherd.electrumServers[network].proto); // tcp or tls
|
|||
|
|||
if (req.query.full && |
|||
req.query.full === 'true') { |
|||
shepherd.listunspent( |
|||
ecl, |
|||
req.query.address, |
|||
network, |
|||
true, |
|||
req.query.verify |
|||
).then((listunspent) => { |
|||
shepherd.log('electrum listunspent ==>', true); |
|||
|
|||
const successObj = { |
|||
msg: 'success', |
|||
result: listunspent, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
}); |
|||
} else { |
|||
shepherd.listunspent(ecl, req.query.address, network) |
|||
.then((listunspent) => { |
|||
ecl.close(); |
|||
shepherd.log('electrum listunspent ==>', true); |
|||
|
|||
const successObj = { |
|||
msg: 'success', |
|||
result: listunspent, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
}); |
|||
} |
|||
}); |
|||
|
|||
return shepherd; |
|||
}; |
@ -0,0 +1,145 @@ |
|||
module.exports = (shepherd) => { |
|||
// get merkle root
|
|||
shepherd.getMerkleRoot = (txid, proof, pos) => { |
|||
const reverse = require('buffer-reverse'); |
|||
let hash = txid; |
|||
let serialized; |
|||
const _sha256 = (data) => { |
|||
return shepherd.crypto.createHash('sha256').update(data).digest(); |
|||
} |
|||
|
|||
shepherd.log(`getMerkleRoot txid ${txid}`, true); |
|||
shepherd.log(`getMerkleRoot pos ${pos}`, true); |
|||
shepherd.log('getMerkleRoot proof', true); |
|||
shepherd.log(`getMerkleRoot ${proof}`, true); |
|||
|
|||
for (i = 0; i < proof.length; i++) { |
|||
const _hashBuff = new Buffer(hash, 'hex'); |
|||
const _proofBuff = new Buffer(proof[i], 'hex'); |
|||
|
|||
if ((pos & 1) == 0) { |
|||
serialized = Buffer.concat([reverse(_hashBuff), reverse(_proofBuff)]); |
|||
} else { |
|||
serialized = Buffer.concat([reverse(_proofBuff), reverse(_hashBuff)]); |
|||
} |
|||
|
|||
hash = reverse(_sha256(_sha256(serialized))).toString('hex'); |
|||
pos /= 2; |
|||
} |
|||
|
|||
return hash; |
|||
} |
|||
|
|||
shepherd.verifyMerkle = (txid, height, serverList, mainServer) => { |
|||
// select random server
|
|||
const getRandomIntInclusive = (min, max) => { |
|||
min = Math.ceil(min); |
|||
max = Math.floor(max); |
|||
|
|||
return Math.floor(Math.random() * (max - min + 1)) + min; // the maximum is inclusive and the minimum is inclusive
|
|||
} |
|||
|
|||
const _rnd = getRandomIntInclusive(0, serverList.length - 1); |
|||
const randomServer = serverList[_rnd]; |
|||
const _randomServer = randomServer.split(':'); |
|||
const _mainServer = mainServer.split(':'); |
|||
|
|||
let ecl = new shepherd.electrumJSCore(_mainServer[1], _mainServer[0], 'tcp'); // tcp or tls
|
|||
|
|||
return new shepherd.Promise((resolve, reject) => { |
|||
shepherd.log(`main server: ${mainServer}`, true); |
|||
shepherd.log(`verification server: ${randomServer}`, true); |
|||
|
|||
ecl.connect(); |
|||
ecl.blockchainTransactionGetMerkle(txid, height) |
|||
.then((merkleData) => { |
|||
if (merkleData && |
|||
merkleData.merkle && |
|||
merkleData.pos) { |
|||
shepherd.log('electrum getmerkle =>', true); |
|||
shepherd.log(merkleData, true); |
|||
ecl.close(); |
|||
|
|||
const _res = shepherd.getMerkleRoot(txid, merkleData.merkle, merkleData.pos); |
|||
shepherd.log(_res, true); |
|||
|
|||
ecl = new shepherd.electrumJSCore(_randomServer[1], _randomServer[0], 'tcp'); |
|||
ecl.connect(); |
|||
|
|||
ecl.blockchainBlockGetHeader(height) |
|||
.then((blockInfo) => { |
|||
if (blockInfo && |
|||
blockInfo['merkle_root']) { |
|||
ecl.close(); |
|||
shepherd.log('blockinfo =>', true); |
|||
shepherd.log(blockInfo, true); |
|||
shepherd.log(blockInfo['merkle_root'], true); |
|||
|
|||
if (blockInfo && |
|||
blockInfo['merkle_root']) { |
|||
if (_res === blockInfo['merkle_root']) { |
|||
resolve(true); |
|||
} else { |
|||
resolve(false); |
|||
} |
|||
} else { |
|||
resolve(shepherd.CONNECTION_ERROR_OR_INCOMPLETE_DATA); |
|||
} |
|||
} else { |
|||
resolve(shepherd.CONNECTION_ERROR_OR_INCOMPLETE_DATA); |
|||
} |
|||
}); |
|||
} else { |
|||
resolve(shepherd.CONNECTION_ERROR_OR_INCOMPLETE_DATA); |
|||
} |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
shepherd.verifyMerkleByCoin = (coin, txid, height) => { |
|||
const _serverList = shepherd.electrumCoins[coin].serverList; |
|||
|
|||
shepherd.log(`verifyMerkleByCoin`, true); |
|||
shepherd.log(shepherd.electrumCoins[coin].server, true); |
|||
shepherd.log(shepherd.electrumCoins[coin].serverList, true); |
|||
|
|||
return new shepherd.Promise((resolve, reject) => { |
|||
if (_serverList !== 'none') { |
|||
let _filteredServerList = []; |
|||
|
|||
for (let i = 0; i < _serverList.length; i++) { |
|||
if (_serverList[i] !== shepherd.electrumCoins[coin].server.ip + ':' + shepherd.electrumCoins[coin].server.port) { |
|||
_filteredServerList.push(_serverList[i]); |
|||
} |
|||
} |
|||
|
|||
shepherd.verifyMerkle( |
|||
txid, |
|||
height, |
|||
_filteredServerList, |
|||
shepherd.electrumCoins[coin].server.ip + ':' + shepherd.electrumCoins[coin].server.port |
|||
).then((proof) => { |
|||
resolve(proof); |
|||
}); |
|||
} else { |
|||
resolve(false); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
shepherd.get('/electrum/merkle/verify', (req, res, next) => { |
|||
shepherd.verifyMerkleByCoin(req.query.coin, req.query.txid, req.query.height) |
|||
.then((verifyMerkleRes) => { |
|||
const successObj = { |
|||
msg: 'success', |
|||
result: { |
|||
merkleProof: verifyMerkleRes, |
|||
}, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
}); |
|||
}); |
|||
|
|||
return shepherd; |
|||
}; |
@ -0,0 +1,143 @@ |
|||
module.exports = (shepherd) => { |
|||
shepherd.getNetworkData = (network) => { |
|||
const coin = shepherd.findNetworkObj(network) || shepherd.findNetworkObj(network.toUpperCase()) || shepherd.findNetworkObj(network.toLowerCase()); |
|||
const coinUC = coin ? coin.toUpperCase() : null; |
|||
|
|||
if (coin === 'SUPERNET' || |
|||
coin === 'REVS' || |
|||
coin === 'SUPERNET' || |
|||
coin === 'PANGEA' || |
|||
coin === 'DEX' || |
|||
coin === 'JUMBLR' || |
|||
coin === 'BET' || |
|||
coin === 'CRYPTO' || |
|||
coin === 'COQUI' || |
|||
coin === 'HODL' || |
|||
coin === 'SHARK' || |
|||
coin === 'MSHARK' || |
|||
coin === 'BOTS' || |
|||
coin === 'MGW' || |
|||
coin === 'MVP' || |
|||
coin === 'KV' || |
|||
coin === 'CEAL' || |
|||
coin === 'MESH' || |
|||
coin === 'WLC' || |
|||
coin === 'MNZ' || |
|||
coinUC === 'SUPERNET' || |
|||
coinUC === 'REVS' || |
|||
coinUC === 'SUPERNET' || |
|||
coinUC === 'PANGEA' || |
|||
coinUC === 'DEX' || |
|||
coinUC === 'JUMBLR' || |
|||
coinUC === 'BET' || |
|||
coinUC === 'CRYPTO' || |
|||
coinUC === 'COQUI' || |
|||
coinUC === 'HODL' || |
|||
coinUC === 'SHARK' || |
|||
coinUC === 'MSHARK' || |
|||
coinUC === 'BOTS' || |
|||
coinUC === 'MGW' || |
|||
coinUC === 'MVP' || |
|||
coinUC === 'KV' || |
|||
coinUC === 'CEAL' || |
|||
coinUC === 'MESH' || |
|||
coinUC === 'WLC' || |
|||
coinUC === 'MNZ') { |
|||
return shepherd.electrumJSNetworks.komodo; |
|||
} else { |
|||
return shepherd.electrumJSNetworks[network]; |
|||
} |
|||
} |
|||
|
|||
shepherd.findNetworkObj = (coin) => { |
|||
for (let key in shepherd.electrumServers) { |
|||
if (shepherd.electrumServers[key].abbr === coin) { |
|||
return key; |
|||
} |
|||
} |
|||
} |
|||
|
|||
shepherd.get('/electrum/servers', (req, res, next) => { |
|||
if (req.query.abbr) { |
|||
let _electrumServers = {}; |
|||
|
|||
for (let key in shepherd.electrumServers) { |
|||
_electrumServers[shepherd.electrumServers[key].abbr] = shepherd.electrumServers[key]; |
|||
} |
|||
|
|||
const successObj = { |
|||
msg: 'success', |
|||
result: { |
|||
servers: _electrumServers, |
|||
}, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
} else { |
|||
const successObj = { |
|||
msg: 'success', |
|||
result: { |
|||
servers: shepherd.electrumServers, |
|||
}, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
} |
|||
}); |
|||
|
|||
shepherd.get('/electrum/coins/server/set', (req, res, next) => { |
|||
shepherd.electrumCoins[req.query.coin].server = { |
|||
ip: req.query.address, |
|||
port: req.query.port, |
|||
}; |
|||
|
|||
for (let key in shepherd.electrumServers) { |
|||
if (shepherd.electrumServers[key].abbr === req.query.coin) { // a bit risky
|
|||
shepherd.electrumServers[key].address = req.query.address; |
|||
shepherd.electrumServers[key].port = req.query.port; |
|||
break; |
|||
} |
|||
} |
|||
|
|||
shepherd.log(JSON.stringify(shepherd.electrumCoins[req.query.coin], null, '\t'), true); |
|||
|
|||
const successObj = { |
|||
msg: 'success', |
|||
result: true, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
}); |
|||
|
|||
shepherd.get('/electrum/servers/test', (req, res, next) => { |
|||
const ecl = new shepherd.electrumJSCore(req.query.port, req.query.address, 'tcp'); // tcp or tls
|
|||
|
|||
ecl.connect(); |
|||
ecl.serverVersion() |
|||
.then((serverData) => { |
|||
ecl.close(); |
|||
shepherd.log('serverData', true); |
|||
shepherd.log(serverData, true); |
|||
|
|||
if (serverData && |
|||
typeof serverData === 'string' && |
|||
serverData.indexOf('Electrum') > -1) { |
|||
const successObj = { |
|||
msg: 'success', |
|||
result: true, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
} else { |
|||
const successObj = { |
|||
msg: 'error', |
|||
result: false, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
} |
|||
}); |
|||
}); |
|||
|
|||
return shepherd; |
|||
}; |
@ -0,0 +1,397 @@ |
|||
module.exports = (shepherd) => { |
|||
shepherd.sortTransactions = (transactions) => { |
|||
return transactions.sort((b, a) => { |
|||
if (a.height < b.height) { |
|||
return -1; |
|||
} |
|||
|
|||
if (a.height > b.height) { |
|||
return 1; |
|||
} |
|||
|
|||
return 0; |
|||
}); |
|||
} |
|||
|
|||
shepherd.get('/electrum/listtransactions', (req, res, next) => { |
|||
const network = req.query.network || shepherd.findNetworkObj(req.query.coin); |
|||
const ecl = new shepherd.electrumJSCore(shepherd.electrumServers[network].port, shepherd.electrumServers[network].address, shepherd.electrumServers[network].proto); // tcp or tls
|
|||
|
|||
shepherd.log('electrum listtransactions ==>', true); |
|||
|
|||
if (!req.query.full) { |
|||
ecl.connect(); |
|||
ecl.blockchainAddressGetHistory(req.query.address) |
|||
.then((json) => { |
|||
ecl.close(); |
|||
shepherd.log(json, true); |
|||
|
|||
json = shepherd.sortTransactions(json); |
|||
|
|||
const successObj = { |
|||
msg: 'success', |
|||
result: json, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
}); |
|||
} else { |
|||
// !expensive call!
|
|||
// TODO: limit e.g. 1-10, 10-20 etc
|
|||
const MAX_TX = req.query.maxlength || 10; |
|||
ecl.connect(); |
|||
|
|||
ecl.blockchainNumblocksSubscribe() |
|||
.then((currentHeight) => { |
|||
if (currentHeight && |
|||
Number(currentHeight) > 0) { |
|||
ecl.blockchainAddressGetHistory(req.query.address) |
|||
.then((json) => { |
|||
if (json && |
|||
json.length) { |
|||
json = shepherd.sortTransactions(json); |
|||
json = json.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) |
|||
.then((blockInfo) => { |
|||
if (blockInfo && |
|||
blockInfo.timestamp) { |
|||
ecl.blockchainTransactionGet(transaction['tx_hash']) |
|||
.then((_rawtxJSON) => { |
|||
shepherd.log('electrum gettransaction ==>', true); |
|||
shepherd.log((index + ' | ' + (_rawtxJSON.length - 1)), true); |
|||
shepherd.log(_rawtxJSON, true); |
|||
|
|||
// decode tx
|
|||
const _network = shepherd.getNetworkData(network); |
|||
const decodedTx = shepherd.electrumJSTxDecoder(_rawtxJSON, _network); |
|||
|
|||
let txInputs = []; |
|||
|
|||
shepherd.log('decodedtx =>', true); |
|||
shepherd.log(decodedTx.outputs, true); |
|||
|
|||
if (decodedTx && |
|||
decodedTx.inputs) { |
|||
shepherd.Promise.all(decodedTx.inputs.map((_decodedInput, index) => { |
|||
return new shepherd.Promise((_resolve, _reject) => { |
|||
if (_decodedInput.txid !== '0000000000000000000000000000000000000000000000000000000000000000') { |
|||
ecl.blockchainTransactionGet(_decodedInput.txid) |
|||
.then((rawInput) => { |
|||
const decodedVinVout = shepherd.electrumJSTxDecoder(rawInput, _network); |
|||
|
|||
shepherd.log('electrum raw input tx ==>', true); |
|||
|
|||
if (decodedVinVout) { |
|||
shepherd.log(decodedVinVout.outputs[_decodedInput.n], true); |
|||
txInputs.push(decodedVinVout.outputs[_decodedInput.n]); |
|||
_resolve(true); |
|||
} else { |
|||
_resolve(true); |
|||
} |
|||
}); |
|||
} else { |
|||
_resolve(true); |
|||
} |
|||
}); |
|||
})) |
|||
.then(promiseResult => { |
|||
const _parsedTx = { |
|||
network: decodedTx.network, |
|||
format: decodedTx.format, |
|||
inputs: txInputs, |
|||
outputs: decodedTx.outputs, |
|||
height: transaction.height, |
|||
timestamp: 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]); |
|||
} |
|||
resolve(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, |
|||
confirmations: Number(transaction.height) === 0 ? 0 : currentHeight - transaction.height, |
|||
}; |
|||
|
|||
const formattedTx = shepherd.parseTransactionAddresses(_parsedTx, req.query.address, network); |
|||
_rawtx.push(formattedTx); |
|||
resolve(true); |
|||
} |
|||
}); |
|||
} else { |
|||
const _parsedTx = { |
|||
network: 'cant parse', |
|||
format: 'cant parse', |
|||
inputs: 'cant parse', |
|||
outputs: 'cant parse', |
|||
height: transaction.height, |
|||
timestamp: 'cant get block info', |
|||
confirmations: Number(transaction.height) === 0 ? 0 : currentHeight - transaction.height, |
|||
}; |
|||
const formattedTx = shepherd.parseTransactionAddresses(_parsedTx, req.query.address, network); |
|||
_rawtx.push(formattedTx); |
|||
resolve(true); |
|||
} |
|||
}); |
|||
}); |
|||
})) |
|||
.then(promiseResult => { |
|||
ecl.close(); |
|||
|
|||
const successObj = { |
|||
msg: 'success', |
|||
result: _rawtx, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
}); |
|||
} else { |
|||
const successObj = { |
|||
msg: 'success', |
|||
result: [], |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
} |
|||
}); |
|||
} else { |
|||
const successObj = { |
|||
msg: 'error', |
|||
result: 'cant get current height', |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
} |
|||
}); |
|||
} |
|||
}); |
|||
|
|||
shepherd.get('/electrum/gettransaction', (req, res, next) => { |
|||
const network = req.query.network || shepherd.findNetworkObj(req.query.coin); |
|||
const ecl = new shepherd.electrumJSCore(shepherd.electrumServers[network].port, shepherd.electrumServers[network].address, shepherd.electrumServers[network].proto); // tcp or tls
|
|||
|
|||
shepherd.log('electrum gettransaction =>', true); |
|||
|
|||
ecl.connect(); |
|||
ecl.blockchainTransactionGet(req.query.txid) |
|||
.then((json) => { |
|||
ecl.close(); |
|||
shepherd.log(json, true); |
|||
|
|||
const successObj = { |
|||
msg: 'success', |
|||
result: json, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
}); |
|||
}); |
|||
|
|||
shepherd.parseTransactionAddresses = (tx, targetAddress, network) => { |
|||
// TODO: - sum vins / sum vouts to the same address
|
|||
// - multi vin multi vout
|
|||
// - detect change address
|
|||
let result = []; |
|||
let _parse = { |
|||
inputs: {}, |
|||
outputs: {}, |
|||
}; |
|||
let _sum = { |
|||
inputs: 0, |
|||
outputs: 0, |
|||
}; |
|||
let _total = { |
|||
inputs: 0, |
|||
outputs: 0, |
|||
}; |
|||
|
|||
shepherd.log('parseTransactionAddresses result ==>', true); |
|||
|
|||
if (tx.format === 'cant parse') { |
|||
return { |
|||
type: 'unknown', |
|||
amount: 'unknown', |
|||
address: targetAddress, |
|||
timestamp: tx.timestamp, |
|||
txid: tx.format.txid, |
|||
confirmations: tx.confirmations, |
|||
} |
|||
} |
|||
|
|||
for (let key in _parse) { |
|||
if (!tx[key].length) { |
|||
_parse[key] = []; |
|||
_parse[key].push(tx[key]); |
|||
} else { |
|||
_parse[key] = tx[key]; |
|||
} |
|||
|
|||
for (let i = 0; i < _parse[key].length; i++) { |
|||
shepherd.log(`key ==>`, true); |
|||
shepherd.log(_parse[key][i], true); |
|||
shepherd.log(Number(_parse[key][i].value), true); |
|||
|
|||
_total[key] += Number(_parse[key][i].value); |
|||
|
|||
if (_parse[key][i].scriptPubKey && |
|||
_parse[key][i].scriptPubKey.addresses && |
|||
_parse[key][i].scriptPubKey.addresses[0] === targetAddress && |
|||
_parse[key][i].value) { |
|||
_sum[key] += Number(_parse[key][i].value); |
|||
} |
|||
} |
|||
} |
|||
|
|||
if (_sum.inputs > 0 && |
|||
_sum.outputs > 0) { |
|||
// vin + change, break into two tx
|
|||
result = [{ // reorder since tx sort by default is from newest to oldest
|
|||
type: 'sent', |
|||
amount: Number(_sum.inputs.toFixed(8)), |
|||
address: targetAddress, |
|||
timestamp: tx.timestamp, |
|||
txid: tx.format.txid, |
|||
confirmations: tx.confirmations, |
|||
}, { |
|||
type: 'received', |
|||
amount: Number(_sum.outputs.toFixed(8)), |
|||
address: targetAddress, |
|||
timestamp: tx.timestamp, |
|||
txid: tx.format.txid, |
|||
confirmations: tx.confirmations, |
|||
}]; |
|||
|
|||
if (network === 'komodo') { // calc claimed interest amount
|
|||
const vinVoutDiff = _total.inputs - _total.outputs; |
|||
|
|||
if (vinVoutDiff < 0) { |
|||
result[1].interest = Number(vinVoutDiff.toFixed(8)); |
|||
} |
|||
} |
|||
} else if (_sum.inputs === 0 && _sum.outputs > 0) { |
|||
result = { |
|||
type: 'received', |
|||
amount: Number(_sum.outputs.toFixed(8)), |
|||
address: targetAddress, |
|||
timestamp: tx.timestamp, |
|||
txid: tx.format.txid, |
|||
confirmations: tx.confirmations, |
|||
}; |
|||
} else if (_sum.inputs > 0 && _sum.outputs === 0) { |
|||
result = { |
|||
type: 'sent', |
|||
amount: Number(_sum.inputs.toFixed(8)), |
|||
address: targetAddress, |
|||
timestamp: tx.timestamp, |
|||
txid: tx.format.txid, |
|||
confirmations: tx.confirmations, |
|||
}; |
|||
} else { |
|||
// (?)
|
|||
result = { |
|||
type: 'other', |
|||
amount: 'unknown', |
|||
address: targetAddress, |
|||
timestamp: tx.timestamp, |
|||
txid: tx.format.txid, |
|||
confirmations: tx.confirmations, |
|||
}; |
|||
} |
|||
|
|||
shepherd.log(_sum, true); |
|||
shepherd.log(result, true); |
|||
|
|||
return result; |
|||
} |
|||
|
|||
shepherd.get('/electrum/decoderawtx', (req, res, next) => { |
|||
const _network = shepherd.getNetworkData(req.query.network); |
|||
const _rawtx = req.query.rawtx; |
|||
// const _rawtx = '0100000001dd6d064f5665f8454293ecaa9dbb55accf4f7e443d35f3b5ab7760f54b6c15fe000000006a473044022056355585a4a501ec9afc96aa5df124cf29ad3ac6454b47cd07cd7d89ec95ec2b022074c4604ee349d30e5336f210598e4dc576bf16ebeb67eeac3f4e82f56e930fee012103b90ba01af308757054e0484bb578765d5df59c4a57adbb94e2419df5e7232a63feffffff0289fc923b000000001976a91424af38fcb13bbc171b0b42bb017244a53b6bb2fa88ac20a10700000000001976a9142f4c0f91fc06ac228c120aee41741d0d3909683288ac49258b58';
|
|||
const decodedTx = shepherd.electrumJSTxDecoder(_rawtx, _network); |
|||
|
|||
shepherd.log('electrum decoderawtx input tx ==>', true); |
|||
|
|||
if (req.query.parseonly || |
|||
decodedTx.inputs[0].txid === '0000000000000000000000000000000000000000000000000000000000000000') { |
|||
const successObj = { |
|||
msg: 'success', |
|||
result: { |
|||
network: decodedTx.network, |
|||
format: decodedTx.format, |
|||
inputs: decodedTx.inputs, |
|||
outputs: decodedTx.outputs, |
|||
}, |
|||
}; |
|||
|
|||
shepherd.log(successObj.result, true); |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
} else { |
|||
const ecl = new shepherd.electrumJSCore(shepherd.electrumServers[req.query.network].port, shepherd.electrumServers[req.query.network].address, shepherd.electrumServers[req.query.network].proto); // tcp or tls
|
|||
|
|||
ecl.connect(); |
|||
ecl.blockchainTransactionGet(decodedTx.inputs[0].txid) |
|||
.then((json) => { |
|||
ecl.close(); |
|||
shepherd.log(json, true); |
|||
|
|||
const decodedVin = shepherd.electrumJSTxDecoder(json, _network); |
|||
|
|||
const successObj = { |
|||
msg: 'success', |
|||
result: { |
|||
network: decodedTx.network, |
|||
format: decodedTx.format, |
|||
inputs: decodedVin.outputs[decodedTx.inputs[0].n], |
|||
outputs: decodedTx.outputs, |
|||
}, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
}); |
|||
} |
|||
}); |
|||
|
|||
return shepherd; |
|||
}; |
@ -0,0 +1,71 @@ |
|||
const fs = require('fs-extra'); |
|||
const path = require('path'); |
|||
let _foldersInitRan = false; |
|||
|
|||
module.exports = (shepherd) => { |
|||
shepherd.readVersionFile = () => { |
|||
// read app version
|
|||
const rootLocation = path.join(__dirname, '../../'); |
|||
const localVersionFile = fs.readFileSync(`${rootLocation}version`, 'utf8'); |
|||
|
|||
return localVersionFile; |
|||
} |
|||
|
|||
shepherd.createAgamaDirs = () => { |
|||
if (!_foldersInitRan) { |
|||
const rootLocation = path.join(__dirname, '../../'); |
|||
|
|||
fs.readdir(rootLocation, (err, items) => { |
|||
for (let i = 0; i < items.length; i++) { |
|||
if (items[i].substr(0, 3) === 'gen') { |
|||
console.log(items[i]); |
|||
fs.unlinkSync(rootLocation + items[i]); |
|||
} |
|||
} |
|||
}); |
|||
|
|||
if (!fs.existsSync(shepherd.agamaDir)) { |
|||
fs.mkdirSync(shepherd.agamaDir); |
|||
|
|||
if (fs.existsSync(shepherd.agamaDir)) { |
|||
shepherd.log(`created agama folder at ${shepherd.agamaDir}`); |
|||
shepherd.writeLog(`created agama folder at ${shepherd.agamaDir}`); |
|||
} |
|||
} else { |
|||
shepherd.log('agama folder already exists'); |
|||
} |
|||
|
|||
if (!fs.existsSync(`${shepherd.agamaDir}/shepherd`)) { |
|||
fs.mkdirSync(`${shepherd.agamaDir}/shepherd`); |
|||
|
|||
if (fs.existsSync(`${shepherd.agamaDir}/shepherd`)) { |
|||
shepherd.log(`created shepherd folder at ${shepherd.agamaDir}/shepherd`); |
|||
shepherd.writeLog(`create shepherd folder at ${shepherd.agamaDir}/shepherd`); |
|||
} |
|||
} else { |
|||
shepherd.log('agama/shepherd folder already exists'); |
|||
} |
|||
|
|||
if (!fs.existsSync(`${shepherd.agamaDir}/shepherd/pin`)) { |
|||
fs.mkdirSync(`${shepherd.agamaDir}/shepherd/pin`); |
|||
|
|||
if (fs.existsSync(`${shepherd.agamaDir}/shepherd/pin`)) { |
|||
shepherd.log(`created pin folder at ${shepherd.agamaDir}/shepherd/pin`); |
|||
shepherd.writeLog(`create pin folder at ${shepherd.agamaDir}/shepherd/pin`); |
|||
} |
|||
} else { |
|||
shepherd.log('shepherd/pin folder already exists'); |
|||
} |
|||
|
|||
if (!fs.existsSync(shepherd.zcashParamsDir)) { |
|||
fs.mkdirSync(shepherd.zcashParamsDir); |
|||
} else { |
|||
shepherd.log('zcashparams folder already exists'); |
|||
} |
|||
|
|||
_foldersInitRan = true; |
|||
} |
|||
} |
|||
|
|||
return shepherd; |
|||
}; |
@ -0,0 +1,45 @@ |
|||
const fs = require('fs-extra'); |
|||
const path = require('path'); |
|||
|
|||
module.exports = (shepherd) => { |
|||
/* |
|||
* type: GET |
|||
* params: coin, type |
|||
*/ |
|||
shepherd.get('/kick', (req, res, next) => { |
|||
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]}`); |
|||
} |
|||
} |
|||
} |
|||
|
|||
const successObj = { |
|||
msg: 'success', |
|||
result: `${_coin} native is kicked`, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
} |
|||
}); |
|||
|
|||
return shepherd; |
|||
}; |
@ -0,0 +1,141 @@ |
|||
module.exports = (shepherd) => { |
|||
shepherd.log = (msg, isSpvOut) => { |
|||
if (shepherd.appConfig.dev || |
|||
shepherd.appConfig.debug) { |
|||
console.log(msg); |
|||
} |
|||
|
|||
if (!isSpvOut) { |
|||
shepherd.appRuntimeLog.push({ |
|||
time: Date.now(), |
|||
msg: msg, |
|||
}); |
|||
} else { |
|||
shepherd.appRuntimeSPVLog.push({ |
|||
time: Date.now(), |
|||
msg: msg, |
|||
}); |
|||
} |
|||
} |
|||
|
|||
shepherd.writeLog = (data) => { |
|||
const logLocation = `${shepherd.agamaDir}/shepherd`; |
|||
const timeFormatted = new Date(Date.now()).toLocaleString('en-US', { hour12: false }); |
|||
|
|||
if (shepherd.appConfig.debug) { |
|||
if (shepherd.fs.existsSync(`${logLocation}/agamalog.txt`)) { |
|||
shepherd.fs.appendFile(`${logLocation}/agamalog.txt`, `${timeFormatted} ${data}\r\n`, (err) => { |
|||
if (err) { |
|||
shepherd.log('error writing log file'); |
|||
} |
|||
}); |
|||
} else { |
|||
shepherd.fs.writeFile(`${logLocation}/agamalog.txt`, `${timeFormatted} ${data}\r\n`, (err) => { |
|||
if (err) { |
|||
shepherd.log('error writing log file'); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
|
|||
shepherd.get('/log/runtime', (req, res, next) => { |
|||
const successObj = { |
|||
msg: 'success', |
|||
result: req.query.spv && req.query.spv === 'true' ? shepherd.appRuntimeSPVLog : shepherd.appRuntimeLog, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
}); |
|||
|
|||
shepherd.getAppRuntimeLog = () => { |
|||
return new shepherd.Promise((resolve, reject) => { |
|||
resolve(shepherd.appRuntimeLog); |
|||
}); |
|||
}; |
|||
|
|||
/* |
|||
* type: POST |
|||
* params: payload |
|||
*/ |
|||
shepherd.post('/guilog', (req, res, next) => { |
|||
const logLocation = `${shepherd.agamaDir}/shepherd`; |
|||
|
|||
if (!shepherd.guiLog[shepherd.appSessionHash]) { |
|||
shepherd.guiLog[shepherd.appSessionHash] = {}; |
|||
} |
|||
|
|||
if (shepherd.guiLog[shepherd.appSessionHash][req.body.timestamp]) { |
|||
shepherd.guiLog[shepherd.appSessionHash][req.body.timestamp].status = req.body.status; |
|||
shepherd.guiLog[shepherd.appSessionHash][req.body.timestamp].response = req.body.response; |
|||
} else { |
|||
shepherd.guiLog[shepherd.appSessionHash][req.body.timestamp] = { |
|||
function: req.body.function, |
|||
type: req.body.type, |
|||
url: req.body.url, |
|||
payload: req.body.payload, |
|||
status: req.body.status, |
|||
}; |
|||
} |
|||
|
|||
shepherd.fs.writeFile(`${logLocation}/agamalog.json`, JSON.stringify(shepherd.guiLog), (err) => { |
|||
if (err) { |
|||
shepherd.writeLog('error writing gui log file'); |
|||
} |
|||
|
|||
const returnObj = { |
|||
msg: 'success', |
|||
result: 'gui log entry is added', |
|||
}; |
|||
|
|||
res.end(JSON.stringify(returnObj)); |
|||
}); |
|||
}); |
|||
|
|||
/* |
|||
* type: GET |
|||
* params: type |
|||
*/ |
|||
shepherd.get('/getlog', (req, res, next) => { |
|||
const logExt = req.query.type === 'txt' ? 'txt' : 'json'; |
|||
|
|||
if (shepherd.fs.existsSync(`${shepherd.agamaDir}/shepherd/agamalog.${logExt}`)) { |
|||
shepherd.fs.readFile(`${shepherd.agamaDir}/shepherd/agamalog.${logExt}`, 'utf8', (err, data) => { |
|||
if (err) { |
|||
const errorObj = { |
|||
msg: 'error', |
|||
result: err, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(errorObj)); |
|||
} else { |
|||
const successObj = { |
|||
msg: 'success', |
|||
result: data ? JSON.parse(data) : '', |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
} |
|||
}); |
|||
} else { |
|||
const errorObj = { |
|||
msg: 'error', |
|||
result: `agama.${logExt} doesnt exist`, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(errorObj)); |
|||
} |
|||
}); |
|||
|
|||
shepherd.printDirs = () => { |
|||
shepherd.log(`agama dir: ${shepherd.agamaDir}`); |
|||
shepherd.log('--------------------------') |
|||
shepherd.log(`komodo dir: ${shepherd.komododBin}`); |
|||
shepherd.log(`komodo bin: ${shepherd.komodoDir}`); |
|||
shepherd.writeLog(`agama dir: ${shepherd.agamaDir}`); |
|||
shepherd.writeLog(`komodo dir: ${shepherd.komododBin}`); |
|||
shepherd.writeLog(`komodo bin: ${shepherd.komodoDir}`); |
|||
} |
|||
|
|||
return shepherd; |
|||
}; |
@ -0,0 +1,82 @@ |
|||
const path = require('path'); |
|||
const fixPath = require('fix-path'); |
|||
const os = require('os'); |
|||
|
|||
module.exports = (shepherd) => { |
|||
shepherd.pathsAgama = () => { |
|||
switch (os.platform()) { |
|||
case 'darwin': |
|||
fixPath(); |
|||
shepherd.agamaDir = `${process.env.HOME}/Library/Application Support/Agama`; |
|||
break; |
|||
|
|||
case 'linux': |
|||
shepherd.agamaDir = `${process.env.HOME}/.agama`; |
|||
break; |
|||
|
|||
case 'win32': |
|||
shepherd.agamaDir = `${process.env.APPDATA}/Agama`; |
|||
shepherd.agamaDir = path.normalize(shepherd.agamaDir); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
shepherd.pathsDaemons = () => { |
|||
switch (os.platform()) { |
|||
case 'darwin': |
|||
fixPath(); |
|||
shepherd.agamaTestDir = `${process.env.HOME}/Library/Application Support/Agama/test`, |
|||
shepherd.komododBin = path.join(__dirname, '../../assets/bin/osx/komodod'), |
|||
shepherd.komodocliBin = path.join(__dirname, '../../assets/bin/osx/komodo-cli'), |
|||
shepherd.komodoDir = shepherd.appConfig.dataDir.length ? shepherd.appConfig.dataDir : `${process.env.HOME}/Library/Application Support/Komodo`, |
|||
shepherd.zcashdBin = '/Applications/ZCashSwingWalletUI.app/Contents/MacOS/zcashd', |
|||
shepherd.zcashcliBin = '/Applications/ZCashSwingWalletUI.app/Contents/MacOS/zcash-cli', |
|||
shepherd.zcashDir = `${process.env.HOME}/Library/Application Support/Zcash`, |
|||
shepherd.zcashParamsDir = `${process.env.HOME}/Library/Application Support/ZcashParams`, |
|||
shepherd.chipsBin = path.join(__dirname, '../../assets/bin/osx/chipsd'), |
|||
shepherd.chipscliBin = path.join(__dirname, '../../assets/bin/osx/chips-cli'), |
|||
shepherd.chipsDir = `${process.env.HOME}/Library/Application Support/Chips`, |
|||
shepherd.coindRootDir = path.join(__dirname, '../../assets/bin/osx/dex/coind'), |
|||
shepherd.mmBin = path.join(__dirname, '../../node_modules/marketmaker/bin/darwin/x64/marketmaker'); |
|||
break; |
|||
|
|||
case 'linux': |
|||
shepherd.agamaTestDir = `${process.env.HOME}/.agama/test`, |
|||
shepherd.komododBin = path.join(__dirname, '../../assets/bin/linux64/komodod'), |
|||
shepherd.komodocliBin = path.join(__dirname, '../../assets/bin/linux64/komodo-cli'), |
|||
shepherd.komodoDir = shepherd.appConfig.dataDir.length ? shepherd.appConfig.dataDir : `${process.env.HOME}/.komodo`, |
|||
shepherd.zcashParamsDir = `${process.env.HOME}/.zcash-params`, |
|||
shepherd.chipsBin = path.join(__dirname, '../../assets/bin/linux64/chipsd'), |
|||
shepherd.chipscliBin = path.join(__dirname, '../../assets/bin/linux64/chips-cli'), |
|||
shepherd.chipsDir = `${process.env.HOME}/.chips`, |
|||
shepherd.coindRootDir = path.join(__dirname, '../../assets/bin/linux64/dex/coind'), |
|||
shepherd.mmBin = path.join(__dirname, '../../node_modules/marketmaker/bin/linux/x64/marketmaker'); |
|||
break; |
|||
|
|||
case 'win32': |
|||
shepherd.agamaTestDir = `${process.env.APPDATA}/Agama/test`; |
|||
shepherd.agamaTestDir = path.normalize(shepherd.agamaTestDir); |
|||
shepherd.komododBin = path.join(__dirname, '../../assets/bin/win64/komodod.exe'), |
|||
shepherd.komododBin = path.normalize(shepherd.komododBin), |
|||
shepherd.komodocliBin = path.join(__dirname, '../../assets/bin/win64/komodo-cli.exe'), |
|||
shepherd.komodocliBin = path.normalize(shepherd.komodocliBin), |
|||
shepherd.komodoDir = shepherd.appConfig.dataDir.length ? shepherd.appConfig.dataDir : `${process.env.APPDATA}/Komodo`, |
|||
shepherd.komodoDir = path.normalize(shepherd.komodoDir); |
|||
shepherd.chipsBin = path.join(__dirname, '../../assets/bin/win64/chipsd.exe'), |
|||
shepherd.chipsBin = path.normalize(shepherd.chipsBin), |
|||
shepherd.chipscliBin = path.join(__dirname, '../../assets/bin/win64/chips-cli.exe'), |
|||
shepherd.chipscliBin = path.normalize(shepherd.chipscliBin), |
|||
shepherd.chipsDir = `${process.env.APPDATA}/Chips`, |
|||
shepherd.chipsDir = path.normalize(shepherd.chipsDir); |
|||
shepherd.zcashParamsDir = `${process.env.APPDATA}/ZcashParams`; |
|||
shepherd.zcashParamsDir = path.normalize(shepherd.zcashParamsDir); |
|||
shepherd.coindRootDir = path.join(__dirname, '../../assets/bin/osx/dex/coind'); |
|||
shepherd.coindRootDir = path.normalize(shepherd.coindRootDir); |
|||
shepherd.mmBin = path.join(__dirname, '../../node_modules/marketmaker/bin/win32/x64/marketmaker.exe'); |
|||
shepherd.mmBin = path.normalize(shepherd.mmBin); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
return shepherd; |
|||
}; |
@ -0,0 +1,146 @@ |
|||
module.exports = (shepherd) => { |
|||
/* |
|||
* type: POST |
|||
* params: none |
|||
*/ |
|||
shepherd.post('/encryptkey', (req, res, next) => { |
|||
if (req.body.key && |
|||
req.body.string && |
|||
req.body.pubkey) { |
|||
const encryptedString = shepherd.aes256.encrypt(req.body.key, req.body.string); |
|||
|
|||
// test pin security
|
|||
// - at least 1 char in upper case
|
|||
// - at least 1 digit
|
|||
// - at least one special character
|
|||
// - min length 8
|
|||
|
|||
const _pin = req.body.key; |
|||
const _pinTest = _pin.match('^(?=.*[A-Z])(?=.*[^<>{}\"/|;:.,~!?@#$%^=&*\\]\\\\()\\[_+]*$)(?=.*[0-9])(?=.*[a-z]).{8}$'); |
|||
|
|||
shepherd.fs.writeFile(`${shepherd.agamaDir}/shepherd/pin/${req.body.pubkey}.pin`, encryptedString, (err) => { |
|||
if (err) { |
|||
shepherd.log('error writing pin file'); |
|||
} |
|||
|
|||
const returnObj = { |
|||
msg: 'success', |
|||
result: encryptedString, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(returnObj)); |
|||
}); |
|||
} else { |
|||
let errorObj = { |
|||
msg: 'error', |
|||
result: '', |
|||
}; |
|||
const _paramsList = [ |
|||
'key', |
|||
'string', |
|||
'pubkey' |
|||
]; |
|||
let _errorParamsList = []; |
|||
|
|||
for (let i = 0; i < _paramsList.length; i++) { |
|||
if (!req.query[_paramsList[i]]) { |
|||
_errorParamsList.push(_paramsList[i]); |
|||
} |
|||
} |
|||
|
|||
errorObj.result = `missing param ${_errorParamsList.join(', ')}`; |
|||
res.end(JSON.stringify(errorObj)); |
|||
} |
|||
}); |
|||
|
|||
shepherd.post('/decryptkey', (req, res, next) => { |
|||
if (req.body.key && |
|||
req.body.pubkey) { |
|||
if (shepherd.fs.existsSync(`${shepherd.agamaDir}/shepherd/pin/${req.body.pubkey}.pin`)) { |
|||
shepherd.fs.readFile(`${shepherd.agamaDir}/shepherd/pin/${req.body.pubkey}.pin`, 'utf8', (err, data) => { |
|||
if (err) { |
|||
const errorObj = { |
|||
msg: 'error', |
|||
result: err, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(errorObj)); |
|||
} else { |
|||
const encryptedKey = shepherd.aes256.decrypt(req.body.key, data); |
|||
// test if stored encrypted passphrase is decrypted correctly
|
|||
// if not then the key is wrong
|
|||
const _regexTest = encryptedKey.match(/^[0-9a-zA-Z ]+$/g); |
|||
let returnObj; |
|||
|
|||
if (!_regexTest) { |
|||
returnObj = { |
|||
msg: 'error', |
|||
result: 'wrong key', |
|||
}; |
|||
} else { |
|||
returnObj = { |
|||
msg: 'success', |
|||
result: encryptedKey, |
|||
}; |
|||
} |
|||
|
|||
res.end(JSON.stringify(returnObj)); |
|||
} |
|||
}); |
|||
} else { |
|||
const errorObj = { |
|||
msg: 'error', |
|||
result: `file ${req.query.pubkey}.pin doesnt exist`, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(errorObj)); |
|||
} |
|||
} else { |
|||
const errorObj = { |
|||
msg: 'error', |
|||
result: 'missing key or pubkey param', |
|||
}; |
|||
|
|||
res.end(JSON.stringify(errorObj)); |
|||
} |
|||
}); |
|||
|
|||
shepherd.get('/getpinlist', (req, res, next) => { |
|||
if (shepherd.fs.existsSync(`${shepherd.agamaDir}/shepherd/pin`)) { |
|||
shepherd.fs.readdir(`${shepherd.agamaDir}/shepherd/pin`, (err, items) => { |
|||
let _pins = []; |
|||
|
|||
for (let i = 0; i < items.length; i++) { |
|||
if (items[i].substr(items[i].length - 4, 4) === '.pin') { |
|||
_pins.push(items[i].substr(0, items[i].length - 4)); |
|||
} |
|||
} |
|||
|
|||
if (!items.length) { |
|||
const errorObj = { |
|||
msg: 'error', |
|||
result: 'no pins', |
|||
}; |
|||
|
|||
res.end(JSON.stringify(errorObj)); |
|||
} else { |
|||
const successObj = { |
|||
msg: 'success', |
|||
result: _pins, |
|||
}; |
|||
|
|||
res.end(JSON.stringify(successObj)); |
|||
} |
|||
}); |
|||
} else { |
|||
const errorObj = { |
|||
msg: 'error', |
|||
result: 'pin folder doesnt exist', |
|||
}; |
|||
|
|||
res.end(JSON.stringify(errorObj)); |
|||
} |
|||
}); |
|||
|
|||
return shepherd; |
|||
}; |
@ -0,0 +1,170 @@ |
|||
const portscanner = require('portscanner'); |
|||
const execFile = require('child_process').execFile; |
|||
|
|||
module.exports = (shepherd) => { |
|||
shepherd.quitKomodod = (timeout = 100) => { |
|||
// if komodod is under heavy load it may not respond to cli stop the first time
|
|||
// exit komodod gracefully
|
|||
let coindExitInterval = {}; |
|||
shepherd.lockDownAddCoin = true; |
|||
|
|||
for (let key in shepherd.coindInstanceRegistry) { |
|||
if (shepherd.appConfig.stopNativeDaemonsOnQuit) { |
|||
const chain = key !== 'komodod' ? key : null; |
|||
let _coindQuitCmd = shepherd.komodocliBin; |
|||
|
|||
// any coind
|
|||
if (shepherd.nativeCoindList[key.toLowerCase()]) { |
|||
_coindQuitCmd = `${shepherd.coindRootDir}/${key.toLowerCase()}/${shepherd.nativeCoindList[key.toLowerCase()].bin.toLowerCase()}-cli`; |
|||
} |
|||
if (key === 'CHIPS') { |
|||
_coindQuitCmd = shepherd.chipscliBin; |
|||
} |
|||
|
|||
const execCliStop = () => { |
|||
let _arg = []; |
|||
if (chain && |
|||
!shepherd.nativeCoindList[key.toLowerCase()] && key !== 'CHIPS') { |
|||
_arg.push(`-ac_name=${chain}`); |
|||
|
|||
if (shepherd.appConfig.dataDir.length) { |
|||
_arg.push(`-datadir=${shepherd.appConfig.dataDir + (key !== 'komodod' ? '/' + key : '')}`); |
|||
} |
|||
} else if (key === 'komodod' && shepherd.appConfig.dataDir.length) { |
|||
_arg.push(`-datadir=${shepherd.appConfig.dataDir}`); |
|||
} |
|||
|
|||
_arg.push('stop'); |
|||
execFile(`${_coindQuitCmd}`, _arg, (error, stdout, stderr) => { |
|||
shepherd.log(`stdout: ${stdout}`); |
|||
shepherd.log(`stderr: ${stderr}`); |
|||
shepherd.log(`send stop sig to ${key}`); |
|||
|
|||
if (stdout.indexOf('EOF reached') > -1 || |
|||
stderr.indexOf('EOF reached') > -1 || |
|||
(error && error.toString().indexOf('Command failed') > -1 && !stderr) || // win "special snowflake" case
|
|||
stdout.indexOf('connect to server: unknown (code -1)') > -1 || |
|||
stderr.indexOf('connect to server: unknown (code -1)') > -1) { |
|||
delete shepherd.coindInstanceRegistry[key]; |
|||
clearInterval(coindExitInterval[key]); |
|||
} |
|||
|
|||
// workaround for AGT-65
|
|||
const _port = shepherd.assetChainPorts[key]; |
|||
setTimeout(() => { |
|||
portscanner.checkPortStatus(_port, '127.0.0.1', (error, status) => { |
|||
// Status is 'open' if currently in use or 'closed' if available
|
|||
if (status === 'closed') { |
|||
delete shepherd.coindInstanceRegistry[key]; |
|||
clearInterval(coindExitInterval[key]); |
|||
} |
|||
}); |
|||
}, 100); |
|||
|
|||
if (error !== null) { |
|||
shepherd.log(`exec error: ${error}`); |
|||
} |
|||
|
|||
setTimeout(() => { |
|||
shepherd.killRogueProcess(key === 'CHIPS' ? 'chips-cli' : 'komodo-cli'); |
|||
}, 100); |
|||
}); |
|||
} |
|||
|
|||
execCliStop(); |
|||
coindExitInterval[key] = setInterval(() => { |
|||
execCliStop(); |
|||
}, timeout); |
|||
} else { |
|||
delete shepherd.coindInstanceRegistry[key]; |
|||
} |
|||
} |
|||
} |
|||
|
|||
shepherd.post('/coind/stop', (req, res) => { |
|||
const _chain = req.body.chain; |
|||
let _coindQuitCmd = shepherd.komodocliBin; |
|||
let _arg = []; |
|||
|
|||
if (_chain) { |
|||
_arg.push(`-ac_name=${_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}`); |
|||
} |
|||
|
|||
_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) { |
|||
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)); |
|||
} |
|||
} |
|||
}); |
|||
}); |
|||
|
|||
shepherd.post('/coins/remove', (req, res) => { |
|||
const _chain = req.body.chain; |
|||
|
|||
if (req.body.mode === 'native') { |
|||
delete shepherd.coindInstanceRegistry[_chain ? _chain : 'komodod']; |
|||
|
|||
const obj = { |
|||
msg: 'success', |
|||
result: 'result', |
|||
}; |
|||
|
|||
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 = {}; |
|||
} |
|||
|
|||
const obj = { |
|||
msg: 'success', |
|||
result: 'result', |
|||
}; |
|||
|
|||
res.end(JSON.stringify(obj)); |
|||
} |
|||
}); |
|||
|
|||
return shepherd; |
|||
}; |
@ -0,0 +1,218 @@ |
|||
module.exports = (shepherd) => { |
|||
shepherd.getConf = (chain) => { |
|||
let _confLocation = chain === 'komodod' ? `${shepherd.komodoDir}/komodo.conf` : `${shepherd.komodoDir}/${chain}/${chain}.conf`; |
|||
_confLocation = chain === 'CHIPS' ? `${shepherd.chipsDir}/chips.conf` : _confLocation; |
|||
|
|||
// any coind
|
|||
if (chain) { |
|||
if (shepherd.nativeCoindList[chain.toLowerCase()]) { |
|||
const _osHome = shepherd.os.platform === 'win32' ? process.env.APPDATA : process.env.HOME; |
|||
let coindDebugLogLocation = `${_osHome}/.${shepherd.nativeCoindList[chain.toLowerCase()].bin.toLowerCase()}/debug.log`; |
|||
|
|||
_confLocation = `${_osHome}/.${shepherd.nativeCoindList[chain.toLowerCase()].bin.toLowerCase()}/${shepherd.nativeCoindList[chain.toLowerCase()].bin.toLowerCase()}.conf`; |
|||
} |
|||
|
|||
if (shepherd.fs.existsSync(_confLocation)) { |
|||
let _port = shepherd.assetChainPorts[chain]; |
|||
const _rpcConf = shepherd.fs.readFileSync(_confLocation, 'utf8'); |
|||
|
|||
// any coind
|
|||
if (shepherd.nativeCoindList[chain.toLowerCase()]) { |
|||
_port = shepherd.nativeCoindList[chain.toLowerCase()].port; |
|||
} |
|||
|
|||
if (_rpcConf.length) { |
|||
let _match; |
|||
let parsedRpcConfig = { |
|||
user: '', |
|||
pass: '', |
|||
port: _port, |
|||
}; |
|||
|
|||
if (_match = _rpcConf.match(/rpcuser=\s*(.*)/)) { |
|||
parsedRpcConfig.user = _match[1]; |
|||
} |
|||
|
|||
if ((_match = _rpcConf.match(/rpcpass=\s*(.*)/)) || |
|||
(_match = _rpcConf.match(/rpcpassword=\s*(.*)/))) { |
|||
parsedRpcConfig.pass = _match[1]; |
|||
} |
|||
|
|||
if (shepherd.nativeCoindList[chain.toLowerCase()]) { |
|||
shepherd.rpcConf[chain] = parsedRpcConfig; |
|||
} else { |
|||
shepherd.rpcConf[chain === 'komodod' ? 'KMD' : chain] = parsedRpcConfig; |
|||
} |
|||
} else { |
|||
shepherd.log(`${_confLocation} is empty`); |
|||
} |
|||
} else { |
|||
shepherd.log(`${_confLocation} doesnt exist`); |
|||
} |
|||
} |
|||
} |
|||
|
|||
/* |
|||
* type: POST |
|||
* params: payload |
|||
*/ |
|||
shepherd.post('/cli', (req, res, next) => { |
|||
if (!req.body.payload) { |
|||
const errorObj = { |
|||
msg: 'error', |
|||
result: 'no payload provided', |
|||
}; |
|||
|
|||
res.end(JSON.stringify(errorObj)); |
|||
} else if (!req.body.payload.cmd.match(/^[0-9a-zA-Z _\,\.\[\]"'/\\]+$/g)) { |
|||
const errorObj = { |
|||
msg: 'error', |
|||
result: 'wrong cli string format', |
|||
}; |
|||
|
|||
res.end(JSON.stringify(errorObj)); |
|||
} else { |
|||
const _mode = req.body.payload.mode === 'passthru' ? 'passthru' : 'default'; |
|||
const _chain = req.body.payload.chain === 'KMD' ? null : req.body.payload.chain; |
|||
const _params = req.body.payload.params ? ` ${req.body.payload.params}` : ''; |
|||
let _cmd = req.body.payload.cmd; |
|||
|
|||
if (!shepherd.rpcConf[_chain]) { |
|||
shepherd.getConf(req.body.payload.chain === 'KMD' || !req.body.payload.chain && shepherd.kmdMainPassiveMode ? 'komodod' : req.body.payload.chain); |
|||
} |
|||
|
|||
if (_mode === 'default') { |
|||
if (_cmd === 'debug' && |
|||
_chain !== 'CHIPS') { |
|||
if (shepherd.nativeCoindList[_chain.toLowerCase()]) { |
|||
const _osHome = shepherd.os.platform === 'win32' ? process.env.APPDATA : process.env.HOME; |
|||
let coindDebugLogLocation; |
|||
|
|||
if (_chain === 'CHIPS') { |
|||
coindDebugLogLocation = `${shepherd.chipsDir}/debug.log`; |
|||
} else { |
|||
coindDebugLogLocation = `${_osHome}/.${shepherd.nativeCoindList[_chain.toLowerCase()].bin.toLowerCase()}/debug.log`; |
|||
} |
|||
|
|||
shepherd.readDebugLog(coindDebugLogLocation, 1) |
|||
.then((result) => { |
|||
const _obj = { |
|||
msg: 'success', |
|||
result: result, |
|||
}; |
|||
|
|||
// shepherd.log('bitcoinrpc debug ====>');
|
|||
// console.log(result);
|
|||
|
|||
res.end(JSON.stringify(_obj)); |
|||
}, (result) => { |
|||
const _obj = { |
|||
error: result, |
|||
result: 'error', |
|||
}; |
|||
|
|||
res.end(JSON.stringify(_obj)); |
|||
}); |
|||
} else { |
|||
res.end({ |
|||
error: 'bitcoinrpc debug error', |
|||
result: 'error', |
|||
}); |
|||
// console.log('bitcoinrpc debug error');
|
|||
} |
|||
} else { |
|||
if (_chain === 'CHIPS' && |
|||
_cmd === 'debug') { |
|||
_cmd = 'getblockchaininfo'; |
|||
} |
|||
|
|||
let _body = { |
|||
agent: 'bitcoinrpc', |
|||
method: _cmd, |
|||
}; |
|||
|
|||
if (req.body.payload.params) { |
|||
_body = { |
|||
agent: 'bitcoinrpc', |
|||
method: _cmd, |
|||
params: req.body.payload.params === ' ' ? [''] : req.body.payload.params, |
|||
}; |
|||
} |
|||
|
|||
if (req.body.payload.chain) { |
|||
const options = { |
|||
url: `http://localhost:${shepherd.rpcConf[req.body.payload.chain].port}`, |
|||
method: 'POST', |
|||
auth: { |
|||
user: shepherd.rpcConf[req.body.payload.chain].user, |
|||
pass: shepherd.rpcConf[req.body.payload.chain].pass, |
|||
}, |
|||
body: JSON.stringify(_body), |
|||
}; |
|||
|
|||
// send back body on both success and error
|
|||
// this bit replicates iguana core's behaviour
|
|||
shepherd.request(options, (error, response, body) => { |
|||
if (response && |
|||
response.statusCode && |
|||
response.statusCode === 200) { |
|||
res.end(body); |
|||
} else { |
|||
res.end(body ? 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; |
|||
|
|||
if (shepherd.nativeCoindList && |
|||
_chain && |
|||
shepherd.nativeCoindList[_chain.toLowerCase()]) { |
|||
_coindCliBin = `${shepherd.coindRootDir}/${_chain.toLowerCase()}/${shepherd.nativeCoindList[_chain.toLowerCase()].bin.toLowerCase()}-cli`; |
|||
} |
|||
|
|||
let _arg = (_chain ? ' -ac_name=' + _chain : '') + ' ' + _cmd + _params; |
|||
|
|||
if (shepherd.appConfig.dataDir.length) { |
|||
_arg = `${_arg} -datadir=${shepherd.appConfig.dataDir + (_chain ? '/' + key : '')}`; |
|||
} |
|||
|
|||
_arg = _arg.trim().split(' '); |
|||
shepherd.execFile(_coindCliBin, _arg, (error, stdout, stderr) => { |
|||
shepherd.log(`stdout: ${stdout}`); |
|||
shepherd.log(`stderr: ${stderr}`); |
|||
|
|||
if (error !== null) { |
|||
shepherd.log(`exec error: ${error}`); |
|||
} |
|||
|
|||
let responseObj; |
|||
|
|||
if (stderr) { |
|||
responseObj = { |
|||
msg: 'error', |
|||
result: stderr, |
|||
}; |
|||
} else { |
|||
responseObj = { |
|||
msg: 'success', |
|||
result: stdout, |
|||
}; |
|||
} |
|||
|
|||
res.end(JSON.stringify(responseObj)); |
|||
shepherd.killRogueProcess('komodo-cli'); |
|||
}); |
|||
} |
|||
} |
|||
}); |
|||
|
|||
return shepherd; |
|||
}; |
@ -1,3 +1,3 @@ |
|||
version=0.2.0.22a |
|||
version=0.2.0.25b |
|||
type=e-beta |
|||
minversion=0.2.0.2 |
@ -1 +1 @@ |
|||
0.2.0.22a-beta |
|||
0.2.0.25b-beta |
Loading…
Reference in new issue