├── .gitignore ├── Cakefile ├── Gruntfile.coffee ├── Makefile ├── README.md ├── admin.js ├── app.js ├── assets ├── css │ ├── admin.styl │ ├── auth.styl │ ├── chat.styl │ ├── errors.styl │ ├── font.styl │ ├── framework.styl │ ├── main.styl │ ├── mixins.styl │ ├── plugins │ │ └── cdn.js │ ├── static.styl │ ├── trade.styl │ ├── vendor │ │ ├── jquery.jgrowl.css │ │ └── normalize.css │ └── wallets.styl └── js │ ├── admin.js │ ├── admin_bootstrap.coffee │ ├── app │ ├── collections │ │ ├── order_logs.coffee │ │ ├── orders.coffee │ │ ├── payments.coffee │ │ ├── trade_stats.coffee │ │ ├── transactions.coffee │ │ └── wallets.coffee │ ├── error_logger.coffee │ ├── helpers │ │ ├── crypto_currency.coffee │ │ └── json.coffee │ ├── models │ │ ├── google_auth.coffee │ │ ├── market_stats.coffee │ │ ├── order.coffee │ │ ├── order_log.coffee │ │ ├── payment.coffee │ │ ├── transaction.coffee │ │ ├── user.coffee │ │ └── wallet.coffee │ └── views │ │ ├── finances.coffee │ │ ├── market_ticker.coffee │ │ ├── master.coffee │ │ ├── order_book.coffee │ │ ├── order_logs.coffee │ │ ├── orders.coffee │ │ ├── pending_transactions.coffee │ │ ├── settings.coffee │ │ ├── trade.coffee │ │ ├── trade_chart.coffee │ │ └── transactions_history.coffee │ ├── app_bootstrap.coffee │ ├── application.js │ ├── auth_bootstrap.coffee │ ├── chat │ ├── collections │ │ └── chat_messages.coffee │ ├── models │ │ └── chat_message.coffee │ └── views │ │ └── chat.coffee │ ├── chat_bootstrap.coffee │ └── vendor │ ├── BigInt.js │ ├── ZeroClipboard.js │ ├── ba-tiny-pubsub.js │ ├── backbone.csrf.js │ ├── backbone.js │ ├── date.format.js │ ├── highstock.exporting.js │ ├── highstock.js │ ├── jquery-1.10.2.js │ ├── jquery.jgrowl.js │ ├── jquery.tmpload.js │ ├── jquery.validate.js │ ├── mailcheck.js │ ├── math.js │ ├── modernizr-2.7.1.min.js │ ├── qrcode.js │ ├── sha256.js │ ├── socket.io.js │ ├── underscore.js │ ├── underscore.string.js │ ├── zxcvbn-async.js │ └── zxcvbn.js ├── config.json.sample ├── configs ├── config.coffee ├── config.js ├── logger.coffee ├── logger.js ├── wallets.coffee ├── wallets.js └── wallets_config │ └── btc.json.sample ├── core_api.js ├── cron.js ├── cron_dump ├── currency.dump.sample └── currency_exclude.dump.sample ├── lib ├── admin_auth.coffee ├── admin_auth.js ├── auth.coffee ├── auth.js ├── client_socket.coffee ├── client_socket.js ├── core_api_client.coffee ├── core_api_client.js ├── crypto_wallet.coffee ├── crypto_wallet.js ├── crypto_wallets │ ├── doge_wallet.coffee │ └── doge_wallet.js ├── emailer.coffee ├── emailer.js ├── fraud_helper.coffee ├── fraud_helper.js ├── json_beautifier.coffee ├── json_beautifier.js ├── json_renderer.coffee ├── json_renderer.js ├── market_helper.coffee ├── market_helper.js ├── market_settings.coffee ├── market_settings.js ├── math.coffee ├── math.js ├── queue │ ├── event.coffee │ ├── event.js │ ├── index.coffee │ ├── index.js │ └── migrations │ │ ├── 20140522192430-add_indexes.coffee │ │ └── 20140522192430-add_indexes.js ├── sockets.coffee ├── sockets.js ├── trade_helper.coffee ├── trade_helper.js ├── transaction_helper.coffee ├── transaction_helper.js ├── underscore_string.coffee └── underscore_string.js ├── models ├── admin_user.coffee ├── admin_user.js ├── auth_stats.coffee ├── auth_stats.js ├── chat.coffee ├── chat.js ├── index.coffee ├── index.js ├── market_stats.coffee ├── market_stats.js ├── migrations │ ├── 20140330192430-add_indexes.coffee │ ├── 20140330192430-add_indexes.js │ ├── 20140425141930-alter_unsigned_amount.coffee │ ├── 20140425141930-alter_unsigned_amount.js │ ├── 20140505195830-add_order_log_indexes.coffee │ ├── 20140505195830-add_order_log_indexes.js │ ├── 20140507195900-alter_unsigned.coffee │ ├── 20140507195900-alter_unsigned.js │ ├── 20140524211930-add_indexes.coffee │ ├── 20140524211930-add_indexes.js │ ├── 20140607215430-add_indexes.coffee │ ├── 20140607215430-add_indexes.js │ ├── 20140610135430-add_indexes.coffee │ └── 20140610135430-add_indexes.js ├── order.coffee ├── order.js ├── order_log.coffee ├── order_log.js ├── payment.coffee ├── payment.js ├── payment_log.coffee ├── payment_log.js ├── seeds │ ├── market_stats.coffee │ ├── market_stats.js │ ├── trade_stats.coffee │ └── trade_stats.js ├── trade_stats.coffee ├── trade_stats.js ├── transaction.coffee ├── transaction.js ├── user.coffee ├── user.js ├── user_token.coffee ├── user_token.js ├── wallet.coffee ├── wallet.js ├── wallet_health.coffee └── wallet_health.js ├── package.json ├── public ├── 503.html ├── ZeroClipboard.swf ├── favicon.ico ├── favicon_admin.ico ├── fonts │ ├── icons.eot │ ├── icons.svg │ ├── icons.ttf │ └── icons.woff ├── img │ ├── appstore-sprite.png │ ├── coin-icons.png │ ├── email │ │ └── logo.png │ ├── hero │ │ ├── coins.svg │ │ ├── hero-bg.png │ │ ├── safe.svg │ │ ├── save.svg │ │ └── support.svg │ ├── logo-gray.png │ ├── logo-gray@2x.png │ ├── logo-white.png │ ├── logo-white@2x.png │ ├── logo.png │ └── logo@2x.png └── robots.txt ├── queue.js ├── routes ├── admin.coffee ├── admin.js ├── api.coffee ├── api.js ├── auth.coffee ├── auth.js ├── chat.coffee ├── chat.js ├── core_api │ ├── stats.coffee │ ├── stats.js │ ├── trade.coffee │ ├── trade.js │ ├── transactions.coffee │ ├── transactions.js │ ├── wallets.coffee │ └── wallets.js ├── errors.coffee ├── errors.js ├── order_logs.coffee ├── order_logs.js ├── orders.coffee ├── orders.js ├── payments.coffee ├── payments.js ├── site.coffee ├── site.js ├── transactions.coffee ├── transactions.js ├── users.coffee ├── users.js ├── wallets.coffee └── wallets.js ├── tests ├── helpers │ ├── auth_helper.js │ ├── btc_wallet_mock.coffee │ ├── btc_wallet_mock.js │ ├── ltc_wallet_mock.coffee │ ├── ltc_wallet_mock.js │ └── spec_helper.js ├── integrational │ └── routes │ │ ├── core_api │ │ ├── stats.coffee │ │ ├── trade.coffee │ │ └── transactions.coffee │ │ └── orders.coffee └── unit │ ├── lib │ ├── fraud_helper.coffee │ └── market_helper.coffee │ └── models │ ├── market_stats.coffee │ ├── order.coffee │ ├── payment.coffee │ ├── transaction.coffee │ ├── user.coffee │ └── wallet.coffee └── views ├── _analytics.jade ├── _config.jade ├── admin.jade ├── admin ├── login.jade ├── markets.jade ├── payments.jade ├── stats.jade ├── transactions.jade ├── user.jade ├── users.jade ├── wallet.jade └── wallets.jade ├── auth.jade ├── auth ├── change_password.jade ├── login.jade ├── resend_verify_link.jade ├── send_password.jade ├── signup.jade └── verify.jade ├── emails ├── change_password.html ├── confirm_email.html └── user_login_notice.html ├── errors.jade ├── errors ├── 404.jade └── 500.jade ├── layout.jade ├── site ├── _chatbox.jade ├── _footer.jade ├── _header.jade ├── _market-ticker.jade ├── _warnings.jade ├── funds.jade ├── funds │ ├── _funds_list.jade │ └── wallet.jade ├── index.jade ├── settings │ ├── preferences.jade │ ├── security.jade │ └── settings.jade ├── status.jade └── trade.jade ├── static ├── api.jade ├── cookie.jade ├── fees.jade ├── privacy.jade ├── security.jade └── terms.jade └── templates ├── chat_message.jade ├── coin_stats.jade ├── market_ticker.jade ├── open_order.jade ├── order_book_order.jade ├── pending_transaction.jade ├── site_closed_order.jade ├── transaction_history.jade ├── wallet_closed_order.jade └── wallet_open_order.jade /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | 10 | pids 11 | logs 12 | results 13 | 14 | npm-debug.log 15 | node_modules 16 | 17 | .DS_Store 18 | .idea 19 | .idea/* 20 | .settings.xml 21 | .monitor 22 | .env 23 | crash.log 24 | builtAssets 25 | config.json 26 | erl_crash.dump 27 | certs 28 | certs/* 29 | 30 | configs/wallets_config/*.json 31 | 32 | cron_dump/*.dump 33 | cron_dump/currency.dump 34 | cron_dump/currency_exclude.dump 35 | -------------------------------------------------------------------------------- /Gruntfile.coffee: -------------------------------------------------------------------------------- 1 | # global module:false 2 | module.exports = (grunt) -> 3 | 4 | coffeeRename = (dest, src)-> 5 | dest + '/' + src.replace(/\.coffee$/, '.js') 6 | 7 | # Project configuration. 8 | grunt.initConfig 9 | watch: 10 | tasks: ["coffee"] 11 | files: ["configs/**/*.coffee", "lib/**/*.coffee", "models/**/*.coffee", "routes/**/*.coffee", "tests/helpers/*.coffee"] 12 | options: 13 | spawn: false 14 | 15 | coffee: 16 | compile: 17 | files: [ 18 | {expand: true, cwd: 'configs', src: ['**/*.coffee'], dest: 'configs', rename: coffeeRename} 19 | {expand: true, cwd: 'models', src: ['**/*.coffee'], dest: 'models', rename: coffeeRename} 20 | {expand: true, cwd: 'lib', src: ['**/*.coffee'], dest: 'lib', rename: coffeeRename} 21 | {expand: true, cwd: 'routes', src: ['**/*.coffee'], dest: 'routes', rename: coffeeRename} 22 | {expand: true, cwd: 'tests/helpers', src: ['*.coffee'], dest: 'tests/helpers', rename: coffeeRename} 23 | ] 24 | 25 | grunt.loadNpmTasks "grunt-contrib-coffee" 26 | grunt.loadNpmTasks "grunt-contrib-watch" 27 | 28 | # Default task. 29 | grunt.registerTask "default", "coffee" -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | REPORTER = nyan 3 | INTEGRATIONAL_TESTS = $(shell find tests/integrational -name "*.coffee") 4 | UNIT_TESTS = $(shell find tests/unit -name "*.coffee") 5 | 6 | test: test-integrational test-unit 7 | 8 | test-integrational: 9 | @NODE_ENV=test ./node_modules/.bin/mocha -u bdd \ 10 | --reporter $(REPORTER) \ 11 | --compilers coffee:coffee-script/register \ 12 | --timeout 10000 \ 13 | $(INTEGRATIONAL_TESTS) 14 | 15 | test-unit: 16 | @NODE_ENV=test ./node_modules/.bin/mocha -u bdd \ 17 | --reporter $(REPORTER) \ 18 | --compilers coffee:coffee-script/register \ 19 | --timeout 10000 \ 20 | $(UNIT_TESTS) 21 | 22 | test-w: 23 | @NODE_ENV=test ./node_modules/.bin/mocha -u bdd -b \ 24 | --reporter $(REPORTER) \ 25 | --compilers coffee:coffee-script/register \ 26 | --watch \ 27 | --growl \ 28 | $(INTEGRATIONAL_TESTS) 29 | 30 | test-one: 31 | @NODE_ENV=test ./node_modules/.bin/mocha -u bdd -b \ 32 | --reporter $(REPORTER) \ 33 | --compilers coffee:coffee-script/register \ 34 | --timeout 3000 \ 35 | $f 36 | 37 | test-one-w: 38 | @NODE_ENV=test ./node_modules/.bin/mocha -u bdd -b \ 39 | --reporter $(REPORTER) \ 40 | --compilers coffee:coffee-script/register \ 41 | --watch \ 42 | --growl \ 43 | $f 44 | 45 | .PHONY: test-integrational test-w test-unit test-one -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | coinnext 2 | ======== 3 | 4 | Virtual coins market 5 | 6 | [App setup guide](https://www.evernote.com/l/AWWu1FCvmwlIIZZQYQmMzk9V1SEmmsFuCdk) 7 | -------------------------------------------------------------------------------- /admin.js: -------------------------------------------------------------------------------- 1 | // Configure logger 2 | if (process.env.NODE_ENV === "production") require("./configs/logger"); 3 | 4 | // Configure modules 5 | var express = require('express'); 6 | var http = require('http'); 7 | var RedisStore = require('connect-redis')(express); 8 | var helmet = require('helmet'); 9 | var CoreAPIClient = require('./lib/core_api_client'); 10 | var _str = require("./lib/underscore_string"); 11 | var _ = require("underscore"); 12 | var environment = process.env.NODE_ENV || 'development'; 13 | 14 | // Configure globals 15 | GLOBAL.appConfig = require("./configs/config"); 16 | GLOBAL.passport = require('passport'); 17 | GLOBAL.coreAPIClient = new CoreAPIClient({host: GLOBAL.appConfig().wallets_host}); 18 | GLOBAL.db = require('./models/index'); 19 | 20 | require('./lib/admin_auth'); 21 | 22 | 23 | // Setup express 24 | var app = express(); 25 | var connectAssetsOptions = environment !== 'development' ? {minifyBuilds: true} : {}; 26 | connectAssetsOptions.helperContext = app.locals 27 | app.locals._ = _; 28 | app.locals._str = _str; 29 | app.enable("trust proxy"); 30 | app.disable('x-powered-by'); 31 | app.configure(function () { 32 | app.set('port', process.env.PORT || 6983); 33 | app.set('views', __dirname + '/views'); 34 | app.set('view engine', 'jade'); 35 | app.use(express.compress()); 36 | app.use(express.bodyParser()); 37 | app.use(express.methodOverride()); 38 | app.use(express.cookieParser(GLOBAL.appConfig().session.admin.cookie_secret)); 39 | app.use(express.session({ 40 | key: GLOBAL.appConfig().session.admin.session_key, 41 | store: new RedisStore(GLOBAL.appConfig().redis), 42 | proxy: true, 43 | cookie: GLOBAL.appConfig().session.admin.cookie 44 | })); 45 | if (environment !== "test") { 46 | app.use(express.csrf()); 47 | app.use(function(req, res, next) { 48 | res.locals.csrfToken = req.csrfToken(); 49 | next(); 50 | }); 51 | app.use(helmet.xframe('sameorigin')); 52 | app.use(helmet.hsts()); 53 | app.use(helmet.iexss({setOnOldIE: true})); 54 | app.use(helmet.ienoopen()); 55 | app.use(helmet.contentTypeOptions()); 56 | app.use(helmet.cacheControl()); 57 | } 58 | app.use(express.static(__dirname + '/public')); 59 | app.use(require('connect-assets')(connectAssetsOptions)); 60 | app.use(passport.initialize()); 61 | app.use(passport.session()); 62 | app.use(app.router); 63 | app.use(function(err, req, res, next) { 64 | console.error(err); 65 | res.send(500, "Oups, seems that there is an error on our side. Your coins are safe and we'll be back shortly..."); 66 | }); 67 | }); 68 | 69 | 70 | // Configuration 71 | 72 | app.configure('development', function(){ 73 | app.use(express.errorHandler({ dumpExceptions: true, showStack: true })); 74 | }); 75 | 76 | app.configure('production', function(){ 77 | app.use(express.errorHandler()); 78 | }); 79 | 80 | var server = http.createServer(app); 81 | 82 | server.listen(app.get('port'), function(){ 83 | console.log("Coinnext admin is running on port %d in %s mode", app.get("port"), app.settings.env); 84 | }); 85 | 86 | 87 | // Routes 88 | require('./routes/admin')(app); 89 | -------------------------------------------------------------------------------- /assets/css/chat.styl: -------------------------------------------------------------------------------- 1 | /* Chatbox */ 2 | .chatbox 3 | @extends .container 4 | width 300px 5 | height 400px 6 | font-size 13px 7 | 8 | .message-list 9 | reset-list() 10 | padding 10px 11 | overflow-y scroll 12 | position absolute 13 | width 100% 14 | bottom 40px 15 | top 0 16 | 17 | .message 18 | margin-bottom 3px 19 | overflow hidden 20 | word-wrap break-word 21 | .sender 22 | font-weight bold 23 | color $blue 24 | 25 | .message-post 26 | position absolute 27 | bottom 0 28 | width 100% 29 | height 40px 30 | overflow hidden 31 | border-top 1px solid #e0e0e0 32 | 33 | .login-required 34 | padding 0 10px 35 | line-height 40px 36 | margin 0 37 | 38 | .chat-message-input 39 | border 0 40 | background transparent 41 | height 40px 42 | font-size 13px 43 | width 100% 44 | padding-right 60px 45 | #send-message-bt 46 | btn($blue) 47 | padding 0 48 | position absolute 49 | top 0 50 | right 0 51 | width 50px 52 | height 30px 53 | margin 5px 54 | font-size 13px 55 | z-index 1 -------------------------------------------------------------------------------- /assets/css/errors.styl: -------------------------------------------------------------------------------- 1 | html, body, div, span, object, iframe, h1, h2, h3, h4, h5, h6, 2 | p, blockquote, pre, a, abbr, address, cite, code, del, dfn, em, 3 | img, ins, kbd, q, samp, small, strong, sub, sup, var, b, i, hr, 4 | dl, dt, dd, ol, ul, li, fieldset, form, label, legend, 5 | table, caption, tbody, tfoot, thead, tr, th, td, 6 | article, aside, canvas, details, figure, figcaption, hgroup, 7 | menu, footer, header, nav, section, summary, time, mark, audio, video 8 | margin 0 9 | padding 0 10 | border 0 11 | 12 | body 13 | background #eceef1 14 | color #484c57 15 | font-family "Helvetica Neue", Helvetica, Arial, sans-serif 16 | font-size 100% 17 | text-align center 18 | 19 | .container 20 | background #fff 21 | border 1px solid #d5d5d5 22 | border-radius 3px 23 | padding 40px 24 | margin 0 auto 25 | margin-top 15% 26 | display inline-block 27 | 28 | h1 29 | margin 0 0 10px 0 30 | color #14294e 31 | 32 | label 33 | font-weight 500 34 | 35 | label, a 36 | color #549cd5 37 | 38 | a 39 | text-decoration none 40 | &:hover 41 | text-decoration underline 42 | 43 | p 44 | font-size 1.125em 45 | line-height 1.5em 46 | font-weight 300 47 | margin 0 -------------------------------------------------------------------------------- /assets/css/plugins/cdn.js: -------------------------------------------------------------------------------- 1 | var plugin = function(){ 2 | return function(style){ 3 | style.define("CDN", function(imgOptions) { 4 | var host = GLOBAL.appConfig().assets_host || ""; 5 | var glueSign = imgOptions.string.indexOf("?") > -1 ? "&" : "?"; 6 | var key = GLOBAL.appConfig().assets_key ? glueSign + "_=" + GLOBAL.appConfig().assets_key : ""; 7 | return host + imgOptions.string + key; 8 | }); 9 | }; 10 | }; 11 | module.exports = plugin; -------------------------------------------------------------------------------- /assets/css/static.styl: -------------------------------------------------------------------------------- 1 | /* Terms */ 2 | .last-updated 3 | font-style italic 4 | background #fafbd2 5 | border-radius 2px 6 | display inline-block 7 | 8 | h2 9 | font-size 15px 10 | margin 15px 0 5px 11 | font-weight 700 12 | 13 | .page-title 14 | color $text-color 15 | font-family "Open Sans", helvetica, arial, sans-serif 16 | margin 0 0 10px 0 17 | font-size 24px 18 | font-weight 500 19 | 20 | .content 21 | ol 22 | ul 23 | padding-left 20px 24 | margin 0 0 10px 0 25 | 26 | p 27 | max-width 800px 28 | 29 | #qr-gen-bt 30 | btn($teal) 31 | display inline-block 32 | 33 | .settings 34 | .con-body 35 | padding 15px 10px 36 | 37 | .appstore-badges 38 | font-size 0 39 | 40 | .appstore 41 | background transparent url(CDN('/img/appstore-sprite.png')) no-repeat 42 | display inline-block 43 | width 143px 44 | height 42px 45 | overflow hidden 46 | text-indent -999px 47 | border-radius 3px 48 | border 1px solid #000 49 | margin-right 10px 50 | 51 | .appstore-apple 52 | background #000 url(CDN('/img/appstore-sprite.png')) -1px -1px no-repeat 53 | 54 | .appstore-google 55 | background #000 url(CDN('/img/appstore-sprite.png')) -155px -1px no-repeat 56 | 57 | #username-update-form 58 | label 59 | margin-right 10px 60 | input 61 | width 250px 62 | 63 | .content 64 | .profile-group 65 | reset-list() 66 | clearfix() 67 | 68 | .profile 69 | width 400px 70 | margin 10px 40px 0px 0 71 | float left 72 | .portrait 73 | border-radius 2px 74 | overflow hidden 75 | float left 76 | margin 0 15px 0 0 77 | h3 78 | margin 0 0 3px 0 79 | font-size 16px 80 | h4 81 | margin 0 0 5px 0 82 | text-transform uppercase 83 | color $lightgray 84 | font-weight 400 85 | 86 | /* Api */ 87 | .api 88 | background #f9f9f9 89 | padding 10px 90 | display block 91 | border 1px solid $border-color 92 | border-radius 2px 93 | margin-bottom 10px 94 | label 95 | &.get 96 | background $blue 97 | color #fff 98 | font-weight bold 99 | display inline-block 100 | padding 5px 10px 101 | border-radius 2px 102 | margin-right 10px 103 | text-transform uppercase 104 | 105 | .example 106 | font-style italic 107 | 108 | /* Status */ 109 | .status-legend 110 | vertical-align top 111 | margin-bottom 10px 112 | max-width 800px 113 | td 114 | padding 5px 0 115 | .label 116 | padding-right 10px 117 | 118 | .wallet-status 119 | color #fff 120 | padding 3px 5px 121 | text-transform uppercase 122 | font-size .8em 123 | font-weight 700 124 | width 70px 125 | display inline-block 126 | text-align center 127 | border-radius 2px 128 | &.normal 129 | background-color $teal 130 | &.blocked 131 | background-color $red 132 | &.delayed 133 | background-color #f4ca06 134 | &.error 135 | background-color #c9c9c9 136 | -------------------------------------------------------------------------------- /assets/js/admin.js: -------------------------------------------------------------------------------- 1 | //= require vendor/jquery-1.10.2 2 | //= require vendor/jquery.tmpload 3 | //= require vendor/date.format 4 | //= require vendor/math 5 | //= require vendor/underscore 6 | //= require vendor/underscore.string 7 | //= require vendor/backbone 8 | 9 | //= require app/helpers/json 10 | 11 | //= require admin_bootstrap -------------------------------------------------------------------------------- /assets/js/app/collections/order_logs.coffee: -------------------------------------------------------------------------------- 1 | class window.App.OrderLogsCollection extends Backbone.Collection 2 | 3 | currency1: null 4 | 5 | currency2: null 6 | 7 | url: ()-> 8 | url = "/order_logs" 9 | params = {} 10 | params.action = @action if @action 11 | params.currency1 = @currency1 if @currency1 12 | params.currency2 = @currency2 if @currency2 13 | params.user_id = @userId if @userId 14 | params.sort_by = @orderBy if @orderBy 15 | url += "?#{$.param(params)}" 16 | 17 | model: window.App.OrderLogModel 18 | 19 | initialize: (models, options = {})-> 20 | @action = options.action 21 | @currency1 = options.currency1 22 | @currency2 = options.currency2 23 | @userId = options.userId 24 | @orderBy = options.orderBy 25 | 26 | calculateVolume: ()-> 27 | total = 0 28 | @each (order)-> 29 | total = App.math.add(total, order.calculateFirstNoFeeAmount()) 30 | _.str.satoshiRound total 31 | 32 | calculateVolumeForPriceLimit: (unitPrice)-> 33 | unitPrice = _.str.satoshiRound(unitPrice) 34 | totalAmount = 0 35 | @each (order)-> 36 | orderPrice = _.str.satoshiRound(order.get("unit_price")) 37 | totalAmount = App.math.add(totalAmount, order.calculateFirstNoFeeAmount()) if orderPrice <= unitPrice 38 | return if orderPrice > unitPrice 39 | _.str.satoshiRound totalAmount 40 | -------------------------------------------------------------------------------- /assets/js/app/collections/orders.coffee: -------------------------------------------------------------------------------- 1 | class window.App.OrdersCollection extends Backbone.Collection 2 | 3 | type: null 4 | 5 | currency1: null 6 | 7 | currency2: null 8 | 9 | published: null 10 | 11 | url: ()-> 12 | url = "/orders" 13 | params = {} 14 | params.status = @type if @type 15 | params.action = @action if @action 16 | params.currency1 = @currency1 if @currency1 17 | params.currency2 = @currency2 if @currency2 18 | params.published = @published if @published? 19 | params.user_id = @userId if @userId 20 | params.sort_by = @orderBy if @orderBy 21 | url += "?#{$.param(params)}" 22 | 23 | model: window.App.OrderModel 24 | 25 | initialize: (models, options = {})-> 26 | @type = options.type 27 | @action = options.action 28 | @currency1 = options.currency1 29 | @currency2 = options.currency2 30 | @published = options.published 31 | @userId = options.userId 32 | @orderBy = options.orderBy 33 | 34 | calculateVolume: ()-> 35 | total = 0 36 | @each (order)-> 37 | total = App.math.add(total, order.calculateFirstNoFeeAmount()) 38 | _.str.satoshiRound total 39 | 40 | calculateVolumeForPriceLimit: (unitPrice)-> 41 | unitPrice = _.str.satoshiRound(unitPrice) 42 | totalAmount = 0 43 | @each (order)-> 44 | orderPrice = _.str.satoshiRound(order.get("unit_price")) 45 | totalAmount = App.math.add(totalAmount, order.calculateFirstNoFeeAmount()) if orderPrice <= unitPrice 46 | return if orderPrice > unitPrice 47 | _.str.satoshiRound totalAmount 48 | 49 | getStacked: ()-> 50 | stackedOrders = {} 51 | @each (order)-> 52 | unitPrice = _.str.toFixed order.get("unit_price") 53 | stackId = "id-#{unitPrice}" 54 | stackedOrders[stackId] = new App.OrderModel if not stackedOrders[stackId] 55 | stackedOrders[stackId].mergeWithOrder order 56 | _.values stackedOrders 57 | 58 | getIds: ()-> 59 | ids = [] 60 | @each (order)-> 61 | ids.push order.id 62 | ids 63 | -------------------------------------------------------------------------------- /assets/js/app/collections/payments.coffee: -------------------------------------------------------------------------------- 1 | class window.App.PaymentsCollection extends Backbone.Collection 2 | 3 | type: null 4 | 5 | walletId: null 6 | 7 | url: ()-> 8 | url = "/payments" 9 | url += "/#{@type}" if @type 10 | url += "/#{@walletId}" if @walletId 11 | url 12 | 13 | model: window.App.PaymentModel 14 | 15 | initialize: (models, options = {})-> 16 | @type = options.type 17 | @walletId = options.walletId 18 | -------------------------------------------------------------------------------- /assets/js/app/collections/trade_stats.coffee: -------------------------------------------------------------------------------- 1 | class window.App.TradeStatsCollection extends Backbone.Collection 2 | 3 | type: null 4 | 5 | url: ()-> 6 | "/trade_stats/#{@type}" 7 | 8 | initialize: (models, options = {})-> 9 | @type = options.type -------------------------------------------------------------------------------- /assets/js/app/collections/transactions.coffee: -------------------------------------------------------------------------------- 1 | class window.App.TransactionsCollection extends Backbone.Collection 2 | 3 | type: null 4 | 5 | walletId: null 6 | 7 | url: ()-> 8 | url = "/transactions" 9 | url += "/#{@type}" if @type 10 | url += "/#{@walletId}" if @walletId 11 | url 12 | 13 | model: window.App.TransactionModel 14 | 15 | initialize: (models, options = {})-> 16 | @type = options.type 17 | @walletId = options.walletId 18 | -------------------------------------------------------------------------------- /assets/js/app/collections/wallets.coffee: -------------------------------------------------------------------------------- 1 | class window.App.WalletsCollection extends Backbone.Collection 2 | 3 | url: "/wallets" 4 | 5 | model: window.App.WalletModel 6 | -------------------------------------------------------------------------------- /assets/js/app/error_logger.coffee: -------------------------------------------------------------------------------- 1 | window.App or= {} 2 | 3 | class App.ErrorLogger 4 | 5 | constructor: ()-> 6 | $.subscribe "error", @renderError 7 | $.subscribe "notice", @renderNotice 8 | 9 | renderError: (ev, xhrError = {}, $form)=> 10 | if xhrError.responseText 11 | try 12 | error = $.parseJSON xhrError.responseText 13 | error = error.error 14 | catch e 15 | error = xhrError.responseText 16 | else 17 | error = xhrError.error or xhrError 18 | return $form.find("#error-cnt").text error if $form 19 | $.jGrowl error, 20 | position: "top-right" 21 | theme: "error" 22 | 23 | renderNotice: (ev, msg)=> 24 | $.jGrowl msg, 25 | position: "top-right" 26 | theme: "notice" -------------------------------------------------------------------------------- /assets/js/app/helpers/crypto_currency.coffee: -------------------------------------------------------------------------------- 1 | window.App = window.App or {} 2 | window.App.Helpers = window.App.Helpers or {} 3 | window.App.Helpers.CryptoCurrency = 4 | 5 | isValidAddress: (address) -> 6 | decoded = @base58_decode(address) 7 | return false unless decoded.length is 25 8 | true 9 | #cksum = decoded.substr(decoded.length - 4) 10 | #rest = decoded.substr(0, decoded.length - 4) 11 | #good_cksum = @hex2a(sha256_digest(@hex2a(sha256_digest(rest)))).substr(0, 4) 12 | #return false unless cksum is good_cksum 13 | #true 14 | 15 | base58_decode: (string) -> 16 | table = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" 17 | table_rev = new Array() 18 | i = undefined 19 | i = 0 20 | while i < 58 21 | table_rev[table[i]] = int2bigInt(i, 8, 0) 22 | i++ 23 | l = string.length 24 | long_value = int2bigInt(0, 1, 0) 25 | num_58 = int2bigInt(58, 8, 0) 26 | c = undefined 27 | i = 0 28 | while i < l 29 | c = string[l - i - 1] 30 | long_value = add(long_value, mult(table_rev[c], @pow(num_58, i))) 31 | i++ 32 | hex = bigInt2str(long_value, 16) 33 | str = @hex2a(hex) 34 | nPad = undefined 35 | nPad = 0 36 | while string[nPad] is table[0] 37 | nPad++ 38 | output = str 39 | output = @repeat("\u0000", nPad) + str if nPad > 0 40 | output 41 | 42 | hex2a: (hex) -> 43 | str = "" 44 | i = 0 45 | 46 | while i < hex.length 47 | str += String.fromCharCode(parseInt(hex.substr(i, 2), 16)) 48 | i += 2 49 | str 50 | 51 | pow: (big, exp) -> 52 | return int2bigInt(1, 1, 0) if exp is 0 53 | i = undefined 54 | newbig = big 55 | i = 1 56 | while i < exp 57 | newbig = mult(newbig, big) 58 | i++ 59 | newbig 60 | 61 | repeat: (s, n) -> 62 | a = [] 63 | a.push s while a.length < n 64 | a.join "" -------------------------------------------------------------------------------- /assets/js/app/helpers/json.coffee: -------------------------------------------------------------------------------- 1 | window.App = window.App or {} 2 | window.App.Helpers = window.App.Helpers or {} 3 | window.App.Helpers.JSON = 4 | 5 | # Format JSON function 6 | # http://ketanjetty.com/coldfusion/javascript/format-json/ 7 | toHTML: (jsonString)-> 8 | jsonString = JSON.stringify(jsonString) if typeof jsonString is "object" 9 | jsonString.replace(/(\\'|\\")/g, "") 10 | retval = '' 11 | pos = 0 12 | strLen = jsonString.length 13 | indentStr = '    ' 14 | newLine = '
' 15 | 16 | for char in jsonString 17 | 18 | if char is "}" or char is "]" 19 | retval = retval + newLine 20 | pos = pos - 1 21 | 22 | j = 0 23 | while j < pos 24 | retval = retval + indentStr 25 | j++ 26 | 27 | retval = retval + char 28 | 29 | if char is "{" or char is "[" or char is "," 30 | retval = retval + newLine 31 | pos = pos + 1 if char is "{" or char is "[" 32 | 33 | k = 0 34 | while k < pos 35 | retval = retval + indentStr 36 | k++ 37 | 38 | retval -------------------------------------------------------------------------------- /assets/js/app/models/google_auth.coffee: -------------------------------------------------------------------------------- 1 | window.App or= {} 2 | 3 | class App.GoogleAuthModel extends Backbone.Model 4 | 5 | urlRoot: "/google_auth" 6 | -------------------------------------------------------------------------------- /assets/js/app/models/market_stats.coffee: -------------------------------------------------------------------------------- 1 | window.App or= {} 2 | 3 | class App.MarketStatsModel extends Backbone.Model 4 | 5 | urlRoot: "/market_stats" 6 | -------------------------------------------------------------------------------- /assets/js/app/models/order_log.coffee: -------------------------------------------------------------------------------- 1 | window.App or= {} 2 | 3 | class App.OrderLogModel extends Backbone.Model 4 | 5 | urlRoot: "/order_logs" 6 | 7 | calculateFirstAmount: ()-> 8 | return _.str.satoshiRound @get("result_amount") if @get("action") is "buy" 9 | return _.str.satoshiRound @get("matched_amount") if @get("action") is "sell" 10 | 11 | calculateSecondAmount: ()-> 12 | return _.str.satoshiRound @get("result_amount") if @get("action") is "sell" 13 | return _.str.satoshiRound App.math.multiply @get("matched_amount"), @get("unit_price") if @get("action") is "buy" 14 | 15 | getTime: ()-> 16 | new Date(@get('time')).format('dd.mm.yy H:MM') 17 | -------------------------------------------------------------------------------- /assets/js/app/models/payment.coffee: -------------------------------------------------------------------------------- 1 | window.App or= {} 2 | 3 | class App.PaymentModel extends Backbone.Model 4 | 5 | urlRoot: "/payments" 6 | -------------------------------------------------------------------------------- /assets/js/app/models/transaction.coffee: -------------------------------------------------------------------------------- 1 | window.App or= {} 2 | 3 | class App.TransactionModel extends Backbone.Model 4 | 5 | urlRoot: "/transactions" 6 | 7 | getCreatedDate: ()-> 8 | new Date(@get('created_at')).format('dd.mm.yy H:MM') -------------------------------------------------------------------------------- /assets/js/app/models/user.coffee: -------------------------------------------------------------------------------- 1 | window.App or= {} 2 | 3 | class App.UserModel extends Backbone.Model 4 | 5 | urlRoot: "/user" 6 | -------------------------------------------------------------------------------- /assets/js/app/models/wallet.coffee: -------------------------------------------------------------------------------- 1 | window.App or= {} 2 | 3 | class App.WalletModel extends Backbone.Model 4 | 5 | urlRoot: "/wallets" 6 | -------------------------------------------------------------------------------- /assets/js/app/views/market_ticker.coffee: -------------------------------------------------------------------------------- 1 | class App.MarketTickerView extends App.MasterView 2 | 3 | model: null 4 | 5 | tpl: "market-ticker-tpl" 6 | 7 | activeCurrency: null 8 | 9 | initialize: (options = {})-> 10 | $.subscribe "market-stats-updated", @onMarketStatsUpdated 11 | 12 | render: ()-> 13 | @model.fetch 14 | success: ()=> 15 | @renderMarketTicker() 16 | error: ()=> 17 | 18 | renderMarketTicker: ()-> 19 | @$el.html @template 20 | marketStats: @model 21 | @markActive() 22 | 23 | markActive: (currency = null)-> 24 | @activeCurrency = currency if currency 25 | @$("[data-market-currency='#{@activeCurrency}']").addClass "active" 26 | 27 | onMarketStatsUpdated: (ev, data)=> 28 | @render() 29 | -------------------------------------------------------------------------------- /assets/js/app/views/master.coffee: -------------------------------------------------------------------------------- 1 | class App.MasterView extends Backbone.View 2 | 3 | template: (data)-> 4 | tpl = $.tmpload 5 | id: @tpl 6 | tpl data 7 | 8 | toggleVisible: ()=> 9 | if @$el.is ":empty" 10 | @$el.parents(".container:first").hide() 11 | else 12 | @$el.parents(".container:first").show() -------------------------------------------------------------------------------- /assets/js/app/views/order_book.coffee: -------------------------------------------------------------------------------- 1 | class App.OrderBookView extends App.MasterView 2 | 3 | tpl: null 4 | 5 | collection: null 6 | 7 | events: 8 | "click .order-book-order": "onOrderClick" 9 | 10 | initialize: (options = {})-> 11 | @tpl = options.tpl if options.tpl 12 | @$totalsEl = options.$totalsEl if options.$totalsEl 13 | $.subscribe "new-order", @onNewOrder 14 | $.subscribe "order-completed", @onOrderMatched 15 | $.subscribe "order-partially-completed", @onOrderMatched 16 | $.subscribe "order-canceled", @onOrderCanceled 17 | 18 | render: ()-> 19 | @collection.fetch 20 | success: ()=> 21 | @$el.empty() 22 | for order in @collection.getStacked() 23 | @$el.append @template 24 | order: order 25 | @renderVolume() if @$totalsEl 26 | 27 | renderVolume: ()-> 28 | @$totalsEl.text _.str.toFixed @collection.calculateVolume() 29 | 30 | onNewOrder: (ev, order)=> 31 | @render() 32 | 33 | onOrderMatched: (ev, order)=> 34 | unitPrice = _.str.toFixed order.get("unit_price") 35 | $existentOrder = @$("[data-unit-price='#{unitPrice}']") 36 | if $existentOrder.length 37 | $existentOrder.addClass "highlight" 38 | setTimeout ()=> 39 | $existentOrder.removeClass "highlight" if $existentOrder.length 40 | @render() 41 | , 1000 42 | 43 | onOrderCanceled: (ev, data)=> 44 | @render() 45 | 46 | onOrderClick: (ev)-> 47 | $row = $(ev.currentTarget) 48 | unitPrice = parseFloat $row.data "unit-price" 49 | action = $row.data "action" 50 | order = new App.OrderModel 51 | unit_price: unitPrice 52 | action: action 53 | amount: @collection.calculateVolumeForPriceLimit unitPrice 54 | $.publish "order-book-order-selected", order -------------------------------------------------------------------------------- /assets/js/app/views/order_logs.coffee: -------------------------------------------------------------------------------- 1 | class App.OrderLogsView extends App.MasterView 2 | 3 | tpl: null 4 | 5 | collection: null 6 | 7 | hideOnEmpty: false 8 | 9 | initialize: (options = {})-> 10 | @tpl = options.tpl if options.tpl 11 | @hideOnEmpty = options.hideOnEmpty if options.hideOnEmpty 12 | @toggleVisible() 13 | $.subscribe "order-completed", @onOrderCompleted 14 | $.subscribe "order-partially-completed", @onOrderPartiallyCompleted 15 | 16 | render: (method)-> 17 | @collection.fetch 18 | success: ()=> 19 | @renderOrders method 20 | @toggleVisible() if @hideOnEmpty 21 | 22 | renderOrders: (method = "append")-> 23 | @collection.each (order)=> 24 | $existentOrder = @$("[data-id='#{order.id}']") 25 | tpl = @template 26 | order: order 27 | @$el[method] tpl if not $existentOrder.length 28 | $existentOrder.replaceWith tpl if $existentOrder.length 29 | 30 | onOrderCompleted: (ev, order)=> 31 | @render "prepend" 32 | 33 | onOrderPartiallyCompleted: (ev, order)=> 34 | @render "prepend" -------------------------------------------------------------------------------- /assets/js/app/views/pending_transactions.coffee: -------------------------------------------------------------------------------- 1 | class App.PendingTransactionsView extends App.MasterView 2 | 3 | tpl: "pending-transaction-tpl" 4 | 5 | collection: null 6 | 7 | payments: null 8 | 9 | initialize: (options = {})-> 10 | @payments = options.payments 11 | @hideOnEmpty = options.hideOnEmpty if options.hideOnEmpty 12 | @toggleVisible() 13 | $.subscribe "payment-submited", @onPaymentSubmited 14 | $.subscribe "payment-processed", @onPaymentProcessed 15 | $.subscribe "transaction-update", @onTransactionUpdate 16 | 17 | render: ()-> 18 | @collection.fetch 19 | success: ()=> 20 | @collection.each (transaction)=> 21 | $tx = @template 22 | transaction: transaction 23 | $existentTx = @$("[data-id='#{transaction.id}']") 24 | if not $existentTx.length 25 | @$el.append $tx 26 | else 27 | $existentTx.replaceWith $tx 28 | @toggleVisible() if @hideOnEmpty 29 | @payments.fetch 30 | success: ()=> 31 | @payments.each (payment)=> 32 | $pm = @template 33 | payment: payment 34 | $existentPm = @$("[data-id='#{payment.id}']") 35 | if not $existentPm.length 36 | @$el.append $pm 37 | else 38 | $existentPm.replaceWith $pm 39 | @toggleVisible() if @hideOnEmpty 40 | 41 | onTransactionUpdate: (ev, transaction)=> 42 | @render() 43 | @$("[data-id='#{transaction.id}']").remove() if transaction.get("balance_loaded") 44 | 45 | onPaymentProcessed: (ev, payment)=> 46 | @render() 47 | @$("[data-id='#{payment.id}']").remove() 48 | 49 | onPaymentSubmited: (ev, payment)=> 50 | @render() 51 | -------------------------------------------------------------------------------- /assets/js/app/views/trade_chart.coffee: -------------------------------------------------------------------------------- 1 | class App.TradeChartView extends App.MasterView 2 | 3 | collection: null 4 | 5 | initialize: (options = {})-> 6 | 7 | render: ()-> 8 | @collection.fetch 9 | success: ()=> 10 | @renderChart @collection.toJSON() 11 | 12 | renderChart: (data)-> 13 | # split the data set into ohlc and volume 14 | ohlc = [] 15 | volume = [] 16 | xAxis = [] 17 | dataLength = data.length 18 | i = 0 19 | while i < dataLength 20 | startTime = new Date(data[i].start_time).getTime() 21 | ohlc.push [ 22 | startTime # the date 23 | data[i].open_price # open 24 | data[i].high_price # high 25 | data[i].low_price # low 26 | data[i].close_price # close 27 | ] 28 | volume.push [ 29 | startTime # the date 30 | data[i].volume # the volume 31 | ] 32 | i++ 33 | 34 | # create the chart 35 | @$el.highcharts "StockChart", 36 | rangeSelector: 37 | enabled: false 38 | scrollbar: 39 | enabled: false 40 | navigator: 41 | enabled: false 42 | 43 | exporting: 44 | buttons: [ 45 | printButton: 46 | enabled: false 47 | exportButton: 48 | enabled: false 49 | ] 50 | credits: 51 | enabled: false 52 | 53 | yAxis: [ 54 | { 55 | lineWidth: 0 56 | gridLineColor: "#ecedef" 57 | } 58 | { 59 | gridLineWidth: 0 60 | opposite: true 61 | } 62 | ] 63 | xAxis: 64 | lineColor: "#ecedef" 65 | type: "time" 66 | dateTimeLabelFormats: 67 | millisecond: '%H:%M' 68 | tooltip: 69 | shared: true 70 | shadow: false 71 | backgroundColor: "#ffffff" 72 | borderColor: "#d1d5dd" 73 | formatter: ()-> 74 | s = Highcharts.dateFormat('%b %e %Y %H:%M', this.x) + "
" 75 | s += "Open: " + _.str.toFixed(@points[1].point.open) + "
" 76 | s += "High: " + _.str.toFixed(@points[1].point.high) + "
" 77 | s += "Low: " + _.str.toFixed(@points[1].point.low) + "
" 78 | s += "Close: " + _.str.toFixed(@points[1].point.close) + "
" 79 | s += "Volume: " + _.str.toFixed(@points[0].point.y) 80 | return s 81 | series: [ 82 | { 83 | type: "column" 84 | name: "Volume" 85 | data: volume 86 | yAxis: 1 87 | color: "#dddddd" 88 | } 89 | { 90 | type: "candlestick" 91 | name: "Price" 92 | data: ohlc 93 | yAxis: 0 94 | color: "#3eae5f" 95 | upColor: "#da4444" 96 | lineColor: "#3eae5f" 97 | upLineColor: "#da4444" 98 | borderWidth: 0 99 | } 100 | ] 101 | -------------------------------------------------------------------------------- /assets/js/app/views/transactions_history.coffee: -------------------------------------------------------------------------------- 1 | class App.TransactionsHistoryView extends App.MasterView 2 | 3 | tpl: "transaction-history-tpl" 4 | 5 | collection: null 6 | 7 | initialize: (options = {})-> 8 | @hideOnEmpty = options.hideOnEmpty if options.hideOnEmpty 9 | @toggleVisible() 10 | $.subscribe "payment-processed", @onPaymentProcessed 11 | $.subscribe "transaction-update", @onTransactionUpdate 12 | 13 | render: ()-> 14 | @collection.fetch 15 | success: ()=> 16 | @renderTransactions() 17 | @toggleVisible() if @hideOnEmpty 18 | 19 | renderTransactions: ()-> 20 | @collection.each (transaction)=> 21 | $existentTransaction = @$("[data-id='#{transaction.id}']") 22 | tpl = @template 23 | transaction: transaction 24 | @$el.append tpl if not $existentTransaction.length 25 | $existentTransaction.replaceWith tpl if $existentTransaction.length 26 | 27 | onTransactionUpdate: (ev, transaction)=> 28 | @render() 29 | 30 | onPaymentProcessed: (ev, payment)=> 31 | @render() 32 | -------------------------------------------------------------------------------- /assets/js/application.js: -------------------------------------------------------------------------------- 1 | //= require vendor/modernizr-2.7.1.min 2 | //= require vendor/jquery-1.10.2 3 | //= require vendor/date.format 4 | //= require vendor/math 5 | //= require vendor/underscore 6 | //= require vendor/underscore.string 7 | //= require vendor/backbone 8 | //= require vendor/qrcode 9 | //= require vendor/ba-tiny-pubsub 10 | //= require vendor/jquery.validate 11 | //= require vendor/jquery.tmpload 12 | //= require vendor/jquery.jgrowl 13 | //= require vendor/ZeroClipboard 14 | //= require vendor/highstock 15 | //= require vendor/highstock.exporting 16 | //= require vendor/BigInt 17 | //= require vendor/sha256 18 | //= require vendor/zxcvbn 19 | //= require vendor/socket.io 20 | //= require vendor/mailcheck 21 | 22 | //= require app/helpers/crypto_currency 23 | 24 | //= require app/models/user 25 | //= require app/models/google_auth 26 | //= require app/models/wallet 27 | //= require app/models/payment 28 | //= require app/models/transaction 29 | //= require app/models/order 30 | //= require app/models/order_log 31 | //= require app/models/market_stats 32 | 33 | //= require app/collections/wallets 34 | //= require app/collections/transactions 35 | //= require app/collections/payments 36 | //= require app/collections/orders 37 | //= require app/collections/order_logs 38 | //= require app/collections/trade_stats 39 | 40 | //= require app/views/master 41 | //= require app/views/finances 42 | //= require app/views/settings 43 | //= require app/views/pending_transactions 44 | //= require app/views/transactions_history 45 | //= require app/views/trade 46 | //= require app/views/trade_chart 47 | //= require app/views/orders 48 | //= require app/views/order_book 49 | //= require app/views/order_logs 50 | //= require app/views/market_ticker 51 | 52 | //= require chat/models/chat_message 53 | //= require chat/collections/chat_messages 54 | //= require chat/views/chat 55 | 56 | //= require app/error_logger 57 | 58 | //= require auth_bootstrap 59 | //= require app_bootstrap 60 | //= require chat_bootstrap -------------------------------------------------------------------------------- /assets/js/chat/collections/chat_messages.coffee: -------------------------------------------------------------------------------- 1 | class App.ChatMessagesCollection extends Backbone.Collection 2 | 3 | model: App.ChatMessageModel 4 | 5 | url: "/chat/messages" 6 | 7 | initialize: (models, options)-> 8 | 9 | -------------------------------------------------------------------------------- /assets/js/chat/models/chat_message.coffee: -------------------------------------------------------------------------------- 1 | class App.ChatMessageModel extends Backbone.Model -------------------------------------------------------------------------------- /assets/js/chat/views/chat.coffee: -------------------------------------------------------------------------------- 1 | class App.ChatView extends App.MasterView 2 | 3 | tpl: "chat-message-tpl" 4 | 5 | chatSocketUrl: null 6 | 7 | messageHistoryRootUrl: null 8 | 9 | events: 10 | "keydown #chat-message-box": "onMessageTyping" 11 | "click #send-message-bt": "onSendClick" 12 | 13 | initialize: ({@user, @chatSocketUrl, @messageHistoryRootUrl})-> 14 | @chatSocketUrl ?= "/chat" 15 | 16 | render: ()-> 17 | @loadHistory() 18 | @socket = io.connect @chatSocketUrl 19 | @socket.on "connect", ()=> 20 | # @socket.emit "join" 21 | @socket.on "new-message", (data)=> 22 | #console.log data 23 | @renderMessage data 24 | 25 | loadHistory: ()-> 26 | messagesCollection = new App.ChatMessagesCollection null, {room: @room} 27 | messagesCollection.rootUrl = @messageHistoryRootUrl if @messageHistoryRootUrl 28 | messagesCollection.fetch 29 | success: ()=> 30 | messagesCollection.each (message)=> 31 | @renderMessage message, "prepend" 32 | 33 | renderMessage: (message = {}, mode = "append")-> 34 | if not (message instanceof App.ChatMessageModel) 35 | message = new App.ChatMessageModel message 36 | $message = $(@template({message: message})) 37 | @$("#messages-list")[mode]($message) 38 | @scrollMessageList() 39 | 40 | scrollMessageList: ()-> 41 | @$("#messages-list").scrollTop 1000000000 42 | 43 | sendMessage: ()-> 44 | $messageBox = @$("#chat-message-box") 45 | messageText = _.str.trim $messageBox.val() 46 | if messageText.length 47 | @socket.emit "add-message", {room: @room, username: @user.get("username"), message: messageText} 48 | $messageBox.val "" 49 | 50 | onMessageTyping: (ev)=> 51 | if ev.keyCode is 13 52 | ev.preventDefault() 53 | @sendMessage() 54 | 55 | onSendClick: (ev)=> 56 | ev.preventDefault() 57 | @sendMessage() 58 | -------------------------------------------------------------------------------- /assets/js/chat_bootstrap.coffee: -------------------------------------------------------------------------------- 1 | $(document).ready ()-> 2 | 3 | $.tmpload.defaults.tplWrapper = _.template 4 | 5 | window.App = window.App or {} 6 | 7 | user = new App.UserModel 8 | 9 | $globalChat = $("#globalchat") 10 | 11 | if $globalChat.length 12 | user.fetch 13 | complete: ()-> 14 | globalChat = new App.ChatView 15 | el: $globalChat 16 | user: user 17 | chatSocketUrl: "#{CONFIG.users.hostname}/chat" 18 | messageHistoryRootUrl: "/chat/messages" 19 | globalChat.render() 20 | -------------------------------------------------------------------------------- /assets/js/vendor/BigInt.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doomhz/coinnext/c89b8e462876c567580e50d45287bda11399ec04/assets/js/vendor/BigInt.js -------------------------------------------------------------------------------- /assets/js/vendor/ba-tiny-pubsub.js: -------------------------------------------------------------------------------- 1 | /*! Tiny Pub/Sub - v0.7.0 - 2013-01-29 2 | * https://github.com/cowboy/jquery-tiny-pubsub 3 | * Copyright (c) 2013 "Cowboy" Ben Alman; Licensed MIT */ 4 | (function($) { 5 | 6 | var o = $({}); 7 | 8 | $.subscribe = function() { 9 | o.on.apply(o, arguments); 10 | }; 11 | 12 | $.unsubscribe = function() { 13 | o.off.apply(o, arguments); 14 | }; 15 | 16 | $.publish = function() { 17 | o.trigger.apply(o, arguments); 18 | }; 19 | 20 | }(jQuery)); -------------------------------------------------------------------------------- /assets/js/vendor/backbone.csrf.js: -------------------------------------------------------------------------------- 1 | Backbone.sync = (function(original) { 2 | return function(method, model, options) { 3 | options.beforeSend = function(xhr) { 4 | xhr.setRequestHeader('X-CSRF-Token', CONFIG.csrf); 5 | }; 6 | original(method, model, options); 7 | }; 8 | })(Backbone.sync); -------------------------------------------------------------------------------- /assets/js/vendor/zxcvbn-async.js: -------------------------------------------------------------------------------- 1 | (function(){var a;a=function(){var a,b;b=document.createElement("script");b.src="/js/vendor/zxcvbn.js";b.type="text/javascript";b.async=!0;a=document.getElementsByTagName("script")[0];return a.parentNode.insertBefore(b,a)};null!=window.attachEvent?window.attachEvent("onload",a):window.addEventListener("load",a,!1)}).call(this); 2 | -------------------------------------------------------------------------------- /configs/config.coffee: -------------------------------------------------------------------------------- 1 | fs = require "fs" 2 | environment = process.env.NODE_ENV or "development" 3 | walletsConfigPath = __dirname + "/wallets_config" 4 | config = JSON.parse(fs.readFileSync(process.cwd() + "/config.json", "utf8"))[environment] 5 | fs.readdirSync(walletsConfigPath).filter((file) -> 6 | /.json$/.test(file) 7 | ).forEach (file) -> 8 | currency = file.replace ".json", "" 9 | config.wallets[currency] = JSON.parse fs.readFileSync("#{walletsConfigPath}/#{file}") 10 | return 11 | exports = module.exports = ()-> 12 | config -------------------------------------------------------------------------------- /configs/config.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var config, environment, exports, fs, walletsConfigPath; 3 | 4 | fs = require("fs"); 5 | 6 | environment = process.env.NODE_ENV || "development"; 7 | 8 | walletsConfigPath = __dirname + "/wallets_config"; 9 | 10 | config = JSON.parse(fs.readFileSync(process.cwd() + "/config.json", "utf8"))[environment]; 11 | 12 | fs.readdirSync(walletsConfigPath).filter(function(file) { 13 | return /.json$/.test(file); 14 | }).forEach(function(file) { 15 | var currency; 16 | currency = file.replace(".json", ""); 17 | config.wallets[currency] = JSON.parse(fs.readFileSync("" + walletsConfigPath + "/" + file)); 18 | }); 19 | 20 | exports = module.exports = function() { 21 | return config; 22 | }; 23 | 24 | }).call(this); 25 | -------------------------------------------------------------------------------- /configs/logger.coffee: -------------------------------------------------------------------------------- 1 | consoleLog = console.log 2 | consoleError = console.error 3 | 4 | console.log = ()-> 5 | args = arguments 6 | args[0] = "#{new Date().toGMTString()} - log: #{args[0]}" if args[0] 7 | consoleLog.apply undefined, args 8 | 9 | console.error = ()-> 10 | args = arguments 11 | args[0] = "#{new Date().toGMTString()} - error: #{args[0]}" if args[0] 12 | consoleError.apply undefined, args 13 | 14 | process.on "uncaughtException", (err)-> 15 | console.error "Uncaught exception, exiting...", err.stack 16 | process.exit() -------------------------------------------------------------------------------- /configs/logger.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var consoleError, consoleLog; 3 | 4 | consoleLog = console.log; 5 | 6 | consoleError = console.error; 7 | 8 | console.log = function() { 9 | var args; 10 | args = arguments; 11 | if (args[0]) { 12 | args[0] = "" + (new Date().toGMTString()) + " - log: " + args[0]; 13 | } 14 | return consoleLog.apply(void 0, args); 15 | }; 16 | 17 | console.error = function() { 18 | var args; 19 | args = arguments; 20 | if (args[0]) { 21 | args[0] = "" + (new Date().toGMTString()) + " - error: " + args[0]; 22 | } 23 | return consoleError.apply(void 0, args); 24 | }; 25 | 26 | process.on("uncaughtException", function(err) { 27 | console.error("Uncaught exception, exiting...", err.stack); 28 | return process.exit(); 29 | }); 30 | 31 | }).call(this); 32 | -------------------------------------------------------------------------------- /configs/wallets.coffee: -------------------------------------------------------------------------------- 1 | fs = require "fs" 2 | MarketHelper = require "../lib/market_helper" 3 | 4 | wallets = {} 5 | 6 | for currency of MarketHelper.getCurrencies() 7 | walletType = currency.toLowerCase() 8 | options = if process.env.NODE_ENV isnt "production" and not GLOBAL.appConfig().wallets[walletType]? then GLOBAL.appConfig().wallets["btc"] else GLOBAL.appConfig().wallets[walletType] 9 | if process.env.NODE_ENV is "test" 10 | path = "#{process.cwd()}/tests/helpers/#{walletType}_wallet_mock.js" 11 | if fs.existsSync path 12 | Wallet = require path 13 | wallets[currency] = new Wallet options 14 | else 15 | path = "#{process.cwd()}/lib/crypto_wallets/#{walletType}_wallet.js" 16 | if fs.existsSync path 17 | Wallet = require path 18 | else 19 | Wallet = require "../lib/crypto_wallet" 20 | wallets[currency] = new Wallet options 21 | 22 | exports = module.exports = wallets -------------------------------------------------------------------------------- /configs/wallets.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var MarketHelper, Wallet, currency, exports, fs, options, path, walletType, wallets; 3 | 4 | fs = require("fs"); 5 | 6 | MarketHelper = require("../lib/market_helper"); 7 | 8 | wallets = {}; 9 | 10 | for (currency in MarketHelper.getCurrencies()) { 11 | walletType = currency.toLowerCase(); 12 | options = process.env.NODE_ENV !== "production" && (GLOBAL.appConfig().wallets[walletType] == null) ? GLOBAL.appConfig().wallets["btc"] : GLOBAL.appConfig().wallets[walletType]; 13 | if (process.env.NODE_ENV === "test") { 14 | path = "" + (process.cwd()) + "/tests/helpers/" + walletType + "_wallet_mock.js"; 15 | if (fs.existsSync(path)) { 16 | Wallet = require(path); 17 | wallets[currency] = new Wallet(options); 18 | } 19 | } else { 20 | path = "" + (process.cwd()) + "/lib/crypto_wallets/" + walletType + "_wallet.js"; 21 | if (fs.existsSync(path)) { 22 | Wallet = require(path); 23 | } else { 24 | Wallet = require("../lib/crypto_wallet"); 25 | } 26 | wallets[currency] = new Wallet(options); 27 | } 28 | } 29 | 30 | exports = module.exports = wallets; 31 | 32 | }).call(this); 33 | -------------------------------------------------------------------------------- /configs/wallets_config/btc.json.sample: -------------------------------------------------------------------------------- 1 | { 2 | "client": { 3 | "host": "127.0.0.1", 4 | "port": 18332, 5 | "user": "", 6 | "pass": "", 7 | "ssl": true, 8 | "sslStrict": false, 9 | "sslCa": false 10 | }, 11 | "wallet": { 12 | "address": "", 13 | "account": "", 14 | "passphrase": "" 15 | }, 16 | "confirmations": 1, 17 | "transaction_fee": 0.0001, 18 | "currency": "BTC" 19 | } -------------------------------------------------------------------------------- /core_api.js: -------------------------------------------------------------------------------- 1 | // Configure logger 2 | if (process.env.NODE_ENV === "production") require("./configs/logger"); 3 | 4 | // Configure modules 5 | var restify = require('restify'); 6 | var environment = process.env.NODE_ENV || 'development'; 7 | 8 | // Configure globals 9 | GLOBAL.appConfig = require("./configs/config"); 10 | GLOBAL.wallets = require('./configs/wallets'); 11 | GLOBAL.db = require('./models/index'); 12 | GLOBAL.queue = require('./lib/queue/index'); 13 | 14 | // Setup express 15 | var server = restify.createServer(); 16 | server.use(restify.bodyParser()); 17 | var port = process.env.PORT || 6000; 18 | server.listen(process.env.PORT || 6000, function(){ 19 | console.log("Coinnext Core API is running on port %d in %s mode", port, environment); 20 | }); 21 | 22 | 23 | // Routes 24 | require('./routes/core_api/wallets')(server); 25 | require('./routes/core_api/transactions')(server); 26 | require('./routes/core_api/trade')(server); 27 | require('./routes/core_api/stats')(server); -------------------------------------------------------------------------------- /cron_dump/currency.dump.sample: -------------------------------------------------------------------------------- 1 | BTC -------------------------------------------------------------------------------- /cron_dump/currency_exclude.dump.sample: -------------------------------------------------------------------------------- 1 | VIO -------------------------------------------------------------------------------- /lib/admin_auth.coffee: -------------------------------------------------------------------------------- 1 | LocalStrategy = require('passport-local').Strategy 2 | AdminUser = GLOBAL.db.AdminUser 3 | strategyConfig = 4 | usernameField: "email" 5 | passwordField: "password" 6 | 7 | passport.use new LocalStrategy strategyConfig, (email, password, done)-> 8 | AdminUser.findByEmail email, (err, user)-> 9 | return done(err) if err 10 | return done(null, false, { message: 'Incorrect email.' }) if not user 11 | return done(null, false, { message: 'Incorrect password.' }) if not user.isValidPassword password 12 | return done(null, user) 13 | 14 | passport.serializeUser (user, done)-> 15 | done(null, user.id) 16 | 17 | passport.deserializeUser (id, done)-> 18 | AdminUser.findById id, done 19 | -------------------------------------------------------------------------------- /lib/admin_auth.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var AdminUser, LocalStrategy, strategyConfig; 3 | 4 | LocalStrategy = require('passport-local').Strategy; 5 | 6 | AdminUser = GLOBAL.db.AdminUser; 7 | 8 | strategyConfig = { 9 | usernameField: "email", 10 | passwordField: "password" 11 | }; 12 | 13 | passport.use(new LocalStrategy(strategyConfig, function(email, password, done) { 14 | return AdminUser.findByEmail(email, function(err, user) { 15 | if (err) { 16 | return done(err); 17 | } 18 | if (!user) { 19 | return done(null, false, { 20 | message: 'Incorrect email.' 21 | }); 22 | } 23 | if (!user.isValidPassword(password)) { 24 | return done(null, false, { 25 | message: 'Incorrect password.' 26 | }); 27 | } 28 | return done(null, user); 29 | }); 30 | })); 31 | 32 | passport.serializeUser(function(user, done) { 33 | return done(null, user.id); 34 | }); 35 | 36 | passport.deserializeUser(function(id, done) { 37 | return AdminUser.findById(id, done); 38 | }); 39 | 40 | }).call(this); 41 | -------------------------------------------------------------------------------- /lib/auth.coffee: -------------------------------------------------------------------------------- 1 | LocalStrategy = require('passport-local').Strategy 2 | User = GLOBAL.db.User 3 | strategyConfig = 4 | usernameField: "email" 5 | passwordField: "password" 6 | passReqToCallback: false 7 | 8 | passport.use "local", new LocalStrategy strategyConfig, (email, password, done)-> 9 | User.findByEmail email, (err, user)-> 10 | return done(err) if err 11 | return done(null, false, { message: 'Incorrect email.' }) if not user 12 | return done(null, false, { message: 'Incorrect password.' }) if not user.isValidPassword password 13 | done(null, user) 14 | 15 | passport.serializeUser (user, done)-> 16 | done(null, user.id) 17 | 18 | passport.deserializeUser (id, done)-> 19 | User.findById id, done 20 | -------------------------------------------------------------------------------- /lib/auth.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var LocalStrategy, User, strategyConfig; 3 | 4 | LocalStrategy = require('passport-local').Strategy; 5 | 6 | User = GLOBAL.db.User; 7 | 8 | strategyConfig = { 9 | usernameField: "email", 10 | passwordField: "password", 11 | passReqToCallback: false 12 | }; 13 | 14 | passport.use("local", new LocalStrategy(strategyConfig, function(email, password, done) { 15 | return User.findByEmail(email, function(err, user) { 16 | if (err) { 17 | return done(err); 18 | } 19 | if (!user) { 20 | return done(null, false, { 21 | message: 'Incorrect email.' 22 | }); 23 | } 24 | if (!user.isValidPassword(password)) { 25 | return done(null, false, { 26 | message: 'Incorrect password.' 27 | }); 28 | } 29 | return done(null, user); 30 | }); 31 | })); 32 | 33 | passport.serializeUser(function(user, done) { 34 | return done(null, user.id); 35 | }); 36 | 37 | passport.deserializeUser(function(id, done) { 38 | return User.findById(id, done); 39 | }); 40 | 41 | }).call(this); 42 | -------------------------------------------------------------------------------- /lib/client_socket.coffee: -------------------------------------------------------------------------------- 1 | redis = require "redis" 2 | 3 | class ClientSocket 4 | 5 | namespace: "users" 6 | 7 | pub: null 8 | 9 | constructor: (options = {})-> 10 | @namespace = options.namespace if options.namespace 11 | @pub = redis.createClient options.redis.port, options.redis.host, {auth_pass: options.redis.pass} 12 | 13 | send: (data)-> 14 | data.namespace = @namespace 15 | @pub.publish "external-events", JSON.stringify data 16 | 17 | close: ()-> 18 | try 19 | @pub.quit() if @pub 20 | catch e 21 | console.error "Could not close Pub connection #{@namespace}", e 22 | 23 | exports = module.exports = ClientSocket -------------------------------------------------------------------------------- /lib/client_socket.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var ClientSocket, exports, redis; 3 | 4 | redis = require("redis"); 5 | 6 | ClientSocket = (function() { 7 | ClientSocket.prototype.namespace = "users"; 8 | 9 | ClientSocket.prototype.pub = null; 10 | 11 | function ClientSocket(options) { 12 | if (options == null) { 13 | options = {}; 14 | } 15 | if (options.namespace) { 16 | this.namespace = options.namespace; 17 | } 18 | this.pub = redis.createClient(options.redis.port, options.redis.host, { 19 | auth_pass: options.redis.pass 20 | }); 21 | } 22 | 23 | ClientSocket.prototype.send = function(data) { 24 | data.namespace = this.namespace; 25 | return this.pub.publish("external-events", JSON.stringify(data)); 26 | }; 27 | 28 | ClientSocket.prototype.close = function() { 29 | var e; 30 | try { 31 | if (this.pub) { 32 | return this.pub.quit(); 33 | } 34 | } catch (_error) { 35 | e = _error; 36 | return console.error("Could not close Pub connection " + this.namespace, e); 37 | } 38 | }; 39 | 40 | return ClientSocket; 41 | 42 | })(); 43 | 44 | exports = module.exports = ClientSocket; 45 | 46 | }).call(this); 47 | -------------------------------------------------------------------------------- /lib/core_api_client.coffee: -------------------------------------------------------------------------------- 1 | request = require "request" 2 | 3 | class CoreAPIClient 4 | 5 | host: null 6 | 7 | commands: 8 | "create_account": "post" 9 | "publish_order": "post" 10 | "cancel_order": "del" 11 | "create_payment": "post" 12 | "process_payment": "put" 13 | "cancel_payment": "del" 14 | "wallet_balance": "get" 15 | "wallet_info": "get" 16 | 17 | constructor: (options = {})-> 18 | @host = options.host if options.host 19 | 20 | send: (command, data, callback = ()->)-> 21 | url = "http://#{@host}/#{command}" 22 | for param in data 23 | url += "/#{param}" 24 | if @commands[command] 25 | try 26 | request[@commands[command]](url, {json: true}, callback) 27 | catch e 28 | console.error e 29 | callback "Bad response '#{e}'" 30 | else 31 | callback "Invalid command '#{command}'" 32 | 33 | sendWithData: (command, data, callback = ()->)-> 34 | uri = "http://#{@host}/#{command}" 35 | if @commands[command] 36 | options = 37 | uri: uri 38 | method: @commands[command] 39 | json: data 40 | try 41 | request options, callback 42 | catch e 43 | console.error e 44 | callback "Bad response #{e}" 45 | else 46 | callback "Invalid command '#{command}'" 47 | 48 | exports = module.exports = CoreAPIClient -------------------------------------------------------------------------------- /lib/core_api_client.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var CoreAPIClient, exports, request; 3 | 4 | request = require("request"); 5 | 6 | CoreAPIClient = (function() { 7 | CoreAPIClient.prototype.host = null; 8 | 9 | CoreAPIClient.prototype.commands = { 10 | "create_account": "post", 11 | "publish_order": "post", 12 | "cancel_order": "del", 13 | "create_payment": "post", 14 | "process_payment": "put", 15 | "cancel_payment": "del", 16 | "wallet_balance": "get", 17 | "wallet_info": "get" 18 | }; 19 | 20 | function CoreAPIClient(options) { 21 | if (options == null) { 22 | options = {}; 23 | } 24 | if (options.host) { 25 | this.host = options.host; 26 | } 27 | } 28 | 29 | CoreAPIClient.prototype.send = function(command, data, callback) { 30 | var e, param, url, _i, _len; 31 | if (callback == null) { 32 | callback = function() {}; 33 | } 34 | url = "http://" + this.host + "/" + command; 35 | for (_i = 0, _len = data.length; _i < _len; _i++) { 36 | param = data[_i]; 37 | url += "/" + param; 38 | } 39 | if (this.commands[command]) { 40 | try { 41 | return request[this.commands[command]](url, { 42 | json: true 43 | }, callback); 44 | } catch (_error) { 45 | e = _error; 46 | console.error(e); 47 | return callback("Bad response '" + e + "'"); 48 | } 49 | } else { 50 | return callback("Invalid command '" + command + "'"); 51 | } 52 | }; 53 | 54 | CoreAPIClient.prototype.sendWithData = function(command, data, callback) { 55 | var e, options, uri; 56 | if (callback == null) { 57 | callback = function() {}; 58 | } 59 | uri = "http://" + this.host + "/" + command; 60 | if (this.commands[command]) { 61 | options = { 62 | uri: uri, 63 | method: this.commands[command], 64 | json: data 65 | }; 66 | try { 67 | return request(options, callback); 68 | } catch (_error) { 69 | e = _error; 70 | console.error(e); 71 | return callback("Bad response " + e); 72 | } 73 | } else { 74 | return callback("Invalid command '" + command + "'"); 75 | } 76 | }; 77 | 78 | return CoreAPIClient; 79 | 80 | })(); 81 | 82 | exports = module.exports = CoreAPIClient; 83 | 84 | }).call(this); 85 | -------------------------------------------------------------------------------- /lib/crypto_wallets/doge_wallet.coffee: -------------------------------------------------------------------------------- 1 | CryptoWallet = require "../crypto_wallet" 2 | 3 | class DogeWallet extends CryptoWallet 4 | 5 | getBalance: (account, callback)-> 6 | @client.getBalance account, (err, balance)=> 7 | balance = if balance.result? then balance.result else balance 8 | balance = @convert @initialCurrency, @currency, balance 9 | callback(err, balance) if callback 10 | 11 | exports = module.exports = DogeWallet -------------------------------------------------------------------------------- /lib/crypto_wallets/doge_wallet.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var CryptoWallet, DogeWallet, exports, 3 | __hasProp = {}.hasOwnProperty, 4 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; 5 | 6 | CryptoWallet = require("../crypto_wallet"); 7 | 8 | DogeWallet = (function(_super) { 9 | __extends(DogeWallet, _super); 10 | 11 | function DogeWallet() { 12 | return DogeWallet.__super__.constructor.apply(this, arguments); 13 | } 14 | 15 | DogeWallet.prototype.getBalance = function(account, callback) { 16 | return this.client.getBalance(account, (function(_this) { 17 | return function(err, balance) { 18 | balance = balance.result != null ? balance.result : balance; 19 | balance = _this.convert(_this.initialCurrency, _this.currency, balance); 20 | if (callback) { 21 | return callback(err, balance); 22 | } 23 | }; 24 | })(this)); 25 | }; 26 | 27 | return DogeWallet; 28 | 29 | })(CryptoWallet); 30 | 31 | exports = module.exports = DogeWallet; 32 | 33 | }).call(this); 34 | -------------------------------------------------------------------------------- /lib/emailer.coffee: -------------------------------------------------------------------------------- 1 | emailer = require("nodemailer") 2 | fs = require("fs") 3 | _ = require("underscore") 4 | 5 | class Emailer 6 | 7 | options: {} 8 | 9 | data: {} 10 | 11 | attachments: [ 12 | ] 13 | 14 | constructor: (@options, @data)-> 15 | @setUrls() 16 | 17 | send: (callback)-> 18 | html = @getHtml(@options.template, @data) 19 | attachments = @getAttachments(html) 20 | messageData = 21 | to: @options.to.email 22 | from: GLOBAL.appConfig().emailer.from 23 | subject: @options.subject 24 | html: html 25 | generateTextFromHTML: true 26 | attachments: attachments 27 | transport = @getTransport() 28 | return callback() if not GLOBAL.appConfig().emailer.enabled 29 | transport.sendMail messageData, callback 30 | 31 | getTransport: ()-> 32 | emailer.createTransport "SMTP", GLOBAL.appConfig().emailer.transport 33 | 34 | getHtml: (templateName, data)-> 35 | templatePath = "./views/emails/#{templateName}.html" 36 | templateContent = fs.readFileSync(templatePath, encoding = "utf8") 37 | _.template templateContent, data, {interpolate: /\{\{(.+?)\}\}/g} 38 | 39 | getAttachments: (html)-> 40 | attachments = [] 41 | for attachment in @attachments 42 | attachments.push(attachment) if html.search("cid:#{attachment.cid}") > -1 43 | attachments 44 | 45 | setUrls: ()-> 46 | @data.site_url = (GLOBAL.appConfig().emailer.host or @data.site_url) 47 | @data.img_path = (GLOBAL.appConfig().assets_host or @data.site_url) + "/img/email" 48 | @data.img_version = if GLOBAL.appConfig().assets_key then "?v=#{GLOBAL.appConfig().assets_key}" else "" 49 | 50 | exports = module.exports = Emailer 51 | -------------------------------------------------------------------------------- /lib/emailer.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var Emailer, emailer, exports, fs, _; 3 | 4 | emailer = require("nodemailer"); 5 | 6 | fs = require("fs"); 7 | 8 | _ = require("underscore"); 9 | 10 | Emailer = (function() { 11 | Emailer.prototype.options = {}; 12 | 13 | Emailer.prototype.data = {}; 14 | 15 | Emailer.prototype.attachments = []; 16 | 17 | function Emailer(options, data) { 18 | this.options = options; 19 | this.data = data; 20 | this.setUrls(); 21 | } 22 | 23 | Emailer.prototype.send = function(callback) { 24 | var attachments, html, messageData, transport; 25 | html = this.getHtml(this.options.template, this.data); 26 | attachments = this.getAttachments(html); 27 | messageData = { 28 | to: this.options.to.email, 29 | from: GLOBAL.appConfig().emailer.from, 30 | subject: this.options.subject, 31 | html: html, 32 | generateTextFromHTML: true, 33 | attachments: attachments 34 | }; 35 | transport = this.getTransport(); 36 | if (!GLOBAL.appConfig().emailer.enabled) { 37 | return callback(); 38 | } 39 | return transport.sendMail(messageData, callback); 40 | }; 41 | 42 | Emailer.prototype.getTransport = function() { 43 | return emailer.createTransport("SMTP", GLOBAL.appConfig().emailer.transport); 44 | }; 45 | 46 | Emailer.prototype.getHtml = function(templateName, data) { 47 | var encoding, templateContent, templatePath; 48 | templatePath = "./views/emails/" + templateName + ".html"; 49 | templateContent = fs.readFileSync(templatePath, encoding = "utf8"); 50 | return _.template(templateContent, data, { 51 | interpolate: /\{\{(.+?)\}\}/g 52 | }); 53 | }; 54 | 55 | Emailer.prototype.getAttachments = function(html) { 56 | var attachment, attachments, _i, _len, _ref; 57 | attachments = []; 58 | _ref = this.attachments; 59 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 60 | attachment = _ref[_i]; 61 | if (html.search("cid:" + attachment.cid) > -1) { 62 | attachments.push(attachment); 63 | } 64 | } 65 | return attachments; 66 | }; 67 | 68 | Emailer.prototype.setUrls = function() { 69 | this.data.site_url = GLOBAL.appConfig().emailer.host || this.data.site_url; 70 | this.data.img_path = (GLOBAL.appConfig().assets_host || this.data.site_url) + "/img/email"; 71 | return this.data.img_version = GLOBAL.appConfig().assets_key ? "?v=" + (GLOBAL.appConfig().assets_key) : ""; 72 | }; 73 | 74 | return Emailer; 75 | 76 | })(); 77 | 78 | exports = module.exports = Emailer; 79 | 80 | }).call(this); 81 | -------------------------------------------------------------------------------- /lib/json_beautifier.coffee: -------------------------------------------------------------------------------- 1 | JsonBeautifier = 2 | 3 | constructor: ()-> 4 | 5 | # Format JSON function 6 | # http://ketanjetty.com/coldfusion/javascript/format-json/ 7 | toHTML: (jsonString)-> 8 | jsonString = JSON.stringify(jsonString) if typeof jsonString is "object" 9 | jsonString.replace(/(\\'|\\")/g, "") 10 | retval = '' 11 | pos = 0 12 | strLen = jsonString.length 13 | indentStr = '    ' 14 | newLine = '
' 15 | 16 | for char in jsonString 17 | 18 | if char is "}" or char is "]" 19 | retval = retval + newLine 20 | pos = pos - 1 21 | 22 | j = 0 23 | while j < pos 24 | retval = retval + indentStr 25 | j++ 26 | 27 | retval = retval + char 28 | 29 | if char is "{" or char is "[" or char is "," 30 | retval = retval + newLine 31 | pos = pos + 1 if char is "{" or char is "[" 32 | 33 | k = 0 34 | while k < pos 35 | retval = retval + indentStr 36 | k++ 37 | 38 | retval 39 | 40 | exports = module.exports = JsonBeautifier -------------------------------------------------------------------------------- /lib/json_beautifier.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var JsonBeautifier, exports; 3 | 4 | JsonBeautifier = { 5 | constructor: function() {}, 6 | toHTML: function(jsonString) { 7 | var char, indentStr, j, k, newLine, pos, retval, strLen, _i, _len; 8 | if (typeof jsonString === "object") { 9 | jsonString = JSON.stringify(jsonString); 10 | } 11 | jsonString.replace(/(\\'|\\")/g, ""); 12 | retval = ''; 13 | pos = 0; 14 | strLen = jsonString.length; 15 | indentStr = '    '; 16 | newLine = '
'; 17 | for (_i = 0, _len = jsonString.length; _i < _len; _i++) { 18 | char = jsonString[_i]; 19 | if (char === "}" || char === "]") { 20 | retval = retval + newLine; 21 | pos = pos - 1; 22 | j = 0; 23 | while (j < pos) { 24 | retval = retval + indentStr; 25 | j++; 26 | } 27 | } 28 | retval = retval + char; 29 | if (char === "{" || char === "[" || char === ",") { 30 | retval = retval + newLine; 31 | if (char === "{" || char === "[") { 32 | pos = pos + 1; 33 | } 34 | k = 0; 35 | while (k < pos) { 36 | retval = retval + indentStr; 37 | k++; 38 | } 39 | } 40 | } 41 | return retval; 42 | } 43 | }; 44 | 45 | exports = module.exports = JsonBeautifier; 46 | 47 | }).call(this); 48 | -------------------------------------------------------------------------------- /lib/math.coffee: -------------------------------------------------------------------------------- 1 | exports = module.exports = require("mathjs") 2 | number: "bignumber" 3 | precision: 20 4 | -------------------------------------------------------------------------------- /lib/math.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var exports; 3 | 4 | exports = module.exports = require("mathjs")({ 5 | number: "bignumber", 6 | precision: 20 7 | }); 8 | 9 | }).call(this); 10 | -------------------------------------------------------------------------------- /lib/queue/event.coffee: -------------------------------------------------------------------------------- 1 | MarketHelper = require "../market_helper" 2 | 3 | module.exports = (sequelize, DataTypes) -> 4 | 5 | EVENTS_FETCH_LIMIT = 1 6 | VALID_EVENTS = [ 7 | MarketHelper.getEventType "order_canceled" 8 | MarketHelper.getEventType "order_added" 9 | MarketHelper.getEventType "orders_match" 10 | ] 11 | 12 | Event = sequelize.define "Event", 13 | type: 14 | type: DataTypes.INTEGER.UNSIGNED 15 | allowNull: false 16 | comment: "orders_match, cancel_order, order_canceled, add_order, order_added" 17 | get: ()-> 18 | MarketHelper.getEventTypeLiteral @getDataValue("type") 19 | set: (type)-> 20 | @setDataValue "type", MarketHelper.getEventType(type) 21 | loadout: 22 | type: DataTypes.TEXT 23 | allowNull: true 24 | get: ()-> 25 | JSON.parse @getDataValue("loadout") 26 | set: (loadout)-> 27 | @setDataValue "loadout", JSON.stringify(loadout) 28 | status: 29 | type: DataTypes.INTEGER.UNSIGNED 30 | allowNull: false 31 | defaultValue: MarketHelper.getEventStatus "pending" 32 | comment: "pending, processed" 33 | get: ()-> 34 | MarketHelper.getEventStatusLiteral @getDataValue("status") 35 | set: (status)-> 36 | @setDataValue "status", MarketHelper.getEventStatus(status) 37 | , 38 | tableName: "events" 39 | classMethods: 40 | 41 | addOrder: (loadout, callback = ()->)-> 42 | data = 43 | type: "add_order" 44 | loadout: loadout 45 | status: "pending" 46 | Event.create(data).complete callback 47 | 48 | addCancelOrder: (loadout, callback = ()->)-> 49 | data = 50 | type: "cancel_order" 51 | loadout: loadout 52 | status: "pending" 53 | Event.create(data).complete callback 54 | 55 | findNext: (type = null, callback = ()->)-> 56 | query = 57 | where: 58 | status: MarketHelper.getEventStatus "pending" 59 | order: [ 60 | ["created_at", "ASC"] 61 | ] 62 | limit: EVENTS_FETCH_LIMIT 63 | query.where.type = MarketHelper.getEventType type if type 64 | Event.find(query).complete callback 65 | 66 | findNextValid: (callback = ()->)-> 67 | query = 68 | where: 69 | status: MarketHelper.getEventStatus "pending" 70 | type: VALID_EVENTS 71 | order: [ 72 | ["created_at", "ASC"] 73 | ] 74 | limit: EVENTS_FETCH_LIMIT 75 | Event.find(query).complete callback 76 | 77 | Event 78 | -------------------------------------------------------------------------------- /lib/queue/index.coffee: -------------------------------------------------------------------------------- 1 | fs = require("fs") 2 | path = require("path") 3 | Sequelize = require("sequelize") 4 | lodash = require("lodash") 5 | 6 | authData = GLOBAL.appConfig().queue 7 | sequelize = new Sequelize authData.db, authData.user, authData.password, 8 | port: authData.port 9 | host: authData.host 10 | logging: authData.logging 11 | maxConcurrentQueries: 100 12 | define: 13 | underscored: true 14 | freezeTableName: false 15 | syncOnAssociation: true 16 | charset: "utf8" 17 | collate: "utf8_general_ci" 18 | timestamps: true 19 | pool: 20 | maxConnections: 100 21 | maxIdleTime: 30 22 | db = {} 23 | 24 | fs.readdirSync(__dirname).filter((file) -> 25 | (file.indexOf(".") isnt 0) and (file.indexOf(".js") isnt -1) and (file isnt "index.js") and (file isnt "associations.js") 26 | ).forEach (file) -> 27 | model = sequelize.import(path.join(__dirname, file)) 28 | db[model.name] = model 29 | return 30 | 31 | Object.keys(db).forEach (modelName) -> 32 | db[modelName].associate db if "associate" of db[modelName] 33 | return 34 | 35 | module.exports = lodash.extend( 36 | sequelize: sequelize 37 | Sequelize: Sequelize 38 | , db) -------------------------------------------------------------------------------- /lib/queue/index.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var Sequelize, authData, db, fs, lodash, path, sequelize; 3 | 4 | fs = require("fs"); 5 | 6 | path = require("path"); 7 | 8 | Sequelize = require("sequelize"); 9 | 10 | lodash = require("lodash"); 11 | 12 | authData = GLOBAL.appConfig().queue; 13 | 14 | sequelize = new Sequelize(authData.db, authData.user, authData.password, { 15 | port: authData.port, 16 | host: authData.host, 17 | logging: authData.logging, 18 | maxConcurrentQueries: 100, 19 | define: { 20 | underscored: true, 21 | freezeTableName: false, 22 | syncOnAssociation: true, 23 | charset: "utf8", 24 | collate: "utf8_general_ci", 25 | timestamps: true 26 | }, 27 | pool: { 28 | maxConnections: 100, 29 | maxIdleTime: 30 30 | } 31 | }); 32 | 33 | db = {}; 34 | 35 | fs.readdirSync(__dirname).filter(function(file) { 36 | return (file.indexOf(".") !== 0) && (file.indexOf(".js") !== -1) && (file !== "index.js") && (file !== "associations.js"); 37 | }).forEach(function(file) { 38 | var model; 39 | model = sequelize["import"](path.join(__dirname, file)); 40 | db[model.name] = model; 41 | }); 42 | 43 | Object.keys(db).forEach(function(modelName) { 44 | if ("associate" in db[modelName]) { 45 | db[modelName].associate(db); 46 | } 47 | }); 48 | 49 | module.exports = lodash.extend({ 50 | sequelize: sequelize, 51 | Sequelize: Sequelize 52 | }, db); 53 | 54 | }).call(this); 55 | -------------------------------------------------------------------------------- /lib/queue/migrations/20140522192430-add_indexes.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | up: (migration, DataTypes, done) -> 3 | migration.addIndex "events", ["status"] 4 | migration.addIndex "events", ["created_at"] 5 | 6 | done() 7 | return 8 | 9 | down: (migration, DataTypes, done) -> 10 | migration.removeIndex "events", ["status"] 11 | migration.removeIndex "events", ["created_at"] 12 | 13 | done() 14 | return -------------------------------------------------------------------------------- /lib/queue/migrations/20140522192430-add_indexes.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | module.exports = { 3 | up: function(migration, DataTypes, done) { 4 | migration.addIndex("events", ["status"]); 5 | migration.addIndex("events", ["created_at"]); 6 | done(); 7 | }, 8 | down: function(migration, DataTypes, done) { 9 | migration.removeIndex("events", ["status"]); 10 | migration.removeIndex("events", ["created_at"]); 11 | done(); 12 | } 13 | }; 14 | 15 | }).call(this); 16 | -------------------------------------------------------------------------------- /lib/sockets.coffee: -------------------------------------------------------------------------------- 1 | io = require "socket.io" 2 | SessionSockets = require "session.socket.io" 3 | redis = require "redis" 4 | SocketsRedisStore = require "socket.io/lib/stores/redis" 5 | socketPub = redis.createClient GLOBAL.appConfig().redis.port, GLOBAL.appConfig().redis.host, {auth_pass: GLOBAL.appConfig().redis.pass} 6 | socketSub = redis.createClient GLOBAL.appConfig().redis.port, GLOBAL.appConfig().redis.host, {auth_pass: GLOBAL.appConfig().redis.pass} 7 | socketClient = redis.createClient GLOBAL.appConfig().redis.port, GLOBAL.appConfig().redis.host, {auth_pass: GLOBAL.appConfig().redis.pass} 8 | externalEventsSub = redis.createClient GLOBAL.appConfig().redis.port, GLOBAL.appConfig().redis.host, {auth_pass: GLOBAL.appConfig().redis.pass} 9 | 10 | Chat = GLOBAL.db.Chat 11 | JsonRenderer = require "./json_renderer" 12 | 13 | sockets = {} 14 | 15 | initSockets = (server, env, sessionStore, cookieParser)-> 16 | ioOptions = 17 | log: if env is "production" then false else false 18 | 19 | sockets.io = io.listen server, ioOptions 20 | 21 | sockets.io.configure "production", ()-> 22 | sockets.io.enable "browser client minification" 23 | sockets.io.enable "browser client etag" 24 | sockets.io.enable "browser client gzip" 25 | sockets.io.set "origins", "#{GLOBAL.appConfig().users.hostname}:*" 26 | 27 | sockets.io.set "store", new SocketsRedisStore 28 | redis: redis 29 | redisPub: socketPub 30 | redisSub: socketSub 31 | redisClient: socketClient 32 | 33 | externalEventsSub.subscribe "external-events" 34 | externalEventsSub.on "message", (channel, data)-> 35 | if channel is "external-events" 36 | try 37 | data = JSON.parse data 38 | if data.namespace is "users" 39 | for sId, so of sockets.io.of("/users").sockets 40 | so.emit data.type, data.eventData if so.user_id is data.user_id 41 | if data.namespace is "orders" 42 | sockets.io.of("/orders").emit data.type, data.eventData 43 | catch e 44 | console.error "Could not emit to socket #{data}: #{e}" 45 | @ 46 | 47 | sockets.sessionSockets = new SessionSockets sockets.io, sessionStore, cookieParser, GLOBAL.appConfig().session.session_key 48 | 49 | sockets.sessionSockets.of("/users").on "connection", (err, socket, session)-> 50 | socket.user_id = session.passport.user if session and session.passport 51 | 52 | sockets.io.of("/orders").on "connection", (socket)-> 53 | 54 | sockets.sessionSockets.of("/chat").on "connection", (err, socket, session)-> 55 | socket.user_id = session.passport.user if session and session.passport 56 | socket.on "add-message", (data)-> 57 | return if not socket.user_id 58 | data.user_id = socket.user_id 59 | Chat.addMessage data, (err, message)-> 60 | return console.error err if err 61 | message.getUser().success (user)-> 62 | sockets.io.of("/chat").emit "new-message", JsonRenderer.chatMessage message, user 63 | @ 64 | 65 | sockets 66 | 67 | exports = module.exports = initSockets -------------------------------------------------------------------------------- /lib/underscore_string.coffee: -------------------------------------------------------------------------------- 1 | _str = require "underscore.string" 2 | 3 | _str.roundTo = (number, decimals = 8)-> 4 | multiplier = Math.pow(10, decimals) 5 | Math.round(parseFloat(number) * multiplier) / multiplier 6 | 7 | _str.satoshiRound = (number)-> 8 | _str.roundTo number, 8 9 | 10 | _str.toFixed = (number, decimals = 8)-> 11 | parseFloat(number).toFixed(decimals) 12 | 13 | exports = module.exports = _str -------------------------------------------------------------------------------- /lib/underscore_string.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var exports, _str; 3 | 4 | _str = require("underscore.string"); 5 | 6 | _str.roundTo = function(number, decimals) { 7 | var multiplier; 8 | if (decimals == null) { 9 | decimals = 8; 10 | } 11 | multiplier = Math.pow(10, decimals); 12 | return Math.round(parseFloat(number) * multiplier) / multiplier; 13 | }; 14 | 15 | _str.satoshiRound = function(number) { 16 | return _str.roundTo(number, 8); 17 | }; 18 | 19 | _str.toFixed = function(number, decimals) { 20 | if (decimals == null) { 21 | decimals = 8; 22 | } 23 | return parseFloat(number).toFixed(decimals); 24 | }; 25 | 26 | exports = module.exports = _str; 27 | 28 | }).call(this); 29 | -------------------------------------------------------------------------------- /models/admin_user.coffee: -------------------------------------------------------------------------------- 1 | crypto = require "crypto" 2 | speakeasy = require "speakeasy" 3 | _ = require "underscore" 4 | 5 | module.exports = (sequelize, DataTypes) -> 6 | 7 | AdminUser = sequelize.define "AdminUser", 8 | email: 9 | type: DataTypes.STRING 10 | allowNull: false 11 | unique: true 12 | validate: 13 | isEmail: true 14 | password: 15 | type: DataTypes.STRING 16 | allowNull: false 17 | validate: 18 | len: [5, 500] 19 | gauth_key: 20 | type: DataTypes.STRING(32) 21 | unique: true 22 | , 23 | tableName: "admin_users" 24 | classMethods: 25 | 26 | findById: (id, callback = ()->)-> 27 | AdminUser.find(id).complete callback 28 | 29 | findByEmail: (email, callback = ()->)-> 30 | AdminUser.find({where:{email: email}}).complete callback 31 | 32 | hashPassword: (password)-> 33 | crypto.createHash("sha256").update("#{password}#{GLOBAL.appConfig().salt}", "utf8").digest("hex") 34 | 35 | createNewUser: (data, callback)-> 36 | userData = _.extend({}, data) 37 | userData.password = AdminUser.hashPassword userData.password 38 | AdminUser.create(userData).complete callback 39 | 40 | instanceMethods: 41 | 42 | isValidPassword: (password)-> 43 | @password is AdminUser.hashPassword(password) 44 | 45 | generateGAuthData: (callback = ()->)-> 46 | data = speakeasy.generate_key 47 | name: "administratiecnx" 48 | length: 20 49 | google_auth_qr: true 50 | @gauth_key = data.base32 51 | @save().complete (err, user)-> 52 | callback data, user 53 | 54 | isValidGAuthPass: (pass)-> 55 | currentPass = speakeasy.time 56 | key: @gauth_key 57 | encoding: "base32" 58 | currentPass is pass 59 | 60 | generateToken: (callback = ()->)-> 61 | @token = crypto.createHash("sha256").update("#{@_id}#{GLOBAL.appConfig().salt}#{Date.now()}", "utf8").digest("hex") 62 | @save().complete (err, u)-> 63 | callback(u.token) 64 | 65 | AdminUser -------------------------------------------------------------------------------- /models/auth_stats.coffee: -------------------------------------------------------------------------------- 1 | require "date-utils" 2 | ipFormatter = require "ip" 3 | Emailer = require "../lib/emailer" 4 | 5 | module.exports = (sequelize, DataTypes) -> 6 | 7 | AuthStats = sequelize.define "AuthStats", 8 | user_id: 9 | type: DataTypes.INTEGER.UNSIGNED 10 | allowNull: false 11 | ip: 12 | type: DataTypes.INTEGER 13 | allowNull: true 14 | set: (ip)-> 15 | @setDataValue "ip", ipFormatter.toLong ip 16 | get: ()-> 17 | ipFormatter.fromLong @getDataValue "ip" 18 | , 19 | tableName: "auth_stats" 20 | classMethods: 21 | 22 | findByUser: (userId, callback = ()->)-> 23 | AuthStats.findAll({where: {user_id: userId}}).complete callback 24 | 25 | log: (data, sendByMail = true, callback = ()->)-> 26 | stats = 27 | user_id: data.user.id 28 | ip: data.ip 29 | AuthStats.create(stats).complete (err, authStats)-> 30 | AuthStats.sendUserLoginNotice authStats, data.user.email if sendByMail 31 | callback err, stats 32 | 33 | sendUserLoginNotice: (stats, email, callback = ()->)-> 34 | data = 35 | ip: stats.ip or "unknown" 36 | auth_date: stats.created_at.toFormat "MMMM D, YYYY at HH24:MI" 37 | email: email 38 | options = 39 | to: 40 | email: email 41 | subject: "Login on Coinnext.com" 42 | template: "user_login_notice" 43 | emailer = new Emailer options, data 44 | emailer.send (err, result)-> 45 | console.error err if err 46 | callback() 47 | 48 | AuthStats -------------------------------------------------------------------------------- /models/auth_stats.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var Emailer, ipFormatter; 3 | 4 | require("date-utils"); 5 | 6 | ipFormatter = require("ip"); 7 | 8 | Emailer = require("../lib/emailer"); 9 | 10 | module.exports = function(sequelize, DataTypes) { 11 | var AuthStats; 12 | AuthStats = sequelize.define("AuthStats", { 13 | user_id: { 14 | type: DataTypes.INTEGER.UNSIGNED, 15 | allowNull: false 16 | }, 17 | ip: { 18 | type: DataTypes.INTEGER, 19 | allowNull: true, 20 | set: function(ip) { 21 | return this.setDataValue("ip", ipFormatter.toLong(ip)); 22 | }, 23 | get: function() { 24 | return ipFormatter.fromLong(this.getDataValue("ip")); 25 | } 26 | } 27 | }, { 28 | tableName: "auth_stats", 29 | classMethods: { 30 | findByUser: function(userId, callback) { 31 | if (callback == null) { 32 | callback = function() {}; 33 | } 34 | return AuthStats.findAll({ 35 | where: { 36 | user_id: userId 37 | } 38 | }).complete(callback); 39 | }, 40 | log: function(data, sendByMail, callback) { 41 | var stats; 42 | if (sendByMail == null) { 43 | sendByMail = true; 44 | } 45 | if (callback == null) { 46 | callback = function() {}; 47 | } 48 | stats = { 49 | user_id: data.user.id, 50 | ip: data.ip 51 | }; 52 | return AuthStats.create(stats).complete(function(err, authStats) { 53 | if (sendByMail) { 54 | AuthStats.sendUserLoginNotice(authStats, data.user.email); 55 | } 56 | return callback(err, stats); 57 | }); 58 | }, 59 | sendUserLoginNotice: function(stats, email, callback) { 60 | var data, emailer, options; 61 | if (callback == null) { 62 | callback = function() {}; 63 | } 64 | data = { 65 | ip: stats.ip || "unknown", 66 | auth_date: stats.created_at.toFormat("MMMM D, YYYY at HH24:MI"), 67 | email: email 68 | }; 69 | options = { 70 | to: { 71 | email: email 72 | }, 73 | subject: "Login on Coinnext.com", 74 | template: "user_login_notice" 75 | }; 76 | emailer = new Emailer(options, data); 77 | emailer.send(function(err, result) { 78 | if (err) { 79 | return console.error(err); 80 | } 81 | }); 82 | return callback(); 83 | } 84 | } 85 | }); 86 | return AuthStats; 87 | }; 88 | 89 | }).call(this); 90 | -------------------------------------------------------------------------------- /models/chat.coffee: -------------------------------------------------------------------------------- 1 | _s = require "underscore.string" 2 | 3 | module.exports = (sequelize, DataTypes) -> 4 | 5 | MESSAGES_LIMIT = 10000 6 | GLOBAL_ROOM_NAME = "global" 7 | 8 | Chat = sequelize.define "Chat", 9 | user_id: 10 | type: DataTypes.INTEGER.UNSIGNED 11 | allowNull: false 12 | message: 13 | type: DataTypes.TEXT 14 | allowNull: false 15 | len: [1, 150] 16 | set: (message)-> 17 | @setDataValue "message", _s.truncate _s.trim(message), 150 18 | , 19 | tableName: "chats" 20 | classMethods: 21 | 22 | findLastMessages: (callback)-> 23 | oneDayAgo = new Date (Date.now() - 24*60*60*1000) 24 | query = 25 | where: 26 | created_at: 27 | gt: oneDayAgo 28 | order: [ 29 | ["created_at", "DESC"] 30 | ] 31 | limit: MESSAGES_LIMIT 32 | include: [ 33 | {model: GLOBAL.db.User, attributes: ["username"]} 34 | ] 35 | Chat.findAll(query).complete callback 36 | 37 | findLastUserMessage: (userId, callback)-> 38 | query = 39 | where: 40 | user_id: userId 41 | order: [ 42 | ["created_at", "DESC"] 43 | ] 44 | limit: 1 45 | Chat.find(query).complete callback 46 | 47 | getGlobalRoomName: ()-> 48 | GLOBAL_ROOM_NAME 49 | 50 | addMessage: (data, callback = ()->)-> 51 | Chat.findLastUserMessage data.user_id, (err, message)-> 52 | return callback "Dropping spam message #{data.message} by user #{data.user_id}." if message and message.isSpam(data) 53 | Chat.create(data).complete callback 54 | 55 | instanceMethods: 56 | 57 | isSpam: (newMessage)-> 58 | @isTooEarly() or @isDuplicate(newMessage) 59 | 60 | isDuplicate: (newMessage)-> 61 | @message is newMessage.message 62 | 63 | isTooEarly: ()-> 64 | twoSecondsAgo = new Date (Date.now() - 2*1000) 65 | @created_at >= twoSecondsAgo 66 | 67 | Chat -------------------------------------------------------------------------------- /models/chat.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var _s; 3 | 4 | _s = require("underscore.string"); 5 | 6 | module.exports = function(sequelize, DataTypes) { 7 | var Chat, GLOBAL_ROOM_NAME, MESSAGES_LIMIT; 8 | MESSAGES_LIMIT = 10000; 9 | GLOBAL_ROOM_NAME = "global"; 10 | Chat = sequelize.define("Chat", { 11 | user_id: { 12 | type: DataTypes.INTEGER.UNSIGNED, 13 | allowNull: false 14 | }, 15 | message: { 16 | type: DataTypes.TEXT, 17 | allowNull: false, 18 | len: [1, 150], 19 | set: function(message) { 20 | return this.setDataValue("message", _s.truncate(_s.trim(message), 150)); 21 | } 22 | } 23 | }, { 24 | tableName: "chats", 25 | classMethods: { 26 | findLastMessages: function(callback) { 27 | var oneDayAgo, query; 28 | oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000); 29 | query = { 30 | where: { 31 | created_at: { 32 | gt: oneDayAgo 33 | } 34 | }, 35 | order: [["created_at", "DESC"]], 36 | limit: MESSAGES_LIMIT, 37 | include: [ 38 | { 39 | model: GLOBAL.db.User, 40 | attributes: ["username"] 41 | } 42 | ] 43 | }; 44 | return Chat.findAll(query).complete(callback); 45 | }, 46 | findLastUserMessage: function(userId, callback) { 47 | var query; 48 | query = { 49 | where: { 50 | user_id: userId 51 | }, 52 | order: [["created_at", "DESC"]], 53 | limit: 1 54 | }; 55 | return Chat.find(query).complete(callback); 56 | }, 57 | getGlobalRoomName: function() { 58 | return GLOBAL_ROOM_NAME; 59 | }, 60 | addMessage: function(data, callback) { 61 | if (callback == null) { 62 | callback = function() {}; 63 | } 64 | return Chat.findLastUserMessage(data.user_id, function(err, message) { 65 | if (message && message.isSpam(data)) { 66 | return callback("Dropping spam message " + data.message + " by user " + data.user_id + "."); 67 | } 68 | return Chat.create(data).complete(callback); 69 | }); 70 | } 71 | }, 72 | instanceMethods: { 73 | isSpam: function(newMessage) { 74 | return this.isTooEarly() || this.isDuplicate(newMessage); 75 | }, 76 | isDuplicate: function(newMessage) { 77 | return this.message === newMessage.message; 78 | }, 79 | isTooEarly: function() { 80 | var twoSecondsAgo; 81 | twoSecondsAgo = new Date(Date.now() - 2 * 1000); 82 | return this.created_at >= twoSecondsAgo; 83 | } 84 | } 85 | }); 86 | return Chat; 87 | }; 88 | 89 | }).call(this); 90 | -------------------------------------------------------------------------------- /models/index.coffee: -------------------------------------------------------------------------------- 1 | fs = require("fs") 2 | path = require("path") 3 | Sequelize = require("sequelize") 4 | lodash = require("lodash") 5 | 6 | authData = GLOBAL.appConfig().mysql 7 | sequelize = new Sequelize authData.db, authData.user, authData.password, 8 | port: authData.port 9 | host: authData.host 10 | logging: authData.logging 11 | maxConcurrentQueries: 100 12 | define: 13 | underscored: true 14 | freezeTableName: false 15 | syncOnAssociation: true 16 | charset: "utf8" 17 | collate: "utf8_general_ci" 18 | timestamps: true 19 | pool: 20 | maxConnections: 151 21 | maxIdleTime: 30 22 | db = {} 23 | 24 | fs.readdirSync(__dirname).filter((file) -> 25 | (file.indexOf(".") isnt 0) and (file.indexOf(".js") isnt -1) and (file isnt "index.js") and (file isnt "associations.js") 26 | ).forEach (file) -> 27 | model = sequelize.import(path.join(__dirname, file)) 28 | db[model.name] = model 29 | return 30 | 31 | Object.keys(db).forEach (modelName) -> 32 | db[modelName].associate db if "associate" of db[modelName] 33 | return 34 | 35 | db.User.hasMany db.Chat 36 | db.User.hasMany db.UserToken 37 | db.User.hasMany db.Transaction 38 | db.Chat.belongsTo db.User 39 | db.Transaction.belongsTo db.User 40 | db.UserToken.belongsTo db.User 41 | db.Payment.hasMany db.PaymentLog 42 | db.PaymentLog.belongsTo db.Payment 43 | db.Order.hasMany db.OrderLog 44 | db.OrderLog.belongsTo db.Order 45 | 46 | module.exports = lodash.extend( 47 | sequelize: sequelize 48 | Sequelize: Sequelize 49 | , db) -------------------------------------------------------------------------------- /models/index.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var Sequelize, authData, db, fs, lodash, path, sequelize; 3 | 4 | fs = require("fs"); 5 | 6 | path = require("path"); 7 | 8 | Sequelize = require("sequelize"); 9 | 10 | lodash = require("lodash"); 11 | 12 | authData = GLOBAL.appConfig().mysql; 13 | 14 | sequelize = new Sequelize(authData.db, authData.user, authData.password, { 15 | port: authData.port, 16 | host: authData.host, 17 | logging: authData.logging, 18 | maxConcurrentQueries: 100, 19 | define: { 20 | underscored: true, 21 | freezeTableName: false, 22 | syncOnAssociation: true, 23 | charset: "utf8", 24 | collate: "utf8_general_ci", 25 | timestamps: true 26 | }, 27 | pool: { 28 | maxConnections: 151, 29 | maxIdleTime: 30 30 | } 31 | }); 32 | 33 | db = {}; 34 | 35 | fs.readdirSync(__dirname).filter(function(file) { 36 | return (file.indexOf(".") !== 0) && (file.indexOf(".js") !== -1) && (file !== "index.js") && (file !== "associations.js"); 37 | }).forEach(function(file) { 38 | var model; 39 | model = sequelize["import"](path.join(__dirname, file)); 40 | db[model.name] = model; 41 | }); 42 | 43 | Object.keys(db).forEach(function(modelName) { 44 | if ("associate" in db[modelName]) { 45 | db[modelName].associate(db); 46 | } 47 | }); 48 | 49 | db.User.hasMany(db.Chat); 50 | 51 | db.User.hasMany(db.UserToken); 52 | 53 | db.User.hasMany(db.Transaction); 54 | 55 | db.Chat.belongsTo(db.User); 56 | 57 | db.Transaction.belongsTo(db.User); 58 | 59 | db.UserToken.belongsTo(db.User); 60 | 61 | db.Payment.hasMany(db.PaymentLog); 62 | 63 | db.PaymentLog.belongsTo(db.Payment); 64 | 65 | db.Order.hasMany(db.OrderLog); 66 | 67 | db.OrderLog.belongsTo(db.Order); 68 | 69 | module.exports = lodash.extend({ 70 | sequelize: sequelize, 71 | Sequelize: Sequelize 72 | }, db); 73 | 74 | }).call(this); 75 | -------------------------------------------------------------------------------- /models/migrations/20140425141930-alter_unsigned_amount.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | up: (migration, DataTypes, done) -> 3 | migration.changeColumn "transactions", "amount", 4 | type: DataTypes.BIGINT 5 | defaultValue: 0 6 | allowNull: false 7 | comment: "FLOAT x 100000000" 8 | 9 | done() 10 | return 11 | 12 | down: (migration, DataTypes, done) -> 13 | migration.changeColumn "transactions", "amount", 14 | type: DataTypes.BIGINT.UNSIGNED 15 | defaultValue: 0 16 | allowNull: false 17 | comment: "FLOAT x 100000000" 18 | 19 | done() 20 | return -------------------------------------------------------------------------------- /models/migrations/20140425141930-alter_unsigned_amount.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | module.exports = { 3 | up: function(migration, DataTypes, done) { 4 | migration.changeColumn("transactions", "amount", { 5 | type: DataTypes.BIGINT, 6 | defaultValue: 0, 7 | allowNull: false, 8 | comment: "FLOAT x 100000000" 9 | }); 10 | done(); 11 | }, 12 | down: function(migration, DataTypes, done) { 13 | migration.changeColumn("transactions", "amount", { 14 | type: DataTypes.BIGINT.UNSIGNED, 15 | defaultValue: 0, 16 | allowNull: false, 17 | comment: "FLOAT x 100000000" 18 | }); 19 | done(); 20 | } 21 | }; 22 | 23 | }).call(this); 24 | -------------------------------------------------------------------------------- /models/migrations/20140505195830-add_order_log_indexes.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | up: (migration, DataTypes, done) -> 3 | migration.addIndex "order_logs", ["order_id"] 4 | migration.addIndex "order_logs", ["active"] 5 | migration.addIndex "order_logs", ["time"] 6 | migration.addIndex "order_logs", ["status"] 7 | 8 | done() 9 | return 10 | 11 | down: (migration, DataTypes, done) -> 12 | migration.removeIndex "order_logs", ["order_id"] 13 | migration.removeIndex "order_logs", ["active"] 14 | migration.removeIndex "order_logs", ["time"] 15 | migration.removeIndex "order_logs", ["status"] 16 | 17 | done() 18 | return -------------------------------------------------------------------------------- /models/migrations/20140505195830-add_order_log_indexes.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | module.exports = { 3 | up: function(migration, DataTypes, done) { 4 | migration.addIndex("order_logs", ["order_id"]); 5 | migration.addIndex("order_logs", ["active"]); 6 | migration.addIndex("order_logs", ["time"]); 7 | migration.addIndex("order_logs", ["status"]); 8 | done(); 9 | }, 10 | down: function(migration, DataTypes, done) { 11 | migration.removeIndex("order_logs", ["order_id"]); 12 | migration.removeIndex("order_logs", ["active"]); 13 | migration.removeIndex("order_logs", ["time"]); 14 | migration.removeIndex("order_logs", ["status"]); 15 | done(); 16 | } 17 | }; 18 | 19 | }).call(this); 20 | -------------------------------------------------------------------------------- /models/migrations/20140507195900-alter_unsigned.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | up: (migration, DataTypes, done) -> 3 | migration.changeColumn "market_stats", "growth_ratio", 4 | type: DataTypes.BIGINT 5 | defaultValue: 0 6 | allowNull: false 7 | comment: "FLOAT x 100000000" 8 | 9 | done() 10 | return 11 | 12 | down: (migration, DataTypes, done) -> 13 | migration.changeColumn "market_stats", "growth_ratio", 14 | type: DataTypes.BIGINT.UNSIGNED 15 | defaultValue: 0 16 | allowNull: false 17 | comment: "FLOAT x 100000000" 18 | 19 | done() 20 | return -------------------------------------------------------------------------------- /models/migrations/20140507195900-alter_unsigned.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | module.exports = { 3 | up: function(migration, DataTypes, done) { 4 | migration.changeColumn("market_stats", "growth_ratio", { 5 | type: DataTypes.BIGINT, 6 | defaultValue: 0, 7 | allowNull: false, 8 | comment: "FLOAT x 100000000" 9 | }); 10 | done(); 11 | }, 12 | down: function(migration, DataTypes, done) { 13 | migration.changeColumn("market_stats", "growth_ratio", { 14 | type: DataTypes.BIGINT.UNSIGNED, 15 | defaultValue: 0, 16 | allowNull: false, 17 | comment: "FLOAT x 100000000" 18 | }); 19 | done(); 20 | } 21 | }; 22 | 23 | }).call(this); 24 | -------------------------------------------------------------------------------- /models/migrations/20140524211930-add_indexes.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | up: (migration, DataTypes, done) -> 3 | migration.addIndex "orders", ["deleted_at"] 4 | 5 | done() 6 | return 7 | 8 | down: (migration, DataTypes, done) -> 9 | migration.removeIndex "orders", ["deleted_at"] 10 | 11 | done() 12 | return -------------------------------------------------------------------------------- /models/migrations/20140524211930-add_indexes.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | module.exports = { 3 | up: function(migration, DataTypes, done) { 4 | migration.addIndex("orders", ["deleted_at"]); 5 | done(); 6 | }, 7 | down: function(migration, DataTypes, done) { 8 | migration.removeIndex("orders", ["deleted_at"]); 9 | done(); 10 | } 11 | }; 12 | 13 | }).call(this); 14 | -------------------------------------------------------------------------------- /models/migrations/20140607215430-add_indexes.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | up: (migration, DataTypes, done) -> 3 | migration.addIndex "wallet_health", ["currency"] 4 | migration.addIndex "wallet_health", ["status"] 5 | 6 | done() 7 | return 8 | 9 | down: (migration, DataTypes, done) -> 10 | migration.removeIndex "wallet_health", ["currency"] 11 | migration.removeIndex "wallet_health", ["status"] 12 | 13 | done() 14 | return -------------------------------------------------------------------------------- /models/migrations/20140607215430-add_indexes.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | module.exports = { 3 | up: function(migration, DataTypes, done) { 4 | migration.addIndex("wallet_health", ["currency"]); 5 | migration.addIndex("wallet_health", ["status"]); 6 | done(); 7 | }, 8 | down: function(migration, DataTypes, done) { 9 | migration.removeIndex("wallet_health", ["currency"]); 10 | migration.removeIndex("wallet_health", ["status"]); 11 | done(); 12 | } 13 | }; 14 | 15 | }).call(this); 16 | -------------------------------------------------------------------------------- /models/migrations/20140610135430-add_indexes.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | up: (migration, DataTypes, done) -> 3 | migration.addIndex "payments", ["fraud"] 4 | 5 | done() 6 | return 7 | 8 | down: (migration, DataTypes, done) -> 9 | migration.removeIndex "payments", ["fraud"] 10 | 11 | done() 12 | return -------------------------------------------------------------------------------- /models/migrations/20140610135430-add_indexes.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | module.exports = { 3 | up: function(migration, DataTypes, done) { 4 | migration.addIndex("payments", ["fraud"]); 5 | done(); 6 | }, 7 | down: function(migration, DataTypes, done) { 8 | migration.removeIndex("payments", ["fraud"]); 9 | done(); 10 | } 11 | }; 12 | 13 | }).call(this); 14 | -------------------------------------------------------------------------------- /models/payment_log.coffee: -------------------------------------------------------------------------------- 1 | module.exports = (sequelize, DataTypes) -> 2 | 3 | PaymentLog = sequelize.define "PaymentLog", 4 | payment_id: 5 | type: DataTypes.INTEGER.UNSIGNED 6 | allowNull: false 7 | log: 8 | type: DataTypes.TEXT 9 | set: (response)-> 10 | try 11 | log = if typeof(response) is "string" then response else "#{response}" 12 | @setDataValue "log", log 13 | catch e 14 | @setDataValue "log", response 15 | , 16 | tableName: "payment_logs" 17 | classMethods: 18 | 19 | findById: (id, callback)-> 20 | PaymentLog.find(id).complete callback 21 | 22 | findByPaymentId: (paymentId, callback)-> 23 | query = 24 | where: 25 | payment_id: paymentId 26 | PaymentLog.findAll(query).complete callback 27 | 28 | PaymentLog -------------------------------------------------------------------------------- /models/payment_log.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | module.exports = function(sequelize, DataTypes) { 3 | var PaymentLog; 4 | PaymentLog = sequelize.define("PaymentLog", { 5 | payment_id: { 6 | type: DataTypes.INTEGER.UNSIGNED, 7 | allowNull: false 8 | }, 9 | log: { 10 | type: DataTypes.TEXT, 11 | set: function(response) { 12 | var e, log; 13 | try { 14 | log = typeof response === "string" ? response : "" + response; 15 | return this.setDataValue("log", log); 16 | } catch (_error) { 17 | e = _error; 18 | return this.setDataValue("log", response); 19 | } 20 | } 21 | } 22 | }, { 23 | tableName: "payment_logs", 24 | classMethods: { 25 | findById: function(id, callback) { 26 | return PaymentLog.find(id).complete(callback); 27 | }, 28 | findByPaymentId: function(paymentId, callback) { 29 | var query; 30 | query = { 31 | where: { 32 | payment_id: paymentId 33 | } 34 | }; 35 | return PaymentLog.findAll(query).complete(callback); 36 | } 37 | } 38 | }); 39 | return PaymentLog; 40 | }; 41 | 42 | }).call(this); 43 | -------------------------------------------------------------------------------- /models/seeds/market_stats.coffee: -------------------------------------------------------------------------------- 1 | MarketHelper = require "../../lib/market_helper" 2 | markets = [] 3 | for literal, int of MarketHelper.getMarkets() 4 | markets.push {type: literal, status: "enabled"} 5 | module.exports = markets -------------------------------------------------------------------------------- /models/seeds/market_stats.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var MarketHelper, int, literal, markets, _ref; 3 | 4 | MarketHelper = require("../../lib/market_helper"); 5 | 6 | markets = []; 7 | 8 | _ref = MarketHelper.getMarkets(); 9 | for (literal in _ref) { 10 | int = _ref[literal]; 11 | markets.push({ 12 | type: literal, 13 | status: "enabled" 14 | }); 15 | } 16 | 17 | module.exports = markets; 18 | 19 | }).call(this); 20 | -------------------------------------------------------------------------------- /models/wallet_health.coffee: -------------------------------------------------------------------------------- 1 | MarketHelper = require "../lib/market_helper" 2 | _ = require "underscore" 3 | 4 | module.exports = (sequelize, DataTypes) -> 5 | 6 | WalletHealth = sequelize.define "WalletHealth", 7 | currency: 8 | type: DataTypes.INTEGER.UNSIGNED 9 | allowNull: false 10 | get: ()-> 11 | MarketHelper.getCurrencyLiteral @getDataValue("currency") 12 | set: (currency)-> 13 | @setDataValue "currency", MarketHelper.getCurrency(currency) 14 | blocks: 15 | type: DataTypes.INTEGER.UNSIGNED 16 | defaultValue: 0 17 | allowNull: false 18 | connections: 19 | type: DataTypes.INTEGER.UNSIGNED 20 | defaultValue: 0 21 | allowNull: false 22 | last_updated: 23 | type: DataTypes.DATE 24 | balance: 25 | type: DataTypes.BIGINT.UNSIGNED 26 | defaultValue: 0 27 | allowNull: false 28 | comment: "FLOAT x 100000000" 29 | status: 30 | type: DataTypes.INTEGER.UNSIGNED 31 | defaultValue: MarketHelper.getWalletStatus "normal" 32 | allowNull: false 33 | comment: "normal, delayed, blocked, inactive" 34 | get: ()-> 35 | MarketHelper.getWalletStatusLiteral @getDataValue("status") 36 | set: (status)-> 37 | @setDataValue "status", MarketHelper.getWalletStatus(status) 38 | , 39 | tableName: "wallet_health" 40 | instanceMethods: 41 | 42 | getFloat: (attribute)-> 43 | return @[attribute] if not @[attribute]? 44 | MarketHelper.fromBigint @[attribute] 45 | 46 | classMethods: 47 | 48 | updateFromWalletInfo: (walletInfo, callback)-> 49 | WalletHealth.findOrCreate({currency: MarketHelper.getCurrency(walletInfo.currency)}).complete (err, wallet, created)-> 50 | wallet.updateAttributes(walletInfo).complete callback 51 | 52 | WalletHealth 53 | 54 | -------------------------------------------------------------------------------- /models/wallet_health.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var MarketHelper, _; 3 | 4 | MarketHelper = require("../lib/market_helper"); 5 | 6 | _ = require("underscore"); 7 | 8 | module.exports = function(sequelize, DataTypes) { 9 | var WalletHealth; 10 | WalletHealth = sequelize.define("WalletHealth", { 11 | currency: { 12 | type: DataTypes.INTEGER.UNSIGNED, 13 | allowNull: false, 14 | get: function() { 15 | return MarketHelper.getCurrencyLiteral(this.getDataValue("currency")); 16 | }, 17 | set: function(currency) { 18 | return this.setDataValue("currency", MarketHelper.getCurrency(currency)); 19 | } 20 | }, 21 | blocks: { 22 | type: DataTypes.INTEGER.UNSIGNED, 23 | defaultValue: 0, 24 | allowNull: false 25 | }, 26 | connections: { 27 | type: DataTypes.INTEGER.UNSIGNED, 28 | defaultValue: 0, 29 | allowNull: false 30 | }, 31 | last_updated: { 32 | type: DataTypes.DATE 33 | }, 34 | balance: { 35 | type: DataTypes.BIGINT.UNSIGNED, 36 | defaultValue: 0, 37 | allowNull: false, 38 | comment: "FLOAT x 100000000" 39 | }, 40 | status: { 41 | type: DataTypes.INTEGER.UNSIGNED, 42 | defaultValue: MarketHelper.getWalletStatus("normal"), 43 | allowNull: false, 44 | comment: "normal, delayed, blocked, inactive", 45 | get: function() { 46 | return MarketHelper.getWalletStatusLiteral(this.getDataValue("status")); 47 | }, 48 | set: function(status) { 49 | return this.setDataValue("status", MarketHelper.getWalletStatus(status)); 50 | } 51 | } 52 | }, { 53 | tableName: "wallet_health", 54 | instanceMethods: { 55 | getFloat: function(attribute) { 56 | if (this[attribute] == null) { 57 | return this[attribute]; 58 | } 59 | return MarketHelper.fromBigint(this[attribute]); 60 | } 61 | }, 62 | classMethods: { 63 | updateFromWalletInfo: function(walletInfo, callback) { 64 | return WalletHealth.findOrCreate({ 65 | currency: MarketHelper.getCurrency(walletInfo.currency) 66 | }).complete(function(err, wallet, created) { 67 | return wallet.updateAttributes(walletInfo).complete(callback); 68 | }); 69 | } 70 | } 71 | }); 72 | return WalletHealth; 73 | }; 74 | 75 | }).call(this); 76 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "coinnext", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "start": "node app", 7 | "test": "make test" 8 | }, 9 | "dependencies": { 10 | "express": "~>3" 11 | , "stylus": "0.44.0" 12 | , "jade": "1.3.1" 13 | , "coffee-script": "latest" 14 | , "connect-assets": "2.x" 15 | , "bcrypt": "0.7.8" 16 | , "connect-redis": "1.4.7" 17 | , "hiredis": "0.1.16" 18 | , "redis": "0.10.1" 19 | , "passport": "0.2.0" 20 | , "passport-local": "1.0.0" 21 | , "underscore": "1.6.0" 22 | , "underscore.string": "2.3.3" 23 | , "node-coind": "1.0.1" 24 | , "date-utils": "1.2.15" 25 | , "socket.io": "0.9.16" 26 | , "socket.io-client": "0.9.16" 27 | , "session.socket.io": "0.1.6" 28 | , "winston": "0.7.3" 29 | , "async": "0.8.0" 30 | , "helmet": "0.2.1" 31 | , "speakeasy": "1.0.3" 32 | , "nodemailer": "0.6.3" 33 | , "restify": "2.7.0" 34 | , "request": "2.34.0" 35 | , "mysql": "2.2.0" 36 | , "sequelize": "1.7.1" 37 | , "lodash": "2.4.1" 38 | , "ip": "0.3.0" 39 | , "phonetic": "0.1.0" 40 | , "recaptcha-async": "0.0.4" 41 | , "mathjs": "0.22.0" 42 | , "express-simple-cdn": "1.0.1" 43 | , "cron": "1.0.4" 44 | , "slackhook": "0.0.3" 45 | }, 46 | "devDependencies": { 47 | "mocha": "latest" 48 | , "should": "latest" 49 | , "growl": "latest" 50 | , "nodemock": "latest" 51 | , "supertest": "latest" 52 | , "grunt-contrib-coffee": "latest" 53 | , "grunt-contrib-watch": "latest" 54 | } 55 | } -------------------------------------------------------------------------------- /public/503.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Coinnext - Error 503 7 | 47 | 48 | 49 |
50 |

