Merge pull request #6721 from matiu/feat/cash2

Feat/cash2
This commit is contained in:
Gustavo Maximiliano Cortez 2017-09-15 15:07:10 -04:00 committed by GitHub
commit 09a35ab95a
25 changed files with 577 additions and 83 deletions

View file

@ -13,6 +13,10 @@ bwcModule.provider("bwcService", function() {
return Client.Bitcore; return Client.Bitcore;
}; };
service.getBitcoreCash = function() {
return Client.BitcoreCash;
};
service.getErrors = function() { service.getErrors = function() {
return Client.errors; return Client.errors;
}; };

View file

@ -56,7 +56,7 @@
"bezier-easing": "^2.0.3", "bezier-easing": "^2.0.3",
"bhttp": "1.2.1", "bhttp": "1.2.1",
"bitauth": "^0.2.1", "bitauth": "^0.2.1",
"bitcore-wallet-client": "6.0.0", "bitcore-wallet-client": "6.2.0",
"bower": "^1.7.9", "bower": "^1.7.9",
"cordova-android": "5.1.1", "cordova-android": "5.1.1",
"cordova-custom-config": "^3.0.5", "cordova-custom-config": "^3.0.5",
@ -107,7 +107,7 @@
"run:android": "cordova run android --device", "run:android": "cordova run android --device",
"run:android-release": "cordova run android --device --release", "run:android-release": "cordova run android --device --release",
"log:android": "adb logcat | grep chromium", "log:android": "adb logcat | grep chromium",
"sign:android": "rm -f platforms/android/build/outputs/apk/android-release-signed-aligned.apk; jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore ../copay.keystore -signedjar platforms/android/build/outputs/apk/android-release-signed.apk platforms/android/build/outputs/apk/android-release-unsigned.apk copay_play && ../android-sdk-macosx/build-tools/25.0.3/zipalign -v 4 platforms/android/build/outputs/apk/android-release-signed.apk platforms/android/build/outputs/apk/android-release-signed-aligned.apk", "sign:android": "rm -f platforms/android/build/outputs/apk/android-release-signed-aligned.apk; jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore ../copay.keystore -signedjar platforms/android/build/outputs/apk/android-release-signed.apk platforms/android/build/outputs/apk/android-release-unsigned.apk copay_play && $ANDROID_HOME/build-tools/26.0.1/zipalign -v 4 platforms/android/build/outputs/apk/android-release-signed.apk platforms/android/build/outputs/apk/android-release-signed-aligned.apk",
"apply:copay": "npm i fs-extra && cd app-template && node apply.js copay && npm i && cordova prepare", "apply:copay": "npm i fs-extra && cd app-template && node apply.js copay && npm i && cordova prepare",
"apply:bitpay": "npm i fs-extra && cd app-template && node apply.js bitpay && npm i && cordova prepare", "apply:bitpay": "npm i fs-extra && cd app-template && node apply.js bitpay && npm i && cordova prepare",
"test": "echo \"no package tests configured\"", "test": "echo \"no package tests configured\"",

View file

@ -14,9 +14,6 @@ angular.module('copayApp.controllers').controller('advancedSettingsController',
$scope.hideNextSteps = { $scope.hideNextSteps = {
value: config.hideNextSteps.enabled value: config.hideNextSteps.enabled
}; };
$scope.cashSupport = {
value: config.cashSupport.enabled
};
}; };
@ -31,19 +28,6 @@ angular.module('copayApp.controllers').controller('advancedSettingsController',
}); });
}; };
$scope.cashSupportChange = function() {
var opts = {
cashSupport: {
enabled: $scope.cashSupport.value
}
};
configService.set(opts, function(err) {
if (err) $log.debug(err);
});
};
$scope.nextStepsChange = function() { $scope.nextStepsChange = function() {
var opts = { var opts = {
hideNextSteps: { hideNextSteps: {
@ -66,16 +50,6 @@ angular.module('copayApp.controllers').controller('advancedSettingsController',
}); });
}; };
$scope.openBitcoinCashWeb = function() {
var url = 'https://www.bitcoincash.org/';
var optIn = true;
var title = null;
var message = gettextCatalog.getString('Open bitcoincash.org?');
var okText = gettextCatalog.getString('Open');
var cancelText = gettextCatalog.getString('Go Back');
externalLinkService.open(url, optIn, title, message, okText, cancelText);
};
$scope.$on("$ionicView.beforeEnter", function(event, data) { $scope.$on("$ionicView.beforeEnter", function(event, data) {
$scope.isWindowsPhoneApp = platformInfo.isCordova && platformInfo.isWP; $scope.isWindowsPhoneApp = platformInfo.isCordova && platformInfo.isWP;
updateConfig(); updateConfig();

View file

@ -309,7 +309,11 @@ angular.module('copayApp.controllers').controller('amountController', function($
if (a) { if (a) {
$scope.alternativeAmount = txFormatService.formatAmount(a * unitToSatoshi, true); $scope.alternativeAmount = txFormatService.formatAmount(a * unitToSatoshi, true);
} else { } else {
$scope.alternativeAmount = 'N/A'; //TODO if (result) {
$scope.alternativeAmount = 'N/A';
} else {
$scope.alternativeAmount = null;
}
$scope.allowSend = false; $scope.allowSend = false;
} }
} else { } else {
@ -369,7 +373,7 @@ angular.module('copayApp.controllers').controller('amountController', function($
id: _id, id: _id,
amount: $scope.useSendMax ? null : _amount, amount: $scope.useSendMax ? null : _amount,
currency: unit.id.toUpperCase(), currency: unit.id.toUpperCase(),
coin: $scope.useSendMax ? null : coin, coin: coin,
useSendMax: $scope.useSendMax useSendMax: $scope.useSendMax
}); });
} else { } else {
@ -388,7 +392,7 @@ angular.module('copayApp.controllers').controller('amountController', function($
toName: $scope.toName, toName: $scope.toName,
toEmail: $scope.toEmail, toEmail: $scope.toEmail,
toColor: $scope.toColor, toColor: $scope.toColor,
coin: $scope.useSendMax ? null : coin, coin: coin,
useSendMax: $scope.useSendMax useSendMax: $scope.useSendMax
}); });
} }

