Merge branch 'ref/design' of https://github.com/bitpay/bitpay-wallet into feature/recieve_view_polish

This commit is contained in:
Jamal Jackson 2016-10-17 09:11:15 -04:00
commit aad986c266
117 changed files with 7705 additions and 5274 deletions

2
.gitignore vendored
View file

@ -50,7 +50,7 @@ build/Release
node_modules node_modules
bower_components bower_components
angular-bitcore-wallet-client/angular-bitcore-wallet-client.js angular-bitcore-wallet-client/angular-bitcore-wallet-client.js
angular-pbkdf2/angular-pbkdf2.js angular-bitauth/angular-bitauth.js
# Users Environment Variables # Users Environment Variables
.lock-wscript .lock-wscript

View file

@ -128,7 +128,7 @@ module.exports = function(grunt) {
'bower_components/angular-md5/angular-md5.js', 'bower_components/angular-md5/angular-md5.js',
'bower_components/angular-mocks/angular-mocks.js', 'bower_components/angular-mocks/angular-mocks.js',
'bower_components/ngtouch/src/ngTouch.js', 'bower_components/ngtouch/src/ngTouch.js',
'angular-pbkdf2/angular-pbkdf2.js', 'angular-bitauth/angular-bitauth.js',
'angular-bitcore-wallet-client/angular-bitcore-wallet-client.js' 'angular-bitcore-wallet-client/angular-bitcore-wallet-client.js'
], ],
dest: 'www/lib/angular.js' dest: 'www/lib/angular.js'
@ -216,15 +216,6 @@ module.exports = function(grunt) {
}], }],
} }
}, },
karma: {
unit: {
configFile: 'test/karma.conf.js'
},
prod: {
configFile: 'test/karma.conf.js',
singleRun: true
}
},
nwjs: { nwjs: {
options: { options: {
appName: 'Copay', appName: 'Copay',
@ -251,7 +242,7 @@ module.exports = function(grunt) {
dist: { dist: {
files: { files: {
'angular-bitcore-wallet-client/angular-bitcore-wallet-client.js': ['angular-bitcore-wallet-client/index.js'], 'angular-bitcore-wallet-client/angular-bitcore-wallet-client.js': ['angular-bitcore-wallet-client/index.js'],
'angular-pbkdf2/angular-pbkdf2.js': ['angular-pbkdf2/index.js'] 'angular-bitauth/angular-bitauth.js': ['angular-bitauth/index.js']
}, },
} }
} }
@ -260,8 +251,6 @@ module.exports = function(grunt) {
grunt.registerTask('default', ['nggettext_compile', 'exec:appConfig', 'exec:externalServices', 'browserify', 'sass', 'concat', 'copy:ionic_fonts', 'copy:ionic_js']); grunt.registerTask('default', ['nggettext_compile', 'exec:appConfig', 'exec:externalServices', 'browserify', 'sass', 'concat', 'copy:ionic_fonts', 'copy:ionic_js']);
grunt.registerTask('prod', ['default', 'uglify']); grunt.registerTask('prod', ['default', 'uglify']);
grunt.registerTask('translate', ['nggettext_extract']); grunt.registerTask('translate', ['nggettext_extract']);
grunt.registerTask('test', ['karma:unit']);
grunt.registerTask('test-coveralls', ['browserify', 'karma:prod', 'exec:coveralls']);
grunt.registerTask('desktop', ['prod', 'nwjs', 'copy:linux', 'compress:linux']); grunt.registerTask('desktop', ['prod', 'nwjs', 'copy:linux', 'compress:linux']);
grunt.registerTask('osx', ['prod', 'nwjs', 'exec:osx']); grunt.registerTask('osx', ['prod', 'nwjs', 'exec:osx']);
grunt.registerTask('chrome', ['exec:chrome']); grunt.registerTask('chrome', ['exec:chrome']);

18
angular-bitauth/index.js vendored Normal file
View file

@ -0,0 +1,18 @@
var bitauthModule = angular.module('bitauthModule', []);
var bitauth = require('../node_modules/bitauth');
bitauthModule.constant('MODULE_VERSION', '1.0.0');
bitauthModule.provider("bitauthService", function() {
var provider = {};
provider.$get = function() {
var service = {};
service = bitauth;
return service;
};
return provider;
});

6380
angular-pbkdf2/angular-pbkdf2.js vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,18 +0,0 @@
var pbkdf2Module = angular.module('pbkdf2Module', []);
var pbkdf2Sync = require('../node_modules/pbkdf2').pbkdf2Sync;
pbkdf2Module.constant('MODULE_VERSION', '1.0.0');
pbkdf2Module.provider("pbkdf2Service", function() {
var provider = {};
provider.$get = function() {
var service = {};
service.pbkdf2Sync = pbkdf2Sync;
return service;
};
return provider;
});

View file

@ -5,6 +5,7 @@
// //
var templates = { var templates = {
'package.json': '/',
'Makefile': 'cordova/', 'Makefile': 'cordova/',
'ProjectMakefile': 'cordova/', 'ProjectMakefile': 'cordova/',
'config-template.xml': '/', 'config-template.xml': '/',

View file

@ -5,12 +5,13 @@
"purposeLine": "Secure Bitcoin Wallet", "purposeLine": "Secure Bitcoin Wallet",
"bundleName": "wallet", "bundleName": "wallet",
"appUri": "bitpay", "appUri": "bitpay",
"name": "bitpay", "name": "bitpay",
"nameNoSpace": "bitpay", "nameNoSpace": "bitpay",
"nameCase": "BitPay", "nameCase": "BitPay",
"nameCaseNoSpace": "BitPay", "nameCaseNoSpace": "BitPay",
"gitHubRepoName": "bitpay-wallet", "gitHubRepoName": "bitpay-wallet",
"gitHubRepoUrl": "git://github.com/bitpay/bitpay-wallet.git",
"gitHubRepoBugs": "https://github.com/bitpay/bitpay-wallet/issues",
"disclaimerUrl": "", "disclaimerUrl": "",
"url": "https://bitpay.com", "url": "https://bitpay.com",
"appDescription": "Secure Bitcoin Wallet", "appDescription": "Secure Bitcoin Wallet",

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

View file

@ -16,7 +16,8 @@
<preference name="iosPersistentFileLocation" value="Library" /> <preference name="iosPersistentFileLocation" value="Library" />
<preference name="DisallowOverscroll" value="true"/> <preference name="DisallowOverscroll" value="true"/>
<preference name="HideKeyboardFormAccessoryBar" value="true"/> <preference name="HideKeyboardFormAccessoryBar" value="true"/>
<preference name="KeyboardDisplayRequiresUserAction" value="false" /> <!-- #355 -->
<!-- <preference name="KeyboardDisplayRequiresUserAction" value="false" /> -->
<preference name="StatusBarBackgroundColor" value="#1e3186" /> <preference name="StatusBarBackgroundColor" value="#1e3186" />
<preference name="BackupWebStorage" value="none"/> <preference name="BackupWebStorage" value="none"/>
<preference name="windows-target-version" value="8.1"/> <preference name="windows-target-version" value="8.1"/>
@ -56,8 +57,9 @@
<plugin name="cordova-plugin-whitelist" spec="~1.3.0" /> <plugin name="cordova-plugin-whitelist" spec="~1.3.0" />
<plugin name="cordova-plugin-wkwebview-engine" spec="https://github.com/driftyco/cordova-plugin-wkwebview-engine.git" /> <plugin name="cordova-plugin-wkwebview-engine" spec="https://github.com/driftyco/cordova-plugin-wkwebview-engine.git" />
<plugin name="cordova-plugin-qrscanner" spec="~2.4.0" /> <plugin name="cordova-plugin-qrscanner" spec="~2.4.0" />
<plugin name="cordova-plugin-customurlscheme" spec="~4.2.0"> <plugin name="cordova-plugin-customurlscheme" spec="https://github.com/cmgustavo/Custom-URL-scheme.git">
<variable name="URL_SCHEME" value="UNUSED" /> <variable name="URL_SCHEME" value="bitcoin" />
<variable name="SECOND_URL_SCHEME" value="*APPURI*" />
</plugin> </plugin>
<plugin name="phonegap-plugin-push" spec="~1.8.2"> <plugin name="phonegap-plugin-push" spec="~1.8.2">
<variable name="SENDER_ID" value="*PUSHSENDERID*"/> <variable name="SENDER_ID" value="*PUSHSENDERID*"/>

View file

@ -3,14 +3,15 @@
"packageDescription": "Copay Bitcoin Wallet", "packageDescription": "Copay Bitcoin Wallet",
"userVisibleName": "Copay", "userVisibleName": "Copay",
"purposeLine": "Copay Bitcoin Wallet", "purposeLine": "Copay Bitcoin Wallet",
"bundleName": "copay",
"appUri": "copay", "appUri": "copay",
"name": "copay", "name": "copay",
"nameNoSpace": "copay", "nameNoSpace": "copay",
"nameCase": "Copay", "nameCase": "Copay",
"nameCaseNoSpace": "Copay", "nameCaseNoSpace": "Copay",
"bundleName": "copay",
"gitHubRepoName": "copay", "gitHubRepoName": "copay",
"gitHubRepoUrl": "git://github.com/bitpay/copay.git",
"gitHubRepoBugs": "https://github.com/bitpay/copay/issues",
"disclaimerUrl": "https://copay.io/disclaimer", "disclaimerUrl": "https://copay.io/disclaimer",
"url": "https://copay.io", "url": "https://copay.io",
"appDescription": "Copay Bitcoin Wallet", "appDescription": "Copay Bitcoin Wallet",

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View file

@ -14,14 +14,14 @@
"main": "www/index.html", "main": "www/index.html",
"window": { "window": {
"title": "*USERVISIBLENAME* - *PURPOSELINE*", "title": "*USERVISIBLENAME* - *PURPOSELINE*",
"icon": "./www/img/icons/icon-256.png", "icon": "www/img/icon-128.png",
"toolbar": false, "toolbar": false,
"show": true, "show": true,
"visible": true, "visible": true,
"resizable": true, "resizable": true,
"frame": true, "frame": true,
"width": 400, "width": 400,
"height": 600, "height": 650,
"position": "center", "position": "center",
"fullscreen": false "fullscreen": false
}, },
@ -32,25 +32,27 @@
}, },
"dom_storage_quota": 200, "dom_storage_quota": 200,
"id": "jid1-x7bV5evAaI1P9Q", "id": "jid1-x7bV5evAaI1P9Q",
"homepage": "https://github.com/bitpay/copay", "homepage": "*URL*",
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"url": "git://github.com/bitpay/copay.git", "url": "*GITHUBREPOURL*",
"type": "git" "type": "git"
}, },
"bugs": { "bugs": {
"url": "https://github.com/bitpay/copay/issues" "url": "*GITHUBREPOBUGS*"
}, },
"dependencies": { "dependencies": {
"adm-zip": "^0.4.7", "adm-zip": "^0.4.7",
"angular": "1.4.6", "angular": "1.4.6",
"angular-mocks": "1.4.10", "angular-mocks": "1.4.10",
"bezier-easing": "^2.0.3",
"bhttp": "^1.2.1", "bhttp": "^1.2.1",
"bitcore-wallet-client": "4.2.1", "bitauth": "^0.3.2",
"bitcore-wallet-client": "4.3.2",
"bower": "^1.7.9", "bower": "^1.7.9",
"chai": "^3.5.0", "chai": "^3.5.0",
"cordova": "5.4.1",
"cordova-android": "5.1.1", "cordova-android": "5.1.1",
"cordova-custom-config": "^3.0.5",
"cordova-plugin-qrscanner": "^2.3.1", "cordova-plugin-qrscanner": "^2.3.1",
"coveralls": "^2.11.9", "coveralls": "^2.11.9",
"express": "^4.11.2", "express": "^4.11.2",
@ -65,39 +67,44 @@
"grunt-contrib-uglify": "^2.0.0", "grunt-contrib-uglify": "^2.0.0",
"grunt-contrib-watch": "^1.0.0", "grunt-contrib-watch": "^1.0.0",
"grunt-exec": "^1.0.0", "grunt-exec": "^1.0.0",
"grunt-karma": "^2.0.0",
"grunt-karma-coveralls": "^2.5.4",
"grunt-nw-builder": "^2.0.3", "grunt-nw-builder": "^2.0.3",
"grunt-sass": "^1.2.0", "grunt-sass": "^1.2.0",
"karma": "^0.13.22",
"karma-chai": "^0.1.0",
"karma-chrome-launcher": "^1.0.1",
"karma-cli": "^1.0.0",
"karma-coverage": "^1.0.0",
"karma-mocha": "^1.0.1",
"karma-mocha-reporter": "^2.0.3",
"karma-phantomjs-launcher": "^1.0.0",
"karma-sinon": "^1.0.5",
"load-grunt-tasks": "^3.5.0", "load-grunt-tasks": "^3.5.0",
"mocha": "^2.4.5", "mocha": "^2.4.5",
"pbkdf2": "^3.0.4",
"phantomjs-prebuilt": "^2.1.7", "phantomjs-prebuilt": "^2.1.7",
"shelljs": "^0.3.0", "shelljs": "^0.3.0"
"xcode": "^0.8.2"
}, },
"scripts": { "scripts": {
"preinstall": "bower install && npm i fs-extra", "preinstall": "bower install && npm i fs-extra",
"build": "grunt", "postinstall": "npm run apply:bitpay && cordova prepare",
"apply:copay": "cd app-template && node apply.js", "start": "npm run build:www && ionic serve --nolivereload --nogulp -s",
"apply:bitpay-wallet": "cd app-template && node apply.js bitpay-wallet", "start:ios": "npm run build:www && npm run build:ios && npm run open:ios",
"start": "npm run build && node app.js", "start:android": "npm run build:www && npm run build:android && npm run run:android",
"watch": "grunt watch", "watch": "grunt watch",
"test": "./node_modules/.bin/grunt test-coveralls", "build:www": "grunt",
"clean": "git clean -dfx", "build:www-release": "grunt prod",
"start:ios": "npm run build && cd cordova && trash project-ios && make ios && open project-ios/platforms/ios/BitPay\\ Wallet.xcodeproj", "build:ios": "cordova prepare ios && cordova build ios --debug",
"start:android": "npm run build && cd cordova && trash project-android && make android && open -a /Applications/Android\\ Studio.app project-android/platforms/android" "build:android": "cordova prepare android && cordova build android --debug",
"build:ios-release": "cordova prepare ios && cordova build ios --release",
"build:android-release": "cordova prepare android && cordova build android --release",
"open:ios": "open platforms/ios/*.xcodeproj",
"open:android": "open -a open -a /Applications/Android\\ Studio.app platforms/android",
"final:www": "npm run clean-all && npm run build:www-release",
"final:ios": "npm run final:www && npm run build:ios-release && npm run open:ios",
"final:android": "npm run final:www && npm run build:android-release && npm run run:android",
"run:android": "cordova run android --device",
"log:android": "adb logcat | grep chromium",
"apply:copay": "cd app-template && node apply.js copay",
"apply:bitpay": "cd app-template && node apply.js bitpay",
"test": "./node_modules/.bin/grunt test-coveralls",
"clean": "trash platforms && trash plugins && npm run postinstall",
"clean-all": "git clean -dfx && npm install"
}, },
"devDependencies": { "devDependencies": {
"trash-cli": "^1.4.0" "cordova": "^6.3.1",
"grunt": "^1.0.1",
"ionic": "^2.1.0",
"trash-cli": "^1.4.0",
"lodash": "^4.3.0"
} }
} }

View file