503

51 |

The server is currently unavailable. We'll be back soon.

52 |
53 | 54 | 55 | -------------------------------------------------------------------------------- /public/ZeroClipboard.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doomhz/coinnext/c89b8e462876c567580e50d45287bda11399ec04/public/ZeroClipboard.swf -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doomhz/coinnext/c89b8e462876c567580e50d45287bda11399ec04/public/favicon.ico -------------------------------------------------------------------------------- /public/favicon_admin.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doomhz/coinnext/c89b8e462876c567580e50d45287bda11399ec04/public/favicon_admin.ico -------------------------------------------------------------------------------- /public/fonts/icons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doomhz/coinnext/c89b8e462876c567580e50d45287bda11399ec04/public/fonts/icons.eot -------------------------------------------------------------------------------- /public/fonts/icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doomhz/coinnext/c89b8e462876c567580e50d45287bda11399ec04/public/fonts/icons.ttf -------------------------------------------------------------------------------- /public/fonts/icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doomhz/coinnext/c89b8e462876c567580e50d45287bda11399ec04/public/fonts/icons.woff -------------------------------------------------------------------------------- /public/img/appstore-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doomhz/coinnext/c89b8e462876c567580e50d45287bda11399ec04/public/img/appstore-sprite.png -------------------------------------------------------------------------------- /public/img/coin-icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doomhz/coinnext/c89b8e462876c567580e50d45287bda11399ec04/public/img/coin-icons.png -------------------------------------------------------------------------------- /public/img/email/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doomhz/coinnext/c89b8e462876c567580e50d45287bda11399ec04/public/img/email/logo.png -------------------------------------------------------------------------------- /public/img/hero/hero-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doomhz/coinnext/c89b8e462876c567580e50d45287bda11399ec04/public/img/hero/hero-bg.png -------------------------------------------------------------------------------- /public/img/hero/save.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 16 | 19 | 20 | -------------------------------------------------------------------------------- /public/img/logo-gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doomhz/coinnext/c89b8e462876c567580e50d45287bda11399ec04/public/img/logo-gray.png -------------------------------------------------------------------------------- /public/img/logo-gray@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doomhz/coinnext/c89b8e462876c567580e50d45287bda11399ec04/public/img/logo-gray@2x.png -------------------------------------------------------------------------------- /public/img/logo-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doomhz/coinnext/c89b8e462876c567580e50d45287bda11399ec04/public/img/logo-white.png -------------------------------------------------------------------------------- /public/img/logo-white@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doomhz/coinnext/c89b8e462876c567580e50d45287bda11399ec04/public/img/logo-white@2x.png -------------------------------------------------------------------------------- /public/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doomhz/coinnext/c89b8e462876c567580e50d45287bda11399ec04/public/img/logo.png -------------------------------------------------------------------------------- /public/img/logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/doomhz/coinnext/c89b8e462876c567580e50d45287bda11399ec04/public/img/logo@2x.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # www.robotstxt.org/ 2 | 3 | # Allow crawling of all content 4 | User-agent: * 5 | Disallow: -------------------------------------------------------------------------------- /routes/chat.coffee: -------------------------------------------------------------------------------- 1 | Chat = GLOBAL.db.Chat 2 | JsonRenderer = require "../lib/json_renderer" 3 | 4 | module.exports = (app)-> 5 | 6 | app.get "/chat/messages", (req, res)-> 7 | Chat.findLastMessages (err, messages)-> 8 | res.setHeader "Access-Control-Allow-Origin", "*" 9 | res.json JsonRenderer.chatMessages messages 10 | -------------------------------------------------------------------------------- /routes/chat.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var Chat, JsonRenderer; 3 | 4 | Chat = GLOBAL.db.Chat; 5 | 6 | JsonRenderer = require("../lib/json_renderer"); 7 | 8 | module.exports = function(app) { 9 | return app.get("/chat/messages", function(req, res) { 10 | return Chat.findLastMessages(function(err, messages) { 11 | res.setHeader("Access-Control-Allow-Origin", "*"); 12 | return res.json(JsonRenderer.chatMessages(messages)); 13 | }); 14 | }); 15 | }; 16 | 17 | }).call(this); 18 | -------------------------------------------------------------------------------- /routes/core_api/stats.coffee: -------------------------------------------------------------------------------- 1 | OrderLog = GLOBAL.db.OrderLog 2 | TradeStats = GLOBAL.db.TradeStats 3 | MarketHelper = require "../../lib/market_helper" 4 | math = require "../../lib/math" 5 | _ = require "underscore" 6 | 7 | module.exports = (app)-> 8 | 9 | app.post "/trade_stats", (req, res, next)-> 10 | now = Date.now() 11 | halfHour = 1800000 12 | endTime = now - now % halfHour 13 | startTime = endTime - halfHour 14 | markets = {} 15 | OrderLog.findByTimeAndAction startTime, endTime, "sell", (err, orderLogs)-> 16 | for orderLog in orderLogs 17 | marketType = "#{orderLog.order.sell_currency}_#{orderLog.order.buy_currency}" 18 | if not markets[marketType] 19 | markets[marketType] = 20 | type: marketType 21 | start_time: startTime 22 | end_time: endTime 23 | open_price: 0 24 | high_price: 0 25 | low_price: 0 26 | volume: 0 27 | exchange_volume: 0 28 | markets[marketType].open_price = orderLog.unit_price if markets[marketType].open_price is 0 29 | markets[marketType].close_price = orderLog.unit_price 30 | markets[marketType].high_price = orderLog.unit_price if orderLog.unit_price > markets[marketType].high_price 31 | markets[marketType].low_price = orderLog.unit_price if orderLog.unit_price < markets[marketType].low_price or markets[marketType].low_price is 0 32 | markets[marketType].volume = parseInt math.add(MarketHelper.toBignum(markets[marketType].volume), MarketHelper.toBignum(orderLog.matched_amount)) 33 | markets[marketType].exchange_volume = parseInt math.add(MarketHelper.toBignum(markets[marketType].exchange_volume), MarketHelper.toBignum(orderLog.result_amount)) 34 | markets = _.values markets 35 | TradeStats.bulkCreate(markets).complete (err, result)-> 36 | res.send 37 | message: "Trade stats aggregated from #{new Date(startTime)} to #{new Date(endTime)}" 38 | result: result 39 | -------------------------------------------------------------------------------- /routes/core_api/stats.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var MarketHelper, OrderLog, TradeStats, math, _; 3 | 4 | OrderLog = GLOBAL.db.OrderLog; 5 | 6 | TradeStats = GLOBAL.db.TradeStats; 7 | 8 | MarketHelper = require("../../lib/market_helper"); 9 | 10 | math = require("../../lib/math"); 11 | 12 | _ = require("underscore"); 13 | 14 | module.exports = function(app) { 15 | return app.post("/trade_stats", function(req, res, next) { 16 | var endTime, halfHour, markets, now, startTime; 17 | now = Date.now(); 18 | halfHour = 1800000; 19 | endTime = now - now % halfHour; 20 | startTime = endTime - halfHour; 21 | markets = {}; 22 | return OrderLog.findByTimeAndAction(startTime, endTime, "sell", function(err, orderLogs) { 23 | var marketType, orderLog, _i, _len; 24 | for (_i = 0, _len = orderLogs.length; _i < _len; _i++) { 25 | orderLog = orderLogs[_i]; 26 | marketType = "" + orderLog.order.sell_currency + "_" + orderLog.order.buy_currency; 27 | if (!markets[marketType]) { 28 | markets[marketType] = { 29 | type: marketType, 30 | start_time: startTime, 31 | end_time: endTime, 32 | open_price: 0, 33 | high_price: 0, 34 | low_price: 0, 35 | volume: 0, 36 | exchange_volume: 0 37 | }; 38 | } 39 | if (markets[marketType].open_price === 0) { 40 | markets[marketType].open_price = orderLog.unit_price; 41 | } 42 | markets[marketType].close_price = orderLog.unit_price; 43 | if (orderLog.unit_price > markets[marketType].high_price) { 44 | markets[marketType].high_price = orderLog.unit_price; 45 | } 46 | if (orderLog.unit_price < markets[marketType].low_price || markets[marketType].low_price === 0) { 47 | markets[marketType].low_price = orderLog.unit_price; 48 | } 49 | markets[marketType].volume = parseInt(math.add(MarketHelper.toBignum(markets[marketType].volume), MarketHelper.toBignum(orderLog.matched_amount))); 50 | markets[marketType].exchange_volume = parseInt(math.add(MarketHelper.toBignum(markets[marketType].exchange_volume), MarketHelper.toBignum(orderLog.result_amount))); 51 | } 52 | markets = _.values(markets); 53 | return TradeStats.bulkCreate(markets).complete(function(err, result) { 54 | return res.send({ 55 | message: "Trade stats aggregated from " + (new Date(startTime)) + " to " + (new Date(endTime)), 56 | result: result 57 | }); 58 | }); 59 | }); 60 | }); 61 | }; 62 | 63 | }).call(this); 64 | -------------------------------------------------------------------------------- /routes/core_api/trade.coffee: -------------------------------------------------------------------------------- 1 | restify = require "restify" 2 | Order = GLOBAL.db.Order 3 | Wallet = GLOBAL.db.Wallet 4 | MarketStats = GLOBAL.db.MarketStats 5 | TradeHelper = require "../../lib/trade_helper" 6 | JsonRenderer = require "../../lib/json_renderer" 7 | MarketHelper = require "../../lib/market_helper" 8 | 9 | module.exports = (app)-> 10 | 11 | app.post "/publish_order", (req, res, next)-> 12 | data = req.body 13 | data.in_queue = true 14 | orderCurrency = data["#{data.action}_currency"] 15 | MarketStats.findEnabledMarket orderCurrency, "BTC", (err, market)-> 16 | return next(new restify.ConflictError "Market for #{orderCurrency} is disabled.") if not market 17 | TradeHelper.createOrder data, (err, newOrder)-> 18 | return next(new restify.ConflictError err) if err 19 | orderData = 20 | external_order_id: newOrder.id 21 | type: newOrder.type 22 | action: newOrder.action 23 | buy_currency: MarketHelper.getCurrency newOrder.buy_currency 24 | sell_currency: MarketHelper.getCurrency newOrder.sell_currency 25 | amount: newOrder.amount 26 | unit_price: newOrder.unit_price 27 | GLOBAL.queue.Event.addOrder orderData, (err)-> 28 | if err 29 | console.error "Could add add_order event for order #{newOrder.id} - #{err}" 30 | return next(new restify.ConflictError "Could not submit order.") if err 31 | res.send 32 | id: newOrder.id 33 | TradeHelper.pushOrderUpdate 34 | type: "order-to-add" 35 | eventData: JsonRenderer.order newOrder 36 | 37 | app.del "/cancel_order/:order_id", (req, res, next)-> 38 | orderId = req.params.order_id 39 | Order.findById orderId, (err, order)-> 40 | return next(new restify.ConflictError err) if err or not order or not order.canBeCanceled() 41 | orderCurrency = order["#{order.action}_currency"] 42 | MarketStats.findEnabledMarket orderCurrency, "BTC", (err, market)-> 43 | return next(new restify.ConflictError "#{new Date()} - Will not process order #{orderId}, the market for #{orderCurrency} is disabled.") if not market 44 | GLOBAL.db.sequelize.transaction (transaction)-> 45 | GLOBAL.queue.Event.addCancelOrder {order_id: orderId}, (err)-> 46 | if err 47 | return transaction.rollback().success ()-> 48 | next(new restify.ConflictError "Could not cancel order #{orderId} - #{err}") 49 | order.in_queue = true 50 | order.save({transaction: transaction}).complete (err)-> 51 | if err 52 | return transaction.rollback().success ()-> 53 | next(new restify.ConflictError "Could not set order #{orderId} for canceling - #{err}") 54 | transaction.commit().success ()-> 55 | res.send 56 | id: orderId 57 | TradeHelper.pushOrderUpdate 58 | type: "order-to-cancel" 59 | eventData: 60 | id: orderId 61 | -------------------------------------------------------------------------------- /routes/errors.coffee: -------------------------------------------------------------------------------- 1 | module.exports = (app)-> 2 | 3 | app.use (req, res)-> 4 | console.error "404 - [#{req.method}][#{req.ip}] #{req.originalUrl}" 5 | res.statusCode = 404 6 | if req.accepts "html" 7 | return res.render "errors/404", 8 | title: "Page not found" 9 | if req.accepts "json" 10 | return res.send 11 | error: "Not found" 12 | res.type("txt").send("Not found") 13 | -------------------------------------------------------------------------------- /routes/errors.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | module.exports = function(app) { 3 | return app.use(function(req, res) { 4 | console.error("404 - [" + req.method + "][" + req.ip + "] " + req.originalUrl); 5 | res.statusCode = 404; 6 | if (req.accepts("html")) { 7 | return res.render("errors/404", { 8 | title: "Page not found" 9 | }); 10 | } 11 | if (req.accepts("json")) { 12 | return res.send({ 13 | error: "Not found" 14 | }); 15 | } 16 | return res.type("txt").send("Not found"); 17 | }); 18 | }; 19 | 20 | }).call(this); 21 | -------------------------------------------------------------------------------- /routes/order_logs.coffee: -------------------------------------------------------------------------------- 1 | OrderLog = GLOBAL.db.OrderLog 2 | JsonRenderer = require "../lib/json_renderer" 3 | 4 | module.exports = (app)-> 5 | 6 | app.get "/order_logs", (req, res)-> 7 | if req.query.user_id? 8 | req.query.user_id = req.user.id if req.user 9 | req.query.user_id = 0 if not req.user 10 | OrderLog.findActiveByOptions req.query, (err, orderLogs)-> 11 | return JsonRenderer.error "Sorry, could not get closed orders...", res if err 12 | res.json JsonRenderer.orderLogs orderLogs 13 | -------------------------------------------------------------------------------- /routes/order_logs.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var JsonRenderer, OrderLog; 3 | 4 | OrderLog = GLOBAL.db.OrderLog; 5 | 6 | JsonRenderer = require("../lib/json_renderer"); 7 | 8 | module.exports = function(app) { 9 | return app.get("/order_logs", function(req, res) { 10 | if (req.query.user_id != null) { 11 | if (req.user) { 12 | req.query.user_id = req.user.id; 13 | } 14 | if (!req.user) { 15 | req.query.user_id = 0; 16 | } 17 | } 18 | return OrderLog.findActiveByOptions(req.query, function(err, orderLogs) { 19 | if (err) { 20 | return JsonRenderer.error("Sorry, could not get closed orders...", res); 21 | } 22 | return res.json(JsonRenderer.orderLogs(orderLogs)); 23 | }); 24 | }); 25 | }; 26 | 27 | }).call(this); 28 | -------------------------------------------------------------------------------- /routes/orders.coffee: -------------------------------------------------------------------------------- 1 | Order = GLOBAL.db.Order 2 | Wallet = GLOBAL.db.Wallet 3 | MarketStats = GLOBAL.db.MarketStats 4 | MarketHelper = require "../lib/market_helper" 5 | JsonRenderer = require "../lib/json_renderer" 6 | _ = require "underscore" 7 | 8 | module.exports = (app)-> 9 | 10 | app.post "/orders", (req, res)-> 11 | return JsonRenderer.error "You need to be logged in to place an order.", res if not req.user 12 | return JsonRenderer.error "Sorry, but you can not trade. Did you verify your account?", res if not req.user.canTrade() 13 | data = req.body 14 | data.user_id = req.user.id 15 | data.status = "open" 16 | data.amount = parseFloat data.amount 17 | data.amount = MarketHelper.toBigint data.amount if _.isNumber(data.amount) and not _.isNaN(data.amount) and _.isFinite(data.amount) 18 | data.unit_price = parseFloat data.unit_price 19 | data.unit_price = MarketHelper.toBigint data.unit_price if _.isNumber(data.unit_price) and not _.isNaN(data.unit_price) and _.isFinite(data.unit_price) 20 | newOrder = Order.build data 21 | errors = newOrder.validate() 22 | return JsonRenderer.error errors, res if errors 23 | newOrder.publish (err, order)-> 24 | return JsonRenderer.error err, res if err 25 | res.json JsonRenderer.order order 26 | 27 | app.get "/orders", (req, res)-> 28 | if req.query.user_id? 29 | req.query.user_id = req.user.id if req.user 30 | req.query.user_id = 0 if not req.user 31 | Order.findByOptions req.query, (err, orders)-> 32 | return JsonRenderer.error "Sorry, could not get open orders...", res if err 33 | res.json JsonRenderer.orders orders 34 | 35 | app.del "/orders/:id", (req, res)-> 36 | return JsonRenderer.error "You need to be logged in to delete an order.", res if not req.user 37 | Order.findByUserAndId req.params.id, req.user.id, (err, order)-> 38 | return JsonRenderer.error "Sorry, could not delete orders...", res if err or not order 39 | order.cancel (err)-> 40 | console.error "Could not cancel order - #{err}" if err 41 | return res.json JsonRenderer.order order if err 42 | res.json {} 43 | -------------------------------------------------------------------------------- /routes/orders.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var JsonRenderer, MarketHelper, MarketStats, Order, Wallet, _; 3 | 4 | Order = GLOBAL.db.Order; 5 | 6 | Wallet = GLOBAL.db.Wallet; 7 | 8 | MarketStats = GLOBAL.db.MarketStats; 9 | 10 | MarketHelper = require("../lib/market_helper"); 11 | 12 | JsonRenderer = require("../lib/json_renderer"); 13 | 14 | _ = require("underscore"); 15 | 16 | module.exports = function(app) { 17 | app.post("/orders", function(req, res) { 18 | var data, errors, newOrder; 19 | if (!req.user) { 20 | return JsonRenderer.error("You need to be logged in to place an order.", res); 21 | } 22 | if (!req.user.canTrade()) { 23 | return JsonRenderer.error("Sorry, but you can not trade. Did you verify your account?", res); 24 | } 25 | data = req.body; 26 | data.user_id = req.user.id; 27 | data.status = "open"; 28 | data.amount = parseFloat(data.amount); 29 | if (_.isNumber(data.amount) && !_.isNaN(data.amount) && _.isFinite(data.amount)) { 30 | data.amount = MarketHelper.toBigint(data.amount); 31 | } 32 | data.unit_price = parseFloat(data.unit_price); 33 | if (_.isNumber(data.unit_price) && !_.isNaN(data.unit_price) && _.isFinite(data.unit_price)) { 34 | data.unit_price = MarketHelper.toBigint(data.unit_price); 35 | } 36 | newOrder = Order.build(data); 37 | errors = newOrder.validate(); 38 | if (errors) { 39 | return JsonRenderer.error(errors, res); 40 | } 41 | return newOrder.publish(function(err, order) { 42 | if (err) { 43 | return JsonRenderer.error(err, res); 44 | } 45 | return res.json(JsonRenderer.order(order)); 46 | }); 47 | }); 48 | app.get("/orders", function(req, res) { 49 | if (req.query.user_id != null) { 50 | if (req.user) { 51 | req.query.user_id = req.user.id; 52 | } 53 | if (!req.user) { 54 | req.query.user_id = 0; 55 | } 56 | } 57 | return Order.findByOptions(req.query, function(err, orders) { 58 | if (err) { 59 | return JsonRenderer.error("Sorry, could not get open orders...", res); 60 | } 61 | return res.json(JsonRenderer.orders(orders)); 62 | }); 63 | }); 64 | return app.del("/orders/:id", function(req, res) { 65 | if (!req.user) { 66 | return JsonRenderer.error("You need to be logged in to delete an order.", res); 67 | } 68 | return Order.findByUserAndId(req.params.id, req.user.id, function(err, order) { 69 | if (err || !order) { 70 | return JsonRenderer.error("Sorry, could not delete orders...", res); 71 | } 72 | return order.cancel(function(err) { 73 | if (err) { 74 | console.error("Could not cancel order - " + err); 75 | } 76 | if (err) { 77 | return res.json(JsonRenderer.order(order)); 78 | } 79 | return res.json({}); 80 | }); 81 | }); 82 | }); 83 | }; 84 | 85 | }).call(this); 86 | -------------------------------------------------------------------------------- /routes/payments.coffee: -------------------------------------------------------------------------------- 1 | Payment = GLOBAL.db.Payment 2 | Wallet = GLOBAL.db.Wallet 3 | MarketHelper = require "../lib/market_helper" 4 | JsonRenderer = require "../lib/json_renderer" 5 | _ = require "underscore" 6 | 7 | module.exports = (app)-> 8 | 9 | app.post "/payments", (req, res)-> 10 | amount = parseFloat req.body.amount 11 | return JsonRenderer.error "Please auth.", res if not req.user 12 | return JsonRenderer.error "Please submit a valid amount.", res if not _.isNumber(amount) or _.isNaN(amount) or not _.isFinite(amount) 13 | data = 14 | user_id: req.user.id 15 | wallet_id: req.body.wallet_id 16 | amount: MarketHelper.toBigint amount 17 | address: req.body.address 18 | Payment.submit data, (err, payment)-> 19 | return JsonRenderer.error err, res if err 20 | res.json JsonRenderer.payment payment 21 | 22 | app.get "/payments/pending/:wallet_id", (req, res)-> 23 | walletId = req.params.wallet_id 24 | return JsonRenderer.error "Please auth.", res if not req.user 25 | Payment.findByUserAndWallet req.user.id, walletId, "pending", (err, payments)-> 26 | console.error err if err 27 | return JsonRenderer.error "Sorry, could not get pending payments...", res if err 28 | res.json JsonRenderer.payments payments 29 | -------------------------------------------------------------------------------- /routes/payments.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var JsonRenderer, MarketHelper, Payment, Wallet, _; 3 | 4 | Payment = GLOBAL.db.Payment; 5 | 6 | Wallet = GLOBAL.db.Wallet; 7 | 8 | MarketHelper = require("../lib/market_helper"); 9 | 10 | JsonRenderer = require("../lib/json_renderer"); 11 | 12 | _ = require("underscore"); 13 | 14 | module.exports = function(app) { 15 | app.post("/payments", function(req, res) { 16 | var amount, data; 17 | amount = parseFloat(req.body.amount); 18 | if (!req.user) { 19 | return JsonRenderer.error("Please auth.", res); 20 | } 21 | if (!_.isNumber(amount) || _.isNaN(amount) || !_.isFinite(amount)) { 22 | return JsonRenderer.error("Please submit a valid amount.", res); 23 | } 24 | data = { 25 | user_id: req.user.id, 26 | wallet_id: req.body.wallet_id, 27 | amount: MarketHelper.toBigint(amount), 28 | address: req.body.address 29 | }; 30 | return Payment.submit(data, function(err, payment) { 31 | if (err) { 32 | return JsonRenderer.error(err, res); 33 | } 34 | return res.json(JsonRenderer.payment(payment)); 35 | }); 36 | }); 37 | return app.get("/payments/pending/:wallet_id", function(req, res) { 38 | var walletId; 39 | walletId = req.params.wallet_id; 40 | if (!req.user) { 41 | return JsonRenderer.error("Please auth.", res); 42 | } 43 | return Payment.findByUserAndWallet(req.user.id, walletId, "pending", function(err, payments) { 44 | if (err) { 45 | console.error(err); 46 | } 47 | if (err) { 48 | return JsonRenderer.error("Sorry, could not get pending payments...", res); 49 | } 50 | return res.json(JsonRenderer.payments(payments)); 51 | }); 52 | }); 53 | }; 54 | 55 | }).call(this); 56 | -------------------------------------------------------------------------------- /routes/transactions.coffee: -------------------------------------------------------------------------------- 1 | Transaction = GLOBAL.db.Transaction 2 | JsonRenderer = require "../lib/json_renderer" 3 | 4 | module.exports = (app)-> 5 | 6 | app.get "/transactions/pending/:wallet_id", (req, res)-> 7 | walletId = req.params.wallet_id 8 | return JsonRenderer.error "Please auth.", res if not req.user 9 | Transaction.findPendingByUserAndWallet req.user.id, walletId, (err, transactions)-> 10 | console.error err if err 11 | return JsonRenderer.error "Sorry, could not get pending transactions...", res if err 12 | res.json JsonRenderer.transactions transactions 13 | 14 | app.get "/transactions/processed/:wallet_id", (req, res)-> 15 | walletId = req.params.wallet_id 16 | return JsonRenderer.error "Please auth.", res if not req.user 17 | Transaction.findProcessedByUserAndWallet req.user.id, walletId, (err, transactions)-> 18 | console.error err if err 19 | return JsonRenderer.error "Sorry, could not get processed transactions...", res if err 20 | res.json JsonRenderer.transactions transactions 21 | 22 | app.get "/transactions/:id", (req, res)-> 23 | id = req.params.id 24 | return JsonRenderer.error "Please auth.", res if not req.user 25 | Transaction.find(id).complete (err, transaction)-> 26 | console.error err if err 27 | return JsonRenderer.error "Sorry, could not find transaction...", res if err 28 | res.json JsonRenderer.transaction transaction 29 | -------------------------------------------------------------------------------- /routes/transactions.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var JsonRenderer, Transaction; 3 | 4 | Transaction = GLOBAL.db.Transaction; 5 | 6 | JsonRenderer = require("../lib/json_renderer"); 7 | 8 | module.exports = function(app) { 9 | app.get("/transactions/pending/:wallet_id", function(req, res) { 10 | var walletId; 11 | walletId = req.params.wallet_id; 12 | if (!req.user) { 13 | return JsonRenderer.error("Please auth.", res); 14 | } 15 | return Transaction.findPendingByUserAndWallet(req.user.id, walletId, function(err, transactions) { 16 | if (err) { 17 | console.error(err); 18 | } 19 | if (err) { 20 | return JsonRenderer.error("Sorry, could not get pending transactions...", res); 21 | } 22 | return res.json(JsonRenderer.transactions(transactions)); 23 | }); 24 | }); 25 | app.get("/transactions/processed/:wallet_id", function(req, res) { 26 | var walletId; 27 | walletId = req.params.wallet_id; 28 | if (!req.user) { 29 | return JsonRenderer.error("Please auth.", res); 30 | } 31 | return Transaction.findProcessedByUserAndWallet(req.user.id, walletId, function(err, transactions) { 32 | if (err) { 33 | console.error(err); 34 | } 35 | if (err) { 36 | return JsonRenderer.error("Sorry, could not get processed transactions...", res); 37 | } 38 | return res.json(JsonRenderer.transactions(transactions)); 39 | }); 40 | }); 41 | return app.get("/transactions/:id", function(req, res) { 42 | var id; 43 | id = req.params.id; 44 | if (!req.user) { 45 | return JsonRenderer.error("Please auth.", res); 46 | } 47 | return Transaction.find(id).complete(function(err, transaction) { 48 | if (err) { 49 | console.error(err); 50 | } 51 | if (err) { 52 | return JsonRenderer.error("Sorry, could not find transaction...", res); 53 | } 54 | return res.json(JsonRenderer.transaction(transaction)); 55 | }); 56 | }); 57 | }; 58 | 59 | }).call(this); 60 | -------------------------------------------------------------------------------- /routes/users.coffee: -------------------------------------------------------------------------------- 1 | User = GLOBAL.db.User 2 | Wallet = GLOBAL.db.Wallet 3 | JsonRenderer = require '../lib/json_renderer' 4 | 5 | module.exports = (app)-> 6 | 7 | app.post "/user", (req, res)-> 8 | data = 9 | email: req.body.email 10 | password: req.body.password 11 | User.createNewUser data, (err, newUser)-> 12 | return JsonRenderer.error err, res if err 13 | newUser.sendEmailVerificationLink() 14 | Wallet.findOrCreateUserWalletByCurrency newUser.id, "BTC" 15 | res.json JsonRenderer.user newUser 16 | 17 | app.get "/user/:id?", (req, res)-> 18 | return JsonRenderer.error null, res, 401, false if not req.user 19 | res.json JsonRenderer.user req.user 20 | 21 | app.put "/user/:id?", (req, res)-> 22 | return JsonRenderer.error null, res, 401, false if not req.user 23 | req.user.updateSettings req.body, (err, user)-> 24 | return JsonRenderer.error err, res if err 25 | res.json JsonRenderer.user user 26 | -------------------------------------------------------------------------------- /routes/users.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var JsonRenderer, User, Wallet; 3 | 4 | User = GLOBAL.db.User; 5 | 6 | Wallet = GLOBAL.db.Wallet; 7 | 8 | JsonRenderer = require('../lib/json_renderer'); 9 | 10 | module.exports = function(app) { 11 | app.post("/user", function(req, res) { 12 | var data; 13 | data = { 14 | email: req.body.email, 15 | password: req.body.password 16 | }; 17 | return User.createNewUser(data, function(err, newUser) { 18 | if (err) { 19 | return JsonRenderer.error(err, res); 20 | } 21 | newUser.sendEmailVerificationLink(); 22 | Wallet.findOrCreateUserWalletByCurrency(newUser.id, "BTC"); 23 | return res.json(JsonRenderer.user(newUser)); 24 | }); 25 | }); 26 | app.get("/user/:id?", function(req, res) { 27 | if (!req.user) { 28 | return JsonRenderer.error(null, res, 401, false); 29 | } 30 | return res.json(JsonRenderer.user(req.user)); 31 | }); 32 | return app.put("/user/:id?", function(req, res) { 33 | if (!req.user) { 34 | return JsonRenderer.error(null, res, 401, false); 35 | } 36 | return req.user.updateSettings(req.body, function(err, user) { 37 | if (err) { 38 | return JsonRenderer.error(err, res); 39 | } 40 | return res.json(JsonRenderer.user(user)); 41 | }); 42 | }); 43 | }; 44 | 45 | }).call(this); 46 | -------------------------------------------------------------------------------- /routes/wallets.coffee: -------------------------------------------------------------------------------- 1 | Wallet = GLOBAL.db.Wallet 2 | MarketHelper = require "../lib/market_helper" 3 | JsonRenderer = require "../lib/json_renderer" 4 | 5 | module.exports = (app)-> 6 | 7 | app.post "/wallets", (req, res)-> 8 | currency = req.body.currency 9 | return JsonRenderer.error "Please auth.", res if not req.user 10 | return JsonRenderer.error "Invalid currency.", res if not MarketHelper.isValidCurrency currency 11 | Wallet.findOrCreateUserWalletByCurrency req.user.id, currency, (err, wallet)-> 12 | console.error err if err 13 | return JsonRenderer.error "Could not create wallet.", res if err 14 | res.json JsonRenderer.wallet wallet 15 | 16 | app.put "/wallets/:id", (req, res)-> 17 | return JsonRenderer.error "Please auth.", res if not req.user 18 | Wallet.findUserWallet req.user.id, req.params.id, (err, wallet)-> 19 | console.error err if err 20 | return JsonRenderer.error "Wrong wallet.", res if err 21 | return res.json JsonRenderer.wallet wallet if wallet.address 22 | wallet.generateAddress (err, wl)-> 23 | console.error err if err 24 | return JsonRenderer.error "Could not generate address.", res if err 25 | res.json JsonRenderer.wallet wl 26 | 27 | app.get "/wallets/:id", (req, res)-> 28 | return JsonRenderer.error "Please auth.", res if not req.user 29 | Wallet.findUserWallet req.user.id, req.params.id, (err, wallet)-> 30 | console.error err if err 31 | return JsonRenderer.error "Wrong wallet.", res if err 32 | return res.json JsonRenderer.wallet wallet 33 | 34 | app.get "/wallets", (req, res)-> 35 | return JsonRenderer.error "Please auth.", res if not req.user 36 | Wallet.findUserWallets req.user.id, (err, wallets)-> 37 | console.error err if err 38 | res.json JsonRenderer.wallets wallets 39 | -------------------------------------------------------------------------------- /routes/wallets.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var JsonRenderer, MarketHelper, Wallet; 3 | 4 | Wallet = GLOBAL.db.Wallet; 5 | 6 | MarketHelper = require("../lib/market_helper"); 7 | 8 | JsonRenderer = require("../lib/json_renderer"); 9 | 10 | module.exports = function(app) { 11 | app.post("/wallets", function(req, res) { 12 | var currency; 13 | currency = req.body.currency; 14 | if (!req.user) { 15 | return JsonRenderer.error("Please auth.", res); 16 | } 17 | if (!MarketHelper.isValidCurrency(currency)) { 18 | return JsonRenderer.error("Invalid currency.", res); 19 | } 20 | return Wallet.findOrCreateUserWalletByCurrency(req.user.id, currency, function(err, wallet) { 21 | if (err) { 22 | console.error(err); 23 | } 24 | if (err) { 25 | return JsonRenderer.error("Could not create wallet.", res); 26 | } 27 | return res.json(JsonRenderer.wallet(wallet)); 28 | }); 29 | }); 30 | app.put("/wallets/:id", function(req, res) { 31 | if (!req.user) { 32 | return JsonRenderer.error("Please auth.", res); 33 | } 34 | return Wallet.findUserWallet(req.user.id, req.params.id, function(err, wallet) { 35 | if (err) { 36 | console.error(err); 37 | } 38 | if (err) { 39 | return JsonRenderer.error("Wrong wallet.", res); 40 | } 41 | if (wallet.address) { 42 | return res.json(JsonRenderer.wallet(wallet)); 43 | } 44 | return wallet.generateAddress(function(err, wl) { 45 | if (err) { 46 | console.error(err); 47 | } 48 | if (err) { 49 | return JsonRenderer.error("Could not generate address.", res); 50 | } 51 | return res.json(JsonRenderer.wallet(wl)); 52 | }); 53 | }); 54 | }); 55 | app.get("/wallets/:id", function(req, res) { 56 | if (!req.user) { 57 | return JsonRenderer.error("Please auth.", res); 58 | } 59 | return Wallet.findUserWallet(req.user.id, req.params.id, function(err, wallet) { 60 | if (err) { 61 | console.error(err); 62 | } 63 | if (err) { 64 | return JsonRenderer.error("Wrong wallet.", res); 65 | } 66 | return res.json(JsonRenderer.wallet(wallet)); 67 | }); 68 | }); 69 | return app.get("/wallets", function(req, res) { 70 | if (!req.user) { 71 | return JsonRenderer.error("Please auth.", res); 72 | } 73 | return Wallet.findUserWallets(req.user.id, function(err, wallets) { 74 | if (err) { 75 | console.error(err); 76 | } 77 | return res.json(JsonRenderer.wallets(wallets)); 78 | }); 79 | }); 80 | }; 81 | 82 | }).call(this); 83 | -------------------------------------------------------------------------------- /tests/helpers/auth_helper.js: -------------------------------------------------------------------------------- 1 | var _ = require("underscore"); 2 | var request = require("supertest"); 3 | var userData = { 4 | email: "test@test.com", 5 | password: "test12345_" 6 | }; 7 | 8 | Auth = { 9 | login: function (options, callback) { 10 | var data = _.extend(userData, typeof(options) !== "function" ? options : {}); 11 | data.email_verified = typeof(options.email_verified) !== "undefined" ? options.email_verified : true; 12 | var cb = typeof(options) === "function" ? options : callback; 13 | GLOBAL.db.User.createNewUser(data, function (err, user) { 14 | if (err) {console.error(err)}; 15 | request(GLOBAL.appConfig().app_host) 16 | .post("/login") 17 | .send(userData) 18 | .end(function (err, res) { 19 | cb(err, res.headers['set-cookie'], user, res); 20 | }); 21 | }); 22 | } 23 | }; 24 | 25 | module.exports = Auth; -------------------------------------------------------------------------------- /tests/helpers/btc_wallet_mock.coffee: -------------------------------------------------------------------------------- 1 | _ = require "underscore" 2 | trTime = Date.now() / 1000 3 | transactionData = 4 | amount: 1 5 | txid: "unique_tx_id" 6 | confirmations: 6 7 | time: trTime 8 | details: [] 9 | transactionDetails = 10 | account: "account" 11 | fee: 0.0001 12 | address: "address" 13 | category: "receive" 14 | transactionsData = 15 | amount: 1 16 | txid: "unique_tx_id" 17 | confirmations: 6 18 | time: trTime 19 | account: "account" 20 | fee: 0.0001 21 | address: "address" 22 | category: "receive" 23 | 24 | class BtcWallet 25 | confirmations: 6 26 | 27 | getTransaction: (txId, callback)-> 28 | tr = _.clone transactionData 29 | tr.details = [_.clone(transactionDetails)] 30 | callback null, tr 31 | getTransactions: (account = "*", limit = 100, from = 0, callback)-> 32 | callback null, [_.clone(transactionsData)] 33 | getBalance: (account, callback)-> 34 | callback null, 1 35 | chargeAccount: (account, balance, callback)-> 36 | callback null, true 37 | sendToAddress: (address, amount, callback)-> 38 | callback null, "unique_tx_id_#{address}" 39 | isBalanceConfirmed: (existentConfirmations)-> 40 | existentConfirmations >= @confirmations 41 | 42 | exports = module.exports = BtcWallet -------------------------------------------------------------------------------- /tests/helpers/btc_wallet_mock.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var BtcWallet, exports, trTime, transactionData, transactionDetails, transactionsData, _; 3 | 4 | _ = require("underscore"); 5 | 6 | trTime = Date.now() / 1000; 7 | 8 | transactionData = { 9 | amount: 1, 10 | txid: "unique_tx_id", 11 | confirmations: 6, 12 | time: trTime, 13 | details: [] 14 | }; 15 | 16 | transactionDetails = { 17 | account: "account", 18 | fee: 0.0001, 19 | address: "address", 20 | category: "receive" 21 | }; 22 | 23 | transactionsData = { 24 | amount: 1, 25 | txid: "unique_tx_id", 26 | confirmations: 6, 27 | time: trTime, 28 | account: "account", 29 | fee: 0.0001, 30 | address: "address", 31 | category: "receive" 32 | }; 33 | 34 | BtcWallet = (function() { 35 | function BtcWallet() {} 36 | 37 | BtcWallet.prototype.confirmations = 6; 38 | 39 | BtcWallet.prototype.getTransaction = function(txId, callback) { 40 | var tr; 41 | tr = _.clone(transactionData); 42 | tr.details = [_.clone(transactionDetails)]; 43 | return callback(null, tr); 44 | }; 45 | 46 | BtcWallet.prototype.getTransactions = function(account, limit, from, callback) { 47 | if (account == null) { 48 | account = "*"; 49 | } 50 | if (limit == null) { 51 | limit = 100; 52 | } 53 | if (from == null) { 54 | from = 0; 55 | } 56 | return callback(null, [_.clone(transactionsData)]); 57 | }; 58 | 59 | BtcWallet.prototype.getBalance = function(account, callback) { 60 | return callback(null, 1); 61 | }; 62 | 63 | BtcWallet.prototype.chargeAccount = function(account, balance, callback) { 64 | return callback(null, true); 65 | }; 66 | 67 | BtcWallet.prototype.sendToAddress = function(address, amount, callback) { 68 | return callback(null, "unique_tx_id_" + address); 69 | }; 70 | 71 | BtcWallet.prototype.isBalanceConfirmed = function(existentConfirmations) { 72 | return existentConfirmations >= this.confirmations; 73 | }; 74 | 75 | return BtcWallet; 76 | 77 | })(); 78 | 79 | exports = module.exports = BtcWallet; 80 | 81 | }).call(this); 82 | -------------------------------------------------------------------------------- /tests/helpers/ltc_wallet_mock.coffee: -------------------------------------------------------------------------------- 1 | trTime = Date.now() / 1000 2 | transactionData = 3 | amount: 1 4 | txid: "unique_tx_id" 5 | confirmations: 6 6 | time: trTime 7 | details: [{ 8 | account: "account" 9 | fee: 0.0001 10 | address: "address" 11 | category: "receive" 12 | }] 13 | 14 | class LtcWallet 15 | 16 | getTransaction: (txId, callback)-> 17 | callback null, transactionData 18 | getBalance: (account, callback)-> 19 | callback null, 1 20 | chargeAccount: (account, balance, callback)-> 21 | callback null, true 22 | sendToAddress: (address, account, amount, callback)-> 23 | callback null, "unique_tx_id" 24 | 25 | exports = module.exports = LtcWallet -------------------------------------------------------------------------------- /tests/helpers/ltc_wallet_mock.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var LtcWallet, exports, trTime, transactionData; 3 | 4 | trTime = Date.now() / 1000; 5 | 6 | transactionData = { 7 | amount: 1, 8 | txid: "unique_tx_id", 9 | confirmations: 6, 10 | time: trTime, 11 | details: [ 12 | { 13 | account: "account", 14 | fee: 0.0001, 15 | address: "address", 16 | category: "receive" 17 | } 18 | ] 19 | }; 20 | 21 | LtcWallet = (function() { 22 | function LtcWallet() {} 23 | 24 | LtcWallet.prototype.getTransaction = function(txId, callback) { 25 | return callback(null, transactionData); 26 | }; 27 | 28 | LtcWallet.prototype.getBalance = function(account, callback) { 29 | return callback(null, 1); 30 | }; 31 | 32 | LtcWallet.prototype.chargeAccount = function(account, balance, callback) { 33 | return callback(null, true); 34 | }; 35 | 36 | LtcWallet.prototype.sendToAddress = function(address, account, amount, callback) { 37 | return callback(null, "unique_tx_id"); 38 | }; 39 | 40 | return LtcWallet; 41 | 42 | })(); 43 | 44 | exports = module.exports = LtcWallet; 45 | 46 | }).call(this); 47 | -------------------------------------------------------------------------------- /tests/helpers/spec_helper.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var environment = process.env.NODE_ENV || 'test'; 3 | var config = JSON.parse(fs.readFileSync(process.cwd() + '/config.json', encoding='utf8'))[environment]; 4 | 5 | GLOBAL.appConfig = function () {return config;}; 6 | GLOBAL.db = require('./../../models/index'); 7 | GLOBAL.queue = require('./../../lib/queue/index'); 8 | 9 | module.exports.should = require("should"); -------------------------------------------------------------------------------- /tests/unit/models/market_stats.coffee: -------------------------------------------------------------------------------- 1 | require "./../../helpers/spec_helper" 2 | speakeasy = require "speakeasy" 3 | 4 | describe "Order", -> 5 | marketStats = undefined 6 | 7 | beforeEach (done)-> 8 | marketStats = GLOBAL.db.MarketStats.build() 9 | GLOBAL.db.sequelize.sync({force: true}).complete ()-> 10 | done() 11 | 12 | describe "calculateGrowthRatio", ()-> 13 | describe "when the last price is 0.2 and the new price is 0.1", ()-> 14 | it "returns -50", ()-> 15 | GLOBAL.db.MarketStats.calculateGrowthRatio(0.2, 0.1).should.eql -50 16 | 17 | describe "when the last price is 2 and the new price is 3", ()-> 18 | it "returns 50", ()-> 19 | GLOBAL.db.MarketStats.calculateGrowthRatio(2, 3).should.eql 50 20 | 21 | describe "when the last price is 2 and the new price is 2.5", ()-> 22 | it "returns 25", ()-> 23 | GLOBAL.db.MarketStats.calculateGrowthRatio(2, 2.5).should.eql 25 24 | -------------------------------------------------------------------------------- /tests/unit/models/payment.coffee: -------------------------------------------------------------------------------- 1 | require "./../../helpers/spec_helper" 2 | 3 | describe "Payment", -> 4 | payment = undefined 5 | 6 | beforeEach (done)-> 7 | payment = GLOBAL.db.Payment.build {id: 1, user_id: 1, wallet_id: 1, amount: 1000000000, currency: "BTC", address: "mrLpnPMsKR8oFqRRYA28y4Txu98TUNQzVw", status: "pending"} 8 | GLOBAL.db.sequelize.sync({force: true}).complete ()-> 9 | done() 10 | 11 | describe "isProcessed", ()-> 12 | describe "when the status is processed", ()-> 13 | it "returns true", ()-> 14 | payment.status = "processed" 15 | payment.isProcessed().should.eql true 16 | 17 | describe "when the status is not processed", ()-> 18 | it "returns false", ()-> 19 | payment.status = "pending" 20 | payment.isProcessed().should.eql false 21 | 22 | 23 | describe "isCanceled", ()-> 24 | describe "when the status is canceled", ()-> 25 | it "returns true", ()-> 26 | payment.status = "canceled" 27 | payment.isCanceled().should.eql true 28 | 29 | describe "when the status is not canceled", ()-> 30 | it "returns false", ()-> 31 | payment.status = "pending" 32 | payment.isCanceled().should.eql false 33 | 34 | 35 | describe "isPending", ()-> 36 | describe "when the status is pending", ()-> 37 | it "returns true", ()-> 38 | payment.status = "pending" 39 | payment.isPending().should.eql true 40 | 41 | describe "when the status is not pending", ()-> 42 | it "returns false", ()-> 43 | payment.status = "canceled" 44 | payment.isPending().should.eql false 45 | 46 | 47 | describe "process", ()-> 48 | it "sets the status processed", (done)-> 49 | payment.process "txid", (err, pm)-> 50 | pm.status.should.eql "processed" 51 | done() 52 | 53 | it "sets the transaction id", (done)-> 54 | payment.process "txid", (err, pm)-> 55 | pm.transaction_id.should.eql "txid" 56 | done() 57 | 58 | it "sets the given result as log", (done)-> 59 | payment.process "txid", (err, pm)-> 60 | GLOBAL.db.PaymentLog.findByPaymentId pm.id, (err, paymentLogs)-> 61 | paymentLogs[0].log.should.eql "txid" 62 | done() 63 | 64 | 65 | describe "cancel", ()-> 66 | it "sets the status canceled", (done)-> 67 | payment.cancel "result", (err, pm)-> 68 | pm.status.should.eql "canceled" 69 | done() 70 | 71 | it "sets the given result as log", (done)-> 72 | payment.cancel "result", (err, pm)-> 73 | GLOBAL.db.PaymentLog.findByPaymentId pm.id, (err, paymentLogs)-> 74 | paymentLogs[0].log.should.eql "result" 75 | done() 76 | 77 | 78 | describe "errored", ()-> 79 | it "keeps the old status", (done)-> 80 | payment.errored {error: "failed"}, (err, pm)-> 81 | pm.status.should.eql "pending" 82 | done() 83 | 84 | it "sets the given result as log", (done)-> 85 | payment.errored "{error:'failed'}", (err, pm)-> 86 | GLOBAL.db.PaymentLog.findByPaymentId pm.id, (err, paymentLogs)-> 87 | paymentLogs[0].log.should.eql "{error:'failed'}" 88 | done() 89 | -------------------------------------------------------------------------------- /tests/unit/models/transaction.coffee: -------------------------------------------------------------------------------- 1 | require "./../../helpers/spec_helper" 2 | 3 | describe "Transaction", -> 4 | transaction = undefined 5 | wallet = GLOBAL.db.Wallet.build {user_id: 1} 6 | trTime = Date.now() / 1000 7 | transactionData = 8 | amount: 1 9 | txid: "unique_tx_id" 10 | confirmations: 6 11 | time: trTime 12 | account: "account" 13 | fee: 0.0001 14 | address: "address" 15 | category: "send" 16 | 17 | beforeEach (done)-> 18 | transaction = GLOBAL.db.Transaction.build() 19 | GLOBAL.db.sequelize.sync({force: true}).complete ()-> 20 | done() 21 | 22 | 23 | describe "addFromWallet", ()-> 24 | describe "when the given transaction does not exist", ()-> 25 | it "creates one", (done)-> 26 | GLOBAL.db.Transaction.addFromWallet transactionData, "BTC", wallet, (err, tr)-> 27 | expectedData = { 28 | id: 1, currency: "BTC", account: "account", fee: 10000, amount: 100000000, address: "address", category: "send", txid: "unique_tx_id", confirmations: 6 29 | } 30 | tr.values.should.have.properties expectedData 31 | done() 32 | 33 | describe "when the given transaction already exists", ()-> 34 | it "updates it", (done)-> 35 | GLOBAL.db.Transaction.addFromWallet transactionData, "BTC", wallet, (err, trOld)-> 36 | newTransactionData = transactionData 37 | newTransactionData.confirmations = 10 38 | GLOBAL.db.Transaction.addFromWallet newTransactionData, "BTC", wallet, (err, tr)-> 39 | expectedData = { 40 | id: 1, currency: "BTC", account: "account", fee: 10000, amount: 100000000, address: "address", category: "send", txid: "unique_tx_id", confirmations: 10 41 | } 42 | tr.values.should.have.properties expectedData 43 | trOld.id.should.eql tr.id 44 | done() 45 | -------------------------------------------------------------------------------- /tests/unit/models/user.coffee: -------------------------------------------------------------------------------- 1 | require "./../../helpers/spec_helper" 2 | speakeasy = require "speakeasy" 3 | 4 | describe "User", -> 5 | describe "hashPassword", ()-> 6 | it "returns the hashed password", ()-> 7 | password = "testPassword" 8 | GLOBAL.db.User.hashPassword(password).should.eql "f7dfe3adc9848f0d258f16ecaf79a524f13e704620a653885c913b1873774f62" 9 | -------------------------------------------------------------------------------- /views/_analytics.jade: -------------------------------------------------------------------------------- 1 | script. 2 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ 3 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), 4 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) 5 | })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); 6 | ga('create', 'UA-48125558-1', 'coinnext.com'); 7 | ga('send', 'pageview'); -------------------------------------------------------------------------------- /views/_config.jade: -------------------------------------------------------------------------------- 1 | script. 2 | CONFIG={csrf:"#{csrfToken}",users:{hostname:"#{GLOBAL.appConfig().users.hostname}"},currentUser:{id:"#{user ? user.uuid : ''}"}} -------------------------------------------------------------------------------- /views/admin.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang='en') 3 | head 4 | meta(charset="utf-8") 5 | meta(http-equiv="X-UA-Compatible" content="IE=edge") 6 | 7 | title #{title ? title : 'Administratie - Coinnext'} 8 | 9 | meta(name='description', content='') 10 | meta(name='viewport', content='width=device-width, initial-scale=1') 11 | 12 | link(href="/favicon_admin.ico" rel="shortcut icon") 13 | 14 | != css('admin') 15 | 16 | script. 17 | CONFIG = { 18 | csrf: "#{csrfToken}", 19 | currencies: ["!{currencies ? currencies.join('", "') : ''}"] 20 | } 21 | 22 | body 23 | - if (adminUser) 24 | .sidebar 25 | .page-head 26 | .site-logo 27 | img(src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==", alt="Coinnext").coinnext-logo 28 | ul.side-nav.nav 29 | li: a(href='/administratie', class=(page === 'stats' ? 'active' : '')) Dashboard 30 | li: a(href='/administratie/transactions', class=(page === 'transactions' ? 'active' : '')) Transactions 31 | li: a(href='/administratie/payments', class=(page === 'payments' ? 'active' : '')) Payments 32 | li: a(href='/administratie/users', class=(page === 'users' ? 'active' : '')) Users 33 | li: a(href='/administratie/markets', class=(page === 'markets' ? 'active' : '')) Markets 34 | li 35 | a(href='/administratie/wallets', class=(page === 'wallets' ? 'active' : '')) Wallets 36 | 37 | .top-bar 38 | .quick-search 39 | form#search-user-form 40 | input(type="text", placeholder="ID, email, username or deposit address", name="term") 41 | button#find-user-bt.btn(type="submit") Find 42 | a#search-user-result(target="_blank") 43 | 44 | ul.user-nav.nav 45 | li: a(href="/administratie/logout") Logout 46 | p.current-user Logged in as #{adminUser.email} 47 | 48 | block content 49 | 50 | != js('admin') 51 | -------------------------------------------------------------------------------- /views/admin/login.jade: -------------------------------------------------------------------------------- 1 | extends ../admin 2 | 3 | block content 4 | .content.auth 5 | .auth-box 6 | h1 Login 7 | form(action="/administratie/login", method="post") 8 | input(name="_csrf" type="hidden", value="#{csrfToken}") 9 | label Email 10 | input(name="email", type="email") 11 | br 12 | label Password 13 | input(name="password", type="password") 14 | br 15 | label Google Token 16 | input(name="gauth_pass", type="text") 17 | br 18 | button.btn(type="submit") Login 19 | -------------------------------------------------------------------------------- /views/admin/markets.jade: -------------------------------------------------------------------------------- 1 | extends ../admin 2 | 3 | block content 4 | .content 5 | h2.content-title Markets 6 | table#markets.table 7 | thead 8 | tr 9 | th Type 10 | th Status 11 | th Action 12 | tbody 13 | - each market in markets 14 | tr 15 | td #{market.type} 16 | td #{market.status} 17 | td 18 | button.market-switcher.btn(data-id="#{market.id}", data-status="#{market.status === 'disabled' ? 'enabled' : 'disabled'}", class="#{market.status === 'disabled' ? 'btn-success' : 'btn-danger'}") 19 | | #{market.status === 'disabled' ? 'Enable' : 'Disable'} 20 | -------------------------------------------------------------------------------- /views/admin/payments.jade: -------------------------------------------------------------------------------- 1 | extends ../admin 2 | 3 | block content 4 | .content 5 | h2.content-title Payments 6 | 7 | p 8 | - if (from - count >= 0) 9 | a(href="/administratie/payments?from=#{from - count}&user_id=#{userId ? userId : ''}") Prev 10 | | | 11 | a(href="/administratie/payments?from=#{from + count}&user_id=#{userId ? userId : ''}") Next 12 | 13 | table#payments.table 14 | thead 15 | tr 16 | th User 17 | th Address 18 | th Amount 19 | th Time 20 | th Status 21 | 22 | tbody 23 | - each payment in payments 24 | tr 25 | td 26 | a(href="/administratie/user/#{payment.user_id}", target="_blank") #{payment.user_id} 27 | a.payment-log-toggler(href="#") (View log) 28 | pre.payment-log.hidden 29 | - each paymentLog in payment.paymentLogs 30 | | !{jsonBeautifier.toHTML(paymentLog.log)} 31 | br 32 | | #{paymentLog.created_at} 33 | hr 34 | br 35 | | IP: #{payment.remote_ip} 36 | td #{payment.address} 37 | td #{_str.toFixed(payment.getFloat('amount'))} #{payment.currency} 38 | td #{payment.created_at.toFormat("DD MMM YYYY HH24:MI")} 39 | td 40 | - if (payment.fraud) 41 | span.fraud FRAUD 42 | span.payment-status #{payment.status} 43 | - if (!payment.isProcessed() && !payment.isCanceled()) 44 | button.pay.btn.btn-success(data-id="#{payment.id}") Accept 45 | button.remove.btn.btn-danger(data-id="#{payment.id}") Decline 46 | 47 | 48 | p 49 | - if (from - count >= 0) 50 | a(href="/administratie/payments?from=#{from - count}&user_id=#{userId ? userId : ''}") Prev 51 | | | 52 | a(href="/administratie/payments?from=#{from + count}&user_id=#{userId ? userId : ''}") Next 53 | -------------------------------------------------------------------------------- /views/admin/stats.jade: -------------------------------------------------------------------------------- 1 | extends ../admin 2 | 3 | block content 4 | .content 5 | h2.content-title Wallet Balances 6 | 7 | table.table.wallet-balance 8 | - for currency in currencies 9 | tr 10 | td.label 11 | a(data-currency="#{currency}").btn.show-wallet-info-bt #{currency} 12 | div(id="wallet-info-cnt-#{currency}").hidden 13 | span(id="wallet-address-#{currency}") 14 | pre(id="wallet-info-#{currency}") 15 | td 16 | span(id="bank-balance-#{currency}") pending... 17 | 18 | br 19 | -------------------------------------------------------------------------------- /views/admin/transactions.jade: -------------------------------------------------------------------------------- 1 | extends ../admin 2 | 3 | block content 4 | .content 5 | h2.content-title Transactions 6 | 7 | ul#transactions-stats.stats 8 | li 9 | | Total transactions: #{totalTransactions} 10 | 11 | p 12 | - if (from - count >= 0) 13 | a(href="/administratie/transactions?from=#{from - count}&user_id=#{userId ? userId : ''}") Prev 14 | | | 15 | a(href="/administratie/transactions?from=#{from + count}&user_id=#{userId ? userId : ''}") Next 16 | 17 | table#transactions.table 18 | thead 19 | tr 20 | th From 21 | th To 22 | th Amount 23 | th Time 24 | tbody 25 | - each transaction in transactions 26 | tr(class="#{transaction.category}") 27 | td 28 | - if (transaction.account && transaction.account.indexOf("wallet_") > -1 && transaction.wallet_id) 29 | a(href="/administratie/wallet/#{transaction.wallet_id}", target="_blank") #{wallet_id} 30 | - else 31 | | #{transaction.account} 32 | br 33 | a.transaction-log-toggler(href="#") View log 34 | pre.transaction-log.hidden 35 | | !{jsonBeautifier.toHTML(transaction)} 36 | td #{transaction.otheraccount || transaction.address} 37 | td #{_str.toFixed(transaction.getFloat('amount'))} #{transaction.currency} #{transaction.balance_loaded ? 'loaded' : ''} 38 | td #{transaction.created_at.toFormat("DD MMM YYYY HH24:MI")} 39 | 40 | p 41 | - if (from - count >= 0) 42 | a(href="/administratie/transactions?from=#{from - count}&user_id=#{userId ? userId : ''}") Prev 43 | | | 44 | a(href="/administratie/transactions?from=#{from + count}&user_id=#{userId ? userId : ''}") Next 45 | -------------------------------------------------------------------------------- /views/admin/user.jade: -------------------------------------------------------------------------------- 1 | extends ../admin 2 | 3 | block content 4 | .content 5 | h2.content-title User #{user.email} / #{user.username} / #{user.id} 6 | 7 | p Email verified: #{user.email_verified} 8 | p Two-factor authentication: #{!!userToken} 9 | p Created: #{user.created_at.toFormat("DD MMM YYYY HH24:MI")} 10 | p Updated: #{user.updated_at.toFormat("DD MMM YYYY HH24:MI")} 11 | 12 | - if (!user.email_verified) 13 | p 14 | button(data-id="#{user.id}")#resend-email-verification Re-send verification email 15 | 16 | p 17 | a(href="/administratie/payments?user_id=#{user.id}", target="_blank") View payments 18 | p 19 | a(href="/administratie/transactions?user_id=#{user.id}", target="_blank") View transactions 20 | 21 | h3 Wallets 22 | 23 | table#wallets.table.table-striped 24 | thead 25 | tr 26 | th Wallet 27 | th Address 28 | th Balance 29 | th Hold balance 30 | th Created 31 | th Updated 32 | tbody 33 | - each wallet in wallets 34 | tr 35 | td 36 | a(href="/administratie/wallet/#{wallet.id}", target="_blank") #{wallet.currency} 37 | td 38 | | #{wallet.address} 39 | td 40 | | #{_str.toFixed(wallet.getFloat('balance'))} 41 | td 42 | | #{_str.toFixed(wallet.getFloat('hold_balance'))} 43 | td 44 | | #{wallet.created_at.toFormat("DD MMM YYYY HH24:MI")} 45 | td 46 | | #{wallet.updated_at.toFormat("DD MMM YYYY HH24:MI")} 47 | 48 | h3 Logins 49 | table#logins.table.table-striped 50 | thead 51 | tr 52 | th IP 53 | th Date 54 | tbody 55 | - each login in authStats 56 | tr 57 | td 58 | | #{login.ip} 59 | td 60 | | #{login.created_at.toFormat("DD MMM YYYY HH24:MI")} 61 | -------------------------------------------------------------------------------- /views/admin/users.jade: -------------------------------------------------------------------------------- 1 | extends ../admin 2 | 3 | block content 4 | .content 5 | h2.content-title Users 6 | 7 | ul#users-stats.stats 8 | li 9 | | Total users: #{totalUsers} 10 | 11 | p 12 | - if (from - count >= 0) 13 | a(href="/administratie/users?from=#{from - count}") Prev 14 | | | 15 | a(href="/administratie/users?from=#{from + count}") Next 16 | 17 | table#users.table.table-striped 18 | thead 19 | tr 20 | th Email 21 | th Username 22 | th Created 23 | th Updated 24 | tbody 25 | - each user in users 26 | tr 27 | td 28 | a(href="/administratie/user/#{user.id}", target="_blank") #{user.email} 29 | td 30 | | #{user.username} 31 | td 32 | | #{user.created_at.toFormat("DD MMM YYYY HH24:MI")} 33 | td 34 | | #{user.updated_at.toFormat("DD MMM YYYY HH24:MI")} 35 | 36 | p 37 | - if (from - count >= 0) 38 | a(href="/administratie/users?from=#{from - count}") Prev 39 | | | 40 | a(href="/administratie/users?from=#{from + count}") Next 41 | -------------------------------------------------------------------------------- /views/admin/wallet.jade: -------------------------------------------------------------------------------- 1 | extends ../admin 2 | 3 | block content 4 | .content 5 | h2.content-title Wallet #{wallet.id} #{wallet.currency} 6 | 7 | p Address: #{wallet.address} 8 | p Balance: #{_str.toFixed(wallet.getFloat('balance'))} #{wallet.currency} 9 | p Hold balance: #{_str.toFixed(wallet.getFloat('hold_balance'))} #{wallet.currency} 10 | p Created: #{wallet.created_at.toFormat("DD MMM YYYY HH24:MI")} 11 | p Updated: #{wallet.updated_at.toFormat("DD MMM YYYY HH24:MI")} 12 | 13 | p 14 | a(href="/administratie/user/#{wallet.user_id}", target="_blank") View user #{wallet.user_id} 15 | 16 | h3 Open orders 17 | 18 | table#open-orders.table 19 | thead 20 | tr 21 | th ID 22 | th Type 23 | th Price 24 | th Amount/Matched 25 | th Received/Spent 26 | th Created 27 | 28 | tbody 29 | - each order in openOrders 30 | tr 31 | td 32 | | #{order.id} 33 | td 34 | | #{order.action} #{order.action == "buy" ? order.buy_currency : order.sell_currency} 35 | td 36 | | #{_str.toFixed(order.getFloat('unit_price'))} 37 | td 38 | | #{_str.toFixed(order.getFloat('amount'))} #{order.action == "buy" ? order.buy_currency : order.sell_currency} / #{_str.toFixed(order.getFloat('matched_amount'))} #{order.action == "buy" ? order.buy_currency : order.sell_currency} 39 | td 40 | | #{order.action == "buy" ? _str.toFixed(order.calculateSpentFromLogs(true)) :_str.toFixed(order.calculateReceivedFromLogs(true))} #{order.action == "buy" ? order.sell_currency : order.buy_currency} 41 | td 42 | | #{order.created_at.toFormat("DD MMM YYYY HH24:MI")} 43 | 44 | 45 | h3 Closed orders 46 | 47 | table#closed-orders.table 48 | thead 49 | tr 50 | th ID 51 | th Type 52 | th Price 53 | th Amount/Matched 54 | th Result/Spent 55 | th Created 56 | th Closed 57 | 58 | tbody 59 | - each order in closedOrders 60 | tr 61 | td 62 | | #{order.id} 63 | td 64 | | #{order.action} #{order.action == "buy" ? order.buy_currency : order.sell_currency} 65 | td 66 | | #{_str.toFixed(order.getFloat('unit_price'))} 67 | td 68 | | #{_str.toFixed(order.getFloat('amount'))} #{order.action == "buy" ? order.buy_currency : order.sell_currency} / #{_str.toFixed(order.getFloat('matched_amount'))} #{order.action == "buy" ? order.buy_currency : order.sell_currency} 69 | td 70 | | #{order.action == "buy" ? _str.toFixed(order.calculateSpentFromLogs(true)) : _str.toFixed(order.calculateReceivedFromLogs(true))} #{order.action == "buy" ? order.sell_currency : order.buy_currency} 71 | td 72 | | #{order.created_at.toFormat("DD MMM YYYY HH24:MI")} 73 | td 74 | | #{order.close_time ? order.close_time.toFormat("DD MMM YYYY HH24:MI") : ''} 75 | -------------------------------------------------------------------------------- /views/admin/wallets.jade: -------------------------------------------------------------------------------- 1 | extends ../admin 2 | 3 | block content 4 | .content 5 | h2.content-title Wallets #{currency} 6 | 7 | ul.currency-nav 8 | - each currency in currencies 9 | li: a(href="/administratie/wallets?currency=#{currency}") #{currency} 10 | 11 | ul#wallets-stats.stats 12 | li 13 | | Total wallets: #{totalWallets} 14 | 15 | p 16 | - if (from - count >= 0) 17 | a(href="/administratie/wallets?from=#{from - count}¤cy=#{currency}") Prev 18 | | | 19 | a(href="/administratie/wallets?from=#{from + count}¤cy=#{currency}") Next 20 | 21 | table#wallets.table.table-striped 22 | thead 23 | tr 24 | th Wallet 25 | th User 26 | th Balance 27 | th Hold balance 28 | th Created 29 | th Updated 30 | tbody 31 | - each wallet in wallets 32 | tr 33 | td 34 | a(href="/administratie/wallet/#{wallet.id}", target="_blank") #{wallet.id} 35 | td 36 | a(href="/administratie/user/#{wallet.user_id}", target="_blank") #{wallet.user_id} 37 | td 38 | | #{_str.toFixed(wallet.getFloat('balance'))} #{wallet.currency} 39 | td 40 | | #{_str.toFixed(wallet.getFloat('hold_balance'))} #{wallet.currency} 41 | td 42 | | #{wallet.created_at.toFormat("DD MMM YYYY HH24:MI")} 43 | td 44 | | #{wallet.updated_at.toFormat("DD MMM YYYY HH24:MI")} 45 | 46 | p 47 | - if (from - count >= 0) 48 | a(href="/administratie/wallets?from=#{from - count}¤cy=#{currency}") Prev 49 | | | 50 | a(href="/administratie/wallets?from=#{from + count}¤cy=#{currency}") Next 51 | -------------------------------------------------------------------------------- /views/auth.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang='en') 3 | head 4 | meta(charset="utf-8") 5 | meta(http-equiv="X-UA-Compatible" content="IE=edge") 6 | 7 | title #{title ? title : 'Coinnext - Cryptocurrency Exchange'} 8 | 9 | meta(name='description', content='Coinnext is a cryptocurrency exchange where you can trade a wide range of coins fast and securely.') 10 | meta(name='viewport', content='width=device-width, initial-scale=1') 11 | 12 | != css('main') 13 | 14 | include _config 15 | 16 | body.auth 17 | header.page-head 18 | .wrapper 19 | a(href="/", title="Return to homepage").site-logo 20 | img(src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==", alt="Coinnext").coinnext-logo 21 | nav.site-nav 22 | ul.nav 23 | - if (user) 24 | li: a(href='/funds', class=(page === 'funds' ? 'active' : '')) Funds 25 | li.dropdown 26 | a(href="/settings", class=(page === 'settings' ? 'active' : '')).dropdown-toggle 27 | | #{user.email} 28 | .caret 29 | ul.dropdown-menu 30 | li: a(href="/settings") Settings 31 | li: a(href="http://support.coinnext.com") Support 32 | li: a(href='/logout') Logout 33 | - else 34 | nav.auth-nav 35 | ul.nav 36 | li: a(href='/login').btn.white Login 37 | li: a(href='/signup').btn Open New Account 38 | block content 39 | 40 | != js('application') 41 | 42 | - if (process.env.NODE_ENV === "production") 43 | include _analytics -------------------------------------------------------------------------------- /views/auth/change_password.jade: -------------------------------------------------------------------------------- 1 | extends ../auth 2 | 3 | block content 4 | .main-content(role='main') 5 | .wrapper 6 | .auth-box 7 | h1 Change your password 8 | - if (success) 9 | p Please check your email for thurfer instructions about how to change your password. Thank you. 10 | - else 11 | form#change-pass-form(action="/change-password", method="post") 12 | input(type="hidden", name="_csrf", value="#{csrfToken}") 13 | #error-cnt 14 | - if (errors) 15 | - each error in errors 16 | - if (error === "wrong-token") 17 | li.error Sorry, but but the URL you provided is invalid. 18 | - if (error === "wrong-pass") 19 | li.error Your passwords do not match. 20 | .control-group 21 | input(type='hidden', name="token", value=token) 22 | label(for="password").label New password 23 | input#change-pass-new-pass(type="password", name="password", placeholder="New password", tabindex="1") 24 | .control-group 25 | label(for="repeat_password").label Confirm password 26 | input(type="password", name="repeat_password" placeholder="Confirm password", tabindex="3") 27 | input#change-pass-btn(type="submit", value="Change", tabindex="4") 28 | -------------------------------------------------------------------------------- /views/auth/login.jade: -------------------------------------------------------------------------------- 1 | extends ../auth 2 | 3 | block content 4 | .main-content(role='main') 5 | .wrapper 6 | .auth-box 7 | h1 Login 8 | form#login-form(action="/login", method="post") 9 | #error-cnt 10 | .control-group 11 | label(for="email").label Email address 12 | .input-prepend 13 | span.add-on 14 | i.icon-envelope 15 | input#login-email(type="email", name="email", placeholder="Email", tabindex="1", autofocus, autocomplete="off") 16 | .control-group 17 | label(for="password").label Password Forgot password? 18 | .input-prepend 19 | span.add-on 20 | i.icon-lock 21 | input#login-password(type="password", name="password", placeholder="Password", tabindex="2", autocomplete="off") 22 | .control-group 23 | label(for="gauth_pass").label Two Factor 24 | input#login-gauth(type="text", name="gauth_pass", value="", placeholder="Auth Token (optional)", tabindex="3", autocomplete="off") 25 | input#login-btn(type="submit", value="Log In", tabindex="4") 26 | .auth-links 27 | 28 | -------------------------------------------------------------------------------- /views/auth/resend_verify_link.jade: -------------------------------------------------------------------------------- 1 | extends ../auth 2 | 3 | block content 4 | .main-content(role='main') 5 | .wrapper 6 | .auth-box 7 | h1 Email Verification 8 | - if (!user || user.email_verified) 9 | p.msg 10 | | Your email was already verified. 11 | - else 12 | p.msg A verification link was sent to your email. 13 | br 14 | | Return to home page. 15 | -------------------------------------------------------------------------------- /views/auth/send_password.jade: -------------------------------------------------------------------------------- 1 | extends ../auth 2 | 3 | block content 4 | .main-content(role='main') 5 | .wrapper 6 | .auth-box.large 7 | h1 Reset Password 8 | - if (success) 9 | p.msg Please check your email for further instructions. 10 | - else 11 | form#send-pass-form(action="/send-password", method="post") 12 | input(type="hidden", name="_csrf", value="#{csrfToken}") 13 | #error-cnt 14 | - if (errors) 15 | - each error in errors 16 | - if (error === "wrong-user") 17 | p.error Sorry, but we could not find a profile with the given email. 18 | - if (error === "invalid-captcha") 19 | p.error Please fill in the correct captcha. 20 | .control-group 21 | label(for="email").label Email address 22 | .input-prepend 23 | span.add-on 24 | i.icon-envelope 25 | input#change-pass-email(type="email", name="email", placeholder="Email", tabindex="1", autofocus) 26 | #captcha 27 | script. 28 | var RecaptchaOptions = {theme : 'clean'}; 29 | script(src="https://www.google.com/recaptcha/api/challenge?k=#{recaptchaPublicKey}") 30 | input#change-pass-btn(type="submit", value="Reset Password", tabindex="2") 31 | -------------------------------------------------------------------------------- /views/auth/signup.jade: -------------------------------------------------------------------------------- 1 | extends ../auth 2 | 3 | block content 4 | .main-content(role='main') 5 | .wrapper 6 | .auth-wrap 7 | .auth-box 8 | h1 9 | | Create your account 10 | .promo Register now and pay no fees for 90 days. 11 | form#signup-form(action="/user", method="post") 12 | #error-cnt 13 | .control-group 14 | label(for="email").label Email address 15 | .input-prepend 16 | span.add-on 17 | i.icon-envelope 18 | input#signup-email(type="email", name="email", placeholder="Email", tabindex="1", autofocus, autocomplete="off") 19 | #hint.hint 20 | .control-group 21 | label(for="password").label Choose a password 22 | .input-prepend 23 | span.add-on 24 | i.icon-lock 25 | input#signup-password(type="password", name="password" placeholder="Minimum of 8 characters, at least 1 number", tabindex="2", autocomplete="off") 26 | .control-group 27 | label(for="repeat_password").label Confirm your password 28 | .input-prepend 29 | span.add-on 30 | i.icon-lock 31 | input(type="password", name="repeat_password" placeholder="Confirm password", tabindex="3", autocomplete="off") 32 | #password-strength 33 | span#s0.cell 34 | span#s1.cell 35 | span#s2.cell 36 | span#s3.cell 37 | span#s4.cell 38 | span#strength-text 39 | input#login-btn(type="submit", value="Create Account", tabindex="4") 40 | p.misc There is no further ID verification required to get started. 41 | 42 | p.terms 43 | | By creating an account you agree
to our Terms of Service and Privacy Policy. 44 | -------------------------------------------------------------------------------- /views/auth/verify.jade: -------------------------------------------------------------------------------- 1 | extends ../auth 2 | 3 | block content 4 | .main-content(role='main') 5 | .wrapper 6 | .auth-box 7 | h1 Email Verification 8 | - if (user && user.email_verified) 9 | p.msg 10 | | Your email was successfully verified. 11 | - else 12 | p.msg Your email could not be verified. 13 | br 14 | | Return to home page. -------------------------------------------------------------------------------- /views/emails/user_login_notice.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 39 | 40 |
11 | 12 | 13 | 16 | 17 |
14 | Coinnext 15 |
18 | 19 | 20 | 29 | 30 |
21 | A successful authorization was made on Coinnext: 22 |