View file

@ -0,0 +1,176 @@
'use strict';
angular.module('copayApp.controllers').controller('cashScanController',
function($rootScope, $timeout, $scope, $state, $stateParams, $ionicModal, $ionicScrollDelegate, $ionicHistory, $window, gettextCatalog, lodash, popupService, ongoingProcess, profileService, walletService, configService, $log, txFormatService, bwcError, pushNotificationsService, bwcService) {
var wallet;
var errors = bwcService.getErrors();
$scope.error = null;
$scope.walletDisabled = '#667';
$scope.$on("$ionicView.beforeEnter", function(event, data) {
updateAllWallets();
});
var goHome = function() {
$ionicHistory.nextViewOptions({
disableAnimate: true,
historyRoot: true
});
$ionicHistory.clearHistory();
$state.go('tabs.settings').then(function() {
$state.transitionTo('tabs.home');
});
}
var updateAllWallets = function() {
var walletsBTC = profileService.getWallets({
coin: 'btc',
onlyComplete: true,
network: 'livenet'
});
if (lodash.isEmpty(walletsBTC)) {
goHome();
return;
}
// Filter out already duplicated wallets
var walletsBCH = profileService.getWallets({
coin: 'bch',
network: 'livenet'
});
var xPubKeyIndex = lodash.indexBy(walletsBCH, "credentials.xPubKey");
walletsBTC = lodash.filter(walletsBTC, function(w) {
return !xPubKeyIndex[w.credentials.xPubKey];
});
// Filter out non BIP44 wallets
var wallets = lodash.filter(walletsBTC, function(w) {
return w.credentials.derivationStrategy == 'BIP44'
});
$scope.wallets = wallets;
$scope.nonBIP44Wallets = lodash.filter(walletsBTC, function(w) {
return w.credentials.derivationStrategy != 'BIP44';
});
var i = wallets.length;
var j = 0;
lodash.each(wallets, function(wallet) {
walletService.getBalance(wallet, {
coin: 'bch'
}, function(err, balance) {
if (err) {
wallet.error = (err === 'WALLET_NOT_REGISTERED') ? gettextCatalog.getString('Wallet not registered') : bwcError.msg(err);
$log.error(err);
return;
}
wallet.error = null;
wallet.bchBalance = txFormatService.formatAmountStr('bch', balance.availableAmount);
if (++j == i) {
//Done
$timeout(function() {
$rootScope.$apply();
}, 10);
}
});
});
};
$scope.duplicate = function(wallet) {
$scope.error = null;
$log.debug('Duplicating wallet for BCH:' + wallet.id + ':' + wallet.name);
var opts = {};
opts.name = wallet.name + '[BCH]';
opts.m = wallet.m;
opts.n = wallet.n;
opts.myName = wallet.credentials.copayerName;
opts.networkName = wallet.network;
opts.coin = 'bch';
opts.walletPrivKey = wallet.credentials.walletPrivKey;
opts.compliantDerivation = wallet.credentials.compliantDerivation;
function setErr(err, cb) {
if (!cb) cb = function() {};
$scope.error = bwcError.cb(err, gettextCatalog.getString('Could not duplicate'), function() {
return cb(err);
});
$timeout(function() {
$rootScope.$apply();
}, 10);
}
function importOrCreate(cb) {
walletService.getStatus(wallet, {}, function(err, status) {
if (err) return cb(err);
opts.singleAddress = status.wallet.singleAddress;
// first try to import
profileService.importExtendedPrivateKey(opts.extendedPrivateKey, opts, function(err, newWallet) {
if (err && !(err instanceof errors.NOT_AUTHORIZED)) {
return setErr(err, cb);
}
if (err) {
// create and store a wallet
return profileService.createWallet(opts, function(err, newWallet) {
if (err) return setErr(err, cb);
return cb(null, newWallet, true);
});
}
return cb(null, newWallet);
});
});
};
// Multisig wallets? add Copayers
function addCopayers(newWallet, isNew, cb) {
if (!isNew) return cb();
if (wallet.n == 1) return cb();
$log.info('Adding copayers for BCH wallet config:' + wallet.m + '-' + wallet.n);
walletService.copyCopayers(wallet, newWallet, function(err) {
if (err) return setErr(err, cb);
return cb();
});
};
walletService.getKeys(wallet, function(err, keys) {
if (err) {
$scope.error = err;
return $timeout(function() {
$rootScope.$apply();
}, 10);
}
opts.extendedPrivateKey = keys.xPrivKey;
ongoingProcess.set('duplicatingWallet', true);
importOrCreate(function(err, newWallet, isNew) {
if (err) {
ongoingProcess.set('duplicatingWallet', false);
return;
}
walletService.updateRemotePreferences(newWallet);
pushNotificationsService.updateSubscription(newWallet);
addCopayers(newWallet, isNew, function(err) {
ongoingProcess.set('duplicatingWallet', false);
if (err)
return setErr(err);
if (isNew)
walletService.startScan(newWallet, function() {});
goHome();
});
});
});
}
});

