├── .dockerignore ├── .gitignore ├── .nanoignore ├── LICENSE ├── README.md ├── api ├── .babelrc ├── .dockerignore ├── .gitignore ├── Dockerfile ├── crontab ├── currencyConfig.js ├── dist │ ├── common │ │ └── index.js │ ├── config.js │ ├── currency_rates │ │ ├── index.js │ │ └── retrieveCurrencyRates.js │ ├── index.js │ ├── mangodb │ │ ├── event_schema.js │ │ ├── exchange_rate_schema.js │ │ ├── index.js │ │ └── schema.js │ ├── response.js │ ├── routes │ │ ├── currentCurrencies.js │ │ ├── exchangeRates.js │ │ ├── index.js │ │ ├── info.js │ │ ├── order.js │ │ ├── quote.js │ │ ├── status.js │ │ └── statusByQuote.js │ ├── simplex │ │ ├── call.js │ │ ├── getOrder.js │ │ ├── getQuote.js │ │ └── index.js │ ├── simplex_events │ │ ├── index.js │ │ └── retrieveEvents.js │ ├── sourceValidate.js │ └── validator.js ├── ecosystem.config.js ├── package-lock.json ├── package.json ├── src │ ├── common │ │ └── index.js │ ├── config.js │ ├── currency_rates │ │ ├── index.js │ │ └── retrieveCurrencyRates.js │ ├── index.js │ ├── mangodb │ │ ├── event_schema.js │ │ ├── exchange_rate_schema.js │ │ ├── index.js │ │ └── schema.js │ ├── response.js │ ├── routes │ │ ├── currentCurrencies.js │ │ ├── exchangeRates.js │ │ ├── index.js │ │ ├── info.js │ │ ├── order.js │ │ ├── quote.js │ │ ├── status.js │ │ └── statusByQuote.js │ ├── simplex │ │ ├── call.js │ │ ├── getOrder.js │ │ ├── getQuote.js │ │ └── index.js │ ├── simplex_events │ │ ├── index.js │ │ └── retrieveEvents.js │ ├── sourceValidate.js │ └── validator.js └── test │ ├── test.api.getEvents.js │ ├── test.api.getQuote.js │ └── test.api.info.js ├── boxfile.yml ├── currencyConfig.js ├── deploy ├── .example_env ├── nginx │ └── nginx.conf └── setup.sh ├── docker-compose.yml └── frontend ├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .postcssrc.js ├── Dockerfile ├── README.md ├── build ├── build.js ├── check-versions.js ├── logo.png ├── utils.js ├── vue-loader.conf.js ├── webpack.base.conf.js ├── webpack.dev.conf.js └── webpack.prod.conf.js ├── config ├── dev.env.js ├── index.js └── prod.env.js ├── currencyConfig.js ├── index.html ├── package-lock.json ├── package.json ├── src ├── App.vue ├── assets │ └── images │ │ ├── background-1.png │ │ ├── icons │ │ ├── calendar.png │ │ ├── clock.png │ │ ├── money.png │ │ └── support.png │ │ ├── logo.png │ │ ├── simplex.png │ │ └── visa-master.png ├── components │ ├── Body │ │ ├── BuyCrypto │ │ │ ├── BuyCrypto.scss │ │ │ └── BuyCrypto.vue │ │ ├── CheckoutForm │ │ │ ├── CheckoutForm.scss │ │ │ └── CheckoutForm.vue │ │ ├── OrderStatus │ │ │ ├── OrderStatus.scss │ │ │ └── OrderStatus.vue │ │ ├── Promo1 │ │ │ ├── Promo1.scss │ │ │ └── Promo1.vue │ │ └── Promo2 │ │ │ ├── Promo2.scss │ │ │ └── Promo2.vue │ ├── Footer │ │ ├── Footer.scss │ │ └── Footer.vue │ ├── Header │ │ ├── Header.scss │ │ └── Header.vue │ ├── LandingPage.scss │ └── LandingPage.vue ├── config.js ├── main.js ├── main.scss ├── router │ └── index.js ├── simplex-api │ └── index.js ├── store │ ├── actions.js │ ├── getters.js │ ├── index.js │ ├── mutations.js │ └── state.js └── var.scss └── static ├── .gitkeep └── images └── icons └── favicon.png /.dockerignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Node template 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Dependency directories 17 | node_modules/ 18 | jspm_packages/ 19 | 20 | # TypeScript v1 declaration files 21 | typings/ 22 | 23 | # Optional npm cache directory 24 | .npm 25 | 26 | # Optional eslint cache 27 | .eslintcache 28 | 29 | # Optional REPL history 30 | .node_repl_history 31 | 32 | # Output of 'npm pack' 33 | *.tgz 34 | 35 | # Yarn Integrity file 36 | .yarn-integrity 37 | 38 | # dotenv environment variables file 39 | **/*.env 40 | 41 | # next.js build output 42 | .next 43 | ### JetBrains template 44 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 45 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 46 | 47 | # User-specific stuff 48 | .idea/ -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | # yarn directory 64 | .yarn 65 | 66 | # intellij settings 67 | .idea 68 | /setup.sh 69 | /takedown.sh 70 | -------------------------------------------------------------------------------- /.nanoignore: -------------------------------------------------------------------------------- 1 | api/node_modules/ 2 | api/src/ 3 | api/test/ 4 | api/.babelrc 5 | 6 | frontend/build/ 7 | frontend/config/ 8 | frontend/node_modules/ 9 | frontend/src/ 10 | frontend/.babelrc 11 | frontend/.editorconfig 12 | frontend/.eslintignore 13 | frontend/.postcssrc.js -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 MyEtherWallet 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simplex backend API 2 | 3 | Full backend api to integrate simplex crypto api powered by nanobox 4 | 5 | ## Getting Started 6 | 7 | These instructions will get you a copy of the project up and running on your local machine for development and testing purposes. See deployment for notes on how to deploy the project on a live system. 8 | 9 | ### Prerequisites 10 | 11 | Setup `.env` file with following variable 12 | ``` 13 | WALLET_ID= 14 | QUOTE_EP=https://sandbox.test-simplexcc.com/wallet/merchant/v2/quote 15 | ORDER_EP=https://sandbox.test-simplexcc.com/wallet/merchant/v2/payments/partner/data 16 | PAYMENT_EP=https://sandbox.test-simplexcc.com/payments/new 17 | SIMPLEX_APIKEY= 18 | RECAPTCHA_SITE_KEY= 19 | RECAPTCHA_SECRET_KEY= 20 | API_HOST= 21 | 22 | API_KEY= 23 | API_KEY_HEADER=apikey 24 | IOS_REFERER= 25 | ANDROID_REFERER= 26 | ``` 27 | 28 | ## Local 29 | ### Installing 30 | Start by installing the dependencies by running ```npm install``` in each of the api and frontend directories. 31 | 32 | ### Running 33 | To start the two components locally run ```npm start``` in the api directory first followed by ```npm start``` in the frontend directories. 34 | 35 | 36 | 37 | ## Running the tests 38 | ``` 39 | npm run test 40 | ``` 41 | 42 | ## Full Dockerized Deployment 43 | 44 | ## Pre-Deployment 45 | Before deploying you need to use the .env file from 'Prerequisites' and depending on whether you follow method 1 or method 2 a nginx.conf file. 46 | In either case you will need to replace the server name values to reflect your particular setup. (I've denoted the items that need to be changed below as apiSubDomain.myDomain.com and frontendSubDomain.myDomain.com). 47 | 48 | **Method 1:** 49 | Before deploying you need to create an .env and a nginx.conf file to place in the location where you will execute the deploy script. 50 | 51 | **Method 2:** 52 | Create a fork of the repository and update the /deploy/nginx.conf file and push those changes to your fork 53 | 54 | Replacing apiSubDomain.myDomain.com and frontendSubDomain.myDomain.com with your urls. 55 | 56 | ##### nginx.conf 57 | ``` 58 | events {} 59 | 60 | 61 | http { 62 | proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=one:8m max_size=3000m inactive=600m; 63 | 64 | gzip_comp_level 6; 65 | gzip_vary on; 66 | gzip_min_length 1000; 67 | gzip_proxied any; 68 | gzip_types text/plain text/css application/json application/x-javascript application/xml application/xml+rss text/javascript; 69 | gzip_buffers 16 8k; 70 | 71 | server { 72 | listen 80 default_server; 73 | listen [::]:80 default_server; 74 | return 200; 75 | } 76 | server { 77 | listen 80; 78 | listen [::]:80; 79 | server_name apiSubDomain.myDomain.com; 80 | location / { 81 | proxy_pass http://api:8080; 82 | proxy_set_header Host $host; 83 | proxy_set_header X-Forwarded-For $remote_addr; 84 | } 85 | } 86 | server { 87 | listen 80; 88 | listen [::]:80; 89 | server_name frontendSubDomain.myDomain.com; 90 | location / { 91 | proxy_pass http://frontend:8080; 92 | proxy_set_header Host $host; 93 | proxy_set_header X-Forwarded-For $remote_addr; 94 | } 95 | } 96 | } 97 | ``` 98 | ## Deployment 99 | To deploy move the .env, nginx.conf (if using method 1), and the setup.sh file located in /simplex-api/deploy to the location where you want to place your deployment. Open a terminal in that directory and run ```bash ./setup.sh``` This will set up the environment including checking for and (if needed) installing docker and docker-compose. 100 | 101 | Running the script with no arguments 102 | - checks for and installs docker and docker-compose 103 | - checks for the presence of an .env file 104 | - clones the latest version of the repository 105 | - builds the docker containers for the api and frontend 106 | - starts the docker containers 107 | 108 | 109 | 110 | ## External APIs 111 | 112 | 113 | 114 | post requests: 115 | 116 | 117 | ### Quote 118 | 119 | - POST /quote\ 120 | returns a quote for the provided input\ 121 | parameter object: 122 | ```javascript 123 | { 124 | digital_currency: "", 125 | fiat_currency: '', 126 | requested_currency: "", 127 | requested_amount "" 'must be a number 128 | } 129 | ``` 130 | - from currency 131 | - to currency 132 | - from amount 133 | 134 | ### Order 135 | 136 | - POST /quote\ 137 | returns a quote for the provided input\ 138 | parameter object: 139 | ```javascript 140 | { 141 | account_details: { 142 | app_end_user_id: "" 143 | }, 144 | transaction_details: { 145 | payment_details: { 146 | fiat_total_amount: { 147 | currency: "", 148 | amount: "" 149 | }, 150 | requested_digital_amount: { 151 | currency: "", 152 | amount: "" 153 | }, 154 | destination_wallet: { 155 | currency: "", 156 | address: "" 157 | } 158 | } 159 | } 160 | } 161 | ``` 162 | Supplied in quote response: 163 | - app_end_user_id 164 | - fiat_total_amount.currency 165 | - fiat_total_amount.amount 166 | - requested_digital_amount.currency 167 | - requested_digital_amount.amount 168 | 169 | Additional in the order request: 170 | - destination_wallet.currency 171 | - destination_wallet.address 172 | 173 | ### Status 174 | - GET /status/:user_id\ 175 | gets the latest status for the particular user_id 176 | - response: 177 | ```javascript 178 | 179 | { 180 | user_id: , 181 | status: , 182 | fiat_total_amount: { 183 | currency: , 184 | amount: 185 | }, 186 | requested_digital_amount: { 187 | currency: , 188 | amount: 189 | } 190 | } 191 | ``` 192 | 193 | - The user_id supplied to the status endpoint the same user_id used to generate the order. 194 | - The status is updated when an event containing the user_id appears. 195 | - Note: The user_id is created on a per order basis, so no correlation exists between various orders. 196 | 197 | 198 | #### Debuging 199 | 200 | Logging Namespaces: 201 | 202 | validation:bypass 203 | 204 | request: 205 | routes-order 206 | routes-quote 207 | 208 | response: 209 | routes-order 210 | routes-quote 211 | 212 | calls: 213 | getOrder 214 | getQuote 215 | 216 | ## Built With 217 | 218 | * [ExpressJS](https://expressjs.com/) - The web framework 219 | * [Mocha](https://mochajs.org/) - The testing framework 220 | * [docker](https://www.docker.com) - Container Platform 221 | 222 | ## License 223 | 224 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details 225 | 226 | -------------------------------------------------------------------------------- /api/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"], 3 | "plugins": ["transform-object-rest-spread"] 4 | } -------------------------------------------------------------------------------- /api/.dockerignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Node template 3 | # Logs 4 | logs 5 | *.log 6 | npm-debug.log* 7 | yarn-debug.log* 8 | yarn-error.log* 9 | 10 | # Runtime data 11 | pids 12 | *.pid 13 | *.seed 14 | *.pid.lock 15 | 16 | # Dependency directories 17 | node_modules/ 18 | jspm_packages/ 19 | 20 | # TypeScript v1 declaration files 21 | typings/ 22 | 23 | # Optional npm cache directory 24 | .npm 25 | 26 | # Optional eslint cache 27 | .eslintcache 28 | 29 | # Optional REPL history 30 | .node_repl_history 31 | 32 | # Output of 'npm pack' 33 | *.tgz 34 | 35 | # Yarn Integrity file 36 | .yarn-integrity 37 | 38 | # dotenv environment variables file 39 | **/*.env 40 | 41 | # next.js build output 42 | .next 43 | ### JetBrains template 44 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 45 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 46 | 47 | # User-specific stuff 48 | .idea/ -------------------------------------------------------------------------------- /api/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | # yarn directory 64 | .yarn -------------------------------------------------------------------------------- /api/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:8.16.0-jessie 2 | 3 | 4 | RUN apt-get update 5 | RUN apt-get -y install cron 6 | 7 | #ADD crontab /etc/cron.d/cron-events 8 | #ADD crontab /etc/crontab 9 | #RUN chmod 0644 /etc/cron.d/cron-events 10 | #RUN service cron start 11 | 12 | ENV NODE_OPTIONS --max-old-space-size=4096 13 | RUN npm install npm@6.9 -g 14 | RUN npm install pm2 -g 15 | RUN node -v && npm -v 16 | WORKDIR /api 17 | 18 | COPY . /api 19 | RUN npm install 20 | RUN npm run build 21 | EXPOSE 8080/tcp 22 | 23 | CMD npm run start:docker 24 | -------------------------------------------------------------------------------- /api/crontab: -------------------------------------------------------------------------------- 1 | * * * * * root bash -c 'cd /api && npm run eventsProd' 2 | * * * * * root bash -c 'cd /api && npm run getExchangeRatesProd' 3 | # Don't remove the empty line at the end of this file. It is required to run the cron job -------------------------------------------------------------------------------- /api/currencyConfig.js: -------------------------------------------------------------------------------- 1 | const fiat = [ 2 | "USD", 3 | "EUR", 4 | "CAD", 5 | "JPY", 6 | "GBP", 7 | "RUB", 8 | "AUD", 9 | "KRW", 10 | "CHF", 11 | "CZK", 12 | "DKK", 13 | "NOK", 14 | "NZD", 15 | "PLN", 16 | "SEK", 17 | "TRY", 18 | "ZAR", 19 | "HUF" 20 | ]; 21 | const crypto = ["ETH", "BTC", "BSC", "DAI", "USDT"]; 22 | 23 | const handler = function (defaultValue = 42) { 24 | return { 25 | get: function (target, name) { 26 | return target.hasOwnProperty(name) ? target[name] : defaultValue; 27 | } 28 | }; 29 | }; 30 | 31 | const minFiatTarget = { USD: 50, EUR: 50 }; 32 | const maxFiatTarget = { USD: 20000, EUR: 20000 }; 33 | 34 | const minFiat = new Proxy(minFiatTarget, handler(50)); 35 | const maxFiat = new Proxy(maxFiatTarget, handler(20000)); 36 | 37 | module.exports = { 38 | fiat, 39 | crypto, 40 | minFiat, 41 | maxFiat 42 | }; 43 | -------------------------------------------------------------------------------- /api/dist/common/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | var getIP = function getIP(req) { 7 | return (req.headers['x-forwarded-for'] || req.connection.remoteAddress || req.socket.remoteAddress || req.connection.socket.remoteAddress).split(',')[0]; 8 | }; 9 | 10 | exports.getIP = getIP; -------------------------------------------------------------------------------- /api/dist/config.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.productValidation = 7 | exports.env = 8 | exports.recaptcha = 9 | exports.mangodb = 10 | exports.simplex = 11 | exports.network = 12 | undefined; 13 | 14 | var _currencyConfig = require("../currencyConfig"); 15 | 16 | require("dotenv").config({ 17 | path: "../../.env" 18 | }); 19 | 20 | var network = { 21 | port: process.env.PORT || 8080 22 | }; 23 | var simplex = { 24 | walletID: process.env.WALLET_ID || "", 25 | quoteEP: process.env.QUOTE_EP || "", 26 | orderEP: process.env.ORDER_EP || "", 27 | paymentEP: process.env.PAYMENT_EP || "", 28 | eventEP: process.env.EVENT_EP || "", 29 | apiKey: process.env.SIMPLEX_APIKEY || "", 30 | apiVersion: "1", 31 | validFiat: process.env.FIAT_CURRENCIES 32 | ? process.env.FIAT_CURRENCIES.split(",") 33 | : _currencyConfig.fiat, // ['USD','EUR'], 34 | validDigital: process.env.DIGITAL_CURRENCIES 35 | ? process.env.DIGITAL_CURRENCIES.split(",") 36 | : _currencyConfig.crypto, // ['BTC', 'BSC', 'ETH'], 37 | currencyApiKey: process.env.FIXER_APIKEY || "", 38 | baseCurrency: process.env.BASE_CURRENCY || "USD", 39 | minBaseCurrency: process.env.FIAT_MIN_USD || 50, // USD 40 | maxBaseCurrency: process.env.FIAT_MAX_USD || 20000, // USD 41 | status: { 42 | initiated: "INITIATED", 43 | sentToSimplex: "SENT_TO_SIMPLEX", 44 | deniedSimplex: "DENIED_SIMPLEX", 45 | processingSimplex: "PROCESSING_SIMPPLEX", 46 | successSimplex: "SUCCESS_SIMPLEX" 47 | } 48 | }; 49 | var mangodb = { 50 | host: process.env.DATA_MONGODB_HOST || "localhost", 51 | port: 27017, 52 | name: "gonano" 53 | }; 54 | var recaptcha = { 55 | siteKey: process.env.RECAPTCHA_SITE_KEY || "", 56 | secretKey: process.env.RECAPTCHA_SECRET_KEY || "" 57 | }; 58 | var env = { 59 | mode: process.env.NODE_ENV || "production", 60 | dev: { 61 | ip: "141.145.165.137", 62 | accept_language: "en-US,en;q=0.9", 63 | user_agent: 64 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36" 65 | } 66 | }; 67 | 68 | var productValidation = { 69 | apiKeyHeaderName: process.env.API_KEY_HEADER || "apikey", 70 | apiKeys: process.env.API_KEY 71 | ? [process.env.API_KEY] 72 | : ["321654987", "abcdefg"], 73 | referrerAppleiOS: process.env.IOS_REFERER || "iOS", 74 | referrerAndroid: process.env.ANDROID_REFERER || "Android", 75 | specialWebOrigins: process.env.SPECIAL_WEB_ORIGINS 76 | ? process.env.SPECIAL_WEB_ORIGINS.split(",") 77 | : [] 78 | }; 79 | 80 | exports.network = network; 81 | exports.simplex = simplex; 82 | exports.mangodb = mangodb; 83 | exports.recaptcha = recaptcha; 84 | exports.env = env; 85 | exports.productValidation = productValidation; 86 | -------------------------------------------------------------------------------- /api/dist/currency_rates/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _retrieveCurrencyRates = require('./retrieveCurrencyRates'); 8 | 9 | var _retrieveCurrencyRates2 = _interopRequireDefault(_retrieveCurrencyRates); 10 | 11 | var _nodeCron = require('node-cron'); 12 | 13 | var _nodeCron2 = _interopRequireDefault(_nodeCron); 14 | 15 | var _logging = require('logging'); 16 | 17 | var _logging2 = _interopRequireDefault(_logging); 18 | 19 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 20 | 21 | var logger = (0, _logging2.default)('currency_rates/index.js'); 22 | 23 | var runCron = function runCron() { 24 | console.log('cron setup for exchange rates'); 25 | var cronTime = '0 * * * *'; 26 | return _nodeCron2.default.schedule(cronTime, function () { 27 | try { 28 | (0, _retrieveCurrencyRates2.default)().then(function () { 29 | logger.info('FixerIO Rates Retrieved'); 30 | }).catch(function (_error) { 31 | logger.error(_error); 32 | }); 33 | } catch (e) { 34 | logger.error(e); 35 | } 36 | }); 37 | }; 38 | 39 | exports.default = runCron; -------------------------------------------------------------------------------- /api/dist/currency_rates/retrieveCurrencyRates.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _mangodb = require('../mangodb'); 8 | 9 | var _logging = require('logging'); 10 | 11 | var _logging2 = _interopRequireDefault(_logging); 12 | 13 | var _config = require('../config'); 14 | 15 | var _request = require('request'); 16 | 17 | var _request2 = _interopRequireDefault(_request); 18 | 19 | var _debug = require('debug'); 20 | 21 | var _debug2 = _interopRequireDefault(_debug); 22 | 23 | var _bignumber = require('bignumber.js'); 24 | 25 | var _bignumber2 = _interopRequireDefault(_bignumber); 26 | 27 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 28 | 29 | var logger = (0, _logging2.default)('currency_rates/retrieveCurrencyRates.js'); 30 | var debugRequest = (0, _debug2.default)('calls:Events'); 31 | 32 | (0, _mangodb.connect)().then(function () { 33 | logger.info('mangodb running on port: ' + _config.mangodb.host + ':' + _config.mangodb.port); 34 | }).catch(function (err) { 35 | logger.error('mangodb error: ' + err); 36 | }); 37 | 38 | var multiply = function multiply(val1, val2) { 39 | return new _bignumber2.default(val1).times(new _bignumber2.default(val2)).toNumber(); 40 | }; 41 | 42 | var getExchangeRates = function getExchangeRates() { 43 | return new Promise(function (resolve, reject) { 44 | var currencies = _config.simplex.validFiat.join(','); 45 | var url = 'http://data.fixer.io/api/latest?access_key=' + _config.simplex.currencyApiKey + '&base=' + _config.simplex.baseCurrency + '&symbols=' + currencies + '&format=1'; 46 | var options = { 47 | url: url, 48 | method: 'get', 49 | json: true 50 | }; 51 | var retrieveCallback = function retrieveCallback(error, response, body) { 52 | try { 53 | if (!error && response.statusCode === 200) { 54 | logger.error(body); 55 | var rates = Object.keys(body.rates).reduce(function (prior, current) { 56 | prior.push({ 57 | pair_key: body.base + current, 58 | base_currency: body.base, 59 | rate_currency: current, 60 | min: multiply(_config.simplex.minBaseCurrency, body.rates[current]), 61 | max: multiply(_config.simplex.maxBaseCurrency, body.rates[current]), 62 | rate: body.rates[current] 63 | }); 64 | return prior; 65 | }, []); 66 | rates.forEach(updateItem); 67 | // processEvent 68 | } else if (response.statusCode === 400) { 69 | logger.error(response); 70 | reject(body); 71 | } else { 72 | logger.error(error); 73 | reject(error); 74 | } 75 | } catch (e) { 76 | logger.error(error); 77 | reject(error); 78 | } 79 | }; 80 | debugRequest(options); 81 | (0, _request2.default)(options, retrieveCallback); 82 | }); 83 | }; 84 | 85 | function updateItem(recordItem) { 86 | (0, _mangodb.findAndUpdateExchangeRates)({ 87 | pair_key: recordItem.pair_key 88 | }, recordItem).catch(function (err) { 89 | logger.error(err); 90 | }); 91 | } 92 | 93 | exports.default = getExchangeRates; -------------------------------------------------------------------------------- /api/dist/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _express = require('express'); 8 | 9 | var _express2 = _interopRequireDefault(_express); 10 | 11 | var _bodyParser = require('body-parser'); 12 | 13 | var _bodyParser2 = _interopRequireDefault(_bodyParser); 14 | 15 | var _logging = require('logging'); 16 | 17 | var _logging2 = _interopRequireDefault(_logging); 18 | 19 | var _debug = require('debug'); 20 | 21 | var _debug2 = _interopRequireDefault(_debug); 22 | 23 | var _simplex_events = require('./simplex_events'); 24 | 25 | var _simplex_events2 = _interopRequireDefault(_simplex_events); 26 | 27 | var _currency_rates = require('./currency_rates'); 28 | 29 | var _currency_rates2 = _interopRequireDefault(_currency_rates); 30 | 31 | var _cors = require('cors'); 32 | 33 | var _cors2 = _interopRequireDefault(_cors); 34 | 35 | var _routes = require('./routes'); 36 | 37 | var _routes2 = _interopRequireDefault(_routes); 38 | 39 | var _mangodb = require('./mangodb'); 40 | 41 | var _config = require('./config'); 42 | 43 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 44 | 45 | var debugRequest = (0, _debug2.default)('index.js'); 46 | var logger = (0, _logging2.default)('index.js'); 47 | var app = (0, _express2.default)(); 48 | 49 | app.use(_bodyParser2.default.json()); 50 | app.use((0, _cors2.default)()); 51 | (0, _routes2.default)(app); 52 | (0, _mangodb.connect)().then(function () { 53 | logger.info('mangodb running on port: ' + _config.mangodb.host + ':' + _config.mangodb.port); 54 | }).catch(function (err) { 55 | logger.error('mangodb error: ' + err); 56 | }); 57 | (0, _simplex_events2.default)(); 58 | (0, _currency_rates2.default)(); 59 | var server = app.listen(_config.network.port, function () { 60 | debugRequest('DEBUG ACTIVE ' + process.env.DEBUG); 61 | logger.info('app running on port: ' + server.address().port); 62 | }); 63 | exports.default = server; -------------------------------------------------------------------------------- /api/dist/mangodb/event_schema.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _mongoose = require('mongoose'); 8 | 9 | var _mongoose2 = _interopRequireDefault(_mongoose); 10 | 11 | var _mongooseTimestamp = require('mongoose-timestamp'); 12 | 13 | var _mongooseTimestamp2 = _interopRequireDefault(_mongooseTimestamp); 14 | 15 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 16 | 17 | var Schema = _mongoose2.default.Schema; 18 | 19 | var eventSchema = new Schema({ 20 | event_id: { 21 | type: String 22 | }, 23 | name: { 24 | type: String 25 | }, 26 | payment: { 27 | id: { 28 | type: String 29 | }, 30 | status: { 31 | type: String 32 | }, 33 | created_at: { 34 | type: String 35 | }, 36 | updated_at: { 37 | type: String 38 | }, 39 | partner_end_user_id: { 40 | type: String 41 | }, 42 | fiat_total_amount: { 43 | currency: { 44 | type: String 45 | }, 46 | amount: { 47 | type: Number 48 | } 49 | } 50 | }, 51 | timestamp: { 52 | type: String 53 | } 54 | }); 55 | eventSchema.plugin(_mongooseTimestamp2.default); 56 | exports.default = _mongoose2.default.model('EventRecord', eventSchema); -------------------------------------------------------------------------------- /api/dist/mangodb/exchange_rate_schema.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _mongoose = require('mongoose'); 8 | 9 | var _mongoose2 = _interopRequireDefault(_mongoose); 10 | 11 | var _mongooseTimestamp = require('mongoose-timestamp'); 12 | 13 | var _mongooseTimestamp2 = _interopRequireDefault(_mongooseTimestamp); 14 | 15 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 16 | 17 | var Schema = _mongoose2.default.Schema; 18 | 19 | var ExchangeRateSchema = new Schema({ 20 | pair_key: { 21 | type: String, 22 | required: true, 23 | unique: true 24 | }, 25 | base_currency: { 26 | type: String, 27 | required: true 28 | }, 29 | rate_currency: { 30 | type: String, 31 | sparse: true 32 | }, 33 | min: { 34 | type: Number, 35 | required: true 36 | }, 37 | max: { 38 | type: Number, 39 | required: true 40 | }, 41 | rate: { 42 | type: Number, 43 | required: true 44 | } 45 | }); 46 | ExchangeRateSchema.plugin(_mongooseTimestamp2.default); 47 | exports.default = _mongoose2.default.model('ExchangeRate', ExchangeRateSchema); -------------------------------------------------------------------------------- /api/dist/mangodb/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.findAndUpdateStatus = exports.findAndUpdate = exports.getOrderById = exports.findAndUpdateExchangeRates = exports.getExchangeRates = exports.ExchangeRateSchema = exports.EventSchema = exports.Order = exports.connect = undefined; 7 | 8 | var _mongoose = require('mongoose'); 9 | 10 | var _mongoose2 = _interopRequireDefault(_mongoose); 11 | 12 | var _config = require('../config'); 13 | 14 | var _schema = require('./schema'); 15 | 16 | var _schema2 = _interopRequireDefault(_schema); 17 | 18 | var _event_schema = require('./event_schema'); 19 | 20 | var _event_schema2 = _interopRequireDefault(_event_schema); 21 | 22 | var _exchange_rate_schema = require('./exchange_rate_schema'); 23 | 24 | var _exchange_rate_schema2 = _interopRequireDefault(_exchange_rate_schema); 25 | 26 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 27 | 28 | var connect = function connect() { 29 | return new Promise(function (resolve, reject) { 30 | _mongoose2.default.connect('mongodb://' + _config.mangodb.host + ':' + _config.mangodb.port + '/' + _config.mangodb.name); 31 | var db = _mongoose2.default.connection; 32 | db.once('error', function (error) { 33 | reject(error); 34 | }); 35 | db.once('open', function () { 36 | resolve(); 37 | }); 38 | }); 39 | }; 40 | 41 | var getOrderById = function getOrderById(_userId, _quoteId) { 42 | return new Promise(function (resolve, reject) { 43 | if (_userId && _quoteId) { 44 | return _schema2.default.find({ 45 | user_id: _userId, 46 | quote_id: _quoteId 47 | }).sort({ 'created_at': -1 }).exec(function (err, res) { 48 | if (err) reject(err);else resolve(res); 49 | }); 50 | } else { 51 | return _schema2.default.find({ 52 | user_id: _userId 53 | }).sort({ 'created_at': -1 }).exec(function (err, res) { 54 | if (err) reject(err);else resolve(res); 55 | }); 56 | } 57 | }); 58 | }; 59 | 60 | var findAndUpdate = function findAndUpdate(_userId, _quoteId, _newVals) { 61 | if (_quoteId && _newVals) { 62 | return _schema2.default.findOneAndUpdate({ 63 | user_id: _userId, 64 | quote_id: _quoteId 65 | }, _newVals); 66 | } else if (!_quoteId && _newVals) { 67 | return _schema2.default.findOneAndUpdate({ 68 | user_id: _userId 69 | }, _newVals); 70 | } else { 71 | return _schema2.default.findOneAndUpdate({ 72 | user_id: _userId 73 | }, _quoteId); // in this case _paymentId contains the content of _newVals 74 | } 75 | }; 76 | 77 | var findAndUpdateStatus = function findAndUpdateStatus(_userId, _paymentId, _newVals) { 78 | return _schema2.default.findOneAndUpdate({ 79 | user_id: _userId, 80 | payment_id: _paymentId 81 | }, _newVals); 82 | }; 83 | 84 | var getExchangeRates = function getExchangeRates() { 85 | var base = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'USD'; 86 | 87 | return _exchange_rate_schema2.default.find({ 88 | base_currency: base 89 | }); 90 | }; 91 | 92 | var findAndUpdateExchangeRates = function findAndUpdateExchangeRates(queryItem, rateItem) { 93 | return _exchange_rate_schema2.default.findOneAndUpdate(queryItem, rateItem, { upsert: true }); 94 | }; 95 | 96 | exports.connect = connect; 97 | exports.Order = _schema2.default; 98 | exports.EventSchema = _event_schema2.default; 99 | exports.ExchangeRateSchema = _exchange_rate_schema2.default; 100 | exports.getExchangeRates = getExchangeRates; 101 | exports.findAndUpdateExchangeRates = findAndUpdateExchangeRates; 102 | exports.getOrderById = getOrderById; 103 | exports.findAndUpdate = findAndUpdate; 104 | exports.findAndUpdateStatus = findAndUpdateStatus; -------------------------------------------------------------------------------- /api/dist/mangodb/schema.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _mongoose = require('mongoose'); 8 | 9 | var _mongoose2 = _interopRequireDefault(_mongoose); 10 | 11 | var _mongooseTimestamp = require('mongoose-timestamp'); 12 | 13 | var _mongooseTimestamp2 = _interopRequireDefault(_mongooseTimestamp); 14 | 15 | var _config = require('../config'); 16 | 17 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 18 | 19 | var Schema = _mongoose2.default.Schema; 20 | 21 | var orderSchema = new Schema({ 22 | user_id: { 23 | type: String, 24 | required: true 25 | // unique: true 26 | }, 27 | quote_id: { 28 | type: String, 29 | unique: true, 30 | sparse: true 31 | }, 32 | payment_id: { 33 | type: String, 34 | unique: true, 35 | sparse: true 36 | }, 37 | order_id: { 38 | type: String, 39 | unique: true, 40 | sparse: true 41 | }, 42 | fiat_total_amount: { 43 | currency: { 44 | type: String, 45 | required: true 46 | }, 47 | amount: { 48 | type: Number, 49 | required: true 50 | } 51 | }, 52 | requested_digital_amount: { 53 | currency: { 54 | type: String, 55 | required: true 56 | }, 57 | amount: { 58 | type: Number, 59 | required: true 60 | } 61 | }, 62 | status: { 63 | type: String, 64 | enum: Object.values(_config.simplex.status), 65 | required: true 66 | }, 67 | source: { 68 | type: String, 69 | required: false 70 | } 71 | }); 72 | orderSchema.plugin(_mongooseTimestamp2.default); 73 | exports.default = _mongoose2.default.model('Order', orderSchema); -------------------------------------------------------------------------------- /api/dist/response.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = { 7 | error: function error(res, msg) { 8 | res.setHeader('Content-Type', 'application/json'); 9 | res.send(JSON.stringify({ 10 | error: true, 11 | result: msg 12 | }, null, 3)); 13 | }, 14 | success: function success(res, msg) { 15 | res.setHeader('Content-Type', 'application/json'); 16 | res.send(JSON.stringify({ 17 | error: false, 18 | result: msg 19 | }, null, 3)); 20 | } 21 | }; -------------------------------------------------------------------------------- /api/dist/routes/currentCurrencies.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _response = require('../response'); 8 | 9 | var _response2 = _interopRequireDefault(_response); 10 | 11 | var _debug = require('debug'); 12 | 13 | var _debug2 = _interopRequireDefault(_debug); 14 | 15 | var _config = require('../config'); 16 | 17 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 18 | 19 | var debugRequest = (0, _debug2.default)('request:info'); 20 | 21 | exports.default = function (app) { 22 | app.get('/current-currencies', function (req, res) { 23 | debugRequest('Current Currencies Request Received'); 24 | var baseFiat = { 25 | USD: { 26 | symbol: 'USD', 27 | name: 'US Dollar' 28 | }, 29 | EUR: { 30 | symbol: 'EUR', 31 | name: 'Euro' 32 | }, 33 | CAD: { 34 | symbol: 'CAD', 35 | name: 'Canadian Dollar' 36 | }, 37 | JPY: { 38 | symbol: 'JPY', 39 | name: 'Japanese Yen' 40 | } 41 | }; 42 | var baseDigital = { 43 | BTC: { 44 | symbol: 'BTC', 45 | name: 'Bitcoin' 46 | }, 47 | ETH: { 48 | symbol: 'ETH', 49 | name: 'Ether' 50 | } 51 | }; 52 | 53 | var fiat = _config.simplex.validFiat.reduce(function (acc, curr) { 54 | if (baseFiat[curr]) { 55 | acc[curr] = baseFiat[curr]; 56 | } else { 57 | acc[curr] = { 58 | symbol: curr, 59 | name: curr 60 | }; 61 | } 62 | return acc; 63 | }, {}); 64 | 65 | var digital = _config.simplex.validDigital.reduce(function (acc, curr) { 66 | if (baseDigital[curr]) { 67 | acc[curr] = baseDigital[curr]; 68 | } else { 69 | acc[curr] = { 70 | symbol: curr, 71 | name: curr 72 | }; 73 | } 74 | return acc; 75 | }, {}); 76 | 77 | _response2.default.success(res, { 78 | fiat: fiat, 79 | digital: digital 80 | }); 81 | }); 82 | }; -------------------------------------------------------------------------------- /api/dist/routes/exchangeRates.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _response = require('../response'); 8 | 9 | var _response2 = _interopRequireDefault(_response); 10 | 11 | var _debug = require('debug'); 12 | 13 | var _debug2 = _interopRequireDefault(_debug); 14 | 15 | var _mangodb = require('../mangodb'); 16 | 17 | var _config = require('../config'); 18 | 19 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 20 | 21 | var debugRequest = (0, _debug2.default)('request:info'); 22 | 23 | exports.default = function (app) { 24 | app.get('/exchange-rates', function (req, res) { 25 | debugRequest('Exchange Rates Request Received'); 26 | (0, _mangodb.getExchangeRates)(_config.simplex.baseCurrency).then(function (rates) { 27 | var cleanedRates = rates.map(function (item) { 28 | return { 29 | rate_currency: item.rate_currency, 30 | base_currency: item.base_currency, 31 | min: item.min, 32 | max: item.max, 33 | rate: item.rate, 34 | updatedAt: item.updatedAt 35 | }; 36 | }); 37 | _response2.default.success(res, { 38 | rates: cleanedRates 39 | }); 40 | }).catch(function (error) { 41 | _response2.default.error(res, error); 42 | }); 43 | }); 44 | }; -------------------------------------------------------------------------------- /api/dist/routes/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _quote = require('./quote'); 8 | 9 | var _quote2 = _interopRequireDefault(_quote); 10 | 11 | var _order = require('./order'); 12 | 13 | var _order2 = _interopRequireDefault(_order); 14 | 15 | var _info = require('./info'); 16 | 17 | var _info2 = _interopRequireDefault(_info); 18 | 19 | var _statusByQuote = require('./statusByQuote'); 20 | 21 | var _statusByQuote2 = _interopRequireDefault(_statusByQuote); 22 | 23 | var _status = require('./status'); 24 | 25 | var _status2 = _interopRequireDefault(_status); 26 | 27 | var _exchangeRates = require('./exchangeRates'); 28 | 29 | var _exchangeRates2 = _interopRequireDefault(_exchangeRates); 30 | 31 | var _currentCurrencies = require('./currentCurrencies'); 32 | 33 | var _currentCurrencies2 = _interopRequireDefault(_currentCurrencies); 34 | 35 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 36 | 37 | exports.default = function (app) { 38 | (0, _quote2.default)(app); 39 | (0, _order2.default)(app); 40 | (0, _info2.default)(app); 41 | (0, _statusByQuote2.default)(app); 42 | (0, _status2.default)(app); 43 | (0, _exchangeRates2.default)(app); 44 | (0, _currentCurrencies2.default)(app); 45 | }; -------------------------------------------------------------------------------- /api/dist/routes/info.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _response = require('../response'); 8 | 9 | var _response2 = _interopRequireDefault(_response); 10 | 11 | var _debug = require('debug'); 12 | 13 | var _debug2 = _interopRequireDefault(_debug); 14 | 15 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 16 | 17 | var packageInfo = require('../../package.json'); 18 | 19 | var debugRequest = (0, _debug2.default)('request:info'); 20 | 21 | exports.default = function (app) { 22 | app.get('/info', function (req, res) { 23 | debugRequest('Info Request Received'); 24 | _response2.default.success(res, { 25 | name: 'Simplex API', 26 | version: packageInfo.version 27 | }); 28 | }); 29 | }; -------------------------------------------------------------------------------- /api/dist/routes/order.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; 8 | 9 | var _logging = require('logging'); 10 | 11 | var _logging2 = _interopRequireDefault(_logging); 12 | 13 | var _walletAddressValidator = require('wallet-address-validator'); 14 | 15 | var _walletAddressValidator2 = _interopRequireDefault(_walletAddressValidator); 16 | 17 | var _v = require('uuid/v4'); 18 | 19 | var _v2 = _interopRequireDefault(_v); 20 | 21 | var _simplex = require('../simplex'); 22 | 23 | var _validator = require('../validator'); 24 | 25 | var _validator2 = _interopRequireDefault(_validator); 26 | 27 | var _response = require('../response'); 28 | 29 | var _response2 = _interopRequireDefault(_response); 30 | 31 | var _config = require('../config'); 32 | 33 | var _mangodb = require('../mangodb'); 34 | 35 | var _common = require('../common'); 36 | 37 | var _sourceValidate = require('../sourceValidate'); 38 | 39 | var _sourceValidate2 = _interopRequireDefault(_sourceValidate); 40 | 41 | var _debug = require('debug'); 42 | 43 | var _debug2 = _interopRequireDefault(_debug); 44 | 45 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 46 | 47 | var logger = (0, _logging2.default)('order.js'); 48 | var catchLogger = (0, _logging2.default)('order.js - catch'); 49 | var debugRequest = (0, _debug2.default)('request:routes-order'); 50 | var debugResponse = (0, _debug2.default)('response:routes-order'); 51 | var validationErrors = (0, _debug2.default)('errors:validation'); 52 | 53 | var validateMinMax = function validateMinMax(val) { 54 | return !(_config.simplex.minFiat > +val || _config.simplex.maxFiat < +val); 55 | }; 56 | var validateAddress = function validateAddress(val) { 57 | var maybeValid = _config.simplex.validDigital.filter(function (cryptoSymbol) { 58 | return _walletAddressValidator2.default.validate(val, cryptoSymbol); 59 | }); 60 | return maybeValid.length > 0; 61 | }; 62 | 63 | var schema = { 64 | account_details: { 65 | app_end_user_id: { 66 | type: String, 67 | required: true, 68 | match: /^[a-zA-Z0-9-_]+$/, 69 | length: { 70 | min: 12, 71 | max: 64 72 | }, 73 | message: 'app_end_user_id required min:12 max:64' 74 | } 75 | }, 76 | transaction_details: { 77 | payment_details: { 78 | quote_id: { 79 | type: String, 80 | // required: true, 81 | match: /^[a-zA-Z0-9-_]+$/, 82 | length: { 83 | min: 12, 84 | max: 64 85 | }, 86 | message: 'app_end_user_id required min:12 max:64' 87 | }, 88 | fiat_total_amount: { 89 | currency: { 90 | type: String, 91 | required: true, 92 | enum: _config.simplex.validFiat, 93 | message: 'fiat currency required' 94 | }, 95 | amount: { 96 | type: Number, 97 | required: true, 98 | use: { 99 | validateMinMax: validateMinMax 100 | }, 101 | message: 'fiat amount is required, must be a number, and must be between 50 and 20,000' 102 | } 103 | }, 104 | requested_digital_amount: { 105 | currency: { 106 | type: String, 107 | required: true, 108 | enum: _config.simplex.validDigital, 109 | message: 'requested currency required' 110 | }, 111 | amount: { 112 | type: Number, 113 | required: true, 114 | message: 'requested amount required and must be a number' 115 | } 116 | }, 117 | destination_wallet: { 118 | currency: { 119 | type: String, 120 | required: true, 121 | enum: _config.simplex.validDigital, 122 | message: 'destination wallet currency required' 123 | }, 124 | address: { 125 | type: String, 126 | required: true, 127 | use: { 128 | validateAddress: validateAddress 129 | }, 130 | message: 'destination address is required and must be a valid BTC or ETH address respectively' 131 | } 132 | } 133 | } 134 | } 135 | }; 136 | var validator = (0, _validator2.default)(schema); 137 | 138 | var valueMatchCheck = function valueMatchCheck(bodyVals, dbVals) { 139 | var mustAllMatch = []; 140 | mustAllMatch.push(bodyVals.fiat_total_amount.amount === dbVals.fiat_total_amount.amount); 141 | mustAllMatch.push(bodyVals.fiat_total_amount.currency === dbVals.fiat_total_amount.currency); 142 | mustAllMatch.push(bodyVals.requested_digital_amount.amount === dbVals.requested_digital_amount.amount); 143 | mustAllMatch.push(bodyVals.requested_digital_amount.currency === dbVals.requested_digital_amount.currency); 144 | return mustAllMatch.every(function (value) { 145 | return value; 146 | }); 147 | }; 148 | 149 | exports.default = function (app) { 150 | app.post('/order', (0, _sourceValidate2.default)(), function (req, res) { 151 | try { 152 | var errors = validator.validate(req.body); 153 | validationErrors(errors); 154 | if (_config.env.mode !== 'development' && req.recaptcha.error) { 155 | logger.error('ERROR: env.mode !== \'development\' && req.recaptcha.error'); 156 | logger.error(errors); 157 | logger.error(req.recaptcha.error); 158 | _response2.default.error(res, req.recaptcha.error); 159 | } else if (errors.length) { 160 | logger.error('Validation Error'); 161 | logger.error(errors); 162 | _response2.default.error(res, errors.map(function (_err) { 163 | return _err.message; 164 | })); 165 | } else { 166 | var userId = req.body.account_details.app_end_user_id; 167 | var quoteIdOrig = req.body.transaction_details.payment_details.quote_id; 168 | (0, _mangodb.getOrderById)(userId, quoteIdOrig).then(function (savedOrder) { 169 | var quoteId = quoteIdOrig || savedOrder[0].quote_id; 170 | var paymentId = (0, _v2.default)(); 171 | var orderId = (0, _v2.default)(); 172 | var acceptLanguage = _config.env.mode === 'development' ? _config.env.dev.accept_language : req.headers['accept-language']; 173 | var ip = _config.env.mode === 'development' ? _config.env.dev.ip : (0, _common.getIP)(req); 174 | var userAgent = _config.env.mode === 'development' ? _config.env.dev.user_agent : req.headers['user-agent']; 175 | if (!valueMatchCheck(req.body.transaction_details.payment_details, savedOrder[0])) { 176 | throw Error('Mismatch between quoted values and order submission values'); 177 | } 178 | var reqObj = { 179 | account_details: _extends({}, req.body.account_details, { 180 | app_provider_id: _config.simplex.walletID, 181 | app_version_id: _config.simplex.apiVersion, 182 | signup_login: { 183 | ip: ip, 184 | uaid: userId, 185 | accept_language: acceptLanguage, 186 | http_accept_language: acceptLanguage, 187 | user_agent: userAgent, 188 | cookie_session_id: userId, 189 | timestamp: new Date().toISOString() 190 | } 191 | }), 192 | transaction_details: { 193 | payment_details: _extends({}, req.body.transaction_details.payment_details, { 194 | quote_id: quoteId, 195 | payment_id: paymentId, 196 | order_id: orderId, 197 | original_http_ref_url: req.header('Referer') 198 | }) 199 | } 200 | }; 201 | (0, _mangodb.findAndUpdate)(userId, quoteId, { 202 | payment_id: paymentId, 203 | order_id: orderId, 204 | status: _config.simplex.status.sentToSimplex 205 | }).catch(function (err) { 206 | logger.error('findAndUpdate catch error'); 207 | logger.error(err); 208 | }); 209 | debugRequest(reqObj); 210 | (0, _simplex.getOrder)(reqObj).then(function (result) { 211 | debugResponse(result); 212 | if ('is_kyc_update_required' in result) { 213 | _response2.default.success(res, { 214 | payment_post_url: _config.simplex.paymentEP.replace(/\u200B/g, ''), 215 | version: _config.simplex.apiVersion, 216 | partner: _config.simplex.walletID, 217 | return_url: 'https://www.myetherwallet.com', 218 | quote_id: quoteId, 219 | payment_id: paymentId, 220 | user_id: userId, 221 | destination_wallet_address: reqObj.transaction_details.payment_details.destination_wallet.address, 222 | destination_wallet_currency: reqObj.transaction_details.payment_details.destination_wallet.currency, 223 | fiat_total_amount_amount: reqObj.transaction_details.payment_details.fiat_total_amount.amount, 224 | fiat_total_amount_currency: reqObj.transaction_details.payment_details.fiat_total_amount.currency, 225 | digital_total_amount_amount: reqObj.transaction_details.payment_details.requested_digital_amount.amount, 226 | digital_total_amount_currency: reqObj.transaction_details.payment_details.requested_digital_amount.currency 227 | }); 228 | } else { 229 | logger.error('is_kyc_update_required error'); 230 | logger.error(result); 231 | _response2.default.error(res, result); 232 | } 233 | }).catch(function (error) { 234 | logger.error('getOrder catch error'); 235 | logger.error(error); 236 | _response2.default.error(res, error); 237 | }); 238 | }).catch(function (err) { 239 | logger.error('getOrderById catch error'); 240 | logger.error(err); 241 | _response2.default.error(res, 'Invalid userId'); 242 | }); 243 | } 244 | } catch (e) { 245 | catchLogger.error(e); 246 | } 247 | }); 248 | }; -------------------------------------------------------------------------------- /api/dist/routes/quote.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _logging = require('logging'); 8 | 9 | var _logging2 = _interopRequireDefault(_logging); 10 | 11 | var _debug = require('debug'); 12 | 13 | var _debug2 = _interopRequireDefault(_debug); 14 | 15 | var _simplex = require('../simplex'); 16 | 17 | var _validator = require('../validator'); 18 | 19 | var _validator2 = _interopRequireDefault(_validator); 20 | 21 | var _v = require('uuid/v4'); 22 | 23 | var _v2 = _interopRequireDefault(_v); 24 | 25 | var _response = require('../response'); 26 | 27 | var _response2 = _interopRequireDefault(_response); 28 | 29 | var _config = require('../config'); 30 | 31 | var _mangodb = require('../mangodb'); 32 | 33 | var _sourceValidate = require('../sourceValidate'); 34 | 35 | var _sourceValidate2 = _interopRequireDefault(_sourceValidate); 36 | 37 | var _common = require('../common'); 38 | 39 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 40 | 41 | var validationLogger = (0, _logging2.default)('quote.js - validation'); 42 | var logger = (0, _logging2.default)('quote.js'); 43 | var debugRequest = (0, _debug2.default)('request:routes-quote'); 44 | var debugResponse = (0, _debug2.default)('response:routes-quote'); 45 | var validationErrors = (0, _debug2.default)('errors:validation'); 46 | 47 | var schema = { 48 | user_id: { 49 | type: String 50 | }, 51 | digital_currency: { 52 | type: String, 53 | required: true, 54 | enum: _config.simplex.validDigital, 55 | message: 'digital_currency required' 56 | }, 57 | fiat_currency: { 58 | type: String, 59 | required: true, 60 | enum: _config.simplex.validFiat, 61 | message: 'fiat_currency required' 62 | }, 63 | requested_currency: { 64 | type: String, 65 | required: true, 66 | enum: _config.simplex.validDigital.concat(_config.simplex.validFiat), 67 | message: 'requested_currency required' 68 | }, 69 | requested_amount: { 70 | type: Number, 71 | required: true, 72 | message: 'requested_amount required and must be a number' 73 | } 74 | }; 75 | 76 | logger.info('TESTTTTTT'); 77 | 78 | var validator = (0, _validator2.default)(schema); 79 | 80 | exports.default = function (app) { 81 | app.post('/quote', (0, _sourceValidate2.default)(), function (req, res) { 82 | logger.info('1'); 83 | var errors = validator.validate(req.body); 84 | validationErrors(errors); 85 | logger.info('2'); 86 | if (errors.length) { 87 | validationLogger.error(errors); 88 | _response2.default.error(res, errors.map(function (_err) { 89 | return _err.message; 90 | })); 91 | } else { 92 | var userId = req.body.user_id ? req.body.user_id : (0, _v2.default)(); 93 | var reqObj = Object.assign(req.body, { 94 | 'end_user_id': userId, 95 | 'wallet_id': _config.simplex.walletID, 96 | 'client_ip': _config.env.mode === 'development' ? _config.env.dev.ip : (0, _common.getIP)(req) 97 | }); 98 | debugRequest(reqObj); 99 | logger.info('3'); 100 | (0, _simplex.getQuote)(reqObj).then(function (result) { 101 | logger.info('result', result); 102 | debugResponse(result); 103 | (0, _mangodb.Order)({ 104 | user_id: userId, 105 | quote_id: result.quote_id, 106 | fiat_total_amount: { 107 | currency: result.fiat_money.currency, 108 | amount: result.fiat_money.total_amount 109 | }, 110 | requested_digital_amount: { 111 | currency: result.digital_money.currency, 112 | amount: result.digital_money.amount 113 | }, 114 | status: _config.simplex.status.initiated, 115 | source: req.mewSourceApplication || 'web' 116 | }).save().catch(function (error) { 117 | logger.error(error); 118 | _response2.default.error(res, error); 119 | }); 120 | _response2.default.success(res, result); 121 | }).catch(function (error) { 122 | logger.error(error); 123 | try { 124 | if (/[C|c]ountry/.test(error.message) && /supported/.test(error.message)) { 125 | _response2.default.error(res, 'Error_1'); 126 | } else { 127 | _response2.default.error(res, error); 128 | } 129 | } catch (e) { 130 | logger.error(e); 131 | } 132 | }); 133 | } 134 | }); 135 | }; -------------------------------------------------------------------------------- /api/dist/routes/status.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _response = require('../response'); 8 | 9 | var _response2 = _interopRequireDefault(_response); 10 | 11 | var _logging = require('logging'); 12 | 13 | var _logging2 = _interopRequireDefault(_logging); 14 | 15 | var _mangodb = require('../mangodb'); 16 | 17 | var _validator = require('../validator'); 18 | 19 | var _validator2 = _interopRequireDefault(_validator); 20 | 21 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 22 | 23 | var logger = (0, _logging2.default)('routes/status.js'); 24 | var loggerInvalidId = (0, _logging2.default)('routes/status.js - invalidId'); 25 | 26 | var schema = { 27 | user_id: { 28 | type: String, 29 | required: true, 30 | match: /^[a-zA-Z0-9-_]+$/, 31 | length: { 32 | min: 12, 33 | max: 64 34 | }, 35 | message: 'user_id required min:12 max:64' 36 | } 37 | }; 38 | 39 | var validator = (0, _validator2.default)(schema); 40 | 41 | exports.default = function (app) { 42 | app.get('/status/:userId', function (req, res) { 43 | var errors = validator.validate({ user_id: req.params.userId }); 44 | if (errors.length) { 45 | _response2.default.error(res, 'invalid user id'); 46 | } else { 47 | (0, _mangodb.getOrderById)(req.params.userId).then(function (result) { 48 | if (result.length === 0) { 49 | loggerInvalidId.error('UserId requested: ' + req.params.userId); 50 | _response2.default.error(res, 'user id does not exist'); 51 | } else { 52 | _response2.default.success(res, { 53 | user_id: result[0].user_id, 54 | status: result[0].status, 55 | fiat_total_amount: { 56 | currency: result[0].fiat_total_amount.currency, 57 | amount: result[0].fiat_total_amount.amount 58 | }, 59 | requested_digital_amount: { 60 | currency: result[0].requested_digital_amount.currency, 61 | amount: result[0].requested_digital_amount.amount 62 | } 63 | }); 64 | } 65 | }).catch(function (err) { 66 | logger.error(err); 67 | }); 68 | } 69 | }); 70 | }; -------------------------------------------------------------------------------- /api/dist/routes/statusByQuote.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _response = require('../response'); 8 | 9 | var _response2 = _interopRequireDefault(_response); 10 | 11 | var _logging = require('logging'); 12 | 13 | var _logging2 = _interopRequireDefault(_logging); 14 | 15 | var _mangodb = require('../mangodb'); 16 | 17 | var _validator = require('../validator'); 18 | 19 | var _validator2 = _interopRequireDefault(_validator); 20 | 21 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 22 | 23 | var logger = (0, _logging2.default)('routes/status.js'); 24 | var loggerInvalidId = (0, _logging2.default)('routes/status.js - invalidId'); 25 | 26 | var schema = { 27 | user_id: { 28 | type: String, 29 | required: true, 30 | match: /^[a-zA-Z0-9-_]+$/, 31 | length: { 32 | min: 12, 33 | max: 64 34 | }, 35 | message: 'user_id required min:12 max:64' 36 | }, 37 | quote_id: { 38 | type: String, 39 | required: true, 40 | match: /^[a-zA-Z0-9-_]+$/, 41 | length: { 42 | min: 12, 43 | max: 64 44 | }, 45 | message: 'quote_id required min:12 max:64' 46 | } 47 | }; 48 | 49 | var validator = (0, _validator2.default)(schema); 50 | 51 | exports.default = function (app) { 52 | app.get('/status/:userId/:quoteId', function (req, res) { 53 | var errors = validator.validate({ user_id: req.params.userId, quote_id: req.params.quoteId }); 54 | if (errors.length) { 55 | _response2.default.error(res, 'invalid user or quote id'); 56 | } else { 57 | (0, _mangodb.getOrderById)(req.params.userId, req.params.quoteId).then(function (result) { 58 | if (result.length === 0) { 59 | loggerInvalidId.error('UserId requested: ' + req.params.userId); 60 | _response2.default.error(res, 'user id does not exist'); 61 | } else { 62 | _response2.default.success(res, { 63 | user_id: result[0].user_id, 64 | quote_id: result[0].quote_id, 65 | status: result[0].status, 66 | fiat_total_amount: { 67 | currency: result[0].fiat_total_amount.currency, 68 | amount: result[0].fiat_total_amount.amount 69 | }, 70 | requested_digital_amount: { 71 | currency: result[0].requested_digital_amount.currency, 72 | amount: result[0].requested_digital_amount.amount 73 | } 74 | }); 75 | } 76 | }).catch(function (err) { 77 | logger.error(err); 78 | }); 79 | } 80 | }); 81 | }; -------------------------------------------------------------------------------- /api/dist/simplex/call.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _logging = require('logging'); 8 | 9 | var _logging2 = _interopRequireDefault(_logging); 10 | 11 | var _config = require('../config'); 12 | 13 | var _request = require('request'); 14 | 15 | var _request2 = _interopRequireDefault(_request); 16 | 17 | var _debug = require('debug'); 18 | 19 | var _debug2 = _interopRequireDefault(_debug); 20 | 21 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 22 | 23 | var logger = (0, _logging2.default)('simplex/call.js'); 24 | var catchLogger = (0, _logging2.default)('simplex/call.js - catch'); 25 | var debugRequest = (0, _debug2.default)('calls:request'); 26 | 27 | exports.default = function (body, path) { 28 | return new Promise(function (resolve, reject) { 29 | var options = { 30 | url: path, 31 | headers: { 32 | 'Authorization': 'ApiKey ' + _config.simplex.apiKey 33 | }, 34 | body: body, 35 | method: 'post', 36 | json: true 37 | }; 38 | var callback = function callback(error, response, body) { 39 | try { 40 | debugRequest(body); 41 | if (!error && response.statusCode === 200) { 42 | resolve(body); 43 | } else if (response.statusCode === 400) { 44 | reject(body); 45 | logger.error(body); 46 | } else { 47 | reject(error); 48 | logger.error(error); 49 | } 50 | } catch (e) { 51 | catchLogger.error(e); 52 | } 53 | }; 54 | debugRequest(options); 55 | (0, _request2.default)(options, callback); 56 | }); 57 | }; -------------------------------------------------------------------------------- /api/dist/simplex/getOrder.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _debug = require('debug'); 8 | 9 | var _debug2 = _interopRequireDefault(_debug); 10 | 11 | var _call = require('./call'); 12 | 13 | var _call2 = _interopRequireDefault(_call); 14 | 15 | var _config = require('../config'); 16 | 17 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 18 | 19 | var debug = (0, _debug2.default)('calls:getOrder'); 20 | 21 | exports.default = function (reqObject) { 22 | debug(reqObject); // todo remove dev item 23 | return (0, _call2.default)(reqObject, _config.simplex.orderEP); 24 | }; -------------------------------------------------------------------------------- /api/dist/simplex/getQuote.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _debug = require('debug'); 8 | 9 | var _debug2 = _interopRequireDefault(_debug); 10 | 11 | var _call = require('./call'); 12 | 13 | var _call2 = _interopRequireDefault(_call); 14 | 15 | var _config = require('../config'); 16 | 17 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 18 | 19 | var debug = (0, _debug2.default)('calls:getQuote'); 20 | 21 | exports.default = function (reqObject) { 22 | debug(reqObject); // todo remove dev item 23 | return (0, _call2.default)(reqObject, _config.simplex.quoteEP); 24 | }; -------------------------------------------------------------------------------- /api/dist/simplex/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.getOrder = exports.getQuote = undefined; 7 | 8 | var _getQuote = require('./getQuote'); 9 | 10 | var _getQuote2 = _interopRequireDefault(_getQuote); 11 | 12 | var _getOrder = require('./getOrder'); 13 | 14 | var _getOrder2 = _interopRequireDefault(_getOrder); 15 | 16 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 17 | 18 | exports.getQuote = _getQuote2.default; 19 | exports.getOrder = _getOrder2.default; -------------------------------------------------------------------------------- /api/dist/simplex_events/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _retrieveEvents = require('./retrieveEvents'); 8 | 9 | var _retrieveEvents2 = _interopRequireDefault(_retrieveEvents); 10 | 11 | var _nodeCron = require('node-cron'); 12 | 13 | var _nodeCron2 = _interopRequireDefault(_nodeCron); 14 | 15 | var _logging = require('logging'); 16 | 17 | var _logging2 = _interopRequireDefault(_logging); 18 | 19 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 20 | 21 | var logger = (0, _logging2.default)('simplex_events/index.js'); 22 | 23 | var runCron = function runCron() { 24 | console.log('cron setup for simplex events'); 25 | var cronTime = '* * * * *'; 26 | return _nodeCron2.default.schedule(cronTime, function () { 27 | try { 28 | (0, _retrieveEvents2.default)().then(function () { 29 | logger.info('Simplex Events Retrieved'); 30 | }).catch(function (_error) { 31 | logger.error(_error); 32 | }); 33 | } catch (e) { 34 | logger.error(e); 35 | } 36 | }); 37 | }; 38 | 39 | exports.default = runCron; -------------------------------------------------------------------------------- /api/dist/simplex_events/retrieveEvents.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _mangodb = require('../mangodb'); 8 | 9 | var _eachOfSeries = require('async/eachOfSeries'); 10 | 11 | var _eachOfSeries2 = _interopRequireDefault(_eachOfSeries); 12 | 13 | var _logging = require('logging'); 14 | 15 | var _logging2 = _interopRequireDefault(_logging); 16 | 17 | var _config = require('../config'); 18 | 19 | var _request = require('request'); 20 | 21 | var _request2 = _interopRequireDefault(_request); 22 | 23 | var _debug = require('debug'); 24 | 25 | var _debug2 = _interopRequireDefault(_debug); 26 | 27 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 28 | 29 | var recordLogger = (0, _logging2.default)('simplex_events/retrieveEvents.js : record-event'); 30 | var logger = (0, _logging2.default)('simplex_events/retrieveEvents.js'); 31 | var debugRequest = (0, _debug2.default)('calls:Events'); 32 | 33 | (0, _mangodb.connect)().then(function () { 34 | logger.info('mangodb running on port: ' + _config.mangodb.host + ':' + _config.mangodb.port); 35 | }).catch(function (err) { 36 | logger.error('mangodb error: ' + err); 37 | }); 38 | 39 | var getEvents = function getEvents() { 40 | return new Promise(function (resolve, reject) { 41 | var options = { 42 | url: _config.simplex.eventEP, 43 | headers: { 44 | 'Authorization': 'ApiKey ' + _config.simplex.apiKey 45 | }, 46 | method: 'get', 47 | json: true 48 | }; 49 | var retrieveCallback = function retrieveCallback(error, response, body) { 50 | try { 51 | if (!error && response.statusCode === 200) { 52 | (0, _eachOfSeries2.default)(body.events, processEvent, function (error) { 53 | if (error) { 54 | logger.error(response); 55 | reject(error); 56 | } else { 57 | resolve(); 58 | } 59 | }); 60 | } else if (response.statusCode === 400) { 61 | logger.error(response); 62 | reject(body); 63 | } else { 64 | logger.error(error); 65 | reject(error); 66 | } 67 | } catch (e) { 68 | logger.error(error); 69 | reject(error); 70 | } 71 | }; 72 | debugRequest(options); 73 | (0, _request2.default)(options, retrieveCallback); 74 | }); 75 | }; 76 | 77 | function updateItem(recordItem, deleteCallback) { 78 | (0, _mangodb.findAndUpdateStatus)(recordItem.payment.partner_end_user_id, recordItem.payment.id, { 79 | status: recordItem.payment.status 80 | }).then(function (resp) { 81 | if (resp) { 82 | var options = { 83 | url: _config.simplex.eventEP + '/' + recordItem.event_id, 84 | headers: { 85 | 'Authorization': 'ApiKey ' + _config.simplex.apiKey 86 | }, 87 | method: 'DELETE', 88 | json: true 89 | }; 90 | (0, _request2.default)(options, deleteCallback); 91 | } else { 92 | (0, _mangodb.findAndUpdate)(recordItem.payment.partner_end_user_id, { 93 | status: recordItem.payment.status 94 | }).then(function (resp) { 95 | if (resp) { 96 | var _options = { 97 | url: _config.simplex.eventEP + '/' + recordItem.event_id, 98 | headers: { 99 | 'Authorization': 'ApiKey ' + _config.simplex.apiKey 100 | }, 101 | method: 'DELETE', 102 | json: true 103 | }; 104 | (0, _request2.default)(_options, deleteCallback); 105 | } else { 106 | console.log('Unknown IDs: ', recordItem.payment.partner_end_user_id, recordItem.payment.id); // todo remove dev item 107 | } 108 | }).catch(function (err) { 109 | logger.error(err); 110 | }); 111 | } 112 | }).catch(function (err) { 113 | logger.error(err); 114 | }); 115 | } 116 | 117 | function processEvent(item, key, callback) { 118 | (0, _mangodb.EventSchema)(item).save().then(function () { 119 | updateItem(item, callback); 120 | }).catch(function (error) { 121 | recordLogger.error(error); 122 | updateItem(item, callback); 123 | }); 124 | } 125 | 126 | exports.default = getEvents; -------------------------------------------------------------------------------- /api/dist/sourceValidate.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | exports.default = sourceyValidate; 7 | 8 | var _logging = require('logging'); 9 | 10 | var _logging2 = _interopRequireDefault(_logging); 11 | 12 | var _debug = require('debug'); 13 | 14 | var _debug2 = _interopRequireDefault(_debug); 15 | 16 | var _response = require('./response'); 17 | 18 | var _response2 = _interopRequireDefault(_response); 19 | 20 | var _config = require('./config'); 21 | 22 | var _expressRecaptcha = require('express-recaptcha'); 23 | 24 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 25 | 26 | var logger = (0, _logging2.default)('sourceValidate.js'); 27 | var debug = (0, _debug2.default)('validation:bypass'); 28 | 29 | var recaptcha = new _expressRecaptcha.Recaptcha(_config.recaptcha.siteKey, _config.recaptcha.secretKey); 30 | 31 | function sourceyValidate() { 32 | var validationOptions = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : _config.productValidation; 33 | 34 | return function (req, res, next) { 35 | if (req.headers['referer'] === validationOptions.referrerAppleiOS) { 36 | if (validationOptions.apiKeys.includes(req.headers[validationOptions.apiKeyHeaderName])) { 37 | req.recaptcha = {}; 38 | req.mewSourceApplication = 'ios'; 39 | debug('Mobile Bypass Success IOS'); 40 | next(); 41 | } else { 42 | logger.error('Invalid API key: IOS'); 43 | _response2.default.error(res, 'Invalid API key'); 44 | } 45 | } else if (req.headers['referer'] === validationOptions.referrerAndroid) { 46 | if (validationOptions.apiKeys.includes(req.headers[validationOptions.apiKeyHeaderName])) { 47 | req.recaptcha = {}; 48 | req.mewSourceApplication = 'android'; 49 | debug('Mobile Bypass Success Android'); 50 | next(); 51 | } else { 52 | logger.error('Invalid API key: Android'); 53 | _response2.default.error(res, 'Invalid API key'); 54 | } 55 | } else if (validationOptions.specialWebOrigins.includes(req.headers['origin'])) { 56 | req.recaptcha = {}; 57 | req.mewSourceApplication = 'mew'; 58 | debug('Web Bypass Success'); 59 | next(); 60 | } else if (/quote/.test(req.route.path)) { 61 | next(); 62 | } else { 63 | return recaptcha.middleware.verify(req, res, next); 64 | } 65 | }; 66 | } -------------------------------------------------------------------------------- /api/dist/validator.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | Object.defineProperty(exports, "__esModule", { 4 | value: true 5 | }); 6 | 7 | var _validate = require('validate'); 8 | 9 | var _validate2 = _interopRequireDefault(_validate); 10 | 11 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 12 | 13 | exports.default = function (_schema) { 14 | var validator = new _validate2.default(_schema); 15 | return validator; 16 | }; -------------------------------------------------------------------------------- /api/ecosystem.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | apps : [{ 3 | name: "simplex-api", 4 | script: "./dist/index.js", 5 | env: { 6 | NODE_ENV: "development", 7 | }, 8 | env_production: { 9 | NODE_ENV: "production", 10 | } 11 | }] 12 | } -------------------------------------------------------------------------------- /api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simplex-api", 3 | "version": "0.1.0", 4 | "description": "API to handle simplex integration", 5 | "main": "index.js", 6 | "scripts": { 7 | "build:other": "npm run lint", 8 | "build": "rimraf dist/ && babel ./src --out-dir dist/", 9 | "start:docker": "npm run build && pm2-runtime dist/index.js", 10 | "start": "npm run build && node dist/index.js", 11 | "prod": "node dist/index.js", 12 | "events": "npm run build && node dist/simplex_events/index.js", 13 | "eventsProd": "node dist/simplex_events/", 14 | "getExchangeRatesProd": "node dist/currency_rates/", 15 | "dev": "env NODE_ENV=development nodemon --exec npm start", 16 | "test": "npm run lint && env NODE_ENV=development mocha --exit --require babel-core/register test/*", 17 | "lint": "standard --fix 'src/**/*.js'" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git+https://github.com/MyEtherWallet/simplex-api.git" 22 | }, 23 | "keywords": [ 24 | "simplex", 25 | "api", 26 | "nanobox" 27 | ], 28 | "author": "kvhnuke", 29 | "license": "MIT", 30 | "bugs": { 31 | "url": "https://github.com/MyEtherWallet/simplex-api/issues" 32 | }, 33 | "homepage": "https://github.com/MyEtherWallet/simplex-api#readme", 34 | "dependencies": { 35 | "async": "^2.6.1", 36 | "body-parser": "^1.18.3", 37 | "cors": "^2.8.4", 38 | "debug": "^4.1.1", 39 | "dotenv": "^6.0.0", 40 | "express": "^4.16.3", 41 | "express-recaptcha": "^4.0.2", 42 | "logging": "^3.2.0", 43 | "mongoose": "^5.5.10", 44 | "mongoose-timestamp": "^0.6.0", 45 | "node-cron": "^2.0.3", 46 | "request": "^2.87.0", 47 | "uuid": "^3.2.1", 48 | "validate": "^4.4.1", 49 | "wallet-address-validator": "^0.1.7" 50 | }, 51 | "devDependencies": { 52 | "babel-cli": "^6.26.0", 53 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 54 | "babel-preset-es2015": "^6.24.1", 55 | "bignumber.js": "^9.0.0", 56 | "chai": "^4.1.2", 57 | "mocha": "^5.2.0", 58 | "nodemon": "^1.17.5", 59 | "rimraf": "^2.6.2", 60 | "standard": "^11.0.1", 61 | "supertest": "^3.1.0" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /api/src/common/index.js: -------------------------------------------------------------------------------- 1 | let getIP = (req) => { 2 | return (req.headers['x-forwarded-for'] || 3 | req.connection.remoteAddress || 4 | req.socket.remoteAddress || 5 | req.connection.socket.remoteAddress).split(',')[0] 6 | } 7 | 8 | export { 9 | getIP 10 | } 11 | -------------------------------------------------------------------------------- /api/src/config.js: -------------------------------------------------------------------------------- 1 | import { fiat, crypto } from "../currencyConfig"; 2 | 3 | require("dotenv").config({ 4 | path: "../../.env" 5 | }); 6 | 7 | let network = { 8 | port: process.env.PORT || 8080 9 | }; 10 | let simplex = { 11 | walletID: process.env.WALLET_ID || "", 12 | quoteEP: process.env.QUOTE_EP || "", 13 | orderEP: process.env.ORDER_EP || "", 14 | paymentEP: process.env.PAYMENT_EP || "", 15 | eventEP: process.env.EVENT_EP || "", 16 | apiKey: process.env.SIMPLEX_APIKEY || "", 17 | apiVersion: "1", 18 | validFiat: process.env.FIAT_CURRENCIES 19 | ? process.env.FIAT_CURRENCIES.split(",") 20 | : fiat, // ['USD','EUR'], 21 | validDigital: process.env.DIGITAL_CURRENCIES 22 | ? process.env.DIGITAL_CURRENCIES.split(",") 23 | : crypto, // ['BTC', 'BSC', 'ETH'], 24 | currencyApiKey: process.env.FIXER_APIKEY || "", 25 | baseCurrency: process.env.BASE_CURRENCY || "USD", 26 | minBaseCurrency: process.env.FIAT_MIN_USD || 50, // USD 27 | maxBaseCurrency: process.env.FIAT_MAX_USD || 20000, // USD 28 | status: { 29 | initiated: "INITIATED", 30 | sentToSimplex: "SENT_TO_SIMPLEX", 31 | deniedSimplex: "DENIED_SIMPLEX", 32 | processingSimplex: "PROCESSING_SIMPPLEX", 33 | successSimplex: "SUCCESS_SIMPLEX" 34 | } 35 | }; 36 | let mangodb = { 37 | host: process.env.DATA_MONGODB_HOST || "localhost", 38 | port: 27017, 39 | name: "gonano" 40 | }; 41 | let recaptcha = { 42 | siteKey: process.env.RECAPTCHA_SITE_KEY || "", 43 | secretKey: process.env.RECAPTCHA_SECRET_KEY || "" 44 | }; 45 | let env = { 46 | mode: process.env.NODE_ENV || "production", 47 | dev: { 48 | ip: "141.145.165.137", 49 | accept_language: "en-US,en;q=0.9", 50 | user_agent: 51 | "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36" 52 | } 53 | }; 54 | 55 | let productValidation = { 56 | apiKeyHeaderName: process.env.API_KEY_HEADER || "apikey", 57 | apiKeys: process.env.API_KEY 58 | ? [process.env.API_KEY] 59 | : ["321654987", "abcdefg"], 60 | referrerAppleiOS: process.env.IOS_REFERER || "iOS", 61 | referrerAndroid: process.env.ANDROID_REFERER || "Android", 62 | specialWebOrigins: process.env.SPECIAL_WEB_ORIGINS 63 | ? process.env.SPECIAL_WEB_ORIGINS.split(",") 64 | : [] 65 | }; 66 | 67 | export { network, simplex, mangodb, recaptcha, env, productValidation }; 68 | -------------------------------------------------------------------------------- /api/src/currency_rates/index.js: -------------------------------------------------------------------------------- 1 | import getRates from './retrieveCurrencyRates' 2 | import cron from 'node-cron' 3 | 4 | import createLogger from 'logging' 5 | const logger = createLogger('currency_rates/index.js') 6 | 7 | const runCron = () => { 8 | console.log('cron setup for exchange rates') 9 | const cronTime = '0 * * * *' 10 | return cron.schedule(cronTime, () => { 11 | try { 12 | getRates() 13 | .then(() => { 14 | logger.info('FixerIO Rates Retrieved') 15 | }) 16 | .catch(_error => { 17 | logger.error(_error) 18 | }) 19 | } catch (e) { 20 | logger.error(e) 21 | } 22 | }) 23 | } 24 | 25 | export default runCron 26 | -------------------------------------------------------------------------------- /api/src/currency_rates/retrieveCurrencyRates.js: -------------------------------------------------------------------------------- 1 | import { 2 | connect, 3 | findAndUpdateExchangeRates 4 | } from '../mangodb' 5 | import createLogger from 'logging' 6 | import { 7 | mangodb, 8 | simplex 9 | } from '../config' 10 | import request from 'request' 11 | import debugLogger from 'debug' 12 | import BigNumber from 'bignumber.js' 13 | 14 | const logger = createLogger('currency_rates/retrieveCurrencyRates.js') 15 | const debugRequest = debugLogger('calls:Events') 16 | 17 | connect().then(() => { 18 | logger.info(`mangodb running on port: ${mangodb.host}:${mangodb.port}`) 19 | }).catch((err) => { 20 | logger.error(`mangodb error: ${err}`) 21 | }) 22 | 23 | const multiply = (val1, val2) => { 24 | return new BigNumber(val1).times(new BigNumber(val2)).toNumber() 25 | } 26 | 27 | let getExchangeRates = () => { 28 | return new Promise((resolve, reject) => { 29 | const currencies = simplex.validFiat.join(',') 30 | const url = `http://data.fixer.io/api/latest?access_key=${simplex.currencyApiKey}&base=${simplex.baseCurrency}&symbols=${currencies}&format=1` 31 | let options = { 32 | url: url, 33 | method: 'get', 34 | json: true 35 | } 36 | let retrieveCallback = (error, response, body) => { 37 | try { 38 | if (!error && response.statusCode === 200) { 39 | logger.error(body) 40 | const rates = Object.keys(body.rates).reduce((prior, current) => { 41 | prior.push({ 42 | pair_key: body.base + current, 43 | base_currency: body.base, 44 | rate_currency: current, 45 | min: multiply(simplex.minBaseCurrency, body.rates[current]), 46 | max: multiply(simplex.maxBaseCurrency, body.rates[current]), 47 | rate: body.rates[current] 48 | }) 49 | return prior 50 | }, []) 51 | rates.forEach(updateItem) 52 | // processEvent 53 | } else if (response.statusCode === 400) { 54 | logger.error(response) 55 | reject(body) 56 | } else { 57 | logger.error(error) 58 | reject(error) 59 | } 60 | } catch (e) { 61 | logger.error(error) 62 | reject(error) 63 | } 64 | } 65 | debugRequest(options) 66 | request(options, retrieveCallback) 67 | }) 68 | } 69 | 70 | function updateItem (recordItem) { 71 | findAndUpdateExchangeRates({ 72 | pair_key: recordItem.pair_key 73 | }, recordItem).catch((err) => { 74 | logger.error(err) 75 | }) 76 | } 77 | 78 | export default getExchangeRates 79 | -------------------------------------------------------------------------------- /api/src/index.js: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | import bodyParser from 'body-parser' 3 | import createLogger from 'logging' 4 | import debugLogger from 'debug' 5 | import retrieveEvents from './simplex_events' 6 | import retrieveRates from './currency_rates' 7 | 8 | import cors from 'cors' 9 | import routes from './routes' 10 | import { 11 | connect as dbConnect 12 | } from './mangodb' 13 | import { 14 | network, 15 | mangodb 16 | } from './config' 17 | 18 | const debugRequest = debugLogger('index.js') 19 | const logger = createLogger('index.js') 20 | let app = express() 21 | 22 | app.use(bodyParser.json()) 23 | app.use(cors()) 24 | routes(app) 25 | dbConnect().then(() => { 26 | logger.info(`mangodb running on port: ${mangodb.host}:${mangodb.port}`) 27 | }).catch((err) => { 28 | logger.error(`mangodb error: ${err}`) 29 | }) 30 | retrieveEvents() 31 | retrieveRates() 32 | let server = app.listen(network.port, () => { 33 | debugRequest(`DEBUG ACTIVE ${process.env.DEBUG}`) 34 | logger.info(`app running on port: ${server.address().port}`) 35 | }) 36 | export default server 37 | -------------------------------------------------------------------------------- /api/src/mangodb/event_schema.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose' 2 | import timestamp from 'mongoose-timestamp' 3 | let Schema = mongoose.Schema 4 | 5 | var eventSchema = new Schema({ 6 | event_id: { 7 | type: String 8 | }, 9 | name: { 10 | type: String 11 | }, 12 | payment: { 13 | id: { 14 | type: String 15 | }, 16 | status: { 17 | type: String 18 | }, 19 | created_at: { 20 | type: String 21 | }, 22 | updated_at: { 23 | type: String 24 | }, 25 | partner_end_user_id: { 26 | type: String 27 | }, 28 | fiat_total_amount: { 29 | currency: { 30 | type: String 31 | }, 32 | amount: { 33 | type: Number 34 | } 35 | } 36 | }, 37 | timestamp: { 38 | type: String 39 | } 40 | }) 41 | eventSchema.plugin(timestamp) 42 | export default mongoose.model('EventRecord', eventSchema) 43 | -------------------------------------------------------------------------------- /api/src/mangodb/exchange_rate_schema.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose' 2 | import timestamp from 'mongoose-timestamp' 3 | let Schema = mongoose.Schema 4 | 5 | var ExchangeRateSchema = new Schema({ 6 | pair_key: { 7 | type: String, 8 | required: true, 9 | unique: true 10 | }, 11 | base_currency: { 12 | type: String, 13 | required: true 14 | }, 15 | rate_currency: { 16 | type: String, 17 | sparse: true 18 | }, 19 | min: { 20 | type: Number, 21 | required: true 22 | }, 23 | max: { 24 | type: Number, 25 | required: true 26 | }, 27 | rate: { 28 | type: Number, 29 | required: true 30 | } 31 | }) 32 | ExchangeRateSchema.plugin(timestamp) 33 | export default mongoose.model('ExchangeRate', ExchangeRateSchema) 34 | -------------------------------------------------------------------------------- /api/src/mangodb/index.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose' 2 | import { 3 | mangodb 4 | } from '../config' 5 | import Order from './schema' 6 | import EventSchema from './event_schema' 7 | import ExchangeRateSchema from './exchange_rate_schema' 8 | 9 | let connect = () => { 10 | return new Promise((resolve, reject) => { 11 | mongoose.connect('mongodb://' + mangodb.host + ':' + mangodb.port + '/' + mangodb.name) 12 | var db = mongoose.connection 13 | db.once('error', (error) => { 14 | reject(error) 15 | }) 16 | db.once('open', () => { 17 | resolve() 18 | }) 19 | }) 20 | } 21 | 22 | let getOrderById = (_userId, _quoteId) => { 23 | return new Promise((resolve, reject) => { 24 | if (_userId && _quoteId) { 25 | return Order.find({ 26 | user_id: _userId, 27 | quote_id: _quoteId 28 | }).sort({'created_at': -1}).exec((err, res) => { 29 | if (err) reject(err) 30 | else resolve(res) 31 | }) 32 | } else { 33 | return Order.find({ 34 | user_id: _userId 35 | }).sort({'created_at': -1}).exec((err, res) => { 36 | if (err) reject(err) 37 | else resolve(res) 38 | }) 39 | } 40 | }) 41 | } 42 | 43 | let findAndUpdate = (_userId, _quoteId, _newVals) => { 44 | if (_quoteId && _newVals) { 45 | return Order.findOneAndUpdate({ 46 | user_id: _userId, 47 | quote_id: _quoteId 48 | }, _newVals) 49 | } else if (!_quoteId && _newVals) { 50 | return Order.findOneAndUpdate({ 51 | user_id: _userId 52 | }, _newVals) 53 | } else { 54 | return Order.findOneAndUpdate({ 55 | user_id: _userId 56 | }, _quoteId) // in this case _paymentId contains the content of _newVals 57 | } 58 | } 59 | 60 | let findAndUpdateStatus = (_userId, _paymentId, _newVals) => { 61 | return Order.findOneAndUpdate({ 62 | user_id: _userId, 63 | payment_id: _paymentId 64 | }, _newVals) 65 | } 66 | 67 | let getExchangeRates = (base = 'USD') => { 68 | return ExchangeRateSchema.find({ 69 | base_currency: base 70 | }) 71 | } 72 | 73 | let findAndUpdateExchangeRates = (queryItem, rateItem) => { 74 | return ExchangeRateSchema.findOneAndUpdate(queryItem, rateItem, {upsert: true}) 75 | } 76 | 77 | export { 78 | connect, 79 | Order, 80 | EventSchema, 81 | ExchangeRateSchema, 82 | getExchangeRates, 83 | findAndUpdateExchangeRates, 84 | getOrderById, 85 | findAndUpdate, 86 | findAndUpdateStatus 87 | } 88 | -------------------------------------------------------------------------------- /api/src/mangodb/schema.js: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose' 2 | import timestamp from 'mongoose-timestamp' 3 | import { 4 | simplex 5 | } from '../config' 6 | let Schema = mongoose.Schema 7 | 8 | var orderSchema = new Schema({ 9 | user_id: { 10 | type: String, 11 | required: true 12 | // unique: true 13 | }, 14 | quote_id: { 15 | type: String, 16 | unique: true, 17 | sparse: true 18 | }, 19 | payment_id: { 20 | type: String, 21 | unique: true, 22 | sparse: true 23 | }, 24 | order_id: { 25 | type: String, 26 | unique: true, 27 | sparse: true 28 | }, 29 | fiat_total_amount: { 30 | currency: { 31 | type: String, 32 | required: true 33 | }, 34 | amount: { 35 | type: Number, 36 | required: true 37 | } 38 | }, 39 | requested_digital_amount: { 40 | currency: { 41 | type: String, 42 | required: true 43 | }, 44 | amount: { 45 | type: Number, 46 | required: true 47 | } 48 | }, 49 | status: { 50 | type: String, 51 | enum: Object.values(simplex.status), 52 | required: true 53 | }, 54 | source: { 55 | type: String, 56 | required: false 57 | } 58 | }) 59 | orderSchema.plugin(timestamp) 60 | export default mongoose.model('Order', orderSchema) 61 | -------------------------------------------------------------------------------- /api/src/response.js: -------------------------------------------------------------------------------- 1 | export default { 2 | error: (res, msg) => { 3 | res.setHeader('Content-Type', 'application/json') 4 | res.send(JSON.stringify({ 5 | error: true, 6 | result: msg 7 | }, null, 3)) 8 | }, 9 | success: (res, msg) => { 10 | res.setHeader('Content-Type', 'application/json') 11 | res.send(JSON.stringify({ 12 | error: false, 13 | result: msg 14 | }, null, 3)) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /api/src/routes/currentCurrencies.js: -------------------------------------------------------------------------------- 1 | import response from "../response"; 2 | import debugLogger from "debug"; 3 | import { simplex } from "../config"; 4 | 5 | const debugRequest = debugLogger("request:info"); 6 | 7 | export default app => { 8 | app.get("/current-currencies", (req, res) => { 9 | debugRequest("Current Currencies Request Received"); 10 | const baseFiat = { 11 | USD: { 12 | symbol: "USD", 13 | name: "US Dollar" 14 | }, 15 | EUR: { 16 | symbol: "EUR", 17 | name: "Euro" 18 | }, 19 | CAD: { 20 | symbol: "CAD", 21 | name: "Canadian Dollar" 22 | }, 23 | JPY: { 24 | symbol: "JPY", 25 | name: "Japanese Yen" 26 | } 27 | }; 28 | const baseDigital = { 29 | BSC: { 30 | symbol: "BSC", 31 | name: "Binance" 32 | }, 33 | BTC: { 34 | symbol: "BTC", 35 | name: "Bitcoin" 36 | }, 37 | ETH: { 38 | symbol: "ETH", 39 | name: "Ether" 40 | }, 41 | MATIC: { 42 | symbol: "MATIC", 43 | name: "Polygon" 44 | } 45 | }; 46 | 47 | const fiat = simplex.validFiat.reduce((acc, curr) => { 48 | if (baseFiat[curr]) { 49 | acc[curr] = baseFiat[curr]; 50 | } else { 51 | acc[curr] = { 52 | symbol: curr, 53 | name: curr 54 | }; 55 | } 56 | return acc; 57 | }, {}); 58 | 59 | const digital = simplex.validDigital.reduce((acc, curr) => { 60 | if (baseDigital[curr]) { 61 | acc[curr] = baseDigital[curr]; 62 | } else { 63 | acc[curr] = { 64 | symbol: curr, 65 | name: curr 66 | }; 67 | } 68 | return acc; 69 | }, {}); 70 | 71 | response.success(res, { 72 | fiat: fiat, 73 | digital: digital 74 | }); 75 | }); 76 | }; 77 | -------------------------------------------------------------------------------- /api/src/routes/exchangeRates.js: -------------------------------------------------------------------------------- 1 | import response from '../response' 2 | import debugLogger from 'debug' 3 | import {getExchangeRates} from '../mangodb' 4 | import { 5 | simplex 6 | } from '../config' 7 | 8 | const debugRequest = debugLogger('request:info') 9 | 10 | export default (app) => { 11 | app.get('/exchange-rates', (req, res) => { 12 | debugRequest('Exchange Rates Request Received') 13 | getExchangeRates(simplex.baseCurrency) 14 | .then(rates => { 15 | const cleanedRates = rates.map(item => { 16 | return { 17 | rate_currency: item.rate_currency, 18 | base_currency: item.base_currency, 19 | min: item.min, 20 | max: item.max, 21 | rate: item.rate, 22 | updatedAt: item.updatedAt 23 | } 24 | }) 25 | response.success(res, { 26 | rates: cleanedRates 27 | }) 28 | }) 29 | .catch((error) => { 30 | response.error(res, error) 31 | }) 32 | }) 33 | } 34 | -------------------------------------------------------------------------------- /api/src/routes/index.js: -------------------------------------------------------------------------------- 1 | import quoteRoute from './quote' 2 | import orderRoute from './order' 3 | import infoRoute from './info' 4 | import statusByQuoteRoute from './statusByQuote' 5 | import statusRoute from './status' 6 | import exchangeRates from './exchangeRates' 7 | import currentCurrencies from './currentCurrencies' 8 | export default (app) => { 9 | quoteRoute(app) 10 | orderRoute(app) 11 | infoRoute(app) 12 | statusByQuoteRoute(app) 13 | statusRoute(app) 14 | exchangeRates(app) 15 | currentCurrencies(app) 16 | } 17 | -------------------------------------------------------------------------------- /api/src/routes/info.js: -------------------------------------------------------------------------------- 1 | import response from '../response' 2 | import debugLogger from 'debug' 3 | const packageInfo = require('../../package.json') 4 | 5 | const debugRequest = debugLogger('request:info') 6 | 7 | export default (app) => { 8 | app.get('/info', (req, res) => { 9 | debugRequest('Info Request Received') 10 | response.success(res, { 11 | name: 'Simplex API', 12 | version: packageInfo.version 13 | }) 14 | }) 15 | } 16 | -------------------------------------------------------------------------------- /api/src/routes/order.js: -------------------------------------------------------------------------------- 1 | import createLogger from 'logging' 2 | import wav from 'wallet-address-validator' 3 | import uuidv4 from 'uuid/v4' 4 | import { 5 | getOrder 6 | } from '../simplex' 7 | import Validator from '../validator' 8 | import response from '../response' 9 | import { 10 | simplex, 11 | env 12 | } from '../config' 13 | import { 14 | getOrderById, 15 | findAndUpdate 16 | } from '../mangodb' 17 | 18 | import { 19 | getIP 20 | } from '../common' 21 | 22 | import sourceValidate from '../sourceValidate' 23 | import debugLogger from 'debug' 24 | 25 | const logger = createLogger('order.js') 26 | const catchLogger = createLogger('order.js - catch') 27 | const debugRequest = debugLogger('request:routes-order') 28 | const debugResponse = debugLogger('response:routes-order') 29 | const validationErrors = debugLogger('errors:validation') 30 | 31 | const validateMinMax = val => { 32 | return !(simplex.minFiat > +val || simplex.maxFiat < +val) 33 | } 34 | const validateAddress = val => { 35 | const maybeValid = simplex.validDigital.filter(cryptoSymbol => { 36 | cryptoSymbol = cryptoSymbol === "BNB" || cryptoSymbol === "MATIC" ? "ETH" : cryptoSymbol; 37 | return wav.validate(val, cryptoSymbol) 38 | }) 39 | return maybeValid.length > 0 40 | } 41 | 42 | let schema = { 43 | account_details: { 44 | app_end_user_id: { 45 | type: String, 46 | required: true, 47 | match: /^[a-zA-Z0-9-_]+$/, 48 | length: { 49 | min: 12, 50 | max: 64 51 | }, 52 | message: 'app_end_user_id required min:12 max:64' 53 | } 54 | }, 55 | transaction_details: { 56 | payment_details: { 57 | quote_id: { 58 | type: String, 59 | // required: true, 60 | match: /^[a-zA-Z0-9-_]+$/, 61 | length: { 62 | min: 12, 63 | max: 64 64 | }, 65 | message: 'app_end_user_id required min:12 max:64' 66 | }, 67 | fiat_total_amount: { 68 | currency: { 69 | type: String, 70 | required: true, 71 | enum: simplex.validFiat, 72 | message: 'fiat currency required' 73 | }, 74 | amount: { 75 | type: Number, 76 | required: true, 77 | use: { 78 | validateMinMax 79 | }, 80 | message: 'fiat amount is required, must be a number, and must be between 50 and 20,000' 81 | } 82 | }, 83 | requested_digital_amount: { 84 | currency: { 85 | type: String, 86 | required: true, 87 | enum: simplex.validDigital, 88 | message: 'requested currency required' 89 | }, 90 | amount: { 91 | type: Number, 92 | required: true, 93 | message: 'requested amount required and must be a number' 94 | } 95 | }, 96 | destination_wallet: { 97 | currency: { 98 | type: String, 99 | required: true, 100 | enum: simplex.validDigital, 101 | message: 'destination wallet currency required' 102 | }, 103 | address: { 104 | type: String, 105 | required: true, 106 | use: { 107 | validateAddress 108 | }, 109 | message: 'destination address is required and must be a valid BTC or ETH address respectively' 110 | } 111 | } 112 | } 113 | } 114 | } 115 | let validator = Validator(schema) 116 | 117 | const valueMatchCheck = (bodyVals, dbVals) => { 118 | const mustAllMatch = [] 119 | mustAllMatch.push(bodyVals.fiat_total_amount.amount === dbVals.fiat_total_amount.amount) 120 | mustAllMatch.push(bodyVals.fiat_total_amount.currency === dbVals.fiat_total_amount.currency) 121 | mustAllMatch.push(bodyVals.requested_digital_amount.amount === dbVals.requested_digital_amount.amount) 122 | mustAllMatch.push(bodyVals.requested_digital_amount.currency === dbVals.requested_digital_amount.currency) 123 | return mustAllMatch.every(value => value) 124 | } 125 | 126 | export default (app) => { 127 | app.post('/order', sourceValidate(), (req, res) => { 128 | try { 129 | let errors = validator.validate(req.body) 130 | validationErrors(errors) 131 | if (env.mode !== 'development' && req.recaptcha.error) { 132 | logger.error('ERROR: env.mode !== \'development\' && req.recaptcha.error') 133 | logger.error(errors) 134 | logger.error(req.recaptcha.error) 135 | response.error(res, req.recaptcha.error) 136 | } else if (errors.length) { 137 | logger.error('Validation Error') 138 | logger.error(errors) 139 | response.error(res, errors.map(_err => _err.message)) 140 | } else { 141 | let userId = req.body.account_details.app_end_user_id 142 | let quoteIdOrig = req.body.transaction_details.payment_details.quote_id 143 | getOrderById(userId, quoteIdOrig).then((savedOrder) => { 144 | let quoteId = quoteIdOrig || savedOrder[0].quote_id 145 | let paymentId = uuidv4() 146 | let orderId = uuidv4() 147 | let acceptLanguage = env.mode === 'development' ? env.dev.accept_language : req.headers['accept-language'] 148 | let ip = env.mode === 'development' ? env.dev.ip : getIP(req) 149 | let userAgent = env.mode === 'development' ? env.dev.user_agent : req.headers['user-agent'] 150 | if (!valueMatchCheck(req.body.transaction_details.payment_details, savedOrder[0])) { 151 | throw Error('Mismatch between quoted values and order submission values') 152 | } 153 | let reqObj = { 154 | account_details: { 155 | ...req.body.account_details, 156 | app_provider_id: simplex.walletID, 157 | app_version_id: simplex.apiVersion, 158 | signup_login: { 159 | ip: ip, 160 | uaid: userId, 161 | accept_language: acceptLanguage, 162 | http_accept_language: acceptLanguage, 163 | user_agent: userAgent, 164 | cookie_session_id: userId, 165 | timestamp: new Date().toISOString() 166 | } 167 | }, 168 | transaction_details: { 169 | payment_details: { 170 | ...req.body.transaction_details.payment_details, 171 | quote_id: quoteId, 172 | payment_id: paymentId, 173 | order_id: orderId, 174 | original_http_ref_url: req.header('Referer') 175 | } 176 | } 177 | } 178 | findAndUpdate(userId, quoteId, { 179 | payment_id: paymentId, 180 | order_id: orderId, 181 | status: simplex.status.sentToSimplex 182 | }).catch((err) => { 183 | logger.error('findAndUpdate catch error') 184 | logger.error(err) 185 | }) 186 | debugRequest(reqObj) 187 | getOrder(reqObj).then((result) => { 188 | debugResponse(result) 189 | if ('is_kyc_update_required' in result) { 190 | response.success(res, { 191 | payment_post_url: simplex.paymentEP.replace(/\u200B/g, ''), 192 | version: simplex.apiVersion, 193 | partner: simplex.walletID, 194 | return_url: 'https://www.myetherwallet.com', 195 | quote_id: quoteId, 196 | payment_id: paymentId, 197 | user_id: userId, 198 | destination_wallet_address: reqObj.transaction_details.payment_details.destination_wallet.address, 199 | destination_wallet_currency: reqObj.transaction_details.payment_details.destination_wallet.currency, 200 | fiat_total_amount_amount: reqObj.transaction_details.payment_details.fiat_total_amount.amount, 201 | fiat_total_amount_currency: reqObj.transaction_details.payment_details.fiat_total_amount.currency, 202 | digital_total_amount_amount: reqObj.transaction_details.payment_details.requested_digital_amount.amount, 203 | digital_total_amount_currency: reqObj.transaction_details.payment_details.requested_digital_amount.currency 204 | }) 205 | } else { 206 | logger.error('is_kyc_update_required error') 207 | logger.error(result) 208 | response.error(res, result) 209 | } 210 | }).catch((error) => { 211 | logger.error('getOrder catch error') 212 | logger.error(error) 213 | response.error(res, error) 214 | }) 215 | }).catch((err) => { 216 | logger.error('getOrderById catch error') 217 | logger.error(err) 218 | response.error(res, 'Invalid userId') 219 | }) 220 | } 221 | } catch (e) { 222 | catchLogger.error(e) 223 | } 224 | }) 225 | } 226 | -------------------------------------------------------------------------------- /api/src/routes/quote.js: -------------------------------------------------------------------------------- 1 | import createLogger from 'logging' 2 | import debugLogger from 'debug' 3 | import { 4 | getQuote 5 | } from '../simplex' 6 | import Validator from '../validator' 7 | import uuidv4 from 'uuid/v4' 8 | import response from '../response' 9 | import { 10 | simplex, 11 | env 12 | } from '../config' 13 | import { 14 | Order 15 | } from '../mangodb' 16 | import sourceValidate from '../sourceValidate' 17 | import { 18 | getIP 19 | } from '../common' 20 | 21 | const validationLogger = createLogger('quote.js - validation') 22 | const logger = createLogger('quote.js') 23 | const debugRequest = debugLogger('request:routes-quote') 24 | const debugResponse = debugLogger('response:routes-quote') 25 | const validationErrors = debugLogger('errors:validation') 26 | 27 | let schema = { 28 | user_id: { 29 | type: String 30 | }, 31 | digital_currency: { 32 | type: String, 33 | required: true, 34 | enum: simplex.validDigital, 35 | message: 'digital_currency required' 36 | }, 37 | fiat_currency: { 38 | type: String, 39 | required: true, 40 | enum: simplex.validFiat, 41 | message: 'fiat_currency required' 42 | }, 43 | requested_currency: { 44 | type: String, 45 | required: true, 46 | enum: simplex.validDigital.concat(simplex.validFiat), 47 | message: 'requested_currency required' 48 | }, 49 | requested_amount: { 50 | type: Number, 51 | required: true, 52 | message: 'requested_amount required and must be a number' 53 | } 54 | } 55 | 56 | logger.info('TESTTTTTT ') 57 | 58 | let validator = Validator(schema) 59 | export default (app) => { 60 | app.post('/quote', sourceValidate(), (req, res) => { 61 | logger.info('1') 62 | let errors = validator.validate(req.body) 63 | validationErrors(errors) 64 | logger.info('2') 65 | if (errors.length) { 66 | validationLogger.error(errors) 67 | response.error(res, errors.map(_err => _err.message)) 68 | } else { 69 | let userId = req.body.user_id ? req.body.user_id : uuidv4() 70 | let reqObj = Object.assign(req.body, { 71 | 'end_user_id': userId, 72 | 'wallet_id': simplex.walletID, 73 | 'client_ip': env.mode === 'development' ? env.dev.ip : getIP(req) 74 | }) 75 | debugRequest(reqObj) 76 | logger.info('3') 77 | getQuote(reqObj).then((result) => { 78 | logger.info('result', result) 79 | debugResponse(result) 80 | if (result.error) throw new Error(result.error); 81 | Order({ 82 | user_id: userId, 83 | quote_id: result.quote_id, 84 | fiat_total_amount: { 85 | currency: result.fiat_money.currency, 86 | amount: result.fiat_money.total_amount 87 | }, 88 | requested_digital_amount: { 89 | currency: result.digital_money.currency, 90 | amount: result.digital_money.amount 91 | }, 92 | status: simplex.status.initiated, 93 | source: req.mewSourceApplication || 'web' 94 | }).save().catch((error) => { 95 | logger.error(error) 96 | response.error(res, error) 97 | }) 98 | response.success(res, result) 99 | }).catch((error) => { 100 | logger.error(error) 101 | try { 102 | if (/[C|c]ountry/.test(error.message) && /supported/.test(error.message)) { 103 | response.error(res, 'Error_1') 104 | } else { 105 | response.error(res, error) 106 | } 107 | } catch (e) { 108 | logger.error(e) 109 | } 110 | }) 111 | } 112 | }) 113 | } 114 | -------------------------------------------------------------------------------- /api/src/routes/status.js: -------------------------------------------------------------------------------- 1 | import response from '../response' 2 | import createLogger from 'logging' 3 | import { 4 | getOrderById 5 | } from '../mangodb' 6 | import Validator from '../validator' 7 | 8 | const logger = createLogger('routes/status.js') 9 | const loggerInvalidId = createLogger('routes/status.js - invalidId') 10 | 11 | let schema = { 12 | user_id: { 13 | type: String, 14 | required: true, 15 | match: /^[a-zA-Z0-9-_]+$/, 16 | length: { 17 | min: 12, 18 | max: 64 19 | }, 20 | message: 'user_id required min:12 max:64' 21 | } 22 | } 23 | 24 | let validator = Validator(schema) 25 | export default (app) => { 26 | app.get('/status/:userId', (req, res) => { 27 | let errors = validator.validate({user_id: req.params.userId}) 28 | if (errors.length) { 29 | response.error(res, 'invalid user id') 30 | } else { 31 | getOrderById(req.params.userId) 32 | .then(result => { 33 | if (result.length === 0) { 34 | loggerInvalidId.error(`UserId requested: ${req.params.userId}`) 35 | response.error(res, 'user id does not exist') 36 | } else { 37 | response.success(res, { 38 | user_id: result[0].user_id, 39 | status: result[0].status, 40 | fiat_total_amount: { 41 | currency: result[0].fiat_total_amount.currency, 42 | amount: result[0].fiat_total_amount.amount 43 | }, 44 | requested_digital_amount: { 45 | currency: result[0].requested_digital_amount.currency, 46 | amount: result[0].requested_digital_amount.amount 47 | } 48 | }) 49 | } 50 | }) 51 | .catch((err) => { 52 | logger.error(err) 53 | }) 54 | } 55 | }) 56 | } 57 | -------------------------------------------------------------------------------- /api/src/routes/statusByQuote.js: -------------------------------------------------------------------------------- 1 | import response from '../response' 2 | import createLogger from 'logging' 3 | import { 4 | getOrderById 5 | } from '../mangodb' 6 | import Validator from '../validator' 7 | 8 | const logger = createLogger('routes/status.js') 9 | const loggerInvalidId = createLogger('routes/status.js - invalidId') 10 | 11 | let schema = { 12 | user_id: { 13 | type: String, 14 | required: true, 15 | match: /^[a-zA-Z0-9-_]+$/, 16 | length: { 17 | min: 12, 18 | max: 64 19 | }, 20 | message: 'user_id required min:12 max:64' 21 | }, 22 | quote_id: { 23 | type: String, 24 | required: true, 25 | match: /^[a-zA-Z0-9-_]+$/, 26 | length: { 27 | min: 12, 28 | max: 64 29 | }, 30 | message: 'quote_id required min:12 max:64' 31 | } 32 | } 33 | 34 | let validator = Validator(schema) 35 | export default (app) => { 36 | app.get('/status/:userId/:quoteId', (req, res) => { 37 | let errors = validator.validate({user_id: req.params.userId, quote_id: req.params.quoteId}) 38 | if (errors.length) { 39 | response.error(res, 'invalid user or quote id') 40 | } else { 41 | getOrderById(req.params.userId, req.params.quoteId) 42 | .then(result => { 43 | if (result.length === 0) { 44 | loggerInvalidId.error(`UserId requested: ${req.params.userId}`) 45 | response.error(res, 'user id does not exist') 46 | } else { 47 | response.success(res, { 48 | user_id: result[0].user_id, 49 | quote_id: result[0].quote_id, 50 | status: result[0].status, 51 | fiat_total_amount: { 52 | currency: result[0].fiat_total_amount.currency, 53 | amount: result[0].fiat_total_amount.amount 54 | }, 55 | requested_digital_amount: { 56 | currency: result[0].requested_digital_amount.currency, 57 | amount: result[0].requested_digital_amount.amount 58 | } 59 | }) 60 | } 61 | }) 62 | .catch((err) => { 63 | logger.error(err) 64 | }) 65 | } 66 | }) 67 | } 68 | -------------------------------------------------------------------------------- /api/src/simplex/call.js: -------------------------------------------------------------------------------- 1 | import createLogger from 'logging' 2 | import { 3 | simplex 4 | } from '../config' 5 | import request from 'request' 6 | import debugLogger from 'debug' 7 | 8 | const logger = createLogger('simplex/call.js') 9 | const catchLogger = createLogger('simplex/call.js - catch') 10 | const debugRequest = debugLogger('calls:request') 11 | 12 | export default (body, path) => { 13 | return new Promise((resolve, reject) => { 14 | var options = { 15 | url: path, 16 | headers: { 17 | 'Authorization': 'ApiKey ' + simplex.apiKey 18 | }, 19 | body: body, 20 | method: 'post', 21 | json: true 22 | } 23 | let callback = (error, response, body) => { 24 | try { 25 | debugRequest(body) 26 | if (!error && response.statusCode === 200) { 27 | resolve(body) 28 | } else if (response.statusCode === 400) { 29 | reject(body) 30 | logger.error(body) 31 | } else { 32 | reject(error) 33 | logger.error(error) 34 | } 35 | } catch (e) { 36 | catchLogger.error(e) 37 | } 38 | } 39 | debugRequest(options) 40 | request(options, callback) 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /api/src/simplex/getOrder.js: -------------------------------------------------------------------------------- 1 | import debugLogger from 'debug' 2 | import call from './call' 3 | import { 4 | simplex 5 | } from '../config' 6 | const debug = debugLogger('calls:getOrder') 7 | export default (reqObject) => { 8 | debug(reqObject) // todo remove dev item 9 | return call(reqObject, simplex.orderEP) 10 | } 11 | -------------------------------------------------------------------------------- /api/src/simplex/getQuote.js: -------------------------------------------------------------------------------- 1 | import debugLogger from 'debug' 2 | import call from './call' 3 | import { 4 | simplex 5 | } from '../config' 6 | const debug = debugLogger('calls:getQuote') 7 | export default (reqObject) => { 8 | debug(reqObject) // todo remove dev item 9 | return call(reqObject, simplex.quoteEP) 10 | } 11 | -------------------------------------------------------------------------------- /api/src/simplex/index.js: -------------------------------------------------------------------------------- 1 | import getQuote from './getQuote' 2 | import getOrder from './getOrder' 3 | export { 4 | getQuote, 5 | getOrder 6 | } 7 | -------------------------------------------------------------------------------- /api/src/simplex_events/index.js: -------------------------------------------------------------------------------- 1 | import getEvents from './retrieveEvents' 2 | import cron from 'node-cron' 3 | 4 | import createLogger from 'logging' 5 | const logger = createLogger('simplex_events/index.js') 6 | 7 | const runCron = () => { 8 | console.log('cron setup for simplex events') 9 | const cronTime = '* * * * *' 10 | return cron.schedule(cronTime, () => { 11 | try { 12 | getEvents() 13 | .then(() => { 14 | logger.info('Simplex Events Retrieved') 15 | }) 16 | .catch(_error => { 17 | logger.error(_error) 18 | }) 19 | } catch (e) { 20 | logger.error(e) 21 | } 22 | }) 23 | } 24 | 25 | export default runCron 26 | -------------------------------------------------------------------------------- /api/src/simplex_events/retrieveEvents.js: -------------------------------------------------------------------------------- 1 | import { 2 | connect, 3 | findAndUpdate, 4 | findAndUpdateStatus, 5 | EventSchema 6 | } from '../mangodb' 7 | import eachOfSeries from 'async/eachOfSeries' 8 | import createLogger from 'logging' 9 | import { 10 | mangodb, 11 | simplex 12 | } from '../config' 13 | import request from 'request' 14 | import debugLogger from 'debug' 15 | 16 | const recordLogger = createLogger('simplex_events/retrieveEvents.js : record-event') 17 | const logger = createLogger('simplex_events/retrieveEvents.js') 18 | const debugRequest = debugLogger('calls:Events') 19 | 20 | connect().then(() => { 21 | logger.info(`mangodb running on port: ${mangodb.host}:${mangodb.port}`) 22 | }).catch((err) => { 23 | logger.error(`mangodb error: ${err}`) 24 | }) 25 | 26 | let getEvents = () => { 27 | return new Promise((resolve, reject) => { 28 | let options = { 29 | url: simplex.eventEP, 30 | headers: { 31 | 'Authorization': 'ApiKey ' + simplex.apiKey 32 | }, 33 | method: 'get', 34 | json: true 35 | } 36 | let retrieveCallback = (error, response, body) => { 37 | try { 38 | if (!error && response.statusCode === 200) { 39 | eachOfSeries(body.events, processEvent, (error) => { 40 | if (error) { 41 | logger.error(response) 42 | reject(error) 43 | } else { 44 | resolve() 45 | } 46 | }) 47 | } else if (response.statusCode === 400) { 48 | logger.error(response) 49 | reject(body) 50 | } else { 51 | logger.error(error) 52 | reject(error) 53 | } 54 | } catch (e) { 55 | logger.error(error) 56 | reject(error) 57 | } 58 | } 59 | debugRequest(options) 60 | request(options, retrieveCallback) 61 | }) 62 | } 63 | 64 | function updateItem (recordItem, deleteCallback) { 65 | findAndUpdateStatus(recordItem.payment.partner_end_user_id, recordItem.payment.id, { 66 | status: recordItem.payment.status 67 | }).then(resp => { 68 | if (resp) { 69 | let options = { 70 | url: `${simplex.eventEP}/${recordItem.event_id}`, 71 | headers: { 72 | 'Authorization': 'ApiKey ' + simplex.apiKey 73 | }, 74 | method: 'DELETE', 75 | json: true 76 | } 77 | request(options, deleteCallback) 78 | } else { 79 | findAndUpdate(recordItem.payment.partner_end_user_id, { 80 | status: recordItem.payment.status 81 | }).then(resp => { 82 | if (resp) { 83 | let options = { 84 | url: `${simplex.eventEP}/${recordItem.event_id}`, 85 | headers: { 86 | 'Authorization': 'ApiKey ' + simplex.apiKey 87 | }, 88 | method: 'DELETE', 89 | json: true 90 | } 91 | request(options, deleteCallback) 92 | } else { 93 | console.log('Unknown IDs: ', recordItem.payment.partner_end_user_id, recordItem.payment.id) // todo remove dev item 94 | } 95 | }).catch((err) => { 96 | logger.error(err) 97 | }) 98 | } 99 | }).catch((err) => { 100 | logger.error(err) 101 | }) 102 | } 103 | 104 | function processEvent (item, key, callback) { 105 | EventSchema(item) 106 | .save() 107 | .then(() => { 108 | updateItem(item, callback) 109 | }) 110 | .catch((error) => { 111 | recordLogger.error(error) 112 | updateItem(item, callback) 113 | }) 114 | } 115 | 116 | export default getEvents 117 | -------------------------------------------------------------------------------- /api/src/sourceValidate.js: -------------------------------------------------------------------------------- 1 | import createLogger from 'logging' 2 | import debugLogger from 'debug' 3 | 4 | import response from './response' 5 | import { 6 | productValidation, 7 | recaptcha as recaptchaConfig 8 | } from './config' 9 | import { 10 | Recaptcha 11 | } from 'express-recaptcha' 12 | 13 | const logger = createLogger('sourceValidate.js') 14 | const debug = debugLogger('validation:bypass') 15 | 16 | const recaptcha = new Recaptcha(recaptchaConfig.siteKey, recaptchaConfig.secretKey) 17 | 18 | export default function sourceyValidate (validationOptions = productValidation) { 19 | return function (req, res, next) { 20 | if (req.headers['referer'] === validationOptions.referrerAppleiOS) { 21 | if (validationOptions.apiKeys.includes(req.headers[validationOptions.apiKeyHeaderName])) { 22 | req.recaptcha = {} 23 | req.mewSourceApplication = 'ios' 24 | debug('Mobile Bypass Success IOS') 25 | next() 26 | } else { 27 | logger.error('Invalid API key: IOS') 28 | response.error(res, 'Invalid API key') 29 | } 30 | } else if (req.headers['referer'] === validationOptions.referrerAndroid) { 31 | if (validationOptions.apiKeys.includes(req.headers[validationOptions.apiKeyHeaderName])) { 32 | req.recaptcha = {} 33 | req.mewSourceApplication = 'android' 34 | debug('Mobile Bypass Success Android') 35 | next() 36 | } else { 37 | logger.error('Invalid API key: Android') 38 | response.error(res, 'Invalid API key') 39 | } 40 | } else if (validationOptions.specialWebOrigins.includes(req.headers['origin'])) { 41 | req.recaptcha = {} 42 | req.mewSourceApplication = 'mew' 43 | debug('Web Bypass Success') 44 | next() 45 | } else if (/quote/.test(req.route.path)) { 46 | next() 47 | } else { 48 | return recaptcha.middleware.verify(req, res, next) 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /api/src/validator.js: -------------------------------------------------------------------------------- 1 | import Schema from 'validate' 2 | export default (_schema) => { 3 | let validator = new Schema(_schema) 4 | return validator 5 | } 6 | -------------------------------------------------------------------------------- /api/test/test.api.getEvents.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyEtherWallet/simplex-api/4c3a42d2e85bbdd107129477fc7f8008e564ea6a/api/test/test.api.getEvents.js -------------------------------------------------------------------------------- /api/test/test.api.getQuote.js: -------------------------------------------------------------------------------- 1 | import app from '../src' 2 | import chai from 'chai' 3 | import request from 'supertest' 4 | import { 5 | simplex 6 | } from '../src/config' 7 | import uuidv4 from 'uuid/v4' 8 | const assert = chai.assert 9 | const expect = chai.expect 10 | const testAddress = { 11 | BTC: '1DECAF2uSpFTP4L1fAHR8GCLrPqdwdLse9', 12 | ETH: '0xDECAF9CD2367cdbb726E904cD6397eDFcAe6068D' 13 | } 14 | describe('API getQuote Test', () => { 15 | let quoteResponse = {} 16 | it('should return quote info', (done) => { 17 | const packageInfo = require('../package.json') 18 | const _digital = simplex.validDigital[Math.floor(Math.random() * simplex.validDigital.length)] 19 | const _fiat = simplex.validFiat[Math.floor(Math.random() * simplex.validFiat.length)] 20 | const _pair = [_digital, _fiat] 21 | const _requested = _pair[Math.floor(Math.random() * _pair.length)] 22 | const _amount = Math.floor(Math.random() * 3) + 1 23 | request(app) 24 | .post('/quote') 25 | .send({ 26 | 'digital_currency': _digital, 27 | 'fiat_currency': _fiat, 28 | 'requested_currency': _digital, 29 | 'requested_amount': _amount 30 | }) 31 | .set('Content-Type', 'application/json') 32 | .set('Accept', 'application/json') 33 | .expect(200) 34 | .end((err, res) => { 35 | let body = res.body 36 | assert.typeOf(body, 'Object') 37 | assert.equal(body.error, false) 38 | let response = body.result 39 | assert.typeOf(response.user_id, 'string') 40 | assert.typeOf(response.quote_id, 'string') 41 | assert.equal(response.wallet_id, simplex.walletID) 42 | assert.equal(response.digital_money.amount, _amount) 43 | assert.equal(response.digital_money.currency, _digital) 44 | assert.equal(response.fiat_money.currency, _fiat) 45 | quoteResponse = response 46 | done(); 47 | }) 48 | }).timeout(5000) 49 | 50 | it('should initiate an order', (done) => { 51 | let reqObject = { 52 | account_details: { 53 | app_end_user_id: quoteResponse.user_id, 54 | }, 55 | transaction_details: { 56 | payment_details: { 57 | fiat_total_amount: { 58 | currency: quoteResponse.fiat_money.currency, 59 | amount: quoteResponse.fiat_money.total_amount 60 | }, 61 | requested_digital_amount: { 62 | currency: quoteResponse.digital_money.currency, 63 | amount: quoteResponse.digital_money.amount 64 | }, 65 | destination_wallet: { 66 | currency: quoteResponse.digital_money.currency, 67 | address: testAddress[quoteResponse.digital_money.currency] 68 | } 69 | } 70 | } 71 | } 72 | request(app) 73 | .post('/order') 74 | .send(reqObject) 75 | .set('Content-Type', 'application/json') 76 | .set('Accept', 'application/json') 77 | .expect(200) 78 | .end((err, res) => { 79 | let body = res.body 80 | assert.equal(body.error, false) 81 | let result = body.result 82 | assert.equal(result.version, simplex.apiVersion) 83 | assert.equal(result.destination_wallet_address, testAddress[quoteResponse.digital_money.currency]) 84 | assert.equal(result.destination_wallet_currency, quoteResponse.digital_money.currency) 85 | assert.equal(result.fiat_total_amount_amount, quoteResponse.fiat_money.total_amount) 86 | assert.equal(result.fiat_total_amount_currency, quoteResponse.fiat_money.currency) 87 | assert.equal(result.digital_total_amount_amount, quoteResponse.digital_money.amount) 88 | assert.equal(result.digital_total_amount_currency, quoteResponse.digital_money.currency) 89 | assert.equal(result.payment_post_url, simplex.paymentEP.replace(/\u200B/g,'')) 90 | done(); 91 | }) 92 | }).timeout(5000) 93 | 94 | it('should attempt to get invalid quote info ', (done) => { 95 | const packageInfo = require('../package.json') 96 | const _digital = simplex.validDigital[Math.floor(Math.random() * simplex.validDigital.length)] 97 | const _fiat = simplex.validFiat[Math.floor(Math.random() * simplex.validFiat.length)] 98 | const _pair = [_digital, _fiat] 99 | const _requested = _pair[Math.floor(Math.random() * _pair.length)] 100 | const _amount = 200 101 | request(app) 102 | .post('/quote') 103 | .send({ 104 | 'digital_currency': _digital, 105 | 'fiat_currency': _fiat, 106 | 'requested_currency': _digital, 107 | 'requested_amount': _amount 108 | }) 109 | .set('Content-Type', 'application/json') 110 | .set('Accept', 'application/json') 111 | .expect(200) 112 | .end((err, res) => { 113 | let body = res.body 114 | assert.typeOf(body, 'Object') 115 | assert.equal(body.error, false) 116 | let response = body.result 117 | assert.typeOf(response.user_id, 'string') 118 | assert.typeOf(response.quote_id, 'string') 119 | assert.equal(response.wallet_id, simplex.walletID) 120 | assert.equal(response.digital_money.amount, _amount) 121 | assert.equal(response.digital_money.currency, _digital) 122 | assert.equal(response.fiat_money.currency, _fiat) 123 | quoteResponse = response 124 | done(); 125 | }) 126 | }).timeout(5000) 127 | 128 | it('should try to initiate an invalid order', (done) => { 129 | let reqObject = { 130 | account_details: { 131 | app_end_user_id: quoteResponse.user_id, 132 | }, 133 | transaction_details: { 134 | payment_details: { 135 | fiat_total_amount: { 136 | currency: quoteResponse.fiat_money.currency, 137 | amount: quoteResponse.fiat_money.total_amount 138 | }, 139 | requested_digital_amount: { 140 | currency: quoteResponse.digital_money.currency, 141 | amount: quoteResponse.digital_money.amount 142 | }, 143 | destination_wallet: { 144 | currency: quoteResponse.digital_money.currency, 145 | address: testAddress[quoteResponse.digital_money.currency] 146 | } 147 | } 148 | } 149 | } 150 | request(app) 151 | .post('/order') 152 | .send(reqObject) 153 | .set('Content-Type', 'application/json') 154 | .set('Accept', 'application/json') 155 | .expect(200) 156 | .end((err, res) => { 157 | let body = res.body 158 | assert.equal(body.error, false) 159 | let result = body.result 160 | assert.equal(result.version, simplex.apiVersion) 161 | assert.equal(result.destination_wallet_address, testAddress[quoteResponse.digital_money.currency]) 162 | assert.equal(result.destination_wallet_currency, quoteResponse.digital_money.currency) 163 | assert.equal(result.fiat_total_amount_amount, quoteResponse.fiat_money.total_amount) 164 | assert.equal(result.fiat_total_amount_currency, quoteResponse.fiat_money.currency) 165 | assert.equal(result.digital_total_amount_amount, quoteResponse.digital_money.amount) 166 | assert.equal(result.digital_total_amount_currency, quoteResponse.digital_money.currency) 167 | done(); 168 | }) 169 | }).timeout(5000) 170 | }) -------------------------------------------------------------------------------- /api/test/test.api.info.js: -------------------------------------------------------------------------------- 1 | import app from '../src' 2 | import chai from 'chai' 3 | import request from 'supertest' 4 | 5 | const expect = chai.expect 6 | describe('API Info Test', function() { 7 | it('should return correct version number', function(done) { 8 | const packageInfo = require('../package.json') 9 | request(app) 10 | .get('/info') 11 | .end(function(err, res) { 12 | expect(res.body.error).to.equal(false) 13 | expect(res.body.result.version).to.equal(packageInfo.version) 14 | expect(res.statusCode).to.equal(200) 15 | done(); 16 | }) 17 | }) 18 | }) -------------------------------------------------------------------------------- /boxfile.yml: -------------------------------------------------------------------------------- 1 | run.config: 2 | engine: nodejs 3 | engine.config: 4 | runtime: nodejs-8.9 5 | dep_manager: npm 6 | extra_packages: 7 | - bash 8 | cache_dirs: 9 | - api/node_modules 10 | - frontend/node_modules 11 | extra_steps: 12 | - cd api && npm install 13 | - cd api && npm run build 14 | - cd frontend && npm install 15 | - cd frontend && npm run build 16 | 17 | data.mongodb: 18 | image: nanobox/mongodb:3.4 19 | 20 | web.api: 21 | start: 22 | - bash -c 'cd api && npm run prod' 23 | routes: 24 | - 'api:/' 25 | cron: 26 | - id: simplex-events 27 | schedule: '* * * * *' 28 | command: bash -c 'cd api && npm run eventsProd' 29 | 30 | web.frontend: 31 | start: 32 | - bash -c 'cd frontend && npm run prod' 33 | routes: 34 | - '/' 35 | -------------------------------------------------------------------------------- /currencyConfig.js: -------------------------------------------------------------------------------- 1 | const fiat = [ 2 | "USD", 3 | "EUR", 4 | "CAD", 5 | "JPY", 6 | "GBP", 7 | "RUB", 8 | "AUD", 9 | "KRW", 10 | "CHF", 11 | "CZK", 12 | "DKK", 13 | "NOK", 14 | "NZD", 15 | "PLN", 16 | "SEK", 17 | "TRY", 18 | "ZAR", 19 | "HUF" 20 | ]; 21 | const crypto = ["BTC", "ETH", "BNB", "MATIC"]; 22 | 23 | const handler = function (defaultValue = 42) { 24 | return { 25 | get: function (target, name) { 26 | return target.hasOwnProperty(name) ? target[name] : defaultValue; 27 | } 28 | }; 29 | }; 30 | 31 | const minFiatTarget = { USD: 50, EUR: 50 }; 32 | const maxFiatTarget = { USD: 20000, EUR: 20000 }; 33 | 34 | const minFiat = new Proxy(minFiatTarget, handler(50)); 35 | const maxFiat = new Proxy(maxFiatTarget, handler(20000)); 36 | 37 | module.exports = { 38 | fiat, 39 | crypto, 40 | minFiat, 41 | maxFiat 42 | }; 43 | -------------------------------------------------------------------------------- /deploy/.example_env: -------------------------------------------------------------------------------- 1 | WALLET_ID= 2 | QUOTE_EP=https://sandbox.test-simplexcc.com/wallet/merchant/v2/quote 3 | ORDER_EP=https://sandbox.test-simplexcc.com/wallet/merchant/v2/payments/partner/data 4 | PAYMENT_EP=https://sandbox.test-simplexcc.com/payments/new 5 | SIMPLEX_APIKEY= 6 | RECAPTCHA_SITE_KEY= 7 | RECAPTCHA_SECRET_KEY= 8 | API_HOST= 9 | 10 | API_KEY= 11 | API_KEY_HEADER=apikey 12 | IOS_REFERER= 13 | ANDROID_REFERER= -------------------------------------------------------------------------------- /deploy/nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | events {} 2 | 3 | 4 | http { 5 | proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=one:8m max_size=3000m inactive=600m; 6 | 7 | gzip_comp_level 6; 8 | gzip_vary on; 9 | gzip_min_length 1000; 10 | gzip_proxied any; 11 | gzip_types text/plain text/css application/json application/x-javascript application/xml application/xml+rss text/javascript; 12 | gzip_buffers 16 8k; 13 | 14 | server { 15 | listen 80 default_server; 16 | listen [::]:80 default_server; 17 | return 200; 18 | } 19 | server { 20 | listen 80; 21 | listen [::]:80; 22 | server_name apiccswap.mewtest.com apiccswap.myetherwallet.com; 23 | location / { 24 | proxy_pass http://api:8080; 25 | proxy_set_header Host $host; 26 | proxy_set_header X-Forwarded-For $remote_addr; 27 | } 28 | } 29 | server { 30 | listen 80; 31 | listen [::]:80; 32 | server_name ccswap.mewtest.com ccswap.myetherwallet.com; 33 | location / { 34 | proxy_pass http://frontend:8080; 35 | proxy_set_header Host $host; 36 | proxy_set_header X-Forwarded-For $remote_addr; 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /deploy/setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | #********************************************************* 3 | # To use this script to setup the environment and service 4 | # place it in the same directory as your .env file 5 | #********************************************************* 6 | 7 | # Setup options: 8 | GIT_URL=https://github.com/MyEtherWallet/simplex-api.git 9 | DOCKER_COMPOSE_VERSION=1.24.0 10 | ENV_FILE='.env' 11 | CURRENCY_FILE='currencyConfig.js' 12 | 13 | # GIT options 14 | # Use a branch other than master 15 | FROM_BRANCH=true 16 | # The name of the branch to use 17 | BRANCH_NAME=add-cad-and-jpy; 18 | 19 | # defaults 20 | RESTART_VAR='false' 21 | STOP_DOCKER='false' 22 | START_DOCKER='false' 23 | FLAGGED='false' 24 | PURGE_DOCKER='false' 25 | PURGE_IMAGES='false' 26 | REBUILD_RESTART='false' 27 | RUN_ALL='false' 28 | NO_CACHE='false' 29 | 30 | POSITIONAL=() 31 | while [[ $# -gt 0 ]] 32 | do 33 | key="$1" 34 | 35 | case $key in 36 | -r|--restart) 37 | RESTART_VAR='true' 38 | FLAGGED='true' 39 | shift # past argument 40 | ;; 41 | -s|--stop-docker) 42 | STOP_DOCKER='true' 43 | FLAGGED='true' 44 | shift # past argument 45 | ;; 46 | -st|--start-docker) 47 | START_DOCKER='true' 48 | FLAGGED='true' 49 | shift # past argument 50 | ;; 51 | -p|--purge-docker) 52 | PURGE_DOCKER='true' 53 | FLAGGED='true' 54 | shift # past argument 55 | ;; 56 | -pi|--purge-images) 57 | PURGE_IMAGES='true' 58 | FLAGGED='true' 59 | shift # past argument 60 | ;; 61 | -b|--rebuild-restart-docker) 62 | REBUILD_RESTART='true' 63 | FLAGGED='true' 64 | shift # past argument 65 | ;; 66 | -h|--help) 67 | HELP='true' 68 | shift # past argument 69 | ;; 70 | -a|--all) 71 | RUN_ALL='true' 72 | FLAGGED='true' 73 | shift # past argument 74 | ;; 75 | --no-cache) 76 | NO_CACHE='true' 77 | echo "not using cache when building docker images" 78 | shift # past argument 79 | ;; 80 | --default) 81 | HELP='true' #DEFAULT=YES 82 | shift # past argument 83 | ;; 84 | *) # unknown option 85 | POSITIONAL+=("$1") # save it in an array for later 86 | shift # past argument 87 | ;; 88 | esac 89 | done 90 | set -- "${POSITIONAL[@]}" # restore positional parameters 91 | 92 | usage(){ 93 | echo "usage: setup.sh [optional flag]" 94 | echo " flags: (Note: only one may be used at a time)" 95 | echo " -r | --restart : stop docker and run docker-compose " 96 | echo " -s | --stop-docker : stop all docker containers" 97 | echo " -st| --start-docker : start all docker containers" 98 | echo " -p | --purge-docker : stop and remove all docker containers" 99 | echo " -pi | --purge-images : purge all docker images not currently attached" 100 | echo " -b | --rebuild-restart-docker : remove docker containers, rebuild and run docker-compose" 101 | echo " -a | --all : run total setup or re-setup without asking for abort" 102 | echo " --no-cache : don't use cache when building docker images" 103 | echo "Running with no arguments initiates total setup or re-setup" 104 | echo "Note: total setup/re-setup does not replace an existing database data directory." 105 | 106 | } 107 | 108 | runFromRepoDeploy(){ 109 | prg=$0 110 | if [ ! -e "$prg" ]; then 111 | case $prg in 112 | (*/*) exit 1;; 113 | (*) prg=$(command -v -- "$prg") || exit;; 114 | esac 115 | fi 116 | 117 | dir=$( 118 | cd -P -- "$(dirname -- "$prg")" && pwd -P 119 | ) || exit 120 | prg=$dir/$(basename -- "$prg") || exit 121 | 122 | echo $prg 123 | if [[ $prg == *"/simplex-api/deploy/"* ]]; then 124 | echo "running from deploy directory" 125 | cd ../ 126 | fi 127 | 128 | } 129 | 130 | if [ "$HELP" == 'true' ]; then 131 | usage 132 | exit 0 133 | fi 134 | 135 | alternateActionsAndAbort(){ 136 | if [ "$RESTART_VAR" = 'true' ]; then 137 | echo "Stopping all docker and running docker-compose" 138 | echo ${RESTART_VAR} 139 | stopDocker 140 | if [ -d "simplex-api" ]; then 141 | cd simplex-api 142 | fi 143 | sudo docker-compose up -d --remove-orphans 144 | fi 145 | 146 | if [ "$REBUILD_RESTART" = 'true' ]; then 147 | echo "Removing docker containers, rebuilding and running docker-compose" 148 | purgeDocker 149 | cd simplex-api; 150 | buildDockerImages 151 | sudo docker-compose up -d --remove-orphans 152 | fi 153 | 154 | if [ "$STOP_DOCKER" == 'true' ]; then 155 | echo "Stopping all Docker containers" 156 | stopDocker 157 | fi 158 | 159 | if [ "$START_DOCKER" == 'true' ]; then 160 | echo "Starting all Docker containers" 161 | startDocker 162 | fi 163 | 164 | if [ "$PURGE_DOCKER" == 'true' ]; then 165 | echo "Stopping and removing all docker containers" 166 | purgeDocker 167 | fi 168 | 169 | if [ "$PURGE_IMAGES" == 'true' ]; then 170 | echo "Purging all docker images not currently attached" 171 | cleanAllImages 172 | fi 173 | 174 | if [ "$RUN_ALL" == 'true' ]; then 175 | echo "Stopping and removing all docker containers" 176 | doSetup 177 | fi 178 | 179 | if [ "$FLAGGED" == 'true' ]; then 180 | exit 0 181 | fi 182 | 183 | echo "Will stop and remove Docker containers, clone repo, build and restart docker." 184 | echo "Press any key to abort:" 185 | 186 | read -t 3 -n 1 SHOULD_ABORT 187 | 188 | if [ $? == 0 ]; then 189 | echo ' ' 190 | echo "Aborting Setup" 191 | exit 0 192 | fi 193 | } 194 | 195 | installDocker(){ 196 | if hash docker 2>/dev/null; then 197 | echo "Docker present" 198 | else 199 | echo "Installing Docker" 200 | sudo apt-get update 201 | sudo apt-get install -y \ 202 | apt-transport-https \ 203 | ca-certificates \ 204 | curl \ 205 | gnupg-agent \ 206 | software-properties-common 207 | curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - 208 | sudo add-apt-repository \ 209 | "deb [arch=amd64] https://download.docker.com/linux/ubuntu \ 210 | $(lsb_release -cs) \ 211 | stable" 212 | sudo apt-get update 213 | sudo apt-get install -y docker-ce docker-ce-cli containerd.io 214 | fi 215 | } 216 | 217 | installDockerCompose(){ 218 | if hash docker-compose 2>/dev/null; then 219 | echo "Docker Compose present" 220 | else 221 | echo "Installing Docker Compose version: ${DOCKER_COMPOSE_VERSION}" 222 | sudo curl -L "https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose 223 | sudo chmod +x /usr/local/bin/docker-compose 224 | echo sudo docker-compose --version 225 | fi 226 | } 227 | 228 | checkoutRepo(){ 229 | echo "Checking out simplex-api" 230 | git clone ${GIT_URL}; 231 | echo "copying setup script to repo top level directory" 232 | cp ./simplex-api/deploy/setup.sh ./simplex-api/ 233 | echo "entering simplex-api directory" 234 | cd simplex-api; 235 | if [ $FROM_BRANCH = "true" ]; then 236 | echo "Checking out branch ${BRANCH_NAME}" 237 | git checkout origin/${BRANCH_NAME}; 238 | git checkout -b ${BRANCH_NAME}; 239 | fi 240 | 241 | if [ -f "nginx.conf" ]; then 242 | echo "copying nginx config files" 243 | cp ./nginx.conf ./simplex-api/deploy/nginx 244 | fi 245 | } 246 | 247 | stopDocker(){ 248 | echo "Stopping Docker Containers" 249 | # sudo docker stop $(sudo docker ps -a -q) 250 | sudo docker stop "frontend" 251 | sudo docker stop "api" 252 | sudo docker stop "nginx" 253 | sudo docker stop "mongo_db" 254 | } 255 | 256 | startDocker(){ 257 | echo "Starting Docker Containers" 258 | # sudo docker stop $(sudo docker ps -a -q) 259 | sudo docker start "frontend" 260 | sudo docker start "api" 261 | sudo docker start "nginx" 262 | sudo docker start "mongo_db" 263 | } 264 | 265 | purgeDocker(){ 266 | stopDocker 267 | echo "Removing Docker Containers" 268 | # sudo docker rm $(sudo docker ps -a -q) 269 | sudo docker rm "frontend" 270 | sudo docker rm "api" 271 | sudo docker rm "nginx" 272 | sudo docker rm "mongo_db" 273 | } 274 | 275 | cleanAllImages(){ 276 | echo "Removing all docker images" 277 | sudo docker image prune 278 | } 279 | 280 | 281 | buildDockerImages(){ 282 | echo $PWD 283 | cp ../${ENV_FILE} ./ 284 | cd api; 285 | cp ../${ENV_FILE} ./ 286 | cp ../${CURRENCY_FILE} ./ 287 | if [ $NO_CACHE = "true" ]; then 288 | cleanAllImages 289 | fi 290 | 291 | if [ $NO_CACHE = "true" ]; then 292 | sudo docker build --force-rm --no-cache --tag=simplex-api . 293 | else 294 | sudo docker build --force-rm --tag=simplex-api . 295 | fi 296 | cd ../ 297 | cd frontend; 298 | cp ../${ENV_FILE} ./ 299 | cp ../${CURRENCY_FILE} ./ 300 | if [ $NO_CACHE = "true" ]; then 301 | sudo docker build --force-rm --no-cache --tag=simplex-frontend . 302 | else 303 | sudo docker build --force-rm --tag=simplex-frontend . 304 | fi 305 | cd ../ 306 | } 307 | 308 | createDataDirectory(){ 309 | if [ -d "dbdata" ]; then 310 | echo "data directory exists" 311 | else 312 | echo "making data directory" 313 | mkdir dbdata 314 | fi 315 | } 316 | 317 | doSetup(){ 318 | if [ -f ${ENV_FILE} ]; then 319 | echo "env file exists" 320 | createDataDirectory 321 | if [ -d "simplex-api" ]; then 322 | purgeDocker 323 | echo "prior simplex-api dir exists" 324 | sudo rm -rf ./simplex-api/ 325 | checkoutRepo 326 | buildDockerImages 327 | sudo docker-compose up -d --remove-orphans 328 | sudo docker ps 329 | else 330 | echo "prior simplex-api dir does not exist" 331 | checkoutRepo 332 | buildDockerImages 333 | sudo docker-compose up -d --remove-orphans 334 | sudo docker ps 335 | fi 336 | else 337 | echo "ERROR: failed to begin setup. .env file does not exist" 338 | fi 339 | } 340 | 341 | alternateActionsAndAbort 342 | 343 | installDocker 344 | installDockerCompose 345 | 346 | runFromRepoDeploy 347 | 348 | doSetup 349 | 350 | 351 | 352 | 353 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | 3 | services: 4 | mongo_db: 5 | container_name: mongo_db 6 | image: mongo:3.4.20-xenial 7 | volumes: 8 | - "../dbdata:/data/db" 9 | ports: 10 | - "27017:27017" # can access via ssh tunnel via host 11 | nginx: 12 | container_name: nginx 13 | image: nginx:latest 14 | volumes: 15 | - ./deploy/nginx/nginx.conf:/etc/nginx/nginx.conf 16 | environment: 17 | - NGINX_HOST=example.com 18 | - NGINX_PORT=80 19 | ports: 20 | - "80:80/tcp" 21 | links: 22 | - api 23 | - frontend 24 | api: 25 | container_name: api 26 | image: simplex-api 27 | env_file: 28 | - .env 29 | depends_on: 30 | - mongo_db 31 | environment: 32 | - DATA_MONGODB_HOST=mongo_db 33 | # - DEBUG=* 34 | links: 35 | - mongo_db 36 | frontend: 37 | container_name: frontend 38 | image: simplex-frontend 39 | env_file: 40 | - .env 41 | depends_on: 42 | - mongo_db 43 | - api 44 | command: npm run prod 45 | -------------------------------------------------------------------------------- /frontend/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false, 5 | "targets": { 6 | "browsers": ["> 1%", "last 2 versions", "not ie <= 8"] 7 | } 8 | }], 9 | "stage-2" 10 | ], 11 | "plugins": ["transform-vue-jsx", "transform-runtime"] 12 | } 13 | -------------------------------------------------------------------------------- /frontend/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /frontend/.eslintignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /config/ 3 | /dist/ 4 | /*.js 5 | -------------------------------------------------------------------------------- /frontend/.eslintrc.js: -------------------------------------------------------------------------------- 1 | // https://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | parserOptions: { 6 | parser: 'babel-eslint' 7 | }, 8 | env: { 9 | browser: true, 10 | }, 11 | extends: [ 12 | // https://github.com/vuejs/eslint-plugin-vue#priority-a-essential-error-prevention 13 | // consider switching to `plugin:vue/strongly-recommended` or `plugin:vue/recommended` for stricter rules. 14 | 'plugin:vue/essential', 15 | // https://github.com/standard/standard/blob/master/docs/RULES-en.md 16 | 'standard' 17 | ], 18 | // required to lint *.vue files 19 | plugins: [ 20 | 'vue' 21 | ], 22 | // add your custom rules here 23 | rules: { 24 | semi: 'off', 25 | // allow async-await 26 | 'generator-star-spacing': 'off', 27 | // allow debugger during development 28 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off' 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | /dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Editor directories and files 9 | .idea 10 | .vscode 11 | *.suo 12 | *.ntvs* 13 | *.njsproj 14 | *.sln 15 | -------------------------------------------------------------------------------- /frontend/.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | "postcss-import": {}, 6 | "postcss-url": {}, 7 | // to edit target browsers: use "browserslist" field in package.json 8 | "autoprefixer": {} 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /frontend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:10.16.0-alpine as build-stage 2 | 3 | ENV NODE_OPTIONS --max-old-space-size=4096 4 | RUN npm install npm@6.9 -g 5 | RUN node -v && npm -v 6 | WORKDIR / 7 | COPY package*.json ./ 8 | RUN npm install 9 | COPY . . 10 | COPY ./.env ./ 11 | 12 | RUN npm run build 13 | EXPOSE 8080/tcp 14 | CMD npm run prod 15 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # mew-simplex-frontend 2 | 3 | > A Vue.js project 4 | 5 | ## Build Setup 6 | 7 | ``` bash 8 | # install dependencies 9 | npm install 10 | 11 | # serve with hot reload at localhost:8080 12 | npm run dev 13 | 14 | # build for production with minification 15 | npm run build 16 | 17 | # build for production and view the bundle analyzer report 18 | npm run build --report 19 | ``` 20 | 21 | For a detailed explanation on how things work, check out the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader). 22 | -------------------------------------------------------------------------------- /frontend/build/build.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | require('./check-versions')() 3 | 4 | process.env.NODE_ENV = 'production' 5 | 6 | const ora = require('ora') 7 | const rm = require('rimraf') 8 | const path = require('path') 9 | const chalk = require('chalk') 10 | const webpack = require('webpack') 11 | const config = require('../config') 12 | const webpackConfig = require('./webpack.prod.conf') 13 | 14 | const spinner = ora('building for production...') 15 | spinner.start() 16 | 17 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { 18 | if (err) throw err 19 | webpack(webpackConfig, (err, stats) => { 20 | spinner.stop() 21 | if (err) throw err 22 | process.stdout.write(stats.toString({ 23 | colors: true, 24 | modules: false, 25 | children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build. 26 | chunks: false, 27 | chunkModules: false 28 | }) + '\n\n') 29 | 30 | if (stats.hasErrors()) { 31 | console.log(chalk.red(' Build failed with errors.\n')) 32 | process.exit(1) 33 | } 34 | 35 | console.log(chalk.cyan(' Build complete.\n')) 36 | console.log(chalk.yellow( 37 | ' Tip: built files are meant to be served over an HTTP server.\n' + 38 | ' Opening index.html over file:// won\'t work.\n' 39 | )) 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /frontend/build/check-versions.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const chalk = require('chalk') 3 | const semver = require('semver') 4 | const packageConfig = require('../package.json') 5 | const shell = require('shelljs') 6 | 7 | function exec (cmd) { 8 | return require('child_process').execSync(cmd).toString().trim() 9 | } 10 | 11 | const versionRequirements = [ 12 | { 13 | name: 'node', 14 | currentVersion: semver.clean(process.version), 15 | versionRequirement: packageConfig.engines.node 16 | } 17 | ] 18 | 19 | if (shell.which('npm')) { 20 | versionRequirements.push({ 21 | name: 'npm', 22 | currentVersion: exec('npm --version'), 23 | versionRequirement: packageConfig.engines.npm 24 | }) 25 | } 26 | 27 | module.exports = function () { 28 | const warnings = [] 29 | 30 | for (let i = 0; i < versionRequirements.length; i++) { 31 | const mod = versionRequirements[i] 32 | 33 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 34 | warnings.push(mod.name + ': ' + 35 | chalk.red(mod.currentVersion) + ' should be ' + 36 | chalk.green(mod.versionRequirement) 37 | ) 38 | } 39 | } 40 | 41 | if (warnings.length) { 42 | console.log('') 43 | console.log(chalk.yellow('To use this template, you must update following to modules:')) 44 | console.log() 45 | 46 | for (let i = 0; i < warnings.length; i++) { 47 | const warning = warnings[i] 48 | console.log(' ' + warning) 49 | } 50 | 51 | console.log() 52 | process.exit(1) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /frontend/build/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyEtherWallet/simplex-api/4c3a42d2e85bbdd107129477fc7f8008e564ea6a/frontend/build/logo.png -------------------------------------------------------------------------------- /frontend/build/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const config = require('../config') 4 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 5 | const packageConfig = require('../package.json') 6 | 7 | exports.assetsPath = function (_path) { 8 | const assetsSubDirectory = process.env.NODE_ENV === 'production' 9 | ? config.build.assetsSubDirectory 10 | : config.dev.assetsSubDirectory 11 | 12 | return path.posix.join(assetsSubDirectory, _path) 13 | } 14 | 15 | exports.cssLoaders = function (options) { 16 | options = options || {} 17 | 18 | const cssLoader = { 19 | loader: 'css-loader', 20 | options: { 21 | sourceMap: options.sourceMap 22 | } 23 | } 24 | 25 | const postcssLoader = { 26 | loader: 'postcss-loader', 27 | options: { 28 | sourceMap: options.sourceMap 29 | } 30 | } 31 | 32 | // generate loader string to be used with extract text plugin 33 | function generateLoaders (loader, loaderOptions) { 34 | const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader] 35 | 36 | if (loader) { 37 | loaders.push({ 38 | loader: loader + '-loader', 39 | options: Object.assign({}, loaderOptions, { 40 | sourceMap: options.sourceMap 41 | }) 42 | }) 43 | } 44 | 45 | // Extract CSS when that option is specified 46 | // (which is the case during production build) 47 | if (options.extract) { 48 | return ExtractTextPlugin.extract({ 49 | use: loaders, 50 | fallback: 'vue-style-loader' 51 | }) 52 | } else { 53 | return ['vue-style-loader'].concat(loaders) 54 | } 55 | } 56 | 57 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html 58 | return { 59 | css: generateLoaders(), 60 | postcss: generateLoaders(), 61 | less: generateLoaders('less'), 62 | sass: generateLoaders('sass', { indentedSyntax: true }), 63 | scss: generateLoaders('sass'), 64 | stylus: generateLoaders('stylus'), 65 | styl: generateLoaders('stylus') 66 | } 67 | } 68 | 69 | // Generate loaders for standalone style files (outside of .vue) 70 | exports.styleLoaders = function (options) { 71 | const output = [] 72 | const loaders = exports.cssLoaders(options) 73 | 74 | for (const extension in loaders) { 75 | const loader = loaders[extension] 76 | output.push({ 77 | test: new RegExp('\\.' + extension + '$'), 78 | use: loader 79 | }) 80 | } 81 | 82 | return output 83 | } 84 | 85 | exports.createNotifierCallback = () => { 86 | const notifier = require('node-notifier') 87 | 88 | return (severity, errors) => { 89 | if (severity !== 'error') return 90 | 91 | const error = errors[0] 92 | const filename = error.file && error.file.split('!').pop() 93 | 94 | notifier.notify({ 95 | title: packageConfig.name, 96 | message: severity + ': ' + error.name, 97 | subtitle: filename || '', 98 | icon: path.join(__dirname, 'logo.png') 99 | }) 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /frontend/build/vue-loader.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const utils = require('./utils') 3 | const config = require('../config') 4 | const isProduction = process.env.NODE_ENV === 'production' 5 | const sourceMapEnabled = isProduction 6 | ? config.build.productionSourceMap 7 | : config.dev.cssSourceMap 8 | 9 | module.exports = { 10 | loaders: utils.cssLoaders({ 11 | sourceMap: sourceMapEnabled, 12 | extract: isProduction 13 | }), 14 | cssSourceMap: sourceMapEnabled, 15 | cacheBusting: config.dev.cacheBusting, 16 | transformToRequire: { 17 | video: ['src', 'poster'], 18 | source: 'src', 19 | img: 'src', 20 | image: 'xlink:href' 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /frontend/build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const utils = require('./utils') 4 | const config = require('../config') 5 | const vueLoaderConfig = require('./vue-loader.conf') 6 | const Dotenv = require('dotenv-webpack') 7 | 8 | function resolve (dir) { 9 | return path.join(__dirname, '..', dir) 10 | } 11 | 12 | const envPath = path.join(path.resolve(__dirname, '../../'), '.env'); 13 | const createLintingRule = () => ({ 14 | test: /\.(js|vue)$/, 15 | loader: 'eslint-loader', 16 | enforce: 'pre', 17 | include: [resolve('src'), resolve('test')], 18 | options: { 19 | formatter: require('eslint-friendly-formatter'), 20 | emitWarning: !config.dev.showEslintErrorsInOverlay, 21 | fix: true 22 | } 23 | }) 24 | 25 | module.exports = { 26 | context: path.resolve(__dirname, '../'), 27 | entry: { 28 | app: './src/main.js' 29 | }, 30 | output: { 31 | path: config.build.assetsRoot, 32 | filename: '[name].js', 33 | publicPath: process.env.NODE_ENV === 'production' 34 | ? config.build.assetsPublicPath 35 | : config.dev.assetsPublicPath 36 | }, 37 | resolve: { 38 | extensions: ['.js', '.vue', '.json'], 39 | alias: { 40 | 'vue$': 'vue/dist/vue.esm.js', 41 | '@': resolve('src'), 42 | } 43 | }, 44 | plugins: [ 45 | new Dotenv({ 46 | path: envPath 47 | }) 48 | ], 49 | module: { 50 | rules: [ 51 | ...(config.dev.useEslint ? [createLintingRule()] : []), 52 | { 53 | test: /\.vue$/, 54 | loader: 'vue-loader', 55 | options: vueLoaderConfig 56 | }, 57 | { 58 | test: /\.js$/, 59 | loader: 'babel-loader', 60 | include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')] 61 | }, 62 | { 63 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 64 | loader: 'url-loader', 65 | options: { 66 | limit: 10000, 67 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 68 | } 69 | }, 70 | { 71 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 72 | loader: 'url-loader', 73 | options: { 74 | limit: 10000, 75 | name: utils.assetsPath('media/[name].[hash:7].[ext]') 76 | } 77 | }, 78 | { 79 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 80 | loader: 'url-loader', 81 | options: { 82 | limit: 10000, 83 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 84 | } 85 | } 86 | ] 87 | }, 88 | node: { 89 | // prevent webpack from injecting useless setImmediate polyfill because Vue 90 | // source contains it (although only uses it if it's native). 91 | setImmediate: false, 92 | // prevent webpack from injecting mocks to Node native modules 93 | // that does not make sense for the client 94 | dgram: 'empty', 95 | fs: 'empty', 96 | net: 'empty', 97 | tls: 'empty', 98 | child_process: 'empty' 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /frontend/build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const utils = require('./utils') 3 | const webpack = require('webpack') 4 | const config = require('../config') 5 | const merge = require('webpack-merge') 6 | const path = require('path') 7 | const baseWebpackConfig = require('./webpack.base.conf') 8 | const CopyWebpackPlugin = require('copy-webpack-plugin') 9 | const HtmlWebpackPlugin = require('html-webpack-plugin') 10 | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') 11 | const portfinder = require('portfinder') 12 | 13 | const HOST = process.env.HOST 14 | const PORT = process.env.PORT && Number(process.env.PORT) 15 | 16 | const devWebpackConfig = merge(baseWebpackConfig, { 17 | module: { 18 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true }) 19 | }, 20 | // cheap-module-eval-source-map is faster for development 21 | devtool: config.dev.devtool, 22 | 23 | // these devServer options should be customized in /config/index.js 24 | devServer: { 25 | clientLogLevel: 'warning', 26 | historyApiFallback: { 27 | rewrites: [ 28 | { from: /.*/, to: path.posix.join(config.dev.assetsPublicPath, 'index.html') }, 29 | ], 30 | }, 31 | hot: true, 32 | https: true, 33 | contentBase: false, // since we use CopyWebpackPlugin. 34 | compress: true, 35 | host: HOST || config.dev.host, 36 | port: PORT || config.dev.port, 37 | open: config.dev.autoOpenBrowser, 38 | overlay: config.dev.errorOverlay 39 | ? { warnings: false, errors: true } 40 | : false, 41 | publicPath: config.dev.assetsPublicPath, 42 | proxy: config.dev.proxyTable, 43 | quiet: true, // necessary for FriendlyErrorsPlugin 44 | watchOptions: { 45 | poll: config.dev.poll, 46 | }, 47 | disableHostCheck: true 48 | }, 49 | plugins: [ 50 | new webpack.DefinePlugin({ 51 | 'process.env': require('../config/dev.env') 52 | }), 53 | new webpack.HotModuleReplacementPlugin(), 54 | new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update. 55 | new webpack.NoEmitOnErrorsPlugin(), 56 | // https://github.com/ampedandwired/html-webpack-plugin 57 | new HtmlWebpackPlugin({ 58 | filename: 'index.html', 59 | template: 'index.html', 60 | inject: true 61 | }), 62 | // copy custom static assets 63 | new CopyWebpackPlugin([ 64 | { 65 | from: path.resolve(__dirname, '../static'), 66 | to: config.dev.assetsSubDirectory, 67 | ignore: ['.*'] 68 | } 69 | ]) 70 | ] 71 | }) 72 | 73 | module.exports = new Promise((resolve, reject) => { 74 | portfinder.basePort = process.env.PORT || config.dev.port 75 | portfinder.getPort((err, port) => { 76 | if (err) { 77 | reject(err) 78 | } else { 79 | // publish the new Port, necessary for e2e tests 80 | process.env.PORT = port 81 | // add port to devServer config 82 | devWebpackConfig.devServer.port = port 83 | 84 | // Add FriendlyErrorsPlugin 85 | devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({ 86 | compilationSuccessInfo: { 87 | messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`], 88 | }, 89 | onErrors: config.dev.notifyOnErrors 90 | ? utils.createNotifierCallback() 91 | : undefined 92 | })) 93 | 94 | resolve(devWebpackConfig) 95 | } 96 | }) 97 | }) 98 | -------------------------------------------------------------------------------- /frontend/build/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const utils = require('./utils') 4 | const webpack = require('webpack') 5 | const config = require('../config') 6 | const merge = require('webpack-merge') 7 | const baseWebpackConfig = require('./webpack.base.conf') 8 | const CopyWebpackPlugin = require('copy-webpack-plugin') 9 | const HtmlWebpackPlugin = require('html-webpack-plugin') 10 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 11 | const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') 12 | const UglifyJsPlugin = require('uglifyjs-webpack-plugin') 13 | 14 | const env = require('../config/prod.env') 15 | 16 | const webpackConfig = merge(baseWebpackConfig, { 17 | module: { 18 | rules: utils.styleLoaders({ 19 | sourceMap: config.build.productionSourceMap, 20 | extract: true, 21 | usePostCSS: true 22 | }) 23 | }, 24 | devtool: config.build.productionSourceMap ? config.build.devtool : false, 25 | output: { 26 | path: config.build.assetsRoot, 27 | filename: utils.assetsPath('js/[name].[chunkhash].js'), 28 | chunkFilename: utils.assetsPath('js/[id].[chunkhash].js') 29 | }, 30 | plugins: [ 31 | // http://vuejs.github.io/vue-loader/en/workflow/production.html 32 | new webpack.DefinePlugin({ 33 | 'process.env': env 34 | }), 35 | new UglifyJsPlugin({ 36 | uglifyOptions: { 37 | compress: { 38 | warnings: false 39 | } 40 | }, 41 | sourceMap: config.build.productionSourceMap, 42 | parallel: true 43 | }), 44 | // extract css into its own file 45 | new ExtractTextPlugin({ 46 | filename: utils.assetsPath('css/[name].[contenthash].css'), 47 | // Setting the following option to `false` will not extract CSS from codesplit chunks. 48 | // Their CSS will instead be inserted dynamically with style-loader when the codesplit chunk has been loaded by webpack. 49 | // It's currently set to `true` because we are seeing that sourcemaps are included in the codesplit bundle as well when it's `false`, 50 | // increasing file size: https://github.com/vuejs-templates/webpack/issues/1110 51 | allChunks: true, 52 | }), 53 | // Compress extracted CSS. We are using this plugin so that possible 54 | // duplicated CSS from different components can be deduped. 55 | new OptimizeCSSPlugin({ 56 | cssProcessorOptions: config.build.productionSourceMap 57 | ? { safe: true, map: { inline: false } } 58 | : { safe: true } 59 | }), 60 | // generate dist index.html with correct asset hash for caching. 61 | // you can customize output by editing /index.html 62 | // see https://github.com/ampedandwired/html-webpack-plugin 63 | new HtmlWebpackPlugin({ 64 | filename: config.build.index, 65 | template: 'index.html', 66 | inject: true, 67 | minify: { 68 | removeComments: true, 69 | collapseWhitespace: true, 70 | removeAttributeQuotes: true 71 | // more options: 72 | // https://github.com/kangax/html-minifier#options-quick-reference 73 | }, 74 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin 75 | chunksSortMode: 'dependency' 76 | }), 77 | // keep module.id stable when vendor modules does not change 78 | new webpack.HashedModuleIdsPlugin(), 79 | // enable scope hoisting 80 | new webpack.optimize.ModuleConcatenationPlugin(), 81 | // split vendor js into its own file 82 | new webpack.optimize.CommonsChunkPlugin({ 83 | name: 'vendor', 84 | minChunks (module) { 85 | // any required modules inside node_modules are extracted to vendor 86 | return ( 87 | module.resource && 88 | /\.js$/.test(module.resource) && 89 | module.resource.indexOf( 90 | path.join(__dirname, '../node_modules') 91 | ) === 0 92 | ) 93 | } 94 | }), 95 | // extract webpack runtime and module manifest to its own file in order to 96 | // prevent vendor hash from being updated whenever app bundle is updated 97 | new webpack.optimize.CommonsChunkPlugin({ 98 | name: 'manifest', 99 | minChunks: Infinity 100 | }), 101 | // This instance extracts shared chunks from code splitted chunks and bundles them 102 | // in a separate chunk, similar to the vendor chunk 103 | // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk 104 | new webpack.optimize.CommonsChunkPlugin({ 105 | name: 'app', 106 | async: 'vendor-async', 107 | children: true, 108 | minChunks: 3 109 | }), 110 | 111 | // copy custom static assets 112 | new CopyWebpackPlugin([ 113 | { 114 | from: path.resolve(__dirname, '../static'), 115 | to: config.build.assetsSubDirectory, 116 | ignore: ['.*'] 117 | } 118 | ]) 119 | ] 120 | }) 121 | 122 | if (config.build.productionGzip) { 123 | const CompressionWebpackPlugin = require('compression-webpack-plugin') 124 | 125 | webpackConfig.plugins.push( 126 | new CompressionWebpackPlugin({ 127 | asset: '[path].gz[query]', 128 | algorithm: 'gzip', 129 | test: new RegExp( 130 | '\\.(' + 131 | config.build.productionGzipExtensions.join('|') + 132 | ')$' 133 | ), 134 | threshold: 10240, 135 | minRatio: 0.8 136 | }) 137 | ) 138 | } 139 | 140 | if (config.build.bundleAnalyzerReport) { 141 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 142 | webpackConfig.plugins.push(new BundleAnalyzerPlugin()) 143 | } 144 | 145 | module.exports = webpackConfig 146 | -------------------------------------------------------------------------------- /frontend/config/dev.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const merge = require('webpack-merge') 4 | const prodEnv = require('./prod.env') 5 | module.exports = merge(prodEnv, { 6 | NODE_ENV: '"development"' 7 | }) 8 | -------------------------------------------------------------------------------- /frontend/config/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // Template version: 1.3.1 3 | // see http://vuejs-templates.github.io/webpack for documentation. 4 | 5 | const path = require('path') 6 | 7 | module.exports = { 8 | dev: { 9 | 10 | // Paths 11 | assetsSubDirectory: 'static', 12 | assetsPublicPath: '/', 13 | proxyTable: {}, 14 | 15 | // Various Dev Server settings 16 | host: 'localhost', // can be overwritten by process.env.HOST 17 | port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined 18 | autoOpenBrowser: false, 19 | errorOverlay: true, 20 | notifyOnErrors: true, 21 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- 22 | 23 | // Use Eslint Loader? 24 | // If true, your code will be linted during bundling and 25 | // linting errors and warnings will be shown in the console. 26 | useEslint: true, 27 | // If true, eslint errors and warnings will also be shown in the error overlay 28 | // in the browser. 29 | showEslintErrorsInOverlay: false, 30 | 31 | /** 32 | * Source Maps 33 | */ 34 | 35 | // https://webpack.js.org/configuration/devtool/#development 36 | devtool: 'cheap-module-eval-source-map', 37 | 38 | // If you have problems debugging vue-files in devtools, 39 | // set this to false - it *may* help 40 | // https://vue-loader.vuejs.org/en/options.html#cachebusting 41 | cacheBusting: true, 42 | 43 | cssSourceMap: true 44 | }, 45 | 46 | build: { 47 | // Template for index.html 48 | index: path.resolve(__dirname, '../dist/index.html'), 49 | 50 | // Paths 51 | assetsRoot: path.resolve(__dirname, '../dist'), 52 | assetsSubDirectory: 'static', 53 | assetsPublicPath: '/', 54 | 55 | /** 56 | * Source Maps 57 | */ 58 | 59 | productionSourceMap: true, 60 | // https://webpack.js.org/configuration/devtool/#production 61 | devtool: '#source-map', 62 | 63 | // Gzip off by default as many popular static hosts such as 64 | // Surge or Netlify already gzip all static assets for you. 65 | // Before setting to `true`, make sure to: 66 | // npm install --save-dev compression-webpack-plugin 67 | productionGzip: false, 68 | productionGzipExtensions: ['js', 'css'], 69 | 70 | // Run the build command with an extra argument to 71 | // View the bundle analyzer report after build finishes: 72 | // `npm run build --report` 73 | // Set to `true` or `false` to always turn it on or off 74 | bundleAnalyzerReport: process.env.npm_config_report 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /frontend/config/prod.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | NODE_ENV: '"production"' 4 | } 5 | -------------------------------------------------------------------------------- /frontend/currencyConfig.js: -------------------------------------------------------------------------------- 1 | const fiat = [ 2 | "USD", 3 | "EUR", 4 | "CAD", 5 | "JPY", 6 | "GBP", 7 | "RUB", 8 | "AUD", 9 | "KRW", 10 | "CHF", 11 | "CZK", 12 | "DKK", 13 | "NOK", 14 | "NZD", 15 | "PLN", 16 | "SEK", 17 | "TRY", 18 | "ZAR", 19 | "HUF", 20 | ]; 21 | const crypto = ["BTC", "ETH", "BNB", "MATIC"]; 22 | 23 | const handler = function (defaultValue = 42) { 24 | return { 25 | get: function (target, name) { 26 | return target.hasOwnProperty(name) ? target[name] : defaultValue; 27 | }, 28 | }; 29 | }; 30 | 31 | const minFiatTarget = { USD: 50, EUR: 50 }; 32 | const maxFiatTarget = { USD: 20000, EUR: 20000 }; 33 | 34 | const minFiat = new Proxy(minFiatTarget, handler(50)); 35 | const maxFiat = new Proxy(maxFiatTarget, handler(20000)); 36 | 37 | export { fiat, crypto, minFiat, maxFiat }; 38 | -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | MyEtherWallet Simplex 7 | 8 | 9 | 10 | 12 | 13 | 26 | 27 | 28 | 29 | 30 |
31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mew-simplex-frontend", 3 | "version": "1.0.0", 4 | "description": "A Vue.js project", 5 | "author": "David Hong ", 6 | "private": true, 7 | "scripts": { 8 | "dev": "webpack-dev-server --host 0.0.0.0 --inline --progress --config build/webpack.dev.conf.js", 9 | "start": "npm run dev", 10 | "lint": "eslint --ext .js,.vue src", 11 | "build": "node build/build.js", 12 | "prod": "history-server dist/" 13 | }, 14 | "dependencies": { 15 | "axios": "^0.18.0", 16 | "dotenv": "^6.0.0", 17 | "dotenv-webpack": "^1.5.7", 18 | "history-server": "^1.3.1", 19 | "lodash": "^4.17.10", 20 | "npm": "^6.1.0", 21 | "vue": "^2.5.2", 22 | "vue-router": "^3.0.1", 23 | "vuex": "^3.0.1", 24 | "wallet-address-validator": "^0.1.7", 25 | "xss": "^1.0.6" 26 | }, 27 | "devDependencies": { 28 | "autoprefixer": "^7.1.2", 29 | "babel-core": "^6.22.1", 30 | "babel-eslint": "^8.2.1", 31 | "babel-helper-vue-jsx-merge-props": "^2.0.3", 32 | "babel-loader": "^7.1.1", 33 | "babel-plugin-syntax-jsx": "^6.18.0", 34 | "babel-plugin-transform-runtime": "^6.22.0", 35 | "babel-plugin-transform-vue-jsx": "^3.5.0", 36 | "babel-preset-env": "^1.3.2", 37 | "babel-preset-stage-2": "^6.22.0", 38 | "bignumber.js": "^9.0.0", 39 | "chalk": "^2.0.1", 40 | "copy-webpack-plugin": "^4.0.1", 41 | "css-loader": "^0.28.0", 42 | "eslint": "^4.15.0", 43 | "eslint-config-standard": "^10.2.1", 44 | "eslint-friendly-formatter": "^3.0.0", 45 | "eslint-loader": "^1.7.1", 46 | "eslint-plugin-import": "^2.7.0", 47 | "eslint-plugin-node": "^5.2.0", 48 | "eslint-plugin-promise": "^3.4.0", 49 | "eslint-plugin-standard": "^3.0.1", 50 | "eslint-plugin-vue": "^4.0.0", 51 | "extract-text-webpack-plugin": "^3.0.0", 52 | "file-loader": "^1.1.4", 53 | "friendly-errors-webpack-plugin": "^1.6.1", 54 | "html-webpack-plugin": "^2.30.1", 55 | "node-notifier": "^5.1.2", 56 | "node-sass": "^4.9.0", 57 | "optimize-css-assets-webpack-plugin": "^3.2.0", 58 | "ora": "^1.2.0", 59 | "portfinder": "^1.0.13", 60 | "postcss-import": "^11.0.0", 61 | "postcss-loader": "^2.0.8", 62 | "postcss-url": "^7.2.1", 63 | "rimraf": "^2.6.0", 64 | "sass-loader": "^7.0.3", 65 | "semver": "^5.7.1", 66 | "shelljs": "^0.7.6", 67 | "style-loader": "^0.21.0", 68 | "uglifyjs-webpack-plugin": "^1.1.1", 69 | "url-loader": "^0.5.8", 70 | "vue-loader": "^13.3.0", 71 | "vue-recaptcha": "^1.1.1", 72 | "vue-style-loader": "^3.0.1", 73 | "vue-template-compiler": "^2.5.2", 74 | "webpack": "^3.6.0", 75 | "webpack-bundle-analyzer": "^2.9.0", 76 | "webpack-dev-server": "^2.9.1", 77 | "webpack-merge": "^4.1.0" 78 | }, 79 | "engines": { 80 | "node": ">= 6.0.0", 81 | "npm": ">= 3.0.0" 82 | }, 83 | "browserslist": [ 84 | "> 1%", 85 | "last 2 versions", 86 | "not ie <= 8" 87 | ] 88 | } 89 | -------------------------------------------------------------------------------- /frontend/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 17 | -------------------------------------------------------------------------------- /frontend/src/assets/images/background-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyEtherWallet/simplex-api/4c3a42d2e85bbdd107129477fc7f8008e564ea6a/frontend/src/assets/images/background-1.png -------------------------------------------------------------------------------- /frontend/src/assets/images/icons/calendar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyEtherWallet/simplex-api/4c3a42d2e85bbdd107129477fc7f8008e564ea6a/frontend/src/assets/images/icons/calendar.png -------------------------------------------------------------------------------- /frontend/src/assets/images/icons/clock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyEtherWallet/simplex-api/4c3a42d2e85bbdd107129477fc7f8008e564ea6a/frontend/src/assets/images/icons/clock.png -------------------------------------------------------------------------------- /frontend/src/assets/images/icons/money.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyEtherWallet/simplex-api/4c3a42d2e85bbdd107129477fc7f8008e564ea6a/frontend/src/assets/images/icons/money.png -------------------------------------------------------------------------------- /frontend/src/assets/images/icons/support.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyEtherWallet/simplex-api/4c3a42d2e85bbdd107129477fc7f8008e564ea6a/frontend/src/assets/images/icons/support.png -------------------------------------------------------------------------------- /frontend/src/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyEtherWallet/simplex-api/4c3a42d2e85bbdd107129477fc7f8008e564ea6a/frontend/src/assets/images/logo.png -------------------------------------------------------------------------------- /frontend/src/assets/images/simplex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyEtherWallet/simplex-api/4c3a42d2e85bbdd107129477fc7f8008e564ea6a/frontend/src/assets/images/simplex.png -------------------------------------------------------------------------------- /frontend/src/assets/images/visa-master.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyEtherWallet/simplex-api/4c3a42d2e85bbdd107129477fc7f8008e564ea6a/frontend/src/assets/images/visa-master.png -------------------------------------------------------------------------------- /frontend/src/components/Body/BuyCrypto/BuyCrypto.scss: -------------------------------------------------------------------------------- 1 | .buy-btc { 2 | padding-top: 90px; 3 | padding-bottom: 160px; 4 | @media all and (max-width: $mobile-width){ 5 | padding-top: 40px; 6 | padding-bottom: 60px; 7 | } 8 | } 9 | 10 | .powered-by, 11 | .powered-by-mobile { 12 | margin-top: 60px; 13 | .simplex { 14 | display: flex; 15 | align-items: center; 16 | margin-bottom: 20px; 17 | span { 18 | font-size: 14px; 19 | } 20 | img { 21 | height: 40px; 22 | margin-left: 15px; 23 | } 24 | } 25 | .visa-master { 26 | display: flex; 27 | align-items: center; 28 | span { 29 | font-size: 14px; 30 | } 31 | img { 32 | height: 25px; 33 | margin-left: 15px; 34 | } 35 | } 36 | } 37 | 38 | .powered-by-mobile { 39 | display: none; 40 | } 41 | 42 | @media all and (max-width: $mobile-width){ 43 | .powered-by-mobile { 44 | display: block; 45 | margin-top: 30px; 46 | } 47 | .powered-by { 48 | display: none; 49 | } 50 | 51 | } 52 | 53 | .invalid-field { 54 | border: solid 1px #d5202f !important; 55 | } 56 | 57 | .block-division { 58 | display: grid; 59 | grid-template-columns: 1fr 1fr; 60 | 61 | @media all and (max-width: $mobile-width){ 62 | grid-template-columns: 1fr; 63 | } 64 | 65 | .block-1 { 66 | .mew-logo-container{ 67 | .mew-logo-image { 68 | width: 200px; 69 | } 70 | } 71 | .text-contents { 72 | .large-title { 73 | font-size: 55px; 74 | font-weight: 700; 75 | line-height: 60px; 76 | margin-top: 100px; 77 | max-width: 350px; 78 | 79 | @media all and (max-width: $mobile-width){ 80 | font-size: 30px; 81 | line-height: 33px; 82 | margin-bottom: 40px; 83 | margin-top: 35px; 84 | max-width: 250px; 85 | } 86 | } 87 | } 88 | } 89 | 90 | .block-2 { 91 | .buy-form-container { 92 | box-shadow: 0 3px 14px 0 rgba(0, 0, 0, 0.05); 93 | border-radius: 16px; 94 | padding: 40px 35px; 95 | background-color: white; 96 | 97 | @media all and (max-width: $mobile-width){ 98 | padding: 30px 15px; 99 | } 100 | 101 | h4 { 102 | margin-bottom: 15px; 103 | text-transform: uppercase; 104 | } 105 | 106 | .price-amount { 107 | @media all and (max-width: $mobile-width){ 108 | grid-template-columns: 1fr; 109 | 110 | .price { 111 | margin-bottom: 40px; 112 | } 113 | } 114 | display: grid; 115 | grid-template-columns: 1fr 1fr; 116 | grid-column-gap: 20px; 117 | margin-bottom: 40px; 118 | 119 | .input-form { 120 | display: grid; 121 | grid-template-columns: 1fr 70px; 122 | 123 | input { 124 | width: 100%; 125 | background-color: #f8f8f8; 126 | border-radius: 5px 0 0 5px; 127 | padding: 14px; 128 | border: 1px solid #f8f8f8; 129 | } 130 | 131 | select { 132 | width: 100%; 133 | border: 0; 134 | background-color: black; 135 | border-radius: 0 5px 5px 0; 136 | padding: 15px 0 15px 10px; 137 | color: white; 138 | border-right: 6px solid black; 139 | 140 | 141 | option { 142 | background-color: white; 143 | color: black; 144 | margin: 5px 0; 145 | } 146 | } // select 147 | } // .input-form 148 | } // .price-amount 149 | 150 | .amount { 151 | 152 | } 153 | 154 | .btc-address { 155 | margin-bottom: 60px; 156 | 157 | 158 | h4 { 159 | span { 160 | float: right; 161 | font-size: 13px; 162 | font-weight: 400; 163 | text-transform: none; 164 | } 165 | } 166 | 167 | input { 168 | width: 100%; 169 | background-color: #f8f8f8; 170 | border: 0; 171 | border-radius: 5px; 172 | padding: 15px; 173 | 174 | &::placeholder { 175 | color: #bcc1c7; 176 | } 177 | } 178 | 179 | // Hack (need David's magic CSS) 180 | .loading-indicator{ 181 | position: absolute; 182 | display: inline-block; 183 | z-index: 1; 184 | text-align: center; 185 | right: 30%; 186 | top: 70%; 187 | } 188 | 189 | } 190 | 191 | .submit-button-container { 192 | text-align: center; 193 | 194 | .cant-continue{ 195 | background: #626262; 196 | } 197 | .button-1 { 198 | position: relative; 199 | i { 200 | position: absolute; 201 | right: 44px; 202 | top: 16px; 203 | font-size: 30px; 204 | } 205 | } 206 | 207 | @media all and (max-width: $mobile-width){ 208 | .button-1 { 209 | width: 100%; 210 | box-sizing: border-box; 211 | } 212 | } 213 | 214 | p { 215 | font-size: 14px; 216 | margin-top: 30px; 217 | } 218 | } 219 | } 220 | } 221 | } 222 | 223 | 224 | .recaptcha{ 225 | position: relative; 226 | margin-bottom: 30px; 227 | left: 15%; 228 | } 229 | 230 | @media (max-width: $mobile-width){ 231 | .recaptcha{ 232 | display: inline-block; 233 | margin-bottom: 30px; 234 | right: auto; 235 | left: auto; 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /frontend/src/components/Body/CheckoutForm/CheckoutForm.scss: -------------------------------------------------------------------------------- 1 | @import '@/var.scss'; 2 | @import '@/components/Body/BuyCrypto/BuyCrypto.scss'; 3 | -------------------------------------------------------------------------------- /frontend/src/components/Body/CheckoutForm/CheckoutForm.vue: -------------------------------------------------------------------------------- 1 | 23 | 41 | 45 | -------------------------------------------------------------------------------- /frontend/src/components/Body/OrderStatus/OrderStatus.scss: -------------------------------------------------------------------------------- 1 | @import '@/var.scss'; 2 | @import '@/components/Body/BuyCrypto/BuyCrypto.scss'; 3 | -------------------------------------------------------------------------------- /frontend/src/components/Body/OrderStatus/OrderStatus.vue: -------------------------------------------------------------------------------- 1 | 6 | 31 | 35 | -------------------------------------------------------------------------------- /frontend/src/components/Body/Promo1/Promo1.scss: -------------------------------------------------------------------------------- 1 | .promo1 { 2 | margin-bottom: 140px; 3 | position: relative; 4 | @media all and (max-width: $mobile-width){ 5 | margin-bottom: 80px; 6 | } 7 | 8 | .left-shapes { 9 | @media all and (max-width: $mobile-width){ 10 | display: none; 11 | } 12 | position: absolute; 13 | left: -110px; 14 | top: 0; 15 | transform: rotate(-45deg); 16 | 17 | .shape1 { 18 | position: absolute; 19 | border-radius: 200px; 20 | width: 200px; 21 | height: 50px; 22 | background-color: #04c699; 23 | top: 0; 24 | left: 0; 25 | } 26 | .shape2 { 27 | position: absolute; 28 | border-radius: 200px; 29 | width: 200px; 30 | height: 36px; 31 | background-color: #7852ed; 32 | top: 30px; 33 | left: -30px; 34 | } 35 | } 36 | } 37 | 38 | .component-title-container { 39 | margin-bottom: 50px; 40 | @media all and (max-width: $mobile-width){ 41 | h2 { 42 | font-size: 28px; 43 | line-height: 32px; 44 | } 45 | } 46 | } 47 | 48 | .promo-content-container { 49 | display: grid; 50 | grid-template-columns: 1fr 1fr 1fr; 51 | grid-column-gap: 20px; 52 | @media all and (max-width: $mobile-width){ 53 | grid-template-columns: 1fr; 54 | grid-row-gap: 30px; 55 | } 56 | 57 | >div { 58 | padding: 50px 30px 55px; 59 | border-top: 5px solid #04c699; 60 | box-shadow: 0 3px 14px 0 rgba(0, 0, 0, 0.05); 61 | 62 | 63 | h2 { 64 | color: #04c699; 65 | margin-bottom: 15px; 66 | } 67 | 68 | p { 69 | font-size: 20px; 70 | font-weight: 600; 71 | color: #8799ab; 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /frontend/src/components/Body/Promo1/Promo1.vue: -------------------------------------------------------------------------------- 1 | 28 | 36 | 40 | -------------------------------------------------------------------------------- /frontend/src/components/Body/Promo2/Promo2.scss: -------------------------------------------------------------------------------- 1 | .promo2 { 2 | margin-bottom: 100px; 3 | position: relative; 4 | overflow: hidden; 5 | @media all and (max-width: $mobile-width){ 6 | margin-bottom: 70px; 7 | } 8 | 9 | .right-shapes { 10 | @media all and (max-width: $mobile-width){ 11 | display: none; 12 | } 13 | position: absolute; 14 | right: -100px; 15 | top: 0; 16 | transform: rotate(135deg); 17 | 18 | .shape1 { 19 | position: absolute; 20 | border-radius: 200px; 21 | width: 200px; 22 | height: 30px; 23 | background-color: #7852ed; 24 | top: 0; 25 | left: 0; 26 | } 27 | .shape2 { 28 | position: absolute; 29 | border-radius: 200px; 30 | width: 200px; 31 | height: 36px; 32 | background-color: #04c699; 33 | top: 20px; 34 | left: -20px; 35 | } 36 | } 37 | } 38 | 39 | .component-title-container { 40 | margin-bottom: 50px; 41 | @media all and (max-width: $mobile-width){ 42 | margin-bottom: 30px; 43 | } 44 | } 45 | 46 | .promo-content-container { 47 | display: grid; 48 | grid-template-columns: 1fr 1fr; 49 | grid-column-gap: 80px; 50 | grid-row-gap: 70px; 51 | 52 | @media all and (max-width: $mobile-width){ 53 | grid-template-columns: 1fr; 54 | grid-row-gap: 40px; 55 | } 56 | 57 | >div { 58 | display: flex; 59 | align-items: center; 60 | @media all and (max-width: $mobile-width){ 61 | flex-direction: column; 62 | } 63 | 64 | .image-container { 65 | padding-right: 20px; 66 | @media all and (max-width: $mobile-width){ 67 | padding-right: 0; 68 | } 69 | } 70 | 71 | .text-container { 72 | @media all and (max-width: $mobile-width){ 73 | text-align: center; 74 | } 75 | 76 | h3 { 77 | margin-bottom: 13px; 78 | } 79 | p { 80 | line-height: 22px; 81 | } 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /frontend/src/components/Body/Promo2/Promo2.vue: -------------------------------------------------------------------------------- 1 | 52 | 60 | 64 | -------------------------------------------------------------------------------- /frontend/src/components/Footer/Footer.scss: -------------------------------------------------------------------------------- 1 | .promo3 { 2 | background-color: #202030; 3 | padding: 80px 0 0; 4 | @media all and (max-width: $mobile-width){ 5 | padding: 60px 0 0; 6 | } 7 | } 8 | 9 | .text-button-container { 10 | margin-bottom: 80px; 11 | display: flex; 12 | align-items: center; 13 | padding: 0 20px; 14 | @media all and (max-width: $mobile-width){ 15 | flex-direction: column; 16 | margin-bottom: 60px; 17 | } 18 | 19 | .text-block { 20 | @media all and (max-width: $mobile-width){ 21 | align-self: flex-start; 22 | } 23 | h1 { 24 | color: white; 25 | max-width: 450px; 26 | @media all and (max-width: $mobile-width){ 27 | max-width: 200px; 28 | } 29 | } 30 | } 31 | 32 | .button-block { 33 | margin-left: auto; 34 | @media all and (max-width: $mobile-width){ 35 | margin-left: initial; 36 | margin-top: 30px; 37 | width: 100%; 38 | .submit-button-container { 39 | .button-2 { 40 | width: 100%; 41 | box-sizing: border-box; 42 | } 43 | } 44 | } 45 | } 46 | 47 | } 48 | 49 | .footer-container { 50 | border-top: 1px solid #626262; 51 | display: flex; 52 | align-items: center; 53 | padding: 35px 20px; 54 | 55 | @media all and (max-width: $mobile-width){ 56 | text-align: center; 57 | flex-direction: column; 58 | padding: 55px 20px 20px; 59 | } 60 | 61 | .terms { 62 | color: #202030; 63 | @media all and (max-width: $mobile-width){ 64 | margin-bottom: 40px; 65 | } 66 | } 67 | 68 | .copyright { 69 | margin-left: auto; 70 | @media all and (max-width: $mobile-width){ 71 | margin: 0 0 30px 0; 72 | } 73 | p { 74 | color: #bcc1c7; 75 | display: inline; 76 | @media all and (max-width: $mobile-width){ 77 | display: block; 78 | margin-bottom: 10px; 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /frontend/src/components/Footer/Footer.vue: -------------------------------------------------------------------------------- 1 | 26 | 39 | 43 | -------------------------------------------------------------------------------- /frontend/src/components/Header/Header.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyEtherWallet/simplex-api/4c3a42d2e85bbdd107129477fc7f8008e564ea6a/frontend/src/components/Header/Header.scss -------------------------------------------------------------------------------- /frontend/src/components/Header/Header.vue: -------------------------------------------------------------------------------- 1 | 7 | 15 | 18 | -------------------------------------------------------------------------------- /frontend/src/components/LandingPage.scss: -------------------------------------------------------------------------------- 1 | .landing-page { 2 | background-image: url('~@/assets/images/background-1.png'); 3 | background-position: top -100px right; 4 | background-repeat: no-repeat; 5 | background-size: 987px; 6 | 7 | @media all and (max-width: $mobile-width){ 8 | background-position: top right -376px; 9 | background-size: 490px; 10 | } 11 | } -------------------------------------------------------------------------------- /frontend/src/components/LandingPage.vue: -------------------------------------------------------------------------------- 1 | 10 | 18 | 22 | -------------------------------------------------------------------------------- /frontend/src/config.js: -------------------------------------------------------------------------------- 1 | import {minFiat, maxFiat, fiat, crypto} from '../currencyConfig'; 2 | 3 | let simplex = { 4 | validFiat: process.env.FIAT_CURRENCIES.split(',') || fiat, 5 | validDigital: process.env.DIGITAL_CURRENCIES.split(',') || crypto, 6 | minFiat: minFiat, 7 | maxFiat: maxFiat 8 | }; 9 | let host = { 10 | url: process.env.API_HOST || 'http://172.20.0.2:8080' 11 | }; 12 | let recaptcha = { 13 | siteKey: process.env.RECAPTCHA_SITE_KEY || '' 14 | }; 15 | export { 16 | simplex, 17 | host, 18 | recaptcha 19 | }; 20 | -------------------------------------------------------------------------------- /frontend/src/main.js: -------------------------------------------------------------------------------- 1 | // The Vue build version to load with the `import` command 2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias. 3 | import Vue from 'vue' 4 | import App from './App' 5 | import router from './router' 6 | 7 | import Header from '@/components/Header/Header' 8 | import Footer from '@/components/Footer/Footer' 9 | import BuyCrypto from '@/components/Body/BuyCrypto/BuyCrypto' 10 | import CheckoutForm from '@/components/Body/CheckoutForm/CheckoutForm' 11 | import Promo1 from '@/components/Body/Promo1/Promo1' 12 | import Promo2 from '@/components/Body/Promo2/Promo2' 13 | import store from '@/store' 14 | 15 | Vue.component('page-header', Header) 16 | Vue.component('page-footer', Footer) 17 | Vue.component('buy-crypto', BuyCrypto) 18 | Vue.component('checkout-form', CheckoutForm) 19 | Vue.component('promo1', Promo1) 20 | Vue.component('promo2', Promo2) 21 | 22 | Vue.config.productionTip = false 23 | 24 | /* eslint-disable no-new */ 25 | new Vue({ 26 | el: '#app', 27 | router, 28 | store, 29 | components: { 30 | App 31 | }, 32 | template: '' 33 | }) 34 | -------------------------------------------------------------------------------- /frontend/src/main.scss: -------------------------------------------------------------------------------- 1 | html, body, div, span, applet, object, iframe, 2 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 3 | a, abbr, acronym, address, big, cite, code, 4 | del, dfn, em, img, ins, kbd, q, s, samp, 5 | small, strike, strong, sub, sup, tt, var, 6 | b, u, center, 7 | dl, dt, dd, ol, ul, li, 8 | fieldset, form, label, legend, 9 | table, caption, tbody, tfoot, thead, tr, th, td, 10 | article, aside, canvas, details, embed, 11 | figure, figcaption, footer, header, hgroup, 12 | menu, nav, output, ruby, section, summary, 13 | time, mark, audio, video { 14 | margin: 0; 15 | padding: 0; 16 | border: 0; 17 | font-size: 100%; 18 | font: inherit; 19 | vertical-align: baseline; 20 | } 21 | /* HTML5 display-role reset for older browsers */ 22 | article, aside, details, figcaption, figure, 23 | footer, header, hgroup, menu, nav, section { 24 | display: block; 25 | } 26 | body { 27 | line-height: 1; 28 | } 29 | ol, ul { 30 | list-style: none; 31 | } 32 | blockquote, q { 33 | quotes: none; 34 | } 35 | blockquote:before, blockquote:after, 36 | q:before, q:after { 37 | content: ''; 38 | content: none; 39 | } 40 | table { 41 | border-collapse: collapse; 42 | border-spacing: 0; 43 | } 44 | 45 | input, select { 46 | font-size: 14px; 47 | outline: 0; 48 | box-sizing: border-box; 49 | } 50 | 51 | .page-container { 52 | margin: 0 auto; 53 | max-width: 1000px; 54 | padding-left: 20px; 55 | padding-right: 20px; 56 | } 57 | 58 | body { 59 | font-family: 'Open Sans', sans-serif; 60 | box-sizing: border-box; 61 | } 62 | 63 | 64 | p, span { 65 | font-size: 16px; 66 | color: #8799ab; 67 | } 68 | 69 | h1 { 70 | font-size: 40px; 71 | font-weight: 700; 72 | line-height: 50px; 73 | @media all and (max-width: $mobile-width){ 74 | font-size: 30px; 75 | line-height: 35px; 76 | } 77 | } 78 | 79 | h2 { 80 | @media all and (max-width: $mobile-width){ 81 | font-size: 28px; 82 | line-height: 33px; 83 | } 84 | 85 | color: #202030; 86 | font-weight: 700; 87 | font-size: 30px; 88 | max-width: 550px; 89 | line-height: 37px; 90 | } 91 | 92 | h3 { 93 | font-size: 24px; 94 | font-weight: 600; 95 | color: #202030; 96 | } 97 | 98 | h4 { 99 | color: #083d43; 100 | font-weight: 700; 101 | font-size: 16px; 102 | } 103 | 104 | 105 | 106 | // Buttons 107 | .button-1 { 108 | background-color: #04c699; 109 | padding: 23px; 110 | border-radius: 100px; 111 | color: white; 112 | font-weight: 600; 113 | text-align: center; 114 | width: 250px; 115 | display: inline-block; 116 | cursor: pointer; 117 | //margin-left: 70px; 118 | &.disable{ 119 | cursor: not-allowed; 120 | color: #8799ab; 121 | } 122 | } 123 | 124 | .button-2 { 125 | background-color: #04c699; 126 | padding: 23px 73px; 127 | border-radius: 100px; 128 | color: white; 129 | font-weight: 600; 130 | text-align: center; 131 | display: inline-block; 132 | cursor: pointer; 133 | } 134 | 135 | 136 | -------------------------------------------------------------------------------- /frontend/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import store from '@/store' 4 | import LandingPage from '@/components/LandingPage' 5 | import OrderStatus from '@/components/Body/OrderStatus/OrderStatus' 6 | import xss from 'xss' 7 | 8 | Vue.use(Router) 9 | 10 | const router = new Router({ 11 | routes: [ 12 | { 13 | path: '/', 14 | name: 'LandingPage', 15 | component: LandingPage 16 | }, { 17 | path: '/status/:userId', 18 | name: 'OrderStatus', 19 | component: OrderStatus, 20 | props: true 21 | } 22 | ] 23 | }) 24 | 25 | router.beforeResolve((to, ___, next) => { 26 | const queryKeys = Object.keys(to.query) 27 | if (queryKeys.length > 0) { 28 | const blankObj = {} 29 | for (const key in to.query) { 30 | blankObj[key] = xss(to.query[key]) 31 | } 32 | store.dispatch('saveQueryVal', blankObj) 33 | next() 34 | } else { 35 | next() 36 | } 37 | }) 38 | 39 | export default router 40 | -------------------------------------------------------------------------------- /frontend/src/simplex-api/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | host 3 | } from '@/config' 4 | import axios from 'axios' 5 | 6 | let getQuote = (reqObj) => { 7 | return axios.post(`${host.url}/quote`, reqObj) 8 | } 9 | let getOrder = (reqObj) => { 10 | return axios.post(`${host.url}/order`, reqObj) 11 | } 12 | let getStatus = (userId) => { 13 | return axios.get(`${host.url}/status/${userId}`) 14 | } 15 | let exchangeRates = (userId) => { 16 | return axios.get(`${host.url}/exchange-rates/`) 17 | } 18 | export { 19 | getQuote, 20 | getOrder, 21 | getStatus, 22 | exchangeRates 23 | } 24 | -------------------------------------------------------------------------------- /frontend/src/store/actions.js: -------------------------------------------------------------------------------- 1 | import wav from 'wallet-address-validator'; 2 | import { simplex } from '@/config.js'; 3 | import { getQuote, exchangeRates } from '@/simplex-api'; 4 | 5 | var quoteChanges = Object.freeze({ 6 | fiat_amount: 1, 7 | digital_amount: 2, 8 | fiat_currency: 3, 9 | digital_currency: 4 10 | }); 11 | 12 | let canQuote = state => 13 | !state.status.invalidFiatAmount || !state.status.invalidDigitalAmount; 14 | let updateValues = (qChange, { state, dispatch, commit }) => { 15 | return new Promise((resolve, reject) => { 16 | let onSuccess = result => { 17 | const resp = result.data; 18 | if (!resp.error) { 19 | commit('setDigitalAmount', resp.result.digital_money.amount); 20 | commit('setFiatAmount', resp.result.fiat_money.base_amount); 21 | commit('setFiatTotal', resp.result.fiat_money.total_amount); 22 | commit('setInvalidDigitalAmount', false); 23 | const isInvalidFiat = 24 | resp.result.fiat_money.total_amount < 25 | state.minFiat[state.orderInfo.fiatCurrency] || 26 | resp.result.fiat_money.total_amount > 27 | state.maxFiat[state.orderInfo.fiatCurrency]; 28 | commit('setInvalidFiatAmount', isInvalidFiat); 29 | commit('setUserId', resp.result.user_id); 30 | commit('setQuoteId', resp.result.quote_id); 31 | resolve(); 32 | } else { 33 | console.error(resp.result); 34 | reject(resp); 35 | } 36 | }; 37 | let onError = err => { 38 | console.error(err); 39 | reject(err); 40 | }; 41 | if (canQuote(state)) { 42 | switch (qChange) { 43 | case quoteChanges.fiat_amount: 44 | commit('setRequestedCurrency', state.orderInfo.fiatCurrency); 45 | getQuote({ 46 | digital_currency: state.orderInfo.digitalCurrency, 47 | fiat_currency: state.orderInfo.fiatCurrency, 48 | requested_currency: state.orderInfo.fiatCurrency, 49 | requested_amount: state.orderInfo.fiatAmount 50 | }) 51 | .then(onSuccess) 52 | .catch(onError); 53 | break; 54 | case quoteChanges.fiat_currency: 55 | commit('setRequestedCurrency', state.orderInfo.fiatCurrency); 56 | getQuote({ 57 | digital_currency: state.orderInfo.digitalCurrency, 58 | fiat_currency: state.orderInfo.fiatCurrency, 59 | requested_currency: state.orderInfo.digitalCurrency, 60 | requested_amount: state.orderInfo.digitalAmount 61 | }) 62 | .then(onSuccess) 63 | .catch(onError); 64 | break; 65 | case quoteChanges.digital_amount: 66 | case quoteChanges.digital_currency: 67 | commit('setRequestedCurrency', state.orderInfo.digitalCurrency); 68 | getQuote({ 69 | digital_currency: state.orderInfo.digitalCurrency, 70 | fiat_currency: state.orderInfo.fiatCurrency, 71 | requested_currency: state.orderInfo.digitalCurrency, 72 | requested_amount: state.orderInfo.digitalAmount 73 | }) 74 | .then(onSuccess) 75 | .catch(onError); 76 | break; 77 | } 78 | } else { 79 | reject(new Error('canQuote false')); 80 | } 81 | }); 82 | }; 83 | 84 | // ?to=0x9C483A851938d1C77a5e7e50eFDA4751A3637215&amount=2&fiat=eur 85 | // ?to=0x9C483A851938d1C77a5e7e50eFDA4751A3637215&amount=2 86 | // ?amount=2 87 | export default { 88 | setCurrencyMaxAndMins ({ dispatch, commit, state }) { 89 | exchangeRates().then(rawResult => { 90 | const result = rawResult.data.result.rates; 91 | const minFiat = {}; 92 | const maxFiat = {}; 93 | simplex.validFiat.forEach(item => { 94 | const details = result.find(entry => entry.rate_currency === item); 95 | if (details) { 96 | minFiat[item] = details.min; 97 | maxFiat[item] = details.max; 98 | } 99 | }); 100 | commit('setMinFiat', minFiat); 101 | commit('setMaxFiat', maxFiat); 102 | }); 103 | }, 104 | saveQueryVal ({ dispatch, commit, state }, val) { 105 | if (val.amount && !val.fiat) { 106 | dispatch('setDigitalAmount', +val.amount); 107 | } else if (val.fiat && !val.amount) { 108 | const upperFiat = val.fiat.toUpperCase(); 109 | if (simplex.validFiat.includes(upperFiat)) { 110 | dispatch('setFiatCurrency', val.fiat.toUpperCase()); 111 | } 112 | } else if (val.amount && val.fiat) { 113 | dispatch('setDigitalAmount', +val.amount).then(() => { 114 | const upperFiat = val.fiat.toUpperCase(); 115 | if (simplex.validFiat.includes(upperFiat)) { 116 | dispatch('setFiatCurrency', val.fiat.toUpperCase()).then(() => { 117 | setTimeout(() => { 118 | dispatch('setDigitalAmount', +val.amount); 119 | }, 500); 120 | }); 121 | } 122 | }); 123 | } 124 | if (val.to) { 125 | dispatch('setDigitalAddress', val.to); 126 | } 127 | 128 | commit('saveQueryValue', val); 129 | }, 130 | setDigitalAddress ({ commit, state }, address) { 131 | let cur = state.orderInfo.digitalCurrency; 132 | cur = cur === 'BNB' || cur === 'MATIC' ? 'ETH' : cur; 133 | if (wav.validate(address, cur)) { 134 | commit('setDigitalAddress', address); 135 | commit('setInvalidAddress', false); 136 | } else { 137 | commit('setInvalidAddress', true); 138 | commit('setDigitalAddress', ''); 139 | } 140 | }, 141 | setFiatAmount ({ commit, state }, amount) { 142 | commit('setFiatAmount', amount); 143 | if ( 144 | amount >= state.minFiat[state.orderInfo.fiatCurrency] && 145 | amount <= state.maxFiat[state.orderInfo.fiatCurrency] 146 | ) { 147 | commit('setInvalidFiatAmount', false); 148 | commit('setInvalidFiatAbove', false); 149 | commit('setInvalidFiatBelow', false); 150 | return updateValues(quoteChanges.fiat_amount, { 151 | commit, 152 | state 153 | }); 154 | } else { 155 | commit('setInvalidFiatAmount', true); 156 | if (amount >= state.maxFiat[state.orderInfo.fiatCurrency]) { 157 | commit('setInvalidFiatAbove', true); 158 | commit('setInvalidFiatBelow', false); 159 | } 160 | if (amount <= state.minFiat[state.orderInfo.fiatCurrency]) { 161 | commit('setInvalidFiatBelow', true); 162 | commit('setInvalidFiatAbove', false); 163 | } 164 | return Promise.resolve(); 165 | } 166 | }, 167 | setDigitalAmount ({ commit, state }, amount) { 168 | commit('setDigitalAmount', amount); 169 | if (amount > 0) { 170 | commit('setInvalidDigitalAmount', false); 171 | return updateValues(quoteChanges.digital_amount, { 172 | commit, 173 | state 174 | }); 175 | } else { 176 | commit('setInvalidDigitalAmount', true); 177 | return Promise.resolve(); 178 | } 179 | }, 180 | setFiatCurrency ({ commit, state }, currency) { 181 | commit('setFiatCurrency', currency); 182 | updateValues(quoteChanges.fiat_currency, { 183 | commit, 184 | state 185 | }); 186 | }, 187 | setDigitalCurrency ({ commit, state }, currency) { 188 | commit('setDigitalCurrency', currency); 189 | updateValues(quoteChanges.digital_currency, { 190 | commit, 191 | state 192 | }); 193 | } 194 | }; 195 | -------------------------------------------------------------------------------- /frontend/src/store/getters.js: -------------------------------------------------------------------------------- 1 | export default {} 2 | -------------------------------------------------------------------------------- /frontend/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import actions from './actions' 4 | import mutations from './mutations' 5 | import getters from './getters' 6 | import state from './state' 7 | Vue.use(Vuex) 8 | const store = new Vuex.Store({ 9 | state: state, 10 | actions: actions, 11 | mutations: mutations, 12 | getters: getters 13 | }) 14 | 15 | export default store 16 | -------------------------------------------------------------------------------- /frontend/src/store/mutations.js: -------------------------------------------------------------------------------- 1 | export default { 2 | setMinFiat (state, _minFiat) { 3 | state.minFiat = _minFiat 4 | }, 5 | setMaxFiat (state, _maxFiat) { 6 | state.maxFiat = _maxFiat 7 | }, 8 | setFiatCurrency (state, _fiatCurrency) { 9 | state.orderInfo.fiatCurrency = _fiatCurrency 10 | }, 11 | setDigitalCurrency (state, _digitalCurrency) { 12 | state.orderInfo.digitalCurrency = _digitalCurrency 13 | }, 14 | setFiatAmount (state, _fiatAmount) { 15 | state.orderInfo.fiatAmount = _fiatAmount 16 | }, 17 | setFiatTotal (state, _fiatTotal) { 18 | state.orderInfo.fiatTotal = _fiatTotal 19 | }, 20 | setDigitalAmount (state, _digitalAmount) { 21 | state.orderInfo.digitalAmount = _digitalAmount 22 | }, 23 | setDigitalAddress (state, _address) { 24 | state.orderInfo.digitalAddress = _address 25 | }, 26 | setInvalidAddress (state, _isInvalid) { 27 | state.status.invalidAddress = _isInvalid 28 | }, 29 | setInvalidFiatAmount (state, _isInvalid) { 30 | state.status.invalidFiatAmount = _isInvalid 31 | }, 32 | setInvalidFiatAbove (state, _isInvalidAbove) { 33 | state.status.invalidFiatAbove = _isInvalidAbove 34 | }, 35 | setInvalidFiatBelow (state, _isInvalidBelow) { 36 | state.status.invalidFiatBelow = _isInvalidBelow 37 | }, 38 | setInvalidDigitalAmount (state, _isInvalid) { 39 | state.status.invalidDigitalAmount = _isInvalid 40 | }, 41 | setRequestedCurrency (state, _currency) { 42 | state.orderInfo.requestedCurrency = _currency 43 | }, 44 | setUserId (state, _id) { 45 | state.orderInfo.userId = _id 46 | }, 47 | setQuoteId (state, _id) { 48 | state.orderInfo.quoteId = _id 49 | }, 50 | saveQueryValue (state, newQuery) { 51 | state.linkQuery = newQuery 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /frontend/src/store/state.js: -------------------------------------------------------------------------------- 1 | 2 | const handler = function (defaultValue = 42) { 3 | return { 4 | get: function (target, name) { 5 | return target.hasOwnProperty(name) ? target[name] : defaultValue; 6 | } 7 | }; 8 | }; 9 | 10 | export default { 11 | status: { 12 | invalidFiatAmount: true, 13 | invalidDigitalAmount: true, 14 | invalidAddress: true, 15 | invalidFiatAbove: false, 16 | invalidFiatBelow: false 17 | }, 18 | orderInfo: { 19 | fiatCurrency: 'USD', 20 | digitalCurrency: 'ETH', 21 | requestedCurrency: 'ETH', 22 | fiatAmount: 0, 23 | fiatTotal: 0, 24 | digitalAmount: 1, 25 | digitalAddress: '', 26 | userId: '', 27 | linkQuery: {} 28 | }, 29 | minFiat: new Proxy({USD: 50}, handler(50)), 30 | maxFiat: new Proxy({USD: 20000}, handler(20000)) 31 | }; 32 | -------------------------------------------------------------------------------- /frontend/src/var.scss: -------------------------------------------------------------------------------- 1 | $mobile-width: 800px; -------------------------------------------------------------------------------- /frontend/static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyEtherWallet/simplex-api/4c3a42d2e85bbdd107129477fc7f8008e564ea6a/frontend/static/.gitkeep -------------------------------------------------------------------------------- /frontend/static/images/icons/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MyEtherWallet/simplex-api/4c3a42d2e85bbdd107129477fc7f8008e564ea6a/frontend/static/images/icons/favicon.png --------------------------------------------------------------------------------