diff --git a/public/views/includes/walletInfo.html b/public/views/includes/walletInfo.html index abcc6a4a7..24ff6d48c 100644 --- a/public/views/includes/walletInfo.html +++ b/public/views/includes/walletInfo.html @@ -1,20 +1,20 @@ -{{index.m}}-of-{{index.n}} -Auditable - -{{wallet.m}}-of-{{wallet.n}} +Auditable + + - - -#{{index.account || 0}} +#{{wallet.account || 0}} - + - - + + - diff --git a/public/views/tab-home.html b/public/views/tab-home.html index 98dd66a58..ada43e865 100644 --- a/public/views/tab-home.html +++ b/public/views/tab-home.html @@ -1,19 +1,25 @@ +

Wallets

- +
  • + menu-toggle href ui-sref="walletDetails({'walletId': item.id})"> {{item.name || item.id}} - - {{item.m}}-of-{{item.n}} + + [ {{item.m}}-of-{{item.n}} ] + + + {{item.availableBalanceStr}} + + +
diff --git a/public/views/walletDetails.html b/public/views/walletDetails.html new file mode 100644 index 000000000..a1f891c3e --- /dev/null +++ b/public/views/walletDetails.html @@ -0,0 +1,230 @@ + +
+ + +
+
+
+
+
+
+
+
+
+ Updating Wallet... +
+
+ +
+ +
+ +
+
+
+ +
{{wallet.name}}
+ +
+ {{wallet.updateError|translate}} + +
+ +
+ Scan status finished with error +
Tap to retry +
+ + +
+ {{wallet.totalBalanceStr}} +
{{wallet.totalBalanceAlternative}} {{wallet.alternativeIsoCode}}
+
+ Pending Confirmation: {{wallet.pendingAmountStr}} +
+
+ +
+ [Balance Hidden] +
+ Tap and hold to show +
+
+
+
+
+ ... +
+
+
+ +
+ +
+
+ +
+
+
+ + + WARNING: Key derivation is not working on this device/wallet. Actions cannot be performed on this wallet. + +
+
+
+
+ + + WARNING: Wallet not registered + +
+
+ This wallet is not registered at the given Bitcore Wallet Service (BWS). You can recreate it from the local information. +
+
+ + Recreate + +
+
+ +
+ {{newRelease}} +
+ +
+

Payment Proposals

+

Unsent transactions

+
+
+
+ +
+ Total Locked Balance: + {{wallet.lockedBalanceStr}} + {{wallet.lockedBalanceAlternative}} + {{wallet.alternativeIsoCode}} +
+
+ + + +

+ Activity + +

