Merge pull request #2 from bitpay/master

Merge master
This commit is contained in:
Dabura667 2017-04-20 12:50:12 +09:00 committed by GitHub
commit 55d657f0f0
87 changed files with 2059 additions and 609 deletions

View file

@ -146,7 +146,7 @@ module.exports = function(grunt) {
'src/js/externalServices.js',
'src/js/init.js',
'src/js/trezor-url.js',
'bower_components/trezor-connect/login.js',
'bower_components/trezor-connect/connect.js',
'node_modules/bezier-easing/dist/bezier-easing.min.js',
'node_modules/cordova-plugin-qrscanner/dist/cordova-plugin-qrscanner-lib.min.js'
],

View file

@ -22,8 +22,8 @@
"windowsAppId": "2d1002d7-ee34-4f60-bd29-0c871ba0c195",
"pushSenderId": "1036948132229",
"description": "Secure Bitcoin Wallet",
"version": "3.0.7",
"androidVersion": "306000",
"version": "3.1.1",
"androidVersion": "311000",
"_extraCSS": null,
"_enabledExtensions": {
"coinbase": true,

View file

@ -22,8 +22,8 @@
"windowsAppId": "804636ee-b017-4cad-8719-e58ac97ffa5c",
"pushSenderId": "1036948132229",
"description": "A Secure Bitcoin Wallet",
"version": "3.0.7",
"androidVersion": "306000",
"version": "3.1.1",
"androidVersion": "311000",
"_extraCSS": null,
"_enabledExtensions": {
"coinbase": true,

View file

@ -94,6 +94,7 @@
"build:windows": "cordova prepare windows && cordova build windows -- --arch=\"x86\"",
"build:ios-release": "cordova prepare ios && cordova build ios --release",
"build:android-release": "cordova prepare android && cordova build android --release",
"build:windows-release": "cordova prepare windows && cordova build windows --release --arch=\"x86\"",
"build:desktop": "grunt desktop",
"build:macos": "grunt macos",
"open:ios": "open platforms/ios/*.xcodeproj",
@ -101,6 +102,7 @@
"final:www": "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 sign:android && npm run run:android-release",
"final:windows": "npm run final:www && npm run build:windows-release",
"final:desktop": "npm run build:desktop && npm run build:macos",
"run:android": "cordova run android --device",
"run:android-release": "cordova run android --device --release",

View file

@ -11,11 +11,11 @@
"angular-gettext": "2.2.1",
"angular-moment": "0.10.1",
"angular-qrcode": "bitpay/angular-qrcode#~6.3.0",
"ionic": "1.3.1",
"ionic": "https://github.com/driftyco/ionic-v1.git",
"moment": "2.10.3",
"ng-lodash": "0.2.3",
"qrcode-decoder-js": "*",
"trezor-connect": "~1.0.1",
"trezor-connect": "~1.1.3",
"ng-csv": "~0.3.6",
"ionic-toast": "^0.4.1",
"angular-clipboard": "^1.4.2",
@ -23,6 +23,7 @@
"ngtouch": "^1.0.1"
},
"resolutions": {
"angular": "1.5.3"
"angular": "1.5.3",
"ionic": "298ad4b645"
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
'use strict';
angular.module('copayApp.controllers').controller('advancedSettingsController', function($scope, $rootScope, $log, $window, lodash, configService, uxLanguage, platformInfo, profileService, feeService, storageService, $ionicHistory, $timeout, $ionicScrollDelegate) {
angular.module('copayApp.controllers').controller('advancedSettingsController', function($scope, $log, configService) {
var updateConfig = function() {
var config = configService.getSync();
@ -11,7 +11,6 @@ angular.module('copayApp.controllers').controller('advancedSettingsController',
$scope.recentTransactionsEnabled = {
value: config.recentTransactions.enabled
};
$scope.hideNextSteps = {
value: config.hideNextSteps.enabled
};
@ -31,7 +30,7 @@ angular.module('copayApp.controllers').controller('advancedSettingsController',
$scope.nextStepsChange = function() {
var opts = {
hideNextSteps: {
enabled: $scope.hideNextSteps.value
enabled: $scope.hideNextSteps.value
},
};
configService.set(opts, function(err) {

View file

@ -29,15 +29,18 @@ angular.module('copayApp.controllers').controller('amazonCardsController',
var index = 0;
var gcds = $scope.giftCards;
lodash.forEach(gcds, function(dataFromStorage) {
if (dataFromStorage.status == 'PENDING') {
$log.debug("creating gift card");
if (dataFromStorage.status == 'PENDING' || dataFromStorage.status == 'invalid') {
$log.debug("Creating / Updating gift card");
$scope.updatingPending[dataFromStorage.invoiceId] = true;
amazonService.createGiftCard(dataFromStorage, function(err, giftCard) {
$scope.updatingPending[dataFromStorage.invoiceId] = false;
if (err) {
popupService.showAlert('Error creating gift card', err);
return;
}
if (giftCard.status != 'PENDING') {
var newData = {};

View file

@ -1,6 +1,6 @@
'use strict';
angular.module('copayApp.controllers').controller('amountController', function($scope, $filter, $timeout, $ionicScrollDelegate, $ionicHistory, gettextCatalog, platformInfo, lodash, configService, rateService, $stateParams, $window, $state, $log, txFormatService, ongoingProcess, popupService, bwcError, payproService, profileService, bitcore, amazonService) {
angular.module('copayApp.controllers').controller('amountController', function($scope, $filter, $timeout, $ionicScrollDelegate, $ionicHistory, gettextCatalog, platformInfo, lodash, configService, rateService, $stateParams, $window, $state, $log, txFormatService, ongoingProcess, popupService, bwcError, payproService, profileService, bitcore, amazonService, nodeWebkitService) {
var _cardId;
var unitToSatoshi;
var satToUnit;
@ -8,6 +8,7 @@ angular.module('copayApp.controllers').controller('amountController', function($
var satToBtc;
var SMALL_FONT_SIZE_LIMIT = 10;
var LENGTH_EXPRESSION_LIMIT = 19;
var isNW = platformInfo.isNW;
$scope.isChromeApp = platformInfo.isChromeApp;
$scope.$on('$ionicView.leave', function() {
@ -15,14 +16,14 @@ angular.module('copayApp.controllers').controller('amountController', function($
});
$scope.$on("$ionicView.beforeEnter", function(event, data) {
// Go to...
_cardId = data.stateParams.id; // Optional (BitPay Card ID)
$scope.nextStep = data.stateParams.nextStep;
$scope.currency = data.stateParams.currency;
$scope.forceCurrency = data.stateParams.forceCurrency;
$scope.showMenu = $ionicHistory.backView() && $ionicHistory.backView().stateName == 'tabs.send';
$scope.showMenu = $ionicHistory.backView() && ($ionicHistory.backView().stateName == 'tabs.send' ||
$ionicHistory.backView().stateName == 'tabs.bitpayCard');
$scope.recipientType = data.stateParams.recipientType || null;
$scope.toAddress = data.stateParams.toAddress;
$scope.toName = data.stateParams.toName;
@ -42,18 +43,20 @@ angular.module('copayApp.controllers').controller('amountController', function($
var reOp = /^[\*\+\-\/]$/;
var disableKeys = angular.element($window).on('keydown', function(e) {
if (!e.key) return;
if (e.which === 8) { // you can add others here inside brackets.
e.preventDefault();
$scope.removeDigit();
}
if (e.key && e.key.match(reNr))
if (e.key.match(reNr)) {
$scope.pushDigit(e.key);
else if (e.key && e.key.match(reOp))
} else if (e.key.match(reOp)) {
$scope.pushOperator(e.key);
else if (e.key && e.key == 'Enter')
} else if (e.keyCode === 86) {
if (e.ctrlKey || e.metaKey)
processClipboard();
} else if (e.keyCode === 13)
$scope.finish();
$timeout(function() {
@ -82,28 +85,35 @@ angular.module('copayApp.controllers').controller('amountController', function($
$scope.amount = (($stateParams.toAmount) * satToUnit).toFixed(unitDecimals);
}
processAmount($scope.amount);
processAmount();
$timeout(function() {
$ionicScrollDelegate.resize();
}, 10);
});
function paste(value) {
$scope.amount = value;
processAmount();
$timeout(function() {
$scope.$apply();
});
};
function processClipboard() {
if (!isNW) return;
var value = nodeWebkitService.readFromClipboard();
if (value && evaluate(value) > 0) paste(evaluate(value));
};
$scope.showSendMaxMenu = function() {
$scope.showSendMax = true;
};
$scope.sendMax = function() {
$scope.showSendMax = false;
$state.transitionTo('tabs.send.confirm', {
recipientType: $scope.recipientType,
toAmount: null,
toAddress: $scope.toAddress,
toName: $scope.toName,
toEmail: $scope.toEmail,
toColor: $scope.toColor,
useSendMax: true,
});
$scope.useSendMax = true;
$scope.finish();
};
$scope.toggleAlternative = function() {
@ -128,7 +138,7 @@ angular.module('copayApp.controllers').controller('amountController', function($
$scope.amount = ($scope.amount + digit).replace('..', '.');
checkFontSize();
processAmount($scope.amount);
processAmount();
};
$scope.pushOperator = function(operator) {
@ -156,7 +166,7 @@ angular.module('copayApp.controllers').controller('amountController', function($
$scope.removeDigit = function() {
$scope.amount = $scope.amount.slice(0, -1);
processAmount($scope.amount);
processAmount();
checkFontSize();
};
@ -166,17 +176,12 @@ angular.module('copayApp.controllers').controller('amountController', function($
checkFontSize();
};
function processAmount(val) {
if (!val) {
$scope.resetAmount();
return;
}
var formatedValue = format(val);
function processAmount() {
var formatedValue = format($scope.amount);
var result = evaluate(formatedValue);
$scope.allowSend = lodash.isNumber(result) && +result > 0;
if (lodash.isNumber(result)) {
$scope.globalResult = isExpression(val) ? '= ' + processResult(result) : '';
$scope.globalResult = isExpression($scope.amount) ? '= ' + processResult(result) : '';
$scope.amountResult = $filter('formatFiatAmount')(toFiat(result));
$scope.alternativeResult = txFormatService.formatAmount(fromFiat(result) * unitToSatoshi, true);
}
@ -223,8 +228,9 @@ angular.module('copayApp.controllers').controller('amountController', function($
if ($scope.nextStep) {
$state.transitionTo($scope.nextStep, {
id: _cardId,
amount: _amount,
currency: $scope.showAlternativeAmount ? $scope.alternativeIsoCode : ''
amount: $scope.useSendMax ? null : _amount,
currency: $scope.showAlternativeAmount ? $scope.alternativeIsoCode : $scope.unitName,
useSendMax: $scope.useSendMax
});
} else {
var amount = $scope.showAlternativeAmount ? fromFiat(_amount) : _amount;
@ -236,13 +242,15 @@ angular.module('copayApp.controllers').controller('amountController', function($
} else {
$state.transitionTo('tabs.send.confirm', {
recipientType: $scope.recipientType,
toAmount: (amount * unitToSatoshi).toFixed(0),
toAmount: $scope.useSendMax ? null : (amount * unitToSatoshi).toFixed(0),
toAddress: $scope.toAddress,
toName: $scope.toName,
toEmail: $scope.toEmail,
toColor: $scope.toColor,
useSendMax: $scope.useSendMax
});
}
}
$scope.useSendMax = null;
};
});

View file

@ -1,10 +1,6 @@
'use strict';
angular.module('copayApp.controllers').controller('backController', function($scope, $state, $stateParams, platformInfo) {
var isCordova = platformInfo.isCordova;
var isWP = platformInfo.isWP;
var usePushNotifications = isCordova && !isWP;
angular.module('copayApp.controllers').controller('backController', function($scope, $state, $stateParams) {
$scope.importGoBack = function() {
if ($stateParams.fromOnboarding) $state.go('onboarding.welcome');
@ -12,8 +8,7 @@ angular.module('copayApp.controllers').controller('backController', function($sc
};
$scope.onboardingMailSkip = function() {
if (!usePushNotifications) $state.go('onboarding.backupRequest');
else $state.go('onboarding.notifications');
$state.go('onboarding.backupRequest');
}
});

View file

@ -112,8 +112,13 @@ angular.module('copayApp.controllers').controller('buyAmazonController', functio
$scope.network = amazonService.getNetwork();
$scope.wallets = profileService.getWallets({
onlyComplete: true,
network: $scope.network
network: $scope.network,
hasFunds: true
});
if (lodash.isEmpty($scope.wallets)) {
showErrorAndBack('No wallets with funds');
return;
}
$scope.wallet = $scope.wallets[0]; // Default first wallet
});
@ -140,7 +145,13 @@ angular.module('copayApp.controllers').controller('buyAmazonController', functio
amazonService.createBitPayInvoice(dataSrc, function(err, dataInvoice) {
if (err) {
ongoingProcess.set('buyingGiftCard', false, statusChangeHandler);
showError('Error creating BitPay invoice', err);
if (err && err.message && err.message.match(/suspended/i)) {
showError('Service not available', 'Amazon Gift Card Service is not available at this moment. Please try back later.');
} else {
showError('Could not access Gift Card Service', err);
};
return;
}

View file

@ -1,6 +1,6 @@
'use strict';
angular.module('copayApp.controllers').controller('buyCoinbaseController', function($scope, $log, $state, $timeout, $ionicHistory, $ionicScrollDelegate, lodash, coinbaseService, popupService, profileService, ongoingProcess, walletService) {
angular.module('copayApp.controllers').controller('buyCoinbaseController', function($scope, $log, $state, $timeout, $ionicHistory, $ionicScrollDelegate, lodash, coinbaseService, popupService, profileService, ongoingProcess, walletService, txFormatService) {
var amount;
var currency;
@ -34,8 +34,8 @@ angular.module('copayApp.controllers').controller('buyCoinbaseController', funct
};
$scope.$on("$ionicView.beforeEnter", function(event, data) {
$scope.isFiat = data.stateParams.currency ? true : false;
var parsedAmount = coinbaseService.parseAmount(
$scope.isFiat = data.stateParams.currency != 'bits' && data.stateParams.currency != 'BTC' ? true : false;
var parsedAmount = txFormatService.parseAmount(
data.stateParams.amount,
data.stateParams.currency);
@ -48,6 +48,11 @@ angular.module('copayApp.controllers').controller('buyCoinbaseController', funct
onlyComplete: true,
network: $scope.network
});
if (lodash.isEmpty($scope.wallets)) {
showErrorAndBack('No wallets available');
return;
}
$scope.wallet = $scope.wallets[0]; // Default first wallet
ongoingProcess.set('connectingCoinbase', true);

View file

@ -1,6 +1,6 @@
'use strict';
angular.module('copayApp.controllers').controller('buyGlideraController', function($scope, $log, $state, $timeout, $ionicHistory, lodash, glideraService, popupService, profileService, ongoingProcess, walletService, platformInfo) {
angular.module('copayApp.controllers').controller('buyGlideraController', function($scope, $log, $state, $timeout, $ionicHistory, lodash, glideraService, popupService, profileService, ongoingProcess, walletService, platformInfo, txFormatService) {
var amount;
var currency;
@ -36,8 +36,8 @@ angular.module('copayApp.controllers').controller('buyGlideraController', functi
};
$scope.$on("$ionicView.beforeEnter", function(event, data) {
$scope.isFiat = data.stateParams.currency ? true : false;
var parsedAmount = glideraService.parseAmount(
$scope.isFiat = data.stateParams.currency != 'bits' && data.stateParams.currency != 'BTC' ? true : false;
var parsedAmount = txFormatService.parseAmount(
data.stateParams.amount,
data.stateParams.currency);
@ -50,6 +50,11 @@ angular.module('copayApp.controllers').controller('buyGlideraController', functi
onlyComplete: true,
network: $scope.network
});
if (lodash.isEmpty($scope.wallets)) {
showErrorAndBack('No wallets available');
return;
}
$scope.wallet = $scope.wallets[0]; // Default first wallet
ongoingProcess.set('connectingGlidera', true);

View file

@ -1,11 +1,7 @@
'use strict';
angular.module('copayApp.controllers').controller('createController',
function($scope, $rootScope, $timeout, $log, lodash, $state, $ionicScrollDelegate, $ionicHistory, profileService, configService, gettextCatalog, ledger, trezor, platformInfo, derivationPathHelper, ongoingProcess, walletService, storageService, popupService, appConfigService) {
var isChromeApp = platformInfo.isChromeApp;
var isCordova = platformInfo.isCordova;
var isDevel = platformInfo.isDevel;
function($scope, $rootScope, $timeout, $log, lodash, $state, $ionicScrollDelegate, $ionicHistory, profileService, configService, gettextCatalog, ledger, trezor, intelTEE, derivationPathHelper, ongoingProcess, walletService, storageService, popupService, appConfigService) {
/* For compressed keys, m*73 + n*34 <= 496 */
var COPAYER_PAIR_LIMITS = {
@ -67,9 +63,11 @@ angular.module('copayApp.controllers').controller('createController',
var seedOptions = [{
id: 'new',
label: gettextCatalog.getString('Random'),
}, {
supportsTestnet: true
}, {
id: 'set',
label: gettextCatalog.getString('Specify Recovery Phrase...'),
supportsTestnet: false
}];
$scope.seedSource = seedOptions[0];
@ -81,16 +79,26 @@ angular.module('copayApp.controllers').controller('createController',
*/
if (appConfigService.name == 'copay') {
if (n > 1 && isChromeApp) {
if (n > 1 && walletService.externalSource.ledger.supported)
seedOptions.push({
id: 'ledger',
label: 'Ledger Hardware Wallet',
id: walletService.externalSource.ledger.id,
label: walletService.externalSource.ledger.longName,
supportsTestnet: walletService.externalSource.ledger.supportsTestnet
});
if (walletService.externalSource.trezor.supported) {
seedOptions.push({
id: walletService.externalSource.trezor.id,
label: walletService.externalSource.trezor.longName,
supportsTestnet: walletService.externalSource.trezor.supportsTestnet
});
}
if (isChromeApp || isDevel) {
if (walletService.externalSource.intelTEE.supported) {
seedOptions.push({
id: 'trezor',
label: 'Trezor Hardware Wallet',
id: walletService.externalSource.intelTEE.id,
label: walletService.externalSource.intelTEE.longName,
supportsTestnet: walletService.externalSource.intelTEE.supportsTestnet
});
}
}
@ -151,23 +159,37 @@ angular.module('copayApp.controllers').controller('createController',
return;
}
if ($scope.seedSource.id == 'ledger' || $scope.seedSource.id == 'trezor') {
if ($scope.seedSource.id == walletService.externalSource.ledger.id || $scope.seedSource.id == walletService.externalSource.trezor.id || $scope.seedSource.id == walletService.externalSource.intelTEE.id) {
var account = $scope.formData.account;
if (!account || account < 1) {
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Invalid account number'));
return;
}
if ($scope.seedSource.id == 'trezor')
if ($scope.seedSource.id == walletService.externalSource.trezor.id || $scope.seedSource.id == walletService.externalSource.intelTEE.id)
account = account - 1;
opts.account = account;
ongoingProcess.set('connecting' + $scope.seedSource.id, true);
ongoingProcess.set('connecting ' + $scope.seedSource.id, true);
var src = $scope.seedSource.id == 'ledger' ? ledger : trezor;
var src;
switch ($scope.seedSource.id) {
case walletService.externalSource.ledger.id:
src = ledger;
break;
case walletService.externalSource.trezor.id:
src = trezor;
break;
case walletService.externalSource.intelTEE.id:
src = intelTEE;
break;
default:
this.error = gettextCatalog.getString('Invalid seed source id: ' + $scope.seedSource.id);
return;
}
src.getInfoForNewWallet(opts.n > 1, account, function(err, lopts) {
ongoingProcess.set('connecting' + $scope.seedSource.id, false);
src.getInfoForNewWallet(opts.n > 1, account, opts.networkName, function(err, lopts) {
ongoingProcess.set('connecting ' + $scope.seedSource.id, false);
if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), err);
return;
@ -211,6 +233,6 @@ angular.module('copayApp.controllers').controller('createController',
}, 100);
} else $state.go('tabs.home');
});
}, 100);
}, 300);
}
});

View file

@ -8,8 +8,8 @@ angular.module('copayApp.controllers').controller('importController',
var errors = bwcService.getErrors();
$scope.init = function() {
$scope.isDevel = platformInfo.isDevel;
$scope.isChromeApp = platformInfo.isChromeApp;
$scope.supportsLedger = platformInfo.supportsLedger;
$scope.supportsTrezor = platformInfo.supportsTrezor;
$scope.isCordova = platformInfo.isCordova;
$scope.formData = {};
$scope.formData.bwsurl = defaults.bws.url;
@ -23,17 +23,17 @@ angular.module('copayApp.controllers').controller('importController',
$scope.seedOptions = [];
if ($scope.isChromeApp) {
if ($scope.supportsLedger) {
$scope.seedOptions.push({
id: 'ledger',
label: 'Ledger Hardware Wallet',
id: walletService.externalSource.ledger.id,
label: walletService.externalSource.ledger.longName,
});
}
if ($scope.isChromeApp || $scope.isDevel) {
if ($scope.supportsTrezor) {
$scope.seedOptions.push({
id: 'trezor',
label: 'Trezor Hardware Wallet',
id: walletService.externalSource.trezor.id,
label: walletService.externalSource.trezor.longName,
});
$scope.formData.seedSource = $scope.seedOptions[0];
}
@ -260,14 +260,14 @@ angular.module('copayApp.controllers').controller('importController',
};
$scope.importTrezor = function(account, isMultisig) {
trezor.getInfoForNewWallet(isMultisig, account, function(err, lopts) {
trezor.getInfoForNewWallet(isMultisig, account, 'livenet', function(err, lopts) {
ongoingProcess.clear();
if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), err);
return;
}
lopts.externalSource = 'trezor';
lopts.externalSource = walletService.externalSource.trezor.id;
lopts.bwsurl = $scope.formData.bwsurl;
ongoingProcess.set('importingWallet', true);
$log.debug('Import opts', lopts);
@ -293,7 +293,7 @@ angular.module('copayApp.controllers').controller('importController',
var account = $scope.formData.account;
if ($scope.formData.seedSource.id == 'trezor') {
if ($scope.formData.seedSource.id == walletService.externalSource.trezor.id) {
if (account < 1) {
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Invalid account number'));
return;
@ -302,11 +302,11 @@ angular.module('copayApp.controllers').controller('importController',
}
switch ($scope.formData.seedSource.id) {
case ('ledger'):
case (walletService.externalSource.ledger.id):
ongoingProcess.set('connectingledger', true);
$scope.importLedger(account);
break;
case ('trezor'):
case (walletService.externalSource.trezor.id):
ongoingProcess.set('connectingtrezor', true);
$scope.importTrezor(account, $scope.formData.isMultisig);
break;
@ -316,14 +316,14 @@ angular.module('copayApp.controllers').controller('importController',
};
$scope.importLedger = function(account) {
ledger.getInfoForNewWallet(true, account, function(err, lopts) {
ledger.getInfoForNewWallet(true, account, 'livenet', function(err, lopts) {
ongoingProcess.clear();
if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), err);
return;
}
lopts.externalSource = 'ledger';
lopts.externalSource = lopts.externalSource = walletService.externalSource.ledger.id;
lopts.bwsurl = $scope.formData.bwsurl;
ongoingProcess.set('importingWallet', true);
$log.debug('Import opts', lopts);

View file

@ -1,10 +1,7 @@
'use strict';
angular.module('copayApp.controllers').controller('joinController',
function($scope, $rootScope, $timeout, $state, $ionicHistory, $ionicScrollDelegate, profileService, configService, storageService, applicationService, gettextCatalog, lodash, ledger, trezor, platformInfo, derivationPathHelper, ongoingProcess, walletService, $log, $stateParams, popupService, appConfigService) {
var isChromeApp = platformInfo.isChromeApp;
var isDevel = platformInfo.isDevel;
function($scope, $rootScope, $timeout, $state, $ionicHistory, $ionicScrollDelegate, profileService, configService, storageService, applicationService, gettextCatalog, lodash, ledger, trezor, intelTEE, derivationPathHelper, ongoingProcess, walletService, $log, $stateParams, popupService, appConfigService) {
var self = this;
var defaults = configService.getDefaults();
@ -64,17 +61,24 @@ angular.module('copayApp.controllers').controller('joinController',
*/
if (appConfigService.name == 'copay') {
if (isChromeApp) {
if (walletService.externalSource.ledger.supported) {
self.seedOptions.push({
id: 'ledger',
label: 'Ledger Hardware Wallet',
id: walletService.externalSource.ledger.id,
label: walletService.externalSource.ledger.longName
});
}
if (isChromeApp || isDevel) {
if (walletService.externalSource.trezor.supported) {
self.seedOptions.push({
id: 'trezor',
label: 'Trezor Hardware Wallet',
id: walletService.externalSource.trezor.id,
label: walletService.externalSource.trezor.longName
});
}
if (walletService.externalSource.intelTEE.supported) {
seedOptions.push({
id: walletService.externalSource.intelTEE.id,
label: walletService.externalSource.intelTEE.longName
});
}
}
@ -97,7 +101,7 @@ angular.module('copayApp.controllers').controller('joinController',
var opts = {
secret: form.secret.$modelValue,
myName: form.myName.$modelValue,
bwsurl: $scope.bwsurl,
bwsurl: $scope.bwsurl
}
var setSeed = self.seedSourceId == 'set';
@ -130,21 +134,38 @@ angular.module('copayApp.controllers').controller('joinController',
return;
}
if (self.seedSourceId == 'ledger' || self.seedSourceId == 'trezor') {
if (self.seedSourceId == walletService.externalSource.ledger.id || self.seedSourceId == walletService.externalSource.trezor.id || self.seedSourceId == walletService.externalSource.intelTEE.id) {
var account = $scope.account;
if (!account || account < 1) {
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Invalid account number'));
return;
}
if (self.seedSourceId == 'trezor')
if (self.seedSourceId == walletService.externalSource.trezor.id || self.seedSourceId == walletService.externalSource.intelTEE.id)
account = account - 1;
opts.account = account;
opts.isMultisig = true;
ongoingProcess.set('connecting' + self.seedSourceId, true);
var src = self.seedSourceId == 'ledger' ? ledger : trezor;
src.getInfoForNewWallet(true, account, function(err, lopts) {
var src;
switch (self.seedSourceId) {
case walletService.externalSource.ledger.id:
src = ledger;
break;
case walletService.externalSource.trezor.id:
src = trezor;
break;
case walletService.externalSource.intelTEE.id:
src = intelTEE;
break;
default:
this.error = gettextCatalog.getString('Invalid seed source id: ' + self.seedSourceId);
return;
}
// TODO: cannot currently join an intelTEE testnet wallet (need to detect from the secret)
src.getInfoForNewWallet(true, account, 'livenet', function(err, lopts) {
ongoingProcess.set('connecting' + self.seedSourceId, false);
if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), err);

View file

@ -0,0 +1,121 @@
'use strict';
angular.module('copayApp.controllers').controller('lockSetupController', function($state, $scope, $timeout, $log, configService, popupService, gettextCatalog, appConfigService, fingerprintService, profileService, lodash) {
function init() {
$scope.options = [
{
method: null,
label: gettextCatalog.getString('Disabled'),
},
{
method: 'pin',
label: gettextCatalog.getString('Lock by PIN'),
needsBackup: null,
},
];
if (fingerprintService.isAvailable()) {
$scope.options.push({
method: 'fingerprint',
label: gettextCatalog.getString('Lock by Fingerprint'),
needsBackup: null,
});
}
var config = configService.getSync();
var method = config.lock && config.lock.method;
if (!method) $scope.currentOption = $scope.options[0];
else $scope.currentOption = lodash.find($scope.options, {
'method': method
});
processWallets();
};
$scope.$on("$ionicView.beforeEnter", function(event) {
init();
});
function processWallets() {
var wallets = profileService.getWallets();
var singleLivenetWallet = wallets.length == 1 && wallets[0].network == 'livenet' && wallets[0].needsBackup;
var atLeastOneLivenetWallet = lodash.any(wallets, function(w) {
return w.network == 'livenet' && w.needsBackup;
});
if (singleLivenetWallet) {
$scope.errorMsg = gettextCatalog.getString('Backup your wallet before using this function');
disableOptsUntilBackup();
} else if (atLeastOneLivenetWallet) {
$scope.errorMsg = gettextCatalog.getString('Backup all livenet wallets before using this function');
disableOptsUntilBackup();
} else {
enableOptsAfterBackup();
$scope.errorMsg = null;
}
function enableOptsAfterBackup() {
$scope.options[1].needsBackup = false;
if ($scope.options[2]) $scope.options[2].needsBackup = false;
};
function disableOptsUntilBackup() {
$scope.options[1].needsBackup = true;
if ($scope.options[2]) $scope.options[2].needsBackup = true;
};
$timeout(function() {
$scope.$apply();
});
};
$scope.select = function(selectedMethod) {
var config = configService.getSync();
var savedMethod = config.lock && config.lock.method;
if (!selectedMethod)
saveConfig();
else if (selectedMethod == 'fingerprint') {
if (savedMethod == 'pin') {
askForDisablePin(function(disablePin) {
if (disablePin) saveConfig('fingerprint');
else init();
});
} else saveConfig('fingerprint');
} else if (selectedMethod == 'pin') {
if (savedMethod == 'pin') return;
$state.transitionTo('tabs.pin', {
fromSettings: true,
locking: savedMethod == 'pin' ? false : true
});
}
$timeout(function() {
$scope.$apply();
});
};
function askForDisablePin(cb) {
var message = gettextCatalog.getString('{{appName}} startup is locked by PIN. Are you sure you want to disable it?', {
appName: appConfigService.nameCase
});
var okText = gettextCatalog.getString('Continue');
var cancelText = gettextCatalog.getString('Cancel');
popupService.showConfirm(null, message, okText, cancelText, function(ok) {
if (!ok) return cb(false);
return cb(true);
});
};
function saveConfig(method) {
var opts = {
lock: {
method: method || null,
value: null,
}
};
configService.set(opts, function(err) {
if (err) $log.debug(err);
});
};
});

View file

@ -0,0 +1,17 @@
'use strict';
angular.module('copayApp.controllers').controller('lockedViewController', function($state, $scope, $ionicHistory, fingerprintService, appConfigService, gettextCatalog) {
$scope.$on("$ionicView.beforeEnter", function(event) {
$scope.title = appConfigService.nameCase + ' ' + gettextCatalog.getString('is locked');
$scope.appName = appConfigService.name;
});
$scope.requestFingerprint = function() {
fingerprintService.check('unlockingApp', function(err) {
if (err) return;
$state.transitionTo('tabs.home').then(function() {
$ionicHistory.clearHistory();
});
});
};
});

View file

@ -1,6 +1,6 @@
'use strict';
angular.module('copayApp.controllers').controller('collectEmailController', function($scope, $state, $timeout, $stateParams, $ionicConfig, profileService, configService, walletService, platformInfo, pushNotificationsService) {
angular.module('copayApp.controllers').controller('collectEmailController', function($scope, $state, $timeout, $stateParams, $ionicConfig, profileService, configService, walletService) {
$scope.$on("$ionicView.beforeLeave", function() {
$ionicConfig.views.swipeBackEnabled(true);
@ -10,11 +10,6 @@ angular.module('copayApp.controllers').controller('collectEmailController', func
$ionicConfig.views.swipeBackEnabled(false);
});
var isCordova = platformInfo.isCordova;
var isWP = platformInfo.isWP;
var usePushNotifications = isCordova && !isWP;
var requiresOptIn = platformInfo.isIOS;
var wallet = profileService.getWallet($stateParams.walletId);
var walletId = wallet.credentials.walletId;
$scope.data = {};
@ -37,20 +32,9 @@ angular.module('copayApp.controllers').controller('collectEmailController', func
};
$scope.goNextView = function() {
if (!usePushNotifications) {
$state.go('onboarding.backupRequest', {
walletId: walletId
});
} else if (requiresOptIn) {
$state.go('onboarding.notifications', {
walletId: walletId
});
} else {
pushNotificationsService.init();
$state.go('onboarding.backupRequest', {
walletId: walletId
});
}
$state.go('onboarding.backupRequest', {
walletId: walletId
});
};
$scope.confirm = function(emailForm) {

View file

@ -1,53 +0,0 @@
'use strict';
angular.module('copayApp.controllers').controller('notificationsController', function($scope, $state, $timeout, $stateParams, $ionicConfig, profileService, configService, $interval, pushNotificationsService) {
$scope.$on("$ionicView.enter", function() {
$ionicConfig.views.swipeBackEnabled(false);
});
$scope.$on("$ionicView.beforeLeave", function() {
$ionicConfig.views.swipeBackEnabled(true);
});
$scope.walletId = $stateParams.walletId;
$scope.allowNotif = function() {
$scope.notificationDialogOpen = true;
$timeout(function() {
pushNotificationsService.init();
});
$scope.notificationPromise = $interval(function() {
PushNotification.hasPermission(function(data) {
if (data.isEnabled) {
$interval.cancel($scope.notificationPromise);
$state.go('onboarding.backupRequest', {
walletId: $scope.walletId
});
}
});
}, 100);
}
$scope.continue = function() {
$interval.cancel($scope.notificationPromise);
$state.go('onboarding.backupRequest', {
walletId: $scope.walletId
});
}
$scope.disableNotif = function() {
var opts = {
pushNotifications: {
enabled: false
}
};
configService.set(opts, function(err) {
if (err) $log.warn(err);
$state.go('onboarding.backupRequest', {
walletId: $scope.walletId
});
});
};
});

View file

@ -1,10 +1,6 @@
'use strict';
angular.module('copayApp.controllers').controller('tourController',
function($scope, $state, $log, $timeout, $filter, ongoingProcess, platformInfo, profileService, rateService, popupService, gettextCatalog) {
var isCordova = platformInfo.isCordova;
var isWP = platformInfo.isWP;
var usePushNotifications = isCordova && !isWP;
function($scope, $state, $log, $timeout, $filter, ongoingProcess, profileService, rateService, popupService, gettextCatalog) {
$scope.data = {
index: 0
@ -43,38 +39,34 @@ angular.module('copayApp.controllers').controller('tourController',
var retryCount = 0;
$scope.createDefaultWallet = function() {
ongoingProcess.set('creatingWallet', true);
profileService.createDefaultWallet(function(err, walletClient) {
if (err) {
$log.warn(err);
$timeout(function() {
profileService.createDefaultWallet(function(err, walletClient) {
if (err) {
$log.warn(err);
return $timeout(function() {
$log.warn('Retrying to create default wallet.....:' + ++retryCount);
if (retryCount > 3) {
ongoingProcess.set('creatingWallet', false);
popupService.showAlert(
gettextCatalog.getString('Cannot Create Wallet'), err,
function() {
retryCount = 0;
return $scope.createDefaultWallet();
}, gettextCatalog.getString('Retry'));
} else {
return $scope.createDefaultWallet();
}
}, 2000);
};
ongoingProcess.set('creatingWallet', false);
var wallet = walletClient;
var walletId = wallet.credentials.walletId;
if (!usePushNotifications) {
return $timeout(function() {
$log.warn('Retrying to create default wallet.....:' + ++retryCount);
if (retryCount > 3) {
ongoingProcess.set('creatingWallet', false);
popupService.showAlert(
gettextCatalog.getString('Cannot Create Wallet'), err,
function() {
retryCount = 0;
return $scope.createDefaultWallet();
}, gettextCatalog.getString('Retry'));
} else {
return $scope.createDefaultWallet();
}
}, 2000);
};
ongoingProcess.set('creatingWallet', false);
var wallet = walletClient;
var walletId = wallet.credentials.walletId;
$state.go('onboarding.backupRequest', {
walletId: walletId
});
} else {
$state.go('onboarding.notifications', {
walletId: walletId
});
}
});
});
}, 300);
};
$scope.goBack = function() {

186
src/js/controllers/pin.js Normal file
View file

@ -0,0 +1,186 @@
'use strict';
angular.module('copayApp.controllers').controller('pinController', function($state, $interval, $stateParams, $ionicHistory, $timeout, $scope, $log, configService, appConfigService) {
var ATTEMPT_LIMIT = 3;
var ATTEMPT_LOCK_OUT_TIME = 5 * 60;
$scope.$on("$ionicView.beforeEnter", function(event) {
$scope.currentPin = $scope.confirmPin = '';
$scope.fromSettings = $stateParams.fromSettings == 'true' ? true : false;
$scope.locking = $stateParams.locking == 'true' ? true : false;
$scope.match = $scope.error = $scope.disableButtons = false;
$scope.currentAttempts = 0;
$scope.appName = appConfigService.name;
});
$scope.$on("$ionicView.enter", function(event) {
configService.whenAvailable(function(config) {
if (!config.lock) return;
$scope.bannedUntil = config.lock.bannedUntil || null;
if ($scope.bannedUntil) {
var now = Math.floor(Date.now() / 1000);
if (now < $scope.bannedUntil) {
$scope.error = $scope.disableButtons = true;
lockTimeControl($scope.bannedUntil);
}
}
});
});
function checkAttempts() {
$scope.currentAttempts += 1;
$log.debug('Attempts to unlock:', $scope.currentAttempts);
if ($scope.currentAttempts === ATTEMPT_LIMIT) {
$scope.currentAttempts = 0;
var limitTime = Math.floor(Date.now() / 1000) + ATTEMPT_LOCK_OUT_TIME;
var config = configService.getSync();
var opts = {
lock: {
method: 'pin',
value: config.lock.value,
bannedUntil: limitTime,
attempts: config.lock.attempts + 1,
}
};
configService.set(opts, function(err) {
if (err) $log.debug(err);
lockTimeControl(limitTime);
});
}
};
function lockTimeControl(limitTime) {
$scope.limitTimeExpired = false;
setExpirationTime();
var countDown = $interval(function() {
setExpirationTime();
}, 1000);
function setExpirationTime() {
var now = Math.floor(Date.now() / 1000);
if (now > limitTime) {
$scope.limitTimeExpired = true;
if (countDown) reset();
} else {
$scope.disableButtons = true;
var totalSecs = limitTime - now;
var m = Math.floor(totalSecs / 60);
var s = totalSecs % 60;
$scope.expires = ('0' + m).slice(-2) + ":" + ('0' + s).slice(-2);
}
};
function reset() {
$scope.expires = $scope.error = $scope.disableButtons = null;
$scope.currentPin = $scope.confirmPin = '';
$interval.cancel(countDown);
$timeout(function() {
$scope.$apply();
});
return;
};
};
$scope.getFilledClass = function(limit) {
return $scope.currentPin.length >= limit ? 'filled-' + $scope.appName : null;
};
$scope.delete = function() {
if ($scope.disableButtons) return;
if ($scope.currentPin.length > 0) {
$scope.currentPin = $scope.currentPin.substring(0, $scope.currentPin.length - 1);
$scope.error = false;
$scope.updatePin();
}
};
$scope.isComplete = function() {
if ($scope.currentPin.length < 4) return false;
else return true;
};
$scope.updatePin = function(value) {
if ($scope.disableButtons) return;
$scope.error = false;
if (value && !$scope.isComplete()) {
$scope.currentPin = $scope.currentPin + value;
$timeout(function() {
$scope.$apply();
});
}
$scope.save();
};
$scope.save = function() {
if (!$scope.isComplete()) return;
var config = configService.getSync();
$scope.match = config.lock && config.lock.method == 'pin' && config.lock.value == $scope.currentPin ? true : false;
if (!$scope.locking) {
if ($scope.match) {
if ($scope.fromSettings) saveSettings();
else {
saveSettings('pin', $scope.currentPin);
$scope.error = false;
}
} else {
$timeout(function() {
$scope.confirmPin = $scope.currentPin = '';
$scope.error = true;
}, 200);
checkAttempts();
}
} else {
processCodes();
}
};
function processCodes() {
if (!$scope.confirmPin) {
$timeout(function() {
$scope.confirmPin = $scope.currentPin;
$scope.currentPin = '';
}, 200);
} else {
if ($scope.confirmPin == $scope.currentPin)
saveSettings('pin', $scope.confirmPin);
else {
$scope.confirmPin = $scope.currentPin = '';
$scope.error = true;
}
}
$timeout(function() {
$scope.$apply();
});
};
function saveSettings(method, value) {
var config = configService.getSync();
var attempts = config.lock && config.lock.attempts ? config.lock.attempts : 0;
var opts = {
lock: {
method: method || null,
value: value || null,
bannedUntil: null,
attempts: attempts + 1,
}
};
configService.set(opts, function(err) {
if (err) $log.debug(err);
$scope.close();
});
};
$scope.close = function(delay) {
$timeout(function() {
var shouldReturn = $ionicHistory.viewHistory().backView && $ionicHistory.viewHistory().backView.stateName != 'starting';
if (shouldReturn)
$ionicHistory.goBack()
else
$state.go('tabs.home');
}, delay || 1);
};
});

View file

@ -89,9 +89,6 @@ angular.module('copayApp.controllers').controller('preferencesController',
value: $scope.wallet.balanceHidden
};
if (wallet.isPrivKeyExternal)
$scope.externalSource = wallet.getPrivKeyExternalSourceName() == 'ledger' ? 'Ledger' : 'Trezor';
$scope.touchIdAvailable = fingerprintService.isAvailable();
$scope.touchIdEnabled = {
value: config.touchIdFor ? config.touchIdFor[walletId] : null

View file

@ -0,0 +1,28 @@
'use strict';
angular.module('copayApp.controllers').controller('preferencesExternalController', function($scope, $stateParams, lodash, gettextCatalog, popupService, profileService, walletService) {
var wallet = profileService.getWallet($stateParams.walletId);
$scope.externalSource = lodash.find(walletService.externalSource, function(source) {
return source.id == wallet.getPrivKeyExternalSourceName();
});
if ($scope.externalSource.isEmbeddedHardware) {
$scope.hardwareConnected = $scope.externalSource.version.length > 0;
$scope.showMneumonicFromHardwarePopup = function() {
var title = gettextCatalog.getString('Warning!');
var message = gettextCatalog.getString('Are you being watched? Anyone with your recovery phrase can access or spend your bitcoin.');
popupService.showConfirm(title, message, null, null, function(res) {
if (res) {
walletService.showMneumonicFromHardware(wallet, function(err) {
if (err) {
popupService.showAlert(gettextCatalog.getString('Error'), err.message || err);
}
});
}
});
};
}
});

View file

@ -1,7 +1,7 @@
'use strict';
angular.module('copayApp.controllers').controller('preferencesInformation',
function($scope, $log, $ionicHistory, platformInfo, lodash, profileService, configService, $stateParams, $state) {
function($scope, $log, $ionicHistory, platformInfo, lodash, profileService, configService, $stateParams, $state, walletService) {
var wallet = profileService.getWallet($stateParams.walletId);
$scope.wallet = wallet;
@ -44,5 +44,13 @@ angular.module('copayApp.controllers').controller('preferencesInformation',
$scope.M = c.m;
$scope.N = c.n;
$scope.pubKeys = lodash.pluck(c.publicKeyRing, 'xPubKey');
$scope.externalSource = null;
if (wallet.isPrivKeyExternal()) {
$scope.externalSource = lodash.find(walletService.externalSource, function(source) {
return source.id == wallet.getPrivKeyExternalSourceName();
}).name;
}
});
});

View file

@ -1,6 +1,6 @@
'use strict';
angular.module('copayApp.controllers').controller('sellCoinbaseController', function($scope, $log, $state, $timeout, $ionicHistory, $ionicScrollDelegate, lodash, coinbaseService, popupService, profileService, ongoingProcess, walletService, appConfigService, configService) {
angular.module('copayApp.controllers').controller('sellCoinbaseController', function($scope, $log, $state, $timeout, $ionicHistory, $ionicScrollDelegate, lodash, coinbaseService, popupService, profileService, ongoingProcess, walletService, appConfigService, configService, txFormatService) {
var amount;
var currency;
@ -117,8 +117,8 @@ angular.module('copayApp.controllers').controller('sellCoinbaseController', func
};
$scope.$on("$ionicView.beforeEnter", function(event, data) {
$scope.isFiat = data.stateParams.currency ? true : false;
var parsedAmount = coinbaseService.parseAmount(
$scope.isFiat = data.stateParams.currency != 'bits' && data.stateParams.currency != 'BTC' ? true : false;
var parsedAmount = txFormatService.parseAmount(
data.stateParams.amount,
data.stateParams.currency);
@ -133,8 +133,15 @@ angular.module('copayApp.controllers').controller('sellCoinbaseController', func
$scope.wallets = profileService.getWallets({
m: 1, // Only 1-signature wallet
onlyComplete: true,
network: $scope.network
network: $scope.network,
hasFunds: true,
minAmount: parsedAmount.amountSat
});
if (lodash.isEmpty($scope.wallets)) {
showErrorAndBack('Insufficient funds');
return;
}
$scope.wallet = $scope.wallets[0]; // Default first wallet
ongoingProcess.set('connectingCoinbase', true);

View file

@ -1,6 +1,6 @@
'use strict';
angular.module('copayApp.controllers').controller('sellGlideraController', function($scope, $log, $state, $timeout, $ionicHistory, lodash, glideraService, popupService, profileService, ongoingProcess, walletService, configService, platformInfo) {
angular.module('copayApp.controllers').controller('sellGlideraController', function($scope, $log, $state, $timeout, $ionicHistory, lodash, glideraService, popupService, profileService, ongoingProcess, walletService, configService, platformInfo, txFormatService) {
var amount;
var currency;
@ -36,8 +36,8 @@ angular.module('copayApp.controllers').controller('sellGlideraController', funct
};
$scope.$on("$ionicView.beforeEnter", function(event, data) {
$scope.isFiat = data.stateParams.currency ? true : false;
var parsedAmount = glideraService.parseAmount(
$scope.isFiat = data.stateParams.currency != 'bits' && data.stateParams.currency != 'BTC' ? true : false;
var parsedAmount = txFormatService.parseAmount(
data.stateParams.amount,
data.stateParams.currency);
@ -49,8 +49,15 @@ angular.module('copayApp.controllers').controller('sellGlideraController', funct
$scope.wallets = profileService.getWallets({
m: 1, // Only 1-signature wallet
onlyComplete: true,
network: $scope.network
network: $scope.network,
hasFunds: true,
minAmount: parsedAmount.amountSat
});
if (lodash.isEmpty($scope.wallets)) {
showErrorAndBack('Insufficient funds');
return;
}
$scope.wallet = $scope.wallets[0]; // Default first wallet
ongoingProcess.set('connectingGlidera', true);

View file

@ -145,6 +145,22 @@ angular.module('copayApp.controllers').controller('tabReceiveController', functi
});
};
$scope.shouldShowReceiveAddressFromHardware = function() {
var wallet = $scope.wallet;
if (wallet.isPrivKeyExternal() && wallet.credentials.hwInfo) {
return (wallet.credentials.hwInfo.name == walletService.externalSource.intelTEE.id);
} else {
return false;
}
};
$scope.showReceiveAddressFromHardware = function() {
var wallet = $scope.wallet;
if (wallet.isPrivKeyExternal() && wallet.credentials.hwInfo) {
walletService.showReceiveAddressFromHardware(wallet, $scope.addr, function(){});
}
};
$scope.$on("$ionicView.beforeEnter", function(event, data) {
$scope.wallets = profileService.getWallets();
@ -165,6 +181,10 @@ angular.module('copayApp.controllers').controller('tabReceiveController', functi
return w.id == $scope.wallet.id;
});
if (w) $scope.updateCurrentWallet();
else if (screen.width > 700 && screen.height > 700 && $scope.wallets[0]) {
$scope.setWallet(0)
$scope.walletPosition(0);
}
}
});

View file

@ -51,7 +51,12 @@ angular.module('copayApp.controllers').controller('tabSettingsController', funct
$scope.$on("$ionicView.beforeEnter", function(event, data) {
$scope.isCordova = platformInfo.isCordova;
$scope.isDevel = platformInfo.isDevel;
$scope.appName = appConfigService.nameCase;
configService.whenAvailable(function(config) {
$scope.locked = config.lock && config.lock.method;
$scope.method = $scope.locked ? config.lock.method.charAt(0).toUpperCase() + config.lock.method.slice(1) : gettextCatalog.getString('Disabled');
});
});
$scope.$on("$ionicView.enter", function(event, data) {

View file

@ -1,10 +1,11 @@
'use strict';
angular.module('copayApp.controllers').controller('topUpController', function($scope, $log, $state, $timeout, $ionicHistory, lodash, popupService, profileService, ongoingProcess, walletService, configService, platformInfo, bitpayService, bitpayCardService, payproService, bwcError) {
angular.module('copayApp.controllers').controller('topUpController', function($scope, $log, $state, $timeout, $ionicHistory, lodash, popupService, profileService, ongoingProcess, walletService, configService, platformInfo, bitpayService, bitpayCardService, payproService, bwcError, txFormatService, sendMaxService) {
var amount;
var currency;
var cardId;
var sendMax;
$scope.isCordova = platformInfo.isCordova;
@ -49,16 +50,32 @@ angular.module('copayApp.controllers').controller('topUpController', function($s
}
};
var checkDailyLimit = function() {
if (currency == 'USD' && amount > 10000) {
showErrorAndBack('Top up is limited to USD 10,000 per day');
return;
}
bitpayCardService.getRates('USD', function(err, data) {
if (err) $log.error(err);
$scope.rate = data.rate;
if (currency == 'BTC' && (amount * data.rate) > 10000) {
showErrorAndBack('Top up is limited to USD 10,000 per day');
return;
}
});
};
$scope.$on("$ionicView.beforeEnter", function(event, data) {
$scope.isFiat = data.stateParams.currency ? true : false;
cardId = data.stateParams.id;
sendMax = data.stateParams.useSendMax;
if (!cardId) {
showErrorAndBack('No card selected');
return;
}
var parsedAmount = bitpayCardService.parseAmount(
var parsedAmount = txFormatService.parseAmount(
data.stateParams.amount,
data.stateParams.currency);
@ -80,11 +97,6 @@ angular.module('copayApp.controllers').controller('topUpController', function($s
}
$scope.onWalletSelect($scope.wallets[0]); // Default first wallet
bitpayCardService.getRates(currency, function(err, data) {
if (err) $log.error(err);
$scope.rate = data.rate;
});
bitpayCardService.get({ cardId: cardId, noRefresh: true }, function(err, card) {
if (err) {
showErrorAndBack(err);
@ -189,6 +201,32 @@ angular.module('copayApp.controllers').controller('topUpController', function($s
$scope.onWalletSelect = function(wallet) {
$scope.wallet = wallet;
if (sendMax) {
ongoingProcess.set('retrievingInputs', true);
sendMaxService.getInfo($scope.wallet, function(err, values) {
ongoingProcess.set('retrievingInputs', false);
if (err) {
showErrorAndBack(err);
return;
}
var config = configService.getSync().wallet.settings;
var unitName = config.unitName;
var amountUnit = txFormatService.satToUnit(values.amount);
var parsedAmount = txFormatService.parseAmount(
amountUnit,
unitName);
amount = parsedAmount.amount;
currency = parsedAmount.currency;
checkDailyLimit();
$scope.amountUnitStr = parsedAmount.amountUnitStr;
$timeout(function() {
$scope.$digest();
}, 100);
});
} else {
checkDailyLimit();
}
};
$scope.goBackHome = function() {

View file

@ -157,7 +157,7 @@ angular.module('copayApp.directives')
scope.$emit('Wallet/Changed', scope.wallets ? scope.wallets[0] : null);
});
scope.$on("$ionicSlides.slideChangeEnd", function(event, data) {
scope.$on("$ionicSlides.slideChangeStart", function(event, data) {
scope.$emit('Wallet/Changed', scope.wallets ? scope.wallets[data.slider.activeIndex] : null);
});
}

View file

@ -119,6 +119,30 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
}
})
/*
*
* Pin
*
*/
.state('pin', {
url: '/pin/',
controller: 'pinController',
templateUrl: 'views/pin.html',
})
/*
*
* Locked
*
*/
.state('lockedView', {
url: '/lockedView/',
controller: 'lockedViewController',
templateUrl: 'views/lockedView.html',
})
/*
*
* URI
@ -439,6 +463,25 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
}
}
})
.state('tabs.lockSetup', {
url: '/lockSetup',
views: {
'tab-settings@tabs': {
controller: 'lockSetupController',
templateUrl: 'views/lockSetup.html',
}
}
})
.state('tabs.pin', {
url: '/pin/:fromSettings/:locking',
views: {
'tab-settings@tabs': {
controller: 'pinController',
templateUrl: 'views/pin.html',
cache: false
}
}
})
/*
*
@ -536,6 +579,15 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
}
}
})
.state('tabs.preferences.preferencesExternal', {
url: '/preferencesExternal',
views: {
'tab-settings@tabs': {
controller: 'preferencesExternalController',
templateUrl: 'views/preferencesExternal.html'
}
}
})
.state('tabs.preferences.delete', {
url: '/delete',
views: {
@ -725,15 +777,6 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
}
}
})
.state('onboarding.notifications', {
url: '/notifications/:walletId',
views: {
'onboarding': {
templateUrl: 'views/onboarding/notifications.html',
controller: 'notificationsController'
}
}
})
.state('onboarding.backupRequest', {
url: '/backupRequest/:walletId',
views: {
@ -1061,7 +1104,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
params: {
id: null,
currency: 'USD',
forceCurrency: true
useSendMax: null
}
})
.state('tabs.bitpayCard.amount', {
@ -1092,7 +1135,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
}
});
})
.run(function($rootScope, $state, $location, $log, $timeout, $ionicHistory, $ionicPlatform, $window, appConfigService, lodash, platformInfo, profileService, uxLanguage, gettextCatalog, openURLService, storageService, scannerService, /* plugins START HERE => */ coinbaseService, glideraService, amazonService, bitpayCardService) {
.run(function($rootScope, $state, $location, $log, $timeout, startupService, fingerprintService, $ionicHistory, $ionicPlatform, $window, appConfigService, lodash, platformInfo, profileService, uxLanguage, gettextCatalog, openURLService, storageService, scannerService, configService, /* plugins START HERE => */ coinbaseService, glideraService, amazonService, bitpayCardService) {
uxLanguage.init();
@ -1124,12 +1167,11 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
var matchWelcome = $ionicHistory.currentStateName() == 'onboarding.welcome' ? true : false;
var matchCollectEmail = $ionicHistory.currentStateName() == 'onboarding.collectEmail' ? true : false;
var matchBackupRequest = $ionicHistory.currentStateName() == 'onboarding.backupRequest' ? true : false;
var matchNotifications = $ionicHistory.currentStateName() == 'onboarding.notifications' ? true : false;
var backedUp = $ionicHistory.backView().stateName == 'onboarding.backup' ? true : false;
var noBackView = $ionicHistory.backView().stateName == 'starting' ? true : false;
var matchDisclaimer = $ionicHistory.currentStateName() == 'onboarding.disclaimer' && (backedUp || noBackView) ? true : false;
var fromOnboarding = matchCollectEmail | matchBackupRequest | matchNotifications | matchWelcome | matchDisclaimer;
var fromOnboarding = matchCollectEmail | matchBackupRequest | matchWelcome | matchDisclaimer;
//views with disable backbutton
var matchComplete = $ionicHistory.currentStateName() == 'tabs.rate.complete' ? true : false;
@ -1153,8 +1195,54 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
// Nothing to do
});
function checkAndApplyLock(onResume) {
var defaultView = 'tabs.home';
if (!platformInfo.isCordova && !platformInfo.isDevel) {
goTo(defaultView);
}
if (onResume) {
var now = Math.floor(Date.now() / 1000);
if (now < openURLService.unlockUntil) {
openURLService.unlockUntil = null;
$log.debug('Skip startup locking');
return;
}
}
function goTo(nextView) {
nextView = nextView || defaultView;
$state.transitionTo(nextView).then(function() {
if (nextView == 'lockedView')
$ionicHistory.clearHistory();
});
};
startupService.ready();
configService.whenAvailable(function(config) {
var lockMethod = config.lock && config.lock.method;
$log.debug('App Lock:' + (lockMethod || 'no'));
if (lockMethod == 'fingerprint' && fingerprintService.isAvailable()) {
fingerprintService.check('unlockingApp', function(err) {
if (err)
goTo('lockedView');
if ($ionicHistory.currentStateName() == 'lockedView' || !onResume)
goTo('tabs.home');
});
} else if (lockMethod == 'pin') {
goTo('pin');
} else {
goTo(defaultView);
}
});
}
$ionicPlatform.on('resume', function() {
// Nothing to do
checkAndApplyLock(true);
});
$ionicPlatform.on('menubutton', function() {
@ -1197,17 +1285,14 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
disableAnimate: true,
historyRoot: true
});
$state.transitionTo('tabs.home').then(function() {
// Clear history
$ionicHistory.clearHistory();
});
});
}
checkAndApplyLock();
});
};
// After everything have been loaded, initialize handler URL
$timeout(function() {
openURLService.init();
}, 1000);
});
});
});

View file

@ -72,8 +72,10 @@ angular.module('copayApp.services').factory('amazonService', function($http, $lo
if (opts && opts.remove) {
delete(inv[gc.invoiceId]);
}
inv = JSON.stringify(inv);
storageService.setAmazonGiftCards(network, inv, function(err) {
homeIntegrationsService.register(homeItem);

View file

@ -1,6 +1,6 @@
'use strict';
angular.module('copayApp.services').factory('bitpayCardService', function($log, $rootScope, $filter, lodash, storageService, bitauthService, platformInfo, moment, appIdentityService, bitpayService, nextStepsService, configService, txFormatService, appConfigService, rateService) {
angular.module('copayApp.services').factory('bitpayCardService', function($log, $rootScope, $filter, lodash, storageService, bitauthService, platformInfo, moment, appIdentityService, bitpayService, nextStepsService, configService, txFormatService, appConfigService) {
var root = {};
var _setError = function(msg, e) {
@ -39,33 +39,6 @@ angular.module('copayApp.services').factory('bitpayCardService', function($log,
return history;
};
root.parseAmount = function(amount, currency) {
var config = configService.getSync().wallet.settings;
var satToBtc = 1 / 100000000;
var unitToSatoshi = config.unitToSatoshi;
var amountUnitStr;
var amountSat;
// IF 'USD'
if (currency) {
amountUnitStr = $filter('formatFiatAmount')(amount) + ' ' + currency;
amountSat = rateService.fromFiat(amount, currency).toFixed(0);
} else {
amountSat = parseInt((amount * unitToSatoshi).toFixed(0));
amountUnitStr = txFormatService.formatAmountStr(amountSat);
// convert unit to BTC
amount = (amountSat * satToBtc).toFixed(8);
currency = 'BTC';
}
return {
amount: amount,
currency: currency,
amountSat: amountSat,
amountUnitStr: amountUnitStr
};
};
root.sync = function(apiContext, cb) {
var json = {
method: 'getDebitCards'

View file

@ -107,30 +107,6 @@ angular.module('copayApp.services').factory('coinbaseService', function($http, $
};
};
root.parseAmount = function(amount, currency) {
var config = configService.getSync().wallet.settings;
var satToBtc = 1 / 100000000;
var unitToSatoshi = config.unitToSatoshi;
var amountUnitStr;
// IF 'USD'
if (currency) {
amountUnitStr = $filter('formatFiatAmount')(amount) + ' ' + currency;
} else {
var amountSat = parseInt((amount * unitToSatoshi).toFixed(0));
amountUnitStr = txFormatService.formatAmountStr(amountSat);
// convert unit to BTC
amount = (amountSat * satToBtc).toFixed(8);
currency = 'BTC';
}
return {
amount: amount,
currency: currency,
amountUnitStr: amountUnitStr
};
};
root.getSignupUrl = function() {
return credentials.HOST + '/signup';
}

View file

@ -53,6 +53,13 @@ angular.module('copayApp.services').factory('configService', function(storageSer
}
},
lock: {
method: null,
value: null,
bannedUntil: null,
attempts: null,
},
// External services
recentTransactions: {
enabled: true,

View file

@ -51,10 +51,9 @@ angular.module('copayApp.services').factory('feeService', function($log, $stateP
if (errLivenet || errTestnet) {
return cb(gettextCatalog.getString('Could not get dynamic fee'));
} else {
for (var i = 0; i < 4; i++) {
levelsLivenet[i]['feePerKBUnit'] = txFormatService.formatAmount(levelsLivenet[i].feePerKB) + ' ' + unitName;
levelsTestnet[i]['feePerKBUnit'] = txFormatService.formatAmount(levelsTestnet[i].feePerKB) + ' ' + unitName;
}
lodash.each(lodash.union(levelsLivenet, levelsTestnet), function(level) {
level.feePerKBUnit = txFormatService.formatAmount(level.feePerKB) + ' ' + unitName;
});
}
return cb(null, {

View file

@ -14,7 +14,7 @@ angular.module('copayApp.services').factory('fingerprintService', function($log,
function(msg) {
FingerprintAuth.isAvailable(function(result) {
if (result.isAvailable)
if (result.isAvailable)
_isAvailable = 'ANDROID';
}, function() {
@ -40,7 +40,7 @@ angular.module('copayApp.services').factory('fingerprintService', function($log,
},
function(msg) {
$log.debug('Finger Failed:' + JSON.stringify(msg));
return cb(gettextCatalog.getString('Finger Scan Failed') + ': ' + msg.localizedDescription);
return cb(gettextCatalog.getString('Finger Scan Failed'));
}
);
} catch (e) {
@ -60,7 +60,7 @@ angular.module('copayApp.services').factory('fingerprintService', function($log,
},
function(msg) {
$log.debug('Touch ID Failed:' + JSON.stringify(msg));
return cb(gettextCatalog.getString('Touch ID Failed') + ': ' + msg.localizedDescription);
return cb(gettextCatalog.getString('Touch ID Failed'));
}
);
} catch (e) {
@ -71,6 +71,7 @@ angular.module('copayApp.services').factory('fingerprintService', function($log,
var isNeeded = function(client) {
if (!_isAvailable) return false;
if (client === 'unlockingApp') return true;
var config = configService.getSync();
config.touchIdFor = config.touchIdFor || {};
@ -84,7 +85,7 @@ angular.module('copayApp.services').factory('fingerprintService', function($log,
root.check = function(client, cb) {
if (isNeeded(client)) {
$log.debug('FingerPrint Service:', _isAvailable);
$log.debug('FingerPrint Service:', _isAvailable);
if (_isAvailable == 'IOS')
return requestTouchId(cb);
else

View file

@ -53,30 +53,6 @@ angular.module('copayApp.services').factory('glideraService', function($http, $l
return 'USD';
};
root.parseAmount = function(amount, currency) {
var config = configService.getSync().wallet.settings;
var satToBtc = 1 / 100000000;
var unitToSatoshi = config.unitToSatoshi;
var amountUnitStr;
// IF 'USD'
if (currency) {
amountUnitStr = $filter('formatFiatAmount')(amount) + ' ' + currency;
} else {
var amountSat = parseInt((amount * unitToSatoshi).toFixed(0));
amountUnitStr = txFormatService.formatAmountStr(amountSat);
// convert unit to BTC
amount = (amountSat * satToBtc).toFixed(8);
currency = 'BTC';
}
return {
amount: amount,
currency: currency,
amountUnitStr: amountUnitStr
};
};
root.getSignupUrl = function() {
return credentials.HOST + '/register';
}

View file

@ -6,9 +6,11 @@ angular.module('copayApp.services')
// Ledger magic number to get xPub without user confirmation
root.ENTROPY_INDEX_PATH = "0xb11e/";
root.M = 'm/';
root.UNISIG_ROOTPATH = 44;
root.MULTISIG_ROOTPATH = 48;
root.LIVENET_PATH = 0;
root.TESTNET_PATH = 1;
root._err = function(data) {
var msg = data.error || data.message || 'unknown';
@ -17,26 +19,49 @@ angular.module('copayApp.services')
root.getRootPath = function(device, isMultisig, account) {
if (!isMultisig) return root.UNISIG_ROOTPATH;
// Compat
if (device == 'ledger' && account == 0) return root.UNISIG_ROOTPATH;
return root.MULTISIG_ROOTPATH;
var path;
if (isMultisig) {
path = root.MULTISIG_ROOTPATH;
} else {
if (device == 'ledger' && account > 0) {
path = root.MULTISIG_ROOTPATH;
} else {
path = root.UNISIG_ROOTPATH;
}
}
if (device == 'intelTEE') {
path = root.M + path;
}
return path;
};
root.getAddressPath = function(device, isMultisig, account) {
return root.getRootPath(device, isMultisig, account) + "'/" + root.LIVENET_PATH + "'/" + account + "'";
}
root.getAddressPath = function(device, isMultisig, account, network) {
network = network || 'livenet';
var networkPath = root.LIVENET_PATH;
if (network == 'testnet') {
networkPath = root.TESTNET_PATH;
}
return root.getRootPath(device, isMultisig, account) + "'/" + networkPath + "'/" + account + "'";
};
root.getEntropyPath = function(device, isMultisig, account) {
var path;
var path = root.ENTROPY_INDEX_PATH;
if (isMultisig) {
path = path + "48'/"
} else {
path = path + "44'/"
}
// Old ledger wallet compat
if (device == 'ledger' && account == 0)
return root.ENTROPY_INDEX_PATH + "0'";
if (device == 'ledger' && account == 0) {
return path + "0'/";
}
return root.ENTROPY_INDEX_PATH + root.getRootPath(device, isMultisig, account) + "'/" + account + "'";
if (device == 'intelTEE') {
path = root.M + path;
}
return path + account + "'";
};
root.pubKeyToEntropySource = function(xPubKey) {

View file

@ -1,6 +1,6 @@
'use strict';
angular.module('copayApp.services').factory('incomingData', function($log, $state, $timeout, $ionicHistory, bitcore, $rootScope, payproService, scannerService, appConfigService) {
angular.module('copayApp.services').factory('incomingData', function($log, $state, $timeout, $ionicHistory, bitcore, $rootScope, payproService, scannerService, appConfigService, popupService, gettextCatalog) {
var root = {};
@ -93,10 +93,10 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat
if (parsed.r) {
payproService.getPayProDetails(parsed.r, function(err, details) {
if (err && addr && amount) {
goSend(addr, amount, message);
}
handlePayPro(details);
if (err) {
if (addr && amount) goSend(addr, amount, message);
else popupService.showAlert(gettextCatalog.getString('Error'), err);
} else handlePayPro(details);
});
} else {
goSend(addr, amount, message);

195
src/js/services/intelTEE.js Normal file
View file

@ -0,0 +1,195 @@
'use strict';
angular.module('copayApp.services')
.factory('intelTEE', function($log, $timeout, gettext, lodash, bitcore, hwWallet, bwcService, platformInfo) {
var root = {};
root.description = {
supported: platformInfo.supportsIntelTEE,
version: platformInfo.versionIntelTEE,
id: 'intelTEE',
name: 'Intel TEE',
longName: 'Intel TEE Hardware Wallet',
derivationStrategy: 'BIP44',
isEmbeddedHardware: true,
supportsTestnet: true
};
if (!root.description.supported) {
return root;
}
var IntelWallet = require('intelWalletCon');
var TEE_APP_ID = '63279de1b6cb4dcf8c206716bd318092f8c206716bd31809263279de1b6cb4dc';
root.walletEnclave = new IntelWallet.Wallet();
var walletEnclaveStatus = root.walletEnclave.initializeEnclave();
if (walletEnclaveStatus != 0) {
$log.error('Failed to create Intel Wallet enclave');
}
root.getInfoForNewWallet = function(isMultisig, account, networkName, callback) {
var opts = {};
initSource(opts, function(err, opts) {
if (err) return callback(err);
root.getEntropySource(opts.hwInfo.id, isMultisig, account, function(err, entropySource) {
if (err) return callback(err);
opts.entropySource = entropySource;
root.getXPubKey(opts.hwInfo.id, hwWallet.getAddressPath(root.description.id, isMultisig, account, networkName), function(data) {
if (!data.success) {
$log.warn(data.message);
return callback(data);
}
opts.extendedPublicKey = data.xpubkey;
opts.externalSource = root.description.id;
opts.derivationStrategy = root.description.derivationStrategy;
return callback(null, opts);
});
});
});
};
root.getXPubKey = function(teeWalletId, path, callback) {
$log.debug('TEE deriving xPub path:', path);
// Expected to be a extended public key.
var xpubkey = root.walletEnclave.getPublicKey(teeWalletId, path);
// Error messages returned in value.
var result = {
success: false,
message: xpubkey.ExtendedPublicKey
};
// Success indicated by status being equal to the tee wallet id.
if (xpubkey.Status == teeWalletId) {
result.success = true;
result.message = 'OK';
result.xpubkey = xpubkey.ExtendedPublicKey;
} else {
$log.error('Failed to get xpubkey from TEE wallet: ' + result.message);
}
callback(result);
};
root.getEntropySource = function(teeWalletId, isMultisig, account, callback) {
root.getXPubKey(teeWalletId, hwWallet.getEntropyPath(root.description.id, isMultisig, account), function(data) {
if (!data.success)
return callback(hwWallet._err(data));
return callback(null, hwWallet.pubKeyToEntropySource(data.xpubkey));
});
};
root.showMneumonic = function(teeWalletId, cb) {
var result = root.walletEnclave.displayWordList(teeWalletId, 'en');
if (result != teeWalletId) {
cb(result);
} else {
cb();
}
};
root.showReceiveAddress = function(teeWalletId, address, cb) {
var isMultisig = false; // TODO
var account = 0; // TODO
var basePath = hwWallet.getAddressPath(root.description.id, isMultisig, account, address.network);
var keyPath = address.path.replace('m', basePath);
var result = root.walletEnclave.displayReceiveAddress(teeWalletId, keyPath);
if (result != teeWalletId) {
cb(result);
} else {
cb();
}
};
root.signTx = function(teeWalletId, txp, callback) {
var account = 0; // TODO
var isMultisig = txp.requiredSignatures > 1;
var basePath = hwWallet.getAddressPath(root.description.id, isMultisig, account, txp.network);
var rawTx = bwcService.Client.getRawTx(txp);
var keypaths = lodash.map(lodash.pluck(txp.inputs, 'path'), function(path) {
return path.replace('m', basePath);
});
var publicKeys = lodash.pluck(txp.inputs, 'publicKeys');
var changePublicKeys = txp.changeAddress.publicKeys;
publicKeys.push(changePublicKeys);
var changeaddrpath;
if (txp.changeAddress) {
changeaddrpath = txp.changeAddress.path.replace('m', basePath);
}
var result;
if (txp.requiredSignatures == 1) {
result = root.walletEnclave.signTransaction(teeWalletId, rawTx, changeaddrpath, keypaths);
} else {
result = root.walletEnclave.signTransaction(teeWalletId, rawTx, changeaddrpath, keypaths, publicKeys, txp.requiredSignatures, changePublicKeys, txp.requiredSignatures);
}
if (result.Status != teeWalletId) {
return callback('TEE failed to sign transction: ' + result.Status);
}
return callback(null, result);
};
function initSource(opts, callback) {
var args = {
"Testnet" : (opts.networkName == 'livenet'? false : true),
"PINUnlockRequired" : false,
"PINSignatureDataRequired" : false,
"PINSignatureTransaction" : 0,
"ExportCount" : 10,
"MaxPINAttempts" : 3,
"PINTimeout" : 30
};
var teeStatus = root.walletEnclave.createWallet(TEE_APP_ID, args);
switch (teeStatus) {
case "CREATE WALLET FAILURE":
case "CREATE WALLET FAILED TO INITIALIZE":
case "CREATE WALLET FAILURE BAD INPUT":
case "CREATE WALLET FAILURE case SERIALIZATION":
case "DELETE_WALLET_AUTHORIZATION_UNSUCCESSFUL":
case "LOAD_WALLET_FAILTURE":
case "IMPORT WORD LIST FAILTURE":
case "IMPORT WORD LIST FAILURE BAD INPUT":
case "IMPORT WORD NOT IN DICTIONARY":
case "INVALID PIN":
case "INVALID APPLICATION ID":
case "DISPLAY WORD LIST FAILURE":
case "DELETE WALLET NO SUCH APPLICATION ID":
case "SIGN DATA FAILURE":
case "SIGN DATA INVALID HASH":
case "SIGN DATA BUFFER TOO SMALL":
case "SIGN DATA INVALID PIN":
case "RECEIVE ADDRESS INVALID INPUT":
case "RECEIVE ADDRESS NULL":
case "RECEIVE ADDRESS BUFFER TOO SMALL":
case "PUBLIC KEY BUFFER TOO SMALL":
case "LOAD WALLET FAILURE":
case "PUBLIC KEY FAILURE":
case "PUBLIC KEY FAIL TO SERIALIZE":
case "UKNOWN ERROR CODE":
$log.error(teeStatus);
return callback(teeStatus); // TODO: translate error text for display
break;
default:
opts.hwInfo = {
name: root.description.id,
id: teeStatus
};
$log.debug('TEE wallet created: ' + opts.hwInfo.id);
return callback(null, opts);
}
};
return root;
});

View file

@ -1,10 +1,19 @@
'use strict';
angular.module('copayApp.services')
.factory('ledger', function($log, bwcService, gettext, hwWallet) {
.factory('ledger', function($log, bwcService, gettext, hwWallet, platformInfo) {
var root = {};
var LEDGER_CHROME_ID = "kkdpmhnladdopljabkgpacgpliggeeaf";
root.description = {
supported: platformInfo.supportsLedger,
id: 'ledger',
name: 'Ledger',
longName: 'Ledger Hardware Wallet',
isEmbeddedHardware: false,
supportsTestnet: false
};
root.callbacks = {};
root.hasSession = function() {
root._message({
@ -13,7 +22,7 @@ angular.module('copayApp.services')
}
root.getEntropySource = function(isMultisig, account, callback) {
root.getXPubKey(hwWallet.getEntropyPath('ledger', isMultisig, account), function(data) {
root.getXPubKey(hwWallet.getEntropyPath(root.description.id, isMultisig, account), function(data) {
if (!data.success)
return callback(hwWallet._err(data));
@ -30,21 +39,28 @@ angular.module('copayApp.services')
});
};
root.getInfoForNewWallet = function(isMultisig, account, callback) {
root.initSource = function(opts, callback) {
// No initialization for this hardware source.
return callback(opts);
};
root.getInfoForNewWallet = function(isMultisig, account, networkName, callback) {
// networkName not used for this hardware (always livenet)
root.getEntropySource(isMultisig, account, function(err, entropySource) {
if (err) return callback(err);
root.getXPubKey(hwWallet.getAddressPath('ledger', isMultisig, account), function(data) {
if (!data.success) return callback(data);
var opts = {};
opts.entropySource = entropySource;
var opts = {};
opts.entropySource = entropySource;
root.getXPubKey(hwWallet.getAddressPath(root.description.id, isMultisig, account), function(data) {
if (!data.success) {
$log.warn(data.message);
return callback(data);
}
opts.extendedPublicKey = data.xpubkey;
opts.externalSource = 'ledger';
opts.account = account;
opts.externalSource = root.description.id;
// Old ledger compat
opts.derivationStrategy = account ? 'BIP48' : 'BIP44';
opts.derivationStrategy = opts.account ? 'BIP48' : 'BIP44';
return callback(null, opts);
});
});
@ -57,7 +73,7 @@ angular.module('copayApp.services')
var tx = bwcService.getUtils().buildTx(txp);
for (var i = 0; i < tx.inputs.length; i++) {
redeemScripts.push(new ByteString(tx.inputs[i].redeemScript.toBuffer().toString('hex'), GP.HEX).toString());
paths.push(hwWallet.getAddressPath('ledger', isMultisig, account) + txp.inputs[i].path.substring(1));
paths.push(hwWallet.getAddressPath(root.description.id, isMultisig, account) + txp.inputs[i].path.substring(1));
}
var splitTransaction = root._splitTransaction(new ByteString(tx.toString(), GP.HEX));
var inputs = [];

View file

@ -3,6 +3,7 @@
angular.module('copayApp.services').factory('ongoingProcess', function($log, $timeout, $filter, lodash, $ionicLoading, gettext, platformInfo) {
var root = {};
var isCordova = platformInfo.isCordova;
var isWP = platformInfo.isWP;
var ongoingProcess = {};
@ -49,7 +50,7 @@ angular.module('copayApp.services').factory('ongoingProcess', function($log, $ti
root.clear = function() {
ongoingProcess = {};
if (isCordova) {
if (isCordova && !isWP) {
window.plugins.spinnerDialog.hide();
} else {
$ionicLoading.hide();
@ -79,21 +80,23 @@ angular.module('copayApp.services').factory('ongoingProcess', function($log, $ti
if (customHandler) {
customHandler(processName, showName, isOn);
} else if (root.onGoingProcessName) {
if (isCordova) {
if (isCordova && !isWP) {
window.plugins.spinnerDialog.show(null, showName, root.clear);
} else {
var tmpl = '<div class="item-icon-left">' + showName + '<ion-spinner class="spinner-stable" icon="lines"></ion-spinner></div>';
var tmpl;
if (isWP) tmpl = '<div>' + showName +'</div>';
else tmpl = '<div class="item-icon-left">' + showName + '<ion-spinner class="spinner-stable" icon="lines"></ion-spinner></div>';
$ionicLoading.show({
template: tmpl
});
}
} else {
if (isCordova) {
if (isCordova && !isWP) {
window.plugins.spinnerDialog.hide();
} else {
$ionicLoading.hide();
}
$ionicLoading.hide();
}
}
};

View file

@ -1,9 +1,15 @@
'use strict';
angular.module('copayApp.services').factory('openURLService', function($rootScope, $ionicHistory, $document, $log, $state, platformInfo, lodash, profileService, incomingData, appConfigService) {
var DELAY_UNLOCK_TIME = 2 * 60;
var root = {};
root.unlockUntil = null;
var handleOpenURL = function(args) {
root.unlockUntil = Math.floor(Date.now() / 1000) + DELAY_UNLOCK_TIME;
$log.debug('Set unlock time until: ' + root.unlockUntil);
$log.info('Handling Open URL: ' + JSON.stringify(args));
// Stop it from caching the first view as one to return when the app opens
$ionicHistory.nextViewOptions({
@ -103,5 +109,5 @@ angular.module('copayApp.services').factory('openURLService', function($rootScop
});
};
return root;
return root;
});

View file

@ -1,51 +1,39 @@
'use strict';
angular.module('copayApp.services').factory('payproService',
function($window, profileService, platformInfo, popupService, gettextCatalog, ongoingProcess, $log) {
function(profileService, platformInfo, gettextCatalog, ongoingProcess, $log) {
var ret = {};
var ret = {};
ret.getPayProDetails = function(uri, cb, disableLoader) {
if (!cb) cb = function() {};
ret.getPayProDetails = function(uri, cb, disableLoader) {
if (!cb) cb = function() {};
var wallet = profileService.getWallets({
onlyComplete: true
})[0];
var wallet = profileService.getWallets({
onlyComplete: true
})[0];
if (!wallet) return cb();
if (!wallet) return cb();
if (platformInfo.isChromeApp) {
popupService.showAlert(gettextCatalog.getString('Payment Protocol not supported on Chrome App'));
return cb(true);
}
$log.debug('Fetch PayPro Request...', uri);
if(!disableLoader) {
ongoingProcess.set('fetchingPayPro', true);
}
wallet.fetchPayPro({
payProUrl: uri,
}, function(err, paypro) {
if(!disableLoader) {
ongoingProcess.set('fetchingPayPro', false);
if (platformInfo.isChromeApp) {
return cb(gettextCatalog.getString('Payment Protocol not supported on Chrome App'));
}
if (err) {
return cb(true);
}
$log.debug('Fetch PayPro Request...', uri);
if (!paypro.verified) {
$log.warn('Failed to verify payment protocol signatures');
popupService.showAlert(gettextCatalog.getString('Payment Protocol Invalid'));
return cb(true);
}
cb(null, paypro);
if (!disableLoader) ongoingProcess.set('fetchingPayPro', true);
});
};
wallet.fetchPayPro({
payProUrl: uri,
}, function(err, paypro) {
if (!disableLoader) ongoingProcess.set('fetchingPayPro', false);
if (err) return cb(err);
else if (!paypro.verified) {
$log.warn('Failed to verify payment protocol signatures');
return cb(gettextCatalog.getString('Payment Protocol Invalid'));
}
return cb(null, paypro);
});
};
return ret;
});
return ret;
});

View file

@ -23,6 +23,27 @@ angular.module('copayApp.services').factory('platformInfo', function($window) {
}
};
var getVersionIntelTee = function() {
var v = '';
var isWindows = navigator.platform.indexOf('Win') > -1;
if (!isNodeWebkit() || !isWindows) {
return v;
}
try {
var IntelWallet = require('intelWalletCon');
if (IntelWallet.getVersion) {
v = IntelWallet.getVersion();
} else {
v = 'Alpha';
}
if (v.length > 0) {
$log.info('Intel TEE library ' + v);
}
} catch (e) {}
return v;
};
// Detect mobile devices
var ret = {
@ -39,5 +60,11 @@ angular.module('copayApp.services').factory('platformInfo', function($window) {
ret.isChromeApp = $window.chrome && chrome.runtime && chrome.runtime.id && !ret.isNW;
ret.isDevel = !ret.isMobile && !ret.isChromeApp && !ret.isNW;
ret.supportsLedger = ret.isChromeApp;
ret.supportsTrezor = ret.isChromeApp || ret.isDevel;
ret.versionIntelTEE = getVersionIntelTee();
ret.supportsIntelTEE = ret.versionIntelTEE.length > 0;
return ret;
});

View file

@ -347,6 +347,7 @@ angular.module('copayApp.services')
account: opts.account || 0,
derivationStrategy: opts.derivationStrategy || 'BIP44',
});
walletClient.credentials.hwInfo = opts.hwInfo;
} catch (ex) {
$log.warn("Creating wallet from Extended Public Key Arg:", ex, opts);
return cb(gettextCatalog.getString('Could not create using the specified extended public key'));

View file

@ -0,0 +1,36 @@
'use strict';
angular.module('copayApp.services').service('sendMaxService', function(feeService, configService, walletService) {
/**
* Get sendMaxInfo
*
* @param {Obj} Wallet
* @param {Callback} Function (optional)
*
*/
this.getInfo = function(wallet, cb) {
feeService.getCurrentFeeValue(wallet.credentials.network, function(err, feePerKb) {
if (err) return cb(err);
var config = configService.getSync().wallet;
walletService.getSendMaxInfo(wallet, {
feePerKb: feePerKb,
excludeUnconfirmedUtxos: !config.spendUnconfirmed,
returnInputs: true,
}, function(err, resp) {
if (err) return cb(err);
return cb(null, {
sendMax: true,
amount: resp.amount,
inputs: resp.inputs,
fee: resp.fee,
feePerKb: feePerKb,
});
});
});
};
});

View file

@ -1,14 +1,24 @@
'use strict';
angular.module('copayApp.services')
.factory('trezor', function($log, $timeout, lodash, bitcore, hwWallet) {
.factory('trezor', function($log, $timeout, lodash, bitcore, hwWallet, platformInfo) {
var root = {};
var SETTLE_TIME = 3000;
root.callbacks = {};
root.description = {
supported: platformInfo.supportsTrezor,
id: 'trezor',
name: 'Trezor',
longName: 'Trezor Hardware Wallet',
derivationStrategy: 'BIP48',
isEmbeddedHardware: false,
supportsTestnet: false
};
root.getEntropySource = function(isMultisig, account, callback) {
root.getXPubKey(hwWallet.getEntropyPath('trezor', isMultisig, account), function(data) {
root.getXPubKey(hwWallet.getEntropyPath(root.description.id, isMultisig, account), function(data) {
if (!data.success)
return callback(hwWallet._err(data));
@ -26,8 +36,13 @@ angular.module('copayApp.services')
}
};
root.initSource = function(opts, callback) {
// No initialization for this hardware source.
return callback(opts);
};
root.getInfoForNewWallet = function(isMultisig, account, callback) {
root.getInfoForNewWallet = function(isMultisig, account, networkName, callback) {
// networkName not used for this hardware (always livenet)
var opts = {};
root.getEntropySource(isMultisig, account, function(err, data) {
if (err) return callback(err);
@ -35,13 +50,12 @@ angular.module('copayApp.services')
$log.debug('Waiting TREZOR to settle...');
$timeout(function() {
root.getXPubKey(hwWallet.getAddressPath('trezor', isMultisig, account), function(data) {
root.getXPubKey(hwWallet.getAddressPath(root.description.id, isMultisig, account), function(data) {
if (!data.success)
return callback(hwWallet._err(data));
opts.extendedPublicKey = data.xpubkey;
opts.externalSource = 'trezor';
opts.account = account;
opts.externalSource = root.description.id;
if (isMultisig)
opts.derivationStrategy = 'BIP48';

View file

@ -155,5 +155,42 @@ angular.module('copayApp.services').factory('txFormatService', function($filter,
return txps;
};
root.parseAmount = function(amount, currency) {
var config = configService.getSync().wallet.settings;
var satToBtc = 1 / 100000000;
var unitToSatoshi = config.unitToSatoshi;
var amountUnitStr;
var amountSat;
var alternativeIsoCode = config.alternativeIsoCode;
// If fiat currency
if (currency != 'bits' && currency != 'BTC') {
amountUnitStr = $filter('formatFiatAmount')(amount) + ' ' + currency;
amountSat = rateService.fromFiat(amount, currency).toFixed(0);
} else {
amountSat = parseInt((amount * unitToSatoshi).toFixed(0));
amountUnitStr = root.formatAmountStr(amountSat);
// convert unit to BTC
amount = (amountSat * satToBtc).toFixed(8);
currency = 'BTC';
}
return {
amount: amount,
currency: currency,
alternativeIsoCode: alternativeIsoCode,
amountSat: amountSat,
amountUnitStr: amountUnitStr
};
};
root.satToUnit = function(amount) {
var config = configService.getSync().wallet.settings;
var unitToSatoshi = config.unitToSatoshi;
var satToUnit = 1 / unitToSatoshi;
var unitDecimals = config.unitDecimals;
return parseFloat((amount * satToUnit).toFixed(unitDecimals));
};
return root;
});

View file

@ -1,10 +1,16 @@
'use strict';
angular.module('copayApp.services').factory('walletService', function($log, $timeout, lodash, trezor, ledger, storageService, configService, rateService, uxLanguage, $filter, gettextCatalog, bwcError, $ionicPopup, fingerprintService, ongoingProcess, gettext, $rootScope, txFormatService, $ionicModal, $state, bwcService, bitcore, popupService) {
angular.module('copayApp.services').factory('walletService', function($log, $timeout, lodash, trezor, ledger, intelTEE, storageService, configService, rateService, uxLanguage, $filter, gettextCatalog, bwcError, $ionicPopup, fingerprintService, ongoingProcess, gettext, $rootScope, txFormatService, $ionicModal, $state, bwcService, bitcore, popupService) {
// `wallet` is a decorated version of client.
var root = {};
root.externalSource = {
ledger: ledger.description,
trezor: trezor.description,
intelTEE: intelTEE.description
}
root.WALLET_STATUS_MAX_TRIES = 7;
root.WALLET_STATUS_DELAY_BETWEEN_TRIES = 1.4 * 1000;
root.SOFT_CONFIRMATION_LIMIT = 12;
@ -40,6 +46,43 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
});
};
var _signWithIntelTEE = function(wallet, txp, cb) {
$log.info('Requesting Intel TEE to sign the transaction');
intelTEE.signTx(wallet.credentials.hwInfo.id, txp, function(err, result) {
if (err) return cb(err);
$log.debug('Intel TEE response', result);
txp.signatures = result.Signatures;
return wallet.signTxProposal(txp, cb);
});
};
root.showMneumonicFromHardware = function(wallet, cb) {
switch (wallet.getPrivKeyExternalSourceName()) {
case root.externalSource.intelTEE.id:
return intelTEE.showMneumonic(wallet.credentials.hwInfo.id, cb);
break;
default:
cb('Error: unrecognized external source');
break;
}
};
root.showReceiveAddressFromHardware = function(wallet, address, cb) {
switch (wallet.getPrivKeyExternalSourceName()) {
case root.externalSource.intelTEE.id:
root.getAddressObj(wallet, address, function(err, addrObj) {
if (err) return cb(err);
return intelTEE.showReceiveAddress(wallet.credentials.hwInfo.id, addrObj, cb);
});
break;
default:
cb('Error: unrecognized external source');
break;
}
};
root.invalidateCache = function(wallet) {
if (wallet.cachedStatus)
wallet.cachedStatus.isValid = false;
@ -629,10 +672,12 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
if (wallet.isPrivKeyExternal()) {
switch (wallet.getPrivKeyExternalSourceName()) {
case 'ledger':
case root.externalSource.ledger.id:
return _signWithLedger(wallet, txp, cb);
case 'trezor':
case root.externalSource.trezor.id:
return _signWithTrezor(wallet, txp, cb);
case root.externalSource.intelTEE.id:
return _signWithIntelTEE(wallet, txp, cb);
default:
var msg = 'Unsupported External Key:' + wallet.getPrivKeyExternalSourceName();
$log.error(msg);
@ -845,6 +890,21 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
});
};
root.getAddressObj = function(wallet, address, cb) {
wallet.getMainAddresses({
reverse: true
}, function(err, addr) {
if (err) return cb(err);
var addrObj = lodash.find(addr, function(a) {
return a.address == address;
});
var err = null;
if (!addrObj) {
err = 'Error: specified address not in wallet';
}
return cb(err, addrObj);
});
};
root.isReady = function(wallet, cb) {
if (!wallet.isComplete())

View file

@ -116,7 +116,6 @@ $v-onboarding-checkbox-on-border: $v-success-color !default;
$v-onboarding-tour-phone-bg: url(../img/onboarding-tour-phone.svg) !default;
$v-onboarding-tour-currency-bg: url(../img/onboarding-tour-currency-bg.svg) !default;
$v-onboarding-tour-control-bg: url(../img/onboarding-tour-control.svg) !default;
$v-onboarding-push-notification-bg: url(../img/onboarding-push-notifications.svg) !default;
$v-onboarding-backup-warning-bg: url(../img/backup-warning.svg) !default;
$v-onboarding-button-back-color: #ffffff !default;

View file

@ -89,6 +89,17 @@
flex-grow: 1;
}
}
&.low-fees {
border-top: none;
display: flex;
font-size: 14px;
color: #aaa;
align-items: center;
margin-top: -20px;
i {
padding-right: 20px;
}
}
}
.item-divider {
padding-top: 1.2rem;

View file

@ -0,0 +1,42 @@
#locked-view {
@mixin img-frame {
height: 60px;
width: 60px;
box-shadow: none;
margin: auto;
}
.img-container-copay {
padding: 20%;
@media(min-width: 480px) {
max-height: 150px;
}
.big-icon-svg {
> .bg {
@include img-frame;
background-image: url("../img/icon-fingerprint-copay.svg");
}
}
}
.img-container-bitpay {
padding: 20%;
@media(min-width: 480px) {
max-height: 150px;
}
.big-icon-svg {
> .bg {
@include img-frame;
background-image: url("../img/icon-fingerprint-bitpay.svg");
}
}
}
.comments {
text-align: center;
.header {
font-size: 20px;
}
.text-content {
width: 90%;
margin: 5% auto;
}
}
}

View file

@ -1,9 +0,0 @@
#onboarding-push-notifications {
#notifications-topic {
margin-top: 3rem;
}
#cta-buttons {
@extend %cta-buttons;
padding-bottom: 1rem;
}
}

View file

@ -82,7 +82,6 @@
@import "onboard-backup-request";
@import "../backup-warning";
@import "onboard-disclaimer";
@import "onboard-push-notifications";
%onboarding-illustration {
width: 100%;
@ -124,10 +123,6 @@
@extend %onboarding-illustration;
background-image: $v-onboarding-tour-control-bg;
}
&-notifications {
@extend %onboarding-illustration;
background-image: $v-onboarding-push-notification-bg;
}
&-backup-warning {
@extend %onboarding-illustration;
background-image: $v-onboarding-backup-warning-bg;

95
src/sass/views/pin.scss Normal file
View file

@ -0,0 +1,95 @@
#pin {
background-color: #FAFAFA;
.bar.bar-clear {
background-color: transparent;
border: none;
.back-button .icon:before {
color: #2d3f50;
}
}
.content {
text-align: center;
width: 100%;
height: 100%;
.app-icon {
margin-top: -55px;
.big-icon-svg {
> .bg {
background-image: url("../img/app/icon.png");
height: 60px;
width: 60px;
margin: auto;
}
}
}
.block-text {
align-items: center;
background-color: #F1F1F1;
height: 30%;
border-bottom: 1px solid #c5c5c5;
.message {
margin: auto;
}
span {
width: 60%;
margin: 10% auto;
}
@media(min-width: 480px) {
span {
font-size: 30px;
width: 90%;
}
}
}
.block-code {
width: 50%;
margin: auto;
@media(min-width: 480px) {
width: 25%;
}
}
.block-buttons {
.col {
padding: 5%;
}
color: $v-dark-gray;
font-size: 1.7rem;
font-family: $v-font-family-light;
cursor: pointer;
position: absolute;
bottom: 3%;
left: 5%;
width: 90%;
@media(min-width: 480px) {
left: 15%;
width: 70%;
max-height: 55%;
}
}
}
@mixin circle {
border-radius: 50%;
box-shadow: 0 0 3px 0px #5b5b5b;
transition: background-color .2s ease-in-out;
padding: 7%;
margin: 5%;
}
.circle-copay {
@include circle;
border: 1px solid $v-accent-color;
}
.circle-bitpay {
@include circle;
border: 1px solid $v-primary-color;
}
.filled-copay {
background-color: $v-accent-color;
}
.filled-bitpay {
background-color: #1f3598;
}
.error {
color: #f13333;
max-width: 70%;
}
}

View file

@ -162,6 +162,17 @@
transform: translate(100%, -40%);
}
}
.overlay {
position: absolute;
width: 220px;
height: 100%;
background-color: rgba(255,255,255,0.8);
button {
width: 100%;
top: 50%;
transform: translateY(-50%);
}
}
@media(max-height: 700px) {
padding: 10vh 0 4vh;
}

View file

@ -3,6 +3,13 @@
.icon-bitpay {
background-image: url("../img/icon-bitpay.svg");
}
.warning {
color: $v-warning-color;
}
.centered {
width: 100%;
text-align: center;
}
.disabled {
color: $v-light-gray;
}

View file

@ -46,3 +46,5 @@
@import "includes/accountSelector";
@import "integrations/integrations";
@import "custom-amount";
@import "pin";
@import "lockedView";

View file

@ -209,6 +209,20 @@
font-size: 13px;
color: $v-mid-gray;
}
.low-fees {
.comment {
color: $v-mid-gray;
font-size: 12.5px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-left: 25px;
}
img {
position: absolute;
margin-top: 3px;
}
}
}
.wallet-details-wallet-info {
@ -238,7 +252,7 @@ a.item {
.recent svg {
margin-left:5px;
width:0.7em;
width:0.7em;
height:0.7em;
stroke: #eee;
}

View file

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" viewBox="0 0 425.034 425.034" style="enable-background:new 0 0 425.034 425.034;" xml:space="preserve" width="512px" height="512px">
<g>
<path d="M133.822,384.2c-33.836-24.953-54.037-64.915-54.037-106.898V147.731c0-3.798,0.162-7.633,0.482-11.396 c0.351-4.127-2.711-7.757-6.838-8.108c-4.123-0.347-7.757,2.711-8.108,6.838c-0.355,4.184-0.536,8.446-0.536,12.666v129.571 c0,23.526,5.669,46.938,16.395,67.704c10.375,20.087,25.5,37.815,43.739,51.266c1.34,0.988,2.899,1.464,4.446,1.464 c2.301,0,4.572-1.055,6.042-3.049C137.865,391.354,137.156,386.659,133.822,384.2z" fill="#1e3186"/>
<path d="M168.819,402.675c-4.619-1.61-9.197-3.499-13.61-5.615c-3.734-1.791-8.215-0.215-10.005,3.521 c-1.791,3.735-0.215,8.215,3.52,10.005c4.915,2.356,10.014,4.46,15.158,6.253c0.817,0.285,1.65,0.42,2.469,0.42 c3.102,0,6.003-1.939,7.082-5.033C174.795,408.315,172.73,404.039,168.819,402.675z" fill="#1e3186"/>
<path d="M352.749,243.219c-4.143,0-7.5,3.358-7.5,7.5v26.584c0,73.188-59.543,132.731-132.732,132.731 c-5.179,0-10.395-0.301-15.502-0.895c-4.12-0.479-7.838,2.469-8.316,6.584c-0.479,4.115,2.469,7.838,6.583,8.316 c5.681,0.661,11.479,0.995,17.235,0.995c81.459,0,147.731-66.272,147.731-147.731v-26.584 C360.249,246.576,356.891,243.219,352.749,243.219z" fill="#1e3186"/>
<path d="M212.517,0c-31.895,0-62.263,10.003-87.824,28.928C99.64,47.478,81.373,72.889,71.867,102.417 c-1.27,3.943,0.898,8.168,4.841,9.438c3.944,1.268,8.168-0.898,9.438-4.841c8.54-26.525,24.955-49.358,47.473-66.03 C156.577,23.985,183.86,15,212.517,15c73.188,0,132.731,59.543,132.731,132.731v72.987c0,4.142,3.357,7.5,7.5,7.5 s7.5-3.358,7.5-7.5v-72.987C360.249,66.272,293.976,0,212.517,0z" fill="#1e3186"/>
<path d="M111.172,318.746c-3.715,1.833-5.24,6.33-3.408,10.044c9.504,19.263,24.13,35.549,42.296,47.096 c18.678,11.873,40.275,18.149,62.457,18.149c16.784,0,32.996-3.504,48.187-10.415c3.771-1.715,5.437-6.162,3.722-9.932 c-1.716-3.77-6.162-5.438-9.933-3.721c-13.227,6.017-27.35,9.068-41.976,9.068c-19.327,0-38.142-5.466-54.411-15.808 c-15.846-10.073-28.603-24.276-36.89-41.073C119.384,318.439,114.885,316.913,111.172,318.746z" fill="#1e3186"/>
<path d="M284.635,366.78c1.761,0,3.529-0.616,4.954-1.872c25.204-22.199,39.659-54.13,39.659-87.605V147.732 c0-24.963-7.801-48.792-22.559-68.909c-2.449-3.34-7.142-4.061-10.483-1.611c-3.34,2.45-4.061,7.144-1.61,10.484 c12.856,17.526,19.652,38.286,19.652,60.036v129.571c0,29.169-12.602,56.997-34.573,76.349c-3.108,2.738-3.409,7.477-0.671,10.585 C280.487,365.92,282.556,366.78,284.635,366.78z" fill="#1e3186"/>
<path d="M270.318,64.059c1.304,0.903,2.792,1.337,4.266,1.337c2.377,0,4.715-1.127,6.171-3.228 c2.359-3.405,1.513-8.077-1.892-10.437c-5.678-3.935-11.733-7.382-18-10.244c-3.771-1.721-8.217-0.061-9.938,3.706 c-1.721,3.768-0.062,8.217,3.706,9.938C260.091,57.625,265.369,60.628,270.318,64.059z" fill="#1e3186"/>
<path d="M232.047,32.634C225.635,31.55,219.064,31,212.517,31C148.152,31,95.786,83.366,95.786,147.732v125.987 c0,4.142,3.358,7.5,7.5,7.5s7.5-3.358,7.5-7.5V147.732C110.786,91.637,156.423,46,212.517,46c5.711,0,11.44,0.479,17.027,1.424 c4.096,0.69,7.955-2.061,8.646-6.144C238.882,37.196,236.131,33.325,232.047,32.634z" fill="#1e3186"/>
<path d="M141.452,99.821c-2.32,3.431-1.419,8.094,2.012,10.414c3.431,2.319,8.094,1.419,10.414-2.012 C167.097,88.672,189.018,77,212.517,77c39.001,0,70.731,31.73,70.731,70.732v83.987c0,4.142,3.357,7.5,7.5,7.5s7.5-3.358,7.5-7.5 v-83.987c0-47.273-38.459-85.732-85.731-85.732C184.031,62,157.465,76.139,141.452,99.821z" fill="#1e3186"/>
<path d="M134.286,173.218c4.142,0,7.5-3.358,7.5-7.5v-17.986c0-4.142-3.358-7.5-7.5-7.5s-7.5,3.358-7.5,7.5v17.986 C126.786,169.86,130.144,173.218,134.286,173.218z" fill="#1e3186"/>
<path d="M126.786,277.303c0,31.025,16.878,59.725,44.048,74.901c1.158,0.647,2.412,0.954,3.65,0.954 c2.629,0,5.182-1.385,6.555-3.844c2.02-3.616,0.726-8.185-2.891-10.205c-22.429-12.528-36.362-36.21-36.362-61.805v-81.584 c0-4.142-3.358-7.5-7.5-7.5s-7.5,3.358-7.5,7.5V277.303z" fill="#1e3186"/>
<path d="M298.249,277.303v-13.584c0-4.142-3.357-7.5-7.5-7.5s-7.5,3.358-7.5,7.5v13.584c0,39.001-31.73,70.732-70.731,70.732 c-5.698,0-11.369-0.682-16.853-2.028c-4.025-0.987-8.084,1.474-9.071,5.497s1.474,8.084,5.497,9.071 c6.653,1.632,13.526,2.46,20.427,2.46C259.79,363.034,298.249,324.575,298.249,277.303z" fill="#1e3186"/>
<path d="M267.249,148.524c0-30.05-24.079-54.953-53.677-55.514c-14.822-0.28-28.786,5.283-39.369,15.668 c-10.587,10.388-16.417,24.258-16.417,39.054v129.571c0,4.142,3.358,7.5,7.5,7.5s7.5-3.358,7.5-7.5V147.732 c0-10.736,4.234-20.803,11.923-28.347c7.685-7.541,17.873-11.576,28.578-11.377c21.483,0.407,38.962,18.583,38.962,40.517v71.194 c0,4.142,3.357,7.5,7.5,7.5s7.5-3.358,7.5-7.5V148.524z" fill="#1e3186"/>
<path d="M188.786,285.987c0,21.632,17.599,39.232,39.231,39.232c21.632,0,39.231-17.599,39.231-39.232v-36.269 c0-4.142-3.357-7.5-7.5-7.5s-7.5,3.358-7.5,7.5v36.269c0,13.361-10.87,24.232-24.231,24.232s-24.231-10.87-24.231-24.232v-64.268 c0-4.142-3.358-7.5-7.5-7.5s-7.5,3.358-7.5,7.5V285.987z" fill="#1e3186"/>
<path d="M228.749,290.603c4.143,0,7.5-3.358,7.5-7.5v-134.9c0-12.932-9.902-23.55-22.545-24.174 c-6.567-0.323-12.787,1.991-17.541,6.516c-4.688,4.463-7.377,10.727-7.377,17.187v41.987c0,4.142,3.358,7.5,7.5,7.5 s7.5-3.358,7.5-7.5v-41.987c0-2.407,0.966-4.653,2.72-6.322c1.75-1.666,4.034-2.518,6.46-2.399 c4.567,0.225,8.283,4.349,8.283,9.192v134.9C221.249,287.245,224.606,290.603,228.749,290.603z" fill="#1e3186"/>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.8 KiB

View file

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 18.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" viewBox="0 0 425.034 425.034" style="enable-background:new 0 0 425.034 425.034;" xml:space="preserve" width="512px" height="512px">
<g>
<path d="M133.822,384.2c-33.836-24.953-54.037-64.915-54.037-106.898V147.731c0-3.798,0.162-7.633,0.482-11.396 c0.351-4.127-2.711-7.757-6.838-8.108c-4.123-0.347-7.757,2.711-8.108,6.838c-0.355,4.184-0.536,8.446-0.536,12.666v129.571 c0,23.526,5.669,46.938,16.395,67.704c10.375,20.087,25.5,37.815,43.739,51.266c1.34,0.988,2.899,1.464,4.446,1.464 c2.301,0,4.572-1.055,6.042-3.049C137.865,391.354,137.156,386.659,133.822,384.2z" fill="#1abb9b"/>
<path d="M168.819,402.675c-4.619-1.61-9.197-3.499-13.61-5.615c-3.734-1.791-8.215-0.215-10.005,3.521 c-1.791,3.735-0.215,8.215,3.52,10.005c4.915,2.356,10.014,4.46,15.158,6.253c0.817,0.285,1.65,0.42,2.469,0.42 c3.102,0,6.003-1.939,7.082-5.033C174.795,408.315,172.73,404.039,168.819,402.675z" fill="#1abb9b"/>
<path d="M352.749,243.219c-4.143,0-7.5,3.358-7.5,7.5v26.584c0,73.188-59.543,132.731-132.732,132.731 c-5.179,0-10.395-0.301-15.502-0.895c-4.12-0.479-7.838,2.469-8.316,6.584c-0.479,4.115,2.469,7.838,6.583,8.316 c5.681,0.661,11.479,0.995,17.235,0.995c81.459,0,147.731-66.272,147.731-147.731v-26.584 C360.249,246.576,356.891,243.219,352.749,243.219z" fill="#1abb9b"/>
<path d="M212.517,0c-31.895,0-62.263,10.003-87.824,28.928C99.64,47.478,81.373,72.889,71.867,102.417 c-1.27,3.943,0.898,8.168,4.841,9.438c3.944,1.268,8.168-0.898,9.438-4.841c8.54-26.525,24.955-49.358,47.473-66.03 C156.577,23.985,183.86,15,212.517,15c73.188,0,132.731,59.543,132.731,132.731v72.987c0,4.142,3.357,7.5,7.5,7.5 s7.5-3.358,7.5-7.5v-72.987C360.249,66.272,293.976,0,212.517,0z" fill="#1abb9b"/>
<path d="M111.172,318.746c-3.715,1.833-5.24,6.33-3.408,10.044c9.504,19.263,24.13,35.549,42.296,47.096 c18.678,11.873,40.275,18.149,62.457,18.149c16.784,0,32.996-3.504,48.187-10.415c3.771-1.715,5.437-6.162,3.722-9.932 c-1.716-3.77-6.162-5.438-9.933-3.721c-13.227,6.017-27.35,9.068-41.976,9.068c-19.327,0-38.142-5.466-54.411-15.808 c-15.846-10.073-28.603-24.276-36.89-41.073C119.384,318.439,114.885,316.913,111.172,318.746z" fill="#1abb9b"/>
<path d="M284.635,366.78c1.761,0,3.529-0.616,4.954-1.872c25.204-22.199,39.659-54.13,39.659-87.605V147.732 c0-24.963-7.801-48.792-22.559-68.909c-2.449-3.34-7.142-4.061-10.483-1.611c-3.34,2.45-4.061,7.144-1.61,10.484 c12.856,17.526,19.652,38.286,19.652,60.036v129.571c0,29.169-12.602,56.997-34.573,76.349c-3.108,2.738-3.409,7.477-0.671,10.585 C280.487,365.92,282.556,366.78,284.635,366.78z" fill="#1abb9b"/>
<path d="M270.318,64.059c1.304,0.903,2.792,1.337,4.266,1.337c2.377,0,4.715-1.127,6.171-3.228 c2.359-3.405,1.513-8.077-1.892-10.437c-5.678-3.935-11.733-7.382-18-10.244c-3.771-1.721-8.217-0.061-9.938,3.706 c-1.721,3.768-0.062,8.217,3.706,9.938C260.091,57.625,265.369,60.628,270.318,64.059z" fill="#1abb9b"/>
<path d="M232.047,32.634C225.635,31.55,219.064,31,212.517,31C148.152,31,95.786,83.366,95.786,147.732v125.987 c0,4.142,3.358,7.5,7.5,7.5s7.5-3.358,7.5-7.5V147.732C110.786,91.637,156.423,46,212.517,46c5.711,0,11.44,0.479,17.027,1.424 c4.096,0.69,7.955-2.061,8.646-6.144C238.882,37.196,236.131,33.325,232.047,32.634z" fill="#1abb9b"/>
<path d="M141.452,99.821c-2.32,3.431-1.419,8.094,2.012,10.414c3.431,2.319,8.094,1.419,10.414-2.012 C167.097,88.672,189.018,77,212.517,77c39.001,0,70.731,31.73,70.731,70.732v83.987c0,4.142,3.357,7.5,7.5,7.5s7.5-3.358,7.5-7.5 v-83.987c0-47.273-38.459-85.732-85.731-85.732C184.031,62,157.465,76.139,141.452,99.821z" fill="#1abb9b"/>
<path d="M134.286,173.218c4.142,0,7.5-3.358,7.5-7.5v-17.986c0-4.142-3.358-7.5-7.5-7.5s-7.5,3.358-7.5,7.5v17.986 C126.786,169.86,130.144,173.218,134.286,173.218z" fill="#1abb9b"/>
<path d="M126.786,277.303c0,31.025,16.878,59.725,44.048,74.901c1.158,0.647,2.412,0.954,3.65,0.954 c2.629,0,5.182-1.385,6.555-3.844c2.02-3.616,0.726-8.185-2.891-10.205c-22.429-12.528-36.362-36.21-36.362-61.805v-81.584 c0-4.142-3.358-7.5-7.5-7.5s-7.5,3.358-7.5,7.5V277.303z" fill="#1abb9b"/>
<path d="M298.249,277.303v-13.584c0-4.142-3.357-7.5-7.5-7.5s-7.5,3.358-7.5,7.5v13.584c0,39.001-31.73,70.732-70.731,70.732 c-5.698,0-11.369-0.682-16.853-2.028c-4.025-0.987-8.084,1.474-9.071,5.497s1.474,8.084,5.497,9.071 c6.653,1.632,13.526,2.46,20.427,2.46C259.79,363.034,298.249,324.575,298.249,277.303z" fill="#1abb9b"/>
<path d="M267.249,148.524c0-30.05-24.079-54.953-53.677-55.514c-14.822-0.28-28.786,5.283-39.369,15.668 c-10.587,10.388-16.417,24.258-16.417,39.054v129.571c0,4.142,3.358,7.5,7.5,7.5s7.5-3.358,7.5-7.5V147.732 c0-10.736,4.234-20.803,11.923-28.347c7.685-7.541,17.873-11.576,28.578-11.377c21.483,0.407,38.962,18.583,38.962,40.517v71.194 c0,4.142,3.357,7.5,7.5,7.5s7.5-3.358,7.5-7.5V148.524z" fill="#1abb9b"/>
<path d="M188.786,285.987c0,21.632,17.599,39.232,39.231,39.232c21.632,0,39.231-17.599,39.231-39.232v-36.269 c0-4.142-3.357-7.5-7.5-7.5s-7.5,3.358-7.5,7.5v36.269c0,13.361-10.87,24.232-24.231,24.232s-24.231-10.87-24.231-24.232v-64.268 c0-4.142-3.358-7.5-7.5-7.5s-7.5,3.358-7.5,7.5V285.987z" fill="#1abb9b"/>
<path d="M228.749,290.603c4.143,0,7.5-3.358,7.5-7.5v-134.9c0-12.932-9.902-23.55-22.545-24.174 c-6.567-0.323-12.787,1.991-17.541,6.516c-4.688,4.463-7.377,10.727-7.377,17.187v41.987c0,4.142,3.358,7.5,7.5,7.5 s7.5-3.358,7.5-7.5v-41.987c0-2.407,0.966-4.653,2.72-6.322c1.75-1.666,4.034-2.518,6.46-2.399 c4.567,0.225,8.283,4.349,8.283,9.192v134.9C221.249,287.245,224.606,290.603,228.749,290.603z" fill="#1abb9b"/>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.8 KiB

View file

@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 13.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 14948) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0"
id="svg2328" inkscape:output_extension="org.inkscape.output.svg.inkscape" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:cc="http://web.resource.org/cc/" sodipodi:version="0.32" sodipodi:docbase="C:\Documents and Settings\Alex Broersma.D2KY7761\Desktop" xmlns:dc="http://purl.org/dc/elements/1.1/" inkscape:version="0.45.1" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" sodipodi:docname="aa.svg" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="293px" height="194px"
viewBox="0 0 293 194" enable-background="new 0 0 293 194" xml:space="preserve">
<sodipodi:namedview id="base" inkscape:current-layer="svg2328" inkscape:window-y="-4" inkscape:window-x="-4" inkscape:cy="131.76504" inkscape:cx="219.80302" inkscape:zoom="4.0618557" pagecolor="#ffffff" bordercolor="#666666" borderopacity="1.0" objecttolerance="10.0" gridtolerance="10.0" guidetolerance="10.0" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:window-width="1440" inkscape:window-height="844">
</sodipodi:namedview>
<path id="path6" fill="#FFFFFF" d="M291.287,55.059c-13.78-67.174-143.762-71.429-227.549-20.25v5.652
C147.415-2.731,266.148-2.456,276.953,59.422c3.641,20.499-7.826,41.818-28.387,54.086v16.053
C273.316,120.48,298.616,91.088,291.287,55.059 M138.926,172.67c-57.824,5.353-118.073-3.071-126.508-48.434
C8.229,101.9,18.427,78.193,31.878,63.484v-7.875C7.624,76.957-5.551,103.961,2.056,135.844
c9.701,40.916,61.407,64.076,140.344,56.364c31.255-3.018,72.157-13.116,100.543-28.782v-22.258
C217.146,156.617,174.479,169.381,138.926,172.67z"/>
<path id="path8" fill="#FFFFFF" d="M238.312,45.348h-15.158v67.812c0,7.965,3.804,14.891,15.158,15.989"/>
<path id="path10" fill="#FFFFFF" d="M57.73,70.13H42.571v44.292c0,7.967,3.804,14.893,15.159,15.99"/>
<rect id="rect12" x="42.571" y="47.408" fill="#FFFFFF" width="15.159" height="14.426"/>
<path id="path14" fill="#FFFFFF" d="M148.571,129.699c-12.291,0-17.473-8.574-17.473-17.036V53.838h14.993V70.13h11.354V82.33
h-11.354v29.426c0,3.461,1.654,5.359,5.235,5.359h6.119v12.584H148.571"/>
<path id="path16" fill="#FFFFFF" d="M188.426,81.589c-5.125,0-9.094,2.665-10.748,6.264c-0.992,2.17-1.323,3.819-1.486,6.486h23.206
C199.066,87.826,196.143,81.589,188.426,81.589 M176.191,104.613c0,7.722,4.848,13.406,13.337,13.406
c6.671,0,9.979-1.868,13.837-5.684l9.262,8.929c-5.954,5.878-12.183,9.45-23.207,9.45c-14.387,0-28.168-7.885-28.168-30.855
c0-19.645,12.016-30.744,27.837-30.744c16.04,0,25.245,12.996,25.245,30.059v5.44L176.191,104.613"/>
<path id="path18" fill="#FFFFFF" d="M98.576,82.33c4.409,0,6.229,2.171,6.229,5.715v41.735h15.049V87.99
c0-8.49-4.521-17.86-17.694-17.86H71.125v59.65h14.993V82.329"/>
<text transform="matrix(1.0217 0 0 1 246.8975 55.2607)" fill="#127CC1" font-family="'ArialMT'" font-size="13.8854">®</text>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -13,7 +13,7 @@
<div class="comment" translate>
If enabled, wallets will also try to spend unconfirmed funds. This option may cause transaction delays.
</div>
<div class="item item-divider"></div>
<ion-toggle class="has-comment" ng-show="!isWP" ng-model="recentTransactionsEnabled.value" toggle-class="toggle-balanced" ng-change="recentTransactionsChange()">

View file

@ -14,16 +14,16 @@
<img src="img/a-smile_color_btn.png" alt="{{id}}" width="40">
<ion-spinner class="spinner-dark recent right m10 size-16" icon="crescent" ng-show="updatingPending[item.invoiceId]">
</ion-spinner>
<h2>
<span ng-if="item.claimCode">{{item.amount | currency : '$ ' : 2}} {{item.currency}}</span>
<span ng-if="!item.claimCode">-</span>
<h2 ng-if="item.amount">
{{item.amount | currency : '$ ' : 2}} {{item.currency}}
</h2>
<p>
<span class="assertive" ng-if="item.status == 'FAILURE' || item.status == 'RESEND'">Error</span>
<span class="assertive" ng-if="item.status == 'expired'">Expired</span>
<span class="assertive" ng-if="item.status == 'invalid'">Still waiting confirmation<br> (Use higher fees setting to faster delivery)</span>
<span class="text-gray" ng-if="item.status == 'PENDING'">Pending to confirmation</span>
<span class="assertive" ng-if="item.status == 'SUCCESS' && item.cardStatus == 'Canceled'">Canceled</span>
<span class="text-gray" ng-if="item.status == 'SUCCESS' && item.cardStatus == 'Fulfilled'">{{item.date | amTimeAgo}}</span>
<span class="text-gray">{{item.date | amTimeAgo}}</span>
</p>
</div>
</div>

View file

@ -76,7 +76,7 @@
<div class="col col-50">
<div class="size-12 text-bold">
{{tx.merchant.name}}
{{tx.merchant.name || 'Unknown Merchant'}}
</div>
<div class="size-12 text-gray">
<span ng-show="tx.merchant.city && tx.merchant.state">{{tx.merchant.city}}, {{tx.merchant.state}}</span>

View file

@ -16,7 +16,7 @@
<span translate>File/Text</span>
</div>
<div class="col" ng-click="hardware = true; phrase = file = false" ng-style="hardware &&
{'border-bottom-style': 'solid'}" ng-show="isCopay && (isChromeApp || isDevel)">
{'border-bottom-style': 'solid'}" ng-show="isCopay && (supportsLedger || supportsTrezor)">
<span translate>Hardware wallet</span>
</div>
</div>

View file

@ -6,8 +6,9 @@
<img style="height:0.6em; margin-right: 1px;" ng-show="wallet.network != 'livenet'" src="img/icon-testnet-white.svg">
<img style="height:0.6em; margin-right: 1px;" ng-show="!wallet.canSign() && !wallet.isPrivKeyExternal()" src="img/icon-read-only-white.svg">
<img style="height:0.6em; margin-right: 1px;" ng-show="wallet.getPrivKeyExternalSourceName() == 'trezor'" src="img/icon-trezor-white.svg">
<img style="height:0.6em; margin-right: 1px;" ng-show="wallet.getPrivKeyExternalSourceName() == 'ledger'" src="img/icon-ledger-white.svg">
<img style="height:0.6em; margin-right: 1px;" ng-show="wallet.getPrivKeyExternalSourceName() == 'trezor'" src="img/icon-trezor-white.svg">
<img style="height:0.6em; margin-right: 1px;" ng-show="wallet.getPrivKeyExternalSourceName() == 'ledger'" src="img/icon-ledger-white.svg">
<img style="height:0.6em; margin-right: 1px;" ng-show="wallet.getPrivKeyExternalSourceName() == 'intelTEE'" src="img/icon-inteltee-white.svg">
<span ng-show="wallet.credentials.n > 1" class="size-12"><span translate>{{wallet.m}}-of-{{wallet.n}}</span></span>
<span class="size-12 dib" style="height:0.6em; margin-right: 1px;" ng-show="wallet.credentials.account">#{{wallet.credentials.account || 0}} </span>

17
www/views/lockSetup.html Normal file
View file

@ -0,0 +1,17 @@
<ion-view class="settings">
<ion-nav-bar class="bar-royal">
<ion-nav-title>{{'Startup Lock' | translate}}</ion-nav-title>
<ion-nav-back-button>
</ion-nav-back-button>
</ion-nav-bar>
<ion-content>
<ion-radio ng-repeat="opt in options" ng-value="opt" ng-model="currentOption" ng-click="select(opt.method)" ng-disabled="opt.needsBackup">
<span ng-class="{'disabled': opt.needsBackup}">{{opt.label}}</span>
</ion-radio>
<div class="assertive" style="text-align: center; margin: 4rem" ng-if="errorMsg">
{{errorMsg}}
</div>
</ion-content>
</ion-view>

21
www/views/lockedView.html Normal file
View file

@ -0,0 +1,21 @@
<ion-view id="locked-view">
<ion-nav-bar class="bar-royal">
<ion-nav-title>{{title}}</ion-nav-title>
</ion-nav-bar>
<ion-content>
<div ng-if="appName == 'copay'" class="img-container-copay">
<i class="icon big-icon-svg"><div class="bg"></div></i>
</div>
<div ng-if="appName == 'bitpay'" class="img-container-bitpay">
<i class="icon big-icon-svg"><div class="bg"></div></i>
</div>
<div class="comments">
<div class="header" translate>One-touch Sign In</div>
<div class="text-content" translate>Please place your fingertip on the scanner to verify your identity</div>
</div>
<button type="submit" style="margin-top: 15%"
class="button button-standard button-primary" ng-click="requestFingerprint()" translate>Scan again
</button>
</ion-content>
</ion-view>

View file

@ -11,13 +11,21 @@
<div class="header-modal text-center">
<img src="img/a_generic.jpg" alt="Amazon.com Gift Card" width="230" ng-click="refreshGiftCard()">
<div class="m10t">
Gift Card Amount:
<span class="text-bold">
{{card.amount | currency : '$ ' : 2}}
</span>
</div>
<div class="m10t">
Created
{{card.date | amTimeAgo}}
</div>
<div ng-show="card.claimCode">
<div class="m10t">
Gift Card Amount:
<span class="text-bold">
{{card.amount | currency : '$ ' : 2}}
</span>
</div>
<div ng-show="card.cardStatus !== 'Canceled'">
Claim code: <span class="text-bold" copy-to-clipboard="card.claimCode">{{card.claimCode}}</span>
</div>
@ -42,6 +50,10 @@
<span class="text-bold" ng-show="card.status == 'PENDING'">
PENDING
</span>
<span class="text-bold" ng-show="card.status=='invalid'">
STILL PENDING
</span>
<span class="text-bold" ng-show="card.status == 'FAILURE' || card.status == 'RESEND'">
FAILURE
</span>

73
www/views/pin.html Normal file
View file

@ -0,0 +1,73 @@
<ion-view id="pin" hide-tabs hide-back-button="!fromSettings">
<ion-nav-bar class="bar-clear">
<ion-nav-back-button>
</ion-nav-back-button>
</ion-nav-bar>
<div class="content">
<div class="block-text row">
<div class="message" ng-if="!confirmPin && !error" translate>Please enter your PIN</div>
<div class="message" ng-if="confirmPin && !error" translate>Confirm your PIN</div>
<div class="message error" ng-if="error">
<div ng-if="!expires" translate>Incorrect PIN, try again.</div>
<time ng-if="expires" translate>Try again in {{expires}}</time>
</div>
</div>
<div class="app-icon">
<i class="icon big-icon-svg">
<div class="bg"></div>
</i>
</div>
<div class="block-code">
<div class="row">
<div class="col circle-{{appName}}" ng-class="getFilledClass(1)"></div>
<div class="col circle-{{appName}}" ng-class="getFilledClass(2)"></div>
<div class="col circle-{{appName}}" ng-class="getFilledClass(3)"></div>
<div class="col circle-{{appName}}" ng-class="getFilledClass(4)"></div>
</div>
</div>
<div class="block-buttons">
<div class="row">
<div class="col" ng-click="updatePin('1')">
<div class="keyboard">1</div>
</div>
<div class="col" ng-click="updatePin('2')">
<div class="keyboard">2</div>
</div>
<div class="col" ng-click="updatePin('3')">
<div class="keyboard">3</div>
</div>
</div>
<div class="row">
<div class="col" ng-click="updatePin('4')">
<div class="keyboard">4</div>
</div>
<div class="col" ng-click="updatePin('5')">
<div class="keyboard">5</div>
</div>
<div class="col" ng-click="updatePin('6')">
<div class="keyboard">6</div>
</div>
</div>
<div class="row">
<div class="col" ng-click="updatePin('7')">
<div class="keyboard">7</div>
</div>
<div class="col" ng-click="updatePin('8')">
<div class="keyboard">8</div>
</div>
<div class="col" ng-click="updatePin('9')">
<div class="keyboard">9</div>
</div>
</div>
<div class="row">
<div class="col"></div>
<div class="col" ng-click="updatePin('0')">
<div class="">0</div>
</div>
<div class="col" ng-click="delete()">
<div class="keyboard icon ion-arrow-left-a"></div>
</div>
</div>
</div>
</div>
</ion-view>

View file

@ -18,12 +18,6 @@
</span>
<i class="icon bp-arrow-right"></i>
</a>
<a class="item" ng-show="wallet.isPrivKeyExternal()">
<span translate>Hardware Wallet</span>
<span class="item-note">
{{externalSource}}
</span>
</a>
<a class="item item-icon-right" ui-sref="tabs.preferences.preferencesColor">
<span translate>Color</span>
<span class="item-note">
@ -38,7 +32,7 @@
<span class="toggle-label" translate>Hide Balance</span>
</ion-toggle>
<div class="item item-divider" translate>
<div class="item item-divider" ng-hide="wallet.isPrivKeyExternal() || !wallet.canSign()" translate>
Security
</div>
<a class="item item-icon-right" ui-sref="tabs.preferences.backupWarning({from: 'tabs.preferences'})" ng-hide="wallet.isPrivKeyExternal()">

View file

@ -0,0 +1,41 @@
<ion-view class="settings">
<ion-nav-bar class="bar-royal">
<ion-nav-title>{{externalSource.longName}}</ion-nav-title>
<ion-nav-back-button>
</ion-nav-back-button>
</ion-nav-bar>
<ion-content>
<div ng-include="'views/includes/walletItem.html'"></div>
<div ng-if="!externalSource.isEmbeddedHardware">
<div ng-if="!hardwareConnected" class="info centered">
<span translate>No hardware information available.</span>
</div>
</div>
<div ng-if="externalSource.isEmbeddedHardware">
<div ng-if="!hardwareConnected" class="warning centered">
<span translate>Hardware not connected.</span><br>
<span translate>Check installation and retry.</span>
</div>
<div ng-if="hardwareConnected">
<div class="list">
<div class="item">
<span translate>Version</span>
<span class="item-note">
{{externalSource.version || 'hardware disconnected'}}
</span>
</div>
</div>
<div class="padding">
<button class="button button-standard button-assertive" ng-click="showMneumonicFromHardwarePopup()">
{{'Show Recovery Phrase'|translate}}
</button>
</div>
</div>
</div>
</ion-content>
</ion-view>

View file

@ -43,12 +43,19 @@
{{derivationStrategy}}
</span>
</div>
<div class="item" ng-show="wallet.isPrivKeyExternal()">
<div class="item" ng-show="wallet.isPrivKeyExternal() && !externalSource.isEmbeddedHardware">
<span translate>Hardware Wallet</span>
<span class="item-note">
{{wallet.getPrivKeyExternalSourceName()}}
{{externalSource}}
</span>
</div>
<a class="item item-icon-right" href ui-sref="tabs.preferences.preferencesExternal" ng-show="wallet.isPrivKeyExternal() && externalSource.isEmbeddedHardware">
<span translate>Hardware Wallet</span>
<span class="item-note">
{{externalSource}}
</span>
<i class="icon bp-arrow-right"></i>
</a>
<div class="item" ng-show="!wallet.isPrivKeyExternal() && !wallet.canSign()">
<span translate></span>
<span class="item-note">

View file

@ -90,7 +90,7 @@
ng-model="formData.derivationPath">
</label>
<ion-toggle ng-show="seedSource.id == 'new'" ng-model="formData.testnetEnabled" toggle-class="toggle-positive">
<ion-toggle ng-show="seedSource.supportsTestnet" ng-model="formData.testnetEnabled" toggle-class="toggle-positive">
<span translate>Testnet</span>
</ion-toggle>

View file

@ -120,7 +120,7 @@
ng-model="formData.derivationPath">
</label>
<ion-toggle ng-show="seedSource.id == 'new'" ng-model="formData.testnetEnabled" toggle-class="toggle-positive">
<ion-toggle ng-show="seedSource.supportsTestnet" ng-model="formData.testnetEnabled" toggle-class="toggle-positive">
Testnet
</ion-toggle>

View file

@ -91,7 +91,7 @@
</span>
<span ng-if="wallet.isComplete()">
<span ng-if="!wallet.balanceHidden"> {{wallet.status.totalBalanceStr ? wallet.status.totalBalanceStr : ( wallet.cachedBalance ? wallet.cachedBalance + (wallet.cachedBalanceUpdatedOn ? ' &middot; ' + ( wallet.cachedBalanceUpdatedOn * 1000 | amTimeAgo) : '') : '' ) }} </span>
<span ng-if="wallet.balanceHidden" translate>[Balance Hidden]</span>
<span class="tab-home__wallet__multisig-number" ng-if="wallet.n > 1">
{{wallet.m}}-of-{{wallet.n}}

View file

@ -15,7 +15,7 @@
<select ng-model="formData.seedSource" ng-options="seed as seed.label for seed in seedOptions"></select>
</label>
<label class="item item-input item-stacked-label" ng-show="formData.seedSource.id == 'trezor' || formData.seedSource.id == 'ledger'">
<label class="item item-input item-stacked-label" ng-show="formData.seedSource.id == 'trezor' || formData.seedSource.id == 'ledger' || formData.seedSource.id == 'intelTee'">
<span class="input-label" translate>Account Number</span>
<input type="number" ng-model="formData.account" ignore-mouse-wheel>
</label>

View file

@ -53,6 +53,11 @@
</div>
<div class="row qr">
<div class="text-center col center-block" copy-to-clipboard="addr" ng-repeat="wallet in wallets track by $index" ng-class="walletPosition($index)">
<span class="overlay" ng-show="shouldShowReceiveAddressFromHardware()">
<button class="button button-standard button-primary" ng-click="showReceiveAddressFromHardware()">
<span translate>Show address</span>
</button>
</span>
<qrcode ng-if="walletAddrs[wallet.id]" size="220" data="bitcoin:{{walletAddrs[wallet.id]}}" color="#334"></qrcode>
</div>
</div>

View file

@ -33,7 +33,7 @@
<i class="icon big-icon-svg">
<img src="img/icon-heart.svg" class="bg"/>
</i>
<span>{{'Share'|translate}} {{appName}}</span>
<span translate>Share {{appName}}</span>
<i class="icon bp-arrow-right"></i>
</a>
@ -89,6 +89,16 @@
<i class="icon bp-arrow-right"></i>
</a>
<a class="item has-setting-value item-icon-left item-icon-right" ui-sref="tabs.lockSetup" ng-if="isCordova || isDevel">
<i class="icon ion-ios-locked-outline" ng-if="locked"></i>
<i class="icon ion-ios-unlocked-outline" ng-if="!locked"></i>
<span class="setting-title">{{'Lock App' | translate}}</span>
<span class="setting-value">
{{method}}
</span>
<i class="icon bp-arrow-right"></i>
</a>
<div class="item item-divider" ng-show="wallets[0]">{{'Wallets & Integrations' | translate}}</div>
<a class="item item-icon-left item-icon-right" href

View file

@ -18,8 +18,9 @@
</div>
<div class="amount-label">
<div class="amount-final">{{amountUnitStr}}</div>
<div class="alternative" ng-show="isFiat && rate">
@ {{rate | currency:'$':2}} per BTC
<div class="alternative">
<span ng-if="rate">@ {{rate | currency:'$':2}} per BTC</span>
<span ng-if="!rate">...</span>
</div>
</div>
</div>

View file

@ -84,6 +84,10 @@
<span ng-if="toggleFeeFiat">{{feeFiatStr}}</span>
</span>
</div>
<div class="item low-fees" ng-if="btx.action == 'received' && btx.lowFees">
<i class="icon"><img src="img/icon-warning.png" width="20px"></i>
<span translate>This transaction could take a long time to confirm or could be dropped due to the low fees set by the sender</span>
</div>
<div class="item single-line">
<span class="label" translate>Confirmations</span>
<span class="item-note">

View file

@ -1,4 +1,4 @@
<ion-view id="walletDetails">
<ion-view id="walletDetails" hide-tabs>
<ion-nav-bar ng-class="{'wallet-background-color-default': !wallet.color}" ng-style="{'background-color': wallet.color}">
<ion-nav-title>{{wallet.name}}</ion-nav-title>
@ -103,7 +103,7 @@
<div
ng-style="{'background-color':wallet.color}"
class="amount"
ng-class="{'collapsible': amountIsCollapsible, 'wallet-background-color-default': !wallet.color}"
ng-class="{'collapsible': amountIsCollapsible, 'wallet-background-color-default': !wallet.color}"
>
<div ng-if="!updatingStatus">
@ -238,6 +238,10 @@
<div ng-show="btx.action == 'received'" class="ellipsis">
<div ng-if="btx.note.body" class="wallet-details__tx-message ellipsis">{{btx.note.body}}</div>
<div ng-if="!btx.note.body" class="wallet-details__tx-message ellipsis" translate>Received</div>
<div class="low-fees" ng-if="btx.lowFees">
<i class="icon"><img src="img/icon-warning.png" width="20px"></i>
<span class="comment" translate>Low fees</span>
</div>
</div>
<div ng-show="btx.action == 'sent'" class="ellipsis">