View file

@ -1,6 +1,6 @@
'use strict'; 'use strict';
angular.module('copayApp.controllers').controller('confirmController', function($rootScope, $scope, $interval, $filter, $timeout, $ionicScrollDelegate, gettextCatalog, walletService, platformInfo, lodash, configService, $stateParams, $window, $state, $log, profileService, bitcore, txFormatService, ongoingProcess, $ionicModal, popupService, $ionicHistory, $ionicConfig, payproService, feeService, bwcError, txConfirmNotification) { angular.module('copayApp.controllers').controller('confirmController', function($rootScope, $scope, $interval, $filter, $timeout, $ionicScrollDelegate, gettextCatalog, walletService, platformInfo, lodash, configService, $stateParams, $window, $state, $log, profileService, bitcore, bitcoreCash, txFormatService, ongoingProcess, $ionicModal, popupService, $ionicHistory, $ionicConfig, payproService, feeService, bwcError, txConfirmNotification) {
var countDown = null; var countDown = null;
var CONFIRM_LIMIT_USD = 20; var CONFIRM_LIMIT_USD = 20;
@ -58,9 +58,10 @@ angular.module('copayApp.controllers').controller('confirmController', function(
}); });
}; };
function setNoWallet(msg) { function setNoWallet(msg, criticalError) {
$scope.wallet = null; $scope.wallet = null;
$scope.noWalletMessage = msg; $scope.noWalletMessage = msg;
$scope.criticalError = criticalError;
$log.warn('Not ready to make the payment:' + msg); $log.warn('Not ready to make the payment:' + msg);
$timeout(function() { $timeout(function() {
$scope.$apply(); $scope.$apply();
@ -81,7 +82,7 @@ angular.module('copayApp.controllers').controller('confirmController', function(
}); });
if (!$scope.wallets || !$scope.wallets.length) { if (!$scope.wallets || !$scope.wallets.length) {
setNoWallet(gettextCatalog.getString('No wallets available')); setNoWallet(gettextCatalog.getString('No wallets available'), true);
return cb(); return cb();
} }
@ -110,7 +111,7 @@ angular.module('copayApp.controllers').controller('confirmController', function(
return cb('Could not update any wallet'); return cb('Could not update any wallet');
if (lodash.isEmpty(filteredWallets)) { if (lodash.isEmpty(filteredWallets)) {
setNoWallet(gettextCatalog.getString('Insufficient funds')); setNoWallet(gettextCatalog.getString('Insufficient funds'), true);
} }
$scope.wallets = lodash.clone(filteredWallets); $scope.wallets = lodash.clone(filteredWallets);
return cb(); return cb();
@ -120,6 +121,9 @@ angular.module('copayApp.controllers').controller('confirmController', function(
}; };
// Setup $scope // Setup $scope
var B = data.stateParams.coin == 'bch' ? bitcoreCash : bitcore;
// Grab stateParams // Grab stateParams
tx = { tx = {
@ -137,7 +141,7 @@ angular.module('copayApp.controllers').controller('confirmController', function(
toName: data.stateParams.toName, toName: data.stateParams.toName,
toEmail: data.stateParams.toEmail, toEmail: data.stateParams.toEmail,
toColor: data.stateParams.toColor, toColor: data.stateParams.toColor,
network: (new bitcore.Address(data.stateParams.toAddress)).network.name, network: (new B.Address(data.stateParams.toAddress)).network.name,
coin: data.stateParams.coin, coin: data.stateParams.coin,
txp: {}, txp: {},
}; };

View file

@ -30,7 +30,7 @@ angular.module('copayApp.controllers').controller('createController',
$scope.formData.derivationPath = derivationPathHelper.default; $scope.formData.derivationPath = derivationPathHelper.default;
$scope.formData.coin = 'btc'; $scope.formData.coin = 'btc';
if (config.cashSupport.enabled) $scope.enableCash = true; if (config.cashSupport) $scope.enableCash = true;
$scope.setTotalCopayers(tc); $scope.setTotalCopayers(tc);
updateRCSelect(tc); updateRCSelect(tc);

View file

@ -0,0 +1,42 @@
'use strict';
angular.module('copayApp.controllers').controller('preferencesCashController', function($scope, $log, $timeout, appConfigService, configService, gettextCatalog, externalLinkService) {
var updateConfig = function() {
var config = configService.getSync();
$scope.appName = appConfigService.nameCase;
$scope.cashSupport = {
value: config.cashSupport
};
$timeout(function() {
$scope.$apply();
});
};
$scope.cashSupportChange = function() {
var opts = {
cashSupport: $scope.cashSupport.value
};
configService.set(opts, function(err) {
if (err) $log.debug(err);
});
};
$scope.openBitcoinCashWeb = function() {
var url = 'https://www.bitcoincash.org/';
var optIn = true;
var title = null;
var message = gettextCatalog.getString('Open bitcoincash.org?');
var okText = gettextCatalog.getString('Open');
var cancelText = gettextCatalog.getString('Go Back');
externalLinkService.open(url, optIn, title, message, okText, cancelText);
};
$scope.$on("$ionicView.beforeEnter", function(event, data) {
updateConfig();
});
});

View file

@ -25,6 +25,11 @@ angular.module('copayApp.controllers').controller('tabSettingsController', funct
}, 10); }, 10);
}); });
$scope.cashSupport = {
value: config.cashSupport
};
// TODO move this to a generic service // TODO move this to a generic service
bitpayCardService.getCards(function(err, cards) { bitpayCardService.getCards(function(err, cards) {
if (err) $log.error(err); if (err) $log.error(err);
@ -62,6 +67,8 @@ angular.module('copayApp.controllers').controller('tabSettingsController', funct
}); });
}); });
$scope.$on("$ionicView.enter", function(event, data) { $scope.$on("$ionicView.enter", function(event, data) {
updateConfig(); updateConfig();
}); });

