@abcpros/bitcore-wallet-service
Version:
A service for Mutisig HD Bitcoin Wallets
598 lines • 24.4 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (_) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.V8 = void 0;
var units_1 = require("@abcpros/crypto-wallet-core/ts_build/src/constants/units");
var async = __importStar(require("async"));
var lodash_1 = __importDefault(require("lodash"));
var request = __importStar(require("request-promise-native"));
var io = require("socket.io-client");
var index_1 = require("../chain/index");
var logger_1 = __importDefault(require("../logger"));
var client_1 = require("./v8/client");
var $ = require('preconditions').singleton();
var Common = require('../common');
var Bitcore = require('@abcpros/bitcore-lib');
var Bitcore_ = {
btc: Bitcore,
bch: require('@abcpros/bitcore-lib-cash'),
xec: require('@abcpros/bitcore-lib-xec'),
eth: Bitcore,
xrp: Bitcore,
doge: require('@abcpros/bitcore-lib-doge'),
xpi: require('@abcpros/bitcore-lib-xpi'),
ltc: require('@abcpros/bitcore-lib-ltc')
};
var config = require('../../config');
var Constants = Common.Constants, Defaults = Common.Defaults, Utils = Common.Utils;
function v8network(bwsNetwork) {
if (bwsNetwork == 'livenet')
return 'mainnet';
if (bwsNetwork == 'testnet' && config.blockchainExplorerOpts.btc.testnet.regtestEnabled) {
return 'regtest';
}
return bwsNetwork;
}
var V8 = (function () {
function V8(opts) {
$.checkArgument(opts);
$.checkArgument(Utils.checkValueInCollection(opts.network, Constants.NETWORKS));
$.checkArgument(Utils.checkValueInCollection(opts.coin, Constants.COINS));
$.checkArgument(opts.url);
this.apiPrefix = lodash_1.default.isUndefined(opts.apiPrefix) ? '/api' : opts.apiPrefix;
this.chain = index_1.ChainService.getChain(opts.coin || Defaults.COIN);
this.coin = this.chain.toLowerCase();
this.network = opts.network || 'livenet';
this.v8network = v8network(this.network);
if (this.chain === 'XEC' && this.coin === 'xec' && this.network === 'livenet') {
this.v8network = 'livenet';
}
this.addressFormat = this.coin == 'bch' ? 'cashaddr' : null;
this.apiPrefix += "/" + this.chain + "/" + this.v8network;
this.host = opts.url;
this.userAgent = opts.userAgent || 'bws';
this.baseUrl = this.host + this.apiPrefix;
this.request = opts.request || request;
this.Client = opts.client || client_1.Client || require('./v8/client');
}
V8.prototype._getClient = function () {
return new this.Client({
baseUrl: this.baseUrl
});
};
V8.prototype._getAuthClient = function (wallet) {
$.checkState(wallet.beAuthPrivateKey2, 'Failed state: wallet.beAuthPrivateKey2 at <_getAuthClient()>');
return new this.Client({
baseUrl: this.baseUrl,
authKey: Bitcore_[this.coin].PrivateKey(wallet.beAuthPrivateKey2)
});
};
V8.prototype.addAddresses = function (wallet, addresses, cb) {
var client = this._getAuthClient(wallet);
var payload = lodash_1.default.map(addresses, function (a) {
return {
address: a
};
});
var k = 'addAddresses' + addresses.length;
console.time(k);
client
.importAddresses({
payload: payload,
pubKey: wallet.beAuthPublicKey2
})
.then(function (ret) {
console.timeEnd(k);
return cb(null, ret);
})
.catch(function (err) {
return cb(err);
});
};
V8.prototype.register = function (wallet, cb) {
if (wallet.coin != this.coin || wallet.network != this.network) {
return cb(new Error('Network coin or network mismatch'));
}
var client = this._getAuthClient(wallet);
var payload = {
name: wallet.id,
pubKey: wallet.beAuthPublicKey2
};
client
.register({
authKey: wallet.beAuthPrivateKey2,
payload: payload
})
.then(function (ret) {
return cb(null, ret);
})
.catch(cb);
};
V8.prototype.getBalance = function (wallet, cb) {
return __awaiter(this, void 0, void 0, function () {
var client, tokenAddress, multisigContractAddress;
return __generator(this, function (_a) {
client = this._getAuthClient(wallet);
tokenAddress = wallet.tokenAddress, multisigContractAddress = wallet.multisigContractAddress;
client
.getBalance({ pubKey: wallet.beAuthPublicKey2, payload: {}, tokenAddress: tokenAddress, multisigContractAddress: multisigContractAddress })
.then(function (ret) {
return cb(null, ret);
})
.catch(cb);
return [2];
});
});
};
V8.prototype.getConnectionInfo = function () {
return 'V8 (' + this.coin + '/' + this.v8network + ') @ ' + this.host;
};
V8.prototype._transformUtxos = function (unspent, bcheight, coin) {
var unitSatoshi = coin && units_1.UNITS[coin] && units_1.UNITS[coin].toSatoshis ? units_1.UNITS[coin].toSatoshis : 1e8;
$.checkState(bcheight > 0, 'Failed state: No BC height passed to _transformUtxos()');
var ret = lodash_1.default.map(lodash_1.default.reject(unspent, function (x) {
return x.spentHeight && x.spentHeight <= -3;
}), function (x) {
var u = {
address: x.address,
satoshis: x.value,
amount: x.value / unitSatoshi,
scriptPubKey: x.script,
txid: x.mintTxid,
vout: x.mintIndex,
locked: false,
confirmations: x.mintHeight > 0 && bcheight >= x.mintHeight ? bcheight - x.mintHeight + 1 : 0,
coinbase: x.coinbase,
immature: false
};
u.immature = u.coinbase && u.confirmations < Defaults.COINBASE_MATURITY;
return u;
});
return ret;
};
V8.prototype.getUtxos = function (wallet, height, cb) {
var _this = this;
$.checkArgument(cb);
var client = this._getAuthClient(wallet);
console.time('V8getUtxos');
client
.getCoins({ pubKey: wallet.beAuthPublicKey2, payload: {} })
.then(function (unspent) {
console.timeEnd('V8getUtxos');
return cb(null, _this._transformUtxos(unspent, height, wallet.coin));
})
.catch(cb);
};
V8.prototype.getCoinsForTx = function (txId, cb) {
$.checkArgument(cb);
var client = this._getClient();
console.time('V8getCoinsForTx');
client
.getCoinsForTx({ txId: txId, payload: {} })
.then(function (coins) {
console.timeEnd('V8getCoinsForTx');
return cb(null, coins);
})
.catch(cb);
};
V8.prototype.getCheckData = function (wallet, cb) {
var client = this._getAuthClient(wallet);
console.time('WalletCheck');
client
.getCheckData({ pubKey: wallet.beAuthPublicKey2, payload: {} })
.then(function (checkInfo) {
console.timeEnd('WalletCheck');
return cb(null, checkInfo);
})
.catch(cb);
};
V8.prototype.broadcast = function (rawTx, cb, count) {
var _this = this;
if (count === void 0) { count = 0; }
var payload = {
rawTx: rawTx,
network: this.v8network,
chain: this.chain
};
var client = this._getClient();
client
.broadcast({ payload: payload })
.then(function (ret) {
if (!ret.txid) {
return cb(new Error('Error broadcasting'));
}
return cb(null, ret.txid);
})
.catch(function (err) {
if (count > 3) {
logger_1.default.error('FINAL Broadcast error:', err);
return cb(err);
}
else {
count++;
setTimeout(function () {
logger_1.default.info('Retrying broadcast after', count * Defaults.BROADCAST_RETRY_TIME);
return _this.broadcast(rawTx, cb, count);
}, count * Defaults.BROADCAST_RETRY_TIME);
}
});
};
V8.prototype.getTransaction = function (txid, cb) {
console.log('[v8.js.207] GET TX', txid);
var client = this._getClient();
client
.getTx({ txid: txid })
.then(function (tx) {
if (!tx || lodash_1.default.isEmpty(tx)) {
return cb();
}
return cb(null, tx);
})
.catch(function (err) {
if (err.statusCode == '404') {
return cb();
}
else {
return cb(err);
}
});
};
V8.prototype.getAddressUtxos = function (address, height, coin, cb) {
var _this = this;
console.log(' GET ADDR UTXO', address, height);
var client = this._getClient();
client
.getAddressTxos({ address: address, unspent: true })
.then(function (utxos) {
return cb(null, _this._transformUtxos(utxos, height, coin));
})
.catch(cb);
};
V8.prototype.getTransactions = function (wallet, startBlock, cb) {
console.time('V8 getTxs');
if (startBlock) {
logger_1.default.debug("getTxs: startBlock " + startBlock);
}
else {
logger_1.default.debug('getTxs: from 0');
}
var coin = wallet.coin;
var unitSatoshi = coin && units_1.UNITS[coin] && units_1.UNITS[coin].toSatoshis ? units_1.UNITS[coin].toSatoshis : 1e8;
var client = this._getAuthClient(wallet);
var acum = '', broken;
var opts = {
includeMempool: true,
pubKey: wallet.beAuthPublicKey2,
payload: {},
startBlock: undefined,
tokenAddress: wallet.tokenAddress,
multisigContractAddress: wallet.multisigContractAddress
};
if (lodash_1.default.isNumber(startBlock))
opts.startBlock = startBlock;
var txStream = client.listTransactions(opts);
txStream.on('data', function (raw) {
acum = acum + raw.toString();
});
txStream.on('end', function () {
if (broken) {
return;
}
var txs = [], unconf = [];
lodash_1.default.each(acum.split(/\r?\n/), function (rawTx) {
if (!rawTx)
return;
var tx;
try {
tx = JSON.parse(rawTx);
}
catch (e) {
logger_1.default.error('v8 error at JSON.parse:' + e + ' Parsing:' + rawTx + ':');
return cb(e);
}
if (tx.value)
tx.amount = tx.satoshis / unitSatoshi;
if (tx.height >= 0)
txs.push(tx);
else
unconf.push(tx);
});
console.timeEnd('V8 getTxs');
return cb(null, lodash_1.default.flatten(lodash_1.default.orderBy(unconf, 'blockTime', 'desc').concat(txs.reverse())));
});
txStream.on('error', function (e) {
logger_1.default.error('v8 error:' + e);
broken = true;
return cb(e);
});
};
V8.prototype.getAddressActivity = function (address, cb) {
var url = this.baseUrl + '/address/' + address + '/txs?limit=1';
console.log('[v8.js.328:url:] CHECKING ADDRESS ACTIVITY', url);
this.request
.get(url, {})
.then(function (ret) {
return cb(null, ret !== '[]');
})
.catch(function (err) {
return cb(err);
});
};
V8.prototype.getTransactionCount = function (address, cb) {
var url = this.baseUrl + '/address/' + address + '/txs/count';
console.log('[v8.js.364:url:] CHECKING ADDRESS NONCE', url);
this.request
.get(url, {})
.then(function (ret) {
ret = JSON.parse(ret);
return cb(null, ret.nonce);
})
.catch(function (err) {
return cb(err);
});
};
V8.prototype.estimateGas = function (opts, cb) {
var url = this.baseUrl + '/gas';
console.log('[v8.js.378:url:] CHECKING GAS LIMIT', url);
this.request
.post(url, { body: opts, json: true })
.then(function (gasLimit) {
gasLimit = JSON.parse(gasLimit);
return cb(null, gasLimit);
})
.catch(function (err) {
return cb(err);
});
};
V8.prototype.getMultisigContractInstantiationInfo = function (opts, cb) {
var url = this.baseUrl + "/ethmultisig/" + opts.sender + "/instantiation/" + opts.txId;
console.log('[v8.js.378:url:] CHECKING CONTRACT INSTANTIATION INFO', url);
this.request
.get(url, {})
.then(function (contractInstantiationInfo) {
contractInstantiationInfo = JSON.parse(contractInstantiationInfo);
return cb(null, contractInstantiationInfo);
})
.catch(function (err) {
return cb(err);
});
};
V8.prototype.getMultisigContractInfo = function (opts, cb) {
var url = this.baseUrl + '/ethmultisig/info/' + opts.multisigContractAddress;
console.log('[v8.js.378:url:] CHECKING CONTRACT INFO', url);
this.request
.get(url, {})
.then(function (contractInfo) {
contractInfo = JSON.parse(contractInfo);
return cb(null, contractInfo);
})
.catch(function (err) {
return cb(err);
});
};
V8.prototype.getTokenContractInfo = function (opts, cb) {
var url = this.baseUrl + '/token/' + opts.tokenAddress;
console.log('[v8.js.378:url:] CHECKING CONTRACT INFO', url);
this.request
.get(url, {})
.then(function (contractInfo) {
contractInfo = JSON.parse(contractInfo);
return cb(null, contractInfo);
})
.catch(function (err) {
return cb(err);
});
};
V8.prototype.getMultisigTxpsInfo = function (opts, cb) {
var url = this.baseUrl + '/ethmultisig/txps/' + opts.multisigContractAddress;
console.log('[v8.js.378:url:] CHECKING CONTRACT TXPS INFO', url);
this.request
.get(url, {})
.then(function (multisigTxpsInfo) {
multisigTxpsInfo = JSON.parse(multisigTxpsInfo);
return cb(null, multisigTxpsInfo);
})
.catch(function (err) {
return cb(err);
});
};
V8.prototype.estimateFee = function (nbBlocks, cb) {
var _this = this;
nbBlocks = nbBlocks || [1, 2, 6, 24];
var result = {};
async.each(nbBlocks, function (x, icb) {
var url = _this.baseUrl + '/fee/' + x;
_this.request
.get(url, {})
.then(function (ret) {
try {
ret = JSON.parse(ret);
if (!lodash_1.default.isUndefined(ret.blocks) && ret.blocks != x) {
logger_1.default.info("Ignoring response for " + x + ":" + JSON.stringify(ret));
return icb();
}
result[x] = ret.feerate;
}
catch (e) {
logger_1.default.warn('fee error:', e);
}
return icb();
})
.catch(function (err) {
return icb(err);
});
}, function (err) {
if (err) {
return cb(err);
}
return cb(null, result);
});
};
V8.prototype.getBlockchainHeight = function (cb) {
var url = this.baseUrl + '/block/tip';
this.request
.get(url, {})
.then(function (ret) {
try {
ret = JSON.parse(ret);
return cb(null, ret.height, ret.hash);
}
catch (err) {
return cb(new Error('Could not get height from block explorer'));
}
})
.catch(cb);
};
V8.prototype.getBlockBits = function (cb) {
var url = this.baseUrl + '/block/tip';
this.request
.get(url, {})
.then(function (ret) {
try {
ret = JSON.parse(ret);
return cb(null, ret.bits);
}
catch (err) {
return cb(new Error('Could not get bits from block explorer'));
}
})
.catch(cb);
};
V8.prototype.getTxidsInBlock = function (blockHash, cb) {
var url = this.baseUrl + '/tx/?blockHash=' + blockHash;
this.request
.get(url, {})
.then(function (ret) {
try {
ret = JSON.parse(ret);
var res = lodash_1.default.map(ret, 'txid');
return cb(null, res);
}
catch (err) {
return cb(new Error('Could not get height from block explorer'));
}
})
.catch(cb);
};
V8.prototype.initSocket = function (callbacks) {
var _this = this;
logger_1.default.info('V8 connecting socket at:' + this.host);
var walletsSocket = io.connect(this.host, { transports: ['websocket'] });
var blockSocket = io.connect(this.host, { transports: ['websocket'] });
var getAuthPayload = function (host) {
var authKey = config.blockchainExplorerOpts.socketApiKey;
if (!authKey)
throw new Error('provide authKey');
var authKeyObj = new Bitcore.PrivateKey(authKey);
var pubKey = authKeyObj.toPublicKey().toString();
var authClient = new client_1.Client({ baseUrl: host, authKey: authKeyObj });
var payload = { method: 'socket', url: host };
var authPayload = { pubKey: pubKey, message: authClient.getMessage(payload), signature: authClient.sign(payload) };
return authPayload;
};
blockSocket.on('connect', function () {
logger_1.default.info("Connected to block " + _this.getConnectionInfo());
blockSocket.emit('room', "/" + _this.chain + "/" + _this.v8network + "/inv");
});
blockSocket.on('connect_error', function () {
logger_1.default.error("Error connecting to " + _this.getConnectionInfo());
});
blockSocket.on('block', function (data) {
return callbacks.onBlock(data.hash);
});
walletsSocket.on('connect', function () {
logger_1.default.info("Connected to wallets " + _this.getConnectionInfo());
walletsSocket.emit('room', "/" + _this.chain + "/" + _this.v8network + "/wallets", getAuthPayload(_this.host));
});
walletsSocket.on('connect_error', function () {
logger_1.default.error("Error connecting to " + _this.getConnectionInfo() + " " + _this.chain + "/" + _this.v8network);
});
walletsSocket.on('failure', function (err) {
logger_1.default.error("Error joining room " + err.message + " " + _this.chain + "/" + _this.v8network);
});
walletsSocket.on('coin', function (data) {
if (!data || !data.coin)
return;
var notification = index_1.ChainService.onCoin(_this.coin, data.coin);
if (!notification)
return;
return callbacks.onIncomingPayments(notification);
});
walletsSocket.on('tx', function (data) {
if (!data || !data.tx)
return;
var notification = index_1.ChainService.onTx(_this.coin, data.tx);
if (!notification)
return;
return callbacks.onIncomingPayments(notification);
});
};
return V8;
}());
exports.V8 = V8;
var _parseErr = function (err, res) {
if (err) {
logger_1.default.warn('V8 error: ', err);
return 'V8 Error';
}
logger_1.default.warn('V8 ' + res.request.href + ' Returned Status: ' + res.statusCode);
return 'Error querying the blockchain';
};
//# sourceMappingURL=v8.js.map