diff --git a/bower.json b/bower.json index e13a87001..572438ec3 100644 --- a/bower.json +++ b/bower.json @@ -17,7 +17,7 @@ "ng-lodash": "~0.2.0", "angular-moment": "~0.10.1", "moment": "~2.10.3", - "angular-bitcore-wallet-client": "^0.0.26", + "angular-bitcore-wallet-client": "^0.0.28", "angular-ui-router": "~0.2.13", "qrcode-decoder-js": "*", "fastclick": "*", diff --git a/public/views/modals/advancedSend.html b/public/views/modals/advancedSend.html new file mode 100644 index 000000000..740903e2c --- /dev/null +++ b/public/views/modals/advancedSend.html @@ -0,0 +1,34 @@ + + + + + diff --git a/public/views/preferences.html b/public/views/preferences.html index bcc4ac0c7..c8753e221 100644 --- a/public/views/preferences.html +++ b/public/views/preferences.html @@ -81,11 +81,9 @@ Bitcoin Network Fee Policy - {{preferences.feeName|translate}} + {{index.currentFeeLevel}} - - - +
  • Bitcore Wallet Service diff --git a/public/views/preferencesFee.html b/public/views/preferencesFee.html index 85191018b..b0c740745 100644 --- a/public/views/preferencesFee.html +++ b/public/views/preferencesFee.html @@ -3,20 +3,28 @@ ng-include="'views/includes/topbar.html'" ng-init="titleSection='Bitcoin Network Fee Policy'; goBackToState = 'preferences'; noColor = true"> - -
    -
    - {{fee.name|translate}} ({{fee.value}} bits per kB) - {{fee.name|translate}} ({{fee.value}} bits per kB) - -
    +
      +
    • + {{fee.level}} + +
    • +
    +
    -
    -Bitcoin transactions may include a fee collected by miners on the network. The higher the fee, the greater the incentive a miner has to include that transaction in a block. The ‘Emergency’ level should only be used when there is a network congestion. +
    + Average confirmation time: {{fee.nbBlocks * 10}} minutes.
    + Current fee rate for this policy: {{fee.feePerKBUnit}}/kiB
    +
    +
    + Bitcoin transactions may include a fee collected by miners on the network. The higher the fee, the greater the incentive a miner has to include that transaction in a block. Actual fees are determined based on network load and the selected policy. +
    +
    diff --git a/src/css/main.css b/src/css/main.css index 2300e15d3..3de53f411 100644 --- a/src/css/main.css +++ b/src/css/main.css @@ -877,6 +877,7 @@ input.ng-invalid-match, input.ng-invalid-match:focus { .text-alert {color: red;} .text-success {color: #1ABC9C;} .text-spacing {letter-spacing:2px;} +.text-capitalize {text-transform: capitalize;} .panel { background: #FFFFFF; diff --git a/src/js/controllers/index.js b/src/js/controllers/index.js index bbd1ddf2e..53a9b5703 100644 --- a/src/js/controllers/index.js +++ b/src/js/controllers/index.js @@ -1,6 +1,6 @@ 'use strict'; -angular.module('copayApp.controllers').controller('indexController', function($rootScope, $scope, $log, $filter, $timeout, lodash, go, profileService, configService, isCordova, rateService, storageService, addressService, gettextCatalog, gettext, amMoment, nodeWebkit, addonManager) { +angular.module('copayApp.controllers').controller('indexController', function($rootScope, $scope, $log, $filter, $timeout, lodash, go, profileService, configService, isCordova, rateService, storageService, addressService, gettextCatalog, gettext, amMoment, nodeWebkit, addonManager, feeService) { var self = this; self.isCordova = isCordova; self.onGoingProcess = {}; @@ -278,6 +278,7 @@ angular.module('copayApp.controllers').controller('indexController', function($r } $log.debug('Wallet Status:', walletStatus); self.setPendingTxps(walletStatus.pendingTxps); + self.setFees(); // Status Shortcuts self.walletName = walletStatus.wallet.name; @@ -305,6 +306,22 @@ angular.module('copayApp.controllers').controller('indexController', function($r }); }; + self.setCurrentFeeLevel = function(level) { + self.currentFeeLevel = level || configService.getSync().wallet.settings.feeLevel || 'priority'; + }; + + self.setFees = function() { + var fc = profileService.focusedClient; + if (!fc) return; + $timeout(function() { + feeService.getFeeLevels(function(levels) { + self.feeLevels = levels; + self.setCurrentFeeLevel(); + $rootScope.$apply(); + }); + }); + }; + self.updateBalance = function() { var fc = profileService.focusedClient; $timeout(function() { @@ -801,6 +818,10 @@ angular.module('copayApp.controllers').controller('indexController', function($r }); }); + $rootScope.$on('Local/FeeLevelUpdated', function(event, level) { + self.setCurrentFeeLevel(level); + }); + $rootScope.$on('Local/ProfileBound', function() { storageService.getRemotePrefsStoredFlag(function(err, val) { if (err || val) return; diff --git a/src/js/controllers/preferences.js b/src/js/controllers/preferences.js index e19db43dc..742db5a84 100644 --- a/src/js/controllers/preferences.js +++ b/src/js/controllers/preferences.js @@ -4,7 +4,6 @@ angular.module('copayApp.controllers').controller('preferencesController', function($scope, $rootScope, $filter, $timeout, $modal, $log, lodash, configService, profileService) { var config = configService.getSync(); this.unitName = config.wallet.settings.unitName; - this.feeName = config.wallet.settings.feeName || 'Priority'; this.bwsurl = config.bws.url; this.selectedAlternative = { name: config.wallet.settings.alternativeName, diff --git a/src/js/controllers/preferencesFee.js b/src/js/controllers/preferencesFee.js index dde8e565b..7e8cfd98f 100644 --- a/src/js/controllers/preferencesFee.js +++ b/src/js/controllers/preferencesFee.js @@ -1,37 +1,20 @@ 'use strict'; angular.module('copayApp.controllers').controller('preferencesFeeController', - function($rootScope, $scope, configService, go, gettext) { - var config = configService.getSync(); - this.feeName = config.wallet.settings.feeName || 'Priority'; - this.feeOpts = [{ - name: gettext('Priority'), - value: 100, - }, { - name: gettext('Normal'), - value: 50, - }, { - name: gettext('Economy'), - value: 10, - }, { - name: gettext('Emergency'), - red: true, - value: 500, - }, ]; + function($rootScope, configService) { this.save = function(newFee) { var opts = { wallet: { settings: { - feeName: newFee.name, - feeValue: newFee.value * 100, + feeLevel: newFee } } }; - this.feeName = newFee.name; + $rootScope.$emit('Local/FeeLevelUpdated', newFee); configService.set(opts, function(err) { - if (err) console.log(err); + if (err) $log.debug(err); }); }; diff --git a/src/js/controllers/walletHome.js b/src/js/controllers/walletHome.js index 38873a39f..c4d19c977 100644 --- a/src/js/controllers/walletHome.js +++ b/src/js/controllers/walletHome.js @@ -1,6 +1,6 @@ 'use strict'; -angular.module('copayApp.controllers').controller('walletHomeController', function($scope, $rootScope, $timeout, $filter, $modal, $log, notification, txStatus, isCordova, profileService, lodash, configService, rateService, storageService, bitcore, isChromeApp, gettext, gettextCatalog, nodeWebkit, addressService) { +angular.module('copayApp.controllers').controller('walletHomeController', function($scope, $rootScope, $timeout, $filter, $modal, $log, notification, txStatus, isCordova, profileService, lodash, configService, rateService, storageService, bitcore, isChromeApp, gettext, gettextCatalog, nodeWebkit, addressService, feeService) { var self = this; $rootScope.hideMenuBar = false; @@ -526,7 +526,7 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi }); }; - // Send + // Send this.canShowAlternative = function() { return $scope.showAlternative; @@ -724,40 +724,43 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi address = form.address.$modelValue; amount = parseInt((form.amount.$modelValue * unitToSat).toFixed(0)); - fc.sendTxProposal({ - toAddress: address, - amount: amount, - message: comment, - payProUrl: paypro ? paypro.url : null, - feePerKb: config.feeValue || 10000, - }, function(err, txp) { - if (err) { - self.setOngoingProcess(); - profileService.lockFC(); - return self.setSendError(err); - } - - if (!fc.canSign()) { - $log.info('No signing proposal: No private key') - self.setOngoingProcess(); - self.resetForm(); - txStatus.notify(txp, function() { - return $scope.$emit('Local/TxProposalAction'); - }); - return; - } - - self.signAndBroadcast(txp, function(err) { - self.setOngoingProcess(); - profileService.lockFC(); - self.resetForm(); + feeService.getCurrentFeeValue(function(err, feePerKb) { + if (err) $log.debug(err); + fc.sendTxProposal({ + toAddress: address, + amount: amount, + message: comment, + payProUrl: paypro ? paypro.url : null, + feePerKb: feePerKb, + }, function(err, txp) { if (err) { - self.error = err.message ? err.message : gettext('The payment was created but could not be completed. Please try again from home screen'); - $scope.$emit('Local/TxProposalAction'); - $timeout(function() { - $scope.$digest(); - }, 1); + self.setOngoingProcess(); + profileService.lockFC(); + return self.setSendError(err); } + + if (!fc.canSign()) { + $log.info('No signing proposal: No private key') + self.setOngoingProcess(); + self.resetForm(); + txStatus.notify(txp, function() { + return $scope.$emit('Local/TxProposalAction'); + }); + return; + } + + self.signAndBroadcast(txp, function(err) { + self.setOngoingProcess(); + profileService.lockFC(); + self.resetForm(); + if (err) { + self.error = err.message ? err.message : gettext('The payment was created but could not be completed. Please try again from home screen'); + $scope.$emit('Local/TxProposalAction'); + $timeout(function() { + $scope.$digest(); + }, 1); + } + }); }); }); }, 100); @@ -970,6 +973,35 @@ angular.module('copayApp.controllers').controller('walletHomeController', functi } }; + // Advanced SEND: set temporary fee policy for each transaction + this.openAdvancedSendModal = function(feeLevels, currentFeeLevel) { + var fc = profileService.focusedClient; + + var ModalInstanceCtrl = function($scope, $modalInstance) { + $scope.feeLevels = feeLevels; + $scope.currentFeeLevel = currentFeeLevel + $scope.network = fc.credentials.network; + $scope.color = fc.backgroundColor; + $scope.save = function(level) { + $scope.currentFeeLevel = level; + }; + + $scope.cancel = function() { + $modalInstance.dismiss('cancel'); + }; + }; + var modalInstance = $modal.open({ + templateUrl: 'views/modals/advancedSend.html', + windowClass: 'full animated slideInUp', + controller: ModalInstanceCtrl + }); + + modalInstance.result.finally(function() { + var m = angular.element(document.getElementsByClassName('reveal-modal')); + m.addClass('slideOutDown'); + }); + }; + // History diff --git a/src/js/services/feeService.js b/src/js/services/feeService.js new file mode 100644 index 000000000..d37df45a7 --- /dev/null +++ b/src/js/services/feeService.js @@ -0,0 +1,52 @@ +'use strict'; + +angular.module('copayApp.services').factory('feeService', function($log, profileService, configService) { + var root = {}; + + root.getCurrentFeeValue = function(cb) { + var fc = profileService.focusedClient; + var config = configService.getSync().wallet.settings; + var feeLevel = config.feeLevel || 'normal'; + // static fee + var fee = 10000; + fc.getFeeLevels(fc.credentials.network, function(err, levels) { + if (err) { + return cb({message: 'Could not get dynamic fee. Using static 10000sat'}, fee); + } + else { + for (var i = 0; i < 3; i++) { + if (levels[i].level == feeLevel) { + fee = levels[i].feePerKB; + } + } + $log.debug('Dynamic fee for ' + feeLevel + ': ' + fee); + return cb(null, fee); + } + }); + }; + + root.getFeeLevels = function(cb) { + var fc = profileService.focusedClient; + var config = configService.getSync().wallet.settings; + var unitName = config.unitName; + + fc.getFeeLevels('livenet', function(errLivenet, levelsLivenet) { + fc.getFeeLevels('testnet', function(errTestnet, levelsTestnet) { + if (errLivenet || errTestnet) $log.debug('Could not get dynamic fee'); + else { + for (var i = 0; i < 3; i++) { + levelsLivenet[i]['feePerKBUnit'] = profileService.formatAmount(levelsLivenet[i].feePerKB) + ' ' + unitName; + levelsTestnet[i]['feePerKBUnit'] = profileService.formatAmount(levelsTestnet[i].feePerKB) + ' ' + unitName; + } + } + + return cb({ + 'livenet': levelsLivenet, + 'testnet': levelsTestnet + }); + }); + }); + }; + + return root; +});