@ -8,7 +8,7 @@
#define MyAppExeName "*PACKAGENAME.exe" #define MyAppExeName "*PACKAGENAME.exe"
[Setup] [Setup]
AppId={{804636ee-b017-4cad-8719-e58ac97ffa5c} AppId={804636ee-b017-4cad-8719-e58ac97ffa5c}
AppName={#MyAppName} AppName={#MyAppName}
AppVersion={#MyAppVersion} AppVersion={#MyAppVersion}
;AppVerName={#MyAppName} {#MyAppVersion} ;AppVerName={#MyAppName} {#MyAppVersion}

View file

@ -1,4 +1,9 @@
{ {
"//":"PLEASE! Do not edit this file directly",
"//":" Modify it at app-template/",
"name": "bitpay",
"description": "Secure Bitcoin Wallet",
"author": "BitPay", "author": "BitPay",
"version": "0.14.0", "version": "0.14.0",
"keywords": [ "keywords": [
@ -9,14 +14,35 @@
"multisignature", "multisignature",
"bitcore" "bitcore"
], ],
"homepage": "https://github.com/bitpay/copay", "main": "www/index.html",
"window": {
"title": "BitPay - Secure Bitcoin Wallet",
"icon": "www/img/icon-128.png",
"toolbar": false,
"show": true,
"visible": true,
"resizable": true,
"frame": true,
"width": 400,
"height": 650,
"position": "center",
"fullscreen": false
},
"webkit": {
"page-cache": false,
"java": false,
"plugin": false
},
"dom_storage_quota": 200,
"id": "jid1-x7bV5evAaI1P9Q",
"homepage": "https://bitpay.com",
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"url": "git://github.com/bitpay/copay.git", "url": "git://github.com/bitpay/bitpay-wallet.git",
"type": "git" "type": "git"
}, },
"bugs": { "bugs": {
"url": "https://github.com/bitpay/copay/issues" "url": "https://github.com/bitpay/bitpay-wallet/issues"
}, },
"dependencies": { "dependencies": {
"adm-zip": "^0.4.7", "adm-zip": "^0.4.7",
@ -24,7 +50,8 @@
"angular-mocks": "1.4.10", "angular-mocks": "1.4.10",
"bezier-easing": "^2.0.3", "bezier-easing": "^2.0.3",
"bhttp": "^1.2.1", "bhttp": "^1.2.1",
"bitcore-wallet-client": "4.3.1", "bitauth": "^0.3.2",
"bitcore-wallet-client": "4.3.2",
"bower": "^1.7.9", "bower": "^1.7.9",
"chai": "^3.5.0", "chai": "^3.5.0",
"cordova-android": "5.1.1", "cordova-android": "5.1.1",
@ -43,22 +70,10 @@
"grunt-contrib-uglify": "^2.0.0", "grunt-contrib-uglify": "^2.0.0",
"grunt-contrib-watch": "^1.0.0", "grunt-contrib-watch": "^1.0.0",
"grunt-exec": "^1.0.0", "grunt-exec": "^1.0.0",
"grunt-karma": "^2.0.0",
"grunt-karma-coveralls": "^2.5.4",
"grunt-nw-builder": "^2.0.3", "grunt-nw-builder": "^2.0.3",
"grunt-sass": "^1.2.0", "grunt-sass": "^1.2.0",
"karma": "^0.13.22",
"karma-chai": "^0.1.0",
"karma-chrome-launcher": "^1.0.1",
"karma-cli": "^1.0.0",
"karma-coverage": "^1.0.0",
"karma-mocha": "^1.0.1",
"karma-mocha-reporter": "^2.0.3",
"karma-phantomjs-launcher": "^1.0.0",
"karma-sinon": "^1.0.5",
"load-grunt-tasks": "^3.5.0", "load-grunt-tasks": "^3.5.0",
"mocha": "^2.4.5", "mocha": "^2.4.5",
"pbkdf2": "^3.0.4",
"phantomjs-prebuilt": "^2.1.7", "phantomjs-prebuilt": "^2.1.7",
"shelljs": "^0.3.0" "shelljs": "^0.3.0"
}, },

View file

@ -0,0 +1,45 @@
<ion-view id="bitpayCard-intro">
<ion-nav-bar class="bar-stable">
<ion-nav-back-button>
</ion-nav-back-button>
<ion-nav-title></ion-nav-title>
<ion-nav-buttons side="secondary">
<button class="button back-button button-clear" ng-click="orderBitPayCard()">
<i class="icon ion-ios-information-outline"></i>
</button>
</ion-nav-buttons>
</ion-nav-bar>
<ion-content scroll="false">
<div class="text-center padding">
<img src="img/bitpay-card-visa.svg" width="100%">
</div>
<ion-slide-box>
<ion-slide>
<p translate>
Turn bitcoin into dollars, swipe anywhere Visa&reg; is accepted.
</p>
</ion-slide>
<ion-slide>
<p translate>
<span translate>Get local cash anywhere you go, from any Visa&reg;-compatible ATM.</span>
<div translate class="size-10 m20t text-center">
*ATM bank fees may apply
</div>
</p>
</ion-slide>
<ion-slide>
<p translate>
Pay 0% fees to turn bitcoin into dollars.
</p>
</ion-slide>
</ion-slide-box>
<div class="cta-button">
<button class="button button-block button-primary" ng-click="orderBitPayCard()" translate>
Order the BitPay Card
</button>
<button class="button button-block button-transparent text-white m10t" ng-click="connectBitPayCard()" translate>
Connect my BitPay Card
</button>
</div>
</ion-content>
</ion-view>

View file

@ -12,7 +12,7 @@ var modules = [
'ngCsv', 'ngCsv',
'angular-md5', 'angular-md5',
'bwcModule', 'bwcModule',
'pbkdf2Module', 'bitauthModule',
'copayApp.filters', 'copayApp.filters',
'copayApp.services', 'copayApp.services',
'copayApp.controllers', 'copayApp.controllers',

View file

@ -2,33 +2,10 @@
angular.module('copayApp.controllers').controller('addressbookViewController', function($scope, $state, $timeout, $stateParams, lodash, addressbookService, popupService, $ionicHistory) { angular.module('copayApp.controllers').controller('addressbookViewController', function($scope, $state, $timeout, $stateParams, lodash, addressbookService, popupService, $ionicHistory) {
$scope.$on("$ionicView.beforeEnter", function(event, data){ $scope.addressbookEntry = {};
var address = data.stateParams.address; $scope.addressbookEntry.name = $stateParams.name;
$scope.addressbookEntry.email = $stateParams.email;
if (!address) { $scope.addressbookEntry.address = $stateParams.address;
$ionicHistory.back();
return;
}
addressbookService.get(address, function(err, obj) {
if (err) {
popupService.showAlert(err);
return;
}
if (!lodash.isObject(obj)) {
var name = obj;
obj = {
'name': name,
'address': address,
'email': ''
};
}
$scope.addressbookEntry = obj;
$timeout(function() {
$scope.$apply();
});
});
});
$scope.sendTo = function() { $scope.sendTo = function() {
$ionicHistory.removeBackView(); $ionicHistory.removeBackView();

View file

@ -5,8 +5,8 @@ angular.module('copayApp.controllers').controller('amazonController',
$scope.network = amazonService.getEnvironment(); $scope.network = amazonService.getEnvironment();
$scope.openExternalLink = function(url, target) { $scope.openExternalLink = function(url, optIn, title, message, okText, cancelText) {
externalLinkService.open(url, target); externalLinkService.open(url, optIn, title, message, okText, cancelText);
}; };
var initAmazon = function() { var initAmazon = function() {
@ -83,7 +83,7 @@ angular.module('copayApp.controllers').controller('amazonController',
}); });
}; };
$scope.$on("$ionicView.beforeEnter", function(event, data){ $scope.$on("$ionicView.beforeEnter", function(event, data) {
initAmazon(); initAmazon();
}); });
}); });

View file

@ -17,13 +17,14 @@ angular.module('copayApp.controllers').controller('amountController', function($
$scope.$on("$ionicView.beforeEnter", function(event, data) { $scope.$on("$ionicView.beforeEnter", function(event, data) {
$scope.isWallet = data.stateParams.isWallet; $scope.isWallet = data.stateParams.isWallet;
$scope.isCard = data.stateParams.isCard; $scope.cardId = data.stateParams.cardId;
$scope.toAddress = data.stateParams.toAddress; $scope.toAddress = data.stateParams.toAddress;
$scope.toName = data.stateParams.toName; $scope.toName = data.stateParams.toName;
$scope.toEmail = data.stateParams.toEmail; $scope.toEmail = data.stateParams.toEmail;
$scope.showAlternativeAmount = !!$scope.isCard; $scope.showAlternativeAmount = !!$scope.cardId;
$scope.toColor = data.stateParams.toColor;
if (!$scope.isCard && !$stateParams.toAddress) { if (!$scope.cardId && !$stateParams.toAddress) {
$log.error('Bad params at amount') $log.error('Bad params at amount')
throw ('bad params'); throw ('bad params');
} }
@ -93,7 +94,7 @@ angular.module('copayApp.controllers').controller('amountController', function($
$scope.pushDigit = function(digit) { $scope.pushDigit = function(digit) {
if ($scope.amount && $scope.amount.length >= LENGTH_EXPRESSION_LIMIT) return; if ($scope.amount && $scope.amount.length >= LENGTH_EXPRESSION_LIMIT) return;
if ($scope.amount.indexOf('.') > -1 && digit == '.') return; if ($scope.amount.indexOf('.') > -1 && digit == '.') return;
if($scope.showAlternativeAmount && $scope.amount.indexOf('.') > -1 && $scope.amount[$scope.amount.indexOf('.') + 2]) return; if ($scope.showAlternativeAmount && $scope.amount.indexOf('.') > -1 && $scope.amount[$scope.amount.indexOf('.') + 2]) return;
$scope.amount = ($scope.amount + digit).replace('..', '.'); $scope.amount = ($scope.amount + digit).replace('..', '.');
checkFontSize(); checkFontSize();
@ -189,7 +190,7 @@ angular.module('copayApp.controllers').controller('amountController', function($
$scope.finish = function() { $scope.finish = function() {
var _amount = evaluate(format($scope.amount)); var _amount = evaluate(format($scope.amount));
if ($scope.isCard) { if ($scope.cardId) {
var amountUSD = $scope.showAlternativeAmount ? _amount : $filter('formatFiatAmount')(toFiat(_amount)); var amountUSD = $scope.showAlternativeAmount ? _amount : $filter('formatFiatAmount')(toFiat(_amount));
var dataSrc = { var dataSrc = {
@ -199,7 +200,7 @@ angular.module('copayApp.controllers').controller('amountController', function($
ongoingProcess.set('Processing Transaction...', true); ongoingProcess.set('Processing Transaction...', true);
$timeout(function() { $timeout(function() {
bitpayCardService.topUp(dataSrc, function(err, invoiceId) { bitpayCardService.topUp($scope.cardId, dataSrc, function(err, invoiceId) {
if (err) { if (err) {
ongoingProcess.set('Processing Transaction...', false); ongoingProcess.set('Processing Transaction...', false);
popupService.showAlert(gettextCatalog.getString('Error'), bwcError.msg(err)); popupService.showAlert(gettextCatalog.getString('Error'), bwcError.msg(err));
@ -215,7 +216,7 @@ angular.module('copayApp.controllers').controller('amountController', function($
var payProUrl = data.paymentUrls.BIP73; var payProUrl = data.paymentUrls.BIP73;
$state.transitionTo('tabs.bitpayCard.confirm', { $state.transitionTo('tabs.bitpayCard.confirm', {
isCard: $scope.isCard, cardId: $scope.cardId,
toName: $scope.toName, toName: $scope.toName,
paypro: payProUrl paypro: payProUrl
}); });

View file

@ -1,39 +1,19 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('bitpayCardController', function($scope, $timeout, $log, lodash, bitpayCardService, configService, profileService, walletService, ongoingProcess, pbkdf2Service, moment, popupService, gettextCatalog, bwcError) { angular.module('copayApp.controllers').controller('bitpayCardController', function($scope, $timeout, $log, $state, lodash, bitpayCardService, moment, popupService, gettextCatalog, $ionicHistory) {
var self = this; var self = this;
$scope.dateRange = 'last30Days'; $scope.dateRange = { value: 'last30Days'};
$scope.network = bitpayCardService.getEnvironment(); $scope.network = bitpayCardService.getEnvironment();
bitpayCardService.getCacheData(function(err, data) { var getFromCache = function(cb) {
if (err || lodash.isEmpty(data)) return; bitpayCardService.getBitpayDebitCardsHistory($scope.cardId, function(err, data) {
$scope.bitpayCardCached = true; if (err || lodash.isEmpty(data)) return cb();
self.bitpayCardTransactionHistory = data.transactions; $scope.historyCached = true;
self.bitpayCardCurrentBalance = data.balance; self.bitpayCardTransactionHistory = data.transactions;
}); self.bitpayCardCurrentBalance = data.balance;
return cb();
var processTransactions = function(invoices, history) { });
for (var i = 0; i < invoices.length; i++) {
var matched = false;
for (var j = 0; j < history.length; j++) {
if (history[j].description[0].indexOf(invoices[i].id) > -1) {
matched = true;
}
}
if (!matched && ['paid', 'confirmed', 'complete'].indexOf(invoices[i].status) > -1) {
history.unshift({
timestamp: invoices[i].invoiceTime,
description: invoices[i].itemDesc,
amount: invoices[i].price,
type: '00611 = Client Funded Deposit',
pending: true,
status: invoices[i].status
});
}
}
return history;
}; };
var setDateRange = function(preset) { var setDateRange = function(preset) {
@ -62,41 +42,35 @@ angular.module('copayApp.controllers').controller('bitpayCardController', functi
}; };
this.update = function() { this.update = function() {
var dateRange = setDateRange($scope.dateRange); var dateRange = setDateRange($scope.dateRange.value);
self.loadingSession = true;
bitpayCardService.isAuthenticated(function(err, bpSession) { $scope.loadingHistory = true;
self.loadingSession = false; bitpayCardService.getHistory($scope.cardId, dateRange, function(err, history) {
$scope.loadingHistory = false;
if (err) { if (err) {
$log.error(err);
$scope.error = gettextCatalog.getString('Could not get transactions');
return; return;
} }
self.bitpayCardAuthenticated = bpSession.isAuthenticated; var txs = lodash.clone(history.txs);
self.bitpayCardTwoFactorPending = bpSession.twoFactorPending ? true : false; for (var i = 0; i < txs.length; i++) {
txs[i] = _getMerchantInfo(txs[i]);
txs[i].icon = _getIconName(txs[i]);
txs[i].desc = _processDescription(txs[i]);
}
self.bitpayCardTransactionHistory = txs;
self.bitpayCardCurrentBalance = history.currentCardBalance;
if (self.bitpayCardTwoFactorPending) return; if ($scope.dateRange.value == 'last30Days') {
$log.debug('BitPay Card: store cache history');
if (self.bitpayCardAuthenticated) { var cacheHistory = {
$scope.loadingHistory = true; balance: history.currentCardBalance,
bitpayCardService.invoiceHistory(function(err, invoices) { transactions: history.txs
};
bitpayCardService.setBitpayDebitCardsHistory($scope.cardId, cacheHistory, {}, function(err) {
if (err) $log.error(err); if (err) $log.error(err);
bitpayCardService.transactionHistory(dateRange, function(err, history) { $scope.historyCached = true;
$scope.loadingHistory = false;
if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Could not get transactions'));
return;
}
self.bitpayCardTransactionHistory = processTransactions(invoices, history.transactionList);
self.bitpayCardCurrentBalance = history.currentCardBalance;
var cacheData = {
balance: self.bitpayCardCurrentBalance,
transactions: self.bitpayCardTransactionHistory
};
bitpayCardService.setCacheData(cacheData, function(err) {
if (err) $log.error(err);
});
});
}); });
} }
$timeout(function() { $timeout(function() {
@ -105,76 +79,43 @@ angular.module('copayApp.controllers').controller('bitpayCardController', functi
}); });
}; };
this.authenticate = function(email, password) { var _getMerchantInfo = function(tx) {
var data = {
emailAddress : email,
hashedPassword : pbkdf2Service.pbkdf2Sync(password, '..............', 200, 64).toString('hex')
};
// POST /authenticate
// emailAddress:
// hashedPassword:
self.authenticating = true;
bitpayCardService.authenticate(data, function(err, auth) {
self.authenticating = false;
if (err && err.data && err.data.error && !err.data.error.twoFactorPending) {
popupService.showAlert(gettextCatalog.getString('Error'), err.statusText || err.data.error || 'Authentiation error');
return;
}
self.update();
$timeout(function() {
$scope.$apply();
}, 100);
});
};
this.authenticate2FA = function(twoFactorCode) {
var data = {
twoFactorCode : twoFactorCode
};
self.authenticating = true;
bitpayCardService.authenticate2FA(data, function(err, auth) {
self.authenticating = false;
if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Authentiation error'));
return;
}
self.update();
$timeout(function() {
$scope.$apply();
}, 100);
});
};
this.getMerchantInfo = function(tx) {
var bpTranCodes = bitpayCardService.bpTranCodes; var bpTranCodes = bitpayCardService.bpTranCodes;
lodash.keys(bpTranCodes).forEach(function(code) { lodash.keys(bpTranCodes).forEach(function(code) {
if (tx.type.indexOf(code) === 0) { if (tx.type.indexOf(code) === 0) {
lodash.assign(tx, bpTranCodes[code]); lodash.assign(tx, bpTranCodes[code]);
} }
}); });
return tx;
}; };
this.getIconName = function(tx) { var _getIconName = function(tx) {
var icon = tx.mcc || tx.category || null; var icon = tx.mcc || tx.category || null;
if (!icon) return 'default'; if (!icon) return 'default';
return bitpayCardService.iconMap[icon]; return bitpayCardService.iconMap[icon];
}; };
this.processDescription = function(tx) { var _processDescription = function(tx) {
if (lodash.isArray(tx.description)) { if (lodash.isArray(tx.description)) {
return tx.description[0]; return tx.description[0];
} }
return tx.description; return tx.description;
}; };
$scope.$on("$ionicView.beforeEnter", function(event, data){ $scope.$on("$ionicView.beforeEnter", function(event, data) {
self.update(); $scope.cardId = data.stateParams.id;
if (!$scope.cardId) {
var msg = gettextCatalog.getString('Bad param');
$ionicHistory.nextViewOptions({
disableAnimate: true
});
$state.go('tabs.home');
popupService.showAlert(null, msg);
} else {
getFromCache(function() {
self.update();
});
}
}); });
}); });

View file

@ -0,0 +1,72 @@
'use strict';
angular.module('copayApp.controllers').controller('bitpayCardIntroController', function($scope, $log, $state, $ionicHistory, storageService, externalLinkService, bitpayCardService, gettextCatalog, popupService) {
var checkOtp = function(obj, cb) {
if (obj.otp) {
var msg = gettextCatalog.getString('Enter Two Factor for BitPay Cards');
popupService.showPrompt(null, msg, null, function(res) {
cb(res);
});
} else {
cb();
}
};
$scope.$on("$ionicView.beforeEnter", function(event, data) {
if (data.stateParams && data.stateParams.secret) {
var obj = {
secret: data.stateParams.secret,
email: data.stateParams.email,
otp: data.stateParams.otp
};
checkOtp(obj, function(otp) {
obj.otp = otp;
bitpayCardService.bitAuthPair(obj, function(err, data) {
if (err) {
popupService.showAlert(null, err);
return;
}
var title = gettextCatalog.getString('Add BitPay Cards?');
var msg = gettextCatalog.getString('Would you like to add this account ({{email}}) to your wallet?', {email: obj.email});
var ok = gettextCatalog.getString('Add cards');
var cancel = gettextCatalog.getString('Go back');
popupService.showConfirm(title, msg, ok, cancel, function(res) {
if (res) {
// Set flag for nextStep
storageService.setNextStep('BitpayCard', true, function(err) {});
// Save data
bitpayCardService.setBitpayDebitCards(data, function(err) {
if (err) return;
$ionicHistory.nextViewOptions({
disableAnimate: true
});
$state.go('tabs.home').then(function() {
if (data.cards[0]) {
$state.transitionTo('tabs.bitpayCard', {id: data.cards[0].id});
}
});
});
}
});
});
});
} else {
bitpayCardService.getCredentials(function(err, credentials) {
if (err) popupService.showAlert(null, err);
else $log.info('BitPay Debit Card Credentials: Ok.');
});
}
});
$scope.orderBitPayCard = function() {
var url = 'https://bitpay.com/visa/';
externalLinkService.open(url);
};
$scope.connectBitPayCard = function() {
var url = 'https://bitpay.com/visa/login';
externalLinkService.open(url);
};
});

View file

@ -16,12 +16,14 @@ angular.module('copayApp.controllers').controller('buyAmazonController',
$log.debug('Wallet changed: ' + w.name); $log.debug('Wallet changed: ' + w.name);
}); });
$scope.openExternalLink = function(url, target) { $scope.openExternalLink = function(url, optIn, title, message, okText, cancelText) {
externalLinkService.open(url, target); externalLinkService.open(url, optIn, title, message, okText, cancelText);
}; };
this.confirm = function() { this.confirm = function() {
var message = gettextCatalog.getString('Amazon.com Gift Card purchase for ${{amount}} USD', {amount: $scope.formData.fiat}); var message = gettextCatalog.getString('Amazon.com Gift Card purchase for ${{amount}} USD', {
amount: $scope.formData.fiat
});
var ok = gettextCatalog.getString('Buy'); var ok = gettextCatalog.getString('Buy');
popupService.showConfirm(null, message, ok, null, function(res) { popupService.showConfirm(null, message, ok, null, function(res) {
if (res) self.createTx(); if (res) self.createTx();
@ -209,8 +211,10 @@ angular.module('copayApp.controllers').controller('buyAmazonController',
}); });
}; };
$scope.$on("$ionicView.enter", function(event, data){ $scope.$on("$ionicView.enter", function(event, data) {
$scope.formData = { fiat: null }; $scope.formData = {
fiat: null
};
$scope.wallets = profileService.getWallets({ $scope.wallets = profileService.getWallets({
network: network, network: network,
onlyComplete: true onlyComplete: true

View file

@ -8,7 +8,7 @@ angular.module('copayApp.controllers').controller('confirmController', function(
$scope.$on("$ionicView.beforeEnter", function(event, data) { $scope.$on("$ionicView.beforeEnter", function(event, data) {
$scope.isWallet = data.stateParams.isWallet; $scope.isWallet = data.stateParams.isWallet;
$scope.isCard = data.stateParams.isCard; $scope.cardId = data.stateParams.cardId;
$scope.toAmount = data.stateParams.toAmount; $scope.toAmount = data.stateParams.toAmount;
$scope.toAddress = data.stateParams.toAddress; $scope.toAddress = data.stateParams.toAddress;
$scope.toName = data.stateParams.toName; $scope.toName = data.stateParams.toName;
@ -36,7 +36,7 @@ angular.module('copayApp.controllers').controller('confirmController', function(
$scope.data = {}; $scope.data = {};
var config = configService.getSync().wallet; var config = configService.getSync().wallet;
$scope.feeLevel = config.settings ? config.settings.feeLevel : ''; $scope.feeLevel = config.settings && config.settings.feeLevel ? config.settings.feeLevel : 'normal';
$scope.toAmount = parseInt($scope.toAmount); $scope.toAmount = parseInt($scope.toAmount);
$scope.amountStr = txFormatService.formatAmountStr($scope.toAmount); $scope.amountStr = txFormatService.formatAmountStr($scope.toAmount);
@ -274,7 +274,7 @@ angular.module('copayApp.controllers').controller('confirmController', function(
txp.message = description; txp.message = description;
txp.payProUrl = paypro; txp.payProUrl = paypro;
txp.excludeUnconfirmedUtxos = config.spendUnconfirmed ? false : true; txp.excludeUnconfirmedUtxos = config.spendUnconfirmed ? false : true;
txp.feeLevel = config.settings.feeLevel || 'normal'; txp.feeLevel = config.settings && config.settings.feeLevel ? config.settings.feeLevel : 'normal';
txp.dryRun = dryRun; txp.dryRun = dryRun;
walletService.createTx(wallet, txp, function(err, ctxp) { walletService.createTx(wallet, txp, function(err, ctxp) {
@ -374,11 +374,22 @@ angular.module('copayApp.controllers').controller('confirmController', function(
}; };
$scope.onSuccessConfirm = function() { $scope.onSuccessConfirm = function() {
var previousView = $ionicHistory.viewHistory().backView && $ionicHistory.viewHistory().backView.stateName;
var fromBitPayCard = previousView.match(/tabs.bitpayCard/) ? true : false;
$ionicHistory.nextViewOptions({ $ionicHistory.nextViewOptions({
disableAnimate: true disableAnimate: true
}); });
$ionicHistory.removeBackView();
$scope.sendStatus = ''; $scope.sendStatus = '';
$state.go('tabs.send');
if (fromBitPayCard) {
$timeout(function() {
$state.transitionTo('tabs.bitpayCard', {id: $stateParams.cardId});
}, 100);
} else {
$state.go('tabs.send');
}
}; };
function publishAndSign(wallet, txp, onSendStatusChange) { function publishAndSign(wallet, txp, onSendStatusChange) {

View file

@ -15,6 +15,45 @@ angular.module('copayApp.controllers').controller('exportController',
}); });
}; };
function getPassword(cb) {
if ($scope.password) return cb(null, $scope.password);
walletService.prepare(wallet, function(err, password) {
if (err) return cb(err);
$scope.password = password;
return cb(null, password);
});
};
$scope.generateQrCode = function() {
if ($scope.formData.exportWalletInfo || !walletService.isEncrypted(wallet)) {
$scope.file.value = false;
}
getPassword(function(err, password) {
if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), err);
return;
}
walletService.getEncodedWalletInfo(wallet, password, function(err, code) {
if (err) return cb(err);
if (!code)
$scope.formData.supported = false;
else {
$scope.formData.supported = true;
$scope.formData.exportWalletInfo = code;
}
$scope.file.value = false;
$timeout(function() {
$scope.$apply();
});
});
});
};
var init = function() { var init = function() {
$scope.formData = {}; $scope.formData = {};
$scope.isEncrypted = wallet.isPrivKeyEncrypted(); $scope.isEncrypted = wallet.isPrivKeyEncrypted();
@ -24,24 +63,6 @@ angular.module('copayApp.controllers').controller('exportController',
$scope.showAdvanced = false; $scope.showAdvanced = false;
$scope.wallet = wallet; $scope.wallet = wallet;
$scope.canSign = wallet.canSign(); $scope.canSign = wallet.canSign();
walletService.getEncodedWalletInfo(wallet, function(err, code) {
if (err || !code) {
$log.warn(err);
return $ionicHistory.goBack();
}
if (!code)
$scope.formData.supported = false;
else {
$scope.formData.supported = true;
$scope.formData.exportWalletInfo = code;
}
$timeout(function() {
$scope.$apply();
}, 1);
});
}; };
/* /*
@ -67,23 +88,31 @@ angular.module('copayApp.controllers').controller('exportController',
}; };
$scope.downloadWalletBackup = function() { $scope.downloadWalletBackup = function() {
$scope.getAddressbook(function(err, localAddressBook) { getPassword(function(err, password) {
if (err) { if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Failed to export')); popupService.showAlert(gettextCatalog.getString('Error'), err);
return; return;
} }
var opts = {
noSign: $scope.formData.noSignEnabled,
addressBook: localAddressBook
};
backupService.walletDownload($scope.formData.password, opts, function(err) { $scope.getAddressbook(function(err, localAddressBook) {
if (err) { if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Failed to export')); popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Failed to export'));
return; return;
} }
$ionicHistory.removeBackView(); var opts = {
$state.go('tabs.home'); noSign: $scope.formData.noSignEnabled,
addressBook: localAddressBook,
password: password
};
backupService.walletDownload($scope.formData.password, opts, function(err) {
if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Failed to export'));
return;
}
$ionicHistory.removeBackView();
$state.go('tabs.home');
});
}); });
}); });
}; };
@ -104,21 +133,29 @@ angular.module('copayApp.controllers').controller('exportController',
}; };
$scope.getBackup = function(cb) { $scope.getBackup = function(cb) {
$scope.getAddressbook(function(err, localAddressBook) { getPassword(function(err, password) {
if (err) { if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Failed to export')); popupService.showAlert(gettextCatalog.getString('Error'), err);
return cb(null); return;
} }
var opts = {
noSign: $scope.formData.noSignEnabled,
addressBook: localAddressBook
};
var ew = backupService.walletExport($scope.formData.password, opts); $scope.getAddressbook(function(err, localAddressBook) {
if (!ew) { if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Failed to export')); popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Failed to export'));
} return cb(null);
return cb(ew); }
var opts = {
noSign: $scope.formData.noSignEnabled,
addressBook: localAddressBook,
password: password
};
var ew = backupService.walletExport($scope.formData.password, opts);
if (!ew) {
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Failed to export'));
}
return cb(ew);
});
}); });
}; };
@ -171,6 +208,11 @@ angular.module('copayApp.controllers').controller('exportController',
$scope.$on("$ionicView.beforeEnter", function(event, data) { $scope.$on("$ionicView.beforeEnter", function(event, data) {
init(); init();
$scope.file = {
value: true
};
$scope.formData.exportWalletInfo = null;
$scope.password = null;
}); });
}); });

View file

@ -5,8 +5,8 @@ angular.module('copayApp.controllers').controller('glideraController',
$scope.network = glideraService.getEnvironment(); $scope.network = glideraService.getEnvironment();
$scope.openExternalLink = function(url, target) { $scope.openExternalLink = function(url, optIn, title, message, okText, cancelText) {
externalLinkService.open(url, target); externalLinkService.open(url, optIn, title, message, okText, cancelText);
}; };
var initGlidera = function(accessToken) { var initGlidera = function(accessToken) {
@ -27,7 +27,9 @@ angular.module('copayApp.controllers').controller('glideraController',
} }
$scope.token = glidera.token; $scope.token = glidera.token;
$scope.permissions = glidera.permissions; $scope.permissions = glidera.permissions;
$scope.update({fullUpdate: true}); $scope.update({
fullUpdate: true
});
}); });
}; };
@ -113,7 +115,7 @@ angular.module('copayApp.controllers').controller('glideraController',
}); });
}; };
$scope.$on("$ionicView.beforeEnter", function(event, data){ $scope.$on("$ionicView.beforeEnter", function(event, data) {
initGlidera(); initGlidera();
}); });

View file

@ -1,6 +1,6 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('amazonCardDetailsController', function($scope, $log, $timeout, bwcError, amazonService, lodash, ongoingProcess, popupService, gettextCatalog) { angular.module('copayApp.controllers').controller('amazonCardDetailsController', function($scope, $log, $timeout, bwcError, amazonService, lodash, ongoingProcess, popupService, gettextCatalog, externalLinkService) {
$scope.cancelGiftCard = function() { $scope.cancelGiftCard = function() {
ongoingProcess.set('Canceling gift card...', true); ongoingProcess.set('Canceling gift card...', true);
@ -62,4 +62,8 @@ angular.module('copayApp.controllers').controller('amazonCardDetailsController',
$scope.amazonCardDetailsModal.hide(); $scope.amazonCardDetailsModal.hide();
}; };
$scope.openExternalLink = function(url, optIn, title, message, okText, cancelText) {
externalLinkService.open(url, optIn, title, message, okText, cancelText);
};
}); });

View file

@ -139,8 +139,8 @@ angular.module('copayApp.controllers').controller('txDetailsController', functio
}); });
}; };
$scope.openExternalLink = function(url, target) { $scope.openExternalLink = function(url, optIn, title, message, okText, cancelText) {
externalLinkService.open(url, target); externalLinkService.open(url, optIn, title, message, okText, cancelText);
}; };
$scope.getShortNetworkName = function() { $scope.getShortNetworkName = function() {

View file

@ -15,8 +15,8 @@ angular.module('copayApp.controllers').controller('termsController', function($s
}); });
}; };
$scope.openExternalLink = function(url, target) { $scope.openExternalLink = function(url, optIn, title, message, okText, cancelText) {
externalLinkService.open(url, target); externalLinkService.open(url, optIn, title, message, okText, cancelText);
}; };
}); });

View file

@ -8,7 +8,7 @@ angular.module('copayApp.controllers').controller('preferencesAbout',
$scope.commitHash = $window.commitHash; $scope.commitHash = $window.commitHash;
$scope.name = $window.appConfig.gitHubRepoName; $scope.name = $window.appConfig.gitHubRepoName;
$scope.openExternalLink = function(url, target) { $scope.openExternalLink = function(url, optIn, title, message, okText, cancelText) {
externalLinkService.open(url, target); externalLinkService.open(url, optIn, title, message, okText, cancelText);
}; };
}); });

View file

@ -1,17 +1,17 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('preferencesBitpayCardController', angular.module('copayApp.controllers').controller('preferencesBitpayCardController',
function($scope, $state, $timeout, $ionicHistory, bitpayCardService, popupService) { function($scope, $state, $timeout, $ionicHistory, bitpayCardService, popupService, gettextCatalog) {
$scope.logout = function() { $scope.remove = function() {
var title = 'Are you sure you would like to log out of your Bitpay Card account?'; var msg = gettextCatalog.getString('Are you sure you would like to remove your BitPay Card account from this device?');
popupService.showConfirm(title, null, null, null, function(res) { popupService.showConfirm(null, msg, null, null, function(res) {
if (res) logout(); if (res) remove();
}); });
}; };
var logout = function() { var remove = function() {
bitpayCardService.logout(function() { bitpayCardService.remove(function() {
$ionicHistory.removeBackView(); $ionicHistory.removeBackView();
$timeout(function() { $timeout(function() {
$state.go('tabs.home'); $state.go('tabs.home');
@ -19,4 +19,11 @@ angular.module('copayApp.controllers').controller('preferencesBitpayCardControll
}); });
}; };
$scope.$on("$ionicView.beforeEnter", function(event, data) {
bitpayCardService.getBitpayDebitCards(function(err, data) {
if (err) return;
$scope.bitpayCards = data.cards;
});
});
}); });

View file

@ -21,7 +21,8 @@ angular.module('copayApp.controllers').controller('preferencesDeleteWalletContro
if (err) { if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), err.message || err); popupService.showAlert(gettextCatalog.getString('Error'), err.message || err);
} else { } else {
$ionicHistory.removeBackView(); $ionicHistory.clearHistory();
$ionicHistory.clearCache();
$state.go('tabs.home'); $state.go('tabs.home');
} }
}); });

View file

@ -2,23 +2,25 @@
angular.module('copayApp.controllers').controller('preferencesEmailController', function($scope, $ionicHistory, $stateParams, gettextCatalog, profileService, walletService, configService) { angular.module('copayApp.controllers').controller('preferencesEmailController', function($scope, $ionicHistory, $stateParams, gettextCatalog, profileService, walletService, configService) {
var wallet = profileService.getWallet($stateParams.walletId); $scope.wallet = profileService.getWallet($stateParams.walletId);
var walletId = wallet.credentials.walletId; var walletId = $scope.wallet.credentials.walletId;
var config = configService.getSync(); var config = configService.getSync();
config.emailFor = config.emailFor || {}; config.emailFor = config.emailFor || {};
$scope.emailForExist = config.emailFor && config.emailFor[walletId];
$scope.email = { $scope.email = {
value: config.emailFor && config.emailFor[walletId] value: config.emailFor && config.emailFor[walletId]
}; };
$scope.save = function() {
$scope.save = function(val) {
var opts = { var opts = {
emailFor: {} emailFor: {}
}; };
opts.emailFor[walletId] = $scope.email.value; opts.emailFor[walletId] = val;
walletService.updateRemotePreferences(wallet, { walletService.updateRemotePreferences($scope.wallet, {
email: $scope.email.value, email: val,
}, function(err) { }, function(err) {
if (err) $log.warn(err); if (err) $log.warn(err);
configService.set(opts, function(err) { configService.set(opts, function(err) {

View file

@ -6,12 +6,12 @@ angular.module('copayApp.controllers').controller('tabHomeController',
var listeners = []; var listeners = [];
var notifications = []; var notifications = [];
$scope.externalServices = {}; $scope.externalServices = {};
$scope.bitpayCardEnabled = true; // TODO
$scope.openTxpModal = txpModalService.open; $scope.openTxpModal = txpModalService.open;
$scope.version = $window.version; $scope.version = $window.version;
$scope.name = $window.appConfig.nameCase; $scope.name = $window.appConfig.nameCase;
$scope.homeTip = $stateParams.fromOnboarding; $scope.homeTip = $stateParams.fromOnboarding;
$scope.isCordova = platformInfo.isCordova; $scope.isCordova = platformInfo.isCordova;
$scope.isAndroid = platformInfo.isAndroid;
$scope.$on("$ionicView.afterEnter", function() { $scope.$on("$ionicView.afterEnter", function() {
startupService.ready(); startupService.ready();
@ -19,7 +19,7 @@ angular.module('copayApp.controllers').controller('tabHomeController',
if (!$scope.homeTip) { if (!$scope.homeTip) {
storageService.getHomeTipAccepted(function(error, value) { storageService.getHomeTipAccepted(function(error, value) {
$scope.homeTip = (value == 'false') ? false : true; $scope.homeTip = (value == 'accepted') ? false : true;
}); });
} }
@ -175,7 +175,7 @@ angular.module('copayApp.controllers').controller('tabHomeController',
}; };
$scope.hideHomeTip = function() { $scope.hideHomeTip = function() {
storageService.setHomeTipAccepted(false, function(error, value) { storageService.setHomeTipAccepted('accepted', function() {
$scope.homeTip = false; $scope.homeTip = false;
$timeout(function() { $timeout(function() {
$scope.$apply(); $scope.$apply();
@ -203,19 +203,32 @@ angular.module('copayApp.controllers').controller('tabHomeController',
}; };
var bitpayCardCache = function() { var bitpayCardCache = function() {
bitpayCardService.getCacheData(function(err, data) { bitpayCardService.getBitpayDebitCards(function(err, data) {
if (err ||  lodash.isEmpty(data)) return; if (err) return;
$scope.bitpayCard = data; if (lodash.isEmpty(data)) {
$scope.bitpayCards = null;
return;
}
$scope.bitpayCards = data.cards;
});
bitpayCardService.getBitpayDebitCardsHistory(null, function(err, data) {
if (err) return;
if (lodash.isEmpty(data)) {
$scope.cardsHistory = null;
return;
}
$scope.cardsHistory = data;
}); });
}; };
$scope.onRefresh = function() { $scope.onRefresh = function() {
$scope.$broadcast('scroll.refreshComplete'); $timeout(function() {
$scope.$broadcast('scroll.refreshComplete');
}, 300);
updateAllWallets(); updateAllWallets();
}; };
$scope.$on("$ionicView.enter", function(event, data) { $scope.$on("$ionicView.enter", function(event, data) {
$scope.bitpayCard = null;
nextStep(); nextStep();
updateAllWallets(); updateAllWallets();

View file

@ -23,7 +23,10 @@ angular.module('copayApp.controllers').controller('tabReceiveController', functi
if (err) popupService.showAlert(gettextCatalog.getString('Error'), err); if (err) popupService.showAlert(gettextCatalog.getString('Error'), err);
$scope.addr = addr; $scope.addr = addr;
if ($scope.wallet.showBackupNeededModal) $scope.openBackupNeededModal(); if ($scope.wallet.showBackupNeededModal) $scope.openBackupNeededModal();
$scope.$apply();
$timeout(function(){
$scope.$apply();
},10);
}); });
}; };

View file

@ -1,6 +1,6 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('tabSendController', function($scope, $log, $timeout, $ionicScrollDelegate, addressbookService, profileService, lodash, $state, walletService, incomingData, popupService) { angular.module('copayApp.controllers').controller('tabSendController', function($scope, $log, $timeout, $ionicScrollDelegate, addressbookService, profileService, lodash, $state, walletService, incomingData, popupService, $rootScope) {
var originalList; var originalList;
var CONTACTS_SHOW_LIMIT; var CONTACTS_SHOW_LIMIT;
@ -59,6 +59,10 @@ angular.module('copayApp.controllers').controller('tabSendController', function(
}); });
}; };
$scope.openScanner = function() {
$state.go('tabs.scan');
}
$scope.showMore = function() { $scope.showMore = function() {
currentContactsPage++; currentContactsPage++;
updateList(); updateList();
@ -98,23 +102,28 @@ angular.module('copayApp.controllers').controller('tabSendController', function(
isWallet: item.isWallet, isWallet: item.isWallet,
toAddress: addr, toAddress: addr,
toName: item.name, toName: item.name,
toEmail: item.email toEmail: item.email,
toColor: item.color
}) })
}); });
}); });
}; };
var updateHasFunds = function() { var updateHasFunds = function() {
$scope.hasFunds = null;
if ($rootScope.everHasFunds) {
$scope.hasFunds = true;
return;
}
$scope.hasFunds = false;
var wallets = profileService.getWallets({ var wallets = profileService.getWallets({
onlyComplete: true, onlyComplete: true,
}); });
if (!wallets || !wallets.length) { if (!wallets || !wallets.length) {
$scope.hasFunds = false; return $timeout(function() {
$timeout(function() {
$scope.$apply(); $scope.$apply();
}); });
} }
@ -128,11 +137,12 @@ angular.module('copayApp.controllers').controller('tabSendController', function(
return; return;
} }
if (status.availableBalanceSat) { if (status.availableBalanceSat > 0) {
$scope.hasFunds = true; $scope.hasFunds = true;
$rootScope.everHasFunds = true;
} }
if (index == wallets.length) { if (index == wallets.length) {
$scope.hasFunds = $scope.hasFunds || false;
$timeout(function() { $timeout(function() {
$scope.$apply(); $scope.$apply();
}); });

View file

@ -24,8 +24,8 @@ angular.module('copayApp.controllers').controller('tabSettingsController', funct
$scope.wallets = profileService.getWallets(); $scope.wallets = profileService.getWallets();
}; };
$scope.openExternalLink = function(url, target) { $scope.openExternalLink = function(url, optIn, title, message, okText, cancelText) {
externalLinkService.open(url, target); externalLinkService.open(url, optIn, title, message, okText, cancelText);
}; };
$scope.$on("$ionicView.beforeEnter", function(event, data) { $scope.$on("$ionicView.beforeEnter", function(event, data) {

View file

@ -6,7 +6,7 @@ angular.module('copayApp.controllers').controller('tabsController', function($ro
if (!incomingData.redir(data)) { if (!incomingData.redir(data)) {
popupService.showAlert(null, gettextCatalog.getString('Invalid data')); popupService.showAlert(null, gettextCatalog.getString('Invalid data'));
} }
} };
$scope.setScanFn = function(scanFn) { $scope.setScanFn = function(scanFn) {
$scope.scan = function() { $scope.scan = function() {
@ -22,32 +22,4 @@ angular.module('copayApp.controllers').controller('tabsController', function($ro
}, 1); }, 1);
}; };
var hideTabsViews = [
'tabs.send.amount',
'tabs.send.confirm',
'tabs.send.addressbook',
'tabs.addressbook',
'tabs.addressbook.add',
'tabs.addressbook.view',
'tabs.preferences.backupWarning',
'tabs.preferences.backup',
'tabs.receive.backupWarning',
'tabs.receive.backup',
'tabs.bitpayCard.amount',
'tabs.bitpayCard.confirm',
];
$rootScope.$on('$ionicView.beforeEnter', function() {
$rootScope.hideTabs = false;
var currentState = $state.current.name;
lodash.each(hideTabsViews, function(view) {
if (currentState === view) {
$rootScope.hideTabs = true;
}
});
});
}); });

View file

@ -9,6 +9,7 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
$scope.completeTxHistory = []; $scope.completeTxHistory = [];
$scope.openTxpModal = txpModalService.open; $scope.openTxpModal = txpModalService.open;
$scope.isCordova = platformInfo.isCordova; $scope.isCordova = platformInfo.isCordova;
$scope.isAndroid = platformInfo.isAndroid;
$scope.openExternalLink = function(url, target) { $scope.openExternalLink = function(url, target) {
externalLinkService.open(url, target); externalLinkService.open(url, target);
@ -161,7 +162,9 @@ angular.module('copayApp.controllers').controller('walletDetailsController', fun
}; };
$scope.onRefresh = function() { $scope.onRefresh = function() {
$scope.$broadcast('scroll.refreshComplete'); $timeout(function() {
$scope.$broadcast('scroll.refreshComplete');
}, 300);
$scope.updateAll(true); $scope.updateAll(true);
}; };

View file

@ -0,0 +1,13 @@
'use strict';
angular.module('copayApp.directives')
.directive('hideTabs', function($rootScope) {
return {
restrict: 'A',
link: function($scope, $el) {
$rootScope.hideTabs = 'tabs-item-hide';
$scope.$on('$destroy', function() {
$rootScope.hideTabs = '';
});
}
};
});

View file

@ -251,7 +251,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
*/ */
.state('tabs.send.amount', { .state('tabs.send.amount', {
url: '/amount/:isWallet/:toAddress/:toName/:toEmail', url: '/amount/:isWallet/:toAddress/:toName/:toEmail/:toColor',
views: { views: {
'tab-send@tabs': { 'tab-send@tabs': {
controller: 'amountController', controller: 'amountController',
@ -571,7 +571,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
} }
}) })
.state('tabs.addressbook.view', { .state('tabs.addressbook.view', {
url: '/view/:address', url: '/view/:address/:email/:name',
views: { views: {
'tab-settings@tabs': { 'tab-settings@tabs': {
templateUrl: 'views/addressbook.view.html', templateUrl: 'views/addressbook.view.html',
@ -846,8 +846,17 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
* *
*/ */
.state('tabs.bitpayCard', { .state('tabs.bitpayCardIntro', {
url: '/bitpay-card', url: '/bitpay-card-intro/:secret/:email/:otp',
views: {
'tab-home@tabs': {
controller: 'bitpayCardIntroController',
templateUrl: 'views/bitpayCardIntro.html'
}
}
})
.state('tabs.bitpayCard', {
url: '/bitpay-card/:id',
views: { views: {
'tab-home@tabs': { 'tab-home@tabs': {
controller: 'bitpayCardController', controller: 'bitpayCardController',
@ -857,7 +866,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
} }
}) })
.state('tabs.bitpayCard.amount', { .state('tabs.bitpayCard.amount', {
url: '/amount/:isCard/:toName', url: '/amount/:cardId/:toName',
views: { views: {
'tab-home@tabs': { 'tab-home@tabs': {
controller: 'amountController', controller: 'amountController',
@ -866,7 +875,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
} }
}) })
.state('tabs.bitpayCard.confirm', { .state('tabs.bitpayCard.confirm', {
url: '/confirm/:isCard/:toAddress/:toName/:toAmount/:toEmail/:description/:paypro', url: '/confirm/:cardId/:toAddress/:toName/:toAmount/:toEmail/:description/:paypro',
views: { views: {
'tab-home@tabs': { 'tab-home@tabs': {
controller: 'confirmController', controller: 'confirmController',
@ -878,6 +887,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
url: '/preferences', url: '/preferences',
views: { views: {
'tab-home@tabs': { 'tab-home@tabs': {
controller: 'preferencesBitpayCardController',
templateUrl: 'views/preferencesBitpayCard.html' templateUrl: 'views/preferencesBitpayCard.html'
} }
} }
@ -970,8 +980,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
if (lodash.isEmpty(profileService.getWallets())) { if (lodash.isEmpty(profileService.getWallets())) {
$log.debug('No wallets and no disclaimer... redirecting'); $log.debug('No wallets and no disclaimer... redirecting');
$state.go('onboarding.welcome'); $state.go('onboarding.welcome');
} } else {
else {
$log.debug('Display disclaimer... redirecting'); $log.debug('Display disclaimer... redirecting');
$state.go('onboarding.disclaimer', { $state.go('onboarding.disclaimer', {
resume: true resume: true
@ -980,8 +989,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
} else { } else {
throw new Error(err); // TODO throw new Error(err); // TODO
} }
} } else {
else {
profileService.storeProfileIfDirty(); profileService.storeProfileIfDirty();
$log.debug('Profile loaded ... Starting UX.'); $log.debug('Profile loaded ... Starting UX.');
scannerService.gentleInitialize(); scannerService.gentleInitialize();

View file

@ -1,195 +1,239 @@
'use strict'; 'use strict';
angular.module('copayApp.services').factory('bitpayCardService', function($http, $log, lodash, storageService) { angular.module('copayApp.services').factory('bitpayCardService', function($http, $log, lodash, storageService, bitauthService, platformInfo, moment) {
var root = {}; var root = {};
var credentials = {}; var BITPAY_CARD_NETWORK = 'livenet';
var bpSession = {}; var BITPAY_CARD_API_URL = BITPAY_CARD_NETWORK == 'livenet' ? 'https://bitpay.com' : 'https://test.bitpay.com';
var _setCredentials = function() { var _getCredentials = function(cb) {
/* var pubkey, sin, isNew;
* Development: 'testnet' storageService.getBitpayCardCredentials(BITPAY_CARD_NETWORK, function(err, data) {
* Production: 'livenet' if (err) return cb(err);
*/ if (lodash.isString(data)) {
credentials.NETWORK = 'livenet'; data = JSON.parse(data);
if (credentials.NETWORK == 'testnet') { }
credentials.BITPAY_API_URL = 'https://test.bitpay.com'; var credentials = data || {};
} if (lodash.isEmpty(credentials) || (credentials && !credentials.priv)) {
else { isNew = true;
credentials.BITPAY_API_URL = 'https://bitpay.com'; credentials = bitauthService.generateSin();
}; }
try {
pubkey = bitauthService.getPublicKeyFromPrivateKey(credentials.priv);
sin = bitauthService.getSinFromPublicKey(pubkey);
if (isNew)
storageService.setBitpayCardCredentials(BITPAY_CARD_NETWORK, JSON.stringify(credentials), function(err) {});
}
catch (e) {
$log.error(e);
return cb(e);
};
return cb(null, credentials);
});
}; };
var _setError = function(msg, e) { var _setError = function(msg, e) {
$log.error(msg); $log.error(msg);
return e; var error = e.data ? e.data.error : msg;
return error;
}; };
var _getUser = function(cb) { var _get = function(endpoint) {
_setCredentials(); return {
storageService.getBitpayCard(credentials.NETWORK, function(err, user) {
if (err) return cb(err);
if (lodash.isString(user)) {
user = JSON.parse(user);
}
return cb(null, user);
});
};
var _setUser = function(user, cb) {
_setCredentials();
user = JSON.stringify(user);
storageService.setBitpayCard(credentials.NETWORK, user, function(err) {
return cb(err);
});
// Show pending task from the UI
storageService.setNextStep('BitpayCard', true, function(err) {});
};
var _getSession = function(cb) {
_setCredentials();
$http({
method: 'GET', method: 'GET',
url: credentials.BITPAY_API_URL + '/visa-api/session', url: BITPAY_CARD_API_URL + endpoint,
headers: { headers: {
'content-type': 'application/json' 'content-type': 'application/json'
} }
}).then(function(data) { };
$log.info('BitPay Get Session: SUCCESS'); };
bpSession = data.data.data;
return cb(null, bpSession); var _post = function(endpoint, json, credentials) {
var dataToSign = BITPAY_CARD_API_URL + endpoint + JSON.stringify(json);
var signedData = bitauthService.sign(dataToSign, credentials.priv);
return {
method: 'POST',
url: BITPAY_CARD_API_URL + endpoint,
headers: {
'content-type': 'application/json',
'x-identity': credentials.pub,
'x-signature': signedData
},
data: json
};
};
var _postAuth = function(endpoint, json, credentials) {
json['params'].signature = bitauthService.sign(JSON.stringify(json.params), credentials.priv);
json['params'].pubkey = credentials.pub;
json['params'] = JSON.stringify(json.params);
var ret = {
method: 'POST',
url: BITPAY_CARD_API_URL + endpoint,
headers: {
'content-type': 'application/json'
},
data: json
};
$log.debug('post auth:' + JSON.stringify(ret));
return ret;
};
var _afterBitAuthSuccess = function(token, obj, credentials, cb) {
var json = {
method: 'getDebitCards'
};
// Get Debit Cards
$http(_post('/api/v2/' + token, json, credentials)).then(function(data) {
if (data && data.data.error) return cb(data.data.error);
$log.info('BitPay Get Debit Cards: SUCCESS');
return cb(data.data.error, {token: token, cards: data.data.data, email: obj.email});
}, function(data) { }, function(data) {
return cb(_setError('BitPay Card Error: Get Session', data)); return cb(_setError('BitPay Card Error: Get Debit Cards', data));
}); });
}; };
var _getBitPay = function(endpoint) { var _processTransactions = function(invoices, history) {
_setCredentials(); invoices = invoices || [];
return { for (var i = 0; i < invoices.length; i++) {
method: 'GET', var matched = false;
url: credentials.BITPAY_API_URL + endpoint, for (var j = 0; j < history.length; j++) {
headers: { if (history[j].description[0].indexOf(invoices[i].id) > -1) {
'content-type': 'application/json', matched = true;
'x-csrf-token': bpSession.csrfToken }
} }
}; var isInvoiceLessThanOneDayOld = moment() < moment(new Date(invoices[i].invoiceTime)).add(1, 'day');
}; if (!matched && isInvoiceLessThanOneDayOld) {
var isInvoiceUnderpaid = invoices[i].exceptionStatus === 'paidPartial';
var _postBitPay = function(endpoint, data) { if(['paid', 'confirmed', 'complete'].indexOf(invoices[i].status) >= 0
_setCredentials(); || (invoices[i].status === 'invalid' || isInvoiceUnderpaid)) {
return {
method: 'POST', history.unshift({
url: credentials.BITPAY_API_URL + endpoint, timestamp: new Date(invoices[i].invoiceTime),
headers: { description: invoices[i].itemDesc,
'Content-Type': 'application/json', amount: invoices[i].price,
'x-csrf-token': bpSession.csrfToken type: '00611 = Client Funded Deposit',
}, pending: true,
data: data status: invoices[i].status
}; });
}
}
}
return history;
}; };
root.getEnvironment = function() { root.getEnvironment = function() {
_setCredentials(); return BITPAY_CARD_NETWORK;
return credentials.NETWORK;
}; };
root.topUp = function(data, cb) { root.getCredentials = function(cb) {
var dataSrc = { _getCredentials(function(err, credentials) {
amount: data.amount, return cb(err, credentials);
currency: data.currency
};
$http(_postBitPay('/visa-api/topUp', dataSrc)).then(function(data) {
$log.info('BitPay TopUp: SUCCESS');
return cb(null, data.data.data.invoice);
}, function(data) {
return cb(_setError('BitPay Card Error: TopUp', data));
}); });
}; };
root.transactionHistory = function(dateRange, cb) { root.bitAuthPair = function(obj, cb) {
var params; var deviceName = 'Unknow device';
if (!dateRange.startDate) { if (platformInfo.isNW) {
params = ''; deviceName = require('os').platform();
} else { } else if (platformInfo.isCordova) {
params = '/?startDate=' + dateRange.startDate + '&endDate=' + dateRange.endDate; deviceName = device.model;
} }
$http(_getBitPay('/visa-api/transactionHistory' + params)).then(function(data) { var json = {
$log.info('BitPay Get Transaction History: SUCCESS'); method: 'createToken',
return cb(null, data.data.data); params: {
}, function(data) { secret: obj.secret,
return cb(_setError('BitPay Card Error: Get Transaction History', data)); version: 2,
deviceName: deviceName,
code: obj.otp
}
};
_getCredentials(function(err, credentials) {
if (err) return cb(err);
$http(_postAuth('/api/v2/', json, credentials)).then(function(data) {
if (data && data.data.error) return cb(data.data.error);
$log.info('BitPay Card BitAuth Create Token: SUCCESS');
_afterBitAuthSuccess(data.data.data, obj, credentials, cb);
}, function(data) {
return cb(_setError('BitPay Card Error Create Token: BitAuth', data));
});
}); });
}; };
root.invoiceHistory = function(cb) { root.getHistory = function(cardId, params, cb) {
$http(_getBitPay('/visa-api/invoiceHistory')).then(function(data) { var invoices, transactions;
$log.info('BitPay Get Invoice History: SUCCESS'); params = params || {};
return cb(null, data.data.data); var json = {
}, function(data) { method: 'getInvoiceHistory',
return cb(_setError('BitPay Card Error: Get Invoice History', data)); params: JSON.stringify(params)
};
_getCredentials(function(err, credentials) {
if (err) return cb(err);
root.getBitpayDebitCards(function(err, data) {
if (err) return cb(err);
var card = lodash.find(data.cards, {id : cardId});
if (!card) return cb(_setError('Not card found'));
// Get invoices
$http(_post('/api/v2/' + card.token, json, credentials)).then(function(data) {
$log.info('BitPay Get Invoices: SUCCESS');
invoices = data.data.data || [];
if (lodash.isEmpty(invoices)) $log.info('No invoices');
json = {
method: 'getTransactionHistory',
params: JSON.stringify(params)
};
// Get transactions list
$http(_post('/api/v2/' + card.token, json, credentials)).then(function(data) {
$log.info('BitPay Get Transactions: SUCCESS');
transactions = data.data.data || {};
transactions['txs'] = _processTransactions(invoices, transactions.transactionList);
return cb(data.data.error, transactions);
}, function(data) {
return cb(_setError('BitPay Card Error: Get Transactions', data));
});
}, function(data) {
return cb(_setError('BitPay Card Error: Get Invoices', data));
});
});
});
};
root.topUp = function(cardId, params, cb) {
params = params || {};
var json = {
method: 'generateTopUpInvoice',
params: JSON.stringify(params)
};
_getCredentials(function(err, credentials) {
if (err) return cb(err);
root.getBitpayDebitCards(function(err, data) {
if (err) return cb(err);
var card = lodash.find(data.cards, {id : cardId});
if (!card) return cb(_setError('Not card found'));
$http(_post('/api/v2/' + card.token, json, credentials)).then(function(data) {
$log.info('BitPay TopUp: SUCCESS');
return cb(data.data.error, data.data.data.invoice);
}, function(data) {
return cb(_setError('BitPay Card Error: TopUp', data));
});
});
}); });
}; };
root.getInvoice = function(id, cb) { root.getInvoice = function(id, cb) {
$http(_getBitPay('/invoices/' + id)).then(function(data) { $http(_get('/invoices/' + id)).then(function(data) {
$log.info('BitPay Get Invoice: SUCCESS'); $log.info('BitPay Get Invoice: SUCCESS');
return cb(null, data.data.data); return cb(data.data.error, data.data.data);
}, function(data) { }, function(data) {
return cb(_setError('BitPay Card Error: Get Invoice', data)); return cb(_setError('BitPay Card Error: Get Invoice', data));
}); });
}; };
root.authenticate = function(userData, cb) { root.getBitpayDebitCards = function(cb) {
_setUser(userData, function(err) { storageService.getBitpayDebitCards(BITPAY_CARD_NETWORK, function(err, data) {
$http(_postBitPay('/visa-api/authenticate', userData)).then(function(data) {
$log.info('BitPay Authenticate: SUCCESS');
_getSession(function(err, session) {
if (err) return cb(err);
return cb(null, session);
});
}, function(data) {
if (data && data.data && data.data.error.twoFactorPending) {
$log.error('BitPay Card needs 2FA Authentication');
_getSession(function(err, session) {
if (err) return cb(err);
return cb(null, session);
});
} else {
return cb(data);
}
});
});
};
root.authenticate2FA = function(userData, cb) {
$http(_postBitPay('/visa-api/verify-two-factor', userData)).then(function(data) {
$log.info('BitPay 2FA: SUCCESS');
return cb(null, data);
}, function(data) {
return cb(_setError('BitPay Card Error: 2FA', data));
});
};
root.isAuthenticated = function(cb) {
_getSession(function(err, session) {
if (err) return cb(err);
if (!session.isAuthenticated) {
_getUser(function(err, user) {
if (err) return cb(err);
if (lodash.isEmpty(user)) return cb(null, session);
root.authenticate(user, function(err, session) {
if (err) return cb(err);
return cb(null, session);
});
});
} else {
return cb(null, session);
}
});
};
root.getCacheData = function(cb) {
_setCredentials();
storageService.getBitpayCardCache(credentials.NETWORK, function(err, data) {
if (err) return cb(err); if (err) return cb(err);
if (lodash.isString(data)) { if (lodash.isString(data)) {
data = JSON.parse(data); data = JSON.parse(data);
@ -199,32 +243,54 @@ angular.module('copayApp.services').factory('bitpayCardService', function($http,
}); });
}; };
root.setCacheData = function(data, cb) { root.setBitpayDebitCards = function(data, cb) {
_setCredentials();
data = JSON.stringify(data); data = JSON.stringify(data);
storageService.setBitpayCardCache(credentials.NETWORK, data, function(err) { storageService.setBitpayDebitCards(BITPAY_CARD_NETWORK, data, function(err) {
if (err) return cb(err); if (err) return cb(err);
return cb(); return cb();
}); });
}; };
root.removeCacheData = function(cb) { root.getBitpayDebitCardsHistory = function(cardId, cb) {
_setCredentials(); storageService.getBitpayDebitCardsHistory(BITPAY_CARD_NETWORK, function(err, data) {
storageService.removeBitpayCardCache(credentials.NETWORK, function(err) {
if (err) return cb(err); if (err) return cb(err);
return cb(); if (lodash.isString(data)) {
data = JSON.parse(data);
}
data = data || {};
if (cardId) data = data[cardId];
return cb(null, data);
}); });
}; };
root.logout = function(cb) { root.setBitpayDebitCardsHistory = function(cardId, data, opts, cb) {
_setCredentials(); storageService.getBitpayDebitCardsHistory(BITPAY_CARD_NETWORK, function(err, oldData) {
root.removeCacheData(function() {}); if (lodash.isString(oldData)) {
storageService.removeBitpayCard(credentials.NETWORK, function(err) { oldData = JSON.parse(oldData);
$http(_getBitPay('/visa-api/logout')).then(function(data) { }
$log.info('BitPay Logout: SUCCESS'); if (lodash.isString(data)) {
return cb(data); data = JSON.parse(data);
}, function(data) { }
return cb(_setError('BitPay Card Error: Logout ', data)); var inv = oldData || {};
inv[cardId] = data;
if (opts && opts.remove) {
delete(inv[cardId]);
}
inv = JSON.stringify(inv);
storageService.setBitpayDebitCardsHistory(BITPAY_CARD_NETWORK, inv, function(err) {
return cb(err);
});
});
};
root.remove = function(cb) {
storageService.removeBitpayCardCredentials(BITPAY_CARD_NETWORK, function(err) {
storageService.removeBitpayDebitCards(BITPAY_CARD_NETWORK, function(err) {
storageService.removeBitpayDebitCardsHistory(BITPAY_CARD_NETWORK, function(err) {
$log.info('BitPay Debit Cards Removed: SUCCESS');
return cb();
});
}); });
}); });
}; };

View file

@ -1,8 +1,8 @@
'use strict'; 'use strict';
angular.module('copayApp.services').service('externalLinkService', function($window, $timeout, $log, platformInfo, nodeWebkitService) { angular.module('copayApp.services').service('externalLinkService', function(platformInfo, nodeWebkitService, popupService, gettextCatalog, $window, $log, $timeout) {
this.open = function(url, target) { this.open = function(url, optIn, title, message, okText, cancelText) {
var old = $window.handleOpenURL; var old = $window.handleOpenURL;
$window.handleOpenURL = function(url) { $window.handleOpenURL = function(url) {
@ -17,8 +17,16 @@ angular.module('copayApp.services').service('externalLinkService', function($win
if (platformInfo.isNW) { if (platformInfo.isNW) {
nodeWebkitService.openExternalLink(url); nodeWebkitService.openExternalLink(url);
} else { } else {
target = target || '_blank'; if (optIn) {
var ref = window.open(url, target, 'location=no'); var message = gettextCatalog.getString(message),
title = gettextCatalog.getString(title),
okText = gettextCatalog.getString(okText),
cancelText = gettextCatalog.getString(cancelText),
openBrowser = function(res) {
if (res) window.open(url, '_system');
};
popupService.showConfirm(title, message, okText, cancelText, openBrowser);
} else window.open(url, '_system');
} }
}; };

View file

@ -1,6 +1,6 @@
'use strict'; 'use strict';
angular.module('copayApp.services').factory('incomingData', function($log, $ionicModal, $state, $window, $timeout, bitcore) { angular.module('copayApp.services').factory('incomingData', function($log, $state, $window, bitcore, lodash) {
var root = {}; var root = {};
@ -23,17 +23,25 @@ angular.module('copayApp.services').factory('incomingData', function($log, $ioni
return newUri; return newUri;
}; };
function getParameterByName(name, url) {
if (!url) return;
name = name.replace(/[\[\]]/g, "\\$&");
var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
results = regex.exec(url);
if (!results) return null;
if (!results[2]) return '';
return decodeURIComponent(results[2].replace(/\+/g, " "));
}
// data extensions for Payment Protocol with non-backwards-compatible request // data extensions for Payment Protocol with non-backwards-compatible request
if ((/^bitcoin:\?r=[\w+]/).exec(data)) { if ((/^bitcoin:\?r=[\w+]/).exec(data)) {
data = decodeURIComponent(data.replace('bitcoin:?r=', '')); data = decodeURIComponent(data.replace('bitcoin:?r=', ''));
$state.go('tabs.send'); $state.go('tabs.send').then(function() {
$timeout(function() {
$state.transitionTo('tabs.send.confirm', {paypro: data}); $state.transitionTo('tabs.send.confirm', {paypro: data});
}, 100); });
return true; return true;
} }
data = sanitizeUri(data); data = sanitizeUri(data);
// BIP21 // BIP21
@ -45,8 +53,7 @@ angular.module('copayApp.services').factory('incomingData', function($log, $ioni
var amount = parsed.amount ? parsed.amount : ''; var amount = parsed.amount ? parsed.amount : '';
$state.go('tabs.send'); $state.go('tabs.send').then(function() {
$timeout(function() {
if (parsed.r) { if (parsed.r) {
$state.transitionTo('tabs.send.confirm', {paypro: parsed.r}); $state.transitionTo('tabs.send.confirm', {paypro: parsed.r});
} else { } else {
@ -56,29 +63,26 @@ angular.module('copayApp.services').factory('incomingData', function($log, $ioni
$state.transitionTo('tabs.send.amount', {toAddress: addr}); $state.transitionTo('tabs.send.amount', {toAddress: addr});
} }
} }
}, 100); });
return true; return true;
// Plain URL // Plain URL
} else if (/^https?:\/\//.test(data)) { } else if (/^https?:\/\//.test(data)) {
$state.go('tabs.send'); $state.go('tabs.send').then(function() {
$timeout(function() {
$state.transitionTo('tabs.send.confirm', {paypro: data}); $state.transitionTo('tabs.send.confirm', {paypro: data});
}, 100); });
return true; return true;
// Plain Address // Plain Address
} else if (bitcore.Address.isValid(data, 'livenet')) { } else if (bitcore.Address.isValid(data, 'livenet')) {
$state.go('tabs.send'); $state.go('tabs.send').then(function() {
$timeout(function() {
$state.transitionTo('tabs.send.amount', {toAddress: data}); $state.transitionTo('tabs.send.amount', {toAddress: data});
}, 100); });
return true; return true;
} else if (bitcore.Address.isValid(data, 'testnet')) { } else if (bitcore.Address.isValid(data, 'testnet')) {
$state.go('tabs.send'); $state.go('tabs.send').then(function() {
$timeout(function() {
$state.transitionTo('tabs.send.amount', {toAddress: data}); $state.transitionTo('tabs.send.amount', {toAddress: data});
}, 100); });
return true; return true;
// Protocol // Protocol
@ -87,20 +91,32 @@ angular.module('copayApp.services').factory('incomingData', function($log, $ioni
} else if (data && data.indexOf($window.appConfig.name + '://coinbase')==0) { } else if (data && data.indexOf($window.appConfig.name + '://coinbase')==0) {
return $state.go('uricoinbase', {url: data}); return $state.go('uricoinbase', {url: data});
// BitPayCard Authentication
} else if (data && data.indexOf($window.appConfig.name + '://')==0) {
var secret = getParameterByName('secret', data);
var email = getParameterByName('email', data);
var otp = getParameterByName('otp', data);
$state.go('tabs.home').then(function() {
$state.transitionTo('tabs.bitpayCardIntro', {
secret: secret,
email: email,
otp: otp
});
});
return true;
// Join // Join
} else if (data && data.match(/^copay:[0-9A-HJ-NP-Za-km-z]{70,80}$/)) { } else if (data && data.match(/^copay:[0-9A-HJ-NP-Za-km-z]{70,80}$/)) {
$state.go('tabs.home'); $state.go('tabs.home').then(function() {
$timeout(function() {
$state.transitionTo('tabs.add.join', {url: data}); $state.transitionTo('tabs.add.join', {url: data});
}, 100); });
return true; return true;
// Old join // Old join
} else if (data && data.match(/^[0-9A-HJ-NP-Za-km-z]{70,80}$/)) { } else if (data && data.match(/^[0-9A-HJ-NP-Za-km-z]{70,80}$/)) {
$state.go('tabs.home'); $state.go('tabs.home').then(function() {
$timeout(function() {
$state.transitionTo('tabs.add.join', {url: data}); $state.transitionTo('tabs.add.join', {url: data});
}, 100); });
return true; return true;
} }

View file

@ -41,7 +41,7 @@ angular.module('copayApp.services').factory('platformInfo', function($window) {
ret.hasClick = false; ret.hasClick = false;
if($window.sessionStorage.getItem('hasClick')) { if ($window.sessionStorage.getItem('hasClick')) {
ret.hasClick = true; ret.hasClick = true;
} }

View file

@ -134,8 +134,7 @@ angular.module('copayApp.services')
if (n.type == "NewBlock" && n.data.network == "testnet") { if (n.type == "NewBlock" && n.data.network == "testnet") {
throttledBwsEvent(n, wallet); throttledBwsEvent(n, wallet);
} } else newBwsEvent(n, wallet);
else newBwsEvent(n, wallet);
}); });
wallet.on('walletCompleted', function() { wallet.on('walletCompleted', function() {
@ -600,6 +599,7 @@ angular.module('copayApp.services')
var walletClient = bwcService.getClient(null, opts); var walletClient = bwcService.getClient(null, opts);
$log.debug('Importing Wallet:', opts); $log.debug('Importing Wallet:', opts);
try { try {
walletClient.import(str, { walletClient.import(str, {
compressed: opts.compressed, compressed: opts.compressed,
@ -611,6 +611,12 @@ angular.module('copayApp.services')
str = JSON.parse(str); str = JSON.parse(str);
if (str.xPrivKey && str.xPrivKeyEncrypted) {
$log.warn('Found both encrypted and decrypted key. Deleting the encrypted version');
delete str.xPrivKeyEncrypted;
delete str.mnemonicEncrypted;
}
var addressBook = str.addressBook || {}; var addressBook = str.addressBook || {};
addAndBindWalletClient(walletClient, { addAndBindWalletClient(walletClient, {

View file

@ -325,28 +325,40 @@ angular.module('copayApp.services')
storage.remove('coinbaseTxs-' + network, cb); storage.remove('coinbaseTxs-' + network, cb);
}; };
root.setBitpayCard = function(network, data, cb) { root.setBitpayDebitCardsHistory = function(network, data, cb) {
storage.set('bitpayCard-' + network, data, cb); storage.set('bitpayDebitCardsHistory-' + network, data, cb);
}; };
root.getBitpayCard = function(network, cb) { root.getBitpayDebitCardsHistory = function(network, cb) {
storage.get('bitpayCard-' + network, cb); storage.get('bitpayDebitCardsHistory-' + network, cb);
}; };
root.removeBitpayCard = function(network, cb) { root.removeBitpayDebitCardsHistory = function(network, cb) {
storage.remove('bitpayCard-' + network, cb); storage.remove('bitpayDebitCardsHistory-' + network, cb);
}; };
root.setBitpayCardCache = function(network, data, cb) { root.setBitpayDebitCards = function(network, data, cb) {
storage.set('bitpayCardCache-' + network, data, cb); storage.set('bitpayDebitCards-' + network, data, cb);
}; };
root.getBitpayCardCache = function(network, cb) { root.getBitpayDebitCards = function(network, cb) {
storage.get('bitpayCardCache-' + network, cb); storage.get('bitpayDebitCards-' + network, cb);
}; };
root.removeBitpayCardCache = function(network, cb) { root.removeBitpayDebitCards = function(network, cb) {
storage.remove('bitpayCardCache-' + network, cb); storage.remove('bitpayDebitCards-' + network, cb);
};
root.setBitpayCardCredentials = function(network, data, cb) {
storage.set('bitpayCardCredentials-' + network, data, cb);
};
root.getBitpayCardCredentials = function(network, cb) {
storage.get('bitpayCardCredentials-' + network, cb);
};
root.removeBitpayCardCredentials = function(network, cb) {
storage.remove('bitpayCardCredentials-' + network, cb);
}; };
root.removeAllWalletData = function(walletId, cb) { root.removeAllWalletData = function(walletId, cb) {

View file

@ -869,9 +869,8 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
if (!root.isEncrypted(wallet)) return cb(); if (!root.isEncrypted(wallet)) return cb();
askPassword(wallet.name, gettext('Enter Spending Password'), function(password) { askPassword(wallet.name, gettext('Enter Spending Password'), function(password) {
if (!password) return cb('no password'); if (!password) return cb('No password');
if (!wallet.checkPassword(password)) return cb('wrong password'); if (!wallet.checkPassword(password)) return cb('Wrong password');
return cb(null, password); return cb(null, password);
}); });
@ -990,8 +989,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
}); });
}; };
root.getEncodedWalletInfo = function(wallet, cb) { root.getEncodedWalletInfo = function(wallet, password, cb) {
var derivationPath = wallet.credentials.getBaseAddressDerivationPath(); var derivationPath = wallet.credentials.getBaseAddressDerivationPath();
var encodingType = { var encodingType = {
mnemonic: 1, mnemonic: 1,
@ -1002,25 +1000,23 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
// not supported yet // not supported yet
if (wallet.credentials.derivationStrategy != 'BIP44' || !wallet.canSign()) if (wallet.credentials.derivationStrategy != 'BIP44' || !wallet.canSign())
return null; return cb(gettextCatalog.getString('Exporting via QR not supported for this wallet'));
root.getKeys(wallet, function(err, keys) { var keys = root.getKeysWithPassword(wallet, password);
if (err || !keys) return cb(err);
if (keys.mnemonic) { if (keys.mnemonic) {
info = { info = {
type: encodingType.mnemonic, type: encodingType.mnemonic,
data: keys.mnemonic, data: keys.mnemonic,
}
} else {
info = {
type: encodingType.xpriv,
data: keys.xPrivKey
}
} }
return cb(null, info.type + '|' + info.data + '|' + wallet.credentials.network.toLowerCase() + '|' + derivationPath + '|' + (wallet.credentials.mnemonicHasPassphrase)); } else {
info = {
type: encodingType.xpriv,
data: keys.xPrivKey
}
}
}); return cb(null, info.type + '|' + info.data + '|' + wallet.credentials.network.toLowerCase() + '|' + derivationPath + '|' + (wallet.credentials.mnemonicHasPassphrase));
}; };
root.setTouchId = function(wallet, enabled, cb) { root.setTouchId = function(wallet, enabled, cb) {
@ -1055,6 +1051,12 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
}); });
}; };
root.getKeysWithPassword = function(wallet, password) {
try {
return wallet.getKeys(password);
} catch (e) {}
}
root.getViewStatus = function(wallet, txp) { root.getViewStatus = function(wallet, txp) {
var status = txp.status; var status = txp.status;
var type; var type;

View file

@ -408,6 +408,14 @@ input[type=file] {
line-height: 0px; line-height: 0px;
} }
.w100p {
width: 100%;
}
.pointer {
cursor: pointer;
}
.text-right { .text-right {
text-align: right; text-align: right;
} }

View file

@ -25,7 +25,7 @@ $font-size-small: 12px;
$font-family-sans-serif: $roboto; $font-family-sans-serif: $roboto;
$font-family-light-sans-serif: $roboto-light; $font-family-light-sans-serif: $roboto-light;
$button-border-radius: $visible-radius; $button-border-radius: $subtle-radius;
$button-height: 52px; $button-height: 52px;
$button-padding: 16px; $button-padding: 16px;

View file

@ -11,6 +11,14 @@
left: 8px; left: 8px;
font-size: 24px; font-size: 24px;
} }
.big-icon-svg {
left:5px;
& > .bg{
width:30px;
height:30px;
box-shadow: none;
}
}
font-size: 11px; font-size: 11px;
padding-left: 48px; padding-left: 48px;
} }

View file

@ -1,14 +1,27 @@
#bitpayCard { #bitpayCard {
.bar-header {
border: 0;
background: #1e3186;
.title, .button {
color: #fff;
}
.button {
background-color: transparent;
}
}
.amount { .amount {
width: 100%; width: 100%;
text-align: center; text-align: center;
padding: 2rem 1rem 1.5rem 1rem; padding: 2rem 1rem 1.5rem 1rem;
min-height: 140px; height: 140px;
border-color: #172565; border-color: #172565;
background-color: #1e3186; background-color: #1e3186;
background-image: linear-gradient(0deg, #172565, #172565 0%, transparent 0%); background-image: linear-gradient(0deg, #172565, #172565 0%, transparent 0%);
color: #fff; color: #fff;
} }
.wallet-details-wallet-info {
bottom: 5px;
}
strong { strong {
line-height: 100%; line-height: 100%;
} }

View file

@ -0,0 +1,70 @@
#bitpayCard-intro {
.slider-pager .slider-pager-page {
color: #fff;
}
.cta-button{
text-align: center;
position: absolute;
bottom: 55px;
padding: 0 1.5rem;
width: 100%;
}
background: rgba(30, 49, 134, 1);
background: -moz-linear-gradient(top, rgba(30, 49, 134, 1) 0%, rgba(17, 27, 73, 1) 100%);
background: -webkit-gradient(left top, left bottom, color-stop(0%, rgba(30, 49, 134, 1)), color-stop(100%, rgba(17, 27, 73, 1)));
background: -webkit-linear-gradient(top, rgba(30, 49, 134, 1) 0%, rgba(17, 27, 73, 1) 100%);
background: -o-linear-gradient(top, rgba(30, 49, 134, 1) 0%, rgba(17, 27, 73, 1) 100%);
background: -ms-linear-gradient(top, rgba(30, 49, 134, 1) 0%, rgba(17, 27, 73, 1) 100%);
background: linear-gradient(to bottom, rgba(30, 49, 134, 1) 0%, rgba(17, 27, 73, 1) 100%);
color: #fff;
height: 100%;
.bar.bar-header {
background: rgb(30, 49, 134);
color: #fff;
button {
color: #fff;
}
.secondary-buttons {
button {
color: rgba(255, 255, 255, .5);
}
}
}
.bar.bar-stable{
border-color: transparent;
border:none;
}
.button-transparent{
background: none !important;
}
.button-translucent{
background: rgba(215, 215, 215, 0.1)
}
.button-primary{
background: rgb(100, 124, 232) !important;
color:#fff;
}
.light-blue{
color:rgb(100, 124, 232);
}
.text-white{
color: #ffffff;
}
ion-content {
background: url(../img/onboarding-welcome-bg.png);
background-position: top center;
background-size: contain;
background-repeat: repeat-x;
height: 100%;
.scroll{
height: 100%;
}
color: #fff;
p {
text-align: center;
margin: 40px 20px;
font-size: 1.2rem;
color: rgba(255, 255, 255, .5);
}
}
}

View file

@ -0,0 +1,15 @@
#export {
.list {
background-color: #fff;
}
.top-tabs.row {
padding: 0;
}
.top-tabs .col {
font-size: 14px;
cursor: pointer;
padding: 10px 5px;
border-bottom-width: 2px;
border-bottom-color: #172565;
}
}

12
src/sass/views/import.scss vendored Normal file
View file

@ -0,0 +1,12 @@
#import {
.top-tabs.row {
padding: 0;
}
.top-tabs .col {
font-size: 14px;
cursor: pointer;
padding: 10px 5px;
border-bottom-width: 2px;
border-bottom-color: #172565;
}
}

View file

@ -107,12 +107,8 @@
i { i {
color: grey; color: grey;
position: inherit; position: inherit;
left: 25px; padding: 0 10px;
vertical-align: super; float: right;
padding-right: 10px;
border-right: 1px solid;
border-color: grey;
font-size: 20px;
} }
contact { contact {
margin-left: 15px; margin-left: 15px;

View file

@ -0,0 +1,14 @@
#glidera {
.glidera-lead {
margin: 1rem;
color: $mid-gray;
font-size: 18px;
text-align: center;
}
.disclosure {
color: $mid-gray;
font-size: 12px;
text-align: left;
margin: 1rem;
}
}

View file

@ -36,7 +36,7 @@
} }
.incomplete { .incomplete {
padding: 50px; padding: 50px;
height: 350px; height: 352px;
.title { .title {
padding: 20px; padding: 20px;
font-size: 25px; font-size: 25px;

View file

@ -17,6 +17,10 @@
color: $mid-gray; color: $mid-gray;
margin: 1rem 0; margin: 1rem 0;
} }
&-description-disabled {
color: cadetblue;
text-decoration: none;
}
.setting-title, .setting-value { .setting-title, .setting-value {
display: block; display: block;
overflow: hidden; overflow: hidden;

View file

@ -12,11 +12,14 @@
@import "walletDetails"; @import "walletDetails";
@import "advancedSettings"; @import "advancedSettings";
@import "bitpayCard"; @import "bitpayCard";
@import "bitpayCardIntro";
@import "address-book"; @import "address-book";
@import "wallet-backup-phrase"; @import "wallet-backup-phrase";
@import "zero-state"; @import "zero-state";
@import "onboarding/onboarding"; @import "onboarding/onboarding";
@import "includes/actionSheet"; @import "includes/actionSheet";
@import "export";
@import "import";
@import "includes/walletActivity"; @import "includes/walletActivity";
@import "includes/wallets"; @import "includes/wallets";
@import "includes/modals/modals"; @import "includes/modals/modals";
@ -27,4 +30,5 @@
@import "includes/txp-details"; @import "includes/txp-details";
@import "includes/tx-status"; @import "includes/tx-status";
@import "includes/walletSelector"; @import "includes/walletSelector";
@import "integrations/coinbase.scss"; @import "integrations/coinbase";
@import "integrations/glidera";

View file

@ -1,49 +0,0 @@
describe('Backup Controller', function() {
var walletService;
describe('Incomplete wallet', function() {
beforeEach(function(done) {
mocks.init(FIXTURES, 'backupController', {
loadProfile: PROFILE.incomplete2of2,
}, done);
});
afterEach(function(done) {
mocks.clear({}, done);
});
it('should be defined', function() {
should.exist(ctrl);
});
it('should set the mnemonic incomplete wallets', function(done) {
scope.initFlow();
should.exist(scope.mnemonicWords);
scope.mnemonicWords.should.deep.equal('dizzy cycle skirt decrease exotic fork sure mixture hair vapor copper hero'.split(' '));
done();
});
});
describe('Complete 1-1 wallet', function() {
beforeEach(function(done) {
mocks.init(FIXTURES, 'backupController', {
loadProfile: PROFILE.testnet1of1,
}, done);
});
afterEach(function(done) {
mocks.clear({}, done);
});
it('should not set the mnemonic for complete wallets', function() {
scope.initFlow();
scope.mnemonicWords.should.deep.equal('cheese where alarm job conduct donkey license pave congress pepper fence current'.split(' '));
});
it('should set main wallet info', function(done) {
scope.walletName.should.equal('kk');
done();
});
});
});

View file

@ -1,18 +0,0 @@
describe('copayers', function() {
var walletService;
var fixtures = {};
beforeEach(function(done){
mocks.init(fixtures, 'copayersController', {}, done);
})
afterEach(function(done){
mocks.clear({}, done);
})
it('should be defined', function() {
should.exist(ctrl);
});
});

View file

@ -1,185 +0,0 @@
describe('createController', function() {
var fixtures = {
// Store prefs
'1eda3e702196b8d5d82fae129249bc79f0d5be2f5309a4e39855e7eb4ad31428': {},
'31f5deeef4cf7fd8fc67297179232e8e4590532960454ad958009132fef3daae': {},
// createWallet 1-1
//
'b665ad8991c67f8f7e8ffb7e86c3b930fd3ff56c68eb6fd441bf374559cfe59c': {
"walletId": "63d910e8-3e1b-4aac-97e9-aa0299a74c2c"
},
'd5cc6adebc752c154998f1c96af2b24e21e52dbd7c07008c333af03b905ffb85': {
"copayerId": "a9dcee10fe9c611300e6c7926ece20780f89b9a98baaa342928038b5503ed929",
"wallet": {
"version": "1.0.0",
"createdOn": 1465385318,
"id": "63d910e8-3e1b-4aac-97e9-aa0299a74c2c",
"name": "{\"iv\":\"BZQVWAP6d1e4G8Fq1rQKbA==\",\"v\":1,\"iter\":1,\"ks\":128,\"ts\":64,\"mode\":\"ccm\",\"adata\":\"\",\"cipher\":\"aes\",\"ct\":\"70OA+k4+xTPxim+QSdDtA5/Cf055\"}",
"m": 1,
"n": 1,
"singleAddress": false,
"status": "complete",
"publicKeyRing": [{
"xPubKey": "xpub6DRjAgkh3vGTWDcEmDp4TPwy48Nu8yrp6swCEdCCLL615CgnZon7r3vXYr8LYibMLJh5DriGSito1FRBwVoBkjD1ZWG4dmgiC935wLj3nQC",
"requestPubKey": "02befcc7499abcecf9608bb05e665f374434a89ca0c4e9baeab7dd28c027143458"
}],
"copayers": [{
"version": 2,
"createdOn": 1465385318,
"xPubKey": "xpub6DRjAgkh3vGTWDcEmDp4TPwy48Nu8yrp6swCEdCCLL615CgnZon7r3vXYr8LYibMLJh5DriGSito1FRBwVoBkjD1ZWG4dmgiC935wLj3nQC",
"id": "a9dcee10fe9c611300e6c7926ece20780f89b9a98baaa342928038b5503ed929",
"name": "{\"iv\":\"BZQVWAP6d1e4G8Fq1rQKbA==\",\"v\":1,\"iter\":1,\"ks\":128,\"ts\":64,\"mode\":\"ccm\",\"adata\":\"\",\"cipher\":\"aes\",\"ct\":\"wwZd+2LQgYR6cA==\"}",
"requestPubKey": "02befcc7499abcecf9608bb05e665f374434a89ca0c4e9baeab7dd28c027143458",
"signature": "3044022042e069126a42f1b9b498c315a825ef4fc9f4214156442651e4fef5c7678245e702205936045d7b22baa36ba36ef827cc3e5d542d57d9a1afb3a54080d12f0b95c67e",
"requestPubKeys": [{
"key": "02befcc7499abcecf9608bb05e665f374434a89ca0c4e9baeab7dd28c027143458",
"signature": "3044022042e069126a42f1b9b498c315a825ef4fc9f4214156442651e4fef5c7678245e702205936045d7b22baa36ba36ef827cc3e5d542d57d9a1afb3a54080d12f0b95c67e"
}],
"customData": "{\"iv\":\"BZQVWAP6d1e4G8Fq1rQKbA==\",\"v\":1,\"iter\":1,\"ks\":128,\"ts\":64,\"mode\":\"ccm\",\"adata\":\"\",\"cipher\":\"aes\",\"ct\":\"1Wjf2KvFkd5k0ypiiSNkSVXk7zdBOiTeCrwzPBI7fMQ/VqXUzrSB6gMGs9jISr+MvCaL1GJIXjaMnlQZNMR0lx/Pd1c6R/nKGBdHjKh0mlI=\"}"
}],
"pubKey": "026d95bb5cc2a30c19e22379ae78b4757aaa2dd0ccbd15a1db054fb50cb98ed361",
"network": "livenet",
"derivationStrategy": "BIP44",
"addressType": "P2PKH",
"addressManager": {
"version": 2,
"derivationStrategy": "BIP44",
"receiveAddressIndex": 0,
"changeAddressIndex": 0,
"copayerIndex": 2147483647
},
"scanStatus": null
}
},
//createWallet 2-2
'5a1d11ebc2a011f018b049de6b5c6b990cdc8e280644103f95a995321dbf0248': {
"walletId": "2f50f598-7550-4e54-8032-15aa892309fb"
},
// join
'58f2f3a6f11cd7dee9a75e026e3ba570c09b952bfea05f596fdb48e6ea323f21': {
"copayerId": "3d4eb9b439eee1b2b73cf792eda52e420f4665109c7234a50cf3cdbf296ea8fb",
"wallet": {
"version": "1.0.0",
"createdOn": 1465347188,
"id": "2f50f598-7550-4e54-8032-15aa892309fb",
"name": "{\"iv\":\"BZQVWAP6d1e4G8Fq1rQKbA==\",\"v\":1,\"iter\":1,\"ks\":128,\"ts\":64,\"mode\":\"ccm\",\"adata\":\"\",\"cipher\":\"aes\",\"ct\":\"70OA+k4+xTPxim+QSdDtA5/Cf055\"}",
"m": 2,
"n": 2,
"singleAddress": false,
"status": "pending",
"publicKeyRing": [],
"copayers": [{
"version": 2,
"createdOn": 1465347188,
"xPubKey": "xpub6CkPnrzSUp9qzBVM3hpo4oS2JKC6GJq6brE1yW59QrnhDpvkFLakpxUGRGXH62fiXb5S2VbnD4h2DLoCMfSkwfonbNgNYTJw9Ko5SqWEqCR",
"id": "3d4eb9b439eee1b2b73cf792eda52e420f4665109c7234a50cf3cdbf296ea8fb",
"name": "{\"iv\":\"BZQVWAP6d1e4G8Fq1rQKbA==\",\"v\":1,\"iter\":1,\"ks\":128,\"ts\":64,\"mode\":\"ccm\",\"adata\":\"\",\"cipher\":\"aes\",\"ct\":\"wwZd+2LQgYR6cA==\"}",
"requestPubKey": "022941a5ecb8c7224f812ad6b03bd1c9bb77861080b21703eabe18ef9a72b48e72",
"signature": "30440220521623cf346f667658c00f1dea113407f23cecf02932c7dcb4b8bf35f1836b7a02202c77b8e4260942f4e13a58faae1f92e1130bae1157492056347e66741150eb2c",
"requestPubKeys": [{
"key": "022941a5ecb8c7224f812ad6b03bd1c9bb77861080b21703eabe18ef9a72b48e72",
"signature": "30440220521623cf346f667658c00f1dea113407f23cecf02932c7dcb4b8bf35f1836b7a02202c77b8e4260942f4e13a58faae1f92e1130bae1157492056347e66741150eb2c"
}],
"customData": "{\"iv\":\"BZQVWAP6d1e4G8Fq1rQKbA==\",\"v\":1,\"iter\":1,\"ks\":128,\"ts\":64,\"mode\":\"ccm\",\"adata\":\"\",\"cipher\":\"aes\",\"ct\":\"YJqN/LtkCY0cOB235RtbGEAY7wKGT0cUUpAvUeLkAUKz3/1axsYZtnG+PU0jHtwQvgmKNLkNcXNR60K+tyRpU0TG1z8pyx4gKwwD3Dt7KzA=\"}"
}],
"pubKey": "026d95bb5cc2a30c19e22379ae78b4757aaa2dd0ccbd15a1db054fb50cb98ed361",
"network": "livenet",
"derivationStrategy": "BIP44",
"addressType": "P2SH",
"addressManager": {
"version": 2,
"derivationStrategy": "BIP44",
"receiveAddressIndex": 0,
"changeAddressIndex": 0,
"copayerIndex": 2147483647
},
"scanStatus": null
}
},
}; // TODO: Read from file
beforeEach(function(done) {
mocks.init(fixtures, 'createController', {}, done);
})
afterEach(function(done) {
mocks.clear({}, done);
});
it('should be defined', function() {
should.exist(ctrl);
});
it('should create a 1-1 wallet from mnemonic', function(done) {
var fakeForm = {};
// FROM DATA
scope.seedSource = {
id: 'set'
};
scope.requiredCopayers = 1;
scope.totalCopayers = 1
scope.walletName = 'A test wallet';
scope.isTestnet = false;
scope.bwsurl = null;
scope.isSingleAddress = false;
scope.privateKey = 'legal winner thank year wave sausage worth useful legal winner thank yellow';
scope._walletPrivKey = 'Kz4CFSTgLzoYfMkt97BTBotUbZYXjMts6Ej9HbVfCf5oLmun1BXy';
ctrl.setSeedSource();
ctrl.create(fakeForm);
should.not.exist(ctrl.error);
mocks.go.walletHome.calledOnce.should.equal(true);
//
// check resulting profile
storageService.getProfile(function(err, profile) {
should.not.exist(err);
var c = profile.credentials[0];
c.network.should.equal('livenet');
// from test vectors from https://dcpos.github.io/bip39/
c.xPrivKey.should.equal('xprv9s21ZrQH143K2x4gnzRB1eZDq92Uuvy9CXbvgQGdvykXZ9mkkot6LBjzDpgaAfvzkuxJe9JKJXQ38VoPutxvACA5MsyoBs5UyQ4HZKGshGs');
done();
});
});
it('should create an incomplete 2-2 wallet from mnemonic', function(done) {
var fakeForm = {};
// FROM DATA
scope.seedSource = {
id: 'set'
};
scope.requiredCopayers = 2;
scope.totalCopayers = 2;
scope.walletName = 'A test wallet';
scope.isTestnet = false;
scope.bwsurl = null;
scope.privateKey = 'dizzy cycle skirt decrease exotic fork sure mixture hair vapor copper hero';
scope._walletPrivKey = 'Kz4CFSTgLzoYfMkt97BTBotUbZYXjMts6Ej9HbVfCf5oLmun1BXy';
ctrl.setSeedSource();
ctrl.create(fakeForm);
should.not.exist(ctrl.error);
mocks.go.walletHome.calledOnce.should.equal(true, 'Go Wallet Home Called');
// check resulting profile
storageService.getProfile(function(err, profile) {
should.not.exist(err);
var c = profile.credentials[0];
c.network.should.equal('livenet');
// from test vectors from https://dcpos.github.io/bip39/
c.xPrivKey.should.equal('xprv9s21ZrQH143K27bhzfejhNcitEAJgLKCfdLxwhr1FLu43FLqLwscAxXgmkucpF4k8eGmepSctkiQDbcR98Qd1bzSeDuR9jeyQAQEanPT2A4');
// m/44'/0'/0'
c.xPubKey.should.equal('xpub6CkPnrzSUp9qzBVM3hpo4oS2JKC6GJq6brE1yW59QrnhDpvkFLakpxUGRGXH62fiXb5S2VbnD4h2DLoCMfSkwfonbNgNYTJw9Ko5SqWEqCR');
done();
});
});
});

View file

@ -1,82 +0,0 @@
describe('disclaimerController', function() {
var walletService;
var storeProfile;
var fixtures = {
'e4d8ae25e03e5fef2e553615b088cfce222083828c13fdb37b8b6cf87bf76236': {
"walletId": "215f125d-57e7-414a-9723-448256113440",
},
'3f3b354d45c3eae3e4fe8830fcb728e5e570515af86e1a35deff0048a7a5e6b5': {
"copayerId": "1a91ead1b6d13da882a25377a20e460df557e77008ea4f60eecbf984f786cf03",
"wallet": {
"version": "1.0.0",
"createdOn": 1465347281,
"id": "215f125d-57e7-414a-9723-448256113440",
"name": "{\"iv\":\"BZQVWAP6d1e4G8Fq1rQKbA==\",\"v\":1,\"iter\":1,\"ks\":128,\"ts\":64,\"mode\":\"ccm\",\"adata\":\"\",\"cipher\":\"aes\",\"ct\":\"/gaG7FIkhCiwsWKZUR0sL/cxH+zHMK0=\"}",
"m": 1,
"n": 1,
"singleAddress": false,
"status": "complete",
"publicKeyRing": [{
"xPubKey": "xpub6Cb7MYAX7mJR28MfFueCsoDVVHhoWkQxRC4viAeHanYwRNgDo5xMF42xmAeExzfyPXX3GaALNA8hWFMekVYvDF2BALommUhMgZ52szh88fd",
"requestPubKey": "029a167eebe3ccd9987d41743477f8b75e1f3c30463187e1b106e0cc1155efa4dd"
}],
"copayers": [{
"version": 2,
"createdOn": 1465347281,
"xPubKey": "xpub6Cb7MYAX7mJR28MfFueCsoDVVHhoWkQxRC4viAeHanYwRNgDo5xMF42xmAeExzfyPXX3GaALNA8hWFMekVYvDF2BALommUhMgZ52szh88fd",
"id": "1a91ead1b6d13da882a25377a20e460df557e77008ea4f60eecbf984f786cf03",
"name": "{\"iv\":\"BZQVWAP6d1e4G8Fq1rQKbA==\",\"v\":1,\"iter\":1,\"ks\":128,\"ts\":64,\"mode\":\"ccm\",\"adata\":\"\",\"cipher\":\"aes\",\"ct\":\"wwZd+2LQgYR6cA==\"}",
"requestPubKey": "029a167eebe3ccd9987d41743477f8b75e1f3c30463187e1b106e0cc1155efa4dd",
"signature": "3045022100ac3f31ef145eabde6a125958aa9d63c2bd4aa27717d7f6905c3e3ff1e733ee8e02206a43200b775ee5c8f7a85c4d3309d155240d5de46a7d9c5e60045bf49779f40b",
"requestPubKeys": [{
"key": "029a167eebe3ccd9987d41743477f8b75e1f3c30463187e1b106e0cc1155efa4dd",
"signature": "3045022100ac3f31ef145eabde6a125958aa9d63c2bd4aa27717d7f6905c3e3ff1e733ee8e02206a43200b775ee5c8f7a85c4d3309d155240d5de46a7d9c5e60045bf49779f40b"
}],
"customData": "{\"iv\":\"BZQVWAP6d1e4G8Fq1rQKbA==\",\"v\":1,\"iter\":1,\"ks\":128,\"ts\":64,\"mode\":\"ccm\",\"adata\":\"\",\"cipher\":\"aes\",\"ct\":\"9l63hoVnA71LshCC5xbOTHA+ivBzux7u8SAci56p4aaVIF4qzXQhQKFX+sAFGfBjULm/E1st6awdXnxbAgjbF7D0zsbBFLFOSCw+ko5Xc6o=\"}"
}],
"pubKey": "026d95bb5cc2a30c19e22379ae78b4757aaa2dd0ccbd15a1db054fb50cb98ed361",
"network": "livenet",
"derivationStrategy": "BIP44",
"addressType": "P2PKH",
"addressManager": {
"version": 2,
"derivationStrategy": "BIP44",
"receiveAddressIndex": 0,
"changeAddressIndex": 0,
"copayerIndex": 2147483647
},
"scanStatus": null
}
},
}; // TODO: Read from file
beforeEach(function(done) {
mocks.init(fixtures, 'disclaimerController', {
initController: true,
noProfile: true,
}, done);
});
afterEach(function(done) {
mocks.clear({}, done);
});
it('should be defined', function() {
should.exist(ctrl);
});
it('should create the initial profile', function(done) {
localStorage.clear();
ctrl.init({
walletPrivKey: 'Kz4CFSTgLzoYfMkt97BTBotUbZYXjMts6Ej9HbVfCf5oLmun1BXy',
mnemonic: 'tunnel fork scare industry noble snow tank bullet over gesture nuclear next',
});
setTimeout(function() {
mocks.ongoingProcess.set.getCall(1).args[0].should.equal('creatingWallet');
mocks.ongoingProcess.set.getCall(1).args[1].should.equal(false);
done();
}, 100);
});
});

View file

@ -1,112 +0,0 @@
describe('importController', function() {
var walletService;
var storeProfile;
var fixtures = {
'31f5deeef4cf7fd8fc67297179232e8e4590532960454ad958009132fef3daae': {},
'4599136eff6deb4c9c78043fa84113617a16d75c45920d662305f6227ae8f0a0': {
"wallet": {
"version": "1.0.0",
"createdOn": 1463488747,
"id": "267bfa75-5575-4af7-8aa3-f5186bc99262",
"name": "{\"iv\":\"BZQVWAP6d1e4G8Fq1rQKbA==\",\"v\":1,\"iter\":1,\"ks\":128,\"ts\":64,\"mode\":\"ccm\",\"adata\":\"\",\"cipher\":\"aes\",\"ct\":\"70OA+k4+xTPxim+QSdDtA5/Cf055\"}",
"m": 1,
"n": 1,
"status": "complete",
"publicKeyRing": [{
"xPubKey": "xpub6DRjAgkh3vGTWDcEmDp4TPwy48Nu8yrp6swCEdCCLL615CgnZon7r3vXYr8LYibMLJh5DriGSito1FRBwVoBkjD1ZWG4dmgiC935wLj3nQC",
"requestPubKey": "02befcc7499abcecf9608bb05e665f374434a89ca0c4e9baeab7dd28c027143458"
}],
"copayers": [{
"version": 2,
"createdOn": 1463490295,
"id": "a9dcee10fe9c611300e6c7926ece20780f89b9a98baaa342928038b5503ed929",
"name": "{\"iv\":\"BZQVWAP6d1e4G8Fq1rQKbA==\",\"v\":1,\"iter\":1,\"ks\":128,\"ts\":64,\"mode\":\"ccm\",\"adata\":\"\",\"cipher\":\"aes\",\"ct\":\"wwZd+2LQgYR6cA==\"}",
"xPubKey": "xpub6DRjAgkh3vGTWDcEmDp4TPwy48Nu8yrp6swCEdCCLL615CgnZon7r3vXYr8LYibMLJh5DriGSito1FRBwVoBkjD1ZWG4dmgiC935wLj3nQC",
"requestPubKey": "02befcc7499abcecf9608bb05e665f374434a89ca0c4e9baeab7dd28c027143458",
"signature": "3044022042e069126a42f1b9b498c315a825ef4fc9f4214156442651e4fef5c7678245e702205936045d7b22baa36ba36ef827cc3e5d542d57d9a1afb3a54080d12f0b95c67e",
"requestPubKeys": [{
"key": "02befcc7499abcecf9608bb05e665f374434a89ca0c4e9baeab7dd28c027143458",
"signature": "3044022042e069126a42f1b9b498c315a825ef4fc9f4214156442651e4fef5c7678245e702205936045d7b22baa36ba36ef827cc3e5d542d57d9a1afb3a54080d12f0b95c67e"
}],
"customData": "{\"iv\":\"BZQVWAP6d1e4G8Fq1rQKbA==\",\"v\":1,\"iter\":1,\"ks\":128,\"ts\":64,\"mode\":\"ccm\",\"adata\":\"\",\"cipher\":\"aes\",\"ct\":\"1Wjf2KvFkd5k0ypiiSNkSVXk7zdBOiTeCrwzPBI7fMQ/VqXUzrSB6gMGs9jISr+MvCaL1GJIXjaMnlQZNMR0lx/Pd1c6R/nKGBdHjKh0mlI=\"}"
}],
"pubKey": "026d95bb5cc2a30c19e22379ae78b4757aaa2dd0ccbd15a1db054fb50cb98ed361",
"network": "livenet",
"derivationStrategy": "BIP44",
"addressType": "P2PKH",
"addressManager": {
"version": 2,
"derivationStrategy": "BIP44",
"receiveAddressIndex": 0,
"changeAddressIndex": 0,
"copayerIndex": 2147483647
},
"scanStatus": null
},
"preferences": {},
"pendingTxps": [],
"balance": {
"totalAmount": 0,
"lockedAmount": 0,
"totalConfirmedAmount": 0,
"lockedConfirmedAmount": 0,
"availableAmount": 0,
"availableConfirmedAmount": 0,
"byAddress": [],
"totalBytesToSendMax": 0,
"totalBytesToSendConfirmedMax": 0
}
}
}; // TODO: Read from file
beforeEach(function(done){
mocks.init(fixtures, 'importController', {}, done);
})
afterEach(function(done){
mocks.clear({}, done);
});
it('should be defined', function() {
should.exist(ctrl);
});
it('should import a 1-1 wallet from mnemonic', function(done) {
var fakeForm = {
words: {
$modelValue: 'legal winner thank year wave sausage worth useful legal winner thank yellow'
},
passphrase: {}
};
// FROM DATA
scope.seedSource = {
id: 'set'
};
scope.bwsurl = null;
scope._walletPrivKey = 'Kz4CFSTgLzoYfMkt97BTBotUbZYXjMts6Ej9HbVfCf5oLmun1BXy';
scope.setSeedSource();
scope.importMnemonic(fakeForm);
should.not.exist(scope.error);
mocks.notification.success.calledOnce.should.equal(true);
// mocks.go.walletHome.calledOnce.should.equal(true);
// check resulting profile
storageService.getProfile(function(err, profile) {
should.not.exist(err);
var c = profile.credentials[0];
c.network.should.equal('livenet');
// from test vectors from https://dcpos.github.io/bip39/
c.xPrivKey.should.equal('xprv9s21ZrQH143K2x4gnzRB1eZDq92Uuvy9CXbvgQGdvykXZ9mkkot6LBjzDpgaAfvzkuxJe9JKJXQ38VoPutxvACA5MsyoBs5UyQ4HZKGshGs');
done();
});
});
});

View file

@ -1,71 +0,0 @@
describe('index', function() {
var walletService;
describe('Incomplete wallet', function() {
beforeEach(function(done) {
mocks.init(FIXTURES, 'indexController', {
loadProfile: PROFILE.incomplete2of2,
initController: true,
}, done);
});
afterEach(function(done) {
mocks.clear({}, done);
});
it('should be defined', function() {
should.exist(ctrl);
});
it('should set the invitation code for incomplete wallets', function(done) {
should.exist(ctrl);
ctrl.walletSecret.should.equal('GJ1A8mopdW7wPNWGVksqwQKz4CFSTgLzoYfMkt97BTBotUbZYXjMts6Ej9HbVfCf5oLmun1BXyL');
// should redirect to copayers
mocks.go.path.getCall(0).args[0].should.equal('copayers');
done();
});
});
describe('Complete 1-1 wallet', function() {
beforeEach(function(done) {
mocks.init(FIXTURES, 'indexController', {
loadProfile: PROFILE.testnet1of1,
initController: true,
}, done);
});
afterEach(function(done) {
mocks.clear({}, done);
});
it('should not set the invitation code for complete wallets', function() {
// should redirect to copayers
mocks.go.path.callCount.should.equal(0);
should.not.exist(ctrl.walletSecret);
});
it('should set main wallet info', function(done) {
ctrl.walletName.should.equal('kk');
ctrl.totalBalanceSat.should.equal(1847686);
done();
});
it('should set information for receive tab', function(done) {
ctrl.tab.should.equal('walletHome');
ctrl.setTab('receive');
ctrl.tab.should.equal('receive');
done();
});
it.skip('should updates remote preferences', function(done) {
ctrl.updateRemotePreferences({}, function() {
done();
});
});
});
});

View file

@ -1,150 +0,0 @@
describe('joinController', function() {
var walletService;
var fixtures = {
// join
'668623e51aaae25c637fb9c57bb30a169a0ff67fa1e67e6e61643c7e5e580a66': {
"copayerId": "962fb5dd31d9f715efdbb33d41533d272bb6c2ecd28bbb8181358f86b08253dd",
"wallet": {
"version": "1.0.0",
"createdOn": 1466006460,
"id": "10387ed3-51cf-43b4-91fe-ad85ca2ae368",
"name": "{\"iv\":\"4Agx234j4p+TQS0QXj7bow==\",\"v\":1,\"iter\":1,\"ks\":128,\"ts\":64,\"mode\":\"ccm\",\"adata\":\"\",\"cipher\":\"aes\",\"ct\":\"IEohefHXl/tr4rA=\"}",
"m": 2,
"n": 2,
"singleAddress": false,
"status": "complete",
"publicKeyRing": [{
"xPubKey": "xpub6C6dynsH56i7VhzHzo2ZcJguHsjYuUuoPcAdku8h6c7ZaJSYb4WQjKcGdggbpWEuaQspY3LHmFUoCQhk1ErmdegXnsJeSxoKqiPD1CUxVvT",
"requestPubKey": "0200fbedb7d04af9edbd1602103c1ff68454fd009fd8b1acd957441e776c69ff59"
}, {
"xPubKey": "xpub6BsR71KDdSPMePtuipRiWKMC2Q9XEXfk6WM1trbJzPEhcwVBKyN9UhWtpnGv2pu4mtZyKFRgwL98hDH6TBdeEFNVp8Jf81kPBKPeWpn4sWr",
"requestPubKey": "02ad777ba00bf085a2d167c0600df290037d40e5e0d33b5f8e345b0b80a8861bd4"
}],
"copayers": [{
"version": 2,
"createdOn": 1466006460,
"id": "4f72d7bc290a0343a5096cf28999d5d329a9be42651b061fb9489130d0cf9af9",
"name": "{\"iv\":\"RZr7/0eA7F70T/wBCJo7kw==\",\"v\":1,\"iter\":1,\"ks\":128,\"ts\":64,\"mode\":\"ccm\",\"adata\":\"\",\"cipher\":\"aes\",\"ct\":\"nL4c40ADLWELtoE=\"}",
"xPubKey": "xpub6C6dynsH56i7VhzHzo2ZcJguHsjYuUuoPcAdku8h6c7ZaJSYb4WQjKcGdggbpWEuaQspY3LHmFUoCQhk1ErmdegXnsJeSxoKqiPD1CUxVvT",
"requestPubKey": "0200fbedb7d04af9edbd1602103c1ff68454fd009fd8b1acd957441e776c69ff59",
"signature": "304402200af094bbb7c432c9a1323534db125431c87bdec9678f40e89a42f209115a222202207a87a27b5f14bf931e1a15d71aa8407118398e5540a8fcbaf7caffef534b6a49",
"requestPubKeys": [{
"key": "0200fbedb7d04af9edbd1602103c1ff68454fd009fd8b1acd957441e776c69ff59",
"signature": "304402200af094bbb7c432c9a1323534db125431c87bdec9678f40e89a42f209115a222202207a87a27b5f14bf931e1a15d71aa8407118398e5540a8fcbaf7caffef534b6a49"
}],
"customData": "{\"iv\":\"kSIFrEhNScxUNG5BMnV34A==\",\"v\":1,\"iter\":1,\"ks\":128,\"ts\":64,\"mode\":\"ccm\",\"adata\":\"\",\"cipher\":\"aes\",\"ct\":\"strUeMXiPhTPmsatrStRfaF9+ZD1LU+m+I6Xeu4m0s43DIqc/DYJwm+37fatohNKQ6J7FQKTCJUgMiidAe30K6Dw7J7GA6mFhedMsGLJNbOmBEhRN0AAbwXW6B0=\"}"
}, {
"version": 2,
"createdOn": 1466006511,
"xPubKey": "xpub6BsR71KDdSPMePtuipRiWKMC2Q9XEXfk6WM1trbJzPEhcwVBKyN9UhWtpnGv2pu4mtZyKFRgwL98hDH6TBdeEFNVp8Jf81kPBKPeWpn4sWr",
"id": "962fb5dd31d9f715efdbb33d41533d272bb6c2ecd28bbb8181358f86b08253dd",
"name": "{\"iv\":\"BZQVWAP6d1e4G8Fq1rQKbA==\",\"v\":1,\"iter\":1,\"ks\":128,\"ts\":64,\"mode\":\"ccm\",\"adata\":\"\",\"cipher\":\"aes\",\"ct\":\"UKYkKqeia8gWrLqaJ+TuzA/LVlrG\"}",
"requestPubKey": "02ad777ba00bf085a2d167c0600df290037d40e5e0d33b5f8e345b0b80a8861bd4",
"signature": "3045022100f7c7bbc49ce679e67420db8614bf59dfbd798e8ad95a0427305ae5008e0aa41b02203997647b80cc6e5a365048dc5b7b1822809b3c9209a053aaeef7e9f3920d7cef",
"requestPubKeys": [{
"key": "02ad777ba00bf085a2d167c0600df290037d40e5e0d33b5f8e345b0b80a8861bd4",
"signature": "3045022100f7c7bbc49ce679e67420db8614bf59dfbd798e8ad95a0427305ae5008e0aa41b02203997647b80cc6e5a365048dc5b7b1822809b3c9209a053aaeef7e9f3920d7cef"
}],
"customData": "{\"iv\":\"BZQVWAP6d1e4G8Fq1rQKbA==\",\"v\":1,\"iter\":1,\"ks\":128,\"ts\":64,\"mode\":\"ccm\",\"adata\":\"\",\"cipher\":\"aes\",\"ct\":\"HTlgRDT46ysMT3+XzhxeXgrOfJ1Fq+kiTWG/q7RqISdWWE+cmP5LcI6+PSysEpo66AjOlI9ofyMVxKtptabWYSNgydrhnqZ5EKY0TnFRq8Ov7a8+btXf9n9BDsM=\"}"
}],
"pubKey": "03bdebf86549b272addd61076e026d2f6a225db514f08b8fad08536a8c4a6792c1",
"network": "livenet",
"derivationStrategy": "BIP44",
"addressType": "P2SH",
"addressManager": {
"version": 2,
"derivationStrategy": "BIP44",
"receiveAddressIndex": 0,
"changeAddressIndex": 0,
"copayerIndex": 2147483647
},
"scanStatus": null
}
},
'197031879d401f75c308e3d5014ac2e9560ec805e1fdd58c778e0ae0bfe7ec0a': {},
}
beforeEach(function(done) {
mocks.init(fixtures, 'joinController', {}, done);
})
afterEach(function(done) {
mocks.clear({}, done);
});
it('should be defined', function() {
should.exist(ctrl);
});
// // Get html template from cache
// beforeEach(inject(function($templateCache) {
// viewHtml = $templateCache.get("some/valid/templateUrl");
// }));
// // beforeEach(inject(function(_$compile_, _$rootScope_){
// $compile = _$compile_;
// $rootScope = _$rootScope_;
//
// $scope = $rootScope.$new();
// $scope.user = {};
// $scope.logout = sinon.stub();
// dropdownElement = angular.element(viewHtml);
// }));
it('should join a wallet once the form is submitted', function(done) {
// View' s joinForm is not available
//join.onQrCodeScanned('aQRCode');
//
scope.seedSource = {
id: 'set'
};
ctrl.setSeedSource();
// FROM DATA
scope._walletPrivKey = 'Kz4CFSTgLzoYfMkt97BTBotUbZYXjMts6Ej9HbVfCf5oLmun1BXy';
var fakeForm = {
secret: {
$modelValue: '31B6DG8f12vGhG7hWhQy2PKwngiNnQ4ijPcePSwanQ2gD6N4mWs3eVPtdwZqRQbHnLQyxhoJksL'
},
myName: {
$modelValue: 'myCopayerName'
},
bwsurl: 'null',
createPassphrase: {
$modelValue: null
},
privateKey: {
$modelValue: 'useful poet rely letter cause fat student tumble animal toddler proof husband',
},
passphrase: {
$modelValue: null,
},
};
ctrl.join(fakeForm);
should.not.exist(ctrl.error);
mocks.go.walletHome.calledOnce.should.equal(true, 'Go Wallet Home Called');
// check resulting profile
storageService.getProfile(function(err, profile) {
should.not.exist(err);
var c = profile.credentials[0];
c.network.should.equal('livenet');
// from test vectors from https://dcpos.github.io/bip39/
c.xPrivKey.should.equal('xprv9s21ZrQH143K3ettHXncETrbUjzrTB7yBfhzjnYjbFgExeNMecTGPvJgje2WQeSFS17Sd8ssz8FQuCbm4rK62ojAwPCX8GHtjHNHsmJsbUa');
// m/44'/0'/0'
c.xPubKey.should.equal('xpub6BsR71KDdSPMePtuipRiWKMC2Q9XEXfk6WM1trbJzPEhcwVBKyN9UhWtpnGv2pu4mtZyKFRgwL98hDH6TBdeEFNVp8Jf81kPBKPeWpn4sWr');
c.walletName.should.equal('2-2');
done();
});
});
});

View file

@ -1,37 +0,0 @@
describe('Preferences History Controller', function() {
var walletService;
var txHistory = '[{"txid":"bf31ecaa8e10ce57f9a889fc4c893b40ff57b016dd763957d942e21ed55fc62c","action":"received","amount":120000,"fees":4862,"time":1464969291,"confirmations":8,"outputs":[{"amount":120000,"address":"2N4HgtF9cJSzxhVkj5gbKxwJSKWBmnb9FNJ","message":null}],"note":{"body":"just a comment","editedBy":"31a8c3c0be9ffbb9f257c95f3fd2f73a59cf81e40199ba5918417270db8c4cdb","editedByName":"2-2","editedOn":1464969101},"message":null,"creatorName":"","hasUnconfirmedInputs":false,"amountStr":"1,200 bits","alternativeAmountStr":"0.68 USD","feeStr":"49 bits","safeConfirmed":"6+"}]';
describe('Complete 1-1 wallet', function() {
beforeEach(function(done) {
mocks.init(FIXTURES, 'preferencesHistory', {
loadProfile: PROFILE.testnet1of1,
loadStorage: {
'txsHistory-66d3afc9-7d76-4b25-850e-aa62fcc53a7d': txHistory,
},
}, done);
});
afterEach(function(done) {
mocks.clear({}, done);
});
it('should be defined', function() {
should.exist(ctrl);
});
it('should export csv', function(done) {
scope.csvHistory(function(err) {
should.not.exist(err);
should.exist(scope.csvReady);
scope.csvReady.should.equal(true);
should.exist(scope.csvContent);
JSON.stringify(scope.csvContent).should.equal('[{"Date":"2016-06-03T15:54:51.000Z","Destination":"","Description":"","Amount":"0.00120000","Currency":"BTC","Txid":"bf31ecaa8e10ce57f9a889fc4c893b40ff57b016dd763957d942e21ed55fc62c","Creator":"","Copayers":"","Comment":"just a comment"}]');
done();
});
});
});
});

View file

@ -1,19 +0,0 @@
describe('walletHome', function() {
var walletService;
var fixtures = {};
beforeEach(function(done){
mocks.init(fixtures, 'walletHomeController', {}, done);
})
afterEach(function(done){
mocks.clear({}, done);
});
it('should be defined', function() {
should.exist(ctrl);
});
});

File diff suppressed because it is too large Load diff

View file

@ -1,262 +0,0 @@
var mocks = {};
// UI-Router mock from
// https://gist.github.com/bmwant/4c8e5fee7a539dba69ace42b617d79c3
mocks.$state = function($q) {
this.expectedTransitions = [];
this.transitionTo = function(stateName) {
if (this.expectedTransitions.length > 0) {
var expectedState = this.expectedTransitions.shift();
if (expectedState !== stateName) {
throw Error("Expected transition to state: " + expectedState + " but transitioned to " + stateName);
}
} else {
throw Error("No more transitions were expected! Tried to transition to " + stateName);
}
console.log("Mock transition to: " + stateName);
this.current = stateName;
var deferred = $q.defer();
var promise = deferred.promise;
deferred.resolve();
return promise;
};
this.is = function(name) {
console.log('[helpers.js.24:name:]', name); //TODO
return this.current == name;
};
this.go = this.transitionTo;
this.expectTransitionTo = function(stateName) {
this.expectedTransitions.push(stateName);
};
this.ensureAllTransitionsHappened = function() {
if (this.expectedTransitions.length > 0) {
throw Error("Not all transitions happened!");
}
};
};
mocks.$timeout = function(cb) {
return cb();
};
mocks.modal = function() {};
mocks.ongoingProcess = {
set: sinon.stub(),
clear: sinon.stub(),
};
mocks.setProfile = function(profile) {};
/*
* opts
*/
var getElements = sinon.stub();
getElements.returns([]);
var getElement = sinon.stub();
getElement.returns({
getElementsByTagName: getElement,
});
mocks.$document = {
getElementById: getElement,
};
mocks.init = function(fixtures, controllerName, opts, done) {
console.log(' * Mock init()');
opts = opts || {};
should.exist(controllerName, 'Provide the name of the Controller to mocks.init()');
mocks.go = {};
mocks.go.walletHome = sinon.stub();
mocks.go.path = sinon.stub();
mocks.go.is = function(name) {
return mocks.go.current == name
};
mocks.notification = {
success: sinon.stub(),
};
angular.module('stateMock', []);
angular.module('stateMock').service("$state", mocks.$state.bind());
module('ionic');
module('ngLodash');
module('angularMoment');
module('gettext');
module('stateMock');
module('bwcModule', function($provide) {
console.log(' * bwcService decorator');
$provide.decorator('bwcService', function($delegate, lodash) {
var getClient = $delegate.getClient;
// Fix Encryption IVs
var utils = $delegate.getUtils();
utils.SJCL.iv = 'BZQVWAP6d1e4G8Fq1rQKbA==';
$delegate.getClient = function(walletData, opts) {
var bwc = new $delegate.Client();
if (walletData)
bwc.import(walletData, {
baseUrl: opts.bwsurl || 'https://bws.bitpay.com/bws/api',
verbose: opts.verbose,
transports: ['polling'],
});
function createHash(method, url, args) {
var headers = JSON.stringify(bwc._getHeaders(method, url, args));
// Fixes BWC version... TODO
headers = headers.replace(/bwc-\d+\.\d+\.\d+/, 'bwc-2.4.0')
var x = method + url + JSON.stringify(args) + headers;
var sjcl = $delegate.getSJCL();
return sjcl.codec.hex.fromBits(sjcl.hash.sha256.hash(x));
};
bwc._originalRequest = bwc._doRequest;
bwc._doGetRequest = function(url, cb) {
url += url.indexOf('?') > 0 ? '&' : '?';
url += 'r=' + 69321;
return this._doRequest('get', url, {}, cb);
};
// Use fixtures
bwc._doRequest = function(method, url, args, cb2) {
// find fixed response:
var hash = createHash(method, url, args);
if (lodash.isUndefined(fixtures[hash])) {
console.log('##### UNDEFINED FIXTURED ####:', hash); //TODO
console.log('##### method:', method); //TODO
console.log('##### url :', url); //TODO
console.log('##### args :', JSON.stringify(args)); //TODO
console.log('##### header:', JSON.stringify(bwc._getHeaders(method, url, args)));
var oldURL = bwc.baseURL;
bwc.baseURL = 'http://localhost:3232/bws/api';
console.log('##### running local: to http://localhost:3232/bws/api');
bwc._originalRequest(method, url, args, function(err, response) {
console.log("### RESPONSE: " + hash + "\n", JSON.stringify(response)); //TODO
bwc.baseURL = oldURL;
return cb2(null, response);
});
} else {
console.log('Using fixture: ' + hash.substr(0, 6) + ' for: ' + url);
return cb2(null, fixtures[hash]);
}
};
return bwc;
};
return $delegate;
});
});
module('copayApp.services', {
$modal: mocks.modal,
$timeout: mocks.$timeout,
$state: mocks.$state,
});
module('copayApp.controllers');
inject(function($rootScope, $controller, $injector, lodash, _configService_, _profileService_, _storageService_) {
scope = $rootScope.$new();
storageService = _storageService_;
// Set up the mock http service responses
$httpBackend = $injector.get('$httpBackend');
// backend definition common for all tests
$httpBackend.when('GET', 'https://bitpay.com/api/rates')
.respond({
code: "BTC",
name: "Bitcoin",
rate: 1
}, {
code: "USD",
name: "US Dollar",
rate: 452.92
});
$httpBackend.whenGET(/views.*/).respond(200, '');
_configService_.get(function() {
function startController() {
console.log(' * starting Controller:', controllerName);
ctrl = $controller(controllerName, {
$scope: scope,
$modal: mocks.modal,
ongoingProcess: mocks.ongoingProcess,
notification: mocks.notification,
configService: _configService_,
profileService: _profileService_,
go: mocks.go,
$document: mocks.$document,
});
};
if (opts.initController)
startController();
if (opts.loadStorage) {
lodash.each(opts.loadStorage, function(v, k) {
localStorage.setItem(k, v);
});
}
if (opts.loadProfile) {
localStorage.setItem('profile', JSON.stringify(opts.loadProfile));
_profileService_.loadAndBindProfile(function(err) {
should.not.exist(err, err);
if (!opts.initController)
startController();
done();
});
} else {
if (opts.noProfile){
return done();
}
_profileService_.create({
noWallet: true
}, function(err) {
should.not.exist(err, err);
if (opts.noDisclaimer){
return done();
}
_profileService_.setDisclaimerAccepted(function() {
if (!opts.initController)
startController();
done();
});
});
}
});
});
};
mocks.clear = function(opts, done) {
opts = opts || {};
if (!opts.keepStorage) {
// Adds walletService's module dependencies
console.log(' * deleting localstorage');
localStorage.clear();
}
done();
};

View file

@ -1,115 +0,0 @@
// Karma configuration
// Generated on Thu Mar 12 2015 18:13:33 GMT-0300 (ART)
module.exports = function(config) {
config.set({
// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: '..',
// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['mocha', 'chai', 'sinon'],
// list of files / patterns to load in the browser
files: [
'bower_components/qrcode-generator/js/qrcode.js',
'bower_components/qrcode-decoder-js/lib/qrcode-decoder.js',
'bower_components/moment/min/moment-with-locales.js',
'bower_components/ionic/release/js/ionic.bundle.js',
'bower_components/angular-moment/angular-moment.js',
'bower_components/ng-lodash/build/ng-lodash.js',
'bower_components/angular-qrcode/angular-qrcode.js',
'bower_components/angular-gettext/dist/angular-gettext.js',
'bower_components/ng-csv/build/ng-csv.js',
'bower_components/ionic-toast/dist/ionic-toast.bundle.min.js',
'bower_components/angular-clipboard/angular-clipboard.js',
'bower_components/angular-mocks/angular-mocks.js',
'angular-pbkdf2/angular-pbkdf2.js',
'angular-bitcore-wallet-client/angular-bitcore-wallet-client.js',
'src/js/**/*.js',
'test/helpers.js',
'test/**/*.js'
],
// list of files to exclude
exclude: [
'src/js/translations.js',
// 'src/js/version.js',
'test/karma.conf.js',
'test/old/*'
],
client: {
mocha: {
// reporter: 'html', // change Karma's debug.html to the mocha web reporter
ui: 'tdd'
}
},
// preprocess matching files before serving them to the browser
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: {
'src/js/**/*.js': ['coverage']
},
// test results reporter to use
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ['mocha', 'coverage'],
// optionally, configure the reporter
coverageReporter: {
dir: 'coverage/',
reporters: [{
type: 'html',
subdir: 'report-html'
}, {
type: 'lcov',
subdir: 'report-lcov'
}, {
type: 'text-summary'
}]
},
// web server port
port: 9876,
// enable / disable colors in the output (reporters and logs)
colors: true,
// level of logging
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,
// enable / disable watching file and executing tests whenever any file changes
autoWatch: true,
// start these browsers
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
browsers: ['PhantomJS'],
plugins: [
'karma-mocha-reporter',
'karma-coverage',
'karma-mocha',
'karma-chai',
'karma-sinon',
'karma-phantomjs-launcher',
'karma-chrome-launcher',
],
// Continuous Integration mode
// if true, Karma captures browsers, runs the tests and exits
singleRun: false
});
};

File diff suppressed because it is too large Load diff

View file

@ -1,250 +0,0 @@
'use strict';
//
// test/unit/directives/directivesSpec.js
//
describe("Unit: Testing Directives", function() {
var $scope, form;
beforeEach(module('copayApp.directives'));
beforeEach(inject(function($rootScope) {
var w = {};
w.isComplete = sinon.stub().returns(true);
w.privateKey = {};
w.settings = {
unitToSatoshi: 100,
unitDecimals: 2,
alternativeName: 'US Dollar',
alternativeIsoCode: 'USD',
};
w.addressBook = {
'juan': '1',
};
w.totalCopayers = 2;
w.getMyCopayerNickname = sinon.stub().returns('nickname');
w.getMyCopayerId = sinon.stub().returns('id');
w.privateKey.toObj = sinon.stub().returns({
wallet: 'mock'
});
w.getSecret = sinon.stub().returns('secret');
w.getName = sinon.stub().returns('fakeWallet');
w.exportEncrypted = sinon.stub().returns('1234567');
w.getTransactionHistory = sinon.stub().yields({});
w.getNetworkName = sinon.stub().returns('testnet');
w.createTx = sinon.stub().yields(null);
w.sendTx = sinon.stub().yields(null);
w.requiresMultipleSignatures = sinon.stub().returns(true);
w.getTxProposals = sinon.stub().returns([1,2,3]);
$rootScope.wallet = w;
}));
describe('Validate Address', function() {
beforeEach(inject(function($compile, $rootScope) {
$scope = $rootScope;
var element = angular.element(
'<form name="form">' +
'<input type="text" id="address" name="address" placeholder="Send to" ng-model="address" valid-address required>' +
'</form>'
);
$scope.model = {
address: null
};
$compile(element)($scope);
$scope.$digest();
form = $scope.form;
}));
it('should validate with network', inject(function($rootScope) {
$rootScope.wallet.getNetworkName = sinon.stub().returns('testnet');
form.address.$setViewValue('mkfTyEk7tfgV611Z4ESwDDSZwhsZdbMpVy');
expect(form.address.$invalid).to.equal(false);
}));
it('should not validate with other network', inject(function($rootScope) {
$rootScope.wallet.getNetworkName = sinon.stub().returns('livenet');
form.address.$setViewValue('mkfTyEk7tfgV611Z4ESwDDSZwhsZdbMpVy');
expect(form.address.$invalid).to.equal(true);
}));
it('should not validate random', function() {
form.address.$setViewValue('thisisaninvalidaddress');
expect(form.address.$invalid).to.equal(true);
});
});
describe('Validate Amount', function() {
describe('Unit: bits', function() {
beforeEach(inject(function($compile, $rootScope) {
$scope = $rootScope;
var element = angular.element(
'<form name="form">' +
'<input type="number" id="amount" name="amount" placeholder="Amount" ng-model="amount" min="0.00000001" max="10000000000" valid-amount required>' +
'</form>'
);
$scope.model = {
amount: null
};
$compile(element)($scope);
$scope.$digest();
form = $scope.form;
}));
it('should validate', function() {
form.amount.$setViewValue(100);
expect(form.amount.$invalid).to.equal(false);
form.amount.$setViewValue(800);
expect(form.amount.$invalid).to.equal(false);
form.amount.$setViewValue(900);
});
it('should not validate', function() {
form.amount.$setViewValue(0);
expect(form.amount.$invalid).to.equal(true);
form.amount.$setViewValue(999999999999);
expect(form.amount.$invalid).to.equal(true);
});
});
describe('Unit: BTC', function() {
beforeEach(inject(function($compile, $rootScope) {
$scope = $rootScope;
var w = $rootScope.wallet;
w.settings.unitToSatoshi = 100000000;
w.settings.unitName = 'BTC';
w.settings.unitDecimals = 8;
$rootScope.availableBalance = 0.04;
var element = angular.element(
'<form name="form">' +
'<input type="number" id="amount" name="amount" placeholder="Amount" ng-model="amount" min="0.00000001" max="10000000000" valid-amount required>' +
'</form>'
);
$scope.model = {
amount: null
};
$compile(element)($scope);
$scope.$digest();
form = $scope.form;
}));
it('should validate', function() {
form.amount.$setViewValue(0.01);
expect(form.amount.$invalid).to.equal(false);
form.amount.$setViewValue(0.039);
expect(form.amount.$invalid).to.equal(false);
form.amount.$setViewValue(100292.039);
expect(form.amount.$invalid).to.equal(false);
});
it('should not validate', function() {
form.amount.$setViewValue(0.039998888888888);
expect(form.amount.$invalid).to.equal(true);
form.amount.$setViewValue(0);
expect(form.amount.$invalid).to.equal(true);
form.amount.$setViewValue(0.0);
expect(form.amount.$invalid).to.equal(true);
});
});
});
describe('Contact directive', function() {
var element1, element2;
beforeEach(inject(function($compile, $rootScope) {
$rootScope.wallet = {
addressBook: {
'2MtBXKLtZuXGDshUcyH6yq7aZ33Snbb49pT': {
label: ':)'
}
}
}
element1 = angular.element(
'<contact address="2MtBXKLtZuXGDshUcyH6yq7aZ33Snbb49pT" />'
);
element2 = angular.element(
'<contact address="2MvCKdnwEMiaexi247gi738U6pwUFZxbhXn" />'
);
$compile(element1)($rootScope);
$compile(element2)($rootScope);
$rootScope.$digest();
}));
it('should replace the content', function() {
expect(element1.html()).to.equal(':)');
expect(element2.html()).to.equal('2MvCKdnwEMiaexi247gi738U6pwUFZxbhXn');
});
});
describe('Password strength', function() {
beforeEach(inject(function($compile, $rootScope) {
$scope = $rootScope;
var element = angular.element(
'<input type="password" name="password" ng-model="password" check-strength="passwordStrength" value="asd" required>'
);
$compile(element)($scope);
$scope.$digest();
}));
it('should check very weak password', function() {
$scope.password = 'asd';
$scope.$digest();
expect($scope.passwordStrength.strength).to.equal(1);
});
it('should check weak password', function() {
$scope.password = 'asdasdASDASD';
$scope.$digest();
expect($scope.passwordStrength.message).to.equal('Weak, add numerals');
});
it('should check medium password', function() {
$scope.password = 'asdasdA1';
$scope.$digest();
expect($scope.passwordStrength.message).to.equal('Medium, add punctuation');
});
it('should check strong password', function() {
$scope.password = 'asdasdASDASD1{';
$scope.$digest();
expect($scope.passwordStrength.message).to.equal('Strong, add punctuation');
});
});
describe('Match Password Inputs', function() {
beforeEach(inject(function($compile, $rootScope) {
$scope = $rootScope;
$rootScope.availableBalance = 1000;
var element = angular.element(
'<form name="form">' +
'<input type="password" ng-model="walletPassword" name="walletPassword" required>' +
'<input type="password" ng-model="walletPasswordConfirm" name="walletPasswordConfirm" match="walletPassword" required>' +
'</form>'
);
$scope.model = {
walletPassword: null,
walletPasswordConfirm: null
};
$compile(element)($scope);
$scope.$digest();
form = $scope.form;
}));
it('should not validate', function() {
form.walletPassword.$setViewValue('mysecretpassword');
form.walletPasswordConfirm.$setViewValue('mySecretPassword');
$scope.$digest();
expect(form.walletPasswordConfirm.$invalid).to.equal(true);
});
it('should validate', function() {
form.walletPassword.$setViewValue('mysecretpassword123');
form.walletPasswordConfirm.$setViewValue('mysecretpassword123');
$scope.$digest();
expect(form.walletPasswordConfirm.$invalid).to.equal(false);
});
});
});

View file

@ -1,168 +0,0 @@
'use strict';
//
// test/unit/filters/filtersSpec.js
//
describe('Angular Filters', function() {
beforeEach(angular.mock.module('copayApp'));
beforeEach(module('copayApp.filters'));
beforeEach(inject(function($rootScope) {
var w = {};
w.isComplete = sinon.stub().returns(true);
w.privateKey = {};
w.settings = {
unitToSatoshi: 100,
unitDecimals: 2,
alternativeName: 'US Dollar',
alternativeIsoCode: 'USD',
};
w.addressBook = {
'juan': '1',
};
w.balanceByAddr = [{
'address1': 1
}];
w.totalCopayers = 2;
w.getMyCopayerNickname = sinon.stub().returns('nickname');
w.getMyCopayerId = sinon.stub().returns('id');
w.privateKey.toObj = sinon.stub().returns({
wallet: 'mock'
});
w.getSecret = sinon.stub().returns('secret');
w.getName = sinon.stub().returns('fakeWallet');
w.getId = sinon.stub().returns('id');
w.exportEncrypted = sinon.stub().returns('1234567');
w.getTransactionHistory = sinon.stub().yields({});
w.getNetworkName = sinon.stub().returns('testnet');
w.getAddressesInfo = sinon.stub().returns({});
w.createTx = sinon.stub().yields(null);
w.sendTx = sinon.stub().yields(null);
w.requiresMultipleSignatures = sinon.stub().returns(true);
w.getTxProposals = sinon.stub().returns([1, 2, 3]);
$rootScope.wallet = w;
}));
var walletConfig = {
requiredCopayers: 3,
totalCopayers: 5,
spendUnconfirmed: 1,
reconnectDelay: 100,
networkName: 'testnet',
alternativeName: 'lol currency',
alternativeIsoCode: 'LOL'
};
describe('removeEmpty addresses', function() {
it('should work with empty lists', inject(function($filter) {
var removeEmpty = $filter('removeEmpty');
expect(removeEmpty([]).length).to.equal(0);
}));
it('should work with undefined', inject(function($filter) {
var removeEmpty = $filter('removeEmpty');
expect(removeEmpty(undefined).length).to.equal(0);
}));
it('should filter empty change addresses from other copayers', inject(function($filter) {
var removeEmpty = $filter('removeEmpty');
var addresses = [{
owned: true,
isChange: false,
balance: 0
}, {
owned: false,
isChange: false,
balance: 0
}, {
owned: true,
isChange: true,
balance: 0
}, {
owned: false,
isChange: true,
balance: 0
}];
expect(removeEmpty(addresses).length).to.equal(2);
}));
});
describe('noFractionNumber', function() {
describe('noFractionNumber bits', function() {
beforeEach(inject(function($rootScope) {
var w = $rootScope.wallet;
w.settings.unitToSatoshi = 100;
w.settings.unitName = 'bits';
}));
it('should format number to display correctly', inject(function($filter) {
var noFraction = $filter('noFractionNumber');
expect(noFraction(3100)).to.equal('3,100');
expect(noFraction(3100200)).to.equal('3,100,200');
expect(noFraction(3)).to.equal('3');
expect(noFraction(0.3)).to.equal(0.3);
expect(noFraction(0.30000000)).to.equal(0.3);
expect(noFraction(3200.01)).to.equal('3,200.01');
expect(noFraction(3200890.010000)).to.equal('3,200,890.01');
}));
});
describe('noFractionNumber BTC', function() {
beforeEach(inject(function($rootScope) {
var w = $rootScope.wallet;
w.settings.unitToSatoshi = 100000000;
w.settings.unitName = 'BTC';
}));
it('should format number to display correctly', inject(function($filter) {
var noFraction = $filter('noFractionNumber');
expect(noFraction(0.30000000)).to.equal(0.3);
expect(noFraction(0.00302000)).to.equal(0.00302);
expect(noFraction(1.00000001)).to.equal(1.00000001);
expect(noFraction(3.10000012)).to.equal(3.10000012);
expect(noFraction(0.00100000)).to.equal(0.001);
expect(noFraction(0.00100009)).to.equal(0.00100009);
expect(noFraction(2000.00312011)).to.equal('2,000.00312011');
expect(noFraction(2000998.00312011)).to.equal('2,000,998.00312011');
}));
});
describe('noFractionNumber mBTC', function() {
beforeEach(inject(function($rootScope) {
var w = $rootScope.wallet;
w.settings.unitToSatoshi = 100000;
w.settings.unitName = 'mBTC';
}));
it('should format number to display correctly', inject(function($filter) {
var noFraction = $filter('noFractionNumber');
expect(noFraction(0.30000)).to.equal(0.3);
expect(noFraction(0.00302)).to.equal(0.00302);
expect(noFraction(1.00001)).to.equal(1.00001);
expect(noFraction(3.10002)).to.equal(3.10002);
expect(noFraction(0.00100000)).to.equal(0.001);
expect(noFraction(0.00100009)).to.equal(0.001);
expect(noFraction(2000.00312)).to.equal('2,000.00312');
expect(noFraction(2000998.00312)).to.equal('2,000,998.00312');
}));
});
describe('noFractionNumber:custom fractionSize', function() {
it('should format number to display correctly', inject(function($filter) {
var noFraction = $filter('noFractionNumber');
expect(noFraction(0.30000, 0)).to.equal('0');
expect(noFraction(1.00001, 0)).to.equal('1');
expect(noFraction(3.10002, 0)).to.equal('3');
expect(noFraction(2000.00312, 0)).to.equal('2,000');
expect(noFraction(2000998.00312, 0)).to.equal('2,000,998');
}));
});
});
});

View file

@ -1,215 +0,0 @@
//
// test/unit/services/servicesSpec.js
//
//
//
var sinon = require('sinon');
var preconditions = require('preconditions').singleton();
describe("Angular services", function() {
beforeEach(angular.mock.module('copayApp'));
beforeEach(angular.mock.module('copayApp.services'));
beforeEach(module(function($provide) {
$provide.value('request', {
'get': function(_, cb) {
cb(null, null, [{
name: 'USD Dollars',
code: 'USD',
rate: 2
}]);
}
});
}));
beforeEach(inject(function($rootScope) {
var w = {};
w.isComplete = sinon.stub().returns(true);
w.privateKey = {};
w.settings = {
unitToSatoshi: 100,
unitDecimals: 2,
alternativeName: 'US Dollar',
alternativeIsoCode: 'USD',
};
w.addressBook = {
'juan': '1',
};
w.balanceByAddr = [{
'address1': 1
}];
w.totalCopayers = 2;
w.getMyCopayerNickname = sinon.stub().returns('nickname');
w.getMyCopayerId = sinon.stub().returns('id');
w.privateKey.toObj = sinon.stub().returns({
wallet: 'mock'
});
w.getSecret = sinon.stub().returns('secret');
w.getName = sinon.stub().returns('fakeWallet');
w.getId = sinon.stub().returns('id');
w.exportEncrypted = sinon.stub().returns('1234567');
w.getTransactionHistory = sinon.stub().yields({});
w.getNetworkName = sinon.stub().returns('testnet');
w.getAddressesInfo = sinon.stub().returns({});
w.createTx = sinon.stub().yields(null);
w.sendTx = sinon.stub().yields(null);
w.requiresMultipleSignatures = sinon.stub().returns(true);
w.getTxProposals = sinon.stub().returns([1, 2, 3]);
$rootScope.wallet = w;
}));
describe("Unit: balanceService", function() {
it('should updateBalance in bits', inject(function(balanceService, $rootScope) {
var w = $rootScope.wallet;
expect(balanceService.update).not.to.equal(null);
var Waddr = Object.keys($rootScope.wallet.balanceByAddr)[0];
var a = {};
a[Waddr] = 200;
w.getBalance = sinon.stub().yields(null, 100000001, a, 90000002, 5);
//retuns values in DEFAULT UNIT(bits)
balanceService.update(w, function() {
var b = w.balanceInfo;
expect(b.totalBalanceBTC).to.be.equal(1.00000001);
expect(b.availableBalanceBTC).to.be.equal(0.90000002);
expect(b.lockedBalanceBTC).to.be.equal(0.09999999);
expect(b.totalBalance).to.be.equal('1,000,000.01');
expect(b.availableBalance).to.be.equal('900,000.02');
expect(b.lockedBalance).to.be.equal('99,999.99');
expect(b.balanceByAddr[Waddr]).to.equal(2);
expect(b.safeUnspentCount).to.equal(5);
expect(b.topAmount).to.equal(899800.02);
}, false);
}));
});
describe("Unit: Notification Service", function() {
it('should contain a notification service', inject(function(notification) {
expect(notification).not.to.equal(null);
}));
});
describe("Unit: identityService Service", function() {
it('should contain a identityService service', inject(function(identityService) {
expect(identityService).not.to.equal(null);
}));
});
describe("Unit: pinService", function() {
it('should contain a pinService service', inject(function(pinService) {
expect(pinService).not.to.equal(null);
}));
it('should be able to check -> save -> get -> clear -> check', function(done) {
inject(function(pinService) {
pinService.save('123', 'user', 'pass', function(err) {
pinService.check(function(err, value) {
should.not.exist(err);
value.should.equal(true);
pinService.get('123', function(err, data) {
should.not.exist(err);
data.email.should.be.equal('user');
data.password.should.be.equal('pass');
pinService.clear(function(err) {
should.not.exist(err);
pinService.check(function(err, value) {
should.not.exist(err);
value.should.equal(false);
done();
});
});
});
});
})
})
});
});
describe("Unit: localstorageService", function() {
it('should contain a localstorageService service', inject(function(localstorageService) {
expect(localstorageService).not.to.equal(null);
}));
});
describe("Unit: Backup Service", function() {
it('should contain a backup service', inject(function(backupService) {
expect(backupService).not.to.equal(null);
}));
it('should backup in file', inject(function(backupService) {
var mock = sinon.mock(window);
var expectation = mock.expects('saveAs');
backupService._download({}, 'test');
expectation.once();
}));
});
describe("Unit: isMobile Service", function() {
it('should contain a isMobile service', inject(function(isMobile) {
expect(isMobile).not.to.equal(null);
}));
it('should not detect mobile by default', inject(function(isMobile) {
isMobile.any().should.equal(false);
}));
it('should detect mobile if user agent is Android', inject(function(isMobile) {
navigator.__defineGetter__('userAgent', function() {
return 'Android 2.2.3';
});
isMobile.any().should.equal(true);
}));
});
describe("Unit: uriHandler service", function() {
it('should contain a uriHandler service', inject(function(uriHandler) {
should.exist(uriHandler);
}));
it('should register', inject(function(uriHandler) {
(function() {
uriHandler.register();
}).should.not.throw();
}));
});
describe('Unit: Rate Service', function() {
it('should be injected correctly', inject(function(rateService) {
should.exist(rateService);
}));
it('should be possible to ask if it is available',
inject(function(rateService) {
should.exist(rateService.isAvailable);
})
);
it('should be possible to ask for conversion from fiat',
function(done) {
inject(function(rateService) {
rateService.whenAvailable(function() {
(1e8).should.equal(rateService.fromFiat(2, 'USD'));
done();
});
})
}
);
it('should be possible to ask for conversion to fiat',
function(done) {
inject(function(rateService) {
rateService.whenAvailable(function() {
(2).should.equal(rateService.toFiat(1e8, 'USD'));
done();
});
})
}
);
});
});

View file

@ -1,11 +0,0 @@
// 'use strict';
//
// describe('sidebarController', function(){
// var scope, controller;
//
// beforeEach(angular.mock.module('copayApp.controllers'));
//
// it('dummy test', function(){
// should.exist(true);
// });
// });

View file

@ -1,65 +0,0 @@
var incomplete2of2Wallet = {
"network": "livenet",
"xPrivKey": "xprv9s21ZrQH143K27bhzfejhNcitEAJgLKCfdLxwhr1FLu43FLqLwscAxXgmkucpF4k8eGmepSctkiQDbcR98Qd1bzSeDuR9jeyQAQEanPT2A4",
"xPubKey": "xpub6CkPnrzSUp9qzBVM3hpo4oS2JKC6GJq6brE1yW59QrnhDpvkFLakpxUGRGXH62fiXb5S2VbnD4h2DLoCMfSkwfonbNgNYTJw9Ko5SqWEqCR",
"requestPrivKey": "0cb89231b31dfaae9034ba794b9c48597eb573429f7b4b1f95e1945b22166bd5",
"requestPubKey": "022941a5ecb8c7224f812ad6b03bd1c9bb77861080b21703eabe18ef9a72b48e72",
"copayerId": "3d4eb9b439eee1b2b73cf792eda52e420f4665109c7234a50cf3cdbf296ea8fb",
"publicKeyRing": [{
"xPubKey": "xpub6CkPnrzSUp9qzBVM3hpo4oS2JKC6GJq6brE1yW59QrnhDpvkFLakpxUGRGXH62fiXb5S2VbnD4h2DLoCMfSkwfonbNgNYTJw9Ko5SqWEqCR",
"requestPubKey": "022941a5ecb8c7224f812ad6b03bd1c9bb77861080b21703eabe18ef9a72b48e72"
}],
"walletId": "7bd8d22f-d132-43e1-b259-d5b430752553",
"walletName": "A test wallet",
"m": 2,
"n": 2,
"walletPrivKey": "Kz4CFSTgLzoYfMkt97BTBotUbZYXjMts6Ej9HbVfCf5oLmun1BXy",
"personalEncryptingKey": "1fgFP/uoLhVxJiMXOWQznA==",
"sharedEncryptingKey": "FZIY4+p4TfBAKRclKtrROw==",
"copayerName": "me",
"mnemonic": "dizzy cycle skirt decrease exotic fork sure mixture hair vapor copper hero",
"entropySource": "79e60ad83e04ee40967147fd6ac58f986c7dcf6c82b125fb4e8c30ff9f9584ee",
"mnemonicHasPassphrase": false,
"derivationStrategy": "BIP44",
"account": 0,
"addressType": "P2SH"
};
var testnet1of1Wallet = {
"network": "testnet",
"xPrivKey": "tprv8ZgxMBicQKsPdK35ubrjCCpPCaBZA7QyKtxNNDWvYyjDAhtxV1HVNLzqwntAJ5QH1RTksRSfbuHLUYvMdGFmy9vHCb4yDRAR2zKqmX8mVa8",
"xPubKey": "tpubDDN7B6QnxsbomkZfPFRj6CVtC7LVh6ufoTpvzHfutjiHbu4hmiEGYDzxo5mgfqkQkBuwZPFkTYLNmQeLg7eFvdb4SFH1LW35sQD6xfymmRP",
"requestPrivKey": "aa39d4d780ad7ec36e26cbd0c0250bce85dfdd8aa7f2222ec7c86d6d62f242d7",
"requestPubKey": "038bb7cc1238280e893dd6949bfce770a319892b3c9045112ec7810191d4157ced",
"copayerId": "5c474b568bde8cd39efe069cd6aff2a80ab1cb18d3b9ae81f8225286f94856bc",
"publicKeyRing": [{
"xPubKey": "tpubDDN7B6QnxsbomkZfPFRj6CVtC7LVh6ufoTpvzHfutjiHbu4hmiEGYDzxo5mgfqkQkBuwZPFkTYLNmQeLg7eFvdb4SFH1LW35sQD6xfymmRP",
"requestPubKey": "038bb7cc1238280e893dd6949bfce770a319892b3c9045112ec7810191d4157ced"
}],
"walletId": "66d3afc9-7d76-4b25-850e-aa62fcc53a7d",
"walletName": "kk",
"m": 1,
"n": 1,
"walletPrivKey": "1d6eb8e5a9f8944e97c2f13423c137ce912fac00f7eb5b3ffe6e3c161ea98bf7",
"personalEncryptingKey": "A2dQiAwpFY2xwIhE26ClFQ==",
"sharedEncryptingKey": "z0BtAIFclGQMH6eHqK9e3w==",
"copayerName": "me",
"mnemonic": "cheese where alarm job conduct donkey license pave congress pepper fence current",
"entropySource": "5c84e65837c0fbd11db935953dbacb60f5c33f40ecfe95e0feded1f62a5ee15d",
"mnemonicHasPassphrase": false,
"derivationStrategy": "BIP44",
"account": 0,
"addressType": "P2PKH"
};
var PROFILE = {
incomplete2of2: {
credentials: [incomplete2of2Wallet],
createdOn: 1463519749,
disclaimerAccepted: true,
},
testnet1of1: {
credentials: [testnet1of1Wallet],
createdOn: 1463519749,
disclaimerAccepted: true,
},
};

View file

@ -1,24 +0,0 @@
describe('walletService', function() {
var walletService;
// Adds walletService's module dependencies
beforeEach(function() {
module('ngLodash');
module('gettext');
module('angularMoment');
module('bwcModule');
module('copayApp.services');
});
beforeEach(inject(function(_walletService_) {
walletService = _walletService_;
}));
it('should be defined', function() {
should.exist(walletService);
});
});

View file

@ -8,7 +8,7 @@
#define MyAppExeName "*PACKAGENAME.exe" #define MyAppExeName "*PACKAGENAME.exe"
[Setup] [Setup]
AppId={{804636ee-b017-4cad-8719-e58ac97ffa5c} AppId={804636ee-b017-4cad-8719-e58ac97ffa5c}
AppName={#MyAppName} AppName={#MyAppName}
AppVersion={#MyAppVersion} AppVersion={#MyAppVersion}
;AppVerName={#MyAppName} {#MyAppVersion} ;AppVerName={#MyAppName} {#MyAppVersion}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 8 KiB

Before After
Before After

BIN
www/img/icon-128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

View file

@ -1,4 +1,4 @@
<ion-view id="add-address"> <ion-view id="add-address" hide-tabs>
<ion-nav-bar class="bar-royal"> <ion-nav-bar class="bar-royal">
<ion-nav-title> <ion-nav-title>
<span translate>Add Contact</span> <span translate>Add Contact</span>

View file

@ -1,4 +1,4 @@
<ion-view id="view-address-book"> <ion-view id="view-address-book" hide-tabs>
<ion-nav-bar class="bar-royal"> <ion-nav-bar class="bar-royal">
<ion-nav-back-button> <ion-nav-back-button>
</ion-nav-back-button> </ion-nav-back-button>
@ -24,7 +24,7 @@
</div> </div>
</ion-content> </ion-content>
<ion-content class="ng-hide" ng-show="!isEmptyList"> <ion-content class="ng-hide" ng-show="!isEmptyList">
<div class="bar bar-header item-input-inset" ng-show="!isEmptyList"> <div class="bar bar-header item-input-inset" ng-show="!isEmptyList && addressbook.length >= 5">
<label class="item-input-wrapper"> <label class="item-input-wrapper">
<i class="icon ion-ios-search placeholder-icon"></i> <i class="icon ion-ios-search placeholder-icon"></i>
<input type="search" <input type="search"
@ -36,7 +36,7 @@
<ion-list> <ion-list>
<ion-item ng-repeat="addrEntry in addressbook" <ion-item ng-repeat="addrEntry in addressbook"
class="item-icon-right item-avatar" class="item-icon-right item-avatar"
ui-sref="tabs.addressbook.view({address:addrEntry.address})"> ui-sref="tabs.addressbook.view({address:addrEntry.address, email: addrEntry.email, name: addrEntry.name})">
<gravatar name="{{addrEntry.name}}" width="50" email="{{addrEntry.email}}"></gravatar> <gravatar name="{{addrEntry.name}}" width="50" email="{{addrEntry.email}}"></gravatar>
<h2>{{addrEntry.name}}</h2> <h2>{{addrEntry.name}}</h2>
<p>{{addrEntry.address}}</p> <p>{{addrEntry.address}}</p>

View file

@ -1,4 +1,4 @@
<ion-view id="address-book-view"> <ion-view id="address-book-view" hide-tabs>
<ion-nav-bar class="bar-royal"> <ion-nav-bar class="bar-royal">
<ion-nav-back-button> <ion-nav-back-button>
</ion-nav-back-button> </ion-nav-back-button>

View file

@ -1,4 +1,4 @@
<ion-view id="view-amount"> <ion-view id="view-amount" hide-tabs>
<ion-nav-bar class="bar-royal"> <ion-nav-bar class="bar-royal">
<ion-nav-title> <ion-nav-title>
{{'Enter Amount'|translate}} {{'Enter Amount'|translate}}
@ -12,15 +12,17 @@
<div> <div>
<div class="item item-no-bottom-border" translate>Recipient</div> <div class="item item-no-bottom-border" translate>Recipient</div>
<div class="item item-text-wrap item-icon-left bitcoin-address" ng-class="{'item-big-icon-left':isCard}"> <div class="item item-text-wrap item-icon-left bitcoin-address" ng-class="{'item-big-icon-left':cardId}">
<i ng-if="isWallet" class="icon ion-briefcase size-21"></i> <i class="icon big-icon-svg" ng-if="isWallet">
<img src="img/icon-wallet.svg" ng-style="{'background-color': toColor}" class="bg"/>
</i>
<span ng-if="!isWallet"> <span ng-if="!isWallet">
<gravatar ng-if="!isCard" class="send-gravatar" name="{{toName}}" width="30" email="{{toEmail}}"></gravatar> <gravatar ng-if="!cardId" class="send-gravatar" name="{{toName}}" width="30" email="{{toEmail}}"></gravatar>
<i ng-if="isCard" class="icon big-icon-svg"> <i ng-if="cardId" class="icon big-icon-svg">
<div class="bg icon-bitpay-card"></div> <div class="bg icon-bitpay-card"></div>
</i> </i>
</span> </span>
<span ng-class="{'m10l':isCard}">{{toName || toAddress}}</span> <span class="m10l">{{toName || toAddress}}</span>
</div> </div>
</div> </div>

View file

@ -1,4 +1,4 @@
<ion-view id="wallet-backup-phrase"> <ion-view id="wallet-backup-phrase" hide-tabs>
<ion-nav-bar class="bar-royal"> <ion-nav-bar class="bar-royal">
<ion-nav-title> <ion-nav-title>
{{viewTitle}} {{viewTitle}}

View file

@ -1,4 +1,4 @@
<ion-view id="backup-warning" class="onboarding" ng-controller="backupWarningController"> <ion-view id="backup-warning" class="onboarding" ng-controller="backupWarningController" hide-tabs>
<ion-nav-bar> <ion-nav-bar>
<ion-nav-buttons side="primary"> <ion-nav-buttons side="primary">
<button class="button button-back button-clear" ng-click="goBack()"> <button class="button button-back button-clear" ng-click="goBack()">

View file

@ -2,9 +2,9 @@
<ion-nav-bar class="bar-royal"> <ion-nav-bar class="bar-royal">
<ion-nav-back-button> <ion-nav-back-button>
</ion-nav-back-button> </ion-nav-back-button>
<ion-nav-title>BitPay Card</ion-nav-title> <ion-nav-title>BitPay Visa&reg; Card</ion-nav-title>
<ion-nav-buttons side="secondary" ng-show="bitpayCard.bitpayCardAuthenticated"> <ion-nav-buttons side="secondary">
<button class="button back-button" ui-sref="tabs.bitpayCard.preferences"> <button class="button back-button" ng-show="!error" ui-sref="tabs.bitpayCard.preferences">
<i class="icon ion-ios-gear-outline"></i> <i class="icon ion-ios-gear-outline"></i>
</button> </button>
</ion-nav-buttons> </ion-nav-buttons>
@ -16,145 +16,77 @@
Sandbox version. Only for testing purpose Sandbox version. Only for testing purpose
</div> </div>
<div class="m20t text-center size-12 text-gray" ng-show="!bitpayCard.bitpayCardAuthenticated && bitpayCard.loadingSession"> <div class="oh pr" ng-show="!error">
Loading... <div class="amount">
</div> <div ng-if="bitpayCard.bitpayCardCurrentBalance" ng-click="bitpayCard.update()">
<div class="size-36 m20b">${{bitpayCard.bitpayCardCurrentBalance}}</div>
<div ng-show="!bitpayCard.bitpayCardAuthenticated && !bitpayCard.loadingSession"> <a class="button button-positive button-small"
<div class="text-center m20t"> ui-sref="tabs.bitpayCard.amount({'cardId': cardId, 'toName': 'BitPay Card'})" translate>
<img src="img/bitpay-card-visa.svg" width="200"> Add Funds
</a>
</div> </div>
<h4 class="text-center"> <div ng-if="!bitpayCard.bitpayCardCurrentBalance" class="m10t">
<span ng-show="!bitpayCard.bitpayCardTwoFactorPending">Login to your account</span> <strong class="size-36">...</strong>
<span ng-show="bitpayCard.bitpayCardTwoFactorPending">2-Step Verification</span>
</h4>
<form
ng-show="!bitpayCard.bitpayCardTwoFactorPending"
name="authenticateForm"
ng-submit="bitpayCard.authenticate(email, password)"
novalidate>
<div class="card list">
<label class="item item-input item-stacked-label">
<span class="input-label">Email</span>
<input name="email"
type="email"
ng-model="email"
ng-disabled="bitpayCard.authenticating"
required>
</label>
<label class="item item-input item-stacked-label">
<span class="input-label">Password</span>
<input name="password"
type="password"
ng-model="password"
ng-disabled="bitpayCard.authenticating"
required>
</label>
</div>
<input class="button button-block button-positive"
type="submit"
ng-disabled="!authenticateForm.$valid || bitpayCard.authenticating"
value="Login">
</form>
<p ng-show="bitpayCard.bitpayCardTwoFactorPending" class="size-12 text-center text-gray">
Enter the verification code generated by the authenticator app on your phone.
</p>
<form
ng-show="bitpayCard.bitpayCardTwoFactorPending"
name="authenticate2FAForm"
ng-submit="bitpayCard.authenticate2FA(twoFactorCode)"
novalidate>
<div class="list">
<label class="item item-input item-stacked-label">
<span class="input-label">Verification Code</span>
<input name="twoFactorCode"
type="text"
ng-model="twoFactorCode"
ng-disabled="bitpayCard.authenticating"
required>
</label>
</div>
<input class="button button-block button-positive"
type="submit"
ng-disabled="!authenticate2FAForm.$valid || bitpayCard.authenticating"
value="Login">
</form>
</div>
<div ng-show="bitpayCard.bitpayCardAuthenticated">
<div class="oh pr">
<div class="amount">
<div ng-if="!loadingHistory && bitpayCard.bitpayCardCurrentBalance" ng-click="bitpayCard.update()">
<div class="size-36 m20b">${{bitpayCard.bitpayCardCurrentBalance}}</div>
<a class="button button-positive button-small" ui-sref="tabs.bitpayCard.amount({'isCard': true, 'toName': 'BitPay Card'})">
<i class="icon ion-ios-plus-empty"></i> {{'Add Funds'|translate}}
</a>
</div>
<div ng-if="loadingHistory" class="m10t">
<strong class="size-36">...</strong>
</div>
</div> </div>
</div> </div>
<div class="wallet-details-wallet-info">
<img style="height:0.6em" ng-show="loadingHistory" src="img/icon-sync-white.svg">
</div>
</div>
<div ng-show="error" class="text-center m10t assertive">
{{error}}
</div>
<div
class="m10t text-center padding"
ng-if="!loadingHistory && !bitpayCard.bitpayCardTransactionHistory[0] && !error">
<i class="icon ion-ios-arrow-thin-up size-24"></i>
<h1>Get started</h1>
<h4>Your BitPay Card is ready. Add funds to your card to start using your card at stores and ATMs worldwide.</h4>
</div>
<div class="list" ng-if="bitpayCard.bitpayCardTransactionHistory[0] && !error">
<div class="item item-divider">
<select class="select-style" ng-model="dateRange.value" ng-change="bitpayCard.update()">
<option value="last30Days">Recent Activity</option>
<option value="lastMonth">Last Month</option>
<option value="all">All Activity</option>
</select>
</div>
<div <div
class="m10t text-center padding" ng-repeat="tx in bitpayCard.bitpayCardTransactionHistory | orderBy: ['pending','-timestamp']"
ng-show="!bitpayCard.bitpayCardTransactionHistory[0] && class="item row">
!bitpayCard.bitpayCardInvoiceHistory[0] && (!loadingHistory || !bitpayCardCached)"> <div class="col col-10">
<i class="icon ion-ios-arrow-thin-up size-24"></i> <img class="m5t" ng-src="img/mcc-icons/{{tx.icon}}.svg" width="22">
<h1>Get started</h1>
<h4>Your BitPay Card is ready. Add funds to your card to start using your card at stores and ATMs worldwide.</h4>
</div>
<div class="list" ng-if="bitpayCardCached">
<div class="item item-divider">
<select class="select-style" ng-model="dateRange" ng-change="bitpayCard.update(dateRange)">
<option value="last30Days">Recent Activity</option>
<option value="lastMonth">Last Month</option>
<option value="all">All Activity</option>
</select>
</div> </div>
<div
ng-repeat="tx in bitpayCard.bitpayCardTransactionHistory | orderBy: ['pending','-timestamp']"
class="item row"
ng-init="bitpayCard.getMerchantInfo(tx)">
<div class="col col-10" ng-init="icon = bitpayCard.getIconName(tx)">
<img class="m5t" ng-src="img/mcc-icons/{{icon}}.svg" width="22">
</div>
<div class="col"> <div class="col col-50">
<div class="size-12 text-bold"> <div class="size-12 text-bold">
{{tx.merchant.name}} {{tx.merchant.name}}
</div>
<div class="size-12">
{{tx.merchant.city}}, {{tx.merchant.state}}
</div>
</div> </div>
<div <div class="size-12">
ng-init="desc = bitpayCard.processDescription(tx)" {{tx.merchant.city}}, {{tx.merchant.state}}
class="col size-12">
{{desc}}
</div> </div>
<div class="col"> </div>
<img ng-show="!tx.pending" ng-src="img/check.svg" width="14"> <!--
<img ng-show="tx.pending" ng-src="img/sync.svg" width="14"> <div class="col size-12">
</div> {{tx.desc}}
<div class="col text-right size-12 text-gray"> </div>
<div class="size-14" -->
ng-class="{ <div class="col col-20 text-center p10t">
'text-success': tx.amount.indexOf('-') == -1 && !tx.pending, <img ng-show="!tx.pending" ng-src="img/check.svg" width="14">
'text-gray': tx.amount.indexOf('-') == -1 && tx.pending}"> <img ng-show="tx.pending" ng-src="img/sync.svg" width="14">
{{tx.amount | currency:'$':2 }} </div>
</div> <div class="col col-20 text-right size-12 text-gray">
<time>{{tx.timestamp | amTimeAgo}}</time> <div class="size-14"
ng-class="{
'text-success': tx.amount.indexOf('-') == -1 && !tx.pending,
'text-gray': tx.amount.indexOf('-') == -1 && tx.pending}">
{{tx.amount | currency:'$':2 }}
</div> </div>
<time ng-if="!tx.pending">{{tx.timestamp | amCalendar}}</time>
<span ng-if="tx.pending" class="tu" translate>Pending</span>
</div> </div>
</div> </div>
</div> </div>

View file

@ -0,0 +1,45 @@
<ion-view id="bitpayCard-intro" hide-tabs>
<ion-nav-bar class="bar-stable">
<ion-nav-back-button>
</ion-nav-back-button>
<ion-nav-title></ion-nav-title>
<ion-nav-buttons side="secondary">
<button class="button back-button button-clear" ng-click="orderBitPayCard()">
<i class="icon ion-ios-information-outline"></i>
</button>
</ion-nav-buttons>
</ion-nav-bar>
<ion-content scroll="false">
<div class="text-center padding">
<img src="img/bitpay-card-visa.svg" width="100%">
</div>
<ion-slide-box>
<ion-slide>
<p translate>
Turn bitcoin into dollars, swipe anywhere Visa&reg; is accepted.
</p>
</ion-slide>
<ion-slide>
<p translate>
<span translate>Get local cash anywhere you go, from any Visa&reg;-compatible ATM.</span>
<div translate class="size-10 text-center">
*ATM bank fees may apply
</div>
</p>
</ion-slide>
<ion-slide>
<p translate>
Pay 0% fees to turn bitcoin into dollars.
</p>
</ion-slide>
</ion-slide-box>
<div class="cta-button">
<button class="button button-block button-primary" ng-click="orderBitPayCard()" translate>
Order the BitPay Card
</button>
<button class="button button-block button-transparent text-white m10t" ng-click="connectBitPayCard()" translate>
Connect my BitPay Card
</button>
</div>
</ion-content>
</ion-view>

View file

@ -105,7 +105,7 @@
</div> </div>
<div class="m10t"> <div class="m10t">
<button class="button button-positive" <button class="button button-positive"
ng-click="openExternalLink('https://www.amazon.com/gc/redeem?claimCode=' + buy.giftCard.claimCode, '_system')"> ng-click="openExternalLink('https://www.amazon.com/gc/redeem?claimCode=' + buy.giftCard.claimCode)">
Redeem Now Redeem Now
</button> </button>
</div> </div>

View file

@ -1,4 +1,4 @@
<ion-view id="view-confirm"> <ion-view id="view-confirm" hide-tabs>
<ion-nav-bar class="bar-royal"> <ion-nav-bar class="bar-royal">
<ion-nav-title> <ion-nav-title>
{{'Confirm'|translate}} {{'Confirm'|translate}}
@ -22,9 +22,22 @@
<div class="info"> <div class="info">
<div class="item"> <div class="item">
<span class="label" translate>To</span> <span class="label" translate>To</span>
<span class="payment-proposal-to" copy-to-clipboard="toAddress"> <span class="payment-proposal-to">
<img src="img/icon-bitcoin-small.svg"> <img ng-if="!cardId" src="img/icon-bitcoin-small.svg">
<contact class="ellipsis" address="{{toAddress}}">{{toAddress}}</contact> <img ng-if="cardId" src="img/icon-card.svg" width="34">
<contact ng-if="!toName && !_paypro" class="ellipsis" address="{{toAddress}}" copy-to-clipboard="toAddress">
{{toAddress}}
</contact>
<span class="m15l size-14" ng-if="toName && !_paypro" copy-to-clipboard="toAddress">{{toName}}</span>
<div ng-if="_paypro" ng-click="openPPModal(_paypro)" class="m15l size-14 w100p pointer">
<i ng-show="_paypro.verified && _paypro.caTrusted" class="ion-locked" style="color:green"></i>
<i ng-show="!_paypro.caTrusted" class="ion-unlocked" style="color:red"></i>
<span class="ellipsis" ng-show="!toName">{{_paypro.domain || _paypro.toAddress}}</span>
<span ng-show="toName">{{toName}}</span>
</div>
<!-- <contact ng-if="!tx.hasMultiplesOutputs" class="ellipsis" address="{{toAddress}}"></contact> <!-- <contact ng-if="!tx.hasMultiplesOutputs" class="ellipsis" address="{{toAddress}}"></contact>
<span ng-if="tx.hasMultiplesOutputs" translate>Multiple recipients</span> --> <span ng-if="tx.hasMultiplesOutputs" translate>Multiple recipients</span> -->
</span> </span>
@ -42,20 +55,19 @@
</div> </div>
<i class="icon bp-arrow-right"></i> <i class="icon bp-arrow-right"></i>
</a> </a>
<a class="item single-line item-icon-right" ng-hide="insuffientFunds"> <a class="item single-line item-icon-right" ng-hide="insuffientFunds" ng-click="showDescriptionPopup()">
<span class="label" translate>{{'Add Memo'|translate}}</span> <span class="label" translate>Add Memo</span>
<span class="item-note"> <span class="item-note m10l">
{{description}} {{description}}
</span> </span>
<i class="icon bp-arrow-right"></i> <i class="icon bp-arrow-right"></i>
</a> </a>
<a class="item single-line item-icon-right" ng-hide="insuffientFunds"> <div class="item single-line" ng-hide="insuffientFunds">
<span class="label" translate>Fee</span> <span class="label" translate>Fee: {{feeLevel}}</span>
<span class="item-note"> <span class="item-note">
{{fee}} {{fee || '...'}}
</span> </span>
<i class="icon bp-arrow-right"></i> </div>
</a>
</div> </div>
</div> </div>
<click-to-accept <click-to-accept

View file

@ -1,21 +1,21 @@
<ion-view> <ion-view id="export">
<ion-nav-bar class="bar-royal"> <ion-nav-bar class="bar-royal">
<ion-nav-title>{{'Export wallet' | translate}}</ion-nav-title> <ion-nav-title>{{'Export wallet' | translate}}</ion-nav-title>
<ion-nav-back-button> <ion-nav-back-button>
</ion-nav-back-button> </ion-nav-back-button>
</ion-nav-bar> </ion-nav-bar>
<ion-content ng-init="file = true"> <ion-content>
<div class="row text-center"> <div class="row text-center top-tabs">
<div class="col" ng-click="file = true" ng-style="file && {'border-bottom': '2px solid'}"> <div class="col" ng-click="file.value = true" ng-style="file.value && {'border-bottom-style': 'solid'}">
<span class="" translate>File/Text</span> <span class="" translate>File/Text</span>
</div> </div>
<div class="col" ng-click="file = false" ng-style="!file && {'border-bottom': '2px solid'}"> <div class="col" ng-click="generateQrCode();" ng-style="!file.value && {'border-bottom-style': 'solid'}">
<span class="" translate>QR Code</span> <span class="" translate>QR Code</span>
</div> </div>
</div> </div>
<div ng-include="'views/tab-export-file.html'" ng-if="file"></div> <div ng-include="'views/tab-export-file.html'" ng-if="file.value"></div>
<div ng-include="'views/tab-export-qrCode.html'" ng-if="!file"></div> <div ng-include="'views/tab-export-qrCode.html'" ng-if="!file.value"></div>
</ion-content> </ion-content>
</ion-view> </ion-view>

View file

@ -1,4 +1,4 @@
<ion-view> <ion-view id="glidera">
<ion-nav-bar class="bar-royal"> <ion-nav-bar class="bar-royal">
<ion-nav-back-button> <ion-nav-back-button>
</ion-nav-back-button> </ion-nav-back-button>
@ -22,22 +22,21 @@
<img src="img/glidera-logo.png" ng-click="update(token, permissions)" width="200"> <img src="img/glidera-logo.png" ng-click="update(token, permissions)" width="200">
</div> </div>
<div class="text-center small-10 small-centered columns" ng-show="!showOauthForm"> <div class="text-center small-10 small-centered columns" ng-show="!showOauthForm">
<p class="m10b">You can buy and sell Bitcoin with a US bank account directly in Copay.</p> <p class="glidera-lead">You can buy and sell Bitcoin with a US bank account directly in this app.</p>
<p class="m20t padding text-gray size-12 text-left"> <p class="disclosure">
DISCLOSURE.<br> DISCLOSURE.<br>
Glidera Inc. (Glidera) is providing the service of buying or selling bitcoins to Copay users. To enable this Glidera Inc. (Glidera) is providing the service of buying or selling bitcoin to BitPay users. To enable this
service, Glidera has registered with US Treasury Departments FinCEN as a Money Service Business service, Glidera has registered with US Treasury Departments FinCEN as a Money Service Business
(#31000042625755). Users of Copay must agree to the service agreement presented by Glidera prior to obtaining (#31000042625755). Users of BitPay must agree to the service agreement presented by Glidera prior to obtaining
Glideras service of buying or selling bitcoins. Service available in U.S. and Canada only. In U.S. (buy & sell) CA, GA, IL, KS, Glideras service of buying or selling bitcoins. Service available in U.S. and Canada only. In U.S. (buy & sell) CA, GA, IL, KS,
MA, MD, MO, MT, MN, SC, TX, AZ, CO, DE, ME, NJ, PA, TN, UT, NV, WI. In Canada (buy & sell) AB, BC, MB, NB, NL, NS, NT, NU, MA, MD, MO, MT, MN, SC, TX, AZ, CO, DE, ME, NJ, PA, TN, UT, NV, WI. In Canada (buy & sell) AB, BC, MB, NB, NL, NS, NT, NU,
ON, PE, SK, YT. ON, PE, SK, YT.
</p> </p>
<p class="m20t text-gray size-12">Connect your Glidera account to get started</p> <p class="m20t text-gray size-12">Connect your Glidera account to get started</p>
<button class="button button-standard button-primary" <button class="button button-standard button-primary"
ng-click="openExternalLink(glidera.getAuthenticateUrl(), '_system'); showOauthForm = true"> ng-click="openExternalLink(glidera.getAuthenticateUrl()); showOauthForm = true">
Connect to Glidera Connect to Glidera
</button> </button>
<div class="m10t"> <div class="m10t">
@ -96,8 +95,7 @@
<h4 class="text-bold"> Complete Setup</h4> <h4 class="text-bold"> Complete Setup</h4>
<div>Your Glidera account is not ready to transact. Please, verify it at <b>Glidera.io</b></div> <div>Your Glidera account is not ready to transact. Please, verify it at <b>Glidera.io</b></div>
<a class="button" <a class="button"
ng-init="glideraUrl = network == 'testnet' ? 'https://sandbox.glidera.io/login' : ng-init="glideraUrl = network == 'testnet' ? 'https://sandbox.glidera.io/login' : 'https://glidera.io/login'"
'https://glidera.io/login'"
ng-click="openExternalLink(glideraUrl)"> ng-click="openExternalLink(glideraUrl)">
Go to Glidera Go to Glidera
</a> </a>

View file

@ -1,4 +1,4 @@
<ion-view ng-controller="tabsController" ng-init="importInit()" class="settings"> <ion-view id="import" ng-controller="tabsController" ng-init="importInit()" class="settings">
<ion-nav-bar class="bar-royal"> <ion-nav-bar class="bar-royal">
<ion-nav-title>{{'Import Wallet' | translate}}</ion-nav-title> <ion-nav-title>{{'Import Wallet' | translate}}</ion-nav-title>
<ion-nav-back-button> <ion-nav-back-button>
@ -6,14 +6,17 @@
</ion-nav-bar> </ion-nav-bar>
<ion-content ng-controller="importController" ng-init="phrase = true; init()"> <ion-content ng-controller="importController" ng-init="phrase = true; init()">
<div class="row text-center"> <div class="row text-center top-tabs">
<div class="col" ng-click="phrase = true; file = hardware = false; showAdv = false" ng-style="phrase && {'border-bottom': '2px solid'}"> <div class="col" ng-click="phrase = true; file = hardware = false; showAdv = false" ng-style="phrase &&
{'border-bottom-style': 'solid'}">
<span translate>Recovery phrase</span> <span translate>Recovery phrase</span>
</div> </div>
<div class="col" ng-click="file = true; phrase = hardware = false; showAdv = false" ng-style="file && {'border-bottom': '2px solid'}"> <div class="col" ng-click="file = true; phrase = hardware = false; showAdv = false" ng-style="file &&
{'border-bottom-style': 'solid'}">
<span translate>File/Text</span> <span translate>File/Text</span>
</div> </div>
<div class="col" ng-click="hardware = true; phrase = file = false; showAdv = false" ng-style="hardware && {'border-bottom': '2px solid'}"> <div class="col" ng-click="hardware = true; phrase = file = false; showAdv = false" ng-style="hardware &&
{'border-bottom-style': 'solid'}">
<span translate>Hardware wallet</span> <span translate>Hardware wallet</span>
</div> </div>
</div> </div>

View file

@ -1,14 +1,17 @@
<li class="p10 oh" copy-to-clipboard="output.toAddress"> <div class="item" copy-to-clipboard="output.toAddress">
<span class="text-gray" translate>To</span>: <span class="text-gray" translate>To</span>:
<span class="right enable_text_select">{{output.toAddress || output.address}}</span> <span class="right enable_text_select">{{output.toAddress || output.address}}</span>
</li> </div>
<li class="p10" copy-to-clipboard="output.amountStr">
<div class="item" copy-to-clipboard="output.amountStr">
<span class="text-gray" translate>Amount</span>: <span class="text-gray" translate>Amount</span>:
<span class="right enable_text_select">{{output.amountStr}} <span class="right enable_text_select">{{output.amountStr}}
<span ng-show="output.alternativeAmountStr" class="label gray radius">{{output.alternativeAmountStr}}</span> <span ng-show="output.alternativeAmountStr">({{output.alternativeAmountStr}})</span>
</span> </span>
</li> </div>
<li class="p10 oh" copy-to-clipboard="output.message">
<div class="item" copy-to-clipboard="output.message" ng-show-"output.message">
<span class="text-gray" translate>Note</span>: <span class="text-gray" translate>Note</span>:
<span class="right enable_text_select">{{output.message}}</span> <span class="right enable_text_select">{{output.message}}</span>
</li> </div>

View file

@ -23,7 +23,7 @@
</div> </div>
<div class="m10t" ng-show="card.cardStatus == 'Fulfilled'"> <div class="m10t" ng-show="card.cardStatus == 'Fulfilled'">
<button class="button button-positive" <button class="button button-positive"
ng-click="openExternalLink('https://www.amazon.com/gc/redeem?claimCode=' + card.claimCode, '_system')"> ng-click="openExternalLink('https://www.amazon.com/gc/redeem?claimCode=' + card.claimCode)">
Redeem Now Redeem Now
</button> </button>
</div> </div>

View file

@ -10,7 +10,7 @@
<ion-content class="has-header" scroll="false"> <ion-content class="has-header" scroll="false">
<ion-scroll ng-include="'views/includes/terms.html'" direction="y" ng-style="{'height': '60%'}"></ion-scroll> <ion-scroll ng-include="'views/includes/terms.html'" direction="y" ng-style="{'height': '60%'}"></ion-scroll>
<div id="agree-to-terms"> <div id="agree-to-terms">
<a ng-click="openExternalLink('https://copay.io/disclaimer')" ng-show="lang != 'en'" translate>Official English Disclaimer</a> <a ng-click="openExternalLink('https://copay.io/disclaimer', true, 'View Terms of Service', 'The official English Terms of Service are available on the BitPay website. Would you like to view them?', 'Open Website', 'Go Back')" ng-show="lang != 'en'" translate>Official English Disclaimer</a>
<ion-checkbox ng-model="terms.accept3"></ion-checkbox> <ion-checkbox ng-model="terms.accept3"></ion-checkbox>
<p translate>I have read, understood, and agree with the Terms of use.</p> <p translate>I have read, understood, and agree with the Terms of use.</p>
<button ng-disabled="!terms.accept3" class="button button-block button-positive" ng-click="termsModal.hide(); confirm()" translate>Confirm & Finish</button> <button ng-disabled="!terms.accept3" class="button button-block button-positive" ng-click="termsModal.hide(); confirm()" translate>Confirm & Finish</button>

Some files were not shown because too many files have changed in this diff Show more