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;
};
service.getBitcoreCash = function() {
return Client.BitcoreCash;
};
service.getErrors = function() {
return Client.errors;
};

View file

@ -56,7 +56,7 @@
"bezier-easing": "^2.0.3",
"bhttp": "1.2.1",
"bitauth": "^0.2.1",
"bitcore-wallet-client": "6.0.0",
"bitcore-wallet-client": "6.2.0",
"bower": "^1.7.9",
"cordova-android": "5.1.1",
"cordova-custom-config": "^3.0.5",
@ -107,7 +107,7 @@
"run:android": "cordova run android --device",
"run:android-release": "cordova run android --device --release",
"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:bitpay": "npm i fs-extra && cd app-template && node apply.js bitpay && npm i && cordova prepare",
"test": "echo \"no package tests configured\"",

View file

@ -14,9 +14,6 @@ angular.module('copayApp.controllers').controller('advancedSettingsController',
$scope.hideNextSteps = {
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() {
var opts = {
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.isWindowsPhoneApp = platformInfo.isCordova && platformInfo.isWP;
updateConfig();

View file

@ -309,7 +309,11 @@ angular.module('copayApp.controllers').controller('amountController', function($
if (a) {
$scope.alternativeAmount = txFormatService.formatAmount(a * unitToSatoshi, true);
} else {
$scope.alternativeAmount = 'N/A'; //TODO
if (result) {
$scope.alternativeAmount = 'N/A';
} else {
$scope.alternativeAmount = null;
}
$scope.allowSend = false;
}
} else {
@ -369,7 +373,7 @@ angular.module('copayApp.controllers').controller('amountController', function($
id: _id,
amount: $scope.useSendMax ? null : _amount,
currency: unit.id.toUpperCase(),
coin: $scope.useSendMax ? null : coin,
coin: coin,
useSendMax: $scope.useSendMax
});
} else {
@ -388,7 +392,7 @@ angular.module('copayApp.controllers').controller('amountController', function($
toName: $scope.toName,
toEmail: $scope.toEmail,
toColor: $scope.toColor,
coin: $scope.useSendMax ? null : coin,
coin: coin,
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';
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 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.noWalletMessage = msg;
$scope.criticalError = criticalError;
$log.warn('Not ready to make the payment:' + msg);
$timeout(function() {
$scope.$apply();
@ -81,7 +82,7 @@ angular.module('copayApp.controllers').controller('confirmController', function(
});
if (!$scope.wallets || !$scope.wallets.length) {
setNoWallet(gettextCatalog.getString('No wallets available'));
setNoWallet(gettextCatalog.getString('No wallets available'), true);
return cb();
}
@ -110,7 +111,7 @@ angular.module('copayApp.controllers').controller('confirmController', function(
return cb('Could not update any wallet');
if (lodash.isEmpty(filteredWallets)) {
setNoWallet(gettextCatalog.getString('Insufficient funds'));
setNoWallet(gettextCatalog.getString('Insufficient funds'), true);
}
$scope.wallets = lodash.clone(filteredWallets);
return cb();
@ -120,6 +121,9 @@ angular.module('copayApp.controllers').controller('confirmController', function(
};
// Setup $scope
var B = data.stateParams.coin == 'bch' ? bitcoreCash : bitcore;
// Grab stateParams
tx = {
@ -137,7 +141,7 @@ angular.module('copayApp.controllers').controller('confirmController', function(
toName: data.stateParams.toName,
toEmail: data.stateParams.toEmail,
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,
txp: {},
};

View file

@ -30,7 +30,7 @@ angular.module('copayApp.controllers').controller('createController',
$scope.formData.derivationPath = derivationPathHelper.default;
$scope.formData.coin = 'btc';
if (config.cashSupport.enabled) $scope.enableCash = true;
if (config.cashSupport) $scope.enableCash = true;
$scope.setTotalCopayers(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);
});
$scope.cashSupport = {
value: config.cashSupport
};
// TODO move this to a generic service
bitpayCardService.getCards(function(err, cards) {
if (err) $log.error(err);
@ -62,6 +67,8 @@ angular.module('copayApp.controllers').controller('tabSettingsController', funct
});
});
$scope.$on("$ionicView.enter", function(event, data) {
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', {
url: '/notifications',
views: {
@ -465,6 +475,7 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr
}
})
/*
*
* 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

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';
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 = {};
@ -84,14 +84,9 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat
data = sanitizeUri(data);
// Bitcoin or Bitcoin Cash URL
if ((/^bitcoin(cash)?:/).exec(data)) {
var coin = 'btc';
if ((/^bitcoincash:/).exec(data)) {
coin = 'bch';
data = data.replace(/bitcoincash:/, 'bitcoin:');
}
if (bitcore.URI.isValid(data)) {
// Bitcoin URL
if (bitcore.URI.isValid(data)) {
var coin = 'btc';
var parsed = new bitcore.URI(data);
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);
}
return true;
// Cash URI
} else if (bitcoreCash.URI.isValid(data)) {
var coin = 'bch';
var parsed = new bitcoreCash.URI(data);
} else {
$log.error('Invalid Bitcoin URL');
return false;
}
var addr = parsed.address ? parsed.address.toString() : '';
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;
// 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
} else if (/^https?:\/\//.test(data)) {
@ -140,6 +199,16 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat
} else {
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) {
var code = getParameterByName('code', data);
$ionicHistory.nextViewOptions({
@ -249,29 +318,29 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat
});
}
}
return false;
};
function goToAmountPage(toAddress) {
function goToAmountPage(toAddress, coin) {
$state.go('tabs.send', {}, {
'reload': true,
'notify': $state.current.name == 'tabs.send' ? false : true
});
$timeout(function() {
$state.transitionTo('tabs.send.amount', {
toAddress: toAddress
toAddress: toAddress,
coin: coin,
});
}, 100);
}
function handlePayPro(payProDetails) {
function handlePayPro(payProDetails, coin) {
var stateParams = {
toAmount: payProDetails.amount,
toAddress: payProDetails.toAddress,
description: payProDetails.memo,
paypro: payProDetails
paypro: payProDetails,
coin: coin,
};
scannerService.pausePreview();
$state.go('tabs.send', {}, {

View file

@ -45,7 +45,8 @@ angular.module('copayApp.services').factory('ongoingProcess', function($log, $ti
'cancelingGiftCard': 'Canceling Gift Card...',
'creatingGiftCard': 'Creating 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() {

View file

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

View file

@ -259,6 +259,8 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
};
function cacheStatus(status) {
if (status.wallet && status.wallet.scanStatus == 'running') return;
wallet.cachedStatus = status ||  {};
var cache = wallet.cachedStatus;
cache.statusUpdatedOn = Date.now();
@ -303,6 +305,8 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
cacheStatus(status);
wallet.scanning = status.wallet && status.wallet.scanStatus == 'running';
return cb(null, status);
});
};
@ -816,13 +820,10 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
$log.debug('Scanning wallet ' + wallet.id);
if (!wallet.isComplete()) return;
wallet.updating = true;
ongoingProcess.set('scanning', true);
wallet.scanning = true;
wallet.startScan({
includeCopayerBranches: true,
}, function(err) {
wallet.updating = false;
ongoingProcess.set('scanning', false);
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
root.getLowAmount = function(wallet, feeLevels, nbOutputs) {
var minFee = root.getMinFee(wallet,feeLevels, nbOutputs);
return parseInt( minFee / LOW_AMOUNT_RATIO);
var minFee = root.getMinFee(wallet, feeLevels, nbOutputs);
return parseInt(minFee / LOW_AMOUNT_RATIO);
};
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();
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');
return cb(err, {
allUtxos: resp || [],
allUtxos: resp || [],
lowUtxos: lowUtxos || [],
warning: minFee / balance > TOTAL_LOW_WARNING_RATIO,
minFee: minFee,
@ -1237,5 +1240,33 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
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;
});

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/logOptions";
@import "includes/checkBar";
@import "cashScan";

View file

@ -8,16 +8,6 @@
<ion-content>
<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()">
<span class="toggle-label" translate>Use Unconfirmed Funds</span>
</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">
<div class="list">
<div class="item head">
<div class="item head" ng-hide="criticalError">
<div class="sending-label">
<img src="img/icon-tx-sent-outline.svg">
<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>
</div>
<div class="item">
<div class="item" ng-hide="criticalError">
<span class="label" translate>To</span>
<span class="payment-proposal-to" ng-if="!recipientType">
<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}}
</span>
</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">
<span translate>Wallet Id</span>
<span class="item-note">

View file

@ -91,9 +91,10 @@
Incomplete
</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 && !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">
{{wallet.m}}-of-{{wallet.n}}
</span>

View file

@ -39,6 +39,20 @@
<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">
<i class="icon big-icon-svg">
<img src="img/icon-notifications.svg" class="bg"/>

View file

@ -31,7 +31,7 @@
<div
ng-click='updateAll(true)'
ng-show="!updateStatusError && !wallet.balanceHidden"
ng-show="!updateStatusError && !wallet.balanceHidden && !wallet.scanning"
on-hold="hideToggle()"
ng-style="{'transform': amountScale}"
ng-class="{amount__balance: amountIsCollapsible}">
@ -46,7 +46,7 @@
<div ng-style="{'transform': amountScale}"
class="amount__balance"
ng-show="!updateStatusError && wallet.balanceHidden"
ng-show="!updateStatusError && wallet.balanceHidden && !wallet.scanning"
on-hold="hideToggle()">
<strong class="size-24" translate>[Balance Hidden]</strong>
<div ng-style="{opacity: altAmountOpacity}" class="size-14 amount-alternative" translate>
@ -54,7 +54,18 @@
</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()">
<i class="icon ion-ios-checkmark-outline"></i>
<strong>