var assert = require('assert') var bufferutils = require('./bufferutils') var crypto = require('crypto') var enforceType = require('./types') var networks = require('./networks') var Address = require('./address') var HDNode = require('./hdnode') var TransactionBuilder = require('./transaction_builder') var Script = require('./script') function Wallet(seed, network) { console.warn('Wallet is deprecated and will be removed in 2.0.0, see #296') seed = seed || crypto.randomBytes(32) network = network || networks.bitcoin // Stored in a closure to make accidental serialization less likely 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) this.addresses = [] this.changeAddresses = [] this.network = network this.unspents = [] // FIXME: remove in 2.0.0 this.unspentMap = {} // FIXME: remove in 2.0.0 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) accountZero = masterKey.deriveHardened(0) externalAccount = accountZero.derive(0) internalAccount = accountZero.derive(1) me.addresses = [] me.changeAddresses = [] me.unspents = [] me.unspentMap = {} } this.getMasterKey = function() { return masterKey } this.getAccountZero = function() { return accountZero } this.getExternalAccount = function() { return externalAccount } this.getInternalAccount = function() { return internalAccount } } Wallet.prototype.createTransaction = function(to, value, options) { // FIXME: remove in 2.0.0 if (typeof options !== 'object') { if (options !== undefined) { console.warn('Non options object parameters are deprecated, use options object instead') options = { fixedFee: arguments[2], changeAddress: arguments[3] } } } options = options || {} assert(value > this.network.dustThreshold, value + ' must be above dust threshold (' + this.network.dustThreshold + ' Satoshis)') var changeAddress = options.changeAddress var fixedFee = options.fixedFee var minConf = options.minConf === undefined ? 0 : options.minConf // FIXME: change minConf:1 by default in 2.0.0 // filter by minConf, then pending and sort by descending value var unspents = this.unspents.filter(function(unspent) { return unspent.confirmations >= minConf }).filter(function(unspent) { return !unspent.pending }).sort(function(o1, o2) { return o2.value - o1.value }) var accum = 0 var addresses = [] var subTotal = value var txb = new TransactionBuilder() txb.addOutput(to, value) for (var i = 0; i < unspents.length; ++i) { var unspent = unspents[i] addresses.push(unspent.address) txb.addInput(unspent.txHash, unspent.index) var fee = fixedFee === undefined ? estimatePaddedFee(txb.buildIncomplete(), this.network) : fixedFee accum += unspent.value subTotal = value + fee if (accum >= subTotal) { var change = accum - subTotal if (change > this.network.dustThreshold) { txb.addOutput(changeAddress || this.getChangeAddress(), change) } break } } assert(accum >= subTotal, 'Not enough funds (incl. fee): ' + accum + ' < ' + subTotal) return this.signWith(txb, addresses).build() } // FIXME: remove in 2.0.0 Wallet.prototype.processPendingTx = function(tx){ this.__processTx(tx, true) } // FIXME: remove in 2.0.0 Wallet.prototype.processConfirmedTx = function(tx){ this.__processTx(tx, false) } // FIXME: remove in 2.0.0 Wallet.prototype.__processTx = function(tx, isPending) { console.warn('processTransaction is considered harmful, see issue #260 for more information') var txId = tx.getId() var txHash = tx.getHash() 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 } var myAddresses = this.addresses.concat(this.changeAddresses) if (myAddresses.indexOf(address) > -1) { var lookup = txId + ':' + i if (lookup in this.unspentMap) return // its unique, add it var unspent = { address: address, confirmations: 0, // no way to determine this without more information index: i, txHash: txHash, txId: txId, value: txOut.value, pending: isPending } this.unspentMap[lookup] = unspent this.unspents.push(unspent) } }, this) tx.ins.forEach(function(txIn, i) { // copy and convert to big-endian hex var txInId = bufferutils.reverse(txIn.hash).toString('hex') var lookup = txInId + ':' + txIn.index if (!(lookup in this.unspentMap)) return var unspent = this.unspentMap[lookup] if (isPending) { unspent.pending = true unspent.spent = true } else { delete this.unspentMap[lookup] this.unspents = this.unspents.filter(function(unspent2) { return unspent !== unspent2 }) } }, this) } Wallet.prototype.generateAddress = function() { var k = this.addresses.length var address = this.getExternalAccount().derive(k).getAddress() this.addresses.push(address.toString()) return this.getReceiveAddress() } Wallet.prototype.generateChangeAddress = function() { var k = this.changeAddresses.length var address = this.getInternalAccount().derive(k).getAddress() this.changeAddresses.push(address.toString()) return this.getChangeAddress() } Wallet.prototype.getAddress = function() { if (this.addresses.length === 0) { this.generateAddress() } return this.addresses[this.addresses.length - 1] } Wallet.prototype.getBalance = function(minConf) { minConf = minConf || 0 return this.unspents.filter(function(unspent) { return unspent.confirmations >= minConf // FIXME: remove spent filter in 2.0.0 }).filter(function(unspent) { return !unspent.spent }).reduce(function(accum, unspent) { return accum + unspent.value }, 0) } Wallet.prototype.getChangeAddress = function() { if (this.changeAddresses.length === 0) { this.generateChangeAddress() } return this.changeAddresses[this.changeAddresses.length - 1] } Wallet.prototype.getInternalPrivateKey = function(index) { return this.getInternalAccount().derive(index).privKey } Wallet.prototype.getPrivateKey = function(index) { return this.getExternalAccount().derive(index).privKey } Wallet.prototype.getPrivateKeyForAddress = function(address) { var index if ((index = this.addresses.indexOf(address)) > -1) { return this.getPrivateKey(index) } if ((index = this.changeAddresses.indexOf(address)) > -1) { return this.getInternalPrivateKey(index) } assert(false, 'Unknown address. Make sure the address is from the keychain and has been generated') } Wallet.prototype.getUnspentOutputs = function(minConf) { minConf = minConf || 0 return this.unspents.filter(function(unspent) { return unspent.confirmations >= minConf // FIXME: remove spent filter in 2.0.0 }).filter(function(unspent) { return !unspent.spent }).map(function(unspent) { return { address: unspent.address, confirmations: unspent.confirmations, index: unspent.index, txId: unspent.txId, value: unspent.value, // FIXME: remove in 2.0.0 hash: unspent.txId, pending: unspent.pending } }) } Wallet.prototype.setUnspentOutputs = function(unspents) { this.unspentMap = {} this.unspents = unspents.map(function(unspent) { // FIXME: remove unspent.hash in 2.0.0 var txId = unspent.txId || unspent.hash var index = unspent.index // FIXME: remove in 2.0.0 if (unspent.hash !== undefined) { console.warn('unspent.hash is deprecated, use unspent.txId instead') } // FIXME: remove in 2.0.0 if (index === undefined) { console.warn('unspent.outputIndex is deprecated, use unspent.index instead') index = unspent.outputIndex } enforceType('String', txId) enforceType('Number', index) enforceType('Number', unspent.value) assert.equal(txId.length, 64, 'Expected valid txId, got ' + txId) assert.doesNotThrow(function() { Address.fromBase58Check(unspent.address) }, 'Expected Base58 Address, got ' + unspent.address) assert(isFinite(index), 'Expected finite index, got ' + index) // FIXME: remove branch in 2.0.0 if (unspent.confirmations !== undefined) { enforceType('Number', unspent.confirmations) } var txHash = bufferutils.reverse(new Buffer(txId, 'hex')) unspent = { address: unspent.address, confirmations: unspent.confirmations || 0, index: index, txHash: txHash, txId: txId, value: unspent.value, // FIXME: remove in 2.0.0 pending: unspent.pending || false } // FIXME: remove in 2.0.0 this.unspentMap[txId + ':' + index] = unspent return unspent }, this) } Wallet.prototype.signWith = function(tx, addresses) { addresses.forEach(function(address, i) { var privKey = this.getPrivateKeyForAddress(address) tx.sign(i, privKey) }, this) return tx } function estimatePaddedFee(tx, network) { var tmpTx = tx.clone() tmpTx.addOutput(Script.EMPTY, network.dustSoftThreshold || 0) return network.estimateFee(tmpTx) } // FIXME: 1.0.0 shims, remove in 2.0.0 Wallet.prototype.getReceiveAddress = Wallet.prototype.getAddress Wallet.prototype.createTx = Wallet.prototype.createTransaction module.exports = Wallet