diff --git a/lib/transaction/transaction.js b/lib/transaction/transaction.js index 232dafe..6bf1c27 100644 --- a/lib/transaction/transaction.js +++ b/lib/transaction/transaction.js @@ -205,7 +205,7 @@ Transaction.prototype.getSerializationError = function(opts) { unspentError = this._hasFeeError(opts, unspent); } - return unspentError || + return unspentError || this._hasDustOutputs(opts) || this._isMissingSignatures(opts); }; @@ -394,7 +394,7 @@ Transaction.prototype._checkConsistency = function() { $.checkState(this._changeScript); $.checkState(this.outputs[this._changeIndex]); $.checkState(this.outputs[this._changeIndex].script.toString() === - this._changeScript.toString()); + this._changeScript.toString()); } // TODO: add other checks }; @@ -634,6 +634,21 @@ Transaction.prototype.fee = function(amount) { return this; }; +/** + * Manually set the fee per KB for this transaction. Beware that this resets all the signatures + * for inputs (in further versions, SIGHASH_SINGLE or SIGHASH_NONE signatures will not + * be reset). + * + * @param {number} amount satoshis per KB to be sent + * @return {Transaction} this, for chaining + */ +Transaction.prototype.feePerKb = function(amount) { + $.checkArgument(_.isNumber(amount), 'amount must be a number'); + this._feePerKb = amount; + this._updateChangeOutput(); + return this; +}; + /* Output management */ /** @@ -831,7 +846,7 @@ Transaction.prototype.getFee = function() { Transaction.prototype._estimateFee = function() { var estimatedSize = this._estimateSize(); var available = this._getUnspentValue(); - return Transaction._estimateFee(estimatedSize, available); + return Transaction._estimateFee(estimatedSize, available, this._feePerKb); }; Transaction.prototype._getUnspentValue = function() { @@ -844,12 +859,12 @@ Transaction.prototype._clearSignatures = function() { }); }; -Transaction._estimateFee = function(size, amountAvailable) { - var fee = Math.ceil(size / Transaction.FEE_PER_KB); +Transaction._estimateFee = function(size, amountAvailable, feePerKb) { + var fee = Math.ceil(size / 1000) * (feePerKb || Transaction.FEE_PER_KB); if (amountAvailable > fee) { size += Transaction.CHANGE_OUTPUT_MAX_SIZE; } - return Math.ceil(size / 1000) * Transaction.FEE_PER_KB; + return Math.ceil(size / 1000) * (feePerKb || Transaction.FEE_PER_KB); }; Transaction.prototype._estimateSize = function() { diff --git a/test/transaction/transaction.js b/test/transaction/transaction.js index 77ae014..8a783a8 100644 --- a/test/transaction/transaction.js +++ b/test/transaction/transaction.js @@ -257,6 +257,22 @@ describe('Transaction', function() { transaction.outputs.length.should.equal(2); transaction.outputs[1].satoshis.should.equal(10000); }); + it('fee per kb can be set up manually', function() { + var inputs = _.map(_.range(10), function(i) { + var utxo = _.clone(simpleUtxoWith100000Satoshis); + utxo.outputIndex = i; + return utxo; + }); + var transaction = new Transaction() + .from(inputs) + .to(toAddress, 950000) + .feePerKb(8000) + .change(changeAddress) + .sign(privateKey); + transaction._estimateSize().should.be.within(1000, 1999); + transaction.outputs.length.should.equal(2); + transaction.outputs[1].satoshis.should.equal(34000); + }); it('if satoshis are invalid', function() { var transaction = new Transaction() .from(simpleUtxoWith100000Satoshis) @@ -406,7 +422,9 @@ describe('Transaction', function() { .fee(10000000); expect(function() { - return transaction.serialize({disableMoreOutputThanInput: true}); + return transaction.serialize({ + disableMoreOutputThanInput: true + }); }).to.throw(errors.Transaction.FeeError.TooLarge); }); describe('skipping checks', function() {