Merge branch 'master' of https://github.com/bitpay/copay into feat/app-identity

This commit is contained in:
Andy Phillipson 2016-12-22 09:59:35 -05:00
commit 9b3a3aab9d
213 changed files with 12663 additions and 7021 deletions

View file

@ -70,7 +70,7 @@ angular.module('copayApp.services').factory('amazonService', function($http, $lo
});
// Show pending task from the UI
storageService.setNextStep('AmazonGiftCards', true, function(err) {});
storageService.setNextStep('AmazonGiftCards', 'true', function(err) {});
};
root.getPendingGiftCards = function(cb) {

View file

@ -1,6 +1,6 @@
'use strict';
angular.module('copayApp.services')
.factory('backupService', function backupServiceFactory($log, $timeout, $stateParams, profileService, sjcl) {
.factory('backupService', function backupServiceFactory($log, $timeout, $stateParams, profileService, sjcl, $window) {
var root = {};
@ -80,7 +80,7 @@ angular.module('copayApp.services')
var walletName = (wallet.alias || '') + (wallet.alias ? '-' : '') + wallet.credentials.walletName;
if (opts.noSign) walletName = walletName + '-noSign'
var filename = walletName + '-Copaybackup.aes.json';
var filename = walletName + '-' + $window.appConfig.nameCase + 'backup.aes.json';
_download(ew, filename, cb)
};
return root;

View file

@ -7,7 +7,7 @@ angular.module('copayApp.services').factory('bitpayCardService', function($http,
var _setError = function(msg, e) {
$log.error(msg);
var error = e.data ? e.data.error : msg;
var error = (e && e.data && e.data.error) ? e.data.error : msg;
return error;
};
@ -104,7 +104,7 @@ angular.module('copayApp.services').factory('bitpayCardService', function($http,
};
root.bitAuthPair = function(obj, cb) {
var deviceName = 'Unknow device';
var deviceName = 'Unknown device';
if (platformInfo.isNW) {
deviceName = require('os').platform();
} else if (platformInfo.isCordova) {
@ -143,7 +143,7 @@ angular.module('copayApp.services').factory('bitpayCardService', function($http,
root.getBitpayDebitCards(function(err, data) {
if (err) return cb(err);
var card = lodash.find(data, {id : cardId});
if (!card) return cb(_setError('Not card found'));
if (!card) return cb(_setError('Card not found'));
// Get invoices
$http(_post('/api/v2/' + card.token, json, appIdentity)).then(function(data) {
$log.info('BitPay Get Invoices: SUCCESS');
@ -180,7 +180,7 @@ angular.module('copayApp.services').factory('bitpayCardService', function($http,
root.getBitpayDebitCards(function(err, data) {
if (err) return cb(err);
var card = lodash.find(data, {id : cardId});
if (!card) return cb(_setError('Not card found'));
if (!card) return cb(_setError('Card not found'));
$http(_post('/api/v2/' + card.token, json, appIdentity)).then(function(data) {
$log.info('BitPay TopUp: SUCCESS');
if(data.data.error) {
@ -258,13 +258,30 @@ angular.module('copayApp.services').factory('bitpayCardService', function($http,
root.remove = function(card, cb) {
storageService.removeBitpayDebitCard(BITPAY_CARD_NETWORK, card, function(err) {
if (err) {
$log.error('Error removing BitPay debit card: ' + err);
// Continue, try to remove/cleanup card history
}
storageService.removeBitpayDebitCardHistory(BITPAY_CARD_NETWORK, card, function(err) {
$log.info('BitPay Debit Card(s) Removed: SUCCESS');
if (err) {
$log.error('Error removing BitPay debit card transaction history: ' + err);
return cb(err);
}
$log.info('Successfully removed BitPay debit card');
return cb();
});
});
};
root.getRates = function(currency, cb) {
$http(_get('/rates/' + currency)).then(function(data) {
$log.info('BitPay Get Rates: SUCCESS');
return cb(data.data.error, data.data.data);
}, function(data) {
return cb(_setError('BitPay Error: Get Rates', data));
});
};
/*
* CONSTANTS
*/

View file

@ -15,6 +15,16 @@ angular.module('copayApp.services').factory('configService', function(storageSer
url: 'https://bws.bitpay.com/bws/api',
},
download: {
url: 'https://bitpay.com/wallet',
},
rateApp: {
ios: 'http://itunes.apple.com/WebObjects/MZStore.woa/wa/viewContentsUserReviews?id=1149581638&pageNumber=0&sortOrdering=2&type=Purple+Software&mt=8',
android: 'https://play.google.com/store/apps/details?id=com.bitpay.wallet',
wp: ''
},
// wallet default config
wallet: {
requiredCopayers: 2,

View file

@ -21,14 +21,10 @@ angular.module('copayApp.services').service('externalLinkService', function(plat
_restoreHandleOpenURL(old);
} else {
if (optIn) {
var message = gettextCatalog.getString(message),
title = gettextCatalog.getString(title),
okText = gettextCatalog.getString(okText),
cancelText = gettextCatalog.getString(cancelText),
openBrowser = function(res) {
if (res) window.open(url, '_system');
_restoreHandleOpenURL(old);
};
var openBrowser = function(res) {
if (res) window.open(url, '_system');
_restoreHandleOpenURL(old);
};
popupService.showConfirm(title, message, okText, cancelText, openBrowser);
} else {
window.open(url, '_system');

View file

@ -1,6 +1,6 @@
'use strict';
angular.module('copayApp.services').factory('feeService', function($log, $stateParams, bwcService, walletService, configService, gettext, lodash, txFormatService) {
angular.module('copayApp.services').factory('feeService', function($log, $stateParams, bwcService, walletService, configService, gettext, lodash, txFormatService, gettextCatalog) {
var root = {};
// Constant fee options to translate
@ -15,48 +15,48 @@ angular.module('copayApp.services').factory('feeService', function($log, $stateP
return configService.getSync().wallet.settings.feeLevel || 'normal';
};
root.getCurrentFeeValue = function(cb) {
console.log('[feeService.js.18:getCurrentFeeValue:] TODO TODO TODO'); //TODO
// TODO TODO TODO
var wallet = profileService.getWallet($stateParams.walletId);
root.getCurrentFeeValue = function(network, cb) {
network = network || 'livenet';
var feeLevel = root.getCurrentFeeLevel();
wallet.getFeeLevels(wallet.credentials.network, function(err, levels) {
if (err)
return cb({
message: 'Could not get dynamic fee'
});
root.getFeeLevels(function(err, levels) {
if (err) return cb(err);
var feeLevelValue = lodash.find(levels, {
var feeLevelValue = lodash.find(levels[network], {
level: feeLevel
});
if (!feeLevelValue || !feeLevelValue.feePerKB)
if (!feeLevelValue || !feeLevelValue.feePerKB) {
return cb({
message: 'Could not get dynamic fee for level: ' + feeLevel
message: gettextCatalog.getString("Could not get dynamic fee for level: {{feeLevel}}", {
feeLevel: feeLevel
})
});
}
var fee = feeLevelValue.feePerKB;
$log.debug('Dynamic fee: ' + feeLevel + ' ' + fee + ' SAT');
return cb(null, fee);
});
};
root.getFeeLevels = function(cb) {
var walletClient = bwcService.getClient();
var unitName = configService.getSync().wallet.settings.unitName;
walletClient.getFeeLevels('livenet', function(errLivenet, levelsLivenet) {
walletClient.getFeeLevels('testnet', function(errTestnet, levelsTestnet) {
if (errLivenet || errTestnet) $log.debug('Could not get dynamic fee');
else {
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;
}
}
return cb({
return cb(null, {
'livenet': levelsLivenet,
'testnet': levelsTestnet
});

View file

@ -0,0 +1,58 @@
'use strict';
angular.module('copayApp.services').factory('feedbackService', function($http, $log, $httpParamSerializer, configService) {
var root = {};
var URL = "https://script.google.com/macros/s/AKfycbybtvNSQKUfgzgXcj3jYLlvCKrcBoktjiJ1V8_cwd2yVkpUBGe3/exec";
root.send = function(dataSrc, cb) {
$http(_post(dataSrc)).then(function() {
$log.info("SUCCESS: Feedback sent");
return cb();
}, function(err) {
$log.info("ERROR: Feedback sent anyway.");
return cb(err);
});
};
var _post = function(dataSrc) {
return {
method: 'POST',
url: URL,
headers: {
'content-type': 'application/x-www-form-urlencoded; charset=UTF-8'
},
data: $httpParamSerializer(dataSrc)
};
};
root.isVersionUpdated = function(currentVersion, savedVersion) {
if (!verifyTagFormat(currentVersion))
return 'Cannot verify the format of version tag: ' + currentVersion;
if (!verifyTagFormat(savedVersion))
return 'Cannot verify the format of the saved version tag: ' + savedVersion;
var current = formatTagNumber(currentVersion);
var saved = formatTagNumber(savedVersion);
if (saved.major > current.major || (saved.major == current.major && saved.minor > current.minor))
return false;
return true;
function verifyTagFormat(tag) {
var regex = /^v?\d+\.\d+\.\d+$/i;
return regex.exec(tag);
};
function formatTagNumber(tag) {
var formattedNumber = tag.replace(/^v/i, '').split('.');
return {
major: +formattedNumber[0],
minor: +formattedNumber[1],
patch: +formattedNumber[2]
};
};
};
return root;
});

View file

@ -81,7 +81,7 @@ angular.module('copayApp.services').factory('glideraService', function($http, $l
$http(req).then(function(data) {
$log.info('Glidera Authorization Access Token: SUCCESS');
// Show pending task from the UI
storageService.setNextStep('BuyAndSell', true, function(err) {});
storageService.setNextStep('BuyAndSell', 'true', function(err) {});
return cb(null, data.data);
}, function(data) {
$log.error('Glidera Authorization Access Token: ERROR ' + data.statusText);
@ -192,8 +192,13 @@ angular.module('copayApp.services').factory('glideraService', function($http, $l
};
root.get2faCode = function(token, cb) {
if (!token) return cb('Invalid Token');
if (!token) {
$log.error('Glidera Sent 2FA code by SMS: ERROR Invalid Token');
return cb('Invalid Token');
}
$http(_get('/authentication/get2faCode', token)).then(function(data) {
$log.info('Glidera Sent 2FA code by SMS: SUCCESS');
return cb(null, data.status == 200 ? true : false);
}, function(data) {

View file

@ -31,17 +31,31 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat
if (!url) return;
name = name.replace(/[\[\]]/g, "\\$&");
var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
results = regex.exec(url);
results = regex.exec(url);
if (!results) return null;
if (!results[2]) return '';
return decodeURIComponent(results[2].replace(/\+/g, " "));
}
function checkPrivateKey(privateKey) {
try {
new bitcore.PrivateKey(privateKey, 'livenet');
} catch (err) {
return false;
}
return true;
}
// data extensions for Payment Protocol with non-backwards-compatible request
if ((/^bitcoin:\?r=[\w+]/).exec(data)) {
data = decodeURIComponent(data.replace('bitcoin:?r=', ''));
$state.go('tabs.send', {}, {'reload': true, 'notify': $state.current.name == 'tabs.send' ? false : true}).then(function() {
$state.transitionTo('tabs.send.confirm', {paypro: data});
$state.go('tabs.send', {}, {
'reload': true,
'notify': $state.current.name == 'tabs.send' ? false : true
}).then(function() {
$state.transitionTo('tabs.send.confirm', {
paypro: data
});
});
return true;
}
@ -55,31 +69,43 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat
var addr = parsed.address ? parsed.address.toString() : '';
var message = parsed.message;
var amount = parsed.amount ? parsed.amount : '';
var amount = parsed.amount ? parsed.amount : '';
if (parsed.r) {
payproService.getPayProDetails(parsed.r, function(err, details) {
handlePayPro(details);
});
} else {
$state.go('tabs.send', {}, {'reload': true, 'notify': $state.current.name == 'tabs.send' ? false : true});
$state.go('tabs.send', {}, {
'reload': true,
'notify': $state.current.name == 'tabs.send' ? false : true
});
// Timeout is required to enable the "Back" button
$timeout(function() {
if (amount) {
$state.transitionTo('tabs.send.confirm', {toAmount: amount, toAddress: addr, description:message});
$state.transitionTo('tabs.send.confirm', {
toAmount: amount,
toAddress: addr,
description: message
});
} else {
$state.transitionTo('tabs.send.amount', {toAddress: addr});
$state.transitionTo('tabs.send.amount', {
toAddress: addr
});
}
}, 100);
}
return true;
// Plain URL
// Plain URL
} else if (/^https?:\/\//.test(data)) {
payproService.getPayProDetails(data, function(err, details) {
if(err) {
root.showMenu({data: data, type: 'url'});
if (err) {
root.showMenu({
data: data,
type: 'url'
});
return;
}
handlePayPro(details);
@ -87,47 +113,75 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat
});
// Plain Address
} else if (bitcore.Address.isValid(data, 'livenet') || bitcore.Address.isValid(data, 'testnet')) {
if($state.includes('tabs.scan')) {
root.showMenu({data: data, type: 'bitcoinAddress'});
if ($state.includes('tabs.scan')) {
root.showMenu({
data: data,
type: 'bitcoinAddress'
});
} else {
goToAmountPage(data);
}
} else if (data && data.indexOf($window.appConfig.name + '://glidera') === 0) {
return $state.go('uriglidera', {url: data});
return $state.go('uriglidera', {
url: data
});
} else if (data && data.indexOf($window.appConfig.name + '://coinbase') === 0) {
return $state.go('uricoinbase', {url: data});
return $state.go('uricoinbase', {
url: data
});
// BitPayCard Authentication
} else if (data && data.indexOf($window.appConfig.name + '://') === 0) {
var secret = getParameterByName('secret', data);
var email = getParameterByName('email', data);
var otp = getParameterByName('otp', data);
$state.go('tabs.home', {}, {'reload': true, 'notify': $state.current.name == 'tabs.home' ? false : true}).then(function() {
$state.transitionTo('tabs.bitpayCardIntro', {
secret: secret,
email: email,
otp: otp
});
var secret = getParameterByName('secret', data);
var email = getParameterByName('email', data);
var otp = getParameterByName('otp', data);
$state.go('tabs.home', {}, {
'reload': true,
'notify': $state.current.name == 'tabs.home' ? false : true
}).then(function() {
$state.transitionTo('tabs.bitpayCardIntro', {
secret: secret,
email: email,
otp: otp
});
return true;
});
return true;
// Join
// Join
} else if (data && data.match(/^copay:[0-9A-HJ-NP-Za-km-z]{70,80}$/)) {
$state.go('tabs.home', {}, {'reload': true, 'notify': $state.current.name == 'tabs.home' ? false : true}).then(function() {
$state.transitionTo('tabs.add.join', {url: data});
$state.go('tabs.home', {}, {
'reload': true,
'notify': $state.current.name == 'tabs.home' ? false : true
}).then(function() {
$state.transitionTo('tabs.add.join', {
url: data
});
});
return true;
// Old join
// Old join
} else if (data && data.match(/^[0-9A-HJ-NP-Za-km-z]{70,80}$/)) {
$state.go('tabs.home', {}, {'reload': true, 'notify': $state.current.name == 'tabs.home' ? false : true}).then(function() {
$state.transitionTo('tabs.add.join', {url: data});
$state.go('tabs.home', {}, {
'reload': true,
'notify': $state.current.name == 'tabs.home' ? false : true
}).then(function() {
$state.transitionTo('tabs.add.join', {
url: data
});
});
return true;
} else if (data && (data.substring(0, 2) == '6P' || checkPrivateKey(data))) {
root.showMenu({
data: data,
type: 'privateKey'
});
} else {
if($state.includes('tabs.scan')) {
root.showMenu({data: data, type: 'text'});
if ($state.includes('tabs.scan')) {
root.showMenu({
data: data,
type: 'text'
});
}
}
@ -136,13 +190,18 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat
};
function goToAmountPage(toAddress) {
$state.go('tabs.send', {}, {'reload': true, 'notify': $state.current.name == 'tabs.send' ? false : true});
$state.go('tabs.send', {}, {
'reload': true,
'notify': $state.current.name == 'tabs.send' ? false : true
});
$timeout(function() {
$state.transitionTo('tabs.send.amount', {toAddress: toAddress});
$state.transitionTo('tabs.send.amount', {
toAddress: toAddress
});
}, 100);
}
function handlePayPro(payProDetails){
function handlePayPro(payProDetails) {
var stateParams = {
toAmount: payProDetails.amount,
toAddress: payProDetails.toAddress,
@ -150,7 +209,10 @@ angular.module('copayApp.services').factory('incomingData', function($log, $stat
paypro: payProDetails
};
scannerService.pausePreview();
$state.go('tabs.send', {}, {'reload': true, 'notify': $state.current.name == 'tabs.send' ? false : true}).then(function() {
$state.go('tabs.send', {}, {
'reload': true,
'notify': $state.current.name == 'tabs.send' ? false : true
}).then(function() {
$timeout(function() {
$state.transitionTo('tabs.send.confirm', stateParams);
});

View file

@ -1,7 +1,7 @@
'use strict';
angular.module('copayApp.services')
.factory('localStorageService', function(platformInfo, $timeout, $log) {
.factory('localStorageService', function(platformInfo, $timeout, $log, lodash) {
var isNW = platformInfo.isNW;
var isChromeApp = platformInfo.isChromeApp;
var root = {};
@ -45,6 +45,14 @@ angular.module('copayApp.services')
root.set = function(k, v, cb) {
if (isChromeApp || isNW) {
var obj = {};
if (lodash.isObject(v)) {
v = JSON.stringify(v);
}
if (!lodash.isString(v)) {
v = v.toString();
}
obj[k] = v;
chrome.storage.local.set(obj, cb);

View file

@ -25,7 +25,7 @@ angular.module('copayApp.services').factory('ongoingProcess', function($log, $ti
'recreating': gettext('Recreating Wallet...'),
'rejectTx': gettext('Rejecting payment proposal'),
'removeTx': gettext('Deleting payment proposal'),
'retrivingInputs': gettext('Retrieving inputs information'),
'retrievingInputs': gettext('Retrieving inputs information'),
'scanning': gettext('Scanning Wallet funds...'),
'sendingTx': gettext('Sending transaction'),
'signingTx': gettext('Signing transaction'),
@ -33,6 +33,13 @@ angular.module('copayApp.services').factory('ongoingProcess', function($log, $ti
'validatingWallet': gettext('Validating wallet integrity...'),
'validatingWords': gettext('Validating recovery phrase...'),
'loadingTxInfo': gettext('Loading transaction info...'),
'sendingFeedback': gettext('Sending feedback...'),
'generatingNewAddress': gettext('Generating new address...'),
'gettingAddresses': gettext('Getting addresses...'),
'sendingByEmail': gettext('Preparing addresses...'),
'sending2faCode': gettext('Sending 2FA code...'),
'buyingBitcoin': gettext('Buying Bitcoin...'),
'sellingBitcoin': gettext('Selling Bitcoin...')
};
root.clear = function() {
@ -64,7 +71,7 @@ angular.module('copayApp.services').factory('ongoingProcess', function($log, $ti
var showName = $filter('translate')(processNames[name] || name);
if(customHandler) {
if (customHandler) {
customHandler(processName, showName, isOn);
} else if (root.onGoingProcessName) {
if (isCordova) {

View file

@ -34,7 +34,8 @@ angular.module('copayApp.services').service('popupService', function($log, $ioni
$ionicPopup.prompt({
title: title,
subTitle: message,
inputType: opts.inputType,
cssClass: opts.class,
template: '<input ng-model="data.response" type="' + opts.inputType + '" autofocus>',
inputPlaceholder: opts.inputPlaceholder,
defaultText: opts.defaultText
}).then(function(res) {
@ -51,12 +52,12 @@ angular.module('copayApp.services').service('popupService', function($log, $ioni
var _cordovaConfirm = function(title, message, okText, cancelText, cb) {
var onConfirm = function(buttonIndex) {
if (buttonIndex == 1) return cb(true);
if (buttonIndex == 2) return cb(true);
else return cb(false);
}
okText = okText || gettextCatalog.getString('OK');
cancelText = cancelText || gettextCatalog.getString('Cancel');
navigator.notification.confirm(message, onConfirm, title, [okText, cancelText]);
navigator.notification.confirm(message, onConfirm, title, [cancelText, okText]);
};
var _cordovaPrompt = function(title, message, opts, cb) {
@ -118,7 +119,7 @@ angular.module('copayApp.services').service('popupService', function($log, $ioni
this.showPrompt = function(title, message, opts, cb) {
$log.warn(title ? (title + ': ' + message) : message);
opts = opts || {};
opts = opts ||  {};
if (isCordova && !opts.forceHTMLPrompt)
_cordovaPrompt(title, message, opts, cb);

View file

@ -1,6 +1,6 @@
'use strict';
angular.module('copayApp.services')
.factory('profileService', function profileServiceFactory($rootScope, $timeout, $filter, $log, sjcl, lodash, storageService, bwcService, configService, pushNotificationsService, gettext, gettextCatalog, bwcError, uxLanguage, platformInfo, txFormatService, $state) {
.factory('profileService', function profileServiceFactory($rootScope, $timeout, $filter, $log, sjcl, lodash, storageService, bwcService, configService, pushNotificationsService, gettextCatalog, bwcError, uxLanguage, platformInfo, txFormatService, $state) {
var isChromeApp = platformInfo.isChromeApp;
@ -361,14 +361,14 @@ angular.module('copayApp.services')
} catch (ex) {
$log.info(ex);
return cb(gettext('Could not create: Invalid wallet recovery phrase'));
return cb(gettextCatalog.getString('Could not create: Invalid wallet recovery phrase'));
}
} else if (opts.extendedPrivateKey) {
try {
walletClient.seedFromExtendedPrivateKey(opts.extendedPrivateKey);
} catch (ex) {
$log.warn(ex);
return cb(gettext('Could not create using the specified extended private key'));
return cb(gettextCatalog.getString('Could not create using the specified extended private key'));
}
} else if (opts.extendedPublicKey) {
try {
@ -378,7 +378,7 @@ angular.module('copayApp.services')
});
} catch (ex) {
$log.warn("Creating wallet from Extended Public Key Arg:", ex, opts);
return cb(gettext('Could not create using the specified extended public key'));
return cb(gettextCatalog.getString('Could not create using the specified extended public key'));
}
} else {
var lang = uxLanguage.getCurrentLanguage();
@ -421,7 +421,7 @@ angular.module('copayApp.services')
singleAddress: opts.singleAddress,
walletPrivKey: opts.walletPrivKey,
}, function(err, secret) {
if (err) return bwcError.cb(err, gettext('Error creating wallet'), cb);
if (err) return bwcError.cb(err, gettextCatalog.getString('Error creating wallet'), cb);
return cb(null, walletClient, secret);
});
});
@ -451,11 +451,11 @@ angular.module('copayApp.services')
if (lodash.find(root.profile.credentials, {
'walletId': walletData.walletId
})) {
return cb(gettext('Cannot join the same wallet more that once'));
return cb(gettextCatalog.getString('Cannot join the same wallet more that once'));
}
} catch (ex) {
$log.debug(ex);
return cb(gettext('Bad wallet invitation'));
return cb(gettextCatalog.getString('Bad wallet invitation'));
}
opts.networkName = walletData.network;
$log.debug('Joining Wallet:', opts);
@ -464,7 +464,7 @@ angular.module('copayApp.services')
if (err) return cb(err);
walletClient.joinWallet(opts.secret, opts.myName || 'me', {}, function(err) {
if (err) return bwcError.cb(err, gettext('Could not join wallet'), cb);
if (err) return bwcError.cb(err, gettextCatalog.getString('Could not join wallet'), cb);
addAndBindWalletClient(walletClient, {
bwsurl: opts.bwsurl
}, cb);
@ -521,12 +521,12 @@ angular.module('copayApp.services')
// Adds and bind a new client to the profile
var addAndBindWalletClient = function(client, opts, cb) {
if (!client || !client.credentials)
return cb(gettext('Could not access wallet'));
return cb(gettextCatalog.getString('Could not access wallet'));
var walletId = client.credentials.walletId
if (!root.profile.addWallet(JSON.parse(client.export())))
return cb(gettext('Wallet already in Copay'));
return cb(gettextCatalog.getString('Wallet already in Copay'));
var skipKeyValidation = root.profile.isChecked(platformInfo.ua, walletId);
@ -595,15 +595,7 @@ angular.module('copayApp.services')
password: opts.password
});
} catch (err) {
return cb(gettext('Could not import. Check input file and spending password'));
}
if (walletClient.hasPrivKeyEncrypted()) {
try {
walletClient.disablePrivateKeyEncryption();
} catch (e) {
$log.warn(e);
}
return cb(gettextCatalog.getString('Could not import. Check input file and spending password'));
}
str = JSON.parse(str);
@ -634,7 +626,7 @@ angular.module('copayApp.services')
if (err instanceof errors.NOT_AUTHORIZED)
return cb(err);
return bwcError.cb(err, gettext('Could not import'), cb);
return bwcError.cb(err, gettextCatalog.getString('Could not import'), cb);
}
addAndBindWalletClient(walletClient, {
@ -665,7 +657,7 @@ angular.module('copayApp.services')
if (err instanceof errors.NOT_AUTHORIZED)
return cb(err);
return bwcError.cb(err, gettext('Could not import'), cb);
return bwcError.cb(err, gettextCatalog.getString('Could not import'), cb);
}
addAndBindWalletClient(walletClient, {
@ -688,7 +680,7 @@ angular.module('copayApp.services')
if (err instanceof errors.NOT_AUTHORIZED)
err.name = 'WALLET_DOES_NOT_EXIST';
return bwcError.cb(err, gettext('Could not import'), cb);
return bwcError.cb(err, gettextCatalog.getString('Could not import'), cb);
}
addAndBindWalletClient(walletClient, {
@ -773,6 +765,12 @@ angular.module('copayApp.services')
});
}
if (opts.m) {
ret = lodash.filter(ret, function(w) {
return (w.credentials.m == opts.m);
});
}
if (opts.onlyComplete) {
ret = lodash.filter(ret, function(w) {
return w.isComplete();
@ -795,7 +793,7 @@ angular.module('copayApp.services')
root.getNotifications = function(opts, cb) {
opts = opts || {};
var TIME_STAMP = 60 * 60 * 24 * 7;
var TIME_STAMP = 60 * 60 * 6;
var MAX = 100;
var typeFilter = {
@ -861,26 +859,25 @@ angular.module('copayApp.services')
var finale = shown; // GROUPING DISABLED!
// var finale = [],
// prev;
//
//
// // Item grouping... DISABLED.
//
// // REMOVE (if we want 1-to-1 notification) ????
// lodash.each(shown, function(x) {
// if (prev && prev.walletId === x.walletId && prev.txpId && prev.txpId === x.txpId && prev.creatorId && prev.creatorId === x.creatorId) {
// prev.types.push(x.type);
// prev.data = lodash.assign(prev.data, x.data);
// prev.txid = prev.txid || x.txid;
// prev.amountStr = prev.amountStr || x.amountStr;
// prev.creatorName = prev.creatorName || x.creatorName;
// } else {
// finale.push(x);
// prev = x;
// }
// });
//
var finale = [],
prev;
// Item grouping... DISABLED.
// REMOVE (if we want 1-to-1 notification) ????
lodash.each(shown, function(x) {
if (prev && prev.walletId === x.walletId && prev.txpId && prev.txpId === x.txpId && prev.creatorId && prev.creatorId === x.creatorId) {
prev.types.push(x.type);
prev.data = lodash.assign(prev.data, x.data);
prev.txid = prev.txid || x.txid;
prev.amountStr = prev.amountStr || x.amountStr;
prev.creatorName = prev.creatorName || x.creatorName;
} else {
finale.push(x);
prev = x;
}
});
var u = bwcService.getUtils();
lodash.each(finale, function(x) {

View file

@ -111,18 +111,24 @@ RateService.prototype.fromFiat = function(amount, code) {
return amount / this.getRate(code) * this.BTC_TO_SAT;
};
RateService.prototype.listAlternatives = function() {
RateService.prototype.listAlternatives = function(sort) {
var self = this;
if (!this.isAvailable()) {
return [];
}
return self.lodash.map(this.getAlternatives(), function(item) {
var alternatives = self.lodash.map(this.getAlternatives(), function(item) {
return {
name: item.name,
isoCode: item.isoCode
}
});
if (sort) {
alternatives.sort(function(a, b) {
return a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1;
});
}
return self.lodash.uniq(alternatives, 'isoCode');
};
angular.module('copayApp.services').factory('rateService', function($http, lodash) {

View file

@ -77,23 +77,23 @@ angular.module('copayApp.services')
////////////////////////////////////////////////////////////////////////////
//
// UPGRADING STORAGE
//
//
// 1. Write a function to upgrade the desired storage key(s). The function should have the protocol:
//
//
// _upgrade_x(key, network, cb), where:
//
//
// `x` is the name of the storage key
// `key` is the name of the storage key being upgraded
// `key` is the name of the storage key being upgraded
// `network` is one of 'livenet', 'testnet'
//
// 2. Add the storage key to `_upgraders` object using the name of the key as the `_upgrader` object key
// with the value being the name of the upgrade function (e.g., _upgrade_x). In order to avoid conflicts
// when a storage key is involved in multiple upgraders as well as predicte the order in which upgrades
// occur the `_upgrader` object key should be prefixed with '##_' (e.g., '01_') to create a unique and
// occur the `_upgrader` object key should be prefixed with '##_' (e.g., '01_') to create a unique and
// sortable name. This format is interpreted by the _upgrade() function.
//
//
// Upgraders are executed in numerical order per the '##_' object key prefix.
//
//
var _upgraders = {
'00_bitpayDebitCards' : _upgrade_bitpayDebitCards, // 2016-11: Upgrade bitpayDebitCards-x to bitpayAccounts-x
'01_bitpayCardCredentials' : _upgrade_bitpayCardCredentials // 2016-11: Upgrade bitpayCardCredentials-x to appIdentity-x
@ -244,6 +244,14 @@ angular.module('copayApp.services')
storage.remove('profile', cb);
};
root.setFeedbackInfo = function(feedbackValues, cb) {
storage.set('feedback', feedbackValues, cb);
};
root.getFeedbackInfo = function(cb) {
storage.get('feedback', cb);
};
root.storeFocusedWalletId = function(id, cb) {
storage.set('focusedWalletId', id || '', cb);
};
@ -390,6 +398,14 @@ angular.module('copayApp.services')
storage.remove('nextStep-' + service, cb);
};
root.setLastCurrencyUsed = function(lastCurrencyUsed, cb) {
storage.set('lastCurrencyUsed', lastCurrencyUsed, cb)
};
root.getLastCurrencyUsed = function(cb) {
storage.get('lastCurrencyUsed', cb)
};
root.checkQuota = function() {
var block = '';
// 50MB
@ -459,7 +475,10 @@ angular.module('copayApp.services')
if (lodash.isEmpty(data) || !data.email) return cb('No card(s) to set');
storage.get('bitpayAccounts-' + network, function(err, bitpayAccounts) {
if (err) return cb(err);
bitpayAccounts = JSON.parse(bitpayAccounts) || {};
if (lodash.isString(bitpayAccounts)) {
bitpayAccounts = JSON.parse(bitpayAccounts);
}
bitpayAccounts = bitpayAccounts || {};
bitpayAccounts[data.email] = bitpayAccounts[data.email] || {};
bitpayAccounts[data.email]['bitpayDebitCards-' + network] = data;
storage.set('bitpayAccounts-' + network, JSON.stringify(bitpayAccounts), cb);
@ -468,7 +487,10 @@ angular.module('copayApp.services')
root.getBitpayDebitCards = function(network, cb) {
storage.get('bitpayAccounts-' + network, function(err, bitpayAccounts) {
bitpayAccounts = JSON.parse(bitpayAccounts) || {};
if (lodash.isString(bitpayAccounts)) {
bitpayAccounts = JSON.parse(bitpayAccounts);
}
bitpayAccounts = bitpayAccounts || {};
var cards = [];
Object.keys(bitpayAccounts).forEach(function(email) {
// For the UI, add the account email to the card object.
@ -490,15 +512,20 @@ angular.module('copayApp.services')
if (lodash.isEmpty(card) || !card.eid) return cb('No card to remove');
storage.get('bitpayAccounts-' + network, function(err, bitpayAccounts) {
if (err) cb(err);
bitpayAccounts = JSON.parse(bitpayAccounts) || {};
if (lodash.isString(bitpayAccounts)) {
bitpayAccounts = JSON.parse(bitpayAccounts);
}
bitpayAccounts = bitpayAccounts || {};
Object.keys(bitpayAccounts).forEach(function(userId) {
var data = bitpayAccounts[userId]['bitpayDebitCards-' + network];
var newCards = lodash.reject(data.cards, {'eid': card.eid});
var newCards = lodash.reject(data.cards, {
'eid': card.eid
});
data.cards = newCards;
root.setBitpayDebitCards(network, data, function(err) {
if (err) cb(err);
// If there are no more cards in storage then re-enable the next step entry.
root.getBitpayDebitCards(network, function(err, cards){
root.getBitpayDebitCards(network, function(err, cards) {
if (err) cb(err);
if (cards.length == 0) {
root.removeNextStep('BitpayCard', cb);

View file

@ -1,7 +1,7 @@
'use strict';
angular.module('copayApp.services')
.factory('trezor', function($log, $timeout, gettext, lodash, bitcore, hwWallet) {
.factory('trezor', function($log, $timeout, lodash, bitcore, hwWallet) {
var root = {};
var SETTLE_TIME = 3000;
@ -82,7 +82,7 @@ angular.module('copayApp.services')
if (txp.outputs.length > 1)
return callback('Only single output TXPs are supported in TREZOR');
} else {
return callback('Unknown TXP at TREZOR');
return callback('Unknown TXP at TREZOR');
}
if (txp.outputs) {

View file

@ -8,42 +8,39 @@ angular.module('copayApp.services')
root.availableLanguages = [{
name: 'English',
isoCode: 'en',
}, {
name: 'Český',
isoCode: 'cs',
}, {
name: 'Français',
isoCode: 'fr',
}, {
name: 'Italiano',
isoCode: 'it',
}, {
name: 'Deutsch',
isoCode: 'de',
}, {
name: 'Español',
isoCode: 'es',
}, {
name: '日本語',
isoCode: 'ja',
useIdeograms: true,
}, {
name: '中文(简体)',
isoCode: 'zh',
useIdeograms: true,
}, {
name: 'Polski',
isoCode: 'pl',
}, {
name: 'Pусский',
isoCode: 'ru',
name: 'Français',
isoCode: 'fr',
// }, {
// name: 'Český',
// isoCode: 'cs',
// }, {
// name: 'Italiano',
// isoCode: 'it',
// }, {
// name: 'Deutsch',
// isoCode: 'de',
// }, {
// name: '日本語',
// isoCode: 'ja',
// useIdeograms: true,
// }, {
// name: '中文(简体)',
// isoCode: 'zh',
// useIdeograms: true,
// }, {
// name: 'Polski',
// isoCode: 'pl',
// }, {
// name: 'Pусский',
// isoCode: 'ru',
}];
root._detect = function(cb) {
return cb('en'); //disable auto detection for release;
var userLang, androidLang;
if (navigator && navigator.globalization) {

View file

@ -29,16 +29,6 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
});
};
// // RECEIVE
// // Check address
// root.isUsed(wallet.walletId, balance.byAddress, function(err, used) {
// if (used) {
// $log.debug('Address used. Creating new');
// $rootScope.$emit('Local/AddressIsUsed');
// }
// });
//
var _signWithLedger = function(wallet, txp, cb) {
$log.info('Requesting Ledger Chrome app to sign the transaction');
@ -67,32 +57,6 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
});
};
// TODO
// This handles errors from BWS/index which normally
// trigger from async events (like updates).
// Debounce function avoids multiple popups
var _handleError = function(err) {
$log.warn('wallet ERROR: ', err);
$log.warn('TODO');
return; // TODO!!!
if (err instanceof errors.NOT_AUTHORIZED) {
console.log('[walletService.js.93] TODO NOT AUTH'); //TODO
// TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO TODO
wallet.notAuthorized = true;
$state.go('tabs.home');
} else if (err instanceof errors.NOT_FOUND) {
popupService.showAlert(gettextCatalog.getString('Error'), gettextCatalog.getString('Could not access Wallet Service: Not found'));
} else {
var msg = ""
$rootScope.$emit('Local/ClientError', (err.error ? err.error : err));
popupService.showAlert(gettextCatalog.getString('Error'), bwcError.msg(err, gettextCatalog.getString('Error at Wallet Service')));
}
};
root.handleError = lodash.debounce(_handleError, 1000);
root.invalidateCache = function(wallet) {
if (wallet.cachedStatus)
wallet.cachedStatus.isValid = false;
@ -180,8 +144,9 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
if (err instanceof errors.NOT_AUTHORIZED) {
return cb('WALLET_NOT_REGISTERED');
}
return cb(bwcError.msg(err, gettext('Could not update Wallet')));
return cb(err);
}
return cb(null, ret);
});
};
@ -196,19 +161,22 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
// Address with Balance
cache.balanceByAddress = balance.byAddress;
// Total wallet balance is same regardless of 'spend unconfirmed funds' setting.
cache.totalBalanceSat = balance.totalAmount;
// Spend unconfirmed funds
if (config.spendUnconfirmed) {
cache.totalBalanceSat = balance.totalAmount;
cache.lockedBalanceSat = balance.lockedAmount;
cache.availableBalanceSat = balance.availableAmount;
cache.totalBytesToSendMax = balance.totalBytesToSendMax;
cache.pendingAmount = null;
cache.pendingAmount = 0;
cache.spendableAmount = balance.totalAmount - balance.lockedAmount;
} else {
cache.totalBalanceSat = balance.totalConfirmedAmount;
cache.lockedBalanceSat = balance.lockedConfirmedAmount;
cache.availableBalanceSat = balance.availableConfirmedAmount;
cache.totalBytesToSendMax = balance.totalBytesToSendConfirmedMax;
cache.pendingAmount = balance.totalAmount - balance.totalConfirmedAmount;
cache.spendableAmount = balance.totalConfirmedAmount - balance.lockedAmount;
}
// Selected unit
@ -220,25 +188,35 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
cache.totalBalanceStr = txFormatService.formatAmount(cache.totalBalanceSat) + ' ' + cache.unitName;
cache.lockedBalanceStr = txFormatService.formatAmount(cache.lockedBalanceSat) + ' ' + cache.unitName;
cache.availableBalanceStr = txFormatService.formatAmount(cache.availableBalanceSat) + ' ' + cache.unitName;
cache.pendingBalanceStr = txFormatService.formatAmount(cache.totalBalanceSat + (cache.pendingAmount === null? 0 : cache.pendingAmount)) + ' ' + cache.unitName;
if (cache.pendingAmount !== null && cache.pendingAmount !== 0) {
cache.pendingAmountStr = txFormatService.formatAmount(cache.pendingAmount) + ' ' + cache.unitName;
} else {
cache.pendingAmountStr = null;
}
cache.spendableBalanceStr = txFormatService.formatAmount(cache.spendableAmount) + ' ' + cache.unitName;
cache.pendingBalanceStr = txFormatService.formatAmount(cache.pendingAmount) + ' ' + cache.unitName;
cache.alternativeName = config.settings.alternativeName;
cache.alternativeIsoCode = config.settings.alternativeIsoCode;
// Check address
root.isAddressUsed(wallet, balance.byAddress, function(err, used) {
if (used) {
$log.debug('Address used. Creating new');
// Force new address
root.getAddress(wallet, true, function(err, addr) {
$log.debug('New address: ', addr);
});
}
});
rateService.whenAvailable(function() {
var totalBalanceAlternative = rateService.toFiat(cache.totalBalanceSat, cache.alternativeIsoCode);
var pendingBalanceAlternative = rateService.toFiat(cache.pendingAmount, cache.alternativeIsoCode);
var lockedBalanceAlternative = rateService.toFiat(cache.lockedBalanceSat, cache.alternativeIsoCode);
var spendableBalanceAlternative = rateService.toFiat(cache.spendableAmount, cache.alternativeIsoCode);
var alternativeConversionRate = rateService.toFiat(100000000, cache.alternativeIsoCode);
cache.totalBalanceAlternative = $filter('formatFiatAmount')(totalBalanceAlternative);
cache.pendingBalanceAlternative = $filter('formatFiatAmount')(pendingBalanceAlternative);
cache.lockedBalanceAlternative = $filter('formatFiatAmount')(lockedBalanceAlternative);
cache.spendableBalanceAlternative = $filter('formatFiatAmount')(spendableBalanceAlternative);
cache.alternativeConversionRate = $filter('formatFiatAmount')(alternativeConversionRate);
cache.alternativeBalanceAvailable = true;
@ -337,7 +315,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
return tx.txid != endingTxid;
});
return cb(null, res, res.length == limit);
return cb(null, res, res.length >= limit);
});
};
@ -428,7 +406,16 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
function getNewTxs(newTxs, skip, cb) {
getTxsFromServer(wallet, skip, endingTxid, requestLimit, function(err, res, shouldContinue) {
if (err) return cb(err);
if (err) {
$log.warn(bwcError.msg(err, 'BWS Error')); //TODO
if (err instanceof errors.CONNECTION_ERROR || (err.message && err.message.match(/5../))) {
log.info('Retrying history download in 5 secs...');
return $timeout(function() {
return getNewTxs(newTxs, skip, cb);
}, 5000);
};
return cb(err);
}
newTxs = newTxs.concat(processNewTxs(wallet, lodash.compact(res)));
@ -587,20 +574,13 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
if (lodash.isEmpty(txp) || lodash.isEmpty(wallet))
return cb('MISSING_PARAMETER');
if (txp.sendMax) {
wallet.createTxProposal(txp, function(err, createdTxp) {
if (err) return cb(err);
else return cb(null, createdTxp);
});
} else {
wallet.createTxProposal(txp, function(err, createdTxp) {
if (err) return cb(err);
else {
$log.debug('Transaction created');
return cb(null, createdTxp);
}
});
}
wallet.createTxProposal(txp, function(err, createdTxp) {
if (err) return cb(err);
else {
$log.debug('Transaction created');
return cb(null, createdTxp);
}
});
};
root.publishTx = function(wallet, txp, cb) {
@ -760,12 +740,13 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
});
};
root.isUsed = function(wallet, byAddress, cb) {
// Check address
root.isAddressUsed = function(wallet, byAddress, cb) {
storageService.getLastAddress(wallet.id, function(err, addr) {
var used = lodash.find(byAddress, {
address: addr
});
return cb(null, used);
return cb(err, used);
});
};
@ -797,13 +778,30 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
});
};
root.getAddress = function(wallet, forceNew, cb) {
root.getMainAddresses = function(wallet, opts, cb) {
opts = opts || {};
opts.reverse = true;
wallet.getMainAddresses(opts, function(err, addresses) {
return cb(err, addresses);
});
};
root.getBalance = function(wallet, opts, cb) {
opts = opts || {};
wallet.getBalance(opts, function(err, resp) {
return cb(err, resp);
});
};
root.getAddress = function(wallet, forceNew, cb) {
storageService.getLastAddress(wallet.id, function(err, addr) {
if (err) return cb(err);
if (!forceNew && addr) return cb(null, addr);
if (!wallet.isComplete())
return cb('WALLET_NOT_COMPLETE');
createAddress(wallet, function(err, _addr) {
if (err) return cb(err, addr);
storageService.storeLastAddress(wallet.id, _addr, function() {
@ -829,7 +827,8 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
var askPassword = function(name, title, cb) {
var opts = {
inputType: 'password',
forceHTMLPrompt: true
forceHTMLPrompt: true,
class: 'text-warn'
};
popupService.showPrompt(title, name, opts, function(res) {
if (!res) return cb();
@ -839,9 +838,12 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
root.encrypt = function(wallet, cb) {
askPassword(wallet.name, gettext('Enter new spending password'), function(password) {
var title = gettextCatalog.getString('Enter new spending password');
var warnMsg = gettextCatalog.getString('Your wallet key will be encrypted. The Spending Password cannot be recovered. Be sure to write it down.');
askPassword(warnMsg, title, function(password) {
if (!password) return cb('no password');
askPassword(wallet.name, gettext('Confirm you new spending password'), function(password2) {
title = gettextCatalog.getString('Confirm you new spending password');
askPassword(warnMsg, gettextCatalog.getString('Confirm you new spending password'), function(password2) {
if (!password2 || password != password2)
return cb('password mismatch');
@ -854,7 +856,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
root.decrypt = function(wallet, cb) {
$log.debug('Disabling private key encryption for' + wallet.name);
askPassword(wallet.name, gettext('Enter Spending Password'), function(password) {
askPassword(null, gettextCatalog.getString('Enter Spending Password'), function(password) {
if (!password) return cb('no password');
try {
@ -869,7 +871,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
root.handleEncryptedWallet = function(wallet, cb) {
if (!root.isEncrypted(wallet)) return cb();
askPassword(wallet.name, gettext('Enter Spending Password'), function(password) {
askPassword(wallet.name, gettextCatalog.getString('Enter Spending Password'), function(password) {
if (!password) return cb('No password');
if (!wallet.checkPassword(password)) return cb('Wrong password');
@ -892,20 +894,14 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
};
root.onlyPublish = function(wallet, txp, cb) {
ongoingProcess.set('sendingTx', true);
root.onlyPublish = function(wallet, txp, cb, customStatusHandler) {
ongoingProcess.set('sendingTx', true, customStatusHandler);
root.publishTx(wallet, txp, function(err, publishedTxp) {
root.invalidateCache(wallet);
ongoingProcess.set('sendingTx', false);
if (err) return cb(err);
var type = root.getViewStatus(wallet, createdTxp);
root.openStatusModal(type, createdTxp, function() {
$rootScope.$emit('Local/TxAction', wallet.id);
return;
});
return cb(null, publishedTxp);
ongoingProcess.set('sendingTx', false, customStatusHandler);
if (err) return cb(bwcError.msg(err));
$rootScope.$emit('Local/TxAction', wallet.id);
return cb();
});
};
@ -934,13 +930,13 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
}
root.prepare(wallet, function(err, password) {
if (err) return cb('Prepare error: ' + err);
if (err) return cb(bwcError.msg(err));
ongoingProcess.set('sendingTx', true, customStatusHandler);
publishFn(wallet, txp, function(err, publishedTxp) {
ongoingProcess.set('sendingTx', false, customStatusHandler);
if (err) return cb('Send Error: ' + err);
if (err) return cb(bwcError.msg(err));
ongoingProcess.set('signingTx', true, customStatusHandler);
root.signTx(wallet, publishedTxp, password, function(err, signedTxp) {
@ -950,10 +946,9 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
if (err) {
$log.warn('sign error:' + err);
// TODO?
var msg = err.message ?
var msg = err && err.message ?
err.message :
gettext('The payment was created but could not be completed. Please try again from home screen');
gettextCatalog.getString('The payment was created but could not be completed. Please try again from home screen');
$rootScope.$emit('Local/TxAction', wallet.id);
return cb(msg);
@ -963,7 +958,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
ongoingProcess.set('broadcastingTx', true, customStatusHandler);
root.broadcastTx(wallet, signedTxp, function(err, broadcastedTxp) {
ongoingProcess.set('broadcastingTx', false, customStatusHandler);
if (err) return cb('sign error' + err);
if (err) return cb(bwcError.msg(err));
$rootScope.$emit('Local/TxAction', wallet.id);
var type = root.getViewStatus(wallet, broadcastedTxp);
@ -1090,6 +1085,12 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim
return type;
};
root.getSendMaxInfo = function(wallet, opts, cb) {
opts = opts || {};
wallet.getSendMaxInfo(opts, function(err, res) {
return cb(err, res);
});
};
return root;
});