commit
9e2e0bd01a
4 changed files with 147 additions and 23 deletions
|
@ -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
|
||||
|
|
|
@ -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() {
|
||||
|
|
93
test/network.js
Normal file
93
test/network.js
Normal file
|
@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
|
@ -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/)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Reference in a new issue