diff --git a/lib/model/txproposal.js b/lib/model/txproposal.js index c102339..97ae8fb 100644 --- a/lib/model/txproposal.js +++ b/lib/model/txproposal.js @@ -37,6 +37,7 @@ TxProposal.create = function(opts) { x.outputOrder = _.shuffle(_.range(2)); x.fee = null; x.network = Bitcore.Address(x.toAddress).toObject().network; + x.feePerKb = opts.feePerKb; return x; }; @@ -68,6 +69,7 @@ TxProposal.fromObj = function(obj) { x.outputOrder = obj.outputOrder; x.fee = obj.fee; x.network = obj.network; + x.feePerKb = obj.feePerKb; return x; }; diff --git a/lib/server.js b/lib/server.js index cfa7903..bdc2db8 100644 --- a/lib/server.js +++ b/lib/server.js @@ -693,8 +693,6 @@ WalletService.prototype.getBalance = function(opts, cb) { WalletService.prototype._selectTxInputs = function(txp, cb) { var self = this; - Bitcore.Transaction.FEE_SECURITY_MARGIN = 1; - self._getUtxos(function(err, utxos) { if (err) return cb(err); @@ -730,6 +728,7 @@ WalletService.prototype._selectTxInputs = function(txp, cb) { if (!bitcoreError) { txp.inputPaths = _.pluck(txp.inputs, 'path'); txp.fee = bitcoreTx.getFee(); + $.checkState(txp.fee < 1e8, 'Fees are too high!'); return cb(); } } catch (ex) { @@ -787,7 +786,8 @@ WalletService.prototype._canCreateTx = function(copayerId, cb) { * @param {number} opts.amount - Amount to transfer in satoshi. * @param {string} opts.message - A message to attach to this transaction. * @param {string} opts.proposalSignature - S(toAddress|amount|message|payProUrl). Used by other copayers to verify the proposal. - * @param {string} opts.payProUrl - Options: Paypro URL for peers to verify TX + * @param {string} opts.feePerKb - Optional: Use an alternative fee per KB for this TX + * @param {string} opts.payProUrl - Optional: Paypro URL for peers to verify TX * @returns {TxProposal} Transaction proposal. */ WalletService.prototype.createTx = function(opts, cb) { @@ -796,6 +796,10 @@ WalletService.prototype.createTx = function(opts, cb) { if (!Utils.checkRequired(opts, ['toAddress', 'amount', 'proposalSignature'])) return cb(new ClientError('Required argument missing')); + var feePerKb = opts.feePerKb || 10000; + if (!_.contains([1000, 5000, 10000], feePerKb)) + return cb(new ClientError('Invalid fee per KB value')); + self._runLocked(cb, function(cb) { self.getWallet({}, function(err, wallet) { if (err) return cb(err); @@ -827,7 +831,6 @@ WalletService.prototype.createTx = function(opts, cb) { if (opts.amount < Bitcore.Transaction.DUST_AMOUNT) return cb(new ClientError('DUSTAMOUNT', 'Amount below dust threshold')); - var changeAddress = wallet.createAddress(true); var txp = Model.TxProposal.create({ @@ -837,6 +840,7 @@ WalletService.prototype.createTx = function(opts, cb) { amount: opts.amount, message: opts.message, proposalSignature: opts.proposalSignature, + feePerKb: feePerKb, payProUrl: opts.payProUrl, changeAddress: changeAddress, requiredSignatures: wallet.m, diff --git a/test/integration/server.js b/test/integration/server.js index c17f4f4..005029f 100644 --- a/test/integration/server.js +++ b/test/integration/server.js @@ -184,13 +184,15 @@ helpers.stubAddressActivity = function(activeAddresses) { helpers.clientSign = WalletUtils.signTxp; -helpers.createProposalOpts = function(toAddress, amount, message, signingKey) { +helpers.createProposalOpts = function(toAddress, amount, message, signingKey, feePerKb) { var opts = { toAddress: toAddress, amount: helpers.toSatoshi(amount), message: message, proposalSignature: null, }; + if (feePerKb) opts.feePerKb = feePerKb; + var hash = WalletUtils.getProposalHash(opts.toAddress, opts.amount, opts.message); try { opts.proposalSignature = WalletUtils.signMessage(hash, signingKey); @@ -1431,6 +1433,30 @@ describe('Wallet service', function() { }); }); + it('should be possible to use a smaller fee', function(done) { + helpers.stubUtxos(server, wallet, 1, function() { + var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.99995, null, TestData.copayers[0].privKey_1H_0); + server.createTx(txOpts, function(err, tx) { + should.exist(err); + err.code.should.equal('INSUFFICIENTFUNDS'); + var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.99995, null, TestData.copayers[0].privKey_1H_0, 5000); + server.createTx(txOpts, function(err, tx) { + should.not.exist(err); + tx.fee.should.equal(5000); + var signatures = helpers.clientSign(tx, TestData.copayers[0].xPrivKey); + // Sign it to make sure Bitcore doesn't complain about the fees + server.signTx({ + txProposalId: tx.id, + signatures: signatures, + }, function(err) { + should.not.exist(err); + done(); + }); + }); + }); + }); + }); + it('should fail to create tx for dust amount', function(done) { helpers.stubUtxos(server, wallet, [1], function() { var txOpts = helpers.createProposalOpts('18PzpUFkFZE8zKWUPvfykkTxmB9oMR8qP7', 0.00000001, null, TestData.copayers[0].privKey_1H_0); @@ -1445,7 +1471,7 @@ describe('Wallet service', function() { it('should fail to create tx that would return change for dust amount', function(done) { helpers.stubUtxos(server, wallet, [1], function() { - var fee = Bitcore.Transaction.FEE_PER_KB / 1e8; + var fee = 10000 / 1e8; var change = 0.00000001; var amount = 1 - fee - change;