UNPKG

bitcore-wallet-service

Version:
637 lines 23.3 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (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 () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.V8 = void 0; const async = __importStar(require("async")); const crypto = __importStar(require("crypto")); const lodash_1 = __importDefault(require("lodash")); const request = __importStar(require("request-promise-native")); const io = __importStar(require("socket.io-client")); const config_1 = __importDefault(require("../../config")); const index_1 = require("../chain/index"); const common_1 = require("../common"); const logger_1 = __importDefault(require("../logger")); const client_1 = require("./v8/client"); const $ = require('preconditions').singleton(); const Bitcore = require('bitcore-lib'); const Bitcore_ = { btc: Bitcore, bch: require('bitcore-lib-cash'), eth: Bitcore, matic: Bitcore, xrp: Bitcore, doge: require('bitcore-lib-doge'), ltc: require('bitcore-lib-ltc'), arb: Bitcore, op: Bitcore, base: Bitcore, sol: Bitcore }; const Constants = common_1.Common.Constants, Defaults = common_1.Common.Defaults, Utils = common_1.Common.Utils; const { sortDesc } = Utils; function v8network(bwsNetwork, chain = 'btc') { if (Utils.getGenericName(bwsNetwork) == 'livenet') return 'mainnet'; if (Utils.getGenericName(bwsNetwork) == 'testnet' && config_1.default.blockchainExplorerOpts?.[chain.toLowerCase()]?.[Utils.getNetworkName(chain.toLowerCase(), 'testnet')]?.regtestEnabled) { return 'regtest'; } return bwsNetwork; } class V8 { constructor(opts) { $.checkArgument(opts); $.checkArgument(Utils.checkValueInCollection(opts.chain, Constants.CHAINS)); $.checkArgument(Utils.checkValueInCollection(opts.network, Constants.NETWORKS[opts.chain])); $.checkArgument(opts.url); this.apiPrefix = opts.apiPrefix == null ? '/api' : opts.apiPrefix; this.chain = opts.chain; this.network = opts.network || 'livenet'; this.v8network = v8network(this.network, this.chain); this.addressFormat = this.chain == 'bch' ? 'cashaddr' : null; this.chainNetwork = `/${this.chain.toUpperCase()}/${this.v8network}`; this.apiPrefix += this.chainNetwork; 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'); } _getClient() { return new this.Client({ baseUrl: this.baseUrl }); } _getAuthClient(wallet) { $.checkState(wallet.beAuthPrivateKey2, 'Failed state: wallet.beAuthPrivateKey2 at <_getAuthClient()>'); return new this.Client({ baseUrl: this.baseUrl, authKey: Bitcore_[this.chain].PrivateKey(wallet.beAuthPrivateKey2) }); } addAddresses(wallet, addresses, cb, opts) { const client = this._getAuthClient(wallet); const payload = addresses.map(a => ({ address: a })); if (opts?.reprocess) { const c = this._getAuthClient({ beAuthPrivateKey2: config_1.default.blockchainExplorerOpts.socketApiKey }); opts.reprocess = c.sign({ method: 'reprocess', url: 'http://thisdontmatter.com/addAddresses' + wallet.beAuthPublicKey2, payload }); } const k = 'addAddresses' + !!opts.reprocess + addresses.length; const perfKey = getPerformanceKey(k); console.time(perfKey); client .importAddresses({ payload, pubKey: wallet.beAuthPublicKey2, reprocess: opts?.reprocess }) .then(ret => { console.timeEnd(perfKey); return cb(null, ret); }) .catch(err => { return cb(err); }); } register(wallet, cb) { if (wallet.chain != this.chain || wallet.network != this.network) { return cb(new Error('Network chain or network mismatch')); } const client = this._getAuthClient(wallet); const payload = { name: wallet.id, pubKey: wallet.beAuthPublicKey2 }; client .register({ authKey: wallet.beAuthPrivateKey2, payload }) .then(ret => { return cb(null, ret); }) .catch(cb); } async getBalance(wallet, cb) { const client = this._getAuthClient(wallet); const { tokenAddress, multisigContractAddress } = wallet; client .getBalance({ pubKey: wallet.beAuthPublicKey2, payload: {}, tokenAddress, multisigContractAddress }) .then(ret => { return cb(null, ret); }) .catch(cb); } getConnectionInfo() { return 'V8 (' + this.chain + '/' + this.v8network + ') @ ' + this.host; } _transformUtxos(utxos, bcheight) { $.checkState(bcheight > 0, 'Failed state: No BC height passed to _transformUtxos()'); const ret = lodash_1.default.map(lodash_1.default.reject(utxos, x => { return x.spentHeight && x.spentHeight <= -3; }), x => { const u = { address: x.address, satoshis: x.value, amount: x.value / 1e8, scriptPubKey: x.script, txid: x.mintTxid, vout: x.mintIndex, locked: false, confirmations: x.mintHeight > 0 && bcheight >= x.mintHeight ? bcheight - x.mintHeight + 1 : 0, spent: x.spentHeight != -2 }; return u; }); return ret; } getUtxos(wallet, height, cb, params = {}) { $.checkArgument(cb); const client = this._getAuthClient(wallet); const perfKey = getPerformanceKey('V8getUtxos'); console.time(perfKey); client .getCoins({ pubKey: wallet.beAuthPublicKey2, payload: {}, ...params }) .then(utxos => { console.timeEnd(perfKey); return cb(null, this._transformUtxos(utxos, height)); }) .catch(cb); } getCoinsForTx(txId, cb) { $.checkArgument(cb); const client = this._getClient(); const perfKey = getPerformanceKey('V8getCoinsForTx'); console.time(perfKey); client .getCoinsForTx({ txId, payload: {} }) .then(coins => { console.timeEnd(perfKey); return cb(null, coins); }) .catch(cb); } getCheckData(wallet, cb) { const client = this._getAuthClient(wallet); const perfKey = getPerformanceKey('WalletCheck'); console.time(perfKey); client .getCheckData({ pubKey: wallet.beAuthPublicKey2, payload: {} }) .then(checkInfo => { console.timeEnd(perfKey); return cb(null, checkInfo); }) .catch(cb); } broadcast(rawTx, cb, count = 0) { const payload = { rawTx, network: this.v8network, chain: this.chain.toUpperCase() }; const client = this._getClient(); client .broadcast({ payload }) .then(ret => { if (!ret.txid) { return cb(new Error('Error broadcasting')); } return cb(null, ret.txid); }) .catch(err => { if (count > 3) { logger_1.default.error('[v8.js] FINAL Broadcast error: %o', err); return cb(err); } else { count++; setTimeout(() => { logger_1.default.info('[v8.js] Retrying broadcast after %o', count * Defaults.BROADCAST_RETRY_TIME); return this.broadcast(rawTx, cb, count); }, count * Defaults.BROADCAST_RETRY_TIME); } }); } getTransaction(txid, cb) { logger_1.default.debug('[v8.js] GET TX %o', txid); const client = this._getClient(); client .getTx({ txid }) .then(tx => { if (!tx || JSON.stringify(tx) === '{}') { return cb(); } return cb(null, tx); }) .catch(err => { if (err.statusCode == '404') { return cb(); } else { return cb(err); } }); } getAddressUtxos(address, height, cb) { logger_1.default.debug('[v8.js] GET ADDR UTXO, %o, %o', address, height); const client = this._getClient(); client .getAddressTxos({ address, unspent: true }) .then(utxos => { return cb(null, this._transformUtxos(utxos, height)); }) .catch(cb); } getTransactions(wallet, startBlock, cb) { const perfKey = getPerformanceKey('V8getTxs'); console.time(perfKey); if (startBlock) { logger_1.default.debug(`getTxs: startBlock ${startBlock}`); } else { logger_1.default.debug('getTxs: from 0'); } const client = this._getAuthClient(wallet); let acum = '', broken; const opts = { includeMempool: true, pubKey: wallet.beAuthPublicKey2, payload: {}, startBlock: undefined, tokenAddress: wallet.tokenAddress, multisigContractAddress: wallet.multisigContractAddress }; if (startBlock != null && !isNaN(startBlock)) opts.startBlock = startBlock; const txStream = client.listTransactions(opts); txStream.on('data', raw => { acum = acum + raw.toString(); }); txStream.on('end', () => { if (broken) { return; } const txs = [], unconf = []; for (const rawTx of acum.split(/\r?\n/)) { if (!rawTx) continue; let tx; try { tx = JSON.parse(rawTx); } catch (e) { logger_1.default.error('[v8.js] Error at JSON.parse:' + e + ' Parsing:' + rawTx + ':'); return cb(e); } if (tx.value) tx.amount = tx.satoshis / 1e8; if (tx.height >= 0) txs.push(tx); else if (tx.height >= -2) unconf.push(tx); } console.timeEnd(perfKey); return cb(null, lodash_1.default.flatten(sortDesc(unconf, 'blockTime').concat(txs.reverse()))); }); txStream.on('error', e => { logger_1.default.error('[v8.js] Error: %o', e); broken = true; return cb(e); }); } getAddressActivity(address, cb) { const url = this.baseUrl + '/address/' + address + '/txs?limit=1'; logger_1.default.debug('[v8.js] CHECKING ADDRESS ACTIVITY %o', url); this.request .get(url, {}) .then(ret => { return cb(null, ret !== '[]'); }) .catch(err => { return cb(err); }); } getTransactionCount(address, cb) { const url = this.baseUrl + '/address/' + address + '/txs/count'; logger_1.default.debug('[v8.js] CHECKING ADDRESS NONCE %o', url); this.request .get(url, {}) .then(ret => { ret = JSON.parse(ret); return cb(null, ret.nonce); }) .catch(err => { return cb(err); }); } estimateGas(opts, cb) { const url = this.baseUrl + '/gas'; logger_1.default.debug('[v8.js] CHECKING GAS LIMIT %o', url); this.request .post(url, { body: opts, json: true }) .then(gasLimit => { gasLimit = JSON.parse(gasLimit); return cb(null, gasLimit); }) .catch(err => { return cb(err); }); } getMultisigContractInstantiationInfo(opts, cb) { const url = `${this.baseUrl}/ethmultisig/${opts.sender}/instantiation/${opts.txId}`; logger_1.default.debug('[v8.js] CHECKING CONTRACT INSTANTIATION INFO %o', url); this.request .get(url, {}) .then(contractInstantiationInfo => { contractInstantiationInfo = JSON.parse(contractInstantiationInfo); return cb(null, contractInstantiationInfo); }) .catch(err => { return cb(err); }); } getMultisigContractInfo(opts, cb) { const url = this.baseUrl + '/ethmultisig/info/' + opts.multisigContractAddress; logger_1.default.debug('[v8.js] CHECKING CONTRACT INFO %o', url); this.request .get(url, {}) .then(contractInfo => { contractInfo = JSON.parse(contractInfo); return cb(null, contractInfo); }) .catch(err => { return cb(err); }); } getTokenContractInfo(opts, cb) { const url = this.baseUrl + '/token/' + opts.tokenAddress; logger_1.default.debug('[v8.js] CHECKING CONTRACT INFO %o', url); this.request .get(url, {}) .then(contractInfo => { contractInfo = JSON.parse(contractInfo); return cb(null, contractInfo); }) .catch(err => { return cb(err); }); } getTokenAllowance(opts, cb) { const url = this.baseUrl + '/token/' + opts.tokenAddress + '/allowance/' + opts.ownerAddress + '/for/' + opts.spenderAddress; logger_1.default.debug('[v8.js] CHECKING TOKEN ALLOWANCE %o', url); this.request .get(url, {}) .then(allowance => { allowance = JSON.parse(allowance); return cb(null, allowance); }) .catch(err => { return cb(err); }); } getMultisigTxpsInfo(opts, cb) { const url = this.baseUrl + '/ethmultisig/txps/' + opts.multisigContractAddress; logger_1.default.debug('[v8.js] CHECKING CONTRACT TXPS INFO %o', url); this.request .get(url, {}) .then(multisigTxpsInfo => { multisigTxpsInfo = JSON.parse(multisigTxpsInfo); return cb(null, multisigTxpsInfo); }) .catch(err => { return cb(err); }); } estimateFee(nbBlocks, cb) { nbBlocks = nbBlocks || [1, 2, 6, 24]; const result = {}; async.each(nbBlocks, (x, icb) => { const url = this.baseUrl + '/fee/' + x; this.request .get(url, {}) .then(ret => { try { ret = JSON.parse(ret); if (!lodash_1.default.isUndefined(ret.blocks) && ret.blocks != x) { logger_1.default.info(`[v8.js] Ignoring response for ${x}: %o`, ret?.body || ret); return icb(); } result[x] = ret.feerate; } catch (e) { logger_1.default.warn('[v8.js] Fee error: %o', e); } return icb(); }) .catch(err => { return icb(err); }); }, err => { if (err) { return cb(err); } return cb(null, result); }); } estimateFeeV2(opts, cb) { const txType = opts.txType; if (Number(txType) !== 2) { return this.estimateFee(opts, cb); } const nbBlocks = Number(opts.nbBlocks) || 2; const url = this.baseUrl + `/fee/${nbBlocks}?txType=${txType}`; let result; this.request .get(url, {}) .then(ret => { try { ret = JSON.parse(ret); result = ret.feerate; } catch (e) { logger_1.default.warn('[v8.js] Fee error: %o', e); } return cb(null, result); }) .catch(err => { return cb(err); }); } estimatePriorityFee(opts, cb) { const percentile = opts.percentile; let result; const url = this.baseUrl + `/priorityFee/${percentile}`; this.request .get(url, {}) .then(ret => { try { ret = JSON.parse(ret); result = ret.feerate; } catch (e) { logger_1.default.warn('[v8.js] Priority fee error: %o', e); } return cb(null, result); }) .catch(err => { return cb(err); }); } getBlockchainHeight(cb) { const url = this.baseUrl + '/block/tip'; this.request .get(url, {}) .then(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); } getTxidsInBlock(blockHash, cb) { const url = this.baseUrl + '/tx/?blockHash=' + blockHash; this.request .get(url, {}) .then(ret => { try { ret = JSON.parse(ret); const 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); } getReserve(cb) { if (this._cachedReserveTs && Date.now() - this._cachedReserveTs < 20 * 60 * 1000) { return cb(null, this._cachedReserve); } const url = this.baseUrl + '/reserve'; this.request .get(url, {}) .then(ret => { try { ret = JSON.parse(ret); if (ret.reserve != null) { this._cachedReserve = ret.reserve; this._cachedReserveTs = Date.now(); } return cb(null, ret.reserve ?? Defaults.MIN_XRP_BALANCE); } catch (err) { logger_1.default.error('[v8.js] Error getting reserve: %o', err); return cb(null, Defaults.MIN_XRP_BALANCE); } }) .catch(cb); } initSocket(callbacks) { logger_1.default.info('V8 connecting socket at:' + this.host); const walletsSocket = io.connect(this.host, { transports: ['websocket'] }); const blockSocket = io.connect(this.host, { transports: ['websocket'] }); const getAuthPayload = host => { const authKey = config_1.default.blockchainExplorerOpts.socketApiKey; if (!authKey) throw new Error('provide authKey'); const authKeyObj = new Bitcore.PrivateKey(authKey); const pubKey = authKeyObj.toPublicKey().toString(); const authClient = new client_1.Client({ baseUrl: host, authKey: authKeyObj }); const payload = { method: 'socket', url: host }; const authPayload = { pubKey, message: authClient.getMessage(payload), signature: authClient.sign(payload) }; return authPayload; }; blockSocket.on('connect', () => { logger_1.default.info(`Connected to block ${this.getConnectionInfo()}`); blockSocket.emit('room', `${this.chainNetwork}/inv`); }); blockSocket.on('connect_error', () => { logger_1.default.error(`Error connecting to ${this.getConnectionInfo()}`); }); blockSocket.on('block', data => { return callbacks.onBlock(data.hash); }); walletsSocket.on('connect', () => { logger_1.default.info(`Connected to wallets ${this.getConnectionInfo()}`); walletsSocket.emit('room', `${this.chainNetwork}/wallets`, getAuthPayload(this.host)); }); walletsSocket.on('connect_error', () => { logger_1.default.error(`Error connecting to ${this.getConnectionInfo()} ${this.chainNetwork}`); }); walletsSocket.on('failure', err => { logger_1.default.error(`Error joining room ${err.message} ${this.chainNetwork}`); }); walletsSocket.on('coin', data => { if (!data || !data.coin) return; const notification = index_1.ChainService.onCoin(this.chain, data.coin); if (!notification) return; return callbacks.onIncomingPayments(notification); }); walletsSocket.on('tx', data => { if (!data || !data.tx) return; const notification = index_1.ChainService.onTx(this.chain, data.tx); if (!notification) return; return callbacks.onIncomingPayments(notification); }); } } exports.V8 = V8; const _parseErr = (err, res) => { if (err) { logger_1.default.warn('[v8.js] V8 raw error: %o', err); return 'V8 Error'; } logger_1.default.warn('[v8.js] ' + res.request.href + ' Returned Status: ' + res.statusCode); return 'Error querying the blockchain'; }; const getPerformanceKey = (name) => { return name + '-' + crypto.randomBytes(5).toString('hex'); }; //# sourceMappingURL=v8.js.map