Browse Source

Merge pull request #241 from SuperNETorg/v0.25

V0.25
master v0.2.0.27a-beta
pbca26 7 years ago
committed by GitHub
parent
commit
3a68890699
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 12
      AgamaApp-windows.md
  2. 47
      README.md
  3. BIN
      assets/bin/linux64/iguana
  4. BIN
      assets/bin/linux64/komodo-cli
  5. BIN
      assets/bin/linux64/komodod
  6. BIN
      assets/bin/osx/iguana
  7. BIN
      assets/bin/osx/komodo-cli
  8. BIN
      assets/bin/osx/komodod
  9. BIN
      assets/bin/osx/libgcc_s.1.dylib
  10. BIN
      assets/bin/osx/libgomp.1.dylib
  11. BIN
      assets/bin/osx/libnanomsg.5.0.0.dylib
  12. BIN
      assets/bin/osx/libstdc++.6.dylib
  13. 19
      assets/bin/win64/genkmdconf.bat
  14. BIN
      assets/bin/win64/iguana.exe
  15. BIN
      assets/bin/win64/komodo-cli.exe
  16. BIN
      assets/bin/win64/komodod.exe
  17. BIN
      assets/bin/win64/libcrypto-1_1.dll
  18. BIN
      assets/bin/win64/libcurl-4.dll
  19. BIN
      assets/bin/win64/libcurl.dll
  20. BIN
      assets/bin/win64/libgcc_s_sjlj-1.dll
  21. BIN
      assets/bin/win64/libnanomsg.dll
  22. BIN
      assets/bin/win64/libssl-1_1.dll
  23. BIN
      assets/bin/win64/libwinpthread-1.dll
  24. BIN
      assets/bin/win64/nanomsg.dll
  25. BIN
      assets/bin/win64/pthreadvc2.dll
  26. 86
      binary_artifacts.sh
  27. 8
      buildscripts/easydex-build.sh
  28. 24
      buildscripts/electron-build-linux.sh
  29. 21
      buildscripts/electron-build-osx.sh
  30. 23
      buildscripts/electron-build-windows.sh
  31. 3
      gui/startup/agama-instance-error.html
  32. 9
      gui/startup/app-closing.html
  33. 3
      gui/startup/app-settings.html
  34. 73
      gui/startup/index.html
  35. 119
      gui/startup/main.html
  36. 18
      index.html
  37. 242
      main.js
  38. 26
      make-deb.js
  39. 26
      make-rpm.js
  40. 35
      package.json
  41. 4
      private/kmdcli.js
  42. 120
      routes/appConfig.js
  43. 962
      routes/cache.js
  44. 262
      routes/electrumjs/electrumServers.js
  45. 354
      routes/electrumjs/electrumjs.core.js
  46. 205
      routes/electrumjs/electrumjs.networks.js
  47. 120
      routes/electrumjs/electrumjs.txdecoder.js
  48. 197
      routes/fetchparams.js
  49. 45
      routes/mock.js
  50. 98
      routes/nativeCoind.js
  51. 4
      routes/ports.js
  52. 3466
      routes/shepherd.js
  53. 103
      routes/shepherd/addCoinShortcuts.js
  54. 78
      routes/shepherd/appInfo.js
  55. 31
      routes/shepherd/auth.js
  56. 239
      routes/shepherd/binsTestUtil.js
  57. 70
      routes/shepherd/binsUtils.js
  58. 99
      routes/shepherd/coindWalletKeys.js
  59. 31
      routes/shepherd/coins.js
  60. 71
      routes/shepherd/coinsList.js
  61. 57
      routes/shepherd/confMaxconnections.js
  62. 152
      routes/shepherd/config.js
  63. 908
      routes/shepherd/daemonControl.js
  64. 312
      routes/shepherd/dashboardUpdate.js
  65. 119
      routes/shepherd/debugLog.js
  66. 44
      routes/shepherd/dex/coind.js
  67. 1
      routes/shepherd/dex/coins.json
  68. 242
      routes/shepherd/dex/mmControl.js
  69. 39
      routes/shepherd/dex/mmRequest.js
  70. 165
      routes/shepherd/downloadBins.js
  71. 154
      routes/shepherd/downloadPatch.js
  72. 49
      routes/shepherd/downloadUtil.js
  73. 134
      routes/shepherd/downloadZcparams.js
  74. 64
      routes/shepherd/electrum/auth.js
  75. 145
      routes/shepherd/electrum/balance.js
  76. 59
      routes/shepherd/electrum/block.js
  77. 91
      routes/shepherd/electrum/coins.js
  78. 487
      routes/shepherd/electrum/createtx.js
  79. 26
      routes/shepherd/electrum/estimate.js
  80. 37
      routes/shepherd/electrum/interest.js
  81. 178
      routes/shepherd/electrum/keys.js
  82. 195
      routes/shepherd/electrum/listunspent.js
  83. 145
      routes/shepherd/electrum/merkle.js
  84. 143
      routes/shepherd/electrum/network.js
  85. 397
      routes/shepherd/electrum/transactions.js
  86. 71
      routes/shepherd/init.js
  87. 45
      routes/shepherd/kickstart.js
  88. 141
      routes/shepherd/log.js
  89. 82
      routes/shepherd/paths.js
  90. 146
      routes/shepherd/pin.js
  91. 170
      routes/shepherd/quitDaemon.js
  92. 218
      routes/shepherd/rpc.js
  93. 2
      version
  94. 2
      version_build

12
AgamaApp-windows.md

@ -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)

47
README.md

