├── .gitignore ├── LICENSE.txt ├── README.md ├── bot18.config-sample.js ├── bot18.config-sample.js.hbs ├── bot18.defaults.json ├── bot18.sh ├── gui ├── _codemap.js ├── conf │ └── _codemap.js ├── controllers │ ├── _codemap.js │ ├── auth.js │ ├── dashboard.js │ ├── home.js │ ├── users.js │ └── webhooks.js ├── db │ ├── _codemap.js │ ├── users.js │ └── webhooks.js ├── hooks │ └── _codemap.js ├── lib │ ├── _codemap.js │ ├── console.js │ └── uuid.js ├── middleware │ ├── _codemap.js │ ├── flash.js │ ├── raw_body.js │ ├── redirect.js │ └── vars.js ├── public │ ├── 404.html │ ├── assets │ │ ├── bg.png │ │ ├── bot18_big_transparent.png │ │ ├── bot18_flag.png │ │ ├── bot18_flag_transparent.png │ │ ├── bot18_hoodie.png │ │ ├── bot18_icon.png │ │ ├── bot18_icon_alt.png │ │ ├── bot18_jersey.png │ │ ├── bot18_jersey2.png │ │ ├── bot18_jersey3.png │ │ ├── bot18_logo.png │ │ ├── bot18_logo_light.png │ │ ├── bot18_logo_transparent2.png │ │ ├── bot18_mug.png │ │ ├── bot18_mug2_tpl.png │ │ ├── bot18_mug_tpl.png │ │ └── bot18_shirt.png │ ├── cryptagon │ │ ├── Your Portfolio _ Cryptagon_files │ │ │ ├── analytics.js │ │ │ ├── frame.1ef16f8b.js │ │ │ ├── main.3d7c04de.css │ │ │ ├── main.486e8019.js │ │ │ ├── polyfills.ff0bf707.js │ │ │ ├── saved_resource(1).html │ │ │ ├── saved_resource.html │ │ │ ├── vendor.35af3dcdecd6ad46c4fb.js │ │ │ └── vzl82zgq │ │ └── index.html │ ├── favicon.ico │ ├── robots.txt │ └── style.css ├── vendor │ └── _codemap.js └── views │ ├── _partials │ └── nav.hbs │ ├── auth │ ├── login.hbs │ └── register.hbs │ ├── dashboard │ └── index.hbs │ ├── home.hbs │ ├── layout.hbs │ └── users │ └── list.hbs ├── launcher ├── get-auth.js ├── get-conf.js ├── get-engine.js ├── get-lib.js ├── get-mongo.js ├── get-wallet.js ├── parse-argv.js ├── print-motd.js └── save-conf.js ├── lib └── mr.js ├── motd.txt ├── package-lock.json ├── package.json ├── snap ├── .snapcraft │ └── state └── snapcraft.yaml └── strats └── orderbook_power_imbalance └── bot18.strat.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.snap 2 | stage/* 3 | prime/* 4 | *.tar.bz2 5 | parts/* 6 | .DS_Store 7 | bot18.config.js 8 | node_modules 9 | .tHe-mAn-bEhInD-tHe-cUrTaIn 10 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | This package, `bot18` on NPM, represents the USER portion 2 | of the Bot18 Client. This code is licensed under the MIT 3 | License. Open-source contributions are welcomed for 4 | trading exchange support code, GUI improvements, bugfixes, 5 | and minor features. 6 | 7 | The ENGINE portion of the Bot18 Client is dynamically linked 8 | from bot18.net, and is closed-source. Use of the ENGINE 9 | requires an Unlock Code, purchasable for $49.99 (for a 10 | limited time!) at https://bot18.net/beta 11 | 12 | For details, see: https://bot18.net/licensing 13 | 14 | ### MIT License, applies to Bot18 Client - USER (this package) 15 | 16 | (c) 2018 Carlos Rodriguez 17 | 18 | Permission is hereby granted, free of charge, to any person obtaining a copy 19 | of this software and associated documentation files (the "Bot18 Client - USER"), 20 | to deal in the Software without restriction, including without limitation the 21 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 22 | copies of the Software, and to permit persons to whom the Software is furnished 23 | to do so, subject to the following conditions: 24 | 25 | The above copyright notice and this permission notice shall be included in 26 | all copies or substantial portions of the Software. 27 | 28 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 29 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 30 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 31 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 32 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 33 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 34 | SOFTWARE. 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Bot18](https://bot18.net/assets/bot18_logo_light.png) 2 | 3 | Bot18 is a high-frequency cryptocurrency trading bot developed by [Zenbot](https://github.com/DeviaVir/zenbot) creator [carlos8f](https://github.com/carlos8f). More information available from the [bot18.net website](https://bot18.net/). 4 | 5 | ## BTW, This Is A Beta. 6 | 7 | Keep in mind this is the BETA RELEASE. I will be constantly adding/changing stuff. Expect things to be broken, unfinished, and inconsistent. Live trading is discouraged unless you're just playing around with small amounts of currency, or really know what you're doing. 8 | 9 | Soon I will develop a roadmap for Beta -> Stable progression, Beta testing rules/instructions, and community tools. 10 | 11 | When the project stabilizes, I will conduct a poll for all Beta testers. If the majority feel it's ready for release, I will release the Stable version, end the Beta program, and raise the Unlock Code prices to $99.99 for Single-IP and $499.99 for Unlimited-IP. All Unlock Codes purchased during the Beta period will work with the Stable version, and forever after. 12 | 13 | Please be patient and stay tuned! 14 | 15 | ## We Are Online. 16 | 17 | Bot18 is "online" software, designed to be invoked without installing. Cloning the repo will only take up space. 18 | The only pre-requisite is having [Node.js](https://nodejs.org/) >= 8.3.0 installed. 19 | 20 | Just open a terminal or cmd prompt and type: 21 | 22 | ```sh 23 | $ npx bot18 24 | ``` 25 | 26 | ## Welcome To ZalgoNet. NetSec Is Our Thing. 27 | 28 | If running Bot18 for the first time, you'll be asked to log into your ZalgoNet account (browser-based signup form is at [bot18.net/register](https://bot18.net/register)) or you can create a new account through the CLI. 29 | 30 | All communications and local storage are safely encrypted using the latest: 31 | 32 | - TLS for all client-server transmissions with bot18.net 33 | - [HMAC_SHA-256](http://nacl.cr.yp.to/auth.html) for all signed code from bot18.net 34 | - [XSalsa20 + Poly1305](http://nacl.cr.yp.to/secretbox.html) for secret key encrytion 35 | - [BCrypt](https://en.wikipedia.org/wiki/Bcrypt) for secure one-way password hashing 36 | 37 | ## Unlock Code Required. 38 | 39 | To get the most out of Bot18, you'll need an 8-digit Unlock Code, purchasable for $49.99 (for a limited time!) at [bot18.net/beta](https://bot18.net/beta). You can pay by credit card or crypto-currency, and the code never expires and grants you automatic code updates for the entire Bot18 product lifecycle. 40 | 41 | Or, you can try out Bot18 for free (enter "guest" as the ZalgoNet username or run with `--channel trial`), but you experience will be very sub-optimal. The "trial" engine distribution (also known as "cripple mode") is heavily DE-optimized (roughly 10x slower), does not support auto-trading strategies, and auto-quits after 15 minutes. If you like what you see, invest in an Unlock Code! 42 | 43 | For full licensing details, see [bot18.net/licensing](https://bot18.net/licensing). 44 | 45 | ## Configuration 46 | 47 | - Copy [this example](https://github.com/carlos8f/bot18/blob/with-gui/bot18.config-sample.js) to "bot18.config.js" in the same folder where you run your `npx bot18` command, to configure the bot. Make sure you `chmod 0600 bot18.config.js` to protect your configured API keys/passwords from exposure due to liberal filesystem permissions. 48 | - You can also specify a specific conf file with `npx bot18 --conf `, and/or define an account-wide conf at `~/.bot18/config.js`. 49 | - Bonus points if you have [MongoDB](https://www.mongodb.com) installed! You can activate data streaming to Mongo by setting `c.mongo.enabled=true` in your conf file. 50 | 51 | ## Beta Features (So far) 52 | 53 | The Beta engine build currently doesn't do anything but print something to stdio: 54 | 55 | ![screenshot](https://user-images.githubusercontent.com/106763/40774021-652490d2-6479-11e8-9cb5-160804c099a5.png) 56 | 57 | The current ALPHA version (now being ported to the Beta platform) is an early proof-of-concept, and supports: 58 | 59 | - Monitoring live trade streams from [Bitfinex](https://www.bitfinex.com/) and [Coinbase Pro](https://pro.coinbase.com/) simultaneously. 60 | - Supports [Coinbase Pro](https://pro.coinbase.com/) (formerly known as GDAX) live trading. Tracks your account balance and reports profit/loss when you trade. 61 | - Press the "l" key to list available key commands. Supports executing manual trades when yellow "M" is displayed at the end of the console columns. 62 | - Enter "A" (capitalized) to enable auto-trading (not available in trial mode) 63 | - The early auto-trading strategy is based on orderbook snapshot power-imbalance, and is highly experimental. **Currently, the Beta's auto-trading strategy is not recommended for serious trading.** 64 | 65 | ## Follow Updates and Keep In Touch 66 | 67 | - For announcements and progress updates, [follow @bot18_net on Twitter!](https://twitter.com/bot18_net) 68 | - To support Bot18 development, [Buy a shirt, hoodie or mug!](https://shop.bot18.net/) 69 | - For inquiries, contact us through the [bot18.net](https://bot18.net) website. 70 | 71 | Cheers and happy trading, 72 | 73 | [@carlos8f](https://github.com/carlos8f), May 31st 2018 74 | 75 | [Salty](https://github.com/carlos8f/salty) ID: `3t27msBTpN2Mn2LP68ZFLUUo3AN37aoGerUFPHdus9tFJg3hw7upmnY9c7nQ9fv1EFFF9nxiU9JzFSYPRAnx8Age` 76 | -------------------------------------------------------------------------------- /bot18.config-sample.js: -------------------------------------------------------------------------------- 1 | /* 2 | Bot18 Configuration (copied from bot18/v0.4.24) 3 | https://bot18.net/ 4 | 5 | ------- 6 | 7 | WARNING REGARDING EXCHANGE API KEYS 8 | 9 | Bot18 will communicate directly with crypto exchanges 10 | using API keys that you add here. Bot18 will not be 11 | able to perform trades on your behalf unless you give 12 | the "trade" permission (or equivalent) to your API key. 13 | For full functionality, give the "view" permission to 14 | the key, to access account balance and historical orders 15 | and fills. 16 | 17 | DO NOT give the "transfer" or "withdraw" permission to 18 | your API keys for use with Bot18, unless you really, 19 | really, really know what you're doing. You DON'T WANT 20 | un-authorized people transferring funds out of your 21 | exchange account if your config.js gets intercepted!!! 22 | 23 | ------- 24 | 25 | The file bot18.config-sample.js represents the defaults. 26 | These are copied to ~/.bot18/config.js when you invoke 27 | Bot18 for the first time. 28 | 29 | Extend/override these variables by (in order of importance): 30 | 31 | 1. "custom": An arbitrary-location copy of this file, with 32 | `npx bot18 --conf /path/to/my/bot18.config.js` 33 | 2. "local": Copying this file to `bot18.config.js` in the same 34 | folder that your run the `npx bot18` command from 35 | 3. "home": Editing your account-wide config at `~/.bot18/config.js` 36 | 37 | Bot18 will attempt to load configuration from all 3 locations, 38 | and merge the results in order of importance. 39 | 40 | Additionally, you can save your current setup to 41 | a new configuration file, by adding the `--save` argument: 42 | 43 | npx bot18 --save my-custom.config.js 44 | 45 | This will copy bot18.config-sample.js to my-custom.config.js, replace 46 | the defaults with your merged settings from all 3 possible config locations, 47 | and chmod it 0600 all in one command. Pretty handy! 48 | */ 49 | 50 | // Export a hash-map of configuration variables. 51 | var c = module.exports = {} 52 | 53 | 54 | /* 55 | Section 1: Paths And Startup Options. 56 | 57 | Engine version to use. This takes effect only if you leave out the 58 | `--channel ` argument. Defaults to "stable". 59 | 60 | Valid options: 61 | 62 | - `stable` -- The latest stable release. This is only available 63 | to ZalgoNet users who purchase a Bot18 Unlock Code. 64 | See: https://bot18.net/beta 65 | - `unstable` -- The latest dev build. You will need a `TEST` License 66 | in your ZalgoNet account to select this option. 67 | - `trial` -- A free 15-minute trial of Bot18, paper-trading mode only. 68 | Available to everyone, also by entering "guest" at the ZalgoNet 69 | login prompt. 70 | */ 71 | c.channel = "stable" 72 | 73 | // Directory for storing persistent settings, etc. 74 | // Files written here will be chmod'ed 0600, subdirectories 0700. 75 | // "~/" will be expanded to your home directory's absolute path, 76 | // or a tmp directory, as a fallback. 77 | c.home = "~/.bot18" 78 | 79 | // Display ZalgoNet MOTD at startup. (Usually a news bulletin from @carlos8f) 80 | c.motd = true 81 | 82 | /* 83 | Section 2.1: Exchange-Pair selection. 84 | 85 | Under the hood, Bot18 uses the CCXT project for exchange abstraction. 86 | For a list of Exchange IDs and Symbols supported by CCXT, see this page: 87 | 88 | https://github.com/ccxt/ccxt/wiki/Manual 89 | 90 | Configuration in this file will follow the naming conventions set forth 91 | by the CCXT project for the following: 92 | 93 | - Exchange ID (a short lowercased, one-word identifier) 94 | - Symbol (an asset-currency pair identifer, such as `BTC/USD`) 95 | - API Configuration (variables such as `apiKey`, `secret`, `uid`, 96 | and `password`) 97 | 98 | In this config file, you will select exchange/asset/currency "pairs" to 99 | run tasks on. These are selected using a hash-map defined in c.pairs: 100 | 101 | c.pairs = { 102 | : 103 | } 104 | 105 | An "exchange-pair-selector" is a string in the form: 106 | 107 | "." 108 | 109 | A "lowercase_ccxt_dashed_symbol" is a string in the form: 110 | 111 | "{asset_id}-{currency_id}" (the CCXT Symbol with "/" replaced with "-") 112 | 113 | A "comma-separated-task-list" is a string in the form: 114 | 115 | "{bot18_task_id}[?optional&urlencoded=true&querystring=toinclude]" 116 | 117 | Valid tasks include: 118 | 119 | (daemon tasks) 120 | 121 | watch: Monitor public trade streams, candles, and stats. 122 | ob: Keep a realtime orderbook mirror. 123 | record: Record trades and orderbook snapshots to mongodb. 124 | paper: Keep a simulated account balance. Do not perform real trades. 125 | trade: Keep track of your real account balance/portfolio, 126 | personal orders/fills, 127 | and enable REAL (manual or auto) trading. 128 | auto: Perform trades recommended by the strategy, without human input. 129 | Turn on with "A" (capitalized) during `trade` task. 130 | Turn off with "m" (any case) during `auto` task. 131 | 132 | Note that "out of the box", Bot18 only enables the `watch` and `ob` tasks, 133 | for only Bitfinex and Coinbase Pro (GDAX) exchanges. It's UP TO YOU to add 134 | `trade` and `auto` tasks to your chosen exchange-pair selection to tell 135 | the bot to perform automatic real trades recommended by your 136 | selected strategy(s). 137 | 138 | (utility tasks) 139 | 140 | list: List available selectors/strategies and exit. 141 | balance: Output balances and exit. 142 | sim: Run simulations and exit. 143 | train: Train machine-learning models and exit. 144 | export: Export data and exit. 145 | 146 | Both keys and values here can use glob patterns. See: 147 | https://npmjs.org/package/minimatch 148 | 149 | Examples 150 | 151 | For example, to watch all USD pairs on Kraken and GDAX, and record data 152 | streams from all GDAX pairs to MongoDB, use something like: 153 | 154 | c.pairs = { 155 | "{kraken,gdax}.*-usd": "watch", 156 | "gdax.*": "+record" 157 | } 158 | 159 | These pair selections can be overriden by CLI args (called "pair-specs", 160 | keys separated from values with a ":"). The above can be specified with 161 | the same effect by starting with: 162 | 163 | npx bot18 '{kraken,gdax}.*-usd:watch' 'gdax.*:+record' 164 | 165 | For each exchange/asset/currency combo selected, a worker 166 | subprocess will be spawned to perform actions in the task-list 167 | relating to the target exchange. 168 | 169 | Valid selector examples: 170 | 171 | bitfinex2.btc-* (all Bitcoin-base pairs on Bitfinex) 172 | *.*-jpy (Japanese Yen-quoted pairs on all exchanges) 173 | *.* (or just "*", selects all supported pairs and exchanges) 174 | 175 | Tips: 176 | 177 | - Task IDs can also have URL-encoded variables attached to them, 178 | e.g.'*:trade?auto_short=true&buy_volume=1.5' 179 | - Task IDs can also be strategy IDs (info on those below), to enable that 180 | strategy on the selected pair(s), e.g. '*:+macd?crossover=0.235' 181 | - Putting "+" before a task_id adds it to the task-list, "-" removes it. 182 | - Wildcards "*" are valid in both keys and values. 183 | 184 | Examples: 185 | 186 | Auto-trade LTC/USD and ETH/USD on Kraken using the macd strategy: 187 | 188 | npx bot18 kraken.{ltc,eth}/usd:+trade,auto,macd?crossover=0.235 189 | 190 | */ 191 | c.pairs = {} 192 | 193 | /** 194 | Section 2.2: Exchange API Keys and Pair-specific Config. 195 | 196 | You can pass configuration variables targeted at specific 197 | exchange-pair selectors. Incidentally, this is how you configure your 198 | exchange API keys. 199 | 200 | You can even configure multiple API keys for a given exchange, 201 | if you specify a specific asset/currency in 202 | the exchange-pair-selector. 203 | 204 | For example: 205 | 206 | c.pair_config = { 207 | 'bitfinex2.*-eur': { 208 | 'bitfinex2.apiKey': 'my-EUR-api-key', 209 | 'bitfinex2.secret': 'my-EUR-api-secret' 210 | } 211 | } 212 | 213 | ..which will use a specific API key for EUR pairs on Bitfinex. 214 | 215 | Define your exchange-pair configurations below, including 216 | all exchange API keys Bot18 needs below (replace "YOUR-API-KEY" 217 | with your actual API key, etc.) 218 | */ 219 | c.pair_config = {} 220 | 221 | 222 | /* 223 | Section 3: Strategy Selection And Configuration. 224 | 225 | A "strat" or Strategy in Bot18 slang, is a combination of 226 | JavaScript code and JSON files, bundled into a directory 227 | (and sometimes packaged as a git repository, npm package, 228 | or tarball). This code tells the Bot18 engine, through the 229 | Engine API, when to trade, and the specifics of those trades. 230 | 231 | - Each strat has a machine-readable ID (`strat_id`), 232 | consisting of lowercased alpha-numeric characters 233 | and underscores. 234 | - Each strat can define its own configuration variables, 235 | which can be configured below in `c.strat_config`. 236 | 237 | Bot18 runs can run multiple strategies at once, mapped 238 | arbitrarily to your exchange-pair selections. 239 | 240 | Strategies are selected using a hash-map defined in c.strats: 241 | 242 | "" -> "" 243 | 244 | For example, to run the Parabolic SAR and MACD strategies on 245 | the Bitfinex BCH/USDT pair, use something like: 246 | 247 | c.strats = { 248 | "bitfinex2.bch-usd": "sar,macd?crossover=0.18181818" 249 | } 250 | 251 | NOTE: 252 | 253 | Bot18 will NOT perform trades automatically, unless you specify 254 | the task `auto` in the exchange-pair selector's task-list, 255 | or enter "A" (capitalized) in your console during the `trade` task. 256 | */ 257 | c.strats = {} 258 | 259 | /** 260 | You can also pass configurations to each strategy. 261 | 262 | For example: 263 | 264 | c.strat_config = { 265 | 'macd': { 266 | 'crossover': 1.352, 267 | 'rsi_overbought': 85 268 | } 269 | } 270 | 271 | ..which will customize the `macd` strategy's configuration. 272 | */ 273 | //Define your strat-specific config below: 274 | c.strat_config = {} 275 | 276 | 277 | /* 278 | Section 4: MongoDB Configuration. 279 | 280 | Bot18 can optionally stream data to MongoDB if you 281 | set c.mongo.enabled=true, and specify your mongod connection details. 282 | For information on MongoDB, visit https://www.mongodb.com/ 283 | */ 284 | c.mongo = {} 285 | 286 | /* 287 | Section 5: Internationalization and Localization 288 | 289 | Bot18 can display in the language and locality of your 290 | choosing, if there is a translation available. 291 | c.locality is an ISO 639-1 2-letter lowercased language code, 292 | and optionally, a dash and a 2-letter uppercased country code. 293 | 294 | Currently, there are no translations other than English. 295 | */ 296 | c.locality = "en" 297 | 298 | 299 | /* 300 | Section 6: Notifiers. 301 | 302 | Bot18 can trigger notifications via 3rd party services, 303 | if you enter your API keys here. 304 | 305 | */ 306 | 307 | // Pushover.net 308 | c.pushover = {} 309 | 310 | 311 | /* 312 | Section 7: Internal Configuration. 313 | 314 | These variables don't usually need changing, 315 | but they are here in case you want to have a custom setup. 316 | */ 317 | // Increment this number to display a whole new palette of colors. 318 | c.color_scheme = 0 319 | // Internal port mapping. 320 | c.port_mapping = { 321 | "engine": "127.0.0.1:1818", 322 | "gui": "127.0.0.1:8018" 323 | } 324 | // Timeout durations. 325 | c.launch_timeout = 30000 326 | c.graceful_exit_timeout = 1000 327 | /* 328 | Verify engine downloads from code.bot18.net against this "master" 329 | Salty pubkey. 330 | This should ALWAYS match the "Salty ID" displayed at the bottom 331 | of https://bot18.net -- See: https://github.com/carlos8f/salty 332 | */ 333 | c.master_pubkey = "3t27msBTpN2Mn2LP68ZFLUUo3AN37aoGerUFPHdus9tFJg3hw7upmnY9c7nQ9fv1EFFF9nxiU9JzFSYPRAnx8Age" 334 | 335 | 336 | // Overrides or additional variables can be defined on `c` below this point. 337 | // ------------------------------------------------------------------------- 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | -------------------------------------------------------------------------------- /bot18.config-sample.js.hbs: -------------------------------------------------------------------------------- 1 | /* 2 | Bot18 Configuration (copied from {{{copied_from}}}) 3 | https://bot18.net/ 4 | 5 | ------- 6 | 7 | WARNING REGARDING EXCHANGE API KEYS 8 | 9 | Bot18 will communicate directly with crypto exchanges 10 | using API keys that you add here. Bot18 will not be 11 | able to perform trades on your behalf unless you give 12 | the "trade" permission (or equivalent) to your API key. 13 | For full functionality, give the "view" permission to 14 | the key, to access account balance and historical orders 15 | and fills. 16 | 17 | DO NOT give the "transfer" or "withdraw" permission to 18 | your API keys for use with Bot18, unless you really, 19 | really, really know what you're doing. You DON'T WANT 20 | un-authorized people transferring funds out of your 21 | exchange account if your config.js gets intercepted!!! 22 | 23 | ------- 24 | 25 | The file bot18.config-sample.js represents the defaults. 26 | These are copied to ~/.bot18/config.js when you invoke 27 | Bot18 for the first time. 28 | 29 | Extend/override these variables by (in order of importance): 30 | 31 | 1. "custom": An arbitrary-location copy of this file, with 32 | `npx bot18 --conf /path/to/my/bot18.config.js` 33 | 2. "local": Copying this file to `bot18.config.js` in the same 34 | folder that your run the `npx bot18` command from 35 | 3. "home": Editing your account-wide config at `~/.bot18/config.js` 36 | 37 | Bot18 will attempt to load configuration from all 3 locations, 38 | and merge the results in order of importance. 39 | 40 | Additionally, you can save your current setup to 41 | a new configuration file, by adding the `--save` argument: 42 | 43 | npx bot18 --save my-custom.config.js 44 | 45 | This will copy bot18.config-sample.js to my-custom.config.js, replace 46 | the defaults with your merged settings from all 3 possible config locations, 47 | and chmod it 0600 all in one command. Pretty handy! 48 | */ 49 | 50 | // Export a hash-map of configuration variables. 51 | var c = module.exports = {} 52 | 53 | 54 | /* 55 | Section 1: Paths And Startup Options. 56 | 57 | Engine version to use. This takes effect only if you leave out the 58 | `--channel ` argument. Defaults to "stable". 59 | 60 | Valid options: 61 | 62 | - `stable` -- The latest stable release. This is only available 63 | to ZalgoNet users who purchase a Bot18 Unlock Code. 64 | See: https://bot18.net/beta 65 | - `unstable` -- The latest dev build. You will need a `TEST` License 66 | in your ZalgoNet account to select this option. 67 | - `trial` -- A free 15-minute trial of Bot18, paper-trading mode only. 68 | Available to everyone, also by entering "guest" at the ZalgoNet 69 | login prompt. 70 | */ 71 | c.channel = {{{channel}}} 72 | 73 | // Directory for storing persistent settings, etc. 74 | // Files written here will be chmod'ed 0600, subdirectories 0700. 75 | // "~/" will be expanded to your home directory's absolute path, 76 | // or a tmp directory, as a fallback. 77 | c.home = {{{home}}} 78 | 79 | // Display ZalgoNet MOTD at startup. (Usually a news bulletin from @carlos8f) 80 | c.motd = {{{motd}}} 81 | 82 | /* 83 | Section 2.1: Exchange-Pair selection. 84 | 85 | Under the hood, Bot18 uses the CCXT project for exchange abstraction. 86 | For a list of Exchange IDs and Symbols supported by CCXT, see this page: 87 | 88 | https://github.com/ccxt/ccxt/wiki/Manual 89 | 90 | Configuration in this file will follow the naming conventions set forth 91 | by the CCXT project for the following: 92 | 93 | - Exchange ID (a short lowercased, one-word identifier) 94 | - Symbol (an asset-currency pair identifer, such as `BTC/USD`) 95 | - API Configuration (variables such as `apiKey`, `secret`, `uid`, 96 | and `password`) 97 | 98 | In this config file, you will select exchange/asset/currency "pairs" to 99 | run tasks on. These are selected using a hash-map defined in c.pairs: 100 | 101 | c.pairs = { 102 | : 103 | } 104 | 105 | An "exchange-pair-selector" is a string in the form: 106 | 107 | "." 108 | 109 | A "lowercase_ccxt_dashed_symbol" is a string in the form: 110 | 111 | "{asset_id}-{currency_id}" (the CCXT Symbol with "/" replaced with "-") 112 | 113 | A "comma-separated-task-list" is a string in the form: 114 | 115 | "{bot18_task_id}[?optional&urlencoded=true&querystring=toinclude]" 116 | 117 | Valid tasks include: 118 | 119 | (daemon tasks) 120 | 121 | watch: Monitor public trade streams, candles, and stats. 122 | ob: Keep a realtime orderbook mirror. 123 | record: Record trades and orderbook snapshots to mongodb. 124 | paper: Keep a simulated account balance. Do not perform real trades. 125 | trade: Keep track of your real account balance/portfolio, 126 | personal orders/fills, 127 | and enable REAL (manual or auto) trading. 128 | auto: Perform trades recommended by the strategy, without human input. 129 | Turn on with "A" (capitalized) during `trade` task. 130 | Turn off with "m" (any case) during `auto` task. 131 | 132 | Note that "out of the box", Bot18 only enables the `watch` and `ob` tasks, 133 | for only Bitfinex and Coinbase Pro (GDAX) exchanges. It's UP TO YOU to add 134 | `trade` and `auto` tasks to your chosen exchange-pair selection to tell 135 | the bot to perform automatic real trades recommended by your 136 | selected strategy(s). 137 | 138 | (utility tasks) 139 | 140 | list: List available selectors/strategies and exit. 141 | balance: Output balances and exit. 142 | sim: Run simulations and exit. 143 | train: Train machine-learning models and exit. 144 | export: Export data and exit. 145 | 146 | Both keys and values here can use glob patterns. See: 147 | https://npmjs.org/package/minimatch 148 | 149 | Examples 150 | 151 | For example, to watch all USD pairs on Kraken and GDAX, and record data 152 | streams from all GDAX pairs to MongoDB, use something like: 153 | 154 | c.pairs = { 155 | "{kraken,gdax}.*-usd": "watch", 156 | "gdax.*": "+record" 157 | } 158 | 159 | These pair selections can be overriden by CLI args (called "pair-specs", 160 | keys separated from values with a ":"). The above can be specified with 161 | the same effect by starting with: 162 | 163 | npx bot18 '{kraken,gdax}.*-usd:watch' 'gdax.*:+record' 164 | 165 | For each exchange/asset/currency combo selected, a worker 166 | subprocess will be spawned to perform actions in the task-list 167 | relating to the target exchange. 168 | 169 | Valid selector examples: 170 | 171 | bitfinex2.btc-* (all Bitcoin-base pairs on Bitfinex) 172 | *.*-jpy (Japanese Yen-quoted pairs on all exchanges) 173 | *.* (or just "*", selects all supported pairs and exchanges) 174 | 175 | Tips: 176 | 177 | - Task IDs can also have URL-encoded variables attached to them, 178 | e.g.'*:trade?auto_short=true&buy_volume=1.5' 179 | - Task IDs can also be strategy IDs (info on those below), to enable that 180 | strategy on the selected pair(s), e.g. '*:+macd?crossover=0.235' 181 | - Putting "+" before a task_id adds it to the task-list, "-" removes it. 182 | - Wildcards "*" are valid in both keys and values. 183 | 184 | Examples: 185 | 186 | Auto-trade LTC/USD and ETH/USD on Kraken using the macd strategy: 187 | 188 | npx bot18 kraken.{ltc,eth}/usd:+trade,auto,macd?crossover=0.235 189 | 190 | */ 191 | c.pairs = {{{pairs}}} 192 | 193 | /** 194 | Section 2.2: Exchange API Keys and Pair-specific Config. 195 | 196 | You can pass configuration variables targeted at specific 197 | exchange-pair selectors. Incidentally, this is how you configure your 198 | exchange API keys. 199 | 200 | You can even configure multiple API keys for a given exchange, 201 | if you specify a specific asset/currency in 202 | the exchange-pair-selector. 203 | 204 | For example: 205 | 206 | c.pair_config = { 207 | 'bitfinex2.*-eur': { 208 | 'bitfinex2.apiKey': 'my-EUR-api-key', 209 | 'bitfinex2.secret': 'my-EUR-api-secret' 210 | } 211 | } 212 | 213 | ..which will use a specific API key for EUR pairs on Bitfinex. 214 | 215 | Define your exchange-pair configurations below, including 216 | all exchange API keys Bot18 needs below (replace "YOUR-API-KEY" 217 | with your actual API key, etc.) 218 | */ 219 | c.pair_config = {{{pair_config}}} 220 | 221 | 222 | /* 223 | Section 3: Strategy Selection And Configuration. 224 | 225 | A "strat" or Strategy in Bot18 slang, is a combination of 226 | JavaScript code and JSON files, bundled into a directory 227 | (and sometimes packaged as a git repository, npm package, 228 | or tarball). This code tells the Bot18 engine, through the 229 | Engine API, when to trade, and the specifics of those trades. 230 | 231 | - Each strat has a machine-readable ID (`strat_id`), 232 | consisting of lowercased alpha-numeric characters 233 | and underscores. 234 | - Each strat can define its own configuration variables, 235 | which can be configured below in `c.strat_config`. 236 | 237 | Bot18 runs can run multiple strategies at once, mapped 238 | arbitrarily to your exchange-pair selections. 239 | 240 | Strategies are selected using a hash-map defined in c.strats: 241 | 242 | "" -> "" 243 | 244 | For example, to run the Parabolic SAR and MACD strategies on 245 | the Bitfinex BCH/USDT pair, use something like: 246 | 247 | c.strats = { 248 | "bitfinex2.bch-usd": "sar,macd?crossover=0.18181818" 249 | } 250 | 251 | NOTE: 252 | 253 | Bot18 will NOT perform trades automatically, unless you specify 254 | the task `auto` in the exchange-pair selector's task-list, 255 | or enter "A" (capitalized) in your console during the `trade` task. 256 | */ 257 | c.strats = {{{strats}}} 258 | 259 | /** 260 | You can also pass configurations to each strategy. 261 | 262 | For example: 263 | 264 | c.strat_config = { 265 | 'macd': { 266 | 'crossover': 1.352, 267 | 'rsi_overbought': 85 268 | } 269 | } 270 | 271 | ..which will customize the `macd` strategy's configuration. 272 | */ 273 | //Define your strat-specific config below: 274 | c.strat_config = {{{strat_config}}} 275 | 276 | 277 | /* 278 | Section 4: MongoDB Configuration. 279 | 280 | Bot18 can optionally stream data to MongoDB if you 281 | set c.mongo.enabled=true, and specify your mongod connection details. 282 | For information on MongoDB, visit https://www.mongodb.com/ 283 | */ 284 | c.mongo = {} 285 | c.mongo.enabled = {{{mongo.enabled}}} 286 | c.mongo.db = {{{mongo.db}}} 287 | c.mongo.host = process.env.MONGODB_PORT_27017_TCP_ADDR || {{{mongo.host}}} 288 | c.mongo.port = {{{mongo.port}}} 289 | c.mongo.username = {{{mongo.username}}} 290 | c.mongo.password = {{{mongo.password}}} 291 | // Or to use a specific connection string, 292 | // customize and uncomment the following line: 293 | //c.mongo.server_uri = "mongodb://user@password:host/db?params" 294 | c.mongo.replica_set = {{{mongo.replica_set}}} 295 | c.mongo.auth_mechanism = {{{mongo.auth_mechanism}}} 296 | 297 | /* 298 | Section 5: Internationalization and Localization 299 | 300 | Bot18 can display in the language and locality of your 301 | choosing, if there is a translation available. 302 | c.locality is an ISO 639-1 2-letter lowercased language code, 303 | and optionally, a dash and a 2-letter uppercased country code. 304 | 305 | Currently, there are no translations other than English. 306 | */ 307 | c.locality = {{{locality}}} 308 | 309 | 310 | /* 311 | Section 6: Notifiers. 312 | 313 | Bot18 can trigger notifications via 3rd party services, 314 | if you enter your API keys here. 315 | 316 | */ 317 | 318 | // Pushover.net 319 | c.pushover = {{{pushover}}} 320 | 321 | 322 | /* 323 | Section 7: Internal Configuration. 324 | 325 | These variables don't usually need changing, 326 | but they are here in case you want to have a custom setup. 327 | */ 328 | // Increment this number to display a whole new palette of colors. 329 | c.color_scheme = {{{color_scheme}}} 330 | // Internal port mapping. 331 | c.port_mapping = {{{port_mapping}}} 332 | // Timeout durations. 333 | c.launch_timeout = {{{launch_timeout}}} 334 | c.graceful_exit_timeout = {{{graceful_exit_timeout}}} 335 | /* 336 | Verify engine downloads from code.bot18.net against this "master" 337 | Salty pubkey. 338 | This should ALWAYS match the "Salty ID" displayed at the bottom 339 | of https://bot18.net -- See: https://github.com/carlos8f/salty 340 | */ 341 | c.master_pubkey = {{{master_pubkey}}} 342 | 343 | 344 | // Overrides or additional variables can be defined on `c` below this point. 345 | // ------------------------------------------------------------------------- 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | -------------------------------------------------------------------------------- /bot18.defaults.json: -------------------------------------------------------------------------------- 1 | { 2 | "channel": "stable", 3 | "motd": true, 4 | "home": "~/.bot18", 5 | "pairs": { 6 | "bitfinex2.{btc,eth,ltc}-usdt": "watch,ob", 7 | "gdax.{btc,eth,ltc}-usd": "watch,ob" 8 | }, 9 | "pair_config": { 10 | "bitfinex2.*": { 11 | "bitfinex2.apiKey": "YOUR-API-KEY", 12 | "bitfinex2.secret": "YOUR-SECRET" 13 | }, 14 | "gdax.*": { 15 | "gdax.apiKey": "YOUR-API-KEY", 16 | "gdax.secret": "YOUR-SECRET" 17 | } 18 | }, 19 | "strats": { 20 | "*": "noop" 21 | }, 22 | "strat_config": { 23 | "noop": { 24 | "test_var": 1.2353 25 | } 26 | }, 27 | "mongo": { 28 | "enabled": false, 29 | "db": "bot18", 30 | "host": "localhost", 31 | "port": 27017, 32 | "username": null, 33 | "password": null, 34 | "replica_set": null, 35 | "auth_mechanism": null, 36 | "server_uri": null 37 | }, 38 | "locality": "en", 39 | "pushover": { 40 | "enabled": false, 41 | "api_token": "YOUR-API-TOKEN", 42 | "user_key": "YOUR-USER-KEY" 43 | }, 44 | "port_mapping": { 45 | "engine": "127.0.0.1:1818", 46 | "gui": "127.0.0.1:8018" 47 | }, 48 | "color_scheme": 0, 49 | "launch_timeout": 30000, 50 | "graceful_exit_timeout": 1000, 51 | "master_pubkey": "3t27msBTpN2Mn2LP68ZFLUUo3AN37aoGerUFPHdus9tFJg3hw7upmnY9c7nQ9fv1EFFF9nxiU9JzFSYPRAnx8Age" 52 | } -------------------------------------------------------------------------------- /bot18.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | 5 | ... .. .... ..... 6 | .=*8888x <"?88h. .x~X88888Hx. .H8888888h. ~-. 7 | X> '8888H> '8888 H8X 888888888h. 888888888888x `> 8 | '88h. `8888 8888 8888:`*888888888: X~ `?888888hx~ 9 | '8888 '8888 "88> 88888: `%8 ' x8.^"*88*" 10 | `888 '8888.xH888x. . `88888 ?> `-:- X8888x 11 | X" :88*~ `*8888> `. ?888% X 488888> 12 | ~" !"` "888> ~*??. > .. `"88* 13 | .H8888h. ?88 .x88888h. < x88888nX" . 14 | :"^"88888h. '! :"""8888888x.. .x !"*8888888n.. : 15 | ^ "88888hx.+" ` `*888888888" ' "*88888888* 16 | ^"**"" ""***"" ^"***"` 17 | oe u+=~~~+u. 18 | .@88 z8F `8N. 19 | ==*88888 d88L 98E 20 | 88888 98888bu.. .@* 21 | 88888 "88888888NNu. 22 | 88888 "*8888888888i 23 | 88888 .zf""*8888888L 24 | 88888 d8F ^%888E 25 | 88888 88> `88~ 26 | 88888 '%N. d*" 27 | '**%%%%%%** ^"====="` 28 | 29 | (c) 2018 Carlos Rodriguez 30 | License: MIT + Paid Unlock Code - See LICENSE.txt 31 | https://bot18.net/ 32 | */ 33 | 34 | ;(function bot18_main () { 35 | // Sanity check. 36 | try { 37 | if (!global || !global.process || !process || !process.versions || !process.versions.node || !require || !console) { 38 | console.error('You are running something that doesn\'t seem to be Node.js. Bot18 only runs on Node. Get it at https://nodejs.org/') 39 | process.exit(1) 40 | } 41 | } 42 | catch (e) { 43 | console.error('You are running something that doesn\'t seem to be Node.js. Bot18 only runs on Node. Get it at https://nodejs.org/') 44 | process.exit(2) 45 | } 46 | try { 47 | var path = require('path') 48 | var pkg = require(path.resolve(__dirname, 'package.json')) 49 | } 50 | catch (e) { 51 | console.error('package.json for Bot18 could not be located. You are likely running a corrupt install and should try re-installing Bot18 and/or Node.js.') 52 | process.exit(3) 53 | } 54 | // Reject old Node versions. 55 | if (require('semver').gt('8.3.0', process.versions.node)) { 56 | console.error('You are running a Node.js version older than 8.3.x. Please upgrade via https://nodejs.org/') 57 | process.exit(4) 58 | } 59 | // Export global BOT18 var. This var holds literally everything. 60 | // It's just an easy way of state-sharing between various parts of 61 | // Bot18, and passing vars to the engine VM, which is pre-compiled and acts as a 62 | // "main() within a main()", only having access to the global scope. 63 | // This will contain live JS instances so don't try to JSON stringify it! 64 | var bot18 = global.BOT18 = { 65 | pkg: pkg, 66 | conf: {}, 67 | lib: {}, 68 | __dirname: __dirname, 69 | __filename: __filename 70 | } 71 | bot18.user_agent = 'bot18/v' + bot18.pkg.version 72 | // Perform all our warm-ups and run the engine. 73 | function warmup (p) { 74 | return require(path.resolve(__dirname, 'launcher', p)) 75 | } 76 | require('async').series([ 77 | warmup('get-lib'), 78 | warmup('parse-argv'), 79 | warmup('get-conf'), 80 | warmup('get-mongo'), 81 | warmup('print-motd'), 82 | warmup('get-wallet'), 83 | warmup('get-auth'), 84 | warmup('get-engine') 85 | ], function (err) { 86 | if (err) { 87 | var msg = (err.stack || err) 88 | console.error(require('chalk').red(msg)) 89 | process.exit(5) 90 | } 91 | bot18.engine() 92 | }) 93 | })() 94 | -------------------------------------------------------------------------------- /gui/_codemap.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | _ns: 'motley', 3 | _maps: [ 4 | require('./conf/_codemap'), 5 | require('./controllers/_codemap'), 6 | require('./db/_codemap'), 7 | require('./hooks/_codemap'), 8 | require('./middleware/_codemap'), 9 | require('./vendor/_codemap') 10 | ] 11 | } -------------------------------------------------------------------------------- /gui/conf/_codemap.js: -------------------------------------------------------------------------------- 1 | var bot18 = global.BOT18 2 | var r = require('path').resolve 3 | 4 | module.exports = { 5 | // meta 6 | _ns: 'motley', 7 | _folder: 'conf', 8 | 9 | // site overrides 10 | '@site.port': parseInt(bot18.conf.port_mapping.gui.split(':')[1], 10), 11 | '@site.title': 'Bot18 - The 🔥 Crypto Trading Bot - Unleash The Zalgo!', 12 | 13 | // middleware overrides 14 | 'middleware.templ{}': { 15 | watch: true 16 | }, 17 | 'middleware.buffet{}': { 18 | watch: true 19 | }, 20 | 21 | 'db.json{}': { 22 | 'path': r(bot18.conf.home, 'db.json'), 23 | 'hashKeys': false 24 | }, 25 | 26 | // other variables 27 | 'auth.strength': 12, 28 | 29 | '@console': { 30 | 'silent': true, 31 | 'colors': true, 32 | 'timestmap': false 33 | }, 34 | 35 | 'middleware.templ.root{}': { 36 | 'cwd': r(bot18.__dirname, 'gui') 37 | }, 38 | 'middleware.buffet.root{}': { 39 | 'cwd': r(bot18.__dirname, 'gui') 40 | } 41 | } -------------------------------------------------------------------------------- /gui/controllers/_codemap.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // meta 3 | _ns: 'motley', 4 | 5 | 'controllers[]': [ 6 | require('./auth'), 7 | require('./dashboard'), 8 | require('./home'), 9 | require('./users'), 10 | require('./webhooks') 11 | ] 12 | } -------------------------------------------------------------------------------- /gui/controllers/auth.js: -------------------------------------------------------------------------------- 1 | var bcryptjs = require('bcryptjs') 2 | var uuid = require('../lib/uuid') 3 | var isEmail = require('isemail').validate 4 | var pwTester = require('owasp-password-strength-test') 5 | 6 | module.exports = function container (get, set) { 7 | return get('controller')() 8 | .post('/login', function (req, res, next) { 9 | if (req.user) return res.redirect('/') 10 | if (!req.body.email) { 11 | req.error('E-mail is required.') 12 | } 13 | else if (!isEmail(req.body.email)) { 14 | req.error('E-mail is invalid.') 15 | } 16 | if (!req.body.password) { 17 | req.error('Password is required.') 18 | } 19 | if (req.inputError) { 20 | return next() 21 | } 22 | get('db.users').select({query: {email: req.body.email.toLowerCase()}}, function (err, users) { 23 | if (err) { 24 | req.error('Unexpected server error while processing your request (error code: LLAMA). Please try again.') 25 | return next() 26 | } 27 | var user = users.pop() 28 | if (!user) { 29 | req.error('E-mail not found.') 30 | return next() 31 | } 32 | else { 33 | bcryptjs.compare(req.body.password, user.password, function (err, passwordOk) { 34 | if (err) { 35 | req.error('Unexpected server error while processing your request (error code: PIGGY). Please try again.') 36 | return next() 37 | } 38 | if (passwordOk) { 39 | req.success('Welcome back, ' + user.username + '!') 40 | req.login(user) 41 | return res.redirect('/dashboard') 42 | } 43 | else { 44 | req.error('Bad password.') 45 | } 46 | return next() 47 | }) 48 | } 49 | }) 50 | }) 51 | .add('/login', function (req, res, next) { 52 | if (req.user) return res.redirect('/dashboard') 53 | res.vars.is_login = true 54 | res.vars.title = 'Log In - ' + res.vars.title 55 | res.render('auth/login') 56 | }) 57 | .post('/register', function (req, res, next) { 58 | var user = { 59 | id: uuid(), 60 | email: (req.body.email || '').trim().toLowerCase(), 61 | username: (req.body.username || '').trim(), 62 | username_lc: (req.body.username || '').trim().toLowerCase(), 63 | time_registered: new Date().getTime(), 64 | remote_ip: req.addr, 65 | last_access: new Date().getTime(), 66 | last_access_url: req.url, 67 | last_access_agent: req.headers['user-agent'], 68 | last_access_method: req.method, 69 | last_access_ip: req.addr 70 | } 71 | if (!req.body.email) { 72 | req.error('E-mail required.') 73 | } 74 | else if (!isEmail(req.body.email)) { 75 | req.error('E-mail not valid.') 76 | } 77 | if (!req.body.username) { 78 | req.error('Username required.') 79 | } 80 | else if (!req.body.username.match(/^[A-Za-z]{1}[A-Za-z0-9_-]{2,}$/)) { 81 | req.error('Username must consist of: A-Z, a-z, 0-9, dashes, underscores, at least 3 characters, starting with a letter.') 82 | } 83 | else if (req.body.username.length > 29) { 84 | req.error('Username needs to be shorter, < 30 chars.') 85 | } 86 | if (!req.body.password) { 87 | req.error('Password required.') 88 | } 89 | else if (req.body.password !== req.body.password2) { 90 | req.error('Password confirmed incorrectly.') 91 | } 92 | else { 93 | var password_info = pwTester.test(req.body.password) 94 | if (password_info.requiredTestErrors.length) { 95 | password_info.requiredTestErrors.forEach(function (text) { 96 | req.error(text) 97 | }) 98 | } 99 | } 100 | if (req.inputError) { 101 | return next() 102 | } 103 | get('db.users').select({query: {email: user.email}}, function (err, already_registered) { 104 | if (err) { 105 | req.error('Unexpected server error while processing your request (error code: PONY). Please try again.') 106 | return next() 107 | } 108 | if (already_registered[0]) { 109 | req.error('E-mail already registered.') 110 | return next() 111 | } 112 | get('db.users').select({query: {username_lc: user.username_lc}}, function (err, already_registered) { 113 | if (err) { 114 | req.error('Unexpected server error while processing your request (error code: KIWI). Please try again.') 115 | return next() 116 | } 117 | if (already_registered[0]) { 118 | req.error('Username already registered.') 119 | return next() 120 | } 121 | bcryptjs.hash(req.body.password, get('conf.auth.strength'), function (err, hash) { 122 | if (err) return next(err) 123 | user.password = hash 124 | get('db.users').save(user, function (err, user) { 125 | if (err) { 126 | req.error('Unexpected server error while processing your request (error code: APE). Please try again.') 127 | return next() 128 | } 129 | req.login(user) 130 | req.success('Your account was created! Congrats!') 131 | res.redirect('/dashboard') 132 | }) 133 | }) 134 | }) 135 | }) 136 | }) 137 | .add('/register', function (req, res, next) { 138 | if (req.user) return res.redirect('/dashboard') 139 | res.vars.is_register = true 140 | res.vars.title = 'Register - ' + res.vars.title 141 | res.render('auth/register') 142 | }) 143 | .get('/logout', function (req, res, next) { 144 | // @todo: check csrf token 145 | req.logout() 146 | res.redirect('/') 147 | }) 148 | } -------------------------------------------------------------------------------- /gui/controllers/dashboard.js: -------------------------------------------------------------------------------- 1 | module.exports = function container (get, set) { 2 | return get('controller')() 3 | .get('/dashboard', function (req, res, next) { 4 | res.vars.is_dashboard = true 5 | res.render('dashboard/index') 6 | }) 7 | } -------------------------------------------------------------------------------- /gui/controllers/home.js: -------------------------------------------------------------------------------- 1 | module.exports = function container (get, set) { 2 | return get('controller')() 3 | .get('/', function (req, res, next) { 4 | res.vars.is_home = true 5 | res.render('home') 6 | }) 7 | } -------------------------------------------------------------------------------- /gui/controllers/users.js: -------------------------------------------------------------------------------- 1 | var moment = require('moment') 2 | var async = require('async') 3 | 4 | module.exports = function container (get, set) { 5 | return get('controller')() 6 | .get('/users', function (req, res, next) { 7 | res.vars.users = [] 8 | get('db.users').select({query: {}, sort: {time_registered: -1}}, function (err, users) { 9 | if (err) { 10 | req.error('Unexpected server error while processing your request (error code: UGH). Please try again.') 11 | return next() 12 | } 13 | // preserve sort order and async load stats 14 | var user_map = {} 15 | var tasks = users.map(function (user) { 16 | return function (cb) { 17 | user.signup_date = moment(user.time_registered).format('ll') 18 | user_map[user.id] = user 19 | cb() 20 | } 21 | }) 22 | async.parallel(tasks, function (err) { 23 | if (err) { 24 | req.error('Unexpected server error while processing your request (error code: SATOSHI). Please try again.') 25 | return next() 26 | } 27 | res.vars.users = users.map(function (user) { 28 | return user_map[user.id] 29 | }) 30 | res.vars.is_users = true 31 | res.render('users/list') 32 | }) 33 | }) 34 | }) 35 | } -------------------------------------------------------------------------------- /gui/controllers/webhooks.js: -------------------------------------------------------------------------------- 1 | var uuid = require('../lib/uuid') 2 | 3 | module.exports = function container (get, set) { 4 | return get('controller')() 5 | .post('/webhooks/test', function (req, res, next) { 6 | var webhook = req.body 7 | if (webhook.id) { 8 | webhook.posted_id = webhook.id 9 | } 10 | webhook.is_verified = false 11 | webhook.id = uuid() 12 | webhook.time = new Date().getTime() 13 | webhook.type = 'test' 14 | webhook.signature = '__test__' 15 | webhook.status = 'test:testing' 16 | saveWebhook() 17 | 18 | function saveWebhook () { 19 | get('db.webhooks').save(webhook, function (err, saved) { 20 | if (err) return res.renderStatus(500) 21 | return res.renderStatus(200) 22 | }) 23 | } 24 | }) 25 | } -------------------------------------------------------------------------------- /gui/db/_codemap.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // meta 3 | _ns: 'motley', 4 | _folder: 'db', 5 | 6 | // named collections 7 | 'users': require('./users'), 8 | 'webhooks': require('./webhooks'), 9 | 10 | // collection registration 11 | 'collections[]': [ 12 | '#db.users', 13 | '#db.webhooks' 14 | // add more collections here. 15 | ] 16 | } -------------------------------------------------------------------------------- /gui/db/users.js: -------------------------------------------------------------------------------- 1 | module.exports = function container (get, set) { 2 | return get('db.createCollection')('users', { 3 | load: function (obj, opts, cb) { 4 | // respond after the obj is loaded 5 | cb(null, obj); 6 | }, 7 | save: function (obj, opts, cb) { 8 | // respond before the obj is saved 9 | cb(null, obj); 10 | }, 11 | afterSave: function (obj, opts, cb) { 12 | // respond after the obj is saved 13 | cb(null, obj); 14 | }, 15 | destroy: function (obj, opts, cb) { 16 | // respond after the obj is destroyed 17 | cb(null, obj) 18 | } 19 | }) 20 | } -------------------------------------------------------------------------------- /gui/db/webhooks.js: -------------------------------------------------------------------------------- 1 | module.exports = function container (get, set) { 2 | return get('db.createCollection')('webhooks', { 3 | load: function (obj, opts, cb) { 4 | // respond after the obj is loaded 5 | cb(null, obj); 6 | }, 7 | save: function (obj, opts, cb) { 8 | // respond before the obj is saved 9 | cb(null, obj); 10 | }, 11 | afterSave: function (obj, opts, cb) { 12 | // respond after the obj is saved 13 | cb(null, obj); 14 | }, 15 | destroy: function (obj, opts, cb) { 16 | // respond after the obj is destroyed 17 | cb(null, obj) 18 | } 19 | }) 20 | } -------------------------------------------------------------------------------- /gui/hooks/_codemap.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // meta 3 | _ns: 'motley', 4 | _folder: 'hooks', 5 | 6 | // core hook registration 7 | 'boot[]': function container (get, set) { 8 | return function task (cb) { 9 | // respond to the boot hook 10 | setImmediate(cb) 11 | } 12 | }, 13 | 'mount[]': function container (get, set) { 14 | return function task (cb) { 15 | // respond to the mount hook 16 | setImmediate(cb) 17 | } 18 | }, 19 | 'listen[]': function container (get, set) { 20 | return function task (cb) { 21 | setImmediate(cb) 22 | } 23 | }, 24 | 'close[1]': function container (get, set) { 25 | return function task (cb) { 26 | setImmediate(cb) 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /gui/lib/_codemap.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | _ns: 'motley', 3 | 'console': require('./console') 4 | } -------------------------------------------------------------------------------- /gui/lib/console.js: -------------------------------------------------------------------------------- 1 | var bot18 = global.BOT18 2 | 3 | module.exports = function container (get, set) { 4 | var conf = get('conf.console') 5 | var cluster = require('cluster') 6 | var d = bot18.debug('startup') 7 | var ch = require('chalk') 8 | return { 9 | _prefixArgs: function (args) { 10 | if (conf.workerId) { 11 | if (cluster.isMaster && Object.keys(cluster.workers).length) { 12 | var msg = '[master]' 13 | if (conf.colors) msg = ch.green(msg) 14 | args.unshift(msg) 15 | } 16 | else if (cluster.isWorker) { 17 | var msg = '[worker:' + cluster.worker.id + ']' 18 | if (conf.colors) msg = ch.cyan(msg) 19 | args.unshift(msg) 20 | } 21 | } 22 | if (conf.timestamp) { 23 | var date = new Date() 24 | var tzMatch = date.toString().match(/\((.*)\)/) 25 | var msg = date.toLocaleString() + ' ' + tzMatch[1] 26 | if (conf.colors) msg = ch.grey(msg) 27 | args.unshift(msg) 28 | } 29 | }, 30 | log: function () { 31 | if (conf.silent) return 32 | var args = [].slice.call(arguments) 33 | this._prefixArgs(args) 34 | if (conf.colors) msg = ch.grey(msg) 35 | d.apply(d, args) 36 | }, 37 | error: function () { 38 | if (conf.silent) return 39 | var args = [].slice.call(arguments) 40 | this._prefixArgs(args) 41 | var msg = '[ERROR]' 42 | if (conf.colors) msg = ch.red(msg) 43 | args.unshift(msg) 44 | d.apply(d, args) 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /gui/lib/uuid.js: -------------------------------------------------------------------------------- 1 | var uuid = require('uuid/v4') 2 | 3 | module.exports = function () { 4 | return uuid().replace(/-/g, '') 5 | } -------------------------------------------------------------------------------- /gui/middleware/_codemap.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // meta 3 | _ns: 'motley', 4 | 5 | // named middleware 6 | 'middleware.flash': require('./flash'), 7 | 'middleware.raw_body': require('./raw_body'), 8 | 'middleware.redirect': require('./redirect'), 9 | 'middleware.vars': require('./vars'), 10 | 11 | // special weight for raw body 12 | 'middleware[-20]': [ 13 | '#middleware.raw_body', 14 | ], 15 | 16 | // register handlers with weights 17 | 'middleware[]': [ 18 | '#middleware.flash', 19 | '#middleware.redirect', 20 | '#middleware.vars' 21 | ] 22 | } -------------------------------------------------------------------------------- /gui/middleware/flash.js: -------------------------------------------------------------------------------- 1 | module.exports = function container (get, set) { 2 | return function handler (req, res, next) { 3 | res.flash = function (message, type) { 4 | if (!req.session.messages) { 5 | req.session.messages = [] 6 | } 7 | req.session.messages.push({ 8 | text: message, 9 | type: type || 'success' 10 | }) 11 | if (type === 'error') { 12 | req.inputError = true 13 | } 14 | } 15 | req.error = function (text) { 16 | res.flash(text, 'error') 17 | } 18 | req.success = function (text) { 19 | res.flash(text, 'success') 20 | } 21 | var _render = res.render 22 | res.render = function () { 23 | res.vars.messages = req.session.messages 24 | delete req.session.messages 25 | _render.apply(res, [].slice.call(arguments)) 26 | } 27 | next() 28 | } 29 | } -------------------------------------------------------------------------------- /gui/middleware/raw_body.js: -------------------------------------------------------------------------------- 1 | module.exports = function container (get, set) { 2 | // 2mb 3 | var raw_body_limit = 1000 * 1000 * 2 4 | return function handler (req, res, next) { 5 | var buf = [] 6 | var total_length = 0 7 | req.on('data', function (d) { 8 | total_length += d.length 9 | if (total_length < raw_body_limit) { 10 | buf.push(d) 11 | } 12 | }) 13 | req.on('end', function () { 14 | req.raw_body = Buffer.concat(buf) 15 | req.raw_body_str = req.raw_body.toString('utf8') 16 | }) 17 | next() 18 | } 19 | } -------------------------------------------------------------------------------- /gui/middleware/redirect.js: -------------------------------------------------------------------------------- 1 | var http = require('http') 2 | 3 | module.exports = function container (get, set) { 4 | return function handler (req, res, next) { 5 | res.redirect = (function (toUrl) { 6 | var req = this.req 7 | , head = 'HEAD' == req.method 8 | , status = 302 9 | , body; 10 | 11 | // allow status / toUrl 12 | if (2 == arguments.length) { 13 | if ('number' == typeof toUrl) { 14 | status = toUrl; 15 | toUrl = arguments[1]; 16 | } else { 17 | status = arguments[1]; 18 | } 19 | } 20 | 21 | // Support text/{plain,html} by default 22 | this.format({ 23 | 'text/plain': function(){ 24 | body = http.STATUS_CODES[status] + '. Redirecting to ' + toUrl; 25 | }, 26 | 27 | 'text/html': function(){ 28 | var u = encodeURIComponent(toUrl); 29 | body = '