View file

@ -374,6 +374,16 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
* *
*/ */
.state('tabs.preferencesCash', {
url: '/preferencesCash',
views: {
'tab-settings@tabs': {
controller: 'preferencesCashController',
templateUrl: 'views/preferencesCash.html'
}
}
})
.state('tabs.notifications', { .state('tabs.notifications', {
url: '/notifications', url: '/notifications',
views: { views: {
@ -465,6 +475,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
} }
}) })
/* /*
* *
* Wallet preferences * Wallet preferences
@ -580,6 +591,16 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
} }
}) })
.state('tabs.preferencesCash.scan', {
url: '/cashScan',
views: {
'tab-settings@tabs': {
controller: 'cashScanController',
templateUrl: 'views/cashScan.html'
}
}
})
/* /*
* *
* Addressbook * Addressbook

View file

@ -0,0 +1,6 @@
'use strict';
angular.module('copayApp.services')
.factory('bitcoreCash', function bitcoreFactory(bwcService) {
var bitcoreCash = bwcService.getBitcoreCash();
return bitcoreCash;
});

View file

@ -1,6 +1,6 @@
'use strict'; 'use strict';
angular.module('copayApp.services').factory('incomingData', function($log, $state, $timeout, $ionicHistory, bitcore, $rootScope, payproService, scannerService, appConfigService, popupService, gettextCatalog) { angular.module('copayApp.services').factory('incomingData', function($log, $state, $timeout, $ionicHistory, bitcore, bitcoreCash, $rootScope, payproService, scannerService, appConfigService, popupService, gettextCatalog) {
var root = {}; var root = {};
@ -84,14 +84,9 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat
data = sanitizeUri(data); data = sanitizeUri(data);
// Bitcoin or Bitcoin Cash URL // Bitcoin URL
if ((/^bitcoin(cash)?:/).exec(data)) { if (bitcore.URI.isValid(data)) {
var coin = 'btc'; var coin = 'btc';
if ((/^bitcoincash:/).exec(data)) {
coin = 'bch';
data = data.replace(/bitcoincash:/, 'bitcoin:');
}
if (bitcore.URI.isValid(data)) {
var parsed = new bitcore.URI(data); var parsed = new bitcore.URI(data);
var addr = parsed.address ? parsed.address.toString() : ''; var addr = parsed.address ? parsed.address.toString() : '';
@ -110,12 +105,76 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat
goSend(addr, amount, message, coin); goSend(addr, amount, message, coin);
} }
return true; return true;
// Cash URI
} else if (bitcoreCash.URI.isValid(data)) {
var coin = 'bch';
var parsed = new bitcoreCash.URI(data);
} else { var addr = parsed.address ? parsed.address.toString() : '';
$log.error('Invalid Bitcoin URL'); var message = parsed.message;
return false;
}
var amount = parsed.amount ? parsed.amount : '';
// paypro not yet supported on cash
if (parsed.r) {
payproService.getPayProDetails(parsed.r, function(err, details) {
if (err) {
if (addr && amount)
goSend(addr, amount, message, coin);
else
popupService.showAlert(gettextCatalog.getString('Error'), err);
}
handlePayPro(details, coin);
});
} else {
goSend(addr, amount, message, coin);
}
return true;
// Cash URI with bitcoin core address version number?
} else if (bitcore.URI.isValid(data.replace(/^bitcoincash:/,'bitcoin:'))) {
$log.debug('Handling bitcoincash URI with legacy address');
var coin = 'bch';
var parsed = new bitcore.URI(data.replace(/^bitcoincash:/,'bitcoin:'));
var oldAddr = parsed.address ? parsed.address.toString() : '';
if (!oldAddr) return false;
var addr = '';
var a = bitcore.Address(oldAddr).toObject();
addr = bitcoreCash.Address.fromObject(a).toString();
// Translate address
$log.debug('address transalated to:' + addr);
popupService.showConfirm(
gettextCatalog.getString('Bitcoin cash Payment'),
gettextCatalog.getString('Payment address was translated to new Bitcoin Cash address format: ' + addr),
gettextCatalog.getString('OK'),
gettextCatalog.getString('Cancel'),
function(ret) {
if (!ret) return false;
var message = parsed.message;
var amount = parsed.amount ? parsed.amount : '';
// paypro not yet supported on cash
if (parsed.r) {
payproService.getPayProDetails(parsed.r, function(err, details) {
if (err) {
if (addr && amount)
goSend(addr, amount, message, coin);
else
popupService.showAlert(gettextCatalog.getString('Error'), err);
}
handlePayPro(details, coin);
});
} else {
goSend(addr, amount, message, coin);
}
}
);
return true;
// Plain URL // Plain URL
} else if (/^https?:\/\//.test(data)) { } else if (/^https?:\/\//.test(data)) {
@ -140,6 +199,16 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat
} else { } else {
goToAmountPage(data); goToAmountPage(data);
} }
} else if (bitcoreCash.Address.isValid(data, 'livenet')) {
if ($state.includes('tabs.scan')) {
root.showMenu({
data: data,
type: 'bitcoinAddress',
coin: 'bch',
});
} else {
goToAmountPage(data, 'bch');
}
} else if (data && data.indexOf(appConfigService.name + '://glidera') === 0) { } else if (data && data.indexOf(appConfigService.name + '://glidera') === 0) {
var code = getParameterByName('code', data); var code = getParameterByName('code', data);
$ionicHistory.nextViewOptions({ $ionicHistory.nextViewOptions({
@ -249,29 +318,29 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat
}); });
} }
} }
return false; return false;
}; };
function goToAmountPage(toAddress) { function goToAmountPage(toAddress, coin) {
$state.go('tabs.send', {}, { $state.go('tabs.send', {}, {
'reload': true, 'reload': true,
'notify': $state.current.name == 'tabs.send' ? false : true 'notify': $state.current.name == 'tabs.send' ? false : true
}); });
$timeout(function() { $timeout(function() {
$state.transitionTo('tabs.send.amount', { $state.transitionTo('tabs.send.amount', {
toAddress: toAddress toAddress: toAddress,
coin: coin,
}); });
}, 100); }, 100);
} }
function handlePayPro(payProDetails) { function handlePayPro(payProDetails, coin) {
var stateParams = { var stateParams = {
toAmount: payProDetails.amount, toAmount: payProDetails.amount,
toAddress: payProDetails.toAddress, toAddress: payProDetails.toAddress,
description: payProDetails.memo, description: payProDetails.memo,
paypro: payProDetails paypro: payProDetails,
coin: coin,
}; };
scannerService.pausePreview(); scannerService.pausePreview();
$state.go('tabs.send', {}, { $state.go('tabs.send', {}, {

View file

@ -45,7 +45,8 @@ angular.module('copayApp.services').factory('ongoingProcess', function($log, $ti
'cancelingGiftCard': 'Canceling Gift Card...', 'cancelingGiftCard': 'Canceling Gift Card...',
'creatingGiftCard': 'Creating Gift Card...', 'creatingGiftCard': 'Creating Gift Card...',
'buyingGiftCard': 'Buying Gift Card...', 'buyingGiftCard': 'Buying Gift Card...',
'topup': gettext('Top up in progress...') 'topup': gettext('Top up in progress...'),
'duplicatingWallet': gettext('Duplicating wallet...'),
}; };
root.clear = function() { root.clear = function() {

View file

@ -322,6 +322,7 @@ angular.module('copayApp.services')
var walletClient = bwcService.getClient(null, opts); var walletClient = bwcService.getClient(null, opts);
var network = opts.networkName || 'livenet'; var network = opts.networkName || 'livenet';
console.log('[profileService.js.324]'); //TODO
if (opts.mnemonic) { if (opts.mnemonic) {
try { try {
opts.mnemonic = root._normalizeMnemonic(opts.mnemonic); opts.mnemonic = root._normalizeMnemonic(opts.mnemonic);
@ -339,7 +340,12 @@ angular.module('copayApp.services')
} }
} else if (opts.extendedPrivateKey) { } else if (opts.extendedPrivateKey) {
try { try {
walletClient.seedFromExtendedPrivateKey(opts.extendedPrivateKey); walletClient.seedFromExtendedPrivateKey(opts.extendedPrivateKey, {
network: network,
account: opts.account || 0,
derivationStrategy: opts.derivationStrategy || 'BIP44',
coin: opts.coin,
});
} catch (ex) { } catch (ex) {
$log.warn(ex); $log.warn(ex);
return cb(gettextCatalog.getString('Could not create using the specified extended private key')); return cb(gettextCatalog.getString('Could not create using the specified extended private key'));
@ -389,6 +395,7 @@ angular.module('copayApp.services')
$log.debug('Creating Wallet:', opts); $log.debug('Creating Wallet:', opts);
$timeout(function() { $timeout(function() {
seedWallet(opts, function(err, walletClient) { seedWallet(opts, function(err, walletClient) {
console.log('[profileService.js.395:walletClient:]',walletClient); //TODO
if (err) return cb(err); if (err) return cb(err);
var name = opts.name || gettextCatalog.getString('Personal Wallet'); var name = opts.name || gettextCatalog.getString('Personal Wallet');
@ -400,6 +407,7 @@ angular.module('copayApp.services')
walletPrivKey: opts.walletPrivKey, walletPrivKey: opts.walletPrivKey,
coin: opts.coin coin: opts.coin
}, function(err, secret) { }, function(err, secret) {
console.log('[profileService.js.407:err:]',err); //TODO
if (err) return bwcError.cb(err, gettextCatalog.getString('Error creating wallet'), cb); if (err) return bwcError.cb(err, gettextCatalog.getString('Error creating wallet'), cb);
return cb(null, walletClient, secret); return cb(null, walletClient, secret);
}); });

View file

@ -259,6 +259,8 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
}; };
function cacheStatus(status) { function cacheStatus(status) {
if (status.wallet && status.wallet.scanStatus == 'running') return;
wallet.cachedStatus = status ||  {}; wallet.cachedStatus = status ||  {};
var cache = wallet.cachedStatus; var cache = wallet.cachedStatus;
cache.statusUpdatedOn = Date.now(); cache.statusUpdatedOn = Date.now();
@ -303,6 +305,8 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
cacheStatus(status); cacheStatus(status);
wallet.scanning = status.wallet && status.wallet.scanStatus == 'running';
return cb(null, status); return cb(null, status);
}); });
}; };
@ -816,13 +820,10 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
$log.debug('Scanning wallet ' + wallet.id); $log.debug('Scanning wallet ' + wallet.id);
if (!wallet.isComplete()) return; if (!wallet.isComplete()) return;
wallet.updating = true; wallet.scanning = true;
ongoingProcess.set('scanning', true);
wallet.startScan({ wallet.startScan({
includeCopayerBranches: true, includeCopayerBranches: true,
}, function(err) { }, function(err) {
wallet.updating = false;
ongoingProcess.set('scanning', false);
return cb(err); return cb(err);
}); });
}; };
@ -931,15 +932,17 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
// Approx utxo amount, from which the uxto is economically redeemable // Approx utxo amount, from which the uxto is economically redeemable
root.getLowAmount = function(wallet, feeLevels, nbOutputs) { root.getLowAmount = function(wallet, feeLevels, nbOutputs) {
var minFee = root.getMinFee(wallet,feeLevels, nbOutputs); var minFee = root.getMinFee(wallet, feeLevels, nbOutputs);
return parseInt( minFee / LOW_AMOUNT_RATIO); return parseInt(minFee / LOW_AMOUNT_RATIO);
}; };
root.getLowUtxos = function(wallet, levels, cb) { root.getLowUtxos = function(wallet, levels, cb) {
wallet.getUtxos({coin: wallet.coin}, function(err, resp) { wallet.getUtxos({
coin: wallet.coin
}, function(err, resp) {
if (err || !resp || !resp.length) return cb(); if (err || !resp || !resp.length) return cb();
var minFee = root.getMinFee(wallet, levels, resp.length); var minFee = root.getMinFee(wallet, levels, resp.length);
@ -955,7 +958,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
var totalLow = lodash.sum(lowUtxos, 'satoshis'); var totalLow = lodash.sum(lowUtxos, 'satoshis');
return cb(err, { return cb(err, {
allUtxos: resp || [], allUtxos: resp || [],
lowUtxos: lowUtxos || [], lowUtxos: lowUtxos || [],
warning: minFee / balance > TOTAL_LOW_WARNING_RATIO, warning: minFee / balance > TOTAL_LOW_WARNING_RATIO,
minFee: minFee, minFee: minFee,
@ -1237,5 +1240,33 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
else return 'bitcoin'; else return 'bitcoin';
} }
root.copyCopayers = function(wallet, newWallet, cb) {
var c = wallet.credentials;
var walletPrivKey = bitcore.PrivateKey.fromString(c.walletPrivKey);
var copayer = 1,
i = 0,
l = c.publicKeyRing.length;
var mainErr = null;
lodash.each(c.publicKeyRing, function(item) {
var name = item.copayerName || ('copayer ' + copayer++);
newWallet._doJoinWallet(newWallet.credentials.walletId, walletPrivKey, item.xPubKey, item.requestPubKey, name, {
coin: newWallet.credentials.coin,
}, function(err) {
//Ignore error is copayer already in wallet
if (err && !(err instanceof errors.COPAYER_IN_WALLET)) {
mainErr = err;
}
if (++i == l) {
return cb(mainErr);
}
});
});
};
return root; return root;
}); });

View file

@ -0,0 +1,39 @@
#cash-scan {
.comment {
color: #667;
font-size: 0.9em;
}
.item {
color: $v-dark-gray;
padding-top: 1.3rem;
padding-bottom: 1.3rem;
}
.heading {
font-size: 17px;
color: $v-dark-gray;
margin: 1rem 0;
padding-top: 5px;
padding-bottom: 5px;
border: none;
}
.text-disabled {
color: $v-light-gray;
}
.supported {
display: flex;
.wallet-content {
padding-left: 7px;
}
}
.duplicate-button {
position: absolute;
right: 15px;
padding-top: .5rem;
}
}

View file

@ -50,3 +50,4 @@
@import "includes/pin"; @import "includes/pin";
@import "includes/logOptions"; @import "includes/logOptions";
@import "includes/checkBar"; @import "includes/checkBar";
@import "cashScan";

View file

@ -8,16 +8,6 @@
<ion-content> <ion-content>
<div class="settings-list list"> <div class="settings-list list">
<ion-toggle class="has-comment" ng-model="cashSupport.value" toggle-class="toggle-balanced" ng-change="cashSupportChange()">
<span class="toggle-label" translate>Support Bitcoin Cash</span>
</ion-toggle>
<div class="comment">
<span translate>Enable Bitcoin Cash wallet creation and operation within the App.</span>
<a ng-click="openBitcoinCashWeb()" translate>Learn more</a>
</div>
<ion-toggle class="has-comment" ng-model="spendUnconfirmed.value" toggle-class="toggle-balanced" ng-change="spendUnconfirmedChange()"> <ion-toggle class="has-comment" ng-model="spendUnconfirmed.value" toggle-class="toggle-balanced" ng-change="spendUnconfirmedChange()">
<span class="toggle-label" translate>Use Unconfirmed Funds</span> <span class="toggle-label" translate>Use Unconfirmed Funds</span>
</ion-toggle> </ion-toggle>

60
www/views/cashScan.html Normal file
View file

@ -0,0 +1,60 @@
<ion-view id="cash-scan" hide-tabs>
<ion-nav-bar class="bar-royal">
<ion-nav-title>
<span translate>Bitcoin Cash (BCH) Balances</span>
</ion-nav-title>
<ion-nav-back-button>
</ion-nav-back-button>
</ion-nav-bar>
<ion-content>
<div class="list card">
<div class="item" ng-if="(!wallets || !wallets[0]) && !nonBIP44Wallets[0]">
<span class="assertive" translate>No wallets eligible for Bitcoin Cash support</span>
</div>
<div class="item" ng-if="error">
<span class="assertive">{{error}}</span>
</div>
<div class="item heading">
<span translate>BTC Wallets</span>
</div>
<div ng-repeat="wallet in wallets track by $index" class="item wallet supported">
<i class="icon big-icon-svg">
<img ng-src="img/{{wallet.network == 'testnet' ? 'icon-wallet-testnet' : (wallet.coin == 'btc' ? 'icon-btc' : 'icon-bch')}}.svg" ng-class="{'wallet-background-color-default': !wallet.color}" ng-style="{'background-color': wallet.color}" class="bg wallet"/>
</i>
<div class="wallet-content">
<div>{{wallet.name || wallet.id}}</div>
<div class="balanced">{{wallet.bchBalance || ('Checking...' | translate)}} </div>
<div class="tab-home__wallet__multisig-number" ng-if="wallet.n > 1" translate>{{wallet.m}}-of-{{wallet.n}}</div>
</div>
<div class="duplicate-button">
<button ng-click="duplicate(wallet)" class="button button-small button-outline button-primary" translate>Duplicate for BCH</button>
</div>
</div>
<div ng-if="nonBIP44Wallets[0]">
<div class="item item-divider"></div>
<div class="item heading">
<span translate>Non eligible BTC wallets</span>
</div>
<div ng-repeat="wallet in nonBIP44Wallets track by $index" class="item item-sub item-icon-left item-big-icon-left item-button-right wallet">
<i class="icon big-icon-svg">
<img ng-src="img/{{wallet.network == 'testnet' ? 'icon-wallet-testnet' : (wallet.coin == 'btc' ? 'icon-btc' : 'icon-bch')}}.svg" ng-class="{'wallet-background-color-default': !wallet.color}" ng-style="{'background-color': walletDisabled}" class="bg wallet"/>
</i>
<span class="text-disabled">{{wallet.name || wallet.id}}</span>
</div>
<div class="item">
<span class="comment">Some of you wallets are not eligible for Bitcon Cash support because there where created before Copay v1.2. Please use our recovery tool to access your Bitcoin Cash balance for those wallets</span>
</div>
</div>
</div>
</ion-content>

View file

@ -9,7 +9,7 @@
<ion-content class="add-bottom-for-cta"> <ion-content class="add-bottom-for-cta">
<div class="list"> <div class="list">
<div class="item head"> <div class="item head" ng-hide="criticalError">
<div class="sending-label"> <div class="sending-label">
<img src="img/icon-tx-sent-outline.svg"> <img src="img/icon-tx-sent-outline.svg">
<span translate ng-if="!tx.sendMax">Sending</span> <span translate ng-if="!tx.sendMax">Sending</span>
@ -27,7 +27,7 @@
<span class="item-note" ng-if="paymentExpired" ng-style="{'color': 'red'}" translate>Expired</span> <span class="item-note" ng-if="paymentExpired" ng-style="{'color': 'red'}" translate>Expired</span>
</div> </div>
<div class="item"> <div class="item" ng-hide="criticalError">
<span class="label" translate>To</span> <span class="label" translate>To</span>
<span class="payment-proposal-to" ng-if="!recipientType"> <span class="payment-proposal-to" ng-if="!recipientType">
<img src="img/icon-bitcoin-small.svg"> <img src="img/icon-bitcoin-small.svg">

View file

@ -0,0 +1,25 @@
<ion-view id="tab-notifications" class="settings" show-tabs>
<ion-nav-bar class="bar-royal">
<ion-nav-title>{{'Bitcoin Cash Support' | translate}}</ion-nav-title>
<ion-nav-back-button>
</ion-nav-back-button>
</ion-nav-bar>
<ion-content>
<div class="list">
<ion-toggle class="has-comment" ng-model="cashSupport.value" toggle-class="toggle-balanced" ng-change="cashSupportChange()">
<span class="toggle-label" translate>Support Bitcoin Cash</span>
</ion-toggle>
</div>
<div class="settings-explanation">
<div class="settings-description" ng-show="!cashSupport.value">
<span translate>Enable Bitcoin Cash wallet creation and operation within the App.</span>
<a ng-click="openBitcoinCashWeb()" translate>Learn more</a>
</div>
</div>
<div class="padding" ng-if="cashSupport.value">
<a class="button button-standard button-primary" ui-sref="tabs.preferencesCash.scan" translate>Scan your wallets for Bitcoin Cash</a>
</div>
</ion-content>
</ion-view>

View file

@ -13,6 +13,12 @@
{{walletName}} {{walletName}}
</span> </span>
</div> </div>
<div class="item" copy-to-clipboard="walletId">
<span translate>Coin</span>
<span class="item-note">
{{wallet.coin}}
</span>
</div>
<div class="item" copy-to-clipboard="walletId"> <div class="item" copy-to-clipboard="walletId">
<span translate>Wallet Id</span> <span translate>Wallet Id</span>
<span class="item-note"> <span class="item-note">

View file

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

View file

@ -39,6 +39,20 @@
<div class="item item-divider">{{'Preferences' | translate}}</div> <div class="item item-divider">{{'Preferences' | translate}}</div>
<a class="item has-setting-value item-icon-left item-icon-right" ui-sref="tabs.preferencesCash">
<i class="icon ion-ios-locked-outline" ng-if="cashSupport.value"></i>
<i class="icon ion-ios-unlocked-outline" ng-if="!cashSupport.value"></i>
<span class="setting-title">{{'Bitcoin Cash Support' | translate}}</span>
<span class="setting-value">
<span translate ng-if="cashSupport.value">Enabled</span>
<span translate ng-if="!cashSupport.value">Disabled</span>
</span>
<i class="icon bp-arrow-right"></i>
</a>
<a class="item item-icon-left item-icon-right" ui-sref="tabs.notifications"> <a class="item item-icon-left item-icon-right" ui-sref="tabs.notifications">
<i class="icon big-icon-svg"> <i class="icon big-icon-svg">
<img src="img/icon-notifications.svg" class="bg"/> <img src="img/icon-notifications.svg" class="bg"/>

View file

@ -31,7 +31,7 @@
<div <div
ng-click='updateAll(true)' ng-click='updateAll(true)'
ng-show="!updateStatusError && !wallet.balanceHidden" ng-show="!updateStatusError && !wallet.balanceHidden && !wallet.scanning"
on-hold="hideToggle()" on-hold="hideToggle()"
ng-style="{'transform': amountScale}" ng-style="{'transform': amountScale}"
ng-class="{amount__balance: amountIsCollapsible}"> ng-class="{amount__balance: amountIsCollapsible}">
@ -46,7 +46,7 @@
<div ng-style="{'transform': amountScale}" <div ng-style="{'transform': amountScale}"
class="amount__balance" class="amount__balance"
ng-show="!updateStatusError && wallet.balanceHidden" ng-show="!updateStatusError && wallet.balanceHidden && !wallet.scanning"
on-hold="hideToggle()"> on-hold="hideToggle()">
<strong class="size-24" translate>[Balance Hidden]</strong> <strong class="size-24" translate>[Balance Hidden]</strong>
<div ng-style="{opacity: altAmountOpacity}" class="size-14 amount-alternative" translate> <div ng-style="{opacity: altAmountOpacity}" class="size-14 amount-alternative" translate>
@ -54,7 +54,18 @@
</div> </div>
</div> </div>
<div ng-if="!wallet.balanceHidden && showBalanceButton" ng-style="{'opacity': altAmountOpacity, 'transform': amountScale}">
<div ng-style="{'transform': amountScale}"
class="amount__balance"
ng-show="wallet.scanning">
<strong class="size-24" translate>[Scanning Funds]</strong>
<div ng-style="{opacity: altAmountOpacity}" class="size-14 amount-alternative" translate>
Please wait
</div>
</div>
<div ng-if="!wallet.balanceHidden && !wallet.scanning && showBalanceButton" ng-style="{'opacity': altAmountOpacity, 'transform': amountScale}">
<button class="button button-standard button-primary amount__button-balance size-14" ng-click="openBalanceModal()"> <button class="button button-standard button-primary amount__button-balance size-14" ng-click="openBalanceModal()">
<i class="icon ion-ios-checkmark-outline"></i> <i class="icon ion-ios-checkmark-outline"></i>
<strong> <strong>