diff --git a/js/controllers/send.js b/js/controllers/send.js index e4f378c1c..eb6c7b25b 100644 --- a/js/controllers/send.js +++ b/js/controllers/send.js @@ -4,32 +4,6 @@ var preconditions = require('preconditions').singleton(); angular.module('copayApp.controllers').controller('SendController', 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 * recursion for watches while preserving the original angular updates @@ -396,13 +370,19 @@ angular.module('copayApp.controllers').controller('SendController', }; $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; }; $scope.topAmount = function(form) { $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(); + }); + + + }); diff --git a/js/models/Wallet.js b/js/models/Wallet.js index 8c4d6bce2..83ef55582 100644 --- a/js/models/Wallet.js +++ b/js/models/Wallet.js @@ -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} * @param {string=} err - an error, if any * @param {number} balance - total number of satoshis for all addresses * @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} safeUnspentCount - total number of safe unspent Outputs that make this balance. */ /** * @desc Returns the balances for all addresses in Satoshis @@ -2190,14 +2201,16 @@ Wallet.prototype.getBalance = function(cb) { 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 amt = u.amount * COIN; safeBalance += amt; } 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; try { ntxid = self.createTxSync(toAddress, amountSatStr, comment, safeUnspent, opts); + log.debub('TX Created: ntxid', ntxid); //TODO } catch (e) { return cb(e); } @@ -2370,6 +2384,7 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos opts[k] = Wallet.builderOpts[k]; } +console.log('[Wallet.js.2386]'); //TODO var b = new Builder(opts) .setUnspent(utxos) .setOutputs([{ @@ -2377,8 +2392,10 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos amountSatStr: amountSatStr, }]); + log.debug('Creating TX: Builder ready'); var selectedUtxos = b.getSelectedUnspent(); +console.log('[Wallet.js.2397:selectedUtxos:]',selectedUtxos); //TODO if (selectedUtxos.size > TX_MAX_INS) throw new Error('BIG: Resulting TX is too big:' + selectedUtxos.size + ' inputs. Aborting'); diff --git a/js/services/controllerUtils.js b/js/services/controllerUtils.js index 94e672817..a106f8d1f 100644 --- a/js/services/controllerUtils.js +++ b/js/services/controllerUtils.js @@ -241,18 +241,19 @@ angular.module('copayApp.services') }; - root._computeBalance = function(w, cb) { + root._fetchBalance = function(w, cb) { cb = cb || function() {}; var satToUnit = 1 / w.settings.unitToSatoshi; 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); var r = {}; r.totalBalance = balanceSat * satToUnit; r.totalBalanceBTC = (balanceSat / COIN); r.availableBalance = safeBalanceSat * satToUnit; + r.safeUnspentCount = safeUnspentCount; r.availableBalanceBTC = (safeBalanceSat / COIN); r.lockedBalance = (balanceSat - safeBalanceSat) * satToUnit; @@ -276,22 +277,10 @@ angular.module('copayApp.services') }); }; - root._updateScope = function(w, data, $scope, cb) { - $scope.totalBalance = data.totalBalance; - $scope.totalBalanceBTC = data.totalBalanceBTC; - $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; - + root._updateScope = function(w, data, scope, cb) { + _.each(data, function(v, k) { + scope[k] = data[k]; + }) if (cb) return cb(); }; @@ -320,7 +309,7 @@ angular.module('copayApp.services') scope.updatingBalance = true; } - root._computeBalance(w, function(err, res) { + root._fetchBalance(w, function(err, res) { if (err) throw err; _balanceCache[wid] = res; root._updateScope(w, _balanceCache[wid], scope, function() { diff --git a/test/Wallet.js b/test/Wallet.js index 7090943ac..cdfa81822 100644 --- a/test/Wallet.js +++ b/test/Wallet.js @@ -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() { it('should call this.network.send', function() { var w = cachedCreateW2(); diff --git a/test/blockchain.Insight.js b/test/blockchain.Insight.js index 244bde8ee..386e16bbf 100644 --- a/test/blockchain.Insight.js +++ b/test/blockchain.Insight.js @@ -128,7 +128,7 @@ describe('Insight model', function() { sinon.stub(blockchain, "requestPost", function(url, data, cb) { url.should.be.equal('/api/tx/send'); - var res = {status: 200}; + var res = {statusCode: 200}; var body = {txid: 1234}; setTimeout(function() { cb(null, res, body); diff --git a/util/swipeWallet.js b/util/swipeWallet.js index 98237497b..94186193f 100755 --- a/util/swipeWallet.js +++ b/util/swipeWallet.js @@ -21,7 +21,7 @@ program .usage('-d n2kMqQ8Si9GndzQ6FrJxcwHMKacK2rCEpK -n 2 -k tprv8ZgxMBicQKsPem5BuuDT6xY9etUC2RohpUoyzoa1MEkkZyAHhszaHPZTmgDheN31hSP1r6bRwpj2JC66r1CPpftwaRrhz') .option('-d, --destination ', 'Destination Address') .option('-n, --required ', 'Required number of signatures', parseInt) - .option('-k, --keys ', 'master private keys', list) + .option('-k, --keys ', 'master private keys, separated by , ', list) .option('-a, --amount ', '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) .parse(process.argv); diff --git a/views/send.html b/views/send.html index 7707ce9dd..539e68905 100644 --- a/views/send.html +++ b/views/send.html @@ -80,9 +80,9 @@ - Use all funds ({{getAvailableAmount()}} {{$root.wallet.settings.unitName}}) + Use all funds ({{availableBalance}} {{$root.wallet.settings.unitName}})