├── favicon.ico ├── img ├── bg.png ├── logo.png ├── main.png ├── logout.png ├── button-s.png ├── favicon.ico ├── loading.png ├── logo_2x.png ├── options.png ├── profile.png ├── settings.png ├── spritex.png ├── throbber.gif ├── throbber5.gif ├── authy-close.gif ├── authy-flags.png ├── logout-gray.png ├── sprite_menu.png ├── icons-dropdown.png ├── mobile │ ├── icon120.png │ ├── icon128.png │ ├── icon152.png │ ├── icon196.png │ ├── icon60.png │ └── icon76.png ├── profile-gray.png ├── settings-gray.png ├── sprite_menu_2x.png ├── transparent_l.gif ├── glyphicons-halflings.png ├── glyphicons-halflings-white.png └── dropdown_arrow.svg ├── res └── dmg │ ├── logo.png │ ├── xrp_ripple_logo.ico │ └── xrp_ripple_logo.icns ├── deps ├── downloadify.swf └── sjcl-custom │ ├── index.js │ ├── sjcl-ecdsa-canonical.js │ ├── sjcl-ecdsa-der.js │ ├── sjcl-validecc.js │ ├── sjcl-ecc-pointextras.js │ ├── sjcl-secp256k1.js │ └── sjcl-extramath.js ├── fonts ├── OpenSans-Bold-webfont.eot ├── OpenSans-Bold-webfont.ttf ├── OpenSans-Bold-webfont.woff ├── OpenSans-Light-webfont.eot ├── OpenSans-Light-webfont.ttf ├── OpenSans-Italic-webfont.eot ├── OpenSans-Italic-webfont.ttf ├── OpenSans-Italic-webfont.woff ├── OpenSans-Light-webfont.woff ├── OpenSans-Regular-webfont.eot ├── OpenSans-Regular-webfont.ttf ├── OpenSans-BoldItalic-webfont.eot ├── OpenSans-BoldItalic-webfont.ttf ├── OpenSans-ExtraBold-webfont.eot ├── OpenSans-ExtraBold-webfont.ttf ├── OpenSans-ExtraBold-webfont.woff ├── OpenSans-Regular-webfont.woff ├── OpenSans-Semibold-webfont.eot ├── OpenSans-Semibold-webfont.ttf ├── OpenSans-Semibold-webfont.woff ├── OpenSans-BoldItalic-webfont.woff ├── OpenSans-LightItalic-webfont.eot ├── OpenSans-LightItalic-webfont.ttf ├── OpenSans-LightItalic-webfont.woff ├── OpenSans-ExtraBoldItalic-webfont.eot ├── OpenSans-ExtraBoldItalic-webfont.ttf ├── OpenSans-SemiboldItalic-webfont.eot ├── OpenSans-SemiboldItalic-webfont.ttf ├── OpenSans-SemiboldItalic-webfont.woff ├── OpenSans-ExtraBoldItalic-webfont.woff └── stylesheet.css ├── icons ├── font │ ├── icons-51c446195575425cef2ecd7189d41eec.eot │ ├── icons-51c446195575425cef2ecd7189d41eec.ttf │ ├── icons-51c446195575425cef2ecd7189d41eec.woff │ ├── icons.less │ └── icons-51c446195575425cef2ecd7189d41eec.svg └── svg │ ├── metal-bars.svg │ └── ripple-logo.svg ├── src ├── templates │ ├── messages │ │ └── sendconvert │ │ │ ├── _waiting.jade │ │ │ ├── _localerror.jade │ │ │ └── _confirmation.jade │ ├── tabs │ │ ├── banner │ │ │ └── _unfunded.jade │ │ ├── settings │ │ │ └── _navbar.jade │ │ ├── settingstrade.jade │ │ ├── coldwalletsettings.jade │ │ ├── tx.jade │ │ ├── submit.jade │ │ ├── balance.jade │ │ ├── accountflags.jade │ │ ├── coldwallet.jade │ │ ├── login.jade │ │ └── history │ │ │ └── _effects.jade │ ├── directives │ │ ├── signedTransaction.jade │ │ └── transactionerror.jade │ ├── client │ │ └── layout.jade │ └── index.jade ├── less │ ├── ripple │ │ ├── browser-chrome.less │ │ ├── main.less │ │ ├── utils.less │ │ ├── sortable.less │ │ ├── tabs │ │ │ ├── submit.less │ │ │ └── settingsTrade.less │ │ ├── buttons.less │ │ ├── combobox.less │ │ ├── variables.less │ │ ├── pagemodes.less │ │ └── forms.less │ └── elements │ │ └── README.md ├── js │ ├── services │ │ ├── api.js │ │ ├── globalwrappers.js │ │ ├── transactions.js │ │ ├── filedialog.js │ │ ├── authflow.js │ │ ├── network.js │ │ ├── popup.js │ │ ├── ledger.js │ │ ├── keychain.js │ │ └── nwhelpers.js │ ├── util │ │ ├── log.js │ │ ├── generic.js │ │ ├── types.js │ │ ├── settings.js │ │ └── base58.js │ ├── directives │ │ ├── events.js │ │ ├── errors.js │ │ ├── datalinks.js │ │ ├── effects.js │ │ ├── signedTransaction.js │ │ └── qr.js │ ├── client │ │ └── tab.js │ ├── tabs │ │ ├── eula.js │ │ ├── tou.js │ │ ├── balance.js │ │ ├── coldwalletsettings.js │ │ ├── tx.js │ │ ├── settingstrade.js │ │ ├── register.js │ │ └── contacts.js │ ├── entry │ │ └── vendor.js │ └── data │ │ ├── pairs.js │ │ └── iso4217.js └── package.json ├── .gitignore ├── test ├── karma.conf.js ├── e2e │ ├── protractor.conf-example.js │ ├── login.js │ └── send.js └── unit │ ├── directivesSpec.js │ ├── controllers │ ├── navbarControllerSpec.js │ └── appControllerSpec.js │ └── tabs │ └── exchangeTabSpec.js ├── LICENSE ├── README.md ├── l10n ├── TRANSLATORS.md ├── languages.json └── DEVELOPERS.md ├── config_example.js ├── package.json ├── .eslintrc └── CONTRIBUTING.md /favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/favicon.ico -------------------------------------------------------------------------------- /img/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/img/bg.png -------------------------------------------------------------------------------- /img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/img/logo.png -------------------------------------------------------------------------------- /img/main.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/img/main.png -------------------------------------------------------------------------------- /img/logout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/img/logout.png -------------------------------------------------------------------------------- /img/button-s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/img/button-s.png -------------------------------------------------------------------------------- /img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/img/favicon.ico -------------------------------------------------------------------------------- /img/loading.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/img/loading.png -------------------------------------------------------------------------------- /img/logo_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/img/logo_2x.png -------------------------------------------------------------------------------- /img/options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/img/options.png -------------------------------------------------------------------------------- /img/profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/img/profile.png -------------------------------------------------------------------------------- /img/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/img/settings.png -------------------------------------------------------------------------------- /img/spritex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/img/spritex.png -------------------------------------------------------------------------------- /img/throbber.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/img/throbber.gif -------------------------------------------------------------------------------- /img/throbber5.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/img/throbber5.gif -------------------------------------------------------------------------------- /res/dmg/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/res/dmg/logo.png -------------------------------------------------------------------------------- /img/authy-close.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/img/authy-close.gif -------------------------------------------------------------------------------- /img/authy-flags.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/img/authy-flags.png -------------------------------------------------------------------------------- /img/logout-gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/img/logout-gray.png -------------------------------------------------------------------------------- /img/sprite_menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/img/sprite_menu.png -------------------------------------------------------------------------------- /deps/downloadify.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/deps/downloadify.swf -------------------------------------------------------------------------------- /img/icons-dropdown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/img/icons-dropdown.png -------------------------------------------------------------------------------- /img/mobile/icon120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/img/mobile/icon120.png -------------------------------------------------------------------------------- /img/mobile/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/img/mobile/icon128.png -------------------------------------------------------------------------------- /img/mobile/icon152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/img/mobile/icon152.png -------------------------------------------------------------------------------- /img/mobile/icon196.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/img/mobile/icon196.png -------------------------------------------------------------------------------- /img/mobile/icon60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/img/mobile/icon60.png -------------------------------------------------------------------------------- /img/mobile/icon76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/img/mobile/icon76.png -------------------------------------------------------------------------------- /img/profile-gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/img/profile-gray.png -------------------------------------------------------------------------------- /img/settings-gray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/img/settings-gray.png -------------------------------------------------------------------------------- /img/sprite_menu_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/img/sprite_menu_2x.png -------------------------------------------------------------------------------- /img/transparent_l.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/img/transparent_l.gif -------------------------------------------------------------------------------- /res/dmg/xrp_ripple_logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/res/dmg/xrp_ripple_logo.ico -------------------------------------------------------------------------------- /img/glyphicons-halflings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/img/glyphicons-halflings.png -------------------------------------------------------------------------------- /res/dmg/xrp_ripple_logo.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/res/dmg/xrp_ripple_logo.icns -------------------------------------------------------------------------------- /fonts/OpenSans-Bold-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/fonts/OpenSans-Bold-webfont.eot -------------------------------------------------------------------------------- /fonts/OpenSans-Bold-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/fonts/OpenSans-Bold-webfont.ttf -------------------------------------------------------------------------------- /fonts/OpenSans-Bold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/fonts/OpenSans-Bold-webfont.woff -------------------------------------------------------------------------------- /fonts/OpenSans-Light-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/fonts/OpenSans-Light-webfont.eot -------------------------------------------------------------------------------- /fonts/OpenSans-Light-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/fonts/OpenSans-Light-webfont.ttf -------------------------------------------------------------------------------- /fonts/OpenSans-Italic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/fonts/OpenSans-Italic-webfont.eot -------------------------------------------------------------------------------- /fonts/OpenSans-Italic-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/fonts/OpenSans-Italic-webfont.ttf -------------------------------------------------------------------------------- /fonts/OpenSans-Italic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/fonts/OpenSans-Italic-webfont.woff -------------------------------------------------------------------------------- /fonts/OpenSans-Light-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/fonts/OpenSans-Light-webfont.woff -------------------------------------------------------------------------------- /fonts/OpenSans-Regular-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/fonts/OpenSans-Regular-webfont.eot -------------------------------------------------------------------------------- /fonts/OpenSans-Regular-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/fonts/OpenSans-Regular-webfont.ttf -------------------------------------------------------------------------------- /img/glyphicons-halflings-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/img/glyphicons-halflings-white.png -------------------------------------------------------------------------------- /fonts/OpenSans-BoldItalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/fonts/OpenSans-BoldItalic-webfont.eot -------------------------------------------------------------------------------- /fonts/OpenSans-BoldItalic-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/fonts/OpenSans-BoldItalic-webfont.ttf -------------------------------------------------------------------------------- /fonts/OpenSans-ExtraBold-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/fonts/OpenSans-ExtraBold-webfont.eot -------------------------------------------------------------------------------- /fonts/OpenSans-ExtraBold-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/fonts/OpenSans-ExtraBold-webfont.ttf -------------------------------------------------------------------------------- /fonts/OpenSans-ExtraBold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/fonts/OpenSans-ExtraBold-webfont.woff -------------------------------------------------------------------------------- /fonts/OpenSans-Regular-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/fonts/OpenSans-Regular-webfont.woff -------------------------------------------------------------------------------- /fonts/OpenSans-Semibold-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/fonts/OpenSans-Semibold-webfont.eot -------------------------------------------------------------------------------- /fonts/OpenSans-Semibold-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/fonts/OpenSans-Semibold-webfont.ttf -------------------------------------------------------------------------------- /fonts/OpenSans-Semibold-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/fonts/OpenSans-Semibold-webfont.woff -------------------------------------------------------------------------------- /fonts/OpenSans-BoldItalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/fonts/OpenSans-BoldItalic-webfont.woff -------------------------------------------------------------------------------- /fonts/OpenSans-LightItalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/fonts/OpenSans-LightItalic-webfont.eot -------------------------------------------------------------------------------- /fonts/OpenSans-LightItalic-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/fonts/OpenSans-LightItalic-webfont.ttf -------------------------------------------------------------------------------- /fonts/OpenSans-LightItalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/fonts/OpenSans-LightItalic-webfont.woff -------------------------------------------------------------------------------- /fonts/OpenSans-ExtraBoldItalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/fonts/OpenSans-ExtraBoldItalic-webfont.eot -------------------------------------------------------------------------------- /fonts/OpenSans-ExtraBoldItalic-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/fonts/OpenSans-ExtraBoldItalic-webfont.ttf -------------------------------------------------------------------------------- /fonts/OpenSans-SemiboldItalic-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/fonts/OpenSans-SemiboldItalic-webfont.eot -------------------------------------------------------------------------------- /fonts/OpenSans-SemiboldItalic-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/fonts/OpenSans-SemiboldItalic-webfont.ttf -------------------------------------------------------------------------------- /fonts/OpenSans-SemiboldItalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/fonts/OpenSans-SemiboldItalic-webfont.woff -------------------------------------------------------------------------------- /fonts/OpenSans-ExtraBoldItalic-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/fonts/OpenSans-ExtraBoldItalic-webfont.woff -------------------------------------------------------------------------------- /icons/font/icons-51c446195575425cef2ecd7189d41eec.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/icons/font/icons-51c446195575425cef2ecd7189d41eec.eot -------------------------------------------------------------------------------- /icons/font/icons-51c446195575425cef2ecd7189d41eec.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/icons/font/icons-51c446195575425cef2ecd7189d41eec.ttf -------------------------------------------------------------------------------- /icons/font/icons-51c446195575425cef2ecd7189d41eec.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ripple/ripple-client-desktop/HEAD/icons/font/icons-51c446195575425cef2ecd7189d41eec.woff -------------------------------------------------------------------------------- /src/templates/messages/sendconvert/_waiting.jade: -------------------------------------------------------------------------------- 1 | // Waiting message for the send and convert tabs 2 | 3 | p.literal(rp-spinner="4", l10n) Sending transaction to the Ripple network -------------------------------------------------------------------------------- /src/templates/tabs/banner/_unfunded.jade: -------------------------------------------------------------------------------- 1 | div.auth-attention.banner 2 | | Your Ripple account is not active. To activate it, you'll need to send at least 20 XRP to ({{address}}). 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .tmp/ 3 | .build/ 4 | .tx/ 5 | .idea/ 6 | npm-debug.log 7 | packages 8 | node_modules 9 | config.js 10 | test/e2e/protractor.conf.js 11 | res/nw/config.js 12 | -------------------------------------------------------------------------------- /src/less/ripple/browser-chrome.less: -------------------------------------------------------------------------------- 1 | input[type=number]::-webkit-inner-spin-button, 2 | input[type=number]::-webkit-outer-spin-button { 3 | -webkit-appearance: none; 4 | margin: 0; 5 | } -------------------------------------------------------------------------------- /src/js/services/api.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Ripple API 3 | * 4 | * The RippleAPI service 5 | */ 6 | 7 | 'use strict'; 8 | 9 | var module = angular.module('app'); 10 | 11 | module.factory('rpApi', function() 12 | { 13 | return new RippleAPI(Options.api); 14 | }); -------------------------------------------------------------------------------- /src/less/elements/README.md: -------------------------------------------------------------------------------- 1 | LESS Elements 2 | ============= 3 | 4 | A set of useful mixins for LESS, the CSS pre-processor: 5 | 6 | More information and usage examples over at: 7 | 8 | TextMate bundle: 9 | -------------------------------------------------------------------------------- /deps/sjcl-custom/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('./sjcl-ecc-pointextras.js'); 3 | require('./sjcl-secp256k1.js'); 4 | require('./sjcl-extramath.js'); 5 | require('./sjcl-validecc.js'); 6 | require('./sjcl-ecdsa-canonical.js'); 7 | require('./sjcl-ecdsa-der.js'); 8 | require('./sjcl-ecdsa-recoverablepublickey.js'); 9 | -------------------------------------------------------------------------------- /src/js/services/globalwrappers.js: -------------------------------------------------------------------------------- 1 | var globals = angular.module('app.globals', []); 2 | 3 | /* 4 | We want to be able to inject mocks into tests with dependencies on these globals 5 | */ 6 | 7 | // deps/js/store.js 8 | globals.constant('store', store); 9 | // config.js 10 | globals.constant('Options', Options); 11 | -------------------------------------------------------------------------------- /src/js/util/log.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Print an exception for debug purposes. 3 | * 4 | * Includes some logic to try and log a stack in various browsers. 5 | */ 6 | exports.exception = function (exception) { 7 | console.log("function" === typeof exception.getStack ? exception.getStack() : exception.stack); 8 | }; 9 | 10 | -------------------------------------------------------------------------------- /src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "index.html", 3 | "name": "RippleAdminConsole", 4 | "version": "1.4.0", 5 | "description": "Ripple Admin Console", 6 | "single-instance": false, 7 | "window": { 8 | "frame": true, 9 | "toolbar": false, 10 | "width": 1024, 11 | "height": 650, 12 | "min_width": 768, 13 | "min_height": 100, 14 | "title": "Ripple Admin Console" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/js/directives/events.js: -------------------------------------------------------------------------------- 1 | /** 2 | * EVENTS 3 | * 4 | * Angular-powered event handling directives go into this file. 5 | */ 6 | 7 | var module = angular.module('events', []); 8 | 9 | /** 10 | * Handle ENTER key press. 11 | */ 12 | module.directive('ngEnter', function() { 13 | return function(scope, elm, attrs) { 14 | elm.bind('keypress', function(e) { 15 | if (e.charCode === 13) scope.$apply(attrs.ngEnter); 16 | }); 17 | }; 18 | }); 19 | -------------------------------------------------------------------------------- /test/karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config) { 2 | config.set({ 3 | frameworks: ['mocha', 'sinon-chai'], 4 | basePath: '../', 5 | 6 | files: [ 7 | 'build/dist/deps-debug.js', 8 | 'deps/js/angular-mocks/angular-mocks.js', 9 | 'src/js/config.js', 10 | 'build/dist/web/ripple-client-debug.js', 11 | 'test/unit/**/*.js' 12 | ], 13 | 14 | browsers: ['Chrome', 'Firefox'], 15 | singleRun: false, 16 | autoWatch: true 17 | }); 18 | }; 19 | -------------------------------------------------------------------------------- /img/dropdown_arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /src/js/util/generic.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Description: Combines 2 arrays removing duplicates based on a object key 3 | * @param arr1: Array of objects 4 | * @param arr2: Array of objects 5 | * @param key: object key to be unique 6 | * 7 | * @return array of unique objects based on key 8 | */ 9 | exports.uniqueObjArray = function(arr1, arr2, key) { 10 | var obj = {}; 11 | _.forEach(arr1, function(v) { 12 | obj[v[key]] = v; 13 | }); 14 | 15 | _.forEach(arr2, function(v) { 16 | if (!(v[key] in obj)) { 17 | obj[v[key]] = v; 18 | } 19 | }); 20 | 21 | return _.values(obj); 22 | }; 23 | -------------------------------------------------------------------------------- /src/templates/tabs/settings/_navbar.jade: -------------------------------------------------------------------------------- 1 | .settingPage 2 | a(href="#/security", ng-class="{active: $route.current.tabName == 'security'}", l10n) Security settings 3 | a(href="#/settingstrade", ng-class="{active: $route.current.tabName == 'settingstrade'}", l10n) Trade settings 4 | a(href="#/advanced", ng-class="{active: $route.current.tabName == 'advanced'}", l10n) Network settings 5 | a(href="#/accountflags", ng-class="{active: $route.current.tabName == 'accountflags'}", l10n) Account flags 6 | a(href="#/coldwalletsettings", ng-class="{active: $route.current.tabName == 'coldwalletsettings'}", l10n) Cold wallet settings -------------------------------------------------------------------------------- /deps/sjcl-custom/sjcl-ecdsa-canonical.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var sjcl = require('sjcl'); 3 | 4 | sjcl.ecc.ecdsa.secretKey.prototype.canonicalizeSignature = function(rs) { 5 | var w = sjcl.bitArray, 6 | R = this._curve.r, 7 | l = R.bitLength(); 8 | 9 | var r = sjcl.bn.fromBits(w.bitSlice(rs, 0, l)), 10 | s = sjcl.bn.fromBits(w.bitSlice(rs, l, 2 * l)); 11 | 12 | // For a canonical signature we want the lower of two possible values for s 13 | // 0 < s <= n/2 14 | if (!R.copy().halveM().greaterEquals(s)) { 15 | s = R.sub(s); 16 | } 17 | 18 | return w.concat(r.toBits(l), s.toBits(l)); 19 | }; 20 | 21 | -------------------------------------------------------------------------------- /test/e2e/protractor.conf-example.js: -------------------------------------------------------------------------------- 1 | exports.config = { 2 | seleniumAddress: 'http://localhost:4444/wd/hub', 3 | specs: ['spec.js'], 4 | capabilities: { 5 | browserName: 'firefox' 6 | }, 7 | baseUrl: 'http://local.rippletrade.com/index_debug.html', 8 | 9 | // Credentials of a funded test account 10 | user: { 11 | username: '', 12 | password: '', 13 | // Blobvault url 14 | url: '', 15 | // Login once, and copy these from your local storage "ripple_auth" 16 | keys: { 17 | "id":"", 18 | "crypt":"" 19 | } 20 | }, 21 | 22 | // Ripple address to send xrp to 23 | counterparty: '' 24 | }; -------------------------------------------------------------------------------- /src/js/directives/errors.js: -------------------------------------------------------------------------------- 1 | /** 2 | * ERRORS 3 | * 4 | * Directives related to errors and error messages. 5 | */ 6 | 7 | var module = angular.module('errors', []); 8 | 9 | /** 10 | * Trust line graph. (Similar to small box plot.) 11 | */ 12 | module.directive('rpTransactionStatus', function() { 13 | return { 14 | restrict: 'E', 15 | template: require('../../templates/directives/transactionerror.jade'), 16 | scope: { 17 | engine_result: '@rpEngineResult', 18 | engine_result_message: '@rpEngineResultMessage', 19 | accepted: '@rpAccepted' 20 | }, 21 | link: function(scope, elm, attrs) { 22 | } 23 | }; 24 | }); 25 | -------------------------------------------------------------------------------- /src/js/directives/datalinks.js: -------------------------------------------------------------------------------- 1 | /** 2 | * DATALINKS 3 | * 4 | * Data-centric links for things like transactions, accounts etc. 5 | */ 6 | 7 | var module = angular.module('datalinks', []); 8 | 9 | module.directive('rpLinkTx', ['$location', function ($location) { 10 | return { 11 | restrict: 'A', 12 | link: function ($scope, element, attr) { 13 | var url; 14 | $scope.$watch(attr.rpLinkTx, function (hash) { 15 | url = "/tx?id="+hash; 16 | }); 17 | element.click(function () { 18 | $scope.$apply(function () { 19 | if (url) $location.url(url); 20 | }); 21 | }); 22 | } 23 | }; 24 | }]); 25 | -------------------------------------------------------------------------------- /src/js/client/tab.js: -------------------------------------------------------------------------------- 1 | var util = require('util'), 2 | webutil = require('../util/web'), 3 | log = require('../util/log'); 4 | 5 | var Tab = function (config) 6 | { 7 | }; 8 | 9 | Tab.prototype.pageMode = 'default'; 10 | 11 | Tab.prototype.mainMenu = 'none'; 12 | 13 | /** 14 | * AngularJS dependencies. 15 | * 16 | * List any controllers the tab uses here. 17 | */ 18 | Tab.prototype.angularDeps = [ 19 | // Directives 20 | 'charts', 21 | 'effects', 22 | 'events', 23 | 'fields', 24 | 'formatters', 25 | 'directives', 26 | 'validators', 27 | 'datalinks', 28 | // Filters 29 | 'filters' 30 | ]; 31 | 32 | /** 33 | * Other routes this tab should handle. 34 | */ 35 | Tab.prototype.aliases = []; 36 | 37 | exports.Tab = Tab; 38 | -------------------------------------------------------------------------------- /src/js/tabs/eula.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | var Tab = require('../client/tab').Tab; 3 | 4 | var EulaTab = function () 5 | { 6 | Tab.call(this); 7 | }; 8 | 9 | util.inherits(EulaTab, Tab); 10 | 11 | EulaTab.prototype.tabName = 'eula'; 12 | EulaTab.prototype.pageMode = 'single'; 13 | EulaTab.prototype.parent = 'main'; 14 | 15 | EulaTab.prototype.generateHtml = function () 16 | { 17 | return require('../../templates/tabs/eula.jade')(); 18 | }; 19 | 20 | EulaTab.prototype.angular = function (module) { 21 | module.controller('EulaCtrl', ['$scope', '$element', 22 | function ($scope, $element) 23 | { 24 | 25 | angular.element('nav').hide(); 26 | 27 | }]); 28 | 29 | }; 30 | 31 | 32 | 33 | module.exports = EulaTab; 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012,2013,2014,2015 Ripple Labs Inc. 2 | 3 | Permission to use, copy, modify, and distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /icons/svg/metal-bars.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/less/ripple/main.less: -------------------------------------------------------------------------------- 1 | /** 2 | * Ripple Desktop Client 3 | * Desktop Stylesheet 4 | * 5 | * Copyright 2015 Ripple Labs, Inc. 6 | */ 7 | 8 | // Bootstrap 9 | @import "../../../node_modules/bootstrap/less/bootstrap.less"; 10 | 11 | // Plugins 12 | @import "../elements/elements.less"; 13 | @import "sortable.less"; 14 | 15 | // Ripple Styles 16 | @import "variables.less"; 17 | @import "utils.less"; 18 | @import "layout.less"; 19 | @import "buttons.less"; 20 | @import "forms.less"; 21 | @import "combobox.less"; 22 | @import "content.less"; 23 | @import "status.less"; 24 | @import "pagemodes.less"; 25 | @import "tabs.less"; 26 | @import "browser-chrome.less"; 27 | 28 | // Icons 29 | @import "../../../icons/font/icons.less"; 30 | 31 | // Tabs 32 | @import "tabs/submit.less"; 33 | 34 | #logo { 35 | display: none; 36 | } -------------------------------------------------------------------------------- /src/js/entry/vendor.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | window.jQuery = window.$ = require('jquery'); 4 | window.moment = require('moment'); 5 | window.store = require('store'); 6 | window.Spinner = require('spin'); 7 | //window.RippleAPI = require('ripple-lib').RippleAPI; 8 | window.ripple = require('ripple-lib')._DEPRECATED; 9 | window.RippleAddressCodec = require('ripple-address-codec'); 10 | window.RippleBinaryCodec = require('ripple-binary-codec'); 11 | window._ = require('lodash'); 12 | window.sjcl = require('sjcl'); 13 | require('../../../deps/sjcl-custom'); 14 | 15 | require('angular'); 16 | require('angular-route'); 17 | require('angular-messages'); 18 | require('angular-ui-bootstrap'); 19 | require('ng-sortable/dist/ng-sortable'); 20 | require('bootstrap/js/modal'); 21 | require('bootstrap/js/dropdown'); 22 | require('bootstrap/js/tooltip'); 23 | require('bootstrap/js/popover'); 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Desktop Client 2 | 3 | ## Install Dependencies 4 | 5 | - Fork and clone the ripple-client-desktop repository 6 | - Run `npm install` 7 | - Download [nw.js](https://github.com/nwjs/npm-installer) 8 | 9 | ## Build 10 | 11 | - In the ripple-client-desktop repository, make a copy of the `config_example.js` file and name it `config.js` 12 | - Run `gulp` in your command line for development 13 | 14 | - Run `gulp packages` in your command line for the production ready client 15 | - Your desktop client is in the `packages/RippleAdminConsole` directory 16 | 17 | ### Note 18 | - There are breaking changes in the c++ API when using node version 4. You should use node version 0.12. 19 | - The current package.json is intended to pull directly from the develop branch of ripple-lib. You may need to clone the ripple-lib respository into node_modules/ripple-lib and then run npm install in the cloned ripple-lib repository. 20 | -------------------------------------------------------------------------------- /src/js/directives/effects.js: -------------------------------------------------------------------------------- 1 | /** 2 | * EFFECTS 3 | * 4 | * Angular-powered animation and visual effects directives go into this file. 5 | */ 6 | 7 | var module = angular.module('effects', []); 8 | 9 | /** 10 | * Animate element creation 11 | */ 12 | module.directive('rpAnimate', function() { 13 | return { 14 | restrict: 'A', 15 | link: function(scope, elm, attrs) { 16 | if (attrs.rpAnimate !== "rp-animate" && !scope.$eval(attrs.rpAnimate)) return; 17 | elm = jQuery(elm); 18 | elm.hide(); 19 | elm.fadeIn(600); 20 | elm.css('background-color', '#E2F5E4'); 21 | elm.addClass('rp-animate-during rp-animate'); 22 | elm.animate({ 23 | 'background-color': '#fff' 24 | }, { 25 | duration: 600, 26 | complete: function () { 27 | elm.removeClass('rp-animate-during').addClass('rp-animate-after'); 28 | } 29 | }); 30 | } 31 | }; 32 | }); 33 | -------------------------------------------------------------------------------- /src/js/tabs/tou.js: -------------------------------------------------------------------------------- 1 | var util = require('util'), 2 | Tab = require('../client/tab').Tab; 3 | 4 | var TouTab = function() { 5 | Tab.call(this); 6 | }; 7 | 8 | util.inherits(TouTab, Tab); 9 | 10 | TouTab.prototype.tabName = 'tou'; 11 | TouTab.prototype.pageMode = 'single'; 12 | TouTab.prototype.parent = 'main'; 13 | 14 | TouTab.prototype.generateHtml = function() { 15 | return require('../../templates/tabs/tou.jade')(); 16 | }; 17 | 18 | TouTab.prototype.angular = function(module) { 19 | 20 | 21 | module.controller('TouCtrl', ['$scope', 'rpId', 22 | function($scope, $id) { 23 | 24 | $scope.acceptTou = function () { 25 | store.set('accepted_tou', true); 26 | $id.goId(); 27 | }; 28 | 29 | $scope.denyTou = function () { 30 | store.set('accepted_tou', false); 31 | $scope.showWarningMessage = true; 32 | } 33 | } 34 | ]); 35 | }; 36 | 37 | module.exports = TouTab; 38 | -------------------------------------------------------------------------------- /src/js/tabs/balance.js: -------------------------------------------------------------------------------- 1 | var util = require('util'), 2 | Tab = require('../client/tab').Tab, 3 | Amount = ripple.Amount, 4 | rewriter = require('../util/jsonrewriter'); 5 | 6 | var BalanceTab = function() { 7 | Tab.call(this); 8 | }; 9 | 10 | util.inherits(BalanceTab, Tab); 11 | 12 | BalanceTab.prototype.tabName = 'balance'; 13 | BalanceTab.prototype.mainMenu = 'wallet'; 14 | 15 | BalanceTab.prototype.angularDeps = Tab.prototype.angularDeps.concat(['qr']); 16 | 17 | BalanceTab.prototype.generateHtml = function() { 18 | return require('../../templates/tabs/balance.jade')(); 19 | }; 20 | 21 | BalanceTab.prototype.angular = function(module) { 22 | 23 | 24 | module.controller('BalanceCtrl', ['$rootScope', 'rpId', 'rpNW', 25 | function($scope, $id, rpNW) { 26 | if (!$id.loginStatus) { 27 | $id.goId(); 28 | } else { 29 | rpNW.initTray(); 30 | } 31 | } 32 | ]); 33 | }; 34 | 35 | module.exports = BalanceTab; 36 | -------------------------------------------------------------------------------- /src/templates/directives/signedTransaction.jade: -------------------------------------------------------------------------------- 1 | .signedTransaction 2 | .row 3 | .col-sm-12.notification-wrapper 4 | .alert.alert-success(ng-show="saved", l10n) Transaction saved: {{fileName}}. 5 | .alert.alert-danger(ng-show="error", l10n) Error saving transaction. Please copy and save manually. 6 | .alert.alert-warning(ng-show="!userBlob.data.defaultDirectory") Please select default directory to save transaction to on the 7 | a(href="#/coldwalletsettings", l10n) Cold Wallet Settings page 8 | | . 9 | h2 Signed transaction 10 | .row 11 | .col-md-8 12 | //- TODO all js goes into directive 13 | textarea.txBlob(readonly="readonly") {{signedTransaction}} 14 | .col-md-4 15 | .row 16 | .col-md-6 17 | button.btn.btn-small.btn-primary.btn-block(ng-click="copy()") Copy 18 | .col-md-6(ng-show="!userBlob.data.defaultDirectory") 19 | button.btn.btn-small.btn-primary.btn-block(ng-click="save()") Save to disk 20 | -------------------------------------------------------------------------------- /src/less/ripple/utils.less: -------------------------------------------------------------------------------- 1 | /** 2 | * Useful helper to create virtual elements like a:after and a:before 3 | */ 4 | .virtualel { 5 | content: ""; 6 | display: block; 7 | } 8 | 9 | 10 | .transition-multi(@elements) { 11 | -webkit-transition: @elements; 12 | -moz-transition: @elements; 13 | transition: @elements; 14 | } 15 | 16 | @-webkit-keyframes fadebg { 17 | 0% { } 18 | 100% { background-color: transparent } 19 | } 20 | @-ms-keyframes fadebg { 21 | 0% { } 22 | 100% { background-color: transparent } 23 | } 24 | @-moz-keyframes fadebg { 25 | 0% { } 26 | 100% { background-color: transparent } 27 | } 28 | @keyframes fadebg { 29 | 0% { } 30 | 100% { background-color: transparent } 31 | } 32 | 33 | .animation(@tween:fadebg, @duration:1s, @fillmode:forwards) { 34 | -webkit-animation: @tween @duration 1; 35 | -moz-animation: @tween @duration 1; 36 | -ms-animation: @tween @duration 1; 37 | animation: @tween @duration 1; 38 | -webkit-animation-fill-mode: @fillmode; 39 | } -------------------------------------------------------------------------------- /src/less/ripple/sortable.less: -------------------------------------------------------------------------------- 1 | /* ************************************** */ 2 | /* Mandatory CSS required for ng-sortable */ 3 | /* ************************************** */ 4 | 5 | .as-sortable-item { 6 | -ms-touch-action: none; 7 | touch-action: none; 8 | } 9 | 10 | .as-sortable-item-handle { 11 | cursor: move; 12 | } 13 | 14 | .as-sortable-placeholder { 15 | } 16 | 17 | .as-sortable-drag { 18 | position: absolute; 19 | pointer-events: none; 20 | z-index: 9999; 21 | } 22 | 23 | .as-sortable-hidden { 24 | display: none !important; 25 | } 26 | 27 | /* ******************************************* */ 28 | /* Optional CSS, default style for ng-sortable */ 29 | /* ******************************************* */ 30 | 31 | .as-sortable-drag { 32 | opacity: .8; 33 | border: solid 1px darkgrey; 34 | background-color: @lightgray; 35 | padding: 10px; 36 | margin: 0; 37 | margin-bottom: 10px; 38 | overflow: hidden; 39 | } 40 | 41 | .as-sortable-drag .delete { 42 | display: none; 43 | } 44 | -------------------------------------------------------------------------------- /deps/sjcl-custom/sjcl-ecdsa-der.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var sjcl = require('sjcl'); 3 | 4 | sjcl.ecc.ecdsa.secretKey.prototype.signDER = function(hash, paranoia) { 5 | return this.encodeDER(this.sign(hash, paranoia)); 6 | }; 7 | 8 | sjcl.ecc.ecdsa.secretKey.prototype.encodeDER = function(rs) { 9 | var w = sjcl.bitArray, 10 | R = this._curve.r, 11 | l = R.bitLength(); 12 | 13 | var rb = sjcl.codec.bytes.fromBits(w.bitSlice(rs, 0, l)), 14 | sb = sjcl.codec.bytes.fromBits(w.bitSlice(rs, l, 2 * l)); 15 | 16 | // Drop empty leading bytes 17 | while (!rb[0] && rb.length) { 18 | rb.shift(); 19 | } 20 | while (!sb[0] && sb.length) { 21 | sb.shift(); 22 | } 23 | 24 | // If high bit is set, prepend an extra zero byte (DER signed integer) 25 | if (rb[0] & 0x80) { 26 | rb.unshift(0); 27 | } 28 | if (sb[0] & 0x80) { 29 | sb.unshift(0); 30 | } 31 | 32 | var buffer = [].concat( 33 | 0x30, 34 | 4 + rb.length + sb.length, 35 | 0x02, 36 | rb.length, 37 | rb, 38 | 0x02, 39 | sb.length, 40 | sb 41 | ); 42 | 43 | return sjcl.codec.bytes.toBits(buffer); 44 | }; 45 | 46 | -------------------------------------------------------------------------------- /src/templates/tabs/settingstrade.jade: -------------------------------------------------------------------------------- 1 | section.col-xs-12.content(ng-controller="SettingsTradeCtrl") 2 | div(ng-show='debug') This page is not available in debug mode 3 | 4 | div(ng-show="!loadingAccount && !account.Balance && loadState.account && connected && !debug") 5 | include banner/_unfunded 6 | .row(ng-show='!debug') 7 | .col-sm-3 8 | include settings/_navbar 9 | .col-sm-9.list 10 | section 11 | h4(l10n) Trade settings 12 | .section 13 | .descriptor(l10n) Trade currency pairs 14 | div(ng-show="!pairs || (pairs && pairs.length == 0)", l10n) 15 | | No trade pairs in this account. To add a pair, go to the 16 | a(href="#/trade", l10n-inc) Trade > Advanced 17 | | section. 18 | div(ng-if="pairs") 19 | div(as-sortable="dragControlListeners", ng-model="pairs") 20 | div.pair(ng-repeat="entry in pairs track by entry.name", as-sortable-item) 21 | .col-xs-12(as-sortable-item-handle) 22 | i.grip 23 | span.description(ng-bind="entry.name") 24 | a(href="", ng-click="deletePair($index)") 25 | span.delete remove 26 | -------------------------------------------------------------------------------- /src/js/tabs/coldwalletsettings.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('util'); 4 | var Tab = require('../client/tab').Tab; 5 | 6 | function ColdwalletSettingsTab() { 7 | Tab.call(this); 8 | } 9 | 10 | util.inherits(ColdwalletSettingsTab, Tab); 11 | 12 | ColdwalletSettingsTab.prototype.tabName = 'coldwalletsettings'; 13 | ColdwalletSettingsTab.prototype.mainMenu = 'coldwalletsettings'; 14 | 15 | ColdwalletSettingsTab.prototype.generateHtml = function() { 16 | return require('../../templates/tabs/coldwalletsettings.jade')(); 17 | }; 18 | 19 | 20 | ColdwalletSettingsTab.prototype.angular = function(module) { 21 | module.controller('ColdwalletSettingsCtrl', ['$scope', '$rootScope', 'rpId', 'rpFileDialog', 22 | function ($scope, $rootScope, id, fileDialog) { 23 | 24 | if (!id.loginStatus) { 25 | id.goId(); 26 | } 27 | 28 | // Update the blob with the new sequence 29 | $scope.saveSequence = function() { 30 | $rootScope.userBlob.set('/sequence', $scope.sequence); 31 | $rootScope.userBlob.set('/fee', $scope.fee); 32 | $rootScope.sequence = $rootScope.userBlob.data.sequence; 33 | $scope.saved = true; 34 | }; 35 | }]); 36 | }; 37 | 38 | module.exports = ColdwalletSettingsTab; 39 | -------------------------------------------------------------------------------- /src/js/data/pairs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Ripple trading default currency pairs. 3 | * 4 | * This list is a bit arbitrary, but it's basically the Majors [1] from forex 5 | * trading with some XRP pairs added. 6 | * 7 | * [1] http://en.wikipedia.org/wiki/Currency_pair#The_Majors 8 | */ 9 | 10 | var DEFAULT_PAIRS = [ 11 | {name: 'XRP/USD.SnapSwap', last_used: 10} 12 | // {name: 'XAU (-0.5%pa)/XRP', last_used: 2}, 13 | // {name: 'XAU (-0.5%pa)/USD', last_used: 2}, 14 | // {name: 'BTC/XRP', last_used: 1}, 15 | // {name: 'XRP/USD', last_used: 1}, 16 | // {name: 'XRP/EUR', last_used: 1}, 17 | // {name: 'XRP/JPY', last_used: 0}, 18 | // {name: 'XRP/GBP', last_used: 0}, 19 | // {name: 'XRP/AUD', last_used: 0}, 20 | // {name: 'XRP/CHF', last_used: 0}, 21 | // {name: 'XRP/CAD', last_used: 0}, 22 | // {name: 'XRP/CNY', last_used: 0}, 23 | // {name: 'XRP/MXN', last_used: 0}, 24 | // {name: 'BTC/USD', last_used: 0}, 25 | // {name: 'BTC/EUR', last_used: 0}, 26 | // {name: 'EUR/USD', last_used: 0}, 27 | // {name: 'USD/JPY', last_used: 0}, 28 | // {name: 'GBP/USD', last_used: 0}, 29 | // {name: 'AUD/USD', last_used: 0}, 30 | // {name: 'USD/MXN', last_used: 0}, 31 | // {name: 'USD/CHF', last_used: 0} 32 | ]; 33 | 34 | module.exports = DEFAULT_PAIRS; -------------------------------------------------------------------------------- /src/less/ripple/tabs/submit.less: -------------------------------------------------------------------------------- 1 | @import "../variables.less"; 2 | #t-submit { 3 | .txFileInput { 4 | cursor: pointer; 5 | text-decoration: none; 6 | display: block; 7 | color: @darkgray; 8 | line-height: 80px; 9 | padding: 0 30px; 10 | background: @white; 11 | border-radius: 0; 12 | border: 1px dashed @midgray; 13 | -webkit-touch-callout: none; 14 | -webkit-user-select: none; 15 | -khtml-user-select: none; 16 | -moz-user-select: none; 17 | -ms-user-select: none; 18 | user-select: none; 19 | 20 | span { 21 | padding: 0 0 0 20px; 22 | } 23 | } 24 | .txFileRow { 25 | margin-bottom: 10px; 26 | padding: 15px 15px 0px 15px; 27 | background: @lightgray; 28 | border: 1px solid @midgray; 29 | .fa.fa-times { 30 | padding-left: 5px; 31 | color: @darkgray; 32 | } 33 | .failed { 34 | color: @midred; 35 | font-family: 'OpenSansRegular'; 36 | } 37 | .success { 38 | color: @midgreen; 39 | font-family: 'OpenSansRegular'; 40 | } 41 | .pending { 42 | color: @midblue; 43 | font-family: 'OpenSansRegular'; 44 | } 45 | } 46 | } 47 | 48 | .panel-default > .panel-heading { 49 | background-color: @lightred; 50 | } -------------------------------------------------------------------------------- /l10n/TRANSLATORS.md: -------------------------------------------------------------------------------- 1 | # Introduction to ripple-l10n 2 | 3 | Ripple uses a custom internationalization system based on the 4 | [GNU gettext](http://en.wikipedia.org/wiki/Gettext) translation solution. 5 | 6 | ## Basic information 7 | 8 | When translating strings you may encounter placeholders for external elements: 9 | 10 | ``` 11 | You will receive {{1}}. 12 | ``` 13 | 14 | These stand for data shown to the user, the example above might show up in the 15 | client as: `You will receive 100 USD.` 16 | 17 | Make sure you keep the tags, i.e. the `{{1}}` intact. These elements must never 18 | be removed or added. However, in most cases it is safe to reorder the elements. 19 | If it is not safe to reorder, the string will be marked with a developer comment 20 | warning you that the order must be preserved. 21 | 22 | ## Subelements 23 | 24 | Sometimes subelements contain text that is part of the overall sentence. This 25 | will look like this: 26 | 27 | ``` 28 | You {{1:must}} agree to the terms before continuing. 29 | ``` 30 | 31 | This could be because "must" has special formatting or because it may be 32 | replaced by other content in some circumstances. When you come across this, 33 | please translate the text inside the brackets along with the rest of the 34 | sentence, but be sure to keep the `{{1:` and `}}` intact. 35 | -------------------------------------------------------------------------------- /l10n/languages.json: -------------------------------------------------------------------------------- 1 | { 2 | "active": [ 3 | {"code": "ca", "name": "catalan", "nativeName": "Català"}, 4 | {"code": "de", "name": "german", "nativeName": "Deutsch"}, 5 | {"code": "en", "name": "english", "nativeName": "English"}, 6 | {"code": "es", "name": "spanish", "nativeName": "Español"}, 7 | {"code": "fr_FR", "name": "french", "nativeName": "Français"}, 8 | {"code": "he_IL", "name": "hebrew_israel", "nativeName": "עברית"}, 9 | {"code": "it", "name": "italian", "nativeName": "Italiano"}, 10 | {"code": "ja", "name": "japanese", "nativeName": "日本の"}, 11 | {"code": "ko", "name": "korean", "nativeName": "한국말"}, 12 | {"code": "nb_NO", "name": "norwegian_bokmal", "nativeName": "Norsk bokmål"}, 13 | {"code": "nl_NL", "name": "dutch_netherlands", "nativeName": "Nederlands"}, 14 | {"code": "pl", "name": "polish", "nativeName": "Polski"}, 15 | {"code": "pt_BR", "name": "portuguese_brazil", "nativeName": "Português brasileiro"}, 16 | {"code": "ro", "name": "romanian", "nativeName": "Român"}, 17 | {"code": "ru", "name": "russian", "nativeName": "Русский"}, 18 | {"code": "sk", "name": "slovak", "nativeName": "Slovenská"}, 19 | {"code": "tr", "name": "turkish", "nativeName": "Türkçe"}, 20 | {"code": "zh_CN", "name": "chinese_china", "nativeName": "中文"} 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /icons/svg/ripple-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /test/unit/directivesSpec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('validators', function () { 4 | var compile,scope,document; 5 | 6 | // load the validators code 7 | beforeEach(module('validators')); 8 | 9 | describe('rpAmount', function() { 10 | var element = angular.element('
'); 11 | 12 | beforeEach(inject(function($compile,$rootScope,$document) { 13 | compile = $compile; 14 | scope = $rootScope; 15 | document = $document; 16 | 17 | angular.element(document[0].body).append(element); 18 | compile(element)(scope); 19 | })); 20 | 21 | var digestCheck = function(){ 22 | scope.$digest(); 23 | 24 | assert.isTrue(scope.testForm.$valid); 25 | }; 26 | 27 | it('should allow very small numbers', function(){ 28 | scope.test = '0.000000000001234123'; 29 | digestCheck(); 30 | }); 31 | 32 | it('should allow very big numbers', function(){ 33 | scope.test = '12312312312312311123'; 34 | digestCheck(); 35 | }); 36 | 37 | it('should allow a dot without the leading zero', function(){ 38 | scope.test = '.1'; 39 | digestCheck(); 40 | }); 41 | 42 | it('should allow a dot for very small numbers without a leading zero', function(){ 43 | scope.test = '.000000000001234123'; 44 | digestCheck(); 45 | }); 46 | }); 47 | }); -------------------------------------------------------------------------------- /src/js/services/transactions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * TRANSACTIONS 3 | * 4 | * The transactions service is used to listen to all Ripple network 5 | * transactions. 6 | * 7 | * This obviously won't scale, but it'll do long enough for us (or somebody 8 | * else) to come up with something better. 9 | */ 10 | 11 | var module = angular.module('transactions', ['network']); 12 | 13 | module.factory('rpTransactions', ['$rootScope', 'rpNetwork', 14 | function($scope, net) { 15 | var listeners = [], 16 | subscribed = false; 17 | 18 | function subscribe() { 19 | if (subscribed) return; 20 | net.remote.requestSubscribe("transactions").request(); 21 | subscribed = true; 22 | } 23 | 24 | function handleTransaction(msg) { 25 | $scope.$apply(function () { 26 | listeners.forEach(function (fn) { 27 | fn(msg); 28 | }); 29 | }); 30 | } 31 | 32 | net.remote.on('net_transaction', handleTransaction); 33 | 34 | return { 35 | addListener: function (fn) { 36 | listeners.push(fn); 37 | subscribe(); 38 | }, 39 | removeListener: function (fn) { 40 | var position = -1; 41 | for (var i = 0, l = listeners.length; i < l; i++) { 42 | if (listeners[i] === fn) { 43 | position = i; 44 | } 45 | } 46 | if (position < 0) return; 47 | listeners.splice(position, 1); 48 | } 49 | }; 50 | }]); 51 | -------------------------------------------------------------------------------- /src/js/directives/signedTransaction.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | 5 | var module = angular.module('app'); 6 | 7 | module.directive('signedTransaction', ['rpFileDialog', function(fileDialog) { 8 | return { 9 | restrict: 'E', 10 | templateUrl: 'templates/' + lang + '/directives/signedTransaction.html', 11 | link: function($scope, element, attrs) { 12 | $scope.copy = function() { 13 | $(element).find('.txBlob').select(); 14 | document.execCommand('copy'); 15 | }; 16 | 17 | // TODO Save in the format specified by DAVE 18 | $scope.save = function() { 19 | // Save with default name 20 | var txJSON = JSON.parse(attrs.txjson); 21 | var sequenceNumber = (Number(txJSON.Sequence)); 22 | var sequenceLength = sequenceNumber.toString().length; 23 | var txnName = $scope.userBlob.data.account_id + '-' + new Array(10 - sequenceLength + 1).join('0') + sequenceNumber + '.txt'; 24 | var txData = JSON.stringify({ 25 | tx_json: txJSON, 26 | hash: attrs.hash, 27 | tx_blob: attrs.data 28 | }); 29 | 30 | // No default name specified -- Save as 31 | fileDialog.saveAs(function(filename) { 32 | $scope.txFile = filename; 33 | 34 | // Write to file 35 | fs.writeFile(filename, txData); 36 | }, txnName); 37 | }; 38 | } 39 | }; 40 | }]); 41 | -------------------------------------------------------------------------------- /src/less/ripple/tabs/settingsTrade.less: -------------------------------------------------------------------------------- 1 | #t-settingstrade{ 2 | .settingPage a { 3 | display: block; 4 | padding: 10px; 5 | border-radius: 2px; 6 | margin: 0 0 3px; 7 | background: @lightgray; 8 | 9 | &:hover, &.active { 10 | color: @lightgray; 11 | text-decoration: none; 12 | background: @midblue; 13 | } 14 | } 15 | 16 | .descriptor { 17 | color: @darkgray; 18 | margin: 0 0 10px; 19 | border-top: 1px solid @midgray; 20 | padding-top: 20px; 21 | } 22 | 23 | .pair { 24 | background-color: @lightgray; 25 | padding: 10px; 26 | margin: 0; 27 | margin-bottom: 10px; 28 | overflow: hidden; 29 | position: relative; 30 | } 31 | 32 | .grip { 33 | display: inline-block; 34 | position: relative;; 35 | left: -10px; 36 | width: 3px; 37 | height: 9px; 38 | background-repeat: no-repeat; 39 | background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAAJAgMAAACK3X5KAAAACVBMVEUAAAAiIiIiIiIxJsNmAAAAAnRSTlMAGRL4ZfgAAAANSURBVAjXY+hgcEHFACGKA7njBRNQAAAAAElFTkSuQmCC); 40 | } 41 | 42 | .delete { 43 | float: right; 44 | color: @midred; 45 | } 46 | 47 | .as-sortable-item, .as-sortable-placeholder { 48 | min-height: 20px; 49 | margin: 1px 2px 5px 1px; 50 | } 51 | 52 | .as-sortable-placeholder { 53 | border: 1px dashed @darkgray; 54 | background-color: @lightgray; 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /src/templates/messages/sendconvert/_localerror.jade: -------------------------------------------------------------------------------- 1 | // Error messages for the send and convert tabs 2 | 3 | group(ng-switch="error_type") 4 | group.result-error(ng-switch-when='noDest') 5 | h2.tx-status(l10n) Transaction failed 6 | p(l10n) Error code: The account you're trying to send to does not exist [noDest]. 7 | group.result-error(ng-switch-when='noPath') 8 | h2.tx-status(l10n) Transaction failed 9 | p(l10n) Error code: Ripple was unable to find a path between you and the destination account [noPath]. 10 | group.result-error(ng-switch-when='maxFeeExceeded') 11 | h2.tx-status(l10n) Transaction failed 12 | p(l10n) Error code: This transaction failed because it exceeded the maximum network fee. [maxfeeExceeded]. 13 | group.result-error(ng-switch-when='invalidTransaction') 14 | h2.tx-status(l10n) Transaction failed 15 | p(l10n) Error code: The transaction was rejected by the Ripple network [invalidTransaction]. 16 | group.result-error(ng-switch-when='unlockFailed') 17 | h2.tx-status(l10n) Account unlock failed 18 | p(l10n) Your account could not be unlocked. Please try again later. 19 | group(ng-switch-default) 20 | p.literal(l10n) Sorry, an error occurred while submitting your transaction. 21 | | Make sure you are connected to the Internet and try again later. 22 | p.literal(l10n) Before trying again, please ensure that the transaction has 23 | | not been executed already. -------------------------------------------------------------------------------- /icons/font/icons.less: -------------------------------------------------------------------------------- 1 | // Generated by grunt-webfont 2 | // Based on https://github.com/endtwist/fontcustom/blob/master/lib/fontcustom/templates/fontcustom.css 3 | 4 | @font-face { 5 | font-family:"icons"; 6 | src:url("../icons/font/icons-51c446195575425cef2ecd7189d41eec.eot"); 7 | src:url("../icons/font/icons-51c446195575425cef2ecd7189d41eec.eot?#iefix") format("embedded-opentype"), 8 | url("../icons/font/icons-51c446195575425cef2ecd7189d41eec.woff") format("woff"), 9 | url("../icons/font/icons-51c446195575425cef2ecd7189d41eec.ttf") format("truetype"), 10 | url("../icons/font/icons-51c446195575425cef2ecd7189d41eec.svg?#icons") format("svg"); 11 | font-weight:normal; 12 | font-style:normal; 13 | } 14 | 15 | 16 | // Bootstrap Overrides 17 | [class^="icon-"]:before, 18 | [class*=" icon-"]:before { 19 | font-family:"icons"; 20 | display:inline-block; 21 | vertical-align:middle; 22 | line-height:1; 23 | font-weight:normal; 24 | font-style:normal; 25 | speak:none; 26 | text-decoration:inherit; 27 | text-transform:none; 28 | text-rendering:optimizeLegibility; 29 | -webkit-font-smoothing:antialiased; 30 | -moz-osx-font-smoothing:grayscale; 31 | } 32 | 33 | // Mixins 34 | 35 | .icon-metal-bars, 36 | .icon-ripple-logo { 37 | &:before { 38 | font-family:"icons"; 39 | display:inline-block; 40 | font-weight:normal; 41 | font-style:normal; 42 | text-decoration:inherit; 43 | } 44 | } 45 | 46 | 47 | // Icons 48 | 49 | .icon-metal-bars { 50 | &:before { 51 | content:"\e001"; 52 | } 53 | 54 | } 55 | 56 | 57 | .icon-ripple-logo { 58 | &:before { 59 | content:"\e002"; 60 | } 61 | 62 | } 63 | 64 | -------------------------------------------------------------------------------- /src/less/ripple/buttons.less: -------------------------------------------------------------------------------- 1 | .btn { 2 | // font-family: 'OpenSansLight'; 3 | font-size: 14px; 4 | font-family: OpenSansRegular; 5 | border: none; 6 | line-height: 1em; 7 | padding-top: 1em; 8 | padding-bottom: 1em; 9 | border-radius: 5px; 10 | @media (max-width: @screen-xs-max) { 11 | font-size: 12px; 12 | } 13 | &:focus { 14 | outline: none; 15 | } 16 | } 17 | .btn-default { 18 | padding-left: 10px; 19 | padding-right: 10px; 20 | } 21 | .btn-cancel { 22 | // padding-left: 10px; 23 | // padding-right: 10px; 24 | background-color: transparent; 25 | color: @midblue; 26 | cursor: pointer; 27 | text-decoration: none !important; 28 | &:hover { 29 | color: darken(@midblue, 15%); 30 | } 31 | 32 | &:hover, &:focus, &:active { 33 | background-color: transparent; 34 | outline: 0 !important; 35 | box-shadow: none; 36 | } 37 | &:hover { 38 | color: #333333; 39 | background-color: #d9d9d9; 40 | border-color: #adadad; 41 | } 42 | } 43 | .btn-link { 44 | padding-left: 10px; 45 | padding-right: 10px; 46 | } 47 | .edit { 48 | color: #428bca; 49 | &:hover { 50 | background-color: #d9d9d9; 51 | border-color: #adadad; 52 | text-decoration: none; 53 | } 54 | } 55 | 56 | .btn.btn-lg.btn-submit { 57 | font-size: 16px; 58 | line-height: 24px; 59 | } 60 | 61 | .btn:disabled { 62 | background: @midgray; 63 | border-color: darken(@midgray, 10); 64 | &:hover { 65 | background-color: #ccccca; 66 | border-color: #ccccca; 67 | } 68 | } 69 | 70 | .btn-inline { 71 | margin: 0 5px; 72 | padding-left: 10px; 73 | padding-right: 10px; 74 | } 75 | .btn-add-trust { 76 | float: right; 77 | } -------------------------------------------------------------------------------- /src/js/services/filedialog.js: -------------------------------------------------------------------------------- 1 | /* Taken from https://github.com/DWand/nw-fileDialog */ 2 | 3 | var module = angular.module('filedialog', []); 4 | 5 | module.factory('rpFileDialog', ['$rootScope', function($scope){ 6 | var callDialog = function(dialog, callback) { 7 | dialog.addEventListener('change', function() { 8 | var result = dialog.value; 9 | callback(result); 10 | }, false); 11 | dialog.click(); 12 | }; 13 | 14 | var dialogs = {}; 15 | 16 | dialogs.saveAs = function(callback, defaultFilename, acceptTypes) { 17 | var dialog = document.createElement('input'); 18 | dialog.type = 'file'; 19 | dialog.nwsaveas = defaultFilename || ''; 20 | if (angular.isArray(acceptTypes)) { 21 | dialog.accept = acceptTypes.join(','); 22 | } else if (angular.isString(acceptTypes)) { 23 | dialog.accept = acceptTypes; 24 | } 25 | callDialog(dialog, callback); 26 | }; 27 | 28 | dialogs.openFile = function(callback, multiple, acceptTypes) { 29 | var dialog = document.createElement('input'); 30 | dialog.type = 'file'; 31 | if (multiple === true) { 32 | dialog.multiple = 'multiple'; 33 | } 34 | if (angular.isArray(acceptTypes)) { 35 | dialog.accept = acceptTypes.join(','); 36 | } else if (angular.isString(acceptTypes)) { 37 | dialog.accept = acceptTypes; 38 | } 39 | callDialog(dialog, callback); 40 | }; 41 | 42 | dialogs.openDir = function(callback) { 43 | var dialog = document.createElement('input'); 44 | dialog.type = 'file'; 45 | dialog.nwdirectory = 'nwdirectory'; 46 | callDialog(dialog, callback); 47 | }; 48 | 49 | return dialogs; 50 | }]); -------------------------------------------------------------------------------- /src/js/util/types.js: -------------------------------------------------------------------------------- 1 | var Base58Utils = require('./base58'); 2 | 3 | var RippleAddress = (function () { 4 | function append_int(a, i) { 5 | return [].concat(a, i >> 24, (i >> 16) & 0xff, (i >> 8) & 0xff, i & 0xff) 6 | } 7 | 8 | function firstHalfOfSHA512(bytes) { 9 | return sjcl.bitArray.bitSlice( 10 | sjcl.hash.sha512.hash(sjcl.codec.bytes.toBits(bytes)), 11 | 0, 256 12 | ); 13 | } 14 | 15 | function SHA256_RIPEMD160(bits) { 16 | return sjcl.hash.ripemd160.hash(sjcl.hash.sha256.hash(bits)); 17 | } 18 | 19 | return function (seed) { 20 | this.seed = Base58Utils.decode_base_check(33, seed); 21 | 22 | if (!this.seed) { 23 | throw "Invalid seed." 24 | } 25 | 26 | this.getAddress = function (seq) { 27 | seq = seq || 0; 28 | 29 | var private_gen, public_gen, i = 0; 30 | do { 31 | private_gen = sjcl.bn.fromBits(firstHalfOfSHA512(append_int(this.seed, i))); 32 | i++; 33 | } while (!sjcl.ecc.curves.c256.r.greaterEquals(private_gen)); 34 | 35 | public_gen = sjcl.ecc.curves.c256.G.mult(private_gen); 36 | 37 | var sec; 38 | i = 0; 39 | do { 40 | sec = sjcl.bn.fromBits(firstHalfOfSHA512(append_int(append_int(public_gen.toBytesCompressed(), seq), i))); 41 | i++; 42 | } while (!sjcl.ecc.curves.c256.r.greaterEquals(sec)); 43 | 44 | var pubKey = sjcl.ecc.curves.c256.G.mult(sec).toJac().add(public_gen).toAffine(); 45 | 46 | return Base58Utils.encode_base_check(0, sjcl.codec.bytes.fromBits(SHA256_RIPEMD160(sjcl.codec.bytes.toBits(pubKey.toBytesCompressed())))); 47 | }; 48 | }; 49 | })(); 50 | 51 | exports.RippleAddress = RippleAddress; 52 | 53 | -------------------------------------------------------------------------------- /config_example.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Ripple Admin Console Configuration 3 | * 4 | * Copy this file to config.js and edit to suit your preferences. 5 | */ 6 | var Options = { 7 | // Rippled to connect 8 | connection: { 9 | trace: false, 10 | trusted: true, 11 | local_signing: true, 12 | 13 | servers: [ 14 | { host: 's-west.ripple.com', port: 443, secure: true }, 15 | { host: 's-east.ripple.com', port: 443, secure: true } 16 | ] 17 | }, 18 | 19 | // Number of transactions each page has in balance tab notifications 20 | transactions_per_page: 50, 21 | 22 | // Number of ledgers ahead of the current ledger index where a tx is valid 23 | tx_last_ledger: 3, 24 | 25 | // Set max transaction fee for network in drops of XRP 26 | max_tx_network_fee: 200000, 27 | 28 | // Set max number of rows for orderbook 29 | orderbook_max_rows: 20, 30 | 31 | gateway_max_limit: 1000000000, 32 | 33 | // Should only be used for development purposes 34 | persistent_auth: false 35 | }; 36 | 37 | // Load client-side overrides 38 | if (store.enabled) { 39 | var settings = JSON.parse(store.get('ripple_settings') || '{}'); 40 | 41 | if (settings.connection && settings.connection.servers) { 42 | var servers = _.filter(settings.connection.servers, function(s) { 43 | return !s.isEmptyServer && _.isNumber(s.port) && !_.isNaN(s.port); 44 | }); 45 | 46 | if (!servers.length) { 47 | servers = Options.connection.servers; 48 | } 49 | settings.connection.servers = servers; 50 | 51 | Options.connection = settings.connection; 52 | } 53 | 54 | if (settings.max_tx_network_fee) { 55 | Options.max_tx_network_fee = settings.max_tx_network_fee; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /icons/font/icons-51c446195575425cef2ecd7189d41eec.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 8 | Created by FontForge 20120731 at Wed Jul 30 16:51:59 2014 9 | By Stefan Thomas,,, 10 | Created by Stefan Thomas,,, with FontForge 2.0 (http://fontforge.sf.net) 11 | 12 | 13 | 14 | 27 | 28 | 30 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /deps/sjcl-custom/sjcl-validecc.js: -------------------------------------------------------------------------------- 1 | /* eslint new-cap: [2, {newIsCapExceptions: ["corrupt"]}] */ 2 | 'use strict'; 3 | var sjcl = require('sjcl'); 4 | 5 | sjcl.ecc.ecdsa.secretKey.prototype.sign = function(hash, paranoia, 6 | k_for_testing) { 7 | var R = this._curve.r, 8 | l = R.bitLength(); 9 | 10 | // k_for_testing should ONLY BE SPECIFIED FOR TESTING 11 | // specifying it will make the signature INSECURE 12 | var k; 13 | if (typeof k_for_testing === 'object' && k_for_testing.length > 0 14 | && typeof k_for_testing[0] === 'number') { 15 | k = k_for_testing; 16 | } else if (typeof k_for_testing === 'string' 17 | && /^[0-9a-fA-F]+$/.test(k_for_testing)) { 18 | k = sjcl.bn.fromBits(sjcl.codec.hex.toBits(k_for_testing)); 19 | } else { 20 | // This is the only option that should be used in production 21 | k = sjcl.bn.random(R.sub(1), paranoia).add(1); 22 | } 23 | 24 | var r = this._curve.G.mult(k).x.mod(R); 25 | var s = sjcl.bn.fromBits(hash).add(r.mul(this._exponent)) 26 | .mul(k.inverseMod(R)).mod(R); 27 | 28 | return sjcl.bitArray.concat(r.toBits(l), s.toBits(l)); 29 | }; 30 | 31 | sjcl.ecc.ecdsa.publicKey.prototype.verify = function(hash, rs) { 32 | var w = sjcl.bitArray; 33 | var R = this._curve.r; 34 | var l = R.bitLength(); 35 | var r = sjcl.bn.fromBits(w.bitSlice(rs, 0, l)); 36 | var s = sjcl.bn.fromBits(w.bitSlice(rs, l, 2 * l)); 37 | var sInv = s.inverseMod(R); 38 | var hG = sjcl.bn.fromBits(hash).mul(sInv).mod(R); 39 | var hA = r.mul(sInv).mod(R); 40 | var r2 = this._curve.G.mult2(hG, hA, this._point).x; 41 | 42 | if (r.equals(0) || s.equals(0) || r.greaterEquals(R) || s.greaterEquals(R) 43 | || !r2.equals(r)) { 44 | throw (new sjcl.exception.corrupt('signature didn\'t check out')); 45 | } 46 | return true; 47 | }; 48 | -------------------------------------------------------------------------------- /src/js/services/authflow.js: -------------------------------------------------------------------------------- 1 | /** 2 | * AUTH FLOW 3 | * 4 | * This auth flow service manages the login and registration procedures for the desktop client. 5 | */ 6 | 7 | var module = angular.module('authflow', []); 8 | 9 | module.factory('rpAuthFlow', ['$rootScope', 'rpBlob', 10 | function ($scope, $blob) 11 | { 12 | var AuthFlow = {}; 13 | 14 | /** 15 | * Login 16 | * 17 | * @param {object} opts 18 | * @param {string} opts.password 19 | * @param {string} opts.walletfile 20 | * @param {function} callback 21 | */ 22 | AuthFlow.login = function (opts, callback) { 23 | var password = opts.password; 24 | 25 | $blob.init(opts.walletfile, password, function (err, blob) { 26 | if (err) { 27 | callback(err); 28 | return; 29 | } 30 | 31 | console.log("client: authflow: login succeeded", blob); 32 | callback(null, blob, '', 'local'); 33 | }); 34 | }; 35 | 36 | /** 37 | * Register an account 38 | * 39 | * @param {object} opts 40 | * @param {string} opts.password 41 | * @param {string} opts.account 42 | * @param {string} opts.masterkey 43 | * @param {string} opts.walletfile 44 | * @param {function} callback 45 | */ 46 | AuthFlow.register = function (opts, callback) { 47 | $blob.create({ 48 | 'account': opts.account, 49 | 'password': opts.password, 50 | 'masterkey': opts.masterkey, 51 | 'walletfile': opts.walletfile 52 | }, 53 | function (err, blob) { 54 | if (err) { 55 | callback(err); 56 | return; 57 | } 58 | 59 | console.log("client: authflow: registration succeeded", blob); 60 | callback(null, blob, 'local'); 61 | }); 62 | }; 63 | 64 | return AuthFlow; 65 | }]); 66 | -------------------------------------------------------------------------------- /src/templates/tabs/coldwalletsettings.jade: -------------------------------------------------------------------------------- 1 | section.col-xs-12.content(ng-controller="ColdwalletSettingsCtrl") 2 | .row 3 | .col-sm-3 4 | include settings/_navbar 5 | .col-sm-8.col-md-9.col-xs-12.list 6 | div 7 | h4(l10n) Default transaction folder 8 | .section 9 | .descriptor(l10n) 10 | label(l10n) The folder you select here is the default location in which your signed transactions will be saved. 11 | | When you create a transaction, the transaction file will be automatically saved to this location. 12 | .form-group#servers 13 | .col-sm-8.col-md-9.col-xs-12.list 14 | a.btn.btn-primary(ng-click='fileInputClick()', l10n) Change folders 15 | .section 16 | hr 17 | .p(l10n) Current default folder: {{defaultDirectory || 'not specified'}}. 18 | h4#serverSettings(ng-hide="onlineMode", l10n) Account Sequence 19 | form.server-row-form(ng-hide="onlineMode", ng-controller="ColdwalletSettingsCtrl", ng-submit="saveSequence()") 20 | ng-form(name="feeForm") 21 | .row 22 | .col-md-3.col-sm-4.col-xs-3 23 | .descriptor(l10n) Sequence 24 | input.form-control#send_sequence( 25 | name='send_sequence', type='number' 26 | ng-model='$root.sequence' 27 | ng-required='!onlineMode', placeholder="sequence") 28 | .col-xs-3.col-sm-3.col-md-2 29 | .descriptor.portNumber(l10n) Update 30 | button.btn.btn-block.btn-success.btn-xs.submit#save(type='submit' 31 | ng-disabled='feeForm.$invalid', l10n) Save 32 | ng-form(name="savedFeeForm") 33 | .row 34 | .col-sm-12.notification-wrapper 35 | .alert.alert-success(ng-show="saved", l10n) Updates saved. 36 | -------------------------------------------------------------------------------- /l10n/DEVELOPERS.md: -------------------------------------------------------------------------------- 1 | # Basic use 2 | 3 | To make a string in one of the Jade templates translatable, simply add the `l10n` attribute: 4 | 5 | p(l10n) I can be translated! 6 | 7 | # Ambiguous strings 8 | 9 | Sometimes the same string translates in two different ways depending on the context. 10 | 11 | button(l10n) Clear 12 | select 13 | option(l10n) Clear 14 | option(l10n) Opaque 15 | 16 | You can deal with this by explicitly assigning a unique ID to one or both of the conflicting instances, like so: 17 | 18 | button(l10n) Clear 19 | select 20 | option(l10n="Clear (transparent)") Clear 21 | option(l10n) Opaque 22 | 23 | # Dealing with subtags 24 | 25 | Often when translating Jade templates you will run into situations where text is splintered across subtags, like so: 26 | 27 | p(l10n) 28 | | Send money to 29 | strong(ng-bind="otherGuy") ??? 30 | 31 | The parser we use will turn this into: 32 | 33 | msgid "Send money to {{1}}" 34 | 35 | This makes it easy for the translators to move the element to a different position in the text if needed. 36 | 37 | However, sometimes the subtag itself contains text that is important for comprehension: 38 | 39 | p(l10n) 40 | | This is a 41 | strong(l10n) great 42 | | example. 43 | 44 | Would become: 45 | 46 | msgid "This is a {{1}} example." 47 | 48 | # ... 49 | 50 | msgid "great" 51 | 52 | Here the context for the inner text snippet is lost. In a case like this we recommend using `l10n-inc` to mark the subtag for inclusion in the parent's translation: 53 | 54 | p(l10n) 55 | | This is a 56 | strong(l10n-inc) great 57 | | example. 58 | 59 | Will become: 60 | 61 | msgid "This is a {{1:great}} example." 62 | 63 | The translator can then translate and move the tag as needed: 64 | 65 | msgid "This is a {{1:great}} example." 66 | msgstr "Dies ist ein {{1:geniales}} Beispiel." 67 | 68 | -------------------------------------------------------------------------------- /src/less/ripple/combobox.less: -------------------------------------------------------------------------------- 1 | .rp-combobox { 2 | position: relative; 3 | 4 | input { 5 | position: relative; 6 | z-index: 20; 7 | } 8 | 9 | &.active input { 10 | border-color: @midgray; 11 | .border-radius(5px, 0, 0, 5px); 12 | } 13 | 14 | ul.completions { 15 | position: absolute; 16 | top: 41px; 17 | left: 0; 18 | right: 0; 19 | padding: 10px; 20 | margin: 0; 21 | list-style-type: none; 22 | border: 1px solid @midgray; 23 | border-top: 1px solid @midgray; 24 | background-color: @white; 25 | .border-radius(0, 5px, 5px, 0); 26 | overflow: auto; 27 | max-height: 290px; 28 | z-index: 30; 29 | .user-select(none); 30 | 31 | li { 32 | width: auto; 33 | padding: 5px; 34 | .rounded(5px); 35 | cursor: default; 36 | 37 | .additional { 38 | display: block; 39 | color: @darkgray; 40 | font-size: 12px; 41 | } 42 | } 43 | 44 | li.cursor, li:hover { 45 | background-color: @midgray; 46 | 47 | .additional { 48 | color: @black; 49 | } 50 | } 51 | } 52 | 53 | .select { 54 | position: absolute; 55 | top: 1px; 56 | right: 1px; 57 | height: 40px; 58 | width: 40px; 59 | z-index: 25; 60 | .bw-gradient(@lightgray, 230, 245); 61 | .border-radius(4px, 4px, 0, 0); 62 | border-left: 1px solid @midgray; 63 | cursor: default; 64 | .transition-duration(0.2s); 65 | 66 | &:hover { 67 | .bw-gradient(@lightgray, 233, 248); 68 | } 69 | 70 | &:after { 71 | .virtualel; 72 | position: absolute; 73 | top: 50%; 74 | right: 50%; 75 | margin: -2px -3px 0 0; 76 | border-top: 5px solid @black; 77 | border-left: solid 4px transparent; 78 | border-right: solid 4px transparent; 79 | } 80 | } 81 | 82 | &.active .select { 83 | .border-radius(4px, 0, 0, 0); 84 | .bw-gradient(@lightgray, 245, 230); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/js/tabs/tx.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | var Tab = require('../client/tab').Tab; 3 | var rewriter = require('../util/jsonrewriter'); 4 | 5 | var TxTab = function () 6 | { 7 | Tab.call(this); 8 | }; 9 | 10 | util.inherits(TxTab, Tab); 11 | 12 | TxTab.prototype.tabName = 'tx'; 13 | 14 | TxTab.prototype.generateHtml = function () 15 | { 16 | return require('../../templates/tabs/tx.jade')(); 17 | }; 18 | 19 | TxTab.prototype.angular = function (module) 20 | { 21 | module.controller('TxCtrl', ['$scope', 'rpNetwork', '$routeParams', 'rpId', '$location', 22 | function ($scope, net, $routeParams, $id, $location) 23 | { 24 | $scope.logoutTx = function () { 25 | $id.logout(); 26 | $location.path('/login'); 27 | }; 28 | 29 | if (!$id.loginStatus) return $scope.logoutTx(); 30 | 31 | $scope.state = 'loading'; 32 | $scope.transaction = { 33 | hash: $routeParams.id 34 | }; 35 | 36 | function loadTx() { 37 | // XXX: Dirty, dirty. But it's going to change soon anyway. 38 | var request = net.remote.request_ledger_hash(); 39 | request.message.command = 'tx'; 40 | request.message.transaction = $routeParams.id; 41 | request.on('success', function (res) { 42 | $scope.$apply(function () { 43 | $scope.state = 'loaded'; 44 | // XXX This is for the upcoming tx RPC call format change. 45 | var tx = res.tx ? res.tx : res; 46 | _.assign($scope.transaction, res); 47 | 48 | $scope.amountSent = rewriter.getAmountSent(tx, tx.meta); 49 | }); 50 | }); 51 | request.on('error', function (res) { 52 | $scope.$apply(function () { 53 | $scope.state = 'error'; 54 | console.log(res); 55 | }); 56 | }); 57 | request.request(); 58 | } 59 | 60 | if (net.connected) loadTx(); 61 | else var removeListener = $scope.$on('$netConnected', function () { 62 | removeListener(); 63 | loadTx(); 64 | }); 65 | }]); 66 | }; 67 | 68 | module.exports = TxTab; 69 | -------------------------------------------------------------------------------- /src/templates/messages/sendconvert/_confirmation.jade: -------------------------------------------------------------------------------- 1 | // Confirmation messages for the send and convert tabs 2 | 3 | group.pending(ng-show='tx_result=="pending"') 4 | h2.tx-status(l10n) Transaction pending. 5 | group.result-success(ng-show='tx_result=="cleared"') 6 | h2.tx-status(l10n) Transaction successful 7 | group.result-error(ng-show='tx_result=="error"') 8 | h2.tx-status(l10n) Transaction failed 9 | p(l10n) Please try again later. 10 | group.result-malformed(ng-show='tx_result=="malformed"') 11 | h2.tx-status(l10n) Transaction failed 12 | p(ng-switch="engine_result") 13 | span(ng-switch-default, l10n) 14 | | Error code: {{engine_result}} - {{engine_result_message}} 15 | group.result-malformed(ng-show='tx_result=="failure"') 16 | h2.tx-status(l10n) Transaction failed 17 | p(ng-switch="engine_result") 18 | span(ng-switch-when="tefDST_TAG_NEEDED", l10n) 19 | | Error code: Destination tag needed [tefDST_TAG_NEEDED]. 20 | group.result-malformed(ng-show='tx_result=="claim"') 21 | h2.tx-status(l10n) Transaction failed 22 | p(ng-switch="engine_result") 23 | span(ng-switch-when="tecNO_DST", l10n) 24 | | Error code: The destination account does not exist [tecNO_DST]. 25 | span(ng-switch-when="tecNO_DST_INSUF_XRP", l10n) 26 | | Error code: Insufficient XRP sent to destination [tecNO_DST_INSUF_XRP]. 27 | span(ng-switch-default, l10n) 28 | | Error: {{engine_result_message}} 29 | group.result-failed(ng-show='tx_result=="failed"') 30 | h2.tx-status(l10n) Transaction failed 31 | p(ng-switch="engine_result") 32 | span(ng-switch-when="terNO_LINE", l10n) 33 | | Error code: You have no trust line in this currency [terNO_LINE]. 34 | span(ng-switch-default, l10n) 35 | | Your transaction failed: {{engine_result_message}} 36 | group.result-failed(ng-show='tx_result=="local"') 37 | h2.tx-status(l10n) Transaction failed 38 | p(ng-switch="engine_result") 39 | span(ng-switch-when="telINSUF_FEE_P", l10n) 40 | | Error code: This transaction failed because it exceeded the maximum network fee, please try again later [telINSUF_FEE_P]. -------------------------------------------------------------------------------- /src/js/util/settings.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Check if userblob is actual blob class, not just empty dummy object 4 | * 5 | * @param userBlob 6 | */ 7 | exports.blobIsValid = function(userBlob) { 8 | return userBlob && typeof userBlob.set === 'function'; 9 | } 10 | 11 | /** 12 | * Check if there is such setting in user blob 13 | * use like hasSetting($scope.userBlob, 'trust.advancedMode') 14 | * 15 | * @param userBlob 16 | * @param settingName 17 | */ 18 | exports.hasSetting = function(userBlob, settingName) { 19 | if (!userBlob || !userBlob.data || typeof settingName !== 'string') return false; 20 | var d = userBlob.data; 21 | if (d.clients && d.clients.rippletradecom) { 22 | var parts = settingName.split('.'); 23 | var o = d.clients.rippletradecom; 24 | while (parts.length) { 25 | var part = parts.shift(); 26 | if (_.has(o, part)) { 27 | o = o[part]; 28 | } else { 29 | return false; 30 | } 31 | } 32 | return true; 33 | } 34 | return false; 35 | }; 36 | 37 | /** 38 | * Check if there is such setting in user blob 39 | * use like var s = getSetting($scope.userBlob, 'trust.advancedMode', false); 40 | * 41 | * @param userBlob 42 | * @param settingName 43 | * @param def Default value. Optional. 44 | */ 45 | exports.getSetting = function(userBlob, settingName, def) { 46 | if (!userBlob || !userBlob.data || typeof settingName !== 'string') return def; 47 | var d = userBlob.data; 48 | if (d.clients && d.clients.rippletradecom) { 49 | var parts = settingName.split('.'); 50 | var o = d.clients.rippletradecom; 51 | while (parts.length) { 52 | var part = parts.shift(); 53 | if (_.has(o, part)) { 54 | o = o[part]; 55 | } else { 56 | return def; 57 | } 58 | } 59 | return o; 60 | } 61 | return def; 62 | }; 63 | 64 | exports.getClearServers = function(servers) { 65 | return _.map(servers, function(server) { 66 | var o = _.pick(server, 'host', 'port', 'secure'); 67 | // when edited it comes as string from input, 68 | // so convert to number to be stored in blob in consisten way 69 | o.port = Number(o.port); 70 | return o; 71 | }); 72 | } 73 | -------------------------------------------------------------------------------- /test/unit/controllers/navbarControllerSpec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var expect = chai.expect; 3 | 4 | function run(scope,done) { 5 | done(); 6 | } 7 | 8 | describe('NavbarCtrl', function(){ 9 | var rootScope, scope, controller_injector, dependencies, ctrl; 10 | 11 | beforeEach(module("rp")); 12 | beforeEach(inject(function($rootScope, $controller, $q) { 13 | rootScope = rootScope; 14 | scope = $rootScope.$new(); 15 | controller_injector = $controller; 16 | 17 | dependencies = { 18 | $scope: scope, 19 | $element: null 20 | } 21 | 22 | ctrl = controller_injector("NavbarCtrl", dependencies); 23 | })); 24 | 25 | describe('initializing the controller', function () { 26 | it('should initialize properly', function (done) { 27 | assert.isNotNull(ctrl); 28 | run(scope,done); 29 | }); 30 | 31 | it('should set up the scope with defaults', function (done) { 32 | run(scope,done); 33 | assert.isUndefined(scope.show_secondary); 34 | }) 35 | 36 | it('should be set up with sinon', function (done) { 37 | assert.isNotNull(sinon); 38 | assert.isFunction(sinon.spy); 39 | run(scope,done); 40 | }); 41 | 42 | it('should be disconnected by default', function (done) { 43 | assert.isUndefined(scope.connected); 44 | done(); 45 | }); 46 | 47 | }); 48 | 49 | describe('public functions on $scope', function () { 50 | 51 | it('should toggle the secondary', function (done) { 52 | assert.isFunction(scope.toggle_secondary); 53 | 54 | scope.toggle_secondary(); 55 | expect(scope.show_secondary).to.be.true; 56 | 57 | scope.toggle_secondary(); 58 | expect(scope.show_secondary).to.be.false; 59 | 60 | run(scope,done); 61 | }); 62 | 63 | }); 64 | 65 | describe('private functions', function() { 66 | 67 | it('should set the connection status', function (done) { 68 | assert.isFunction(ctrl.setConnectionStatus); 69 | run(scope,done); 70 | }); 71 | 72 | it('should enqueue', function (done) { 73 | assert.isFunction(ctrl.enqueue); 74 | run(scope,done); 75 | }); 76 | 77 | it('should tick', function (done) { 78 | assert.isFunction(ctrl.tick); 79 | run(scope,done); 80 | }); 81 | 82 | }); 83 | }); 84 | -------------------------------------------------------------------------------- /src/js/tabs/settingstrade.js: -------------------------------------------------------------------------------- 1 | (function(module) { 2 | 'use strict'; 3 | 4 | /* global ripple: false, angular: false, _: false, jQuery: false, store: false, Options: false */ 5 | 6 | var util = require('util'), 7 | webutil = require('../util/web'), 8 | settings = require('../util/settings'), 9 | Tab = require('../client/tab').Tab; 10 | 11 | var SettingsTradeTab = function() { 12 | Tab.call(this); 13 | }; 14 | 15 | util.inherits(SettingsTradeTab, Tab); 16 | 17 | SettingsTradeTab.prototype.tabName = 'settingstrade'; 18 | SettingsTradeTab.prototype.mainMenu = 'settingstrade'; 19 | 20 | SettingsTradeTab.prototype.generateHtml = function() { 21 | return require('../../templates/tabs/settingstrade.jade')(); 22 | }; 23 | 24 | SettingsTradeTab.prototype.angular = function(module) { 25 | module.controller('SettingsTradeCtrl', SettingsTradeCtrl); 26 | 27 | SettingsTradeCtrl.$inject = ['$scope', '$timeout']; 28 | 29 | function SettingsTradeCtrl($scope, $timeout) { 30 | if ($scope.userBlob.data && $scope.userCredentials.username) { 31 | $scope.pairs = settings.getSetting($scope.userBlob, 'trade_currency_pairs'); 32 | } else { 33 | var removeWatcher = $scope.$on('$blobUpdate', function() { 34 | if (!$scope.userCredentials.username) 35 | return; 36 | $scope.pairs = settings.getSetting($scope.userBlob, 'trade_currency_pairs'); 37 | removeWatcher(); 38 | }); 39 | } 40 | 41 | $scope.deletePair = function(index){ 42 | for (var i = 0; i < $scope.pairs.length; i++) { 43 | if ($scope.pairs[i].name === this.entry.name) { 44 | $scope.userBlob.unset('/clients/rippletradecom/trade_currency_pairs/' + index); 45 | return; 46 | } 47 | } 48 | } 49 | 50 | $scope.dragControlListeners = { 51 | orderChanged: function(event) { 52 | var sourceObj = _.clone($scope.pairs[event.source.index]); 53 | var destObj = _.clone($scope.pairs[event.dest.index]); 54 | $scope.userBlob.set('/clients/rippletradecom/trade_currency_pairs/' + event.source.index, sourceObj); 55 | $scope.userBlob.set('/clients/rippletradecom/trade_currency_pairs/' + event.dest.index, destObj); 56 | } 57 | }; 58 | } 59 | }; 60 | 61 | module.exports = SettingsTradeTab; 62 | 63 | })(module); 64 | -------------------------------------------------------------------------------- /deps/sjcl-custom/sjcl-ecc-pointextras.js: -------------------------------------------------------------------------------- 1 | /* eslint new-cap: [2, {newIsCapExceptions: ["bn"]}] */ 2 | 'use strict'; 3 | var sjcl = require('sjcl'); 4 | /** 5 | * Check that the point is valid based on the method described in 6 | * SEC 1: Elliptic Curve Cryptography, section 3.2.2.1: 7 | * Elliptic Curve Public Key Validation Primitive 8 | * http://www.secg.org/download/aid-780/sec1-v2.pdf 9 | * 10 | * @returns {Boolean} true if point is valid 11 | */ 12 | sjcl.ecc.point.prototype.isValidPoint = function() { 13 | 14 | var self = this; 15 | 16 | var field_modulus = self.curve.field.modulus; 17 | 18 | if (self.isIdentity) { 19 | return false; 20 | } 21 | 22 | // Check that coordinatres are in bounds 23 | // Return false if x < 1 or x > (field_modulus - 1) 24 | if (((new sjcl.bn(1).greaterEquals(self.x)) && 25 | !self.x.equals(1)) || 26 | (self.x.greaterEquals(field_modulus.sub(1))) && 27 | !self.x.equals(1)) { 28 | 29 | return false; 30 | } 31 | 32 | // Return false if y < 1 or y > (field_modulus - 1) 33 | if (((new sjcl.bn(1).greaterEquals(self.y)) && 34 | !self.y.equals(1)) || 35 | (self.y.greaterEquals(field_modulus.sub(1))) && 36 | !self.y.equals(1)) { 37 | 38 | return false; 39 | } 40 | 41 | if (!self.isOnCurve()) { 42 | return false; 43 | } 44 | 45 | // TODO check to make sure point is a scalar multiple of base_point 46 | 47 | return true; 48 | 49 | }; 50 | 51 | /** 52 | * Check that the point is on the curve 53 | * 54 | * @returns {Boolean} true if point is on the curve 55 | */ 56 | sjcl.ecc.point.prototype.isOnCurve = function() { 57 | 58 | var self = this; 59 | 60 | var component_a = self.curve.a; 61 | var component_b = self.curve.b; 62 | var field_modulus = self.curve.field.modulus; 63 | 64 | var left_hand_side = self.y.mul(self.y).mod(field_modulus); 65 | var right_hand_side = self.x.mul(self.x).mul(self.x).add( 66 | component_a.mul(self.x)).add(component_b).mod(field_modulus); 67 | 68 | return left_hand_side.equals(right_hand_side); 69 | 70 | }; 71 | 72 | 73 | sjcl.ecc.point.prototype.toString = function() { 74 | return '(' + 75 | this.x.toString() + ', ' + 76 | this.y.toString() + 77 | ')'; 78 | }; 79 | 80 | sjcl.ecc.pointJac.prototype.toString = function() { 81 | return '(' + 82 | this.x.toString() + ', ' + 83 | this.y.toString() + ', ' + 84 | this.z.toString() + 85 | ')'; 86 | }; 87 | -------------------------------------------------------------------------------- /src/js/services/network.js: -------------------------------------------------------------------------------- 1 | /** 2 | * NETWORK 3 | * 4 | * The network service is used to communicate with the Ripple network. 5 | * 6 | * It encapsulates a ripple.Remote instance. 7 | */ 8 | 9 | var module = angular.module('network', []); 10 | 11 | module.factory('rpNetwork', ['$rootScope', function($scope) 12 | { 13 | /** 14 | * Manage network state. 15 | * 16 | * This class is intended to manage the connection status to the 17 | * Ripple network. 18 | * 19 | * Note that code in other places *is allowed* to call the Ripple 20 | * library directly. This is not to be intended to be an abstraction 21 | * layer on top of an abstraction layer. 22 | */ 23 | var Network = function() { 24 | this.connected = false; 25 | this.remote = new ripple.Remote(Options.connection, true); 26 | }; 27 | 28 | Network.prototype.connect = function(serverSettings) { 29 | serverSettings = serverSettings ? serverSettings : Options.connection; 30 | 31 | this.remote = new ripple.Remote(serverSettings, true); 32 | this.remote.on('connected', this.handleConnect.bind(this)); 33 | this.remote.on('disconnected', this.handleDisconnect.bind(this)); 34 | 35 | // Set network max transaction fee from Options, or default to 12 drops of XRP 36 | this.remote.max_fee = Options.max_tx_network_fee || 12; 37 | 38 | if (serverSettings && serverSettings.servers && serverSettings.servers.length) { 39 | this.remote.connect(); 40 | } 41 | }; 42 | 43 | Network.prototype.disconnect = function() { 44 | if (this.remote) { 45 | this.remote.disconnect(); 46 | } 47 | }; 48 | 49 | /** 50 | * Setup listeners for identity state. 51 | * 52 | * This function causes the network object to start listening to 53 | * changes in the identity state and automatically subscribe to 54 | * accounts accordingly. 55 | */ 56 | Network.prototype.listenId = function (id) 57 | { 58 | var self = this; 59 | }; 60 | 61 | Network.prototype.handleConnect = function (e) 62 | { 63 | var self = this; 64 | 65 | self.connected = true; 66 | $scope.connected = true; 67 | $scope.$broadcast('$netConnected'); 68 | 69 | if(!$scope.$$phase) { 70 | $scope.$apply() 71 | } 72 | }; 73 | 74 | Network.prototype.handleDisconnect = function (e) 75 | { 76 | var self = this; 77 | self.connected = false; 78 | $scope.connected = false; 79 | $scope.$broadcast('$netDisconnected'); 80 | 81 | if(!$scope.$$phase) { 82 | $scope.$apply() 83 | } 84 | }; 85 | 86 | return new Network(); 87 | }]); 88 | 89 | -------------------------------------------------------------------------------- /src/templates/tabs/tx.jade: -------------------------------------------------------------------------------- 1 | section.single.ddpage.content(ng-controller="TxCtrl", ng-switch="state") 2 | group(ng-switch-when="loading") 3 | p.literal.throbber(l10n) Loading transaction details... 4 | group(ng-switch-when="error") 5 | p.literal(l10n) An error occurred while loading the transaction details. 6 | group(ng-switch-when="loaded") 7 | p.literal.hash 8 | span(l10n) Transaction # 9 | span {{transaction.hash}} 10 | hr 11 | p.literal.type(l10n) Transaction type: 12 | strong {{transaction.TransactionType}} 13 | group(ng-switch="transaction.TransactionType") 14 | group(ng-switch-when="Payment") 15 | group.clearfix 16 | dl.details.half 17 | dt 18 | span(l10n) Address sent from 19 | span : 20 | dd {{transaction.Account}} 21 | dt 22 | span(l10n) Amount sent 23 | span : 24 | dd {{amountSent | rpamount}} {{amountSent | rpcurrency}} 25 | dt 26 | span(l10n) Currency sent 27 | span : 28 | dd {{amountSent | rpcurrencyfull}} 29 | dl.details.half 30 | dt 31 | span(l10n) Address sent to 32 | span : 33 | dd {{transaction.Destination}} 34 | dt 35 | span(l10n) Amount received 36 | span : 37 | dd {{transaction.Amount | rpamount}} {{transaction.Amount | rpcurrency}} 38 | dt 39 | span(l10n) Currency received 40 | span : 41 | dd {{transaction.Amount | rpcurrencyfull}} 42 | hr 43 | group.clearfix 44 | dl.details.half 45 | dt 46 | span(l10n) Network fee paid 47 | span : 48 | dd {{transaction.Fee | rpamount}} XRP 49 | dl.details.half 50 | group(ng-show="transaction.DestinationTag !== null && transaction.DestinationTag !== undefined") 51 | dt 52 | span(l10n) Destination tag 53 | span : 54 | dd {{transaction.DestinationTag}} 55 | hr 56 | dl.details 57 | dt 58 | span(l10n) Ledger number 59 | span : 60 | dd {{transaction.inLedger}} 61 | group(ng-switch-default) 62 | group.clearfix 63 | dl.details.half 64 | dt(l10n) Address sent from: 65 | dd {{transaction.Account}} 66 | dl.details.half 67 | hr 68 | p.literal(l10n="Sorry, we don't have an info page layout for this transaction type yet.") 69 | | Sorry, we don't have an info page layout for this transaction type yet. -------------------------------------------------------------------------------- /deps/sjcl-custom/sjcl-secp256k1.js: -------------------------------------------------------------------------------- 1 | /* eslint new-cap: [2, {newIsCapExceptions: ["pointJac"]}] */ 2 | 'use strict'; 3 | var sjcl = require('sjcl'); 4 | 5 | // ----- for secp256k1 ------ 6 | 7 | sjcl.ecc.point.prototype.toBytesCompressed = function() { 8 | var header = this.y.mod(2).toString() === '0x0' ? 0x02 : 0x03; 9 | return [header].concat(sjcl.codec.bytes.fromBits(this.x.toBits())); 10 | }; 11 | 12 | // Replace point addition and doubling algorithms 13 | // NIST-P256 is a=-3, we need algorithms for a=0 14 | // 15 | // This is a custom point addition formula that 16 | // only works for a=-3 Jacobian curve. It's much 17 | // faster than the generic implementation 18 | sjcl.ecc.pointJac.prototype.add = function(T) { 19 | var self = this; 20 | if (self.curve !== T.curve) { 21 | throw ('sjcl.ecc.add(): Points must be on the same curve to add them!'); 22 | } 23 | 24 | if (self.isIdentity) { 25 | return T.toJac(); 26 | } else if (T.isIdentity) { 27 | return self; 28 | } 29 | 30 | var z1z1 = self.z.square(); 31 | var h = T.x.mul(z1z1).subM(self.x); 32 | var s2 = T.y.mul(self.z).mul(z1z1); 33 | 34 | if (h.equals(0)) { 35 | if (self.y.equals(T.y.mul(z1z1.mul(self.z)))) { 36 | // same point 37 | return self.doubl(); 38 | } 39 | // inverses 40 | return new sjcl.ecc.pointJac(self.curve); 41 | } 42 | 43 | var hh = h.square(); 44 | var i = hh.copy().doubleM().doubleM(); 45 | var j = h.mul(i); 46 | var r = s2.sub(self.y).doubleM(); 47 | var v = self.x.mul(i); 48 | 49 | var x = r.square().subM(j).subM(v.copy().doubleM()); 50 | var y = r.mul(v.sub(x)).subM(self.y.mul(j).doubleM()); 51 | var z = self.z.add(h).square().subM(z1z1).subM(hh); 52 | 53 | return new sjcl.ecc.pointJac(this.curve, x, y, z); 54 | }; 55 | 56 | // This is a custom doubling algorithm that 57 | // only works for a=-3 Jacobian curve. It's much 58 | // faster than the generic implementation 59 | sjcl.ecc.pointJac.prototype.doubl = function() { 60 | if (this.isIdentity) { 61 | return this; 62 | } 63 | 64 | var a = this.x.square(); 65 | var b = this.y.square(); 66 | var c = b.square(); 67 | var d = this.x.add(b).square().subM(a).subM(c).doubleM(); 68 | var e = a.mul(3); 69 | var f = e.square(); 70 | var x = f.sub(d.copy().doubleM()); 71 | var y = e.mul(d.sub(x)).subM(c.doubleM().doubleM().doubleM()); 72 | var z = this.z.mul(this.y).doubleM(); 73 | return new sjcl.ecc.pointJac(this.curve, x, y, z); 74 | }; 75 | 76 | // DEPRECATED: 77 | // previously the c256 curve was overridden with the secp256k1 curve 78 | // since then, sjcl has been updated to support k256 79 | // this override exist to keep supporting the old c256 with k256 behavior 80 | // this will be removed in future release 81 | sjcl.ecc.curves.c256 = sjcl.ecc.curves.k256; 82 | -------------------------------------------------------------------------------- /test/e2e/login.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Chai is a BDD / TDD assertion library 4 | var chai = require('chai'); 5 | var chaiAsPromised = require('chai-as-promised'); 6 | var config = require('./protractor.conf.js').config; 7 | 8 | chai.use(chaiAsPromised); 9 | var expect = chai.expect; 10 | 11 | // For some weird reason config ignores allScriptsTimeout 12 | browser.manage().timeouts().setScriptTimeout(20000); 13 | 14 | 15 | 16 | describe('bootstrap', function() { 17 | before(function() { 18 | browser.get('/#'); 19 | // Remove session 20 | browser.executeScript('store.set("ripple_auth")'); 21 | browser.navigate().refresh(); 22 | }); 23 | 24 | it('should automatically redirect to /register when location hash/fragment is empty', function(done) { 25 | expect(browser.getCurrentUrl()) 26 | .to.eventually.contain('/register') 27 | .and.notify(done); 28 | }); 29 | 30 | }); 31 | 32 | describe('migration', function() { 33 | 34 | before(function(){ 35 | browser.get('#/migrate'); 36 | }); 37 | 38 | it('should render migrate when user navigates to /migrate', function(done) { 39 | expect($("form[name='loginForm']").getText()).to.exist.and.notify(done); 40 | }); 41 | 42 | it('should take old user to registration page', function(done) { 43 | // Fill the form 44 | $(".auth-form-container #login_username").sendKeys(config.oldUser.username); 45 | $(".auth-form-container #login_password").sendKeys(config.oldUser.password); 46 | $(".auth-form-container .submit-btn-container button").click(); 47 | 48 | // Check if it takes to the register page 49 | 50 | // TODO not a good solution. The test will hang if it doesn't go as expected 51 | browser.wait(function() { 52 | return browser.getCurrentUrl().then(function(url) { 53 | if (/\/register/.test(url)) { 54 | done(); 55 | return true; 56 | } 57 | }); 58 | }); 59 | // expect(browser.getCurrentUrl()) 60 | // .to.eventually.contain('/register') 61 | // .and.notify(done); 62 | }); 63 | 64 | }); 65 | 66 | describe('login', function() { 67 | 68 | before(function(){ 69 | browser.get('#/login'); 70 | }); 71 | 72 | it('should render login when user navigates to /login', function(done) { 73 | expect($("form[name='loginForm']").getText()).to.exist.and.notify(done); 74 | }); 75 | 76 | it('should login the test user', function(done) { 77 | // Fill the form 78 | $(".auth-form-container #login_username").sendKeys(config.user.username); 79 | $(".auth-form-container #login_password").sendKeys(config.user.password); 80 | $(".auth-form-container .submit-btn-container button").click(); 81 | 82 | // Check if it takes to the balance page (success login) 83 | expect(browser.getCurrentUrl()) 84 | .to.eventually.contain('/balance') 85 | .and.notify(done); 86 | }); 87 | 88 | }); 89 | -------------------------------------------------------------------------------- /src/templates/client/layout.jade: -------------------------------------------------------------------------------- 1 | // Wrapper 2 | div#wrapper 3 | // Header 4 | header 5 | h1(ng-show="['t-login','t-register', 't-submit', 't-coldwallet'].indexOf($route.current.tabClass) !== -1", l10n) Welcome to {{productName}}, a global value exchange 6 | include ./_navbar.jade 7 | .container 8 | 9 | // Main 10 | .row.main(id="{{$route.current.tabClass}}", role="main", ng-view) 11 | 12 | // Footer 13 | footer.row 14 | .col-sm-3 15 | a(href="https://github.com/ripple/ripple-client-desktop/releases", target="_blank", l10n) 16 | | Version: {{version}} 17 | 18 | //- .col-sm-9.right-links 19 | //- a(href="https://rippletrade.com/#/tou", target="_blank", l10n) Terms of Use 20 | //- a(href="https://rippletrade.com/#/privacypolicy", target="_blank", l10n) Privacy Policy 21 | //- a(href="https://ripple.com/knowledge_center/about-ripple-trade-info/", target="_blank", l10n) Support 22 | //- a(href="https://ripplelabs.atlassian.net/browse/WC", target="_blank", l10n) Bug reports 23 | //- a(href="#/lang/en") English 24 | //- a(href="#/lang/zh_CN") 中文 25 | //- a(href="#/lang/ja") 日本語 26 | 27 | //- // Language selector 28 | //- rp-popup 29 | //- a.last(href="", rp-popup-link, l10n) more... 30 | //- div.languageSelector(rp-popup-content) 31 | //- .modal-header 32 | //- button.close(type="button", data-dismiss="modal", aria-hidden="true") × 33 | //- .modal-title(l10n) Select a language 34 | //- .modal-body.row.list 35 | //- .col-sm-6 36 | //- a(href="#/lang/en") English 37 | //- .col-sm-6 38 | //- a(href="#/lang/zh_CN") 中文 39 | //- .col-sm-6 40 | //- a(href="#/lang/nl_NL") Nederlands 41 | //- .col-sm-6 42 | //- a(href="#/lang/it") Italiano 43 | //- .col-sm-6 44 | //- a(href="#/lang/pl") Polski 45 | //- .col-sm-6 46 | //- a(href="#/lang/es") Español 47 | //- .col-sm-6 48 | //- a(href="#/lang/pt") Português 49 | //- .col-sm-6 50 | //- a(href="#/lang/de") Deutsch 51 | //- .col-sm-6 52 | //- a(href="#/lang/nb_NO") Norsk 53 | //- .col-sm-6 54 | //- a(href="#/lang/sk") Slovenská 55 | //- .col-sm-6 56 | //- a(href="#/lang/ca") Català 57 | //- .col-sm-6 58 | //- a(href="#/lang/he_IL") עברית 59 | //- .col-sm-6 60 | //- a(href="#/lang/ru") Русский 61 | //- .col-sm-6 62 | //- a(href="#/lang/ro") Român 63 | //- .col-sm-6 64 | //- a(href="#/lang/ja") 日本語 65 | //- .col-sm-6 66 | //- a(href="#/lang/tr") Türkçe 67 | -------------------------------------------------------------------------------- /src/templates/directives/transactionerror.jade: -------------------------------------------------------------------------------- 1 | 2 | .transaction-error(ng-switch="engine_result | rptruncate:3") 3 | group(ng-switch-when='tes') 4 | group.pending(ng-hide="accepted") 5 | h2.tx-status(l10n) Payment pending 6 | group.result-success(ng-show="accepted") 7 | h2.tx-status(l10n) Payment successful 8 | group.result-malformed(ng-switch-when='tem') 9 | h2.tx-status(l10n) Payment failed 10 | p(ng-switch="engine_result") 11 | span(ng-switch-default, l10n) 12 | | Error code: {{engine_result}} - {{engine_result_message}} 13 | group.result-malformed(ng-switch-when='tef') 14 | div(ng-switch="engine_result") 15 | div(ng-switch-when="tefMAX_LEDGER") 16 | h2.tx-status(l10n) Payment failed 17 | p(ng-switch="engine_result") 18 | span(l10n) Your payment timed out, please try again. 19 | div(ng-switch-default) 20 | h2.tx-status(l10n) Payment failed 21 | p(ng-switch="engine_result") 22 | span(ng-switch-when="tefDST_TAG_NEEDED", l10n) 23 | | Error code: Destination tag needed [tefDST_TAG_NEEDED]. 24 | group.result-failed(ng-switch-when='tel') 25 | h2.tx-status(l10n) Payment failed 26 | p(ng-switch="engine_result") 27 | span(ng-switch-when="telINSUF_FEE_P", l10n) 28 | | Error code: This transaction failed because it exceeded the maximum network fee, please try again later [telINSUF_FEE_P]. 29 | group.result-malformed(ng-switch-when='tec') 30 | h2.tx-status(l10n) Payment failed 31 | p(ng-switch="engine_result") 32 | span(ng-switch-when="tecNO_DST", l10n) 33 | | Error code: No destination [tecNO_DST]. 34 | span(ng-switch-when="tecNO_DST_INSUF_XRP", l10n) 35 | | Error code: Insufficient XRP sent to destination [tecNO_DST_INSUF_XRP]. 36 | span(ng-switch-default, l10n) 37 | | Error: {{engine_result_message}} 38 | group.result-failed(ng-switch-when='ter') 39 | h2.tx-status(l10n) Payment failed 40 | p(ng-switch="engine_result") 41 | span(ng-switch-when="terNO_LINE", l10n) 42 | | Error code: You have no trust line in this currency [terNO_LINE]. 43 | span(ng-switch-default, l10n) 44 | | Your payment failed: {{engine_result}} - {{engine_result_message}} 45 | group.result-failed(ng-switch-when='tej') 46 | h2.tx-status(l10n) Payment failed 47 | p(ng-switch="engine_result") 48 | span(ng-switch-when="tejLost", l10n) 49 | | Error code: Transaction could not be submitted [tejLost]. 50 | span(ng-switch-when="tejMaxFeeExceeded", l10n) 51 | | This transaction failed because the current network fee is higher than your account limit. You can adjust your maximum network fee in Settings > Advanced, or try again later.
52 | | Error code: Network fee exceeded [tejMaxFeeExceeded]. 53 | span(ng-switch-default, l10n) 54 | | Your payment could not be submitted: {{engine_result}} - {{engine_result_message}} 55 | -------------------------------------------------------------------------------- /src/templates/tabs/submit.jade: -------------------------------------------------------------------------------- 1 | //- TODO sometimes doesn't add the file at first time 2 | section.col-xs-12.content(ng-controller="SubmitCtrl") 3 | 4 | group.disconnected(ng-hide="connected") 5 | p.literal(l10n) You have to be online to see this screen 6 | 7 | group(ng-if="connected") 8 | div.panel.panel-default(ng-show="invalidTxns.length") 9 | div.panel-heading 10 | button.close(type="button", class="close", data-dismiss="alert", aria-label="Close", ng-click="invalidTxns = {}") 11 | span(aria-hidden="true") 12 | i.fa.fa-times 13 | strong The following file(s) were not added 14 | div.panel-body 15 | div.table-responsive 16 | table.table.table-striped.table-bordered 17 | thead 18 | tr 19 | th File 20 | th Error 21 | tbody 22 | tr(ng-repeat="invalidTxn in invalidTxns") 23 | td {{invalidTxn.path}} 24 | td {{invalidTxn.error}} 25 | form(name="submitForm", ng-submit='submit()') 26 | .txFileRow(ng-controller="TxRowCtrl", ng-repeat="txFile in txFiles") 27 | .row 28 | .col-sm-8 29 | div.fileName 30 | dl.dl-horizontal 31 | dt Transaction file 32 | dd {{txFile.path}} 33 | dt Account address 34 | dd {{txFile.txJson.Account}} 35 | dt Sequence number 36 | dd {{txFile.txJson.Sequence}} 37 | dt Transaction type 38 | dd {{txFile.txJson.TransactionType}} 39 | .col-sm-2 40 | .col-sm-2 41 | .pull-right 42 | a(ng-hide="state", href="", ng-click="remove()") Remove 43 | a.pull-right(ng-show="state == 'error' || state == 'unfunded' || state == 'bad_auth_master' || state == 'success'", ng-click="remove()") 44 | i.fa.fa-times 45 | div.failed(ng-show="state == 'error'") 46 | | {{message}} 47 | div.failed(ng-show="state == 'unfunded'") 48 | | {{message}} 49 | div.failed(ng-show="state == 'bad_auth_master'") 50 | | {{message}} 51 | div.success(ng-show="state == 'success'") 52 | | {{message}} 53 | div.pending(ng-show="state == 'pending'") 54 | | {{message}} 55 | 56 | 57 | .form-group.hide 58 | input.form-control(type="file", name="tx[{{$index}}]", ng-model="txFile") 59 | .form-group 60 | a.txFileInput#txDropZone(ng-click='fileInputClick()', ng-init="initDropzone()" l10n) 61 | | + Add or drop transaction file(s) here 62 | .form-group(ng-show="txFiles.length") 63 | .row 64 | .col-md-3 65 | button.btn.btn-submit.btn-block.btn-success(type="submit" 66 | ng-disabled="submitForm.$invalid || loading", rp-spinner="{{loading ? 4 : null}}") 67 | span(l10n) Submit 68 | button.btn.btn-success(ng-click='gotoLogin()') Back to login. -------------------------------------------------------------------------------- /src/js/services/popup.js: -------------------------------------------------------------------------------- 1 | /** 2 | * POPUP 3 | * 4 | * The popup service is used to provide modals, alerts, and confirmation screens 5 | */ 6 | 7 | var module = angular.module('popup', []); 8 | 9 | module.factory('rpPopup', ['$compile', 10 | function ($compile) 11 | { 12 | var popupService = {}; 13 | 14 | // Get the popup 15 | popupService.getPopup = function(create) 16 | { 17 | if (!popupService.popupElement && create) 18 | { 19 | popupService.popupElement = $( '' ); 20 | popupService.popupElement.appendTo( 'BODY' ); 21 | } 22 | 23 | return popupService.popupElement; 24 | }; 25 | 26 | popupService.compileAndRunPopup = function (popup, scope, options) { 27 | $compile(popup)(scope); 28 | popup.modal(options); 29 | }; 30 | 31 | popupService.blank = function(content,scope) { 32 | var popup = popupService.getPopup(true); 33 | 34 | var html = ''; 37 | 38 | popup.html(html); 39 | 40 | popupService.compileAndRunPopup(popup, scope); 41 | }; 42 | 43 | popupService.confirm = function(title, actionText, actionButtonText, actionFunction, actionButtonCss, cancelButtonText, cancelFunction, cancelButtonCss, scope, options) { 44 | actionText = (actionText) ? actionText : "Are you sure?"; 45 | actionButtonText = (actionButtonText) ? actionButtonText : "Ok"; 46 | actionButtonCss = (actionButtonCss) ? actionButtonCss : "btn btn-info"; 47 | cancelButtonText = (cancelButtonText) ? cancelButtonText : "Cancel"; 48 | cancelButtonCss = (cancelButtonCss) ? cancelButtonCss : "btn btn-cancel"; 49 | 50 | var popup = popupService.getPopup(true); 51 | var confirmHTML = '"; 75 | 76 | popup.html(confirmHTML); 77 | 78 | popup.find(".btn").click(function () { 79 | popupService.close(); 80 | }); 81 | 82 | popupService.compileAndRunPopup(popup, scope, options); 83 | }; 84 | 85 | popupService.close = function() 86 | { 87 | var popup = popupService.getPopup(); 88 | if (popup) { 89 | popup.modal('hide'); 90 | } 91 | }; 92 | 93 | return popupService; 94 | }]); 95 | -------------------------------------------------------------------------------- /src/js/tabs/register.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | var Tab = require('../client/tab').Tab; 3 | 4 | var RegisterTab = function () 5 | { 6 | Tab.call(this); 7 | }; 8 | 9 | util.inherits(RegisterTab, Tab); 10 | 11 | RegisterTab.prototype.tabName = 'register'; 12 | RegisterTab.prototype.pageMode = 'single'; 13 | RegisterTab.prototype.parent = 'main'; 14 | 15 | RegisterTab.prototype.generateHtml = function () 16 | { 17 | return require('../../templates/tabs/register.jade')(); 18 | }; 19 | 20 | RegisterTab.prototype.angular = function (module) { 21 | 22 | module.controller('RegisterCtrl', ['$scope', '$location', '$element', 23 | '$timeout', 'rpId', 'rpFileDialog', 24 | function ($scope, $location, $element, 25 | $timeout, $id, filedialog) 26 | { 27 | if ($id.loginStatus) { 28 | $location.path('/balance'); 29 | return; 30 | } 31 | $scope.goTo = function(url){ 32 | $location.path(url); 33 | }; 34 | 35 | $scope.reset = function() 36 | { 37 | $scope.password = ''; 38 | $scope.passwordSet = {}; 39 | $scope.password1 = ''; 40 | $scope.password2 = ''; 41 | $scope.master = ''; 42 | $scope.key = ''; 43 | $scope.mode = 'register_new_account'; 44 | //$scope.mode = 'register_empty_wallet1'; 45 | $scope.showMasterKeyInput = false; 46 | $scope.submitLoading = false; 47 | 48 | if ($scope.registerForm) $scope.registerForm.$setPristine(true); 49 | }; 50 | 51 | $scope.fileInputClick = function() { 52 | filedialog.saveAs(function(filename) { 53 | $scope.$apply(function() { 54 | $scope.walletfile = filename; 55 | store.set('walletfile', filename); 56 | $scope.mode = 'register_empty_wallet'; 57 | }); 58 | }, 'wallet'); 59 | }; 60 | 61 | $scope.submitSecretKeyForm = function(){ 62 | $scope.masterkey = $scope.secretKey; 63 | $scope.fileInputClick(); 64 | }; 65 | 66 | $scope.register = function() 67 | { 68 | $id.register({ 69 | 'username': 'local', 70 | 'password': $scope.password1, 71 | 'masterkey': $scope.masterkey, 72 | 'walletfile': $scope.walletfile 73 | }, 74 | function(err, key){ 75 | if (err) { 76 | $scope.mode = "failed"; 77 | $scope.error_detail = err.message; 78 | return; 79 | } 80 | $scope.password = new Array($scope.password1.length+1).join("*"); 81 | $scope.keyOpen = key; 82 | $scope.key = $scope.keyOpen[0] + new Array($scope.keyOpen.length).join("*"); 83 | 84 | $scope.$apply(function(){ 85 | $scope.mode = 'welcome'; 86 | }); 87 | 88 | }); 89 | }; 90 | 91 | $scope.submitForm = function() 92 | { 93 | $scope.register(); 94 | }; 95 | 96 | $scope.goToFund = function() 97 | { 98 | $scope.mode = 'register_empty_wallet'; 99 | $scope.reset(); 100 | 101 | $location.path('/xrp'); 102 | }; 103 | 104 | $scope.reset(); 105 | 106 | }]); 107 | }; 108 | 109 | module.exports = RegisterTab; 110 | -------------------------------------------------------------------------------- /src/js/util/base58.js: -------------------------------------------------------------------------------- 1 | 2 | var Base58Utils = (function () { 3 | var alphabets = { 4 | 'ripple': "rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz", 5 | 'bitcoin': "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" 6 | }; 7 | 8 | var SHA256 = function (bytes) { 9 | return sjcl.codec.bytes.fromBits(sjcl.hash.sha256.hash(sjcl.codec.bytes.toBits(bytes))); 10 | }; 11 | 12 | return { 13 | // --> input: big-endian array of bytes. 14 | // <-- string at least as long as input. 15 | encode_base: function (input, alphabetName) { 16 | var alphabet = alphabets[alphabetName || 'ripple'], 17 | base = new sjcl.bn(alphabet.length), 18 | bi = sjcl.bn.fromBits(sjcl.codec.bytes.toBits(input)), 19 | buffer = []; 20 | 21 | while (bi.greaterEquals(base)) { 22 | var mod = bi.mod(base); 23 | buffer.push(alphabet[mod.limbs[0]]); 24 | bi = bi.div(base); 25 | } 26 | buffer.push(alphabet[bi.limbs[0]]); 27 | 28 | // Convert leading zeros too. 29 | for (var i = 0; i != input.length && !input[i]; i += 1) { 30 | buffer.push(alphabet[0]); 31 | } 32 | 33 | return buffer.reverse().join(""); 34 | }, 35 | 36 | // --> input: String 37 | // <-- array of bytes or undefined. 38 | decode_base: function (input, alphabetName) { 39 | var alphabet = alphabets[alphabetName || 'ripple'], 40 | base = new sjcl.bn(alphabet.length), 41 | bi = new sjcl.bn(0); 42 | 43 | var i; 44 | while (i != input.length && input[i] === alphabet[0]) { 45 | i += 1; 46 | } 47 | 48 | for (i = 0; i != input.length; i += 1) { 49 | var v = alphabet.indexOf(input[i]); 50 | 51 | if (v < 0) { 52 | return null; 53 | } 54 | 55 | bi = bi.mul(base).addM(v); 56 | } 57 | 58 | var bytes = sjcl.codec.bytes.fromBits(bi.toBits()).reverse(); 59 | 60 | // Remove leading zeros 61 | while(bytes[bytes.length-1] === 0) { 62 | bytes.pop(); 63 | } 64 | 65 | // Add the right number of leading zeros 66 | for (i = 0; input[i] === alphabet[0]; i++) { 67 | bytes.push(0); 68 | } 69 | 70 | bytes.reverse(); 71 | 72 | return bytes; 73 | }, 74 | 75 | // --> input: Array 76 | // <-- String 77 | encode_base_check: function (version, input, alphabet) { 78 | var buffer = [].concat(version, input); 79 | var check = SHA256(SHA256(buffer)).slice(0, 4); 80 | return Base58Utils.encode_base([].concat(buffer, check), alphabet); 81 | }, 82 | 83 | // --> input : String 84 | // <-- NaN || BigInteger 85 | decode_base_check: function (version, input, alphabet) { 86 | var buffer = Base58Utils.decode_base(input, alphabet); 87 | 88 | if (!buffer || buffer[0] !== version || buffer.length < 5) { 89 | return NaN; 90 | } 91 | 92 | var computed = SHA256(SHA256(buffer.slice(0, -4))).slice(0, 4), 93 | checksum = buffer.slice(-4); 94 | 95 | var i; 96 | for (i = 0; i != 4; i += 1) 97 | if (computed[i] !== checksum[i]) 98 | return NaN; 99 | 100 | return buffer.slice(1, -4); 101 | } 102 | }; 103 | })(); 104 | 105 | module.exports = Base58Utils; 106 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "RippleAdminConsole", 3 | "version": "1.4.0", 4 | "description": "Desktop client for the Ripple payment network", 5 | "author": { 6 | "name": "Ripple, Inc.", 7 | "email": "info@ripple.com", 8 | "url": "https://ripple.com/" 9 | }, 10 | "node-remote": "http://localhost:3000", 11 | "main": "http://localhost:3000", 12 | "single-instance": false, 13 | "window": { 14 | "frame": true, 15 | "toolbar": true, 16 | "width": 1024, 17 | "height": 650, 18 | "min_width": 768, 19 | "min_height": 100 20 | }, 21 | "dependencies": { 22 | "angular": "^1.3.12", 23 | "angular-messages": "^1.3.12", 24 | "angular-route": "^1.3.12", 25 | "angular-ui-bootstrap": "^0.13.3", 26 | "async": "~0.2.10", 27 | "bootstrap": "^3.3.5", 28 | "bootstrap-datetimepicker": "0.0.7", 29 | "extend": "~1.2.1", 30 | "font-awesome": "^4.4.0", 31 | "jade": "~0.35.0", 32 | "jade-l10n": "~0.1.7", 33 | "jade-l10n-loader": "~0.0.3", 34 | "jade-loader": "~0.5.2", 35 | "jquery": "^2.1.4", 36 | "jsdom": "~0.8.11", 37 | "jshint-loader": "~0.5.0", 38 | "json-loader": "~0.5.0", 39 | "json2csv": "^3.0.1", 40 | "lodash": "^3.10.1", 41 | "moment": "^2.8.4", 42 | "ng-sortable": "^1.3.0", 43 | "node-po": "~0.1.2", 44 | "nw-builder": "^2.0.2", 45 | "priorityqueuejs": "^1.0.0", 46 | "ripple-address-codec": "^2.0.1", 47 | "ripple-binary-codec": "0.0.6", 48 | "ripple-lib": "0.13.0-rc14", 49 | "setimmediate": "^1.0.2", 50 | "sjcl": "^1.0.3", 51 | "spin": "0.0.1", 52 | "store": "^1.3.17", 53 | "webpack": "~1.3.7" 54 | }, 55 | "license": "ISC", 56 | "scripts": { 57 | "postinstall": "cd node_modules/sjcl; ./configure --with-all --compress=none; make" 58 | }, 59 | "repository": { 60 | "type": "git", 61 | "url": "git://github.com/ripple/ripple-client.git" 62 | }, 63 | "readmeFilename": "README", 64 | "devDependencies": { 65 | "babel-core": "^5.8.24", 66 | "babel-loader": "^5.3.2", 67 | "browser-sync": "^2.8.2", 68 | "chai": "~1.9.1", 69 | "chai-as-promised": "~4.1.1", 70 | "connect-modrewrite": "^0.8.2", 71 | "del": "^1.2.1", 72 | "delayed-stream": "0.0.5", 73 | "express": "~4.8.7", 74 | "gulp": "^3.9.0", 75 | "gulp-cached": "^1.1.0", 76 | "gulp-changed": "^1.3.0", 77 | "gulp-csso": "^1.0.0", 78 | "gulp-debug": "^2.1.0", 79 | "gulp-filter": "^3.0.1", 80 | "gulp-if": "^1.2.5", 81 | "gulp-jade": "^1.1.0", 82 | "gulp-jade-inheritance": "^0.5.3", 83 | "gulp-jade-l10n-extractor": "^1.0.2", 84 | "gulp-less": "^3.0.3", 85 | "gulp-load-plugins": "^0.10.0", 86 | "gulp-minify-html": "^1.0.4", 87 | "gulp-preprocess": "^1.2.0", 88 | "gulp-rev": "^5.1.0", 89 | "gulp-rev-replace": "^0.4.2", 90 | "gulp-shell": "^0.4.2", 91 | "gulp-size": "^1.3.0", 92 | "gulp-uglify": "^1.2.0", 93 | "gulp-useref": "^1.3.0", 94 | "gulp-watch": "^4.3.5", 95 | "gulp-webpack": "^1.5.0", 96 | "gulp-zip": "^3.0.2", 97 | "karma": "~0.12.16", 98 | "karma-cli": "0.0.4", 99 | "karma-firefox-launcher": "~0.1.3", 100 | "karma-mocha": "~0.1.3", 101 | "karma-sinon-chai": "~0.1.6", 102 | "merge-stream": "^1.0.0", 103 | "mime": "~1.2.11", 104 | "nw": "^0.12.3", 105 | "run-sequence": "^1.1.2" 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/less/ripple/variables.less: -------------------------------------------------------------------------------- 1 | /** 2 | * Overwrite bootstrap variables 3 | */ 4 | 5 | // Components 6 | // ------------------------- 7 | @padding-base-vertical: 10px; 8 | @padding-base-horizontal: 15px; 9 | 10 | /** 11 | * Ripple variables 12 | */ 13 | @font-size-medium: 16px; 14 | @font-size-big: 24px; 15 | 16 | /** 17 | * Ripple colors 18 | */ 19 | @black: #1f1f1e; // This is for all standard text and anywhere there is a 'black'. 20 | @darkgray: #888888; // Gray text on gray background. 21 | @midgray: #ccccca; // Inactive or faded text. Background for inactive buttons. Border at 1px for all buttons and form elements. 22 | @lightgray: #f3f3f3; // Background for sections in RT. Background for UI form elements. 23 | @white: #ffffff; // Anywhere there is white. Includes primary background. Text color on colored buttons 24 | @darkgreen: #1a6834; // Text color on Success messages and 'All Good' information. Text color for integer balance. 25 | @midgreen: #41a447; // Used for green buttons. Used for green text not on a green background. 26 | @lightgreen: #b8dba5; // Background for success messages for all good banners. Used for the decimal and remainder on balance. 27 | @darkyellow: #676027; // Text color on Warning messages and issue information. 28 | @lightyellow: #f2edbf; // Background for Warning messages and issue information. 29 | @midred: #b01e2e; // Text color on Error messages. Button color for red buttons. Any red text. 30 | @lightred: #e9b8b9; // Background for Error messages. 31 | @darkblue: #1e3f78; // General message text color. 32 | @midblue: #346aa9; // Primary brand color. Most buttons. All links outside of messages. 33 | @lightblue: #d1def1; // General message background color. 34 | // @redbtn: #b01e2e; // new red button background. 35 | 36 | /** 37 | * Brand colors 38 | */ 39 | @brand-success: #346aa9; 40 | 41 | @brand-danger: @midred; 42 | /** 43 | * Currency colors 44 | */ 45 | 46 | @color-xrp: @midblue; 47 | @color-usd: #32b450; 48 | @color-usd-dark: @darkgreen; 49 | @color-btc: #dc8246; 50 | @color-eur: #fae632; 51 | @color-eur-dark: darken(@color-eur, 20%); 52 | @color-cny: #c82832; 53 | @color-jpy: #8c466e; 54 | @color-cad: #8264be; 55 | @color-xau: #BF9D3E; 56 | @color-xag: #AAA; 57 | @color-xpt: #A2BDB9; 58 | @color-xpd: #797681; 59 | @color-xal: #5F5F69; 60 | @color-xcu: #9E5502; 61 | @color-xni: #7B625E; 62 | @color-xzn: #9FA09B; 63 | @color-xpb: #363640; 64 | @color-xsn: #2B3E12; 65 | @color-generic1: #3c3ca0; 66 | @color-generic2: #999999; 67 | @color-other: #cccccc; 68 | 69 | 70 | @notification-info-background: @lightblue; 71 | @notification-info-text: @darkblue; 72 | @notification-info-border: @midgray; 73 | 74 | @notification-warning-background: #f3f0c1; 75 | @notification-warning-text: #686128; 76 | @notification-warning-border: @midgray; 77 | 78 | @notification-error-background: #ebbaba; 79 | @notification-error-text: #b0202f; 80 | @notification-error-border: @midgray; 81 | 82 | @notification-success-background: #b8dca6; 83 | @notification-success-text: #176934; 84 | @notification-success-border: @midgray; 85 | 86 | 87 | @state-danger-bg: @notification-error-background; 88 | @state-danger-text: @notification-error-text; 89 | @state-danger-border: @notification-error-border; 90 | 91 | @state-warning-bg: @notification-warning-background; 92 | @state-warning-text: @notification-warning-text; 93 | @state-warning-border: @notification-warning-border; 94 | 95 | @state-success-bg: @notification-success-background; 96 | @state-success-text: @notification-success-text; 97 | @state-success-border: @notification-success-border; 98 | 99 | -------------------------------------------------------------------------------- /src/templates/index.jade: -------------------------------------------------------------------------------- 1 | doctype 2 | 3 | 6 | 9 | 12 | 13 | html.no-js(ng-app='rp', ng-controller='AppCtrl', ng-class='[$route.current.tabClass, $route.current.pageMode]', lang='en') 14 | 15 | 16 | html(ng-app='rp', ng-controller='AppCtrl', ng-class='[$route.current.tabClass, $route.current.pageMode]') 17 | head 18 | meta(charset='utf-8') 19 | title Ripple Admin Console 20 | link(rel='apple-touch-icon', href='img/mobile/icon60.png') 21 | link(rel='apple-touch-icon', sizes='76x76', href='img/mobile/icon76.png') 22 | link(rel='apple-touch-icon', sizes='120x120', href='img/mobile/icon120.png') 23 | link(rel='apple-touch-icon', sizes='152x152', href='img/mobile/icon152.png') 24 | link(rel='icon', sizes='196x196', href='img/mobile/icon196.png') 25 | link(rel='icon', sizes='128x128', href='img/mobile/icon128.png') 26 | meta(name='viewport', content='width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no') 27 | 28 | // build:css css/vendor.css 29 | link(href='../node_modules/font-awesome/css/font-awesome.min.css', rel='stylesheet') 30 | // endbuild 31 | 32 | // build:css css/main.css 33 | link(href='../fonts/stylesheet.css', rel='stylesheet') 34 | link(href='../.tmp/main.css', rel='stylesheet') 35 | // endbuild 36 | 37 | // FAVICON 38 | link(rel='shortcut icon', href='img/favicon.ico', type='image/x-icon') 39 | link(rel='icon', href='img/favicon.ico', type='image/x-icon') 40 | // END FAVICON 41 | 42 | // build:js js/head.js 43 | script(src='../deps/modernizr.js') 44 | // endbuild 45 | 46 | body(ng-class="{loaded: !pageLoading}") 47 | //- TODO make this work div(ng-include="'templates/' + lang + '/client/layout.html'") 48 | div(ng-include="'templates/en/client/layout.html'") 49 | 50 | #appLoading 51 | img.loader(src='img/main.png', alt='', title='') 52 | // VERSION 53 | #version [unprocessed] 54 | #noWebSockets 55 | p We apologize, but the Ripple Admin Console needs JavaScript and WebSockets in order to function. 56 | br 57 | | Please refer to this page and download a browser that supports WebSockets. Thank you! 58 | br 59 | a(href='http://caniuse.com/websockets') http://caniuse.com/websockets 60 | 61 | // JAVASCRIPT 62 | script(src='js/vendor.js') 63 | 64 | // build:js js/deps.js 65 | script(src='../deps/bootstrap-datepicker.js') 66 | // endbuild 67 | 68 | // build:js config.js 69 | script(src='../config.js') 70 | // endbuild 71 | 72 | script. 73 | window.name = 'NG_DEFER_BOOTSTRAP!'; 74 | 75 | 76 | script(type='text/javascript'). 77 | $.getScript('js/app.js'); 78 | 79 | 80 | 81 | script(type='text/javascript'). 82 | $.ajaxSetup({ 83 | cache: true 84 | }); 85 | var version = ''; 86 | $.getScript('js/app.js?v=' + version); 87 | 88 | 89 | // loading state 90 | script. 91 | setTimeout(function(){ $("body").addClass("loading");}, 100); 92 | -------------------------------------------------------------------------------- /deps/sjcl-custom/sjcl-extramath.js: -------------------------------------------------------------------------------- 1 | /* eslint new-cap: [2, {newIsCapExceptions: ["bn"]}] */ 2 | 'use strict'; 3 | var sjcl = require('sjcl'); 4 | 5 | sjcl.bn.ZERO = new sjcl.bn(0); 6 | 7 | /* [ this / that , this % that ] */ 8 | sjcl.bn.prototype.divRem = function(that) { 9 | if (typeof that !== 'object') { 10 | that = new this._class(that); 11 | } 12 | var thisa = this.abs(), thata = that.abs(), quot = new this._class(0), 13 | ci = 0; 14 | if (!thisa.greaterEquals(thata)) { 15 | return [new sjcl.bn(0), this.copy()]; 16 | } else if (thisa.equals(thata)) { 17 | return [new sjcl.bn(1), new sjcl.bn(0)]; 18 | } 19 | 20 | for (; thisa.greaterEquals(thata); ci++) { 21 | thata.doubleM(); 22 | } 23 | for (; ci > 0; ci--) { 24 | quot.doubleM(); 25 | thata.halveM(); 26 | if (thisa.greaterEquals(thata)) { 27 | quot.addM(1); 28 | thisa.subM(that).normalize(); 29 | } 30 | } 31 | return [quot, thisa]; 32 | }; 33 | 34 | /* this /= that (rounded to nearest int) */ 35 | sjcl.bn.prototype.divRound = function(that) { 36 | var dr = this.divRem(that), quot = dr[0], rem = dr[1]; 37 | 38 | if (rem.doubleM().greaterEquals(that)) { 39 | quot.addM(1); 40 | } 41 | 42 | return quot; 43 | }; 44 | 45 | /* this /= that (rounded down) */ 46 | sjcl.bn.prototype.div = function(that) { 47 | var dr = this.divRem(that); 48 | return dr[0]; 49 | }; 50 | 51 | sjcl.bn.prototype.sign = function() { 52 | return this.greaterEquals(sjcl.bn.ZERO) ? 1 : -1; 53 | }; 54 | 55 | /* -this */ 56 | sjcl.bn.prototype.neg = function() { 57 | return sjcl.bn.ZERO.sub(this); 58 | }; 59 | 60 | /* |this| */ 61 | sjcl.bn.prototype.abs = function() { 62 | if (this.sign() === -1) { 63 | return this.neg(); 64 | } 65 | return this; 66 | }; 67 | 68 | /* this >> that */ 69 | sjcl.bn.prototype.shiftRight = function(that) { 70 | if (typeof that !== 'number') { 71 | throw new Error('shiftRight expects a number'); 72 | } 73 | 74 | that = +that; 75 | 76 | if (that < 0) { 77 | return this.shiftLeft(that); 78 | } 79 | 80 | var a = new sjcl.bn(this); 81 | 82 | while (that >= this.radix) { 83 | a.limbs.shift(); 84 | that -= this.radix; 85 | } 86 | 87 | while (that--) { 88 | a.halveM(); 89 | } 90 | 91 | return a; 92 | }; 93 | 94 | /* this >> that */ 95 | sjcl.bn.prototype.shiftLeft = function(that) { 96 | if (typeof that !== 'number') { 97 | throw new Error('shiftLeft expects a number'); 98 | } 99 | 100 | that = +that; 101 | 102 | if (that < 0) { 103 | return this.shiftRight(that); 104 | } 105 | 106 | var a = new sjcl.bn(this); 107 | 108 | while (that >= this.radix) { 109 | a.limbs.unshift(0); 110 | that -= this.radix; 111 | } 112 | 113 | while (that--) { 114 | a.doubleM(); 115 | } 116 | 117 | return a; 118 | }; 119 | 120 | /* (int)this */ 121 | // NOTE Truncates to 32-bit integer 122 | sjcl.bn.prototype.toNumber = function() { 123 | return this.limbs[0] | 0; 124 | }; 125 | 126 | /* find n-th bit, 0 = LSB */ 127 | sjcl.bn.prototype.testBit = function(bitIndex) { 128 | var limbIndex = Math.floor(bitIndex / this.radix); 129 | var bitIndexInLimb = bitIndex % this.radix; 130 | 131 | if (limbIndex >= this.limbs.length) { 132 | return 0; 133 | } 134 | 135 | return (this.limbs[limbIndex] >>> bitIndexInLimb) & 1; 136 | }; 137 | 138 | /* set n-th bit, 0 = LSB */ 139 | sjcl.bn.prototype.setBitM = function(bitIndex) { 140 | var limbIndex = Math.floor(bitIndex / this.radix); 141 | var bitIndexInLimb = bitIndex % this.radix; 142 | 143 | while (limbIndex >= this.limbs.length) { 144 | this.limbs.push(0); 145 | } 146 | 147 | this.limbs[limbIndex] |= 1 << bitIndexInLimb; 148 | 149 | this.cnormalize(); 150 | 151 | return this; 152 | }; 153 | 154 | sjcl.bn.prototype.modInt = function(n) { 155 | return this.toNumber() % n; 156 | }; 157 | -------------------------------------------------------------------------------- /src/js/data/iso4217.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | // Ripple Credits 3 | "XRP":[0, 0], 4 | 5 | // Official ISO-4217 6 | "AFN":[971, 2], 7 | "EUR":[978, 2], 8 | "ALL":[8, 2], 9 | "DZD":[12, 2], 10 | "USD":[840, 2], 11 | "AOA":[973, 2], 12 | "XCD":[951, 2], 13 | "ARS":[32, 2], 14 | "AMD":[51, 2], 15 | "AWG":[533, 2], 16 | "AUD":[36, 2], 17 | "AZN":[944, 2], 18 | "BSD":[44, 2], 19 | "BHD":[48, 3], 20 | "BDT":[50, 2], 21 | "BBD":[52, 2], 22 | "BYR":[974, 0], 23 | "BZD":[84, 2], 24 | "XOF":[952, 0], 25 | "BMD":[60, 2], 26 | "BTN":[64, 2], 27 | "INR":[356, 2], 28 | "BOB":[68, 2], 29 | "BOV":[984, 2], 30 | "BAM":[977, 2], 31 | "BWP":[72, 2], 32 | "NOK":[578, 2], 33 | "BRL":[986, 2], 34 | "BND":[96, 2], 35 | "BGN":[975, 2], 36 | "BIF":[108, 0], 37 | "KHR":[116, 2], 38 | "XAF":[950, 0], 39 | "CAD":[124, 2], 40 | "CVE":[132, 2], 41 | "KYD":[136, 2], 42 | "CLF":[990, 0], 43 | "CLP":[152, 0], 44 | "CNY":[156, 2], 45 | "COP":[170, 2], 46 | "COU":[970, 2], 47 | "KMF":[174, 0], 48 | "CDF":[976, 2], 49 | "NZD":[554, 2], 50 | "CRC":[188, 2], 51 | "HRK":[191, 2], 52 | "CUC":[931, 2], 53 | "CUP":[192, 2], 54 | "ANG":[532, 2], 55 | "CZK":[203, 2], 56 | "DKK":[208, 2], 57 | "DJF":[262, 0], 58 | "DOP":[214, 2], 59 | "EGP":[818, 2], 60 | "SVC":[222, 2], 61 | "ERN":[232, 2], 62 | "ETB":[230, 2], 63 | "FKP":[238, 2], 64 | "FJD":[242, 2], 65 | "XPF":[953, 0], 66 | "GMD":[270, 2], 67 | "GEL":[981, 2], 68 | "GHS":[936, 2], 69 | "GIP":[292, 2], 70 | "GTQ":[320, 2], 71 | "GBP":[826, 2], 72 | "GNF":[324, 0], 73 | "GYD":[328, 2], 74 | "HTG":[332, 2], 75 | "HNL":[340, 2], 76 | "HKD":[344, 2], 77 | "HUF":[348, 2], 78 | "ISK":[352, 0], 79 | "IDR":[360, 2], 80 | "IRR":[364, 2], 81 | "IQD":[368, 3], 82 | "ILS":[376, 2], 83 | "JMD":[388, 2], 84 | "JPY":[392, 0], 85 | "JOD":[400, 3], 86 | "KZT":[398, 2], 87 | "KES":[404, 2], 88 | "KPW":[408, 2], 89 | "KRW":[410, 0], 90 | "KWD":[414, 3], 91 | "KGS":[417, 2], 92 | "LAK":[418, 2], 93 | "LVL":[428, 2], 94 | "LBP":[422, 2], 95 | "LSL":[426, 2], 96 | "ZAR":[710, 2], 97 | "LRD":[430, 2], 98 | "LYD":[434, 3], 99 | "CHF":[756, 2], 100 | "LTL":[440, 2], 101 | "MOP":[446, 2], 102 | "MKD":[807, 2], 103 | "MGA":[969, 2], 104 | "MWK":[454, 2], 105 | "MYR":[458, 2], 106 | "MVR":[462, 2], 107 | "MRO":[478, 2], 108 | "MUR":[480, 2], 109 | "MXN":[484, 2], 110 | "MXV":[979, 2], 111 | "MDL":[498, 2], 112 | "MNT":[496, 2], 113 | "MAD":[504, 2], 114 | "MZN":[943, 2], 115 | "MMK":[104, 2], 116 | "NAD":[516, 2], 117 | "NPR":[524, 2], 118 | "NIO":[558, 2], 119 | "NGN":[566, 2], 120 | "OMR":[512, 3], 121 | "PKR":[586, 2], 122 | "PAB":[590, 2], 123 | "PGK":[598, 2], 124 | "PYG":[600, 0], 125 | "PEN":[604, 2], 126 | "PHP":[608, 2], 127 | "PLN":[985, 2], 128 | "QAR":[634, 2], 129 | "RON":[946, 2], 130 | "RUB":[643, 2], 131 | "RWF":[646, 0], 132 | "SHP":[654, 2], 133 | "WST":[882, 2], 134 | "STD":[678, 2], 135 | "SAR":[682, 2], 136 | "RSD":[941, 2], 137 | "SCR":[690, 2], 138 | "SLL":[694, 2], 139 | "SGD":[702, 2], 140 | "SBD":[90, 2], 141 | "SOS":[706, 2], 142 | "SSP":[728, 2], 143 | "LKR":[144, 2], 144 | "SDG":[938, 2], 145 | "SRD":[968, 2], 146 | "SZL":[748, 2], 147 | "SEK":[752, 2], 148 | "CHE":[947, 2], 149 | "CHW":[948, 2], 150 | "SYP":[760, 2], 151 | "TWD":[901, 2], 152 | "TJS":[972, 2], 153 | "TZS":[834, 2], 154 | "THB":[764, 2], 155 | "TOP":[776, 2], 156 | "TTD":[780, 2], 157 | "TND":[788, 3], 158 | "TRY":[949, 2], 159 | "TMT":[934, 2], 160 | "UGX":[800, 0], 161 | "UAH":[980, 2], 162 | "AED":[784, 2], 163 | "USN":[997, 2], 164 | "USS":[998, 2], 165 | "UYI":[940, 0], 166 | "UYU":[858, 2], 167 | "UZS":[860, 2], 168 | "VUV":[548, 0], 169 | "VEF":[937, 2], 170 | "VND":[704, 0], 171 | "YER":[886, 2], 172 | "ZMK":[894, 2], 173 | "ZWL":[932, 2] 174 | } 175 | -------------------------------------------------------------------------------- /src/less/ripple/pagemodes.less: -------------------------------------------------------------------------------- 1 | // Desktop 2 | @pmSingleMargin: 60px; 3 | @pmPanelLeftMargin: 40px; 4 | @pmPanelRightMargin: 10px; 5 | @pmPanelLeftMarginWide: @pmSingleMargin; 6 | @pmPanelRightMarginWide: @pmSingleMargin - 15px; 7 | 8 | // Phone 9 | @pmSingleMarginPhone: 5px; 10 | @pmPanelLeftMarginPhone: 5px; 11 | @pmPanelRightMarginPhone: 5px; 12 | @pmPanelLeftMarginWidePhone: @pmSingleMarginPhone; 13 | @pmPanelRightMarginWidePhone: @pmSingleMarginPhone; 14 | 15 | .modal { 16 | .modal-body { 17 | // padding: 25px 15px 25px; 18 | } 19 | .modal-footer{ 20 | .unlock-btns-wrapper { 21 | .modal-close-btn{ 22 | width: 16%; 23 | } 24 | .modal-submit-btn { 25 | width: 25%; 26 | } 27 | @media (max-width: @screen-xs-max) { 28 | .modal-close-btn{ 29 | width: 24%; 30 | } 31 | .modal-submit-btn { 32 | width: 73%; 33 | margin-left: 0; 34 | } 35 | 36 | } 37 | @media (max-width: @screen-sm-max) and (min-width: @screen-xs-max) { 38 | .modal-close-btn{ 39 | width: 16.666666666666664%; 40 | } 41 | .modal-submit-btn { 42 | width: 33.33333333333333%; 43 | } 44 | } 45 | 46 | 47 | } 48 | } 49 | .question { 50 | color: @black; 51 | text-align: center; 52 | margin: 0 0 0 10px; 53 | font-size: 15px; 54 | } 55 | 56 | .actions { 57 | margin: 40px 0 0 0; 58 | text-align: center; 59 | 60 | .btn { 61 | margin: 0 5px; 62 | padding: 10px 30px; 63 | } 64 | //media for md size 65 | @media (min-width: @screen-md) { 66 | .delete-contact-btn { 67 | width: 45%; 68 | margin: 0; 69 | } 70 | .cancel-btn { 71 | width: 15%; 72 | margin: 0; 73 | margin-left: 5%; 74 | padding-left: 18px; 75 | } 76 | } 77 | // media for sm size 78 | @media (max-width: @screen-sm-max) and (min-width: @screen-xs-max) { 79 | .delete-contact-btn { 80 | width: 30%; 81 | margin: 0; 82 | } 83 | .cancel-btn { 84 | width: 15%; 85 | margin: 0; 86 | margin-left: 5%; 87 | padding-left: 18px; 88 | } 89 | 90 | } 91 | // media for xs size 92 | @media (max-width: @screen-xs-max) { 93 | .delete-contact-btn { 94 | width: 60%; 95 | margin: 0; 96 | } 97 | .cancel-btn { 98 | width: 30%; 99 | margin: 0; 100 | margin-left: 5%; 101 | } 102 | 103 | } 104 | a { 105 | margin: 0 5px; 106 | } 107 | } 108 | } 109 | .modal-backdrop { 110 | opacity: 0.8 !important; 111 | } 112 | 113 | /* A common class for all data-driven pages */ 114 | .ddpage { 115 | font-size: 13px; 116 | 117 | p.hash { 118 | overflow: hidden; 119 | text-overflow: ellipsis; 120 | } 121 | 122 | p.type { 123 | background-color: @lightgray; 124 | font-size: 18px; 125 | padding-top: 10px; 126 | padding-bottom: 10px; 127 | padding-left: 10px; 128 | color: @darkgray; 129 | 130 | strong { 131 | font-weight: normal; 132 | font-family: "OpenSansRegular"; 133 | padding-left: 10px; 134 | color: @black; 135 | } 136 | } 137 | 138 | dl.details { 139 | .clearfix; 140 | dt { 141 | font-family: "OpenSansRegular"; 142 | color: @darkgray; 143 | clear: left; 144 | float: left; 145 | width: 120px; 146 | margin: 0; 147 | padding: 8px 0; 148 | @media (max-width: @screen-xs-max) { 149 | width: 100%; 150 | } 151 | } 152 | dd { 153 | float: left; 154 | margin: 0; 155 | padding: 8px 0; 156 | color: @black; 157 | } 158 | 159 | &.half { 160 | float: left; 161 | width: 450px; 162 | &:first-child { 163 | padding-right: 0; 164 | } 165 | &:last-child { 166 | padding-right: 0; 167 | } 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/templates/tabs/balance.jade: -------------------------------------------------------------------------------- 1 | section.col-xs-12.content(ng-controller="BalanceCtrl") 2 | //- Disconnected 3 | 4 | group.disconnected(ng-hide="connected") 5 | p.literal(l10n) You have to be online to see this screen. 6 | span(ng-show="onlineMode") Please make sure you are connected to the Internet and you have configured the correct 7 | a(href='#advanced', l10n-inc) servers 8 | | . 9 | 10 | group.disconnected(ng-hide="!connected || loadState.account") 11 | p.literal(l10n) Loading... 12 | 13 | //- Connected 14 | //- Connected 15 | div(ng-show='!loadingAccount && !account.Balance && loadState.account && connected') 16 | include ../tabs/banner/_unfunded 17 | 18 | group(ng-show="connected && loadState.account") 19 | .row 20 | .col-xs-12 21 | .currency-summary 22 | .balancebox.currency-xrp 23 | .total.row.row-padding-small 24 | .lbl.col-xs-8 25 | a(href="", ng-click="entry.show=!entry.show") 26 | i.icon-ripple-logo 27 | | XRP – Ripple 28 | .balance.col-xs-2 29 | span(ng-show="account.Balance") {{ account.Balance | rpamount:{precision: 20, min_precision: 5, max_sig_digits: 20} }} 30 | span(ng-hide="account.Balance") 0 31 | .component.row.row-padding-small(ng-show="entry.show") 32 | .lbl.col-xs-8 33 | span(l10n) Available 34 | i.fa.fa-question-circle( 35 | rp-popover 36 | rp-popover-placement="bottom" 37 | l10n-rp-popover-title="Available amount" 38 | rp-popover-trigger="hover" 39 | l10n-data-content="Total amount of XRP in your account minus the reserve amount.") 40 | .balance.col-xs-2 41 | span(ng-show="account.max_spend") {{account.max_spend | rpamount:{precision: 20 , min_precision: 6, max_sig_digits: 20} }} 42 | span(ng-hide="account.max_spend") 0 43 | .component.row.row-padding-small(ng-show="entry.show") 44 | .lbl.col-xs-8 45 | span(l10n) Reserve 46 | i.fa.fa-question-circle( 47 | rp-popover 48 | rp-popover-placement="bottom" 49 | l10n-rp-popover-title="Reserve amount" 50 | rp-popover-trigger="hover" 51 | l10n-data-content="Minimum amount of XRP required to fund your account. You cannot spend the reserve.") 52 | .balance.col-xs-2 53 | span(ng-show="account.reserve") {{ account.reserve | rpamount:{precision: 20, min_precision: 6, max_sig_digits: 20} }} 54 | span(ng-hide="account.reserve") 0 55 | //- TODO Determine currencies order 56 | .balancebox.currency-non-native(ng-repeat="entry in balances", ng-class="'currency-' + (entry.total | rpcurrency | lowercase)") 57 | .total.row.row-padding-small 58 | .lbl.col-xs-8 59 | a(href="", ng-click="entry.show=!entry.show") 60 | i(class="icon fa fa-money", ng-class="'fa-' + (entry.total | rpcurrency | lowercase)") 61 | span(rp-currency="entry.total", rp-currency-full) 62 | .balance.col-xs-2 63 | a(href="", ng-click="entry.show=!entry.show") {{ entry.total | rpamount:{precision: 20, min_precision: 5, max_sig_digits: 20} }} 64 | //- TODO Remember selections 65 | .component.row.row-padding-small(ng-repeat="(issuer, component) in entry.components", ng-show="entry.show") 66 | .head 67 | .lbl.col-xs-8(ng-show="component.gateway.app") {{component.gateway.app.name}} 68 | span.status.unverified(ng-hide="true", l10n) Unverified 69 | .lbl.balance-text-wrapper.col-xs-8(ng-hide="component.gateway.app", rp-pretty-identity="issuer" 70 | rp-pretty-issuer-contacts="userBlob.data.contacts") 71 | .balance.col-xs-2 {{ component | rpamount:{precision: 20, min_precision: 6, max_sig_digits: 20} }} -------------------------------------------------------------------------------- /src/templates/tabs/accountflags.jade: -------------------------------------------------------------------------------- 1 | section.col-xs-12.content(ng-controller="AccountFlagsCtrl") 2 | div(ng-show="!loadingAccount && !account.Balance && loadState.account && connected && !debug") 3 | include ../tabs/banner/_unfunded 4 | 5 | .notification-wrapper 6 | div(ng-repeat="(flag, opts) in flags") 7 | .alert.alert-info(ng-show="opts.saving && onlineMode", l10n) Updating {{flag}} flag... 8 | .alert.alert-success(ng-show="notif == flag + 'Updated' && onlineMode", l10n) {{flag}} flag updated 9 | .alert.alert-danger(ng-show="notif == flag + 'Failed' && onlineMode", l10n) {{flag}} flag update failed. {{opts.engine_result}}: {{opts.engine_result_message}} 10 | 11 | .row 12 | .col-sm-3 13 | include settings/_navbar 14 | .col-sm-9.list(ng-show="!loadingAccount && !account.Balance && loadState.account && connected") 15 | .unfunded.literal(l10n) 16 | | Your account has to be 17 | a(href="https://ripple.com/knowledge_center/activating-your-wallet/", target="_blank", l10n-inc) activated 18 | | before you can see this page. 19 | .col-sm-9.list(ng-show='((connected && account.Balance) || !onlineMode)') 20 | 21 | //- Offline Flag Change 22 | div(ng-show='offlineSettingsChange') 23 | signed-transaction(data="{{signedTransaction}}", txJSON="{{txJSON}}", hash="{{hash}}") 24 | section.content#gateways 25 | h4(l10n) Account Flags 26 | div(ng-repeat="(flag, opts) in flags", ng-controller="FlagCtrl") 27 | // UI for online mode 28 | form.trust-line-form(name="accountsAdvForm", ng-submit='save()', ng-show="onlineMode") 29 | .descriptor {{opts.description}} 30 | .row.accountflags-form-row(ng-hide="opts.edit") 31 | .col-sm-3 32 | .description(l10n) {{flag}} 33 | .col-sm-6 34 | .description(ng-show="opts.enabled", l10n) Enabled 35 | .description(ng-hide="opts.enabled", l10n) Disabled 36 | .description(ng-show="opts.saving") 37 | img(src="img/button-s.png", class="loader") 38 | .col-sm-3 39 | a.btn.btn-cancel.btn-block(href="", ng-click="opts.edit = true", ng-hide="edit.saving", l10n) edit 40 | .row.row-padding-small.accountflags-form-row(ng-show="opts.edit") 41 | .col-sm-3 42 | .description(l10n) {{flag}} 43 | .col-sm-4 44 | div.helperInput 45 | label 46 | input(type="checkbox", name="acct_adv", ng-model="opts.newEnabled", ng-disabled="opts.saving") 47 | span(l10n) Enable 48 | .col-sm-3 49 | button.btn.btn-block.btn-success.btn-xs.submit#save(type='submit' 50 | ng-disabled='serverForm.$invalid || opts.saving', l10n) 51 | span(ng-hide="opts.saving") Save 52 | span(ng-show="opts.saving") Saving... 53 | .col-sm-2 54 | a.btn.btn-cancel.btn-block(href="", ng-click='cancel()', ng-hide="opts.saving", l10n) cancel 55 | 56 | // UI for offline mode 57 | form.trust-line-form(name="accountsAdvForm", ng-show="!onlineMode") 58 | .descriptor(l10n) {{opts.description}} 59 | .row.accountflags-form-row(ng-hide="opts.edit") 60 | .col-sm-3 61 | .description(l10n) {{flag}} 62 | .col-sm-6 63 | .col-sm-3 64 | a.btn.btn-cancel.btn-block(href="", ng-click="opts.edit = true", l10n) edit 65 | .row.accountflags-form-row(ng-show="opts.edit") 66 | .col-sm-3 67 | .description(l10n) {{flag}} 68 | .col-sm-3 69 | a.btn.btn-success.btn-block(href="", ng-click='saveOffline("add")', ng-disabled='!$root.sequence || !$root.fee', l10n) Add Flag 70 | .col-sm-3 71 | a.btn.btn-danger.btn-block(href="", ng-click='saveOffline("remove")', ng-disabled='!$root.sequence || !$root.fee', l10n) Remove Flag 72 | .col-sm-3 73 | a.btn.btn-cancel.btn-block(href="", ng-click='cancel()', l10n) cancel -------------------------------------------------------------------------------- /src/templates/tabs/coldwallet.jade: -------------------------------------------------------------------------------- 1 | container(ng-controller="ColdWalletCtrl") 2 | group.error(ng-show="accountError") {{accountError}} 3 | hr 4 | 5 | group.disconnected(ng-hide="connected") 6 | p.literal(l10n) You have to be online to see this screen 7 | 8 | group(ng-if="connected && accountLoaded", ng-hide="accountError") 9 | div.row.row-content 10 | div.col-xs-12.col-sm-6 11 | div.panel.panel-primary 12 | div.panel-heading 13 | h3.panel-title Account: {{address}} 14 | div.panel-body 15 | dl.dl-horizontal 16 | dt Sequence Number 17 | dd {{sequenceNumber}} 18 | dt Network Fee 19 | dd {{networkFee}} XRP 20 | dt Regular Key Enabled 21 | dd {{regularKeyEnabled}} 22 | //div.col-xs-12.col-sm-7 23 | div.well 24 | p(ng-hide="transactionError") {{lastTxn}} The transaction was validated at {{txnTime | date:'yyyy-MM-dd HH:mm:ss Z'}}. 25 | p.error(ng-show="transactionError") {{transactionErrorMessage}} 26 | div.row.row-content 27 | div.col-xs-12 28 | h4 Account Flags 29 | i.fa.fa-question-circle.help-icon( 30 | rp-popover 31 | rp-popover-placement="right" 32 | l10n-rp-popover-title="Setup Information" 33 | rp-popover-trigger="click" 34 | l10n-data-content="Best practices for these account flags may be found here.") 35 | div.table-responsive 36 | table.table.table-striped.table-bordered 37 | thead 38 | tr 39 | th Setting 40 | th Enabled 41 | th Description 42 | tbody 43 | tr(ng-repeat="line in accountInfo") 44 | td {{line.setting}} 45 | td {{line.enabled}} 46 | td {{line.description}} 47 | div.row.row-content 48 | div.col-xs-12 49 | h4 Balances 50 | div.table-responsive 51 | table.table.table-striped.table-bordered 52 | thead 53 | tr 54 | th Currency 55 | th Counterparty 56 | th Value 57 | tbody 58 | tr 59 | td XRP 60 | td 61 | td {{xrpBalance}} 62 | tr.available 63 | td Available 64 | td 65 | td {{ max_spend | rpamount:{precision: 6} }} 66 | tr.reserve 67 | td Reserve 68 | td 69 | td {{ reserve | rpamount:{precision: 6} }} 70 | tr(ng-repeat="balance in lines") 71 | td {{balance.currency}} 72 | td {{balance.account}} 73 | td {{balance.balance}} 74 | div.row.row-content(ng-hide="!warningLines.length") 75 | div.col-xs-12 76 | h4 Trust Lines with warnings 77 | div.table-responsive 78 | table.table.table-striped.table-bordered 79 | thead.danger 80 | tr 81 | th Counterparty 82 | th Currency 83 | th Rippling 84 | th Authorized 85 | th Warning 86 | tbody 87 | tr(ng-repeat="line in warningLines") 88 | td {{line.account}} 89 | td {{line.currency}} 90 | td(ng-if="!line.no_ripple") Enabled 91 | td(ng-if="line.no_ripple") Disabled 92 | td {{line.authorized}} 93 | td(ng-if="line.warning1 && line.warning2") {{line.warning1}}
{{line.warning2}} 94 | td(ng-if="line.warning1 && !line.warning2") {{line.warning1}} 95 | td(ng-if="line.warning2 && !line.warning1") {{line.warning2}} 96 | div.row.row-content 97 | .col-xs-6.col-sm-2 98 | button.btn.btn-success.btn-block(ng-click='refresh()') Refresh 99 | .col-xs-6.col-sm-2 100 | a.btn.btn-success.btn-block(href="#/login") Back to login 101 | -------------------------------------------------------------------------------- /test/unit/tabs/exchangeTabSpec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | describe('ExchangeCtrl', function() { 4 | var scope, dependencies, ctrl, network; 5 | 6 | beforeEach(module("rp")); 7 | beforeEach(inject(function ($rootScope, $controller, rpNetwork) { 8 | scope = $rootScope.$new(); 9 | network = rpNetwork; 10 | 11 | scope.currencies_all = [ 12 | { name: 'Ripples', value: 'XRP'}, 13 | { name: 'US Dollar', value: 'USD'} 14 | ]; 15 | 16 | scope.currencies_all_keyed = { 17 | "XRP": { 18 | "value": "XRP", 19 | "name": "Ripples", 20 | "order": 5 21 | }, 22 | "USD": { 23 | "value": "USD", 24 | "name": "US Dollar", 25 | "order": 4 26 | }, 27 | "BTC": { 28 | "value": "BTC", 29 | "name": "Bitcoin", 30 | "order": 2 31 | }, 32 | "XAU": { 33 | "value": "XAU", 34 | "name": "Gold", 35 | "order": 0 36 | } 37 | }; 38 | 39 | dependencies = { 40 | $scope: scope, 41 | $element: null, 42 | $network: rpNetwork, 43 | rpId: { 44 | loginStatus: true, 45 | account: 'r4EwBWxrx5HxYRyisfGzMto3AT8FZiYdWk' 46 | } 47 | }; 48 | 49 | 50 | ctrl = $controller("ExchangeCtrl", dependencies); 51 | })); 52 | 53 | it('should be initialized with defaults', function (done) { 54 | assert.isObject(scope.xrp); 55 | assert.strictEqual(scope.xrp.name, 'XRP - Ripples'); 56 | assert.strictEqual(scope.xrp.code, 'XRP'); 57 | assert.isObject(scope.xrp.currency); 58 | done(); 59 | }); 60 | 61 | it('should update currency_choices after setting trust lines', function(done) { 62 | 63 | scope.lines = { 64 | "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59BUSD": { 65 | "account": "rvYAfWj5gh67oV6fW32ZzP3Aw4Eubs59B", 66 | "currency": "USD" 67 | }, 68 | "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2qUSD": { 69 | "account": "rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q", 70 | "currency": "USD" 71 | }, 72 | "rhXzSyt1q9J8uiFXpK3qSugAAPJKXLtnrFUSD": { 73 | "account": "rhXzSyt1q9J8uiFXpK3qSugAAPJKXLtnrF", 74 | "currency": "USD" 75 | }, 76 | "rs9M85karFkCRjvc6KMWn8Coigm9cbcgcx015841551A748AD2C1F76FF6ECB0CCCD00000000": { 77 | "account": "rs9M85karFkCRjvc6KMWn8Coigm9cbcgcx", 78 | "currency": "015841551A748AD2C1F76FF6ECB0CCCD00000000" 79 | } 80 | }; 81 | 82 | // kicks off watches 83 | scope.$apply(); 84 | 85 | assert.strictEqual(scope.currency_choices[0], 'XRP - Ripples'); 86 | 87 | done(); 88 | }); 89 | 90 | it('should update paths after entering 2 as amount for XRP', function(done) { 91 | 92 | scope.exchange.amount = "2"; 93 | var spy = sinon.spy(scope, 'reset_paths'); 94 | scope.$apply(); 95 | 96 | assert(spy.called); 97 | assert.strictEqual(scope.exchange.alternatives.length, 0); 98 | assert.strictEqual(scope.exchange.amount_feedback.to_text_full(), "2/XRP"); 99 | 100 | done(); 101 | }); 102 | 103 | it('should update paths after changing currency choice', function(done) { 104 | 105 | var spy = sinon.spy(scope, 'update_exchange'); 106 | scope.exchange.currency_name = "USD"; 107 | 108 | scope.$apply(); 109 | 110 | assert(spy.called); 111 | 112 | assert.isObject(scope.exchange.currency_obj); 113 | assert.strictEqual(scope.exchange.currency_code, 'USD'); 114 | assert.strictEqual(scope.exchange.currency_name, 'USD - US Dollar'); 115 | 116 | assert.strictEqual(scope.exchange.path_status, 'waiting'); 117 | 118 | done(); 119 | }); 120 | 121 | 122 | it('should update paths after entering 2 as amount for XRP', function(done) { 123 | 124 | scope.exchange.amount = "0.001"; 125 | scope.exchange.currency_name = "USD"; 126 | var spy = sinon.spy(scope, 'reset_paths'); 127 | scope.$apply(); 128 | 129 | assert(spy.called); 130 | assert.strictEqual(scope.exchange.alternatives.length, 0); 131 | assert.strictEqual(scope.exchange.amount_feedback.to_text_full(), "0.001/USD/r4EwBWxrx5HxYRyisfGzMto3AT8FZiYdWk"); 132 | 133 | done(); 134 | }); 135 | 136 | 137 | 138 | }); -------------------------------------------------------------------------------- /src/less/ripple/forms.less: -------------------------------------------------------------------------------- 1 | // Common form controls 2 | // 3 | // Shared size and type resets for form controls. Apply `.form-control` to any 4 | // of the following form controls: 5 | // 6 | // select 7 | // textarea 8 | // input[type="text"] 9 | // input[type="password"] 10 | // input[type="datetime"] 11 | // input[type="datetime-local"] 12 | // input[type="date"] 13 | // input[type="month"] 14 | // input[type="time"] 15 | // input[type="week"] 16 | // input[type="number"] 17 | // input[type="email"] 18 | // input[type="url"] 19 | // input[type="search"] 20 | // input[type="tel"] 21 | // input[type="color"] 22 | 23 | .call-to-action { 24 | margin-top: 20px; 25 | } 26 | 27 | .form-control { 28 | font-size: @font-size-medium; 29 | .form-control-focus(@darkgray); 30 | margin-bottom: 20px; 31 | @media (max-width: @screen-xs-max) { 32 | font-size: 13px; 33 | } 34 | } 35 | 36 | .btn { 37 | &.spinner { 38 | position: relative; 39 | .spinnerInner { 40 | position: absolute !important; 41 | left: 23px !important; 42 | top: 22px !important; 43 | } 44 | } 45 | } 46 | 47 | label { 48 | font-weight: normal; 49 | } 50 | 51 | form { 52 | .errorGroup { 53 | position: relative; 54 | z-index: 1; 55 | height: 0; 56 | overflow: visible; 57 | top: -20px; 58 | left: 0; 59 | } 60 | .input-group + .errorGroup { 61 | position: inherit; 62 | height: auto; 63 | } 64 | 65 | .error, 66 | err { 67 | color: @midred; 68 | font-size: 13px; 69 | height: 23px; 70 | } 71 | 72 | .field-hint { 73 | font-size: 0.87em; 74 | color: @darkgray; 75 | } 76 | 77 | .notice { 78 | color: @lightyellow; 79 | } 80 | 81 | .success { 82 | color: @darkgreen; 83 | font-size: 13px; 84 | } 85 | 86 | .amount input, .amount select { 87 | vertical-align: bottom; 88 | } 89 | 90 | .amount .value { 91 | margin-right: 10px; 92 | } 93 | 94 | .helperInput { 95 | input { 96 | display: inline; 97 | margin: 0 5px 0 0; 98 | } 99 | label { 100 | display: inline; 101 | font-size: 12px; 102 | } 103 | } 104 | } 105 | 106 | .submit-btn-container { 107 | margin: 30px auto 20px; 108 | width: 50%; 109 | 110 | .btn { 111 | font-size: 16px; 112 | min-height: 60px; 113 | } 114 | 115 | .btn.spinner { 116 | padding: 0; 117 | 118 | .spinnerInner { 119 | top: 30px !important; 120 | left: 30px !important; 121 | } 122 | } 123 | } 124 | 125 | // Undo default Bootstrap invalid style for Angular-managed forms 126 | input.ng-invalid:focus:required:invalid, 127 | textarea.ng-invalid:focus:required:invalid, 128 | select.ng-invalid:focus:required:invalid { 129 | // color: @darkgray; 130 | // border-color: @darkgray; 131 | // .box-shadow(~"inset 0 1px 1px rgba(0,0,0,.075), 0 0 10px rgba(0,0,0,.2)"); 132 | } 133 | 134 | // Only mark field invalid that have been edited 135 | input.ng-invalid.ng-dirty { 136 | color: @midred; 137 | border-color: darken(@midred, 10%); 138 | 139 | &:focus { 140 | @shadow: 0 0 6px lighten(@midred, 20%); 141 | .box-shadow(@shadow); 142 | } 143 | } 144 | 145 | // De-emphasize submit button if form invalid 146 | // 147 | // This is better than just disabling the button because this way we can handle 148 | // a click on the button. If the user tries to submit, we can detect this and 149 | // then show them *why* the form is not yet valid. 150 | form.ng-invalid .btn-success { 151 | background: @midgray; 152 | border-color: @midgray; 153 | } 154 | 155 | .inputSpinner { 156 | position: absolute; 157 | top: 20px; 158 | right: 20px; 159 | } 160 | 161 | .spinnerEnabledInput { 162 | position: relative; 163 | } 164 | 165 | .txtbtn { 166 | display: inline-block; 167 | padding: 10px; 168 | } 169 | 170 | .input-margin-group { 171 | margin-bottom: 20px; 172 | } 173 | 174 | .radio, 175 | .checkbox { 176 | display: block; 177 | min-height: @line-height-computed; // clear the floating input if there is no label text 178 | margin-top: 10px; 179 | margin-bottom: 10px; 180 | padding-left: 20px; 181 | vertical-align: middle; 182 | label { 183 | display: inline; 184 | margin-bottom: 0; 185 | font-weight: normal; 186 | cursor: pointer; 187 | } 188 | } -------------------------------------------------------------------------------- /src/templates/tabs/login.jade: -------------------------------------------------------------------------------- 1 | section.col-xs-12.content(ng-controller="LoginCtrl") 2 | 3 | .row.options 4 | .col-md-offset-2.col-md-8 5 | ul.nav.nav-pills 6 | li(ng-class="{active: mode=='open'}") 7 | a(href="", ng-click="mode='open'") Open Account 8 | li(ng-class="{active: mode=='readOnly'}") 9 | a(href="", ng-click="mode='readOnly'") Read Only Mode 10 | li(ng-class="{active: mode=='coldwallet'}") 11 | a(href="", ng-click="mode='coldwallet'") Cold Wallet 12 | li(ng-class="{active: mode=='submittxn'}") 13 | a(href="#/submit", ng-click="mode='submittxn'") Submit Txn 14 | li(ng-class="{active: mode=='serversettings'}") 15 | a(href="#/advanced", ng-click="mode='serversettings'") Server Settings 16 | 17 | .row.auth-form-container(ng-show="mode=='open'") 18 | .col-md-offset-2.col-md-8 19 | .auth-form-wrapper 20 | form(name='loginForm', ng-disabled="loginForm.$invalid || ajax_loading", , ng-submit='submitForm()') 21 | h2(l10n) Open account 22 | 23 | .form-group 24 | a.btn.btn-block.btn-link(href='#register', l10n) Create new account 25 | 26 | .form-group 27 | a.btn.btn-block.btn-default#walletfile(ng-click='fileInputClick()', l10n) Select{{walletfile ? "ed" : ""}} account file {{walletfile}} 28 | 29 | .form-group.hide 30 | label(for='login_walletfile', l10n) Select account file 31 | input.form-control#login_walletfile(name='login_walletfile', type="file" 32 | ng-model='walletfile') 33 | .form-group 34 | label(for='login_password', l10n) Account password 35 | input.form-control#login_password(name='login_password', type='password' 36 | ng-model='password', rp-focus-on-empty, required) 37 | .text-error(ng-show='error') 38 | br 39 | span {{error}} 40 | .text-status(ng-show='status') 41 | span {{status}} 42 | 43 | .backend(ng-repeat="message in backendMessages") 44 | b {{message.backend}} 45 | span {{message.message}} 46 | .submit-btn-container 47 | button.btn.btn-submit.btn-block.btn-success(type='submit', ng-disabled="loginForm.$invalid || ajax_loading", rp-spinner="{{ajax_loading ? 4 : null}}") 48 | span(l10n) Open account 49 | 50 | .row.auth-form-container(ng-show="mode=='readOnly'") 51 | .col-md-offset-2.col-md-8 52 | .auth-form-wrapper 53 | form(name='readOnlyForm', ng-disabled="readOnlyForm.$invalid || ajax_loading", ng-submit='submitReadOnlyForm()') 54 | h2(l10n) Open account 55 | 56 | .form-group 57 | label(for='readOnlyAddress', l10n) Enter Ripple Address 58 | input.form-control(name='readOnly', ng-model='readOnly' 59 | rp-focus-on-empty, required, rp-dest, rp-dest-address) 60 | br 61 | .errorGroup(rp-errors='readOnly') 62 | .error(rp-error-on='rpDest', l10n) 63 | | Input should be a valid Ripple address. 64 | 65 | .submit-btn-container 66 | button.btn.btn-submit.btn-block.btn-success(type='submit', ng-disabled="readOnlyForm.$invalid || ajax_loading", rp-spinner="{{ajax_loading ? 4 : null}}") 67 | span(l10n) Open account 68 | 69 | .row.auth-form-container(ng-show="mode=='coldwallet'") 70 | .col-md-offset-2.col-md-8 71 | .auth-form-wrapper 72 | form(name='coldWalletForm', ng-disabled="coldWalletForm.$invalid || ajax_loading", ng-submit='submitColdWalletForm()') 73 | h2(l10n) View cold wallet 74 | 75 | .form-group 76 | label(for='coldWalletAddress', l10n) Enter Ripple Address 77 | input.form-control(name='coldWallet', ng-model='coldWallet' 78 | rp-focus-on-empty, required, rp-dest, rp-dest-address) 79 | br 80 | .errorGroup(rp-errors='coldWallet') 81 | .error(rp-error-on='rpDest', l10n) 82 | | Input should be a valid Ripple address. 83 | 84 | .submit-btn-container 85 | button.btn.btn-submit.btn-block.btn-success(type='submit', ng-disabled="coldWalletForm.$invalid || ajax_loading", rp-spinner="{{ajax_loading ? 4 : null}}") 86 | span(l10n) Open account 87 | -------------------------------------------------------------------------------- /src/js/tabs/contacts.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | var webutil = require('../util/web'); 3 | var Tab = require('../client/tab').Tab; 4 | 5 | var ContactsTab = function () 6 | { 7 | Tab.call(this); 8 | }; 9 | 10 | util.inherits(ContactsTab, Tab); 11 | 12 | ContactsTab.prototype.tabName = 'contacts'; 13 | ContactsTab.prototype.mainMenu = 'wallet'; 14 | 15 | // /contact is the way it appears in Ripple URIs 16 | ContactsTab.prototype.aliases = ['contact']; 17 | 18 | ContactsTab.prototype.generateHtml = function () 19 | { 20 | return require('../../templates/tabs/contacts.jade')(); 21 | }; 22 | 23 | ContactsTab.prototype.angular = function (module) { 24 | module.controller('ContactsCtrl', ['$scope', 'rpId', 25 | function ($scope, $id) 26 | { 27 | if (!$id.loginStatus) $id.goId(); 28 | 29 | $scope.reset_form = function () 30 | { 31 | $scope.contact = { 32 | name: '', 33 | view: '', 34 | address: '' 35 | }; 36 | if ($scope.addForm) $scope.addForm.$setPristine(); 37 | }; 38 | 39 | $scope.reset_form(); 40 | 41 | /** 42 | * Toggle "add contact" form 43 | */ 44 | $scope.toggle_form = function () 45 | { 46 | $scope.addform_visible = !$scope.addform_visible; 47 | $scope.reset_form(); 48 | }; 49 | 50 | /** 51 | * Create contact 52 | */ 53 | $scope.create = function () 54 | { 55 | var contact = { 56 | name: $scope.contact.name, 57 | view: $scope.contact.view, 58 | address: $scope.contact.address 59 | }; 60 | 61 | if ($scope.contact.dt) { 62 | contact.dt = $scope.contact.dt; 63 | } 64 | 65 | // Enable the animation 66 | $scope.enable_highlight = true; 67 | 68 | // Add an element 69 | $scope.userBlob.unshift("/contacts", contact); 70 | 71 | // Hide the form 72 | $scope.toggle_form(); 73 | 74 | // Clear form 75 | $scope.reset_form(); 76 | }; 77 | }]); 78 | 79 | module.controller('ContactRowCtrl', ['$scope', '$location', 80 | function ($scope, $location) { 81 | $scope.editing = false; 82 | 83 | /** 84 | * Switch to edit mode 85 | * 86 | * @param index 87 | */ 88 | $scope.edit = function (index) 89 | { 90 | $scope.editing = true; 91 | $scope.editname = $scope.entry.name; 92 | $scope.editaddress = $scope.entry.address; 93 | $scope.editview = $scope.entry.view || $scope.entry.address; 94 | $scope.editdt = $scope.entry.dt; 95 | }; 96 | 97 | /** 98 | * Update contact 99 | * 100 | * @param index 101 | */ 102 | $scope.update = function (index) 103 | { 104 | if (!$scope.inlineAddress.editaddress.$error.rpUnique 105 | && !$scope.inlineAddress.editaddress.$error.rpDest 106 | && !$scope.inlineName.editname.$error.rpUnique) { 107 | 108 | var entry = { 109 | name: $scope.editname, 110 | view: $scope.editview, 111 | address: $scope.editaddress 112 | }; 113 | 114 | if ($scope.editdt) { 115 | entry.dt = $scope.editdt; 116 | } 117 | 118 | // Update blob 119 | $scope.userBlob.filter('/contacts', 'name', $scope.entry.name, 120 | 'extend', '', entry); 121 | 122 | $scope.editing = false; 123 | } 124 | }; 125 | 126 | /** 127 | * Remove contact 128 | * 129 | * @param index 130 | */ 131 | $scope.remove = function (name) { 132 | // Update blob 133 | $scope.userBlob.filter('/contacts', 'name', $scope.entry.name, 134 | 'unset', ''); 135 | }; 136 | 137 | /** 138 | * Cancel contact edit 139 | * 140 | * @param index 141 | */ 142 | $scope.cancel = function (index) 143 | { 144 | $scope.editing = false; 145 | }; 146 | 147 | $scope.send = function (index) 148 | { 149 | var search = {to: $scope.entry.name}; 150 | 151 | $location.path('/send'); 152 | $location.search(search); 153 | }; 154 | }]); 155 | }; 156 | 157 | module.exports = ContactsTab; 158 | -------------------------------------------------------------------------------- /test/unit/controllers/appControllerSpec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | var expect = chai.expect; 3 | 4 | function run(scope,done) { 5 | // This variable means the app controller has run 6 | scope.$watch("app_loaded", function() { done(); }); 7 | scope.$digest(); 8 | } 9 | 10 | describe('AppCtrl', function(){ 11 | var scope, rootScope, location, controller_injector, ctrl; 12 | 13 | beforeEach(module("rp")); 14 | beforeEach(inject(function($rootScope, $controller, $q) { 15 | rootScope = rootScope; 16 | scope = $rootScope.$new(); 17 | controller_injector = $controller; 18 | 19 | var dependencies = { 20 | $rootScope: scope, 21 | $element: null, 22 | rpId: { 23 | init: function() { 24 | /*console.log('Called: init')*/; 25 | } 26 | }, 27 | rpNetwork: { 28 | listenId :function() {/*console.log('Called: listenId')*/;}, 29 | init: function() {/*console.log('Called: init')*/; 30 | } 31 | } 32 | }; 33 | 34 | ctrl = controller_injector("AppCtrl", dependencies); 35 | })); 36 | 37 | module(function($exceptionHandlerProvider) { 38 | $exceptionHandlerProvider.mode('log'); 39 | }); 40 | 41 | describe('Public Functions', function () { 42 | 43 | it('should be able to reset', function(done) { 44 | assert.isFunction(ctrl.reset); 45 | run(scope,done); 46 | }); 47 | 48 | it('should handle when an account is loaded', function(done) { 49 | assert.isFunction(ctrl.handleAccountLoad); 50 | run(scope,done); 51 | }); 52 | 53 | it('should handle when an account is unloaded', function(done) { 54 | assert.isFunction(ctrl.handleAccountUnload); 55 | run(scope,done); 56 | }); 57 | 58 | it('should handle ripple lines', function(done) { 59 | assert.isFunction(ctrl.handleRippleLines); 60 | run(scope,done); 61 | }); 62 | 63 | it('should handle ripple lines error', function(done) { 64 | assert.isFunction(ctrl.handleRippleLinesError); 65 | run(scope,done); 66 | }); 67 | 68 | it('should handle offers', function(done) { 69 | assert.isFunction(ctrl.handleOffers); 70 | run(scope,done); 71 | }); 72 | 73 | it('should handle offers error', function(done) { 74 | assert.isFunction(ctrl.handleOffersError); 75 | run(scope,done); 76 | }); 77 | 78 | it('should handle an account entry', function(done) { 79 | assert.isFunction(ctrl.handleAccountEntry); 80 | run(scope,done); 81 | }); 82 | 83 | it('should handle an account transaction', function(done) { 84 | assert.isFunction(ctrl.handleAccountTx); 85 | run(scope,done); 86 | }); 87 | 88 | it('should handle and account transaction error', function(done) { 89 | assert.isFunction(ctrl.handleAccountTxError); 90 | run(scope,done); 91 | }); 92 | 93 | it('should handle an account event', function(done) { 94 | assert.isFunction(ctrl.handleAccountEvent); 95 | run(scope,done); 96 | }); 97 | 98 | it('should process a transaction', function(done) { 99 | assert.isFunction(ctrl.processTxn); 100 | run(scope,done); 101 | }); 102 | 103 | it('should update an offer', function(done) { 104 | assert.isFunction(ctrl.updateOffer); 105 | run(scope,done); 106 | }); 107 | 108 | it('should update the ripple lines', function(done) { 109 | assert.isFunction(ctrl.updateLines); 110 | run(scope,done); 111 | }); 112 | 113 | it('should should update the ripple balance', function(done) { 114 | assert.isFunction(ctrl.updateRippleBalance); 115 | run(scope,done); 116 | }); 117 | 118 | it('should compare', function(done) { 119 | assert.isFunction(ctrl.compare); 120 | run(scope,done); 121 | }); 122 | 123 | it('should handle the first connection', function(done) { 124 | assert.isFunction(ctrl.handleFirstConnection); 125 | run(scope,done); 126 | }); 127 | }); 128 | 129 | describe('Initializing the App Controller', function() { 130 | it('should be initialized with an empty account', function(done) { 131 | expect(scope.account).to.be.an('object'); 132 | expect(scope.account).to.be.empty; 133 | 134 | run(scope,done); 135 | }); 136 | }); 137 | 138 | }); 139 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | # ESLint documentation can be found at http://eslint.org/docs/ 2 | env: 3 | browser: true 4 | node: true 5 | amd: false 6 | mocha: true 7 | jasmine: false 8 | rules: 9 | no-alert: 2 10 | no-array-constructor: 2 11 | no-bitwise: 0 12 | no-caller: 2 13 | no-catch-shadow: 2 14 | no-comma-dangle: 2 15 | no-cond-assign: [2, 'always'] 16 | no-console: 0 17 | no-constant-condition: 2 18 | no-control-regex: 2 19 | no-debugger: 2 20 | no-delete-var: 2 21 | no-div-regex: 0 22 | no-dupe-keys: 2 23 | no-dupe-args: 2 24 | no-duplicate-case: 2 25 | no-else-return: 2 26 | no-empty: 2 27 | no-empty-class: 2 28 | no-empty-label: 2 29 | no-eq-null: 2 30 | no-eval: 2 31 | no-ex-assign: 2 32 | no-extend-native: 2 33 | no-extra-bind: 2 34 | no-extra-boolean-cast: 2 35 | no-extra-parens: 0 36 | no-extra-semi: 2 37 | no-fallthrough: 2 38 | no-floating-decimal: 0 39 | no-func-assign: 2 40 | no-implied-eval: 2 41 | no-inline-comments: 0 42 | no-inner-declarations: [2, 'functions'] 43 | no-invalid-regexp: 2 44 | no-irregular-whitespace: 2 45 | no-iterator: 2 46 | no-label-var: 2 47 | no-labels: 2 48 | no-lone-blocks: 2 49 | no-lonely-if: 2 50 | no-loop-func: 2 51 | no-mixed-requires: [0, false] 52 | no-mixed-spaces-and-tabs: [2, false] 53 | no-multi-spaces: 2 54 | no-multi-str: 2 55 | no-multiple-empty-lines: [2, {max: 2}] 56 | no-native-reassign: 2 57 | no-negated-in-lhs: 2 58 | no-nested-ternary: 0 59 | no-new: 2 60 | no-new-func: 2 61 | no-new-object: 2 62 | no-new-require: 0 63 | no-new-wrappers: 2 64 | no-obj-calls: 2 65 | no-octal: 2 66 | no-octal-escape: 2 67 | no-path-concat: 0 68 | no-plusplus: 0 69 | no-process-env: 0 70 | no-process-exit: 0 71 | no-proto: 2 72 | no-redeclare: 2 73 | no-regex-spaces: 2 74 | no-reserved-keys: 0 75 | no-restricted-modules: 0 76 | no-return-assign: 2 77 | no-script-url: 2 78 | no-self-compare: 2 79 | no-sequences: 2 80 | no-shadow: 2 81 | no-shadow-restricted-names: 2 82 | no-space-before-semi: 2 83 | no-spaced-func: 2 84 | no-sparse-arrays: 2 85 | no-sync: 0 86 | no-ternary: 0 87 | no-trailing-spaces: 2 88 | no-undef: 2 89 | no-undef-init: 2 90 | no-undefined: 0 91 | no-underscore-dangle: 0 92 | no-unreachable: 2 93 | no-unused-expressions: 2 94 | no-unused-vars: [2, {vars: 'all', args: 'after-used'}] 95 | no-use-before-define: 2 96 | no-void: 2 97 | no-var: 0 98 | no-warning-comments: [0, {terms: ['todo', 'fixme', 'xxx'], location: 'start'}] 99 | no-with: 2 100 | no-wrap-func: 2 101 | block-scoped-var: 2 102 | brace-style: 2 103 | camelcase: 0 104 | comma-spacing: 2 105 | comma-style: 2 106 | complexity: [0, 11] 107 | consistent-return: 2 108 | consistent-this: [2, 'self'] 109 | curly: [2, 'all'] 110 | default-case: 0 111 | dot-notation: [2, {allowKeywords: true}] 112 | eol-last: 2 113 | eqeqeq: 2 114 | func-names: 0 115 | func-style: [2, 'declaration'] 116 | generator-star: 0 117 | guard-for-in: 0 118 | handle-callback-err: 2 119 | indent: [2, 2] 120 | key-spacing: [2, {beforeColon: false, afterColon: true}] 121 | max-depth: [1, 4] 122 | max-len: [2, 80] 123 | max-nested-callbacks: [1, 2] 124 | max-params: [1, 4] 125 | max-statements: [0, 10] 126 | new-cap: 2 127 | new-parens: 2 128 | one-var: 0 129 | operator-assignment: [0, 'always'] 130 | padded-blocks: 0 131 | quote-props: 0 132 | quotes: [2, 'single'] 133 | radix: 2 134 | semi: 2 135 | sort-vars: 0 136 | space-after-function-name: [0, 'never'] 137 | space-after-keywords: 2 138 | space-before-blocks: 2 139 | space-before-function-parentheses: [2, 'never'] 140 | space-in-brackets: 2 141 | space-in-parens: 2 142 | space-infix-ops: 2 143 | space-return-throw-case: 2 144 | space-unary-ops: [2, {words: true, nonwords: false}] 145 | spaced-line-comment: 2 146 | strict: [2, 'global'] 147 | use-isnan: 2 148 | valid-jsdoc: 2 149 | valid-typeof: 2 150 | vars-on-top: 0 151 | wrap-iife: 0 152 | wrap-regex: 0 153 | yoda: [2, 'never'] 154 | globals: 155 | _: false 156 | $: false 157 | ripple: false 158 | RippleAPI: false 159 | moment: false 160 | Spinner: false 161 | RippleAddressCodec: false 162 | sjcl: false 163 | store: false 164 | RippleBinaryCodec: false 165 | -------------------------------------------------------------------------------- /src/js/directives/qr.js: -------------------------------------------------------------------------------- 1 | /** 2 | * QR 3 | * 4 | * Directive to render a QR code. 5 | */ 6 | 7 | var module = angular.module('qr', []); 8 | 9 | /** 10 | * angular-qrcode v3.1.0 11 | * (c) 2013 Monospaced http://monospaced.com 12 | * License: MIT 13 | */ 14 | module.directive('rpQrcode', ['$window', function($window) { 15 | var canvas2D = !!$window.CanvasRenderingContext2D, 16 | levels = { 17 | 'L': 'Low', 18 | 'M': 'Medium', 19 | 'Q': 'Quartile', 20 | 'H': 'High' 21 | }, 22 | draw = function(context, qr, modules, tile) { 23 | for (var row = 0; row < modules; row++) { 24 | for (var col = 0; col < modules; col++) { 25 | var w = (Math.ceil((col + 1) * tile) - Math.floor(col * tile)), 26 | h = (Math.ceil((row + 1) * tile) - Math.floor(row * tile)); 27 | 28 | context.fillStyle = qr.isDark(row, col) ? '#000' : '#fff'; 29 | context.fillRect(Math.round(col * tile), 30 | Math.round(row * tile), w, h); 31 | } 32 | } 33 | }; 34 | 35 | return { 36 | restrict: 'E', 37 | template: '', 38 | link: function(scope, element, attrs) { 39 | var domElement = element[0], 40 | canvas = element.find('canvas')[0], 41 | context = canvas2D ? canvas.getContext('2d') : null, 42 | trim = /^\s+|\s+$/g, 43 | error, 44 | version, 45 | errorCorrectionLevel, 46 | data, 47 | size, 48 | modules, 49 | tile, 50 | qr, 51 | setVersion = function(value) { 52 | version = Math.max(1, Math.min(parseInt(value, 10), 10)) || 4; 53 | }, 54 | setErrorCorrectionLevel = function(value) { 55 | errorCorrectionLevel = value in levels ? value : 'M'; 56 | }, 57 | setData = function(value) { 58 | if (!value) { 59 | return; 60 | } 61 | 62 | data = value.replace(trim, ''); 63 | qr = qrcode(version, errorCorrectionLevel); 64 | qr.addData(data); 65 | 66 | try { 67 | qr.make(); 68 | } catch(e) { 69 | error = e.message; 70 | return; 71 | } 72 | 73 | error = false; 74 | modules = qr.getModuleCount(); 75 | }, 76 | setSize = function(value) { 77 | size = parseInt(value, 10) || modules * 2; 78 | tile = size / modules; 79 | canvas.width = canvas.height = size; 80 | }, 81 | render = function() { 82 | if (!qr) { 83 | return; 84 | } 85 | 86 | if (error) { 87 | if (!canvas2D) { 88 | domElement.innerHTML = ''; 90 | } 91 | scope.$emit('qrcode:error', error); 92 | return; 93 | } 94 | 95 | if (canvas2D) { 96 | draw(context, qr, modules, tile); 97 | } else { 98 | domElement.innerHTML = qr.createImgTag(tile, 0); 99 | } 100 | }; 101 | 102 | setVersion(attrs.version); 103 | setErrorCorrectionLevel(attrs.errorCorrectionLevel); 104 | setSize(attrs.size); 105 | 106 | attrs.$observe('version', function(value) { 107 | if (!value) { 108 | return; 109 | } 110 | 111 | setVersion(value); 112 | setData(data); 113 | setSize(size); 114 | render(); 115 | }); 116 | 117 | attrs.$observe('errorCorrectionLevel', function(value) { 118 | if (!value) { 119 | return; 120 | } 121 | 122 | setErrorCorrectionLevel(value); 123 | setData(data); 124 | setSize(size); 125 | render(); 126 | }); 127 | 128 | attrs.$observe('data', function(value) { 129 | if (!value) { 130 | return; 131 | } 132 | 133 | setData(value); 134 | setSize(size); 135 | render(); 136 | }); 137 | 138 | attrs.$observe('size', function(value) { 139 | if (!value) { 140 | return; 141 | } 142 | 143 | setSize(value); 144 | render(); 145 | }); 146 | } 147 | }; 148 | }]); 149 | -------------------------------------------------------------------------------- /src/js/services/ledger.js: -------------------------------------------------------------------------------- 1 | /** 2 | * LEDGER 3 | * 4 | * The ledger service is used to provide information that requires watching the 5 | * entire ledger. 6 | * 7 | * This obviously won't scale, but it'll do long enough for us (or somebody 8 | * else) to come up with something better. 9 | */ 10 | 11 | var module = angular.module('ledger', ['network', 'transactions']); 12 | 13 | module.factory('rpLedger', ['$q', '$rootScope', 'rpNetwork', 'rpTransactions', 14 | function($q, $rootScope, net, transactions) 15 | { 16 | 17 | var offerPromise = $q.defer(); 18 | var tickerPromise = $q.defer(); 19 | var requested = false; 20 | 21 | var ledger = { 22 | offers: offerPromise.promise, 23 | tickers: tickerPromise.promise, 24 | getOrders: getOrders 25 | }; 26 | 27 | function filterOrder(buyCurrency, sellCurrency, buyIssuer, sellIssuer, 28 | pays, gets) { 29 | if (buyCurrency !== gets.currency || sellCurrency !== pays.currency) { 30 | return false; 31 | } 32 | 33 | if (buyCurrency !== 'XRP' && buyIssuer && gets.issuer !== buyIssuer) { 34 | return false; 35 | } 36 | 37 | if (sellCurrency !== 'XRP' && sellIssuer && pays.issuer !== sellIssuer) { 38 | return false; 39 | } 40 | 41 | return true; 42 | } 43 | 44 | function getOrders(buyCurrency, sellCurrency, buyIssuer, sellIssuer) { 45 | var obj = { 46 | asks: [], 47 | bids: [] 48 | }; 49 | 50 | if (!Array.isArray(ledger.offers)) return obj; 51 | 52 | ledger.offers.forEach(function (node) { 53 | var gets = rewriteAmount(node.TakerGets); 54 | var pays = rewriteAmount(node.TakerPays); 55 | 56 | if (filterOrder(buyCurrency, sellCurrency, buyIssuer, sellIssuer, pays, gets)) { 57 | obj.asks.push({i: gets, o: pays}); 58 | 59 | // A bid can't also be an ask 60 | return; 61 | } 62 | 63 | if (filterOrder(buyCurrency, sellCurrency, buyIssuer, sellIssuer, gets, pays)) { 64 | obj.bids.push({i: pays, o: gets}); 65 | } 66 | }); 67 | 68 | obj.asks.sort(function (a, b) { 69 | var aRatio = a.o.amount.ratio_human(a.i.amount, {reference_date: new Date()}); 70 | var bRatio = b.o.amount.ratio_human(b.i.amount, {reference_date: new Date()}); 71 | return aRatio.compareTo(bRatio); 72 | }); 73 | 74 | obj.bids.sort(function (a, b) { 75 | var aRatio = a.o.amount.ratio_human(a.i.amount, {reference_date: new Date()}); 76 | var bRatio = b.o.amount.ratio_human(b.i.amount, {reference_date: new Date()}); 77 | return bRatio.compareTo(aRatio); 78 | }); 79 | 80 | fillSum(obj.asks, 'i'); 81 | fillSum(obj.bids, 'i'); 82 | 83 | return obj; 84 | } 85 | 86 | function rewriteAmount(amountJson) { 87 | var amount = ripple.Amount.from_json(amountJson); 88 | return { 89 | amount: amount, 90 | // Pretty dirty hack, but to_text for native values gives 1m * value... 91 | // In the future we will likely remove this field altogether (and use 92 | // Amount class math instead), so it's ok. 93 | num: +amount.to_human({group_sep: false}), 94 | currency: amount.currency().to_human(), 95 | issuer: amount.issuer() 96 | }; 97 | } 98 | 99 | /** 100 | * Fill out the sum field in the bid or ask orders array. 101 | */ 102 | function fillSum(array, field) { 103 | var sum = null; 104 | for (var i = 0, l = array.length; i 26 | ## Bug reports 27 | 28 | A bug is a _demonstrable problem_ that is caused by the code in the repository. 29 | Good bug reports are extremely helpful - thank you! 30 | 31 | Guidelines for bug reports: 32 | 33 | 1. **Use the GitHub issue search** — check if the issue has already been 34 | reported. 35 | 36 | 2. **Check if the issue has been fixed** — try to reproduce it using the 37 | latest `master` or development branch in the repository. 38 | 39 | 3. **Isolate the problem** — ideally create a [reduced test 40 | case](http://css-tricks.com/6263-reduced-test-cases/) and a live example. 41 | 42 | A good bug report shouldn't leave others needing to chase you up for more 43 | information. Please try to be as detailed as possible in your report. What is 44 | your environment? What steps will reproduce the issue? What browser(s) and OS 45 | experience the problem? What would you expect to be the outcome? All these 46 | details will help people to fix any potential bugs. 47 | 48 | Example: 49 | 50 | > Short and descriptive example bug report title 51 | > 52 | > A summary of the issue and the browser/OS environment in which it occurs. If 53 | > suitable, include the steps required to reproduce the bug. 54 | > 55 | > 1. This is the first step 56 | > 2. This is the second step 57 | > 3. Further steps, etc. 58 | > 59 | > `` - a link to the reduced test case 60 | > 61 | > Any other information you want to share that is relevant to the issue being 62 | > reported. This might include the lines of code that you have identified as 63 | > causing the bug, and potential solutions (and your opinions on their 64 | > merits). 65 | 66 | 67 | 68 | ## Feature requests 69 | 70 | Feature requests are welcome. But take a moment to find out whether your idea 71 | fits with the scope and aims of the project. It's up to *you* to make a strong 72 | case to convince the project's developers of the merits of this feature. Please 73 | provide as much detail and context as possible. 74 | 75 | 76 | 77 | ## Pull requests 78 | 79 | Good pull requests - patches, improvements, new features - are a fantastic 80 | help. They should remain focused in scope and avoid containing unrelated 81 | commits. 82 | 83 | **Please ask first** before embarking on any significant pull request (e.g. 84 | implementing features, refactoring code, porting to a different language), 85 | otherwise you risk spending a lot of time working on something that the 86 | project's developers might not want to merge into the project. 87 | 88 | Please adhere to the coding conventions used throughout a project (indentation, 89 | accurate comments, etc.) and any other requirements (such as test coverage). 90 | 91 | Adhering to the following this process is the best way to get your work 92 | included in the project: 93 | 94 | 1. [Fork](http://help.github.com/fork-a-repo/) the project, clone your fork, 95 | and configure the remotes: 96 | 97 | ```bash 98 | # Clone your fork of the repo into the current directory 99 | git clone https://github.com// 100 | # Navigate to the newly cloned directory 101 | cd 102 | # Assign the original repo to a remote called "upstream" 103 | git remote add upstream https://github.com// 104 | ``` 105 | 106 | 2. If you cloned a while ago, get the latest changes from upstream: 107 | 108 | ```bash 109 | git checkout 110 | git pull upstream 111 | ``` 112 | 113 | 3. Create a new topic branch (off the main project development branch) to 114 | contain your feature, change, or fix: 115 | 116 | ```bash 117 | git checkout -b 118 | ``` 119 | 120 | 4. Commit your changes in logical chunks. Please adhere to these [git commit 121 | message guidelines](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html) 122 | or your code is unlikely be merged into the main project. Use Git's 123 | [interactive rebase](https://help.github.com/articles/interactive-rebase) 124 | feature to tidy up your commits before making them public. 125 | 126 | 5. Locally merge (or rebase) the upstream development branch into your topic branch: 127 | 128 | ```bash 129 | git pull [--rebase] upstream 130 | ``` 131 | 132 | 6. Push your topic branch up to your fork: 133 | 134 | ```bash 135 | git push origin 136 | ``` 137 | 138 | 7. [Open a Pull Request](https://help.github.com/articles/using-pull-requests/) 139 | with a clear title and description. 140 | 141 | **IMPORTANT**: By submitting a patch, you agree to allow the project owner to 142 | license your work under the same license as that used by the project. 143 | --------------------------------------------------------------------------------