Merge pull request #1686 from matiu/bug/swipe

Fixes on swipeWallet and unspent
This commit is contained in:
Gustavo Maximiliano Cortez 2014-11-03 17:54:54 -03:00
commit f1005625dd
9 changed files with 200 additions and 104 deletions

View file

@ -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
@ -103,8 +77,7 @@ angular.module('copayApp.controllers').controller('SendController',
$scope.submitForm = function(form) { $scope.submitForm = function(form) {
if (form.$invalid) { if (form.$invalid) {
var message = 'Unable to send transaction proposal'; $scope.error = 'Unable to send transaction proposal';
notification.error('Error', message);
return; return;
} }
@ -116,8 +89,15 @@ angular.module('copayApp.controllers').controller('SendController',
function done(err, ntxid, merchantData) { function done(err, ntxid, merchantData) {
if (err) { if (err) {
var message = 'The transaction' + (w.isShared() ? ' proposal' : '') + ' could not be created'; copay.logger.error(err);
notification.error('Error', message);
var msg = err.toString();
if (msg.match('BIG'))
msg = 'The transaction have too many inputs. Try creating many transactions for smaller amounts.'
var message = 'The transaction' + (w.isShared() ? ' proposal' : '') + ' could not be created: ' + msg;
$scope.error = message;
$scope.loading = false; $scope.loading = false;
$scope.loadTxs(); $scope.loadTxs();
return; return;
@ -161,7 +141,7 @@ angular.module('copayApp.controllers').controller('SendController',
} }
notification.success('Transaction broadcasted', message); notification.success('Transaction broadcasted', message);
} else { } else {
notification.error('Error', 'There was an error sending the transaction'); $scope.error = 'There was an error sending the transaction';
} }
$scope.loading = false; $scope.loading = false;
$scope.loadTxs(); $scope.loadTxs();
@ -396,13 +376,16 @@ 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;
var estimatedFee = copay.Wallet.estimatedFee($rootScope.safeUnspentCount);
var amount = ((($rootScope.availableBalance * w.settings.unitToSatoshi).toFixed(0) - estimatedFee) / w.settings.unitToSatoshi);
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;
}; };
@ -411,7 +394,7 @@ angular.module('copayApp.controllers').controller('SendController',
$rootScope.txAlertCount = 0; $rootScope.txAlertCount = 0;
w.sendTx(ntxid, function(txid, merchantData) { w.sendTx(ntxid, function(txid, merchantData) {
if (!txid) { if (!txid) {
notification.error('Error', 'There was an error sending the transaction'); $scope.error = 'There was an error sending the transaction';
} else { } else {
if (!merchantData) { if (!merchantData) {
notification.success('Transaction broadcasted', 'Transaction id: ' + txid); notification.success('Transaction broadcasted', 'Transaction id: ' + txid);
@ -435,7 +418,7 @@ angular.module('copayApp.controllers').controller('SendController',
$scope.loading = true; $scope.loading = true;
w.sign(ntxid, function(ret) { w.sign(ntxid, function(ret) {
if (!ret) { if (!ret) {
notification.error('Error', 'There was an error signing the transaction'); $scope.error = 'There was an error signing the transaction';
$scope.loadTxs(); $scope.loadTxs();
} else { } else {
var p = w.txProposals.getTxProposal(ntxid); var p = w.txProposals.getTxProposal(ntxid);
@ -615,4 +598,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();
});
}); });

View file

@ -5,6 +5,7 @@ var async = require('async');
var request = require('request'); var request = require('request');
var bitcore = require('bitcore'); var bitcore = require('bitcore');
var io = require('socket.io-client'); var io = require('socket.io-client');
var _ = require('lodash');
var log = require('../log'); var log = require('../log');
var EventEmitter = require('events').EventEmitter; var EventEmitter = require('events').EventEmitter;
@ -255,7 +256,7 @@ Insight.prototype.broadcast = function(rawtx, cb) {
this.requestPost('/api/tx/send', { this.requestPost('/api/tx/send', {
rawtx: rawtx rawtx: rawtx
}, function(err, res, body) { }, function(err, res, body) {
if (err || res.status != 200) cb(err || res); if (err || res.statusCode != 200) cb(err || body);
cb(null, body ? body.txid : null); cb(null, body ? body.txid : null);
}); });
}; };
@ -310,9 +311,15 @@ Insight.prototype.getUnspent = function(addresses, cb) {
this.requestPost('/api/addrs/utxo', { this.requestPost('/api/addrs/utxo', {
addrs: addresses.join(',') addrs: addresses.join(',')
}, function(err, res, body) { }, function(err, res, unspentRaw) {
if (err || res.statusCode != 200) return cb(err || res); if (err || res.statusCode != 200) return cb(err || res);
cb(null, body);
// This filter out possible broken unspent, as reported on
// https://github.com/bitpay/copay/issues/1585
// and later gitter conversation.
var unspent = _.filter(unspentRaw, 'scriptPubKey');
cb(null, unspent);
}); });
}; };

View file

@ -30,6 +30,8 @@ var Async = require('./Async');
var Insight = module.exports.Insight = require('./Insight'); var Insight = module.exports.Insight = require('./Insight');
var copayConfig = require('../../config'); var copayConfig = require('../../config');
var TX_MAX_SIZE_KB = 50;
var TX_MAX_INS = 70;
/** /**
* @desc * @desc
* Wallet manages a private key for Copay, network, storage of the wallet for * Wallet manages a private key for Copay, network, storage of the wallet for
@ -1381,14 +1383,17 @@ Wallet.prototype.sendTx = function(ntxid, cb) {
var self = this; var self = this;
this.blockchain.broadcast(txHex, function(err, txid) { this.blockchain.broadcast(txHex, function(err, txid) {
log.debug('Wallet:' + self.id + ' BITCOIND txid:', txid); if (err)
log.error('Error sending TX:',err);
if (txid) { if (txid) {
log.debug('Wallet:' + self.getName() + ' Broadcasted TX. BITCOIND txid:', txid);
self.txProposals.get(ntxid).setSent(txid); self.txProposals.get(ntxid).setSent(txid);
self.sendTxProposal(ntxid); self.sendTxProposal(ntxid);
self.emitAndKeepAlive('txProposalsUpdated'); self.emitAndKeepAlive('txProposalsUpdated');
return cb(txid); return cb(txid);
} else { } else {
log.debug('Wallet:' + self.id + ' Sent failed. Checking if the TX was sent already'); log.info('Wallet:' + self.getName() + '. Sent failed. Checking if the TX was sent already');
self._checkSentTx(ntxid, function(txid) { self._checkSentTx(ntxid, function(txid) {
if (txid) if (txid)
self.emitAndKeepAlive('txProposalsUpdated'); self.emitAndKeepAlive('txProposalsUpdated');
@ -1879,15 +1884,9 @@ Wallet.prototype.createPaymentTxSync = function(options, merchantData, unspent)
merchantData.total = merchantData.total.toString(10); merchantData.total = merchantData.total.toString(10);
var b; var b = new Builder(opts)
try { .setUnspent(unspent)
b = new Builder(opts) .setOutputs(outs);
.setUnspent(unspent)
.setOutputs(outs);
} catch (e) {
log.debug(e.message);
return;
};
merchantData.pr.pd.outputs.forEach(function(output, i) { merchantData.pr.pd.outputs.forEach(function(output, i) {
var script = { var script = {
@ -2155,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
@ -2191,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);
}); });
}; };
@ -2320,7 +2332,14 @@ Wallet.prototype.createTx = function(toAddress, amountSatStr, comment, opts, cb)
this.getUnspent(function(err, safeUnspent) { this.getUnspent(function(err, safeUnspent) {
if (err) return cb(new Error('Could not get list of UTXOs')); if (err) return cb(new Error('Could not get list of UTXOs'));
var ntxid = self.createTxSync(toAddress, amountSatStr, comment, safeUnspent, opts); var ntxid;
try {
ntxid = self.createTxSync(toAddress, amountSatStr, comment, safeUnspent, opts);
log.debug('TX Created: ntxid', ntxid); //TODO
} catch (e) {
return cb(e);
}
if (!ntxid) { if (!ntxid) {
return cb(new Error('Error creating the transaction')); return cb(new Error('Error creating the transaction'));
} }
@ -2365,21 +2384,21 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos
opts[k] = Wallet.builderOpts[k]; opts[k] = Wallet.builderOpts[k];
} }
var b; var b = new Builder(opts)
.setUnspent(utxos)
.setOutputs([{
address: toAddress.data,
amountSatStr: amountSatStr,
}]);
try { log.debug('Creating TX: Builder ready');
b = new Builder(opts)
.setUnspent(utxos)
.setOutputs([{
address: toAddress.data,
amountSatStr: amountSatStr,
}]);
} catch (e) {
log.debug(e.message);
return;
};
var selectedUtxos = b.getSelectedUnspent(); var selectedUtxos = b.getSelectedUnspent();
if (selectedUtxos.length > TX_MAX_INS)
throw new Error('BIG: Resulting TX is too big:' + selectedUtxos.length + ' inputs. Aborting');
var inputChainPaths = selectedUtxos.map(function(utxo) { var inputChainPaths = selectedUtxos.map(function(utxo) {
return pkr.pathForAddress(utxo.address); return pkr.pathForAddress(utxo.address);
}); });
@ -2396,6 +2415,12 @@ Wallet.prototype.createTxSync = function(toAddress, amountSatStr, comment, utxos
if (!tx.countInputSignatures(0)) if (!tx.countInputSignatures(0))
throw new Error('Could not sign generated tx'); throw new Error('Could not sign generated tx');
var txSize = tx.getSize();
if (txSize/1024 > TX_MAX_SIZE_KB)
throw new Error('BIG: Resulting TX is too big ' + txSize + ' bytes. Aborting');
var me = {}; var me = {};
me[myId] = now; me[myId] = now;

View file

@ -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() {

View file

@ -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();

View file

@ -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);

View file

@ -42,6 +42,7 @@ describe("Unit: Controllers", function() {
beforeEach(inject(function($controller, $rootScope) { beforeEach(inject(function($controller, $rootScope) {
scope = $rootScope.$new(); scope = $rootScope.$new();
$rootScope.iden = sinon.stub(); $rootScope.iden = sinon.stub();
$rootScope.safeUnspentCount = 1;
var w = {}; var w = {};
w.isReady = sinon.stub().returns(true); w.isReady = sinon.stub().returns(true);
@ -467,6 +468,7 @@ describe("Unit: Controllers", function() {
expect(form.amount.$pristine).to.equal(false); expect(form.amount.$pristine).to.equal(false);
}); });
it('should return available amount', function() { it('should return available amount', function() {
form.amount.$setViewValue(123356);
var amount = scope.getAvailableAmount(); var amount = scope.getAvailableAmount();
expect(amount).to.equal(123356); expect(amount).to.equal(123356);
}); });

View file

@ -1,8 +1,8 @@
#!/usr/bin/env node #!/usr/bin/env node
'use strict'; 'use strict';
var copay = require('../copay'); var copay = require('../copay');
var program = require('commander');
var _ = require('lodash'); var _ = require('lodash');
var config = require('../config'); var config = require('../config');
var version = require('../version').version; var version = require('../version').version;
@ -10,6 +10,22 @@ var sinon = require('sinon');
var bitcore = require('bitcore'); var bitcore = require('bitcore');
var readline = require('readline'); var readline = require('readline');
var async = require('async'); var async = require('async');
var program = require('commander');
function list(val) {
return val.split(',');
}
program
.version('0.0.1')
.usage('-d n2kMqQ8Si9GndzQ6FrJxcwHMKacK2rCEpK -n 2 -k tprv8ZgxMBicQKsPem5BuuDT6xY9etUC2RohpUoyzoa1MEkkZyAHhszaHPZTmgDheN31hSP1r6bRwpj2JC66r1CPpftwaRrhz')
.option('-d, --destination <n>', 'Destination Address')
.option('-n, --required <n>', 'Required number of signatures', parseInt)
.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('-f, --fee [n]', 'Optional, fee in BTC (default 0.0001 BTC), only if amount is not provided', parseFloat)
.parse(process.argv);
var rl = readline.createInterface({ var rl = readline.createInterface({
input: process.stdin, input: process.stdin,
@ -17,26 +33,39 @@ var rl = readline.createInterface({
}); });
var args = process.argv; var args = process.argv;
var destAddr = args[2]; var requiredCopayers = program.required;
var DFLT_FEE = 0.0001 * bitcore.util.COIN; var extPrivKeys = program.keys;
var destAddr = program.destination;
var amount = program.amount;
if (!args[4]) { if (amount && program.fee) {
console.log('\n\tusage: swipeWallet.js <destionation address> <required signature number> <master private key 0> [<master private key 1> ...]'); console.log('If amount if given, fee will be automatically calculated');
console.log('\t e.g.: ./swipeWallet.js mxBVchwitGLXBHtT4Vah7DdP8J9M23ftE6 2 tprv8ZgxMBicQKsPejj9Xpky8M7NFv7szxqszBR2VvZTEkBTCCXZLtJfQwRxhUycNCu4sqyZepx8AfT1vuJr949np1gxYbZaJK3R9qekYPCZiJz tprv8ZgxMBicQKsPdWe14mn5SPY4zjG7fJnrmhkVZgTHQfYp91Kf1Lxof38KBQJiis4xv2zvZ2pVHgLn4GFRDUd8kR2HkMxDqLDNWTmnKqp95mZ tprv8ZgxMBicQKsPdzoFwT72Lwhr6n48ZyPahTAhPNaoAP4srVA1mcfPon7GWQaiwfAWesWACHm3aCBLYNGNPVKSU3E9vr1cLiBoMkayZiARywe');
process.exit(1); process.exit(1);
} }
var requiredCopayers = parseInt(args[3]); if (!requiredCopayers || !extPrivKeys || !extPrivKeys.length || !destAddr){
var extPrivKeys = args.slice(4); program.outputHelp();
var totalCopayers = extPrivKeys.length; process.exit(1);
}
// Fee to asign to the tx. Please put a bigger number if you get 'unsufficient unspent'
var fee = program.fee || 0.0001;
var totalCopayers = extPrivKeys.length;
var addr = new bitcore.Address(destAddr); var addr = new bitcore.Address(destAddr);
if (!addr.isValid()) { if (!addr.isValid()) {
console.log('\tBad destination address'); //TODO console.log('\tBad destination address'); //TODO
process.exit(1); process.exit(1);
} }
var networkName = addr.network().name; var networkName = addr.network().name;
console.log('\tNetwork: %s\n\tDestination Address:%s\n\tRequired copayers: %d\n\tTotal copayers: %d\n\tKeys:', networkName, destAddr, requiredCopayers, totalCopayers, extPrivKeys); //TODO console.log('\tNetwork: %s\n\tDestination Address:%s\n\tRequired copayers: %d\n\tTotal copayers: %d\n\tFee: %d\n\tKeys:',
networkName, destAddr, requiredCopayers,
totalCopayers, fee, extPrivKeys); //TODO
console.log('\n ----------------------------'); console.log('\n ----------------------------');
if (requiredCopayers > totalCopayers) if (requiredCopayers > totalCopayers)
@ -119,17 +148,30 @@ firstWallet.updateIndexes(function() {
console.log('Balance per address:', balanceByAddr); //TODO console.log('Balance per address:', balanceByAddr); //TODO
if (!balance) { if (!balance) {
console.log('Could not find any balance from the generated wallet'); //TODO throw ('Could not find any coins in the generated wallet');
process.exit(1);
} }
rl.question("\n\tShould I swipe the wallet (destination address" + destAddr + ")?\n\t(`yes` to continue)\n\t", function(answer) {
if (answer !== 'yes')
process.exit(1);
var amount = balance - DFLT_FEE; if (amount && amount >= balance)
throw ('Not enought balance fund to fullfill ' + amount + ' satoshis');
rl.question("\n\tShould I swipe the wallet (destination address is:" + destAddr + " Amount: "+ amount + "Satoshis )?\n\t(`yes` to continue)\n\t", function(answer) {
if (answer !== 'yes')
process.exit(1);
amount = amount || balance - fee * bitcore.util.COIN;
firstWallet.createTx(destAddr, amount, '', {}, function(err, ntxid) { firstWallet.createTx(destAddr, amount, '', {}, function(err, ntxid) {
console.log('\n\t### Tx Proposal Created...\n\tWith copayer 0 signature.'); if (err || !ntxid) {
if (err && err.toString().match('BIG')) {
throw new Error('Could not create tx' + err );
} else {
throw new Error('Could not create tx' + err + '. Try a bigger fee (--fee).');
}
}
console.log('\n\t### Tx Proposal Created...\n\tWith copayer 0 signature.');
if (requiredCopayers === 1) { if (requiredCopayers === 1) {
firstWallet.sendTx(ntxid, function(txid) { firstWallet.sendTx(ntxid, function(txid) {
console.log('\t ####### SENT TXID:', txid); console.log('\t ####### SENT TXID:', txid);
@ -161,6 +203,9 @@ firstWallet.updateIndexes(function() {
var p = firstWallet.txProposals.getTxProposal(ntxid); var p = firstWallet.txProposals.getTxProposal(ntxid);
if (p.builder.isFullySigned()) { if (p.builder.isFullySigned()) {
console.log('\t FULLY SIGNED. BROADCASTING NOW....'); console.log('\t FULLY SIGNED. BROADCASTING NOW....');
var tx = p.builder.build();
var txHex = tx.serialize().toString('hex');
//console.log('\t RAW TX: ', txHex);
firstWallet.sendTx(ntxid, function(txid) { firstWallet.sendTx(ntxid, function(txid) {
console.log('\t ####### SENT TXID:', txid); console.log('\t ####### SENT TXID:', txid);
process.exit(1); process.exit(1);

View file

@ -11,6 +11,12 @@
<div class="row collapse m0"> <div class="row collapse m0">
<div class="large-6 columns"> <div class="large-6 columns">
<form name="sendForm" ng-submit="submitForm(sendForm)" novalidate> <form name="sendForm" ng-submit="submitForm(sendForm)" novalidate>
<p class="text-warning size-16"
ng-show="error">
<i class="fi-alert"></i>
{{error|translate}}
</p>
<div class="row collapse"> <div class="row collapse">
<div class="large-12 columns"> <div class="large-12 columns">
<div class="row collapse"> <div class="row collapse">
@ -80,9 +86,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">