├── .gitignore ├── AUTHORS ├── COPYRIGHT ├── Gruntfile.js ├── HACKING.md ├── README.md ├── bower.json ├── manifest.json ├── package.json ├── src ├── css │ ├── app.css │ └── normalize.css ├── fonts │ ├── ClearSans-Bold.ttf │ ├── ClearSans-BoldItalic.ttf │ ├── ClearSans-Italic.ttf │ └── ClearSans-Regular.ttf ├── html │ ├── background.html │ ├── index.html │ ├── modals │ │ ├── ask-password.html │ │ ├── confirm-delete.html │ │ ├── import.html │ │ ├── payment-review.html │ │ ├── pick-contact.html │ │ ├── prompt-for-input.html │ │ ├── prompt-ok-cancel.html │ │ ├── prompt-ok.html │ │ ├── prompt-sign-msg.html │ │ ├── prompt-spinner.html │ │ ├── prompt-text-area.html │ │ ├── scan-qr.html │ │ └── show-qr.html │ ├── partials │ │ ├── contact.html │ │ ├── contacts.html │ │ ├── identities.html │ │ ├── login.html │ │ ├── new_wallet.html │ │ ├── offline_spend.html │ │ ├── settings.html │ │ ├── tools.html │ │ └── wallet.html │ ├── popup.html │ ├── tools │ │ ├── signing.html │ │ └── verifying.html │ └── wallet │ │ ├── actions.html │ │ ├── addresses.html │ │ ├── dashboard.html │ │ ├── history.html │ │ ├── new_account.html │ │ ├── new_imported_account.html │ │ ├── new_imported_address.html │ │ ├── new_imported_watch_account.html │ │ ├── new_imported_watch_address.html │ │ ├── pockets.html │ │ ├── quick_send.html │ │ └── receiving_addresses.html ├── i18n │ ├── _index.js │ ├── _update.js │ ├── de_DE.json │ ├── en_US.json │ ├── es_ES.json │ ├── ru_RU.json │ ├── tw_CN.json │ └── zh_CN.json ├── images │ ├── coins.svg │ ├── encrypt.svg │ ├── faces │ │ └── wallet.svg │ ├── fiat.svg │ ├── icon_128.png │ ├── logo.svg │ ├── net.svg │ ├── notifications.svg │ ├── seed.svg │ ├── send.svg │ ├── virtual.svg │ └── wallet.svg ├── js │ ├── arcbit.js │ ├── backend │ │ ├── loader.js │ │ ├── main.js │ │ ├── port.js │ │ └── services │ │ │ └── wallet.js │ ├── frontend │ │ ├── app.js │ │ ├── controllers │ │ │ ├── accountaction.js │ │ │ ├── accountcreate.js │ │ │ ├── addresses.js │ │ │ ├── backups.js │ │ │ ├── contacts.js │ │ │ ├── history.js │ │ │ ├── identities.js │ │ │ ├── importedaccountcreate.js │ │ │ ├── importedaddresscreate.js │ │ │ ├── importedwatchaccountcreate.js │ │ │ ├── importedwatchaddresscreate.js │ │ │ ├── index.js │ │ │ ├── login.js │ │ │ ├── module.js │ │ │ ├── new_wallet.js │ │ │ ├── ngmodal.js │ │ │ ├── offline_spend.js │ │ │ ├── receivingaddresses.js │ │ │ ├── send.js │ │ │ ├── settings.js │ │ │ ├── signing.js │ │ │ ├── tools.js │ │ │ └── wallet.js │ │ ├── directives │ │ │ ├── identicon.js │ │ │ ├── index.js │ │ │ ├── module.js │ │ │ ├── qr-save-button.js │ │ │ ├── qr-scanner.js │ │ │ └── qr.js │ │ ├── filters │ │ │ ├── currency.js │ │ │ ├── i18n.js │ │ │ ├── index.js │ │ │ └── module.js │ │ ├── loader.js │ │ ├── popup │ │ │ ├── app.js │ │ │ ├── controller.js │ │ │ ├── loader.js │ │ │ └── providers.js │ │ ├── port.js │ │ ├── providers │ │ │ ├── clipboard.js │ │ │ ├── history.js │ │ │ ├── index.js │ │ │ ├── modals.js │ │ │ ├── module.js │ │ │ ├── notify.js │ │ │ ├── sounds.js │ │ │ └── tabs.js │ │ ├── routes.js │ │ └── scripts │ │ │ └── inject.js │ ├── model │ │ ├── TLAccountObject.js │ │ ├── TLAccounts.js │ │ ├── TLAppDelegate.js │ │ ├── TLBIP38.js │ │ ├── TLBitcoinJSWrapper.js │ │ ├── TLBitcoinListener.js │ │ ├── TLBlockExplorerAPI.js │ │ ├── TLBlockchainAPI.js │ │ ├── TLBlockchainStatus.js │ │ ├── TLCoin.js │ │ ├── TLContacts.js │ │ ├── TLCrypto.js │ │ ├── TLCurrencyFormat.js │ │ ├── TLExchangeRate.js │ │ ├── TLGlobalSettings.js │ │ ├── TLHDWalletWrapper.js │ │ ├── TLImportedAddress.js │ │ ├── TLImportedAddresses.js │ │ ├── TLInsightAPI.js │ │ ├── TLNetworking.js │ │ ├── TLPreferences.js │ │ ├── TLPushTxAPI.js │ │ ├── TLSpaghettiGodSend.js │ │ ├── TLStealthAddress.js │ │ ├── TLStealthExplorerAPI.js │ │ ├── TLStealthServerConfig.js │ │ ├── TLStealthWallet.js │ │ ├── TLStealthWebSocket.js │ │ ├── TLTxFeeAPI.js │ │ ├── TLTxObject.js │ │ ├── TLUtils.js │ │ ├── TLWallet.js │ │ ├── TLWalletJSONKeys.js │ │ ├── TLWalletJson.js │ │ ├── TLWalletUtils.js │ │ ├── identity.js │ │ └── keyring.js │ └── util │ │ └── fixes.js ├── sass │ ├── _angular-csp.scss │ ├── _animation.scss │ ├── _fonts.scss │ ├── _foundation.scss │ ├── _settings.scss │ ├── _toaster.scss │ ├── _variables.scss │ └── app.scss ├── sound │ └── keygenEnd.opus └── vendors │ ├── AngularJS-Toaster │ ├── toaster.css │ └── toaster.js │ ├── angular-animate │ └── angular-animate.min.js │ ├── angular-foundation │ └── mm-foundation-tpls.min.js │ ├── angular-mocks │ └── angular-mocks.js │ ├── angular-route │ └── angular-route.min.js │ ├── angular-socket-io │ ├── .gitignore │ ├── bower.json │ └── socket.min.js │ ├── angular-spinner │ ├── .gitignore │ ├── angular-spinner.min.js │ └── bower.json │ ├── angular-translate-loader-static-files │ └── angular-translate-loader-static-files.min.js │ ├── angular-translate │ └── angular-translate.min.js │ ├── angular-xeditable │ └── dist │ │ ├── css │ │ └── xeditable.css │ │ └── js │ │ └── xeditable.min.js │ ├── angular │ ├── angular-csp.css │ └── angular.min.js │ ├── async │ └── lib │ │ └── async.js │ ├── big.js │ └── big.min.js │ ├── bip39 │ └── bip39.js │ ├── bitcoinjs-lib │ └── bitcoinjs.js │ ├── crypto-js │ └── cryptojs.js │ ├── font-awesome │ ├── css │ │ └── font-awesome.min.css │ └── fonts │ │ └── fontawesome-webfont.woff │ ├── identicon │ ├── identicon.js │ └── pnglib.js │ ├── jsqrcode-concat.sh │ ├── jsqrcode │ └── jsqrcode.js │ ├── ngprogress │ ├── build │ │ └── ngProgress.js │ └── ngProgress.css │ ├── qrcodejs │ └── qrcode.js │ ├── requirejs-domready │ └── domReady.js │ ├── requirejs │ └── require.js │ ├── sjcl │ └── sjcl.js │ ├── socket-io-client │ ├── bower.json │ └── socket.io.js │ └── spin.js │ ├── .gitignore │ ├── bower.json │ └── spin.min.js └── test ├── karma.conf.js ├── mock ├── arcbit_mock.js ├── available_languages.js ├── chrome_mock.js ├── date.js ├── frontend_app.js └── testUtils.js ├── test-main.js └── unit ├── backend └── portSpec.js ├── frontend └── providers │ ├── clipboardSpec.js │ ├── notifySpec.js │ └── soundsSpec.js └── model ├── TLBitcoinJSWrapperSpec.js ├── TLCoinSpec.js ├── TLCryptoSpec.js ├── TLHDWalletWrapperSpec.js ├── TLSpaghettiGodSendSpec.js └── TLStealthAddressSpec.js /.gitignore: -------------------------------------------------------------------------------- 1 | # ignore IDE/hidden/testing/OS cache files 2 | .* 3 | *~ 4 | /nbproject 5 | /nb-configuration.xml 6 | Session.vim 7 | *.tmproj 8 | *.tmproject 9 | tmtags 10 | Thumbs.db 11 | Desktop.ini 12 | 13 | # don't ignore special configs 14 | !/.travis.yml 15 | !.gitignore 16 | 17 | # ignore some vendor's files 18 | /src/vendors/mnemonic.js/chrome/ 19 | /src/vendors/sjcl/* 20 | !/src/vendors/sjcl/sjcl.js 21 | /src/vendors/bootstrap/ 22 | /src/vendors/jquery/ 23 | /src/vendors/bitcoinjs-lib/* 24 | !/src/vendors/bitcoinjs-lib/bitcoinjs.js 25 | /src/vendors/angular/* 26 | !/src/vendors/angular/*.min.js 27 | !/src/vendors/angular/*.css 28 | /src/vendors/angular-animate/* 29 | !/src/vendors/angular-animate/*.min.js 30 | /src/vendors/angular-route/* 31 | !/src/vendors/angular-route/*.min.js 32 | /src/vendors/angular-foundation/* 33 | !/src/vendors/angular-foundation/mm-foundation-tpls.min.js 34 | /src/vendors/angular-xeditable/* 35 | !/src/vendors/angular-xeditable/dist 36 | /src/vendors/angular-xeditable/dist/js/xeditable.js 37 | /src/vendors/angular-mocks/* 38 | !/src/vendors/angular-mocks/angular-mocks.js 39 | /src/vendors/angular-translate/* 40 | !/src/vendors/angular-translate/angular-translate.min.js 41 | /src/vendors/angular-translate-loader-static-files/* 42 | !/src/vendors/angular-translate-loader-static-files/angular-translate-loader-static-files.min.js 43 | /src/vendors/ngprogress/* 44 | !/src/vendors/ngprogress/*.css 45 | !/src/vendors/ngprogress/build 46 | /src/vendors/ngprogress/build/* 47 | !/src/vendors/ngprogress/build/ngProgress.js 48 | /src/vendors/AngularJS-Toaster/* 49 | !/src/vendors/AngularJS-Toaster/*.css 50 | !/src/vendors/AngularJS-Toaster/toaster.js 51 | /src/vendors/font-awesome/* 52 | !/src/vendors/font-awesome/fonts 53 | /src/vendors/font-awesome/fonts/* 54 | !/src/vendors/font-awesome/fonts/fontawesome-webfont.woff 55 | !/src/vendors/font-awesome/css 56 | /src/vendors/font-awesome/css/* 57 | !/src/vendors/font-awesome/css/font-awesome.min.css 58 | /src/vendors/foundation/* 59 | /src/vendors/fastclick/* 60 | /src/vendors/jquery-placeholder/* 61 | /src/vendors/modernizr/* 62 | /src/vendors/jquery.cookie/* 63 | /src/vendors/requirejs*/* 64 | !/src/vendors/requirejs*/*.js 65 | /src/vendors/qrcodejs/* 66 | !/src/vendors/qrcodejs/qrcode.js 67 | /src/vendors/jsqrcode/* 68 | !/src/vendors/jsqrcode/jsqrcode.js 69 | /src/vendors/identicon/* 70 | !/src/vendors/identicon/pnglib.js 71 | !/src/vendors/identicon/identicon.js 72 | /src/vendors/async/* 73 | !/src/vendors/async/lib 74 | /src/vendors/angular-moment/* 75 | !/src/vendors/angular-moment/angular-moment.min.js 76 | /src/vendors/moment/* 77 | !/src/vendors/moment/min/ 78 | /src/vendors/moment/min/* 79 | !/src/vendors/moment/min/moment-with-locales.min.js 80 | /src/vendors/big.js/* 81 | !/src/vendors/big.js/big.min.js 82 | 83 | # Ignore Node Modules (install them only if you need to edit scss) 84 | /node_modules/* 85 | 86 | # Ignore test coverage folder 87 | /test/coverage/* 88 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | ArcBit Developers: 2 | Tim Lee 3 | 4 | Forked Project Developers: 5 | 6 | Main Developers: 7 | David Llop 8 | Pablo Martin 9 | 10 | Design and css: 11 | Sergio Ibáñez 12 | 13 | Bitcoin expert: 14 | Amir Taaki 15 | 16 | Patches and pull requests: 17 | Andres Vargas 18 | Jonathan Cross 19 | 20 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(grunt) { 4 | grunt.initConfig({ 5 | pkg: grunt.file.readJSON('package.json'), 6 | 7 | sass: { 8 | options: { 9 | includePaths: ['src/vendors/foundation/scss'] 10 | }, 11 | dist: { 12 | options: { 13 | outputStyle: 'extended' 14 | }, 15 | files: { 16 | 'src/css/app.css': 'src/sass/app.scss' 17 | } 18 | } 19 | }, 20 | 21 | watch: { 22 | grunt: { 23 | files: ['Gruntfile.js'], 24 | tasks: ['sass'] 25 | }, 26 | sass: { 27 | files: 'src/sass/**/*.scss', 28 | tasks: ['sass'] 29 | } 30 | } 31 | 32 | }); 33 | 34 | grunt.loadNpmTasks('grunt-sass'); 35 | grunt.loadNpmTasks('grunt-contrib-watch'); 36 | 37 | grunt.registerTask('build', ['sass']); 38 | grunt.registerTask('default', ['watch']); 39 | }; 40 | -------------------------------------------------------------------------------- /HACKING.md: -------------------------------------------------------------------------------- 1 | Testing 2 | ----------- 3 | 4 | We use the tool `karma` to run the tests. 5 | 6 | All the following commands should be done from the arcbit root folder. 7 | 8 | ### Setup your environment 9 | 10 | ```sh 11 | $ npm -d install 12 | $ npm install -g karma-cli # you may need sudo here 13 | ``` 14 | 15 | ### Running the tests 16 | 17 | ```sh 18 | $ karma start test/karma.conf.js 19 | ``` 20 | 21 | Javascript tasks 22 | ----------- 23 | 24 | To update or modify dependencies you may need the following information: 25 | 26 | ### Adding/Upgrading dependencies with bower 27 | If you want to add/upgrade a dependency, use bower. 28 | 29 | ```bash 30 | $ bower install angular --save # --save option modify bower.json file 31 | ``` 32 | 33 | Read [bower documentation](http://bower.io) for more info. 34 | 35 | 36 | Icon set 37 | ----------- 38 | 39 | You can use the following cheatsheet to look for icon codes: 40 | 41 | - http://fortawesome.github.io/Font-Awesome/cheatsheet/ 42 | 43 | Search other icon sets for useful icons: 44 | 45 | - http://icomoon.io 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ArcBit 2 | =========== 3 | Bitcoin web wallet for Chrome. Visit http://www.arcbit.io/ for more information. 4 | 5 | 6 | ArcBit Web is a port of ArcBit iOS project found here https://github.com/arcbit/arcbit-ios 7 | You may notice similarities of the UI to the DarkWallet project (https://github.com/darkwallet/darkwallet). This is because a lot of the frontend code was taken from there and used in ArcBit. However, from a functional standpoint, ArcBit is very different then DarkWallet. ArcBit uses Insight or blockchain.info (depending on what the user picked in settings) to fetch blockchain data and an ArcBit server to keep track of reusable address transactions. ArcBit also gives the user the 2 different options to manage their wallet security. 8 | 1. You can have the ArcBit Wallet always encrypted in the browsers local storage. This means that you will always require to enter your login/decryption password (by default it is the 12 word seed that is generated for you when you create a wallet) when you close the ArcBit wallet tab or Chrome. 9 | 2. Alternatively you can have your ArcBit Wallet encrypted only if you explicitly click logout. This means that if you just close the ArcBit Wallet browser tab or even if you exit Chrome without clicking logout, you can reopen ArcBit without having to login. 10 | 11 | #####Chrome store link: 12 | https://chrome.google.com/webstore/detail/arcbit-bitcoin-wallet/dkceiphcnbfahjbomhpdgjmphnpgogfk 13 | 14 | #####Other ArcBit services: 15 | https://itunes.apple.com/app/arcbit-bitcoin-wallet/id999487888 16 | https://arcbitbrainwallet.com 17 | 18 | #####Features: 19 | - No signup required 20 | - Single recovery passphrase that works forever 21 | - Private keys never leave your device 22 | - Send and receive bitcoin payments 23 | - View transactions and wallet balance 24 | - HD wallet support 25 | - Reusable/stealth address support 26 | - Over 150 local currencies support 27 | - Bitcoin, millibits and bits denomination support 28 | - Automatic cycling of addresses to prevent address reuse 29 | - Open source 30 | - Strong encryption 31 | - xpub keys stored client side unlike many other wallets, which offers better privacy 32 | - Can access private keys without an internet connection 33 | - Advance mode for Bitcoin experts 34 | 35 | 36 | #####Features only available on web, but not on mobile: 37 | - Multiple wallets(identities) instead of one per device. 38 | - Ability to backup/restore from a encrypted file, that includes all wallet metadata, not just bitcoins 39 | - Multiple outputs in transaction 40 | - A brain wallet tool that allow for the creation of wallets and transactions from cold storage 41 | - Sign/Verify signatures 42 | 43 | ##### Advance features: 44 | - Pick Your Preferred block explorer API, currently we support Bitpay’s Insight and blockchain.info. You can also point ArcBit to your own Insight Server. 45 | - Import private keys support 46 | - Import BIP38 encrypted private keys support 47 | - Import watch only addresses support 48 | - Import HD wallet account keys support 49 | - Import HD wallet watch only account keys support 50 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Arcbit", 3 | "version": "1.0.0", 4 | "homepage": "http://arcbit.io", 5 | "authors": [ 6 | "Tim ", 7 | "Caedes ", 8 | "Sem " 9 | ], 10 | "description": "Bitcoin wallet", 11 | "main": "js/main.js", 12 | "keywords": [ 13 | "bitcoin", 14 | "crypto" 15 | ], 16 | "license": "AGPLv3", 17 | "private": true, 18 | "ignore": [ 19 | "**/.*", 20 | "node_modules", 21 | "bower_components", 22 | "vendors", 23 | "test", 24 | "tests" 25 | ], 26 | "dependencies": { 27 | "font-awesome": "~4.0.3", 28 | "mnemonic.js": "~1.0.0", 29 | "sjcl": "~1.0.0", 30 | "bitcoinjs-lib": "git://github.com/bitcoinjs/bitcoinjs-lib#master", 31 | "angular": "~1.2.13", 32 | "ngprogress": "~1.0.4", 33 | "AngularJS-Toaster": "~0.4.3", 34 | "angular-route": "~1.2.14", 35 | "foundation": "~5.3", 36 | "angular-foundation": "~0.3.1", 37 | "requirejs": "~2.1.11", 38 | "requirejs-domready": "~2.0.1", 39 | "jsqrcode": "https://github.com/LazarSoft/jsqrcode.git#master", 40 | "qrcodejs": "https://github.com/davidshimjs/qrcodejs.git", 41 | "identicon": "https://github.com/stewartlord/identicon.js.git", 42 | "angular-xeditable": "~0.1.8", 43 | "async": "~0.7.0", 44 | "big.js": "~2.5.0", 45 | "angular-translate": "~2.2.0", 46 | "angular-translate-loader-static-files": "~2.2.0", 47 | "angular-spinner": "~0.8.0" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ArcBit Bitcoin Wallet", 3 | "short_name": "ArcBit", 4 | "description": "Full featured yet simple bitcoin wallet.", 5 | "homepage_url": "http://arcbit.io", 6 | "version": "1.2.5", 7 | "manifest_version": 2, 8 | "icons": { 9 | "128": "src/images/icon_128.png" 10 | }, 11 | "browser_action": { 12 | "default_title": "Arcbit", 13 | "default_icon": "src/images/icon_128.png", 14 | "default_popup": "src/html/popup.html" 15 | }, 16 | "background": { 17 | "page": "src/html/background.html" 18 | }, 19 | "permissions": [ 20 | "storage", "unlimitedStorage", "" 21 | ], 22 | "options_page": "src/html/index.html#/settings", 23 | "minimum_chrome_version": "48.0" 24 | } 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "arcbit", 3 | "description": "You only need to install this if you are compiling SCSS for the frontend", 4 | "version": "1.0.0", 5 | "homepage": "", 6 | "author": { 7 | "name": "", 8 | "email": "" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "" 13 | }, 14 | "devDependencies": { 15 | "node-sass": "~0.8.1", 16 | "grunt": "~0.4.2", 17 | "grunt-contrib-watch": "git://github.com/gruntjs/grunt-contrib-watch", 18 | "grunt-contrib-clean": "~0.5.0", 19 | "grunt-contrib-copy": "~0.5.0", 20 | "grunt-contrib-cssmin": "~0.7.0", 21 | "grunt-uncss": "~0.1.9", 22 | "grunt-contrib-uglify": "~0.3.2", 23 | "grunt-contrib-concat": "~0.3.0", 24 | "grunt-contrib-jshint": "~0.8.0", 25 | "grunt-contrib-connect": "~0.6.0", 26 | "grunt-usemin": "~2.0.2", 27 | "grunt-sass": "~0.10.0", 28 | "karma": "~v0.12", 29 | "karma-jasmine": "~v0.2.2", 30 | "karma-requirejs": "~v0.2.1", 31 | "karma-firefox-launcher": "~v0.1.3", 32 | "karma-coverage": "~0.2.1", 33 | "coveralls": "~2.10.0", 34 | "recursive-readdir": "^1.2.0", 35 | "js-beautify": "^1.5.4" 36 | }, 37 | "scripts": { 38 | "test": "./node_modules/karma/bin/karma start test/karma.conf.js --single-run", 39 | "i18n-update": "node src/i18n/_update.js > src/i18n/en_US.json" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/fonts/ClearSans-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcbit/arcbit-web/fe0e9d00f08b3e79b4804dda0f977f8dc6db17a5/src/fonts/ClearSans-Bold.ttf -------------------------------------------------------------------------------- /src/fonts/ClearSans-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcbit/arcbit-web/fe0e9d00f08b3e79b4804dda0f977f8dc6db17a5/src/fonts/ClearSans-BoldItalic.ttf -------------------------------------------------------------------------------- /src/fonts/ClearSans-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcbit/arcbit-web/fe0e9d00f08b3e79b4804dda0f977f8dc6db17a5/src/fonts/ClearSans-Italic.ttf -------------------------------------------------------------------------------- /src/fonts/ClearSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcbit/arcbit-web/fe0e9d00f08b3e79b4804dda0f977f8dc6db17a5/src/fonts/ClearSans-Regular.ttf -------------------------------------------------------------------------------- /src/html/background.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Background page 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ArcBit 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 18 |
19 | 20 | 26 | 27 | 28 | 40 | 45 | 46 | 47 | 48 |
49 |
50 |
51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/html/modals/ask-password.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
{{vars.text}}
4 | 5 |
6 | 7 | {{'Cancel'|_}} 8 |
9 |
10 |
11 |
12 | 13 |
14 | -------------------------------------------------------------------------------- /src/html/modals/confirm-delete.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 7 |
8 |
9 | 10 |
11 | -------------------------------------------------------------------------------- /src/html/modals/import.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