+ +
No transactions yet ZZZZ {{wallet.totalBalanceStr}} +
+ +
+ +
+
+
+ +
+
+
{{wallet.txProgress}} transactions downloaded
+
Updating transaction history. Please stand by.
+
+
+
+ +
+
+
+ +
+
+   +
+
+   +
+
+
+ +
+
+
+ sync + sync + sync +
+
+ + + {{btx.note.body}} + Received + + + + + {{btx.message}} + {{btx.note.body}} + {{wallet.addressbook[btx.addressTo]}} + Sent + + + + + {{btx.note.body}} + Moved + + + + Invalid +
+
+ +
+ + + + - + + (possible double spend) + + + {{btx.amountStr}} + + +
+ + + Unconfirmed + +
+
+
+ +
+
+ +
+
+ +
+
+ + + +
+
+ +
+
+
+
+
diff --git a/src/js/controllers/tab-home.js b/src/js/controllers/tab-home.js index 3acea1dc7..71c926dd4 100644 --- a/src/js/controllers/tab-home.js +++ b/src/js/controllers/tab-home.js @@ -19,35 +19,14 @@ angular.module('copayApp.controllers').controller('tabHomeController', self.setWallets(); }); - self.setWallets = function() { - if (!profileService.profile) return; - - var config = configService.getSync(); - config.colorFor = config.colorFor || {}; - config.aliasFor = config.aliasFor || {}; - - // Sanitize empty wallets (fixed in BWC 1.8.1, and auto fixed when wallets completes) - var credentials = lodash.filter(profileService.profile.credentials, 'walletName'); - var ret = lodash.map(credentials, function(c) { - return { - m: c.m, - n: c.n, - name: config.aliasFor[c.walletId] || c.walletName, - id: c.walletId, - color: config.colorFor[c.walletId] || '#4A90E2', - }; - }); - - $scope.wallets = lodash.sortBy(ret, 'name'); + self.setWallets = function() { + $scope.wallets = profileService.getWallets(); }; - self.updateAllClients = function() { - lodash.each(profileService.getClients(), function(client) { - walletService.updateStatus(client, {}, function(err, status) { - if (err) -console.log('[tab-home.js.47]', err); //TODO -console.log('[tab-home.js.47:console:]',status); //TODO - + self.updateAllClients = function() { + lodash.each(profileService.getWallets(), function(wallet) { + walletService.updateStatus(wallet, {}, function(err, status) { + if (err) {} // TODO }); }); } @@ -55,7 +34,4 @@ console.log('[tab-home.js.47:console:]',status); //TODO self.setWallets(); self.updateAllClients(); $scope.bitpayCardEnabled = true; // TODO - - - }); diff --git a/src/js/controllers/walletDetails.js b/src/js/controllers/walletDetails.js new file mode 100644 index 000000000..4fe1f219d --- /dev/null +++ b/src/js/controllers/walletDetails.js @@ -0,0 +1,76 @@ +'use strict'; + +angular.module('copayApp.controllers').controller('walletDetailsController', function($scope, $rootScope, $interval, $timeout, $filter, $log, $ionicModal, $ionicPopover, $stateParams, profileService, lodash, configService, gettext, gettextCatalog, platformInfo, go, walletService ) { + + +console.log('[walletDetails.js.5]', $stateParams); //TODO + var isCordova = platformInfo.isCordova; + var isWP = platformInfo.isWP; + var isAndroid = platformInfo.isAndroid; + var isChromeApp = platformInfo.isChromeApp; + + var self = this; + $rootScope.shouldHideMenuBar = false; + $rootScope.wpInputFocused = false; + var config = configService.getSync(); + var configWallet = config.wallet; + var walletSettings = configWallet.settings; + var ret = {}; + + // INIT. Global value + ret.unitToSatoshi = walletSettings.unitToSatoshi; + ret.satToUnit = 1 / ret.unitToSatoshi; + ret.unitName = walletSettings.unitName; + ret.alternativeIsoCode = walletSettings.alternativeIsoCode; + ret.alternativeName = walletSettings.alternativeName; + ret.alternativeAmount = 0; + ret.unitDecimals = walletSettings.unitDecimals; + ret.isCordova = isCordova; + ret.addresses = []; + ret.isMobile = platformInfo.isMobile; + ret.isWindowsPhoneApp = platformInfo.isWP; + ret.countDown = null; + ret.sendMaxInfo = {}; + ret.showAlternative = false; + + $scope.openSearchModal = function() { + var fc = profileService.focusedClient; + $scope.color = fc.backgroundColor; + $scope.self = self; + + $ionicModal.fromTemplateUrl('views/modals/search.html', { + scope: $scope, + focusFirstInput: true + }).then(function(modal) { + $scope.searchModal = modal; + $scope.searchModal.show(); + }); + }; + + this.openTxModal = function(btx) { + var self = this; + + $scope.btx = lodash.cloneDeep(btx); + $scope.self = self; + + $ionicModal.fromTemplateUrl('views/modals/tx-details.html', { + scope: $scope, + hideDelay: 500 + }).then(function(modal) { + $scope.txDetailsModal = modal; + $scope.txDetailsModal.show(); + }); + }; + + $scope.update = function() { +console.log('[walletDetails.js.65:update:] TODO'); //TODO +// {triggerTxUpdate: true} + }; + + $scope.hideToggle = function() { +console.log('[walletDetails.js.70:hideToogle:] TODO'); //TODO + }; + $scope.wallet = profileService.getWallet($stateParams.walletId); + +console.log('[walletDetails.js.66]',$scope.wallet); //TODO +}); diff --git a/src/js/routes.js b/src/js/routes.js index ab8f649a5..dd832ec0e 100644 --- a/src/js/routes.js +++ b/src/js/routes.js @@ -14,7 +14,7 @@ if (window && window.navigator) { //Setting up route angular.module('copayApp').config(function(historicLogProvider, $provide, $logProvider, $stateProvider, $urlRouterProvider, $compileProvider) { - $urlRouterProvider.otherwise('/'); + $urlRouterProvider.otherwise('/tabs'); $logProvider.debugEnabled(true); $provide.decorator('$log', ['$delegate', 'platformInfo', @@ -93,8 +93,20 @@ angular.module('copayApp').config(function(historicLogProvider, $provide, $logPr } } }) + .state('walletDetails', { + url: '/details', + needProfile: true, + views: { + 'main': { + templateUrl: 'views/walletDetails.html', + }, + }, + params: { + walletId: null, + }, + }) .state('walletHome', { - url: '/', + url: '/old', needProfile: true, views: { 'main': { diff --git a/src/js/services/addressService.js b/src/js/services/addressService.js index 2e4f0e79e..6df0a71a0 100644 --- a/src/js/services/addressService.js +++ b/src/js/services/addressService.js @@ -3,82 +3,5 @@ angular.module('copayApp.services') .factory('addressService', function(storageService, profileService, $log, $timeout, lodash, bwcError, gettextCatalog) { var root = {}; - - root.expireAddress = function(walletId, cb) { - $log.debug('Cleaning Address ' + walletId); - storageService.clearLastAddress(walletId, function(err) { - return cb(err); - }); - }; - - root.isUsed = function(walletId, byAddress, cb) { - storageService.getLastAddress(walletId, function(err, addr) { - var used = lodash.find(byAddress, { - address: addr - }); - return cb(null, used); - }); - }; - - root._createAddress = function(walletId, cb) { - var client = profileService.getClient(walletId); - - $log.debug('Creating address for wallet:', walletId); - - client.createAddress({}, function(err, addr) { - if (err) { - var prefix = gettextCatalog.getString('Could not create address'); - if (err.error && err.error.match(/locked/gi)) { - $log.debug(err.error); - return $timeout(function() { - root._createAddress(walletId, cb); - }, 5000); - } else if (err.message && err.message == 'MAIN_ADDRESS_GAP_REACHED') { - $log.warn(err.message); - prefix = null; - client.getMainAddresses({ - reverse: true, - limit: 1 - }, function(err, addr) { - if (err) return cb(err); - return cb(null, addr[0].address); - }); - } - return bwcError.cb(err, prefix, cb); - } - return cb(null, addr.address); - }); - }; - - root.getAddress = function(walletId, forceNew, cb) { - - var firstStep; - if (forceNew) { - firstStep = storageService.clearLastAddress; - } else { - firstStep = function(walletId, cb) { - return cb(); - }; - } - - firstStep(walletId, function(err) { - if (err) return cb(err); - - storageService.getLastAddress(walletId, function(err, addr) { - if (err) return cb(err); - - if (addr) return cb(null, addr); - - root._createAddress(walletId, function(err, addr) { - if (err) return cb(err); - storageService.storeLastAddress(walletId, addr, function() { - if (err) return cb(err); - return cb(null, addr); - }); - }); - }); - }); - }; - return root; }); diff --git a/src/js/services/profileService.js b/src/js/services/profileService.js index 46a0eb14a..490a69af5 100644 --- a/src/js/services/profileService.js +++ b/src/js/services/profileService.js @@ -17,31 +17,19 @@ angular.module('copayApp.services') root.profile = null; root.focusedClient = null; - root.walletClients = {}; - - root.Utils = bwcService.getUtils(); - root.formatAmount = function(amount, fullPrecision) { - var config = configService.getSync().wallet.settings; - if (config.unitCode == 'sat') return amount; - - //TODO : now only works for english, specify opts to change thousand separator and decimal separator - var opts = { - fullPrecision: !!fullPrecision - }; - return this.Utils.formatAmount(amount, config.unitCode, opts); - }; + root.wallet = {}; // decorated version of client root._setFocus = function(walletId, cb) { $log.debug('Set focus:', walletId); // Set local object if (walletId) - root.focusedClient = root.walletClients[walletId]; + root.focusedClient = root.wallet[walletId]; else root.focusedClient = []; if (lodash.isEmpty(root.focusedClient)) { - root.focusedClient = root.walletClients[lodash.keys(root.walletClients)[0]]; + root.focusedClient = root.wallet[lodash.keys(root.wallet)[0]]; } // Still nothing? @@ -51,7 +39,7 @@ angular.module('copayApp.services') $rootScope.$emit('Local/NewFocusedWallet'); // Set update period - lodash.each(root.walletClients, function(client, id) { + lodash.each(root.wallet, function(client, id) { client.setNotificationsInterval(BACKGROUND_UPDATE_PERIOD); }); root.focusedClient.setNotificationsInterval(FOREGROUND_UPDATE_PERIOD); @@ -66,18 +54,37 @@ angular.module('copayApp.services') }); }; + root.setCustomBWSFlag = function(wallet) { + var defaults = configService.getDefaults(); + var config = configService.getSync(); + + wallet.usingCustomBWS = config.bwsFor && config.bwsFor[wallet.id] && (config.bwsFor[wallet.id] != defaults.bws.url); + }; + // Adds a wallet client to profileService root.bindWalletClient = function(client, opts) { var opts = opts || {}; var walletId = client.credentials.walletId; + var config = configService.getSync(); + config.colorFor = config.colorFor || {}; + config.aliasFor = config.aliasFor || {}; - if ((root.walletClients[walletId] && root.walletClients[walletId].started) || opts.force) { + + if ((root.wallet[walletId] && root.wallet[walletId].started) || opts.force) { return false; } - root.walletClients[walletId] = client; - root.walletClients[walletId].started = true; - root.walletClients[walletId].doNotVerifyPayPro = isChromeApp; + // INIT WALLET CLIENT VIEWMODEL + var c = client; + c.id = walletId; + c.started = true; + c.doNotVerifyPayPro = isChromeApp; + c.name = config.aliasFor[walletId] || client.credentials.walletName; + c.color = config.colorFor[walletId] || '#4A90E2'; + c.network = client.credentials.network; + root.setCustomBWSFlag(c); + + root.wallet[walletId] = c; client.removeAllListeners(); client.on('report', function(n) { @@ -162,7 +169,7 @@ angular.module('copayApp.services') // Used when reading wallets from the profile root.bindWallet = function(credentials, cb) { - if (!credentials.walletId) + if (!credentials.walletId || !credentials.m) return cb('bindWallet should receive credentials JSON'); @@ -239,7 +246,7 @@ angular.module('copayApp.services') root.pushNotificationsInit = function() { var defaults = configService.getDefaults(); - var push = pushNotificationsService.init(root.walletClients); + var push = pushNotificationsService.init(root.wallet); push.on('notification', function(data) { if (!data.additionalData.foreground) { @@ -431,14 +438,15 @@ angular.module('copayApp.services') }); }; - root.getClient = function(walletId) { - return root.walletClients[walletId]; + root.getWallet = function(walletId) { + return root.wallet[walletId]; }; + root.deleteWalletClient = function(client, cb) { var walletId = client.credentials.walletId; - pushNotificationsService.unsubscribe(root.getClient(walletId), function(err) { + pushNotificationsService.unsubscribe(root.getWallet(walletId), function(err) { if (err) $log.warn('Unsubscription error: ' + err.message); else $log.debug('Unsubscribed from push notifications service'); }); @@ -448,7 +456,7 @@ angular.module('copayApp.services') root.profile.deleteWallet(walletId); - delete root.walletClients[walletId]; + delete root.wallet[walletId]; root.focusedClient = null; @@ -529,7 +537,7 @@ angular.module('copayApp.services') storageService.storeProfile(root.profile, function(err) { var config = configService.getSync(); if (config.pushNotifications.enabled) - pushNotificationsService.enableNotifications(root.walletClients); + pushNotificationsService.enableNotifications(root.wallet); return cb(err, walletId); }); @@ -705,8 +713,22 @@ angular.module('copayApp.services') storageService.storeProfile(root.profile, cb); }; - root.getClients = function() { - return lodash.values(root.walletClients); + root.getWallets = function(network, n) { + var ret = lodash.values(root.wallet); + + if (network) { + ret = lodash.filter(ret, function(x) { + return (x.credentials.network == network); + }); + } + + if (n) { + ret = lodash.filter(ret, function(w) { + return (w.credentials.n == n); + }); + } + + return lodash.sortBy(ret, 'name'); }; root.needsBackup = function(client, cb) { @@ -732,36 +754,5 @@ angular.module('copayApp.services') }); }; - root.getWallets = function(network, n) { - if (!root.profile) return []; - - var config = configService.getSync(); - config.colorFor = config.colorFor || {}; - config.aliasFor = config.aliasFor || {}; - var ret = lodash.map(root.profile.credentials, function(c) { - return { - m: c.m, - n: c.n, - name: config.aliasFor[c.walletId] || c.walletName, - id: c.walletId, - network: c.network, - color: config.colorFor[c.walletId] || '#4A90E2', - copayerId: c.copayerId - }; - }); - if (network) { - ret = lodash.filter(ret, function(w) { - return (w.network == network); - }); - } - if (n) { - ret = lodash.filter(ret, function(w) { - return (w.n == n); - }); - } - - return lodash.sortBy(ret, 'name'); - }; - return root; }); diff --git a/src/js/services/walletService.js b/src/js/services/walletService.js index 62ea4980b..915130e41 100644 --- a/src/js/services/walletService.js +++ b/src/js/services/walletService.js @@ -1,15 +1,42 @@ 'use strict'; // DO NOT INCLUDE STORAGE HERE \/ \/ -angular.module('copayApp.services').factory('walletService', function($log, $timeout, lodash, trezor, ledger, storageService, configService, uxLanguage) { +angular.module('copayApp.services').factory('walletService', function($log, $timeout, lodash, trezor, ledger, storageService, configService, rateService, uxLanguage, bwcService, $filter) { // DO NOT INCLUDE STORAGE HERE ^^ + // + // + // `wallet` is a decorated version of client. var root = {}; - var _signWithLedger = function(client, txp, cb) { + + // // RECEIVE + // // Check address + // root.isUsed(wallet.walletId, balance.byAddress, function(err, used) { + // if (used) { + // $log.debug('Address used. Creating new'); + // $rootScope.$emit('Local/AddressIsUsed'); + // } + // }); + // + + root.Utils = bwcService.getUtils(); + root.formatAmount = function(amount, fullPrecision) { + var config = configService.getSync().wallet.settings; + if (config.unitCode == 'sat') return amount; + + //TODO : now only works for english, specify opts to change thousand separator and decimal separator + var opts = { + fullPrecision: !!fullPrecision + }; + return this.Utils.formatAmount(amount, config.unitCode, opts); + }; + + + var _signWithLedger = function(wallet, txp, cb) { $log.info('Requesting Ledger Chrome app to sign the transaction'); - ledger.signTx(txp, client.credentials.account, function(result) { + ledger.signTx(txp, wallet.credentials.account, function(result) { $log.debug('Ledger response', result); if (!result.success) return cb(result.message || result.error); @@ -17,27 +44,27 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim txp.signatures = lodash.map(result.signatures, function(s) { return s.substring(0, s.length - 2); }); - return client.signTxProposal(txp, cb); + return wallet.signTxProposal(txp, cb); }); }; - var _signWithTrezor = function(client, txp, cb) { + var _signWithTrezor = function(wallet, txp, cb) { $log.info('Requesting Trezor to sign the transaction'); - var xPubKeys = lodash.pluck(client.credentials.publicKeyRing, 'xPubKey'); - trezor.signTx(xPubKeys, txp, client.credentials.account, function(err, result) { + var xPubKeys = lodash.pluck(wallet.credentials.publicKeyRing, 'xPubKey'); + trezor.signTx(xPubKeys, txp, wallet.credentials.account, function(err, result) { if (err) return cb(err); $log.debug('Trezor response', result); txp.signatures = result.signatures; - return client.signTxProposal(txp, cb); + return wallet.signTxProposal(txp, cb); }); }; - root.needsBackup = function(client) { - if (client.isPrivKeyExternal()) return false; - if (!client.credentials.mnemonic) return false; - if (client.credentials.network == 'testnet') return false; + root.needsBackup = function(wallet) { + if (wallet.isPrivKeyExternal()) return false; + if (!wallet.credentials.mnemonic) return false; + if (wallet.credentials.network == 'testnet') return false; return true; }; @@ -58,10 +85,10 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim // trigger from async events (like updates). // Debounce function avoids multiple popups var _handleError = function(err) { - $log.warn('Client ERROR: ', err); + $log.warn('wallet ERROR: ', err); $log.warn('TODO'); - return ; // TODO!!! + return; // TODO!!! if (err instanceof errors.NOT_AUTHORIZED) { self.notAuthorized = true; go.walletHome(); @@ -76,14 +103,83 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim }; root.handleError = lodash.debounce(_handleError, 1000); - // emits - // statusUpdated walletId, err ,statusObj - // - root.updateStatus = function(client, opts, cb, initStatusHash, tries) { + + root.setBalance = function(wallet, balance) { + if (!balance) return; + + var config = configService.getSync().wallet.settings; + var COIN = 1e8; + + // Address with Balance + wallet.balanceByAddress = balance.byAddress; + + // Spend unconfirmed funds + if (wallet.spendUnconfirmed) { + wallet.totalBalanceSat = balance.totalAmount; + wallet.lockedBalanceSat = balance.lockedAmount; + wallet.availableBalanceSat = balance.availableAmount; + wallet.totalBytesToSendMax = balance.totalBytesToSendMax; + wallet.pendingAmount = null; + } else { + wallet.totalBalanceSat = balance.totalConfirmedAmount; + wallet.lockedBalanceSat = balance.lockedConfirmedAmount; + wallet.availableBalanceSat = balance.availableConfirmedAmount; + wallet.totalBytesToSendMax = balance.totalBytesToSendConfirmedMax; + wallet.pendingAmount = balance.totalAmount - balance.totalConfirmedAmount; + } + + // Selected unit + wallet.unitToSatoshi = config.unitToSatoshi; + wallet.satToUnit = 1 / wallet.unitToSatoshi; + wallet.unitName = config.unitName; + + //STR + wallet.totalBalanceStr = root.formatAmount(wallet.totalBalanceSat) + ' ' + wallet.unitName; + wallet.lockedBalanceStr = root.formatAmount(wallet.lockedBalanceSat) + ' ' + wallet.unitName; + wallet.availableBalanceStr = root.formatAmount(wallet.availableBalanceSat) + ' ' + wallet.unitName; + + if (wallet.pendingAmount) { + wallet.pendingAmountStr = root.formatAmount(wallet.pendingAmount) + ' ' + wallet.unitName; + } else { + wallet.pendingAmountStr = null; + } + + wallet.alternativeName = config.alternativeName; + wallet.alternativeIsoCode = config.alternativeIsoCode; + + rateService.whenAvailable(function() { + + var totalBalanceAlternative = rateService.toFiat(wallet.totalBalanceSat, wallet.alternativeIsoCode); + var lockedBalanceAlternative = rateService.toFiat(wallet.lockedBalanceSat, wallet.alternativeIsoCode); + var alternativeConversionRate = rateService.toFiat(100000000, wallet.alternativeIsoCode); + + wallet.totalBalanceAlternative = $filter('formatFiatAmount')(totalBalanceAlternative); + wallet.lockedBalanceAlternative = $filter('formatFiatAmount')(lockedBalanceAlternative); + wallet.alternativeConversionRate = $filter('formatFiatAmount')(alternativeConversionRate); + + wallet.alternativeBalanceAvailable = true; + wallet.isRateAvailable = true; + }); + }; + + root.setStatus = function(wallet, status) { + + wallet.status = status; + wallet.statusUpdatedOn = Date.now(); + wallet.isValid = true; + root.setBalance(wallet, status.balance); + + }; + + root.updateStatus = function(wallet, opts, cb, initStatusHash, tries) { tries = tries || 0; opts = opts || {}; - var walletId = client.credentials.walletId + if (wallet.isValid && ! opts.force) + return; + + + var walletId = wallet.id; if (opts.untilItChanges && lodash.isUndefined(initStatusHash)) { initStatusHash = _walletStatusHash(); @@ -94,7 +190,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim if (opts.walletStatus) return cb(null, opts.walletStatus); else { - return client.getStatus({ + return wallet.getStatus({ twoStep: true }, function(err, ret) { if (err) @@ -102,7 +198,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim // TODO?? // self.isSingleAddress = !!ret.wallet.singleAddress; // self.updating = ret.wallet.scanStatus == 'running'; - return cb(err); + return cb(null, ret); }); } }; @@ -117,16 +213,16 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim $timeout(function() { // if (!opts.quiet) - // self.updating = true; + // self.updating = true; - $log.debug('Updating Status:', client.credentials.walletName, tries); + $log.debug('Updating Status:', wallet.credentials.walletName, tries); get(function(err, walletStatus) { var currentStatusHash = _walletStatusHash(walletStatus); $log.debug('Status update. hash:' + currentStatusHash + ' Try:' + tries); if (!err && opts.untilItChanges && initStatusHash == currentStatusHash && tries < 7 && walletId == profileService.focusedClient.credentials.walletId) { return $timeout(function() { $log.debug('Retrying update... ' + walletId + ' Try:' + tries) - return root.updateStatus(client, { + return root.updateStatus(wallet, { walletStatus: null, untilItChanges: true, triggerTxUpdate: opts.triggerTxUpdate, @@ -138,8 +234,9 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim root.handleError(err); return cb(err); } + $log.debug('Got Wallet Status for:' + wallet.credentials.walletName); - $log.debug('Wallet Status:' + client.credentials.walletName, walletStatus); + root.setStatus(wallet, walletStatus); // self.setPendingTxps(walletStatus.pendingTxps); // @@ -160,11 +257,11 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim // TODO if (opts.triggerTxUpdate && opts.untilItChanges) { - $timeout(function() { - root.debounceUpdateHistory(); - }, 1); + $timeout(function() { + root.debounceUpdateHistory(); + }, 1); } - return cb(null, walletStatus); + return cb(); // } else { // self.loadingWallet = false; // } @@ -192,10 +289,10 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim }); }; - var getTxsFromServer = function(client, skip, endingTxid, limit, cb) { + var getTxsFromServer = function(wallet, skip, endingTxid, limit, cb) { var res = []; - client.getTxHistory({ + wallet.getTxHistory({ skip: skip, limit: limit }, function(err, txsFromServer) { @@ -213,11 +310,11 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim }; - var updateLocalTxHistory = function(client, cb) { + var updateLocalTxHistory = function(wallet, cb) { var FIRST_LIMIT = 5; var LIMIT = 50; var requestLimit = FIRST_LIMIT; - var walletId = client.credentials.walletId; + var walletId = wallet.credentials.walletId; var config = configService.getSync().wallet.settings; var fixTxsUnit = function(txs) { @@ -233,8 +330,8 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim $log.debug('Fixing Tx Cache Unit to:' + name) lodash.each(txs, function(tx) { - tx.amountStr = profileService.formatAmount(tx.amount) + name; - tx.feeStr = profileService.formatAmount(tx.fees) + name; + tx.amountStr = root.formatAmount(tx.amount) + name; + tx.feeStr = root.formatAmount(tx.fees) + name; }); }; @@ -260,7 +357,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim historyUpdateInProgress[walletId] = true; function getNewTxs(newTxs, skip, i_cb) { - getTxsFromServer(client, skip, endingTxid, requestLimit, function(err, res, shouldContinue) { + getTxsFromServer(wallet, skip, endingTxid, requestLimit, function(err, res, shouldContinue) { if (err) return i_cb(err); newTxs = newTxs.concat(lodash.compact(res)); @@ -306,7 +403,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim if (!endingTs) return cb2(); $log.debug('Syncing notes from: ' + endingTs); - client.getTxNotes({ + wallet.getTxNotes({ minTs: endingTs }, function(err, notes) { if (err) { @@ -352,10 +449,10 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim }; - root.updateHistory = function(client) { - var walletId = client.credentials.walletId; + root.updateHistory = function(wallet) { + var walletId = wallet.credentials.walletId; - if (!client.isComplete()) return; + if (!wallet.isComplete()) return; $log.debug('Updating Transaction History'); @@ -363,7 +460,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim self.updatingTxHistory = true; $timeout(function() { - updateLocalTxHistory(client, function(err) { + updateLocalTxHistory(wallet, function(err) { historyUpdateInProgress[walletId] = self.updatingTxHistory = false; self.loadingWallet = false; self.txProgress = 0; @@ -382,45 +479,45 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim - root.isEncrypted = function(client) { - if (lodash.isEmpty(client)) return; - var isEncrypted = client.isPrivKeyEncrypted(); + root.isEncrypted = function(wallet) { + if (lodash.isEmpty(wallet)) return; + var isEncrypted = wallet.isPrivKeyEncrypted(); if (isEncrypted) $log.debug('Wallet is encrypted'); return isEncrypted; }; - root.lock = function(client) { + root.lock = function(wallet) { try { - client.lock(); + wallet.lock(); } catch (e) { $log.warn('Encrypting wallet:', e); }; }; - root.unlock = function(client, password) { - if (lodash.isEmpty(client)) + root.unlock = function(wallet, password) { + if (lodash.isEmpty(wallet)) return 'MISSING_PARAMETER'; if (lodash.isEmpty(password)) return 'NO_PASSWORD_GIVEN'; try { - client.unlock(password); + wallet.unlock(password); } catch (e) { $log.warn('Decrypting wallet:', e); return 'PASSWORD_INCORRECT'; } }; - root.createTx = function(client, txp, cb) { - if (lodash.isEmpty(txp) || lodash.isEmpty(client)) + root.createTx = function(wallet, txp, cb) { + if (lodash.isEmpty(txp) || lodash.isEmpty(wallet)) return cb('MISSING_PARAMETER'); if (txp.sendMax) { - client.createTxProposal(txp, function(err, createdTxp) { + wallet.createTxProposal(txp, function(err, createdTxp) { if (err) return cb(err); else return cb(null, createdTxp); }); } else { - client.getFeeLevels(client.credentials.network, function(err, levels) { + wallet.getFeeLevels(wallet.credentials.network, function(err, levels) { if (err) return cb(err); var feeLevelValue = lodash.find(levels, { @@ -435,7 +532,7 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim $log.debug('Dynamic fee: ' + txp.feeLevel + ' ' + feeLevelValue.feePerKB + ' SAT'); txp.feePerKb = feeLevelValue.feePerKB; - client.createTxProposal(txp, function(err, createdTxp) { + wallet.createTxProposal(txp, function(err, createdTxp) { if (err) return cb(err); else { $log.debug('Transaction created'); @@ -446,11 +543,11 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim } }; - root.publishTx = function(client, txp, cb) { - if (lodash.isEmpty(txp) || lodash.isEmpty(client)) + root.publishTx = function(wallet, txp, cb) { + if (lodash.isEmpty(txp) || lodash.isEmpty(wallet)) return cb('MISSING_PARAMETER'); - client.publishTxProposal({ + wallet.publishTxProposal({ txp: txp }, function(err, publishedTx) { if (err) return cb(err); @@ -461,25 +558,25 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim }); }; - root.signTx = function(client, txp, cb) { - if (lodash.isEmpty(txp) || lodash.isEmpty(client)) + root.signTx = function(wallet, txp, cb) { + if (lodash.isEmpty(txp) || lodash.isEmpty(wallet)) return cb('MISSING_PARAMETER'); - if (client.isPrivKeyExternal()) { - switch (client.getPrivKeyExternalSourceName()) { + if (wallet.isPrivKeyExternal()) { + switch (wallet.getPrivKeyExternalSourceName()) { case 'ledger': - return _signWithLedger(client, txp, cb); + return _signWithLedger(wallet, txp, cb); case 'trezor': - return _signWithTrezor(client, txp, cb); + return _signWithTrezor(wallet, txp, cb); default: - var msg = 'Unsupported External Key:' + client.getPrivKeyExternalSourceName(); + var msg = 'Unsupported External Key:' + wallet.getPrivKeyExternalSourceName(); $log.error(msg); return cb(msg); } } else { try { - client.signTxProposal(txp, function(err, signedTxp) { + wallet.signTxProposal(txp, function(err, signedTxp) { $log.debug('Transaction signed'); return cb(err, signedTxp); }); @@ -490,14 +587,14 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim } }; - root.broadcastTx = function(client, txp, cb) { - if (lodash.isEmpty(txp) || lodash.isEmpty(client)) + root.broadcastTx = function(wallet, txp, cb) { + if (lodash.isEmpty(txp) || lodash.isEmpty(wallet)) return cb('MISSING_PARAMETER'); if (txp.status != 'accepted') return cb('TX_NOT_ACCEPTED'); - client.broadcastTxProposal(txp, function(err, broadcastedTxp, memo) { + wallet.broadcastTxProposal(txp, function(err, broadcastedTxp, memo) { if (err) return cb(err); @@ -508,21 +605,21 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim }); }; - root.rejectTx = function(client, txp, cb) { - if (lodash.isEmpty(txp) || lodash.isEmpty(client)) + root.rejectTx = function(wallet, txp, cb) { + if (lodash.isEmpty(txp) || lodash.isEmpty(wallet)) return cb('MISSING_PARAMETER'); - client.rejectTxProposal(txp, null, function(err, rejectedTxp) { + wallet.rejectTxProposal(txp, null, function(err, rejectedTxp) { $log.debug('Transaction rejected'); return cb(err, rejectedTxp); }); }; - root.removeTx = function(client, txp, cb) { - if (lodash.isEmpty(txp) || lodash.isEmpty(client)) + root.removeTx = function(wallet, txp, cb) { + if (lodash.isEmpty(txp) || lodash.isEmpty(wallet)) return cb('MISSING_PARAMETER'); - client.removeTxProposal(txp, function(err) { + wallet.removeTxProposal(txp, function(err) { $log.debug('Transaction removed'); return cb(err); }); @@ -535,11 +632,11 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim clients = [clients]; function updateRemotePreferencesFor(clients, prefs, cb) { - var client = clients.shift(); - if (!client) return cb(); - $log.debug('Saving remote preferences', client.credentials.walletName, prefs); + var wallet = clients.shift(); + if (!wallet) return cb(); + $log.debug('Saving remote preferences', wallet.credentials.walletName, prefs); - client.savePreferences(prefs, function(err) { + wallet.savePreferences(prefs, function(err) { // we ignore errors here if (err) $log.warn(err); @@ -603,9 +700,9 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim }); }; - root.recreate = function(client, cb) { + root.recreate = function(wallet, cb) { ongoingProcess.set('recreating', true); - client.recreateWallet(function(err) { + wallet.recreateWallet(function(err) { self.notAuthorized = false; ongoingProcess.set('recreating', false); @@ -615,20 +712,20 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim return; } - profileService.bindWalletClient(client, { + profileService.bindWalletClient(wallet, { force: true }); - self.startScan(client); + self.startScan(wallet); }); }; - root.startScan = function(client) { - $log.debug('Scanning wallet ' + client.credentials.walletId); - if (!client.isComplete()) return; + root.startScan = function(wallet) { + $log.debug('Scanning wallet ' + wallet.credentials.walletId); + if (!wallet.isComplete()) return; -// self.updating = true; + // self.updating = true; - client.startScan({ + wallet.startScan({ includeCopayerBranches: true, }, function(err) { @@ -642,6 +739,80 @@ angular.module('copayApp.services').factory('walletService', function($log, $tim }; + root.expireAddress = function(wallet, cb) { + $log.debug('Cleaning Address ' + wallet.id); + storageService.clearLastAddress(wallet.id, function(err) { + return cb(err); + }); + }; + + root.isUsed = function(wallet, byAddress, cb) { + storageService.getLastAddress(wallet.id, function(err, addr) { + var used = lodash.find(byAddress, { + address: addr + }); + return cb(null, used); + }); + }; + + root._createAddress = function(wallet, cb) { + $log.debug('Creating address for wallet:', wallet.id); + + wallet.createAddress({}, function(err, addr) { + if (err) { + var prefix = gettextCatalog.getString('Could not create address'); + if (err.error && err.error.match(/locked/gi)) { + $log.debug(err.error); + return $timeout(function() { + root._createAddress(walletId, cb); + }, 5000); + } else if (err.message && err.message == 'MAIN_ADDRESS_GAP_REACHED') { + $log.warn(err.message); + prefix = null; + wallet.getMainAddresses({ + reverse: true, + limit: 1 + }, function(err, addr) { + if (err) return cb(err); + return cb(null, addr[0].address); + }); + } + return bwcError.cb(err, prefix, cb); + } + return cb(null, addr.address); + }); + }; + + root.getAddress = function(wallet, forceNew, cb) { + + var firstStep; + if (forceNew) { + firstStep = storageService.clearLastAddress; + } else { + firstStep = function(walletId, cb) { + return cb(); + }; + } + + firstStep(wallet.id, function(err) { + if (err) return cb(err); + + storageService.getLastAddress(wallet.id, function(err, addr) { + if (err) return cb(err); + + if (addr) return cb(null, addr); + + root._createAddress(wallet, function(err, addr) { + if (err) return cb(err); + storageService.storeLastAddress(wallet.id, addr, function() { + if (err) return cb(err); + return cb(null, addr); + }); + }); + }); + }); + }; + return root; });