23 |

Email: {{email}}

24 |

IP: {{ip}}

25 |

Date: {{auth_date}}

26 |

27 | Sincerely, The Coinnext Team 28 |
31 | 32 | 33 | 36 | 37 | 38 |
41 | 42 | -------------------------------------------------------------------------------- /views/errors.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang='en') 3 | head 4 | meta(charset="utf-8") 5 | meta(http-equiv="X-UA-Compatible" content="IE=edge") 6 | 7 | title #{title ? title : 'Unrecoverable Error - Coinnext - Cryptocurrency Exchange'} 8 | 9 | meta(name='description', content='Coinnext is a cryptocurrency exchange where you can trade a wide range of coins fast and securely.') 10 | meta(name='viewport', content='width=device-width, initial-scale=1') 11 | 12 | != css('errors') 13 | 14 | body 15 | block content 16 | 17 | - if (process.env.NODE_ENV === "production") 18 | include _analytics -------------------------------------------------------------------------------- /views/errors/404.jade: -------------------------------------------------------------------------------- 1 | extends ../errors 2 | 3 | block content 4 | .container 5 | h1 404 6 | p The requested page could not be found. Return to the homepage. -------------------------------------------------------------------------------- /views/errors/500.jade: -------------------------------------------------------------------------------- 1 | extends ../errors 2 | 3 | block content 4 | .container 5 | h1 500 6 | p We are experiencing an internal server problem. Please try again later. -------------------------------------------------------------------------------- /views/layout.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html(lang='en') 3 | head 4 | meta(charset="utf-8") 5 | meta(http-equiv="X-UA-Compatible" content="IE=edge") 6 | 7 | title #{title ? title : 'Coinnext - Cryptocurrency Exchange'} 8 | 9 | meta(name='description', content='Coinnext is a cryptocurrency exchange where you can trade a wide range of coins fast and securely.') 10 | meta(name='viewport', content='width=device-width, initial-scale=1') 11 | 12 | != css('main') 13 | 14 | include _config 15 | 16 | body 17 | include site/_header 18 | block content 19 | include site/_footer 20 | 21 | include templates/market_ticker 22 | include templates/chat_message 23 | 24 | != js('application') 25 | 26 | - if (process.env.NODE_ENV === "production") 27 | include _analytics -------------------------------------------------------------------------------- /views/site/_chatbox.jade: -------------------------------------------------------------------------------- 1 | #globalchat.chatbox 2 | ul#messages-list.message-list 3 | .message-post 4 | - if (user) 5 | input#chat-message-box(type="text", placeholder="Type here to chat...").chat-message-input 6 | button#send-message-bt Send 7 | - else 8 | p.login-required You need to be logged in to chat. Login -------------------------------------------------------------------------------- /views/site/_footer.jade: -------------------------------------------------------------------------------- 1 | - if (!user) 2 | .footer-cta 3 | h3.tagline Start trading today and pay no fees for 90 days! 4 | a(href='/signup').cta Get Started Now 5 | 6 | footer 7 | .footer-body 8 | .wrapper 9 | .column 10 | h2 Resources 11 | ul 12 | li: a(href='http://support.coinnext.com/') Support Center 13 | li: a(href='/api') API 14 | li: a(href='/status') Status 15 | .column 16 | h2 Policies 17 | ul 18 | li: a(href='/security') Security 19 | li: a(href='/legal/terms') Legal 20 | li: a(href='/fees') Fees 21 | .column 22 | h2 Community 23 | ul 24 | li: a(href='https://bitcointalk.org/index.php?topic=606729.0', target='_blank') BitcoinTalk 25 | li: a(href='http://www.reddit.com/r/coinnext/', target='_blank') Reddit 26 | li: a(href='http://blog.coinnext.com', target='_blank') Blog 27 | li: a(href='https://twitter.com/coinnext', target='_blank') Twitter 28 | .column.logo 29 | img(src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==", alt="Coinnext").coinnext-logo 30 | .copyright Copyright © 2014 Coinnext Cryptocurrency Exchange. All rights reserved. -------------------------------------------------------------------------------- /views/site/_header.jade: -------------------------------------------------------------------------------- 1 | header.page-head 2 | .wrapper 3 | a(href="/", title="Return to homepage").site-logo 4 | img(src="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==", alt="Coinnext").coinnext-logo 5 | //.search-bar 6 | // form.search-bar-form 7 | // i.icon-search 8 | // input(type="text", placeholder="Enter Symbol or Name") 9 | nav.site-nav 10 | ul.nav 11 | li: a(href='/', class=(page === 'home' ? 'active' : '')) Home 12 | li: a(href='/trade', class=(page === 'trade' ? 'active' : '')) Trade 13 | //li: a(href='/status', class=(page === 'status' ? 'active' : '')) Status 14 | - if (user) 15 | li: a(href='/funds', class=(page === 'funds' ? 'active' : '')) Funds 16 | li.dropdown 17 | a(href="/settings/preferences", class=(page === 'preferences' ? 'active' : '')).dropdown-toggle 18 | | #{user.email} 19 | .caret 20 | ul.dropdown-menu 21 | li: a(href="/settings/preferences") Settings 22 | li: a(href="http://support.coinnext.com") Support 23 | li: a(href='/logout') Logout 24 | - else 25 | nav.auth-nav 26 | ul.nav 27 | li: a(href='/login').btn.white Login 28 | // li: a(href='/signup').btn Open New Account -------------------------------------------------------------------------------- /views/site/_market-ticker.jade: -------------------------------------------------------------------------------- 1 | - if (marketStats) 2 | .market-ticker 3 | .con-header 4 | h2.con-header-title BTC Markets 5 | // a(href='').edit-ticker 6 | nav#market-ticker.market-ticker-coins 7 | 8 | ul 9 | - each info, type in marketStats 10 | - var growthRatio = _str.roundTo(info.growth_ratio, 2); 11 | - if (info.label) 12 | li.market-ticker-coin(data-market-type="#{type}", data-market-currency="#{info.label}", class="#{info.label == currency1 ? 'active' : ''}") 13 | a(href="/trade/#{type.replace('_', '/')}") 14 | .label #{info.label} 15 | - if (info.status === "disabled") 16 | div(class="rate on-hold") ON HOLD 17 | - else 18 | div(class="rate #{growthRatio > 0 ? 'up' : ''} #{growthRatio < 0 ? 'down' : ''} #{growthRatio == 0 ? 'none' : ''}") #{_str.toFixed(info.last_price, 8)} 19 | .move-coin 20 | 21 | .add-coin 22 | select 23 | option(val='') Display Exchange 24 | option(val='CL') CL - CopperLark 25 | option(val='DVC') DVC - Devcoin 26 | option(val='MNC') MNC - MinCoin -------------------------------------------------------------------------------- /views/site/_warnings.jade: -------------------------------------------------------------------------------- 1 | - if (user && !user.canTrade()) 2 | .alert.warning 3 | p 4 | | Before we enable trading on your account, we need to verify your email address.
Please click the verification link we have sent to the email address you used to create your account.
If you did not receive a verification link or the link expired, click here to resend the email. 5 | 6 | .alert.warning 7 | p 8 | | We are closing down Coinnext on 24th of July.
Please withdraw your coins at your earliest convenience. See here for more information. 9 | p 10 | | Also, our software is available for purchase, sale details here. -------------------------------------------------------------------------------- /views/site/funds.jade: -------------------------------------------------------------------------------- 1 | extends ../layout 2 | 3 | block content 4 | #finances.main-content.finances(role='main') 5 | .wrapper 6 | .col-aside 7 | include funds/_funds_list 8 | 9 | .col-main#finances-cnt 10 | #wallets.container 11 | .con-header 12 | h3.con-header-title Balances 13 | table.trade-data 14 | tr 15 | th Currency 16 | th Total Balance 17 | th Available Balance 18 | th Held for Orders 19 | - each wallet in wallets 20 | tr.wallet(data-id="#{wallet._id}") 21 | td #{wallet.currency} 22 | td #{_str.toFixed(wallet.getFloat('total_balance'))} #{wallet.currency} 23 | td #{_str.toFixed(wallet.getFloat('balance'))} #{wallet.currency} 24 | td #{_str.toFixed(wallet.getFloat('hold_balance'))} #{wallet.currency} 25 | 26 | // Open Orders (All) 27 | #open-orders.container 28 | .con-header 29 | h3.con-header-title Open Orders 30 | table.trade-data 31 | thead 32 | tr 33 | th Market 34 | th Date 35 | th Type 36 | th Price 37 | th Amount 38 | th Total 39 | th Action 40 | tbody#overview-open-orders-cnt 41 | 42 | // Order History 43 | #order-history.order-history.container 44 | .con-header 45 | h3.con-header-title Order History 46 | .table-wrap 47 | table.trade-data 48 | thead 49 | tr 50 | th Market 51 | th Date 52 | th Type 53 | th Price 54 | th Amount 55 | th Total 56 | tbody#overview-closed-orders-cnt 57 | 58 | include ../templates/wallet_open_order 59 | include ../templates/wallet_closed_order -------------------------------------------------------------------------------- /views/site/funds/_funds_list.jade: -------------------------------------------------------------------------------- 1 | .fund-list.container 2 | .con-header 3 | .con-header-title Wallets 4 | - var availableCurrencies = []; 5 | ul.fund-nav 6 | li(class="#{wallet ? '' : 'active'}"): a(href='/funds') Overview 7 | - each wl in wallets 8 | li(class="#{wallet && wallet.id == wl.id ? 'active' : ''}"): a(href='/funds/#{wl.currency}') 9 | .label #{wl.currency} 10 | .balance #{_str.toFixed(wl.getFloat('balance'))} 11 | - availableCurrencies.push(wl.currency); 12 | - if (availableCurrencies.length !== Object.keys(currencies).length) 13 | .add-wallet 14 | form#add-wallet-form 15 | select#currency-type(name="currency_type") 16 | - each currencyName, currency in currencies 17 | - if (availableCurrencies.indexOf(currency) === -1) 18 | option(value="#{currency}") #{currency} - #{currencyName} 19 | button(type="submit") Add -------------------------------------------------------------------------------- /views/site/settings/preferences.jade: -------------------------------------------------------------------------------- 1 | extends ../../layout 2 | 3 | block content 4 | #settings.main-content.settings(role='main') 5 | .wrapper 6 | .col-aside 7 | ul.sub-nav 8 | //li: a(href='/settings') Settings 9 | li.active: a(href='/settings/preferences') Preferences 10 | li: a(href='/settings/security') Security 11 | 12 | .col-main 13 | .container 14 | .con-header 15 | h1.con-header-title Social 16 | .con-body 17 | fieldset.checkbox 18 | label(for="chat_enabled") 19 | input(type="checkbox" name="chat_enabled" id="chat_enabled" checked=(user.chat_enabled)) 20 | | Chat 21 | form#username-update-form 22 | fieldset 23 | label(for="username") Username 24 | input(type="text" name="username" id="username" placeholder="username" value=user.username) 25 | .container 26 | .con-header 27 | h1.con-header-title Notifications 28 | .con-body 29 | fieldset.checkbox 30 | label(for="email_auth_enabled") 31 | input(type="checkbox" name="email_auth_enabled" id="email_auth_enabled" checked=(user.email_auth_enabled)) 32 | | Email on authentication 33 | -------------------------------------------------------------------------------- /views/site/settings/settings.jade: -------------------------------------------------------------------------------- 1 | extends ../../layout 2 | 3 | block content 4 | #settings.main-content.settings(role='main') 5 | .wrapper 6 | .col-aside 7 | ul.sub-nav 8 | //li.active: a(href='/settings') Settings 9 | li: a(href='/settings/preferences') Preferences 10 | li: a(href='/settings/security') Security 11 | 12 | .col-main 13 | .container 14 | .con-header 15 | h1.con-header-title Settings 16 | .con-body 17 | -------------------------------------------------------------------------------- /views/site/status.jade: -------------------------------------------------------------------------------- 1 | extends ../layout 2 | 3 | block content 4 | #status.main-content.status(role='main') 5 | .wrapper 6 | .one-col.content#status-cnt 7 | h1.page-title Status 8 | p Here you can find information about the current performance of all our wallets. 9 | table.status-legend 10 | tr 11 | td.label Normal 12 | td Means the wallet is running normal and blocks have been seen in the last 30 mins. 13 | tr 14 | td.label Delayed 15 | td Means we have not seen a block in the last 30 mins. This could be happening because of long block times or if there is a problem with the wallet. Please check with an official block explorer and see what's happening. 16 | tr 17 | td.label Blocked 18 | td Means we have not seen a block in over 60 mins. This could be happening because of long block times or if there is a problem with the wallet. Please check with an official block explorer and see what's happening. 19 | tr 20 | td.label Error 21 | td Means our wallet is currently not responding and is offline. 22 | 23 | #wallets.container 24 | .con-header 25 | h3.con-header-title Wallet Status 26 | table.trade-data 27 | tr 28 | th Status 29 | th Currency 30 | th Blocks 31 | th Connections 32 | th Last update 33 | th Last check 34 | 35 | - each wallet in wallets 36 | tr.wallet(data-id="#{wallet._id}") 37 | td 38 | span(class="wallet-status #{wallet.status}") #{wallet.status} 39 | td #{wallet.currency} 40 | td #{wallet.blocks} 41 | td #{wallet.connections} 42 | - lastUpdated = new Date(wallet.last_updated) 43 | - if (lastUpdated.getTime() !== lastUpdated.getTime()) 44 | td N/A 45 | - else 46 | td #{new Date(wallet.last_updated).toFormat('DD.MM.YY HH24:MI')} 47 | td #{new Date(wallet.updated_at).toFormat('DD.MM.YY HH24:MI')} 48 | -------------------------------------------------------------------------------- /views/static/cookie.jade: -------------------------------------------------------------------------------- 1 | extends ../layout 2 | 3 | block content 4 | .main-content(role='main') 5 | .wrapper 6 | .col-aside 7 | ul.sub-nav 8 | li: a(href='/legal/terms') Terms of Service 9 | li: a(href='/legal/privacy') Privacy Policy 10 | li.active: a(href='/legal/cookie') Cookie Policy 11 | .col-main.content 12 | h1.page-title Cookie Policy 13 | p By using the coinnext.com web site ("Service"), a service distributed by Coinnext Limited ("Coinnext"), you are agreeing to be bound by the following cookie policy ("Cookie Policy"). 14 | p Cookies are small data files which are placed on your computer or other mobile or handheld device (such as smartphones) as you browse the Website. They are used to ‘remember’ when your computer or device accesses the Website. The cookies are essential to the effective operation of our Website and enables you to use our services. 15 | 16 | p By using the Service you agree and give your consent to Coinnext to install cookies in your computer or other mobile or handheld device. 17 | 18 | p Some cookies collect information about browsing and your behavior at the Website. We do not use cookies to collect or record information on your name, address or other contact details. Third parties are not able to identify you by using cookies collected by us. 19 | 20 | p 21 | | If you want to disable cookies you need to change your website browser settings to reject cookies. How to do this will depend on the browser you use. 22 | | If you disable cookies, the Website may not operate properly. 23 | 24 | p.last-updated This agreement has been last updated on January 20th, 2014. 25 | -------------------------------------------------------------------------------- /views/static/fees.jade: -------------------------------------------------------------------------------- 1 | extends ../layout 2 | 3 | block content 4 | .main-content(role='main') 5 | .wrapper 6 | .one-col.content 7 | h1.page-title Fees 8 | 9 | h2 Trading 10 | .fees.container 11 | table.trade-data 12 | tr 13 | th Buying 14 | td 0.2% FREE 15 | tr 16 | th Selling 17 | td 0.2% FREE 18 | 19 | h2 Deposit 20 | .fees.container 21 | table.trade-data 22 | tr 23 | th Coins 24 | td FREE 25 | 26 | 27 | h2 Withdraw 28 | p Withdrawals are free. We do add a transaction fee to ensure swift processing by the network. 29 | .fees.container 30 | table.trade-data 31 | - each currencyLabel in MarketHelper.getSortedCurrencyTypes() 32 | tr 33 | th #{currencyLabel} 34 | td #{MarketHelper.fromBigint(MarketHelper.getWithdrawalFee(currencyLabel))} 35 | p All fees are subject to change. Any fee change will be announced in writing prior to going live. 36 | 37 | -------------------------------------------------------------------------------- /views/static/security.jade: -------------------------------------------------------------------------------- 1 | extends ../layout 2 | 3 | block content 4 | .main-content(role='main') 5 | .wrapper 6 | .one-col.content 7 | h1.page-title Security Policy 8 | 9 | h2 Built securely from the ground up 10 | p 11 | | Coinnext meets all industry standards to safeguard your data. We use various methods to secure our servers as well as our software. If you have any questions or concerns about our security, please contact us at security@coinnext.com 12 | 13 | h2 Cryptocurrency 14 | p 15 | | A majority of customer deposits are stored in offline wallets. These are wallets that we keep offline with complete air-gap isolation. 16 | | An absolute minimum of coins to maintain operational liquidity are stored in online wallets. 17 | | All wallets are encrypted at all times. The wallets and paper backups of them are stored in several geographically distributed vaults. 18 | | Furthermore, we maintain full reserves at all times. 19 | 20 | h2 Server 21 | p 22 | | Network infrastructure is segregated and protected by physical firewalls.
23 | | We use Cloudflare for DDoS protection.
24 | | All data is backed up on a regular basis to ensure redundancy.
25 | | We regularly scan our networks for any security issues using several third-party service providers.
26 | | All-network traffic is encrypted via SSL and SSH. 27 | 28 | h2 Software 29 | p 30 | | All application traffic is sent over SSL/TLS. 31 |
32 | | We perform regular internal source code audits. 33 | | We run an active white hat program through various third party service providers. 34 | 35 | 36 | h2 Account 37 | p 38 | | We offer two-factor authentication using Google Authenticator as an extra security layer for authentication.
39 | | All sensitive data like your password is encrypted before stored in the database. 40 | 41 | h2 Organizational 42 | p 43 | | Prospective employees undergo security screenings during the hiring process. 44 | | All employees use encrypted storage and encrypted password management. -------------------------------------------------------------------------------- /views/templates/chat_message.jade: -------------------------------------------------------------------------------- 1 | script(type="text/template", id="chat-message-tpl") 2 | li(class="message") 3 | span(class="sender", data-username="<%= message.get('username') %>") 4 | <%= _.str.truncate(message.get('username'), 10) %> 5 | | : <%= _.str.escapeHTML(message.get('message')) %> -------------------------------------------------------------------------------- /views/templates/coin_stats.jade: -------------------------------------------------------------------------------- 1 | script(type="text/template", id="coin-stats-tpl") 2 | table.trade-data 3 | tr.last-price 4 | th Last Price: 5 | td 6 | <%= _.str.toFixed(coinStats.last_price) %> <%= currency2 %> 7 | span(class="rate <%= coinStats.growth_ratio > 0 ? 'up' : '' %> <%= coinStats.growth_ratio < 0 ? 'down' : '' %> <%= coinStats.growth_ratio == 0 ? 'none' : '' %>") <%= _.str.roundTo(coinStats.growth_ratio, 2) * (coinStats.growth_ratio < 0 ? -1 : 1) %>% 8 | tr.high-price 9 | th 24hr High: 10 | td <%= _.str.toFixed(coinStats.day_high) %> <%= currency2 %> 11 | tr.low-price 12 | th 24hr Low: 13 | td <%= _.str.toFixed(coinStats.day_low) %> <%= currency2 %> 14 | tr.volume 15 | th Volume: 16 | td <%= _.str.satoshiRound(coinStats.volume2) %> <%= currency2 %> -------------------------------------------------------------------------------- /views/templates/market_ticker.jade: -------------------------------------------------------------------------------- 1 | script(type="text/template", id="market-ticker-tpl") 2 | ul 3 | |<% _.each(marketStats.attributes, function (info, type) { var growthRatio = _.str.roundTo(info.growth_ratio, 2); %> 4 | |<% if (info.label) { %> 5 | li.market-ticker-coin(data-market-type="<%= type %>", data-market-currency="<%= info.label %>") 6 | // a(href='', title='Hide Exchange').hide-coin 7 | a(href="/trade/<%= type.replace('_', '/') %>") 8 | .label <%= info.label %> 9 | |<% if (info.status === "disabled") { %> 10 | div(class="rate on-hold") ON HOLD 11 | |<% } else { %> 12 | div(class="rate <%= growthRatio > 0 ? 'up' : '' %> <%= growthRatio < 0 ? 'down' : '' %> <%= growthRatio == 0 ? 'none' : '' %>") <%= _.str.toFixed(info.last_price, 8) %> 13 | |<% } %> 14 | .move-coin 15 | |<% } %> 16 | |<% }); %> -------------------------------------------------------------------------------- /views/templates/open_order.jade: -------------------------------------------------------------------------------- 1 | script(type="text/template", id="open-order-tpl") 2 | tr(class="order-row <%= order.get('action') %>", data-id="<%= order.id %>") 3 | td.trade-date <%= order.getCreatedDate() %> 4 | td.trade-type <%= order.get('action') %> 5 | td.trade-price <%= _.str.toFixed(order.get('unit_price')) %> 6 | td.trade-amount <%= _.str.toFixed(order.calculateFirstNoFeeAmount()) %> 7 | td.trade-total <%= _.str.toFixed(order.calculateSecondNoFeeAmount()) %> 8 | td.trade-action 9 | | <% if (!order.get("in_queue")) { %> 10 | Cancel 11 | | <% } else { %> 12 | span Pending... 13 | | <% } %> -------------------------------------------------------------------------------- /views/templates/order_book_order.jade: -------------------------------------------------------------------------------- 1 | script(type="text/template", id="order-book-order-tpl") 2 | tr.order-book-order(data-action="<%= order.get('action') %>", data-unit-price="<%= _.str.toFixed(order.get('unit_price')) %>") 3 | td.trade-price <%= _.str.toFixed(order.get('unit_price')) %> 4 | td.trade-amount <%= _.str.toFixed(order.calculateFirstNoFeeAmount()) %> 5 | td.trade-total <%= _.str.toFixed(order.calculateSecondNoFeeAmount()) %> -------------------------------------------------------------------------------- /views/templates/pending_transaction.jade: -------------------------------------------------------------------------------- 1 | script(type="text/template", id="pending-transaction-tpl") 2 | | <% if (typeof(transaction) !== "undefined") { %> 3 | tr(class="<%= transaction.get('category') === 'send' ? 'substract' : 'add' %>", data-id="<%= transaction.id %>") 4 | td <%= transaction.get('txid') %> 5 | td.trade-amount <%= transaction.get('category') === 'receive' ? '+' : '' %><%= _.str.toFixed(transaction.get("amount")) %> <%= transaction.get("currency") %> 6 | td <%= transaction.get("confirmations") %>/<%= transaction.get("min_confirmations") %> 7 | | <% } %> 8 | | <% if (typeof(payment) !== "undefined") { %> 9 | tr.substract(data-id="<%= payment.id %>") 10 | td Withdrawal to <%= payment.get("address") %> 11 | td.trade-amount -<%= _.str.toFixed(payment.get("amount")) %> <%= payment.get("currency") %> 12 | td Pending approval 13 | | <% } %> -------------------------------------------------------------------------------- /views/templates/site_closed_order.jade: -------------------------------------------------------------------------------- 1 | script(type="text/template", id="site-closed-order-tpl") 2 | tr(class="<%= order.get('action') %>", data-id="<%= order.id %>") 3 | td.trade-date <%= order.getTime() %> 4 | td.trade-type <%= order.get('action') %> 5 | td.trade-price <%= _.str.toFixed(order.get('unit_price')) %> 6 | td.trade-amount <%= _.str.toFixed(order.calculateFirstAmount()) %> 7 | td.trade-total <%= _.str.toFixed(order.calculateSecondAmount()) %> -------------------------------------------------------------------------------- /views/templates/transaction_history.jade: -------------------------------------------------------------------------------- 1 | script(type="text/template", id="transaction-history-tpl") 2 | | <% if (typeof(transaction) !== "undefined") { %> 3 | tr(class="<%= transaction.get('category') === 'send' ? 'substract' : 'add' %>", data-id="<%= transaction.id %>") 4 | td <%= transaction.getCreatedDate() %> 5 | td <%= transaction.get('category') === 'send' ? 'Withdrawal to ' + transaction.get('address') : 'Deposit' %> 6 | td.trade-amount <%= transaction.get('category') === 'receive' ? '+' : '' %><%= _.str.toFixed(transaction.get("amount")) %> <%= transaction.get("currency") %> 7 | | <% } %> 8 | -------------------------------------------------------------------------------- /views/templates/wallet_closed_order.jade: -------------------------------------------------------------------------------- 1 | script(type="text/template", id="wallet-closed-order-tpl") 2 | tr(class="<%= order.get('action') %>", data-id="<%= order.id %>") 3 | | <% if (order.get('action') === 'sell') { %> 4 | td.trade-date <%= order.get('sell_currency') %>/<%= order.get('buy_currency') %> 5 | | <% } else { %> 6 | td.trade-date <%= order.get('buy_currency') %>/<%= order.get('sell_currency') %> 7 | | <% } %> 8 | td.trade-type <%= order.getTime() %> 9 | td.trade-type <%= order.get('action') %> 10 | td.trade-price <%= _.str.toFixed(order.get('unit_price')) %> 11 | td.trade-amount <%= _.str.toFixed(order.calculateFirstAmount()) %> 12 | td.trade-total <%= _.str.toFixed(order.calculateSecondAmount()) %> -------------------------------------------------------------------------------- /views/templates/wallet_open_order.jade: -------------------------------------------------------------------------------- 1 | script(type="text/template", id="wallet-open-order-tpl") 2 | tr(class="order-row <%= order.get('action') %>", data-id="<%= order.id %>") 3 | | <% if (order.get('action') === 'sell') { %> 4 | td.trade-date <%= order.get('sell_currency') %>/<%= order.get('buy_currency') %> 5 | | <% } else { %> 6 | td.trade-date <%= order.get('buy_currency') %>/<%= order.get('sell_currency') %> 7 | | <% } %> 8 | td.trade-type <%= order.getCreatedDate() %> 9 | td.trade-type <%= order.get('action') %> 10 | td.trade-price <%= _.str.toFixed(order.get('unit_price')) %> 11 | td.trade-amount <%= _.str.toFixed(order.calculateFirstNoFeeAmount()) %> 12 | td.trade-total <%= _.str.toFixed(order.calculateSecondNoFeeAmount()) %> 13 | td.trade-action Cancel --------------------------------------------------------------------------------