Skip to content
This repository has been archived by the owner on Apr 3, 2019. It is now read-only.

Commit

Permalink
Merge pull request #12 from sanchaz/add_fee_per_byte
Browse files Browse the repository at this point in the history
Add feePerByte for fee calculation options
  • Loading branch information
matiu authored Nov 30, 2018
2 parents 9cb4217 + 66aedc2 commit df618e7
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 3 deletions.
36 changes: 34 additions & 2 deletions lib/transaction/transaction.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ Transaction.NLOCKTIME_MAX_VALUE = 4294967295;
// Value used for fee estimation (satoshis per kilobyte)
Transaction.FEE_PER_KB = 100000;

// Value used for fee estimation (satoshis per byte)
Transaction.FEE_PER_BYTE = 1;

// Safe upper bound for change address script size in bytes
Transaction.CHANGE_OUTPUT_MAX_SIZE = 20 + 4 + 34 + 4;
Transaction.MAXIMUM_EXTRA_SIZE = 4 + 9 + 9 + 4;
Expand Down Expand Up @@ -665,6 +668,7 @@ Transaction.prototype.fee = function(amount) {
* 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).
* Takes priority over fee per Byte, for backwards compatibility
*
* @param {number} amount satoshis per KB to be sent
* @return {Transaction} this, for chaining
Expand All @@ -676,6 +680,22 @@ Transaction.prototype.feePerKb = function(amount) {
return this;
};

/**
* Manually set the fee per Byte 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).
* fee per Byte will be ignored if fee per KB is set
*
* @param {number} amount satoshis per Byte to be sent
* @return {Transaction} this, for chaining
*/
Transaction.prototype.feePerByte = function(amount) {
$.checkArgument(_.isNumber(amount), 'amount must be a number');
this._feePerByte = amount;
this._updateChangeOutput();
return this;
};

/* Output management */

/**
Expand Down Expand Up @@ -888,7 +908,11 @@ Transaction.prototype.getFee = function() {
Transaction.prototype._estimateFee = function() {
var estimatedSize = this._estimateSize();
var available = this._getUnspentValue();
return Transaction._estimateFee(estimatedSize, available, this._feePerKb);
if (this._feePerByte && !this._feePerKb) {
return Transaction._estimateFeePerByte(estimatedSize, available, this._feePerByte);
} else {
return Transaction._estimateFeePerKb(estimatedSize, available, this._feePerKb);
}
};

Transaction.prototype._getUnspentValue = function() {
Expand All @@ -901,14 +925,22 @@ Transaction.prototype._clearSignatures = function() {
});
};

Transaction._estimateFee = function(size, amountAvailable, feePerKb) {
Transaction._estimateFeePerKb = 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) * (feePerKb || Transaction.FEE_PER_KB);
};

Transaction._estimateFeePerByte = function(size, amountAvailable, feePerByte) {
var fee = size * (feePerByte || Transaction.FEE_PER_BYTE);
if (amountAvailable > fee) {
size += Transaction.CHANGE_OUTPUT_MAX_SIZE;
}
return size * (feePerByte || Transaction.FEE_PER_BYTE);
};

Transaction.prototype._estimateSize = function() {
var result = Transaction.MAXIMUM_EXTRA_SIZE;
_.each(this.inputs, function(input) {
Expand Down
65 changes: 64 additions & 1 deletion test/transaction/transaction.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ describe('Transaction', function() {
});

it('should parse the version as a signed integer', function() {
var transaction = Transaction('ffffffff0000ffffffff')
var transaction = Transaction('ffffffff0000ffffffff');
transaction.version.should.equal(-1);
transaction.nLockTime.should.equal(0xffffffff);
});
Expand Down Expand Up @@ -390,6 +390,69 @@ describe('Transaction', function() {
transaction.outputs.length.should.equal(2);
transaction.outputs[1].satoshis.should.equal(34000);
});
it('fee per byte (low fee) 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)
.feePerByte(1)
.change(changeAddress)
.sign(privateKey);
transaction._estimateSize().should.be.within(1000, 1999);
transaction.outputs.length.should.equal(2);
transaction.outputs[1].satoshis.should.be.within(48001, 49000);
});
it('fee per byte (high fee) 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)
.feePerByte(2)
.change(changeAddress)
.sign(privateKey);
transaction._estimateSize().should.be.within(1000, 1999);
transaction.outputs.length.should.equal(2);
transaction.outputs[1].satoshis.should.be.within(46002, 48000);
});
it('fee per byte 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)
.feePerByte(13)
.change(changeAddress)
.sign(privateKey);
transaction._estimateSize().should.be.within(1000, 1999);
transaction.outputs.length.should.equal(2);
transaction.outputs[1].satoshis.should.be.within(24013, 37000);
});
it('fee per byte not enough for change', 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, 999999)
.feePerByte(1)
.change(changeAddress)
.sign(privateKey);
transaction._estimateSize().should.be.within(1000, 1999);
transaction.outputs.length.should.equal(1);
});
it('if satoshis are invalid', function() {
var transaction = new Transaction()
.from(simpleUtxoWith100000Satoshis)
Expand Down

0 comments on commit df618e7

Please sign in to comment.