custom fee feature

This commit is contained in:
Gabriel Bazán 2017-07-07 10:26:31 -03:00
parent 3141feef27
commit 3fca4b90d3
6 changed files with 97 additions and 33 deletions

View File

@ -202,7 +202,9 @@ angular.module('copayApp.controllers').controller('confirmController', function(
txp.inputs = tx.sendMaxInfo.inputs; txp.inputs = tx.sendMaxInfo.inputs;
txp.fee = tx.sendMaxInfo.fee; txp.fee = tx.sendMaxInfo.fee;
} else { } else {
txp.feeLevel = tx.feeLevel; if (tx.feeLevel == 'custom') {
txp.feePerKb = tx.feeRate;
} else txp.feeLevel = tx.feeLevel;
} }
txp.message = tx.description; txp.message = tx.description;
@ -245,12 +247,12 @@ angular.module('copayApp.controllers').controller('confirmController', function(
refresh(); refresh();
// End of quick refresh, before wallet is selected. // End of quick refresh, before wallet is selected.
if (!wallet)return cb(); if (!wallet) return cb();
feeService.getFeeRate(tx.network, tx.feeLevel, function(err, feeRate) { feeService.getFeeRate(tx.network, tx.feeLevel, function(err, feeRate) {
if (err) return cb(err); if (err) return cb(err);
tx.feeRate = feeRate; if (tx.feeLevel != 'custom') tx.feeRate = feeRate;
tx.feeLevelName = feeService.feeOpts[tx.feeLevel]; tx.feeLevelName = feeService.feeOpts[tx.feeLevel];
if (!wallet) if (!wallet)
@ -294,8 +296,7 @@ angular.module('copayApp.controllers').controller('confirmController', function(
var per = (txp.fee / (txp.amount + txp.fee) * 100); var per = (txp.fee / (txp.amount + txp.fee) * 100);
txp.feeRatePerStr = per.toFixed(2) + '%'; txp.feeRatePerStr = per.toFixed(2) + '%';
txp.feeToHigh = per > FEE_TOO_HIGH_LIMIT_PER; txp.feeToHigh = per > FEE_TOO_HIGH_LIMIT_PER;
tx.txp[wallet.id] = txp; tx.txp[wallet.id] = txp;
$log.debug('Confirm. TX Fully Updated for wallet:' + wallet.id, tx); $log.debug('Confirm. TX Fully Updated for wallet:' + wallet.id, tx);
@ -558,9 +559,12 @@ angular.module('copayApp.controllers').controller('confirmController', function(
scope.network = tx.network; scope.network = tx.network;
scope.feeLevel = tx.feeLevel; scope.feeLevel = tx.feeLevel;
scope.noSave = true; scope.noSave = true;
if (tx.feeLevel == 'custom') scope.feePerSatByte = (tx.feeRate / 1000).toFixed();
$ionicModal.fromTemplateUrl('views/modals/chooseFeeLevel.html', { $ionicModal.fromTemplateUrl('views/modals/chooseFeeLevel.html', {
scope: scope, scope: scope,
backdropClickToClose: false,
hardwareBackButtonClose: false
}).then(function(modal) { }).then(function(modal) {
scope.chooseFeeLevelModal = modal; scope.chooseFeeLevelModal = modal;
scope.openModal(); scope.openModal();
@ -569,18 +573,19 @@ angular.module('copayApp.controllers').controller('confirmController', function(
scope.chooseFeeLevelModal.show(); scope.chooseFeeLevelModal.show();
}; };
scope.hideModal = function(customFeeLevel) { scope.hideModal = function(newFeeLevel, customFeePerKBValue) {
scope.chooseFeeLevelModal.hide(); scope.chooseFeeLevelModal.hide();
$log.debug('Custom fee level choosen:' + customFeeLevel + ' was:' + tx.feeLevel); $log.debug('New fee level choosen:' + newFeeLevel + ' was:' + tx.feeLevel);
if (tx.feeLevel == customFeeLevel)
return; if (tx.feeLevel == newFeeLevel && !customFeePerKBValue) return;
tx.feeLevel = newFeeLevel;
if (customFeePerKBValue) tx.feeRate = parseInt(customFeePerKBValue);
tx.feeLevel = customFeeLevel;
updateTx(tx, wallet, { updateTx(tx, wallet, {
clearCache: true, clearCache: true,
dryRun: true, dryRun: true
}, function() { }, function() {});
});
}; };
}; };

View File

@ -2,14 +2,14 @@
angular.module('copayApp.controllers').controller('preferencesFeeController', function($scope, $timeout, $ionicHistory, lodash, gettextCatalog, configService, feeService, ongoingProcess, popupService) { angular.module('copayApp.controllers').controller('preferencesFeeController', function($scope, $timeout, $ionicHistory, lodash, gettextCatalog, configService, feeService, ongoingProcess, popupService) {
var network;
$scope.save = function(newFee) { $scope.save = function(newFee) {
$scope.currentFeeLevel = newFee;
updateCurrentValues();
if ($scope.noSave) $scope.currentFeeLevel = newFee;
return;
if ($scope.currentFeeLevel != 'custom') updateCurrentValues();
else showCustomFeePrompt();
if ($scope.noSave) return;
var opts = { var opts = {
wallet: { wallet: {
@ -32,7 +32,6 @@ angular.module('copayApp.controllers').controller('preferencesFeeController', fu
}); });
$scope.init = function() { $scope.init = function() {
$scope.network = $scope.network || 'livenet'; $scope.network = $scope.network || 'livenet';
$scope.feeOpts = feeService.feeOpts; $scope.feeOpts = feeService.feeOpts;
$scope.currentFeeLevel = $scope.feeLevel || feeService.getCurrentFeeLevel(); $scope.currentFeeLevel = $scope.feeLevel || feeService.getCurrentFeeLevel();
@ -60,16 +59,56 @@ angular.module('copayApp.controllers').controller('preferencesFeeController', fu
}); });
if (lodash.isEmpty(value)) { if (lodash.isEmpty(value)) {
$scope.feePerSatByte = null; $scope.feePerSatByte = $scope.currentFeeLevel == 'custom' ? $scope.feePerSatByte : null;
$scope.avgConfirmationTime = null; $scope.avgConfirmationTime = null;
setMinWarning();
setMaxWarning();
return; return;
} }
$scope.feePerSatByte = (value.feePerKB / 1000).toFixed(); $scope.feePerSatByte = (value.feePerKB / 1000).toFixed();
$scope.avgConfirmationTime = value.nbBlocks * 10; $scope.avgConfirmationTime = value.nbBlocks * 10;
$scope.invalidCustomFeeEntered = false;
setMinWarning();
setMaxWarning();
}; };
$scope.chooseNewFee = function() { $scope.chooseNewFee = function() {
$scope.hideModal($scope.currentFeeLevel); $scope.hideModal($scope.currentFeeLevel, $scope.customFeePerKB);
}; };
var showCustomFeePrompt = function() {
$scope.invalidCustomFeeEntered = true;
$scope.showMaxWarning = false;
popupService.showPrompt(null, gettextCatalog.getString('Custom Fee'), null, function(text) {
if (!text || !parseInt(text)) return;
$scope.feePerSatByte = parseInt(text);
$scope.customFeePerKB = ($scope.feePerSatByte * 1000).toFixed();
setMaxWarning();
setMinWarning();
});
};
$scope.getMinimumRecommeded = function() {
var value = lodash.find($scope.feeLevels[$scope.network], {
level: 'superEconomy'
});
return parseInt((value.feePerKB / 1000).toFixed());
};
var setMinWarning = function() {
if (parseInt($scope.feePerSatByte) < $scope.getMinimumRecommeded()) $scope.showMinWarning = true;
else $scope.showMinWarning = false;
};
var setMaxWarning = function() {
if (parseInt($scope.feePerSatByte) > 1000) {
$scope.showMaxWarning = true;
$scope.invalidCustomFeeEntered = true;
} else {
$scope.showMaxWarning = false;
$scope.invalidCustomFeeEntered = false;
}
};
}); });

View File

@ -11,7 +11,8 @@ angular.module('copayApp.services').factory('feeService', function($log, $timeou
priority: gettext('Priority'), priority: gettext('Priority'),
normal: gettext('Normal'), normal: gettext('Normal'),
economy: gettext('Economy'), economy: gettext('Economy'),
superEconomy: gettext('Super Economy') superEconomy: gettext('Super Economy'),
custom: gettext('Custom')
}; };
var cache = { var cache = {
@ -24,6 +25,9 @@ angular.module('copayApp.services').factory('feeService', function($log, $timeou
root.getFeeRate = function(network, feeLevel, cb) { root.getFeeRate = function(network, feeLevel, cb) {
if (feeLevel == 'custom') return cb();
network = network || 'livenet'; network = network || 'livenet';
root.getFeeLevels(function(err, levels, fromCache) { root.getFeeLevels(function(err, levels, fromCache) {

View File

@ -25,6 +25,12 @@
background-color: #fff; background-color: #fff;
font-size:0.9em; font-size:0.9em;
color: $v-mid-gray; color: $v-mid-gray;
.text {
padding-left: 30px;
}
.icon {
position: absolute;
}
} }
&-explanation, &-button-group { &-explanation, &-button-group {
padding: 0 1rem; padding: 0 1rem;
@ -162,6 +168,7 @@
#settings-fee { #settings-fee {
.estimates { .estimates {
min-height: 6rem;
font-size: 15px; font-size: 15px;
color: $v-dark-gray; color: $v-dark-gray;
margin-bottom: .5rem; margin-bottom: .5rem;

View File

@ -1,26 +1,27 @@
<ion-modal-view id="settings-fee" class="settings" ng-controller="preferencesFeeController" > <ion-modal-view id="settings-fee" class="settings" ng-controller="preferencesFeeController">
<ion-header-bar align-title="center" class="bar-royal"> <ion-header-bar align-title="center" class="bar-royal">
<div class="title"> <div class="title">
{{'Bitcoin Network Fee Policy'|translate}} {{'Bitcoin Network Fee Policy'|translate}}
</div> </div>
<button class="button button-clear" ng-click="chooseNewFee()"> <button ng-disabled="invalidCustomFeeEntered" class="button button-clear" ng-click="chooseNewFee()">
OK OK
</button> </button>
</ion-header-bar> </ion-header-bar>
<ion-content ng-init="init(network)"> <ion-content ng-init="init()">
<div class="settings-explanation"> <div class="settings-explanation">
<div class="estimates"> <div class="estimates">
<div> <div>
<span translate>Average confirmation time</span>: <span translate>Average confirmation time</span>:
<span class="fee-minutes" ng-if="avgConfirmationTime">{{avgConfirmationTime | amDurationFormat: 'minute'}}</span> <span class="fee-minutes" ng-if="avgConfirmationTime && currentFeeLevel != 'custom'">{{avgConfirmationTime | amDurationFormat: 'minute'}}</span>
<span ng-if="loadingFee">...</span> <span class="fee-minutes" ng-if="currentFeeLevel == 'custom' && !invalidCustomFeeEntered" translate>Could not be estimated</span>
<span ng-if="loadingFee || invalidCustomFeeEntered">...</span>
</div> </div>
<div> <div>
<span translate>Current fee rate for this policy</span>: <span translate>Current fee rate for this policy</span>:
<span class="fee-rate" ng-if="feePerSatByte">{{feePerSatByte}} satoshis/byte</span> <span class="fee-rate" ng-if="feePerSatByte && !invalidCustomFeeEntered">{{feePerSatByte}} satoshis/byte</span>
<span ng-if="loadingFee">...</span> <span ng-if="loadingFee || invalidCustomFeeEntered">...</span>
</div> </div>
<div ng-if="network!='livenet'">[{{network}}]</span> <span ng-if="network!='livenet'">[{{network}}]</span>
</div> </div>
</div> </div>
<div class="fee-policies"> <div class="fee-policies">
@ -28,5 +29,13 @@
{{level|translate}} {{level|translate}}
</ion-radio> </ion-radio>
</div> </div>
<div class="comment" ng-if="showMinWarning">
<i class="icon"><img src="img/icon-warning.png" width="20px"></i>
<span class="text" translate>Your fee is lower than the minimum recommended ({{getMinimumRecommeded()}} satoshis/byte). Your transaction may never get confirmed.</span>
</div>
<div class="comment" ng-if="showMaxWarning">
<i class="icon"><img src="img/icon-warning.png" width="20px"></i>
<span class="text" translate>Your could not set a fee higher than 1000 satoshis/byte.</span>
</div>
</ion-content> </ion-content>
</ion-modal-view> </ion-modal-view>

View File

@ -24,7 +24,7 @@
</div> </div>
</div> </div>
<div class="fee-policies"> <div class="fee-policies">
<ion-radio ng-repeat="(fee, level) in feeOpts" ng-value="fee" ng-model="currentFeeLevel" ng-click="save(fee)"> <ion-radio ng-repeat="(fee, level) in feeOpts" ng-if="fee != 'custom'" ng-value="fee" ng-model="currentFeeLevel" ng-click="save(fee)">
{{level|translate}} {{level|translate}}
</ion-radio> </ion-radio>
</div> </div>