{{'Import from file'|_}}

4 | 5 |
6 |
7 |

{{importFile.name}} {{importFile.size}} kB

8 |

{{'This file is password protected'|_}}

9 |
10 | 11 | {{error}} 12 |
13 | 14 | {{'Cancel'|_}} 15 |
16 |
17 |
18 | 19 |
20 | {{'Cancel'|_}} 21 | 22 |
23 |
24 |
25 | 26 |
27 | -------------------------------------------------------------------------------- /src/html/modals/payment-review.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

{{'Transaction Summary'|_}}

4 |
{{'To'|_}}:
5 |
6 | {{vars.tos[0].address}} 7 |
{{'Amount'|_}}:
8 | {{vars.tos[0].bitcoinAmount}} {{vars.bitcoinCode}} / {{vars.tos[0].fiatAmount}} {{vars.currencyCode}} 9 |
10 |
11 |
12 | {{to.address}} 13 |

{{to.bitcoinAmount}} {{vars.bitcoinCode}} / {{to.fiatAmount}} {{vars.currencyCode}}

14 |
15 |
16 |
{{'Fee Amount'|_}}:
17 | {{vars.feeAmount}} {{vars.bitcoinCode}} 18 |
19 | 20 |
21 |
22 |
{{'Transaction Hex'|_}}:
23 | {{vars.txHex}} 24 |
25 |
26 | 27 | {{'Cancel'|_}} 28 |
29 |
30 |
31 |
32 | 33 |
34 | -------------------------------------------------------------------------------- /src/html/modals/pick-contact.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 |

{{'Select a contact'|_}}

6 |
7 |
8 | 9 |
10 |
11 |
12 |
    13 |
  • 14 |
    15 | 16 |
    {{contact.name}}
    17 |
    18 |
  • 19 |
20 |
21 |
22 |
23 |
24 | 25 |
26 | 27 |
28 | -------------------------------------------------------------------------------- /src/html/modals/prompt-for-input.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
{{vars.title}}
4 | {{vars.description}} 5 | 6 |
7 | 8 | {{'Cancel'|_}} 9 |
10 |
11 |
12 |
13 | 14 |
15 | -------------------------------------------------------------------------------- /src/html/modals/prompt-ok-cancel.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
{{vars.title}}
4 | {{vars.description}} 5 |
6 | 7 | 8 | {{vars.cancelText}} 9 | {{'Cancel'|_}} 10 |
11 |
12 |
13 |
14 | 15 |
16 | -------------------------------------------------------------------------------- /src/html/modals/prompt-ok.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
{{vars.title}}
4 | {{vars.description}} 5 |
6 | 7 | 8 |
9 |
10 |
11 |
12 | 13 |
14 | -------------------------------------------------------------------------------- /src/html/modals/prompt-sign-msg.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

{{'Sign message'|_}}

4 | 5 |
6 | 7 | 8 | 9 | 10 |
11 |
12 | 13 | 14 |
15 | 16 | 17 |
18 | 19 | 20 |
21 | 25 |
26 |
27 |
28 | 29 |
30 | -------------------------------------------------------------------------------- /src/html/modals/prompt-spinner.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 |
6 |
{{vars.desc}}
7 |
8 |
9 | -------------------------------------------------------------------------------- /src/html/modals/prompt-text-area.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

{{vars.title}}

4 | 5 | 6 | 7 | 8 | {{'Cancel'|_}} 9 |
10 |
11 |
12 | 13 |
14 | -------------------------------------------------------------------------------- /src/html/modals/scan-qr.html: -------------------------------------------------------------------------------- 1 |
2 |

{{'Scan QR code'|_}}

3 | 4 |
5 |
6 | 7 |
8 | -------------------------------------------------------------------------------- /src/html/modals/show-qr.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
{{vars.value}}
7 |
8 |
9 |
10 | 11 | 12 |
13 | -------------------------------------------------------------------------------- /src/html/partials/contact.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | {{vars.contact.name}} 6 |
7 | 8 |
9 |
10 |
11 | 12 |
13 |
14 | {{'Copy'|_}} 15 |
16 |
17 | QR 18 |
19 |
20 |
21 | 22 |
23 |
24 |
25 | {{'Back'|_}} 26 |
27 |
28 |
29 | 30 |
31 | 35 |
36 |
37 | 38 | -------------------------------------------------------------------------------- /src/html/partials/contacts.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

{{'Contacts'|_}}

4 |
5 | 8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | 16 |
17 |
18 | 19 |
20 |
21 |
22 |
23 | 24 |
25 |
26 | 27 |
28 | 29 |
30 |
31 |
32 |
33 |
34 | {{'Cancel'|_}} 35 | 36 |
37 |
38 |
39 |
40 | 41 |
42 |
43 |
44 |
    45 |
  • 46 |
    47 | 48 |
    {{contact.name}}
    49 |
    50 |
  • 51 |
52 |
53 |
54 |
55 | -------------------------------------------------------------------------------- /src/html/partials/identities.html: -------------------------------------------------------------------------------- 1 |
2 | 8 |
9 |
10 |
    11 |
  • 12 |
    13 |

    {{identityName}}

    14 |
    15 |
    16 | 17 |
    18 |
    19 | 20 |
    21 |
    22 |
    23 |
  • 24 |
25 |
26 |
27 |
28 | -------------------------------------------------------------------------------- /src/html/partials/login.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |
5 | 6 |
7 |
8 |
9 |
10 |
11 | 12 |
13 |
14 |
15 |
16 | -------------------------------------------------------------------------------- /src/html/partials/tools.html: -------------------------------------------------------------------------------- 1 |
2 |

{{'Tools'|_}}

3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | -------------------------------------------------------------------------------- /src/html/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Arcbit 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 18 | 19 | 20 |
21 |
22 | {{currentIdentity}} 23 |
24 |
25 | {{anIdentity.id}} 26 |
27 |
28 | 29 | {{'Open wallet'|_}} 30 | 31 |
32 | 37 |
38 |
39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/html/tools/signing.html: -------------------------------------------------------------------------------- 1 |

{{'Sign message'|_}}

2 | {{'Sign'|_}} 3 | 4 | -------------------------------------------------------------------------------- /src/html/tools/verifying.html: -------------------------------------------------------------------------------- 1 |

{{'Verify signed message'|_}}

