|
|
@ -421,6 +421,20 @@ WalletService.prototype._getUtxos = function(cb) { |
|
|
|
}); |
|
|
|
}; |
|
|
|
|
|
|
|
WalletService.prototype._totalizeUtxos = function(utxos) { |
|
|
|
var balance = {}; |
|
|
|
balance.totalAmount = Utils.strip(_.reduce(utxos, function(sum, utxo) { |
|
|
|
return sum + utxo.satoshis; |
|
|
|
}, 0)); |
|
|
|
|
|
|
|
balance.lockedAmount = Utils.strip(_.reduce(_.filter(utxos, { |
|
|
|
locked: true |
|
|
|
}), function(sum, utxo) { |
|
|
|
return sum + utxo.satoshis; |
|
|
|
}, 0)); |
|
|
|
return balance; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
* Creates a new transaction proposal. |
|
|
@ -433,16 +447,7 @@ WalletService.prototype.getBalance = function(opts, cb) { |
|
|
|
self._getUtxos(function(err, utxos) { |
|
|
|
if (err) return cb(err); |
|
|
|
|
|
|
|
var balance = {}; |
|
|
|
balance.totalAmount = Utils.strip(_.reduce(utxos, function(sum, utxo) { |
|
|
|
return sum + utxo.satoshis; |
|
|
|
}, 0)); |
|
|
|
|
|
|
|
balance.lockedAmount = Utils.strip(_.reduce(_.filter(utxos, { |
|
|
|
locked: true |
|
|
|
}), function(sum, utxo) { |
|
|
|
return sum + utxo.satoshis; |
|
|
|
}, 0)); |
|
|
|
var balance = self._totalizeUtxos(utxos); |
|
|
|
|
|
|
|
// Compute balance by address
|
|
|
|
var byAddress = {}; |
|
|
@ -464,33 +469,54 @@ WalletService.prototype.getBalance = function(opts, cb) { |
|
|
|
}); |
|
|
|
}; |
|
|
|
|
|
|
|
WalletService.prototype._selectTxInputs = function(txp, cb) { |
|
|
|
var self = this; |
|
|
|
|
|
|
|
WalletService.prototype._selectUtxos = function(txp, utxos) { |
|
|
|
var i = 0; |
|
|
|
var total = 0; |
|
|
|
var selected = []; |
|
|
|
var inputs = _.sortBy(utxos, 'amount'); |
|
|
|
self._getUtxos(function(err, utxos) { |
|
|
|
if (err) return cb(err); |
|
|
|
|
|
|
|
while (i < inputs.length) { |
|
|
|
selected.push(inputs[i]); |
|
|
|
total += inputs[i].satoshis; |
|
|
|
var balance = self._totalizeUtxos(utxos); |
|
|
|
|
|
|
|
if (total >= txp.amount + Bitcore.Transaction.FEE_PER_KB) { |
|
|
|
try { |
|
|
|
// Check if there are enough fees
|
|
|
|
txp.inputs = selected; |
|
|
|
var raw = txp.getRawTx(); |
|
|
|
return; |
|
|
|
} catch (ex) { |
|
|
|
if (ex.name != 'bitcore.ErrorTransactionFeeError') { |
|
|
|
throw ex.message; |
|
|
|
var txMinAmount = txp.amount + Bitcore.Transaction.FEE_PER_KB; |
|
|
|
if (balance.totalAmount < txMinAmount) |
|
|
|
return cb(new ClientError('INSUFFICIENTFUNDS', 'Insufficient funds')); |
|
|
|
|
|
|
|
if ((balance.totalAmount - balance.lockedAmount) < txMinAmount) |
|
|
|
return cb(new ClientError('LOCKEDFUNDS', 'Funds are locked by pending transaction proposals')); |
|
|
|
|
|
|
|
|
|
|
|
utxos = _.reject(utxos, { |
|
|
|
locked: true |
|
|
|
}); |
|
|
|
|
|
|
|
var i = 0; |
|
|
|
var total = 0; |
|
|
|
var selected = []; |
|
|
|
var inputs = _.sortBy(utxos, 'amount'); |
|
|
|
var bitcoreTx; |
|
|
|
|
|
|
|
while (i < inputs.length) { |
|
|
|
selected.push(inputs[i]); |
|
|
|
total += inputs[i].satoshis; |
|
|
|
|
|
|
|
if (total >= txMinAmount) { |
|
|
|
try { |
|
|
|
// Check if there are enough fees
|
|
|
|
txp.inputs = selected; |
|
|
|
bitcoreTx = txp.getBitcoreTx(); |
|
|
|
txp.inputPaths = _.pluck(txp.inputs, 'path'); |
|
|
|
return cb(); |
|
|
|
} catch (ex) { |
|
|
|
if (ex.name != 'bitcore.ErrorTransactionFeeError') { |
|
|
|
return cb(ex); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
i++; |
|
|
|
}; |
|
|
|
txp.inputs = null; |
|
|
|
return; |
|
|
|
i++; |
|
|
|
}; |
|
|
|
|
|
|
|
return cb(new ClientError('INSUFFICIENTFUNDS', 'Insufficient funds for fee')); |
|
|
|
}); |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
@ -534,36 +560,25 @@ WalletService.prototype.createTx = function(opts, cb) { |
|
|
|
if (opts.amount < Bitcore.Transaction.DUST_AMOUNT) |
|
|
|
return cb(new ClientError('DUSTAMOUNT', 'Amount below dust threshold')); |
|
|
|
|
|
|
|
self._getUtxos(function(err, utxos) { |
|
|
|
if (err) return cb(err); |
|
|
|
|
|
|
|
var changeAddress = wallet.createAddress(true); |
|
|
|
|
|
|
|
utxos = _.reject(utxos, { |
|
|
|
locked: true |
|
|
|
}); |
|
|
|
var changeAddress = wallet.createAddress(true); |
|
|
|
|
|
|
|
var txp = TxProposal.create({ |
|
|
|
creatorId: self.copayerId, |
|
|
|
toAddress: opts.toAddress, |
|
|
|
amount: opts.amount, |
|
|
|
message: opts.message, |
|
|
|
proposalSignature: opts.proposalSignature, |
|
|
|
changeAddress: changeAddress, |
|
|
|
requiredSignatures: wallet.m, |
|
|
|
requiredRejections: Math.min(wallet.m, wallet.n - wallet.m + 1), |
|
|
|
}); |
|
|
|
var txp = TxProposal.create({ |
|
|
|
creatorId: self.copayerId, |
|
|
|
toAddress: opts.toAddress, |
|
|
|
amount: opts.amount, |
|
|
|
message: opts.message, |
|
|
|
proposalSignature: opts.proposalSignature, |
|
|
|
changeAddress: changeAddress, |
|
|
|
requiredSignatures: wallet.m, |
|
|
|
requiredRejections: Math.min(wallet.m, wallet.n - wallet.m + 1), |
|
|
|
}); |
|
|
|
|
|
|
|
try { |
|
|
|
self._selectUtxos(txp, utxos); |
|
|
|
} catch (ex) { |
|
|
|
return cb(new ClientError(ex.toString())); |
|
|
|
} |
|
|
|
|
|
|
|
if (!txp.inputs) |
|
|
|
return cb(new ClientError('INSUFFICIENTFUNDS', 'Insufficient funds')); |
|
|
|
self._selectTxInputs(txp, function(err) { |
|
|
|
if (err) return cb(err); |
|
|
|
|
|
|
|
txp.inputPaths = _.pluck(txp.inputs, 'path'); |
|
|
|
$.checkState(txp.inputs); |
|
|
|
|
|
|
|
self.storage.storeAddressAndWallet(wallet, changeAddress, function(err) { |
|
|
|
if (err) return cb(err); |
|
|
|