|
|
@ -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,6 +186,7 @@ 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/) |
|
|
@ -169,51 +194,55 @@ describe('Wallet', function() { |
|
|
|
}) |
|
|
|
|
|
|
|
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) |
|
|
|