walletdb. txdb. webhooks.
This commit is contained in:
parent
ad8090dc7c
commit
e6048e85c8
@ -844,7 +844,7 @@ HDPrivateKey.prototype.toJSON = function toJSON(passphrase) {
|
||||
return json;
|
||||
}
|
||||
|
||||
json.xpubkey = this.hd.xpubkey;
|
||||
json.xpubkey = this.xpubkey;
|
||||
|
||||
return json;
|
||||
};
|
||||
|
||||
@ -182,72 +182,94 @@ HTTPServer.prototype._init = function _init() {
|
||||
// Create/get wallet
|
||||
this.post('/wallet/:id', function(req, res, next, send) {
|
||||
req.body.id = req.params.id;
|
||||
self.node.walletdb.createJSON(req.body.id, req.body, function(err, json) {
|
||||
self.node.walletdb.create(req.body, function(err, wallet) {
|
||||
var wallet;
|
||||
|
||||
if (err)
|
||||
return next(err);
|
||||
|
||||
if (!json)
|
||||
if (!wallet)
|
||||
return send(404);
|
||||
|
||||
json = wallet.toJSON();
|
||||
wallet.destroy();
|
||||
|
||||
send(200, json);
|
||||
});
|
||||
});
|
||||
|
||||
// Update wallet / sync address depth
|
||||
this.put('/wallet/:id', function(req, res, next, send) {
|
||||
req.body.id = req.params.id;
|
||||
self.node.walletdb.saveJSON(req.body.id, req.body, function(err, json) {
|
||||
var id = req.params.id;
|
||||
var receive = req.body.receiveDepth;
|
||||
var change = req.body.changeDepth;
|
||||
self.node.walletdb.syncDepth(id, receive, change, function(err) {
|
||||
if (err)
|
||||
return next(err);
|
||||
|
||||
if (!json)
|
||||
return send(404);
|
||||
|
||||
send(200, json);
|
||||
send(200, { success: true });
|
||||
});
|
||||
});
|
||||
|
||||
// Wallet Balance
|
||||
this.get('/wallet/:id/balance', function(req, res, next, send) {
|
||||
self.node.walletdb.getBalance(req.params.id, function(err, balance) {
|
||||
if (err)
|
||||
return next(err);
|
||||
|
||||
if (!coins.length)
|
||||
return send(404);
|
||||
|
||||
send(200, { balance: utils.btc(balance) });
|
||||
});
|
||||
});
|
||||
|
||||
// Wallet UTXOs
|
||||
this.get('/wallet/:id/utxo', function(req, res, next, send) {
|
||||
self.node.walletdb.getJSON(req.params.id, function(err, json) {
|
||||
self.node.walletdb.getUnspent(req.params.id, function(err, coins) {
|
||||
if (err)
|
||||
return next(err);
|
||||
|
||||
if (!json)
|
||||
if (!coins.length)
|
||||
return send(404);
|
||||
|
||||
self.node.getCoinByAddress(Object.keys(json.addressMap), function(err, coins) {
|
||||
if (err)
|
||||
return next(err);
|
||||
|
||||
if (!coins.length)
|
||||
return send(404);
|
||||
|
||||
send(200, coins.map(function(coin) { return coin.toJSON(); }));
|
||||
});
|
||||
send(200, coins.map(function(coin) { return coin.toJSON(); }));
|
||||
});
|
||||
});
|
||||
|
||||
// Wallet TXs
|
||||
this.get('/wallet/:id/tx', function(req, res, next, send) {
|
||||
self.node.walletdb.getJSON(req.params.id, function(err, json) {
|
||||
self.node.walletdb.getAll(req.params.id, function(err, txs) {
|
||||
if (err)
|
||||
return next(err);
|
||||
|
||||
if (!json)
|
||||
if (!txs.length)
|
||||
return send(404);
|
||||
|
||||
self.node.getTXByAddress(Object.keys(json.addressMap), function(err, txs) {
|
||||
if (err)
|
||||
return next(err);
|
||||
|
||||
if (!txs.length)
|
||||
return send(404);
|
||||
|
||||
send(200, coins.map(function(tx) { return tx.toJSON(); }));
|
||||
});
|
||||
send(200, txs.map(function(tx) { return tx.toJSON(); }));
|
||||
});
|
||||
});
|
||||
|
||||
// Wallet Pending TXs
|
||||
this.get('/wallet/:id/pending', function(req, res, next, send) {
|
||||
self.node.walletdb.getPending(req.params.id, function(err, txs) {
|
||||
if (err)
|
||||
return next(err);
|
||||
|
||||
if (!txs.length)
|
||||
return send(404);
|
||||
|
||||
send(200, txs.map(function(tx) { return tx.toJSON(); }));
|
||||
});
|
||||
});
|
||||
|
||||
// Emit events for any wallet txs that come in.
|
||||
this.node.on('wallet tx', function(tx, ids) {
|
||||
self.sendWebhook({ tx: tx, ids: ids });
|
||||
});
|
||||
};
|
||||
|
||||
HTTPServer.prototype.listen = function listen(port, host, callback) {
|
||||
@ -370,6 +392,42 @@ HTTPServer.prototype.del = function del(path, callback) {
|
||||
this.routes.del.push({ path: path, callback: callback });
|
||||
};
|
||||
|
||||
// Send webhooks to notify other servers
|
||||
// of incoming txs and wallet updates.
|
||||
HTTPServer.prototype.sendWebhook = function sendWebhook(msg, callback) {
|
||||
var request, body, secret, hmac;
|
||||
|
||||
callback = utils.ensure(callback);
|
||||
|
||||
if (!this.options.webhook)
|
||||
return callback();
|
||||
|
||||
try {
|
||||
request = require('request');
|
||||
} catch (e) {
|
||||
return callback(e);
|
||||
}
|
||||
|
||||
body = new Buffer(JSON.stringify(msg) + '\n', 'utf8');
|
||||
secret = new Buffer(this.options.webhook.secret || '', 'utf8');
|
||||
hmac = utils.sha512hmac(body, secret);
|
||||
|
||||
request({
|
||||
method: 'POST',
|
||||
uri: this.options.webhook.endpoint,
|
||||
headers: {
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
'Content-Length': body.length + '',
|
||||
'X-Bcoin-Hmac': hmac.toString('hex')
|
||||
},
|
||||
body: body
|
||||
}, function(err, res, body) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
return callback();
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Helpers
|
||||
*/
|
||||
|
||||
@ -81,7 +81,8 @@ Fullnode.prototype._init = function _init() {
|
||||
// and blockdb.
|
||||
this.http = new bcoin.http(this, {
|
||||
key: this.options.httpKey,
|
||||
cert: this.options.httpCert
|
||||
cert: this.options.httpCert,
|
||||
webhook: this.options.webhook
|
||||
});
|
||||
|
||||
// Bind to errors
|
||||
@ -97,12 +98,20 @@ Fullnode.prototype._init = function _init() {
|
||||
self.emit('error', err);
|
||||
});
|
||||
|
||||
this.walletdb.on('error', function(err) {
|
||||
self.emit('error', err);
|
||||
});
|
||||
|
||||
// Emit events for any TX we see that's
|
||||
// is relevant to one of our wallets.
|
||||
this.walletdb.on('wallet tx', function(tx, ids) {
|
||||
self.emit('wallet tx', tx, ids);
|
||||
});
|
||||
|
||||
this.on('tx', function(tx) {
|
||||
self.walletdb.tx.addTX(tx, function(err, updated) {
|
||||
if (updated)
|
||||
self.emit('wallet tx', tx);
|
||||
self.walletdb.tx.addTX(tx, function(err) {
|
||||
if (err)
|
||||
self.emit('error', err);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -43,7 +43,7 @@ function Node(options) {
|
||||
this.pool = null;
|
||||
this.chain = null;
|
||||
this.miner = null;
|
||||
this.profiler = null;
|
||||
this.walletdb = null;
|
||||
|
||||
Node.global = this;
|
||||
}
|
||||
|
||||
@ -14,18 +14,27 @@ var EventEmitter = require('events').EventEmitter;
|
||||
* TXPool
|
||||
*/
|
||||
|
||||
function TXPool(prefix, db) {
|
||||
function TXPool(prefix, db, options) {
|
||||
var self = this;
|
||||
|
||||
if (!(this instanceof TXPool))
|
||||
return new TXPool(wallet, txs);
|
||||
return new TXPool(prefix, db, options);
|
||||
|
||||
EventEmitter.call(this);
|
||||
|
||||
if (!options)
|
||||
options = {};
|
||||
|
||||
this.db = db;
|
||||
this.prefix = prefix || 'pool';
|
||||
this.options = options;
|
||||
this.busy = false;
|
||||
this.jobs = [];
|
||||
|
||||
if (options.addressFilter)
|
||||
this._hasAddress = options.addressFilter;
|
||||
|
||||
this.setMaxListeners(Number.MAX_SAFE_INTEGER);
|
||||
}
|
||||
|
||||
utils.inherits(TXPool, EventEmitter);
|
||||
@ -93,6 +102,7 @@ TXPool.prototype._add = function add(tx, callback, force) {
|
||||
var p = this.prefix + '/';
|
||||
var hash = tx.hash('hex');
|
||||
var updated = false;
|
||||
var own = false;
|
||||
var batch;
|
||||
|
||||
var unlock = this._lock(add, [tx, callback], force);
|
||||
@ -112,6 +122,7 @@ TXPool.prototype._add = function add(tx, callback, force) {
|
||||
batch = self.db.batch();
|
||||
|
||||
if (existing) {
|
||||
own = true;
|
||||
// Tricky - update the tx and coin in storage,
|
||||
// and remove pending flag to mark as confirmed.
|
||||
if (existing.ts === 0 && tx.ts !== 0) {
|
||||
@ -217,6 +228,7 @@ TXPool.prototype._add = function add(tx, callback, force) {
|
||||
return done(null, false);
|
||||
|
||||
updated = true;
|
||||
own = true;
|
||||
|
||||
type = input.getType();
|
||||
address = input.getAddress();
|
||||
@ -250,6 +262,8 @@ TXPool.prototype._add = function add(tx, callback, force) {
|
||||
if (!result)
|
||||
return next();
|
||||
|
||||
own = true;
|
||||
|
||||
self.getTX(input.prevout.hash, function(err, result) {
|
||||
if (err)
|
||||
return done(err);
|
||||
@ -305,6 +319,7 @@ TXPool.prototype._add = function add(tx, callback, force) {
|
||||
|
||||
key = hash + '/' + i;
|
||||
coin = bcoin.coin(tx, i);
|
||||
own = true;
|
||||
|
||||
self.db.get(p + 'o/' + key, function(err, orphans) {
|
||||
var some;
|
||||
@ -400,6 +415,9 @@ TXPool.prototype._add = function add(tx, callback, force) {
|
||||
if (err)
|
||||
return done(err);
|
||||
|
||||
if (!own)
|
||||
return done(null, false);
|
||||
|
||||
batch.put(p + 't/t/' + hash, tx.toExtended());
|
||||
if (tx.ts === 0)
|
||||
batch.put(p + 'p/t/' + hash, new Buffer([]));
|
||||
@ -418,11 +436,12 @@ TXPool.prototype._add = function add(tx, callback, force) {
|
||||
|
||||
self.emit('tx', tx);
|
||||
|
||||
if (tx.ts !== 0)
|
||||
self.emit('confirmed', tx);
|
||||
if (updated) {
|
||||
if (tx.ts !== 0)
|
||||
self.emit('confirmed', tx);
|
||||
|
||||
if (updated)
|
||||
self.emit('updated', tx);
|
||||
}
|
||||
|
||||
return done(null, true);
|
||||
});
|
||||
@ -856,6 +875,8 @@ TXPool.prototype.getHeightHashes = function getHeightHashes(height, callback) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
txs = utils.uniqs(txs);
|
||||
|
||||
return callback(null, txs);
|
||||
});
|
||||
}
|
||||
|
||||
@ -44,6 +44,8 @@ function Wallet(options) {
|
||||
options.master = bcoin.hd.fromSeed();
|
||||
|
||||
this.options = options;
|
||||
this.db = options.db || null;
|
||||
this.tx = options.tx || null;
|
||||
this.provider = options.provider || null;
|
||||
this.addresses = [];
|
||||
this.master = options.master || null;
|
||||
@ -87,6 +89,10 @@ function Wallet(options) {
|
||||
|
||||
this.id = this.getID();
|
||||
|
||||
// Non-alphanumeric IDs will break leveldb sorting.
|
||||
if (!/^[a-zA-Z0-9]$/.test(this.id))
|
||||
throw new Error('Wallet IDs must be alphanumeric.');
|
||||
|
||||
this.addKey(this.accountKey);
|
||||
|
||||
(options.keys || []).forEach(function(key) {
|
||||
@ -124,6 +130,63 @@ Wallet.prototype._init = function _init() {
|
||||
assert(this.receiveAddress);
|
||||
assert(!this.receiveAddress.change);
|
||||
assert(this.changeAddress.change);
|
||||
|
||||
if (!this.tx)
|
||||
return;
|
||||
|
||||
this.tx.on('tx', this._onTX = function(tx) {
|
||||
if (!self.ownInput(tx) && !self.ownOutput(tx))
|
||||
return;
|
||||
|
||||
self.emit('tx', tx);
|
||||
});
|
||||
|
||||
this.tx.on('updated', this._onUpdated = function(tx) {
|
||||
if (!self.ownInput(tx) && !self.ownOutput(tx))
|
||||
return;
|
||||
|
||||
self.emit('updated', tx);
|
||||
|
||||
if (!self.provider)
|
||||
return;
|
||||
|
||||
self.getBalance(function(err, balance) {
|
||||
if (err)
|
||||
return self.emit('error', err);
|
||||
|
||||
self.emit('balance', balance);
|
||||
});
|
||||
});
|
||||
|
||||
this.tx.on('confirmed', this._onConfirmed = function(tx) {
|
||||
if (!self.ownInput(tx) && !self.ownOutput(tx))
|
||||
return;
|
||||
|
||||
self.syncOutputDepth(tx);
|
||||
self.emit('confirmed', tx);
|
||||
});
|
||||
};
|
||||
|
||||
Wallet.prototype.destroy = function destroy() {
|
||||
if (this.tx) {
|
||||
if (this._onTX) {
|
||||
this.tx.removeListener('tx', this._onTX);
|
||||
delete this._onTX;
|
||||
}
|
||||
|
||||
if (this._onUpdated) {
|
||||
this.tx.removeListener('updated', this._onUpdated);
|
||||
delete this._onUpdated;
|
||||
}
|
||||
|
||||
if (this._onConfirmed) {
|
||||
this.tx.removeListener('confirmed', this._onConfirmed);
|
||||
delete this._onConfirmed;
|
||||
}
|
||||
}
|
||||
this.db = null;
|
||||
this.tx = null;
|
||||
this.provider = null;
|
||||
};
|
||||
|
||||
Wallet.prototype.addKey = function addKey(key) {
|
||||
@ -341,6 +404,10 @@ Wallet.prototype.deriveAddress = function deriveAddress(change, index) {
|
||||
if (this.witness)
|
||||
this.addressMap[address.getProgramAddress()] = data.path;
|
||||
|
||||
// Update the DB with the new address.
|
||||
if (this.db)
|
||||
this.db.update(this, address);
|
||||
|
||||
this.emit('add address', address);
|
||||
|
||||
return address;
|
||||
@ -473,27 +540,9 @@ Wallet.prototype.fillPrevout = function fillPrevout(tx, callback) {
|
||||
return this.provider.fillCoin(tx, callback);
|
||||
};
|
||||
|
||||
Wallet.prototype.sync = function sync(callback) {
|
||||
Wallet.prototype.createTX = function createTX(options, outputs, callback) {
|
||||
var self = this;
|
||||
|
||||
callback = utils.ensure(callback);
|
||||
|
||||
this.getUnspent(function(err, unspent) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
unspent.forEach(function(coin) {
|
||||
self.syncOutputDepth(coin);
|
||||
});
|
||||
|
||||
return callback();
|
||||
});
|
||||
};
|
||||
|
||||
Wallet.prototype.tx = function _tx(options, outputs, callback) {
|
||||
var self = this;
|
||||
var tx = bcoin.mtx();
|
||||
var target;
|
||||
var tx;
|
||||
|
||||
if (typeof outputs === 'function') {
|
||||
callback = outputs;
|
||||
@ -508,6 +557,9 @@ Wallet.prototype.tx = function _tx(options, outputs, callback) {
|
||||
if (!Array.isArray(outputs))
|
||||
outputs = [outputs];
|
||||
|
||||
// Create mutable tx
|
||||
tx = bcoin.mtx();
|
||||
|
||||
// Add the outputs
|
||||
outputs.forEach(function(output) {
|
||||
tx.addOutput(output);
|
||||
@ -521,20 +573,9 @@ Wallet.prototype.tx = function _tx(options, outputs, callback) {
|
||||
// Sort members a la BIP69
|
||||
tx.sortMembers();
|
||||
|
||||
// Find the necessary locktime if there is
|
||||
// a checklocktimeverify script in the unspents.
|
||||
target = tx.getTargetLocktime();
|
||||
|
||||
// No target value. The unspents have an
|
||||
// incompatible locktime type.
|
||||
if (!target)
|
||||
return callback(new Error('Incompatible locktime.'));
|
||||
|
||||
// Set the locktime to target value or
|
||||
// `height - whatever` to avoid fee sniping.
|
||||
if (target.value > 0)
|
||||
tx.setLocktime(target.value);
|
||||
else if (options.locktime != null)
|
||||
if (options.locktime != null)
|
||||
tx.setLocktime(options.locktime);
|
||||
else
|
||||
tx.avoidFeeSniping();
|
||||
@ -758,12 +799,10 @@ Wallet.prototype.sign = function sign(tx, type, index) {
|
||||
};
|
||||
|
||||
Wallet.prototype.addTX = function addTX(tx, callback) {
|
||||
this.syncOutputDepth(tx);
|
||||
if (!this.tx)
|
||||
return callback(new Error('No transaction pool available.'));
|
||||
|
||||
if (!this.provider)
|
||||
return callback(new Error('No wallet provider available.'));
|
||||
|
||||
return this.provider.addTX(tx, callback);
|
||||
return this.tx.add(tx, callback);
|
||||
};
|
||||
|
||||
Wallet.prototype.getAll = function getAll(callback) {
|
||||
@ -929,6 +968,10 @@ Wallet.prototype.toJSON = function toJSON() {
|
||||
receiveDepth: this.receiveDepth,
|
||||
changeDepth: this.changeDepth,
|
||||
master: this.master.toJSON(this.options.passphrase),
|
||||
// Store account key to:
|
||||
// a. save ourselves derivations.
|
||||
// b. allow deriving of addresses without decrypting the master key.
|
||||
accountKey: this.accountKey.xpubkey,
|
||||
addressMap: this.addressMap,
|
||||
keys: this.keys.map(function(key) {
|
||||
return key.xpubkey;
|
||||
@ -961,6 +1004,39 @@ Wallet._fromJSON = function _fromJSON(json, passphrase) {
|
||||
};
|
||||
};
|
||||
|
||||
// For updating the address table quickly
|
||||
// without decrypting the master key.
|
||||
Wallet.sync = function sync(json, options) {
|
||||
var master, wallet;
|
||||
|
||||
assert.equal(json.v, 3);
|
||||
assert.equal(json.name, 'wallet');
|
||||
|
||||
if (json.network)
|
||||
assert.equal(json.network, network.type);
|
||||
|
||||
master = json.master;
|
||||
json.master = json.accountKey;
|
||||
wallet = new Wallet(json);
|
||||
|
||||
if (options.txs != null) {
|
||||
options.txs.forEach(function(tx) {
|
||||
wallet.syncOutputDepth(tx);
|
||||
});
|
||||
}
|
||||
|
||||
if (options.receiveDepth != null)
|
||||
wallet.setReceiveDepth(options.receiveDepth);
|
||||
|
||||
if (options.changeDepth != null)
|
||||
wallet.setChangeDepth(options.changeDepth);
|
||||
|
||||
wallet = wallet.toJSON();
|
||||
wallet.master = master;
|
||||
|
||||
return wallet;
|
||||
};
|
||||
|
||||
Wallet.fromJSON = function fromJSON(json, passphrase) {
|
||||
return new Wallet(Wallet._fromJSON(json, passphrase));
|
||||
};
|
||||
|
||||
@ -83,7 +83,7 @@ WalletDB.prototype.dump = function dump(callback) {
|
||||
});
|
||||
}
|
||||
|
||||
records[key] = value.slice(0, 50).toString('hex');
|
||||
records[key] = value;
|
||||
|
||||
next();
|
||||
});
|
||||
@ -116,8 +116,75 @@ WalletDB.prototype._init = function _init() {
|
||||
|
||||
this.db = WalletDB._db[this.file];
|
||||
|
||||
this.tx = new bcoin.txdb('w', this.db);
|
||||
this.tx._hasAddress = this.hasAddress.bind(this);
|
||||
this.tx = new bcoin.txdb('w', this.db, {
|
||||
addressFilter: this.hasAddress.bind(this)
|
||||
});
|
||||
|
||||
this.tx.on('error', function(err) {
|
||||
self.emit('error', err);
|
||||
});
|
||||
|
||||
this.tx.on('updated', function(tx) {
|
||||
self.getIDs(tx.getOutputAddresses(), function(err, ids) {
|
||||
if (err)
|
||||
return self.emit('error', err);
|
||||
|
||||
self.emit('wallet tx', tx, ids);
|
||||
|
||||
// Only sync for confirmed txs.
|
||||
if (tx.ts === 0)
|
||||
return;
|
||||
|
||||
utils.forEachSerial(ids, function(id, next) {
|
||||
self.getJSON(id, function(err, json) {
|
||||
if (err) {
|
||||
self.emit('error', err);
|
||||
return next();
|
||||
}
|
||||
|
||||
// Allocate new addresses if necessary.
|
||||
json = bcoin.wallet.sync(json, { txs: [tx] });
|
||||
|
||||
self.saveJSON(id, json, function(err) {
|
||||
if (err)
|
||||
return next(err);
|
||||
next();
|
||||
});
|
||||
});
|
||||
}, function(err) {
|
||||
if (err)
|
||||
self.emit('error', err);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
WalletDB.prototype.syncDepth = function syncDepth(id, changeDepth, receiveDepth, callback) {
|
||||
callback = utils.ensure(callback);
|
||||
|
||||
if (!receiveDepth)
|
||||
receiveDepth = 0;
|
||||
|
||||
if (!changeDepth)
|
||||
changeDepth = 0;
|
||||
|
||||
self.getJSON(id, function(err, json) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
// Allocate new addresses if necessary.
|
||||
json = bcoin.wallet.sync(json, {
|
||||
receiveDepth: receiveDepth,
|
||||
changeDepth: changeDepth
|
||||
});
|
||||
|
||||
self.saveJSON(id, json, function(err) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
self.emit('sync depth', id, receiveDepth, changeDepth);
|
||||
callback();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
WalletDB.prototype.getJSON = function getJSON(id, callback) {
|
||||
@ -251,6 +318,8 @@ WalletDB.prototype._removeDB = function _removeDB(id, callback) {
|
||||
};
|
||||
|
||||
WalletDB.prototype.get = function get(id, passphrase, callback) {
|
||||
var self = this;
|
||||
|
||||
if (typeof passphrase === 'function') {
|
||||
callback = passphrase;
|
||||
passphrase = null;
|
||||
@ -268,14 +337,15 @@ WalletDB.prototype.get = function get(id, passphrase, callback) {
|
||||
return callback();
|
||||
|
||||
try {
|
||||
wallet = bcoin.wallet.fromJSON(options, passphrase);
|
||||
wallet.provider = self;
|
||||
options = bcoin.wallet._fromJSON(options, passphrase);
|
||||
options.db = self;
|
||||
options.tx = self.tx;
|
||||
options.provider = self;
|
||||
wallet = new bcoin.wallet(options);
|
||||
} catch (e) {
|
||||
return callback(e);
|
||||
}
|
||||
|
||||
wallet.on('add address', self._onAddress(wallet, wallet.id));
|
||||
|
||||
return callback(null, wallet);
|
||||
});
|
||||
};
|
||||
@ -286,14 +356,8 @@ WalletDB.prototype.save = function save(options, callback) {
|
||||
|
||||
callback = utils.ensure(callback);
|
||||
|
||||
if (options instanceof bcoin.wallet) {
|
||||
if (!options.provider) {
|
||||
options.on('add address', self._onAddress(options, options.id));
|
||||
options.provider = self;
|
||||
}
|
||||
if (options instanceof bcoin.wallet)
|
||||
options = options.toJSON();
|
||||
}
|
||||
if (options instanceof bcoin.wallet)
|
||||
assert(options.db === this);
|
||||
|
||||
this.saveJSON(options.id, options, callback);
|
||||
};
|
||||
@ -304,7 +368,7 @@ WalletDB.prototype.remove = function remove(id, callback) {
|
||||
callback = utils.ensure(callback);
|
||||
|
||||
if (id instanceof bcoin.wallet) {
|
||||
id.provider = null;
|
||||
id.destroy();
|
||||
id = id.id;
|
||||
}
|
||||
|
||||
@ -337,13 +401,18 @@ WalletDB.prototype.create = function create(options, callback) {
|
||||
|
||||
if (json) {
|
||||
try {
|
||||
wallet = bcoin.wallet.fromJSON(json, options.passphrase);
|
||||
wallet.provider = self;
|
||||
options = bcoin.wallet._fromJSON(json, options.passphrase);
|
||||
options.db = self;
|
||||
options.tx = self.tx;
|
||||
options.provider = self;
|
||||
wallet = new bcoin.wallet(options);
|
||||
} catch (e) {
|
||||
return callback(e);
|
||||
}
|
||||
done();
|
||||
} else {
|
||||
options.db = self;
|
||||
options.tx = self.tx;
|
||||
options.provider = self;
|
||||
wallet = new bcoin.wallet(options);
|
||||
self.saveJSON(wallet.id, wallet.toJSON(), done);
|
||||
@ -353,44 +422,46 @@ WalletDB.prototype.create = function create(options, callback) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
wallet.on('add address', self._onAddress(wallet, wallet.id));
|
||||
|
||||
return callback(null, wallet);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
WalletDB.prototype._onAddress = function _onAddress(wallet, id) {
|
||||
WalletDB.prototype.update = function update(wallet, address) {
|
||||
var self = this;
|
||||
return function(address) {
|
||||
var batch = self.db.batch();
|
||||
var batch;
|
||||
|
||||
// Ugly hack to avoid extra writes.
|
||||
if (!wallet.changeAddress && wallet.changeDepth > 1)
|
||||
return;
|
||||
|
||||
batch = this.db.batch();
|
||||
|
||||
batch.put(
|
||||
'w/a/' + address.getKeyAddress() + '/' + wallet.id,
|
||||
new Buffer([]));
|
||||
|
||||
if (address.type === 'multisig') {
|
||||
batch.put(
|
||||
'w/a/' + address.getKeyAddress() + '/' + id,
|
||||
'w/a/' + address.getScriptAddress() + '/' + wallet.id,
|
||||
new Buffer([]));
|
||||
}
|
||||
|
||||
if (address.type === 'multisig') {
|
||||
batch.put(
|
||||
'w/a/' + address.getScriptAddress() + '/' + id,
|
||||
new Buffer([]));
|
||||
}
|
||||
if (address.witness) {
|
||||
batch.put(
|
||||
'w/a/' + address.getProgramAddress() + '/' + wallet.id,
|
||||
new Buffer([]));
|
||||
}
|
||||
|
||||
if (address.witness) {
|
||||
batch.put(
|
||||
'w/a/' + address.getProgramAddress() + '/' + id,
|
||||
new Buffer([]));
|
||||
}
|
||||
batch.write(function(err) {
|
||||
if (err)
|
||||
self.emit('error', err);
|
||||
|
||||
batch.write(function(err) {
|
||||
self._saveDB(wallet.id, wallet.toJSON(), function(err) {
|
||||
if (err)
|
||||
self.emit('error', err);
|
||||
|
||||
self._saveDB(wallet.id, wallet.toJSON(), function(err) {
|
||||
if (err)
|
||||
self.emit('error', err);
|
||||
});
|
||||
});
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
WalletDB.prototype.addTX = function addTX(tx, callback) {
|
||||
@ -448,7 +519,8 @@ WalletDB.prototype.getLast = function getLast(id, callback) {
|
||||
};
|
||||
|
||||
WalletDB.prototype.getAddresses = function getAddresses(id, callback) {
|
||||
if (typeof id === 'string')
|
||||
// Try to avoid a database lookup if we can...
|
||||
if (typeof id === 'string' && bcoin.address.validate(id))
|
||||
return callback(null, [id]);
|
||||
|
||||
if (Array.isArray(id))
|
||||
@ -476,44 +548,6 @@ WalletDB.prototype.getAddresses = function getAddresses(id, callback) {
|
||||
});
|
||||
};
|
||||
|
||||
WalletDB.prototype.getIDs = function _getIDs(address, callback) {
|
||||
var self = this;
|
||||
var ids = [];
|
||||
|
||||
var iter = this.db.db.iterator({
|
||||
gte: 'w/a/' + address,
|
||||
lte: 'w/a/' + address + '~',
|
||||
keys: true,
|
||||
values: false,
|
||||
fillCache: false,
|
||||
keyAsBuffer: false
|
||||
});
|
||||
|
||||
callback = utils.ensure(callback);
|
||||
|
||||
(function next() {
|
||||
iter.next(function(err, key, value) {
|
||||
if (err) {
|
||||
return iter.end(function() {
|
||||
callback(err);
|
||||
});
|
||||
}
|
||||
|
||||
if (key === undefined) {
|
||||
return iter.end(function(err) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
return callback(null, ids);
|
||||
});
|
||||
}
|
||||
|
||||
ids.push(key.split('/')[2]);
|
||||
|
||||
next();
|
||||
});
|
||||
})();
|
||||
};
|
||||
|
||||
WalletDB.prototype.hasAddress = function hasAddress(address, callback) {
|
||||
var self = this;
|
||||
|
||||
@ -556,6 +590,64 @@ WalletDB.prototype.hasAddress = function hasAddress(address, callback) {
|
||||
})();
|
||||
};
|
||||
|
||||
WalletDB.prototype.getIDs = function getIDs(address, callback) {
|
||||
var self = this;
|
||||
var ids = [];
|
||||
|
||||
if (Array.isArray(address)) {
|
||||
return utils.forEachSerial(address, function(address, next) {
|
||||
self.getIDs(address, function(err, id) {
|
||||
if (err)
|
||||
return next(err);
|
||||
|
||||
ids = ids.concat(id);
|
||||
|
||||
next();
|
||||
});
|
||||
}, function(err) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
|
||||
ids = utils.uniqs(ids);
|
||||
|
||||
return callback(null, ids);
|
||||
});
|
||||
}
|
||||
|
||||
var iter = this.db.db.iterator({
|
||||
gte: 'w/a/' + address,
|
||||
lte: 'w/a/' + address + '~',
|
||||
keys: true,
|
||||
values: false,
|
||||
fillCache: false,
|
||||
keyAsBuffer: false
|
||||
});
|
||||
|
||||
callback = utils.ensure(callback);
|
||||
|
||||
(function next() {
|
||||
iter.next(function(err, key, value) {
|
||||
if (err) {
|
||||
return iter.end(function() {
|
||||
callback(err);
|
||||
});
|
||||
}
|
||||
|
||||
if (key === undefined) {
|
||||
return iter.end(function(err) {
|
||||
if (err)
|
||||
return callback(err);
|
||||
return callback(null, ids);
|
||||
});
|
||||
}
|
||||
|
||||
ids.push(key.split('/')[3]);
|
||||
|
||||
next();
|
||||
});
|
||||
})();
|
||||
};
|
||||
|
||||
/**
|
||||
* Expose
|
||||
*/
|
||||
|
||||
Loading…
Reference in New Issue
Block a user