├── .gitignore ├── LICENSE ├── README.md ├── coins ├── 21coin.json ├── 365coin.json ├── 42.json ├── alphacoin.json ├── anoncoin.json ├── applecoin.json ├── arkenstone.json ├── arkhash.json ├── asiccoin.json ├── auroracoin.json ├── battlecoin.json ├── benjamins.json ├── betacoin.json ├── bitcoin.json ├── bitraam.json ├── bitstar.json ├── bluecoin.json ├── bottlecaps.json ├── bunnycoin.json ├── bytecoin.json ├── cachecoin.json ├── casinocoin.json ├── catcoin.json ├── coino.json ├── continuumcoin.json ├── copperbars.json ├── copperlark.json ├── cryptogenicbullion.json ├── cryptographicanomaly.json ├── cryptometh.json ├── darkcoin.json ├── defcoin.json ├── devcoin.json ├── diamondcoin.json ├── digibyte.json ├── dogecoin.json ├── earthcoin.json ├── einsteinium.json ├── elephantcoin.json ├── emark.json ├── emerald.json ├── execoin.json ├── ezcoin.json ├── fastcoin.json ├── fastcoinsha.json ├── feathercoin.json ├── fedoracoin.json ├── fireflycoin.json ├── flappycoin.json ├── florincoin.json ├── fluttercoin.json ├── frankocoin.json ├── freecoin.json ├── freicoin.json ├── galaxycoin.json ├── galleon.json ├── gamecoin.json ├── giarcoin.json ├── globalboost.json ├── globalcoin.json ├── globaldenomination.json ├── globaltoken.json ├── goldpressedlatinum.json ├── grandcoin.json ├── groestlcoin.json ├── guarantcoin.json ├── helixcoin.json ├── hirocoin.json ├── hobonickels.json ├── infinitecoin.conf ├── internetcoin.json ├── ixcoin.json ├── jennycoin.json ├── joulecoin.json ├── junkcoin.json ├── kittehcoin.json ├── klondikecoin.json ├── krugercoin.json ├── kumacoin.json ├── litecoin.json ├── lottocoin.json ├── luckycoin.json ├── maxcoin.json ├── mazacoin.json ├── memecoin.json ├── microcoin.json ├── mintcoin.json ├── monacoin.json ├── muniti.json ├── myriadcoin.json ├── neocoin.json ├── netcoin.json ├── noirbits.json ├── octocoin.json ├── onecoin.json ├── opensourcecoin.json ├── pawncoin.json ├── peercoin.json ├── phoenixcoin.json ├── plncoin.json ├── potcoin.json ├── procoin.json ├── quarkcoin.json ├── radioactivecoin.json ├── reddcoin.json ├── ronpaulcoin.json ├── rubycoin.json ├── saffroncoin.json ├── sayacoin.json ├── sexcoin.json ├── sha1coin.json ├── skeincoin.json ├── spartancoin.json ├── spots.json ├── stablecoin.json ├── starcoin.json ├── stashcoin.json ├── stoopidcoin.json ├── suncoin.json ├── takcoin.json ├── teacoin.json ├── tekcoin.json ├── terracoin.json ├── tigercoin.json ├── ultimatecoin.json ├── ultracoin.json ├── unobtanium.json ├── velocitycoin.json ├── vertcoin.json ├── viacoin.json ├── wearesatoshi.json ├── wecoin.json ├── whitecoin.json ├── xencoin.json ├── yacoin.json ├── ybcoin.json ├── zedcoin.json ├── zetacoin.json └── zzcoin.json ├── config_example.json ├── init.js ├── libs ├── api.js ├── apiBittrex.js ├── apiCoinWarz.js ├── apiCryptsy.js ├── apiMintpal.js ├── apiPoloniex.js ├── cliListener.js ├── logUtil.js ├── mposCompatibility.js ├── paymentProcessor.js ├── poolWorker.js ├── profitSwitch.js ├── shareProcessor.js ├── stats.js ├── website.js └── workerapi.js ├── package.json ├── pool_configs └── litecoin_example.json ├── scripts ├── blocknotify.c └── cli.js └── website ├── index.html ├── key.html ├── pages ├── admin.html ├── api.html ├── getting_started.html ├── home.html ├── mining_key.html ├── stats.html ├── tbs.html └── workers.html └── static ├── admin.js ├── favicon.png ├── logo.svg ├── main.js ├── nvd3.css ├── nvd3.js ├── stats.js └── style.css /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .idea/ 3 | config.json 4 | pool_configs/*.json 5 | !pool_configs/litecoin_example.json 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /coins/21coin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "21coin", 3 | "symbol": "21", 4 | "algorithm": "sha256", │····································· 5 | "peerMagic": "21212121", │····································· 6 | "peerMagicTestnet": "01fefe05" 7 | } 8 | -------------------------------------------------------------------------------- /coins/365coin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "365coin", 3 | "symbol": "365", 4 | "algorithm": "keccak" 5 | } -------------------------------------------------------------------------------- /coins/42.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "42", 3 | "symbol": "42", 4 | "algorithm": "scrypt" 5 | } 6 | -------------------------------------------------------------------------------- /coins/alphacoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Alphacoin", 3 | "symbol": "ALF", 4 | "algorithm": "scrypt", 5 | "peerMagic": "fbc0b6db", 6 | "peerMagicTestnet": "fcc1b7dc" 7 | } 8 | -------------------------------------------------------------------------------- /coins/anoncoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Anoncoin", 3 | "symbol": "ANC", 4 | "algorithm": "scrypt" 5 | } -------------------------------------------------------------------------------- /coins/applecoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Applecoin", 3 | "symbol": "APC", 4 | "algorithm": "scrypt-jane", 5 | "chainStartTime": 1384720832 6 | } -------------------------------------------------------------------------------- /coins/arkenstone.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Arkenstone", 3 | "symbol": "ARS", 4 | "algorithm": "sha256" 5 | } 6 | -------------------------------------------------------------------------------- /coins/arkhash.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Arkhash", 3 | "symbol": "ARK", 4 | "algorithm": "sha256" 5 | } 6 | -------------------------------------------------------------------------------- /coins/asiccoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ASICcoin", 3 | "symbol": "ASC", 4 | "algorithm": "sha256" 5 | } 6 | -------------------------------------------------------------------------------- /coins/auroracoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Auroracoin", 3 | "symbol": "AUR", 4 | "algorithm": "scrypt" 5 | } -------------------------------------------------------------------------------- /coins/battlecoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Battlecoin", 3 | "symbol": "BCX", 4 | "algorithm": "sha256", 5 | "peerMagic": "03e803e4", 6 | "peerMagicTestnet": "cdf2c0ef" 7 | } 8 | -------------------------------------------------------------------------------- /coins/benjamins.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Benjamins", 3 | "symbol": "BEN", 4 | "algorithm": "sha256", 5 | "peerMagic": "de698778", │····································· 6 | "peerMagicTestnet": "0b110907" 7 | } 8 | -------------------------------------------------------------------------------- /coins/betacoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Betacoin", 3 | "symbol": "BET", 4 | "algorithm": "sha256" 5 | } 6 | -------------------------------------------------------------------------------- /coins/bitcoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Bitcoin", 3 | "symbol": "BTC", 4 | "algorithm": "sha256" 5 | } -------------------------------------------------------------------------------- /coins/bitraam.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "BitRaam", 3 | "symbol": "BRM", 4 | "algorithm": "sha256" 5 | } 6 | -------------------------------------------------------------------------------- /coins/bitstar.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bitstar", 3 | "symbol": "bits", 4 | "algorithm": "scrypt", 5 | "peerMagic": "cef1dbfa", 6 | "peerMagicTestnet": "cdf1c0ef" 7 | } 8 | -------------------------------------------------------------------------------- /coins/bluecoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bluecoin", 3 | "symbol": "blu", 4 | "algorithm": "scrypt", 5 | "peerMagic": "fef5abaa", 6 | "peerMagicTestnet": "eaceedcd" 7 | } 8 | -------------------------------------------------------------------------------- /coins/bottlecaps.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Bottlecaps", 3 | "symbol": "CAP", 4 | "algorithm": "scrypt", 5 | "peerMagic": "e4e8e9e5", 6 | "peerMagicTestnet": "cdf2c0ef" 7 | } 8 | -------------------------------------------------------------------------------- /coins/bunnycoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "BunnyCoin", 3 | "symbol": "BUN", 4 | "algorithm": "scrypt", 5 | "peerMagic": "c0c0c0c0", 6 | "peerMagicTestnet": "fcc1b7dc" 7 | } 8 | -------------------------------------------------------------------------------- /coins/bytecoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Bytecoin", 3 | "symbol": "BTE", 4 | "algorithm": "sha256" 5 | } 6 | -------------------------------------------------------------------------------- /coins/cachecoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Cachecoin", 3 | "symbol": "CACH", 4 | "algorithm": "scrypt-jane", 5 | "chainStartTime": 1388949883 6 | } -------------------------------------------------------------------------------- /coins/casinocoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Casinocoin", 3 | "symbol": "CSC", 4 | "algorithm": "scrypt", 5 | "peerMagic": "fac3b6da", 6 | "peerMagicTestnet": "fcc1b7dc" 7 | } 8 | -------------------------------------------------------------------------------- /coins/catcoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Catcoin", 3 | "symbol": "CAT", 4 | "algorithm": "scrypt" 5 | } -------------------------------------------------------------------------------- /coins/coino.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Coino", 3 | "symbol": "COINO", 4 | "algorithm": "scrypt", 5 | "peerMagic": "f1d1a7d8", 6 | "peerMagicTestnet": "fcc1b7dc" 7 | } 8 | -------------------------------------------------------------------------------- /coins/continuumcoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Continuumcoin", 3 | "symbol": "CTM", 4 | "algorithm": "sha256" 5 | } 6 | -------------------------------------------------------------------------------- /coins/copperbars.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Copperbars", 3 | "symbol": "CPR", 4 | "algorithm": "scrypt-jane", 5 | "chainStartTime": 1376184687 6 | } -------------------------------------------------------------------------------- /coins/copperlark.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Copperlark", 3 | "symbol": "CLR", 4 | "algorithm": "keccak", 5 | "normalHashing": true, 6 | "diffShift": 32 7 | } -------------------------------------------------------------------------------- /coins/cryptogenicbullion.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "CryptogenicBullion", 3 | "symbol": "CGB", 4 | "algorithm": "scrypt", 5 | "peerMagic": "e4e8e9e5", 6 | "peerMagicTestnet": "cdf2c0ef" 7 | } 8 | -------------------------------------------------------------------------------- /coins/cryptographicanomaly.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "CryptographicAnomaly", 3 | "symbol": "CGA", 4 | "algorithm": "scrypt", 5 | "peerMagic": "fbc0b6db", 6 | "peerMagicTestnet": "fcc1b7dc" 7 | } 8 | -------------------------------------------------------------------------------- /coins/cryptometh.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Cryptometh", 3 | "symbol": "METH", 4 | "algorithm": "keccak", 5 | 6 | "peerMagic": "2bf2ed4f" , 7 | "peerMagicTestNet": "b28cfda7" 8 | } 9 | -------------------------------------------------------------------------------- /coins/darkcoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Darkcoin", 3 | "symbol": "DRK", 4 | "algorithm": "x11", 5 | "mposDiffMultiplier": 256 6 | } 7 | -------------------------------------------------------------------------------- /coins/defcoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Defcoin", 3 | "symbol": "DEF", 4 | "algorithm": "scrypt" 5 | } 6 | -------------------------------------------------------------------------------- /coins/devcoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Devcoin", 3 | "symbol": "DVC", 4 | "algorithm": "sha256" 5 | } 6 | -------------------------------------------------------------------------------- /coins/diamondcoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Diamondcoin", 3 | "symbol": "DMD", 4 | "algorithm": "scrypt", 5 | "txMessages": true 6 | } -------------------------------------------------------------------------------- /coins/digibyte.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Digibyte", 3 | "symbol": "DGB", 4 | "algorithm": "scrypt", 5 | "peerMagic": "fac3b6da", 6 | "peerMagicTestnet": "fdc8bddd" 7 | } -------------------------------------------------------------------------------- /coins/dogecoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Dogecoin", 3 | "symbol": "DOGE", 4 | "algorithm": "scrypt", 5 | "peerMagic": "c0c0c0c0", 6 | "peerMagicTestnet": "fcc1b7dc" 7 | } 8 | -------------------------------------------------------------------------------- /coins/earthcoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Earthcoin", 3 | "symbol": "EAC", 4 | "algorithm": "scrypt" 5 | } -------------------------------------------------------------------------------- /coins/einsteinium.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Einsteinium", 3 | "symbol": "EMC2", 4 | "algorithm": "scrypt" 5 | } 6 | -------------------------------------------------------------------------------- /coins/elephantcoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Elephantcoin", 3 | "symbol": "ELP", 4 | "algorithm": "scrypt" 5 | } -------------------------------------------------------------------------------- /coins/emark.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eMark", 3 | "symbol": "DEM", 4 | "algorithm": "sha256", 5 | "reward": "POS" 6 | } 7 | -------------------------------------------------------------------------------- /coins/emerald.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Emerald", 3 | "symbol": "EMD", 4 | "algorithm": "scrypt" 5 | } -------------------------------------------------------------------------------- /coins/execoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Execoin", 3 | "symbol": "EXE", 4 | "algorithm": "scrypt-n", 5 | "timeTable": { 6 | "2048": 1390959880, 7 | "4096": 1438295269, 8 | "8192": 1485630658, 9 | "16384": 1532966047, 10 | "32768": 1580301436, 11 | "65536": 1627636825, 12 | "131072": 1674972214, 13 | "262144": 1722307603 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /coins/ezcoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Ezcoin", 3 | "symbol": "EZC", 4 | "algorithm": "scrypt" 5 | } -------------------------------------------------------------------------------- /coins/fastcoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Fastcoin", 3 | "symbol": "FST", 4 | "algorithm": "scrypt", 5 | "peerMagic": "fdc2b5dc" 6 | } 7 | -------------------------------------------------------------------------------- /coins/fastcoinsha.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Fastcoinsha", 3 | "symbol": "FSS", 4 | "algorithm": "sha256" 5 | } 6 | -------------------------------------------------------------------------------- /coins/feathercoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Feathercoin", 3 | "symbol": "FTC", 4 | "algorithm": "scrypt" 5 | } 6 | -------------------------------------------------------------------------------- /coins/fedoracoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "FedoraCoin", 3 | "symbol": "TiPS", 4 | "algorithm": "scrypt" 5 | } 6 | -------------------------------------------------------------------------------- /coins/fireflycoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Fireflycoin", 3 | "symbol": "FFC", 4 | "algorithm": "sha256" 5 | } 6 | -------------------------------------------------------------------------------- /coins/flappycoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Flappycoin", 3 | "symbol": "FLAP", 4 | "algorithm": "scrypt" 5 | } -------------------------------------------------------------------------------- /coins/florincoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Florincoin", 3 | "symbol": "FLO", 4 | "algorithm": "scrypt", 5 | "txMessages": true 6 | } -------------------------------------------------------------------------------- /coins/fluttercoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "FlutterCoin", 3 | "symbol": "FLT", 4 | "algorithm": "scrypt" 5 | } 6 | -------------------------------------------------------------------------------- /coins/frankocoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Frankocoin", 3 | "symbol": "FRK", 4 | "algorithm": "scrypt" 5 | } -------------------------------------------------------------------------------- /coins/freecoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Freecoin", 3 | "symbol": "FEC", 4 | "algorithm": "scrypt-jane", 5 | "chainStartTime": 1375801200, 6 | "nMin": 6, 7 | "nMax": 32 8 | } -------------------------------------------------------------------------------- /coins/freicoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Freicoin", 3 | "symbol": "FRC", 4 | "algorithm": "sha256" 5 | } 6 | -------------------------------------------------------------------------------- /coins/galaxycoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Galaxycoin", 3 | "symbol": "GLX", 4 | "algorithm": "scrypt" 5 | } -------------------------------------------------------------------------------- /coins/galleon.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Galleon", 3 | "symbol": "GLN", 4 | "algorithm": "keccak" 5 | } -------------------------------------------------------------------------------- /coins/gamecoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Gamecoin", 3 | "symbol": "GME", 4 | "algorithm": "scrypt" 5 | } -------------------------------------------------------------------------------- /coins/giarcoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Giarcoin", 3 | "symbol": "GIAR", 4 | "algorithm": "scrypt-n" 5 | } 6 | -------------------------------------------------------------------------------- /coins/globalboost.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "GlobalBoost", 3 | "symbol": "BST", 4 | "algorithm": "scrypt" 5 | } 6 | -------------------------------------------------------------------------------- /coins/globalcoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Globalcoin", 3 | "symbol": "GLC", 4 | "algorithm": "scrypt", 5 | "peerMagic": "fcd9b7dd", 6 | "peerMagicTestnet": "fbc0b8db" 7 | } 8 | -------------------------------------------------------------------------------- /coins/globaldenomination.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "GlobalDenomination", 3 | "symbol": "GDN", 4 | "algorithm": "x11", 5 | "peerMagic": "fec3b9de", 6 | "peerMagicTestnet": "fec4bade" 7 | } 8 | -------------------------------------------------------------------------------- /coins/globaltoken.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "GlobalToken", 3 | "symbol": "GLT", 4 | "algorithm": "sha256", 5 | "peerMagic": "c708d32d", 6 | "peerMagicTestnet": "3a6f375b" 7 | } 8 | -------------------------------------------------------------------------------- /coins/goldpressedlatinum.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "GoldPressedLatinum", 3 | "symbol": "GPL", 4 | "algorithm": "scrypt-jane", 5 | "chainStartTime": 1377557832 6 | } -------------------------------------------------------------------------------- /coins/grandcoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Grandcoin", 3 | "symbol": "GDC", 4 | "algorithm": "scrypt", 5 | "peerMagic": "fdc1a5db", 6 | "txMessages": true 7 | } 8 | -------------------------------------------------------------------------------- /coins/groestlcoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Groestlcoin", 3 | "symbol": "GRS", 4 | "algorithm": "groestl", 5 | "peerMagic": "f9beb4d4", 6 | "peerMagicTestnet": "0b110907" 7 | } 8 | -------------------------------------------------------------------------------- /coins/guarantcoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Guarantcoin", 3 | "symbol": "GTC", 4 | "algorithm": "scrypt-n" 5 | } 6 | -------------------------------------------------------------------------------- /coins/helixcoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Helixcoin", 3 | "symbol": "HXC", 4 | "algorithm": "keccak" 5 | } -------------------------------------------------------------------------------- /coins/hirocoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Hirocoin", 3 | "symbol": "HIRO", 4 | "algorithm": "x11", 5 | "mposDiffMultiplier": 256 6 | } 7 | -------------------------------------------------------------------------------- /coins/hobonickels.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Hobonickels", 3 | "symbol": "HBN", 4 | "algorithm": "scrypt" 5 | } -------------------------------------------------------------------------------- /coins/infinitecoin.conf: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Infinitecoin", 3 | "symbol": "IFC", 4 | "algorithm": "scrypt", 5 | "peerMagic": "fbc0b6db", 6 | "peerMagicTestnet": "fcc1b7dc" 7 | } 8 | -------------------------------------------------------------------------------- /coins/internetcoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Internetcoin", 3 | "symbol": "ITC", 4 | "algorithm": "scrypt-jane", 5 | "chainStartTime": 1388385602 6 | } -------------------------------------------------------------------------------- /coins/ixcoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Ixcoin", 3 | "symbol": "IXC", 4 | "algorithm": "sha256" 5 | } 6 | -------------------------------------------------------------------------------- /coins/jennycoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Jennycoin", 3 | "symbol": "JNY", 4 | "algorithm": "scrypt" 5 | } 6 | -------------------------------------------------------------------------------- /coins/joulecoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Joulecoin", 3 | "symbol": "XJO", 4 | "algorithm": "sha256" 5 | } 6 | -------------------------------------------------------------------------------- /coins/junkcoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Junkcoin", 3 | "symbol": "JKC", 4 | "algorithm": "scrypt" 5 | } -------------------------------------------------------------------------------- /coins/kittehcoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Kittehcoin", 3 | "symbol": "MEOW", 4 | "algorithm": "scrypt" 5 | } -------------------------------------------------------------------------------- /coins/klondikecoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Klondikecoin", 3 | "symbol": "KDC", 4 | "algorithm": "scrypt" 5 | } 6 | -------------------------------------------------------------------------------- /coins/krugercoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Krugercoin", 3 | "symbol": "KGC", 4 | "algorithm": "scrypt" 5 | } -------------------------------------------------------------------------------- /coins/kumacoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Kumacoin", 3 | "symbol": "KUMA", 4 | "algorithm": "quark", 5 | "mposDiffMultiplier": 256 6 | } 7 | -------------------------------------------------------------------------------- /coins/litecoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Litecoin", 3 | "symbol": "LTC", 4 | "algorithm": "scrypt", 5 | "peerMagic": "fbc0b6db", 6 | "peerMagicTestnet": "fdd2c8f1" 7 | } 8 | -------------------------------------------------------------------------------- /coins/lottocoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Lottocoin", 3 | "symbol": "LOT", 4 | "algorithm": "scrypt", 5 | "peerMagic": "a5fdb6c1", 6 | "peerMagicTestnet": "fdc3b6f1" 7 | } -------------------------------------------------------------------------------- /coins/luckycoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Luckycoin", 3 | "symbol": "LKY", 4 | "algorithm": "scrypt" 5 | } -------------------------------------------------------------------------------- /coins/maxcoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Maxcoin", 3 | "symbol": "MAX", 4 | "algorithm": "keccak", 5 | 6 | "peerMagic": "f9bebbd2", 7 | "peerMagicTestNet": "0b11bb07" 8 | } 9 | -------------------------------------------------------------------------------- /coins/mazacoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Mazacoin", 3 | "symbol": "MZC", 4 | "algorithm": "sha256", 5 | "peerMagic": "f8b503df", │····································· 6 | "peerMagicTestnet": "05fea901" 7 | } 8 | -------------------------------------------------------------------------------- /coins/memecoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Memecoin", 3 | "symbol": "MEM", 4 | "algorithm": "scrypt" 5 | } -------------------------------------------------------------------------------- /coins/microcoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Microcoin", 3 | "symbol": "MCR", 4 | "algorithm": "scrypt-jane", 5 | "chainStartTime": 1389028879, 6 | "nMin": 6, 7 | "nMax": 32 8 | } -------------------------------------------------------------------------------- /coins/mintcoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Mintcoin", 3 | "symbol": "MINT", 4 | "algorithm": "scrypt" 5 | } -------------------------------------------------------------------------------- /coins/monacoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Monacoin", 3 | "symbol": "MONA", 4 | "algorithm": "scrypt" 5 | } -------------------------------------------------------------------------------- /coins/muniti.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Muniti", 3 | "symbol": "MUN", 4 | "algorithm": "x11", 5 | "mposDiffMultiplier": 256 6 | } 7 | -------------------------------------------------------------------------------- /coins/myriadcoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Myriadcoin", 3 | "symbol": "MYR", 4 | "algorithm": "scrypt" 5 | } -------------------------------------------------------------------------------- /coins/neocoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Neocoin", 3 | "symbol": "NEC", 4 | "algorithm": "scrypt", 5 | "txMessages": true 6 | } -------------------------------------------------------------------------------- /coins/netcoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Netcoin", 3 | "symbol": "NET", 4 | "algorithm": "scrypt", 5 | "txMessages": true 6 | } -------------------------------------------------------------------------------- /coins/noirbits.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Noirbits", 3 | "symbol": "NRB", 4 | "algorithm": "scrypt" 5 | } -------------------------------------------------------------------------------- /coins/octocoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Octocoin", 3 | "symbol": "888", 4 | "algorithm": "scrypt", 5 | "peerMagic": "fbc0b6db", 6 | "peerMagicTestnet": "fcc1b7dc" 7 | } 8 | -------------------------------------------------------------------------------- /coins/onecoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Onecoin", 3 | "symbol": "ONC", 4 | "algorithm": "scrypt-jane", 5 | "chainStartTime": 1371119462, 6 | "nMin": 6 7 | } -------------------------------------------------------------------------------- /coins/opensourcecoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "OpenSourcecoin", 3 | "symbol": "OSC", 4 | "algorithm": "sha256", 5 | "txMessages" : true 6 | } 7 | -------------------------------------------------------------------------------- /coins/pawncoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pawncoin", 3 | "symbol": "pawn", 4 | "algorithm": "scrypt", 5 | "peerMagic": "fcc1b7dc", 6 | "peerMagicTestnet": "c0c0c0c0" 7 | } 8 | -------------------------------------------------------------------------------- /coins/peercoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Peercoin", 3 | "symbol": "PPC", 4 | "algorithm": "sha256" 5 | } -------------------------------------------------------------------------------- /coins/phoenixcoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "Phoenixcoin", 3 | "symbol" : "PXC", 4 | "algorithm" : "scrypt" 5 | } -------------------------------------------------------------------------------- /coins/plncoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "plncoin", 3 | "symbol": "plnc", 4 | "algorithm": "scrypt", 5 | "peerMagic": "fbc0b6db", 6 | "peerMagicTestnet": "fcc1b7dc" 7 | } 8 | -------------------------------------------------------------------------------- /coins/potcoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Potcoin", 3 | "symbol": "POT", 4 | "algorithm": "scrypt" 5 | } 6 | -------------------------------------------------------------------------------- /coins/procoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Procoin", 3 | "symbol": "PCN", 4 | "algorithm": "scrypt" 5 | } 6 | -------------------------------------------------------------------------------- /coins/quarkcoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Quarkcoin", 3 | "symbol": "QRK", 4 | "algorithm": "quark", 5 | "mposDiffMultiplier": 256 6 | } 7 | -------------------------------------------------------------------------------- /coins/radioactivecoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Radioactivecoin", 3 | "symbol": "RAD", 4 | "algorithm": "scrypt-jane", 5 | "chainStartTime": 1389196388 6 | } -------------------------------------------------------------------------------- /coins/reddcoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Reddcoin", 3 | "symbol": "REDD", 4 | "algorithm": "scrypt" 5 | } -------------------------------------------------------------------------------- /coins/ronpaulcoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "RonPaulCoin", 3 | "symbol": "RPC", 4 | "algorithm": "scrypt" 5 | } 6 | -------------------------------------------------------------------------------- /coins/rubycoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Rubycoin", 3 | "symbol": "RUBY", 4 | "algorithm": "scrypt" 5 | } 6 | -------------------------------------------------------------------------------- /coins/saffroncoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "saffroncoin", 3 | "symbol": "SFR", 4 | "algorithm": "scrypt", 5 | "peerMagic": "cf0567ea", 6 | "peerMagicTestnet": "01f555a4" 7 | } 8 | -------------------------------------------------------------------------------- /coins/sayacoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Sayacoin", 3 | "symbol": "SYC", 4 | "algorithm": "sha256" 5 | } -------------------------------------------------------------------------------- /coins/sexcoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Sexcoin", 3 | "symbol": "SXC", 4 | "algorithm": "scrypt" 5 | } -------------------------------------------------------------------------------- /coins/sha1coin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Sha1coin", 3 | "symbol": "SHA", 4 | "algorithm": "sha1coin" 5 | } 6 | -------------------------------------------------------------------------------- /coins/skeincoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Skeincoin", 3 | "symbol": "SKC", 4 | "algorithm": "skein" 5 | } -------------------------------------------------------------------------------- /coins/spartancoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Spartancoin", 3 | "symbol": "SPN", 4 | "algorithm": "scrypt" 5 | } 6 | -------------------------------------------------------------------------------- /coins/spots.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Spots", 3 | "symbol": "SPT", 4 | "algorithm": "scrypt" 5 | } -------------------------------------------------------------------------------- /coins/stablecoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Stablecoin", 3 | "symbol": "SBC", 4 | "algorithm": "scrypt" 5 | } -------------------------------------------------------------------------------- /coins/starcoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Starcoin", 3 | "symbol": "STR", 4 | "algorithm": "scrypt", 5 | "peerMagic": "e4e8effd" 6 | } 7 | -------------------------------------------------------------------------------- /coins/stashcoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Stashcoin", 3 | "symbol": "STA", 4 | "algorithm": "sha256" 5 | } 6 | -------------------------------------------------------------------------------- /coins/stoopidcoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Stoopidcoin", 3 | "symbol": "STP", 4 | "algorithm": "scrypt" 5 | } 6 | -------------------------------------------------------------------------------- /coins/suncoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Suncoin", 3 | "symbol": "SUN", 4 | "algorithm": "scrypt", 5 | "peerMagic":"fcd9b7dd", 6 | "peerMagicTestnet":"fbc0b8db" 7 | } 8 | -------------------------------------------------------------------------------- /coins/takcoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Takcoin", 3 | "symbol": "TAK", 4 | "algorithm": "sha256" 5 | } 6 | -------------------------------------------------------------------------------- /coins/teacoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Teacoin", 3 | "symbol": "TEA", 4 | "algorithm": "sha256" 5 | } 6 | -------------------------------------------------------------------------------- /coins/tekcoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Tekcoin", 3 | "symbol": "TEK", 4 | "algorithm": "sha256", 5 | "txMessages": "true" 6 | } 7 | -------------------------------------------------------------------------------- /coins/terracoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Terracoin", 3 | "symbol": "TRC", 4 | "algorithm": "sha256" 5 | } 6 | -------------------------------------------------------------------------------- /coins/tigercoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Tigercoin", 3 | "symbol": "TGC", 4 | "algorithm": "sha256" 5 | } 6 | -------------------------------------------------------------------------------- /coins/ultimatecoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Ultimatecoin", 3 | "symbol": "ULT", 4 | "algorithm": "scrypt", 5 | "peerMagic": "f9f7c0e8" 6 | } 7 | -------------------------------------------------------------------------------- /coins/ultracoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Ultracoin", 3 | "symbol": "UTC", 4 | "algorithm": "scrypt-jane", 5 | "chainStartTime": 1388361600 6 | } -------------------------------------------------------------------------------- /coins/unobtanium.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Unobtanium", 3 | "symbol": "UNO", 4 | "algorithm": "sha256" 5 | } 6 | -------------------------------------------------------------------------------- /coins/velocitycoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Velocitycoin", 3 | "symbol": "VEL", 4 | "algorithm": "scrypt-jane", 5 | "chainStartTime": 1387769316 6 | } -------------------------------------------------------------------------------- /coins/vertcoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Vertcoin", 3 | "symbol": "VTC", 4 | "algorithm": "scrypt-n", 5 | "peerMagic": "fabfb5da", 6 | "peerMagicTestnet": "76657274" 7 | } -------------------------------------------------------------------------------- /coins/viacoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Viacoin", 3 | "symbol": "VIA", 4 | "algorithm": "scrypt", 5 | "peerMagic": "0f68c6cb", 6 | "peerMagicTestnet": "a9c5ef92" 7 | } 8 | -------------------------------------------------------------------------------- /coins/wearesatoshi.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "WeAreSatoshi", 3 | "symbol": "WAS", 4 | "algorithm": "sha256" 5 | } 6 | -------------------------------------------------------------------------------- /coins/wecoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Wecoin", 3 | "symbol": "WEC", 4 | "algorithm": "max" 5 | } -------------------------------------------------------------------------------- /coins/whitecoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Whitecoin", 3 | "symbol": "WC", 4 | "algorithm": "scrypt" 5 | } 6 | -------------------------------------------------------------------------------- /coins/xencoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Xencoin", 3 | "symbol": "XNC", 4 | "algorithm": "scrypt" 5 | } -------------------------------------------------------------------------------- /coins/yacoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Yacoin", 3 | "symbol": "YAC", 4 | "algorithm": "scrypt-jane", 5 | "chainStartTime": 1367991200 6 | } -------------------------------------------------------------------------------- /coins/ybcoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "YBcoin", 3 | "symbol": "YBC", 4 | "algorithm": "scrypt-jane", 5 | "chainStartTime": 1372386273 6 | } -------------------------------------------------------------------------------- /coins/zedcoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zedcoin", 3 | "symbol": "zed", 4 | "algorithm": "scrypt", 5 | "peerMagic": "c0dbf1fd", 6 | "peerMagicTestnet": "fdc2b6f1" 7 | } 8 | -------------------------------------------------------------------------------- /coins/zetacoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Zetacoin", 3 | "symbol": "ZTC", 4 | "algorithm": "sha256" 5 | } 6 | -------------------------------------------------------------------------------- /coins/zzcoin.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ZZcoin", 3 | "symbol": "ZZC", 4 | "algorithm": "scrypt-jane", 5 | "chainStartTime": 1375817223, 6 | "nMin": 12 7 | } -------------------------------------------------------------------------------- /config_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "logLevel": "debug", 3 | "logColors": true, 4 | 5 | "cliPort": 17117, 6 | 7 | "clustering": { 8 | "enabled": true, 9 | "forks": "auto" 10 | }, 11 | 12 | "defaultPoolConfigs": { 13 | "blockRefreshInterval": 1000, 14 | "jobRebroadcastTimeout": 55, 15 | "connectionTimeout": 600, 16 | "emitInvalidBlockHashes": false, 17 | "validateWorkerUsername": true, 18 | "tcpProxyProtocol": false, 19 | "banning": { 20 | "enabled": true, 21 | "time": 600, 22 | "invalidPercent": 50, 23 | "checkThreshold": 500, 24 | "purgeInterval": 300 25 | }, 26 | "redis": { 27 | "host": "127.0.0.1", 28 | "port": 6379 29 | } 30 | }, 31 | 32 | "website": { 33 | "enabled": true, 34 | "host": "0.0.0.0", 35 | "port": 80, 36 | "stratumHost": "cryppit.com", 37 | "stats": { 38 | "updateInterval": 60, 39 | "historicalRetention": 43200, 40 | "hashrateWindow": 300 41 | }, 42 | "adminCenter": { 43 | "enabled": false, 44 | "password": "password" 45 | } 46 | }, 47 | 48 | "redis": { 49 | "host": "127.0.0.1", 50 | "port": 6379 51 | }, 52 | 53 | "switching": { 54 | "switch1": { 55 | "enabled": false, 56 | "algorithm": "sha256", 57 | "ports": { 58 | "3333": { 59 | "diff": 10, 60 | "varDiff": { 61 | "minDiff": 16, 62 | "maxDiff": 512, 63 | "targetTime": 15, 64 | "retargetTime": 90, 65 | "variancePercent": 30 66 | } 67 | } 68 | } 69 | }, 70 | "switch2": { 71 | "enabled": false, 72 | "algorithm": "scrypt", 73 | "ports": { 74 | "4444": { 75 | "diff": 10, 76 | "varDiff": { 77 | "minDiff": 16, 78 | "maxDiff": 512, 79 | "targetTime": 15, 80 | "retargetTime": 90, 81 | "variancePercent": 30 82 | } 83 | } 84 | } 85 | }, 86 | "switch3": { 87 | "enabled": false, 88 | "algorithm": "x11", 89 | "ports": { 90 | "5555": { 91 | "diff": 0.001, 92 | "varDiff": { 93 | "minDiff": 0.001, 94 | "maxDiff": 1, 95 | "targetTime": 15, 96 | "retargetTime": 60, 97 | "variancePercent": 30 98 | } 99 | } 100 | } 101 | } 102 | }, 103 | 104 | "profitSwitch": { 105 | "enabled": false, 106 | "updateInterval": 600, 107 | "depth": 0.90, 108 | "usePoloniex": true, 109 | "useCryptsy": true, 110 | "useMintpal": true, 111 | "useBittrex": true 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /init.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var path = require('path'); 3 | var os = require('os'); 4 | var cluster = require('cluster'); 5 | 6 | var async = require('async'); 7 | var extend = require('extend'); 8 | 9 | var PoolLogger = require('./libs/logUtil.js'); 10 | var CliListener = require('./libs/cliListener.js'); 11 | var PoolWorker = require('./libs/poolWorker.js'); 12 | var PaymentProcessor = require('./libs/paymentProcessor.js'); 13 | var Website = require('./libs/website.js'); 14 | var ProfitSwitch = require('./libs/profitSwitch.js'); 15 | 16 | var algos = require('stratum-pool/lib/algoProperties.js'); 17 | 18 | JSON.minify = JSON.minify || require("node-json-minify"); 19 | 20 | if (!fs.existsSync('config.json')){ 21 | console.log('config.json file does not exist. Read the installation/setup instructions.'); 22 | return; 23 | } 24 | 25 | var portalConfig = JSON.parse(JSON.minify(fs.readFileSync("config.json", {encoding: 'utf8'}))); 26 | var poolConfigs; 27 | 28 | 29 | var logger = new PoolLogger({ 30 | logLevel: portalConfig.logLevel, 31 | logColors: portalConfig.logColors 32 | }); 33 | 34 | 35 | 36 | 37 | try { 38 | require('newrelic'); 39 | if (cluster.isMaster) 40 | logger.debug('NewRelic', 'Monitor', 'New Relic initiated'); 41 | } catch(e) {} 42 | 43 | 44 | //Try to give process ability to handle 100k concurrent connections 45 | try{ 46 | var posix = require('posix'); 47 | try { 48 | posix.setrlimit('nofile', { soft: 100000, hard: 100000 }); 49 | } 50 | catch(e){ 51 | if (cluster.isMaster) 52 | logger.warning('POSIX', 'Connection Limit', '(Safe to ignore) Must be ran as root to increase resource limits'); 53 | } 54 | finally { 55 | // Find out which user used sudo through the environment variable 56 | var uid = parseInt(process.env.SUDO_UID); 57 | // Set our server's uid to that user 58 | if (uid) { 59 | process.setuid(uid); 60 | logger.debug('POSIX', 'Connection Limit', 'Raised to 100K concurrent connections, now running as non-root user: ' + process.getuid()); 61 | } 62 | } 63 | } 64 | catch(e){ 65 | if (cluster.isMaster) 66 | logger.debug('POSIX', 'Connection Limit', '(Safe to ignore) POSIX module not installed and resource (connection) limit was not raised'); 67 | } 68 | 69 | 70 | if (cluster.isWorker){ 71 | 72 | switch(process.env.workerType){ 73 | case 'pool': 74 | new PoolWorker(logger); 75 | break; 76 | case 'paymentProcessor': 77 | new PaymentProcessor(logger); 78 | break; 79 | case 'website': 80 | new Website(logger); 81 | break; 82 | case 'profitSwitch': 83 | new ProfitSwitch(logger); 84 | break; 85 | } 86 | 87 | return; 88 | } 89 | 90 | 91 | //Read all pool configs from pool_configs and join them with their coin profile 92 | var buildPoolConfigs = function(){ 93 | var configs = {}; 94 | var configDir = 'pool_configs/'; 95 | 96 | var poolConfigFiles = []; 97 | 98 | 99 | /* Get filenames of pool config json files that are enabled */ 100 | fs.readdirSync(configDir).forEach(function(file){ 101 | if (!fs.existsSync(configDir + file) || path.extname(configDir + file) !== '.json') return; 102 | var poolOptions = JSON.parse(JSON.minify(fs.readFileSync(configDir + file, {encoding: 'utf8'}))); 103 | if (!poolOptions.enabled) return; 104 | poolOptions.fileName = file; 105 | poolConfigFiles.push(poolOptions); 106 | }); 107 | 108 | 109 | /* Ensure no pool uses any of the same ports as another pool */ 110 | for (var i = 0; i < poolConfigFiles.length; i++){ 111 | var ports = Object.keys(poolConfigFiles[i].ports); 112 | for (var f = 0; f < poolConfigFiles.length; f++){ 113 | if (f === i) continue; 114 | var portsF = Object.keys(poolConfigFiles[f].ports); 115 | for (var g = 0; g < portsF.length; g++){ 116 | if (ports.indexOf(portsF[g]) !== -1){ 117 | logger.error('Master', poolConfigFiles[f].fileName, 'Has same configured port of ' + portsF[g] + ' as ' + poolConfigFiles[i].fileName); 118 | process.exit(1); 119 | return; 120 | } 121 | } 122 | 123 | if (poolConfigFiles[f].coin === poolConfigFiles[i].coin){ 124 | logger.error('Master', poolConfigFiles[f].fileName, 'Pool has same configured coin file coins/' + poolConfigFiles[f].coin + ' as ' + poolConfigFiles[i].fileName + ' pool'); 125 | process.exit(1); 126 | return; 127 | } 128 | 129 | } 130 | } 131 | 132 | 133 | poolConfigFiles.forEach(function(poolOptions){ 134 | 135 | poolOptions.coinFileName = poolOptions.coin; 136 | 137 | var coinFilePath = 'coins/' + poolOptions.coinFileName; 138 | if (!fs.existsSync(coinFilePath)){ 139 | logger.error('Master', poolOptions.coinFileName, 'could not find file: ' + coinFilePath); 140 | return; 141 | } 142 | 143 | var coinProfile = JSON.parse(JSON.minify(fs.readFileSync(coinFilePath, {encoding: 'utf8'}))); 144 | poolOptions.coin = coinProfile; 145 | poolOptions.coin.name = poolOptions.coin.name.toLowerCase(); 146 | 147 | if (poolOptions.coin.name in configs){ 148 | 149 | logger.error('Master', poolOptions.fileName, 'coins/' + poolOptions.coinFileName 150 | + ' has same configured coin name ' + poolOptions.coin.name + ' as coins/' 151 | + configs[poolOptions.coin.name].coinFileName + ' used by pool config ' 152 | + configs[poolOptions.coin.name].fileName); 153 | 154 | process.exit(1); 155 | return; 156 | } 157 | 158 | for (var option in portalConfig.defaultPoolConfigs){ 159 | if (!(option in poolOptions)){ 160 | var toCloneOption = portalConfig.defaultPoolConfigs[option]; 161 | var clonedOption = {}; 162 | if (toCloneOption.constructor === Object) 163 | extend(true, clonedOption, toCloneOption); 164 | else 165 | clonedOption = toCloneOption; 166 | poolOptions[option] = clonedOption; 167 | } 168 | } 169 | 170 | 171 | configs[poolOptions.coin.name] = poolOptions; 172 | 173 | if (!(coinProfile.algorithm in algos)){ 174 | logger.error('Master', coinProfile.name, 'Cannot run a pool for unsupported algorithm "' + coinProfile.algorithm + '"'); 175 | delete configs[poolOptions.coin.name]; 176 | } 177 | 178 | }); 179 | return configs; 180 | }; 181 | 182 | 183 | 184 | var spawnPoolWorkers = function(){ 185 | 186 | Object.keys(poolConfigs).forEach(function(coin){ 187 | var p = poolConfigs[coin]; 188 | 189 | if (!Array.isArray(p.daemons) || p.daemons.length < 1){ 190 | logger.error('Master', coin, 'No daemons configured so a pool cannot be started for this coin.'); 191 | delete poolConfigs[coin]; 192 | } 193 | }); 194 | 195 | if (Object.keys(poolConfigs).length === 0){ 196 | logger.warning('Master', 'PoolSpawner', 'No pool configs exists or are enabled in pool_configs folder. No pools spawned.'); 197 | return; 198 | } 199 | 200 | 201 | var serializedConfigs = JSON.stringify(poolConfigs); 202 | 203 | var numForks = (function(){ 204 | if (!portalConfig.clustering || !portalConfig.clustering.enabled) 205 | return 1; 206 | if (portalConfig.clustering.forks === 'auto') 207 | return os.cpus().length; 208 | if (!portalConfig.clustering.forks || isNaN(portalConfig.clustering.forks)) 209 | return 1; 210 | return portalConfig.clustering.forks; 211 | })(); 212 | 213 | var poolWorkers = {}; 214 | 215 | var createPoolWorker = function(forkId){ 216 | var worker = cluster.fork({ 217 | workerType: 'pool', 218 | forkId: forkId, 219 | pools: serializedConfigs, 220 | portalConfig: JSON.stringify(portalConfig) 221 | }); 222 | worker.forkId = forkId; 223 | worker.type = 'pool'; 224 | poolWorkers[forkId] = worker; 225 | worker.on('exit', function(code, signal){ 226 | logger.error('Master', 'PoolSpawner', 'Fork ' + forkId + ' died, spawning replacement worker...'); 227 | setTimeout(function(){ 228 | createPoolWorker(forkId); 229 | }, 2000); 230 | }).on('message', function(msg){ 231 | switch(msg.type){ 232 | case 'banIP': 233 | Object.keys(cluster.workers).forEach(function(id) { 234 | if (cluster.workers[id].type === 'pool'){ 235 | cluster.workers[id].send({type: 'banIP', ip: msg.ip}); 236 | } 237 | }); 238 | break; 239 | } 240 | }); 241 | }; 242 | 243 | var i = 0; 244 | var spawnInterval = setInterval(function(){ 245 | createPoolWorker(i); 246 | i++; 247 | if (i === numForks){ 248 | clearInterval(spawnInterval); 249 | logger.debug('Master', 'PoolSpawner', 'Spawned ' + Object.keys(poolConfigs).length + ' pool(s) on ' + numForks + ' thread(s)'); 250 | } 251 | }, 250); 252 | 253 | }; 254 | 255 | 256 | var startCliListener = function(){ 257 | 258 | var cliPort = portalConfig.cliPort; 259 | 260 | var listener = new CliListener(cliPort); 261 | listener.on('log', function(text){ 262 | logger.debug('Master', 'CLI', text); 263 | }).on('command', function(command, params, options, reply){ 264 | 265 | switch(command){ 266 | case 'blocknotify': 267 | Object.keys(cluster.workers).forEach(function(id) { 268 | cluster.workers[id].send({type: 'blocknotify', coin: params[0], hash: params[1]}); 269 | }); 270 | reply('Pool workers notified'); 271 | break; 272 | case 'coinswitch': 273 | processCoinSwitchCommand(params, options, reply); 274 | break; 275 | case 'reloadpool': 276 | Object.keys(cluster.workers).forEach(function(id) { 277 | cluster.workers[id].send({type: 'reloadpool', coin: params[0] }); 278 | }); 279 | reply('reloaded pool ' + params[0]); 280 | break; 281 | default: 282 | reply('unrecognized command "' + command + '"'); 283 | break; 284 | } 285 | }).start(); 286 | }; 287 | 288 | 289 | var processCoinSwitchCommand = function(params, options, reply){ 290 | 291 | var logSystem = 'CLI'; 292 | var logComponent = 'coinswitch'; 293 | 294 | var replyError = function(msg){ 295 | reply(msg); 296 | logger.error(logSystem, logComponent, msg); 297 | }; 298 | 299 | if (!params[0]) { 300 | replyError('Coin name required'); 301 | return; 302 | } 303 | 304 | if (!params[1] && !options.algorithm){ 305 | replyError('If switch key is not provided then algorithm options must be specified'); 306 | return; 307 | } 308 | else if (params[1] && !portalConfig.switching[params[1]]){ 309 | replyError('Switch key not recognized: ' + params[1]); 310 | return; 311 | } 312 | else if (options.algorithm && !Object.keys(portalConfig.switching).filter(function(s){ 313 | return portalConfig.switching[s].algorithm === options.algorithm; 314 | })[0]){ 315 | replyError('No switching options contain the algorithm ' + options.algorithm); 316 | return; 317 | } 318 | 319 | var messageCoin = params[0].toLowerCase(); 320 | var newCoin = Object.keys(poolConfigs).filter(function(p){ 321 | return p.toLowerCase() === messageCoin; 322 | })[0]; 323 | 324 | if (!newCoin){ 325 | replyError('Switch message to coin that is not recognized: ' + messageCoin); 326 | return; 327 | } 328 | 329 | 330 | var switchNames = []; 331 | 332 | if (params[1]) { 333 | switchNames.push(params[1]); 334 | } 335 | else{ 336 | for (var name in portalConfig.switching){ 337 | if (portalConfig.switching[name].enabled && portalConfig.switching[name].algorithm === options.algorithm) 338 | switchNames.push(name); 339 | } 340 | } 341 | 342 | switchNames.forEach(function(name){ 343 | if (poolConfigs[newCoin].coin.algorithm !== portalConfig.switching[name].algorithm){ 344 | replyError('Cannot switch a ' 345 | + portalConfig.switching[name].algorithm 346 | + ' algo pool to coin ' + newCoin + ' with ' + poolConfigs[newCoin].coin.algorithm + ' algo'); 347 | return; 348 | } 349 | 350 | Object.keys(cluster.workers).forEach(function (id) { 351 | cluster.workers[id].send({type: 'coinswitch', coin: newCoin, switchName: name }); 352 | }); 353 | }); 354 | 355 | reply('Switch message sent to pool workers'); 356 | 357 | }; 358 | 359 | 360 | 361 | var startPaymentProcessor = function(){ 362 | 363 | var enabledForAny = false; 364 | for (var pool in poolConfigs){ 365 | var p = poolConfigs[pool]; 366 | var enabled = p.enabled && p.paymentProcessing && p.paymentProcessing.enabled; 367 | if (enabled){ 368 | enabledForAny = true; 369 | break; 370 | } 371 | } 372 | 373 | if (!enabledForAny) 374 | return; 375 | 376 | var worker = cluster.fork({ 377 | workerType: 'paymentProcessor', 378 | pools: JSON.stringify(poolConfigs) 379 | }); 380 | worker.on('exit', function(code, signal){ 381 | logger.error('Master', 'Payment Processor', 'Payment processor died, spawning replacement...'); 382 | setTimeout(function(){ 383 | startPaymentProcessor(poolConfigs); 384 | }, 2000); 385 | }); 386 | }; 387 | 388 | 389 | var startWebsite = function(){ 390 | 391 | if (!portalConfig.website.enabled) return; 392 | 393 | var worker = cluster.fork({ 394 | workerType: 'website', 395 | pools: JSON.stringify(poolConfigs), 396 | portalConfig: JSON.stringify(portalConfig) 397 | }); 398 | worker.on('exit', function(code, signal){ 399 | logger.error('Master', 'Website', 'Website process died, spawning replacement...'); 400 | setTimeout(function(){ 401 | startWebsite(portalConfig, poolConfigs); 402 | }, 2000); 403 | }); 404 | }; 405 | 406 | 407 | var startProfitSwitch = function(){ 408 | 409 | if (!portalConfig.profitSwitch || !portalConfig.profitSwitch.enabled){ 410 | //logger.error('Master', 'Profit', 'Profit auto switching disabled'); 411 | return; 412 | } 413 | 414 | var worker = cluster.fork({ 415 | workerType: 'profitSwitch', 416 | pools: JSON.stringify(poolConfigs), 417 | portalConfig: JSON.stringify(portalConfig) 418 | }); 419 | worker.on('exit', function(code, signal){ 420 | logger.error('Master', 'Profit', 'Profit switching process died, spawning replacement...'); 421 | setTimeout(function(){ 422 | startWebsite(portalConfig, poolConfigs); 423 | }, 2000); 424 | }); 425 | }; 426 | 427 | 428 | 429 | (function init(){ 430 | 431 | poolConfigs = buildPoolConfigs(); 432 | 433 | spawnPoolWorkers(); 434 | 435 | startPaymentProcessor(); 436 | 437 | startWebsite(); 438 | 439 | startProfitSwitch(); 440 | 441 | startCliListener(); 442 | 443 | })(); 444 | -------------------------------------------------------------------------------- /libs/api.js: -------------------------------------------------------------------------------- 1 | var redis = require('redis'); 2 | var async = require('async'); 3 | 4 | var stats = require('./stats.js'); 5 | 6 | module.exports = function(logger, portalConfig, poolConfigs){ 7 | 8 | 9 | var _this = this; 10 | 11 | var portalStats = this.stats = new stats(logger, portalConfig, poolConfigs); 12 | 13 | this.liveStatConnections = {}; 14 | 15 | this.handleApiRequest = function(req, res, next){ 16 | switch(req.params.method){ 17 | case 'stats': 18 | res.writeHead(200, { 'Content-Type': 'application/json' }); 19 | res.end(portalStats.statsString); 20 | return; 21 | case 'pool_stats': 22 | res.writeHead(200, { 'Content-Type': 'application/json' }); 23 | res.end(JSON.stringify(portalStats.statPoolHistory)); 24 | return; 25 | case 'live_stats': 26 | res.writeHead(200, { 27 | 'Content-Type': 'text/event-stream', 28 | 'Cache-Control': 'no-cache', 29 | 'Connection': 'keep-alive' 30 | }); 31 | res.write('\n'); 32 | var uid = Math.random().toString(); 33 | _this.liveStatConnections[uid] = res; 34 | req.on("close", function() { 35 | delete _this.liveStatConnections[uid]; 36 | }); 37 | 38 | return; 39 | default: 40 | next(); 41 | } 42 | }; 43 | 44 | 45 | this.handleAdminApiRequest = function(req, res, next){ 46 | switch(req.params.method){ 47 | case 'pools': { 48 | res.end(JSON.stringify({result: poolConfigs})); 49 | return; 50 | } 51 | default: 52 | next(); 53 | } 54 | }; 55 | 56 | }; 57 | -------------------------------------------------------------------------------- /libs/apiBittrex.js: -------------------------------------------------------------------------------- 1 | var request = require('request'); 2 | var nonce = require('nonce'); 3 | 4 | module.exports = function() { 5 | 'use strict'; 6 | 7 | // Module dependencies 8 | 9 | // Constants 10 | var version = '0.1.0', 11 | PUBLIC_API_URL = 'https://bittrex.com/api/v1/public', 12 | PRIVATE_API_URL = 'https://bittrex.com/api/v1/market', 13 | USER_AGENT = 'nomp/node-open-mining-portal' 14 | 15 | // Constructor 16 | function Bittrex(key, secret){ 17 | // Generate headers signed by this user's key and secret. 18 | // The secret is encapsulated and never exposed 19 | this._getPrivateHeaders = function(parameters){ 20 | var paramString, signature; 21 | 22 | if (!key || !secret){ 23 | throw 'Bittrex: Error. API key and secret required'; 24 | } 25 | 26 | // Sort parameters alphabetically and convert to `arg1=foo&arg2=bar` 27 | paramString = Object.keys(parameters).sort().map(function(param){ 28 | return encodeURIComponent(param) + '=' + encodeURIComponent(parameters[param]); 29 | }).join('&'); 30 | 31 | signature = crypto.createHmac('sha512', secret).update(paramString).digest('hex'); 32 | 33 | return { 34 | Key: key, 35 | Sign: signature 36 | }; 37 | }; 38 | } 39 | 40 | // If a site uses non-trusted SSL certificates, set this value to false 41 | Bittrex.STRICT_SSL = true; 42 | 43 | // Helper methods 44 | function joinCurrencies(currencyA, currencyB){ 45 | return currencyA + '-' + currencyB; 46 | } 47 | 48 | // Prototype 49 | Bittrex.prototype = { 50 | constructor: Bittrex, 51 | 52 | // Make an API request 53 | _request: function(options, callback){ 54 | if (!('headers' in options)){ 55 | options.headers = {}; 56 | } 57 | 58 | options.headers['User-Agent'] = USER_AGENT; 59 | options.json = true; 60 | options.strictSSL = Bittrex.STRICT_SSL; 61 | 62 | request(options, function(err, response, body) { 63 | callback(err, body); 64 | }); 65 | 66 | return this; 67 | }, 68 | 69 | // Make a public API request 70 | _public: function(parameters, callback){ 71 | var options = { 72 | method: 'GET', 73 | url: PUBLIC_API_URL, 74 | qs: parameters 75 | }; 76 | 77 | return this._request(options, callback); 78 | }, 79 | 80 | // Make a private API request 81 | _private: function(parameters, callback){ 82 | var options; 83 | 84 | parameters.nonce = nonce(); 85 | options = { 86 | method: 'POST', 87 | url: PRIVATE_API_URL, 88 | form: parameters, 89 | headers: this._getPrivateHeaders(parameters) 90 | }; 91 | 92 | return this._request(options, callback); 93 | }, 94 | 95 | 96 | ///// 97 | 98 | 99 | // PUBLIC METHODS 100 | 101 | getTicker: function(callback){ 102 | var options = { 103 | method: 'GET', 104 | url: PUBLIC_API_URL + '/getmarketsummaries', 105 | qs: null 106 | }; 107 | 108 | return this._request(options, callback); 109 | }, 110 | 111 | // getBuyOrderBook: function(currencyA, currencyB, callback){ 112 | // var options = { 113 | // method: 'GET', 114 | // url: PUBLIC_API_URL + '/orders/' + currencyB + '/' + currencyA + '/BUY', 115 | // qs: null 116 | // }; 117 | 118 | // return this._request(options, callback); 119 | // }, 120 | 121 | getOrderBook: function(currencyA, currencyB, callback){ 122 | var parameters = { 123 | market: joinCurrencies(currencyA, currencyB), 124 | type: 'buy', 125 | depth: '50' 126 | } 127 | var options = { 128 | method: 'GET', 129 | url: PUBLIC_API_URL + '/getorderbook', 130 | qs: parameters 131 | } 132 | 133 | return this._request(options, callback); 134 | }, 135 | 136 | getTradeHistory: function(currencyA, currencyB, callback){ 137 | var parameters = { 138 | command: 'returnTradeHistory', 139 | currencyPair: joinCurrencies(currencyA, currencyB) 140 | }; 141 | 142 | return this._public(parameters, callback); 143 | }, 144 | 145 | 146 | ///// 147 | 148 | 149 | // PRIVATE METHODS 150 | 151 | myBalances: function(callback){ 152 | var parameters = { 153 | command: 'returnBalances' 154 | }; 155 | 156 | return this._private(parameters, callback); 157 | }, 158 | 159 | myOpenOrders: function(currencyA, currencyB, callback){ 160 | var parameters = { 161 | command: 'returnOpenOrders', 162 | currencyPair: joinCurrencies(currencyA, currencyB) 163 | }; 164 | 165 | return this._private(parameters, callback); 166 | }, 167 | 168 | myTradeHistory: function(currencyA, currencyB, callback){ 169 | var parameters = { 170 | command: 'returnTradeHistory', 171 | currencyPair: joinCurrencies(currencyA, currencyB) 172 | }; 173 | 174 | return this._private(parameters, callback); 175 | }, 176 | 177 | buy: function(currencyA, currencyB, rate, amount, callback){ 178 | var parameters = { 179 | command: 'buy', 180 | currencyPair: joinCurrencies(currencyA, currencyB), 181 | rate: rate, 182 | amount: amount 183 | }; 184 | 185 | return this._private(parameters, callback); 186 | }, 187 | 188 | sell: function(currencyA, currencyB, rate, amount, callback){ 189 | var parameters = { 190 | command: 'sell', 191 | currencyPair: joinCurrencies(currencyA, currencyB), 192 | rate: rate, 193 | amount: amount 194 | }; 195 | 196 | return this._private(parameters, callback); 197 | }, 198 | 199 | cancelOrder: function(currencyA, currencyB, orderNumber, callback){ 200 | var parameters = { 201 | command: 'cancelOrder', 202 | currencyPair: joinCurrencies(currencyA, currencyB), 203 | orderNumber: orderNumber 204 | }; 205 | 206 | return this._private(parameters, callback); 207 | }, 208 | 209 | withdraw: function(currency, amount, address, callback){ 210 | var parameters = { 211 | command: 'withdraw', 212 | currency: currency, 213 | amount: amount, 214 | address: address 215 | }; 216 | 217 | return this._private(parameters, callback); 218 | } 219 | }; 220 | 221 | return Bittrex; 222 | }(); 223 | -------------------------------------------------------------------------------- /libs/apiCoinWarz.js: -------------------------------------------------------------------------------- 1 | var request = require('request'); 2 | var nonce = require('nonce'); 3 | 4 | module.exports = function() { 5 | 'use strict'; 6 | 7 | // Module dependencies 8 | 9 | // Constants 10 | var version = '0.0.1', 11 | PUBLIC_API_URL = 'http://www.coinwarz.com/v1/api/profitability/?apikey=YOUR_API_KEY&algo=all', 12 | USER_AGENT = 'nomp/node-open-mining-portal' 13 | 14 | // Constructor 15 | function Cryptsy(key, secret){ 16 | // Generate headers signed by this user's key and secret. 17 | // The secret is encapsulated and never exposed 18 | this._getPrivateHeaders = function(parameters){ 19 | var paramString, signature; 20 | 21 | if (!key || !secret){ 22 | throw 'CoinWarz: Error. API key and secret required'; 23 | } 24 | 25 | // Sort parameters alphabetically and convert to `arg1=foo&arg2=bar` 26 | paramString = Object.keys(parameters).sort().map(function(param){ 27 | return encodeURIComponent(param) + '=' + encodeURIComponent(parameters[param]); 28 | }).join('&'); 29 | 30 | signature = crypto.createHmac('sha512', secret).update(paramString).digest('hex'); 31 | 32 | return { 33 | Key: key, 34 | Sign: signature 35 | }; 36 | }; 37 | } 38 | 39 | // If a site uses non-trusted SSL certificates, set this value to false 40 | Cryptsy.STRICT_SSL = true; 41 | 42 | // Helper methods 43 | function joinCurrencies(currencyA, currencyB){ 44 | return currencyA + '_' + currencyB; 45 | } 46 | 47 | // Prototype 48 | CoinWarz.prototype = { 49 | constructor: CoinWarz, 50 | 51 | // Make an API request 52 | _request: function(options, callback){ 53 | if (!('headers' in options)){ 54 | options.headers = {}; 55 | } 56 | 57 | options.headers['User-Agent'] = USER_AGENT; 58 | options.json = true; 59 | options.strictSSL = CoinWarz.STRICT_SSL; 60 | 61 | request(options, function(err, response, body) { 62 | callback(err, body); 63 | }); 64 | 65 | return this; 66 | }, 67 | 68 | // Make a public API request 69 | _public: function(parameters, callback){ 70 | var options = { 71 | method: 'GET', 72 | url: PUBLIC_API_URL, 73 | qs: parameters 74 | }; 75 | 76 | return this._request(options, callback); 77 | }, 78 | 79 | 80 | ///// 81 | 82 | 83 | // PUBLIC METHODS 84 | 85 | getTicker: function(callback){ 86 | var parameters = { 87 | method: 'marketdatav2' 88 | }; 89 | 90 | return this._public(parameters, callback); 91 | }, 92 | 93 | getOrderBook: function(currencyA, currencyB, callback){ 94 | var parameters = { 95 | command: 'returnOrderBook', 96 | currencyPair: joinCurrencies(currencyA, currencyB) 97 | }; 98 | 99 | return this._public(parameters, callback); 100 | }, 101 | 102 | getTradeHistory: function(currencyA, currencyB, callback){ 103 | var parameters = { 104 | command: 'returnTradeHistory', 105 | currencyPair: joinCurrencies(currencyA, currencyB) 106 | }; 107 | 108 | return this._public(parameters, callback); 109 | }, 110 | 111 | 112 | //// 113 | 114 | return CoinWarz; 115 | }(); 116 | -------------------------------------------------------------------------------- /libs/apiCryptsy.js: -------------------------------------------------------------------------------- 1 | var request = require('request'); 2 | var nonce = require('nonce'); 3 | 4 | module.exports = function() { 5 | 'use strict'; 6 | 7 | // Module dependencies 8 | 9 | // Constants 10 | var version = '0.1.0', 11 | PUBLIC_API_URL = 'http://pubapi.cryptsy.com/api.php', 12 | PRIVATE_API_URL = 'https://api.cryptsy.com/api', 13 | USER_AGENT = 'nomp/node-open-mining-portal' 14 | 15 | // Constructor 16 | function Cryptsy(key, secret){ 17 | // Generate headers signed by this user's key and secret. 18 | // The secret is encapsulated and never exposed 19 | this._getPrivateHeaders = function(parameters){ 20 | var paramString, signature; 21 | 22 | if (!key || !secret){ 23 | throw 'Cryptsy: Error. API key and secret required'; 24 | } 25 | 26 | // Sort parameters alphabetically and convert to `arg1=foo&arg2=bar` 27 | paramString = Object.keys(parameters).sort().map(function(param){ 28 | return encodeURIComponent(param) + '=' + encodeURIComponent(parameters[param]); 29 | }).join('&'); 30 | 31 | signature = crypto.createHmac('sha512', secret).update(paramString).digest('hex'); 32 | 33 | return { 34 | Key: key, 35 | Sign: signature 36 | }; 37 | }; 38 | } 39 | 40 | // If a site uses non-trusted SSL certificates, set this value to false 41 | Cryptsy.STRICT_SSL = true; 42 | 43 | // Helper methods 44 | function joinCurrencies(currencyA, currencyB){ 45 | return currencyA + '_' + currencyB; 46 | } 47 | 48 | // Prototype 49 | Cryptsy.prototype = { 50 | constructor: Cryptsy, 51 | 52 | // Make an API request 53 | _request: function(options, callback){ 54 | if (!('headers' in options)){ 55 | options.headers = {}; 56 | } 57 | 58 | options.headers['User-Agent'] = USER_AGENT; 59 | options.json = true; 60 | options.strictSSL = Cryptsy.STRICT_SSL; 61 | 62 | request(options, function(err, response, body) { 63 | callback(err, body); 64 | }); 65 | 66 | return this; 67 | }, 68 | 69 | // Make a public API request 70 | _public: function(parameters, callback){ 71 | var options = { 72 | method: 'GET', 73 | url: PUBLIC_API_URL, 74 | qs: parameters 75 | }; 76 | 77 | return this._request(options, callback); 78 | }, 79 | 80 | // Make a private API request 81 | _private: function(parameters, callback){ 82 | var options; 83 | 84 | parameters.nonce = nonce(); 85 | options = { 86 | method: 'POST', 87 | url: PRIVATE_API_URL, 88 | form: parameters, 89 | headers: this._getPrivateHeaders(parameters) 90 | }; 91 | 92 | return this._request(options, callback); 93 | }, 94 | 95 | 96 | ///// 97 | 98 | 99 | // PUBLIC METHODS 100 | 101 | getTicker: function(callback){ 102 | var parameters = { 103 | method: 'marketdatav2' 104 | }; 105 | 106 | return this._public(parameters, callback); 107 | }, 108 | 109 | getOrderBook: function(currencyA, currencyB, callback){ 110 | var parameters = { 111 | command: 'returnOrderBook', 112 | currencyPair: joinCurrencies(currencyA, currencyB) 113 | }; 114 | 115 | return this._public(parameters, callback); 116 | }, 117 | 118 | getTradeHistory: function(currencyA, currencyB, callback){ 119 | var parameters = { 120 | command: 'returnTradeHistory', 121 | currencyPair: joinCurrencies(currencyA, currencyB) 122 | }; 123 | 124 | return this._public(parameters, callback); 125 | }, 126 | 127 | 128 | ///// 129 | 130 | 131 | // PRIVATE METHODS 132 | 133 | myBalances: function(callback){ 134 | var parameters = { 135 | command: 'returnBalances' 136 | }; 137 | 138 | return this._private(parameters, callback); 139 | }, 140 | 141 | myOpenOrders: function(currencyA, currencyB, callback){ 142 | var parameters = { 143 | command: 'returnOpenOrders', 144 | currencyPair: joinCurrencies(currencyA, currencyB) 145 | }; 146 | 147 | return this._private(parameters, callback); 148 | }, 149 | 150 | myTradeHistory: function(currencyA, currencyB, callback){ 151 | var parameters = { 152 | command: 'returnTradeHistory', 153 | currencyPair: joinCurrencies(currencyA, currencyB) 154 | }; 155 | 156 | return this._private(parameters, callback); 157 | }, 158 | 159 | buy: function(currencyA, currencyB, rate, amount, callback){ 160 | var parameters = { 161 | command: 'buy', 162 | currencyPair: joinCurrencies(currencyA, currencyB), 163 | rate: rate, 164 | amount: amount 165 | }; 166 | 167 | return this._private(parameters, callback); 168 | }, 169 | 170 | sell: function(currencyA, currencyB, rate, amount, callback){ 171 | var parameters = { 172 | command: 'sell', 173 | currencyPair: joinCurrencies(currencyA, currencyB), 174 | rate: rate, 175 | amount: amount 176 | }; 177 | 178 | return this._private(parameters, callback); 179 | }, 180 | 181 | cancelOrder: function(currencyA, currencyB, orderNumber, callback){ 182 | var parameters = { 183 | command: 'cancelOrder', 184 | currencyPair: joinCurrencies(currencyA, currencyB), 185 | orderNumber: orderNumber 186 | }; 187 | 188 | return this._private(parameters, callback); 189 | }, 190 | 191 | withdraw: function(currency, amount, address, callback){ 192 | var parameters = { 193 | command: 'withdraw', 194 | currency: currency, 195 | amount: amount, 196 | address: address 197 | }; 198 | 199 | return this._private(parameters, callback); 200 | } 201 | }; 202 | 203 | return Cryptsy; 204 | }(); 205 | -------------------------------------------------------------------------------- /libs/apiMintpal.js: -------------------------------------------------------------------------------- 1 | var request = require('request'); 2 | var nonce = require('nonce'); 3 | 4 | module.exports = function() { 5 | 'use strict'; 6 | 7 | // Module dependencies 8 | 9 | // Constants 10 | var version = '0.1.0', 11 | PUBLIC_API_URL = 'https://api.mintpal.com/v2/market', 12 | PRIVATE_API_URL = 'https://api.mintpal.com/v2/market', 13 | USER_AGENT = 'nomp/node-open-mining-portal' 14 | 15 | // Constructor 16 | function Mintpal(key, secret){ 17 | // Generate headers signed by this user's key and secret. 18 | // The secret is encapsulated and never exposed 19 | this._getPrivateHeaders = function(parameters){ 20 | var paramString, signature; 21 | 22 | if (!key || !secret){ 23 | throw 'Mintpal: Error. API key and secret required'; 24 | } 25 | 26 | // Sort parameters alphabetically and convert to `arg1=foo&arg2=bar` 27 | paramString = Object.keys(parameters).sort().map(function(param){ 28 | return encodeURIComponent(param) + '=' + encodeURIComponent(parameters[param]); 29 | }).join('&'); 30 | 31 | signature = crypto.createHmac('sha512', secret).update(paramString).digest('hex'); 32 | 33 | return { 34 | Key: key, 35 | Sign: signature 36 | }; 37 | }; 38 | } 39 | 40 | // If a site uses non-trusted SSL certificates, set this value to false 41 | Mintpal.STRICT_SSL = true; 42 | 43 | // Helper methods 44 | function joinCurrencies(currencyA, currencyB){ 45 | return currencyA + '_' + currencyB; 46 | } 47 | 48 | // Prototype 49 | Mintpal.prototype = { 50 | constructor: Mintpal, 51 | 52 | // Make an API request 53 | _request: function(options, callback){ 54 | if (!('headers' in options)){ 55 | options.headers = {}; 56 | } 57 | 58 | options.headers['User-Agent'] = USER_AGENT; 59 | options.json = true; 60 | options.strictSSL = Mintpal.STRICT_SSL; 61 | 62 | request(options, function(err, response, body) { 63 | callback(err, body); 64 | }); 65 | 66 | return this; 67 | }, 68 | 69 | // Make a public API request 70 | _public: function(parameters, callback){ 71 | var options = { 72 | method: 'GET', 73 | url: PUBLIC_API_URL, 74 | qs: parameters 75 | }; 76 | 77 | return this._request(options, callback); 78 | }, 79 | 80 | // Make a private API request 81 | _private: function(parameters, callback){ 82 | var options; 83 | 84 | parameters.nonce = nonce(); 85 | options = { 86 | method: 'POST', 87 | url: PRIVATE_API_URL, 88 | form: parameters, 89 | headers: this._getPrivateHeaders(parameters) 90 | }; 91 | 92 | return this._request(options, callback); 93 | }, 94 | 95 | 96 | ///// 97 | 98 | 99 | // PUBLIC METHODS 100 | 101 | getTicker: function(callback){ 102 | var options = { 103 | method: 'GET', 104 | url: PUBLIC_API_URL + '/summary', 105 | qs: null 106 | }; 107 | 108 | return this._request(options, callback); 109 | }, 110 | 111 | getBuyOrderBook: function(currencyA, currencyB, callback){ 112 | var options = { 113 | method: 'GET', 114 | url: PUBLIC_API_URL + '/orders/' + currencyB + '/' + currencyA + '/BUY', 115 | qs: null 116 | }; 117 | 118 | return this._request(options, callback); 119 | }, 120 | 121 | getOrderBook: function(currencyA, currencyB, callback){ 122 | var parameters = { 123 | command: 'returnOrderBook', 124 | currencyPair: joinCurrencies(currencyA, currencyB) 125 | }; 126 | 127 | return this._public(parameters, callback); 128 | }, 129 | 130 | getTradeHistory: function(currencyA, currencyB, callback){ 131 | var parameters = { 132 | command: 'returnTradeHistory', 133 | currencyPair: joinCurrencies(currencyA, currencyB) 134 | }; 135 | 136 | return this._public(parameters, callback); 137 | }, 138 | 139 | 140 | ///// 141 | 142 | 143 | // PRIVATE METHODS 144 | 145 | myBalances: function(callback){ 146 | var parameters = { 147 | command: 'returnBalances' 148 | }; 149 | 150 | return this._private(parameters, callback); 151 | }, 152 | 153 | myOpenOrders: function(currencyA, currencyB, callback){ 154 | var parameters = { 155 | command: 'returnOpenOrders', 156 | currencyPair: joinCurrencies(currencyA, currencyB) 157 | }; 158 | 159 | return this._private(parameters, callback); 160 | }, 161 | 162 | myTradeHistory: function(currencyA, currencyB, callback){ 163 | var parameters = { 164 | command: 'returnTradeHistory', 165 | currencyPair: joinCurrencies(currencyA, currencyB) 166 | }; 167 | 168 | return this._private(parameters, callback); 169 | }, 170 | 171 | buy: function(currencyA, currencyB, rate, amount, callback){ 172 | var parameters = { 173 | command: 'buy', 174 | currencyPair: joinCurrencies(currencyA, currencyB), 175 | rate: rate, 176 | amount: amount 177 | }; 178 | 179 | return this._private(parameters, callback); 180 | }, 181 | 182 | sell: function(currencyA, currencyB, rate, amount, callback){ 183 | var parameters = { 184 | command: 'sell', 185 | currencyPair: joinCurrencies(currencyA, currencyB), 186 | rate: rate, 187 | amount: amount 188 | }; 189 | 190 | return this._private(parameters, callback); 191 | }, 192 | 193 | cancelOrder: function(currencyA, currencyB, orderNumber, callback){ 194 | var parameters = { 195 | command: 'cancelOrder', 196 | currencyPair: joinCurrencies(currencyA, currencyB), 197 | orderNumber: orderNumber 198 | }; 199 | 200 | return this._private(parameters, callback); 201 | }, 202 | 203 | withdraw: function(currency, amount, address, callback){ 204 | var parameters = { 205 | command: 'withdraw', 206 | currency: currency, 207 | amount: amount, 208 | address: address 209 | }; 210 | 211 | return this._private(parameters, callback); 212 | } 213 | }; 214 | 215 | return Mintpal; 216 | }(); 217 | -------------------------------------------------------------------------------- /libs/apiPoloniex.js: -------------------------------------------------------------------------------- 1 | var request = require('request'); 2 | var nonce = require('nonce'); 3 | 4 | module.exports = function() { 5 | 'use strict'; 6 | 7 | // Module dependencies 8 | 9 | // Constants 10 | var version = '0.1.0', 11 | PUBLIC_API_URL = 'https://poloniex.com/public', 12 | PRIVATE_API_URL = 'https://poloniex.com/tradingApi', 13 | USER_AGENT = 'npm-crypto-apis/' + version 14 | 15 | // Constructor 16 | function Poloniex(key, secret){ 17 | // Generate headers signed by this user's key and secret. 18 | // The secret is encapsulated and never exposed 19 | this._getPrivateHeaders = function(parameters){ 20 | var paramString, signature; 21 | 22 | if (!key || !secret){ 23 | throw 'Poloniex: Error. API key and secret required'; 24 | } 25 | 26 | // Sort parameters alphabetically and convert to `arg1=foo&arg2=bar` 27 | paramString = Object.keys(parameters).sort().map(function(param){ 28 | return encodeURIComponent(param) + '=' + encodeURIComponent(parameters[param]); 29 | }).join('&'); 30 | 31 | signature = crypto.createHmac('sha512', secret).update(paramString).digest('hex'); 32 | 33 | return { 34 | Key: key, 35 | Sign: signature 36 | }; 37 | }; 38 | } 39 | 40 | // If a site uses non-trusted SSL certificates, set this value to false 41 | Poloniex.STRICT_SSL = true; 42 | 43 | // Helper methods 44 | function joinCurrencies(currencyA, currencyB){ 45 | return currencyA + '_' + currencyB; 46 | } 47 | 48 | // Prototype 49 | Poloniex.prototype = { 50 | constructor: Poloniex, 51 | 52 | // Make an API request 53 | _request: function(options, callback){ 54 | if (!('headers' in options)){ 55 | options.headers = {}; 56 | } 57 | 58 | options.headers['User-Agent'] = USER_AGENT; 59 | options.json = true; 60 | options.strictSSL = Poloniex.STRICT_SSL; 61 | 62 | request(options, function(err, response, body) { 63 | callback(err, body); 64 | }); 65 | 66 | return this; 67 | }, 68 | 69 | // Make a public API request 70 | _public: function(parameters, callback){ 71 | var options = { 72 | method: 'GET', 73 | url: PUBLIC_API_URL, 74 | qs: parameters 75 | }; 76 | 77 | return this._request(options, callback); 78 | }, 79 | 80 | // Make a private API request 81 | _private: function(parameters, callback){ 82 | var options; 83 | 84 | parameters.nonce = nonce(); 85 | options = { 86 | method: 'POST', 87 | url: PRIVATE_API_URL, 88 | form: parameters, 89 | headers: this._getPrivateHeaders(parameters) 90 | }; 91 | 92 | return this._request(options, callback); 93 | }, 94 | 95 | 96 | ///// 97 | 98 | 99 | // PUBLIC METHODS 100 | 101 | getTicker: function(callback){ 102 | var parameters = { 103 | command: 'returnTicker' 104 | }; 105 | 106 | return this._public(parameters, callback); 107 | }, 108 | 109 | get24hVolume: function(callback){ 110 | var parameters = { 111 | command: 'return24hVolume' 112 | }; 113 | 114 | return this._public(parameters, callback); 115 | }, 116 | 117 | getOrderBook: function(currencyA, currencyB, callback){ 118 | var parameters = { 119 | command: 'returnOrderBook', 120 | currencyPair: joinCurrencies(currencyA, currencyB) 121 | }; 122 | 123 | return this._public(parameters, callback); 124 | }, 125 | 126 | getTradeHistory: function(currencyA, currencyB, callback){ 127 | var parameters = { 128 | command: 'returnTradeHistory', 129 | currencyPair: joinCurrencies(currencyA, currencyB) 130 | }; 131 | 132 | return this._public(parameters, callback); 133 | }, 134 | 135 | 136 | ///// 137 | 138 | 139 | // PRIVATE METHODS 140 | 141 | myBalances: function(callback){ 142 | var parameters = { 143 | command: 'returnBalances' 144 | }; 145 | 146 | return this._private(parameters, callback); 147 | }, 148 | 149 | myOpenOrders: function(currencyA, currencyB, callback){ 150 | var parameters = { 151 | command: 'returnOpenOrders', 152 | currencyPair: joinCurrencies(currencyA, currencyB) 153 | }; 154 | 155 | return this._private(parameters, callback); 156 | }, 157 | 158 | myTradeHistory: function(currencyA, currencyB, callback){ 159 | var parameters = { 160 | command: 'returnTradeHistory', 161 | currencyPair: joinCurrencies(currencyA, currencyB) 162 | }; 163 | 164 | return this._private(parameters, callback); 165 | }, 166 | 167 | buy: function(currencyA, currencyB, rate, amount, callback){ 168 | var parameters = { 169 | command: 'buy', 170 | currencyPair: joinCurrencies(currencyA, currencyB), 171 | rate: rate, 172 | amount: amount 173 | }; 174 | 175 | return this._private(parameters, callback); 176 | }, 177 | 178 | sell: function(currencyA, currencyB, rate, amount, callback){ 179 | var parameters = { 180 | command: 'sell', 181 | currencyPair: joinCurrencies(currencyA, currencyB), 182 | rate: rate, 183 | amount: amount 184 | }; 185 | 186 | return this._private(parameters, callback); 187 | }, 188 | 189 | cancelOrder: function(currencyA, currencyB, orderNumber, callback){ 190 | var parameters = { 191 | command: 'cancelOrder', 192 | currencyPair: joinCurrencies(currencyA, currencyB), 193 | orderNumber: orderNumber 194 | }; 195 | 196 | return this._private(parameters, callback); 197 | }, 198 | 199 | withdraw: function(currency, amount, address, callback){ 200 | var parameters = { 201 | command: 'withdraw', 202 | currency: currency, 203 | amount: amount, 204 | address: address 205 | }; 206 | 207 | return this._private(parameters, callback); 208 | } 209 | }; 210 | 211 | return Poloniex; 212 | }(); 213 | -------------------------------------------------------------------------------- /libs/cliListener.js: -------------------------------------------------------------------------------- 1 | var events = require('events'); 2 | var net = require('net'); 3 | 4 | var listener = module.exports = function listener(port){ 5 | 6 | var _this = this; 7 | 8 | var emitLog = function(text){ 9 | _this.emit('log', text); 10 | }; 11 | 12 | 13 | this.start = function(){ 14 | net.createServer(function(c) { 15 | 16 | var data = ''; 17 | try { 18 | c.on('data', function (d) { 19 | data += d; 20 | if (data.slice(-1) === '\n') { 21 | var message = JSON.parse(data); 22 | _this.emit('command', message.command, message.params, message.options, function(message){ 23 | c.end(message); 24 | }); 25 | } 26 | }); 27 | c.on('end', function () { 28 | 29 | }); 30 | c.on('error', function () { 31 | 32 | }); 33 | } 34 | catch(e){ 35 | emitLog('CLI listener failed to parse message ' + data); 36 | } 37 | 38 | }).listen(port, '127.0.0.1', function() { 39 | emitLog('CLI listening on port ' + port) 40 | }); 41 | } 42 | 43 | }; 44 | 45 | listener.prototype.__proto__ = events.EventEmitter.prototype; 46 | -------------------------------------------------------------------------------- /libs/logUtil.js: -------------------------------------------------------------------------------- 1 | var dateFormat = require('dateformat'); 2 | var colors = require('colors'); 3 | 4 | 5 | var severityToColor = function(severity, text) { 6 | switch(severity) { 7 | case 'special': 8 | return text.cyan.underline; 9 | case 'debug': 10 | return text.green; 11 | case 'warning': 12 | return text.yellow; 13 | case 'error': 14 | return text.red; 15 | default: 16 | console.log("Unknown severity " + severity); 17 | return text.italic; 18 | } 19 | }; 20 | 21 | var severityValues = { 22 | 'debug': 1, 23 | 'warning': 2, 24 | 'error': 3, 25 | 'special': 4 26 | }; 27 | 28 | 29 | var PoolLogger = function (configuration) { 30 | 31 | 32 | var logLevelInt = severityValues[configuration.logLevel]; 33 | var logColors = configuration.logColors; 34 | 35 | 36 | 37 | var log = function(severity, system, component, text, subcat) { 38 | 39 | if (severityValues[severity] < logLevelInt) return; 40 | 41 | if (subcat){ 42 | var realText = subcat; 43 | var realSubCat = text; 44 | text = realText; 45 | subcat = realSubCat; 46 | } 47 | 48 | var entryDesc = dateFormat(new Date(), 'yyyy-mm-dd HH:MM:ss') + ' [' + system + ']\t'; 49 | if (logColors) { 50 | entryDesc = severityToColor(severity, entryDesc); 51 | 52 | var logString = 53 | entryDesc + 54 | ('[' + component + '] ').italic; 55 | 56 | if (subcat) 57 | logString += ('(' + subcat + ') ').bold.grey; 58 | 59 | logString += text.grey; 60 | } 61 | else { 62 | var logString = 63 | entryDesc + 64 | '[' + component + '] '; 65 | 66 | if (subcat) 67 | logString += '(' + subcat + ') '; 68 | 69 | logString += text; 70 | } 71 | 72 | console.log(logString); 73 | 74 | 75 | }; 76 | 77 | // public 78 | 79 | var _this = this; 80 | Object.keys(severityValues).forEach(function(logType){ 81 | _this[logType] = function(){ 82 | var args = Array.prototype.slice.call(arguments, 0); 83 | args.unshift(logType); 84 | log.apply(this, args); 85 | }; 86 | }); 87 | }; 88 | 89 | module.exports = PoolLogger; -------------------------------------------------------------------------------- /libs/mposCompatibility.js: -------------------------------------------------------------------------------- 1 | var mysql = require('mysql'); 2 | var cluster = require('cluster'); 3 | module.exports = function(logger, poolConfig){ 4 | 5 | var mposConfig = poolConfig.mposMode; 6 | var coin = poolConfig.coin.name; 7 | 8 | var connection = mysql.createPool({ 9 | host: mposConfig.host, 10 | port: mposConfig.port, 11 | user: mposConfig.user, 12 | password: mposConfig.password, 13 | database: mposConfig.database 14 | }); 15 | 16 | 17 | var logIdentify = 'MySQL'; 18 | var logComponent = coin; 19 | 20 | 21 | 22 | this.handleAuth = function(workerName, password, authCallback){ 23 | 24 | if (poolConfig.validateWorkerUsername !== true && mposConfig.autoCreateWorker !== true){ 25 | authCallback(true); 26 | return; 27 | } 28 | 29 | connection.query( 30 | 'SELECT password FROM pool_worker WHERE username = LOWER(?)', 31 | [workerName.toLowerCase()], 32 | function(err, result){ 33 | if (err){ 34 | logger.error(logIdentify, logComponent, 'Database error when authenticating worker: ' + 35 | JSON.stringify(err)); 36 | authCallback(false); 37 | } 38 | else if (!result[0]){ 39 | if(mposConfig.autoCreateWorker){ 40 | var account = workerName.split('.')[0]; 41 | connection.query( 42 | 'SELECT id,username FROM accounts WHERE username = LOWER(?)', 43 | [account.toLowerCase()], 44 | function(err, result){ 45 | if (err){ 46 | logger.error(logIdentify, logComponent, 'Database error when authenticating account: ' + 47 | JSON.stringify(err)); 48 | authCallback(false); 49 | }else if(!result[0]){ 50 | authCallback(false); 51 | }else{ 52 | connection.query( 53 | "INSERT INTO `pool_worker` (`account_id`, `username`, `password`) VALUES (?, ?, ?);", 54 | [result[0].id,workerName.toLowerCase(),password], 55 | function(err, result){ 56 | if (err){ 57 | logger.error(logIdentify, logComponent, 'Database error when insert worker: ' + 58 | JSON.stringify(err)); 59 | authCallback(false); 60 | }else { 61 | authCallback(true); 62 | } 63 | }) 64 | } 65 | } 66 | ); 67 | } 68 | else{ 69 | authCallback(false); 70 | } 71 | } 72 | else if (mposConfig.checkPassword && result[0].password !== password) 73 | authCallback(false); 74 | else 75 | authCallback(true); 76 | } 77 | ); 78 | 79 | }; 80 | 81 | this.handleShare = function(isValidShare, isValidBlock, shareData){ 82 | 83 | var dbData = [ 84 | shareData.ip, 85 | shareData.worker, 86 | isValidShare ? 'Y' : 'N', 87 | isValidBlock ? 'Y' : 'N', 88 | shareData.difficulty * (poolConfig.coin.mposDiffMultiplier || 1), 89 | typeof(shareData.error) === 'undefined' ? null : shareData.error, 90 | shareData.blockHash ? shareData.blockHash : (shareData.blockHashInvalid ? shareData.blockHashInvalid : '') 91 | ]; 92 | connection.query( 93 | 'INSERT INTO `shares` SET time = NOW(), rem_host = ?, username = ?, our_result = ?, upstream_result = ?, difficulty = ?, reason = ?, solution = ?', 94 | dbData, 95 | function(err, result) { 96 | if (err) 97 | logger.error(logIdentify, logComponent, 'Insert error when adding share: ' + JSON.stringify(err)); 98 | else 99 | logger.debug(logIdentify, logComponent, 'Share inserted'); 100 | } 101 | ); 102 | }; 103 | 104 | this.handleDifficultyUpdate = function(workerName, diff){ 105 | 106 | connection.query( 107 | 'UPDATE `pool_worker` SET `difficulty` = ' + diff + ' WHERE `username` = ' + connection.escape(workerName), 108 | function(err, result){ 109 | if (err) 110 | logger.error(logIdentify, logComponent, 'Error when updating worker diff: ' + 111 | JSON.stringify(err)); 112 | else if (result.affectedRows === 0){ 113 | connection.query('INSERT INTO `pool_worker` SET ?', {username: workerName, difficulty: diff}); 114 | } 115 | else 116 | console.log('Updated difficulty successfully', result); 117 | } 118 | ); 119 | }; 120 | 121 | 122 | }; 123 | -------------------------------------------------------------------------------- /libs/poolWorker.js: -------------------------------------------------------------------------------- 1 | var Stratum = require('stratum-pool'); 2 | var redis = require('redis'); 3 | var net = require('net'); 4 | 5 | var MposCompatibility = require('./mposCompatibility.js'); 6 | var ShareProcessor = require('./shareProcessor.js'); 7 | 8 | module.exports = function(logger){ 9 | 10 | var _this = this; 11 | 12 | var poolConfigs = JSON.parse(process.env.pools); 13 | var portalConfig = JSON.parse(process.env.portalConfig); 14 | 15 | var forkId = process.env.forkId; 16 | 17 | var pools = {}; 18 | 19 | var proxySwitch = {}; 20 | 21 | var redisClient = redis.createClient(portalConfig.redis.port, portalConfig.redis.host); 22 | 23 | //Handle messages from master process sent via IPC 24 | process.on('message', function(message) { 25 | switch(message.type){ 26 | 27 | case 'banIP': 28 | for (var p in pools){ 29 | if (pools[p].stratumServer) 30 | pools[p].stratumServer.addBannedIP(message.ip); 31 | } 32 | break; 33 | 34 | case 'blocknotify': 35 | 36 | var messageCoin = message.coin.toLowerCase(); 37 | var poolTarget = Object.keys(pools).filter(function(p){ 38 | return p.toLowerCase() === messageCoin; 39 | })[0]; 40 | 41 | if (poolTarget) 42 | pools[poolTarget].processBlockNotify(message.hash, 'blocknotify script'); 43 | 44 | break; 45 | 46 | // IPC message for pool switching 47 | case 'coinswitch': 48 | var logSystem = 'Proxy'; 49 | var logComponent = 'Switch'; 50 | var logSubCat = 'Thread ' + (parseInt(forkId) + 1); 51 | 52 | var switchName = message.switchName; 53 | 54 | var newCoin = message.coin; 55 | 56 | var algo = poolConfigs[newCoin].coin.algorithm; 57 | 58 | var newPool = pools[newCoin]; 59 | var oldCoin = proxySwitch[switchName].currentPool; 60 | var oldPool = pools[oldCoin]; 61 | var proxyPorts = Object.keys(proxySwitch[switchName].ports); 62 | 63 | if (newCoin == oldCoin) { 64 | logger.debug(logSystem, logComponent, logSubCat, 'Switch message would have no effect - ignoring ' + newCoin); 65 | break; 66 | } 67 | 68 | logger.debug(logSystem, logComponent, logSubCat, 'Proxy message for ' + algo + ' from ' + oldCoin + ' to ' + newCoin); 69 | 70 | if (newPool) { 71 | oldPool.relinquishMiners( 72 | function (miner, cback) { 73 | // relinquish miners that are attached to one of the "Auto-switch" ports and leave the others there. 74 | cback(proxyPorts.indexOf(miner.client.socket.localPort.toString()) !== -1) 75 | }, 76 | function (clients) { 77 | newPool.attachMiners(clients); 78 | } 79 | ); 80 | proxySwitch[switchName].currentPool = newCoin; 81 | 82 | redisClient.hset('proxyState', algo, newCoin, function(error, obj) { 83 | if (error) { 84 | logger.error(logSystem, logComponent, logSubCat, 'Redis error writing proxy config: ' + JSON.stringify(err)) 85 | } 86 | else { 87 | logger.debug(logSystem, logComponent, logSubCat, 'Last proxy state saved to redis for ' + algo); 88 | } 89 | }); 90 | 91 | } 92 | break; 93 | } 94 | }); 95 | 96 | 97 | Object.keys(poolConfigs).forEach(function(coin) { 98 | 99 | var poolOptions = poolConfigs[coin]; 100 | 101 | var logSystem = 'Pool'; 102 | var logComponent = coin; 103 | var logSubCat = 'Thread ' + (parseInt(forkId) + 1); 104 | 105 | var handlers = { 106 | auth: function(){}, 107 | share: function(){}, 108 | diff: function(){} 109 | }; 110 | 111 | //Functions required for MPOS compatibility 112 | if (poolOptions.mposMode && poolOptions.mposMode.enabled){ 113 | var mposCompat = new MposCompatibility(logger, poolOptions); 114 | 115 | handlers.auth = function(port, workerName, password, authCallback){ 116 | mposCompat.handleAuth(workerName, password, authCallback); 117 | }; 118 | 119 | handlers.share = function(isValidShare, isValidBlock, data){ 120 | mposCompat.handleShare(isValidShare, isValidBlock, data); 121 | }; 122 | 123 | handlers.diff = function(workerName, diff){ 124 | mposCompat.handleDifficultyUpdate(workerName, diff); 125 | } 126 | } 127 | 128 | //Functions required for internal payment processing 129 | else{ 130 | 131 | var shareProcessor = new ShareProcessor(logger, poolOptions); 132 | 133 | handlers.auth = function(port, workerName, password, authCallback){ 134 | if (poolOptions.validateWorkerUsername !== true) 135 | authCallback(true); 136 | else { 137 | if (workerName.length === 40) { 138 | try { 139 | new Buffer(workerName, 'hex'); 140 | authCallback(true); 141 | } 142 | catch (e) { 143 | authCallback(false); 144 | } 145 | } 146 | else { 147 | pool.daemon.cmd('validateaddress', [workerName], function (results) { 148 | var isValid = results.filter(function (r) { 149 | return r.response.isvalid 150 | }).length > 0; 151 | authCallback(isValid); 152 | }); 153 | } 154 | 155 | } 156 | }; 157 | 158 | handlers.share = function(isValidShare, isValidBlock, data){ 159 | shareProcessor.handleShare(isValidShare, isValidBlock, data); 160 | }; 161 | } 162 | 163 | var authorizeFN = function (ip, port, workerName, password, callback) { 164 | handlers.auth(port, workerName, password, function(authorized){ 165 | 166 | var authString = authorized ? 'Authorized' : 'Unauthorized '; 167 | 168 | logger.debug(logSystem, logComponent, logSubCat, authString + ' ' + workerName + ':' + password + ' [' + ip + ']'); 169 | callback({ 170 | error: null, 171 | authorized: authorized, 172 | disconnect: false 173 | }); 174 | }); 175 | }; 176 | 177 | 178 | var pool = Stratum.createPool(poolOptions, authorizeFN, logger); 179 | pool.on('share', function(isValidShare, isValidBlock, data){ 180 | 181 | var shareData = JSON.stringify(data); 182 | 183 | if (data.blockHash && !isValidBlock) 184 | logger.debug(logSystem, logComponent, logSubCat, 'We thought a block was found but it was rejected by the daemon, share data: ' + shareData); 185 | 186 | else if (isValidBlock) 187 | logger.debug(logSystem, logComponent, logSubCat, 'Block found: ' + data.blockHash + ' by ' + data.worker); 188 | 189 | if (isValidShare) { 190 | if(data.shareDiff > 1000000000) 191 | logger.debug(logSystem, logComponent, logSubCat, 'Share was found with diff higher than 1.000.000.000!'); 192 | else if(data.shareDiff > 1000000) 193 | logger.debug(logSystem, logComponent, logSubCat, 'Share was found with diff higher than 1.000.000!'); 194 | logger.debug(logSystem, logComponent, logSubCat, 'Share accepted at diff ' + data.difficulty + '/' + data.shareDiff + ' by ' + data.worker + ' [' + data.ip + ']' ); 195 | 196 | } else if (!isValidShare) 197 | logger.debug(logSystem, logComponent, logSubCat, 'Share rejected: ' + shareData); 198 | 199 | handlers.share(isValidShare, isValidBlock, data) 200 | 201 | 202 | }).on('difficultyUpdate', function(workerName, diff){ 203 | logger.debug(logSystem, logComponent, logSubCat, 'Difficulty update to diff ' + diff + ' workerName=' + JSON.stringify(workerName)); 204 | handlers.diff(workerName, diff); 205 | }).on('log', function(severity, text) { 206 | logger[severity](logSystem, logComponent, logSubCat, text); 207 | }).on('banIP', function(ip, worker){ 208 | process.send({type: 'banIP', ip: ip}); 209 | }).on('started', function(){ 210 | _this.setDifficultyForProxyPort(pool, poolOptions.coin.name, poolOptions.coin.algorithm); 211 | }); 212 | 213 | pool.start(); 214 | pools[poolOptions.coin.name] = pool; 215 | }); 216 | 217 | 218 | if (portalConfig.switching) { 219 | 220 | var logSystem = 'Switching'; 221 | var logComponent = 'Setup'; 222 | var logSubCat = 'Thread ' + (parseInt(forkId) + 1); 223 | 224 | var proxyState = {}; 225 | 226 | // 227 | // Load proxy state for each algorithm from redis which allows NOMP to resume operation 228 | // on the last pool it was using when reloaded or restarted 229 | // 230 | logger.debug(logSystem, logComponent, logSubCat, 'Loading last proxy state from redis'); 231 | 232 | 233 | 234 | /*redisClient.on('error', function(err){ 235 | logger.debug(logSystem, logComponent, logSubCat, 'Pool configuration failed: ' + err); 236 | });*/ 237 | 238 | redisClient.hgetall("proxyState", function(error, obj) { 239 | if (!error && obj) { 240 | proxyState = obj; 241 | logger.debug(logSystem, logComponent, logSubCat, 'Last proxy state loaded from redis'); 242 | } 243 | 244 | // 245 | // Setup proxySwitch object to control proxy operations from configuration and any restored 246 | // state. Each algorithm has a listening port, current coin name, and an active pool to 247 | // which traffic is directed when activated in the config. 248 | // 249 | // In addition, the proxy config also takes diff and varDiff parmeters the override the 250 | // defaults for the standard config of the coin. 251 | // 252 | Object.keys(portalConfig.switching).forEach(function(switchName) { 253 | 254 | var algorithm = portalConfig.switching[switchName].algorithm; 255 | 256 | if (!portalConfig.switching[switchName].enabled) return; 257 | 258 | 259 | var initalPool = proxyState.hasOwnProperty(algorithm) ? proxyState[algorithm] : _this.getFirstPoolForAlgorithm(algorithm); 260 | proxySwitch[switchName] = { 261 | algorithm: algorithm, 262 | ports: portalConfig.switching[switchName].ports, 263 | currentPool: initalPool, 264 | servers: [] 265 | }; 266 | 267 | 268 | Object.keys(proxySwitch[switchName].ports).forEach(function(port){ 269 | var f = net.createServer(function(socket) { 270 | var currentPool = proxySwitch[switchName].currentPool; 271 | 272 | logger.debug(logSystem, 'Connect', logSubCat, 'Connection to ' 273 | + switchName + ' from ' 274 | + socket.remoteAddress + ' on ' 275 | + port + ' routing to ' + currentPool); 276 | 277 | if (pools[currentPool]) 278 | pools[currentPool].getStratumServer().handleNewClient(socket); 279 | else 280 | pools[initalPool].getStratumServer().handleNewClient(socket); 281 | 282 | }).listen(parseInt(port), function() { 283 | logger.debug(logSystem, logComponent, logSubCat, 'Switching "' + switchName 284 | + '" listening for ' + algorithm 285 | + ' on port ' + port 286 | + ' into ' + proxySwitch[switchName].currentPool); 287 | }); 288 | proxySwitch[switchName].servers.push(f); 289 | }); 290 | 291 | }); 292 | }); 293 | } 294 | 295 | this.getFirstPoolForAlgorithm = function(algorithm) { 296 | var foundCoin = ""; 297 | Object.keys(poolConfigs).forEach(function(coinName) { 298 | if (poolConfigs[coinName].coin.algorithm == algorithm) { 299 | if (foundCoin === "") 300 | foundCoin = coinName; 301 | } 302 | }); 303 | return foundCoin; 304 | }; 305 | 306 | // 307 | // Called when stratum pool emits its 'started' event to copy the initial diff and vardiff 308 | // configuation for any proxy switching ports configured into the stratum pool object. 309 | // 310 | this.setDifficultyForProxyPort = function(pool, coin, algo) { 311 | 312 | logger.debug(logSystem, logComponent, algo, 'Setting proxy difficulties after pool start'); 313 | 314 | Object.keys(portalConfig.switching).forEach(function(switchName) { 315 | if (!portalConfig.switching[switchName].enabled) return; 316 | 317 | var switchAlgo = portalConfig.switching[switchName].algorithm; 318 | if (pool.options.coin.algorithm !== switchAlgo) return; 319 | 320 | // we know the switch configuration matches the pool's algo, so setup the diff and 321 | // vardiff for each of the switch's ports 322 | for (var port in portalConfig.switching[switchName].ports) { 323 | 324 | if (portalConfig.switching[switchName].ports[port].varDiff) 325 | pool.setVarDiff(port, portalConfig.switching[switchName].ports[port].varDiff); 326 | 327 | if (portalConfig.switching[switchName].ports[port].diff){ 328 | if (!pool.options.ports.hasOwnProperty(port)) 329 | pool.options.ports[port] = {}; 330 | pool.options.ports[port].diff = portalConfig.switching[switchName].ports[port].diff; 331 | } 332 | } 333 | }); 334 | }; 335 | }; 336 | -------------------------------------------------------------------------------- /libs/shareProcessor.js: -------------------------------------------------------------------------------- 1 | var redis = require('redis'); 2 | var Stratum = require('stratum-pool'); 3 | 4 | 5 | 6 | /* 7 | This module deals with handling shares when in internal payment processing mode. It connects to a redis 8 | database and inserts shares with the database structure of: 9 | 10 | key: coin_name + ':' + block_height 11 | value: a hash with.. 12 | key: 13 | 14 | */ 15 | 16 | 17 | 18 | module.exports = function(logger, poolConfig){ 19 | 20 | var redisConfig = poolConfig.redis; 21 | var coin = poolConfig.coin.name; 22 | 23 | 24 | var forkId = process.env.forkId; 25 | var logSystem = 'Pool'; 26 | var logComponent = coin; 27 | var logSubCat = 'Thread ' + (parseInt(forkId) + 1); 28 | 29 | var connection = redis.createClient(redisConfig.port, redisConfig.host); 30 | 31 | connection.on('ready', function(){ 32 | logger.debug(logSystem, logComponent, logSubCat, 'Share processing setup with redis (' + redisConfig.host + 33 | ':' + redisConfig.port + ')'); 34 | }); 35 | connection.on('error', function(err){ 36 | logger.error(logSystem, logComponent, logSubCat, 'Redis client had an error: ' + JSON.stringify(err)) 37 | }); 38 | connection.on('end', function(){ 39 | logger.error(logSystem, logComponent, logSubCat, 'Connection to redis database has been ended'); 40 | }); 41 | 42 | connection.info(function(error, response){ 43 | if (error){ 44 | logger.error(logSystem, logComponent, logSubCat, 'Redis version check failed'); 45 | return; 46 | } 47 | var parts = response.split('\r\n'); 48 | var version; 49 | var versionString; 50 | for (var i = 0; i < parts.length; i++){ 51 | if (parts[i].indexOf(':') !== -1){ 52 | var valParts = parts[i].split(':'); 53 | if (valParts[0] === 'redis_version'){ 54 | versionString = valParts[1]; 55 | version = parseFloat(versionString); 56 | break; 57 | } 58 | } 59 | } 60 | if (!version){ 61 | logger.error(logSystem, logComponent, logSubCat, 'Could not detect redis version - but be super old or broken'); 62 | } 63 | else if (version < 2.6){ 64 | logger.error(logSystem, logComponent, logSubCat, "You're using redis version " + versionString + " the minimum required version is 2.6. Follow the damn usage instructions..."); 65 | } 66 | }); 67 | 68 | 69 | this.handleShare = function(isValidShare, isValidBlock, shareData){ 70 | 71 | var redisCommands = []; 72 | 73 | if (isValidShare){ 74 | redisCommands.push(['hincrbyfloat', coin + ':shares:roundCurrent', shareData.worker, shareData.difficulty]); 75 | redisCommands.push(['hincrby', coin + ':stats', 'validShares', 1]); 76 | } 77 | else{ 78 | redisCommands.push(['hincrby', coin + ':stats', 'invalidShares', 1]); 79 | } 80 | /* Stores share diff, worker, and unique value with a score that is the timestamp. Unique value ensures it 81 | doesn't overwrite an existing entry, and timestamp as score lets us query shares from last X minutes to 82 | generate hashrate for each worker and pool. */ 83 | var dateNow = Date.now(); 84 | var hashrateData = [ isValidShare ? shareData.difficulty : -shareData.difficulty, shareData.worker, dateNow]; 85 | redisCommands.push(['zadd', coin + ':hashrate', dateNow / 1000 | 0, hashrateData.join(':')]); 86 | 87 | if (isValidBlock){ 88 | redisCommands.push(['rename', coin + ':shares:roundCurrent', coin + ':shares:round' + shareData.height]); 89 | redisCommands.push(['sadd', coin + ':blocksPending', [shareData.blockHash, shareData.txHash, shareData.height].join(':')]); 90 | redisCommands.push(['hincrby', coin + ':stats', 'validBlocks', 1]); 91 | } 92 | else if (shareData.blockHash){ 93 | redisCommands.push(['hincrby', coin + ':stats', 'invalidBlocks', 1]); 94 | } 95 | 96 | connection.multi(redisCommands).exec(function(err, replies){ 97 | if (err) 98 | logger.error(logSystem, logComponent, logSubCat, 'Error with share processor multi ' + JSON.stringify(err)); 99 | }); 100 | 101 | 102 | }; 103 | 104 | }; 105 | -------------------------------------------------------------------------------- /libs/stats.js: -------------------------------------------------------------------------------- 1 | var zlib = require('zlib'); 2 | 3 | var redis = require('redis'); 4 | var async = require('async'); 5 | 6 | 7 | var os = require('os'); 8 | 9 | var algos = require('stratum-pool/lib/algoProperties.js'); 10 | 11 | 12 | module.exports = function(logger, portalConfig, poolConfigs){ 13 | 14 | var _this = this; 15 | 16 | var logSystem = 'Stats'; 17 | 18 | var redisClients = []; 19 | var redisStats; 20 | 21 | this.statHistory = []; 22 | this.statPoolHistory = []; 23 | 24 | this.stats = {}; 25 | this.statsString = ''; 26 | 27 | setupStatsRedis(); 28 | gatherStatHistory(); 29 | 30 | var canDoStats = true; 31 | 32 | Object.keys(poolConfigs).forEach(function(coin){ 33 | 34 | if (!canDoStats) return; 35 | 36 | var poolConfig = poolConfigs[coin]; 37 | 38 | var redisConfig = poolConfig.redis; 39 | 40 | for (var i = 0; i < redisClients.length; i++){ 41 | var client = redisClients[i]; 42 | if (client.client.port === redisConfig.port && client.client.host === redisConfig.host){ 43 | client.coins.push(coin); 44 | return; 45 | } 46 | } 47 | redisClients.push({ 48 | coins: [coin], 49 | client: redis.createClient(redisConfig.port, redisConfig.host) 50 | }); 51 | }); 52 | 53 | 54 | function setupStatsRedis(){ 55 | redisStats = redis.createClient(portalConfig.redis.port, portalConfig.redis.host); 56 | redisStats.on('error', function(err){ 57 | logger.error(logSystem, 'Historics', 'Redis for stats had an error ' + JSON.stringify(err)); 58 | }); 59 | } 60 | 61 | function gatherStatHistory(){ 62 | 63 | var retentionTime = (((Date.now() / 1000) - portalConfig.website.stats.historicalRetention) | 0).toString(); 64 | 65 | redisStats.zrangebyscore(['statHistory', retentionTime, '+inf'], function(err, replies){ 66 | if (err) { 67 | logger.error(logSystem, 'Historics', 'Error when trying to grab historical stats ' + JSON.stringify(err)); 68 | return; 69 | } 70 | for (var i = 0; i < replies.length; i++){ 71 | _this.statHistory.push(JSON.parse(replies[i])); 72 | } 73 | _this.statHistory = _this.statHistory.sort(function(a, b){ 74 | return a.time - b.time; 75 | }); 76 | _this.statHistory.forEach(function(stats){ 77 | addStatPoolHistory(stats); 78 | }); 79 | }); 80 | } 81 | 82 | function addStatPoolHistory(stats){ 83 | var data = { 84 | time: stats.time, 85 | pools: {} 86 | }; 87 | for (var pool in stats.pools){ 88 | data.pools[pool] = { 89 | hashrate: stats.pools[pool].hashrate, 90 | workerCount: stats.pools[pool].workerCount, 91 | blocks: stats.pools[pool].blocks 92 | } 93 | } 94 | _this.statPoolHistory.push(data); 95 | } 96 | 97 | 98 | 99 | 100 | this.getGlobalStats = function(callback){ 101 | 102 | var statGatherTime = Date.now() / 1000 | 0; 103 | 104 | var allCoinStats = {}; 105 | 106 | async.each(redisClients, function(client, callback){ 107 | var windowTime = (((Date.now() / 1000) - portalConfig.website.stats.hashrateWindow) | 0).toString(); 108 | var redisCommands = []; 109 | 110 | 111 | var redisCommandTemplates = [ 112 | ['zremrangebyscore', ':hashrate', '-inf', '(' + windowTime], 113 | ['zrangebyscore', ':hashrate', windowTime, '+inf'], 114 | ['hgetall', ':stats'], 115 | ['scard', ':blocksPending'], 116 | ['scard', ':blocksConfirmed'], 117 | ['scard', ':blocksKicked'] 118 | ]; 119 | 120 | var commandsPerCoin = redisCommandTemplates.length; 121 | 122 | client.coins.map(function(coin){ 123 | redisCommandTemplates.map(function(t){ 124 | var clonedTemplates = t.slice(0); 125 | clonedTemplates[1] = coin + clonedTemplates[1]; 126 | redisCommands.push(clonedTemplates); 127 | }); 128 | }); 129 | 130 | 131 | client.client.multi(redisCommands).exec(function(err, replies){ 132 | if (err){ 133 | logger.error(logSystem, 'Global', 'error with getting global stats ' + JSON.stringify(err)); 134 | callback(err); 135 | } 136 | else{ 137 | for(var i = 0; i < replies.length; i += commandsPerCoin){ 138 | var coinName = client.coins[i / commandsPerCoin | 0]; 139 | var coinStats = { 140 | name: coinName, 141 | symbol: poolConfigs[coinName].coin.symbol.toUpperCase(), 142 | algorithm: poolConfigs[coinName].coin.algorithm, 143 | hashrates: replies[i + 1], 144 | poolStats: { 145 | validShares: replies[i + 2] ? (replies[i + 2].validShares || 0) : 0, 146 | validBlocks: replies[i + 2] ? (replies[i + 2].validBlocks || 0) : 0, 147 | invalidShares: replies[i + 2] ? (replies[i + 2].invalidShares || 0) : 0, 148 | totalPaid: replies[i + 2] ? (replies[i + 2].totalPaid || 0) : 0 149 | }, 150 | blocks: { 151 | pending: replies[i + 3], 152 | confirmed: replies[i + 4], 153 | orphaned: replies[i + 5] 154 | } 155 | }; 156 | allCoinStats[coinStats.name] = (coinStats); 157 | } 158 | callback(); 159 | } 160 | }); 161 | }, function(err){ 162 | if (err){ 163 | logger.error(logSystem, 'Global', 'error getting all stats' + JSON.stringify(err)); 164 | callback(); 165 | return; 166 | } 167 | 168 | var portalStats = { 169 | time: statGatherTime, 170 | global:{ 171 | workers: 0, 172 | hashrate: 0 173 | }, 174 | algos: {}, 175 | pools: allCoinStats 176 | }; 177 | 178 | Object.keys(allCoinStats).forEach(function(coin){ 179 | var coinStats = allCoinStats[coin]; 180 | coinStats.workers = {}; 181 | coinStats.shares = 0; 182 | coinStats.hashrates.forEach(function(ins){ 183 | var parts = ins.split(':'); 184 | var workerShares = parseFloat(parts[0]); 185 | var worker = parts[1]; 186 | if (workerShares > 0) { 187 | coinStats.shares += workerShares; 188 | if (worker in coinStats.workers) 189 | coinStats.workers[worker].shares += workerShares; 190 | else 191 | coinStats.workers[worker] = { 192 | shares: workerShares, 193 | invalidshares: 0, 194 | hashrateString: null 195 | }; 196 | } 197 | else { 198 | if (worker in coinStats.workers) 199 | coinStats.workers[worker].invalidshares -= workerShares; // workerShares is negative number! 200 | else 201 | coinStats.workers[worker] = { 202 | shares: 0, 203 | invalidshares: -workerShares, 204 | hashrateString: null 205 | }; 206 | } 207 | }); 208 | 209 | var shareMultiplier = Math.pow(2, 32) / algos[coinStats.algorithm].multiplier; 210 | coinStats.hashrate = shareMultiplier * coinStats.shares / portalConfig.website.stats.hashrateWindow; 211 | 212 | coinStats.workerCount = Object.keys(coinStats.workers).length; 213 | portalStats.global.workers += coinStats.workerCount; 214 | 215 | /* algorithm specific global stats */ 216 | var algo = coinStats.algorithm; 217 | if (!portalStats.algos.hasOwnProperty(algo)){ 218 | portalStats.algos[algo] = { 219 | workers: 0, 220 | hashrate: 0, 221 | hashrateString: null 222 | }; 223 | } 224 | portalStats.algos[algo].hashrate += coinStats.hashrate; 225 | portalStats.algos[algo].workers += Object.keys(coinStats.workers).length; 226 | 227 | for (var worker in coinStats.workers) { 228 | coinStats.workers[worker].hashrateString = _this.getReadableHashRateString(shareMultiplier * coinStats.workers[worker].shares / portalConfig.website.stats.hashrateWindow); 229 | } 230 | 231 | delete coinStats.hashrates; 232 | delete coinStats.shares; 233 | coinStats.hashrateString = _this.getReadableHashRateString(coinStats.hashrate); 234 | }); 235 | 236 | Object.keys(portalStats.algos).forEach(function(algo){ 237 | var algoStats = portalStats.algos[algo]; 238 | algoStats.hashrateString = _this.getReadableHashRateString(algoStats.hashrate); 239 | }); 240 | 241 | _this.stats = portalStats; 242 | _this.statsString = JSON.stringify(portalStats); 243 | 244 | 245 | 246 | _this.statHistory.push(portalStats); 247 | addStatPoolHistory(portalStats); 248 | 249 | var retentionTime = (((Date.now() / 1000) - portalConfig.website.stats.historicalRetention) | 0); 250 | 251 | for (var i = 0; i < _this.statHistory.length; i++){ 252 | if (retentionTime < _this.statHistory[i].time){ 253 | if (i > 0) { 254 | _this.statHistory = _this.statHistory.slice(i); 255 | _this.statPoolHistory = _this.statPoolHistory.slice(i); 256 | } 257 | break; 258 | } 259 | } 260 | 261 | redisStats.multi([ 262 | ['zadd', 'statHistory', statGatherTime, _this.statsString], 263 | ['zremrangebyscore', 'statHistory', '-inf', '(' + retentionTime] 264 | ]).exec(function(err, replies){ 265 | if (err) 266 | logger.error(logSystem, 'Historics', 'Error adding stats to historics ' + JSON.stringify(err)); 267 | }); 268 | callback(); 269 | }); 270 | 271 | }; 272 | 273 | this.getReadableHashRateString = function(hashrate){ 274 | var i = -1; 275 | var byteUnits = [ ' KH', ' MH', ' GH', ' TH', ' PH' ]; 276 | do { 277 | hashrate = hashrate / 1000; 278 | i++; 279 | } while (hashrate > 1000); 280 | return hashrate.toFixed(2) + byteUnits[i]; 281 | }; 282 | 283 | }; 284 | -------------------------------------------------------------------------------- /libs/website.js: -------------------------------------------------------------------------------- 1 | 2 | var fs = require('fs'); 3 | var path = require('path'); 4 | 5 | var async = require('async'); 6 | var watch = require('node-watch'); 7 | var redis = require('redis'); 8 | 9 | var dot = require('dot'); 10 | var express = require('express'); 11 | var bodyParser = require('body-parser'); 12 | var compress = require('compression'); 13 | 14 | var Stratum = require('stratum-pool'); 15 | var util = require('stratum-pool/lib/util.js'); 16 | 17 | var api = require('./api.js'); 18 | 19 | 20 | module.exports = function(logger){ 21 | 22 | dot.templateSettings.strip = false; 23 | 24 | var portalConfig = JSON.parse(process.env.portalConfig); 25 | var poolConfigs = JSON.parse(process.env.pools); 26 | 27 | var websiteConfig = portalConfig.website; 28 | 29 | var portalApi = new api(logger, portalConfig, poolConfigs); 30 | var portalStats = portalApi.stats; 31 | 32 | var logSystem = 'Website'; 33 | 34 | 35 | var pageFiles = { 36 | 'index.html': 'index', 37 | 'home.html': '', 38 | 'getting_started.html': 'getting_started', 39 | 'stats.html': 'stats', 40 | 'tbs.html': 'tbs', 41 | 'workers.html': 'workers', 42 | 'api.html': 'api', 43 | 'admin.html': 'admin', 44 | 'mining_key.html': 'mining_key' 45 | }; 46 | 47 | var pageTemplates = {}; 48 | 49 | var pageProcessed = {}; 50 | var indexesProcessed = {}; 51 | 52 | var keyScriptTemplate = ''; 53 | var keyScriptProcessed = ''; 54 | 55 | 56 | var processTemplates = function(){ 57 | 58 | for (var pageName in pageTemplates){ 59 | if (pageName === 'index') continue; 60 | pageProcessed[pageName] = pageTemplates[pageName]({ 61 | poolsConfigs: poolConfigs, 62 | stats: portalStats.stats, 63 | portalConfig: portalConfig 64 | }); 65 | indexesProcessed[pageName] = pageTemplates.index({ 66 | page: pageProcessed[pageName], 67 | selected: pageName, 68 | stats: portalStats.stats, 69 | poolConfigs: poolConfigs, 70 | portalConfig: portalConfig 71 | }); 72 | } 73 | 74 | //logger.debug(logSystem, 'Stats', 'Website updated to latest stats'); 75 | }; 76 | 77 | 78 | 79 | var readPageFiles = function(files){ 80 | async.each(files, function(fileName, callback){ 81 | var filePath = 'website/' + (fileName === 'index.html' ? '' : 'pages/') + fileName; 82 | fs.readFile(filePath, 'utf8', function(err, data){ 83 | var pTemp = dot.template(data); 84 | pageTemplates[pageFiles[fileName]] = pTemp 85 | callback(); 86 | }); 87 | }, function(err){ 88 | if (err){ 89 | console.log('error reading files for creating dot templates: '+ JSON.stringify(err)); 90 | return; 91 | } 92 | processTemplates(); 93 | }); 94 | }; 95 | 96 | 97 | //If an html file was changed reload it 98 | watch('website', function(filename){ 99 | var basename = path.basename(filename); 100 | if (basename in pageFiles){ 101 | console.log(filename); 102 | readPageFiles([basename]); 103 | logger.debug(logSystem, 'Server', 'Reloaded file ' + basename); 104 | } 105 | }); 106 | 107 | portalStats.getGlobalStats(function(){ 108 | readPageFiles(Object.keys(pageFiles)); 109 | }); 110 | 111 | var buildUpdatedWebsite = function(){ 112 | portalStats.getGlobalStats(function(){ 113 | processTemplates(); 114 | 115 | var statData = 'data: ' + JSON.stringify(portalStats.stats) + '\n\n'; 116 | for (var uid in portalApi.liveStatConnections){ 117 | var res = portalApi.liveStatConnections[uid]; 118 | res.write(statData); 119 | } 120 | 121 | }); 122 | }; 123 | 124 | setInterval(buildUpdatedWebsite, websiteConfig.stats.updateInterval * 1000); 125 | 126 | 127 | var buildKeyScriptPage = function(){ 128 | async.waterfall([ 129 | function(callback){ 130 | var client = redis.createClient(portalConfig.redis.port, portalConfig.redis.host); 131 | client.hgetall('coinVersionBytes', function(err, coinBytes){ 132 | if (err){ 133 | client.quit(); 134 | return callback('Failed grabbing coin version bytes from redis ' + JSON.stringify(err)); 135 | } 136 | callback(null, client, coinBytes || {}); 137 | }); 138 | }, 139 | function (client, coinBytes, callback){ 140 | var enabledCoins = Object.keys(poolConfigs).map(function(c){return c.toLowerCase()}); 141 | var missingCoins = []; 142 | enabledCoins.forEach(function(c){ 143 | if (!(c in coinBytes)) 144 | missingCoins.push(c); 145 | }); 146 | callback(null, client, coinBytes, missingCoins); 147 | }, 148 | function(client, coinBytes, missingCoins, callback){ 149 | var coinsForRedis = {}; 150 | async.each(missingCoins, function(c, cback){ 151 | var coinInfo = (function(){ 152 | for (var pName in poolConfigs){ 153 | if (pName.toLowerCase() === c) 154 | return { 155 | daemon: poolConfigs[pName].paymentProcessing.daemon, 156 | address: poolConfigs[pName].address 157 | } 158 | } 159 | })(); 160 | var daemon = new Stratum.daemon.interface([coinInfo.daemon], function(severity, message){ 161 | logger[severity](logSystem, c, message); 162 | }); 163 | daemon.cmd('dumpprivkey', [coinInfo.address], function(result){ 164 | if (result[0].error){ 165 | logger.error(logSystem, c, 'Could not dumpprivkey for ' + c + ' ' + JSON.stringify(result[0].error)); 166 | cback(); 167 | return; 168 | } 169 | 170 | var vBytePub = util.getVersionByte(coinInfo.address)[0]; 171 | var vBytePriv = util.getVersionByte(result[0].response)[0]; 172 | 173 | coinBytes[c] = vBytePub.toString() + ',' + vBytePriv.toString(); 174 | coinsForRedis[c] = coinBytes[c]; 175 | cback(); 176 | }); 177 | }, function(err){ 178 | callback(null, client, coinBytes, coinsForRedis); 179 | }); 180 | }, 181 | function(client, coinBytes, coinsForRedis, callback){ 182 | if (Object.keys(coinsForRedis).length > 0){ 183 | client.hmset('coinVersionBytes', coinsForRedis, function(err){ 184 | if (err) 185 | logger.error(logSystem, 'Init', 'Failed inserting coin byte version into redis ' + JSON.stringify(err)); 186 | client.quit(); 187 | }); 188 | } 189 | else{ 190 | client.quit(); 191 | } 192 | callback(null, coinBytes); 193 | } 194 | ], function(err, coinBytes){ 195 | if (err){ 196 | logger.error(logSystem, 'Init', err); 197 | return; 198 | } 199 | try{ 200 | keyScriptTemplate = dot.template(fs.readFileSync('website/key.html', {encoding: 'utf8'})); 201 | keyScriptProcessed = keyScriptTemplate({coins: coinBytes}); 202 | } 203 | catch(e){ 204 | logger.error(logSystem, 'Init', 'Failed to read key.html file'); 205 | } 206 | }); 207 | 208 | }; 209 | buildKeyScriptPage(); 210 | 211 | var getPage = function(pageId){ 212 | if (pageId in pageProcessed){ 213 | var requestedPage = pageProcessed[pageId]; 214 | return requestedPage; 215 | } 216 | }; 217 | 218 | var route = function(req, res, next){ 219 | var pageId = req.params.page || ''; 220 | if (pageId in indexesProcessed){ 221 | res.header('Content-Type', 'text/html'); 222 | res.end(indexesProcessed[pageId]); 223 | } 224 | else 225 | next(); 226 | 227 | }; 228 | 229 | 230 | 231 | var app = express(); 232 | 233 | 234 | app.use(bodyParser.json()); 235 | 236 | app.get('/get_page', function(req, res, next){ 237 | var requestedPage = getPage(req.query.id); 238 | if (requestedPage){ 239 | res.end(requestedPage); 240 | return; 241 | } 242 | next(); 243 | }); 244 | 245 | app.get('/key.html', function(req, res, next){ 246 | res.end(keyScriptProcessed); 247 | }); 248 | 249 | app.get('/:page', route); 250 | app.get('/', route); 251 | 252 | app.get('/api/:method', function(req, res, next){ 253 | portalApi.handleApiRequest(req, res, next); 254 | }); 255 | 256 | app.post('/api/admin/:method', function(req, res, next){ 257 | if (portalConfig.website 258 | && portalConfig.website.adminCenter 259 | && portalConfig.website.adminCenter.enabled){ 260 | if (portalConfig.website.adminCenter.password === req.body.password) 261 | portalApi.handleAdminApiRequest(req, res, next); 262 | else 263 | res.send(401, JSON.stringify({error: 'Incorrect Password'})); 264 | 265 | } 266 | else 267 | next(); 268 | 269 | }); 270 | 271 | app.use(compress()); 272 | app.use('/static', express.static('website/static')); 273 | 274 | app.use(function(err, req, res, next){ 275 | console.error(err.stack); 276 | res.send(500, 'Something broke!'); 277 | }); 278 | 279 | try { 280 | app.listen(portalConfig.website.port, portalConfig.website.host, function () { 281 | logger.debug(logSystem, 'Server', 'Website started on ' + portalConfig.website.host + ':' + portalConfig.website.port); 282 | }); 283 | } 284 | catch(e){ 285 | logger.error(logSystem, 'Server', 'Could not start website on ' + portalConfig.website.host + ':' + portalConfig.website.port 286 | + ' - its either in use or you do not have permission'); 287 | } 288 | 289 | 290 | }; 291 | -------------------------------------------------------------------------------- /libs/workerapi.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var os = require('os'); 3 | 4 | 5 | function workerapi(listen) { 6 | var _this = this; 7 | var app = express(); 8 | var counters = { 9 | validShares : 0, 10 | validBlocks : 0, 11 | invalidShares : 0 12 | }; 13 | 14 | var lastEvents = { 15 | lastValidShare : 0 , 16 | lastValidBlock : 0, 17 | lastInvalidShare : 0 18 | }; 19 | 20 | app.get('/stats', function (req, res) { 21 | res.send({ 22 | "clients" : Object.keys(_this.poolObj.stratumServer.getStratumClients()).length, 23 | "counters" : counters, 24 | "lastEvents" : lastEvents 25 | }); 26 | }); 27 | 28 | 29 | this.start = function (poolObj) { 30 | this.poolObj = poolObj; 31 | this.poolObj.once('started', function () { 32 | app.listen(listen, function (lol) { 33 | console.log("LISTENING "); 34 | }); 35 | }) 36 | .on('share', function(isValidShare, isValidBlock, shareData) { 37 | var now = Date.now(); 38 | if (isValidShare) { 39 | counters.validShares ++; 40 | lastEvents.lastValidShare = now; 41 | if (isValidBlock) { 42 | counters.validBlocks ++; 43 | lastEvents.lastValidBlock = now; 44 | } 45 | } else { 46 | counters.invalidShares ++; 47 | lastEvents.lastInvalidShare = now; 48 | } 49 | }); 50 | } 51 | } 52 | 53 | 54 | 55 | module.exports = workerapi; 56 | 57 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-open-mining-portal", 3 | "version": "0.0.4", 4 | "description": "An extremely efficient, highly scalable, all-in-one, easy to setup cryptocurrency mining pool", 5 | "keywords": [ 6 | "stratum", 7 | "mining", 8 | "pool", 9 | "server", 10 | "poolserver", 11 | "bitcoin", 12 | "litecoin", 13 | "scrypt" 14 | ], 15 | "homepage": "https://github.com/zone117x/node-open-mining-portal", 16 | "bugs": { 17 | "url": "https://github.com/zone117x/node-open-mining-portal/issues" 18 | }, 19 | "license": "GPL-2.0", 20 | "author": "Matthew Little", 21 | "contributors": [ 22 | "vekexasia", 23 | "TheSeven" 24 | ], 25 | "main": "init.js", 26 | "bin": { 27 | "block-notify": "./scripts/blockNotify.js" 28 | }, 29 | "repository": { 30 | "type": "git", 31 | "url": "https://github.com/zone117x/node-open-mining-portal.git" 32 | }, 33 | "dependencies": { 34 | "stratum-pool": "https://github.com/zone117x/node-stratum-pool.git", 35 | "dateformat": "1.0.12", 36 | "node-json-minify": "*", 37 | "redis": "0.12.1", 38 | "mysql": "*", 39 | "async": "1.5.2", 40 | "express": "*", 41 | "body-parser": "*", 42 | "compression": "*", 43 | "dot": "*", 44 | "colors": "*", 45 | "node-watch": "0.5.9", 46 | "request": "2.69.0", 47 | "nonce": "*", 48 | "bignum": "0.13.1", 49 | "extend": "*" 50 | }, 51 | "engines": { 52 | "node": ">=0.10" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /pool_configs/litecoin_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "enabled": false, 3 | "coin": "litecoin.json", 4 | 5 | "address": "n4jSe18kZMCdGcZqaYprShXW6EH1wivUK1", 6 | 7 | "rewardRecipients": { 8 | "n37vuNFkXfk15uFnGoVyHZ6PYQxppD3QqK": 1.5, 9 | "22851477d63a085dbc2398c8430af1c09e7343f6": 0.1 10 | }, 11 | 12 | "paymentProcessing": { 13 | "enabled": true, 14 | "paymentInterval": 20, 15 | "minimumPayment": 70, 16 | "daemon": { 17 | "host": "127.0.0.1", 18 | "port": 19332, 19 | "user": "testuser", 20 | "password": "testpass" 21 | } 22 | }, 23 | 24 | "ports": { 25 | "3008": { 26 | "diff": 8 27 | }, 28 | "3032": { 29 | "diff": 32, 30 | "varDiff": { 31 | "minDiff": 8, 32 | "maxDiff": 512, 33 | "targetTime": 15, 34 | "retargetTime": 90, 35 | "variancePercent": 30 36 | } 37 | }, 38 | "3256": { 39 | "diff": 256 40 | } 41 | }, 42 | 43 | "daemons": [ 44 | { 45 | "host": "127.0.0.1", 46 | "port": 19332, 47 | "user": "testuser", 48 | "password": "testpass" 49 | } 50 | ], 51 | 52 | "p2p": { 53 | "enabled": true, 54 | "host": "127.0.0.1", 55 | "port": 19333, 56 | "disableTransactions": true 57 | }, 58 | 59 | "mposMode": { 60 | "enabled": false, 61 | "host": "127.0.0.1", 62 | "port": 3306, 63 | "user": "me", 64 | "password": "mypass", 65 | "database": "ltc", 66 | "checkPassword": true, 67 | "autoCreateWorker": false 68 | } 69 | 70 | } -------------------------------------------------------------------------------- /scripts/blocknotify.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | /* 10 | 11 | Contributed by Alex Petrov aka SysMan at sysman.net 12 | Updated by Alejandro Reyero - TodoJuegos.com 13 | 14 | Part of NOMP project 15 | Simple lightweight & fast - a more efficient block notify script in pure C. 16 | 17 | (may also work as coin switch) 18 | 19 | Platforms : Linux, BSD, Solaris (mostly OS independent) 20 | 21 | Build with: 22 | gcc blocknotify.c -o blocknotify 23 | 24 | 25 | Example usage in daemon coin.conf using default NOMP CLI port of 17117 26 | blocknotify="/bin/blocknotify 127.0.0.1:17117 dogecoin %s" 27 | 28 | 29 | 30 | */ 31 | 32 | 33 | int main(int argc, char **argv) 34 | { 35 | int sockfd,n; 36 | struct sockaddr_in servaddr, cliaddr; 37 | char sendline[1000]; 38 | char recvline[1000]; 39 | char host[200]; 40 | char *p, *arg, *errptr; 41 | int port; 42 | 43 | if (argc < 3) 44 | { 45 | // print help 46 | printf("NOMP pool block notify\n usage: \n"); 47 | exit(1); 48 | } 49 | 50 | strncpy(host, argv[1], (sizeof(host)-1)); 51 | p = host; 52 | 53 | if ( (arg = strchr(p,':')) ) 54 | { 55 | *arg = '\0'; 56 | 57 | errno = 0; // reset errno 58 | port = strtol(++arg, &errptr, 10); 59 | 60 | if ( (errno != 0) || (errptr == arg) ) 61 | { 62 | fprintf(stderr, "port number fail [%s]\n", errptr); 63 | } 64 | 65 | } 66 | 67 | snprintf(sendline, sizeof(sendline) - 1, "{\"command\":\"blocknotify\",\"params\":[\"%s\",\"%s\"]}\n", argv[2], argv[3]); 68 | 69 | sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 70 | bzero(&servaddr, sizeof(servaddr)); 71 | servaddr.sin_family = AF_INET; 72 | servaddr.sin_addr.s_addr = inet_addr(host); 73 | servaddr.sin_port = htons(port); 74 | connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); 75 | 76 | int result = send(sockfd, sendline, strlen(sendline), 0); 77 | close(sockfd); 78 | 79 | if(result == -1) { 80 | printf("Error sending: %i\n", errno); 81 | exit(-1); 82 | } 83 | exit(0); 84 | } 85 | -------------------------------------------------------------------------------- /scripts/cli.js: -------------------------------------------------------------------------------- 1 | var net = require('net'); 2 | 3 | var defaultPort = 17117; 4 | var defaultHost = '127.0.0.1'; 5 | 6 | var args = process.argv.slice(2); 7 | var params = []; 8 | var options = {}; 9 | 10 | for(var i = 0; i < args.length; i++){ 11 | if (args[i].indexOf('-') === 0 && args[i].indexOf('=') !== -1){ 12 | var s = args[i].substr(1).split('='); 13 | options[s[0]] = s[1]; 14 | } 15 | else 16 | params.push(args[i]); 17 | } 18 | 19 | var command = params.shift(); 20 | 21 | 22 | 23 | var client = net.connect(options.port || defaultPort, options.host || defaultHost, function () { 24 | client.write(JSON.stringify({ 25 | command: command, 26 | params: params, 27 | options: options 28 | }) + '\n'); 29 | }).on('error', function(error){ 30 | if (error.code === 'ECONNREFUSED') 31 | console.log('Could not connect to NOMP instance at ' + defaultHost + ':' + defaultPort); 32 | else 33 | console.log('Socket error ' + JSON.stringify(error)); 34 | }).on('data', function(data) { 35 | console.log(data.toString()); 36 | }).on('close', function () { 37 | }); -------------------------------------------------------------------------------- /website/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | NOMP 25 | 26 | 27 | 28 | 29 | 30 |
31 | 68 |
69 | 70 |
71 | {{=it.page}} 72 |
73 | 74 | 75 |
76 | 77 |
78 | This site is powered by the open source NOMP  79 | project created by Matthew Little and licensed under the GPL 80 |
81 |
82 |   Support this project by donating  BTC: 1KRotMnQpxu3sePQnsVLRy3EraRFYfJQFR 83 |
84 |
85 | Community  :  #nomp IRC 86 |   |   87 | /r/nomp 88 |   |   89 | 90 |
91 | 92 |
93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /website/pages/admin.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 18 | 19 |
20 |
21 | Password 22 | 23 | 24 | 25 | 28 | 29 | 30 |
31 |
32 | 33 |
34 | 35 |
36 | Administration 37 | 41 |
42 | 43 |
44 | 45 |
46 | 47 | 48 | 49 | 50 |
-------------------------------------------------------------------------------- /website/pages/api.html: -------------------------------------------------------------------------------- 1 |
2 | API Docs here 3 | 4 |
    5 |
  • /stats - raw json statistic
  • 6 |
  • /pool_stats - historical time per pool json
  • 7 |
  • /live_stats - live stats
  • 8 | 9 |
10 |
11 | -------------------------------------------------------------------------------- /website/pages/getting_started.html: -------------------------------------------------------------------------------- 1 | 182 | 183 |
184 | 185 | 228 | 229 | 249 |
250 | 251 | 252 | 253 | 267 | 268 | 319 | -------------------------------------------------------------------------------- /website/pages/home.html: -------------------------------------------------------------------------------- 1 | 75 | 76 | 77 |
78 |
79 | 80 |
81 |
82 |
Welcome to the future of mining
83 |
    84 |
  • Low fees
  • 85 |
  • High performance Node.js backend
  • 86 |
  • User friendly mining client
  • 87 |
  • Multi-coin / multi-pool
  • 88 |
89 |
90 |
91 | 92 |
93 | 94 |
95 |
96 |
Global Stats
97 |
98 | {{ for(var algo in it.stats.algos) { }} 99 |
100 |
{{=algo}}
101 |
{{=it.stats.algos[algo].workers}} Miners
102 |
{{=it.stats.algos[algo].hashrateString}}
103 |
104 | {{ } }} 105 |
106 |
107 |
108 | 109 |
110 |
111 |
Pools / Coins
112 |
113 | {{ for(var pool in it.stats.pools) { }} 114 |
115 |
{{=pool}}
116 |
{{=it.stats.pools[pool].workerCount}} Miners
117 |
{{=it.stats.pools[pool].hashrateString}}
118 |
119 | {{ } }} 120 |
121 |
122 |
123 | 124 |
125 | 126 | -------------------------------------------------------------------------------- /website/pages/mining_key.html: -------------------------------------------------------------------------------- 1 | 13 | 14 |
15 | 16 |

17 | This script run client-side (in your browser). For maximum security download the script and run it locally and 18 | offline in a modern web browser. 19 |

20 | 21 | 24 | 25 |
26 | -------------------------------------------------------------------------------- /website/pages/stats.html: -------------------------------------------------------------------------------- 1 | 30 | 31 |
32 | 33 |
34 |
Workers Per Pool
35 |
36 |
37 | 38 |
39 |
Hashrate Per Pool
40 |
41 |
42 | 43 |
44 |
Blocks Pending Per Pool
45 |
46 |
47 | 48 |
49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /website/pages/tbs.html: -------------------------------------------------------------------------------- 1 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | {{ for(var pool in it.stats.pools) { }} 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | {{ } }} 64 |
PoolAlgoWorkersValid SharesInvalid SharesTotal BlocksPendingConfirmedOrphanedHashrate
{{=it.stats.pools[pool].name}}{{=it.stats.pools[pool].algorithm}}{{=Object.keys(it.stats.pools[pool].workers).length}}{{=it.stats.pools[pool].poolStats.validShares}}{{=it.stats.pools[pool].poolStats.invalidShares}}{{=it.stats.pools[pool].poolStats.validBlocks}}{{=it.stats.pools[pool].blocks.pending}}{{=it.stats.pools[pool].blocks.confirmed}}{{=it.stats.pools[pool].blocks.orphaned}}{{=it.stats.pools[pool].hashrateString}}
65 | -------------------------------------------------------------------------------- /website/pages/workers.html: -------------------------------------------------------------------------------- 1 | 34 | 35 | 36 |
37 | 38 | {{ for(var pool in it.stats.pools) { }} 39 | 40 |
41 |
{{=it.stats.pools[pool].name}}
42 |
43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | {{ for(var worker in it.stats.pools[pool].workers) { }} 54 | {{var workerstat = it.stats.pools[pool].workers[worker];}} 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | {{ } }} 63 |
AddressSharesInvalid sharesEfficiencyHashrate
{{=worker}}{{=Math.floor(workerstat.shares)}}{{=Math.floor(workerstat.invalidshares)}}{{? workerstat.shares > 0}} {{=Math.floor(10000 * workerstat.shares / (workerstat.shares + workerstat.invalidshares)) / 100}}% {{??}} 0% {{?}}{{=workerstat.hashrateString}}
64 |
65 |
66 | {{ } }} 67 | -------------------------------------------------------------------------------- /website/static/admin.js: -------------------------------------------------------------------------------- 1 | var docCookies = { 2 | getItem: function (sKey) { 3 | return decodeURIComponent(document.cookie.replace(new RegExp("(?:(?:^|.*;)\\s*" + encodeURIComponent(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=\\s*([^;]*).*$)|^.*$"), "$1")) || null; 4 | }, 5 | setItem: function (sKey, sValue, vEnd, sPath, sDomain, bSecure) { 6 | if (!sKey || /^(?:expires|max\-age|path|domain|secure)$/i.test(sKey)) { return false; } 7 | var sExpires = ""; 8 | if (vEnd) { 9 | switch (vEnd.constructor) { 10 | case Number: 11 | sExpires = vEnd === Infinity ? "; expires=Fri, 31 Dec 9999 23:59:59 GMT" : "; max-age=" + vEnd; 12 | break; 13 | case String: 14 | sExpires = "; expires=" + vEnd; 15 | break; 16 | case Date: 17 | sExpires = "; expires=" + vEnd.toUTCString(); 18 | break; 19 | } 20 | } 21 | document.cookie = encodeURIComponent(sKey) + "=" + encodeURIComponent(sValue) + sExpires + (sDomain ? "; domain=" + sDomain : "") + (sPath ? "; path=" + sPath : "") + (bSecure ? "; secure" : ""); 22 | return true; 23 | }, 24 | removeItem: function (sKey, sPath, sDomain) { 25 | if (!sKey || !this.hasItem(sKey)) { return false; } 26 | document.cookie = encodeURIComponent(sKey) + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT" + ( sDomain ? "; domain=" + sDomain : "") + ( sPath ? "; path=" + sPath : ""); 27 | return true; 28 | }, 29 | hasItem: function (sKey) { 30 | return (new RegExp("(?:^|;\\s*)" + encodeURIComponent(sKey).replace(/[\-\.\+\*]/g, "\\$&") + "\\s*\\=")).test(document.cookie); 31 | } 32 | }; 33 | 34 | var password = docCookies.getItem('password'); 35 | 36 | 37 | function showLogin(){ 38 | $('#adminCenter').hide(); 39 | $('#passwordForm').show(); 40 | } 41 | 42 | function showAdminCenter(){ 43 | $('#passwordForm').hide(); 44 | $('#adminCenter').show(); 45 | } 46 | 47 | function tryLogin(){ 48 | apiRequest('pools', {}, function(response){ 49 | showAdminCenter(); 50 | displayMenu(response.result) 51 | }); 52 | } 53 | 54 | function displayMenu(pools){ 55 | $('#poolList').after(Object.keys(pools).map(function(poolName){ 56 | return '
  • ' + poolName + '
  • '; 57 | }).join('')); 58 | } 59 | 60 | function apiRequest(func, data, callback){ 61 | var httpRequest = new XMLHttpRequest(); 62 | httpRequest.onreadystatechange = function(){ 63 | if (httpRequest.readyState === 4 && httpRequest.responseText){ 64 | if (httpRequest.status === 401){ 65 | docCookies.removeItem('password'); 66 | $('#password').val(''); 67 | showLogin(); 68 | alert('Incorrect Password'); 69 | } 70 | else{ 71 | var response = JSON.parse(httpRequest.responseText); 72 | callback(response); 73 | } 74 | } 75 | }; 76 | httpRequest.open('POST', '/api/admin/' + func); 77 | data.password = password; 78 | httpRequest.setRequestHeader('Content-Type', 'application/json'); 79 | httpRequest.send(JSON.stringify(data)); 80 | } 81 | 82 | if (password){ 83 | tryLogin(); 84 | } 85 | else{ 86 | showLogin(); 87 | } 88 | 89 | $('#passwordForm').submit(function(event){ 90 | event.preventDefault(); 91 | password = $('#password').val(); 92 | if (password){ 93 | if ($('#remember').is(':checked')) 94 | docCookies.setItem('password', password, Infinity); 95 | else 96 | docCookies.setItem('password', password); 97 | tryLogin(); 98 | } 99 | return false; 100 | }); 101 | -------------------------------------------------------------------------------- /website/static/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zone117x/node-open-mining-portal/b1b4daaa76d1ca98b29be056c710222518e7ac72/website/static/favicon.png -------------------------------------------------------------------------------- /website/static/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /website/static/main.js: -------------------------------------------------------------------------------- 1 | $(function(){ 2 | 3 | var hotSwap = function(page, pushSate){ 4 | if (pushSate) history.pushState(null, null, '/' + page); 5 | $('.pure-menu-selected').removeClass('pure-menu-selected'); 6 | $('a[href="/' + page + '"]').parent().addClass('pure-menu-selected'); 7 | $.get("/get_page", {id: page}, function(data){ 8 | $('main').html(data); 9 | }, 'html') 10 | }; 11 | 12 | $('.hot-swapper').click(function(event){ 13 | if (event.which !== 1) return; 14 | var pageId = $(this).attr('href').slice(1); 15 | hotSwap(pageId, true); 16 | event.preventDefault(); 17 | return false; 18 | }); 19 | 20 | window.addEventListener('load', function() { 21 | setTimeout(function() { 22 | window.addEventListener("popstate", function(e) { 23 | hotSwap(location.pathname.slice(1)); 24 | }); 25 | }, 0); 26 | }); 27 | 28 | window.statsSource = new EventSource("/api/live_stats"); 29 | 30 | }); 31 | -------------------------------------------------------------------------------- /website/static/nvd3.css: -------------------------------------------------------------------------------- 1 | .chartWrap{margin:0;padding:0;overflow:hidden}.nvtooltip.with-3d-shadow,.with-3d-shadow .nvtooltip{-moz-box-shadow:0 5px 10px rgba(0,0,0,.2);-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.nvtooltip{position:absolute;background-color:rgba(255,255,255,1);padding:1px;border:1px solid rgba(0,0,0,.2);z-index:10000;font-family:Arial;font-size:13px;text-align:left;pointer-events:none;white-space:nowrap;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.nvtooltip.with-transitions,.with-transitions .nvtooltip{transition:opacity 250ms linear;-moz-transition:opacity 250ms linear;-webkit-transition:opacity 250ms linear;transition-delay:250ms;-moz-transition-delay:250ms;-webkit-transition-delay:250ms}.nvtooltip.x-nvtooltip,.nvtooltip.y-nvtooltip{padding:8px}.nvtooltip h3{margin:0;padding:4px 14px;line-height:18px;font-weight:400;background-color:rgba(247,247,247,.75);text-align:center;border-bottom:1px solid #ebebeb;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.nvtooltip p{margin:0;padding:5px 14px;text-align:center}.nvtooltip span{display:inline-block;margin:2px 0}.nvtooltip table{margin:6px;border-spacing:0}.nvtooltip table td{padding:2px 9px 2px 0;vertical-align:middle}.nvtooltip table td.key{font-weight:400}.nvtooltip table td.value{text-align:right;font-weight:700}.nvtooltip table tr.highlight td{padding:1px 9px 1px 0;border-bottom-style:solid;border-bottom-width:1px;border-top-style:solid;border-top-width:1px}.nvtooltip table td.legend-color-guide div{width:8px;height:8px;vertical-align:middle}.nvtooltip .footer{padding:3px;text-align:center}.nvtooltip-pending-removal{position:absolute;pointer-events:none}svg{-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;display:block;width:100%;height:100%}svg text{font:400 12px Arial}svg .title{font:700 14px Arial}.nvd3 .nv-background{fill:#fff;fill-opacity:0}.nvd3.nv-noData{font-size:18px;font-weight:700}.nv-brush .extent{fill-opacity:.125;shape-rendering:crispEdges}.nvd3 .nv-legend .nv-series{cursor:pointer}.nvd3 .nv-legend .disabled circle{fill-opacity:0}.nvd3 .nv-axis{pointer-events:none}.nvd3 .nv-axis path{fill:none;stroke:#000;stroke-opacity:.75;shape-rendering:crispEdges}.nvd3 .nv-axis path.domain{stroke-opacity:.75}.nvd3 .nv-axis.nv-x path.domain{stroke-opacity:0}.nvd3 .nv-axis line{fill:none;stroke:#e5e5e5;shape-rendering:crispEdges}.nvd3 .nv-axis .zero line,.nvd3 .nv-axis line.zero{stroke-opacity:.75}.nvd3 .nv-axis .nv-axisMaxMin text{font-weight:700}.nvd3 .x .nv-axis .nv-axisMaxMin text,.nvd3 .x2 .nv-axis .nv-axisMaxMin text,.nvd3 .x3 .nv-axis .nv-axisMaxMin text{text-anchor:middle}.nv-brush .resize path{fill:#eee;stroke:#666}.nvd3 .nv-bars .negative rect{zfill:brown}.nvd3 .nv-bars rect{zfill:#4682b4;fill-opacity:.75;transition:fill-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear}.nvd3 .nv-bars rect.hover{fill-opacity:1}.nvd3 .nv-bars .hover rect{fill:#add8e6}.nvd3 .nv-bars text{fill:rgba(0,0,0,0)}.nvd3 .nv-bars .hover text{fill:rgba(0,0,0,1)}.nvd3 .nv-multibar .nv-groups rect,.nvd3 .nv-multibarHorizontal .nv-groups rect,.nvd3 .nv-discretebar .nv-groups rect{stroke-opacity:0;transition:fill-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear}.nvd3 .nv-multibar .nv-groups rect:hover,.nvd3 .nv-multibarHorizontal .nv-groups rect:hover,.nvd3 .nv-discretebar .nv-groups rect:hover{fill-opacity:1}.nvd3 .nv-discretebar .nv-groups text,.nvd3 .nv-multibarHorizontal .nv-groups text{font-weight:700;fill:rgba(0,0,0,1);stroke:rgba(0,0,0,0)}.nvd3.nv-pie path{stroke-opacity:0;transition:fill-opacity 250ms linear,stroke-width 250ms linear,stroke-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear,stroke-width 250ms linear,stroke-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear,stroke-width 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-pie .nv-slice text{stroke:#000;stroke-width:0}.nvd3.nv-pie path{stroke:#fff;stroke-width:1px;stroke-opacity:1}.nvd3.nv-pie .hover path{fill-opacity:.7}.nvd3.nv-pie .nv-label{pointer-events:none}.nvd3.nv-pie .nv-label rect{fill-opacity:0;stroke-opacity:0}.nvd3 .nv-groups path.nv-line{fill:none;stroke-width:1.5px}.nvd3 .nv-groups path.nv-line.nv-thin-line{stroke-width:1px}.nvd3 .nv-groups path.nv-area{stroke:none}.nvd3 .nv-line.hover path{stroke-width:6px}.nvd3.nv-line .nvd3.nv-scatter .nv-groups .nv-point{fill-opacity:0;stroke-opacity:0}.nvd3.nv-scatter.nv-single-point .nv-groups .nv-point{fill-opacity:.5!important;stroke-opacity:.5!important}.with-transitions .nvd3 .nv-groups .nv-point{transition:stroke-width 250ms linear,stroke-opacity 250ms linear;-moz-transition:stroke-width 250ms linear,stroke-opacity 250ms linear;-webkit-transition:stroke-width 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-scatter .nv-groups .nv-point.hover,.nvd3 .nv-groups .nv-point.hover{stroke-width:7px;fill-opacity:.95!important;stroke-opacity:.95!important}.nvd3 .nv-point-paths path{stroke:#aaa;stroke-opacity:0;fill:#eee;fill-opacity:0}.nvd3 .nv-indexLine{cursor:ew-resize}.nvd3 .nv-distribution{pointer-events:none}.nvd3 .nv-groups .nv-point.hover{stroke-width:20px;stroke-opacity:.5}.nvd3 .nv-scatter .nv-point.hover{fill-opacity:1}.nvd3.nv-stackedarea path.nv-area{fill-opacity:.7;stroke-opacity:0;transition:fill-opacity 250ms linear,stroke-opacity 250ms linear;-moz-transition:fill-opacity 250ms linear,stroke-opacity 250ms linear;-webkit-transition:fill-opacity 250ms linear,stroke-opacity 250ms linear}.nvd3.nv-stackedarea path.nv-area.hover{fill-opacity:.9}.nvd3.nv-stackedarea .nv-groups .nv-point{stroke-opacity:0;fill-opacity:0}.nvd3.nv-linePlusBar .nv-bar rect{fill-opacity:.75}.nvd3.nv-linePlusBar .nv-bar rect:hover{fill-opacity:1}.nvd3.nv-bullet{font:10px sans-serif}.nvd3.nv-bullet .nv-measure{fill-opacity:.8}.nvd3.nv-bullet .nv-measure:hover{fill-opacity:1}.nvd3.nv-bullet .nv-marker{stroke:#000;stroke-width:2px}.nvd3.nv-bullet .nv-markerTriangle{stroke:#000;fill:#fff;stroke-width:1.5px}.nvd3.nv-bullet .nv-tick line{stroke:#666;stroke-width:.5px}.nvd3.nv-bullet .nv-range.nv-s0{fill:#eee}.nvd3.nv-bullet .nv-range.nv-s1{fill:#ddd}.nvd3.nv-bullet .nv-range.nv-s2{fill:#ccc}.nvd3.nv-bullet .nv-title{font-size:14px;font-weight:700}.nvd3.nv-bullet .nv-subtitle{fill:#999}.nvd3.nv-bullet .nv-range{fill:#bababa;fill-opacity:.4}.nvd3.nv-bullet .nv-range:hover{fill-opacity:.7}.nvd3.nv-sparkline path{fill:none}.nvd3.nv-sparklineplus g.nv-hoverValue{pointer-events:none}.nvd3.nv-sparklineplus .nv-hoverValue line{stroke:#333;stroke-width:1.5px}.nvd3.nv-sparklineplus,.nvd3.nv-sparklineplus g{pointer-events:all}.nvd3 .nv-hoverArea{fill-opacity:0;stroke-opacity:0}.nvd3.nv-sparklineplus .nv-xValue,.nvd3.nv-sparklineplus .nv-yValue{stroke-width:0;font-size:.9em;font-weight:400}.nvd3.nv-sparklineplus .nv-yValue{stroke:#f66}.nvd3.nv-sparklineplus .nv-maxValue{stroke:#2ca02c;fill:#2ca02c}.nvd3.nv-sparklineplus .nv-minValue{stroke:#d62728;fill:#d62728}.nvd3.nv-sparklineplus .nv-currentValue{font-weight:700;font-size:1.1em}.nvd3.nv-ohlcBar .nv-ticks .nv-tick{stroke-width:2px}.nvd3.nv-ohlcBar .nv-ticks .nv-tick.hover{stroke-width:4px}.nvd3.nv-ohlcBar .nv-ticks .nv-tick.positive{stroke:#2ca02c}.nvd3.nv-ohlcBar .nv-ticks .nv-tick.negative{stroke:#d62728}.nvd3.nv-historicalStockChart .nv-axis .nv-axislabel{font-weight:700}.nvd3.nv-historicalStockChart .nv-dragTarget{fill-opacity:0;stroke:none;cursor:move}.nvd3 .nv-brush .extent{fill-opacity:0!important}.nvd3 .nv-brushBackground rect{stroke:#000;stroke-width:.4;fill:#fff;fill-opacity:.7}.nvd3.nv-indentedtree .name{margin-left:5px}.nvd3.nv-indentedtree .clickable{color:#08C;cursor:pointer}.nvd3.nv-indentedtree span.clickable:hover{color:#005580;text-decoration:underline}.nvd3.nv-indentedtree .nv-childrenCount{display:inline-block;margin-left:5px}.nvd3.nv-indentedtree .nv-treeicon{cursor:pointer}.nvd3.nv-indentedtree .nv-treeicon.nv-folded{cursor:pointer}.nvd3 .background path{fill:none;stroke:#ccc;stroke-opacity:.4;shape-rendering:crispEdges}.nvd3 .foreground path{fill:none;stroke:#4682b4;stroke-opacity:.7}.nvd3 .brush .extent{fill-opacity:.3;stroke:#fff;shape-rendering:crispEdges}.nvd3 .axis line,.axis path{fill:none;stroke:#000;shape-rendering:crispEdges}.nvd3 .axis text{text-shadow:0 1px 0 #fff}.nvd3 .nv-interactiveGuideLine{pointer-events:none}.nvd3 line.nv-guideline{stroke:#ccc} -------------------------------------------------------------------------------- /website/static/stats.js: -------------------------------------------------------------------------------- 1 | var poolWorkerData; 2 | var poolHashrateData; 3 | var poolBlockData; 4 | 5 | var poolWorkerChart; 6 | var poolHashrateChart; 7 | var poolBlockChart; 8 | 9 | var statData; 10 | var poolKeys; 11 | 12 | function buildChartData(){ 13 | 14 | var pools = {}; 15 | 16 | poolKeys = []; 17 | for (var i = 0; i < statData.length; i++){ 18 | for (var pool in statData[i].pools){ 19 | if (poolKeys.indexOf(pool) === -1) 20 | poolKeys.push(pool); 21 | } 22 | } 23 | 24 | 25 | for (var i = 0; i < statData.length; i++){ 26 | 27 | var time = statData[i].time * 1000; 28 | 29 | for (var f = 0; f < poolKeys.length; f++){ 30 | var pName = poolKeys[f]; 31 | var a = pools[pName] = (pools[pName] || { 32 | hashrate: [], 33 | workers: [], 34 | blocks: [] 35 | }); 36 | if (pName in statData[i].pools){ 37 | a.hashrate.push([time, statData[i].pools[pName].hashrate]); 38 | a.workers.push([time, statData[i].pools[pName].workerCount]); 39 | a.blocks.push([time, statData[i].pools[pName].blocks.pending]) 40 | } 41 | else{ 42 | a.hashrate.push([time, 0]); 43 | a.workers.push([time, 0]); 44 | a.blocks.push([time, 0]) 45 | } 46 | 47 | } 48 | 49 | } 50 | 51 | poolWorkerData = []; 52 | poolHashrateData = []; 53 | poolBlockData = []; 54 | 55 | for (var pool in pools){ 56 | poolWorkerData.push({ 57 | key: pool, 58 | values: pools[pool].workers 59 | }); 60 | poolHashrateData.push({ 61 | key: pool, 62 | values: pools[pool].hashrate 63 | }); 64 | poolBlockData.push({ 65 | key: pool, 66 | values: pools[pool].blocks 67 | }) 68 | } 69 | } 70 | 71 | function getReadableHashRateString(hashrate){ 72 | var i = -1; 73 | var byteUnits = [ ' KH', ' MH', ' GH', ' TH', ' PH' ]; 74 | do { 75 | hashrate = hashrate / 1024; 76 | i++; 77 | } while (hashrate > 1024); 78 | return Math.round(hashrate) + byteUnits[i]; 79 | } 80 | 81 | function timeOfDayFormat(timestamp){ 82 | var dStr = d3.time.format('%I:%M %p')(new Date(timestamp)); 83 | if (dStr.indexOf('0') === 0) dStr = dStr.slice(1); 84 | return dStr; 85 | } 86 | 87 | function displayCharts(){ 88 | 89 | nv.addGraph(function() { 90 | poolWorkerChart = nv.models.stackedAreaChart() 91 | .margin({left: 40, right: 40}) 92 | .x(function(d){ return d[0] }) 93 | .y(function(d){ return d[1] }) 94 | .useInteractiveGuideline(true) 95 | .clipEdge(true); 96 | 97 | poolWorkerChart.xAxis.tickFormat(timeOfDayFormat); 98 | 99 | poolWorkerChart.yAxis.tickFormat(d3.format('d')); 100 | 101 | d3.select('#poolWorkers').datum(poolWorkerData).call(poolWorkerChart); 102 | 103 | return poolWorkerChart; 104 | }); 105 | 106 | 107 | nv.addGraph(function() { 108 | poolHashrateChart = nv.models.lineChart() 109 | .margin({left: 60, right: 40}) 110 | .x(function(d){ return d[0] }) 111 | .y(function(d){ return d[1] }) 112 | .useInteractiveGuideline(true); 113 | 114 | poolHashrateChart.xAxis.tickFormat(timeOfDayFormat); 115 | 116 | poolHashrateChart.yAxis.tickFormat(function(d){ 117 | return getReadableHashRateString(d); 118 | }); 119 | 120 | d3.select('#poolHashrate').datum(poolHashrateData).call(poolHashrateChart); 121 | 122 | return poolHashrateChart; 123 | }); 124 | 125 | 126 | nv.addGraph(function() { 127 | poolBlockChart = nv.models.multiBarChart() 128 | .x(function(d){ return d[0] }) 129 | .y(function(d){ return d[1] }); 130 | 131 | poolBlockChart.xAxis.tickFormat(timeOfDayFormat); 132 | 133 | poolBlockChart.yAxis.tickFormat(d3.format('d')); 134 | 135 | d3.select('#poolBlocks').datum(poolBlockData).call(poolBlockChart); 136 | 137 | return poolBlockChart; 138 | }); 139 | } 140 | 141 | function TriggerChartUpdates(){ 142 | poolWorkerChart.update(); 143 | poolHashrateChart.update(); 144 | poolBlockChart.update(); 145 | } 146 | 147 | nv.utils.windowResize(TriggerChartUpdates); 148 | 149 | $.getJSON('/api/pool_stats', function(data){ 150 | statData = data; 151 | buildChartData(); 152 | displayCharts(); 153 | }); 154 | 155 | statsSource.addEventListener('message', function(e){ 156 | var stats = JSON.parse(e.data); 157 | statData.push(stats); 158 | 159 | 160 | var newPoolAdded = (function(){ 161 | for (var p in stats.pools){ 162 | if (poolKeys.indexOf(p) === -1) 163 | return true; 164 | } 165 | return false; 166 | })(); 167 | 168 | if (newPoolAdded || Object.keys(stats.pools).length > poolKeys.length){ 169 | buildChartData(); 170 | displayCharts(); 171 | } 172 | else { 173 | var time = stats.time * 1000; 174 | for (var f = 0; f < poolKeys.length; f++) { 175 | var pool = poolKeys[f]; 176 | for (var i = 0; i < poolWorkerData.length; i++) { 177 | if (poolWorkerData[i].key === pool) { 178 | poolWorkerData[i].values.shift(); 179 | poolWorkerData[i].values.push([time, pool in stats.pools ? stats.pools[pool].workerCount : 0]); 180 | break; 181 | } 182 | } 183 | for (var i = 0; i < poolHashrateData.length; i++) { 184 | if (poolHashrateData[i].key === pool) { 185 | poolHashrateData[i].values.shift(); 186 | poolHashrateData[i].values.push([time, pool in stats.pools ? stats.pools[pool].hashrate : 0]); 187 | break; 188 | } 189 | } 190 | for (var i = 0; i < poolBlockData.length; i++) { 191 | if (poolBlockData[i].key === pool) { 192 | poolBlockData[i].values.shift(); 193 | poolBlockData[i].values.push([time, pool in stats.pools ? stats.pools[pool].blocks.pending : 0]); 194 | break; 195 | } 196 | } 197 | } 198 | TriggerChartUpdates(); 199 | } 200 | 201 | 202 | }); -------------------------------------------------------------------------------- /website/static/style.css: -------------------------------------------------------------------------------- 1 | html, button, input, select, textarea, .pure-g [class *= "pure-u"], .pure-g-r [class *= "pure-u"]{ 2 | font-family: 'Open Sans', sans-serif; 3 | } 4 | 5 | html{ 6 | background: #2d2d2d; 7 | overflow-y: scroll; 8 | } 9 | 10 | body{ 11 | display: flex; 12 | flex-direction: column; 13 | max-width: 1160px; 14 | margin: 0 auto; 15 | } 16 | 17 | header > .home-menu{ 18 | background: inherit !important; 19 | height: 54px; 20 | display: flex; 21 | } 22 | 23 | header > .home-menu > a.pure-menu-heading, header > .home-menu > ul, header > .home-menu > ul > li{ 24 | display: flex !important; 25 | align-items: center; 26 | justify-content: center; 27 | line-height: normal !important; 28 | } 29 | 30 | header > .home-menu > a.pure-menu-heading{ 31 | color: white; 32 | font-size: 1.5em; 33 | } 34 | 35 | header > .home-menu > ul > li > a{ 36 | color: #ced4d9; 37 | } 38 | 39 | header > .home-menu > ul > li > a:hover, header > .home-menu > ul > li > a:focus{ 40 | background: inherit !important; 41 | } 42 | 43 | header > .home-menu > ul > li > a:hover, header > .home-menu > ul > li.pure-menu-selected > a{ 44 | color: white; 45 | } 46 | 47 | main{ 48 | background-color: #ebf4fa; 49 | position: relative; 50 | } 51 | 52 | footer{ 53 | text-align: center; 54 | color: #b3b3b3; 55 | text-decoration: none; 56 | font-size: 0.8em; 57 | padding: 15px; 58 | line-height: 24px; 59 | } 60 | 61 | footer a{ 62 | color: #fff; 63 | text-decoration: none; 64 | } 65 | 66 | footer iframe{ 67 | vertical-align: middle; 68 | } --------------------------------------------------------------------------------