diff --git a/src/wallet.js b/src/wallet.js index c056c9531..6e2661204 100644 --- a/src/wallet.js +++ b/src/wallet.js @@ -5,32 +5,35 @@ var networks = require('./networks') var Address = require('./address') var HDNode = require('./hdnode') var Transaction = require('./transaction') +var Script = require('./script') -function Wallet(seed, network) { +function Wallet(seed, network, unspents) { + seed = seed || crypto.randomBytes(32) network = network || networks.bitcoin // Stored in a closure to make accidental serialization less likely - var masterkey = null - var me = this - var accountZero = null - var internalAccount = null - var externalAccount = null + var masterKey = HDNode.fromSeedBuffer(seed, network) + + // HD first-level child derivation method should be hardened + // See https://bitcointalk.org/index.php?topic=405179.msg4415254#msg4415254 + var accountZero = masterKey.deriveHardened(0) + var externalAccount = accountZero.derive(0) + var internalAccount = accountZero.derive(1) - // Addresses this.addresses = [] this.changeAddresses = [] + this.network = network + this.outputs = unspents ? processUnspentOutputs(unspents) : {} - // Transaction output data - this.outputs = {} - - // Make a new master key + // FIXME: remove in 2.x.y + var me = this this.newMasterKey = function(seed) { + console.warn('newMasterKey is deprecated, please make a new Wallet instance instead') + seed = seed || crypto.randomBytes(32) - masterkey = HDNode.fromSeedBuffer(seed, network) + masterKey = HDNode.fromSeedBuffer(seed, network) - // HD first-level child derivation method should be hardened - // See https://bitcointalk.org/index.php?topic=405179.msg4415254#msg4415254 - accountZero = masterkey.deriveHardened(0) + accountZero = masterKey.deriveHardened(0) externalAccount = accountZero.derive(0) internalAccount = accountZero.derive(1) @@ -40,265 +43,262 @@ function Wallet(seed, network) { me.outputs = {} } - this.newMasterKey(seed) + this.getMasterKey = function() { return masterKey } + this.getAccountZero = function() { return accountZero } + this.getExternalAccount = function() { return externalAccount } + this.getInternalAccount = function() { return internalAccount } +} - this.generateAddress = function() { - var key = externalAccount.derive(this.addresses.length) - this.addresses.push(key.getAddress().toString()) - return this.addresses[this.addresses.length - 1] - } +Wallet.prototype.createTx = function(to, value, fixedFee, changeAddress) { + assert(value > this.network.dustThreshold, value + ' must be above dust threshold (' + this.network.dustThreshold + ' Satoshis)') - this.generateChangeAddress = function() { - var key = internalAccount.derive(this.changeAddresses.length) - this.changeAddresses.push(key.getAddress().toString()) - return this.changeAddresses[this.changeAddresses.length - 1] - } + var utxos = getCandidateOutputs(this.outputs, value) + var accum = 0 + var subTotal = value + var addresses = [] - this.getBalance = function() { - return this.getUnspentOutputs().reduce(function(memo, output){ - return memo + output.value - }, 0) - } + var tx = new Transaction() + tx.addOutput(to, value) - this.getUnspentOutputs = function() { - var utxo = [] + for (var i = 0; i < utxos.length; ++i) { + var utxo = utxos[i] + addresses.push(utxo.address) - for(var key in this.outputs){ - var output = this.outputs[key] - if(!output.to) utxo.push(outputToUnspentOutput(output)) - } + var outpoint = utxo.from.split(':') + tx.addInput(outpoint[0], parseInt(outpoint[1])) - return utxo - } + var fee = fixedFee == undefined ? estimatePaddedFee(tx, this.network) : fixedFee - this.setUnspentOutputs = function(utxo) { - var outputs = {} + accum += utxo.value + subTotal = value + fee + if (accum >= subTotal) { + var change = accum - subTotal - utxo.forEach(function(uo){ - validateUnspentOutput(uo) - var o = unspentOutputToOutput(uo) - outputs[o.from] = o - }) + if (change > this.network.dustThreshold) { + tx.addOutput(changeAddress || this.getChangeAddress(), change) + } - this.outputs = outputs + break + } } - function outputToUnspentOutput(output){ - var hashAndIndex = output.from.split(":") + assert(accum >= subTotal, 'Not enough funds (incl. fee): ' + accum + ' < ' + subTotal) - return { - hash: hashAndIndex[0], - outputIndex: parseInt(hashAndIndex[1]), - address: output.address, - value: output.value, - pending: output.pending - } - } + this.signWith(tx, addresses) + return tx +} - function unspentOutputToOutput(o) { - var hash = o.hash - var key = hash + ":" + o.outputIndex - return { - from: key, - address: o.address, - value: o.value, - pending: o.pending - } - } +Wallet.prototype.processPendingTx = function(tx){ + processTx.bind(this)(tx, true) +} - function validateUnspentOutput(uo) { - var missingField +Wallet.prototype.processConfirmedTx = function(tx){ + processTx.bind(this)(tx, false) +} - if (isNullOrUndefined(uo.hash)) { - missingField = "hash" - } +Wallet.prototype.generateAddress = function() { + var k = this.addresses.length + var address = this.getExternalAccount().derive(k).getAddress() - var requiredKeys = ['outputIndex', 'address', 'value'] - requiredKeys.forEach(function (key) { - if (isNullOrUndefined(uo[key])){ - missingField = key - } - }) - - if (missingField) { - var message = [ - 'Invalid unspent output: key', missingField, 'is missing.', - 'A valid unspent output must contain' - ] - message.push(requiredKeys.join(', ')) - message.push("and hash") - throw new Error(message.join(' ')) - } - } + this.addresses.push(address.toString()) - function isNullOrUndefined(value) { - return value == undefined - } + return this.getReceiveAddress() +} - this.processPendingTx = function(tx){ - processTx(tx, true) - } +Wallet.prototype.generateChangeAddress = function() { + var k = this.changeAddresses.length + var address = this.getInternalAccount().derive(k).getAddress() + + this.changeAddresses.push(address.toString()) - this.processConfirmedTx = function(tx){ - processTx(tx, false) + return this.getChangeAddress() +} + +Wallet.prototype.getBalance = function() { + return this.getUnspentOutputs().reduce(function(accum, output) { + return accum + output.value + }, 0) +} + +Wallet.prototype.getChangeAddress = function() { + if (this.changeAddresses.length === 0) { + this.generateChangeAddress() } - function processTx(tx, isPending) { - var txid = tx.getId() + return this.changeAddresses[this.changeAddresses.length - 1] +} - tx.outs.forEach(function(txOut, i) { - var address +Wallet.prototype.getInternalPrivateKey = function(index) { + return this.getInternalAccount().derive(index).privKey +} - try { - address = Address.fromOutputScript(txOut.script, network).toString() - } catch(e) { - if (!(e.message.match(/has no matching Address/))) throw e - } +Wallet.prototype.getPrivateKey = function(index) { + return this.getExternalAccount().derive(index).privKey +} - if (isMyAddress(address)) { - var output = txid + ':' + i +Wallet.prototype.getPrivateKeyForAddress = function(address) { + if (includeAddress(this.addresses, address)) { + var index = this.addresses.indexOf(address) - me.outputs[output] = { - from: output, - value: txOut.value, - address: address, - pending: isPending - } - } - }) + return this.getPrivateKey(index) + } - tx.ins.forEach(function(txIn, i) { - // copy and convert to big-endian hex - var txinId = new Buffer(txIn.hash) - Array.prototype.reverse.call(txinId) - txinId = txinId.toString('hex') + if (includeAddress(this.changeAddresses, address)) { + var index = this.changeAddresses.indexOf(address) - var output = txinId + ':' + txIn.index + return this.getInternalPrivateKey(index) + } - if (!(output in me.outputs)) return + assert(false, 'Unknown address. Make sure the address is from the keychain and has been generated') +} - if (isPending) { - me.outputs[output].to = txid + ':' + i - me.outputs[output].pending = true - } else { - delete me.outputs[output] - } - }) +Wallet.prototype.getReceiveAddress = function() { + if (this.addresses.length === 0) { + this.generateAddress() } - this.createTx = function(to, value, fixedFee, changeAddress) { - assert(value > network.dustThreshold, value + ' must be above dust threshold (' + network.dustThreshold + ' Satoshis)') + return this.addresses[this.addresses.length - 1] +} - var utxos = getCandidateOutputs(value) - var accum = 0 - var subTotal = value - var addresses = [] +Wallet.prototype.getUnspentOutputs = function() { + var utxo = [] - var tx = new Transaction() - tx.addOutput(to, value) + for(var key in this.outputs){ + var output = this.outputs[key] + if(!output.to) utxo.push(outputToUnspentOutput(output)) + } - for (var i = 0; i < utxos.length; ++i) { - var utxo = utxos[i] - addresses.push(utxo.address) + return utxo +} - var outpoint = utxo.from.split(':') - tx.addInput(outpoint[0], parseInt(outpoint[1])) +Wallet.prototype.setUnspentOutputs = function(utxo) { + console.warn('setUnspentOutputs is deprecated, please use the constructor option instead') - var fee = fixedFee == undefined ? estimateFeePadChangeOutput(tx) : fixedFee + this.outputs = processUnspentOutputs(utxo) +} - accum += utxo.value - subTotal = value + fee - if (accum >= subTotal) { - var change = accum - subTotal +Wallet.prototype.signWith = function(tx, addresses) { + assert.equal(tx.ins.length, addresses.length, 'Number of addresses must match number of transaction inputs') - if (change > network.dustThreshold) { - tx.addOutput(changeAddress || getChangeAddress(), change) - } + addresses.forEach(function(address, i) { + var key = this.getPrivateKeyForAddress(address) - break - } - } + tx.sign(i, key) + }, this) + + return tx +} - assert(accum >= subTotal, 'Not enough funds (incl. fee): ' + accum + ' < ' + subTotal) +function outputToUnspentOutput(output){ + var hashAndIndex = output.from.split(":") - this.signWith(tx, addresses) - return tx + return { + hash: hashAndIndex[0], + index: parseInt(hashAndIndex[1]), + address: output.address, + value: output.value, + pending: output.pending } +} - function getCandidateOutputs() { - var unspent = [] +function estimatePaddedFee(tx, network) { + var tmpTx = tx.clone() + tmpTx.addOutput(Script.EMPTY, network.dustSoftThreshold || 0) - for (var key in me.outputs) { - var output = me.outputs[key] - if (!output.pending) unspent.push(output) - } + return network.estimateFee(tmpTx) +} - var sortByValueDesc = unspent.sort(function(o1, o2){ - return o2.value - o1.value - }) +function processUnspentOutputs(utxos) { + var outputs = {} - return sortByValueDesc - } + utxos.forEach(function(utxo){ + var hash = new Buffer(utxo.hash, 'hex') + var index = utxo.index + var address = utxo.address + var value = utxo.value - function estimateFeePadChangeOutput(tx) { - var tmpTx = tx.clone() - tmpTx.addOutput(getChangeAddress(), network.dustSoftThreshold || 0) + // FIXME: remove alternative in 2.x.y + if (index === undefined) index = utxo.outputIndex - return network.estimateFee(tmpTx) - } + assert.equal(hash.length, 32, 'Expected hash length of 32, got ' + hash.length) + assert.equal(typeof index, 'number', 'Expected number index, got ' + index) + assert.doesNotThrow(function() { Address.fromBase58Check(address) }, 'Expected Base58 Address, got ' + address) + assert.equal(typeof value, 'number', 'Expected number value, got ' + value) - function getChangeAddress() { - if(me.changeAddresses.length === 0) me.generateChangeAddress(); - return me.changeAddresses[me.changeAddresses.length - 1] - } + var key = utxo.hash + ':' + utxo.index - this.signWith = function(tx, addresses) { - assert.equal(tx.ins.length, addresses.length, 'Number of addresses must match number of transaction inputs') + outputs[key] = { + from: key, + address: address, + value: value, + pending: utxo.pending + } + }) - addresses.forEach(function(address, i) { - var key = me.getPrivateKeyForAddress(address) + return outputs +} - tx.sign(i, key) - }) +function getCandidateOutputs(outputs/*, value*/) { + var unspent = [] - return tx + for (var key in outputs) { + var output = outputs[key] + if (!output.pending) unspent.push(output) } - this.getMasterKey = function() { return masterkey } - this.getAccountZero = function() { return accountZero } - this.getInternalAccount = function() { return internalAccount } - this.getExternalAccount = function() { return externalAccount } + var sortByValueDesc = unspent.sort(function(o1, o2){ + return o2.value - o1.value + }) - this.getPrivateKey = function(index) { - return externalAccount.derive(index).privKey - } + return sortByValueDesc +} - this.getInternalPrivateKey = function(index) { - return internalAccount.derive(index).privKey - } +function processTx(tx, isPending) { + var txid = tx.getId() - this.getPrivateKeyForAddress = function(address) { - var index - if((index = this.addresses.indexOf(address)) > -1) { - return this.getPrivateKey(index) - } else if((index = this.changeAddresses.indexOf(address)) > -1) { - return this.getInternalPrivateKey(index) - } else { - throw new Error('Unknown address. Make sure the address is from the keychain and has been generated.') + tx.outs.forEach(function(txOut, i) { + var address + + try { + address = Address.fromOutputScript(txOut.script, this.network).toString() + } catch(e) { + if (!(e.message.match(/has no matching Address/))) throw e } - } - function isReceiveAddress(address){ - return me.addresses.indexOf(address) > -1 - } + var myAddresses = this.addresses.concat(this.changeAddresses) + if (includeAddress(myAddresses, address)) { + var output = txid + ':' + i - function isChangeAddress(address){ - return me.changeAddresses.indexOf(address) > -1 - } + this.outputs[output] = { + from: output, + value: txOut.value, + address: address, + pending: isPending + } + } + }, this) - function isMyAddress(address) { - return isReceiveAddress(address) || isChangeAddress(address) - } + tx.ins.forEach(function(txIn, i) { + // copy and convert to big-endian hex + var txinId = new Buffer(txIn.hash) + Array.prototype.reverse.call(txinId) + txinId = txinId.toString('hex') + + var output = txinId + ':' + txIn.index + + if (!(output in this.outputs)) return + + if (isPending) { + this.outputs[output].to = txid + ':' + i + this.outputs[output].pending = true + } else { + delete this.outputs[output] + } + }, this) +} + +function includeAddress(addresses, address) { + return addresses.indexOf(address) > -1 } module.exports = Wallet diff --git a/test/wallet.js b/test/wallet.js index 80fd7a8a8..c08ae2fe8 100644 --- a/test/wallet.js +++ b/test/wallet.js @@ -26,13 +26,17 @@ function fakeTxId(i) { } describe('Wallet', function() { - var seed, wallet + var seed beforeEach(function(){ seed = crypto.sha256("don't use a string seed like this in real life") - wallet = new Wallet(seed) }) describe('constructor', function() { + var wallet + beforeEach(function(){ + wallet = new Wallet(seed) + }) + it('defaults to Bitcoin network', function() { assert.equal(wallet.getMasterKey().network, networks.bitcoin) }) @@ -116,6 +120,11 @@ describe('Wallet', function() { }) describe('generateChangeAddress', function(){ + var wallet + beforeEach(function(){ + wallet = new Wallet(seed) + }) + it('generates change addresses', function(){ var wallet = new Wallet(seed, networks.testnet) var expectedAddresses = ["mnXiDR4MKsFxcKJEZjx4353oXvo55iuptn"] @@ -126,6 +135,11 @@ describe('Wallet', function() { }) describe('getPrivateKey', function(){ + var wallet + beforeEach(function(){ + wallet = new Wallet(seed) + }) + it('returns the private key at the given index of external account', function(){ var wallet = new Wallet(seed, networks.testnet) @@ -135,6 +149,11 @@ describe('Wallet', function() { }) describe('getInternalPrivateKey', function(){ + var wallet + beforeEach(function(){ + wallet = new Wallet(seed) + }) + it('returns the private key at the given index of internal account', function(){ var wallet = new Wallet(seed, networks.testnet) @@ -144,6 +163,11 @@ describe('Wallet', function() { }) describe('getPrivateKeyForAddress', function(){ + var wallet + beforeEach(function(){ + wallet = new Wallet(seed) + }) + it('returns the private key for the given address', function(){ var wallet = new Wallet(seed, networks.testnet) wallet.generateChangeAddress() @@ -162,58 +186,63 @@ describe('Wallet', function() { it('raises an error when address is not found', function(){ var wallet = new Wallet(seed, networks.testnet) + assert.throws(function() { wallet.getPrivateKeyForAddress("n2fiWrHqD6GM5GiEqkbWAc6aaZQp3ba93X") - }, /Unknown address. Make sure the address is from the keychain and has been generated./) + }, /Unknown address. Make sure the address is from the keychain and has been generated/) }) }) describe('Unspent Outputs', function(){ - var expectedUtxo, expectedOutputKey + var utxo, expectedOutputKey + var wallet + beforeEach(function(){ - expectedUtxo = { - "hash":"6a4062273ac4f9ea4ffca52d9fd102b08f6c32faa0a4d1318e3a7b2e437bb9c7", - "outputIndex": 0, + utxo = { "address" : "1AZpKpcfCzKDUeTFBQUL4MokQai3m3HMXv", - "value": 20000, - "pending": true + "hash": fakeTxId(6), + "index": 0, + "pending": true, + "value": 20000 } - expectedOutputKey = expectedUtxo.hash + ":" + expectedUtxo.outputIndex + + expectedOutputKey = utxo.hash + ":" + utxo.index }) - function addUtxoToOutput(utxo){ - var key = utxo.hash + ":" + utxo.outputIndex - wallet.outputs[key] = { - from: key, - address: utxo.address, - value: utxo.value, - pending: utxo.pending - } - } + describe('on construction', function(){ + beforeEach(function(){ + wallet = new Wallet(seed, networks.bitcoin, [utxo]) + }) - describe('getBalance', function(){ - var utxo1 + it('matches the expected behaviour', function(){ + var output = wallet.outputs[expectedOutputKey] + assert(output) + assert.equal(output.value, utxo.value) + assert.equal(output.address, utxo.address) + }) + }) + + describe('getBalance', function(){ beforeEach(function(){ - utxo1 = cloneObject(expectedUtxo) - utxo1.hash = utxo1.hash.replace('7', 'l') + var utxo1 = cloneObject(utxo) + utxo1.hash = fakeTxId(5) + + wallet = new Wallet(seed, networks.bitcoin, [utxo, utxo1]) }) it('sums over utxo values', function(){ - addUtxoToOutput(expectedUtxo) - addUtxoToOutput(utxo1) - assert.equal(wallet.getBalance(), 40000) }) }) describe('getUnspentOutputs', function(){ beforeEach(function(){ - addUtxoToOutput(expectedUtxo) + wallet = new Wallet(seed, networks.bitcoin, [utxo]) }) it('parses wallet outputs to the expect format', function(){ - assert.deepEqual(wallet.getUnspentOutputs(), [expectedUtxo]) + assert.deepEqual(wallet.getUnspentOutputs(), [utxo]) }) it("ignores pending spending outputs (outputs with 'to' property)", function(){ @@ -223,40 +252,54 @@ describe('Wallet', function() { assert.deepEqual(wallet.getUnspentOutputs(), []) }) }) + }) - describe('setUnspentOutputs', function(){ - var utxo - beforeEach(function(){ - utxo = cloneObject([expectedUtxo]) - }) + // FIXME: remove in 2.x.y + describe('setUnspentOutputs', function(){ + var utxo + var expectedOutputKey - it('matches the expected behaviour', function(){ - wallet.setUnspentOutputs(utxo) - verifyOutputs() - }) + beforeEach(function(){ + utxo = { + hash: fakeTxId(0), + index: 0, + address: '115qa7iPZqn6as57hxLL8E9VUnhmGQxKWi', + value: 500000 + } - describe('required fields', function(){ - ['outputIndex', 'address', 'hash', 'value'].forEach(function(field){ - it("throws an error when " + field + " is missing", function(){ - delete utxo[0][field] + expectedOutputKey = utxo.hash + ":" + utxo.index - assert.throws(function() { - wallet.setUnspentOutputs(utxo) - }, new RegExp('Invalid unspent output: key ' + field + ' is missing')) + wallet = new Wallet(seed, networks.bitcoin) + }) + + it('matches the expected behaviour', function(){ + wallet.setUnspentOutputs([utxo]) + + var output = wallet.outputs[expectedOutputKey] + assert(output) + assert.equal(output.value, utxo.value) + assert.equal(output.address, utxo.address) + }) + + describe('required fields', function(){ + ['index', 'address', 'hash', 'value'].forEach(function(field){ + it("throws an error when " + field + " is missing", function(){ + delete utxo[field] + + assert.throws(function() { + wallet.setUnspentOutputs([utxo]) }) }) }) - - function verifyOutputs() { - var output = wallet.outputs[expectedOutputKey] - assert(output) - assert.equal(output.value, utxo[0].value) - assert.equal(output.address, utxo[0].address) - } }) }) describe('Process transaction', function(){ + var wallet + beforeEach(function(){ + wallet = new Wallet(seed) + }) + var addresses var tx @@ -389,39 +432,42 @@ describe('Wallet', function() { }) describe('createTx', function(){ - var to, value + var wallet var address1, address2 + var to, value beforeEach(function(){ - to = '15mMHKL96tWAUtqF3tbVf99Z8arcmnJrr3' + to = 'mt7MyTVVEWnbwpF5hBn6fgnJcv95Syk2ue' value = 500000 - // generate 2 addresses - address1 = wallet.generateAddress() - address2 = wallet.generateAddress() + address1 = "n1GyUANZand9Kw6hGSV9837cCC9FFUQzQa" + address2 = "n2fiWrHqD6GM5GiEqkbWAc6aaZQp3ba93X" - // set up 3 utxo - utxo = [ + // set up 3 utxos + var utxos = [ { "hash": fakeTxId(1), - "outputIndex": 0, - "address" : address1, + "index": 0, + "address": address1, "value": 400000 // not enough for value }, { "hash": fakeTxId(2), - "outputIndex": 1, - "address" : address1, + "index": 1, + "address": address1, "value": 500000 // enough for only value }, { "hash": fakeTxId(3), - "outputIndex": 0, + "index": 0, "address" : address2, "value": 510000 // enough for value and fee } ] - wallet.setUnspentOutputs(utxo) + + wallet = new Wallet(seed, networks.testnet, utxos) + wallet.generateAddress() + wallet.generateAddress() }) describe('transaction fee', function(){ @@ -441,17 +487,18 @@ describe('Wallet', function() { }) it('does not overestimate fees when network has dustSoftThreshold', function(){ - var wallet = new Wallet(seed, networks.litecoin) - var address = wallet.generateAddress() - wallet.setUnspentOutputs([{ + var utxo = { hash: fakeTxId(0), - outputIndex: 0, - address: address, + index: 0, + address: "LeyySKbQrRRwodKEj1W4a8y3YQupPLw5os", value: 500000 - }]) + } + + var wallet = new Wallet(seed, networks.litecoin, [utxo]) + wallet.generateAddress() value = 200000 - var tx = wallet.createTx(address, value) + var tx = wallet.createTx(utxo.address, value) assert.equal(getFee(wallet, tx), 100000) }) @@ -477,66 +524,38 @@ describe('Wallet', function() { assert.equal(tx.ins[0].index, 0) }) - it('ignores pending outputs', function(){ - utxo.push( - { - "hash": fakeTxId(4), - "outputIndex": 0, - "address" : address2, - "value": 530000, - "pending": true - } - ) - wallet.setUnspentOutputs(utxo) + it('uses confirmed outputs', function(){ + var tx2 = new Transaction() + tx2.addInput(fakeTxId(4), 0) + tx2.addOutput(address2, 530000) + wallet.processConfirmedTx(tx2) var tx = wallet.createTx(to, value) assert.equal(tx.ins.length, 1) - assert.deepEqual(tx.ins[0].hash, fakeTxHash(3)) + assert.deepEqual(tx.ins[0].hash, tx2.getHash()) assert.equal(tx.ins[0].index, 0) }) - }) - - describe('works for testnet', function(){ - it('should create transaction', function(){ - var wallet = new Wallet(seed, networks.testnet) - var address = wallet.generateAddress() - wallet.setUnspentOutputs([{ - hash: fakeTxId(0), - outputIndex: 0, - address: address, - value: value - }]) - - var to = 'mt7MyTVVEWnbwpF5hBn6fgnJcv95Syk2ue' - var toValue = value - 10000 + it('ignores pending outputs', function(){ + var tx2 = new Transaction() + tx2.addInput(fakeTxId(4), 0) + tx2.addOutput(address2, 530000) - var tx = wallet.createTx(to, toValue) - assert.equal(tx.outs.length, 1) + wallet.processPendingTx(tx2) + var tx = wallet.createTx(to, value) - var outAddress = Address.fromOutputScript(tx.outs[0].script, networks.testnet) - assert.equal(outAddress.toString(), to) - assert.equal(tx.outs[0].value, toValue) + assert.equal(tx.ins.length, 1) + assert.deepEqual(tx.ins[0].hash, fakeTxHash(3)) + assert.equal(tx.ins[0].index, 0) }) }) describe('changeAddress', function(){ it('should allow custom changeAddress', function(){ - var wallet = new Wallet(seed, networks.testnet) - var address = wallet.generateAddress() - - wallet.setUnspentOutputs([{ - hash: fakeTxId(0), - outputIndex: 0, - address: address, - value: value - }]) - assert.equal(wallet.getBalance(), value) - var changeAddress = 'mfrFjnKZUvTcvdAK2fUX5D8v1Epu5H8JCk' - var to = 'mt7MyTVVEWnbwpF5hBn6fgnJcv95Syk2ue' - var toValue = value / 2 + var fromValue = 510000 + var toValue = fromValue / 2 var fee = 1e3 var tx = wallet.createTx(to, toValue, fee, changeAddress) @@ -549,7 +568,7 @@ describe('Wallet', function() { assert.equal(tx.outs[0].value, toValue) assert.equal(outAddress1.toString(), changeAddress) - assert.equal(tx.outs[1].value, value - (toValue + fee)) + assert.equal(tx.outs[1].value, fromValue - (toValue + fee)) }) }) @@ -559,7 +578,7 @@ describe('Wallet', function() { assert.equal(tx.outs.length, 1) var out = tx.outs[0] - var outAddress = Address.fromOutputScript(out.script) + var outAddress = Address.fromOutputScript(out.script, networks.testnet) assert.equal(outAddress.toString(), to) assert.equal(out.value, value) @@ -574,7 +593,7 @@ describe('Wallet', function() { assert.equal(tx.outs.length, 2) var out = tx.outs[1] - var outAddress = Address.fromOutputScript(out.script) + var outAddress = Address.fromOutputScript(out.script, networks.testnet) assert.equal(outAddress.toString(), wallet.changeAddresses[1]) assert.equal(out.value, 10000) @@ -588,7 +607,7 @@ describe('Wallet', function() { assert.equal(wallet.changeAddresses.length, 1) var out = tx.outs[1] - var outAddress = Address.fromOutputScript(out.script) + var outAddress = Address.fromOutputScript(out.script, networks.testnet) assert.equal(outAddress.toString(), wallet.changeAddresses[0]) assert.equal(out.value, 10000)