2 | {{'Verify'|_}} 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 |
12 | 13 |
14 |
15 | {{'Cancel'|_}} 16 |
17 |
18 | 19 |
20 | -------------------------------------------------------------------------------- /src/html/wallet/actions.html: -------------------------------------------------------------------------------- 1 |
2 | 19 | 20 |
21 | 35 |
36 | 37 |
38 |
39 |
{{'Scan For Missing Reusable Address Payment'|_}}
40 |
41 |
42 |
43 | 44 |
45 | 46 | {{'Scan For Payment'|_}} 47 |
48 |
49 |
50 |
51 | 52 |
53 |
54 |
{{'Get Unspent Outputs'|_}}
55 |
56 | 59 |
60 |
61 |
62 |
63 | -------------------------------------------------------------------------------- /src/html/wallet/dashboard.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | -------------------------------------------------------------------------------- /src/html/wallet/history.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | {{'You have no history in this account.'|_}} 5 |
6 |
7 | 8 |
9 | 23 | 24 | 25 |
26 |
27 | {{'Address'|_}} 28 |
29 | 30 |
31 | {{'Amount'|_}} 32 |
33 |
34 | {{'Balance'|_}} 35 |
36 |
37 |
38 |
39 | 40 | 41 | 42 | 43 | {{row.address}} 44 | 45 | 46 | {{row.description}} 47 | 48 | 49 | 50 |
51 |
52 | {{row.timeText}} 53 |
54 | 55 |
56 | 57 | {{row.amount | formatProperCurrency:row.amountType:false}} 58 |
59 |
60 | {{row.balanceAsOfTx | formatProperCurrency:1:false}} 61 |
62 |
63 |
64 |
65 | 70 |
71 |
72 |
73 |
74 | -------------------------------------------------------------------------------- /src/html/wallet/new_account.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 | 8 |
9 | 10 |
11 | 12 |
13 |
14 |
15 |
16 | 17 |
18 |
19 | -------------------------------------------------------------------------------- /src/html/wallet/new_imported_account.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 |
7 |
8 |
9 |
10 | 11 |
12 | 13 |
14 |
15 |
16 |
17 |
18 | 19 |
20 |
21 | 22 |
23 | 24 |
25 | 26 |
27 |
28 | -------------------------------------------------------------------------------- /src/html/wallet/new_imported_address.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 |
7 |
8 |
9 |
10 | 11 | 12 | 13 |
14 | 15 |
16 |
17 |
18 |
19 | 20 | 21 |
22 | 23 |
24 |
25 | 26 |
27 |
28 | 29 |
30 | 31 |
32 | 33 |
34 |
35 | -------------------------------------------------------------------------------- /src/html/wallet/new_imported_watch_account.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
7 |
{{'You can spend from a watch only account by importing the account private key before you spend, or you can use the ArcBit brain wallet tool to make a transaction without ever having your private keys touch a internet facing computer.'|_}}
8 |
9 |
10 | 11 |
12 |
13 |
14 |
15 | 16 |
17 | 18 |
19 |
20 |
21 |
22 |
23 | 24 |
25 |
26 | 27 |
28 | 29 |
30 | 31 |
32 |
33 | -------------------------------------------------------------------------------- /src/html/wallet/new_imported_watch_address.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | 6 |
7 |
8 |
9 |
10 | 11 |
12 | 13 |
14 |
15 |
16 |
17 |
18 | 19 |
20 |
21 | 22 |
23 | 24 |
25 | 26 |
27 |
28 | -------------------------------------------------------------------------------- /src/html/wallet/quick_send.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
{{'To'|_}}
6 |
7 | 8 |
9 |
10 | 11 |
12 |
13 | 14 |
15 |
16 | 17 |
18 |
19 |
20 |
{{'Amount'|_}}
21 |
22 | 23 |
24 | 27 |
28 |
29 | 30 |
31 |
32 |
33 | 34 |
35 | 38 |
39 |
40 |
41 | 42 |
43 | 46 |
47 | 48 |
49 |
50 |
{{'Fee Amount'|_}}
51 |
52 | 53 |
54 | 57 |
58 |
59 |
60 | 61 |
62 | 65 |
66 |
67 |
68 | -------------------------------------------------------------------------------- /src/html/wallet/receiving_addresses.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 | {{(pocket.type == 'hd'||pocket.type == 'ihd') ? ('Reusable Address'|_) : ('Address'|_)}} 6 |
7 |
8 | 9 |
10 |
11 | 12 |
13 |
14 | 15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | 36 | 37 |
38 |
39 | 40 |
41 | 42 |
43 | {{address.address}} 44 |
45 |
46 |
47 |
48 |
49 | 50 |
51 |
52 |
53 |
54 | -------------------------------------------------------------------------------- /src/i18n/_index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define(function() { 4 | var lang = [ 5 | {"name": "English (US)", "code": "en_US"}, 6 | {"name": "简体中文", "code": "zh_CN"}, 7 | {"name": "繁体中文", "code": "tw_CN"}, 8 | {"name": "Español", "code": "es_ES"}, 9 | {"name": "русский", "code": "ru_RU"}, 10 | {"name": "Deutsche", "code": "de_DE"} 11 | ]; 12 | 13 | var fallbacks = { 14 | "en": "en_US", 15 | "zh": "zh_CN", 16 | "tw": "tw_CN", 17 | "es": "es_ES", 18 | "ru": "ru_RU", 19 | "de": "de_DE" 20 | }; 21 | 22 | Object.defineProperty(lang, 'preferedLanguage', { 23 | value: function(preferedLanguages) { 24 | preferedLanguages = preferedLanguages || navigator.languages; 25 | preferedLanguages = preferedLanguages.join('|').replace(/-/g, '_').split('|'); 26 | 27 | var availableLanguages = lang.map(function(l) { 28 | return l.code; 29 | }); 30 | for (var key in fallbacks) { 31 | availableLanguages.push(key); 32 | }; 33 | 34 | // Intersection between available and prefered languages 35 | var intersection = preferedLanguages.map(function(i) { 36 | return availableLanguages.indexOf(i) >= 0 ? fallbacks[i] || i : null; 37 | }); 38 | // Suppress null values in array 39 | intersection = intersection.filter(function(i) { 40 | return i; 41 | }); 42 | 43 | return intersection[0]; 44 | } 45 | }); 46 | return lang; 47 | }); 48 | -------------------------------------------------------------------------------- /src/i18n/_update.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var recursive = require('recursive-readdir'); 4 | var fs = require('fs'); 5 | var beautify = require('js-beautify').js_beautify; 6 | 7 | recursive('src/html', function (err, htmlFiles) { 8 | recursive('src/js', function (err, jsFiles) { 9 | var files = htmlFiles.concat(jsFiles); 10 | var arr = []; 11 | var obj = {}; 12 | // Files is an array of filename 13 | files.forEach(function(file) { 14 | var data = fs.readFileSync(file, 'utf-8'); 15 | data = data.replace(/\\'/g, "@replace@"); 16 | var inHtml = data.match(/[\{\(]'[^']*'\|_/g); 17 | var inJs = data.match(/\W_\('[^']*'/g); 18 | var inErrors = data.match(/\WError\(\'[^'|]*['|]/g) 19 | if (inHtml) { 20 | arr = arr.concat(inHtml); 21 | } 22 | if (inJs) { 23 | arr = arr.concat(inJs); 24 | } 25 | if (inErrors) { 26 | arr = arr.concat(inErrors); 27 | } 28 | }); 29 | arr.forEach(function(str, key) { 30 | if (str.indexOf('_(') >= 0) { 31 | str = str.slice(4,str.length-1) 32 | } else if (/\WError\(\'/.test(str)) { 33 | str = str.slice(8,str.length-1); 34 | } else { 35 | str = str.slice(2,str.length-3); 36 | } 37 | arr[key] = str.replace("@replace@", "'"); 38 | }); 39 | arr.sort(); 40 | arr.forEach(function(str) { 41 | obj[str] = str; 42 | }); 43 | console.log(beautify(JSON.stringify(obj))); 44 | }); 45 | }); 46 | -------------------------------------------------------------------------------- /src/images/encrypt.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 9 | 10 | 11 | 13 | 14 | 15 | 24 | 25 | 26 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/images/icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcbit/arcbit-web/fe0e9d00f08b3e79b4804dda0f977f8dc6db17a5/src/images/icon_128.png -------------------------------------------------------------------------------- /src/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | image/svg+xml 56 | -------------------------------------------------------------------------------- /src/images/notifications.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 13 | 14 | 16 | 17 | 18 | 20 | 21 | 22 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/images/send.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 14 | 15 | 16 | 27 | 28 | 29 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/images/virtual.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | 11 | 12 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/js/arcbit.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @fileOverview Main object for generic dark wallet api. 3 | */ 4 | 'use strict'; 5 | 6 | define(function() { 7 | // ArcBit object. 8 | var ArcBit = { 9 | 10 | /** 11 | * Internal api version. Gets saved by the backend as servicesStatus.apiVersion, 12 | * so frontend code can see if the backend needs to be restarted. 13 | * check like: 14 | * 15 | * ArcBit.apiVersion === ArcBit.core.servicesStatus.apiVersion 16 | */ 17 | apiVersion: 5, 18 | 19 | /** 20 | * Get the wallet service. 21 | * 22 | * @returns {Object} 23 | */ 24 | get core() {return chrome.extension.getBackgroundPage();}, 25 | 26 | /** 27 | * Get a service from the background script. 28 | * 29 | * @returns {Object} 30 | */ 31 | get service() {return ArcBit.core.getServices();}, 32 | 33 | /** 34 | * Identity key ring. Holds all identities. 35 | * 36 | * @returns {Object} 37 | */ 38 | getKeyRing: function() {return ArcBit.core.getKeyRing();}, 39 | 40 | /** 41 | * Get identity 42 | * 43 | * @param {Number} [idx] Index of the identity, default is current. 44 | * @returns {Object} 45 | */ 46 | getIdentity: function(idx) {return ArcBit.core.getIdentity(idx);} 47 | }; 48 | return ArcBit; 49 | }); 50 | -------------------------------------------------------------------------------- /src/js/backend/loader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * configure RequireJS 3 | * prefer named modules to long paths 4 | */ 5 | 'use strict'; 6 | 7 | requirejs.config({ 8 | baseUrl: '../js', 9 | paths: { 10 | 'angular': '../vendors/angular/angular.min', 11 | 'angular-animate': '../vendors/angular-animate/angular-animate.min', 12 | 'angular-route': '../vendors/angular-route/angular-route.min', 13 | 'mm.foundation': '../vendors/angular-foundation/mm-foundation-tpls.min', 14 | 'angular-xeditable': '../vendors/angular-xeditable/dist/js/xeditable.min', 15 | 'angular-translate': '../vendors/angular-translate/angular-translate.min', 16 | 'angular-translate-loader-static-file': '../vendors/angular-translate-loader-static-files/angular-translate-loader-static-files.min', 17 | 'ngProgress': "../vendors/ngprogress/build/ngProgress", 18 | 'toaster': "../vendors/AngularJS-Toaster/toaster", 19 | 'crypto-js': "../vendors/crypto-js/cryptojs", 20 | 'identicon': "../vendors/identicon/identicon", 21 | 'pnglib': "../vendors/identicon/pnglib", 22 | 'qrcodejs': "../vendors/qrcodejs/qrcode", 23 | 'jsqrcode': "../vendors/jsqrcode/jsqrcode", 24 | 'async': "../vendors/async/lib/async", 25 | 'big': "../vendors/big.js/big.min", 26 | 'bip39': "../vendors/bip39/bip39", 27 | 'spin': "../vendors/spin.js/spin.min", 28 | 'angularSpinner': "../vendors/angular-spinner/angular-spinner.min", 29 | 'socket-io': '../vendors/socket-io-client/socket.io', 30 | 'angular-socket-io': '../vendors/angular-socket-io/socket.min', 31 | 32 | 'bitcoinjs-lib': "../vendors/bitcoinjs-lib/bitcoinjs", 33 | 'sjcl-real': "../vendors/sjcl/sjcl", 34 | 35 | 'domReady': '../vendors/requirejs-domready/domReady', 36 | 'sjcl': 'util/fixes', 37 | 38 | 'available_languages': '../i18n/_index' 39 | }, 40 | 41 | /** 42 | * for libs that either do not support AMD out of the box 43 | */ 44 | shim: { 45 | 'angular': { 46 | exports: 'angular' 47 | }, 48 | 'angular-animate': { 49 | deps: ['angular'] 50 | }, 51 | 'angular-route': { 52 | deps: ['angular'] 53 | }, 54 | 'mm.foundation': { 55 | deps: ['angular'] 56 | }, 57 | 'angular-xeditable': { 58 | deps: ['angular'] 59 | }, 60 | 'angular-translate': { 61 | deps: ['angular'] 62 | }, 63 | 'angular-translate-loader-static-file': { 64 | deps: ['angular', 'angular-translate'] 65 | }, 66 | 'ngProgress': { 67 | deps: ['angular'] 68 | }, 69 | 'toaster': { 70 | deps: ['angular'] 71 | }, 72 | 'socket-io': { 73 | exports: 'io' 74 | }, 75 | 'angular-socket-io': { 76 | deps: ['angular', 'socket-io'] 77 | }, 78 | 'qrcodejs': { 79 | exports: 'QRCode' 80 | }, 81 | 'crypto-js': { 82 | exports: 'CryptoJS' 83 | }, 84 | 'jsqrcode': { 85 | exports: 'qrcode' 86 | }, 87 | 'arcbit': { 88 | exports: 'ArcBit' 89 | }, 90 | 'identicon': { 91 | deps: ['pnglib'], 92 | exports: 'Identicon' 93 | }, 94 | 'pnglib': { 95 | exports: 'PNGlib' 96 | }, 97 | 'sjcl-real': { 98 | exports: 'sjcl' 99 | } 100 | } 101 | }); 102 | -------------------------------------------------------------------------------- /src/js/backend/main.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @fileOverview Background service running for the wallet 3 | */ 4 | 'use strict'; 5 | 6 | requirejs([ 7 | 'arcbit', 8 | 'backend/port', 9 | 'backend/services/wallet'], 10 | function(ArcBit, Port) { 11 | 12 | var serviceClasses = [].splice.call(arguments, 2); 13 | 14 | function ArcBitService(serviceClasses) { 15 | var self = this; 16 | 17 | // Backend services 18 | var services = this.initializeServices(serviceClasses); 19 | 20 | 21 | /*************************************** 22 | /* Hook up some utility functions 23 | */ 24 | 25 | this.loadIdentity = function(idx, callback) { 26 | return services.wallet.loadIdentity(idx, callback); 27 | }; 28 | 29 | // Get an identity from the keyring 30 | this.getIdentity = function(idx) { 31 | return services.wallet.getIdentity(idx); 32 | }; 33 | this.getCurrentIdentity = function() { 34 | return services.wallet.getCurrentIdentity(); 35 | }; 36 | 37 | /*************************************** 38 | /* Global communications 39 | */ 40 | 41 | this.getKeyRing = function() { 42 | return services.wallet.getKeyRing(); 43 | }; 44 | 45 | this.getServices = function() { 46 | return self.service; 47 | }; 48 | } 49 | 50 | 51 | ArcBitService.prototype.initializeServices = function(serviceClasses) { 52 | this.service = {}; 53 | var services = {}; 54 | 55 | for(var i in serviceClasses) { 56 | var service = new serviceClasses[i](this); 57 | if (!service.name) { 58 | throw Error('Service {0} has no name property|'+ serviceClasses[i].name); 59 | } 60 | if (Object.keys(services).indexOf(service.name) !== -1) { 61 | throw Error('Name of service {0} repeated|'+ service.name); 62 | } 63 | services[service.name] = service; 64 | } 65 | 66 | // Public API 67 | for(var i in services) { 68 | Object.defineProperty(this.service, i, { 69 | get: function() { 70 | var j = services[i]; 71 | return function() { 72 | return j; 73 | }; 74 | }() 75 | }); 76 | }; 77 | 78 | // We save the apiVersion here so we can check what version the backend is running 79 | this.servicesStatus = {apiVersion: ArcBit.apiVersion }; 80 | return services; 81 | }; 82 | 83 | /*************************************** 84 | /* Communications 85 | */ 86 | var sendInternalMessage = function(msg) { 87 | chrome.runtime.sendMessage(chrome.runtime.id, msg); 88 | }; 89 | 90 | var addListener = function(callback) { 91 | chrome.runtime.onMessage.addListener(callback); 92 | }; 93 | 94 | 95 | /*************************************** 96 | /* Service instance that will be running in the background page 97 | */ 98 | var service = new ArcBitService(serviceClasses); 99 | 100 | 101 | /*************************************** 102 | /* Bindings for the page window so we can have easy access 103 | */ 104 | 105 | window.connect = function(_server) { return service.connect(_server); }; 106 | 107 | window.loadIdentity = service.loadIdentity; 108 | window.getIdentity = function(idx) { return service.getIdentity(idx); }; 109 | window.getCurrentIdentity = service.getCurrentIdentity; 110 | 111 | window.getKeyRing = service.getKeyRing; 112 | window.servicesStatus = service.servicesStatus; 113 | 114 | window.getClient = service.getClient; 115 | window.getServices = service.getServices; 116 | 117 | window.addListener = addListener; 118 | window.sendInternalMessage = sendInternalMessage; 119 | }); 120 | -------------------------------------------------------------------------------- /src/js/backend/port.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define(function () { 4 | var allPorts = {}; 5 | var instances = {}; 6 | var Port = { 7 | /* 8 | * Listen on the given port 9 | * @param {String} name Service name 10 | * @param {Function} onMessage Message callback 11 | * @param {Function} onConnect Connect callback 12 | * @param {Function} onDisconnect Disconnect callback 13 | */ 14 | listen: function(name, onMessage, onConnect, onDisconnect) { 15 | if (!allPorts.hasOwnProperty(name)) { 16 | allPorts[name] = []; 17 | } 18 | if (!instances.hasOwnProperty(name)) { 19 | // Save callbacks for interaction with child services 20 | // TODO: child services cannot send messages directly for now 21 | instances[name] = {onConnect: function(port) { 22 | onConnect ? onConnect(port) : null; 23 | port.postMessage({type: 'portConnected'}); 24 | }, onDisconnect: onDisconnect}; 25 | } else { 26 | throw Error('Service with duplicate name!'); 27 | } 28 | chrome.runtime.onConnect.addListener(function(port) { 29 | if (port.name == name) { 30 | allPorts[name].push(port); 31 | // Call the onConnect callback 32 | onConnect ? onConnect(port) : null; 33 | // Register onMessage callback 34 | onMessage ? port.onMessage.addListener(onMessage) : null; 35 | port.onDisconnect.addListener(function(_port) { 36 | if (_port.name == name) { 37 | allPorts[name].splice(allPorts[name].indexOf(_port), 1); 38 | // Call the onDisconnect callback 39 | onDisconnect ? onDisconnect(_port) : null; 40 | } 41 | }); 42 | port.postMessage({type: 'portConnected'}); 43 | } 44 | }); 45 | }, 46 | 47 | /* 48 | * Post data to all listeners on given Port 49 | * @param {String} name Service name 50 | * @param {Object} data Object to send to listeners 51 | */ 52 | post: function(name, data) { 53 | if (allPorts.hasOwnProperty(name)) { 54 | allPorts[name].forEach(function(port) { 55 | port.postMessage(data); 56 | }); 57 | } 58 | }, 59 | 60 | /* 61 | * Connect a backend module to a port 62 | * @param {String} name Service name 63 | * @param {Object} onMessage Callback for messages on the given port 64 | */ 65 | connect: function(name, onMessage) { 66 | if (!allPorts.hasOwnProperty(name)) { 67 | allPorts[name] = []; 68 | } 69 | var port = {postMessage: onMessage}; 70 | allPorts[name].push(port); 71 | if (instances[name]) { 72 | instances[name].onConnect ? instances[name].onConnect(port) : null; 73 | } else { 74 | // TODO: won't get the onConnect callback 75 | console.log("["+name+"] pre-connect child service"); 76 | } 77 | return port; 78 | } 79 | }; 80 | return Port; 81 | }); 82 | -------------------------------------------------------------------------------- /src/js/frontend/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * loads sub modules and wraps them up into the main module 3 | * this should be used for top-level module definitions only 4 | */ 5 | 'use strict'; 6 | 7 | define([ 8 | 'require', 9 | 'angular', 10 | 'available_languages', 11 | 'angular-route', 12 | 'angular-animate', 13 | 'angular-socket-io', 14 | 'mm.foundation', 15 | 'angular-xeditable', 16 | 'angular-translate', 17 | 'angular-translate-loader-static-file', 18 | 'ngProgress', 19 | 'toaster', 20 | 'angularSpinner', 21 | 'frontend/controllers/index', 22 | 'frontend/directives/index', 23 | 'frontend/filters/index', 24 | 'frontend/providers/index' 25 | ], function (requirejs, angular, AvailableLanguages) { 26 | var app = angular.module('ArcBit', [ 27 | 'btford.socket-io', 28 | 'ngRoute', 'mm.foundation', 'xeditable', 'pascalprecht.translate', 29 | 'ngProgress', 'ngAnimate', 'toaster', 'angularSpinner', 30 | 'ArcBit.controllers', 31 | 'ArcBit.directives', 32 | 'ArcBit.filters', 33 | 'ArcBit.providers' 34 | ]); 35 | requirejs(['domReady!'], function (document) { 36 | // * NOTE: the ng-app attribute should not be on the index.html when using ng.bootstrap 37 | angular.bootstrap(document, ['ArcBit']); 38 | }); 39 | // angular-translate configuration. 40 | app.config(function($translateProvider) { 41 | $translateProvider.useStaticFilesLoader({ 42 | prefix: '../i18n/', 43 | suffix: '.json' 44 | }); 45 | $translateProvider.preferredLanguage(AvailableLanguages.preferedLanguage()); 46 | }); 47 | app.config(function($animateProvider) { 48 | $animateProvider.classNameFilter(/^(?:(?!ng-animate-disabled).)*$/); 49 | }); 50 | // In case we need to initialize something after the application is created. 51 | app.initialize = function() { 52 | }; 53 | return app; 54 | }); 55 | -------------------------------------------------------------------------------- /src/js/frontend/controllers/accountcreate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview AccountCreateCtrl angular controller 3 | */ 4 | 'use strict'; 5 | 6 | define(['./module', 'arcbit', 'model/TLWalletJSONKeys'], function (controllers, ArcBit, TLWalletJSONKeys) { 7 | controllers.controller('AccountCreateCtrl', ['$scope', '$history', '$tabs', 'modals', 'notify', '_Filter', function($scope, $history, $tabs, modals, notify, _) { 8 | $scope.newPocket = {}; 9 | $scope.createPocket = function() { 10 | if ($scope.newPocket.name) { 11 | var identity = ArcBit.getIdentity(); 12 | $scope.selectPocket($scope.newPocket.name, identity.appDelegate.accounts.getNumberOfAccounts()); 13 | var accountObject = identity.appDelegate.accounts.createNewAccount($scope.newPocket.name, TLWalletJSONKeys.TLAccount.NORMAL, true); 14 | // accountObject.getAccountData(function() { 15 | // }, function() { 16 | // }); 17 | $scope.newPocket = {name:''}; 18 | } else { 19 | // cancel 20 | $tabs.open(); 21 | } 22 | }; 23 | }]); 24 | }); 25 | -------------------------------------------------------------------------------- /src/js/frontend/controllers/identities.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define(['./module', 'frontend/port', 'arcbit'], function (controllers, Port, ArcBit) { 4 | controllers.controller('IdentitiesCtrl', ['$scope', '$window', 'modals', 'notify', '_Filter', function($scope, $window, modals, notify, _) { 5 | Port.connectNg('wallet', $scope, function(data) { 6 | if (data.type == 'ready' || data.type == 'rename') { 7 | $scope.currentIdentity = ArcBit.getIdentity().name; 8 | $scope.identities = ArcBit.getKeyRing().identities; 9 | $scope.loadedIdentities = Object.keys($scope.identities); 10 | $scope.availableIdentities = ArcBit.getKeyRing().availableIdentities; 11 | if (!$scope.$$phase) { 12 | $scope.$apply(); 13 | } 14 | } 15 | }); 16 | 17 | var deleteCurrentIdentity = function(identityName) { 18 | var identityIdx = $scope.availableIdentities.indexOf(identityName); 19 | if ($scope.availableIdentities.length == 1) { 20 | notify.warning(_('Can\'t delete the last wallet!')); 21 | return; 22 | } 23 | var nextIdentity = identityIdx ? 0 : 1; 24 | ArcBit.core.loadIdentity(nextIdentity, function() { 25 | // Delete the identity after a new identity is loaded 26 | deleteOtherIdentity(identityName); 27 | }); 28 | }; 29 | 30 | var deleteOtherIdentity = function(identityName) { 31 | var keyRing = ArcBit.getKeyRing(); 32 | keyRing.remove(identityName, function() { 33 | var identityIdx = $scope.availableIdentities.indexOf(identityName); 34 | if (identityIdx > -1) { 35 | $scope.availableIdentities.splice(identityIdx, 1); 36 | } 37 | notify.note(_('{0} has been deleted.', identityName)); 38 | if (!$scope.$$phase) { 39 | $scope.$apply(); 40 | } 41 | }); 42 | }; 43 | 44 | var confirmDeleteIdentity = function(identityName) { 45 | var identity = ArcBit.getIdentity(); 46 | if (identityName == identity.name) { 47 | deleteCurrentIdentity(identityName); 48 | } else { 49 | deleteOtherIdentity(identityName); 50 | } 51 | }; 52 | 53 | $scope.deleteIdentity = function(identityName) { 54 | if (identityName == ArcBit.getIdentity().appDelegate.walletName) { 55 | notify.warning(_('Can\'t delete your current wallet!')); 56 | return; 57 | } 58 | var aa = _('Accept'); 59 | modals.promptForOKCancel(_('Are you sure you want to delete {0}?', identityName), _('This action can\'t be reverted!'), _('Accept'), null, function() { 60 | confirmDeleteIdentity(identityName); 61 | }); 62 | }; 63 | 64 | }]); 65 | }); 66 | -------------------------------------------------------------------------------- /src/js/frontend/controllers/importedaccountcreate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview ImportedAccountCreateCtrl angular controller 3 | */ 4 | 'use strict'; 5 | 6 | define(['./module', 'arcbit', 'model/TLWallet', 'model/TLHDWalletWrapper', 'model/TLWalletUtils', 'model/TLBitcoinJSWrapper'], function (controllers, ArcBit, 7 | TLWallet, TLHDWalletWrapper, TLWalletUtils, TLBitcoinJSWrapper) { 8 | controllers.controller('ImportedAccountCreateCtrl', ['$scope', '$history', '$animate', '$timeout', '$tabs', 'modals', 9 | 'sounds', 'notify', '_Filter', function($scope, $history, $animate, $timeout, $tabs, modals, sounds, notify, _) { 10 | 11 | $scope.newPocket = {}; 12 | 13 | $scope.getAddressFromQRCode = function() { 14 | modals.scanQr(function(data) { 15 | var pars = TLWalletUtils.parseURI(data); 16 | if (!pars || !pars.address) { 17 | notify.warning(_('URI not supported')); 18 | return; 19 | } 20 | $scope.newPocket.name = pars.address; 21 | sounds.play('keygenEnd'); 22 | }); 23 | }; 24 | 25 | $scope.createImportedAccount = function() { 26 | if ($scope.newPocket.name) { 27 | var identity = ArcBit.getIdentity(); 28 | 29 | if (!TLHDWalletWrapper.isValidExtendedPrivateKey($scope.newPocket.name, TLBitcoinJSWrapper.getNetwork(identity.appDelegate.appWallet.isTestnet()))) { 30 | notify.error(_("Invalid account private key")); 31 | return; 32 | } 33 | $animate.enabled(false); 34 | modals.showSpinner(_('Importing Account')); 35 | 36 | // timeout is used as a workaround for spinner modal showing delay issue 37 | $timeout(function() { 38 | identity.appDelegate.saveWalletNow(); 39 | identity.appDelegate.saveWalletJSONEnabled = false; 40 | $scope.selectImportedAccount($scope.newPocket.name, identity.appDelegate.importedAccounts.getNumberOfAccounts()); 41 | var defaultName = _('Imported Account') + ' ' + (identity.appDelegate.importedAccounts.getNumberOfAccounts() + 1).toString(); 42 | var accountObject = identity.appDelegate.importedAccounts.addAccountWithExtendedKey($scope.newPocket.name, defaultName); 43 | 44 | accountObject.recoverAccount(true, function() { 45 | 46 | accountObject.getAccountData(function() { 47 | $animate.enabled(identity.appDelegate.preferences.getAnimation()); 48 | modals.cancel(); 49 | identity.appDelegate.saveWalletJSONEnabled = true; 50 | identity.appDelegate.saveWalletNow(); 51 | }, function() { 52 | $animate.enabled(identity.appDelegate.preferences.getAnimation()); 53 | modals.cancel(); 54 | identity.appDelegate.saveWalletJSONEnabled = true; 55 | identity.appDelegate.saveWalletNow(); 56 | }); 57 | 58 | $scope.newPocket = {name:''}; 59 | }, function() { 60 | $animate.enabled(identity.appDelegate.preferences.getAnimation()); 61 | modals.cancel(); 62 | }); 63 | }, 27, false); // 27 is min delay for work around for spinner modal showing delay issue 64 | } else { 65 | // cancel 66 | $tabs.open(); 67 | } 68 | }; 69 | }]); 70 | }); 71 | -------------------------------------------------------------------------------- /src/js/frontend/controllers/importedwatchaccountcreate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview ImportedWatchAccountCreateCtrl angular controller 3 | */ 4 | 'use strict'; 5 | 6 | define(['./module', 'arcbit', 'model/TLWallet', 'model/TLHDWalletWrapper', 'model/TLWalletUtils', 'model/TLBitcoinJSWrapper'], function (controllers, ArcBit, 7 | TLWallet, TLHDWalletWrapper, TLWalletUtils, TLBitcoinJSWrapper) { 8 | controllers.controller('ImportedWatchAccountCreateCtrl', ['$scope', '$history', '$animate', '$timeout', '$tabs', 'modals', 9 | 'sounds', 'notify', '_Filter', function($scope, $history, $animate, $timeout, $tabs, modals, sounds, notify, _) { 10 | 11 | $scope.newPocket = {}; 12 | 13 | $scope.getAddressFromQRCode = function() { 14 | modals.scanQr(function(data) { 15 | var pars = TLWalletUtils.parseURI(data); 16 | if (!pars || !pars.address) { 17 | notify.warning(_('URI not supported')); 18 | return; 19 | } 20 | $scope.newPocket.name = pars.address; 21 | sounds.play('keygenEnd'); 22 | }); 23 | }; 24 | 25 | $scope.createImportedWatchAccount = function() { 26 | if ($scope.newPocket.name) { 27 | var identity = ArcBit.getIdentity(); 28 | 29 | if (!TLHDWalletWrapper.isValidExtendedPublicKey($scope.newPocket.name, TLBitcoinJSWrapper.getNetwork(identity.appDelegate.appWallet.isTestnet()))) { 30 | notify.error(_("Invalid account public key")); 31 | return; 32 | } 33 | $animate.enabled(false); 34 | modals.showSpinner(_('Importing Account')); 35 | 36 | $timeout(function() { 37 | identity.appDelegate.saveWalletNow(); 38 | identity.appDelegate.saveWalletJSONEnabled = false; 39 | $scope.selectImportedWatchAccount($scope.newPocket.name, identity.appDelegate.importedWatchAccounts.getNumberOfAccounts()); 40 | 41 | var defaultName = _('Watch Account') + ' ' + (identity.appDelegate.importedWatchAccounts.getNumberOfAccounts() + 1).toString(); 42 | var accountObject = identity.appDelegate.importedWatchAccounts.addAccountWithExtendedKey($scope.newPocket.name, defaultName); 43 | accountObject.recoverAccount(true, function() { 44 | 45 | accountObject.getAccountData(function() { 46 | $animate.enabled(identity.appDelegate.preferences.getAnimation()); 47 | modals.cancel(); 48 | identity.appDelegate.saveWalletJSONEnabled = true; 49 | identity.appDelegate.saveWalletNow(); 50 | }, function() { 51 | $animate.enabled(identity.appDelegate.preferences.getAnimation()); 52 | modals.cancel(); 53 | identity.appDelegate.saveWalletJSONEnabled = true; 54 | identity.appDelegate.saveWalletNow(); 55 | }); 56 | 57 | $scope.newPocket = {name:''}; 58 | }, function() { 59 | $animate.enabled(identity.appDelegate.preferences.getAnimation()); 60 | modals.cancel(); 61 | }); 62 | }, 30, false); 63 | } else { 64 | // cancel 65 | $tabs.open(); 66 | } 67 | }; 68 | }]); 69 | }); 70 | -------------------------------------------------------------------------------- /src/js/frontend/controllers/importedwatchaddresscreate.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview ImportedWatchAddressCreateCtrl angular controller 3 | */ 4 | 'use strict'; 5 | 6 | define(['./module', 'arcbit', 'model/TLWallet', 'model/TLBitcoinJSWrapper', 'model/TLStealthAddress', 'model/TLWalletUtils'], 7 | function (controllers, ArcBit, TLWallet, TLBitcoinJSWrapper, TLStealthAddress, TLWalletUtils) { 8 | controllers.controller('ImportedWatchAddressCreateCtrl', ['$scope', '$history', '$animate', '$timeout', '$tabs', 'modals', 'sounds', 'notify', '_Filter', 9 | function($scope, $history, $animate, $timeout, $tabs, modals, sounds, notify, _) { 10 | 11 | $scope.newPocket = {}; 12 | 13 | $scope.getAddressFromQRCode = function() { 14 | modals.scanQr(function(data) { 15 | var pars = TLWalletUtils.parseURI(data); 16 | if (!pars || !pars.address) { 17 | notify.warning(_('URI not supported')); 18 | return; 19 | } 20 | $scope.newPocket.name = pars.address; 21 | sounds.play('keygenEnd'); 22 | }); 23 | }; 24 | 25 | $scope.createImportedWatchAddress = function() { 26 | if ($scope.newPocket.name) { 27 | var identity = ArcBit.getIdentity(); 28 | if (TLStealthAddress.isStealthAddress($scope.newPocket.name, identity.appDelegate.appWallet.isTestnet())) { 29 | notify.error(_("Cannot import reusable address")); 30 | return; 31 | } 32 | if (!TLBitcoinJSWrapper.isValidAddress($scope.newPocket.name, identity.appDelegate.appWallet.isTestnet())) { 33 | notify.error(_("Invalid address")); 34 | return; 35 | } 36 | $animate.enabled(false); 37 | modals.showSpinner(_('Importing Address')); 38 | $timeout(function() { 39 | $scope.selectImportedWatchAddress($scope.newPocket.name, identity.appDelegate.importedWatchAddresses.getCount()); 40 | 41 | var importedAddress = identity.appDelegate.importedWatchAddresses.addImportedWatchAddress($scope.newPocket.name); 42 | //* 43 | importedAddress.getSingleAddressData(function() { 44 | $animate.enabled(identity.appDelegate.preferences.getAnimation()); 45 | modals.cancel(); 46 | notify.success(_("Imported")); 47 | if(!$scope.$$phase) { 48 | $scope.$apply(); 49 | } 50 | }, function() { 51 | $animate.enabled(identity.appDelegate.preferences.getAnimation()); 52 | modals.cancel(); 53 | notify.success(_("Imported")); 54 | if(!$scope.$$phase) { 55 | $scope.$apply(); 56 | } 57 | }); 58 | $scope.newPocket = {name:''}; 59 | }, 30, false); 60 | } else { 61 | // cancel 62 | $tabs.open(); 63 | } 64 | }; 65 | }]); 66 | }); 67 | -------------------------------------------------------------------------------- /src/js/frontend/controllers/index.js: -------------------------------------------------------------------------------- 1 | /** attach controllers to this module 2 | * if you get 'unknown {x}Provider' errors from angular, be sure they are 3 | * properly referenced in one of the module dependencies in the array. 4 | * below, you can see we bring in our services and constants modules 5 | * which avails each controller of, for example, the `config` constants object. 6 | **/ 7 | 'use strict'; 8 | 9 | define([ 10 | './addresses', 11 | './receivingaddresses', 12 | './backups', 13 | './contacts', 14 | './offline_spend', 15 | './history', 16 | './identities', 17 | './ngmodal', 18 | './login', 19 | './new_wallet', 20 | './accountaction', 21 | './accountcreate', 22 | './importedaccountcreate', 23 | './importedwatchaccountcreate', 24 | './importedaddresscreate', 25 | './importedwatchaddresscreate', 26 | './send', 27 | './settings', 28 | './wallet', 29 | './tools', 30 | './signing' 31 | ], function () {}); 32 | -------------------------------------------------------------------------------- /src/js/frontend/controllers/login.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview ContactsCtrl angular controller 3 | */ 4 | 'use strict'; 5 | 6 | define(['./module', 'arcbit'], function (controllers, ArcBit) { 7 | controllers.controller('loginCtrl', ['$scope', '$routeParams', '$location', '$route', '$history', '$tabs', 'notify', '_Filter', 8 | function($scope, $routeParams, $location, $route, $history, $tabs, notify, _) { 9 | 10 | $scope.loginPassword = ''; 11 | $scope.identityName = ArcBit.getKeyRing().currentIdentityName; 12 | 13 | $scope.login = function() { 14 | var keyRing = ArcBit.getKeyRing(); 15 | 16 | var currentIdentityName = ArcBit.getKeyRing().currentIdentityName; 17 | 18 | keyRing.load(currentIdentityName, function(loadedIdentity) { 19 | 20 | ArcBit.service.wallet.setCurrentIdentity(currentIdentityName); 21 | var identity = ArcBit.getIdentity(); 22 | 23 | try { 24 | identity.appDelegate.initAppDelegate($scope.loginPassword); 25 | } catch(errMsg) { 26 | notify.error(_('Incorrect password')); 27 | if(!$scope.$$phase) { 28 | $scope.$apply(); 29 | } 30 | return; 31 | } 32 | $history.initHistoryProvider(); 33 | identity.appDelegate.saveWalletNow(); 34 | identity.appDelegate.postEvent('wallet', {type:'EVENT_WALLET_LOGIN', identityName:currentIdentityName}); 35 | $scope.loginPassword = ''; 36 | }); 37 | }; 38 | }]); 39 | }); 40 | -------------------------------------------------------------------------------- /src/js/frontend/controllers/module.js: -------------------------------------------------------------------------------- 1 | /** attach controllers to this module 2 | * if you get 'unknown {x}Provider' errors from angular, be sure they are 3 | * properly referenced in one of the module dependencies in the array. 4 | * below, you can see we bring in our services and constants modules 5 | * which avails each controller of, for example, the `config` constants object. 6 | **/ 7 | 'use strict'; 8 | 9 | define(['angular'], function (ng) { 10 | return ng.module('ArcBit.controllers', []); 11 | }); 12 | -------------------------------------------------------------------------------- /src/js/frontend/controllers/ngmodal.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileOverview OverviewCtrl angular controller 3 | */ 4 | 'use strict'; 5 | 6 | define(['./module'], function (controllers) { 7 | controllers.controller('NgModalCtrl', ['$scope', function($scope) { 8 | 9 | $scope.vars = $scope.modals.vars; 10 | 11 | $scope.ok = function(data) { 12 | $scope.modals.show = false; 13 | if ($scope.modals.okCallback) { 14 | $scope.modals.okCallback(data, $scope.vars); 15 | } 16 | } 17 | $scope.cancel = function(reason) { 18 | $scope.modals.cancel(reason); 19 | } 20 | }]); 21 | }); 22 | -------------------------------------------------------------------------------- /src/js/frontend/controllers/signing.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define(['./module', 'arcbit', 'bitcoinjs-lib', 'model/TLBitcoinJSWrapper'], function (controllers, ArcBit, Bitcoin, TLBitcoinJSWrapper) { 4 | 5 | // Controller 6 | controllers.controller('SigningCtrl', ['$scope', 'notify', 'modals', '_Filter', function($scope, notify, modals, _) { 7 | 8 | /** 9 | * Verify a signature 10 | */ 11 | 12 | $scope.verifyText = function() { 13 | var identity = ArcBit.getIdentity(); 14 | var address = $scope.tools.verifyAddress; 15 | var sigText = $scope.tools.verifySig; 16 | var text = $scope.tools.verifyText; 17 | 18 | if (sigText == null) { 19 | notify.warning(_('Missing signature')); 20 | return; 21 | } 22 | if (!address || !TLBitcoinJSWrapper.isValidAddress(address, identity.appDelegate.appWallet.isTestnet())) { 23 | notify.warning(_('Invalid address')); 24 | return; 25 | } 26 | 27 | try { 28 | if (TLBitcoinJSWrapper.verifySignature(address, sigText, text)) { 29 | notify.success(_('Signature ok')); 30 | } else { 31 | notify.warning(_('Invalid signature')); 32 | } 33 | } catch(err) { 34 | notify.warning(_('Invalid signature')); 35 | } 36 | $scope.tools.output = ''; 37 | $scope.verifyOpen = false; 38 | $scope.tools.open = false; 39 | }; 40 | 41 | /** 42 | * Sign the given text 43 | */ 44 | $scope.signText = function() { 45 | var identity = ArcBit.getIdentity(); 46 | modals.promptSignMessage(true, null, function(vars) { 47 | if (vars.key != null && TLBitcoinJSWrapper.isValidPrivateKey(vars.key)) { 48 | vars.addr = TLBitcoinJSWrapper.getAddress(vars.key, identity.appDelegate.appWallet.isTestnet()); 49 | } else { 50 | vars.addr = null; 51 | } 52 | }, function() { 53 | }, function(reason, vars) { 54 | if (reason == 'dont_dismiss') { 55 | if (vars.msg == null) { 56 | notify.warning(_('Missing message')); 57 | return; 58 | } 59 | if (vars.key == null || !TLBitcoinJSWrapper.isValidPrivateKey(vars.key)) { 60 | notify.warning(_('Invalid Private Key')); 61 | return; 62 | } 63 | vars.addr = TLBitcoinJSWrapper.getAddress(vars.key, identity.appDelegate.appWallet.isTestnet()); 64 | vars.sig = TLBitcoinJSWrapper.getSignatureFromKey(vars.key, vars.msg); 65 | if (!$scope.$$phase) { 66 | $scope.$apply(); 67 | } 68 | } 69 | }); 70 | 71 | } 72 | 73 | }]); 74 | }); 75 | -------------------------------------------------------------------------------- /src/js/frontend/controllers/tools.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define(['./module'], function (controllers) { 4 | controllers.controller('ToolsCtrl', ['$scope', function($scope) { 5 | $scope.tools = {}; 6 | }]); 7 | }); 8 | -------------------------------------------------------------------------------- /src/js/frontend/directives/identicon.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define(['./module', 'identicon', 'crypto-js'], function (directives, Identicon, CryptoJS) { 4 | var iconCache = {}; 5 | var hashCache = {}; 6 | directives.directive('identicon', function () { 7 | return { 8 | restrict: 'E', // element 9 | scope: { 10 | hash: '=', 11 | data: '=', 12 | iconSize: '=' 13 | }, 14 | link: function(scope, element, attrs) { 15 | var iconSize = scope.iconSize || 32; 16 | 17 | // Create the identicon 18 | function createFromHex(dataHex) { 19 | var data; 20 | var iconId = [dataHex, iconSize]; 21 | if (iconCache.hasOwnProperty(iconId)) { 22 | data = iconCache[iconId]; 23 | } else { 24 | data = new Identicon(dataHex, iconSize).toString(); 25 | iconCache[iconId] = data; 26 | } 27 | element.html(''); 28 | } 29 | // Watch for hash changes 30 | scope.$watch('hash', function() { 31 | if (scope.hash) { 32 | // take 11 bytes: 22 hex - 60 bit shape, 28 bit color 33 | // skip first 8 bytes to avoid headers (the input can be arbitrary hex) 34 | createFromHex(scope.hash.substr(16, 38)); 35 | } 36 | }); 37 | scope.$watch('data', function() { 38 | if (scope.data) { 39 | var hash; 40 | if (hashCache.hasOwnProperty(scope.data)) { 41 | hash = hashCache[scope.data]; 42 | } else { 43 | hash = CryptoJS.SHA256(scope.data).toString(); 44 | hashCache[scope.data] = hash; 45 | } 46 | // take 11 bytes: 22 hex - 60 bit shape, 28 bit color 47 | // skip first 8 bytes to avoid headers (the input can be arbitrary hex) 48 | createFromHex(hash.substr(16, 38)); 49 | } 50 | }); 51 | } 52 | }; 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /src/js/frontend/directives/index.js: -------------------------------------------------------------------------------- 1 | /** attach directives to this module 2 | **/ 3 | 'use strict'; 4 | 5 | define([ 6 | './identicon', 7 | './qr', 8 | './qr-save-button', 9 | './qr-scanner' 10 | ], function () {}); 11 | -------------------------------------------------------------------------------- /src/js/frontend/directives/module.js: -------------------------------------------------------------------------------- 1 | /** attach directives to this module 2 | **/ 3 | 'use strict'; 4 | 5 | define(['angular'], function (ng) { 6 | 'use strict'; 7 | return ng.module('ArcBit.directives', []); 8 | }); 9 | -------------------------------------------------------------------------------- /src/js/frontend/directives/qr-save-button.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define(['./module', 'qrcodejs'], function (directives, QRCode) { 4 | directives.directive('qrSaveButton', ['$window', '$timeout', function($window, $timeout) { 5 | return { 6 | restrict: 'A', 7 | link: function(scope, element, attrs) { 8 | var div = $window.document.createElement('div'); 9 | var qrcode = new QRCode(div, { 10 | width : attrs.width || 400, 11 | height : attrs.height || 400, 12 | correctLevel : QRCode.CorrectLevel.H 13 | }); 14 | qrcode.makeCode(attrs.data); 15 | $timeout(function() { 16 | var data = div.lastChild.getAttribute('src'); 17 | element[0].setAttribute('href', data); 18 | element[0].setAttribute('download', attrs.data + '.png'); 19 | }, 1); 20 | } 21 | 22 | }; 23 | }]); 24 | }); 25 | -------------------------------------------------------------------------------- /src/js/frontend/directives/qr-scanner.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define(['./module', 'jsqrcode'], function (directives) { 4 | directives.directive('qrScanner', ['$interval', '$window', function($interval, $window) { 5 | return { 6 | restrict: 'E', 7 | scope: { 8 | ngSuccess: '&ngSuccess', 9 | ngError: '&ngError', 10 | ngVideoError: '&ngVideoError' 11 | }, 12 | link: function(scope, element, attrs) { 13 | $window.URL = $window.URL || $window.webkitURL || $window.mozURL || $window.msURL; 14 | navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia; 15 | 16 | var height = attrs.height || 300; 17 | var width = attrs.width || 250; 18 | 19 | var video = $window.document.createElement('video'); 20 | video.setAttribute('width', width); 21 | video.setAttribute('height', height); 22 | video.setAttribute('style', '-moz-transform:rotateY(-180deg);-webkit-transform:rotateY(-180deg);transform:rotateY(-180deg);'); 23 | var canvas = $window.document.createElement('canvas'); 24 | canvas.setAttribute('id', 'qr-canvas'); 25 | canvas.setAttribute('width', width); 26 | canvas.setAttribute('height', height); 27 | canvas.setAttribute('style', 'display:none;'); 28 | 29 | angular.element(element).append(video); 30 | angular.element(element).append(canvas); 31 | var context = canvas.getContext('2d'); 32 | var stopScan; 33 | var localMediaStream; 34 | 35 | var scan = function() { 36 | if (localMediaStream) { 37 | context.drawImage(video, 0, 0, 307,250); 38 | try { 39 | qrcode.decode(); 40 | } catch(e) { 41 | scope.ngError({error: e}); 42 | } 43 | } 44 | }; 45 | 46 | var successCallback = function(stream) { 47 | video.src = ($window.URL && $window.URL.createObjectURL(stream)) || stream; 48 | localMediaStream = stream; 49 | 50 | scope.video = video; 51 | video.play(); 52 | stopScan = $interval(scan, 500); 53 | }; 54 | 55 | // Call the getUserMedia method with our callback functions 56 | if (navigator.getUserMedia) { 57 | navigator.getUserMedia({video: true}, successCallback, function(e) { 58 | scope.ngVideoError({error: e}); 59 | }); 60 | } else { 61 | scope.ngVideoError({error: new Error('Native web camera streaming (getUserMedia) not supported in this browser.')}); 62 | } 63 | 64 | element.bind('$destroy', function() { 65 | if (localMediaStream) { 66 | //localMediaStream.stop(); // get error localMediaStream.stop is not a function 67 | } 68 | if (stopScan) { 69 | $interval.cancel(stopScan); 70 | } 71 | }); 72 | 73 | qrcode.callback = function(data) { 74 | scope.ngSuccess({data: data}); 75 | }; 76 | } 77 | }; 78 | }]); 79 | }); 80 | -------------------------------------------------------------------------------- /src/js/frontend/directives/qr.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define(['./module', 'qrcodejs'], function (directives, QRCode) { 4 | directives.directive('qr', ['$window', function($window) { 5 | return { 6 | restrict: 'A', 7 | link: function(scope, element, attrs) { 8 | var qrcode = new QRCode(element[0], { 9 | width : 512, 10 | height : 512, 11 | correctLevel : QRCode.CorrectLevel.H, 12 | }); 13 | qrcode.makeCode(attrs.data); 14 | if (attrs.width) element[0].lastChild.setAttribute('width', attrs.width); 15 | if (attrs.height) element[0].lastChild.setAttribute('height', attrs.height); 16 | } 17 | }; 18 | }]); 19 | }); 20 | -------------------------------------------------------------------------------- /src/js/frontend/filters/currency.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define(['./module', 'arcbit', 'model/TLWalletUtils'], function (filters, ArcBit, TLWalletUtils) { 4 | 5 | filters.filter('formatProperCurrency', function() { 6 | return function(input, amountType, hideSymbol) { 7 | var currencyFormat = ArcBit.getIdentity().appDelegate.currencyFormat; 8 | if (amountType !== TLWalletUtils.TLAccountTxType.SEND) { 9 | return currencyFormat.getProperAmount(input, hideSymbol); 10 | } else { 11 | return '-' + currencyFormat.getProperAmount(input, hideSymbol); 12 | } 13 | }; 14 | }); 15 | 16 | filters.filter('formatProperBitcoin', function() { 17 | return function(input, hideSymbol) { 18 | var currencyFormat = ArcBit.getIdentity().appDelegate.currencyFormat; 19 | if (currencyFormat) { 20 | return currencyFormat.coinToProperBitcoinAmountStringWithSymbol(input, hideSymbol); 21 | } else { 22 | return null; 23 | } 24 | }; 25 | }); 26 | 27 | filters.filter('formatProperFiat', function() { 28 | return function(input, hideSymbol) { 29 | var currencyFormat = ArcBit.getIdentity().appDelegate.currencyFormat; 30 | if (currencyFormat) { 31 | return currencyFormat.coinToProperLocalCurrencyAmountStringWithSymbol(input, hideSymbol); 32 | } else { 33 | return null; 34 | } 35 | }; 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /src/js/frontend/filters/i18n.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define(['./module'], function (filters) { 4 | filters.filter('_', ['translateFilter', '$translate', '$sce', function(translateFilter, $translate, $sce) { 5 | 6 | var format = function(format, args) { 7 | var string = format.replace(/{(\d+)}/g, function(match, number) { 8 | return typeof args[number] != 'undefined' 9 | ? args[number] 10 | : match 11 | ; 12 | }); 13 | string = string.replace(/\*\*(.+)\*\*/g, '$1'); 14 | string = string.replace(/\*(.+)\*/g, '$1'); 15 | string = string.replace(/\{[\w ]+\}/g, ''); 16 | return $sce.trustAsHtml(string); 17 | }; 18 | 19 | return function(input) { 20 | if (input === undefined) { 21 | return $translate.use().replace('_', '-'); 22 | } 23 | if (typeof input !== 'string' && input.message) { 24 | input = input.message; 25 | } 26 | 27 | var args; 28 | if (input.indexOf('|') >= 0) { 29 | args = input.split('|'); 30 | input = args.shift(); 31 | } 32 | args = args || Array.prototype.slice.call(arguments, 1); 33 | input = translateFilter(input); 34 | return format(input, args); 35 | }; 36 | }]); 37 | 38 | filters.filter('pocket', ['_Filter', function(_) { 39 | return function(input) { 40 | // _('spending'), _('business'), _('savings') 41 | if(['spending', 'business', 'savings'].indexOf(input) >= 0) { 42 | input = _(input); 43 | } 44 | return input; 45 | }; 46 | }]); 47 | }); 48 | -------------------------------------------------------------------------------- /src/js/frontend/filters/index.js: -------------------------------------------------------------------------------- 1 | /** attach filters to this module 2 | **/ 3 | 'use strict'; 4 | 5 | define([ 6 | './currency', 7 | './i18n' 8 | ], function () {}); 9 | -------------------------------------------------------------------------------- /src/js/frontend/filters/module.js: -------------------------------------------------------------------------------- 1 | /** attach filters to this module 2 | **/ 3 | 'use strict'; 4 | 5 | define(['angular'], function (ng) { 6 | return ng.module('ArcBit.filters', []); 7 | }); 8 | -------------------------------------------------------------------------------- /src/js/frontend/loader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * configure RequireJS 3 | * prefer named modules to long paths 4 | */ 5 | 'use strict'; 6 | 7 | requirejs.config({ 8 | deps: [ 9 | 'frontend/app', 10 | 'frontend/routes' 11 | ], 12 | callback: function(app, routes) { 13 | app.initialize(); 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /src/js/frontend/popup/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Minimal file for the popup 3 | */ 4 | 'use strict'; 5 | 6 | define([ 7 | 'angular', 8 | 'available_languages', 9 | 'angular-animate', 10 | 'mm.foundation', 11 | 'angular-translate', 12 | 'angular-translate-loader-static-file', 13 | 'frontend/popup/controller', 14 | 'frontend/popup/providers', 15 | 'frontend/controllers/ngmodal', 16 | 'frontend/filters/currency', 17 | 'frontend/filters/i18n', 18 | 'frontend/providers/sounds', 19 | 'frontend/providers/modals', 20 | 'frontend/directives/identicon' 21 | ], function (angular, AvailableLanguages) { 22 | var app = angular.module('ArcBit', [ 23 | 'mm.foundation', 24 | 'ngAnimate', 25 | 'pascalprecht.translate', 26 | 'ArcBit.controllers', 27 | 'ArcBit.filters', 28 | 'ArcBit.providers', 29 | 'ArcBit.directives' 30 | ]); 31 | // angular-translate configuration. 32 | app.config(function($translateProvider) { 33 | $translateProvider.useStaticFilesLoader({ 34 | prefix: '../i18n/', 35 | suffix: '.json' 36 | }); 37 | $translateProvider.preferredLanguage(AvailableLanguages.preferedLanguage()); 38 | }); 39 | app.config(function($animateProvider) { 40 | $animateProvider.classNameFilter(/^(?:(?!ng-animate-disabled).)*$/); 41 | }); 42 | // In case we need to initialize something after the application is created. 43 | app.initialize = function() { 44 | }; 45 | angular.bootstrap(document, ['ArcBit']); 46 | return app; 47 | }); 48 | -------------------------------------------------------------------------------- /src/js/frontend/popup/loader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * configure RequireJS 3 | * prefer named modules to long paths 4 | */ 5 | 'use strict'; 6 | 7 | requirejs.config({ 8 | deps: [ 9 | 'frontend/popup/app' 10 | ], 11 | callback: function(app) { 12 | app.initialize(); 13 | } 14 | }); 15 | -------------------------------------------------------------------------------- /src/js/frontend/popup/providers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define(['frontend/providers/module'], function (providers) { 4 | 5 | providers.value('notify', { 6 | note: function() {}, 7 | error: function() {}, 8 | warning: function() {}, 9 | success: function() {} 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/js/frontend/port.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define(function () { 4 | var Port = { 5 | /* 6 | * Connect to given service 7 | * @param {String} name Port name 8 | * @param {Function} onMessage Message callback 9 | * @param {Function} onConnect Connect callback 10 | * @param {Function} onDisconnect Disconnect callback 11 | * returns a chrome.runtime.Port. 12 | */ 13 | connect: function(name, onMessage, onConnect, onDisconnect) { 14 | var port = chrome.runtime.connect({name: name}); 15 | port.onMessage.addListener(function(data) { 16 | if (data.type == 'portConnected') { 17 | onConnect ? onConnect(port, data) : null; 18 | } else { 19 | onMessage ? onMessage(data) : null; 20 | } 21 | }); 22 | 23 | //onConnect ? port.onConnect.addListener(onConnect) : null; 24 | onDisconnect ? port.onDisconnect.addListener(onDisconnect) : null; 25 | return port; 26 | }, 27 | 28 | /* 29 | * Connect to given service and register destruction with 30 | * angular scope, aimed at controllers. 31 | * @param {String} name Port name 32 | * @param {Angular.Scope} scope Controller scope 33 | * @param {Function} onMessage Message callback 34 | * @param {Function} onConnect Connect callback 35 | * @param {Function} onDisconnect Disconnect callback 36 | * returns a chrome.runtime.Port. 37 | */ 38 | connectNg: function(name, scope, onMessage, onConnect, onDisconnect) { 39 | var port = Port.connect(name, onMessage, onConnect, onDisconnect); 40 | scope.$on('$destroy', function () { port.disconnect(); }); 41 | return port; 42 | } 43 | }; 44 | return Port; 45 | }); 46 | -------------------------------------------------------------------------------- /src/js/frontend/providers/clipboard.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define(['./module'], function (providers) { 4 | 5 | providers.factory('clipboard', ['notify', '$window', '_Filter', function(notify, $window, _) { 6 | 7 | var clipboard = { 8 | 9 | copy: function(text, notification) { 10 | text = text.replace(/\n/g, "
"); 11 | var copyDiv = $window.document.createElement('div'); 12 | copyDiv.contentEditable = true; 13 | //copyDiv.style="position: fixed;"; //using 'copyDiv.setAttribute('style', "position: fixed;");' will not cause error, but is it what I need? 14 | $window.document.getElementById('fixed').appendChild(copyDiv); 15 | copyDiv.innerHTML = text; 16 | copyDiv.unselectable = "off"; 17 | copyDiv.focus(); 18 | $window.document.execCommand('SelectAll'); 19 | $window.document.execCommand("Copy", false, null); 20 | $window.document.getElementById('fixed').removeChild(copyDiv); 21 | 22 | notification = notification || _('Copied to clipboard'); 23 | notify.note(notification); 24 | }, 25 | 26 | paste: function() { 27 | var pasteDiv = $window.document.createElement('div'); 28 | pasteDiv.contentEditable = true; 29 | $window.document.getElementById('fixed').appendChild(pasteDiv); 30 | pasteDiv.innerHTML = ''; 31 | pasteDiv.unselectable = "off"; 32 | pasteDiv.focus(); 33 | $window.document.execCommand("paste"); 34 | var text = pasteDiv.textContent; 35 | $window.document.getElementById('fixed').removeChild(pasteDiv); 36 | return text; 37 | } 38 | 39 | }; 40 | return clipboard; 41 | }]); 42 | }); 43 | -------------------------------------------------------------------------------- /src/js/frontend/providers/index.js: -------------------------------------------------------------------------------- 1 | /** attach providers to this module 2 | **/ 3 | 'use strict'; 4 | 5 | define([ 6 | './clipboard', 7 | './modals', 8 | './history', 9 | './notify', 10 | './sounds', 11 | './tabs' 12 | ], function () {}); 13 | -------------------------------------------------------------------------------- /src/js/frontend/providers/module.js: -------------------------------------------------------------------------------- 1 | /** attach providers to this module 2 | **/ 3 | 'use strict'; 4 | 5 | define(['angular'], function (ng) { 6 | return ng.module('ArcBit.providers', []); 7 | }); 8 | -------------------------------------------------------------------------------- /src/js/frontend/providers/notify.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define(['./module'], function (providers) { 4 | 5 | providers.factory('notify', ['toaster', 'ngProgress', function(toaster, ngProgress) { 6 | return { 7 | note: function(title, body) { 8 | toaster.pop('note', title, body); 9 | }, 10 | error: function(title, body) { 11 | toaster.pop('error', title, body); 12 | }, 13 | warning: function(title, body) { 14 | toaster.pop('warning', title, body); 15 | }, 16 | success: function(title, body) { 17 | toaster.pop('success', title, body); 18 | }, 19 | progress: ngProgress 20 | }; 21 | }]); 22 | }); 23 | -------------------------------------------------------------------------------- /src/js/frontend/providers/sounds.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define(['./module'], function (providers) { 4 | 5 | providers.factory('sounds', ['$window', function($window) { 6 | return { 7 | play: function(sound) { 8 | var audio = $window.document.createElement('audio'); 9 | audio.setAttribute('autoplay', 'autoplay'); 10 | audio.innerHTML = ''; 11 | $window.document.getElementById('fixed').appendChild(audio); 12 | } 13 | }; 14 | }]); 15 | }); 16 | -------------------------------------------------------------------------------- /src/js/frontend/routes.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Defines the main routes in the application. 3 | * The routes you see here will be anchors '#/' unless specifically configured otherwise. 4 | */ 5 | 'use strict'; 6 | 7 | define(['frontend/app'], function (app) { 8 | 'use strict'; 9 | return app.config(['$routeProvider', function($routeProvider) { 10 | $routeProvider.when('/contact/:contactId', {templateUrl: 'partials/contact.html', controller: 'ContactsCtrl'}); 11 | $routeProvider.when('/contact/:section/:contactId', {templateUrl: 'partials/contact.html', controller: 'ContactsCtrl'}); 12 | $routeProvider.when('/tools', {templateUrl: 'partials/tools.html', controller: 'ToolsCtrl'}); 13 | $routeProvider.when('/wallet', {templateUrl: 'partials/wallet.html', controller: 'HistoryCtrl'}); 14 | $routeProvider.when('/wallet/:section', {templateUrl: 'partials/wallet.html', controller: 'HistoryCtrl'}); 15 | $routeProvider.when('/wallet/:section/:pocketId', {templateUrl: 'partials/wallet.html', controller: 'HistoryCtrl'}); 16 | $routeProvider.when('/wallet/:section/:pocketType/:pocketId', {templateUrl: 'partials/wallet.html', controller: 'HistoryCtrl'}); 17 | $routeProvider.when('/contacts', {templateUrl: 'partials/contacts.html', controller: 'ContactsCtrl'}); 18 | $routeProvider.when('/settings', {templateUrl: 'partials/settings.html', controller: 'WalletSettingsCtrl'}); 19 | $routeProvider.when('/identities', {templateUrl: 'partials/identities.html', controller: 'IdentitiesCtrl'}); 20 | $routeProvider.when('/new_wallet', {templateUrl: 'partials/new_wallet.html', controller: 'NewWalletCtrl'}); 21 | $routeProvider.when('/popup', {templateUrl: 'partials/popup.html', controller: 'PopupCtrl'}); 22 | $routeProvider.when('/login', {templateUrl: 'partials/login.html', controller: 'loginCtrl'}); 23 | $routeProvider.when('/offline_spend', {templateUrl: 'partials/offline_spend.html', controller: 'OfflineSpendCtrl'}); 24 | $routeProvider.when('/offline_spend/:section', {templateUrl: 'partials/offline_spend.html', controller: 'OfflineSpendCtrl'}); 25 | $routeProvider.otherwise({redirectTo: '/wallet'}); 26 | }]); 27 | }); 28 | -------------------------------------------------------------------------------- /src/js/frontend/scripts/inject.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* 4 | * We don't use registerProtocolHandler() because we have to declare web accessible 5 | * resources, and that exposes to the web that the user is using that extension. 6 | * 7 | * Instead, we listen the click event on links that have bitcoin uris. 8 | */ 9 | 10 | var isBitcoinUri = function(uri) { 11 | if (!chrome.runtime) { 12 | return false; 13 | } 14 | if (typeof uri != 'string') { 15 | return false; 16 | } 17 | if (uri.indexOf('bitcoin:') == 0) { 18 | return true; 19 | } 20 | return false; 21 | }; 22 | 23 | if (document.body) { 24 | document.body.addEventListener('click', function(e) { 25 | var elem = e.target; 26 | while (elem && elem.tagName != 'A') { 27 | elem = elem.parentNode; 28 | } 29 | if (elem && elem.tagName == 'A' && isBitcoinUri(elem.href)) { 30 | chrome.runtime.sendMessage({ type: 'handleBitcoinURI', url: elem.href }); 31 | e.preventDefault(); 32 | } 33 | }, false); 34 | } 35 | -------------------------------------------------------------------------------- /src/js/model/TLBlockExplorerAPI.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define(['model/TLBlockchainAPI', 'model/TLInsightAPI'], 4 | function(TLBlockchainAPI, TLInsightAPI) { 5 | TLBlockExplorerAPI.TLBlockExplorer = { 6 | BLOCKCHAIN : 'blockchain.info', 7 | INSIGHT : 'insight', 8 | BLOCKR: 'blockr' 9 | }; 10 | 11 | function TLBlockExplorerAPI(preferences) { 12 | var blockExplorerAPI =preferences.getSelectedBlockExplorerAPI(); 13 | var blockExplorerURL = preferences.getSelectedBlockExplorerURL(blockExplorerAPI, preferences.getSelectedBlockExplorerURLIdx()); 14 | this.setUpAPI(preferences.getSelectedBlockExplorerAPI(), blockExplorerURL); 15 | } 16 | 17 | TLBlockExplorerAPI.prototype.setUpAPI = function(blockExplorerAPI, blockExplorerURL) { 18 | this.blockExplorerAPI = blockExplorerAPI; 19 | this.blockExplorerURL = blockExplorerURL; 20 | if (blockExplorerAPI == TLBlockExplorerAPI.TLBlockExplorer.BLOCKCHAIN) { 21 | this.blockchainAPI = new TLBlockchainAPI(blockExplorerURL); 22 | //needed for push tx api for stealth addresses 23 | this.insightAPI = new TLInsightAPI("https://insight.bitpay.com/"); 24 | } else if (blockExplorerAPI == TLBlockExplorerAPI.TLBlockExplorer.INSIGHT) { 25 | this.insightAPI = new TLInsightAPI(blockExplorerURL); 26 | } 27 | }; 28 | 29 | TLBlockExplorerAPI.prototype.getBlockHeight = function(success, failure) { 30 | if (this.blockExplorerAPI == TLBlockExplorerAPI.TLBlockExplorer.BLOCKCHAIN) { 31 | return this.blockchainAPI.getBlockHeight(function(height) { 32 | success({'height': height}) 33 | }, failure); 34 | } else { 35 | //Insight does not have a good way to get block height 36 | } 37 | }; 38 | 39 | TLBlockExplorerAPI.prototype.getAddressesInfo = function(addressArray, success, failure) { 40 | if (this.blockExplorerAPI == TLBlockExplorerAPI.TLBlockExplorer.BLOCKCHAIN) { 41 | return this.blockchainAPI.getAddressesInfo(addressArray, success, failure); 42 | } else { 43 | return this.insightAPI.getAddressesInfo(addressArray, 0, [], success, failure); 44 | } 45 | }; 46 | 47 | TLBlockExplorerAPI.prototype.getUnspentOutputs = function(addressArray, success, failure) { 48 | if (this.blockExplorerAPI == TLBlockExplorerAPI.TLBlockExplorer.BLOCKCHAIN) { 49 | return this.blockchainAPI.getUnspentOutputs(addressArray, success, failure); 50 | } else { 51 | return this.insightAPI.getUnspentOutputs(addressArray, success, failure); 52 | } 53 | }; 54 | 55 | TLBlockExplorerAPI.prototype.getTx = function(txHash, success, failure) { 56 | if (this.blockExplorerAPI == TLBlockExplorerAPI.TLBlockExplorer.BLOCKCHAIN) { 57 | return this.blockchainAPI.getTx(txHash, success, failure); 58 | } else { 59 | return this.insightAPI.getTx(txHash, function (jsonData) { 60 | var transformedTx = TLInsightAPI.insightTxToBlockchainTx(jsonData); 61 | success(transformedTx); 62 | }, failure); 63 | } 64 | }; 65 | 66 | TLBlockExplorerAPI.prototype.pushTx = function(txHex, success, failure) { 67 | if (this.blockExplorerAPI == TLBlockExplorerAPI.TLBlockExplorer.BLOCKCHAIN) { 68 | return this.blockchainAPI.pushTx(txHex, success, failure); 69 | } else { 70 | return this.insightAPI.pushTx(txHex, success, failure); 71 | } 72 | }; 73 | 74 | return TLBlockExplorerAPI; 75 | }); 76 | -------------------------------------------------------------------------------- /src/js/model/TLBlockchainAPI.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define(['model/TLNetworking'], 4 | function(TLNetworking) { 5 | 6 | function TLBlockchainAPI(baseURL) { 7 | this.baseURL = baseURL; 8 | this.networking = new TLNetworking(); 9 | } 10 | 11 | TLBlockchainAPI.prototype.getBlockHeight = function(success, failure) { 12 | var endPoint = 'q/getblockcount'; 13 | var parameters = {}; 14 | var url = this.baseURL + endPoint; 15 | this.networking.httpGET(url, parameters, function (jsonData) { 16 | success(jsonData); 17 | }, function (response) { 18 | failure(response); 19 | }); 20 | }; 21 | 22 | TLBlockchainAPI.prototype.getTx = function(txHash, success, failure) { 23 | var endPoint = 'tx/' + txHash; 24 | var parameters = {'format': 'json'}; 25 | var url = this.baseURL + endPoint; 26 | this.networking.httpGET(url, parameters, success, failure); 27 | }; 28 | 29 | TLBlockchainAPI.prototype.pushTx = function(txHex, success, failure) { 30 | var endPoint = 'pushtx'; 31 | var parameters = { 32 | 'format': 'plain', 33 | 'tx': txHex 34 | }; 35 | var url = this.baseURL + endPoint; 36 | this.networking.httpPOST(url, parameters, success, failure); 37 | }; 38 | 39 | TLBlockchainAPI.prototype.getUnspentOutputs = function(addressArray, success, failure) { 40 | var endPoint = 'unspent'; 41 | var parameters = {'active': addressArray.join('|')}; 42 | var url = this.baseURL + endPoint; 43 | this.networking.httpGET(url, parameters, success, failure); 44 | }; 45 | 46 | TLBlockchainAPI.prototype.getAddressesInfo = function(addressArray, success, failure) { 47 | var endPoint = 'multiaddr'; 48 | var parameters = {'active': addressArray.join('|')}; 49 | var url = this.baseURL + endPoint; 50 | this.networking.httpGET(url, parameters, success, failure); 51 | }; 52 | 53 | return TLBlockchainAPI; 54 | }); 55 | -------------------------------------------------------------------------------- /src/js/model/TLBlockchainStatus.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define([], 4 | function() { 5 | function TLBlockchainStatus() { 6 | this.blockHeight = Number.MAX_VALUE; 7 | } 8 | 9 | TLBlockchainStatus.prototype.getBlockHeight = function() { 10 | return this.blockHeight; 11 | }; 12 | 13 | return TLBlockchainStatus; 14 | }); 15 | -------------------------------------------------------------------------------- /src/js/model/TLContacts.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define(['bitcoinjs-lib', 'crypto-js'], function(Bitcoin, CryptoJS) { 4 | 5 | function TLContacts(appDelegate) { 6 | this.appDelegate = appDelegate; 7 | var addressBook = this.appDelegate.appWallet.getAddressBook(); 8 | var contacts = []; 9 | var self = this; 10 | addressBook.forEach(function(contact) { 11 | contacts.push({ 12 | name: contact.name, 13 | address: contact.addr, 14 | hash: self.generateAddressHash(contact.addr)}); 15 | }); 16 | this.contacts = contacts; 17 | } 18 | 19 | TLContacts.prototype.generateAddressHash = function(address) { 20 | return CryptoJS.SHA256(address).toString(); 21 | }; 22 | 23 | TLContacts.prototype.addContact = function (contact) { 24 | this.appDelegate.appWallet.addAddressBookEntry(contact.address, contact.name); 25 | var newContact = { 26 | name: contact.name, 27 | address: contact.address, 28 | hash: this.generateAddressHash(contact.address)}; 29 | this.contacts.push(newContact); 30 | return newContact; 31 | }; 32 | 33 | TLContacts.prototype.deleteContact = function (contact) { 34 | var i = this.contacts.indexOf(contact); 35 | if (i === -1) { 36 | throw new Error('Contact does not exist!'); 37 | } 38 | this.appDelegate.appWallet.deleteAddressBookEntry(i); 39 | this.contacts.splice(i, 1); 40 | }; 41 | 42 | TLContacts.prototype.editName = function (contact, name) { 43 | var i = this.contacts.indexOf(contact); 44 | if (i === -1) { 45 | throw new Error('Contact does not exist!'); 46 | } 47 | this.appDelegate.appWallet.editAddressBookEntry(i, name); 48 | }; 49 | 50 | return TLContacts; 51 | }); 52 | -------------------------------------------------------------------------------- /src/js/model/TLCrypto.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define(['bitcoinjs-lib', 'crypto-js', 'sjcl'], 4 | function(Bitcoin, CryptoJS, sjcl) { 5 | 6 | function TLCrypto() { 7 | } 8 | 9 | TLCrypto.wordsToBytes = function(words) { 10 | var bytes = [] 11 | for (var b = 0; b < words.length * 32; b += 8) { 12 | bytes.push((words[b >>> 5] >>> (24 - b % 32)) & 0xFF) 13 | } 14 | return bytes 15 | }; 16 | 17 | TLCrypto.wordArrayToBytes = function(wordArray) { 18 | return TLCrypto.wordsToBytes(wordArray.words) 19 | }; 20 | 21 | TLCrypto.hexStringToData = function(hexString) { 22 | return new Bitcoin.Buffer(hexString, 'hex'); 23 | }; 24 | 25 | TLCrypto.getPasswordDigest = function(password) { 26 | var SHA256 = CryptoJS.SHA256; 27 | var passwordDigest = TLCrypto.wordArrayToBytes(SHA256(SHA256(SHA256(password)))); 28 | return new Bitcoin.Buffer(passwordDigest).toString('hex'); 29 | // maybe go with this password digest or something to that effect 30 | //return sjcl.codec.base64.fromBits(sjcl.misc.pbkdf2(password, email, 1000)); 31 | }; 32 | 33 | TLCrypto.getPasswordHash = function(password) { 34 | var SHA256 = CryptoJS.SHA256; 35 | var passwordDigest = TLCrypto.wordArrayToBytes(SHA256(SHA256(SHA256(SHA256(SHA256(password)))))); 36 | return new Bitcoin.Buffer(passwordDigest).toString('hex'); 37 | }; 38 | 39 | TLCrypto.encrypt = function(plainText, password) { 40 | var passwordDigest = TLCrypto.getPasswordDigest(password); 41 | var privData = sjcl.encrypt(passwordDigest, plainText, {ks: 256, ts: 128}); 42 | return privData; 43 | }; 44 | 45 | TLCrypto.decrypt = function(cipherText, password) { 46 | var passwordDigest = TLCrypto.getPasswordDigest(password); 47 | var data = sjcl.decrypt(passwordDigest, cipherText); 48 | return data; 49 | }; 50 | 51 | return TLCrypto; 52 | }); 53 | -------------------------------------------------------------------------------- /src/js/model/TLExchangeRate.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define(['model/TLNetworking', 'model/TLCoin'], 4 | function(TLNetworking, TLCoin) { 5 | function TLExchangeRate() { 6 | this.networking = new TLNetworking(); 7 | this.exchangeRateDict = {}; 8 | } 9 | 10 | TLExchangeRate.prototype.getExchangeRate = function(currency) { 11 | if (this.exchangeRateDict == null || this.exchangeRateDict[currency] == null) { 12 | return 0; 13 | } else { 14 | return this.exchangeRateDict[currency]["rate"]; 15 | } 16 | }; 17 | 18 | TLExchangeRate.prototype.getExchangeRates = function(success, failure) { 19 | var self = this; 20 | this.networking.httpGET('https://bitpay.com/api/rates', null, function(jsonData) { 21 | for(var i = 0; i < jsonData.length; i++) { 22 | var dict = jsonData[i]; 23 | self.exchangeRateDict[dict["code"]] = dict; 24 | } 25 | success(); 26 | }, function(response) { 27 | failure(response); 28 | }); 29 | }; 30 | 31 | TLExchangeRate.prototype.fiatAmountFromBitcoin = function(currency, bitcoinAmount) { 32 | var exchangeRate = this.getExchangeRate(currency); 33 | return bitcoinAmount.bigIntegerToBitcoin() * exchangeRate; 34 | }; 35 | 36 | TLExchangeRate.prototype.bitcoinAmountFromFiat = function(currency, fiatAmount) { 37 | var exchangeRate = this.getExchangeRate(currency); 38 | var bitcoinAmount = (parseFloat(fiatAmount)/exchangeRate).toString(); 39 | return TLCoin.fromString(bitcoinAmount, TLCoin.TLBitcoinDenomination.BTC); 40 | }; 41 | 42 | TLExchangeRate.prototype.fiatAmountStringFromBitcoin = function(currency, bitcoinAmount) { 43 | return this.fiatAmountFromBitcoin(currency, bitcoinAmount).toFixed(2).toString(); 44 | }; 45 | 46 | return TLExchangeRate; 47 | }); 48 | -------------------------------------------------------------------------------- /src/js/model/TLGlobalSettings.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define([], function() { 4 | 5 | TLGlobalSettings.ARCBIT_SETTINGS_NS = 'ab:settings'; 6 | var PREFERENCE_FIAT_DISPLAY = "fiat_display"; 7 | var PREFERENCE_LANGUAGE = "language"; 8 | var PREFERENCE_ALWAYS_ENCRYPT = "always_encrypt"; 9 | var PREFERENCE = "prefs"; 10 | var PREFERENCE_INSTALL_DATE = "install_date"; 11 | var PREFERENCE_APP_VERSION = "app_version"; 12 | var PREFERENCE_CURRENT_IDENTITY = "current_identity"; // need this cuz when close chrome with tab still open and relaunch app, need to know last wallet used, otherwise, identity is set from popup 13 | 14 | function TLGlobalSettings() { 15 | this.settings = {prefs:{fiat_display: 'USD', language: 'en_US'}}; 16 | //this.settings = {fiat_display: 'EUR', language: 'es_ES'}; 17 | } 18 | 19 | TLGlobalSettings.prototype.getInstallDate = function() { 20 | return this.settings[PREFERENCE_INSTALL_DATE]; 21 | }; 22 | 23 | TLGlobalSettings.prototype.setInstallDate = function() { 24 | this.settings[PREFERENCE_INSTALL_DATE] = Math.floor(Date.now() / 1000); 25 | }; 26 | 27 | TLGlobalSettings.prototype.getAppVersion = function() { 28 | return this.settings[PREFERENCE_APP_VERSION]; 29 | }; 30 | 31 | TLGlobalSettings.prototype.setAppVersion = function(version) { 32 | this.settings[PREFERENCE_APP_VERSION] = version; 33 | }; 34 | 35 | TLGlobalSettings.prototype.getCurrency = function() { 36 | return this.settings[PREFERENCE][PREFERENCE_FIAT_DISPLAY]; 37 | }; 38 | 39 | TLGlobalSettings.prototype.setCurrentIdentityName = function(value, callback) { 40 | this.settings[PREFERENCE_CURRENT_IDENTITY] = value; 41 | this.save(callback); 42 | }; 43 | 44 | TLGlobalSettings.prototype.getCurrentIdentityName = function() { 45 | return this.settings[PREFERENCE_CURRENT_IDENTITY]; 46 | }; 47 | 48 | TLGlobalSettings.prototype.setCurrency = function(value, callback) { 49 | this.settings[PREFERENCE][PREFERENCE_FIAT_DISPLAY] = value; 50 | this.save(callback); 51 | }; 52 | 53 | TLGlobalSettings.prototype.getLanguage = function() { 54 | return this.settings[PREFERENCE][PREFERENCE_LANGUAGE]; 55 | }; 56 | 57 | TLGlobalSettings.prototype.setLanguage = function(value, callback) { 58 | this.settings[PREFERENCE][PREFERENCE_LANGUAGE] = value; 59 | this.save(callback); 60 | }; 61 | 62 | TLGlobalSettings.prototype.getAlwaysEncrypt = function() { 63 | return this.settings[PREFERENCE][PREFERENCE_ALWAYS_ENCRYPT]; 64 | }; 65 | 66 | TLGlobalSettings.prototype.setAlwaysEncrypt = function(value, callback) { 67 | this.settings[PREFERENCE][PREFERENCE_ALWAYS_ENCRYPT] = value; 68 | this.save(callback); 69 | }; 70 | 71 | TLGlobalSettings.prototype.save = function(callback) { 72 | chrome.storage.local.set({'ab:settings':this.settings}); 73 | var self = this; 74 | chrome.storage.local.get(TLGlobalSettings.ARCBIT_SETTINGS_NS, function(obj) { 75 | if (obj[TLGlobalSettings.ARCBIT_SETTINGS_NS]) { 76 | self.settings = obj[TLGlobalSettings.ARCBIT_SETTINGS_NS]; 77 | } 78 | callback ? callback() : null; 79 | }); 80 | }; 81 | 82 | TLGlobalSettings.prototype.loadGlobalSettings = function(callback) { 83 | var self = this; 84 | chrome.storage.local.get(TLGlobalSettings.ARCBIT_SETTINGS_NS, function(obj) { 85 | if (obj[TLGlobalSettings.ARCBIT_SETTINGS_NS]) { 86 | self.settings = obj[TLGlobalSettings.ARCBIT_SETTINGS_NS]; 87 | } 88 | callback ? callback() : null; 89 | }); 90 | }; 91 | 92 | return TLGlobalSettings; 93 | }); 94 | -------------------------------------------------------------------------------- /src/js/model/TLNetworking.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define(['angular'], 4 | function(angular) { 5 | TLNetworking.HTTP_ERROR_CODE = 'HTTPErrorCode'; 6 | TLNetworking.HTTP_ERROR_MSG = 'HTTPErrorMsg'; 7 | var $http = angular.injector(['ng']).get('$http'); 8 | function TLNetworking() { 9 | } 10 | 11 | TLNetworking.prototype.httpGET = function(url, data, success, failure) { 12 | //console.log("httpGET url " + url); 13 | //console.log("httpGET data " + JSON.stringify(data)); 14 | $http.get(url, {params:data}). 15 | then(function(response) { 16 | //console.log("httpGET " + JSON.stringify(response)); 17 | success(response['data']); 18 | }, function(response) { 19 | //console.log("httpGET Error: " + JSON.stringify(response)); 20 | failure(response); 21 | }); 22 | }; 23 | 24 | TLNetworking.prototype.httpPOST = function(url, data, success, failure) { 25 | //console.log("httpPOST url " + url); 26 | //console.log("httpPOST data " + JSON.stringify(data)); 27 | $http({url:url, params:data, method:'POST'}). 28 | then(function(response) { 29 | //console.log("httpPOST " + JSON.stringify(response)); 30 | success(response['data']); 31 | }, function(response) { 32 | //console.log("httpPOST Error: " + JSON.stringify(response)); 33 | failure(response); 34 | }); 35 | }; 36 | 37 | TLNetworking.prototype.httpPOST2 = function(url, data, success, failure) { 38 | //console.log("httpPOST url " + url); 39 | //console.log("httpPOST data " + JSON.stringify(data)); 40 | $http.post(url, data). 41 | then(function(response) { 42 | //console.log("httpPOST " + JSON.stringify(response)); 43 | success(response['data']); 44 | }, function(response) { 45 | //console.log("httpPOST Error: " + JSON.stringify(response)); 46 | failure(response); 47 | }); 48 | }; 49 | 50 | return TLNetworking; 51 | }); 52 | -------------------------------------------------------------------------------- /src/js/model/TLPushTxAPI.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define(['model/TLStealthAddress', 'model/TLStealthExplorerAPI'], 4 | function(TLStealthAddress, TLStealthExplorerAPI) { 5 | 6 | function TLPushTxAPI(appDelegate, isTestnet) { 7 | this.appDelegate = appDelegate; 8 | this.isTestnet = isTestnet; 9 | } 10 | 11 | TLPushTxAPI.prototype.sendTx = function(txHex, txHash, possibleStealthAddress, success, failure) { 12 | console.log("sendTx: txHash " + txHash); 13 | console.log("sendTx: txHex " + txHex); 14 | if (!TLStealthAddress.isStealthAddress(possibleStealthAddress, this.isTestnet)) { 15 | this.appDelegate.blockExplorerAPI.pushTx(txHex, success, failure); 16 | } else { 17 | var self = this; 18 | this.appDelegate.blockExplorerAPI.insightAPI.pushTx(txHex, function(jsonData) { 19 | var txid = jsonData["txid"]; 20 | if (txid == null) { 21 | failure({status:'500', data:'No txid returned'}); 22 | return; 23 | } 24 | self.appDelegate.stealthExplorerAPI.lookupTx(possibleStealthAddress, txid, function(jsonData) { 25 | var errorCode = jsonData[TLStealthExplorerAPI.SERVER_ERROR_CODE]; 26 | if (errorCode != null && errorCode == TLStealthExplorerAPI.INVALID_SIGNATURE_ERROR) { 27 | failure({status:errorCode, data:jsonData[TLStealthExplorerAPI.SERVER_ERROR_MSG]}); 28 | } else { 29 | success({txid:txid}); 30 | } 31 | }, function(response) { 32 | failure(response); 33 | }); 34 | 35 | }, function(response) { 36 | failure(response); 37 | }); 38 | } 39 | }; 40 | 41 | return TLPushTxAPI; 42 | }); 43 | -------------------------------------------------------------------------------- /src/js/model/TLStealthExplorerAPI.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define(['model/TLNetworking'], 4 | function(TLNetworking) { 5 | TLStealthExplorerAPI.STEALTH_PAYMENTS_FETCH_COUNT = 50; 6 | 7 | TLStealthExplorerAPI.UNEXPECTED_ERROR = -1000; 8 | TLStealthExplorerAPI.DATABASE_ERROR = -1001; 9 | TLStealthExplorerAPI.INVALID_STEALTH_ADDRESS_ERROR = -1002; 10 | TLStealthExplorerAPI.INVALID_SIGNATURE_ERROR = -1003; 11 | TLStealthExplorerAPI.INVALID_SCAN_KEY_ERROR = -1004; 12 | TLStealthExplorerAPI.TX_DECODE_FAILED_ERROR = -1005; 13 | TLStealthExplorerAPI.INVALID_PARAMETER_ERROR = -1006; 14 | TLStealthExplorerAPI.SEND_TX_ERROR = -1007; 15 | 16 | TLStealthExplorerAPI.SERVER_ERROR_CODE = 'error_code'; 17 | TLStealthExplorerAPI.SERVER_ERROR_MSG = 'error_msg'; 18 | 19 | function TLStealthExplorerAPI(preferences, stealthServerConfig) { 20 | preferences.resetStealthExplorerAPIURL(); 21 | preferences.resetStealthServerPort(); 22 | this.baseURL = stealthServerConfig.getWebServerProtocol() + "://" + preferences.getStealthExplorerURL() 23 | + ":" + preferences.getStealthServerPort(); 24 | this.networking = new TLNetworking(); 25 | } 26 | 27 | TLStealthExplorerAPI.prototype.ping = function(success, failure) { 28 | var endPoint = '/ping'; 29 | var parameters = {}; 30 | var url = this.baseURL + endPoint; 31 | this.networking.httpGET(url, parameters, success, failure); 32 | }; 33 | 34 | TLStealthExplorerAPI.prototype.getChallenge = function(success, failure) { 35 | var endPoint = '/challenge'; 36 | var parameters = {}; 37 | var url = this.baseURL + endPoint; 38 | this.networking.httpGET(url, parameters, success, failure); 39 | }; 40 | 41 | TLStealthExplorerAPI.prototype.getStealthPayments = function(stealthAddress, signature, offset, success, failure) { 42 | var endPoint = '/payments'; 43 | var parameters = { 44 | addr: stealthAddress, 45 | sig: signature, 46 | offset: offset 47 | }; 48 | var url = this.baseURL + endPoint; 49 | this.networking.httpGET(url, parameters, success, failure); 50 | }; 51 | 52 | TLStealthExplorerAPI.prototype.watchStealthAddress = function(stealthAddress, scanPriv, signature, success, failure) { 53 | var endPoint = '/watch'; 54 | var parameters = { 55 | addr: stealthAddress, 56 | scan_key: scanPriv, 57 | sig: signature 58 | }; 59 | var url = this.baseURL + endPoint; 60 | this.networking.httpGET(url, parameters, success, failure); 61 | }; 62 | 63 | TLStealthExplorerAPI.prototype.lookupTx = function(stealthAddress, txid, success, failure) { 64 | var endPoint = '/lookuptx'; 65 | var parameters = { 66 | addr: stealthAddress, 67 | txid: txid 68 | }; 69 | var url = this.baseURL + endPoint; 70 | this.networking.httpGET(url, parameters, success, failure); 71 | }; 72 | 73 | return TLStealthExplorerAPI; 74 | }); 75 | -------------------------------------------------------------------------------- /src/js/model/TLStealthServerConfig.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define([], 4 | function() { 5 | 6 | function TLStealthServerConfig() { 7 | this.stealthServerUrl = "www.arcbit.net"; 8 | this.stealthServerPort = 443; 9 | this.webSocketServerPort = 443; 10 | this.webServerProtocol = "https"; 11 | this.webSocketProtocol = "wss"; 12 | this.webSocketEndpoint = "/inv"; 13 | } 14 | 15 | TLStealthServerConfig.prototype.getWebServerProtocol = function() { 16 | return this.webServerProtocol; 17 | }; 18 | 19 | TLStealthServerConfig.prototype.getWebSocketProtocol = function() { 20 | return this.webSocketProtocol; 21 | }; 22 | 23 | TLStealthServerConfig.prototype.getWebSocketEndpoint = function() { 24 | return this.webSocketEndpoint; 25 | }; 26 | 27 | TLStealthServerConfig.prototype.getStealthServerUrl = function() { 28 | return this.stealthServerUrl; 29 | }; 30 | 31 | TLStealthServerConfig.prototype.getStealthServerPort = function() { 32 | return this.stealthServerPort; 33 | }; 34 | 35 | TLStealthServerConfig.prototype.getWebSocketServerPort = function() { 36 | return this.webSocketServerPort; 37 | }; 38 | 39 | return TLStealthServerConfig; 40 | }); 41 | -------------------------------------------------------------------------------- /src/js/model/TLTxFeeAPI.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define(['model/TLNetworking'], 4 | function(TLNetworking) { 5 | TLTxFeeAPI.TLDynamicFeeSetting = { 6 | FASTEST_FEE : 'fastestFee', 7 | HALF_HOUR_FEE : 'halfHourFee', 8 | HOUR_FEE: 'hourFee' 9 | }; 10 | 11 | function TLTxFeeAPI(preferences) { 12 | this.preferences = preferences; 13 | this.networking = new TLNetworking(); 14 | this.cachedDynamicFees = null; 15 | this.cachedDynamicFeesTime = null; 16 | this.isFetchingTxFees = false; 17 | } 18 | 19 | TLTxFeeAPI.prototype.getCachedDynamicFee = function() { 20 | if (this.cachedDynamicFees != null) { 21 | var dynamicFeeSetting = this.preferences.getDynamicFeeSetting(); 22 | if (dynamicFeeSetting == TLTxFeeAPI.TLDynamicFeeSetting.FASTEST_FEE) { 23 | console.log('FastestFee'); 24 | return this.cachedDynamicFees[TLTxFeeAPI.TLDynamicFeeSetting.FASTEST_FEE]; 25 | } else if (dynamicFeeSetting == TLTxFeeAPI.TLDynamicFeeSetting.HALF_HOUR_FEE) { 26 | console.log('HalfHourFee'); 27 | return this.cachedDynamicFees[TLTxFeeAPI.TLDynamicFeeSetting.HALF_HOUR_FEE]; 28 | } else if (dynamicFeeSetting == TLTxFeeAPI.TLDynamicFeeSetting.HOUR_FEE) { 29 | console.log('HourFee'); 30 | return this.cachedDynamicFees[TLTxFeeAPI.TLDynamicFeeSetting.HOUR_FEE]; 31 | } 32 | } 33 | return null; 34 | }; 35 | 36 | TLTxFeeAPI.prototype.haveUpdatedCachedDynamicFees = function() { 37 | var nowUnixTime = new Date().getTime()/1000; 38 | if (this.cachedDynamicFeesTime == null || nowUnixTime - this.cachedDynamicFeesTime > 600.0) { // 600.0 = 10 minutes 39 | return false; 40 | } 41 | return true; 42 | }; 43 | 44 | TLTxFeeAPI.prototype.getDynamicTxFee = function(success, failure) { 45 | if (this.isFetchingTxFees) { 46 | return; 47 | } 48 | this.isFetchingTxFees = true; 49 | var self = this; 50 | this.networking.httpGET('https://bitcoinfees.21.co/api/v1/fees/recommended', null, function (jsonData) { 51 | if (jsonData != null) { 52 | self.cachedDynamicFeesTime = new Date().getTime()/1000; 53 | self.cachedDynamicFees = jsonData; 54 | } else { 55 | self.cachedDynamicFees = null; 56 | } 57 | self.isFetchingTxFees = false; 58 | success(jsonData); 59 | }, function (response) { 60 | self.cachedDynamicFees = null; 61 | this.isFetchingTxFees = false; 62 | failure(response); 63 | }); 64 | }; 65 | 66 | return TLTxFeeAPI; 67 | }); 68 | -------------------------------------------------------------------------------- /src/js/model/TLUtils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define([], 4 | function() { 5 | 6 | function TLUtils() { 7 | } 8 | 9 | TLUtils.getBrowserLanguage = function() { 10 | var uiLanguage = chrome.i18n.getUILanguage(); 11 | console.log("uiLanguage: " + uiLanguage); 12 | if (uiLanguage == "en") { 13 | return "en_US"; 14 | } else if (uiLanguage == "zh-CN") { 15 | return "zh_CN"; 16 | } else if (uiLanguage == "zh-TW") { 17 | return "tw_CN"; 18 | } else if (uiLanguage == "es") { 19 | return "es_ES"; 20 | } else if (uiLanguage == "ru") { 21 | return "ru_RU"; 22 | } else if (uiLanguage == "de") { 23 | return "de_DE"; 24 | } 25 | return "en_US"; 26 | }; 27 | 28 | TLUtils.defaultAppName = function() { 29 | return "ArcBit"; 30 | }; 31 | 32 | TLUtils.dictionaryToJSONString = function(prettyPrint, dict) { 33 | if (!prettyPrint) { 34 | return JSON.stringify(dict); 35 | 36 | } else { 37 | return JSON.stringify(dict, null, 2); 38 | } 39 | }; 40 | 41 | TLUtils.JSONStringToDictionary = function(jsonString) { 42 | return JSON.parse(jsonString); 43 | }; 44 | 45 | TLUtils.dateToString = function(d) { 46 | function padStr(i) { 47 | return (i < 10) ? "0" + i : "" + i; 48 | } 49 | function isToday(i) { 50 | var today = new Date(); 51 | return ((today.getFullYear()==d.getFullYear())&&(today.getMonth()==d.getMonth())&&(today.getDate()==d.getDate())); 52 | } 53 | 54 | if (isToday(d)) { 55 | return 'Today ' + padStr(d.getHours()) + ':' + padStr(d.getMinutes()) + ':' + padStr(d.getSeconds()); 56 | } else { 57 | return padStr(d.getFullYear()) + '-' + padStr(1 + d.getMonth()) + '-' + padStr(d.getDate()) + ' ' + padStr(d.getHours()) + ':' + padStr(d.getMinutes()) + ':' + padStr(d.getSeconds()); 58 | } 59 | }; 60 | 61 | return TLUtils; 62 | }); 63 | -------------------------------------------------------------------------------- /src/js/model/TLWalletJson.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define(['model/TLCrypto', 'model/TLUtils', 'model/TLHDWalletWrapper', 'model/TLWalletJSONKeys'], 4 | function(TLCrypto, TLUtils, TLHDWalletWrapper, TLWalletJSONKeys) { 5 | 6 | TLWalletJson.ARCBIT_NS = 'ab:identity:'; 7 | TLWalletJson.ARCBIT_VERSION = 'ab:version'; 8 | 9 | function TLWalletJson() { 10 | } 11 | 12 | TLWalletJson.getPasswordHash = function(password) { 13 | return TLCrypto.getPasswordHash(password); 14 | }; 15 | 16 | TLWalletJson.getEncryptedWalletJsonContainer = function(walletJson, password, shouldEncrypt) { 17 | var str = TLUtils.dictionaryToJSONString(false, walletJson); 18 | if (shouldEncrypt) { 19 | str = TLCrypto.encrypt(str, password); 20 | } 21 | var walletJsonEncryptedWrapperDict = { 22 | "payload": str, 23 | "encrypted": shouldEncrypt, 24 | "password_hash": TLWalletJson.getPasswordHash(password), 25 | "date": Math.floor(Date.now() / 1000) 26 | }; 27 | walletJsonEncryptedWrapperDict[TLWalletJSONKeys.WALLET_PAYLOAD_KEY_ENCRYPTION_VERSION] = "1"; 28 | //console.log(" getEncryptedWalletJsonContainer: " + JSON.stringify(walletJsonEncryptedWrapperDict)); 29 | return walletJsonEncryptedWrapperDict; 30 | }; 31 | 32 | TLWalletJson.getWalletJsonDict = function(walletObj, password) { 33 | if (walletObj == null) { 34 | throw new Error("walletObj is null"); 35 | } 36 | 37 | var version = walletObj[TLWalletJSONKeys.WALLET_PAYLOAD_KEY_ENCRYPTION_VERSION]; 38 | if (version != "1") { 39 | throw new Error("Incorrect encryption version"); 40 | } 41 | var walletPayloadDict = walletObj[TLWalletJSONKeys.WALLET_PAYLOAD_KEY_PAYLOAD]; 42 | 43 | if (walletObj[TLWalletJSONKeys.WALLET_PAYLOAD_KEY_ENCRYPTED]) { 44 | if (password == null) { 45 | throw new Error("Missing password"); 46 | } 47 | 48 | var walletJsonString = TLWalletJson.decryptWalletJSONFile(walletPayloadDict, password); 49 | if (walletJsonString == null) { 50 | throw new Error("Invalid Password"); 51 | } 52 | var walletDict = TLUtils.JSONStringToDictionary(walletJsonString); 53 | return walletDict; 54 | } else { 55 | var walletDict = TLUtils.JSONStringToDictionary(walletPayloadDict); 56 | return walletDict; 57 | } 58 | }; 59 | 60 | TLWalletJson.decryptWalletJSONFile = function(encryptedWalletJSONFile, password) { 61 | if (encryptedWalletJSONFile == null || password == null) { 62 | return null; 63 | } 64 | var str = TLCrypto.decrypt(encryptedWalletJSONFile, password); 65 | return str; 66 | }; 67 | 68 | TLWalletJson.saveWalletJson = function(name, walletFile, callback) { 69 | var obj = {}; 70 | obj[TLWalletJson.ARCBIT_NS+name] = walletFile; 71 | chrome.storage.local.set(obj, callback); 72 | }; 73 | 74 | TLWalletJson.getLocalWalletJSONFile = function(name, callback) { 75 | chrome.storage.local.get(TLWalletJson.ARCBIT_NS+name, function(obj) { 76 | callback(obj[TLWalletJson.ARCBIT_NS+name]); 77 | }); 78 | }; 79 | 80 | TLWalletJson.deleteWalletJson = function(name, callback) { 81 | chrome.storage.local.remove(TLWalletJson.ARCBIT_NS+name, function() { 82 | callback(); 83 | }); 84 | }; 85 | 86 | return TLWalletJson; 87 | }); 88 | -------------------------------------------------------------------------------- /src/js/model/TLWalletUtils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define(['model/TLBitcoinJSWrapper', 'model/TLCoin'], 4 | function(TLBitcoinJSWrapper, TLCoin) { 5 | 6 | TLWalletUtils.MIN_FEE_AMOUNT = 10000; 7 | TLWalletUtils.MAX_FEE_AMOUNT = 1000000; 8 | 9 | TLWalletUtils.SHOULD_SAVE_ARCHIVED_ADDRESSES_IN_JSON = false; 10 | TLWalletUtils.ENABLE_STEALTH_ADDRESS = false; 11 | TLWalletUtils.ALLOW_MANUAL_SCAN_FOR_STEALTH_PAYMENT = true; 12 | 13 | TLWalletUtils.TLSelectedAccountType = { 14 | HD_WALLET : 'hd', 15 | IMPORTED_ACCOUNT : 'ihd', 16 | IMPORTED_WATCH_ACCOUNT : 'iwhd', 17 | IMPORTED_ADDRESS : 'ikey', 18 | IMPORTED_WATCH_ADDRESS : 'iaddr', 19 | ARCHIVED_HD_WALLET : 'ahd', 20 | ARCHIVED_IMPORTED_ACCOUNT : 'aihd', 21 | ARCHIVED_IMPORTED_WATCH_ACCOUNT : 'aiwhd', 22 | ARCHIVED_IMPORTED_ADDRESS : 'aikey', 23 | ARCHIVED_IMPORTED_WATCH_ADDRESS : 'aiaddr' 24 | }; 25 | 26 | TLWalletUtils.TLAccountType = { 27 | UNKNOWN : 0, 28 | HD_WALLET : 1, 29 | IMPORTED : 2, 30 | IMPORTED_WATCH : 3 31 | }; 32 | 33 | TLWalletUtils.TLAccountTxType = { 34 | SEND : 0, 35 | RECEIVE : 1, 36 | MOVE_BETWEEN_WALLET : 2 37 | }; 38 | 39 | function TLWalletUtils() { 40 | } 41 | 42 | TLWalletUtils.DEFAULT_FEE_AMOUNT = function() { 43 | return 10000; 44 | }; 45 | 46 | TLWalletUtils.isValidInputTransactionFee = function(amount) { 47 | var maxFeeAmount = new TLCoin(TLWalletUtils.MAX_FEE_AMOUNT); 48 | var minFeeAmount = new TLCoin(TLWalletUtils.MIN_FEE_AMOUNT); 49 | if (amount.greater(maxFeeAmount) || amount.less(minFeeAmount)) { 50 | return false; 51 | } 52 | return true; 53 | }; 54 | 55 | TLWalletUtils.parseURI = function(uri) { 56 | uri = decodeURIComponent(uri); 57 | var pars = {}; 58 | var req; // BIP-0021 59 | pars.address = uri.replace('bitcoin:', '').split('?')[0]; 60 | if (uri.split('?')[1]) { 61 | uri.split('?')[1].split('&').forEach(function(parsed) { 62 | if(parsed) { 63 | pars[parsed.split('=')[0]] = parsed.split('=')[1]; 64 | if (parsed.split('=')[0].indexOf('req-') == 0) { 65 | req = true; 66 | } 67 | } 68 | }); 69 | } 70 | return !req ? pars : null; 71 | }; 72 | 73 | return TLWalletUtils; 74 | }); 75 | -------------------------------------------------------------------------------- /src/js/model/identity.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | define([], 4 | function() { 5 | 6 | function Identity(name, appDelegate) { 7 | this.appDelegate = appDelegate; 8 | this.name = name; 9 | } 10 | 11 | Identity.prototype.isLocalWalletDataReady = function() { 12 | return this.appDelegate != null && this.appDelegate.walletDecrypted == true; 13 | }; 14 | 15 | return Identity; 16 | }); 17 | -------------------------------------------------------------------------------- /src/js/util/fixes.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @fileOverview Fixes for frightening things in other libraries 3 | */ 4 | 'use strict'; 5 | 6 | define(['sjcl-real'], function(sjcl) { 7 | /* 8 | * Uncached version of apparently very frightening sjcl function 9 | * (gets called by sjcl.encrypt and sjcl.decrypt) 10 | * This version: 11 | * - adds more bits of salt 12 | * - removes password cache 13 | * - sanitizes use of obj.iter 14 | */ 15 | sjcl.misc.cachedPbkdf2 = function (password, obj) { 16 | obj = obj || {}; 17 | if (!obj.iter) { 18 | obj.iter = 1000; 19 | } 20 | 21 | obj.salt = obj.salt ? obj.salt.slice(0) : sjcl.random.randomWords(4,0); 22 | 23 | var val = sjcl.misc.pbkdf2(password, obj.salt, obj.iter); 24 | return { key: val.slice(0), salt: obj.salt.slice(0) }; 25 | }; 26 | 27 | return sjcl; 28 | }); 29 | -------------------------------------------------------------------------------- /src/sass/_angular-csp.scss: -------------------------------------------------------------------------------- 1 | /* Include this file in your html if you are using the CSP mode. */ 2 | 3 | @charset "UTF-8"; 4 | 5 | [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], 6 | .ng-cloak, .x-ng-cloak, 7 | .ng-hide { 8 | display: none !important; 9 | } 10 | 11 | ng\:form { 12 | display: block; 13 | } 14 | 15 | .ng-animate-block-transitions { 16 | transition:0s all!important; 17 | -webkit-transition:0s all!important; 18 | } 19 | -------------------------------------------------------------------------------- /src/sass/_foundation.scss: -------------------------------------------------------------------------------- 1 | // Make sure the charset is set appropriately 2 | @charset "UTF-8"; 3 | 4 | // Foundation Components (comment out as necessary) 5 | @import 6 | "foundation/components/grid", 7 | // "foundation/components/accordion", 8 | "foundation/components/alert-boxes", 9 | "foundation/components/block-grid", 10 | // "foundation/components/breadcrumbs", 11 | "foundation/components/button-groups", 12 | "foundation/components/buttons", 13 | // "foundation/components/clearing", 14 | "foundation/components/dropdown", 15 | // "foundation/components/dropdown-buttons", 16 | // "foundation/components/flex-video", 17 | "foundation/components/forms", 18 | "foundation/components/icon-bar", 19 | "foundation/components/inline-lists", 20 | // "foundation/components/joyride", 21 | // "foundation/components/keystrokes", 22 | "foundation/components/labels", 23 | // "foundation/components/magellan", 24 | // "foundation/components/orbit", 25 | "foundation/components/pagination", 26 | "foundation/components/panels", 27 | // "foundation/components/pricing-tables", 28 | "foundation/components/progress-bars", 29 | "foundation/components/range-slider", 30 | // "foundation/components/reveal", 31 | "foundation/components/side-nav", 32 | // "foundation/components/split-buttons", 33 | "foundation/components/sub-nav", 34 | "foundation/components/switches", 35 | // "foundation/components/tables", 36 | "foundation/components/tabs", 37 | // "foundation/components/thumbs", 38 | "foundation/components/tooltips", 39 | "foundation/components/top-bar", 40 | "foundation/components/type", 41 | "foundation/components/offcanvas", 42 | "foundation/components/visibility"; 43 | -------------------------------------------------------------------------------- /src/sass/_toaster.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcbit/arcbit-web/fe0e9d00f08b3e79b4804dda0f977f8dc6db17a5/src/sass/_toaster.scss -------------------------------------------------------------------------------- /src/sass/_variables.scss: -------------------------------------------------------------------------------- 1 | /* ========================================================================== 2 | You can copy the variables file here or simply add new overrides here. 3 | _variables.scss 4 | ========================================================================== 5 | */ 6 | 7 | $row-width: rem-calc(1280) !default; 8 | $column-gutter: rem-calc(30) !default; 9 | $total-columns: 12 !default; 10 | -------------------------------------------------------------------------------- /src/sound/keygenEnd.opus: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcbit/arcbit-web/fe0e9d00f08b3e79b4804dda0f977f8dc6db17a5/src/sound/keygenEnd.opus -------------------------------------------------------------------------------- /src/vendors/angular-route/angular-route.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | AngularJS v1.2.26 3 | (c) 2010-2014 Google, Inc. http://angularjs.org 4 | License: MIT 5 | */ 6 | (function(n,e,A){'use strict';function x(s,g,h){return{restrict:"ECA",terminal:!0,priority:400,transclude:"element",link:function(a,c,b,f,w){function y(){p&&(p.remove(),p=null);k&&(k.$destroy(),k=null);l&&(h.leave(l,function(){p=null}),p=l,l=null)}function v(){var b=s.current&&s.current.locals;if(e.isDefined(b&&b.$template)){var b=a.$new(),d=s.current;l=w(b,function(d){h.enter(d,null,l||c,function(){!e.isDefined(t)||t&&!a.$eval(t)||g()});y()});k=d.scope=b;k.$emit("$viewContentLoaded");k.$eval(u)}else y()} 7 | var k,l,p,t=b.autoscroll,u=b.onload||"";a.$on("$routeChangeSuccess",v);v()}}}function z(e,g,h){return{restrict:"ECA",priority:-400,link:function(a,c){var b=h.current,f=b.locals;c.html(f.$template);var w=e(c.contents());b.controller&&(f.$scope=a,f=g(b.controller,f),b.controllerAs&&(a[b.controllerAs]=f),c.data("$ngControllerController",f),c.children().data("$ngControllerController",f));w(a)}}}n=e.module("ngRoute",["ng"]).provider("$route",function(){function s(a,c){return e.extend(new (e.extend(function(){}, 8 | {prototype:a})),c)}function g(a,e){var b=e.caseInsensitiveMatch,f={originalPath:a,regexp:a},h=f.keys=[];a=a.replace(/([().])/g,"\\$1").replace(/(\/)?:(\w+)([\?\*])?/g,function(a,e,b,c){a="?"===c?c:null;c="*"===c?c:null;h.push({name:b,optional:!!a});e=e||"";return""+(a?"":e)+"(?:"+(a?e:"")+(c&&"(.+?)"||"([^/]+)")+(a||"")+")"+(a||"")}).replace(/([\/$\*])/g,"\\$1");f.regexp=RegExp("^"+a+"$",b?"i":"");return f}var h={};this.when=function(a,c){h[a]=e.extend({reloadOnSearch:!0},c,a&&g(a,c));if(a){var b= 9 | "/"==a[a.length-1]?a.substr(0,a.length-1):a+"/";h[b]=e.extend({redirectTo:a},g(b,c))}return this};this.otherwise=function(a){this.when(null,a);return this};this.$get=["$rootScope","$location","$routeParams","$q","$injector","$http","$templateCache","$sce",function(a,c,b,f,g,n,v,k){function l(){var d=p(),m=r.current;if(d&&m&&d.$$route===m.$$route&&e.equals(d.pathParams,m.pathParams)&&!d.reloadOnSearch&&!u)m.params=d.params,e.copy(m.params,b),a.$broadcast("$routeUpdate",m);else if(d||m)u=!1,a.$broadcast("$routeChangeStart", 10 | d,m),(r.current=d)&&d.redirectTo&&(e.isString(d.redirectTo)?c.path(t(d.redirectTo,d.params)).search(d.params).replace():c.url(d.redirectTo(d.pathParams,c.path(),c.search())).replace()),f.when(d).then(function(){if(d){var a=e.extend({},d.resolve),c,b;e.forEach(a,function(d,c){a[c]=e.isString(d)?g.get(d):g.invoke(d)});e.isDefined(c=d.template)?e.isFunction(c)&&(c=c(d.params)):e.isDefined(b=d.templateUrl)&&(e.isFunction(b)&&(b=b(d.params)),b=k.getTrustedResourceUrl(b),e.isDefined(b)&&(d.loadedTemplateUrl= 11 | b,c=n.get(b,{cache:v}).then(function(a){return a.data})));e.isDefined(c)&&(a.$template=c);return f.all(a)}}).then(function(c){d==r.current&&(d&&(d.locals=c,e.copy(d.params,b)),a.$broadcast("$routeChangeSuccess",d,m))},function(c){d==r.current&&a.$broadcast("$routeChangeError",d,m,c)})}function p(){var a,b;e.forEach(h,function(f,h){var q;if(q=!b){var g=c.path();q=f.keys;var l={};if(f.regexp)if(g=f.regexp.exec(g)){for(var k=1,p=g.length;kinput,.editable-wrap .editable-controls>select,.editable-wrap .editable-controls>textarea{margin-bottom:0}.editable-wrap .editable-input{display:inline-block}.editable-buttons{display:inline-block;vertical-align:top}.editable-buttons button{margin-left:5px}.editable-input.editable-has-buttons{width:auto}.editable-bstime .editable-input input[type=text]{width:46px}.editable-bstime .well-small{margin-bottom:0;padding:10px}.editable-range output{display:inline-block;min-width:30px;vertical-align:top;text-align:center}.editable-color input[type=color]{width:50px}.editable-checkbox label span,.editable-checklist label span,.editable-radiolist label span{margin-left:7px;margin-right:10px}.editable-hide{display:none!important}.editable-click,a.editable-click{text-decoration:none;color:#428bca;border-bottom:dashed 1px #428bca}.editable-click:hover,a.editable-click:hover{text-decoration:none;color:#2a6496;border-bottom-color:#2a6496}.editable-empty,.editable-empty:hover,.editable-empty:focus,a.editable-empty,a.editable-empty:hover,a.editable-empty:focus{font-style:italic;color:#D14;text-decoration:none} 8 | -------------------------------------------------------------------------------- /src/vendors/angular/angular-csp.css: -------------------------------------------------------------------------------- 1 | /* Include this file in your html if you are using the CSP mode. */ 2 | 3 | @charset "UTF-8"; 4 | 5 | [ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], 6 | .ng-cloak, .x-ng-cloak, 7 | .ng-hide { 8 | display: none !important; 9 | } 10 | 11 | ng\:form { 12 | display: block; 13 | } 14 | 15 | .ng-animate-block-transitions { 16 | transition:0s all!important; 17 | -webkit-transition:0s all!important; 18 | } 19 | 20 | /* show the element during a show/hide animation when the 21 | * animation is ongoing, but the .ng-hide class is active */ 22 | .ng-hide-add-active, .ng-hide-remove { 23 | display: block!important; 24 | } 25 | -------------------------------------------------------------------------------- /src/vendors/font-awesome/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arcbit/arcbit-web/fe0e9d00f08b3e79b4804dda0f977f8dc6db17a5/src/vendors/font-awesome/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /src/vendors/identicon/identicon.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Identicon.js v1.0 3 | * http://github.com/stewartlord/identicon.js 4 | * 5 | * Requires PNGLib 6 | * http://www.xarg.org/download/pnglib.js 7 | * 8 | * Copyright 2013, Stewart Lord 9 | * Released under the BSD license 10 | * http://www.opensource.org/licenses/bsd-license.php 11 | */ 12 | 13 | (function() { 14 | Identicon = function(hash, size, margin){ 15 | this.hash = hash; 16 | this.size = size || 64; 17 | this.margin = margin || .08; 18 | } 19 | 20 | Identicon.prototype = { 21 | hash: null, 22 | size: null, 23 | margin: null, 24 | 25 | render: function(){ 26 | var hash = this.hash, 27 | size = this.size, 28 | margin = Math.floor(size * this.margin), 29 | cell = Math.floor((size - (margin * 2)) / 5), 30 | image = new PNGlib(size, size, 256); 31 | 32 | // light-grey background 33 | var bg = image.color(240, 240, 240, 0); 34 | 35 | // foreground is last 7 chars as hue at 50% saturation, 70% brightness 36 | var rgb = this.hsl2rgb(parseInt(hash.substr(-7), 16) / 0xfffffff, .5, .7), 37 | fg = image.color(rgb[0] * 255, rgb[1] * 255, rgb[2] * 255); 38 | 39 | // the first 15 characters of the hash control the pixels (even/odd) 40 | // they are drawn down the middle first, then mirrored outwards 41 | var i, color; 42 | for (i = 0; i < 15; i++) { 43 | color = parseInt(hash.charAt(i), 16) % 2 ? bg : fg; 44 | if (i < 5) { 45 | this.rectangle(2 * cell + margin, i * cell + margin, cell, cell, color, image); 46 | } else if (i < 10) { 47 | this.rectangle(1 * cell + margin, (i - 5) * cell + margin, cell, cell, color, image); 48 | this.rectangle(3 * cell + margin, (i - 5) * cell + margin, cell, cell, color, image); 49 | } else if (i < 15) { 50 | this.rectangle(0 * cell + margin, (i - 10) * cell + margin, cell, cell, color, image); 51 | this.rectangle(4 * cell + margin, (i - 10) * cell + margin, cell, cell, color, image); 52 | } 53 | } 54 | 55 | return image; 56 | }, 57 | 58 | rectangle: function(x, y, w, h, color, image) { 59 | var i, j; 60 | for (i = x; i < x + w; i++) { 61 | for (j = y; j < y + h; j++) { 62 | image.buffer[image.index(i, j)] = color; 63 | } 64 | } 65 | }, 66 | 67 | // adapted from: https://gist.github.com/aemkei/1325937 68 | hsl2rgb: function(h, s, b){ 69 | h *= 6; 70 | s = [ 71 | b += s *= b < .5 ? b : 1 - b, 72 | b - h % 1 * s * 2, 73 | b -= s *= 2, 74 | b, 75 | b + h % 1 * s, 76 | b + s 77 | ]; 78 | 79 | return[ 80 | s[ ~~h % 6 ], // red 81 | s[ (h|16) % 6 ], // green 82 | s[ (h|8) % 6 ] // blue 83 | ]; 84 | }, 85 | 86 | toString: function(){ 87 | return this.render().getBase64(); 88 | } 89 | } 90 | 91 | window.Identicon = Identicon; 92 | })(); 93 | -------------------------------------------------------------------------------- /src/vendors/jsqrcode-concat.sh: -------------------------------------------------------------------------------- 1 | cat jsqrcode/src/grid.js 2 | cat jsqrcode/src/version.js 3 | cat jsqrcode/src/detector.js 4 | cat jsqrcode/src/formatinf.js 5 | cat jsqrcode/src/errorlevel.js 6 | cat jsqrcode/src/bitmat.js 7 | cat jsqrcode/src/datablock.js 8 | cat jsqrcode/src/bmparser.js 9 | cat jsqrcode/src/datamask.js 10 | cat jsqrcode/src/rsdecoder.js 11 | cat jsqrcode/src/gf256poly.js 12 | cat jsqrcode/src/gf256.js 13 | cat jsqrcode/src/decoder.js 14 | cat jsqrcode/src/qrcode.js 15 | cat jsqrcode/src/findpat.js 16 | cat jsqrcode/src/alignpat.js 17 | cat jsqrcode/src/databr.js 18 | -------------------------------------------------------------------------------- /src/vendors/ngprogress/ngProgress.css: -------------------------------------------------------------------------------- 1 | /* Styling for the ngProgress itself */ 2 | #ngProgress { 3 | margin: 0; 4 | padding: 0; 5 | z-index: 99998; 6 | background-color: green; 7 | color: green; 8 | box-shadow: 0 0 10px 0; /* Inherits the font color */ 9 | height: 2px; 10 | opacity: 0; 11 | 12 | /* Add CSS3 styles for transition smoothing */ 13 | -webkit-transition: all 0.5s ease-in-out; 14 | -moz-transition: all 0.5s ease-in-out; 15 | -o-transition: all 0.5s ease-in-out; 16 | transition: all 0.5s ease-in-out; 17 | } 18 | 19 | /* Styling for the ngProgress-container */ 20 | #ngProgress-container { 21 | position: fixed; 22 | margin: 0; 23 | padding: 0; 24 | top: 0; 25 | left: 0; 26 | right: 0; 27 | z-index: 99999; 28 | } 29 | -------------------------------------------------------------------------------- /src/vendors/socket-io-client/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "socket-io-client", 3 | "version": "1.4.3", 4 | "description": "Realtime application framework (client) for Bower", 5 | "keywords": [ 6 | "socket-io", 7 | "socket", 8 | "io", 9 | "realtime" 10 | ], 11 | "homepage": "https://github.com/Alexandre-io/socket.io-client.git", 12 | "bugs": "https://github.com/Alexandre-io/socket.io-client/issues", 13 | "author": { 14 | "name": "Alexandre L.", 15 | "email": "", 16 | "url": "https://github.com/Alexandre-io" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git://github.com/Alexandre-io/socket.io-client.git" 21 | }, 22 | "main": "socket.io.js", 23 | "ignore": [ 24 | ".*", 25 | "node_modules", 26 | "bower_components", 27 | "demo*", 28 | "src*", 29 | "Gruntfile.js", 30 | "karma.conf.js", 31 | "package.json" 32 | ], 33 | "license": "MIT" 34 | } 35 | -------------------------------------------------------------------------------- /src/vendors/spin.js/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .grunt 3 | node_modules 4 | site/spin.js 5 | site/spin.min.js 6 | site/jquery.spin.js 7 | -------------------------------------------------------------------------------- /src/vendors/spin.js/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "spin.js", 3 | "main": "spin.js", 4 | "dependencies": {}, 5 | "readme": "README.md", 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/fgnass/spin.js.git" 9 | }, 10 | "ignore": [ 11 | "Gruntfile.js", 12 | "site", 13 | "bower.json", 14 | "component.json", 15 | "package.json" 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /src/vendors/spin.js/spin.min.js: -------------------------------------------------------------------------------- 1 | // http://spin.js.org/#v2.3.2 2 | !function(a,b){"object"==typeof module&&module.exports?module.exports=b():"function"==typeof define&&define.amd?define(b):a.Spinner=b()}(this,function(){"use strict";function a(a,b){var c,d=document.createElement(a||"div");for(c in b)d[c]=b[c];return d}function b(a){for(var b=1,c=arguments.length;c>b;b++)a.appendChild(arguments[b]);return a}function c(a,b,c,d){var e=["opacity",b,~~(100*a),c,d].join("-"),f=.01+c/d*100,g=Math.max(1-(1-a)/b*(100-f),a),h=j.substring(0,j.indexOf("Animation")).toLowerCase(),i=h&&"-"+h+"-"||"";return m[e]||(k.insertRule("@"+i+"keyframes "+e+"{0%{opacity:"+g+"}"+f+"%{opacity:"+a+"}"+(f+.01)+"%{opacity:1}"+(f+b)%100+"%{opacity:"+a+"}100%{opacity:"+g+"}}",k.cssRules.length),m[e]=1),e}function d(a,b){var c,d,e=a.style;if(b=b.charAt(0).toUpperCase()+b.slice(1),void 0!==e[b])return b;for(d=0;d',c)}k.addRule(".spin-vml","behavior:url(#default#VML)"),h.prototype.lines=function(a,d){function f(){return e(c("group",{coordsize:k+" "+k,coordorigin:-j+" "+-j}),{width:k,height:k})}function h(a,h,i){b(m,b(e(f(),{rotation:360/d.lines*a+"deg",left:~~h}),b(e(c("roundrect",{arcsize:d.corners}),{width:j,height:d.scale*d.width,left:d.scale*d.radius,top:-d.scale*d.width>>1,filter:i}),c("fill",{color:g(d.color,a),opacity:d.opacity}),c("stroke",{opacity:0}))))}var i,j=d.scale*(d.length+d.width),k=2*d.scale*j,l=-(d.width+d.length)*d.scale*2+"px",m=e(f(),{position:"absolute",top:l,left:l});if(d.shadow)for(i=1;i<=d.lines;i++)h(i,-2,"progid:DXImageTransform.Microsoft.Blur(pixelradius=2,makeshadow=1,shadowopacity=.3)");for(i=1;i<=d.lines;i++)h(i);return b(a,m)},h.prototype.opacity=function(a,b,c,d){var e=a.firstChild;d=d.shadow&&d.lines||0,e&&b+d>1)+"px"})}for(var i,k=0,l=(f.lines-1)*(1-f.direction)/2;k