diff --git a/src/wallet.js b/src/wallet.js index a77cd02..65651f0 100644 --- a/src/wallet.js +++ b/src/wallet.js @@ -77,20 +77,21 @@ function Wallet(seed, network) { utxo.forEach(function(uo){ validateUnspentOutput(uo) var o = unspentOutputToOutput(uo) - outputs[o.receive] = o + outputs[o.from] = o }) this.outputs = outputs } function outputToUnspentOutput(output){ - var hashAndIndex = output.receive.split(":") + var hashAndIndex = output.from.split(":") return { hash: hashAndIndex[0], outputIndex: parseInt(hashAndIndex[1]), address: output.address, - value: output.value + value: output.value, + pending: output.pending } } @@ -98,7 +99,7 @@ function Wallet(seed, network) { var hash = o.hash var key = hash + ":" + o.outputIndex return { - receive: key, + from: key, address: o.address, value: o.value, pending: o.pending @@ -158,7 +159,7 @@ function Wallet(seed, network) { var output = txid + ':' + i me.outputs[output] = { - receive: output, + from: output, value: txOut.value, address: address, pending: isPending @@ -174,7 +175,13 @@ function Wallet(seed, network) { var output = txinId + ':' + txIn.index - if(me.outputs[output]) delete me.outputs[output] + if (!(output in me.outputs)) return + + if (isPending) { + return me.outputs[output].pending = true + } + + delete me.outputs[output] }) } @@ -193,7 +200,7 @@ function Wallet(seed, network) { var utxo = utxos[i] addresses.push(utxo.address) - var outpoint = utxo.receive.split(':') + var outpoint = utxo.from.split(':') tx.addInput(outpoint[0], parseInt(outpoint[1])) var fee = fixedFee == undefined ? estimateFeePadChangeOutput(tx) : fixedFee @@ -234,7 +241,7 @@ function Wallet(seed, network) { function estimateFeePadChangeOutput(tx) { var tmpTx = tx.clone() - tmpTx.addOutput(getChangeAddress(), 0) + tmpTx.addOutput(getChangeAddress(), network.dustSoftThreshold || 0) return network.estimateFee(tmpTx) } diff --git a/test/wallet.js b/test/wallet.js index eec0ac0..456838e 100644 --- a/test/wallet.js +++ b/test/wallet.js @@ -175,7 +175,8 @@ describe('Wallet', function() { "hash":"6a4062273ac4f9ea4ffca52d9fd102b08f6c32faa0a4d1318e3a7b2e437bb9c7", "outputIndex": 0, "address" : "1AZpKpcfCzKDUeTFBQUL4MokQai3m3HMXv", - "value": 20000 + "value": 20000, + "pending": true } expectedOutputKey = expectedUtxo.hash + ":" + expectedUtxo.outputIndex }) @@ -183,9 +184,10 @@ describe('Wallet', function() { function addUtxoToOutput(utxo){ var key = utxo.hash + ":" + utxo.outputIndex wallet.outputs[key] = { - receive: key, + from: key, address: utxo.address, - value: utxo.value + value: utxo.value, + pending: utxo.pending } } @@ -262,12 +264,35 @@ describe('Wallet', function() { }) describe("processPendingTx", function(){ - it("sets the pending flag on output", function(){ + it("incoming: sets the pending flag on output", function(){ wallet.addresses = [addresses[0]] wallet.processPendingTx(tx) verifyOutputAdded(0, true) }) + + describe("when tx ins outpoint contains a known txhash:i", function(){ + var spendTx + beforeEach(function(){ + wallet.addresses = [addresses[0]] + wallet.processConfirmedTx(tx) + + spendTx = Transaction.fromHex(fixtureTx2Hex) + }) + + it("outgoing: sets the pending flag on output instead of deleting it", function(){ + var txIn = spendTx.ins[0] + var txInId = new Buffer(txIn.hash) + Array.prototype.reverse.call(txInId) + txInId = txInId.toString('hex') + + var key = txInId + ':' + txIn.index + assert(!wallet.outputs[key].pending) + + wallet.processPendingTx(spendTx) + assert(wallet.outputs[key].pending) + }) + }) }) describe('processConfirmedTx', function(){ @@ -350,7 +375,7 @@ describe('Wallet', function() { var txOut = tx.outs[index] var key = tx.getId() + ":" + index var output = wallet.outputs[key] - assert.equal(output.receive, key) + assert.equal(output.from, key) assert.equal(output.value, txOut.value) assert.equal(output.pending, pending) @@ -395,25 +420,12 @@ describe('Wallet', function() { wallet.setUnspentOutputs(utxo) }) - describe('choosing utxo', function(){ - it('calculates fees', function(){ - var tx = wallet.createTx(to, value) - - assert.equal(tx.ins.length, 1) - assert.deepEqual(tx.ins[0].hash, fakeTxHash(3)) - assert.equal(tx.ins[0].index, 0) - }) - + describe('transaction fee', function(){ it('allows fee to be specified', function(){ var fee = 30000 var tx = wallet.createTx(to, value, fee) - assert.equal(tx.ins.length, 2) - - assert.deepEqual(tx.ins[0].hash, fakeTxHash(3)) - assert.equal(tx.ins[0].index, 0) - assert.deepEqual(tx.ins[1].hash, fakeTxHash(2)) - assert.equal(tx.ins[1].index, 1) + assert.equal(getFee(wallet, tx), fee) }) it('allows fee to be set to zero', function(){ @@ -421,6 +433,41 @@ describe('Wallet', function() { var fee = 0 var tx = wallet.createTx(to, value, fee) + assert.equal(getFee(wallet, tx), fee) + }) + + it('does not overestimate fees when network has dustSoftThreshold', function(){ + var wallet = new Wallet(seed, networks.litecoin) + var address = wallet.generateAddress() + wallet.setUnspentOutputs([{ + hash: fakeTxId(0), + outputIndex: 0, + address: address, + value: 500000 + }]) + + value = 200000 + var tx = wallet.createTx(address, value) + + assert.equal(getFee(wallet, tx), 100000) + }) + + function getFee(wallet, tx) { + var inputValue = tx.ins.reduce(function(memo, input){ + var id = Array.prototype.reverse.call(input.hash).toString('hex') + return memo + wallet.outputs[id + ':' + input.index].value + }, 0) + + return tx.outs.reduce(function(memo, output){ + return memo - output.value + }, inputValue) + } + }) + + describe('choosing utxo', function(){ + it('takes fees into account', function(){ + var tx = wallet.createTx(to, value) + assert.equal(tx.ins.length, 1) assert.deepEqual(tx.ins[0].hash, fakeTxHash(3)) assert.equal(tx.ins[0].index, 0) @@ -446,7 +493,7 @@ describe('Wallet', function() { }) }) - describe(networks.testnet, function(){ + describe('works for testnet', function(){ it('should create transaction', function(){ var wallet = new Wallet(seed, networks.testnet) var address = wallet.generateAddress()