variable fee for "send all funds"
This commit is contained in:
parent
e01cf88a90
commit
1f9b9c8dca
7 changed files with 78 additions and 53 deletions
|
|
@ -4,32 +4,6 @@ var preconditions = require('preconditions').singleton();
|
||||||
|
|
||||||
angular.module('copayApp.controllers').controller('SendController',
|
angular.module('copayApp.controllers').controller('SendController',
|
||||||
function($scope, $rootScope, $window, $timeout, $anchorScroll, $modal, isMobile, notification, controllerUtils, rateService) {
|
function($scope, $rootScope, $window, $timeout, $anchorScroll, $modal, isMobile, notification, controllerUtils, rateService) {
|
||||||
controllerUtils.redirIfNotComplete();
|
|
||||||
|
|
||||||
var w = $rootScope.wallet;
|
|
||||||
preconditions.checkState(w);
|
|
||||||
preconditions.checkState(w.settings.unitToSatoshi);
|
|
||||||
|
|
||||||
$rootScope.title = 'Send';
|
|
||||||
$scope.loading = false;
|
|
||||||
var satToUnit = 1 / w.settings.unitToSatoshi;
|
|
||||||
$scope.defaultFee = bitcore.TransactionBuilder.FEE_PER_1000B_SAT * satToUnit;
|
|
||||||
$scope.unitToBtc = w.settings.unitToSatoshi / bitcore.util.COIN;
|
|
||||||
$scope.unitToSatoshi = w.settings.unitToSatoshi;
|
|
||||||
|
|
||||||
$scope.alternativeName = w.settings.alternativeName;
|
|
||||||
$scope.alternativeIsoCode = w.settings.alternativeIsoCode;
|
|
||||||
|
|
||||||
$scope.isRateAvailable = false;
|
|
||||||
$scope.rateService = rateService;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
rateService.whenAvailable(function() {
|
|
||||||
$scope.isRateAvailable = true;
|
|
||||||
$scope.$digest();
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setting the two related amounts as properties prevents an infinite
|
* Setting the two related amounts as properties prevents an infinite
|
||||||
* recursion for watches while preserving the original angular updates
|
* recursion for watches while preserving the original angular updates
|
||||||
|
|
@ -396,13 +370,19 @@ angular.module('copayApp.controllers').controller('SendController',
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.getAvailableAmount = function() {
|
$scope.getAvailableAmount = function() {
|
||||||
var amount = ((($rootScope.availableBalance * w.settings.unitToSatoshi).toFixed(0) - bitcore.TransactionBuilder.FEE_PER_1000B_SAT) / w.settings.unitToSatoshi);
|
if (!$rootScope.safeUnspentCount) return null;
|
||||||
|
|
||||||
|
// Each signature takes
|
||||||
|
var estimatedFee = copay.Wallet.estimatedFee($rootScope.safeUnspentCount);
|
||||||
|
console.log('[send.js.376:estimatedFee:]',estimatedFee); //TODO
|
||||||
|
var amount = ((($rootScope.availableBalance * w.settings.unitToSatoshi).toFixed(0) - estimatedFee) / w.settings.unitToSatoshi);
|
||||||
|
|
||||||
|
console.log('[send.js.402:amount:]',amount); //TODO
|
||||||
return amount > 0 ? amount : 0;
|
return amount > 0 ? amount : 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.topAmount = function(form) {
|
$scope.topAmount = function(form) {
|
||||||
$scope.amount = $scope.getAvailableAmount();
|
$scope.amount = $scope.getAvailableAmount();
|
||||||
form.amount.$pristine = false;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -615,4 +595,31 @@ angular.module('copayApp.controllers').controller('SendController',
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
controllerUtils.redirIfNotComplete();
|
||||||
|
|
||||||
|
var w = $rootScope.wallet;
|
||||||
|
preconditions.checkState(w);
|
||||||
|
preconditions.checkState(w.settings.unitToSatoshi);
|
||||||
|
|
||||||
|
$rootScope.title = 'Send';
|
||||||
|
$scope.loading = false;
|
||||||
|
var satToUnit = 1 / w.settings.unitToSatoshi;
|
||||||
|
$scope.defaultFee = bitcore.TransactionBuilder.FEE_PER_1000B_SAT * satToUnit;
|
||||||
|
$scope.unitToBtc = w.settings.unitToSatoshi / bitcore.util.COIN;
|
||||||
|
$scope.unitToSatoshi = w.settings.unitToSatoshi;
|
||||||
|
|
||||||
|
$scope.alternativeName = w.settings.alternativeName;
|
||||||
|
$scope.alternativeIsoCode = w.settings.alternativeIsoCode;
|
||||||
|
|
||||||
|
$scope.isRateAvailable = false;
|
||||||
|
$scope.rateService = rateService;
|
||||||
|
$scope.availableBalance = $scope.getAvailableAmount();
|
||||||
|
|
||||||
|
rateService.whenAvailable(function() {
|
||||||
|
$scope.isRateAvailable = true;
|
||||||
|
$scope.$digest();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -2154,12 +2154,23 @@ Wallet.prototype.addressIsOwn = function(addrStr, opts) {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Estimate a tx fee in satoshis given its input count
|
||||||
|
* only for spending all wallet funds
|
||||||
|
*/
|
||||||
|
Wallet.estimatedFee = function(unspentCount) {
|
||||||
|
preconditions.checkArgument(_.isNumber(unspentCount));
|
||||||
|
var estimatedSizeKb = Math.ceil( ( 500 + unspentCount * 250) / 1024 );
|
||||||
|
return parseInt( estimatedSizeKb * bitcore.TransactionBuilder.FEE_PER_1000B_SAT);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @callback {getBalanceCallback}
|
* @callback {getBalanceCallback}
|
||||||
* @param {string=} err - an error, if any
|
* @param {string=} err - an error, if any
|
||||||
* @param {number} balance - total number of satoshis for all addresses
|
* @param {number} balance - total number of satoshis for all addresses
|
||||||
* @param {Object} balanceByAddr - maps string addresses to satoshis
|
* @param {Object} balanceByAddr - maps string addresses to satoshis
|
||||||
* @param {number} safeBalance - total number of satoshis in UTXOs that are not part of any TxProposal
|
* @param {number} safeBalance - total number of satoshis in UTXOs that are not part of any TxProposal
|
||||||
|
* @param {number} safeUnspentCount - total number of safe unspent Outputs that make this balance.
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* @desc Returns the balances for all addresses in Satoshis
|
* @desc Returns the balances for all addresses in Satoshis
|
||||||
|
|
@ -2190,14 +2201,16 @@ Wallet.prototype.getBalance = function(cb) {
|
||||||
|
|
||||||
balance = parseInt(balance.toFixed(0), 10);
|
balance = parseInt(balance.toFixed(0), 10);
|
||||||
|
|
||||||
for (var i = 0; i < safeUnspent.length; i++) {
|
var safeUnspentCount = safeUnspent.length;
|
||||||
|
|
||||||
|
for (var i = 0; i < safeUnspentCount; i++) {
|
||||||
var u = safeUnspent[i];
|
var u = safeUnspent[i];
|
||||||
var amt = u.amount * COIN;
|
var amt = u.amount * COIN;
|
||||||
safeBalance += amt;
|
safeBalance += amt;
|
||||||
}
|
}
|
||||||
|
|
||||||
safeBalance = parseInt(safeBalance.toFixed(0), 10);
|
safeBalance = parseInt(safeBalance.toFixed(0), 10);
|
||||||
return cb(null, balance, balanceByAddr, safeBalance);
|
return cb(null, balance, balanceByAddr, safeBalance, safeUnspentCount);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -2322,6 +2335,7 @@ Wallet.prototype.createTx = function(toAddress, amountSatStr, comment, opts, cb)
|
||||||
var ntxid;
|
var ntxid;
|
||||||
try {
|
try {
|
||||||
ntxid = self.createTxSync(toAddress, amountSatStr, comment, safeUnspent, opts);
|
ntxid = self.createTxSync(toAddress, amountSatStr, comment, safeUnspent, opts);
|
||||||
|
log.debub('TX Created: ntxid', ntxid); //TODO
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return cb(e);
|
return cb(e);
|
||||||
}
|
}
|
||||||
|
|
@ -2370,6 +2384,7 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos
|
||||||
opts[k] = Wallet.builderOpts[k];
|
opts[k] = Wallet.builderOpts[k];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('[Wallet.js.2386]'); //TODO
|
||||||
var b = new Builder(opts)
|
var b = new Builder(opts)
|
||||||
.setUnspent(utxos)
|
.setUnspent(utxos)
|
||||||
.setOutputs([{
|
.setOutputs([{
|
||||||
|
|
@ -2377,8 +2392,10 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos
|
||||||
amountSatStr: amountSatStr,
|
amountSatStr: amountSatStr,
|
||||||
}]);
|
}]);
|
||||||
|
|
||||||
|
log.debug('Creating TX: Builder ready');
|
||||||
|
|
||||||
var selectedUtxos = b.getSelectedUnspent();
|
var selectedUtxos = b.getSelectedUnspent();
|
||||||
|
console.log('[Wallet.js.2397:selectedUtxos:]',selectedUtxos); //TODO
|
||||||
|
|
||||||
if (selectedUtxos.size > TX_MAX_INS)
|
if (selectedUtxos.size > TX_MAX_INS)
|
||||||
throw new Error('BIG: Resulting TX is too big:' + selectedUtxos.size + ' inputs. Aborting');
|
throw new Error('BIG: Resulting TX is too big:' + selectedUtxos.size + ' inputs. Aborting');
|
||||||
|
|
|
||||||
|
|
@ -241,18 +241,19 @@ angular.module('copayApp.services')
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
root._computeBalance = function(w, cb) {
|
root._fetchBalance = function(w, cb) {
|
||||||
cb = cb || function() {};
|
cb = cb || function() {};
|
||||||
var satToUnit = 1 / w.settings.unitToSatoshi;
|
var satToUnit = 1 / w.settings.unitToSatoshi;
|
||||||
var COIN = bitcore.util.COIN;
|
var COIN = bitcore.util.COIN;
|
||||||
|
|
||||||
w.getBalance(function(err, balanceSat, balanceByAddrSat, safeBalanceSat) {
|
w.getBalance(function(err, balanceSat, balanceByAddrSat, safeBalanceSat, safeUnspentCount) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
|
|
||||||
var r = {};
|
var r = {};
|
||||||
r.totalBalance = balanceSat * satToUnit;
|
r.totalBalance = balanceSat * satToUnit;
|
||||||
r.totalBalanceBTC = (balanceSat / COIN);
|
r.totalBalanceBTC = (balanceSat / COIN);
|
||||||
r.availableBalance = safeBalanceSat * satToUnit;
|
r.availableBalance = safeBalanceSat * satToUnit;
|
||||||
|
r.safeUnspentCount = safeUnspentCount;
|
||||||
r.availableBalanceBTC = (safeBalanceSat / COIN);
|
r.availableBalanceBTC = (safeBalanceSat / COIN);
|
||||||
|
|
||||||
r.lockedBalance = (balanceSat - safeBalanceSat) * satToUnit;
|
r.lockedBalance = (balanceSat - safeBalanceSat) * satToUnit;
|
||||||
|
|
@ -276,22 +277,10 @@ angular.module('copayApp.services')
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
root._updateScope = function(w, data, $scope, cb) {
|
root._updateScope = function(w, data, scope, cb) {
|
||||||
$scope.totalBalance = data.totalBalance;
|
_.each(data, function(v, k) {
|
||||||
$scope.totalBalanceBTC = data.totalBalanceBTC;
|
scope[k] = data[k];
|
||||||
$scope.availableBalance = data.availableBalance;
|
})
|
||||||
$scope.availableBalanceBTC = data.availableBalanceBTC;
|
|
||||||
|
|
||||||
$scope.lockedBalance = data.lockedBalance;
|
|
||||||
$scope.lockedBalanceBTC = data.lockedBalanceBTC;
|
|
||||||
|
|
||||||
$scope.balanceByAddr = data.balanceByAddr;
|
|
||||||
|
|
||||||
$scope.totalBalanceAlternative = data.totalBalanceAlternative;
|
|
||||||
$scope.alternativeIsoCode = data.alternativeIsoCode;
|
|
||||||
$scope.lockedBalanceAlternative = data.lockedBalanceAlternative;
|
|
||||||
$scope.alternativeConversionRate = data.alternativeConversionRate;
|
|
||||||
|
|
||||||
if (cb) return cb();
|
if (cb) return cb();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -320,7 +309,7 @@ angular.module('copayApp.services')
|
||||||
scope.updatingBalance = true;
|
scope.updatingBalance = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
root._computeBalance(w, function(err, res) {
|
root._fetchBalance(w, function(err, res) {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
_balanceCache[wid] = res;
|
_balanceCache[wid] = res;
|
||||||
root._updateScope(w, _balanceCache[wid], scope, function() {
|
root._updateScope(w, _balanceCache[wid], scope, function() {
|
||||||
|
|
|
||||||
|
|
@ -1005,6 +1005,18 @@ describe('Wallet model', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#estimatedFee', function() {
|
||||||
|
it('should calculate estimated fee', function() {
|
||||||
|
var COIN = 100000000;
|
||||||
|
Wallet.estimatedFee(1).should.equal(0.0001 * COIN);
|
||||||
|
Wallet.estimatedFee(2).should.equal(0.0001 * COIN);
|
||||||
|
Wallet.estimatedFee(3).should.equal(0.0002 * COIN);
|
||||||
|
Wallet.estimatedFee(1000).should.equal(0.0245 * COIN);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
describe('#send', function() {
|
describe('#send', function() {
|
||||||
it('should call this.network.send', function() {
|
it('should call this.network.send', function() {
|
||||||
var w = cachedCreateW2();
|
var w = cachedCreateW2();
|
||||||
|
|
|
||||||
|
|
@ -128,7 +128,7 @@ describe('Insight model', function() {
|
||||||
|
|
||||||
sinon.stub(blockchain, "requestPost", function(url, data, cb) {
|
sinon.stub(blockchain, "requestPost", function(url, data, cb) {
|
||||||
url.should.be.equal('/api/tx/send');
|
url.should.be.equal('/api/tx/send');
|
||||||
var res = {status: 200};
|
var res = {statusCode: 200};
|
||||||
var body = {txid: 1234};
|
var body = {txid: 1234};
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
cb(null, res, body);
|
cb(null, res, body);
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ program
|
||||||
.usage('-d n2kMqQ8Si9GndzQ6FrJxcwHMKacK2rCEpK -n 2 -k tprv8ZgxMBicQKsPem5BuuDT6xY9etUC2RohpUoyzoa1MEkkZyAHhszaHPZTmgDheN31hSP1r6bRwpj2JC66r1CPpftwaRrhz')
|
.usage('-d n2kMqQ8Si9GndzQ6FrJxcwHMKacK2rCEpK -n 2 -k tprv8ZgxMBicQKsPem5BuuDT6xY9etUC2RohpUoyzoa1MEkkZyAHhszaHPZTmgDheN31hSP1r6bRwpj2JC66r1CPpftwaRrhz')
|
||||||
.option('-d, --destination <n>', 'Destination Address')
|
.option('-d, --destination <n>', 'Destination Address')
|
||||||
.option('-n, --required <n>', 'Required number of signatures', parseInt)
|
.option('-n, --required <n>', 'Required number of signatures', parseInt)
|
||||||
.option('-k, --keys <items>', 'master private keys', list)
|
.option('-k, --keys <items>', 'master private keys, separated by , ', list)
|
||||||
.option('-a, --amount <n>', 'Optional, amount to transfer, in Satoshis. If not provided, will wipe all funds', parseInt)
|
.option('-a, --amount <n>', 'Optional, amount to transfer, in Satoshis. If not provided, will wipe all funds', parseInt)
|
||||||
.option('-f, --fee [n]', 'Optional, fee in BTC (default 0.0001 BTC), only if amount is not provided', parseFloat)
|
.option('-f, --fee [n]', 'Optional, fee in BTC (default 0.0001 BTC), only if amount is not provided', parseFloat)
|
||||||
.parse(process.argv);
|
.parse(process.argv);
|
||||||
|
|
|
||||||
|
|
@ -80,9 +80,9 @@
|
||||||
<small class="icon-input" ng-show="sendForm.amount.$invalid &&
|
<small class="icon-input" ng-show="sendForm.amount.$invalid &&
|
||||||
!sendForm.amount.$pristine && !notValidAmount"><i class="fi-x"></i></small>
|
!sendForm.amount.$pristine && !notValidAmount"><i class="fi-x"></i></small>
|
||||||
<a class="small input-note" title="{{'Send all funds'|translate}}"
|
<a class="small input-note" title="{{'Send all funds'|translate}}"
|
||||||
ng-show="$root.availableBalance > 0 && (!$root.merchant || +$root.merchant.total === 0)"
|
ng-show="availableBalance && (!$root.merchant || +$root.merchant.total === 0)"
|
||||||
ng-click="topAmount(sendForm)">
|
ng-click="topAmount(sendForm)">
|
||||||
<span translate>Use all funds</span> ({{getAvailableAmount()}} {{$root.wallet.settings.unitName}})
|
<span translate>Use all funds</span> ({{availableBalance}} {{$root.wallet.settings.unitName}})
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="small-3 columns">
|
<div class="small-3 columns">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue