408 lines
8.0 KiB
JavaScript
408 lines
8.0 KiB
JavaScript
/*!
|
|
* client.js - http client for wallets
|
|
* Copyright (c) 2014-2015, Fedor Indutny (MIT License)
|
|
* Copyright (c) 2014-2017, Christopher Jeffrey (MIT License).
|
|
* https://github.com/bcoin-org/bcoin
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
var Network = require('../protocol/network');
|
|
var AsyncObject = require('../utils/asyncobject');
|
|
var TX = require('../primitives/tx');
|
|
var BlockMeta = require('./records').BlockMeta;
|
|
var Headers = require('../primitives/headers');
|
|
var Amount = require('../btc/amount');
|
|
var util = require('../utils/util');
|
|
var BufferReader = require('../utils/reader');
|
|
var co = require('../utils/co');
|
|
var IOClient = require('socket.io-client');
|
|
|
|
/**
|
|
* Bcoin HTTP client.
|
|
* @alias module:wallet.WalletClient
|
|
* @constructor
|
|
* @param {String} uri
|
|
* @param {Object?} options
|
|
*/
|
|
|
|
function WalletClient(options) {
|
|
if (!(this instanceof WalletClient))
|
|
return new WalletClient(options);
|
|
|
|
if (!options)
|
|
options = {};
|
|
|
|
if (typeof options === 'string')
|
|
options = { uri: options };
|
|
|
|
AsyncObject.call(this);
|
|
|
|
this.options = options;
|
|
this.network = Network.get(options.network);
|
|
|
|
this.uri = options.uri || 'http://localhost:' + this.network.rpcPort;
|
|
this.apiKey = options.apiKey;
|
|
|
|
this.socket = null;
|
|
}
|
|
|
|
util.inherits(WalletClient, AsyncObject);
|
|
|
|
/**
|
|
* Open the client, wait for socket to connect.
|
|
* @alias WalletClient#open
|
|
* @returns {Promise}
|
|
*/
|
|
|
|
WalletClient.prototype._open = co(function* _open() {
|
|
var self = this;
|
|
|
|
this.socket = new IOClient(this.uri, {
|
|
transports: ['websocket'],
|
|
forceNew: true
|
|
});
|
|
|
|
this.socket.on('error', function(err) {
|
|
self.emit('error', err);
|
|
});
|
|
|
|
this.socket.on('version', function(info) {
|
|
if (info.network !== self.network.type)
|
|
self.emit('error', new Error('Wrong network.'));
|
|
});
|
|
|
|
this.socket.on('block connect', function(entry, txs) {
|
|
var block;
|
|
|
|
try {
|
|
block = parseBlock(entry, txs);
|
|
} catch (e) {
|
|
self.emit('error', e);
|
|
return;
|
|
}
|
|
|
|
self.emit('block connect', block.entry, block.txs);
|
|
});
|
|
|
|
this.socket.on('block disconnect', function(entry) {
|
|
var block;
|
|
|
|
try {
|
|
block = parseEntry(entry);
|
|
} catch (e) {
|
|
self.emit('error', e);
|
|
return;
|
|
}
|
|
|
|
self.emit('block disconnect', block);
|
|
});
|
|
|
|
this.socket.on('block rescan', function(entry, txs, cb) {
|
|
var block;
|
|
|
|
try {
|
|
block = parseBlock(entry, txs);
|
|
} catch (e) {
|
|
self.emit('error', e);
|
|
return cb();
|
|
}
|
|
|
|
self.emit('block rescan', block.entry, block.txs, cb);
|
|
});
|
|
|
|
this.socket.on('chain reset', function(tip) {
|
|
var block;
|
|
|
|
try {
|
|
block = parseEntry(tip);
|
|
} catch (e) {
|
|
self.emit('error', e);
|
|
return;
|
|
}
|
|
|
|
self.emit('chain reset', block);
|
|
});
|
|
|
|
this.socket.on('tx', function(tx) {
|
|
try {
|
|
tx = parseTX(tx);
|
|
} catch (e) {
|
|
self.emit('error', e);
|
|
return;
|
|
}
|
|
self.emit('tx', tx);
|
|
});
|
|
|
|
yield this.onConnect();
|
|
yield this.sendAuth();
|
|
yield this.sendOptions({ raw: true });
|
|
yield this.watchChain();
|
|
});
|
|
|
|
/**
|
|
* Close the client, wait for the socket to close.
|
|
* @alias WalletClient#close
|
|
* @returns {Promise}
|
|
*/
|
|
|
|
WalletClient.prototype._close = function close() {
|
|
if (!this.socket)
|
|
return Promise.resolve();
|
|
|
|
this.socket.disconnect();
|
|
this.socket = null;
|
|
|
|
return Promise.resolve();
|
|
};
|
|
|
|
/**
|
|
* Wait for websocket connection.
|
|
* @private
|
|
* @returns {Promise}
|
|
*/
|
|
|
|
WalletClient.prototype.onConnect = function onConnect() {
|
|
var self = this;
|
|
return new Promise(function(resolve, reject) {
|
|
self.socket.once('connect', resolve);
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Wait for websocket auth.
|
|
* @private
|
|
* @returns {Promise}
|
|
*/
|
|
|
|
WalletClient.prototype.sendAuth = function sendAuth() {
|
|
var self = this;
|
|
return new Promise(function(resolve, reject) {
|
|
self.socket.emit('auth', self.apiKey, wrap(resolve, reject));
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Wait for websocket options.
|
|
* @private
|
|
* @returns {Promise}
|
|
*/
|
|
|
|
WalletClient.prototype.sendOptions = function sendOptions(options) {
|
|
var self = this;
|
|
return new Promise(function(resolve, reject) {
|
|
self.socket.emit('options', options, wrap(resolve, reject));
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Wait for websocket options.
|
|
* @private
|
|
* @returns {Promise}
|
|
*/
|
|
|
|
WalletClient.prototype.watchChain = function watchChain() {
|
|
var self = this;
|
|
return new Promise(function(resolve, reject) {
|
|
self.socket.emit('watch chain', wrap(resolve, reject));
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Get chain tip.
|
|
* @returns {Promise}
|
|
*/
|
|
|
|
WalletClient.prototype.getTip = function getTip() {
|
|
var self = this;
|
|
return new Promise(function(resolve, reject) {
|
|
self.socket.emit('get tip', wrap(resolve, reject, parseEntry));
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Get chain entry.
|
|
* @param {Hash} hash
|
|
* @returns {Promise}
|
|
*/
|
|
|
|
WalletClient.prototype.getEntry = function getEntry(block) {
|
|
var self = this;
|
|
return new Promise(function(resolve, reject) {
|
|
if (typeof block === 'string')
|
|
block = util.revHex(block);
|
|
|
|
self.socket.emit('get entry', block, wrap(resolve, reject, parseEntry));
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Send a transaction. Do not wait for promise.
|
|
* @param {TX} tx
|
|
* @returns {Promise}
|
|
*/
|
|
|
|
WalletClient.prototype.send = function send(tx) {
|
|
var self = this;
|
|
return new Promise(function(resolve, reject) {
|
|
var raw = tx.toRaw();
|
|
self.socket.emit('send', raw, wrap(resolve, reject));
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Set bloom filter.
|
|
* @param {Bloom} filter
|
|
* @returns {Promise}
|
|
*/
|
|
|
|
WalletClient.prototype.setFilter = function setFilter(filter) {
|
|
var self = this;
|
|
return new Promise(function(resolve, reject) {
|
|
var raw = filter.toRaw();
|
|
self.socket.emit('set filter', raw, wrap(resolve, reject));
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Add data to filter.
|
|
* @param {Buffer} data
|
|
* @returns {Promise}
|
|
*/
|
|
|
|
WalletClient.prototype.addFilter = function addFilter(chunks) {
|
|
var self = this;
|
|
var out = [];
|
|
var i;
|
|
|
|
if (!Array.isArray(chunks))
|
|
chunks = [chunks];
|
|
|
|
for (i = 0; i < chunks.length; i++)
|
|
out.push(chunks[i]);
|
|
|
|
return new Promise(function(resolve, reject) {
|
|
self.socket.emit('add filter', out, wrap(resolve, reject));
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Reset filter.
|
|
* @returns {Promise}
|
|
*/
|
|
|
|
WalletClient.prototype.resetFilter = function resetFilter() {
|
|
var self = this;
|
|
return new Promise(function(resolve, reject) {
|
|
self.socket.emit('reset filter', wrap(resolve, reject));
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Esimate smart fee.
|
|
* @param {Number?} blocks
|
|
* @returns {Promise}
|
|
*/
|
|
|
|
WalletClient.prototype.estimateFee = function estimateFee(blocks) {
|
|
var self = this;
|
|
return new Promise(function(resolve, reject) {
|
|
self.socket.emit('estimate fee', blocks,
|
|
wrap(resolve, reject, Amount.value));
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Rescan for any missed transactions.
|
|
* @param {Number|Hash} start - Start block.
|
|
* @param {Bloom} filter
|
|
* @param {Function} iter - Iterator.
|
|
* @returns {Promise}
|
|
*/
|
|
|
|
WalletClient.prototype.rescan = function rescan(start) {
|
|
var self = this;
|
|
return new Promise(function(resolve, reject) {
|
|
if (typeof start === 'string')
|
|
start = util.revHex(start);
|
|
|
|
self.socket.emit('rescan', start, wrap(resolve, reject));
|
|
});
|
|
};
|
|
|
|
/*
|
|
* Helpers
|
|
*/
|
|
|
|
function parseEntry(data, enc) {
|
|
var br, block, hash, height;
|
|
|
|
if (typeof data === 'string')
|
|
data = new Buffer(data, 'hex');
|
|
|
|
br = new BufferReader(data);
|
|
|
|
block = Headers.fromAbbr(br);
|
|
height = br.readU32();
|
|
hash = block.hash('hex');
|
|
|
|
return new BlockMeta(hash, height, block.ts);
|
|
}
|
|
|
|
function parseBlock(entry, txs) {
|
|
var block = parseEntry(entry);
|
|
var out = [];
|
|
var i, tx;
|
|
|
|
for (i = 0; i < txs.length; i++) {
|
|
tx = txs[i];
|
|
tx = parseTX(tx);
|
|
out.push(tx);
|
|
}
|
|
|
|
return new BlockResult(block, out);
|
|
}
|
|
|
|
function parseTX(data) {
|
|
return TX.fromRaw(data, 'hex');
|
|
}
|
|
|
|
function BlockResult(entry, txs) {
|
|
this.entry = entry;
|
|
this.txs = txs;
|
|
}
|
|
|
|
function wrap(resolve, reject, parse) {
|
|
return function(err, result) {
|
|
if (err) {
|
|
reject(new Error(err.error));
|
|
return;
|
|
}
|
|
|
|
if (!result) {
|
|
resolve(null);
|
|
return;
|
|
}
|
|
|
|
if (!parse) {
|
|
resolve(result);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
result = parse(result);
|
|
} catch (e) {
|
|
reject(e);
|
|
return;
|
|
}
|
|
|
|
resolve(result);
|
|
};
|
|
}
|
|
|
|
/*
|
|
* Expose
|
|
*/
|
|
|
|
module.exports = WalletClient;
|