@ -6,11 +6,20 @@ You must have `node.js` and `npm` installed on your machine.
Clone Agama Desktop App with EasyDEX-GUI submodule
```shell
git clone --recursive https://github.com/SuperNETorg/Agama.git
cd gui/EasyDEX-GUI/
git checkout master
git pull
cd ../../
1) git clone https://github.com/supernetorg/agama --recursive --branch pkg_automation_electrum --single-branch
with this command you git clone agama - but explicitly just the pkg_automation_electrum branch (therefore --single-branch) which we also use for the release packages.
2) cd agama && cd gui/EasyDEX-GUI/
3) git checkout electrum && git pull origin electrum
4) npm install && npm install webpack
5) ./binary_artifacts.sh
6) npm start in projects' root folder
7) cd gui/EasyDEX-GUI/react/src
8) npm start
8) toggle dev and debug options in settings
9) restart the app
10) sync komodod and/or asset chains
You're ready to dev
```
Install Agama App
@ -25,34 +34,6 @@ npm start
```
### Important dev notes
#### Use the following config.json for dev
Place it in ./iguana folder.
```
{
"edexGuiOnly": true,
"iguanaGuiOnly": false,
"manualIguanaStart": false,
"skipBasiliskNetworkCheck": true,
"minNotaries": 8,
"host": "127.0.0.1",
"agamaPort": 17777,
"iguanaCorePort": 7778,
"maxDescriptors": {
"darwin": 90000,
"linux": 1000000
},
"killIguanaOnStart": true,
"dev": true,
"v2": true,
"useBasiliskInstance": true,
"debug": true,
"iguanaAppPort": 17777,
"forks": {
"basilisk": false,
"all": false
}
}
```
#### Sockets.io
In dev mode backend is configured to send/receive messages from/to http://127.0.0.1:3000 address. If you open it as http://localhost:3000 sockets server will reject any messages.

BIN
assets/bin/linux64/iguana

Binary file not shown.

BIN
assets/bin/linux64/komodo-cli

Binary file not shown.

BIN
assets/bin/linux64/komodod

Binary file not shown.

BIN
assets/bin/osx/iguana

Binary file not shown.

BIN
assets/bin/osx/komodo-cli

Binary file not shown.

BIN
assets/bin/osx/komodod

Binary file not shown.

BIN
assets/bin/osx/libgcc_s.1.dylib

Binary file not shown.

BIN
assets/bin/osx/libgomp.1.dylib

Binary file not shown.

BIN
assets/bin/osx/libnanomsg.5.0.0.dylib

Binary file not shown.

BIN
assets/bin/osx/libstdc++.6.dylib

Binary file not shown.

19
assets/bin/win64/genkmdconf.bat

@ -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
)

BIN
assets/bin/win64/iguana.exe

Binary file not shown.

BIN
assets/bin/win64/komodo-cli.exe

Binary file not shown.

BIN
assets/bin/win64/komodod.exe

Binary file not shown.

BIN
assets/bin/win64/libcrypto-1_1.dll

Binary file not shown.

BIN
assets/bin/win64/libcurl-4.dll

Binary file not shown.

BIN
assets/bin/win64/libcurl.dll

Binary file not shown.

BIN
assets/bin/win64/libgcc_s_sjlj-1.dll

Binary file not shown.

BIN
assets/bin/win64/libnanomsg.dll

Binary file not shown.

BIN
assets/bin/win64/libssl-1_1.dll

Binary file not shown.

BIN
assets/bin/win64/libwinpthread-1.dll

Binary file not shown.

BIN
assets/bin/win64/nanomsg.dll

Binary file not shown.

BIN
assets/bin/win64/pthreadvc2.dll

Binary file not shown.

86
binary_artifacts.sh

@ -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

8
buildscripts/easydex-build.sh

@ -9,13 +9,15 @@ echo "Building EasyDEX-GUI"
echo "Actual directory is: ${PWD}"
echo "Checkout to redux branch."
git checkout redux
git pull origin redux
git checkout electrum
git pull origin electrum
[ -d react ] && cd react || echo "!!! I can't find react"
echo "Actual directory is: ${PWD}"
echo "Installing nodejs modules."
npm install
npm install webpack
echo "Building EasyDEX-GUI app."
npm run build
echo "EasyDEX-GUI is built!"
echo "EasyDEX-GUI is built!"

24
buildscripts/electron-build-linux.sh

@ -3,25 +3,19 @@
### Created by mmaxian, 3/2017
[ -z $AGAMA_VERSION ] && echo "AGAMA_VERSION variable is not set." && exit 0
[ ! -d build ] && mkdir build
echo
echo =========================================
echo Step: Removing old binaries
rm -rvf artifacts.supernet.org
echo
echo Step: Cloning latest binaries for build
wget --recursive --no-parent -q https://artifacts.supernet.org/latest/linux/
find artifacts.supernet.org/latest/linux/ -exec ls -l {} \;
cd ..
echo =========================================
echo
echo "Build script for Iguana application for Linux x64 platform."
echo "Preparing electron package $AGAMA_VERSION"
electron-packager . --platform=linux --arch=x64 \
--icon=assets/icons/agama_icons/128x128.png \
--out=build/ --buildVersion=$AGAMA_VERSION \
--ignore=build/artifacts.supernet.org/latest/windows \
--ignore=build/artifacts.supernet.org/latest/osx \
--overwrite
--out=build/ \
--buildVersion=$AGAMA_VERSION \
--ignore=assets/bin/win64 \
--ignore=assets/bin/osx \
--ignore=react/node_modules \
--ignore=react/src \
--ignore=react/www \
--overwrite

21
buildscripts/electron-build-osx.sh

@ -3,25 +3,18 @@
### Created by mmaxian, 3/2017
[ -z $AGAMA_VERSION ] && echo "AGAMA_VERSION variable is not set." && exit 0
[ ! -d build ] && mkdir build
echo
echo =========================================
echo Step: Removing old binaries
rm -rvf artifacts.supernet.org
echo
echo Step: Cloning latest binaries for build
wget --recursive --no-parent -q https://artifacts.supernet.org/latest/osx/
find artifacts.supernet.org/latest/osx/ -exec ls -l {} \;
cd ..
echo =========================================
echo
echo "Build script for Iguana application for MacOS platform."
echo "Preparing electron package $AGAMA_VERSION"
electron-packager . --platform=darwin --arch=x64 \
--icon=assets/icons/agama_app_icon.icns \
--out=build/ --buildVersion=$AGAMA_VERSION \
--ignore=build/artifacts.supernet.org/latest/windows \
--ignore=build/artifacts.supernet.org/latest/linux \
--overwrite
--ignore=assets/bin/win64 \
--ignore=assets/bin/linux64 \
--ignore=react/node_modules \
--ignore=react/src \
--ignore=react/www \
--overwrite

23
buildscripts/electron-build-windows.sh

@ -3,33 +3,26 @@
### Created by mmaxian, 3/2017
[ -z $AGAMA_VERSION ] && echo "AGAMA_VERSION variable is not set." && exit 0
[ ! -d build ] && mkdir build
echo
echo =========================================
echo Step: Removing old binaries
rm -rvf artifacts.supernet.org
echo
echo Step: Cloning latest binaries for build
wget --recursive --no-parent -q https://artifacts.supernet.org/latest/windows/
find artifacts.supernet.org/latest/windows/ -exec ls -l {} \;
cd ..
echo =========================================
echo
echo "Build script for Iguana application for Windows x64 platform."
echo "Preparing electron package $AGAMA_VERSION"
electron-packager . --platform=win32 \
--arch=ia32 \
--icon=assets/icons/agama_app_icon.ico \
--out=build \
--out=build/ \
--buildVersion=$AGAMA_VERSION \
--ignore=build/artifacts.supernet.org/latest/osx \
--ignore=build/artifacts.supernet.org/latest/linux \
--ignore=assets/bin/osx \
--ignore=assets/bin/linux64 \
--ignore=react/node_modules \
--ignore=react/src \
--ignore=react/www \
--overwrite \
--version-string.CompanyName="SuperNET" \
--version-string.FileDescription="Agama" \
--version-string.OriginalFilename="Agama" \
--version-string.ProductName="Agama" \
--version-string.InternalName="Agama" \
--app-copyright="Copyright (C) 2017 SuperNET. All rights reserved."
--app-copyright="Copyright (C) 2017 SuperNET. All rights reserved."

3
gui/startup/agama-instance-error.html

@ -3,14 +3,12 @@
<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">
<link rel="stylesheet" href="../EasyDEX-GUI/assets/mainWindow/css/jRoll.min.css">
<link rel="stylesheet" href="../EasyDEX-GUI/assets/mainWindow/css/loading.css">
<script type="text/javascript" src="../EasyDEX-GUI/assets/mainWindow/js/module-hack.js"></script>
<script type="text/javascript" src="../EasyDEX-GUI/assets/global/vendor/jquery/jquery.min.js"></script>
<script type="text/javascript" src="../EasyDEX-GUI/assets/scripts/config.js"></script>
<script type="text/javascript" src="../EasyDEX-GUI/assets/mainWindow/js/bluebird.min.js"></script>
<script type="text/javascript" src="../EasyDEX-GUI/assets/mainWindow/js/loading.js"></script>
<script type="text/javascript" src="../EasyDEX-GUI/assets/mainWindow/js/jRoll.min.js"></script>
<script>if (window.module) module = window.module;</script>
</head>
<body class="agamaMode agama-default-window-height">
@ -30,6 +28,5 @@
class="btn btn-primary btn-close-app">Quit</button>
</div>
</div>
<script type="text/javascript" src="../EasyDEX-GUI/assets/mainWindow/js/init.js"></script>
</body>
</html>

9
gui/startup/app-closing.html

@ -3,17 +3,15 @@
<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">
<link rel="stylesheet" href="../EasyDEX-GUI/assets/mainWindow/css/jRoll.min.css">
<link rel="stylesheet" href="../EasyDEX-GUI/assets/mainWindow/css/loading.css">
<script type="text/javascript" src="../EasyDEX-GUI/assets/mainWindow/js/module-hack.js"></script>
<script type="text/javascript" src="../EasyDEX-GUI/assets/global/vendor/jquery/jquery.min.js"></script>
<script type="text/javascript" src="../EasyDEX-GUI/assets/scripts/config.js"></script>
<script type="text/javascript" src="../EasyDEX-GUI/assets/mainWindow/js/bluebird.min.js"></script>
<script type="text/javascript" src="../EasyDEX-GUI/assets/mainWindow/js/loading.js"></script>
<script type="text/javascript" src="../EasyDEX-GUI/assets/mainWindow/js/jRoll.min.js"></script>
<script>if (window.module) module = window.module;</script>
</head>
<body class="agamaMode agama-default-window-height">
<body class="agamaMode closing-window-height">
<div class="text-center">
<div
id="agamaMode"
@ -24,7 +22,10 @@
alt="Agama Wallet"
width="80"
height="100" />
<div id="agamaModeStatus">App is closing. Please wait...</div>
<div id="agamaModeStatus">
App is closing. Please wait...<br/><br/>
<small>This may take a while depending on your system resources and current state of daemon applications.</small>
</div>
</div>
</div>
</body>

3
gui/startup/app-settings.html

@ -3,14 +3,12 @@
<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">
<link rel="stylesheet" href="../EasyDEX-GUI/assets/mainWindow/css/jRoll.min.css">
<link rel="stylesheet" href="../EasyDEX-GUI/assets/mainWindow/css/loading.css">
<script type="text/javascript" src="../EasyDEX-GUI/assets/mainWindow/js/module-hack.js"></script>
<script type="text/javascript" src="../EasyDEX-GUI/assets/global/vendor/jquery/jquery.min.js"></script>
<script type="text/javascript" src="../EasyDEX-GUI/assets/scripts/config.js"></script>
<script type="text/javascript" src="../EasyDEX-GUI/assets/mainWindow/js/bluebird.min.js"></script>
<script type="text/javascript" src="../EasyDEX-GUI/assets/mainWindow/js/loading.js"></script>
<script type="text/javascript" src="../EasyDEX-GUI/assets/mainWindow/js/jRoll.min.js"></script>
<script>if (window.module) module = window.module;</script>
</head>
<body class="agamaMode agama-app-settings-window">
@ -38,6 +36,7 @@
<div
id="debugOverlay"
class="debug-overlay hide"></div>
<div class="padding-top-40">Any changes to app config require app restart!</div>
<div class="settings-buttons-block">
<button
onClick="testBins('komodod')"

73
gui/startup/index.html

@ -3,14 +3,12 @@
<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">
<link rel="stylesheet" href="../EasyDEX-GUI/assets/mainWindow/css/jRoll.min.css">
<link rel="stylesheet" href="../EasyDEX-GUI/assets/mainWindow/css/loading.css">
<script type="text/javascript" src="../EasyDEX-GUI/assets/mainWindow/js/module-hack.js"></script>
<script type="text/javascript" src="../EasyDEX-GUI/assets/global/vendor/jquery/jquery.min.js"></script>
<script type="text/javascript" src="../EasyDEX-GUI/assets/scripts/config.js"></script>
<script type="text/javascript" src="../EasyDEX-GUI/assets/mainWindow/js/bluebird.min.js"></script>
<script type="text/javascript" src="../EasyDEX-GUI/assets/mainWindow/js/loading.js"></script>
<script type="text/javascript" src="../EasyDEX-GUI/assets/mainWindow/js/jRoll.min.js"></script>
<script>if (window.module) module = window.module;</script>
</head>
<body class="agamaMode loading-window">
@ -21,13 +19,33 @@
onClick="quitApp()">
<img src="../EasyDEX-GUI/assets/mainWindow/img/fa-close.png">
</div>
<img
src="../EasyDEX-GUI/assets/mainWindow/img/agama-icon.svg"
class="agama-logo"
alt="Agama Wallet"
width="80"
height="100" />
<div id="agamaModeStatus">Choose Agama mode</div>
<div class="intro">
<img
src="../EasyDEX-GUI/assets/mainWindow/img/agama-icon.svg"
class="agama-logo"
alt="Agama Wallet"
width="80"
height="100" />
<div id="agamaModeStatus">
<span id="agamaModeStatusText">Choose Agama mode</span>
<div
onClick="toggleMainWindowHelp()"
class="settings-help pointer"
title="About Agama modes">
<img src="../EasyDEX-GUI/assets/mainWindow/img/fa-question.png" />
</div>
</div>
</div>
<div class="agama-modes-help" style="display: none">
<img
onClick="toggleMainWindowHelp()"
class="close-btn"
src="../EasyDEX-GUI/assets/mainWindow/img/fa-close.png">
<strong>Be aware:</strong> <u>Native mode</u> requires to download the whole blockchain data to a local disk before you can start using it. This may take from <strong>several hours to a day</strong> depending on your connection and hardware. Please <u>try to keep Agama running</u> until the whole process is finished.
<br /><br />
If you need a quick and easy access to your funds try <u>Lite (SPV) mode</u> which doesn't require any blockchain to be loaded locally. All data is requested on demand from Electrum servers. <strong>To gain access to Lite mode toggle "Enable experimental features" in Settings.</strong>
</div>
<div class="mode-desc native">Full blockchain mode</div>
<button
id="nativeOnlyBtn"
onClick="closeMainWindow(true)"
@ -40,33 +58,54 @@
onClick="toggleDropdown()">
<img src="../EasyDEX-GUI/assets/mainWindow/img/fa-caret-down.png">
</div>
<ul class="dropdown-menu hide">
<ul class="dropdown-menu native hide">
<li onClick="closeMainWindow()">
<a>KMD + REVS + JUMBLR</a>
</li>
<li onClick="closeMainWindow(null, true)">
<a>Custom</a>
</li>
<li onClick="startKMDPassive()">
<li id="kmdPassiveMode" onClick="startKMDPassive()">
<a>KMD (passive)</a>
</li>
</ul>
<div class="mode-desc spv">Starts in seconds</div>
<button
id="normalStartBtn"
onClick="normalStart()"
class="btn btn-primary btn-mode">
<img src="../EasyDEX-GUI/assets/mainWindow/img/fa-cubes.png"> All modes
id="spvBtn"
onClick="startSPV('KMD')"
class="btn btn-primary btn-mode btn-native margin-left-20">
<img src="../EasyDEX-GUI/assets/mainWindow/img/fa-flash.png"> KMD lite
</button>
<div class="margin-top-20 settings-stick-to-right">
<div
id="spvBtnCarret"
class="btn btn-primary btn-caret"
onClick="toggleDropdown('lite')">
<img src="../EasyDEX-GUI/assets/mainWindow/img/fa-caret-down.png">
</div>
<ul class="dropdown-menu lite hide">
<li onClick="startSPV('KMD+REVS+JUMBLR')">
<a>Lite: KMD + REVS + JUMBLR</a>
</li>
<li onClick="startSPV('CHIPS')">
<a>CHIPS lite</a>
</li>
<li onClick="closeMainWindow(null, true)">
<a>Custom</a>
</li>
</ul>
<div class="margin-top-20">
<button
id="settingsBtn"
onClick="openSettingsWindow()"
class="btn btn-info btn-mode">
<img src="../EasyDEX-GUI/assets/mainWindow/img/fa-cogs.png"> Settings
</button>
<button class="btn btn-primary btn-mode hide" onClick="closeMainWindow(null, true)">Custom</button>
</div>
</div>
</div>
<script type="text/javascript" src="../EasyDEX-GUI/assets/mainWindow/js/init.js"></script>
<script type="text/javascript">init()</script>
</body>
</html>

119
gui/startup/main.html

@ -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>

18
index.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>

242
main.js

@ -1,39 +1,42 @@
// main proc for EasyDEX GUI
// main proc for Agama
// this app spawns iguana in background in nontech-mode
const electron = require('electron'),
app = electron.app,
BrowserWindow = electron.BrowserWindow,
path = require('path'),
url = require('url'),
os = require('os'),
md5 = require('./routes/md5'),
exec = require('child_process').exec,
{ Menu } = require('electron'),
portscanner = require('portscanner'),
osPlatform = os.platform(),
fixPath = require('fix-path');
var express = require('express'),
bodyParser = require('body-parser'),
fs = require('fs'),
fsnode = require('fs'),
fs = require('fs-extra'),
pm2 = require('pm2'),
cluster = require('cluster'),
numCPUs = require('os').cpus().length;
const electron = require('electron');
const app = electron.app;
const BrowserWindow = electron.BrowserWindow;
const path = require('path');
const url = require('url');
const os = require('os');
const md5 = require('./routes/md5');
const exec = require('child_process').exec;
const { Menu } = require('electron');
const portscanner = require('portscanner');
const osPlatform = os.platform();
const fixPath = require('fix-path');
const express = require('express');
const bodyParser = require('body-parser');
const fsnode = require('fs');
const fs = require('fs-extra');
const numCPUs = require('os').cpus().length;
Promise = require('bluebird');
if (osPlatform === 'linux') {
process.env.ELECTRON_RUN_AS_NODE = true;
// console.log(process.env);
}
// GUI APP settings and starting gui on address http://120.0.0.1:17777
var shepherd = require('./routes/shepherd');
var guiapp = express();
shepherd.createAgamaDirs();
var appConfig = shepherd.loadLocalConfig(); // load app config
const nativeCoindList = shepherd.scanNativeCoindBins();
shepherd.setVar('nativeCoindList', nativeCoindList);
let localVersion;
let localVersionFile = shepherd.readVersionFile();
@ -51,8 +54,7 @@ const appBasicInfo = {
app.setName(appBasicInfo.name);
app.setVersion(appBasicInfo.version);
shepherd.binFixRights();
shepherd.createIguanaDirs();
shepherd.createAgamaDirs();
const appSessionHash = md5(Date.now().toString());
@ -67,20 +69,28 @@ shepherd.writeLog(`platform: ${osPlatform}`);
shepherd.writeLog(`os_release: ${os.release()}`);
shepherd.writeLog(`os_type: ${os.type()}`);
var appConfig = shepherd.loadLocalConfig(); // load app config
shepherd.log(`app init ${appSessionHash}`);
shepherd.log(`app info: ${appBasicInfo.name} ${appBasicInfo.version}`);
shepherd.log('sys info:');
shepherd.log(`totalmem_readable: ${formatBytes(os.totalmem())}`);
shepherd.log(`arch: ${os.arch()}`);
shepherd.log(`cpu: ${os.cpus()[0].model}`);
shepherd.log(`cpu_cores: ${os.cpus().length}`);
shepherd.log(`platform: ${osPlatform}`);
shepherd.log(`os_release: ${os.release()}`);
shepherd.log(`os_type: ${os.type()}`);
appConfig['daemonOutput'] = false; // shadow setting
let __defaultAppSettings = require('./routes/appConfig.js').config;
__defaultAppSettings['daemonOutput'] = false; // shadow setting
const _defaultAppSettings = __defaultAppSettings;
shepherd.log(`app started in ${(appConfig.dev ? 'dev mode' : ' user mode')}`);
shepherd.writeLog(`app started in ${(appConfig.dev ? 'dev mode' : ' user mode')}`);
shepherd.setConfKMD();
if (appConfig.killIguanaOnStart) {
shepherd.killRogueProcess('iguana');
}
shepherd.setConfKMD('CHIPS');
guiapp.use(function(req, res, next) {
res.header('Access-Control-Allow-Origin', appConfig.dev ? '*' : 'http://127.0.0.1:3000');
@ -103,9 +113,9 @@ process.once('loaded', () => {
process.setFdLimit(appConfig.maxDescriptors.darwin);
app.setAboutPanelOptions({
applicationName: app.getName(),
applicationVersion: app.getVersion(),
applicationVersion: `${app.getVersion().replace('version=', '')}-beta`,
copyright: 'Released under the MIT license',
credits: 'SuperNET Team'
credits: 'SuperNET Team',
})
}
if (osPlatform === 'linux') {
@ -148,13 +158,13 @@ let forceQuitApp = false;
const _zcashParamsExist = shepherd.zcashParamsExist();
module.exports = guiapp;
let iguanaIcon;
let agamaIcon;
if (os.platform() === 'linux') {
iguanaIcon = path.join(__dirname, '/assets/icons/agama_icons/128x128.png');
agamaIcon = path.join(__dirname, '/assets/icons/agama_icons/128x128.png');
}
if (os.platform() === 'win32') {
iguanaIcon = path.join(__dirname, '/assets/icons/agama_app_icon.ico');
agamaIcon = path.join(__dirname, '/assets/icons/agama_app_icon.ico');
}
function createLoadingWindow() {
@ -166,7 +176,7 @@ function createLoadingWindow() {
width: 500,
height: 355,
frame: false,
icon: iguanaIcon,
icon: agamaIcon,
show: false,
});
} catch(e) {}
@ -183,7 +193,7 @@ function createLoadingWindow() {
// start sockets.io
io.set('origins', appConfig.dev ? 'http://127.0.0.1:3000' : `http://127.0.0.1:${appConfig.agamaPort}`); // set origin
io.on('connection', function(client) {
/*io.on('connection', function(client) {
shepherd.log('EDEX GUI is connected...');
shepherd.writeLog('EDEX GUI is connected...');
@ -197,7 +207,7 @@ function createLoadingWindow() {
shepherd.log(data);
client.emit('messages', 'Sockets server is listening');
});
});
});*/
});
} else {
willQuitApp = true;
@ -219,6 +229,8 @@ function createLoadingWindow() {
loadingWindow.forseCloseApp = forseCloseApp;
loadingWindow.createAppSettingsWindow = createAppSettingsWindow;
loadingWindow.startKMDNative = shepherd.startKMDNative;
loadingWindow.startSPV = shepherd.startSPV;
loadingWindow.arch = os.arch();
// load our index.html (i.e. easyDEX GUI)
loadingWindow.loadURL(`http://${appConfig.host}:${appConfig.agamaPort}/gui/startup`);
@ -229,9 +241,6 @@ function createLoadingWindow() {
});
shepherd.writeLog('show loading window');
// DEVTOOLS - only for dev purposes - ca333
// loadingWindow.webContents.openDevTools()
// if window closed we kill iguana proc
loadingWindow.on('hide', function() {
// our app does not have multiwindow - so we dereference the window object instead of
@ -274,9 +283,9 @@ function createAppCloseWindow() {
// initialise window
appCloseWindow = new BrowserWindow({ // dirty hack to prevent main window flash on quit
width: 500,
height: 355,
height: 320,
frame: false,
icon: iguanaIcon,
icon: agamaIcon,
show: false,
});
@ -299,9 +308,9 @@ function createAppSettingsWindow() {
// initialise window
appSettingsWindow = new BrowserWindow({ // dirty hack to prevent main window flash on quit
width: 750,
height: !appConfig.experimentalFeatures ? 570 : 700,
height: 610,
frame: false,
icon: iguanaIcon,
icon: agamaIcon,
show: false,
});
@ -343,7 +352,7 @@ function createWindow(status) {
mainWindow = new BrowserWindow({ // dirty hack to prevent main window flash on quit
width: closeAppAfterLoading ? 1 : 1280,
height: closeAppAfterLoading ? 1 : 800,
icon: iguanaIcon,
icon: agamaIcon,
show: false,
});
@ -371,39 +380,40 @@ function createWindow(status) {
]);
// load our index.html (i.e. easyDEX GUI)
if (appConfig.edexGuiOnly) {
if (appConfig.v2) {
shepherd.writeLog('show edex gui');
mainWindow.appConfig = appConfig;
mainWindow.appConfigSchema = shepherd.appConfigSchema;
mainWindow.appBasicInfo = appBasicInfo;
mainWindow.appSessionHash = appSessionHash;
mainWindow.assetChainPorts = require('./routes/ports.js');
mainWindow.zcashParamsExist = _zcashParamsExist;
mainWindow.iguanaIcon = iguanaIcon;
mainWindow.testLocation = shepherd.testLocation;
mainWindow.kmdMainPassiveMode = shepherd.kmdMainPassiveMode;
mainWindow.getAppRuntimeLog = shepherd.getAppRuntimeLog;
if (appConfig.dev) {
mainWindow.loadURL('http://127.0.0.1:3000');
} else {
mainWindow.loadURL(`http://${appConfig.host}:${appConfig.agamaPort}/gui/EasyDEX-GUI/react/build`);
}
mainWindow.webContents.on('did-finish-load', function() {
setTimeout(function() {
mainWindow.show();
}, 40);
});
} else {
shepherd.writeLog('show edex gui');
mainWindow.loadURL(`http://${appConfig.host}:${appConfig.agamaPort}/gui/EasyDEX-GUI/`);
}
shepherd.writeLog('show edex gui');
mainWindow.appConfig = appConfig;
mainWindow.appConfigSchema = shepherd.appConfigSchema;
mainWindow.arch = os.arch();
mainWindow.appBasicInfo = appBasicInfo;
mainWindow.appSessionHash = appSessionHash;
mainWindow.assetChainPorts = require('./routes/ports.js');
mainWindow.zcashParamsExist = _zcashParamsExist;
mainWindow.zcashParamsExistPromise = shepherd.zcashParamsExistPromise;
mainWindow.agamaIcon = agamaIcon;
mainWindow.testLocation = shepherd.testLocation;
mainWindow.kmdMainPassiveMode = shepherd.kmdMainPassiveMode;
mainWindow.getAppRuntimeLog = shepherd.getAppRuntimeLog;
mainWindow.nativeCoindList = nativeCoindList;
mainWindow.zcashParamsDownloadLinks = shepherd.zcashParamsDownloadLinks;
mainWindow.isWindows = os.platform() === 'win32' ? true : false; // obsolete(?)
mainWindow.appExit = appExit;
mainWindow.getMaxconKMDConf = shepherd.getMaxconKMDConf;
mainWindow.setMaxconKMDConf = shepherd.setMaxconKMDConf;
mainWindow.getMMCacheData = shepherd.getMMCacheData;
mainWindow.activeSection = 'wallets';
if (appConfig.dev) {
mainWindow.loadURL('http://127.0.0.1:3000');
} else {
mainWindow.loadURL(`http://${appConfig.host}:${appConfig.agamaPort}/gui/startup/main.html`);
mainWindow.loadURL(`http://${appConfig.host}:${appConfig.agamaPort}/gui/EasyDEX-GUI/react/build`);
}
mainWindow.webContents.on('did-finish-load', function() {
setTimeout(function() {
mainWindow.show();
}, 40);
});
mainWindow.webContents.on('context-menu', (e, params) => { // context-menu returns params
const { selectionText, isEditable } = params; // params obj
@ -417,25 +427,15 @@ function createWindow(status) {
// DEVTOOLS - only for dev purposes - ca333
// mainWindow.webContents.openDevTools()
function pm2Exit() {
const ConnectToPm2 = function() {
function appExit() {
const CloseDaemons = function() {
return new Promise(function(resolve, reject) {
shepherd.log('Closing Main Window...');
shepherd.writeLog('exiting app...');
shepherd.dumpCacheBeforeExit();
shepherd.quitKomodod(1000);
pm2.connect(true, function(err) {
shepherd.log('connecting to pm2...');
shepherd.writeLog('connecting to pm2...');
if (err) {
shepherd.log(err);
}
});
shepherd.quitKomodod(appConfig.cliStopTimeout);
const result = 'Connecting To Pm2: done';
const result = 'Closing daemons: done';
shepherd.log(result);
shepherd.writeLog(result);
@ -443,31 +443,6 @@ function createWindow(status) {
})
}
const KillPm2 = function() {
return new Promise(function(resolve, reject) {
shepherd.log('killing to pm2...');
shepherd.writeLog('killing to pm2...');
pm2.killDaemon(function(err) {
pm2.disconnect();
shepherd.log('killed to pm2...');
shepherd.writeLog('killed to pm2...');
if (err)
throw err;
});
const result = 'Killing Pm2: done';
setTimeout(function() {
shepherd.log(result);
shepherd.writeLog(result);
resolve(result);
}, 2000);
})
}
const HideMainWindow = function() {
return new Promise(function(resolve, reject) {
const result = 'Hiding Main Window: done';
@ -490,7 +465,6 @@ function createWindow(status) {
return new Promise(function(resolve, reject) {
const result = 'Quiting App: done';
KillPm2(); // required for normal app quit in iguana-less mode
app.quit();
shepherd.log(result);
resolve(result);
@ -498,10 +472,7 @@ function createWindow(status) {
}
const closeApp = function() {
ConnectToPm2()
.then(function(result) {
return KillPm2();
})
CloseDaemons()
.then(HideMainWindow)
.then(HideAppClosingWindow)
.then(QuitApp);
@ -509,11 +480,13 @@ function createWindow(status) {
let _appClosingInterval;
if (!Object.keys(shepherd.coindInstanceRegistry).length) {
// shepherd.killRogueProcess('marketmaker');
if (!Object.keys(shepherd.coindInstanceRegistry).length ||
!appConfig.stopNativeDaemonsOnQuit) {
closeApp();
} else {
createAppCloseWindow();
shepherd.quitKomodod(1000);
shepherd.quitKomodod(appConfig.cliStopTimeout);
_appClosingInterval = setInterval(function() {
if (!Object.keys(shepherd.coindInstanceRegistry).length) {
closeApp();
@ -524,7 +497,7 @@ function createWindow(status) {
// if window closed we kill iguana proc
mainWindow.on('closed', function() {
pm2Exit();
appExit();
});
}
}
@ -543,23 +516,28 @@ app.on('window-all-closed', function() {
// Calling event.preventDefault() will prevent the default behaviour, which is terminating the application.
app.on('before-quit', function(event) {
shepherd.log('before-quit');
shepherd.killRogueProcess('iguana'); // kill any rogue iguana core instances
// shepherd.killRogueProcess('marketmaker');
if (!forceQuitApp && mainWindow === null && loadingWindow != null) { // mainWindow not intitialised and loadingWindow not dereferenced
/*if (!forceQuitApp &&
mainWindow === null &&
loadingWindow != null) { // mainWindow not intitialised and loadingWindow not dereferenced
// loading window is still open
shepherd.log('before-quit prevented');
shepherd.writeLog('quit app after loading is done');
closeAppAfterLoading = true;
// obsolete(?)
let code = `$('#loading_status_text').html('Preparing to shutdown the wallet.<br/>Please wait while all daemons are closed...')`;
loadingWindow.webContents.executeJavaScript(code);
event.preventDefault();
}
}*/
});
// Emitted when all windows have been closed and the application will quit.
// Calling event.preventDefault() will prevent the default behaviour, which is terminating the application.
app.on('will-quit', function(event) {
if (!forceQuitApp && mainWindow === null && loadingWindow != null) {
if (!forceQuitApp &&
mainWindow === null &&
loadingWindow != null) {
// loading window is still open
shepherd.log('will-quit while loading window active');
event.preventDefault();
@ -569,18 +547,20 @@ app.on('will-quit', function(event) {
// Emitted when the application is quitting.
// Calling event.preventDefault() will prevent the default behaviour, which is terminating the application.
app.on('quit', function(event) {
if (!forceQuitApp && mainWindow === null && loadingWindow != null) {
if (!forceQuitApp &&
mainWindow === null &&
loadingWindow != null) {
shepherd.log('quit while loading window active');
event.preventDefault();
}
})
app.on('activate', function() {
if (mainWindow === null) {
// createWindow('open');
}
if (mainWindow === null) {}
});
app.commandLine.appendSwitch('ignore-certificate-errors'); // dirty hack
function formatBytes(bytes, decimals) {
if (bytes === 0) {
return '0 Bytes';

26
make-deb.js

@ -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);
});

26
make-rpm.js

@ -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);
});

35
package.json

@ -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"
}
}

4
private/kmdcli.js

@ -116,11 +116,11 @@ function parse_kmdcli_commands(callback) {
* "notaryid" : -1,
* "pubkey" : "000000000000000000000000000000000000000000000000000000000000000000"
* }
*
*
*/
function command(kmd_command, callback) {
if (callback) {
return this.exec(komodocliBin + " " + kmd_command,
parse_kmdcli_commands(callback));
parse_kmdcli_commands(callback));
}
}

120
routes/appConfig.js

@ -1,58 +1,24 @@
const appConfig = {
config: { // default config
edexGuiOnly: true,
iguanaGuiOnly: false,
manualIguanaStart: false,
skipBasiliskNetworkCheck: true,
minNotaries: 8,
host: '127.0.0.1',
agamaPort: 17777,
iguanaCorePort: 7778,
maxDescriptors: {
darwin: 90000,
linux: 1000000,
},
killIguanaOnStart: true,
dev: false,
v2: true,
useBasiliskInstance: true,
debug: false,
cli: {
passthru: true,
default: true,
},
iguanaLessMode: true,
roundValues: false,
experimentalFeatures: false,
dataDir: '',
dex: {
walletUnlockTimeout: 3600,
},
cliStopTimeout: 1000,
failedRPCAttemptsThreshold: 10,
stopNativeDaemonsOnQuit: true,
},
schema: {
edexGuiOnly: {
display: false,
type: 'boolean',
displayName: 'EDEX GUI only',
},
iguanaGuiOnly: {
display: false,
type: 'boolean',
displayName: 'Iguana GUI only',
},
manualIguanaStart: {
display: false,
type: 'boolean',
displayName: 'Manual Iguana Start',
},
skipBasiliskNetworkCheck: {
display: false,
type: 'boolean',
displayName: 'Skip Basilisk Network Check',
},
minNotaries: {
display: false,
type: 'number',
displayName: 'Minimum notaries count',
info: 'Minimum number of notaries to connect to on startup',
},
host: {
display: true,
type: 'string',
@ -65,13 +31,6 @@ const appConfig = {
displayName: 'Agama Port',
info: 'Agama HTTP port. Required to run GUI.',
},
iguanaCorePort: {
display: true,
initDisplay: true,
type: 'number',
displayName: 'Iguana Core Port',
info: 'Default Iguana Core Port. Change it if you have conflicts with other applications.',
},
maxDescriptors: {
display: false,
displayName: 'Max Descriptors per Process',
@ -86,12 +45,6 @@ const appConfig = {
type: 'number',
},
},
killIguanaOnStart: {
display: true,
displayName: 'Kill Iguana Core Processes on Startup',
info: 'Kill any rogue Iguana Core processes during app startup',
type: 'boolean',
},
dev: {
display: true,
initDisplay: true,
@ -99,17 +52,6 @@ const appConfig = {
info: 'Enable developer mode',
type: 'boolean',
},
v2: {
display: false,
type: 'boolean',
},
useBasiliskInstance: {
display: true,
initDisplay: true,
displayName: 'Iguana Core Basilisk Instance',
info: 'Enable dedicated Iguana Core instance to handle all Basilisk network requests',
type: 'boolean',
},
debug: {
display: true,
initDisplay: true,
@ -117,28 +59,6 @@ const appConfig = {
info: 'Enable debug output',
type: 'boolean',
},
cli: {
display: true,
displayName: 'Direct BitcoinRPC passthru interface',
info: 'Enable direct BitcoinRPC passthru interface. It will bypass Iguana Core and send requests directly to Bitcoin JSON server.',
passthru: {
display: true,
displayName: 'Enable Direct Passthru',
type: 'boolean',
},
default: {
display: true,
displayName: 'Enable CLI passthru',
info: 'Enable komodo-cli passthru. This allows you to send CLI compatible commands directly from UI to komodo-cli.',
type: 'boolean',
},
},
iguanaLessMode: {
display: true,
displayName: 'Enable Native Only mode',
info: 'Limited to only Komodo native mode to speed up loading and reduce hardware resources consumption.',
type: 'boolean',
},
roundValues: {
display: true,
displayName: 'Enable amount rounding',
@ -158,13 +78,33 @@ const appConfig = {
info: 'The data directory is the location where Komodo data files are stored, including the wallet data file',
type: 'folder',
},
daemonOutput: {
dex: {
display: false,
initDisplay: false,
displayName: 'Output daemon prints (debug)',
info: 'Output daemon prints to GUI for debug purposes',
displayName: 'dex',
walletUnlockTimeout: {
display: true,
displayName: 'walletUnlockTimeout',
type: 'number',
},
},
cliStopTimeout: {
display: true,
displayName: 'CLI stop timeout',
info: 'Timeout between consequent CLI stop commands',
type: 'number',
},
stopNativeDaemonsOnQuit: {
display: true,
displayName: 'Stop native daemons on app quit',
info: 'If set to false agama will run in detached coin daemon mode',
type: 'boolean',
},
failedRPCAttemptsThreshold: {
display: true,
displayName: 'Failed RPC connect attempts threshold',
info: 'Number of allowed consequent RPC connect failures before the app marks native coin daemon as not running properly',
type: 'number',
},
},
};

962
routes/cache.js

@ -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;

262
routes/electrumjs/electrumServers.js

@ -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;

354
routes/electrumjs/electrumjs.core.js

@ -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;

205
routes/electrumjs/electrumjs.networks.js

@ -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,
};*/

120
routes/electrumjs/electrumjs.txdecoder.js

@ -0,0 +1,120 @@
/*
MIT License
Copyright (c) 2017 Yuki Akiyama, SuperNET
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
var bitcoin = require('bitcoinjs-lib');
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;
}

197
routes/fetchparams.js

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

45
routes/mock.js

@ -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;

98
routes/nativeCoind.js

@ -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;

4
routes/ports.js

@ -1,7 +1,9 @@
const assetChainPorts = {
'komodod': '7771',
'CHIPS': '57776',
'SUPERNET': '11341',
'REVS': '10196',
'MNZ': '14337',
'WLC': '12167',
'PANGEA': '14068',
'DEX': '11890',
@ -9,7 +11,7 @@ const assetChainPorts = {
'BET': '14250',
'CRYPTO': '8516',
'HODL': '14431',
'SHARK': '10114',
'MSHARK': '8846',
'BOTS': '11964',
'MGW': '12386',
'COQUI': '14276',

3466
routes/shepherd.js

File diff suppressed because it is too large

103
routes/shepherd/addCoinShortcuts.js

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

78
routes/shepherd/appInfo.js

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

31
routes/shepherd/auth.js

@ -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;
};

239
routes/shepherd/binsTestUtil.js

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

70
routes/shepherd/binsUtils.js

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

99
routes/shepherd/coindWalletKeys.js

@ -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;
};

31
routes/shepherd/coins.js

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

71
routes/shepherd/coinsList.js

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

57
routes/shepherd/confMaxconnections.js

@ -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;
};

152
routes/shepherd/config.js

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

908
routes/shepherd/daemonControl.js

@ -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;
};

312
routes/shepherd/dashboardUpdate.js

@ -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;
};

119
routes/shepherd/debugLog.js

@ -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;
};

44
routes/shepherd/dex/coind.js

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

1
routes/shepherd/dex/coins.json

File diff suppressed because one or more lines are too long

242
routes/shepherd/dex/mmControl.js

@ -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;
};

39
routes/shepherd/dex/mmRequest.js

@ -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;
};

165
routes/shepherd/downloadBins.js

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

154
routes/shepherd/downloadPatch.js

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

49
routes/shepherd/downloadUtil.js

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

134
routes/shepherd/downloadZcparams.js

@ -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;
};

64
routes/shepherd/electrum/auth.js

@ -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;
};

145
routes/shepherd/electrum/balance.js

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

59
routes/shepherd/electrum/block.js

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

91
routes/shepherd/electrum/coins.js

@ -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;
};

487
routes/shepherd/electrum/createtx.js

@ -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;
};

26
routes/shepherd/electrum/estimate.js

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

37
routes/shepherd/electrum/interest.js

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

178
routes/shepherd/electrum/keys.js

@ -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;
};

195
routes/shepherd/electrum/listunspent.js

@ -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;
};

145
routes/shepherd/electrum/merkle.js

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

143
routes/shepherd/electrum/network.js

@ -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;
};

397
routes/shepherd/electrum/transactions.js

@ -0,0 +1,397 @@
module.exports = (shepherd) => {
shepherd.sortTransactions = (transactions) => {
return transactions.sort((b, a) => {
if (a.height < b.height) {
return -1;
}
if (a.height > b.height) {
return 1;
}
return 0;
});
}
shepherd.get('/electrum/listtransactions', (req, res, next) => {
const network = req.query.network || shepherd.findNetworkObj(req.query.coin);
const ecl = new shepherd.electrumJSCore(shepherd.electrumServers[network].port, shepherd.electrumServers[network].address, shepherd.electrumServers[network].proto); // tcp or tls
shepherd.log('electrum listtransactions ==>', true);
if (!req.query.full) {
ecl.connect();
ecl.blockchainAddressGetHistory(req.query.address)
.then((json) => {
ecl.close();
shepherd.log(json, true);
json = shepherd.sortTransactions(json);
const successObj = {
msg: 'success',
result: json,
};
res.end(JSON.stringify(successObj));
});
} else {
// !expensive call!
// TODO: limit e.g. 1-10, 10-20 etc
const MAX_TX = req.query.maxlength || 10;
ecl.connect();
ecl.blockchainNumblocksSubscribe()
.then((currentHeight) => {
if (currentHeight &&
Number(currentHeight) > 0) {
ecl.blockchainAddressGetHistory(req.query.address)
.then((json) => {
if (json &&
json.length) {
json = shepherd.sortTransactions(json);
json = json.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;
};

71
routes/shepherd/init.js

@ -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;
};

45
routes/shepherd/kickstart.js

@ -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;
};

141
routes/shepherd/log.js

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

82
routes/shepherd/paths.js

@ -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;
};

146
routes/shepherd/pin.js

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

170
routes/shepherd/quitDaemon.js

@ -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;
};

218
routes/shepherd/rpc.js

@ -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;
};

2
version

@ -1,3 +1,3 @@
version=0.2.0.22a
version=0.2.0.25b
type=e-beta
minversion=0.2.0.2

2
version_build

@ -1 +1 @@
0.2.0.22a-beta
0.2.0.25b-beta
Loading…
Cancel
Save