' + http.STATUS_CODES[status] + '. Redirecting to ' + u + '

'; 30 | }, 31 | 32 | default: function(){ 33 | body = ''; 34 | } 35 | }); 36 | 37 | // Respond 38 | this.status(status); 39 | this.set('Location', toUrl); 40 | this.set('Content-Length', Buffer.byteLength(body)); 41 | this.end(head ? null : body); 42 | }).bind(res) 43 | next() 44 | } 45 | } -------------------------------------------------------------------------------- /gui/middleware/vars.js: -------------------------------------------------------------------------------- 1 | module.exports = function container (get, set) { 2 | return function handler (req, res, next) { 3 | res.vars.title = get('conf.site.title') 4 | res.vars.post = req.body 5 | res.vars.user = req.user 6 | res.vars.messages = [] 7 | res.vars.user = req.user 8 | res.vars.user_email = req.user ? req.user.email : '' 9 | next() 10 | } 11 | } -------------------------------------------------------------------------------- /gui/public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Page not found 6 | 7 | 8 | 9 | 10 |

Page not found!

11 | 12 | -------------------------------------------------------------------------------- /gui/public/assets/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carlos8f/bot18/d36a03a9a0e0450b4a0efaa5bd1fbce75ad3f42e/gui/public/assets/bg.png -------------------------------------------------------------------------------- /gui/public/assets/bot18_big_transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carlos8f/bot18/d36a03a9a0e0450b4a0efaa5bd1fbce75ad3f42e/gui/public/assets/bot18_big_transparent.png -------------------------------------------------------------------------------- /gui/public/assets/bot18_flag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carlos8f/bot18/d36a03a9a0e0450b4a0efaa5bd1fbce75ad3f42e/gui/public/assets/bot18_flag.png -------------------------------------------------------------------------------- /gui/public/assets/bot18_flag_transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carlos8f/bot18/d36a03a9a0e0450b4a0efaa5bd1fbce75ad3f42e/gui/public/assets/bot18_flag_transparent.png -------------------------------------------------------------------------------- /gui/public/assets/bot18_hoodie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carlos8f/bot18/d36a03a9a0e0450b4a0efaa5bd1fbce75ad3f42e/gui/public/assets/bot18_hoodie.png -------------------------------------------------------------------------------- /gui/public/assets/bot18_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carlos8f/bot18/d36a03a9a0e0450b4a0efaa5bd1fbce75ad3f42e/gui/public/assets/bot18_icon.png -------------------------------------------------------------------------------- /gui/public/assets/bot18_icon_alt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carlos8f/bot18/d36a03a9a0e0450b4a0efaa5bd1fbce75ad3f42e/gui/public/assets/bot18_icon_alt.png -------------------------------------------------------------------------------- /gui/public/assets/bot18_jersey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carlos8f/bot18/d36a03a9a0e0450b4a0efaa5bd1fbce75ad3f42e/gui/public/assets/bot18_jersey.png -------------------------------------------------------------------------------- /gui/public/assets/bot18_jersey2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carlos8f/bot18/d36a03a9a0e0450b4a0efaa5bd1fbce75ad3f42e/gui/public/assets/bot18_jersey2.png -------------------------------------------------------------------------------- /gui/public/assets/bot18_jersey3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carlos8f/bot18/d36a03a9a0e0450b4a0efaa5bd1fbce75ad3f42e/gui/public/assets/bot18_jersey3.png -------------------------------------------------------------------------------- /gui/public/assets/bot18_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carlos8f/bot18/d36a03a9a0e0450b4a0efaa5bd1fbce75ad3f42e/gui/public/assets/bot18_logo.png -------------------------------------------------------------------------------- /gui/public/assets/bot18_logo_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carlos8f/bot18/d36a03a9a0e0450b4a0efaa5bd1fbce75ad3f42e/gui/public/assets/bot18_logo_light.png -------------------------------------------------------------------------------- /gui/public/assets/bot18_logo_transparent2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carlos8f/bot18/d36a03a9a0e0450b4a0efaa5bd1fbce75ad3f42e/gui/public/assets/bot18_logo_transparent2.png -------------------------------------------------------------------------------- /gui/public/assets/bot18_mug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carlos8f/bot18/d36a03a9a0e0450b4a0efaa5bd1fbce75ad3f42e/gui/public/assets/bot18_mug.png -------------------------------------------------------------------------------- /gui/public/assets/bot18_mug2_tpl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carlos8f/bot18/d36a03a9a0e0450b4a0efaa5bd1fbce75ad3f42e/gui/public/assets/bot18_mug2_tpl.png -------------------------------------------------------------------------------- /gui/public/assets/bot18_mug_tpl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carlos8f/bot18/d36a03a9a0e0450b4a0efaa5bd1fbce75ad3f42e/gui/public/assets/bot18_mug_tpl.png -------------------------------------------------------------------------------- /gui/public/assets/bot18_shirt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carlos8f/bot18/d36a03a9a0e0450b4a0efaa5bd1fbce75ad3f42e/gui/public/assets/bot18_shirt.png -------------------------------------------------------------------------------- /gui/public/cryptagon/Your Portfolio _ Cryptagon_files/analytics.js: -------------------------------------------------------------------------------- 1 | (function(){var $c=function(a){this.w=a||[]};$c.prototype.set=function(a){this.w[a]=!0};$c.prototype.encode=function(){for(var a=[],b=0;b\x3c/script>')):(c=M.createElement("script"), 3 | c.type="text/javascript",c.async=!0,c.src=a,b&&(c.id=b),a=M.getElementsByTagName("script")[0],a.parentNode.insertBefore(c,a)))},be=function(a,b){return E(M.location[b?"href":"search"],a)},E=function(a,b){return(a=a.match("(?:&|#|\\?)"+K(b).replace(/([.*+?^=!:${}()|\[\]\/\\])/g,"\\$1")+"=([^&#]*)"))&&2==a.length?a[1]:""},xa=function(){var a=""+M.location.hostname;return 0==a.indexOf("www.")?a.substring(4):a},de=function(a,b){var c=a.indexOf(b);if(5==c||6==c)if(a=a.charAt(c+b.length),"/"==a||"?"==a|| 4 | ""==a||":"==a)return!0;return!1},ya=function(a,b){var c=M.referrer;if(/^(https?|android-app):\/\//i.test(c)){if(a)return c;a="//"+M.location.hostname;if(!de(c,a))return b&&(b=a.replace(/\./g,"-")+".cdn.ampproject.org",de(c,b))?void 0:c}},za=function(a,b){if(1==b.length&&null!=b[0]&&"object"===typeof b[0])return b[0];for(var c={},d=Math.min(a.length+1,b.length),e=0;e=b.length)wc(a,b,c);else if(8192>=b.length)x(a,b,c)||wd(a,b,c)||wc(a,b,c);else throw ge("len",b.length),new Da(b.length);},pe=function(a,b,c,d){d=d||ua;wd(a+"?"+b,"",d,c)},wc=function(a,b,c){var d=ta(a+"?"+b);d.onload=d.onerror=function(){d.onload=null;d.onerror=null;c()}},wd=function(a,b,c,d){var e=O.XMLHttpRequest; 9 | if(!e)return!1;var g=new e;if(!("withCredentials"in g))return!1;a=a.replace(/^http:/,"https:");g.open("POST",a,!0);g.withCredentials=!0;g.setRequestHeader("Content-Type","text/plain");g.onreadystatechange=function(){if(4==g.readyState){if(d)try{var a=g.responseText;if(1>a.length)ge("xhr","ver","0"),c();else if("1"!=a.charAt(0))ge("xhr","ver",String(a.length)),c();else if(3=100*R(a,Ka))throw"abort";}function Ma(a){if(G(P(a,Na)))throw"abort";}function Oa(){var a=M.location.protocol;if("http:"!=a&&"https:"!=a)throw"abort";} 12 | function Pa(a){try{O.navigator.sendBeacon?J(42):O.XMLHttpRequest&&"withCredentials"in new O.XMLHttpRequest&&J(40)}catch(c){}a.set(ld,Td(a),!0);a.set(Ac,R(a,Ac)+1);var b=[];Qa.map(function(c,d){d.F&&(c=a.get(c),void 0!=c&&c!=d.defaultValue&&("boolean"==typeof c&&(c*=1),b.push(d.F+"="+K(""+c))))});b.push("z="+Bd());a.set(Ra,b.join("&"),!0)} 13 | function Sa(a){var b=P(a,gd)||oe()+"/collect",c=a.get(qe),d=P(a,fa);!d&&a.get(Vd)&&(d="beacon");if(c)pe(b,P(a,Ra),c,a.get(Ia));else if(d){c=d;d=P(a,Ra);var e=a.get(Ia);e=e||ua;"image"==c?wc(b,d,e):"xhr"==c&&wd(b,d,e)||"beacon"==c&&x(b,d,e)||ba(b,d,e)}else ba(b,P(a,Ra),a.get(Ia));b=a.get(Na);b=h(b);c=b.hitcount;b.hitcount=c?c+1:1;b=a.get(Na);delete h(b).pending_experiments;a.set(Ia,ua,!0)} 14 | function Hc(a){(O.gaData=O.gaData||{}).expId&&a.set(Nc,(O.gaData=O.gaData||{}).expId);(O.gaData=O.gaData||{}).expVar&&a.set(Oc,(O.gaData=O.gaData||{}).expVar);var b=a.get(Na);if(b=h(b).pending_experiments){var c=[];for(d in b)b.hasOwnProperty(d)&&b[d]&&c.push(encodeURIComponent(d)+"."+encodeURIComponent(b[d]));var d=c.join("!")}else d=void 0;d&&a.set(m,d,!0)}function cd(){if(O.navigator&&"preview"==O.navigator.loadPurpose)throw"abort";} 15 | function yd(a){var b=O.gaDevIds;ka(b)&&0!=b.length&&a.set("&did",b.join(","),!0)}function vb(a){if(!a.get(Na))throw"abort";};var hd=function(){return Math.round(2147483647*Math.random())},Bd=function(){try{var a=new Uint32Array(1);O.crypto.getRandomValues(a);return a[0]&2147483647}catch(b){return hd()}};function Ta(a){var b=R(a,Ua);500<=b&&J(15);var c=P(a,Va);if("transaction"!=c&&"item"!=c){c=R(a,Wa);var d=(new Date).getTime(),e=R(a,Xa);0==e&&a.set(Xa,d);e=Math.round(2*(d-e)/1E3);0=c)throw"abort";a.set(Wa,--c)}a.set(Ua,++b)};var Ya=function(){this.data=new ee},Qa=new ee,Za=[];Ya.prototype.get=function(a){var b=$a(a),c=this.data.get(a);b&&void 0==c&&(c=ea(b.defaultValue)?b.defaultValue():b.defaultValue);return b&&b.Z?b.Z(this,a,c):c};var P=function(a,b){a=a.get(b);return void 0==a?"":""+a},R=function(a,b){a=a.get(b);return void 0==a||""===a?0:1*a};Ya.prototype.set=function(a,b,c){if(a)if("object"==typeof a)for(var d in a)a.hasOwnProperty(d)&&ab(this,d,a[d],c);else ab(this,a,b,c)}; 16 | var ab=function(a,b,c,d){if(void 0!=c)switch(b){case Na:wb.test(c)}var e=$a(b);e&&e.o?e.o(a,b,c,d):a.data.set(b,c,d)},bb=function(a,b,c,d,e){this.name=a;this.F=b;this.Z=d;this.o=e;this.defaultValue=c},$a=function(a){var b=Qa.get(a);if(!b)for(var c=0;c=b?!1:!0},gc=function(a){var b={};if(Ec(b)||Fc(b)){var c=b[Eb];void 0==c||Infinity==c||isNaN(c)||(0c)a[b]=void 0},Fd=function(a){return function(b){if("pageview"==b.get(Va)&& 27 | !a.I){a.I=!0;var c=aa(b),d=0a.length)J(12);else{for(var d=[],e=0;e=a&&d.push({hash:ca[0],R:e[g],O:ca})}if(0!=d.length)return 1==d.length?d[0]:Zc(b,d)||Zc(c,d)||Zc(null,d)||d[0]}function Zc(a,b){if(null==a)var c=a=1;else c=La(a),a=La(D(a,".")?a.substring(1):"."+a);for(var d=0;d=ca[0]||0>=ca[1]?"":ca.join("x");a.set(rb,c);a.set(tb,fc());a.set(ob,M.characterSet||M.charset);a.set(sb,b&&"function"===typeof b.javaEnabled&&b.javaEnabled()|| 45 | !1);a.set(nb,(b&&(b.language||b.browserLanguage)||"").toLowerCase());a.data.set(ce,be("gclid",!0));a.data.set(ie,be("gclsrc",!0));a.data.set(fe,Math.round((new Date).getTime()/1E3));if(d&&a.get(cc)&&(b=M.location.hash)){b=b.split(/[?&#]+/);d=[];for(c=0;carguments.length)){if("string"===typeof arguments[0]){var b=arguments[0];var c=[].slice.call(arguments,1)}else b=arguments[0]&&arguments[0][Va],c=arguments;b&&(c=za(qc[b]||[],c),c[Va]=b,this.b.set(c,void 0,!0),this.filters.D(this.b),this.b.data.m={})}}; 47 | pc.prototype.ma=function(a,b){var c=this;u(a,c,b)||(v(a,function(){u(a,c,b)}),y(String(c.get(V)),a,void 0,b,!0))};var rc=function(a){if("prerender"==M.visibilityState)return!1;a();return!0},z=function(a){if(!rc(a)){J(16);var b=!1,c=function(){if(!b&&rc(a)){b=!0;var d=c,e=M;e.removeEventListener?e.removeEventListener("visibilitychange",d,!1):e.detachEvent&&e.detachEvent("onvisibilitychange",d)}};L(M,"visibilitychange",c)}};var td=/^(?:(\w+)\.)?(?:(\w+):)?(\w+)$/,sc=function(a){if(ea(a[0]))this.u=a[0];else{var b=td.exec(a[0]);null!=b&&4==b.length&&(this.c=b[1]||"t0",this.K=b[2]||"",this.C=b[3],this.a=[].slice.call(a,1),this.K||(this.A="create"==this.C,this.i="require"==this.C,this.g="provide"==this.C,this.ba="remove"==this.C),this.i&&(3<=this.a.length?(this.X=this.a[1],this.W=this.a[2]):this.a[1]&&(qa(this.a[1])?this.X=this.a[1]:this.W=this.a[1])));b=a[1];a=a[2];if(!this.C)throw"abort";if(this.i&&(!qa(b)||""==b))throw"abort"; 48 | if(this.g&&(!qa(b)||""==b||!ea(a)))throw"abort";if(ud(this.c)||ud(this.K))throw"abort";if(this.g&&"t0"!=this.c)throw"abort";}};function ud(a){return 0<=a.indexOf(".")||0<=a.indexOf(":")};var Yd,Zd,$d,A;Yd=new ee;$d=new ee;A=new ee;Zd={ec:45,ecommerce:46,linkid:47}; 49 | var u=function(a,b,c){b==N||b.get(V);var d=Yd.get(a);if(!ea(d))return!1;b.plugins_=b.plugins_||new ee;if(b.plugins_.get(a))return!0;b.plugins_.set(a,new d(b,c||{}));return!0},y=function(a,b,c,d,e){if(!ea(Yd.get(b))&&!$d.get(b)){Zd.hasOwnProperty(b)&&J(Zd[b]);if(p.test(b)){J(52);a=N.j(a);if(!a)return!0;c=d||{};d={id:b,B:c.dataLayer||"dataLayer",ia:!!a.get("anonymizeIp"),sync:e,G:!1};a.get(">m")==b&&(d.G=!0);var g=String(a.get("name"));"t0"!=g&&(d.target=g);G(String(a.get("trackingId")))||(d.clientId= 50 | String(a.get(Q)),d.ka=Number(a.get(n)),c=c.palindrome?r:q,c=(c=M.cookie.replace(/^|(; +)/g,";").match(c))?c.sort().join("").substring(1):void 0,d.la=c,d.qa=E(a.b.get(kb)||"","gclid"));a=d.B;c=(new Date).getTime();O[a]=O[a]||[];c={"gtm.start":c};e||(c.event="gtm.js");O[a].push(c);c=t(d)}!c&&Zd.hasOwnProperty(b)?(J(39),c=b+".js"):J(43);c&&(c&&0<=c.indexOf("/")||(c=(Ba||"https:"==M.location.protocol?"https:":"http:")+"//www.google-analytics.com/plugins/ua/"+c),d=ae(c),a=d.protocol,c=M.location.protocol, 51 | ("https:"==a||a==c||("http:"!=a?0:"http:"==c))&&B(d)&&(wa(d.url,void 0,e),$d.set(b,!0)))}},v=function(a,b){var c=A.get(a)||[];c.push(b);A.set(a,c)},C=function(a,b){Yd.set(a,b);b=A.get(a)||[];for(var c=0;ca.split("/")[0].indexOf(":")&& 53 | (a=ca+e[2].substring(0,e[2].lastIndexOf("/"))+"/"+a);c.href=a;d=b(c);return{protocol:(c.protocol||"").toLowerCase(),host:d[0],port:d[1],path:d[2],query:c.search||"",url:a||""}};var Z={ga:function(){Z.f=[]}};Z.ga();Z.D=function(a){var b=Z.J.apply(Z,arguments);b=Z.f.concat(b);for(Z.f=[];0c;c++){var d=b[c].src;if(d&&0==d.indexOf("https://www.google-analytics.com/analytics")){b= 58 | !0;break a}}b=!1}b&&(Ba=!0)}(O.gaplugins=O.gaplugins||{}).Linker=Dc;b=Dc.prototype;C("linker",Dc);X("decorate",b,b.ca,20);X("autoLink",b,b.S,25);C("displayfeatures",fd);C("adfeatures",fd);a=a&&a.q;ka(a)?Z.D.apply(N,a):J(50)}};N.da=function(){for(var a=N.getAll(),b=0;b>21:b}return b};})(window); 59 | -------------------------------------------------------------------------------- /gui/public/cryptagon/Your Portfolio _ Cryptagon_files/main.3d7c04de.css: -------------------------------------------------------------------------------- 1 | @import url(https://fonts.googleapis.com/css?family=Roboto+Mono);@font-face{font-family:Circular;font-weight:400;font-style:normal;src:url(/static/media/circular-book.9fe4b2a0.woff) format("woff"),url(/static/media/circular-book.1f6e1b9f.ttf) format("truetype")}@font-face{font-family:Circular;font-weight:500;font-style:normal;src:url(/static/media/circular-medium.2a4f3a1a.woff) format("woff"),url(/static/media/circular-medium.3900fcac.ttf) format("truetype")}@font-face{font-family:Circular;font-weight:700;font-style:normal;src:url(/static/media/circular-bold.fc61ae08.woff) format("woff"),url(/static/media/circular-bold.fc45c938.ttf) format("truetype")}.Select{position:relative}.Select input::-webkit-contacts-auto-fill-button,.Select input::-webkit-credentials-auto-fill-button{display:none!important}.Select input::-ms-clear,.Select input::-ms-reveal{display:none!important}.Select,.Select div,.Select input,.Select span{-webkit-box-sizing:border-box;box-sizing:border-box}.Select.is-disabled .Select-arrow-zone{cursor:default;pointer-events:none;opacity:.35}.Select.is-disabled>.Select-control{background-color:#f9f9f9}.Select.is-disabled>.Select-control:hover{-webkit-box-shadow:none;box-shadow:none}.Select.is-open>.Select-control{border-bottom-right-radius:0;border-bottom-left-radius:0;background:#fff;border-color:#b3b3b3 #ccc #d9d9d9}.Select.is-open>.Select-control .Select-arrow{top:-2px;border-color:transparent transparent #999;border-width:0 5px 5px}.Select.is-searchable.is-focused:not(.is-open)>.Select-control,.Select.is-searchable.is-open>.Select-control{cursor:text}.Select.is-focused>.Select-control{background:#fff}.Select.is-focused:not(.is-open)>.Select-control{border-color:#007eff;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 0 3px rgba(0,126,255,.1);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 0 3px rgba(0,126,255,.1);background:#fff}.Select.has-value.is-clearable.Select--single>.Select-control .Select-value{padding-right:42px}.Select.has-value.is-pseudo-focused.Select--single>.Select-control .Select-value .Select-value-label,.Select.has-value.Select--single>.Select-control .Select-value .Select-value-label{color:#333}.Select.has-value.is-pseudo-focused.Select--single>.Select-control .Select-value a.Select-value-label,.Select.has-value.Select--single>.Select-control .Select-value a.Select-value-label{cursor:pointer;text-decoration:none}.Select.has-value.is-pseudo-focused.Select--single>.Select-control .Select-value a.Select-value-label:focus,.Select.has-value.is-pseudo-focused.Select--single>.Select-control .Select-value a.Select-value-label:hover,.Select.has-value.Select--single>.Select-control .Select-value a.Select-value-label:focus,.Select.has-value.Select--single>.Select-control .Select-value a.Select-value-label:hover{color:#007eff;outline:none;text-decoration:underline}.Select.has-value.is-pseudo-focused.Select--single>.Select-control .Select-value a.Select-value-label:focus,.Select.has-value.Select--single>.Select-control .Select-value a.Select-value-label:focus{background:#fff}.Select.has-value.is-pseudo-focused .Select-input{opacity:0}.Select.is-open .Select-arrow,.Select .Select-arrow-zone:hover>.Select-arrow{border-top-color:#666}.Select.Select--rtl{direction:rtl;text-align:right}.Select-control{background-color:#fff;border-color:#d9d9d9 #ccc #b3b3b3;border-radius:4px;border:1px solid #ccc;color:#333;cursor:default;display:table;border-spacing:0;border-collapse:separate;height:36px;outline:none;overflow:hidden;position:relative;width:100%}.Select-control:hover{-webkit-box-shadow:0 1px 0 rgba(0,0,0,.06);box-shadow:0 1px 0 rgba(0,0,0,.06)}.Select-control .Select-input:focus{outline:none;background:#fff}.Select--single>.Select-control .Select-value,.Select-placeholder{bottom:0;color:#aaa;left:0;line-height:34px;padding-left:10px;padding-right:10px;position:absolute;right:0;top:0;max-width:100%;overflow:hidden;-o-text-overflow:ellipsis;text-overflow:ellipsis;white-space:nowrap}.Select-input{height:34px;padding-left:10px;padding-right:10px;vertical-align:middle}.Select-input>input{width:100%;background:none transparent;border:0 none;-webkit-box-shadow:none;box-shadow:none;cursor:default;display:inline-block;font-family:inherit;font-size:inherit;margin:0;outline:none;line-height:17px;padding:8px 0 12px;-webkit-appearance:none}.is-focused .Select-input>input{cursor:text}.has-value.is-pseudo-focused .Select-input{opacity:0}.Select-control:not(.is-searchable)>.Select-input{outline:none}.Select-loading-zone{cursor:pointer;display:table-cell;text-align:center}.Select-loading,.Select-loading-zone{position:relative;vertical-align:middle;width:16px}.Select-loading{-webkit-animation:Select-animation-spin .4s infinite linear;animation:Select-animation-spin .4s infinite linear;height:16px;-webkit-box-sizing:border-box;box-sizing:border-box;border-radius:50%;border:2px solid #ccc;border-right-color:#333;display:inline-block}.Select-clear-zone{-webkit-animation:Select-animation-fadeIn .2s;animation:Select-animation-fadeIn .2s;color:#999;cursor:pointer;display:table-cell;position:relative;text-align:center;vertical-align:middle;width:17px}.Select-clear-zone:hover{color:#d0021b}.Select-clear{display:inline-block;font-size:18px;line-height:1}.Select--multi .Select-clear-zone{width:17px}.Select-arrow-zone{cursor:pointer;display:table-cell;position:relative;text-align:center;vertical-align:middle;width:25px;padding-right:5px}.Select--rtl .Select-arrow-zone{padding-right:0;padding-left:5px}.Select-arrow{border-color:#999 transparent transparent;border-style:solid;border-width:5px 5px 2.5px;display:inline-block;height:0;width:0;position:relative}.Select-control>:last-child{padding-right:5px}.Select--multi .Select-multi-value-wrapper{display:inline-block}.Select .Select-aria-only{position:absolute;display:inline-block;height:1px;width:1px;margin:-1px;clip:rect(0,0,0,0);overflow:hidden;float:left}@-webkit-keyframes Select-animation-fadeIn{0%{opacity:0}to{opacity:1}}@keyframes Select-animation-fadeIn{0%{opacity:0}to{opacity:1}}.Select-menu-outer{border-bottom-right-radius:4px;border-bottom-left-radius:4px;background-color:#fff;border:1px solid #ccc;border-top-color:#e6e6e6;-webkit-box-shadow:0 1px 0 rgba(0,0,0,.06);box-shadow:0 1px 0 rgba(0,0,0,.06);-webkit-box-sizing:border-box;box-sizing:border-box;margin-top:-1px;max-height:200px;position:absolute;top:100%;width:100%;z-index:1;-webkit-overflow-scrolling:touch}.Select-menu{max-height:198px;overflow-y:auto}.Select-option{-webkit-box-sizing:border-box;box-sizing:border-box;background-color:#fff;color:#666;cursor:pointer;display:block;padding:8px 10px}.Select-option:last-child{border-bottom-right-radius:4px;border-bottom-left-radius:4px}.Select-option.is-selected{background-color:#f5faff;background-color:rgba(0,126,255,.04);color:#333}.Select-option.is-focused{background-color:#ebf5ff;background-color:rgba(0,126,255,.08);color:#333}.Select-option.is-disabled{color:#ccc;cursor:default}.Select-noresults{-webkit-box-sizing:border-box;box-sizing:border-box;color:#999;cursor:default;display:block;padding:8px 10px}.Select--multi .Select-input{vertical-align:middle;margin-left:10px;padding:0}.Select--multi.Select--rtl .Select-input{margin-left:0;margin-right:10px}.Select--multi.has-value .Select-input{margin-left:5px}.Select--multi .Select-value{background-color:#ebf5ff;background-color:rgba(0,126,255,.08);border-radius:2px;border:1px solid #c2e0ff;border:1px solid rgba(0,126,255,.24);color:#007eff;display:inline-block;font-size:.9em;line-height:1.4;margin-left:5px;margin-top:5px;vertical-align:top}.Select--multi .Select-value-icon,.Select--multi .Select-value-label{display:inline-block;vertical-align:middle}.Select--multi .Select-value-label{border-bottom-right-radius:2px;border-top-right-radius:2px;cursor:default;padding:2px 5px}.Select--multi a.Select-value-label{color:#007eff;cursor:pointer;text-decoration:none}.Select--multi a.Select-value-label:hover{text-decoration:underline}.Select--multi .Select-value-icon{cursor:pointer;border-bottom-left-radius:2px;border-top-left-radius:2px;border-right:1px solid #c2e0ff;border-right:1px solid rgba(0,126,255,.24);padding:1px 5px 3px}.Select--multi .Select-value-icon:focus,.Select--multi .Select-value-icon:hover{background-color:#d8eafd;background-color:rgba(0,113,230,.08);color:#0071e6}.Select--multi .Select-value-icon:active{background-color:#c2e0ff;background-color:rgba(0,126,255,.24)}.Select--multi.Select--rtl .Select-value{margin-left:0;margin-right:5px}.Select--multi.Select--rtl .Select-value-icon{border-right:none;border-left:1px solid #c2e0ff;border-left:1px solid rgba(0,126,255,.24)}.Select--multi.is-disabled .Select-value{background-color:#fcfcfc;border:1px solid #e3e3e3;color:#333}.Select--multi.is-disabled .Select-value-icon{cursor:not-allowed;border-right:1px solid #e3e3e3}.Select--multi.is-disabled .Select-value-icon:active,.Select--multi.is-disabled .Select-value-icon:focus,.Select--multi.is-disabled .Select-value-icon:hover{background-color:#fcfcfc}@keyframes Select-animation-spin{to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@-webkit-keyframes Select-animation-spin{to{-webkit-transform:rotate(1turn)}}.tippy-touch{cursor:pointer!important}.tippy-notransition{-webkit-transition:none!important;-o-transition:none!important;transition:none!important}.tippy-popper{max-width:400px;-webkit-perspective:800px;perspective:800px;z-index:9999;outline:0;-webkit-transition-timing-function:cubic-bezier(.165,.84,.44,1);-o-transition-timing-function:cubic-bezier(.165,.84,.44,1);transition-timing-function:cubic-bezier(.165,.84,.44,1);pointer-events:none}.tippy-popper.html-template{max-width:96%;max-width:calc(100% - 20px)}.tippy-popper[x-placement^=top] [x-arrow]{border-top:7px solid #333;border-right:7px solid transparent;border-left:7px solid transparent;bottom:-7px;margin:0 9px}.tippy-popper[x-placement^=top] [x-arrow].arrow-small{border-top:5px solid #333;border-right:5px solid transparent;border-left:5px solid transparent;bottom:-5px}.tippy-popper[x-placement^=top] [x-arrow].arrow-big{border-top:10px solid #333;border-right:10px solid transparent;border-left:10px solid transparent;bottom:-10px}.tippy-popper[x-placement^=top] [x-circle]{-webkit-transform-origin:0 33%;-ms-transform-origin:0 33%;transform-origin:0 33%}.tippy-popper[x-placement^=top] [x-circle].enter{-webkit-transform:scale(1) translate(-50%,-55%);-ms-transform:scale(1) translate(-50%,-55%);transform:scale(1) translate(-50%,-55%);opacity:1}.tippy-popper[x-placement^=top] [x-circle].leave{-webkit-transform:scale(.15) translate(-50%,-50%);-ms-transform:scale(.15) translate(-50%,-50%);transform:scale(.15) translate(-50%,-50%);opacity:0}.tippy-popper[x-placement^=top] .tippy-tooltip.light-theme [x-circle]{background-color:#fff}.tippy-popper[x-placement^=top] .tippy-tooltip.light-theme [x-arrow]{border-top:7px solid #fff;border-right:7px solid transparent;border-left:7px solid transparent}.tippy-popper[x-placement^=top] .tippy-tooltip.light-theme [x-arrow].arrow-small{border-top:5px solid #fff;border-right:5px solid transparent;border-left:5px solid transparent}.tippy-popper[x-placement^=top] .tippy-tooltip.light-theme [x-arrow].arrow-big{border-top:10px solid #fff;border-right:10px solid transparent;border-left:10px solid transparent}.tippy-popper[x-placement^=top] .tippy-tooltip.transparent-theme [x-circle]{background-color:rgba(0,0,0,.7)}.tippy-popper[x-placement^=top] .tippy-tooltip.transparent-theme [x-arrow]{border-top:7px solid rgba(0,0,0,.7);border-right:7px solid transparent;border-left:7px solid transparent}.tippy-popper[x-placement^=top] .tippy-tooltip.transparent-theme [x-arrow].arrow-small{border-top:5px solid rgba(0,0,0,.7);border-right:5px solid transparent;border-left:5px solid transparent}.tippy-popper[x-placement^=top] .tippy-tooltip.transparent-theme [x-arrow].arrow-big{border-top:10px solid rgba(0,0,0,.7);border-right:10px solid transparent;border-left:10px solid transparent}.tippy-popper[x-placement^=top] [data-animation=perspective]{-webkit-transform-origin:bottom;-ms-transform-origin:bottom;transform-origin:bottom}.tippy-popper[x-placement^=top] [data-animation=perspective].enter{opacity:1;-webkit-transform:translateY(-10px) rotateX(0);transform:translateY(-10px) rotateX(0)}.tippy-popper[x-placement^=top] [data-animation=perspective].leave{opacity:0;-webkit-transform:translateY(0) rotateX(90deg);transform:translateY(0) rotateX(90deg)}.tippy-popper[x-placement^=top] [data-animation=fade].enter{opacity:1;-webkit-transform:translateY(-10px);-ms-transform:translateY(-10px);transform:translateY(-10px)}.tippy-popper[x-placement^=top] [data-animation=fade].leave{opacity:0;-webkit-transform:translateY(-10px);-ms-transform:translateY(-10px);transform:translateY(-10px)}.tippy-popper[x-placement^=top] [data-animation=shift].enter{opacity:1;-webkit-transform:translateY(-10px);-ms-transform:translateY(-10px);transform:translateY(-10px)}.tippy-popper[x-placement^=top] [data-animation=shift].leave{opacity:0;-webkit-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}.tippy-popper[x-placement^=top] [data-animation=scale].enter{opacity:1;-webkit-transform:translateY(-10px) scale(1);-ms-transform:translateY(-10px) scale(1);transform:translateY(-10px) scale(1)}.tippy-popper[x-placement^=top] [data-animation=scale].leave{opacity:0;-webkit-transform:translateY(0) scale(0);-ms-transform:translateY(0) scale(0);transform:translateY(0) scale(0)}.tippy-popper[x-placement^=bottom] [x-arrow]{border-bottom:7px solid #333;border-right:7px solid transparent;border-left:7px solid transparent;top:-7px;margin:0 9px}.tippy-popper[x-placement^=bottom] [x-arrow].arrow-small{border-bottom:5px solid #333;border-right:5px solid transparent;border-left:5px solid transparent;top:-5px}.tippy-popper[x-placement^=bottom] [x-arrow].arrow-big{border-bottom:10px solid #333;border-right:10px solid transparent;border-left:10px solid transparent;top:-10px}.tippy-popper[x-placement^=bottom] [x-circle]{-webkit-transform-origin:0 -50%;-ms-transform-origin:0 -50%;transform-origin:0 -50%}.tippy-popper[x-placement^=bottom] [x-circle].enter{-webkit-transform:scale(1) translate(-50%,-45%);-ms-transform:scale(1) translate(-50%,-45%);transform:scale(1) translate(-50%,-45%);opacity:1}.tippy-popper[x-placement^=bottom] [x-circle].leave{-webkit-transform:scale(.15) translate(-50%,-5%);-ms-transform:scale(.15) translate(-50%,-5%);transform:scale(.15) translate(-50%,-5%);opacity:0}.tippy-popper[x-placement^=bottom] .tippy-tooltip.light-theme [x-circle]{background-color:#fff}.tippy-popper[x-placement^=bottom] .tippy-tooltip.light-theme [x-arrow]{border-bottom:7px solid #fff;border-right:7px solid transparent;border-left:7px solid transparent}.tippy-popper[x-placement^=bottom] .tippy-tooltip.light-theme [x-arrow].arrow-small{border-bottom:5px solid #fff;border-right:5px solid transparent;border-left:5px solid transparent}.tippy-popper[x-placement^=bottom] .tippy-tooltip.light-theme [x-arrow].arrow-big{border-bottom:10px solid #fff;border-right:10px solid transparent;border-left:10px solid transparent}.tippy-popper[x-placement^=bottom] .tippy-tooltip.transparent-theme [x-circle]{background-color:rgba(0,0,0,.7)}.tippy-popper[x-placement^=bottom] .tippy-tooltip.transparent-theme [x-arrow]{border-bottom:7px solid rgba(0,0,0,.7);border-right:7px solid transparent;border-left:7px solid transparent}.tippy-popper[x-placement^=bottom] .tippy-tooltip.transparent-theme [x-arrow].arrow-small{border-bottom:5px solid rgba(0,0,0,.7);border-right:5px solid transparent;border-left:5px solid transparent}.tippy-popper[x-placement^=bottom] .tippy-tooltip.transparent-theme [x-arrow].arrow-big{border-bottom:10px solid rgba(0,0,0,.7);border-right:10px solid transparent;border-left:10px solid transparent}.tippy-popper[x-placement^=bottom] [data-animation=perspective]{-webkit-transform-origin:top;-ms-transform-origin:top;transform-origin:top}.tippy-popper[x-placement^=bottom] [data-animation=perspective].enter{opacity:1;-webkit-transform:translateY(10px) rotateX(0);transform:translateY(10px) rotateX(0)}.tippy-popper[x-placement^=bottom] [data-animation=perspective].leave{opacity:0;-webkit-transform:translateY(0) rotateX(-90deg);transform:translateY(0) rotateX(-90deg)}.tippy-popper[x-placement^=bottom] [data-animation=fade].enter{opacity:1;-webkit-transform:translateY(10px);-ms-transform:translateY(10px);transform:translateY(10px)}.tippy-popper[x-placement^=bottom] [data-animation=fade].leave{opacity:0;-webkit-transform:translateY(10px);-ms-transform:translateY(10px);transform:translateY(10px)}.tippy-popper[x-placement^=bottom] [data-animation=shift].enter{opacity:1;-webkit-transform:translateY(10px);-ms-transform:translateY(10px);transform:translateY(10px)}.tippy-popper[x-placement^=bottom] [data-animation=shift].leave{opacity:0;-webkit-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}.tippy-popper[x-placement^=bottom] [data-animation=scale].enter{opacity:1;-webkit-transform:translateY(10px) scale(1);-ms-transform:translateY(10px) scale(1);transform:translateY(10px) scale(1)}.tippy-popper[x-placement^=bottom] [data-animation=scale].leave{opacity:0;-webkit-transform:translateY(0) scale(0);-ms-transform:translateY(0) scale(0);transform:translateY(0) scale(0)}.tippy-popper[x-placement^=left] [x-arrow]{border-left:7px solid #333;border-top:7px solid transparent;border-bottom:7px solid transparent;right:-7px;margin:6px 0}.tippy-popper[x-placement^=left] [x-arrow].arrow-small{border-left:5px solid #333;border-top:5px solid transparent;border-bottom:5px solid transparent;right:-5px}.tippy-popper[x-placement^=left] [x-arrow].arrow-big{border-left:10px solid #333;border-top:10px solid transparent;border-bottom:10px solid transparent;right:-10px}.tippy-popper[x-placement^=left] [x-circle]{-webkit-transform-origin:50% 0;-ms-transform-origin:50% 0;transform-origin:50% 0}.tippy-popper[x-placement^=left] [x-circle].enter{-webkit-transform:scale(1) translate(-50%,-50%);-ms-transform:scale(1) translate(-50%,-50%);transform:scale(1) translate(-50%,-50%);opacity:1}.tippy-popper[x-placement^=left] [x-circle].leave{-webkit-transform:scale(.15) translate(-50%,-50%);-ms-transform:scale(.15) translate(-50%,-50%);transform:scale(.15) translate(-50%,-50%);opacity:0}.tippy-popper[x-placement^=left] .tippy-tooltip.light-theme [x-circle]{background-color:#fff}.tippy-popper[x-placement^=left] .tippy-tooltip.light-theme [x-arrow]{border-left:7px solid #fff;border-top:7px solid transparent;border-bottom:7px solid transparent}.tippy-popper[x-placement^=left] .tippy-tooltip.light-theme [x-arrow].arrow-small{border-left:5px solid #fff;border-top:5px solid transparent;border-bottom:5px solid transparent}.tippy-popper[x-placement^=left] .tippy-tooltip.light-theme [x-arrow].arrow-big{border-left:10px solid #fff;border-top:10px solid transparent;border-bottom:10px solid transparent}.tippy-popper[x-placement^=left] .tippy-tooltip.transparent-theme [x-circle]{background-color:rgba(0,0,0,.7)}.tippy-popper[x-placement^=left] .tippy-tooltip.transparent-theme [x-arrow]{border-left:7px solid rgba(0,0,0,.7);border-top:7px solid transparent;border-bottom:7px solid transparent}.tippy-popper[x-placement^=left] .tippy-tooltip.transparent-theme [x-arrow].arrow-small{border-left:5px solid rgba(0,0,0,.7);border-top:5px solid transparent;border-bottom:5px solid transparent}.tippy-popper[x-placement^=left] .tippy-tooltip.transparent-theme [x-arrow].arrow-big{border-left:10px solid rgba(0,0,0,.7);border-top:10px solid transparent;border-bottom:10px solid transparent}.tippy-popper[x-placement^=left] [data-animation=perspective]{-webkit-transform-origin:right;-ms-transform-origin:right;transform-origin:right}.tippy-popper[x-placement^=left] [data-animation=perspective].enter{opacity:1;-webkit-transform:translateX(-10px) rotateY(0);transform:translateX(-10px) rotateY(0)}.tippy-popper[x-placement^=left] [data-animation=perspective].leave{opacity:0;-webkit-transform:translateX(0) rotateY(-90deg);transform:translateX(0) rotateY(-90deg)}.tippy-popper[x-placement^=left] [data-animation=fade].enter{opacity:1;-webkit-transform:translateX(-10px);-ms-transform:translateX(-10px);transform:translateX(-10px)}.tippy-popper[x-placement^=left] [data-animation=fade].leave{opacity:0;-webkit-transform:translateX(-10px);-ms-transform:translateX(-10px);transform:translateX(-10px)}.tippy-popper[x-placement^=left] [data-animation=shift].enter{opacity:1;-webkit-transform:translateX(-10px);-ms-transform:translateX(-10px);transform:translateX(-10px)}.tippy-popper[x-placement^=left] [data-animation=shift].leave{opacity:0;-webkit-transform:translateX(0);-ms-transform:translateX(0);transform:translateX(0)}.tippy-popper[x-placement^=left] [data-animation=scale].enter{opacity:1;-webkit-transform:translateX(-10px) scale(1);-ms-transform:translateX(-10px) scale(1);transform:translateX(-10px) scale(1)}.tippy-popper[x-placement^=left] [data-animation=scale].leave{opacity:0;-webkit-transform:translateX(0) scale(0);-ms-transform:translateX(0) scale(0);transform:translateX(0) scale(0)}.tippy-popper[x-placement^=right] [x-arrow]{border-right:7px solid #333;border-top:7px solid transparent;border-bottom:7px solid transparent;left:-7px;margin:6px 0}.tippy-popper[x-placement^=right] [x-arrow].arrow-small{border-right:5px solid #333;border-top:5px solid transparent;border-bottom:5px solid transparent;left:-5px}.tippy-popper[x-placement^=right] [x-arrow].arrow-big{border-right:10px solid #333;border-top:10px solid transparent;border-bottom:10px solid transparent;left:-10px}.tippy-popper[x-placement^=right] [x-circle]{-webkit-transform-origin:-50% 0;-ms-transform-origin:-50% 0;transform-origin:-50% 0}.tippy-popper[x-placement^=right] [x-circle].enter{-webkit-transform:scale(1) translate(-50%,-50%);-ms-transform:scale(1) translate(-50%,-50%);transform:scale(1) translate(-50%,-50%);opacity:1}.tippy-popper[x-placement^=right] [x-circle].leave{-webkit-transform:scale(.15) translate(-50%,-50%);-ms-transform:scale(.15) translate(-50%,-50%);transform:scale(.15) translate(-50%,-50%);opacity:0}.tippy-popper[x-placement^=right] .tippy-tooltip.light-theme [x-circle]{background-color:#fff}.tippy-popper[x-placement^=right] .tippy-tooltip.light-theme [x-arrow]{border-right:7px solid #fff;border-top:7px solid transparent;border-bottom:7px solid transparent}.tippy-popper[x-placement^=right] .tippy-tooltip.light-theme [x-arrow].arrow-small{border-right:5px solid #fff;border-top:5px solid transparent;border-bottom:5px solid transparent}.tippy-popper[x-placement^=right] .tippy-tooltip.light-theme [x-arrow].arrow-big{border-right:10px solid #fff;border-top:10px solid transparent;border-bottom:10px solid transparent}.tippy-popper[x-placement^=right] .tippy-tooltip.transparent-theme [x-circle]{background-color:rgba(0,0,0,.7)}.tippy-popper[x-placement^=right] .tippy-tooltip.transparent-theme [x-arrow]{border-right:7px solid rgba(0,0,0,.7);border-top:7px solid transparent;border-bottom:7px solid transparent}.tippy-popper[x-placement^=right] .tippy-tooltip.transparent-theme [x-arrow].arrow-small{border-right:5px solid rgba(0,0,0,.7);border-top:5px solid transparent;border-bottom:5px solid transparent}.tippy-popper[x-placement^=right] .tippy-tooltip.transparent-theme [x-arrow].arrow-big{border-right:10px solid rgba(0,0,0,.7);border-top:10px solid transparent;border-bottom:10px solid transparent}.tippy-popper[x-placement^=right] [data-animation=perspective]{-webkit-transform-origin:left;-ms-transform-origin:left;transform-origin:left}.tippy-popper[x-placement^=right] [data-animation=perspective].enter{opacity:1;-webkit-transform:translateX(10px) rotateY(0);transform:translateX(10px) rotateY(0)}.tippy-popper[x-placement^=right] [data-animation=perspective].leave{opacity:0;-webkit-transform:translateX(0) rotateY(90deg);transform:translateX(0) rotateY(90deg)}.tippy-popper[x-placement^=right] [data-animation=fade].enter{opacity:1;-webkit-transform:translateX(10px);-ms-transform:translateX(10px);transform:translateX(10px)}.tippy-popper[x-placement^=right] [data-animation=fade].leave{opacity:0;-webkit-transform:translateX(10px);-ms-transform:translateX(10px);transform:translateX(10px)}.tippy-popper[x-placement^=right] [data-animation=shift].enter{opacity:1;-webkit-transform:translateX(10px);-ms-transform:translateX(10px);transform:translateX(10px)}.tippy-popper[x-placement^=right] [data-animation=shift].leave{opacity:0;-webkit-transform:translateX(0);-ms-transform:translateX(0);transform:translateX(0)}.tippy-popper[x-placement^=right] [data-animation=scale].enter{opacity:1;-webkit-transform:translateX(10px) scale(1);-ms-transform:translateX(10px) scale(1);transform:translateX(10px) scale(1)}.tippy-popper[x-placement^=right] [data-animation=scale].leave{opacity:0;-webkit-transform:translateX(0) scale(0);-ms-transform:translateX(0) scale(0);transform:translateX(0) scale(0)}.tippy-popper .tippy-tooltip.transparent-theme{background-color:rgba(0,0,0,.7)}.tippy-popper .tippy-tooltip.transparent-theme[data-animatefill]{background-color:transparent}.tippy-popper .tippy-tooltip.light-theme{color:#26323d;-webkit-box-shadow:0 4px 20px 4px rgba(0,20,60,.1),0 4px 80px -8px rgba(0,20,60,.2);box-shadow:0 4px 20px 4px rgba(0,20,60,.1),0 4px 80px -8px rgba(0,20,60,.2);background-color:#fff}.tippy-popper .tippy-tooltip.light-theme[data-animatefill]{background-color:transparent}.tippy-tooltip{position:relative;color:#fff;border-radius:4px;font-size:.95rem;padding:.4rem .8rem;text-align:center;will-change:transform;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;background-color:#333}.tippy-tooltip--small{padding:.25rem .5rem;font-size:.8rem}.tippy-tooltip--big{padding:.6rem 1.2rem;font-size:1.2rem}.tippy-tooltip[data-animatefill]{overflow:hidden;background-color:transparent}.tippy-tooltip[data-interactive]{pointer-events:auto}.tippy-tooltip[data-inertia]{-webkit-transition-timing-function:cubic-bezier(.53,2,.36,.85);-o-transition-timing-function:cubic-bezier(.53,2,.36,.85);transition-timing-function:cubic-bezier(.53,2,.36,.85)}.tippy-tooltip [x-arrow]{position:absolute;width:0;height:0}.tippy-tooltip [x-circle]{position:absolute;will-change:transform;background-color:#333;border-radius:50%;width:130%;width:calc(110% + 2rem);left:50%;top:50%;z-index:-1;overflow:hidden;-webkit-transition:all ease;-o-transition:all ease;transition:all ease}.tippy-tooltip [x-circle]:before{content:"";padding-top:90%;float:left}@media (max-width:450px){.tippy-popper{max-width:96%;max-width:calc(100% - 20px)}}@-webkit-keyframes dots-pulse{0%{opacity:.1}15%{opacity:1}to{opacity:.1}}.rai-dots .rai-circle{border-radius:100%;display:inline-block;margin-right:.1875em;background-color:#727981;width:.5em;height:.5em;-webkit-animation-name:dots-pulse;animation-name:dots-pulse;-webkit-animation-duration:inherit;animation-duration:inherit;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite}.rai-dots .rai-circle:last-child{margin-right:0}@-webkit-keyframes levels-pulse{0%{height:30%;opacity:.5}20%{height:100%;opacity:1}35%{height:30%;opacity:.5}55%{height:100%;opacity:1}70%{height:50%;opacity:.5}80%{height:100%;opacity:1}to{height:30%;opacity:.5}}@keyframes levels-pulse{0%{height:30%;opacity:.5}20%{height:100%;opacity:1}35%{height:30%;opacity:.5}55%{height:100%;opacity:1}70%{height:50%;opacity:.5}80%{height:100%;opacity:1}to{height:30%;opacity:.5}}.rai-levels .rai-levels-container{text-align:left;height:1em;line-height:1em}.rai-levels .rai-bar,.rai-levels .rai-levels-container{display:inline-block;-webkit-animation-duration:inherit;animation-duration:inherit}.rai-levels .rai-bar{bottom:0;vertical-align:bottom;width:.25em;background:#727981;margin-right:.125em;height:50%;-webkit-animation-name:levels-pulse;animation-name:levels-pulse;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite}.rai-levels .rai-bar:last-child{margin-right:0}@-webkit-keyframes rai-sentry-pulse{0%{-webkit-transform:scale(0);transform:scale(0);opacity:1}to{-webkit-transform:scale(1);transform:scale(1);opacity:0}}@keyframes rai-sentry-pulse{0%{-webkit-transform:scale(0);transform:scale(0);opacity:1}to{-webkit-transform:scale(1);transform:scale(1);opacity:0}}.rai-sentry{text-align:left;display:inline-block;position:relative;width:2em;height:2em}.rai-sentry .rai-wave-container{display:-ms-inline-flexbox;display:inline-flex;-ms-flex-align:center;align-items:center;-ms-flex-pack:center;justify-content:center;position:absolute}.rai-sentry .rai-wave,.rai-sentry .rai-wave-container{width:100%;height:100%;-webkit-animation-duration:inherit;animation-duration:inherit}.rai-sentry .rai-wave{display:inline-block;border:.125em solid #727981;border-radius:100%;-webkit-box-sizing:border-box;box-sizing:border-box;-webkit-animation-name:rai-sentry-pulse;animation-name:rai-sentry-pulse;-webkit-animation-timing-function:linear;animation-timing-function:linear;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite}@-webkit-keyframes rai-spinner{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes rai-spinner{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}.rai-spinner{width:1.5em;height:1.5em;position:relative;margin:0}.rai-spinner-inner,.rai-spinner-outer{position:absolute;top:0;left:0;width:100%;height:100%;border-radius:100%;margin-left:-.0625em;margin-top:-.0625em}.rai-spinner-outer{border:.125em solid #727981;opacity:.2}.rai-spinner-inner{position:absolute;top:0;left:0;width:100%;height:100%;border-radius:100%;border-top:.125em solid #727981;border-right:.125em solid transparent!important;border-bottom:.125em solid transparent!important;border-left:.125em solid transparent!important;-webkit-animation-name:rai-spinner;animation-name:rai-spinner;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite;-webkit-animation-timing-function:linear;animation-timing-function:linear;-webkit-animation-duration:inherit;animation-duration:inherit}@keyframes dots-pulse{0%{opacity:.1}15%{opacity:1}to{opacity:.1}}.rai-squares .rai-square{display:inline-block;margin-right:.1875em;background-color:#727981;width:.5em;height:.5em;-webkit-animation-name:dots-pulse;animation-name:dots-pulse;-webkit-animation-duration:inherit;animation-duration:inherit;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite}.rai-squares .rai-square:last-child{margin-right:0}@-webkit-keyframes digital-pulse{0%,40%,to{-webkit-transform:scaleY(.4);transform:scaleY(.4)}20%{-webkit-transform:scaleY(1);transform:scaleY(1)}}@keyframes digital-pulse{0%,40%,to{-webkit-transform:scaleY(.4);transform:scaleY(.4)}20%{-webkit-transform:scaleY(1);transform:scaleY(1)}}.rai-digital{height:1em;text-align:center}.rai-digital>div{display:inline-block;margin-right:.125em;background-color:#727981;width:.25em;height:100%;-webkit-animation-name:digital-pulse;animation-name:digital-pulse;-webkit-animation-duration:inherit;animation-duration:inherit;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite}.rai-digital>div:last-child{margin-right:0}@-webkit-keyframes bounce-pulse{0%{-webkit-transform:translateY(.1875em);transform:translateY(.1875em)}30%{-webkit-transform:translateY(-.25em);transform:translateY(-.25em)}50%{-webkit-transform:translateY(3);transform:translateY(3)}}@keyframes bounce-pulse{0%{-webkit-transform:translateY(.1875em);transform:translateY(.1875em)}30%{-webkit-transform:translateY(-.25em);transform:translateY(-.25em)}50%{-webkit-transform:translateY(3);transform:translateY(3)}}.rai-bounce>div{-webkit-transform:translateY(.1875em);-ms-transform:translateY(.1875em);transform:translateY(.1875em);border-radius:100%;display:inline-block;margin-right:.1875em;background-color:#727981;width:.5em;height:.5em;-webkit-animation-name:bounce-pulse;animation-name:bounce-pulse;-webkit-animation-duration:inherit;animation-duration:inherit;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite}.rai-bounce>div:last-child{margin-right:0}@-webkit-keyframes windill-pulse{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}99%{-webkit-transform:rotate(356deg);transform:rotate(356deg)}}@keyframes windill-pulse{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}99%{-webkit-transform:rotate(356deg);transform:rotate(356deg)}}.rai-windill{width:1em;height:1em;position:relative}.rai-windill>div{border-radius:.125em;position:absolute;top:0;left:50%;margin-left:-.0625em;width:.125em;height:100%;-webkit-animation-name:windill-pulse;animation-name:windill-pulse;-webkit-animation-duration:inherit;animation-duration:inherit;-webkit-animation-timing-function:linear;animation-timing-function:linear;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite}.DayPicker{display:inline-block}.DayPicker-wrapper{position:relative;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;padding-bottom:1rem;-ms-flex-direction:row;flex-direction:row}.DayPicker-Months{display:-ms-flexbox;display:flex;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-pack:center;justify-content:center}.DayPicker-Month{display:table;border-collapse:collapse;border-spacing:0;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;margin:0 1rem;margin-top:1rem}.DayPicker-NavButton{position:absolute;cursor:pointer;top:1rem;right:1.5rem;margin-top:2px;color:#8b9898;width:1.25rem;height:1.25rem;display:inline-block;background-size:50%;background-repeat:no-repeat;background-position:50%}.DayPicker-NavButton:hover{opacity:.8}.DayPicker-NavButton--prev{margin-right:1.5rem;background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAwCAYAAAB5R9gVAAAABGdBTUEAALGPC/xhBQAAAVVJREFUWAnN2G0KgjAYwPHpGfRkaZeqvgQaK+hY3SUHrk1YzNLay/OiEFp92I+/Mp2F2Mh2lLISWnflFjzH263RQjzMZ19wgs73ez0o1WmtW+dgA01VxrE3p6l2GLsnBy1VYQOtVSEH/atCCgqpQgKKqYIOiq2CBkqtggLKqQIKgqgCBjpJ2Y5CdJ+zrT9A7HHSTA1dxUdHgzCqJIEwq0SDsKsEg6iqBIEoq/wEcVRZBXFV+QJxV5mBtlDFB5VjYTaGZ2sf4R9PM7U9ZU+lLuaetPP/5Die3ToO1+u+MKtHs06qODB2zBnI/jBd4MPQm1VkY79Tb18gB+C62FdBFsZR6yeIo1YQiLJWMIiqVjQIu1YSCLNWFgijVjYIuhYYCKoWKAiiFgoopxYaKLUWOii2FgkophYp6F3r42W5A9s9OcgNvva8xQaysKXlFytoqdYmQH6tF3toSUo0INq9AAAAAElFTkSuQmCC")}.DayPicker-NavButton--next{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAwCAYAAAB5R9gVAAAABGdBTUEAALGPC/xhBQAAAXRJREFUWAnN119ugjAcwPHWzJ1gnmxzB/BBE0n24m4xfNkTaOL7wOtsl3AXMMb+Vjaa1BG00N8fSEibPpAP3xAKKs2yjzTPH9RAjhEo9WzPr/Vm8zgE0+gXATAxxuxtqeJ9t5tIwv5AtQAApsfT6TPdbp+kUBcgVwvO51KqVhMkXKsVJFXrOkigVhCIs1Y4iKlWZxB1rX4gwlpRIIpa8SDkWmggrFq4IIRaJKCYWnSgnrXIQV1r8YD+1Vrn+bReagysIFfLABRt31v8oBu1xEBttfRbltmfjgEcWh9snUS2kNdBK6WN1vrOWxObWsz+fjxevsxmB1GQDfINWiev83nhaoiB/CoOU438oPrhXS0WpQ9xc1ZQWxWHqUYe0I0qrKCQKjygDlXIQV2r0IF6ViEBxVTBBSFUQQNhVYkHIVeJAtkNsbQ7c1LtzP6FsObhb2rCKv7NBIGoq4SDmKoEgTirXAcJVGkFSVVpgoSrXICGUMUH/QBZNSUy5XWUhwAAAABJRU5ErkJggg==")}.DayPicker-NavButton--interactionDisabled{display:none}.DayPicker-Caption{padding:0 .5rem;display:table-caption;text-align:left;margin-bottom:.5rem}.DayPicker-Caption>div{font-size:1.15rem;font-weight:500}.DayPicker-Weekdays{margin-top:1rem;display:table-header-group}.DayPicker-WeekdaysRow{display:table-row}.DayPicker-Weekday{display:table-cell;padding:.5rem;font-size:.875em;text-align:center;color:#8b9898}.DayPicker-Weekday abbr[title]{border-bottom:none;text-decoration:none}.DayPicker-Body{display:table-row-group}.DayPicker-Week{display:table-row}.DayPicker-Day{text-align:center;outline:none}.DayPicker-Day,.DayPicker-WeekNumber{display:table-cell;padding:.5rem;cursor:pointer;vertical-align:middle}.DayPicker-WeekNumber{text-align:right;min-width:1rem;font-size:.75em;color:#8b9898;border-right:1px solid #eaecec}.DayPicker--interactionDisabled .DayPicker-Day{cursor:default}.DayPicker-Footer{padding-top:.5rem}.DayPicker-TodayButton{border:none;background-image:none;background-color:transparent;-webkit-box-shadow:none;box-shadow:none;cursor:pointer;color:#4a90e2;font-size:.875em}.DayPicker-Day--today{color:#d0021b;font-weight:700}.DayPicker-Day--outside{cursor:default;color:#8b9898}.DayPicker-Day--disabled{color:#dce0e0;cursor:default}.DayPicker-Day--sunday{background-color:#f7f8f8}.DayPicker-Day--sunday:not(.DayPicker-Day--today){color:#dce0e0}.DayPicker-Day--selected:not(.DayPicker-Day--disabled):not(.DayPicker-Day--outside){position:relative;color:#f0f8ff;background-color:#4a90e2;border-radius:100%}.DayPicker-Day--selected:not(.DayPicker-Day--disabled):not(.DayPicker-Day--outside):hover{background-color:#51a0fa}.DayPicker:not(.DayPicker--interactionDisabled) .DayPicker-Day:not(.DayPicker-Day--disabled):not(.DayPicker-Day--selected):not(.DayPicker-Day--outside):hover{background-color:#f0f8ff;border-radius:50%}.DayPickerInput{display:inline-block}.DayPickerInput-OverlayWrapper{position:relative}.DayPickerInput-Overlay{left:0;z-index:1;position:absolute;background:#fff;-webkit-box-shadow:0 2px 5px rgba(0,0,0,.15);box-shadow:0 2px 5px rgba(0,0,0,.15)}.ReactTable,.ReactTable .rt-table{display:-ms-flexbox}.ReactTable{position:relative;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column}.ReactTable *{-webkit-box-sizing:border-box;box-sizing:border-box}.ReactTable .rt-table{-ms-flex:auto 1;flex:auto 1;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-align:stretch;align-items:stretch;width:100%;border-collapse:collapse;overflow:auto}.ReactTable .rt-thead{-ms-flex:1 0 auto;flex:1 0 auto;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.ReactTable .rt-thead .rt-resizable-header{overflow:visible}.ReactTable .rt-thead .rt-resizable-header:last-child{overflow:hidden}.ReactTable .rt-thead .rt-resizable-header-content{overflow:hidden;-o-text-overflow:ellipsis;text-overflow:ellipsis}.ReactTable .rt-tbody{-ms-flex:99999 1 auto;flex:99999 1 auto;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;overflow:auto}.ReactTable .rt-tfoot,.ReactTable .rt-tr-group{display:-ms-flexbox}.ReactTable .rt-tbody .rt-expandable{cursor:pointer}.ReactTable .rt-tr-group{-ms-flex:1 0 auto;flex:1 0 auto;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-ms-flex-align:stretch;align-items:stretch}.ReactTable .rt-tr{-ms-flex:1 0 auto;flex:1 0 auto;display:-ms-inline-flexbox;display:inline-flex}.ReactTable .rt-td,.ReactTable .rt-th{-ms-flex:1 0 0px;flex:1 0;white-space:nowrap;-o-text-overflow:ellipsis;text-overflow:ellipsis;overflow:hidden;-webkit-transition:.3s ease;-o-transition:.3s ease;transition:.3s ease;-webkit-transition-property:width,min-width,padding,opacity;-o-transition-property:width,min-width,padding,opacity;transition-property:width,min-width,padding,opacity}.ReactTable .rt-td.-hidden,.ReactTable .rt-th.-hidden{width:0!important;min-width:0!important;padding:0!important;border:0!important;opacity:0!important}.ReactTable .rt-expander{display:inline-block;position:relative;color:transparent;margin:0 10px}.ReactTable .rt-resizer{display:inline-block;position:absolute;width:36px;top:0;bottom:0;right:-18px;cursor:col-resize;z-index:10}.ReactTable .rt-tfoot{-ms-flex:1 0 auto;flex:1 0 auto;display:-ms-flexbox;display:flex;-ms-flex-direction:column;flex-direction:column;-webkit-box-shadow:0 0 15px 0 rgba(0,0,0,.15);box-shadow:0 0 15px 0 rgba(0,0,0,.15)}.ReactTable.-striped .rt-tr.-odd{background:rgba(0,0,0,.03)}.ReactTable.-highlight .rt-tbody .rt-tr:not(.-padRow):hover{background:rgba(0,0,0,.05)}.ReactTable .-pagination{z-index:1;display:-ms-flexbox;display:flex;-ms-flex-pack:justify;justify-content:space-between;-ms-flex-align:stretch;align-items:stretch;-ms-flex-wrap:wrap;flex-wrap:wrap}.ReactTable .-pagination input,.ReactTable .-pagination select{border:1px solid rgba(0,0,0,.1);background:#fff;padding:5px 7px;font-size:inherit;border-radius:3px;font-weight:400;outline:0}.ReactTable .-pagination .-btn{-webkit-appearance:none;-moz-appearance:none;appearance:none;display:block;width:100%;height:100%;border:0;border-radius:3px;padding:6px;font-size:1em;color:rgba(0,0,0,.6);background:rgba(0,0,0,.1);-webkit-transition:all .1s ease;-o-transition:all .1s ease;transition:all .1s ease;cursor:pointer;outline:0}.ReactTable .-pagination .-btn[disabled]{opacity:.5;cursor:default}.ReactTable .-pagination .-btn:not([disabled]):hover{background:rgba(0,0,0,.3);color:#fff}.ReactTable .-loading,.ReactTable .rt-noData{background:hsla(0,0%,100%,.8);-webkit-transition:all .3s ease;-o-transition:all .3s ease;transition:all .3s ease;pointer-events:none;position:absolute}.ReactTable .-pagination .-next,.ReactTable .-pagination .-previous{-ms-flex:1;flex:1 1;text-align:center}.ReactTable .-pagination .-center{-ms-flex:1.5;flex:1.5 1;text-align:center;margin-bottom:0;display:-ms-flexbox;display:flex;-ms-flex-direction:row;flex-direction:row;-ms-flex-wrap:wrap;flex-wrap:wrap;-ms-flex-align:center;align-items:center;-ms-flex-pack:distribute;justify-content:space-around}.ReactTable .-pagination .-pageInfo{display:inline-block;margin:3px 10px;white-space:nowrap}.ReactTable .-pagination .-pageJump{display:inline-block}.ReactTable .-pagination .-pageJump input{width:70px;text-align:center}.ReactTable .-pagination .-pageSizeOptions{margin:3px 10px}.ReactTable .rt-noData{display:block;left:50%;top:50%;-webkit-transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%);transform:translate(-50%,-50%);z-index:1;padding:20px;color:rgba(0,0,0,.5)}.ReactTable .-loading{display:block;left:0;right:0;top:0;bottom:0;z-index:-1;opacity:0}.ReactTable .-loading>div{position:absolute;display:block;text-align:center;width:100%;top:50%;left:0;font-size:15px;color:rgba(0,0,0,.6);-webkit-transform:translateY(-52%);-ms-transform:translateY(-52%);transform:translateY(-52%);-webkit-transition:all .3s cubic-bezier(.25,.46,.45,.94);-o-transition:all .3s cubic-bezier(.25,.46,.45,.94);transition:all .3s cubic-bezier(.25,.46,.45,.94)}.ReactTable .-loading.-active{opacity:1;z-index:2;pointer-events:all}.ReactTable .-loading.-active>div{-webkit-transform:translateY(50%);-ms-transform:translateY(50%);transform:translateY(50%)}.ReactTable .rt-resizing .rt-td,.ReactTable .rt-resizing .rt-th{-webkit-transition:none!important;-o-transition:none!important;transition:none!important;cursor:col-resize;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none} 2 | /*# sourceMappingURL=main.3d7c04de.css.map*/ -------------------------------------------------------------------------------- /gui/public/cryptagon/Your Portfolio _ Cryptagon_files/polyfills.ff0bf707.js: -------------------------------------------------------------------------------- 1 | webpackJsonp([1],{2758:function(t,e,r){"use strict";"undefined"===typeof Promise&&(r(2759).enable(),window.Promise=r(695)),r(2760),Object.assign=r(67)},2759:function(t,e,r){"use strict";function o(){f=!1,a._47=null,a._71=null}function n(t){function e(e){(t.allRejections||s(h[e].error,t.whitelist||u))&&(h[e].displayId=d++,t.onUnhandled?(h[e].logged=!0,t.onUnhandled(h[e].displayId,h[e].error)):(h[e].logged=!0,i(h[e].displayId,h[e].error)))}function r(e){h[e].logged&&(t.onHandled?t.onHandled(h[e].displayId,h[e].error):h[e].onUnhandled||(console.warn("Promise Rejection Handled (id: "+h[e].displayId+"):"),console.warn(' This means you can ignore any previous messages of the form "Possible Unhandled Promise Rejection" with id '+h[e].displayId+".")))}t=t||{},f&&o(),f=!0;var n=0,d=0,h={};a._47=function(t){2===t._83&&h[t._56]&&(h[t._56].logged?r(t._56):clearTimeout(h[t._56].timeout),delete h[t._56])},a._71=function(t,r){0===t._75&&(t._56=n++,h[t._56]={displayId:null,error:r,timeout:setTimeout(e.bind(null,t._56),s(r,u)?100:2e3),logged:!1})}}function i(t,e){console.warn("Possible Unhandled Promise Rejection (id: "+t+"):"),((e&&(e.stack||e))+"").split("\n").forEach(function(t){console.warn(" "+t)})}function s(t,e){return e.some(function(e){return t instanceof e})}var a=r(122),u=[ReferenceError,TypeError,RangeError],f=!1;e.disable=o,e.enable=n},2760:function(t,e){!function(t){"use strict";function e(t){if("string"!==typeof t&&(t=String(t)),/[^a-z0-9\-#$%&'*+.\^_`|~]/i.test(t))throw new TypeError("Invalid character in header field name");return t.toLowerCase()}function r(t){return"string"!==typeof t&&(t=String(t)),t}function o(t){var e={next:function(){var e=t.shift();return{done:void 0===e,value:e}}};return m.iterable&&(e[Symbol.iterator]=function(){return e}),e}function n(t){this.map={},t instanceof n?t.forEach(function(t,e){this.append(e,t)},this):Array.isArray(t)?t.forEach(function(t){this.append(t[0],t[1])},this):t&&Object.getOwnPropertyNames(t).forEach(function(e){this.append(e,t[e])},this)}function i(t){if(t.bodyUsed)return Promise.reject(new TypeError("Already read"));t.bodyUsed=!0}function s(t){return new Promise(function(e,r){t.onload=function(){e(t.result)},t.onerror=function(){r(t.error)}})}function a(t){var e=new FileReader,r=s(e);return e.readAsArrayBuffer(t),r}function u(t){var e=new FileReader,r=s(e);return e.readAsText(t),r}function f(t){for(var e=new Uint8Array(t),r=new Array(e.length),o=0;o-1?e:t}function c(t,e){e=e||{};var r=e.body;if(t instanceof c){if(t.bodyUsed)throw new TypeError("Already read");this.url=t.url,this.credentials=t.credentials,e.headers||(this.headers=new n(t.headers)),this.method=t.method,this.mode=t.mode,r||null==t._bodyInit||(r=t._bodyInit,t.bodyUsed=!0)}else this.url=String(t);if(this.credentials=e.credentials||this.credentials||"omit",!e.headers&&this.headers||(this.headers=new n(e.headers)),this.method=l(e.method||this.method||"GET"),this.mode=e.mode||this.mode||null,this.referrer=null,("GET"===this.method||"HEAD"===this.method)&&r)throw new TypeError("Body not allowed for GET or HEAD requests");this._initBody(r)}function y(t){var e=new FormData;return t.trim().split("&").forEach(function(t){if(t){var r=t.split("="),o=r.shift().replace(/\+/g," "),n=r.join("=").replace(/\+/g," ");e.append(decodeURIComponent(o),decodeURIComponent(n))}}),e}function p(t){var e=new n;return t.split(/\r?\n/).forEach(function(t){var r=t.split(":"),o=r.shift().trim();if(o){var n=r.join(":").trim();e.append(o,n)}}),e}function b(t,e){e||(e={}),this.type="default",this.status="status"in e?e.status:200,this.ok=this.status>=200&&this.status<300,this.statusText="statusText"in e?e.statusText:"OK",this.headers=new n(e.headers),this.url=e.url||"",this._initBody(t)}if(!t.fetch){var m={searchParams:"URLSearchParams"in t,iterable:"Symbol"in t&&"iterator"in Symbol,blob:"FileReader"in t&&"Blob"in t&&function(){try{return new Blob,!0}catch(t){return!1}}(),formData:"FormData"in t,arrayBuffer:"ArrayBuffer"in t};if(m.arrayBuffer)var w=["[object Int8Array]","[object Uint8Array]","[object Uint8ClampedArray]","[object Int16Array]","[object Uint16Array]","[object Int32Array]","[object Uint32Array]","[object Float32Array]","[object Float64Array]"],_=function(t){return t&&DataView.prototype.isPrototypeOf(t)},v=ArrayBuffer.isView||function(t){return t&&w.indexOf(Object.prototype.toString.call(t))>-1};n.prototype.append=function(t,o){t=e(t),o=r(o);var n=this.map[t];this.map[t]=n?n+","+o:o},n.prototype.delete=function(t){delete this.map[e(t)]},n.prototype.get=function(t){return t=e(t),this.has(t)?this.map[t]:null},n.prototype.has=function(t){return this.map.hasOwnProperty(e(t))},n.prototype.set=function(t,o){this.map[e(t)]=r(o)},n.prototype.forEach=function(t,e){for(var r in this.map)this.map.hasOwnProperty(r)&&t.call(e,this.map[r],r,this)},n.prototype.keys=function(){var t=[];return this.forEach(function(e,r){t.push(r)}),o(t)},n.prototype.values=function(){var t=[];return this.forEach(function(e){t.push(e)}),o(t)},n.prototype.entries=function(){var t=[];return this.forEach(function(e,r){t.push([r,e])}),o(t)},m.iterable&&(n.prototype[Symbol.iterator]=n.prototype.entries);var B=["DELETE","GET","HEAD","OPTIONS","POST","PUT"];c.prototype.clone=function(){return new c(this,{body:this._bodyInit})},h.call(c.prototype),h.call(b.prototype),b.prototype.clone=function(){return new b(this._bodyInit,{status:this.status,statusText:this.statusText,headers:new n(this.headers),url:this.url})},b.error=function(){var t=new b(null,{status:0,statusText:""});return t.type="error",t};var T=[301,302,303,307,308];b.redirect=function(t,e){if(-1===T.indexOf(e))throw new RangeError("Invalid status code");return new b(null,{status:e,headers:{location:t}})},t.Headers=n,t.Request=c,t.Response=b,t.fetch=function(t,e){return new Promise(function(r,o){var n=new c(t,e),i=new XMLHttpRequest;i.onload=function(){var t={status:i.status,statusText:i.statusText,headers:p(i.getAllResponseHeaders()||"")};t.url="responseURL"in i?i.responseURL:t.headers.get("X-Request-URL");var e="response"in i?i.response:i.responseText;r(new b(e,t))},i.onerror=function(){o(new TypeError("Network request failed"))},i.ontimeout=function(){o(new TypeError("Network request failed"))},i.open(n.method,n.url,!0),"include"===n.credentials&&(i.withCredentials=!0),"responseType"in i&&m.blob&&(i.responseType="blob"),n.headers.forEach(function(t,e){i.setRequestHeader(e,t)}),i.send("undefined"===typeof n._bodyInit?null:n._bodyInit)})},t.fetch.polyfill=!0}}("undefined"!==typeof self?self:this)}},[2758]); 2 | //# sourceMappingURL=polyfills.ff0bf707.js.map -------------------------------------------------------------------------------- /gui/public/cryptagon/Your Portfolio _ Cryptagon_files/saved_resource.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /gui/public/cryptagon/Your Portfolio _ Cryptagon_files/vzl82zgq: -------------------------------------------------------------------------------- 1 | !function(n){function t(o){if(e[o])return e[o].exports;var r=e[o]={i:o,l:!1,exports:{}};return n[o].call(r.exports,r,r.exports,t),r.l=!0,r.exports}var e={};t.m=n,t.c=e,t.d=function(n,e,o){t.o(n,e)||Object.defineProperty(n,e,{configurable:!1,enumerable:!0,get:o})},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,"a",e),e},t.o=function(n,t){return Object.prototype.hasOwnProperty.call(n,t)},t.p="https://js.intercomcdn.com/",t(t.s=360)}({360:function(n,t,e){n.exports=e(361)},361:function(n,t,e){"use strict";var o=e(362),r=e(363),i=r.addTurbolinksEventListeners,c="Intercom",u=/bot|googlebot|crawler|spider|robot|crawling/i,a=function(){return window[c]&&window[c].booted},d=function(){return window[c].booted=!0},s=function(){return"attachEvent"in window&&!window.addEventListener},f=function(){return navigator&&navigator.userAgent&&/MSIE 9\.0/.test(navigator.userAgent)&&window.addEventListener&&!window.atob},w=function(){return"onpropertychange"in document&&!!window.matchMedia&&/MSIE 10\.0/.test(navigator.userAgent)},l=function(){return navigator&&navigator.userAgent&&u.test(navigator.userAgent)},p=function(){return window.isIntercomMessengerSheet},m=function(){var n=document.createElement("script");return n.type="text/javascript",n.charset="utf-8",n.src=o,n},v=function(){var n=document.createElement("iframe");n.id="intercom-frame",n.style.display="none",document.body.appendChild(n),n.contentWindow.document.open("text/html","replace"),n.contentWindow.document.write("\n \n \n \n \n "),n.contentWindow.document.close();var t=m();return n.contentWindow.document.head.appendChild(t),n},g=function(){var n=document.getElementById("intercom-frame");n&&n.parentNode&&n.parentNode.removeChild(n)},h=function(){if(!window[c]){var n=function n(){for(var t=arguments.length,e=Array(t),o=0;o 2 | {{#if user}} 3 |
  • Dashboard
  • 4 |
  • Users
  • 5 |
  • Log Out
  • 6 | {{else}} 7 |
  • Log In
  • 8 | {{/if}} 9 | 10 | -------------------------------------------------------------------------------- /gui/views/auth/login.hbs: -------------------------------------------------------------------------------- 1 |

    BOT18Log In

    2 | 3 | {{#if messages}} 4 |
    5 | {{#each messages}} 6 |
    {{{text}}}
    7 | {{/each}} 8 |
    9 | {{/if}} 10 | 11 |

    12 | Don't have an account yet? » 13 | 14 | 15 |

    16 | 17 |
    18 |

    19 |

    20 |

    21 |
    22 | -------------------------------------------------------------------------------- /gui/views/auth/register.hbs: -------------------------------------------------------------------------------- 1 |

    BOT18Register

    2 | 3 | {{#if messages}} 4 |
    5 | {{#each messages}} 6 |
    {{{text}}}
    7 | {{/each}} 8 |
    9 | {{/if}} 10 | 11 |

    12 | 13 | Already signed up? » 14 | 15 |

    16 | 17 |
    18 |

    19 |

    20 |

    21 |

    22 |

    23 |
    24 | -------------------------------------------------------------------------------- /gui/views/dashboard/index.hbs: -------------------------------------------------------------------------------- 1 |

    BOT18Dashboard

    2 | 3 |

    [dashboard here]

    4 | -------------------------------------------------------------------------------- /gui/views/home.hbs: -------------------------------------------------------------------------------- 1 | {{#if messages}} 2 |
    3 | {{#each messages}} 4 |
    {{{text}}}
    5 | {{/each}} 6 |
    7 | {{/if}} 8 | 9 | {{#if user}} 10 |

    Welcome back, {{user.username}}!

    11 | {{else}} 12 | 13 | 14 | {{/if}} 15 | -------------------------------------------------------------------------------- /gui/views/users/list.hbs: -------------------------------------------------------------------------------- 1 |

    BOT18Users

    2 | 3 | 4 | 5 | 6 | 7 | 8 | {{#each users}} 9 | 10 | 11 | 12 | 13 | {{/each}} 14 | 15 |
    USERNAMESIGNUP DATE
    {{username}}{{signup_date}}
    16 | -------------------------------------------------------------------------------- /launcher/get-auth.js: -------------------------------------------------------------------------------- 1 | var bot18 = global.BOT18 2 | 3 | /* 4 | launcher.getAuth(cb) 5 | 6 | - Prompts for ZalgoNet email/password at first startup, 7 | requests an auth_token from ZalgoNet, and 8 | caches it at ~/.bot18/auth.json 9 | - Subsequent API calls include auth_token in headers, 10 | to bind to the associated user account and claimed codes. 11 | */ 12 | 13 | module.exports = function getAuth (cb) { 14 | var r = require('path').resolve 15 | var fs = require('fs') 16 | var debug = require('debug')('launcher') 17 | var prompt = require('cli-prompt') 18 | var linkify = require('linkify-terminal') 19 | var chalk = require('chalk') 20 | var conf = bot18.conf 21 | if (bot18.cmd.dev_engine) { 22 | // Skip auth if we already have a local engine. 23 | return cb() 24 | } 25 | // Determine our auth status with ZalgoNet. 26 | // Check for cached auth_token. 27 | fs.readFile(r(conf.home, 'auth.json'), {encoding: 'utf8'}, function (err, auth_json) { 28 | if (err && err.code !== 'ENOENT') { 29 | return cb(err) 30 | } 31 | if (auth_json) { 32 | // We have a cached auth_token, validate it with ZalgoNet 33 | try { 34 | bot18.auth = JSON.parse(auth_json) 35 | debug(chalk.grey('Using cached auth: ' + r(conf.home, 'auth.json'))) 36 | } 37 | catch (e) { 38 | debug(chalk.red('Error parsing cached auth at ' + r(conf.home, 'auth.json'))) 39 | return getNewAuth() 40 | } 41 | if (bot18.cmd.offline) { 42 | // Skip validation, avoid connection to ZalgoNet. 43 | return cb() 44 | } 45 | debug(chalk.grey('Validating cached auth with ZalgoNet - Please stand by...')) 46 | bot18.lib.mr_get('https://code.bot18.net/auth/' + bot18.auth.user_info.username, function (err, resp, body) { 47 | if (err) { 48 | return cb(new Error('Your network connection is down. Please try again later.')) 49 | } 50 | switch (resp.statusCode) { 51 | case 200: 52 | if (body && body.authorized) { 53 | // We good. 54 | bot18.auth = body 55 | withAuth() 56 | } 57 | else { 58 | cb(new Error('Unreadable response from ZalgoNet. The REST API may have changed, or your DNS may be tampered with.')) 59 | } 60 | return 61 | case 403: 62 | // Invalid or expired auth_token. 63 | return getNewAuth() 64 | case 400: 65 | return cb(new Error('Invalid request to ZalgoNet. The REST API may have changed.')) 66 | case 429: 67 | return cb(new Error('Your IP (' + body.request_ip + ') is making too many requests/sec to ZalgoNet. (Limit is ' + body.max_reqs_per_sec + ' reqs/sec, your reqs/sec: ' + (body.reqs_blocked + 1) + ')')) 68 | case 500: 69 | default: 70 | return cb(new Error('ZalgoNet is temporarily down. Please try again later.')) 71 | } 72 | }) 73 | } 74 | else { 75 | getNewAuth() 76 | } 77 | }) 78 | // Request ZalgoNet credentials from stdin. 79 | function getNewAuth () { 80 | debug(' ' + chalk.yellow.inverse(' Beep Boop, This is Bot18! ')) 81 | if (!bot18.auth) { 82 | debug(chalk.yellow(' I\'m performing intial setup to connect your ') + chalk.cyan('ZalgoNet') + chalk.yellow(' account.')) 83 | debug(chalk.yellow(' Your information will be handled securely and encrypted whenever possible.')) 84 | debug(chalk.yellow(' If you don\'t have a ') + chalk.cyan('ZalgoNet') + chalk.yellow(' account yet, sign up at: ') + chalk.green(linkify('https://bot18.net/register', {pretty: true}))) 85 | //debug(' Here\'s a tip: enter "guest" for a 15-minute Bot18 trial!'.cyan) 86 | } 87 | else { 88 | debug(chalk.yellow(' Your cached auth token expired, so I\'m trying to reconnect your ') + chalk.cyan('ZalgoNet') + chalk.yellow(' account.')) 89 | debug(chalk.yellow(' Please re-enter your ') + chalk.cyan('ZalgoNet') + chalk.yellow(' credentials so we can authorize you:')) 90 | } 91 | console.error() 92 | getCreds() 93 | } 94 | function getCreds () { 95 | ;(function promptForUsername () { 96 | prompt(chalk.grey(' Username: (CTRL-C to exit)') + '\n ', function (username) { 97 | username = (username || '').trim() 98 | if (!username) { 99 | return promptForUsername() 100 | } 101 | if (username.toLowerCase() === 'guest') { 102 | return withCreds('guest', 'uNl3AsH tHe ZaLG0') 103 | } 104 | // Check if username exists. 105 | debug(chalk.grey('Searching ') + chalk.cyan('ZalgoNet') + chalk.grey(' - Please stand by...')) 106 | bot18.lib.mr_get('https://code.bot18.net/auth/' + username, {query: {check_username: true}}, function (err, resp, body) { 107 | if (err) { 108 | return cb(new Error('Your network connection is down. Please try again later.')) 109 | } 110 | switch (resp.statusCode) { 111 | case 200: 112 | if (body && body.exists === true) { 113 | // Username is ok. 114 | debug(chalk.grey('Username ') + chalk.yellow(body.username) + chalk.green(' found.')) 115 | return setTimeout(promptForPassword, 0) 116 | break 117 | } 118 | else { 119 | debug(chalk.grey('Username ') + chalk.yellow(username) + chalk.red(' not found!')) 120 | return setTimeout(promptForUsername, 0) 121 | } 122 | case 429: 123 | return cb(new Error('Your IP (' + body.request_ip + ') is making too many requests/sec to ZalgoNet. (Limit is ' + body.max_reqs_per_sec + ' reqs/sec, your reqs/sec: ' + (body.reqs_blocked + 1) + ')')) 124 | case 500: 125 | default: 126 | // Server Error. 127 | return cb(new Error('ZalgoNet is temporarily down. Please try again later.')) 128 | } 129 | function promptForPassword () { 130 | prompt.password(chalk.grey(' Password:') + '\n ', function (password) { 131 | password = (password || '').trim() 132 | if (!password) { 133 | return promptForPassword() 134 | } 135 | withCreds(username, password) 136 | }) 137 | } 138 | }) 139 | }) 140 | })() 141 | } 142 | // Log into ZalgoNet using username/password. 143 | function withCreds (username, password) { 144 | var opts = { 145 | data: { 146 | password: password, 147 | pubkey: bot18.pubkey.pubkey 148 | } 149 | } 150 | debug(chalk.grey('Authenticating - Please stand by...')) 151 | bot18.lib.mr_post('https://code.bot18.net/auth/' + username, opts, function (err, resp, body) { 152 | if (err) { 153 | return cb(new Error('Your network connection is down. Please try again later.')) 154 | } 155 | switch (resp.statusCode) { 156 | case 200: 157 | if (body && body.authorized) { 158 | // We good. 159 | bot18.auth = body 160 | withAuth() 161 | } 162 | else { 163 | cb(new Error('Unreadable response from ZalgoNet. The REST API may have changed, or your DNS may be tampered with.')) 164 | } 165 | return 166 | case 403: 167 | // Bad password. 168 | debug(chalk.red('Bad password.')) 169 | return getCreds() 170 | case 400: 171 | return cb(new Error('Invalid request to ZalgoNet. The REST API may have changed.')) 172 | case 429: 173 | return cb(new Error('Your IP (' + body.request_ip + ') is making too many requests/sec to ZalgoNet. (Limit is ' + body.max_reqs_per_sec + ' reqs/sec, your reqs/sec: ' + (body.reqs_blocked + 1) + ')')) 174 | case 500: 175 | default: 176 | return cb(new Error('ZalgoNet is temporarily down. Please try again later.')) 177 | } 178 | }) 179 | } 180 | 181 | function withAuth () { 182 | debug(chalk.green('Success!') + chalk.grey(' You are logged into ') + chalk.cyan('ZalgoNet') + chalk.grey(' as: ') + chalk.yellow(bot18.auth.user_info.username)) 183 | debug(chalk.grey('Updating ' + r(conf.home, 'auth.json') + ' (chmod 0600)')) 184 | fs.writeFile(r(conf.home, 'auth.json'), JSON.stringify(bot18.auth, null, 2), {encoding: 'utf8', mode: parseInt('0600', 8)}, function (err) { 185 | if (err) return cb(err) 186 | cb() 187 | }) 188 | } 189 | } -------------------------------------------------------------------------------- /launcher/get-conf.js: -------------------------------------------------------------------------------- 1 | var bot18 = global.BOT18 2 | 3 | /* 4 | launcher.getConf(cb) 5 | 6 | - Assembles conf variables from various sources. 7 | */ 8 | 9 | module.exports = function getConf (cb) { 10 | var conf = bot18.conf 11 | var path = require('path') 12 | var r = require('path').resolve 13 | var fs = require('fs') 14 | conf.paths = [] 15 | // 0. --conf (already added in applyOverrides()) 16 | if (bot18.cmd.conf) { 17 | conf.paths.push({ 18 | name: 'custom', 19 | path: r(process.cwd(), bot18.cmd.conf) 20 | }) 21 | } 22 | conf.paths = conf.paths.concat([ 23 | // 1. `pwd`/bot18.config.js if present: 24 | {name: 'local', path: r(process.cwd(), 'bot18.config.js')}, 25 | // 2. ~/.bot18/config.js if present: 26 | {name: 'home', path: r(require('home-or-tmp'), '.bot18', 'config.js')}, 27 | // 3. Install dir bot18.config.js if present: 28 | {name: 'install', path: r(__dirname, '..', 'bot18.config.js')}, 29 | // 4. Fall back to defaults defined in bot18.config-sample.js 30 | {name: 'defaults', path: r(__dirname, '..', 'bot18.config-sample.js')} 31 | ]) 32 | // Function for loading a conf file and merging its vars. 33 | function loadConf (p, cb) { 34 | p.loaded = false 35 | p.vars = {} 36 | fs.exists(p.path, function (exists) { 37 | if (exists) { 38 | try { 39 | p.vars = require(p.path) 40 | } 41 | catch (e) { 42 | return cb(e) 43 | } 44 | p.loaded = true 45 | } 46 | return cb(null, p.vars) 47 | }) 48 | } 49 | require('async').mapSeries(conf.paths, loadConf, function (err, results) { 50 | if (err) return cb(err) 51 | require('lodash.defaultsdeep').apply(null, [conf].concat(results)) 52 | // Use debug module for bot output messages. Outputs to stderr. 53 | // bot18 --debug will output all debug() calls including those from dependencies. 54 | if (!process.env.DEBUG) { 55 | process.env.DEBUG = conf.debug ? '*' : 'launcher,engine,startup,stdin,execute,errors' 56 | } 57 | if (!process.env.DEBUG_DEPTH) { 58 | process.env.DEBUG_DEPTH = '10' 59 | } 60 | if (!process.env.DEBUG_SHOW_HIDDEN) { 61 | process.env.DEBUG_SHOW_HIDDEN = 'true' 62 | } 63 | // We need to require debug module AFTER process.env.DEBUG is finalized. 64 | // It's convenient to keep it cached on bot18 so we don't have to 65 | // require it from every file. 66 | bot18.debug = require('debug') 67 | // Expand "~/" to home-or-tmp() in paths defined in the conf. 68 | var home = require('home-or-tmp') 69 | Object.keys(conf).forEach(function (k) { 70 | if (typeof conf[k] === 'string') { 71 | conf[k] = conf[k].replace(/\~\//g, home + '/') 72 | } 73 | }) 74 | // Attach gui_codemap (must be required now, 75 | // since npx yanks the filesystem out from under us) 76 | bot18.gui_codemap = require(r(bot18.__dirname, 'gui', '_codemap')) 77 | // Execute the --save subcommand if present. 78 | if (bot18.cmd.save) { 79 | require(r(__dirname, 'save-conf'))(bot18.cmd.save, function (err, out_p) { 80 | if (err) { 81 | return cb(err) 82 | } 83 | process.exit(0) 84 | }) 85 | } 86 | else { 87 | cb() 88 | } 89 | }) 90 | } -------------------------------------------------------------------------------- /launcher/get-engine.js: -------------------------------------------------------------------------------- 1 | var bot18 = global.BOT18 2 | 3 | /* 4 | lib.getEngine(cb) 5 | 6 | - Fetches encrypted/signed engine code from ZalgoNet. 7 | - Requires a valid auth_token. 8 | - Comes in 3 flavors (selected by conf.channel): 9 | "stable" (default, beta tested) 10 | "unstable" (latest, untested dev build), 11 | "free" (crippled free version) 12 | - The result is cached to disk in encrypted, packed form by the key: 13 | ~/.bot18/builds/{sha1(auth_token + pubkey + request_ip + channel + current_engine_version)}.bot18 14 | - If any of these variables change, a new engine is downloaded. 15 | - All downloads from bot18.net will be signed with master Bot18 pubkey defined in conf.master_pubkey 16 | Uses D. J. Bernstein's NaCl library, and my crypto library Salty: https://github.com/carlos8f/salty 17 | Engine code is decrypted using Curve25519, Salsa20, and Poly1305. 18 | - Decrypts to plain JS, creates a VM, and returns cb(err, engine) 19 | ...where `engine` is a callable JS function to invoke main() of the Bot18 engine VM. 20 | */ 21 | 22 | module.exports = function getEngine (cb) { 23 | var salty = require('salty') 24 | var vm = require('vm') 25 | var zlib = require('zlib') 26 | var debug = bot18.debug('launcher') 27 | var path = require('path') 28 | var r = path.resolve 29 | var fs = require('fs') 30 | var readline = require('readline') 31 | var bytes = require('bytes') 32 | var _get = require('lodash.get') 33 | var chalk = require('chalk') 34 | var conf = bot18.conf 35 | if (bot18.cmd.dev_engine) { 36 | // Support linking to a local engine copy for development. 37 | conf.channel = 'dev' 38 | bot18.engine = function dev_main () { 39 | console.error() // A little spacing. 40 | require(r(process.cwd(), bot18.cmd.dev_engine)) 41 | } 42 | return cb() 43 | } 44 | conf.channel = (conf.channel || 'stable').toLowerCase() 45 | // Check the local engine cache. 46 | bot18.etag = _get(bot18, 'auth.channels.' + conf.channel + '.etag', null) 47 | if (bot18.etag) { 48 | var p = r(conf.home, 'builds', bot18.etag + '.bot18') 49 | fs.stat(p, function (err, stat) { 50 | if (err && err.code === 'ENOENT') { 51 | // Cache miss. 52 | bot18.engine_cache = 'miss' 53 | if (bot18.cmd.offline) { 54 | return cb(new Error('No cached engine found. Unable to run offline.')) 55 | } 56 | fetchEngine() 57 | } 58 | else if (err) { 59 | return cb(err) 60 | } 61 | else { 62 | // Cache hit. 63 | bot18.engine_cache = 'hit' 64 | debug(chalk.grey('Using cached engine at ' + p)) 65 | returnEngine(fs.createReadStream(p), stat.size) 66 | } 67 | }) 68 | } 69 | else { 70 | fetchEngine() 71 | } 72 | function fetchEngine () { 73 | debug(chalk.grey('Compiling bot18_engine.vm (' + conf.channel + ', ' + _get(bot18, 'auth.channels.' + conf.channel + '.current_version', '') + ') - Please stand by...')) 74 | var opts = { 75 | query: { 76 | channel: conf.channel 77 | }, 78 | stream: true 79 | } 80 | bot18.lib.mr_get('https://code.bot18.net/dl/' + bot18.auth.user_info.username, opts, function (err, resp, body) { 81 | if (err) { 82 | return cb(new Error('Your network connection is down. Please try again later.')) 83 | } 84 | switch (resp.statusCode) { 85 | case 200: 86 | returnEngine(body, parseInt(resp.headers['content-length'], 10)) 87 | if (resp.headers['etag']) { 88 | // also async pipe to the local cache location. 89 | var cache_p = path.join('builds', resp.headers['etag'] + '.bot18') 90 | debug(chalk.grey('Caching engine to ' + r(conf.home, cache_p) + ' (chmod 0600)')) 91 | body.pipe(fs.createWriteStream(r(conf.home, cache_p), {mode: parseInt('0600', 8)})) 92 | } 93 | return 94 | case 400: 95 | return cb(new Error('Invalid request to ZalgoNet. The REST API may have changed.')) 96 | case 403: 97 | var err = new Error('Authorization expired. Please log in again.') 98 | return require(r(__dirname, 'get-auth'))(cb) 99 | case 404: 100 | return cb(new Error('Invalid engine distribution channel. Please choose either "stable", "unstable", or "trial".')) 101 | case 429: 102 | return cb(new Error('Your IP (' + body.request_ip + ') is making too many requests/sec to ZalgoNet. (Limit is ' + body.max_reqs_per_sec + ' reqs/sec, your reqs/sec: ' + (body.reqs_blocked + 1) + ')')) 103 | case 500: 104 | return cb(new Error('Engine code failed to compile. Please contact info@bot18.net and report this.')) 105 | default: 106 | return cb(new Error('Unreadable response from ZalgoNet. The REST API may have changed or your DNS may be tampered with.')) 107 | } 108 | }) 109 | } 110 | 111 | function returnEngine (saltyStream, totalLength) { 112 | var jsChunks = [] 113 | var verified = false 114 | var saltyHeader 115 | var packedSize = 0 116 | salty.decrypt(saltyStream, bot18.wallet, totalLength) 117 | .once('header', function (header) { 118 | conf.saltyHeader = header 119 | if (header['from-salty-id'] === conf.master_pubkey) { 120 | verified = true 121 | } 122 | }) 123 | .on('data', function (buf) { 124 | packedSize += buf.length 125 | }) 126 | .once('error', function (err) { 127 | debug(chalk.red('decrypt err')) 128 | cb(new Error('Error decrypting/verifying engine download. The download might\'ve been corrupted or pirated. (Error code: BOWIE)')) 129 | }) 130 | .pipe(zlib.createGunzip()) 131 | .once('error', function (err) { 132 | debug(chalk.red('gunzip err')) 133 | cb(new Error('Error decompressing engine download. The download might\'ve been corrupted. (Error code: CASEYJONES)')) 134 | }) 135 | .on('data', function (buf) { 136 | jsChunks.push(buf) 137 | }) 138 | .once('end', function () { 139 | if (!verified) { 140 | debug(chalk.red('saltyHeader'), conf.saltyHeader) 141 | return cb(new Error('The engine download was signed with a pubkey different from conf.master_pubkey. It may have been tampered with. (Error code: TWINPEAKS)')) 142 | } 143 | var src = Buffer.concat(jsChunks).toString('utf8') 144 | try { 145 | var script = new vm.Script(src, {filename: 'bot18_engine.vm'}) 146 | } 147 | catch (e) { 148 | debug('init err', e) 149 | return cb(new Error('initializing engine. (Error code: BATMAN)')) 150 | } 151 | function engine () { 152 | /* 153 | console.error() 154 | debug('Handing control over to bot18_engine.vm -- HOLD ONTO YOUR BUTTS!'.cyan) 155 | console.error() 156 | console.error(' ' + ' ---- '.yellow.inverse) 157 | */ 158 | console.error() // A little spacing. 159 | try { 160 | // Hand over the conf and pass control of the current process to the VM. 161 | // We can only do this safely now that the signature has been verified. 162 | var context = vm.createContext({ 163 | global: global, 164 | console: console, 165 | process: process, 166 | require: require, 167 | Buffer: Buffer, 168 | BOT18: bot18, 169 | // @todo: use lolex for these? 170 | setImmediate: setImmediate, 171 | setInterval: setInterval, 172 | setTimeout: setTimeout 173 | }) 174 | return script.runInContext(context, {filename: 'bot18_engine.vm'}) 175 | } 176 | catch (e) { 177 | debug(chalk.red('engine init err'), e) 178 | debug(chalk.red('Engine exception. (Error code: DOGMA)')) 179 | // @todo: restart the engine for recoverable errors? 180 | process.exit(42) 181 | } 182 | } 183 | debug(chalk.yellow(' Engine decrypted successfully!')) 184 | debug(chalk.grey(' Packed Size: ' + bytes(packedSize).toLowerCase() + ' (' + (((src.length / packedSize) - 1) * 100).toFixed(0) + '% compression)')) 185 | debug(chalk.grey(' Unpacked Size: ' + bytes(src.length).toLowerCase())) 186 | debug(chalk.grey(' Launcher Version: v' + require(r(__dirname, '..', 'package.json')).version)) 187 | debug(chalk.grey(' Engine Version: ') + chalk.yellow(conf.saltyHeader['x-bot18-engine-version']) + chalk.grey(' (') + chalk.cyan(conf.saltyHeader['x-bot18-channel']) + chalk.grey(')')) 188 | debug(chalk.grey(' SHA256 Hash: ' + conf.saltyHeader['hash'])) 189 | debug(chalk.grey(' From Pubkey: ' + conf.saltyHeader['from-salty-id'])) 190 | debug(chalk.grey('Signature from @carlos8f: ' + conf.saltyHeader['signature'])) 191 | console.error() 192 | bot18.engine = engine 193 | cb() 194 | }) 195 | } 196 | } -------------------------------------------------------------------------------- /launcher/get-lib.js: -------------------------------------------------------------------------------- 1 | var bot18 = global.BOT18 2 | 3 | /** 4 | lib.getLib() 5 | 6 | - Attaches random useful stuff onto bot18. 7 | */ 8 | 9 | module.exports = function (cb) { 10 | var r = require('path').resolve 11 | require(r(__dirname, '..', 'lib', 'mr'))() 12 | cb() 13 | } -------------------------------------------------------------------------------- /launcher/get-mongo.js: -------------------------------------------------------------------------------- 1 | var bot18 = global.BOT18 2 | 3 | /* 4 | lib.getMongo(cb) 5 | 6 | - If conf.mongo.enabled=true, attaches a connected MongoDB client to bot18.mongo_client 7 | - Attaches a ref to the MongoDB db to bot18.db 8 | */ 9 | 10 | module.exports = function getMongo (cb) { 11 | var mongo_conf = bot18.conf.mongo 12 | if (mongo_conf && mongo_conf.enabled) { 13 | var auth_str = '', auth_mechanism 14 | if (mongo_conf.username) { 15 | auth_str = encodeURIComponent(mongo_conf.username) 16 | if (mongo_conf.password) auth_str += ':' + encodeURIComponent(mongo_conf.password) 17 | auth_str += '@' 18 | auth_mechanism = mongo_conf.auth_mechanism || 'DEFAULT' 19 | } 20 | var connection_str 21 | if (mongo_conf.connection_str) { 22 | connectionString = mongo_conf.connectionString 23 | } 24 | else { 25 | connection_str = 'mongodb://' + auth_str + mongo_conf.host + ':' + mongo_conf.port + '/?' + 26 | (mongo_conf.replica_set ? '&replicaSet=' + mongo_conf.replica_set : '' ) + 27 | (auth_mechanism ? '&authMechanism=' + auth_mechanism : '' ) 28 | } 29 | require('mongodb').MongoClient.connect(connection_str, {useNewUrlParser: true}, function (err, client) { 30 | if (err) { 31 | return cb(new Error('Failed to connect to MongoDB. Check your bot18.config.js and try again.')) 32 | } 33 | bot18.mongo_client = client 34 | bot18.db = client.db(mongo_conf.db) 35 | cb() 36 | }) 37 | } 38 | else { 39 | cb() 40 | } 41 | } -------------------------------------------------------------------------------- /launcher/get-wallet.js: -------------------------------------------------------------------------------- 1 | var bot18 = global.BOT18 2 | 3 | /* 4 | lib.getWallet(cb) 5 | 6 | - Creates ~/.bot18 directory if it doesn't exist 7 | - Creates an anonymous, unencrypted Salty wallet at ~/.bot18/id_salty 8 | (used for verifying downloads from ZalgoNet) 9 | - Prompts for ZalgoNet email/password at first startup, 10 | requests an auth_token from ZalgoNet, and 11 | caches it at ~/.bot18/auth_token 12 | - Subsequent API calls include auth_token in headers, 13 | to bind to the associated user account and claimed codes. 14 | */ 15 | 16 | module.exports = function getWallet (cb) { 17 | var path = require('path') 18 | var r = path.resolve 19 | var fs = require('fs') 20 | var debug = require('debug')('launcher') 21 | var pempal = require('pempal') 22 | var salty = require('salty') 23 | var chalk = require('chalk') 24 | var conf = bot18.conf 25 | // set up the settings dir if it doesn't exist. 26 | fs.stat(conf.home, function (err, stat) { 27 | if (err && err.code === 'ENOENT') { 28 | debug(chalk.grey('Creating ' + conf.home + ' (chmod 0700)')) 29 | fs.mkdir(conf.home, parseInt('0700', 8), function (err) { 30 | if (err) return cb(err) 31 | debug(chalk.grey('Creating ' + r(__dirname, 'save-conf') + ' (chmod 0600)')) 32 | require(r(__dirname, 'save-conf'))('home', function (err) { 33 | if (err) return cb(err) 34 | debug(chalk.grey('Creating ' + r(conf.home, 'builds') + ' (chmod 0700)')) 35 | fs.mkdir(r(conf.home, 'builds'), parseInt('0700', 8), function (err) { 36 | if (err) return cb(err) 37 | withHome() 38 | }) 39 | }) 40 | }) 41 | } 42 | else if (err) { 43 | return cb(err) 44 | } 45 | else { 46 | withHome() 47 | } 48 | }) 49 | 50 | // Set up the local Salty wallet (ephemeral, unencrypted, 51 | // used for verifying downloads from ZalgoNet) 52 | function withHome () { 53 | var id_salty_path = r(conf.home, 'id_salty') 54 | fs.stat(id_salty_path, function (err, stat) { 55 | if (err && err.code === 'ENOENT') { 56 | // generate a new Salty wallet. 57 | debug(chalk.grey('Creating ' + id_salty_path + ' (chmod 0600)')) 58 | bot18.wallet = salty.wallet.create() 59 | bot18.pubkey = bot18.wallet.pubkey 60 | fs.writeFile(id_salty_path, bot18.wallet.toPEM() + '\n', {mode: parseInt('0600', 8)}, function (err) { 61 | if (err) return cb(err) 62 | debug(chalk.grey('Creating ' + r(conf.home, 'id_salty.pub') + ' (chmod 0644)')) 63 | fs.writeFile(r(conf.home, 'id_salty.pub'), bot18.pubkey.pubkey + '\n', {mode: parseInt('0644', 8)}, function (err) { 64 | if (err) return cb(err) 65 | cb() 66 | }) 67 | }) 68 | } 69 | else if (err) { 70 | return cb(err) 71 | } 72 | else { 73 | fs.readFile(id_salty_path, {encoding: 'utf8'}, function (err, id_salty) { 74 | if (err) return cb(err) 75 | try { 76 | var pem = pempal.decode(id_salty, {tag: 'SALTY WALLET'}) 77 | bot18.wallet = salty.wallet.parse(pem.body) 78 | } 79 | catch (e) { 80 | return cb(e) 81 | } 82 | fs.readFile(r(conf.home, 'id_salty.pub'), {encoding: 'utf8'}, function (err, id_salty_pub) { 83 | if (err) return cb(err) 84 | bot18.wallet.pubkey = salty.pubkey.parse(id_salty_pub) 85 | bot18.pubkey = bot18.wallet.pubkey 86 | cb() 87 | }) 88 | }) 89 | } 90 | }) 91 | } 92 | } -------------------------------------------------------------------------------- /launcher/parse-argv.js: -------------------------------------------------------------------------------- 1 | var bot18 = global.BOT18 2 | 3 | /* 4 | lib.parseArgv() 5 | 6 | - Defines valid args for `bot18` command. 7 | - Parses args using commander.js 8 | - Applies any flags/overrides to bot18.conf 9 | */ 10 | 11 | module.exports = function (cb) { 12 | var cmd = bot18.cmd = require('commander') 13 | cmd._name = 'bot18' 14 | cmd 15 | .version('v' + bot18.pkg.version) 16 | .arguments('[pair-specs...]') 17 | .description('Launch the Bot18 multi-pair cryptocurrency trading engine. Bot18 is a component of Zalgo.net') 18 | .option('--channel ', 'Select the engine distribution channel. (Default: stable)') 19 | .option('--conf ', 'Path to a customized configuration file.') 20 | .option('--save ', 'Save current setup and exit. ("home" will save to ~/.bot18/config.js)') 21 | .option('--login', 'Log into ZalgoNet, cache an auth token, and exit.') 22 | .option('--logout', 'Destroy cached auth token and exit.') 23 | .option('--clear', 'Clear caches and exit.') 24 | .option('--drop_sessions', 'Delete stored sessions and exit.') 25 | .option('--headless', 'Do not launch a built-in webserver/GUI.') 26 | .option('--non_interactive', 'Ignore key commands.') 27 | .option('--reset_profit', 'Reset profit/loss calculation to zero.') 28 | .option('--offline', 'Do not check for engine updates, just run the cached engine.') 29 | .option('--no_telemetry', 'Disable telemetry to ZalgoNet. CLOUD/SWARM features will be unavailable.') 30 | .option('--dev_engine ', 'Run a dev copy of the engine.') 31 | .option('--debug', 'Enable detailed output to stderr.') 32 | // Parse CLI args. 33 | cmd.parse(process.argv) 34 | // Add each arg as a pair-spec. 35 | // Implementation incomplete. 36 | bot18.conf.pairs = {} 37 | cmd.args.forEach(function (arg) { 38 | var parts = arg.split(/\s*:\s*/) 39 | if (parts.length === 1) { 40 | parts = ['*', parts[0]] 41 | } 42 | // @todo: handle +/- task operators and wildcards. 43 | bot18.conf.pairs[parts[0] || '*'] = parts[1] 44 | }) 45 | // Apply --channel override (overrides all conf definitions): 46 | if (cmd.channel) { 47 | bot18.conf.channel = cmd.channel 48 | } 49 | cb() 50 | } -------------------------------------------------------------------------------- /launcher/print-motd.js: -------------------------------------------------------------------------------- 1 | var bot18 = global.BOT18 2 | 3 | /* 4 | lib.printMotd() 5 | 6 | - Prints the ZalgoNet MOTD to stderr. 7 | */ 8 | 9 | module.exports = function (cb) { 10 | var r = require('path').resolve 11 | var chalk = require('chalk') 12 | if (bot18.conf.motd) { 13 | var linkify = require('linkify-terminal') 14 | require('fs').readFile(r(__dirname, '..', 'motd.txt'), {encoding: 'utf8'}, function (err, motd) { 15 | if (err) { 16 | return cb(err) 17 | } 18 | console.error('\n' + chalk.cyan(linkify(motd, {pretty: true})) + '\n') 19 | cb() 20 | }) 21 | } 22 | else { 23 | cb() 24 | } 25 | } -------------------------------------------------------------------------------- /launcher/save-conf.js: -------------------------------------------------------------------------------- 1 | var bot18 = global.BOT18 2 | 3 | module.exports = function (save_p, cb) { 4 | var r = require('path').resolve 5 | var fs = require('fs') 6 | var vm = require('vm') 7 | var chalk = require('chalk') 8 | var defaults = require(r(__dirname, '..', 'bot18.defaults.json')) 9 | defaults.pkg = bot18.pkg 10 | fs.readFile(r(__dirname, '..', 'bot18.config-sample.js.hbs'), {encoding: 'utf8'}, function (err, tpl) { 11 | if (err) { 12 | return cb(err) 13 | } 14 | var tpl_func = require('handlebars').compile(tpl) 15 | var conf_copy = JSON.parse(JSON.stringify(bot18.conf)) 16 | require('lodash.defaultsdeep')(conf_copy, defaults) 17 | var subconfig_keys = ['pair_config', 'strat_config'] 18 | Object.keys(conf_copy).forEach(function (k) { 19 | // Since Handlebars doesn't output null or booleans, 20 | // convert them to string equivalents. 21 | function convertValue (val, is_flat) { 22 | // special keys that don't convert to JSON. 23 | if (!is_flat && k.match(/^(mongo|pkg)$/)) { 24 | if (typeof val === 'object' && val !== null) { 25 | var ret = {} 26 | Object.keys(val).forEach(function (k) { 27 | ret[k] = convertValue(val[k]) 28 | }) 29 | return ret 30 | } 31 | else { 32 | return convertValue(val, true) 33 | } 34 | } 35 | else if (val === true) { 36 | return 'true' 37 | } 38 | else if (val === false) { 39 | return 'false' 40 | } 41 | else if (val === null) { 42 | return 'null' 43 | } 44 | else { 45 | // Fall back to indented JSON for complex types. 46 | return JSON.stringify(val, null, 2) 47 | } 48 | } 49 | conf_copy[k] = convertValue(conf_copy[k]) 50 | }) 51 | conf_copy.copied_from = bot18.user_agent 52 | var out = tpl_func(conf_copy) 53 | // Test out the resulting source code. 54 | try { 55 | var script = new vm.Script(out) 56 | } 57 | catch (e) { 58 | bot18.debug('launcher')(chalk.red('save conf err'), e) 59 | return cb(new Error('Caught syntax error, refusing to write result.')) 60 | } 61 | var target_p = save_p === 'home' ? r(bot18.conf.home, 'config.js') : r(process.cwd(), save_p) 62 | bot18.debug('launcher')(chalk.grey('Writing ' + target_p)) 63 | fs.writeFile(target_p, out, {encoding: 'utf8'}, function (err) { 64 | if (err) { 65 | return cb(err) 66 | } 67 | cb(null, target_p) 68 | }) 69 | }) 70 | } -------------------------------------------------------------------------------- /lib/mr.js: -------------------------------------------------------------------------------- 1 | var bot18 = global.BOT18 2 | 3 | module.exports = function () { 4 | function wrapMethod (method) { 5 | return function (url, opts, cb) { 6 | var defaults = { 7 | headers: { 8 | 'user-agent': bot18.user_agent 9 | } 10 | } 11 | if (bot18.auth && bot18.auth.authorized && bot18.auth.auth_token) { 12 | defaults.headers['x-bot18-auth'] = bot18.auth.auth_token 13 | } 14 | if (typeof opts === 'function') { 15 | cb = opts 16 | opts = {} 17 | } 18 | var opts_copy = JSON.parse(JSON.stringify(opts)) 19 | var opts_merged = require('lodash.defaultsdeep')(opts_copy, defaults) 20 | return require('micro-request')[method](url, opts_merged, cb) 21 | } 22 | } 23 | ;['get', 'post', 'put', 'delete'].forEach(function (method) { 24 | bot18.lib['mr_' + method] = wrapMethod(method) 25 | }) 26 | } -------------------------------------------------------------------------------- /motd.txt: -------------------------------------------------------------------------------- 1 | |'''''|| '|| '|. '|' . 2 | .|' .... || ... . ... |'| | .... .||. 3 | || '' .|| || || || .| '|. | '|. | .|...|| || 4 | .|' .|' || || |'' || || | ||| || || 5 | ||......| '|..'|' .||. '||||. '|..|' .|. '| '|...' '|.' 6 | .|....' 7 | 8 | 9 | MOTD: May 31st 2018 10 | Working on the trading engine! 11 | 12 | Hey guys, 13 | 14 | I'm doing some major refactoring on the engine, making it "multi-threaded" 15 | by having a single supervisor process, which manages the strategy, and 16 | controls an army of sub-processes for each task/exchange/asset combo you 17 | want to trade. Things are going well but the scope is expanding quickly. 18 | 19 | I hope to have a testable Bot18 client/engine by next week, and if I do, 20 | I'll officially start the Beta, send an announcement email out, and anyone 21 | with an Unlock Code will be able to run the most up-to-date build. 22 | 23 | The client/engine will not be very close to finished when the Beta starts, 24 | so expect some unstability and it may go through several re-codes before 25 | the Beta is over. 26 | 27 | Also, I plan on doing a full re-design of bot18.net, making it more "trustable" 28 | as a brand, and I'm purchasing Zalgo.net, for expanding later as a cloud 29 | service and social network. 30 | 31 | For more info on this release, see: https://www.npmjs.com/package/bot18 32 | Follow the latest news via Twitter: https://twitter.com/bot18_net 33 | 34 | --Carlos -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bot18", 3 | "version": "0.4.35", 4 | "description": "A high-frequency cryptocurrency trading bot by Zenbot creator @carlos8f", 5 | "bin": { 6 | "bot18": "./bot18.sh" 7 | }, 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/carlos8f/bot18.git" 11 | }, 12 | "dependencies": { 13 | "@slack/client": "^4.2.2", 14 | "async": "^2.6.1", 15 | "axon": "^2.0.3", 16 | "bcryptjs": "^2.4.3", 17 | "big.js": "^5.1.2", 18 | "bintrees": "^1.0.2", 19 | "bitfinex-api-node": "^2.0.0-beta.1", 20 | "blessed": "^0.1.81", 21 | "blessed-contrib": "^4.8.5", 22 | "bytes": "^3.0.0", 23 | "ccxt": "^1.14.118", 24 | "chalk": "^2.4.1", 25 | "cli-columns": "^3.1.2", 26 | "cli-progress-bar": "^1.0.1", 27 | "cli-prompt": "^0.6.0", 28 | "cli-table3": "^0.5.0", 29 | "columnify": "^1.5.4", 30 | "commander": "github:carlos8f/commander.js", 31 | "debug": "^3.1.0", 32 | "gdax": "^0.8.0", 33 | "handlebars": "^4.0.11", 34 | "home-or-tmp": "^3.0.0", 35 | "ink": "^0.5.0", 36 | "isemail": "^3.1.2", 37 | "linkify-terminal": "0.0.0", 38 | "lodash.defaultsdeep": "^4.6.0", 39 | "lodash.get": "^4.4.2", 40 | "lolex": "^2.7.0", 41 | "micro-request": "^666.0.10", 42 | "minimatch": "^3.0.4", 43 | "moment": "^2.22.2", 44 | "moment-duration-format": "^2.2.2", 45 | "mongodb": "^3.1.0-beta4", 46 | "motley": "^2.2.8", 47 | "motley-auth": "^2.0.1", 48 | "motley-buffet": "^2.0.2", 49 | "motley-json": "^2.0.3", 50 | "motley-templ": "^2.0.4", 51 | "node-telegram-bot-api": "^0.30.0", 52 | "open": "0.0.5", 53 | "owasp-password-strength-test": "^1.3.0", 54 | "pempal": "^1.0.2", 55 | "runtrap": "^1.0.3", 56 | "salty": "^4.1.3", 57 | "semver": "^5.5.0", 58 | "simple-xmpp": "^1.3.0", 59 | "sosa_json": "^1.0.2", 60 | "timebucket": "^0.4.0", 61 | "uuid": "^3.2.1", 62 | "zero-fill": "^2.2.3" 63 | }, 64 | "keywords": [ 65 | "crypto", 66 | "btc", 67 | "bitcoin", 68 | "ether", 69 | "ethereum", 70 | "eth", 71 | "litecoin", 72 | "ltc", 73 | "cryptocurrency", 74 | "currency", 75 | "trading", 76 | "algo", 77 | "trader", 78 | "bot", 79 | "daemon", 80 | "profit", 81 | "bacon", 82 | "zalgo", 83 | "bot18.net", 84 | "#bot18", 85 | "carlos8f", 86 | "zenbot" 87 | ], 88 | "author": "Carlos Rodriguez ", 89 | "homepage": "https://bot18.net/", 90 | "bugs": "https://github.com/carlos8f/bot18/issues", 91 | "license": "https://bot18.net/licensing", 92 | "engines": { 93 | "node": ">=8.3.0" 94 | }, 95 | "bundledDependencies": [ 96 | "@slack/client", 97 | "async", 98 | "axon", 99 | "bcryptjs", 100 | "big.js", 101 | "bintrees", 102 | "bitfinex-api-node", 103 | "blessed", 104 | "blessed-contrib", 105 | "bytes", 106 | "ccxt", 107 | "chalk", 108 | "cli-columns", 109 | "cli-progress-bar", 110 | "cli-prompt", 111 | "cli-table3", 112 | "columnify", 113 | "commander", 114 | "debug", 115 | "gdax", 116 | "handlebars", 117 | "home-or-tmp", 118 | "ink", 119 | "isemail", 120 | "linkify-terminal", 121 | "lodash.defaultsdeep", 122 | "lodash.get", 123 | "lolex", 124 | "micro-request", 125 | "minimatch", 126 | "moment", 127 | "moment-duration-format", 128 | "mongodb", 129 | "motley", 130 | "motley-auth", 131 | "motley-buffet", 132 | "motley-json", 133 | "motley-templ", 134 | "node-telegram-bot-api", 135 | "open", 136 | "owasp-password-strength-test", 137 | "pempal", 138 | "runtrap", 139 | "salty", 140 | "semver", 141 | "simple-xmpp", 142 | "sosa_json", 143 | "timebucket", 144 | "uuid", 145 | "zero-fill" 146 | ] 147 | } 148 | -------------------------------------------------------------------------------- /snap/.snapcraft/state: -------------------------------------------------------------------------------- 1 | !GlobalState 2 | assets: 3 | build-packages: [] 4 | build-snaps: [] 5 | -------------------------------------------------------------------------------- /snap/snapcraft.yaml: -------------------------------------------------------------------------------- 1 | name: bot18 2 | version: git 3 | summary: A high-frequency cryptocurrency trading bot. 4 | description: | 5 | More info available at: https://bot18.net/ 6 | 7 | grade: stable 8 | confinement: strict 9 | 10 | apps: 11 | bot18: 12 | command: bin/bot18 13 | plugs: [network, network-bind, home] 14 | node-service: 15 | command: bin/bot18 16 | daemon: simple 17 | restart-condition: always 18 | plugs: [network, network-bind, home] 19 | 20 | parts: 21 | launcher: 22 | plugin: nodejs 23 | source: . 24 | node-engine: "8.11.2" 25 | 26 | icon: gui/public/assets/bot18_icon.png 27 | -------------------------------------------------------------------------------- /strats/orderbook_power_imbalance/bot18.strat.js: -------------------------------------------------------------------------------- 1 | var s = module.exports = {} 2 | 3 | s.name = 'orderbook_power_imbalance' 4 | s.version = 'v0.0.0' 5 | s.author = 'Carlos Rodriguez ' 6 | s.homepage = 'https://bot18.net/' 7 | 8 | // standard size for buying 9 | s.buy_one_size = '1.0' 10 | // when buy_mkt is triggered, use these funds for buy 11 | s.buy_mkt_funds = '1000.00' 12 | // stop buying after acquiring this (size in asset) 13 | s.max_asset = '10.0' 14 | // position sell order just before entry with >= this size 15 | s.ask_wall = '2.00' 16 | // position buy order just before entry with >= this size 17 | s.buy_wall = '5.00' 18 | // require this for buying, if it drops below, hold 19 | s.buy_min_vwap_pct_bias = '4.0' 20 | // if it drops below, sell 21 | s.sell_max_vwap_pct_bias = '1.0' 22 | // require this for buying, if it drops below, sell 23 | s.min_vol_bias = '0.4' 24 | // mark up buys immediately by % 25 | s.profit_stop = '0.3' 26 | s.profit_stop_mkt = '0.5' 27 | 28 | // require this amount of selling in 500ms window to trigger sell_mkt 29 | s.sell_mkt_trigger = '20.0' 30 | // wait this for cancels before pulling sell_mkt trigger (in ms) 31 | s.sell_mkt_delay = 520 32 | // if vwap_pct_bias drops below this, considered "urgent" 33 | s.urgent_max_vwap_pct_bias = '-3.0' 34 | 35 | // require this amount of buying in 500ms window to trigger buy_mkt 36 | s.buy_mkt_trigger = '40.0' 37 | // wait this for cancels before pulling buy_mkt trigger (in ms) 38 | s.buy_mkt_delay = 520 39 | // require vwap_pct_bias of at least this to trigger buy_mkt 40 | s.buy_mkt_min_vwap_pct_bias = '10.0' 41 | 42 | // failsafe cancels exchange-side 43 | s.sell_cancel_after = 'day' 44 | s.buy_cancel_after = 'hour' 45 | // allow price to drop this (in currency) before considered "urgent" status 46 | s.loss_tolerance = '1.00' 47 | // backoff order price if diff is >= this (in currency) 48 | s.outbid_min_backoff = '0.05' 49 | // after buy error, wait for this (in ms) before retrying 50 | s.buy_error_backoff = 30000 51 | // allow this many hold periods before canceling buy order 52 | s.buy_max_holds = 2 53 | // calculate slippage of market order of this size (for eval) 54 | s.bulk_vol = '100.00' 55 | // maintain order book buffer this % of each side (for order calc) 56 | s.book_range_pct = '2.0' 57 | // analyze book until this % of each side (for eval) 58 | s.book_stats_range_pct = '0.5' 59 | // length of stats window in ms 60 | s.window_length = 1000 * 60 * 2 61 | // ms to wait after a successful buy, to buy again 62 | s.buy_timeout = 60000 63 | // ms to wait after a buy error, to buy again 64 | s.sell_timeout = 10000 65 | // ms to wait between timeout notifications 66 | s.timeout_notification_timeout = 5000 67 | // core loop intervals in ms 68 | s.heartbeat_interval = 10000 69 | s.heartbeat_notifications = '30m' 70 | s.render_interval = 500 71 | s.book_sync_interval = 400 72 | s.balance_sync_interval = 2000 73 | s.launch_timeout = 30000 74 | s.graceful_exit_timeout = 1000 75 | s.reboot_timeout = 5000 76 | s.profit_stop_timeout = 1000 77 | // change if ur from china or some shit 78 | s.currency = 'USD' 79 | // in asset 80 | s.min_buy_notification_size = '1.0' 81 | s.min_sell_notification_size = '1.0' 82 | // in currency 83 | s.min_short_notification_amt = '1.00' 84 | --------------------------------------------------------------------------------