diff --git a/src/networks.js b/src/networks.js index da71d23..fccef52 100644 --- a/src/networks.js +++ b/src/networks.js @@ -1,6 +1,7 @@ // https://en.bitcoin.it/wiki/List_of_address_prefixes // Dogecoin BIP32 is a proposed standard: https://bitcointalk.org/index.php?topic=409731 -module.exports = { + +var networks = { bitcoin: { magicPrefix: '\x18Bitcoin Signed Message:\n', bip32: { @@ -9,7 +10,10 @@ module.exports = { }, pubKeyHash: 0x00, scriptHash: 0x05, - wif: 0x80 + wif: 0x80, + dustThreshold: 546, // https://github.com/bitcoin/bitcoin/blob/v0.9.2/src/core.h#L151-L162 + feePerKb: 10000, // https://github.com/bitcoin/bitcoin/blob/v0.9.2/src/main.cpp#L53 + estimateFee: estimateFee('bitcoin') }, dogecoin: { magicPrefix: '\x19Dogecoin Signed Message:\n', @@ -19,7 +23,11 @@ module.exports = { }, pubKeyHash: 0x1e, scriptHash: 0x16, - wif: 0x9e + wif: 0x9e, + dustThreshold: 0, // https://github.com/dogecoin/dogecoin/blob/v1.7.1/src/core.h#L155-L160 + dustSoftThreshold: 100000000, // https://github.com/dogecoin/dogecoin/blob/v1.7.1/src/main.h#L62 + feePerKb: 100000000, // https://github.com/dogecoin/dogecoin/blob/v1.7.1/src/main.cpp#L58 + estimateFee: estimateFee('dogecoin') }, litecoin: { magicPrefix: '\x19Litecoin Signed Message:\n', @@ -29,7 +37,11 @@ module.exports = { }, pubKeyHash: 0x30, scriptHash: 0x05, - wif: 0xb0 + wif: 0xb0, + dustThreshold: 0, // https://github.com/litecoin-project/litecoin/blob/v0.8.7.2/src/main.cpp#L360-L365 + dustSoftThreshold: 100000, // https://github.com/litecoin-project/litecoin/blob/v0.8.7.2/src/main.h#L53 + feePerKb: 100000, // https://github.com/litecoin-project/litecoin/blob/v0.8.7.2/src/main.cpp#L56 + estimateFee: estimateFee('litecoin') }, testnet: { magicPrefix: '\x18Bitcoin Signed Message:\n', @@ -39,6 +51,30 @@ module.exports = { }, pubKeyHash: 0x6f, scriptHash: 0xc4, - wif: 0xef + wif: 0xef, + dustThreshold: 546, + feePerKb: 10000, + estimateFee: estimateFee('bitcoin') } } + +function estimateFee(type) { + return function(tx) { + var network = networks[type] + var baseFee = network.feePerKb + var byteSize = tx.toBuffer().length + + var fee = baseFee * Math.ceil(byteSize / 1000) + if(network.dustSoftThreshold == undefined) return fee + + tx.outs.forEach(function(e){ + if(e.value < network.dustSoftThreshold) { + fee += baseFee + } + }) + + return fee + } +} + +module.exports = networks diff --git a/src/wallet.js b/src/wallet.js index 1501456..a77cd02 100644 --- a/src/wallet.js +++ b/src/wallet.js @@ -20,9 +20,6 @@ function Wallet(seed, network) { this.addresses = [] this.changeAddresses = [] - // Dust value - this.dustThreshold = 5430 - // Transaction output data this.outputs = {} @@ -182,7 +179,7 @@ function Wallet(seed, network) { } this.createTx = function(to, value, fixedFee, changeAddress) { - assert(value > this.dustThreshold, value + ' must be above dust threshold (' + this.dustThreshold + ' Satoshis)') + assert(value > network.dustThreshold, value + ' must be above dust threshold (' + network.dustThreshold + ' Satoshis)') var utxos = getCandidateOutputs(value) var accum = 0 @@ -206,7 +203,7 @@ function Wallet(seed, network) { if (accum >= subTotal) { var change = accum - subTotal - if (change > this.dustThreshold) { + if (change > network.dustThreshold) { tx.addOutput(changeAddress || getChangeAddress(), change) } @@ -235,13 +232,11 @@ function Wallet(seed, network) { return sortByValueDesc } - var feePerKb = 20000 function estimateFeePadChangeOutput(tx) { var tmpTx = tx.clone() tmpTx.addOutput(getChangeAddress(), 0) - var byteSize = tmpTx.toBuffer().length - return feePerKb * Math.ceil(byteSize / 1000) + return network.estimateFee(tmpTx) } function getChangeAddress() { diff --git a/test/network.js b/test/network.js new file mode 100644 index 0000000..8662b1c --- /dev/null +++ b/test/network.js @@ -0,0 +1,93 @@ +var assert = require('assert') +var networks = require('../src/networks') +var sinon = require('sinon') +var Transaction = require('../src/transaction') + +describe('networks', function() { + var txToBuffer + before(function(){ + txToBuffer = sinon.stub(Transaction.prototype, "toBuffer") + }) + + after(function(){ + Transaction.prototype.toBuffer.restore() + }) + + describe('bitcoin', function() { + describe('estimateFee', function() { + var estimateFee = networks.bitcoin.estimateFee + + it('works at boundry', function() { + txToBuffer.returns(new Buffer(1000)) + var tx = new Transaction() + assert.equal(estimateFee(tx), 10000) + }) + + it('rounds up to the closest kb for estimation', function() { + txToBuffer.returns(new Buffer(2800)) + var tx = new Transaction() + assert.equal(estimateFee(tx), 30000) + }) + }) + }) + + describe('dogecoin', function() { + describe('estimateFee', function() { + var estimateFee = networks.dogecoin.estimateFee + + it('regular fee per kb applies when every output has value no less than DUST_SOFT_LIMIT', function() { + txToBuffer.returns(new Buffer(1000)) + var tx = new Transaction() + tx.outs[0] = { value: 100000000 } + + assert.equal(estimateFee(tx), 100000000) + }) + + it('applies additional fee on every output with value below DUST_SOFT_LIMIT', function() { + txToBuffer.returns(new Buffer(1000)) + var tx = new Transaction() + tx.outs[0] = { value: 99999999 } + tx.outs[1] = { value: 99999999 } + + assert.equal(estimateFee(tx), 3 * 100000000) + }) + + it('rounds up to the closest kb for estimation', function() { + txToBuffer.returns(new Buffer(2800)) + var tx = new Transaction() + + assert.equal(estimateFee(tx), 300000000) + }) + }) + }) + + describe('litecoin', function() { + describe('estimateFee', function() { + var estimateFee = networks.litecoin.estimateFee + + it('regular fee per kb applies when every output has value no less than DUST_SOFT_LIMIT', function() { + txToBuffer.returns(new Buffer(1000)) + var tx = new Transaction() + tx.outs[0] = { value: 100000 } + + assert.equal(estimateFee(tx), 100000) + }) + + it('applies additional fee on every output with value below DUST_SOFT_LIMIT', function() { + txToBuffer.returns(new Buffer(1000)) + var tx = new Transaction() + tx.outs[0] = { value: 99999 } + tx.outs[1] = { value: 99999 } + + assert.equal(estimateFee(tx), 3 * 100000) + }) + + it('rounds up to the closest kb for estimation', function() { + txToBuffer.returns(new Buffer(2800)) + var tx = new Transaction() + + assert.equal(estimateFee(tx), 300000) + }) + }) + }) +}) diff --git a/test/wallet.js b/test/wallet.js index ad94d03..eec0ac0 100644 --- a/test/wallet.js +++ b/test/wallet.js @@ -389,7 +389,7 @@ describe('Wallet', function() { "hash": fakeTxId(3), "outputIndex": 0, "address" : address2, - "value": 520000 // enough for value and fee + "value": 510000 // enough for value and fee } ] wallet.setUnspentOutputs(utxo) @@ -417,7 +417,7 @@ describe('Wallet', function() { }) it('allows fee to be set to zero', function(){ - value = 520000 + value = 510000 var fee = 0 var tx = wallet.createTx(to, value, fee) @@ -459,7 +459,7 @@ describe('Wallet', function() { }]) var to = 'mt7MyTVVEWnbwpF5hBn6fgnJcv95Syk2ue' - var toValue = value - 20000 + var toValue = value - 10000 var tx = wallet.createTx(to, toValue) assert.equal(tx.outs.length, 1) @@ -516,7 +516,7 @@ describe('Wallet', function() { describe('change', function(){ it('uses the last change address if there is any', function(){ - var fee = 5000 + var fee = 0 wallet.generateChangeAddress() wallet.generateChangeAddress() var tx = wallet.createTx(to, value, fee) @@ -526,11 +526,11 @@ describe('Wallet', function() { var outAddress = Address.fromOutputScript(out.script) assert.equal(outAddress.toString(), wallet.changeAddresses[1]) - assert.equal(out.value, 15000) + assert.equal(out.value, 10000) }) it('generates a change address if there is not any', function(){ - var fee = 5000 + var fee = 0 assert.equal(wallet.changeAddresses.length, 0) var tx = wallet.createTx(to, value, fee) @@ -540,7 +540,7 @@ describe('Wallet', function() { var outAddress = Address.fromOutputScript(out.script) assert.equal(outAddress.toString(), wallet.changeAddresses[0]) - assert.equal(out.value, 15000) + assert.equal(out.value, 10000) }) it('skips change if it is not above dust threshold', function(){ @@ -569,11 +569,11 @@ describe('Wallet', function() { describe('when value is below dust threshold', function(){ it('throws an error', function(){ - var value = 5430 + var value = 546 assert.throws(function() { wallet.createTx(to, value) - }, /5430 must be above dust threshold \(5430 Satoshis\)/) + }, /546 must be above dust threshold \(546 Satoshis\)/) }) }) @@ -583,7 +583,7 @@ describe('Wallet', function() { assert.throws(function() { wallet.createTx(to, value) - }, /Not enough funds \(incl. fee\): 1420000 < 1420001/) + }, /Not enough funds \(incl. fee\): 1410000 < 1410001/) }) }) })