New base code for mempool blockchain explorerer
This commit is contained in:
@@ -1,25 +1,19 @@
|
||||
{
|
||||
"ENV": "dev",
|
||||
"HTTP_PORT": 8999,
|
||||
"DB_HOST": "localhost",
|
||||
"DB_PORT": 3306,
|
||||
"DB_USER": "mempool",
|
||||
"DB_PASSWORD": "mempool",
|
||||
"DB_DATABASE": "mempool",
|
||||
"HTTP_PORT": 3000,
|
||||
"API_ENDPOINT": "/api/v1/",
|
||||
"CHAT_SSL_ENABLED": false,
|
||||
"CHAT_SSL_PRIVKEY": "",
|
||||
"CHAT_SSL_CERT": "",
|
||||
"CHAT_SSL_CHAIN": "",
|
||||
"MEMPOOL_REFRESH_RATE_MS": 500,
|
||||
"INITIAL_BLOCK_AMOUNT": 8,
|
||||
"ELECTRS_POLL_RATE_MS": 2000,
|
||||
"MEMPOOL_REFRESH_RATE_MS": 10000,
|
||||
"DEFAULT_PROJECTED_BLOCKS_AMOUNT": 3,
|
||||
"KEEP_BLOCK_AMOUNT": 24,
|
||||
"BITCOIN_NODE_HOST": "localhost",
|
||||
"BITCOIN_NODE_PORT": 8332,
|
||||
"BITCOIN_NODE_USER": "",
|
||||
"BITCOIN_NODE_PASS": "",
|
||||
"BACKEND_API": "bitcoind",
|
||||
"ELECTRS_API_URL": "https://www.blockstream.info/api",
|
||||
"TX_PER_SECOND_SPAN_SECONDS": 150
|
||||
"INITIAL_BLOCK_AMOUNT": 8,
|
||||
"TX_PER_SECOND_SPAN_SECONDS": 150,
|
||||
"ELECTRS_API_URL": "https://www.blockstream.info/testnet/api",
|
||||
"SSL": false,
|
||||
"SSL_CERT_FILE_PATH": "/etc/letsencrypt/live/mysite/fullchain.pem",
|
||||
"SSL_KEY_FILE_PATH": "/etc/letsencrypt/live/mysite/privkey.pem"
|
||||
}
|
||||
|
||||
@@ -1,31 +1,26 @@
|
||||
{
|
||||
"name": "mempool-backend",
|
||||
"name": "mempool-space-explorer-backend",
|
||||
"version": "1.0.0",
|
||||
"description": "Bitcoin Mempool Visualizer",
|
||||
"description": "Mempool space backend",
|
||||
"main": "index.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"start": "npm run build && node dist/index.js"
|
||||
},
|
||||
"author": {
|
||||
"name": "Simon Lindh",
|
||||
"url": "https://github.com/mempool-space/mempool.space"
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bitcoin": "^3.0.1",
|
||||
"compression": "^1.7.3",
|
||||
"express": "^4.16.3",
|
||||
"compression": "^1.7.4",
|
||||
"express": "^4.17.1",
|
||||
"mysql2": "^1.6.1",
|
||||
"request": "^2.88.0",
|
||||
"ws": "^6.0.0"
|
||||
"ws": "^7.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.16.0",
|
||||
"@types/mysql2": "github:types/mysql2",
|
||||
"@types/compression": "^1.0.1",
|
||||
"@types/express": "^4.17.2",
|
||||
"@types/request": "^2.48.2",
|
||||
"@types/ws": "^6.0.1",
|
||||
"@types/ws": "^6.0.4",
|
||||
"tslint": "^5.11.0",
|
||||
"typescript": "^3.1.1"
|
||||
"typescript": "~3.6.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import { IMempoolInfo, ITransaction, IBlock } from '../../interfaces';
|
||||
|
||||
export interface AbstractBitcoinApi {
|
||||
getMempoolInfo(): Promise<IMempoolInfo>;
|
||||
getRawMempool(): Promise<ITransaction['txid'][]>;
|
||||
getRawTransaction(txId: string): Promise<ITransaction>;
|
||||
getBlockCount(): Promise<number>;
|
||||
getBlockAndTransactions(hash: string): Promise<IBlock>;
|
||||
getBlockHash(height: number): Promise<string>;
|
||||
|
||||
getBlock(hash: string): Promise<IBlock>;
|
||||
getBlockTransactions(hash: string): Promise<IBlock>;
|
||||
getBlockTransactionsFromIndex(hash: string, index: number): Promise<IBlock>;
|
||||
getBlocks(): Promise<string>;
|
||||
getBlocksFromHeight(height: number): Promise<string>;
|
||||
getAddress(address: string): Promise<IBlock>;
|
||||
getAddressTransactions(address: string): Promise<IBlock>;
|
||||
getAddressTransactionsFromLastSeenTxid(address: string, lastSeenTxid: string): Promise<IBlock>;
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
const config = require('../../../mempool-config.json');
|
||||
import { AbstractBitcoinApi } from './bitcoin-api-abstract-factory';
|
||||
import BitcoindApi from './bitcoind-api';
|
||||
import ElectrsApi from './electrs-api';
|
||||
|
||||
function factory(): AbstractBitcoinApi {
|
||||
switch (config.BACKEND_API) {
|
||||
case 'electrs':
|
||||
return new ElectrsApi();
|
||||
case 'bitcoind':
|
||||
default:
|
||||
return new BitcoindApi();
|
||||
}
|
||||
}
|
||||
|
||||
export default factory();
|
||||
@@ -1,110 +0,0 @@
|
||||
const config = require('../../../mempool-config.json');
|
||||
import * as bitcoin from 'bitcoin';
|
||||
import { ITransaction, IMempoolInfo, IBlock } from '../../interfaces';
|
||||
import { AbstractBitcoinApi } from './bitcoin-api-abstract-factory';
|
||||
|
||||
class BitcoindApi implements AbstractBitcoinApi {
|
||||
client: any;
|
||||
|
||||
constructor() {
|
||||
this.client = new bitcoin.Client({
|
||||
host: config.BITCOIN_NODE_HOST,
|
||||
port: config.BITCOIN_NODE_PORT,
|
||||
user: config.BITCOIN_NODE_USER,
|
||||
pass: config.BITCOIN_NODE_PASS,
|
||||
});
|
||||
}
|
||||
|
||||
getMempoolInfo(): Promise<IMempoolInfo> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.client.getMempoolInfo((err: Error, mempoolInfo: any) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve(mempoolInfo);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getRawMempool(): Promise<ITransaction['txid'][]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.client.getRawMemPool((err: Error, transactions: ITransaction['txid'][]) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve(transactions);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getRawTransaction(txId: string): Promise<ITransaction> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.client.getRawTransaction(txId, true, (err: Error, txData: ITransaction) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve(txData);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getBlockCount(): Promise<number> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.client.getBlockCount((err: Error, response: number) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve(response);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getBlockAndTransactions(hash: string, verbosity: 1 | 2 = 1): Promise<IBlock> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.client.getBlock(hash, verbosity, (err: Error, block: IBlock) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve(block);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getBlockHash(height: number): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.client.getBlockHash(height, (err: Error, response: string) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve(response);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getBlock(hash: string): Promise<IBlock> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
getBlocks(): Promise<string> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
getBlocksFromHeight(height: number): Promise<string> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
getBlockTransactions(hash: string): Promise<IBlock> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
getBlockTransactionsFromIndex(hash: string, index: number): Promise<IBlock> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
getAddress(address: string): Promise<IBlock> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
getAddressTransactions(address: string): Promise<IBlock> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
getAddressTransactionsFromLastSeenTxid(address: string, lastSeenTxid: string): Promise<IBlock> {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
|
||||
export default BitcoindApi;
|
||||
@@ -1,33 +1,15 @@
|
||||
const config = require('../../../mempool-config.json');
|
||||
import { ITransaction, IMempoolInfo, IBlock } from '../../interfaces';
|
||||
import { AbstractBitcoinApi } from './bitcoin-api-abstract-factory';
|
||||
import { Transaction, Block } from '../../interfaces';
|
||||
import * as request from 'request';
|
||||
|
||||
class ElectrsApi implements AbstractBitcoinApi {
|
||||
class ElectrsApi {
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
getMempoolInfo(): Promise<IMempoolInfo> {
|
||||
getRawMempool(): Promise<Transaction['txid'][]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
request(config.ELECTRS_API_URL + '/mempool', { json: true, timeout: 10000 }, (err, res, response) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else if (res.statusCode !== 200) {
|
||||
reject(response);
|
||||
} else {
|
||||
resolve({
|
||||
size: response.count,
|
||||
bytes: response.vsize,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getRawMempool(): Promise<ITransaction['txid'][]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
request(config.ELECTRS_API_URL + '/mempool/txids', { json: true, timeout: 10000 }, (err, res, response) => {
|
||||
request(config.ELECTRS_API_URL + '/mempool/txids', { json: true, timeout: 10000, forever: true }, (err, res, response) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else if (res.statusCode !== 200) {
|
||||
@@ -39,24 +21,21 @@ class ElectrsApi implements AbstractBitcoinApi {
|
||||
});
|
||||
}
|
||||
|
||||
getRawTransaction(txId: string): Promise<ITransaction> {
|
||||
getRawTransaction(txId: string): Promise<Transaction> {
|
||||
return new Promise((resolve, reject) => {
|
||||
request(config.ELECTRS_API_URL + '/tx/' + txId, { json: true, timeout: 10000 }, (err, res, response) => {
|
||||
request(config.ELECTRS_API_URL + '/tx/' + txId, { json: true, timeout: 10000, forever: true }, (err, res, response) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else if (res.statusCode !== 200) {
|
||||
reject(response);
|
||||
} else {
|
||||
response.vsize = Math.round(response.weight / 4);
|
||||
response.fee = response.fee / 100000000;
|
||||
response.blockhash = response.status.block_hash;
|
||||
resolve(response);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getBlockCount(): Promise<number> {
|
||||
getBlockHeightTip(): Promise<number> {
|
||||
return new Promise((resolve, reject) => {
|
||||
request(config.ELECTRS_API_URL + '/blocks/tip/height', { json: true, timeout: 10000 }, (err, res, response) => {
|
||||
if (err) {
|
||||
@@ -70,29 +49,15 @@ class ElectrsApi implements AbstractBitcoinApi {
|
||||
});
|
||||
}
|
||||
|
||||
getBlockAndTransactions(hash: string): Promise<IBlock> {
|
||||
getTxIdsForBlock(hash: string): Promise<Block> {
|
||||
return new Promise((resolve, reject) => {
|
||||
request(config.ELECTRS_API_URL + '/block/' + hash, { json: true, timeout: 10000 }, (err, res, response) => {
|
||||
request(config.ELECTRS_API_URL + '/block/' + hash + '/txids', { json: true, timeout: 10000 }, (err, res, response) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else if (res.statusCode !== 200) {
|
||||
reject(response);
|
||||
} else {
|
||||
request(config.ELECTRS_API_URL + '/block/' + hash + '/txids', { json: true, timeout: 10000 }, (err2, res2, response2) => {
|
||||
if (err2) {
|
||||
reject(err2);
|
||||
} else if (res.statusCode !== 200) {
|
||||
reject(response);
|
||||
} else {
|
||||
const block = response;
|
||||
block.hash = hash;
|
||||
block.nTx = block.tx_count;
|
||||
block.time = block.timestamp;
|
||||
block.tx = response2;
|
||||
|
||||
resolve(block);
|
||||
}
|
||||
});
|
||||
resolve(response);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -112,20 +77,6 @@ class ElectrsApi implements AbstractBitcoinApi {
|
||||
});
|
||||
}
|
||||
|
||||
getBlocks(): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
request(config.ELECTRS_API_URL + '/blocks', { json: true, timeout: 10000 }, (err, res, response) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else if (res.statusCode !== 200) {
|
||||
reject(response);
|
||||
} else {
|
||||
resolve(response);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getBlocksFromHeight(height: number): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
request(config.ELECTRS_API_URL + '/blocks/' + height, { json: true, timeout: 10000 }, (err, res, response) => {
|
||||
@@ -140,7 +91,7 @@ class ElectrsApi implements AbstractBitcoinApi {
|
||||
});
|
||||
}
|
||||
|
||||
getBlock(hash: string): Promise<IBlock> {
|
||||
getBlock(hash: string): Promise<Block> {
|
||||
return new Promise((resolve, reject) => {
|
||||
request(config.ELECTRS_API_URL + '/block/' + hash, { json: true, timeout: 10000 }, (err, res, response) => {
|
||||
if (err) {
|
||||
@@ -153,77 +104,6 @@ class ElectrsApi implements AbstractBitcoinApi {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getBlockTransactions(hash: string): Promise<IBlock> {
|
||||
return new Promise((resolve, reject) => {
|
||||
request(config.ELECTRS_API_URL + '/block/' + hash + '/txs', { json: true, timeout: 10000 }, (err, res, response) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else if (res.statusCode !== 200) {
|
||||
reject(response);
|
||||
} else {
|
||||
resolve(response);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getBlockTransactionsFromIndex(hash: string, index: number): Promise<IBlock> {
|
||||
return new Promise((resolve, reject) => {
|
||||
request(config.ELECTRS_API_URL + '/block/' + hash + '/txs/' + index, { json: true, timeout: 10000 }, (err, res, response) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else if (res.statusCode !== 200) {
|
||||
reject(response);
|
||||
} else {
|
||||
resolve(response);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getAddress(address: string): Promise<IBlock> {
|
||||
return new Promise((resolve, reject) => {
|
||||
request(config.ELECTRS_API_URL + '/address/' + address, { json: true, timeout: 10000 }, (err, res, response) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else if (res.statusCode !== 200) {
|
||||
reject(response);
|
||||
} else {
|
||||
resolve(response);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getAddressTransactions(address: string): Promise<IBlock> {
|
||||
return new Promise((resolve, reject) => {
|
||||
request(config.ELECTRS_API_URL + '/address/' + address + '/txs', { json: true, timeout: 10000 }, (err, res, response) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else if (res.statusCode !== 200) {
|
||||
reject(response);
|
||||
} else {
|
||||
resolve(response);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getAddressTransactionsFromLastSeenTxid(address: string, lastSeenTxid: string): Promise<IBlock> {
|
||||
return new Promise((resolve, reject) => {
|
||||
request(config.ELECTRS_API_URL + '/address/' + address + '/txs/chain/' + lastSeenTxid,
|
||||
{ json: true, timeout: 10000 }, (err, res, response) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else if (res.statusCode !== 200) {
|
||||
reject(response);
|
||||
} else {
|
||||
resolve(response);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default ElectrsApi;
|
||||
export default new ElectrsApi();
|
||||
|
||||
@@ -1,206 +1,61 @@
|
||||
const config = require('../../mempool-config.json');
|
||||
import bitcoinApi from './bitcoin/bitcoin-api-factory';
|
||||
import { DB } from '../database';
|
||||
import { IBlock, ITransaction } from '../interfaces';
|
||||
import memPool from './mempool';
|
||||
import bitcoinApi from './bitcoin/electrs-api';
|
||||
import { Block } from '../interfaces';
|
||||
|
||||
class Blocks {
|
||||
private blocks: IBlock[] = [];
|
||||
private newBlockCallback: Function | undefined;
|
||||
private blocks: Block[] = [];
|
||||
private currentBlockHeight = 0;
|
||||
private newBlockCallback: Function = () => {};
|
||||
|
||||
constructor() {
|
||||
setInterval(this.$clearOldTransactionsAndBlocksFromDatabase.bind(this), 86400000);
|
||||
constructor() { }
|
||||
|
||||
public getBlocks(): Block[] {
|
||||
return this.blocks;
|
||||
}
|
||||
|
||||
public setNewBlockCallback(fn: Function) {
|
||||
this.newBlockCallback = fn;
|
||||
}
|
||||
|
||||
public getBlocks(): IBlock[] {
|
||||
return this.blocks;
|
||||
}
|
||||
|
||||
public formatBlock(block: IBlock) {
|
||||
return {
|
||||
hash: block.hash,
|
||||
height: block.height,
|
||||
nTx: block.nTx - 1,
|
||||
size: block.size,
|
||||
time: block.time,
|
||||
weight: block.weight,
|
||||
fees: block.fees,
|
||||
minFee: block.minFee,
|
||||
maxFee: block.maxFee,
|
||||
medianFee: block.medianFee,
|
||||
};
|
||||
}
|
||||
|
||||
public async updateBlocks() {
|
||||
try {
|
||||
const blockCount = await bitcoinApi.getBlockCount();
|
||||
const blockHeightTip = await bitcoinApi.getBlockHeightTip();
|
||||
|
||||
if (this.blocks.length === 0) {
|
||||
this.currentBlockHeight = blockCount - config.INITIAL_BLOCK_AMOUNT;
|
||||
this.currentBlockHeight = blockHeightTip - config.INITIAL_BLOCK_AMOUNT;
|
||||
} else {
|
||||
this.currentBlockHeight = this.blocks[this.blocks.length - 1].height;
|
||||
}
|
||||
|
||||
while (this.currentBlockHeight < blockCount) {
|
||||
this.currentBlockHeight++;
|
||||
|
||||
let block: IBlock | undefined;
|
||||
|
||||
const storedBlock = await this.$getBlockFromDatabase(this.currentBlockHeight);
|
||||
if (storedBlock) {
|
||||
block = storedBlock;
|
||||
while (this.currentBlockHeight < blockHeightTip) {
|
||||
if (this.currentBlockHeight === 0) {
|
||||
this.currentBlockHeight = blockHeightTip;
|
||||
} else {
|
||||
const blockHash = await bitcoinApi.getBlockHash(this.currentBlockHeight);
|
||||
block = await bitcoinApi.getBlockAndTransactions(blockHash);
|
||||
|
||||
const coinbase = await memPool.getRawTransaction(block.tx[0], true);
|
||||
if (coinbase && coinbase.totalOut) {
|
||||
block.fees = coinbase.totalOut;
|
||||
}
|
||||
|
||||
const mempool = memPool.getMempool();
|
||||
let found = 0;
|
||||
let notFound = 0;
|
||||
|
||||
let transactions: ITransaction[] = [];
|
||||
|
||||
for (let i = 1; i < block.tx.length; i++) {
|
||||
if (mempool[block.tx[i]]) {
|
||||
transactions.push(mempool[block.tx[i]]);
|
||||
found++;
|
||||
} else {
|
||||
console.log(`Fetching block tx ${i} of ${block.tx.length}`);
|
||||
const tx = await memPool.getRawTransaction(block.tx[i]);
|
||||
if (tx) {
|
||||
transactions.push(tx);
|
||||
}
|
||||
notFound++;
|
||||
}
|
||||
}
|
||||
|
||||
transactions.sort((a, b) => b.feePerVsize - a.feePerVsize);
|
||||
transactions = transactions.filter((tx: ITransaction) => tx.feePerVsize);
|
||||
|
||||
block.minFee = transactions[transactions.length - 1] ? transactions[transactions.length - 1].feePerVsize : 0;
|
||||
block.maxFee = transactions[0] ? transactions[0].feePerVsize : 0;
|
||||
block.medianFee = this.median(transactions.map((tx) => tx.feePerVsize));
|
||||
|
||||
console.log(`New block found (#${this.currentBlockHeight})! `
|
||||
+ `${found} of ${block.tx.length} found in mempool. ${notFound} not found.`);
|
||||
|
||||
if (this.newBlockCallback) {
|
||||
this.newBlockCallback(block);
|
||||
}
|
||||
|
||||
this.$saveBlockToDatabase(block);
|
||||
this.$saveTransactionsToDatabase(block.height, transactions);
|
||||
this.currentBlockHeight++;
|
||||
console.log(`New block found (#${this.currentBlockHeight})!`);
|
||||
}
|
||||
|
||||
const blockHash = await bitcoinApi.getBlockHash(this.currentBlockHeight);
|
||||
const block = await bitcoinApi.getBlock(blockHash);
|
||||
const txIds = await bitcoinApi.getTxIdsForBlock(blockHash);
|
||||
|
||||
block.medianFee = 2;
|
||||
block.feeRange = [1, 3];
|
||||
|
||||
this.blocks.push(block);
|
||||
if (this.blocks.length > config.KEEP_BLOCK_AMOUNT) {
|
||||
this.blocks.shift();
|
||||
}
|
||||
|
||||
this.newBlockCallback(block, txIds);
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.log('Error getBlockCount', err);
|
||||
}
|
||||
}
|
||||
|
||||
private async $getBlockFromDatabase(height: number): Promise<IBlock | undefined> {
|
||||
try {
|
||||
const connection = await DB.pool.getConnection();
|
||||
const query = `
|
||||
SELECT * FROM blocks WHERE height = ?
|
||||
`;
|
||||
|
||||
const [rows] = await connection.query<any>(query, [height]);
|
||||
connection.release();
|
||||
|
||||
if (rows[0]) {
|
||||
return rows[0];
|
||||
}
|
||||
} catch (e) {
|
||||
console.log('$get() block error', e);
|
||||
}
|
||||
}
|
||||
|
||||
private async $saveBlockToDatabase(block: IBlock) {
|
||||
try {
|
||||
const connection = await DB.pool.getConnection();
|
||||
const query = `
|
||||
INSERT IGNORE INTO blocks
|
||||
(height, hash, size, weight, minFee, maxFee, time, fees, nTx, medianFee)
|
||||
VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`;
|
||||
|
||||
const params: (any)[] = [
|
||||
block.height,
|
||||
block.hash,
|
||||
block.size,
|
||||
block.weight,
|
||||
block.minFee,
|
||||
block.maxFee,
|
||||
block.time,
|
||||
block.fees,
|
||||
block.nTx - 1,
|
||||
block.medianFee,
|
||||
];
|
||||
|
||||
await connection.query(query, params);
|
||||
connection.release();
|
||||
} catch (e) {
|
||||
console.log('$create() block error', e);
|
||||
}
|
||||
}
|
||||
|
||||
private async $saveTransactionsToDatabase(blockheight: number, transactions: ITransaction[]) {
|
||||
try {
|
||||
const connection = await DB.pool.getConnection();
|
||||
|
||||
for (let i = 0; i < transactions.length; i++) {
|
||||
const query = `
|
||||
INSERT IGNORE INTO transactions
|
||||
(blockheight, txid, fee, feePerVsize)
|
||||
VALUES(?, ?, ?, ?)
|
||||
`;
|
||||
|
||||
const params: (any)[] = [
|
||||
blockheight,
|
||||
transactions[i].txid,
|
||||
transactions[i].fee,
|
||||
transactions[i].feePerVsize,
|
||||
];
|
||||
|
||||
await connection.query(query, params);
|
||||
}
|
||||
|
||||
connection.release();
|
||||
} catch (e) {
|
||||
console.log('$create() transaction error', e);
|
||||
}
|
||||
}
|
||||
|
||||
private async $clearOldTransactionsAndBlocksFromDatabase() {
|
||||
try {
|
||||
const connection = await DB.pool.getConnection();
|
||||
let query = `DELETE FROM blocks WHERE height < ?`;
|
||||
await connection.query<any>(query, [this.currentBlockHeight - config.KEEP_BLOCK_AMOUNT]);
|
||||
query = `DELETE FROM transactions WHERE blockheight < ?`;
|
||||
await connection.query<any>(query, [this.currentBlockHeight - config.KEEP_BLOCK_AMOUNT]);
|
||||
connection.release();
|
||||
} catch (e) {
|
||||
console.log('$clearOldTransactionsFromDatabase() error', e);
|
||||
console.log('updateBlocks error', err);
|
||||
}
|
||||
}
|
||||
|
||||
private median(numbers: number[]) {
|
||||
if (!numbers.length) { return 0; }
|
||||
let medianNr = 0;
|
||||
const numsLen = numbers.length;
|
||||
numbers.sort();
|
||||
@@ -211,6 +66,20 @@ class Blocks {
|
||||
}
|
||||
return medianNr;
|
||||
}
|
||||
|
||||
private getFeesInRange(transactions: any[], rangeLength: number) {
|
||||
const arr = [transactions[transactions.length - 1].feePerVsize];
|
||||
const chunk = 1 / (rangeLength - 1);
|
||||
let itemsToAdd = rangeLength - 2;
|
||||
|
||||
while (itemsToAdd > 0) {
|
||||
arr.push(transactions[Math.floor(transactions.length * chunk * itemsToAdd)].feePerVsize);
|
||||
itemsToAdd--;
|
||||
}
|
||||
|
||||
arr.push(transactions[0].feePerVsize);
|
||||
return arr;
|
||||
}
|
||||
}
|
||||
|
||||
export default new Blocks();
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import projectedBlocks from './projected-blocks';
|
||||
import projectedBlocks from './mempool-blocks';
|
||||
import { DB } from '../database';
|
||||
|
||||
class FeeApi {
|
||||
constructor() { }
|
||||
|
||||
public getRecommendedFee() {
|
||||
const pBlocks = projectedBlocks.getProjectedBlocks();
|
||||
const pBlocks = projectedBlocks.getMempoolBlocks();
|
||||
if (!pBlocks.length) {
|
||||
return {
|
||||
'fastestFee': 0,
|
||||
@@ -15,7 +15,7 @@ class FeeApi {
|
||||
}
|
||||
let firstMedianFee = Math.ceil(pBlocks[0].medianFee);
|
||||
|
||||
if (pBlocks.length === 1 && pBlocks[0].blockWeight <= 2000000) {
|
||||
if (pBlocks.length === 1 && pBlocks[0].blockVSize <= 500000) {
|
||||
firstMedianFee = 1;
|
||||
}
|
||||
|
||||
|
||||
97
backend/src/api/mempool-blocks.ts
Normal file
97
backend/src/api/mempool-blocks.ts
Normal file
@@ -0,0 +1,97 @@
|
||||
const config = require('../../mempool-config.json');
|
||||
import { MempoolBlock, SimpleTransaction } from '../interfaces';
|
||||
|
||||
class MempoolBlocks {
|
||||
private mempoolBlocks: MempoolBlock[] = [];
|
||||
|
||||
constructor() {}
|
||||
|
||||
public getMempoolBlocks(): MempoolBlock[] {
|
||||
return this.mempoolBlocks;
|
||||
}
|
||||
|
||||
public updateMempoolBlocks(memPool: { [txid: string]: SimpleTransaction }): void {
|
||||
const latestMempool = memPool;
|
||||
const memPoolArray: SimpleTransaction[] = [];
|
||||
for (const i in latestMempool) {
|
||||
if (latestMempool.hasOwnProperty(i)) {
|
||||
memPoolArray.push(latestMempool[i]);
|
||||
}
|
||||
}
|
||||
memPoolArray.sort((a, b) => b.feePerVsize - a.feePerVsize);
|
||||
const transactionsSorted = memPoolArray.filter((tx) => tx.feePerVsize);
|
||||
this.mempoolBlocks = this.calculateMempoolBlocks(transactionsSorted);
|
||||
}
|
||||
|
||||
private calculateMempoolBlocks(transactionsSorted: SimpleTransaction[]): MempoolBlock[] {
|
||||
const mempoolBlocks: MempoolBlock[] = [];
|
||||
let blockWeight = 0;
|
||||
let blockSize = 0;
|
||||
let transactions: SimpleTransaction[] = [];
|
||||
transactionsSorted.forEach((tx) => {
|
||||
if (blockWeight + tx.vsize < 1000000 || mempoolBlocks.length === config.DEFAULT_PROJECTED_BLOCKS_AMOUNT) {
|
||||
blockWeight += tx.vsize;
|
||||
blockSize += tx.size;
|
||||
transactions.push(tx);
|
||||
} else {
|
||||
mempoolBlocks.push(this.dataToMempoolBlocks(transactions, blockSize, blockWeight, mempoolBlocks.length));
|
||||
blockWeight = 0;
|
||||
blockSize = 0;
|
||||
transactions = [];
|
||||
}
|
||||
});
|
||||
if (transactions.length) {
|
||||
mempoolBlocks.push(this.dataToMempoolBlocks(transactions, blockSize, blockWeight, mempoolBlocks.length));
|
||||
}
|
||||
return mempoolBlocks;
|
||||
}
|
||||
|
||||
private dataToMempoolBlocks(transactions: SimpleTransaction[], blockSize: number, blockVSize: number, blocksIndex: number): MempoolBlock {
|
||||
let rangeLength = 3;
|
||||
if (blocksIndex === 0) {
|
||||
rangeLength = 8;
|
||||
}
|
||||
if (transactions.length > 4000) {
|
||||
rangeLength = 5;
|
||||
} else if (transactions.length > 10000) {
|
||||
rangeLength = 8;
|
||||
} else if (transactions.length > 25000) {
|
||||
rangeLength = 10;
|
||||
}
|
||||
return {
|
||||
blockSize: blockSize,
|
||||
blockVSize: blockVSize,
|
||||
nTx: transactions.length,
|
||||
medianFee: this.median(transactions.map((tx) => tx.feePerVsize)),
|
||||
feeRange: this.getFeesInRange(transactions, rangeLength),
|
||||
};
|
||||
}
|
||||
|
||||
private median(numbers: number[]) {
|
||||
let medianNr = 0;
|
||||
const numsLen = numbers.length;
|
||||
numbers.sort();
|
||||
if (numsLen % 2 === 0) {
|
||||
medianNr = (numbers[numsLen / 2 - 1] + numbers[numsLen / 2]) / 2;
|
||||
} else {
|
||||
medianNr = numbers[(numsLen - 1) / 2];
|
||||
}
|
||||
return medianNr;
|
||||
}
|
||||
|
||||
private getFeesInRange(transactions: SimpleTransaction[], rangeLength: number) {
|
||||
const arr = [transactions[transactions.length - 1].feePerVsize];
|
||||
const chunk = 1 / (rangeLength - 1);
|
||||
let itemsToAdd = rangeLength - 2;
|
||||
|
||||
while (itemsToAdd > 0) {
|
||||
arr.push(transactions[Math.floor(transactions.length * chunk * itemsToAdd)].feePerVsize);
|
||||
itemsToAdd--;
|
||||
}
|
||||
|
||||
arr.push(transactions[0].feePerVsize);
|
||||
return arr;
|
||||
}
|
||||
}
|
||||
|
||||
export default new MempoolBlocks();
|
||||
@@ -1,10 +1,10 @@
|
||||
const config = require('../../mempool-config.json');
|
||||
import bitcoinApi from './bitcoin/bitcoin-api-factory';
|
||||
import { ITransaction, IMempoolInfo, IMempool } from '../interfaces';
|
||||
import bitcoinApi from './bitcoin/electrs-api';
|
||||
import { MempoolInfo, SimpleTransaction, Transaction } from '../interfaces';
|
||||
|
||||
class Mempool {
|
||||
private mempool: IMempool = {};
|
||||
private mempoolInfo: IMempoolInfo | undefined;
|
||||
private mempoolCache: any = {};
|
||||
private mempoolInfo: MempoolInfo | undefined;
|
||||
private mempoolChangedCallback: Function | undefined;
|
||||
|
||||
private txPerSecondArray: number[] = [];
|
||||
@@ -21,15 +21,18 @@ class Mempool {
|
||||
this.mempoolChangedCallback = fn;
|
||||
}
|
||||
|
||||
public getMempool(): { [txid: string]: ITransaction } {
|
||||
return this.mempool;
|
||||
public getMempool(): { [txid: string]: SimpleTransaction } {
|
||||
return this.mempoolCache;
|
||||
}
|
||||
|
||||
public setMempool(mempoolData: any) {
|
||||
this.mempool = mempoolData;
|
||||
this.mempoolCache = mempoolData;
|
||||
if (this.mempoolChangedCallback && mempoolData) {
|
||||
this.mempoolChangedCallback(mempoolData);
|
||||
}
|
||||
}
|
||||
|
||||
public getMempoolInfo(): IMempoolInfo | undefined {
|
||||
public getMempoolInfo(): MempoolInfo | undefined {
|
||||
return this.mempoolInfo;
|
||||
}
|
||||
|
||||
@@ -41,52 +44,16 @@ class Mempool {
|
||||
return this.vBytesPerSecond;
|
||||
}
|
||||
|
||||
public async updateMemPoolInfo() {
|
||||
public async getRawTransaction(txId: string): Promise<SimpleTransaction | false> {
|
||||
try {
|
||||
this.mempoolInfo = await bitcoinApi.getMempoolInfo();
|
||||
} catch (err) {
|
||||
console.log('Error getMempoolInfo', err);
|
||||
}
|
||||
}
|
||||
|
||||
public async getRawTransaction(txId: string, isCoinbase = false): Promise<ITransaction | false> {
|
||||
try {
|
||||
const transaction = await bitcoinApi.getRawTransaction(txId);
|
||||
|
||||
let totalOut = 0;
|
||||
transaction.vout.forEach((output) => totalOut += output.value);
|
||||
|
||||
if (config.BACKEND_API === 'electrs') {
|
||||
transaction.feePerWeightUnit = (transaction.fee * 100000000) / transaction.weight || 0;
|
||||
transaction.feePerVsize = (transaction.fee * 100000000) / (transaction.vsize) || 0;
|
||||
transaction.totalOut = totalOut / 100000000;
|
||||
} else {
|
||||
let totalIn = 0;
|
||||
if (!isCoinbase) {
|
||||
for (let i = 0; i < transaction.vin.length; i++) {
|
||||
try {
|
||||
const result = await bitcoinApi.getRawTransaction(transaction.vin[i].txid);
|
||||
transaction.vin[i]['value'] = result.vout[transaction.vin[i].vout].value;
|
||||
totalIn += result.vout[transaction.vin[i].vout].value;
|
||||
} catch (err) {
|
||||
console.log('Locating historical tx error');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (totalIn > totalOut) {
|
||||
transaction.fee = parseFloat((totalIn - totalOut).toFixed(8));
|
||||
transaction.feePerWeightUnit = (transaction.fee * 100000000) / (transaction.vsize * 4) || 0;
|
||||
transaction.feePerVsize = (transaction.fee * 100000000) / (transaction.vsize) || 0;
|
||||
} else if (!isCoinbase) {
|
||||
transaction.fee = 0;
|
||||
transaction.feePerVsize = 0;
|
||||
transaction.feePerWeightUnit = 0;
|
||||
console.log('Minus fee error!');
|
||||
}
|
||||
transaction.totalOut = totalOut;
|
||||
}
|
||||
return transaction;
|
||||
const transaction: Transaction = await bitcoinApi.getRawTransaction(txId);
|
||||
return {
|
||||
txid: transaction.txid,
|
||||
fee: transaction.fee,
|
||||
size: transaction.size,
|
||||
vsize: transaction.weight / 4,
|
||||
feePerVsize: transaction.fee / (transaction.weight / 4)
|
||||
};
|
||||
} catch (e) {
|
||||
console.log(txId + ' not found');
|
||||
return false;
|
||||
@@ -100,12 +67,13 @@ class Mempool {
|
||||
let txCount = 0;
|
||||
try {
|
||||
const transactions = await bitcoinApi.getRawMempool();
|
||||
const diff = transactions.length - Object.keys(this.mempool).length;
|
||||
for (const tx of transactions) {
|
||||
if (!this.mempool[tx]) {
|
||||
const transaction = await this.getRawTransaction(tx);
|
||||
const diff = transactions.length - Object.keys(this.mempoolCache).length;
|
||||
|
||||
for (const txid of transactions) {
|
||||
if (!this.mempoolCache[txid]) {
|
||||
const transaction = await this.getRawTransaction(txid);
|
||||
if (transaction) {
|
||||
this.mempool[tx] = transaction;
|
||||
this.mempoolCache[txid] = transaction;
|
||||
txCount++;
|
||||
this.txPerSecondArray.push(new Date().getTime());
|
||||
this.vBytesPerSecondArray.push({
|
||||
@@ -114,33 +82,34 @@ class Mempool {
|
||||
});
|
||||
hasChange = true;
|
||||
if (diff > 0) {
|
||||
console.log('Calculated fee for transaction ' + txCount + ' / ' + diff);
|
||||
console.log('Fetched transaction ' + txCount + ' / ' + diff);
|
||||
} else {
|
||||
console.log('Calculated fee for transaction ' + txCount);
|
||||
console.log('Fetched transaction ' + txCount);
|
||||
}
|
||||
} else {
|
||||
console.log('Error finding transaction in mempool.');
|
||||
}
|
||||
}
|
||||
|
||||
if ((new Date().getTime()) - start > config.MEMPOOL_REFRESH_RATE_MS * 10) {
|
||||
if ((new Date().getTime()) - start > config.MEMPOOL_REFRESH_RATE_MS) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const newMempool: IMempool = {};
|
||||
// Replace mempool to clear already confirmed transactions
|
||||
const newMempool: any = {};
|
||||
transactions.forEach((tx) => {
|
||||
if (this.mempool[tx]) {
|
||||
newMempool[tx] = this.mempool[tx];
|
||||
if (this.mempoolCache[tx]) {
|
||||
newMempool[tx] = this.mempoolCache[tx];
|
||||
} else {
|
||||
hasChange = true;
|
||||
}
|
||||
});
|
||||
|
||||
this.mempool = newMempool;
|
||||
this.mempoolCache = newMempool;
|
||||
|
||||
if (hasChange && this.mempoolChangedCallback) {
|
||||
this.mempoolChangedCallback(this.mempool);
|
||||
this.mempoolChangedCallback(this.mempoolCache);
|
||||
}
|
||||
|
||||
const end = new Date().getTime();
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
const config = require('../../mempool-config.json');
|
||||
import { ITransaction, IProjectedBlock, IMempool, IProjectedBlockInternal } from '../interfaces';
|
||||
|
||||
class ProjectedBlocks {
|
||||
private transactionsSorted: ITransaction[] = [];
|
||||
|
||||
constructor() {}
|
||||
|
||||
public getProjectedBlockFeesForBlock(index: number) {
|
||||
const projectedBlock = this.getProjectedBlocksInternal()[index];
|
||||
|
||||
if (!projectedBlock) {
|
||||
throw new Error('No projected block for that index');
|
||||
}
|
||||
|
||||
return projectedBlock.txFeePerVsizes.map((fpv) => {
|
||||
return {'fpv': fpv};
|
||||
});
|
||||
}
|
||||
|
||||
public updateProjectedBlocks(memPool: IMempool): void {
|
||||
const latestMempool = memPool;
|
||||
const memPoolArray: ITransaction[] = [];
|
||||
for (const i in latestMempool) {
|
||||
if (latestMempool.hasOwnProperty(i)) {
|
||||
memPoolArray.push(latestMempool[i]);
|
||||
}
|
||||
}
|
||||
memPoolArray.sort((a, b) => b.feePerWeightUnit - a.feePerWeightUnit);
|
||||
this.transactionsSorted = memPoolArray.filter((tx) => tx.feePerWeightUnit);
|
||||
}
|
||||
|
||||
public getProjectedBlocks(txId?: string, numberOfBlocks: number = config.DEFAULT_PROJECTED_BLOCKS_AMOUNT): IProjectedBlock[] {
|
||||
return this.getProjectedBlocksInternal(numberOfBlocks).map((projectedBlock) => {
|
||||
return {
|
||||
blockSize: projectedBlock.blockSize,
|
||||
blockWeight: projectedBlock.blockWeight,
|
||||
nTx: projectedBlock.nTx,
|
||||
minFee: projectedBlock.minFee,
|
||||
maxFee: projectedBlock.maxFee,
|
||||
minWeightFee: projectedBlock.minWeightFee,
|
||||
maxWeightFee: projectedBlock.maxWeightFee,
|
||||
medianFee: projectedBlock.medianFee,
|
||||
fees: projectedBlock.fees,
|
||||
hasMytx: txId ? projectedBlock.txIds.some((tx) => tx === txId) : false
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private getProjectedBlocksInternal(numberOfBlocks: number = config.DEFAULT_PROJECTED_BLOCKS_AMOUNT): IProjectedBlockInternal[] {
|
||||
const projectedBlocks: IProjectedBlockInternal[] = [];
|
||||
let blockWeight = 0;
|
||||
let blockSize = 0;
|
||||
let transactions: ITransaction[] = [];
|
||||
this.transactionsSorted.forEach((tx) => {
|
||||
if (blockWeight + tx.vsize * 4 < 4000000 || projectedBlocks.length === numberOfBlocks) {
|
||||
blockWeight += tx.weight || tx.vsize * 4;
|
||||
blockSize += tx.size;
|
||||
transactions.push(tx);
|
||||
} else {
|
||||
projectedBlocks.push(this.dataToProjectedBlock(transactions, blockSize, blockWeight));
|
||||
blockWeight = 0;
|
||||
blockSize = 0;
|
||||
transactions = [];
|
||||
}
|
||||
});
|
||||
if (transactions.length) {
|
||||
projectedBlocks.push(this.dataToProjectedBlock(transactions, blockSize, blockWeight));
|
||||
}
|
||||
return projectedBlocks;
|
||||
}
|
||||
|
||||
private dataToProjectedBlock(transactions: ITransaction[], blockSize: number, blockWeight: number): IProjectedBlockInternal {
|
||||
return {
|
||||
blockSize: blockSize,
|
||||
blockWeight: blockWeight,
|
||||
nTx: transactions.length,
|
||||
minFee: transactions[transactions.length - 1].feePerVsize,
|
||||
maxFee: transactions[0].feePerVsize,
|
||||
minWeightFee: transactions[transactions.length - 1].feePerWeightUnit,
|
||||
maxWeightFee: transactions[0].feePerWeightUnit,
|
||||
medianFee: this.median(transactions.map((tx) => tx.feePerVsize)),
|
||||
txIds: transactions.map((tx) => tx.txid),
|
||||
txFeePerVsizes: transactions.map((tx) => tx.feePerVsize).reverse(),
|
||||
fees: transactions.map((tx) => tx.fee).reduce((acc, currValue) => acc + currValue),
|
||||
};
|
||||
}
|
||||
|
||||
private median(numbers: number[]) {
|
||||
let medianNr = 0;
|
||||
const numsLen = numbers.length;
|
||||
numbers.sort();
|
||||
if (numsLen % 2 === 0) {
|
||||
medianNr = (numbers[numsLen / 2 - 1] + numbers[numsLen / 2]) / 2;
|
||||
} else {
|
||||
medianNr = numbers[(numsLen - 1) / 2];
|
||||
}
|
||||
return medianNr;
|
||||
}
|
||||
}
|
||||
|
||||
export default new ProjectedBlocks();
|
||||
@@ -1,7 +1,7 @@
|
||||
import memPool from './mempool';
|
||||
import { DB } from '../database';
|
||||
|
||||
import { ITransaction, IMempoolStats } from '../interfaces';
|
||||
import { Statistic, SimpleTransaction } from '../interfaces';
|
||||
|
||||
class Statistics {
|
||||
protected intervalTimer: NodeJS.Timer | undefined;
|
||||
@@ -37,42 +37,28 @@ class Statistics {
|
||||
|
||||
console.log('Running statistics');
|
||||
|
||||
let memPoolArray: ITransaction[] = [];
|
||||
let memPoolArray: SimpleTransaction[] = [];
|
||||
for (const i in currentMempool) {
|
||||
if (currentMempool.hasOwnProperty(i)) {
|
||||
memPoolArray.push(currentMempool[i]);
|
||||
}
|
||||
}
|
||||
// Remove 0 and undefined
|
||||
memPoolArray = memPoolArray.filter((tx) => tx.feePerWeightUnit);
|
||||
memPoolArray = memPoolArray.filter((tx) => tx.feePerVsize);
|
||||
|
||||
if (!memPoolArray.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
memPoolArray.sort((a, b) => a.feePerWeightUnit - b.feePerWeightUnit);
|
||||
memPoolArray.sort((a, b) => a.feePerVsize - b.feePerVsize);
|
||||
const totalWeight = memPoolArray.map((tx) => tx.vsize).reduce((acc, curr) => acc + curr) * 4;
|
||||
const totalFee = memPoolArray.map((tx) => tx.fee).reduce((acc, curr) => acc + curr);
|
||||
|
||||
const logFees = [1, 2, 3, 4, 5, 6, 8, 10, 12, 15, 20, 30, 40, 50, 60, 70, 80, 90, 100, 125, 150, 175, 200,
|
||||
250, 300, 350, 400, 500, 600, 700, 800, 900, 1000, 1200, 1400, 1600, 1800, 2000];
|
||||
|
||||
const weightUnitFees: { [feePerWU: number]: number } = {};
|
||||
const weightVsizeFees: { [feePerWU: number]: number } = {};
|
||||
|
||||
memPoolArray.forEach((transaction) => {
|
||||
for (let i = 0; i < logFees.length; i++) {
|
||||
if ((logFees[i] === 2000 && transaction.feePerWeightUnit >= 2000) || transaction.feePerWeightUnit <= logFees[i]) {
|
||||
if (weightUnitFees[logFees[i]]) {
|
||||
weightUnitFees[logFees[i]] += transaction.vsize * 4;
|
||||
} else {
|
||||
weightUnitFees[logFees[i]] = transaction.vsize * 4;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
memPoolArray.forEach((transaction) => {
|
||||
for (let i = 0; i < logFees.length; i++) {
|
||||
if ((logFees[i] === 2000 && transaction.feePerVsize >= 2000) || transaction.feePerVsize <= logFees[i]) {
|
||||
@@ -93,10 +79,7 @@ class Statistics {
|
||||
vbytes_per_second: Math.round(vBytesPerSecond),
|
||||
mempool_byte_weight: totalWeight,
|
||||
total_fee: totalFee,
|
||||
fee_data: JSON.stringify({
|
||||
'wu': weightUnitFees,
|
||||
'vsize': weightVsizeFees
|
||||
}),
|
||||
fee_data: '',
|
||||
vsize_1: weightVsizeFees['1'] || 0,
|
||||
vsize_2: weightVsizeFees['2'] || 0,
|
||||
vsize_3: weightVsizeFees['3'] || 0,
|
||||
@@ -143,7 +126,7 @@ class Statistics {
|
||||
}
|
||||
}
|
||||
|
||||
private async $create(statistics: IMempoolStats): Promise<number | undefined> {
|
||||
private async $create(statistics: Statistic): Promise<number | undefined> {
|
||||
try {
|
||||
const connection = await DB.pool.getConnection();
|
||||
const query = `INSERT INTO statistics(
|
||||
@@ -295,7 +278,7 @@ class Statistics {
|
||||
AVG(vsize_2000) AS vsize_2000 FROM statistics GROUP BY UNIX_TIMESTAMP(added) DIV ${groupBy} ORDER BY id DESC LIMIT ${days}`;
|
||||
}
|
||||
|
||||
public async $get(id: number): Promise<IMempoolStats | undefined> {
|
||||
public async $get(id: number): Promise<Statistic | undefined> {
|
||||
try {
|
||||
const connection = await DB.pool.getConnection();
|
||||
const query = `SELECT * FROM statistics WHERE id = ?`;
|
||||
@@ -307,7 +290,7 @@ class Statistics {
|
||||
}
|
||||
}
|
||||
|
||||
public async $list2H(): Promise<IMempoolStats[]> {
|
||||
public async $list2H(): Promise<Statistic[]> {
|
||||
try {
|
||||
const connection = await DB.pool.getConnection();
|
||||
const query = `SELECT * FROM statistics ORDER BY id DESC LIMIT 120`;
|
||||
@@ -320,7 +303,7 @@ class Statistics {
|
||||
}
|
||||
}
|
||||
|
||||
public async $list24H(): Promise<IMempoolStats[]> {
|
||||
public async $list24H(): Promise<Statistic[]> {
|
||||
try {
|
||||
const connection = await DB.pool.getConnection();
|
||||
const query = this.getQueryForDays(120, 720);
|
||||
@@ -332,7 +315,7 @@ class Statistics {
|
||||
}
|
||||
}
|
||||
|
||||
public async $list1W(): Promise<IMempoolStats[]> {
|
||||
public async $list1W(): Promise<Statistic[]> {
|
||||
try {
|
||||
const connection = await DB.pool.getConnection();
|
||||
const query = this.getQueryForDays(120, 5040);
|
||||
@@ -345,7 +328,7 @@ class Statistics {
|
||||
}
|
||||
}
|
||||
|
||||
public async $list1M(): Promise<IMempoolStats[]> {
|
||||
public async $list1M(): Promise<Statistic[]> {
|
||||
try {
|
||||
const connection = await DB.pool.getConnection();
|
||||
const query = this.getQueryForDays(120, 20160);
|
||||
@@ -358,7 +341,7 @@ class Statistics {
|
||||
}
|
||||
}
|
||||
|
||||
public async $list3M(): Promise<IMempoolStats[]> {
|
||||
public async $list3M(): Promise<Statistic[]> {
|
||||
try {
|
||||
const connection = await DB.pool.getConnection();
|
||||
const query = this.getQueryForDays(120, 60480);
|
||||
@@ -371,7 +354,7 @@ class Statistics {
|
||||
}
|
||||
}
|
||||
|
||||
public async $list6M(): Promise<IMempoolStats[]> {
|
||||
public async $list6M(): Promise<Statistic[]> {
|
||||
try {
|
||||
const connection = await DB.pool.getConnection();
|
||||
const query = this.getQueryForDays(120, 120960);
|
||||
|
||||
@@ -6,40 +6,42 @@ import * as http from 'http';
|
||||
import * as https from 'https';
|
||||
import * as WebSocket from 'ws';
|
||||
|
||||
import bitcoinApi from './api/bitcoin/bitcoin-api-factory';
|
||||
import diskCache from './api/disk-cache';
|
||||
import memPool from './api/mempool';
|
||||
import blocks from './api/blocks';
|
||||
import projectedBlocks from './api/projected-blocks';
|
||||
import statistics from './api/statistics';
|
||||
import { IBlock, IMempool, ITransaction, IMempoolStats } from './interfaces';
|
||||
|
||||
import routes from './routes';
|
||||
import blocks from './api/blocks';
|
||||
import memPool from './api/mempool';
|
||||
import mempoolBlocks from './api/mempool-blocks';
|
||||
import diskCache from './api/disk-cache';
|
||||
import statistics from './api/statistics';
|
||||
|
||||
import { Block, SimpleTransaction, Statistic } from './interfaces';
|
||||
|
||||
import fiatConversion from './api/fiat-conversion';
|
||||
|
||||
class MempoolSpace {
|
||||
class Server {
|
||||
private wss: WebSocket.Server;
|
||||
private server: https.Server | http.Server;
|
||||
private app: any;
|
||||
|
||||
constructor() {
|
||||
this.app = express();
|
||||
|
||||
this.app
|
||||
.use((req, res, next) => {
|
||||
.use((req, res, next) => {
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
next();
|
||||
})
|
||||
.use(compression());
|
||||
if (config.ENV === 'dev') {
|
||||
this.server = http.createServer(this.app);
|
||||
this.wss = new WebSocket.Server({ server: this.server });
|
||||
} else {
|
||||
|
||||
if (config.SSL === true) {
|
||||
const credentials = {
|
||||
cert: fs.readFileSync('/etc/letsencrypt/live/mempool.space/fullchain.pem'),
|
||||
key: fs.readFileSync('/etc/letsencrypt/live/mempool.space/privkey.pem'),
|
||||
cert: fs.readFileSync(config.SSL_CERT_FILE_PATH),
|
||||
key: fs.readFileSync(config.SSL_KEY_FILE_PATH),
|
||||
};
|
||||
this.server = https.createServer(credentials, this.app);
|
||||
this.wss = new WebSocket.Server({ server: this.server });
|
||||
} else {
|
||||
this.server = http.createServer(this.app);
|
||||
this.wss = new WebSocket.Server({ server: this.server });
|
||||
}
|
||||
|
||||
this.setUpRoutes();
|
||||
@@ -50,20 +52,15 @@ class MempoolSpace {
|
||||
statistics.startStatistics();
|
||||
fiatConversion.startService();
|
||||
|
||||
const opts = {
|
||||
host: '127.0.0.1',
|
||||
port: 8999
|
||||
};
|
||||
this.server.listen(opts, () => {
|
||||
console.log(`Server started on ${opts.host}:${opts.port}`);
|
||||
this.server.listen(config.HTTP_PORT, () => {
|
||||
console.log(`Server started on port ${config.HTTP_PORT}`);
|
||||
});
|
||||
}
|
||||
|
||||
private async runMempoolIntervalFunctions() {
|
||||
await blocks.updateBlocks();
|
||||
await memPool.updateMemPoolInfo();
|
||||
await memPool.updateMempool();
|
||||
setTimeout(this.runMempoolIntervalFunctions.bind(this), config.MEMPOOL_REFRESH_RATE_MS);
|
||||
setTimeout(this.runMempoolIntervalFunctions.bind(this), config.ELECTRS_POLL_RATE_MS);
|
||||
}
|
||||
|
||||
private setUpMempoolCache() {
|
||||
@@ -81,161 +78,36 @@ class MempoolSpace {
|
||||
|
||||
private setUpWebsocketHandling() {
|
||||
this.wss.on('connection', (client: WebSocket) => {
|
||||
let theBlocks = blocks.getBlocks();
|
||||
theBlocks = theBlocks.concat([]).splice(theBlocks.length - config.INITIAL_BLOCK_AMOUNT);
|
||||
const formatedBlocks = theBlocks.map((b) => blocks.formatBlock(b));
|
||||
|
||||
client.send(JSON.stringify({
|
||||
'mempoolInfo': memPool.getMempoolInfo(),
|
||||
'blocks': formatedBlocks,
|
||||
'projectedBlocks': projectedBlocks.getProjectedBlocks(),
|
||||
'txPerSecond': memPool.getTxPerSecond(),
|
||||
'vBytesPerSecond': memPool.getVBytesPerSecond(),
|
||||
'conversions': fiatConversion.getTickers()['BTCUSD'],
|
||||
}));
|
||||
|
||||
client.on('message', async (message: any) => {
|
||||
client.on('message', (message: any) => {
|
||||
try {
|
||||
const parsedMessage = JSON.parse(message);
|
||||
|
||||
if (parsedMessage.action === 'want') {
|
||||
client['want-stats'] = parsedMessage.data.indexOf('stats') > -1;
|
||||
client['want-blocks'] = parsedMessage.data.indexOf('blocks') > -1;
|
||||
client['want-projected-blocks'] = parsedMessage.data.indexOf('projected-blocks') > -1;
|
||||
client['want-live-2h-chart'] = parsedMessage.data.indexOf('live-2h-chart') > -1;
|
||||
}
|
||||
|
||||
if (parsedMessage.action === 'track-tx' && parsedMessage.txId && /^[a-fA-F0-9]{64}$/.test(parsedMessage.txId)) {
|
||||
const tx = await memPool.getRawTransaction(parsedMessage.txId);
|
||||
if (tx) {
|
||||
console.log('Now tracking: ' + parsedMessage.txId);
|
||||
client['trackingTx'] = true;
|
||||
client['txId'] = parsedMessage.txId;
|
||||
client['tx'] = tx;
|
||||
|
||||
if (tx.blockhash) {
|
||||
const currentBlocks = blocks.getBlocks();
|
||||
const foundBlock = currentBlocks.find((block) => block.tx && block.tx.some((i: string) => i === parsedMessage.txId));
|
||||
if (foundBlock) {
|
||||
console.log('Found block by looking in local cache');
|
||||
client['blockHeight'] = foundBlock.height;
|
||||
} else {
|
||||
const theBlock = await bitcoinApi.getBlockAndTransactions(tx.blockhash);
|
||||
if (theBlock) {
|
||||
client['blockHeight'] = theBlock.height;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
client['blockHeight'] = 0;
|
||||
}
|
||||
client.send(JSON.stringify({
|
||||
'projectedBlocks': projectedBlocks.getProjectedBlocks(client['txId']),
|
||||
'track-tx': {
|
||||
tracking: true,
|
||||
blockHeight: client['blockHeight'],
|
||||
tx: client['tx'],
|
||||
}
|
||||
}));
|
||||
} else {
|
||||
console.log('TX NOT FOUND, NOT TRACKING');
|
||||
client['trackingTx'] = false;
|
||||
client['blockHeight'] = 0;
|
||||
client['tx'] = null;
|
||||
client.send(JSON.stringify({
|
||||
'track-tx': {
|
||||
tracking: false,
|
||||
blockHeight: 0,
|
||||
message: 'not-found',
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
if (parsedMessage.action === 'stop-tracking-tx') {
|
||||
console.log('STOP TRACKING');
|
||||
client['trackingTx'] = false;
|
||||
client.send(JSON.stringify({
|
||||
'track-tx': {
|
||||
tracking: false,
|
||||
blockHeight: 0,
|
||||
message: 'not-found',
|
||||
}
|
||||
}));
|
||||
if (parsedMessage && parsedMessage.txId && /^[a-fA-F0-9]{64}$/.test(parsedMessage.txId)) {
|
||||
client['txId'] = parsedMessage.txId;
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
});
|
||||
|
||||
client.on('close', () => {
|
||||
client['trackingTx'] = false;
|
||||
});
|
||||
const _blocks = blocks.getBlocks();
|
||||
if (!_blocks) {
|
||||
return;
|
||||
}
|
||||
client.send(JSON.stringify({
|
||||
'blocks': _blocks,
|
||||
'conversions': fiatConversion.getTickers()['BTCUSD'],
|
||||
'mempool-blocks': mempoolBlocks.getMempoolBlocks(),
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
blocks.setNewBlockCallback((block: IBlock) => {
|
||||
const formattedBlocks = blocks.formatBlock(block);
|
||||
|
||||
this.wss.clients.forEach((client) => {
|
||||
if (client.readyState !== WebSocket.OPEN) {
|
||||
return;
|
||||
}
|
||||
|
||||
const response = {};
|
||||
|
||||
if (client['trackingTx'] === true && client['blockHeight'] === 0) {
|
||||
if (block.tx.some((tx: ITransaction) => tx === client['txId'])) {
|
||||
client['blockHeight'] = block.height;
|
||||
}
|
||||
}
|
||||
|
||||
response['track-tx'] = {
|
||||
tracking: client['trackingTx'] || false,
|
||||
blockHeight: client['blockHeight'],
|
||||
};
|
||||
|
||||
response['block'] = formattedBlocks;
|
||||
|
||||
client.send(JSON.stringify(response));
|
||||
});
|
||||
});
|
||||
|
||||
memPool.setMempoolChangedCallback((newMempool: IMempool) => {
|
||||
projectedBlocks.updateProjectedBlocks(newMempool);
|
||||
|
||||
const pBlocks = projectedBlocks.getProjectedBlocks();
|
||||
const mempoolInfo = memPool.getMempoolInfo();
|
||||
const txPerSecond = memPool.getTxPerSecond();
|
||||
const vBytesPerSecond = memPool.getVBytesPerSecond();
|
||||
|
||||
this.wss.clients.forEach((client: WebSocket) => {
|
||||
if (client.readyState !== WebSocket.OPEN) {
|
||||
return;
|
||||
}
|
||||
|
||||
const response = {};
|
||||
|
||||
if (client['want-stats']) {
|
||||
response['mempoolInfo'] = mempoolInfo;
|
||||
response['txPerSecond'] = txPerSecond;
|
||||
response['vBytesPerSecond'] = vBytesPerSecond;
|
||||
response['track-tx'] = {
|
||||
tracking: client['trackingTx'] || false,
|
||||
blockHeight: client['blockHeight'],
|
||||
};
|
||||
}
|
||||
|
||||
if (client['want-projected-blocks'] && client['trackingTx'] && client['blockHeight'] === 0) {
|
||||
response['projectedBlocks'] = projectedBlocks.getProjectedBlocks(client['txId']);
|
||||
} else if (client['want-projected-blocks']) {
|
||||
response['projectedBlocks'] = pBlocks;
|
||||
}
|
||||
|
||||
if (Object.keys(response).length) {
|
||||
client.send(JSON.stringify(response));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
statistics.setNewStatisticsEntryCallback((stats: IMempoolStats) => {
|
||||
statistics.setNewStatisticsEntryCallback((stats: Statistic) => {
|
||||
this.wss.clients.forEach((client: WebSocket) => {
|
||||
if (client.readyState !== WebSocket.OPEN) {
|
||||
return;
|
||||
@@ -248,14 +120,47 @@ class MempoolSpace {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
blocks.setNewBlockCallback((block: Block, txIds: string[]) => {
|
||||
this.wss.clients.forEach((client) => {
|
||||
if (client.readyState !== WebSocket.OPEN) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (client['txId'] && txIds.indexOf(client['txId']) > -1) {
|
||||
client['txId'] = null;
|
||||
client.send(JSON.stringify({
|
||||
'block': block,
|
||||
'txConfirmed': true,
|
||||
}));
|
||||
} else {
|
||||
client.send(JSON.stringify({
|
||||
'block': block,
|
||||
}));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
memPool.setMempoolChangedCallback((newMempool: { [txid: string]: SimpleTransaction }) => {
|
||||
mempoolBlocks.updateMempoolBlocks(newMempool);
|
||||
const pBlocks = mempoolBlocks.getMempoolBlocks();
|
||||
|
||||
this.wss.clients.forEach((client: WebSocket) => {
|
||||
if (client.readyState !== WebSocket.OPEN) {
|
||||
return;
|
||||
}
|
||||
|
||||
client.send(JSON.stringify({
|
||||
'mempool-blocks': pBlocks
|
||||
}));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private setUpRoutes() {
|
||||
this.app
|
||||
.get(config.API_ENDPOINT + 'transactions/height/:id', routes.$getgetTransactionsForBlock)
|
||||
.get(config.API_ENDPOINT + 'transactions/projected/:id', routes.getgetTransactionsForProjectedBlock)
|
||||
.get(config.API_ENDPOINT + 'fees/recommended', routes.getRecommendedFees)
|
||||
.get(config.API_ENDPOINT + 'fees/projected-blocks', routes.getProjectedBlocks)
|
||||
.get(config.API_ENDPOINT + 'fees/mempool-blocks', routes.getMempoolBlocks)
|
||||
.get(config.API_ENDPOINT + 'statistics/2h', routes.get2HStatistics)
|
||||
.get(config.API_ENDPOINT + 'statistics/24h', routes.get24HStatistics.bind(routes))
|
||||
.get(config.API_ENDPOINT + 'statistics/1w', routes.get1WHStatistics.bind(routes))
|
||||
@@ -263,22 +168,7 @@ class MempoolSpace {
|
||||
.get(config.API_ENDPOINT + 'statistics/3m', routes.get3MStatistics.bind(routes))
|
||||
.get(config.API_ENDPOINT + 'statistics/6m', routes.get6MStatistics.bind(routes))
|
||||
;
|
||||
|
||||
if (config.BACKEND_API === 'electrs') {
|
||||
this.app
|
||||
.get(config.API_ENDPOINT + 'explorer/blocks', routes.getBlocks)
|
||||
.get(config.API_ENDPOINT + 'explorer/blocks/:height', routes.getBlocks)
|
||||
.get(config.API_ENDPOINT + 'explorer/tx/:id', routes.getRawTransaction)
|
||||
.get(config.API_ENDPOINT + 'explorer/block/:hash', routes.getBlock)
|
||||
.get(config.API_ENDPOINT + 'explorer/block/:hash/tx', routes.getBlockTransactions)
|
||||
.get(config.API_ENDPOINT + 'explorer/block/:hash/tx/:index', routes.getBlockTransactionsFromIndex)
|
||||
.get(config.API_ENDPOINT + 'explorer/address/:address', routes.getAddress)
|
||||
.get(config.API_ENDPOINT + 'explorer/address/:address/tx', routes.getAddressTransactions)
|
||||
.get(config.API_ENDPOINT + 'explorer/address/:address/tx/chain/:txid', routes.getAddressTransactionsFromTxid)
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
const mempoolSpace = new MempoolSpace();
|
||||
const server = new Server();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export interface IMempoolInfo {
|
||||
export interface MempoolInfo {
|
||||
size: number;
|
||||
bytes: number;
|
||||
usage?: number;
|
||||
@@ -7,80 +7,110 @@ export interface IMempoolInfo {
|
||||
minrelaytxfee?: number;
|
||||
}
|
||||
|
||||
export interface ITransaction {
|
||||
export interface MempoolBlock {
|
||||
blockSize: number;
|
||||
blockVSize: number;
|
||||
nTx: number;
|
||||
medianFee: number;
|
||||
feeRange: number[];
|
||||
}
|
||||
|
||||
export interface Transaction {
|
||||
txid: string;
|
||||
hash: string;
|
||||
version: number;
|
||||
size: number;
|
||||
vsize: number;
|
||||
weight: number;
|
||||
locktime: number;
|
||||
fee: number;
|
||||
size: number;
|
||||
weight: number;
|
||||
vin: Vin[];
|
||||
vout: Vout[];
|
||||
hex: string;
|
||||
status: Status;
|
||||
}
|
||||
|
||||
export interface SimpleTransaction {
|
||||
txid: string;
|
||||
fee: number;
|
||||
feePerWeightUnit: number;
|
||||
feePerVsize: number;
|
||||
blockhash?: string;
|
||||
confirmations?: number;
|
||||
time?: number;
|
||||
blocktime?: number;
|
||||
totalOut?: number;
|
||||
}
|
||||
|
||||
export interface IBlock {
|
||||
hash: string;
|
||||
confirmations: number;
|
||||
strippedsize: number;
|
||||
size: number;
|
||||
weight: number;
|
||||
height: number;
|
||||
version: number;
|
||||
versionHex: string;
|
||||
merkleroot: string;
|
||||
tx: any;
|
||||
time: number;
|
||||
mediantime: number;
|
||||
nonce: number;
|
||||
bits: string;
|
||||
difficulty: number;
|
||||
chainwork: string;
|
||||
nTx: number;
|
||||
previousblockhash: string;
|
||||
fees: number;
|
||||
|
||||
minFee?: number;
|
||||
maxFee?: number;
|
||||
medianFee?: number;
|
||||
vsize: number;
|
||||
feePerVsize: number;
|
||||
}
|
||||
|
||||
interface ScriptSig {
|
||||
asm: string;
|
||||
hex: string;
|
||||
export interface Prevout {
|
||||
scriptpubkey: string;
|
||||
scriptpubkey_asm: string;
|
||||
scriptpubkey_type: string;
|
||||
scriptpubkey_address: string;
|
||||
value: number;
|
||||
}
|
||||
|
||||
interface Vin {
|
||||
export interface Vin {
|
||||
txid: string;
|
||||
vout: number;
|
||||
scriptSig: ScriptSig;
|
||||
sequence: number;
|
||||
prevout: Prevout;
|
||||
scriptsig: string;
|
||||
scriptsig_asm: string;
|
||||
inner_redeemscript_asm?: string;
|
||||
is_coinbase: boolean;
|
||||
sequence: any;
|
||||
witness?: string[];
|
||||
inner_witnessscript_asm?: string;
|
||||
}
|
||||
|
||||
interface ScriptPubKey {
|
||||
asm: string;
|
||||
hex: string;
|
||||
reqSigs: number;
|
||||
type: string;
|
||||
addresses: string[];
|
||||
}
|
||||
|
||||
interface Vout {
|
||||
export interface Vout {
|
||||
scriptpubkey: string;
|
||||
scriptpubkey_asm: string;
|
||||
scriptpubkey_type: string;
|
||||
scriptpubkey_address: string;
|
||||
value: number;
|
||||
n: number;
|
||||
scriptPubKey: ScriptPubKey;
|
||||
}
|
||||
|
||||
export interface IMempoolStats {
|
||||
export interface Status {
|
||||
confirmed: boolean;
|
||||
block_height?: number;
|
||||
block_hash?: string;
|
||||
block_time?: number;
|
||||
}
|
||||
|
||||
export interface Block {
|
||||
id: string;
|
||||
height: number;
|
||||
version: number;
|
||||
timestamp: number;
|
||||
tx_count: number;
|
||||
size: number;
|
||||
weight: number;
|
||||
merkle_root: string;
|
||||
previousblockhash: string;
|
||||
nonce: any;
|
||||
bits: number;
|
||||
|
||||
medianFee?: number;
|
||||
feeRange?: number[];
|
||||
}
|
||||
|
||||
export interface Address {
|
||||
address: string;
|
||||
chain_stats: ChainStats;
|
||||
mempool_stats: MempoolStats;
|
||||
}
|
||||
|
||||
export interface ChainStats {
|
||||
funded_txo_count: number;
|
||||
funded_txo_sum: number;
|
||||
spent_txo_count: number;
|
||||
spent_txo_sum: number;
|
||||
tx_count: number;
|
||||
}
|
||||
|
||||
export interface MempoolStats {
|
||||
funded_txo_count: number;
|
||||
funded_txo_sum: number;
|
||||
spent_txo_count: number;
|
||||
spent_txo_sum: number;
|
||||
tx_count: number;
|
||||
}
|
||||
|
||||
export interface Statistic {
|
||||
id?: number;
|
||||
added: string;
|
||||
unconfirmed_transactions: number;
|
||||
@@ -130,23 +160,10 @@ export interface IMempoolStats {
|
||||
vsize_2000: number;
|
||||
}
|
||||
|
||||
export interface IProjectedBlockInternal extends IProjectedBlock {
|
||||
txIds: string[];
|
||||
txFeePerVsizes: number[];
|
||||
export interface Outspend {
|
||||
spent: boolean;
|
||||
txid: string;
|
||||
vin: number;
|
||||
status: Status;
|
||||
}
|
||||
|
||||
export interface IProjectedBlock {
|
||||
blockSize: number;
|
||||
blockWeight: number;
|
||||
maxFee: number;
|
||||
maxWeightFee: number;
|
||||
medianFee: number;
|
||||
minFee: number;
|
||||
minWeightFee: number;
|
||||
nTx: number;
|
||||
fees: number;
|
||||
hasMyTxId?: boolean;
|
||||
}
|
||||
|
||||
export interface IMempool { [txid: string]: ITransaction; }
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import statistics from './api/statistics';
|
||||
import feeApi from './api/fee-api';
|
||||
import projectedBlocks from './api/projected-blocks';
|
||||
import bitcoinApi from './api/bitcoin/bitcoin-api-factory';
|
||||
import mempoolBlocks from './api/mempool-blocks';
|
||||
|
||||
class Routes {
|
||||
private cache = {};
|
||||
@@ -50,149 +49,14 @@ class Routes {
|
||||
res.send(result);
|
||||
}
|
||||
|
||||
public async $getgetTransactionsForBlock(req, res) {
|
||||
const result = await feeApi.$getTransactionsForBlock(req.params.id);
|
||||
res.send(result);
|
||||
}
|
||||
|
||||
public async getgetTransactionsForProjectedBlock(req, res) {
|
||||
public async getMempoolBlocks(req, res) {
|
||||
try {
|
||||
const result = await projectedBlocks.getProjectedBlockFeesForBlock(req.params.id);
|
||||
const result = await mempoolBlocks.getMempoolBlocks();
|
||||
res.send(result);
|
||||
} catch (e) {
|
||||
res.status(500).send(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
public async getProjectedBlocks(req, res) {
|
||||
try {
|
||||
let txId: string | undefined;
|
||||
if (req.query.txId && /^[a-fA-F0-9]{64}$/.test(req.query.txId)) {
|
||||
txId = req.query.txId;
|
||||
}
|
||||
const result = await projectedBlocks.getProjectedBlocks(txId, 6);
|
||||
res.send(result);
|
||||
} catch (e) {
|
||||
res.status(500).send(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
public async getBlocks(req, res) {
|
||||
try {
|
||||
let result: string;
|
||||
if (req.params.height) {
|
||||
result = await bitcoinApi.getBlocksFromHeight(req.params.height);
|
||||
} else {
|
||||
result = await bitcoinApi.getBlocks();
|
||||
}
|
||||
res.send(result);
|
||||
} catch (e) {
|
||||
res.status(500).send(e.message);
|
||||
}
|
||||
}
|
||||
|
||||
public async getRawTransaction(req, res) {
|
||||
try {
|
||||
const result = await bitcoinApi.getRawTransaction(req.params.id);
|
||||
res.send(result);
|
||||
} catch (e) {
|
||||
if (e.response) {
|
||||
res.status(e.response.status).send(e.response.data);
|
||||
} else {
|
||||
res.status(500, e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async getBlock(req, res) {
|
||||
try {
|
||||
const result = await bitcoinApi.getBlock(req.params.hash);
|
||||
res.send(result);
|
||||
} catch (e) {
|
||||
if (e.response) {
|
||||
res.status(e.response.status).send(e.response.data);
|
||||
} else {
|
||||
res.status(500, e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async getBlockTransactions(req, res) {
|
||||
try {
|
||||
const result = await bitcoinApi.getBlockTransactions(req.params.hash);
|
||||
res.send(result);
|
||||
} catch (e) {
|
||||
if (e.response) {
|
||||
res.status(e.response.status).send(e.response.data);
|
||||
} else {
|
||||
if (e.response) {
|
||||
res.status(e.response.status).send(e.response.data);
|
||||
} else {
|
||||
res.status(500, e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async getBlockTransactionsFromIndex(req, res) {
|
||||
try {
|
||||
const result = await bitcoinApi.getBlockTransactionsFromIndex(req.params.hash, req.params.index);
|
||||
res.send(result);
|
||||
} catch (e) {
|
||||
if (e.response) {
|
||||
res.status(e.response.status).send(e.response.data);
|
||||
} else {
|
||||
if (e.response) {
|
||||
res.status(e.response.status).send(e.response.data);
|
||||
} else {
|
||||
res.status(500, e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async getAddress(req, res) {
|
||||
try {
|
||||
const result = await bitcoinApi.getAddress(req.params.address);
|
||||
res.send(result);
|
||||
} catch (e) {
|
||||
if (e.response) {
|
||||
res.status(e.response.status).send(e.response.data);
|
||||
} else {
|
||||
if (e.response) {
|
||||
res.status(e.response.status).send(e.response.data);
|
||||
} else {
|
||||
res.status(500, e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async getAddressTransactions(req, res) {
|
||||
try {
|
||||
const result = await bitcoinApi.getAddressTransactions(req.params.address);
|
||||
res.send(result);
|
||||
} catch (e) {
|
||||
if (e.response) {
|
||||
res.status(e.response.status).send(e.response.data);
|
||||
} else {
|
||||
res.status(500, e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async getAddressTransactionsFromTxid(req, res) {
|
||||
try {
|
||||
const result = await bitcoinApi.getAddressTransactionsFromLastSeenTxid(req.params.address, req.params.txid);
|
||||
res.send(result);
|
||||
} catch (e) {
|
||||
if (e.response) {
|
||||
res.status(e.response.status).send(e.response.data);
|
||||
} else {
|
||||
res.status(500, e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new Routes();
|
||||
|
||||
@@ -31,6 +31,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/caseless/-/caseless-0.12.2.tgz#f65d3d6389e01eeb458bd54dc8f52b95a9463bc8"
|
||||
integrity sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==
|
||||
|
||||
"@types/compression@^1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/compression/-/compression-1.0.1.tgz#f3682a6b3ce2dbd4aece48547153ebc592281fa7"
|
||||
integrity sha512-GuoIYzD70h+4JUqUabsm31FGqvpCYHGKcLtor7nQ/YvUyNX0o9SJZ9boFI5HjFfbOda5Oe/XOvNK6FES8Y/79w==
|
||||
dependencies:
|
||||
"@types/express" "*"
|
||||
|
||||
"@types/connect@*":
|
||||
version "3.4.32"
|
||||
resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.32.tgz#aa0e9616b9435ccad02bc52b5b454ffc2c70ba28"
|
||||
@@ -39,17 +46,17 @@
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/express-serve-static-core@*":
|
||||
version "4.16.10"
|
||||
resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.16.10.tgz#3c1313c6e6b75594561b473a286f016a9abf2132"
|
||||
integrity sha512-gM6evDj0OvTILTRKilh9T5dTaGpv1oYiFcJAfgSejuMJgGJUsD9hKEU2lB4aiTNy4WwChxRnjfYFuBQsULzsJw==
|
||||
version "4.17.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.0.tgz#e80c25903df5800e926402b7e8267a675c54a281"
|
||||
integrity sha512-Xnub7w57uvcBqFdIGoRg1KhNOeEj0vB6ykUM7uFWyxvbdE89GFyqgmUcanAriMr4YOxNFZBAWkfcWIb4WBPt3g==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
"@types/range-parser" "*"
|
||||
|
||||
"@types/express@^4.16.0":
|
||||
version "4.17.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.1.tgz#4cf7849ae3b47125a567dfee18bfca4254b88c5c"
|
||||
integrity sha512-VfH/XCP0QbQk5B5puLqTLEeFgR8lfCJHZJKkInZ9mkYd+u8byX0kztXEQxEk4wZXJs8HI+7km2ALXjn4YKcX9w==
|
||||
"@types/express@*", "@types/express@^4.17.2":
|
||||
version "4.17.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.2.tgz#a0fb7a23d8855bac31bc01d5a58cadd9b2173e6c"
|
||||
integrity sha512-5mHFNyavtLoJmnusB8OKJ5bshSzw+qkMIBAobLrIM48HJvunFva9mOa6aBwh64lBFyNwBbs0xiEFuj4eU/NjCA==
|
||||
dependencies:
|
||||
"@types/body-parser" "*"
|
||||
"@types/express-serve-static-core" "*"
|
||||
@@ -60,20 +67,10 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.1.tgz#dc488842312a7f075149312905b5e3c0b054c79d"
|
||||
integrity sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==
|
||||
|
||||
"@types/mysql2@github:types/mysql2":
|
||||
version "1.0.0"
|
||||
resolved "https://codeload.github.com/types/mysql2/tar.gz/217efd4ccf9eccc0797522aa745d8a9e264f6a75"
|
||||
dependencies:
|
||||
"@types/mysql" types/mysql#v2.0.0
|
||||
|
||||
"@types/mysql@types/mysql#v2.0.0":
|
||||
version "2.0.0"
|
||||
resolved "https://codeload.github.com/types/mysql/tar.gz/da645a82afd66419ed439dddf174648aa68ba1f9"
|
||||
|
||||
"@types/node@*":
|
||||
version "12.11.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.11.1.tgz#1fd7b821f798b7fa29f667a1be8f3442bb8922a3"
|
||||
integrity sha512-TJtwsqZ39pqcljJpajeoofYRfeZ7/I/OMUQ5pR4q5wOKf2ocrUvBAZUMhWsOvKx3dVc/aaV5GluBivt0sWqA5A==
|
||||
version "12.12.17"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.17.tgz#191b71e7f4c325ee0fb23bc4a996477d92b8c39b"
|
||||
integrity sha512-Is+l3mcHvs47sKy+afn2O1rV4ldZFU7W8101cNlOd+MRbjM4Onida8jSZnJdTe/0Pcf25g9BNIUsuugmE6puHA==
|
||||
|
||||
"@types/range-parser@*":
|
||||
version "1.2.3"
|
||||
@@ -99,14 +96,14 @@
|
||||
"@types/mime" "*"
|
||||
|
||||
"@types/tough-cookie@*":
|
||||
version "2.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-2.3.5.tgz#9da44ed75571999b65c37b60c9b2b88db54c585d"
|
||||
integrity sha512-SCcK7mvGi3+ZNz833RRjFIxrn4gI1PPR3NtuIS+6vMkvmsGjosqTJwRt5bAEFLRz+wtJMWv8+uOnZf2hi2QXTg==
|
||||
version "2.3.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-2.3.6.tgz#c880579e087d7a0db13777ff8af689f4ffc7b0d5"
|
||||
integrity sha512-wHNBMnkoEBiRAd3s8KTKwIuO9biFtTf0LehITzBhSco+HQI0xkXZbLOD55SW3Aqw3oUkHstkm5SPv58yaAdFPQ==
|
||||
|
||||
"@types/ws@^6.0.1":
|
||||
version "6.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-6.0.3.tgz#b772375ba59d79066561c8d87500144d674ba6b3"
|
||||
integrity sha512-yBTM0P05Tx9iXGq00BbJPo37ox68R5vaGTXivs6RGh/BQ6QP5zqZDGWdAO6JbRE/iR1l80xeGAwCQS2nMV9S/w==
|
||||
"@types/ws@^6.0.4":
|
||||
version "6.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-6.0.4.tgz#7797707c8acce8f76d8c34b370d4645b70421ff1"
|
||||
integrity sha512-PpPrX7SZW9re6+Ha8ojZG4Se8AZXgf0GK6zmfqEuCsY49LFDNXO3SByp44X3dFEqtB73lkCDAdUazhAjVPiNwg==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
@@ -159,7 +156,7 @@ assert-plus@1.0.0, assert-plus@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
|
||||
integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=
|
||||
|
||||
async-limiter@~1.0.0:
|
||||
async-limiter@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd"
|
||||
integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==
|
||||
@@ -175,17 +172,9 @@ aws-sign2@~0.7.0:
|
||||
integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=
|
||||
|
||||
aws4@^1.8.0:
|
||||
version "1.8.0"
|
||||
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
|
||||
integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==
|
||||
|
||||
axios@^0.19.0:
|
||||
version "0.19.0"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.0.tgz#8e09bff3d9122e133f7b8101c8fbdd00ed3d2ab8"
|
||||
integrity sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ==
|
||||
dependencies:
|
||||
follow-redirects "1.5.10"
|
||||
is-buffer "^2.0.2"
|
||||
version "1.9.0"
|
||||
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.0.tgz#24390e6ad61386b0a747265754d2a17219de862c"
|
||||
integrity sha512-Uvq6hVe90D0B2WEnUqtdgY1bATGz3mw33nH9Y+dmA+w5DHvUmBgkr5rM/KCHpCsiFNRUfokW/szpPPgMK2hm4A==
|
||||
|
||||
balanced-match@^1.0.0:
|
||||
version "1.0.0"
|
||||
@@ -199,11 +188,6 @@ bcrypt-pbkdf@^1.0.0:
|
||||
dependencies:
|
||||
tweetnacl "^0.14.3"
|
||||
|
||||
bitcoin@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/bitcoin/-/bitcoin-3.0.1.tgz#ff9e0b62a71bbb8adddb34ee2e427dac21c1096f"
|
||||
integrity sha1-/54LYqcbu4rd2zTuLkJ9rCHBCW8=
|
||||
|
||||
body-parser@1.19.0:
|
||||
version "1.19.0"
|
||||
resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a"
|
||||
@@ -288,7 +272,7 @@ compressible@~2.0.16:
|
||||
dependencies:
|
||||
mime-db ">= 1.40.0 < 2"
|
||||
|
||||
compression@^1.7.3:
|
||||
compression@^1.7.4:
|
||||
version "1.7.4"
|
||||
resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f"
|
||||
integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==
|
||||
@@ -347,13 +331,6 @@ debug@2.6.9:
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
debug@=3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
|
||||
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
delayed-stream@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
|
||||
@@ -422,7 +399,7 @@ etag@~1.8.1:
|
||||
resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
|
||||
integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
|
||||
|
||||
express@^4.16.3:
|
||||
express@^4.17.1:
|
||||
version "4.17.1"
|
||||
resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134"
|
||||
integrity sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==
|
||||
@@ -496,13 +473,6 @@ finalhandler@~1.1.2:
|
||||
statuses "~1.5.0"
|
||||
unpipe "~1.0.0"
|
||||
|
||||
follow-redirects@1.5.10:
|
||||
version "1.5.10"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a"
|
||||
integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==
|
||||
dependencies:
|
||||
debug "=3.1.0"
|
||||
|
||||
forever-agent@~0.6.1:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
|
||||
@@ -556,9 +526,9 @@ getpass@^0.1.1:
|
||||
assert-plus "^1.0.0"
|
||||
|
||||
glob@^7.1.1:
|
||||
version "7.1.4"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255"
|
||||
integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==
|
||||
version "7.1.6"
|
||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
|
||||
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
|
||||
dependencies:
|
||||
fs.realpath "^1.0.0"
|
||||
inflight "^1.0.4"
|
||||
@@ -624,9 +594,9 @@ iconv-lite@0.4.24:
|
||||
safer-buffer ">= 2.1.2 < 3"
|
||||
|
||||
iconv-lite@^0.5.0:
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.5.0.tgz#59cdde0a2a297cc2aeb0c6445a195ee89f127550"
|
||||
integrity sha512-NnEhI9hIEKHOzJ4f697DMz9IQEXr/MMJ5w64vN2/4Ai+wRnvV7SBrL0KLoRlwaKVghOc7LQ5YkPLuX146b6Ydw==
|
||||
version "0.5.1"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.5.1.tgz#b2425d3c7b18f7219f2ca663d103bddb91718d64"
|
||||
integrity sha512-ONHr16SQvKZNSqjQT9gy5z24Jw+uqfO02/ngBSBoqChZ+W8qXX7GPRa1RoUnzGADw8K63R1BXUMzarCVQBpY8Q==
|
||||
dependencies:
|
||||
safer-buffer ">= 2.1.2 < 3"
|
||||
|
||||
@@ -653,11 +623,6 @@ ipaddr.js@1.9.0:
|
||||
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.0.tgz#37df74e430a0e47550fe54a2defe30d8acd95f65"
|
||||
integrity sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==
|
||||
|
||||
is-buffer@^2.0.2:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.4.tgz#3e572f23c8411a5cfd9557c849e3665e0b290623"
|
||||
integrity sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==
|
||||
|
||||
is-property@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84"
|
||||
@@ -751,22 +716,17 @@ methods@~1.1.2:
|
||||
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
|
||||
integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
|
||||
|
||||
mime-db@1.40.0:
|
||||
version "1.40.0"
|
||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.40.0.tgz#a65057e998db090f732a68f6c276d387d4126c32"
|
||||
integrity sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==
|
||||
|
||||
"mime-db@>= 1.40.0 < 2":
|
||||
mime-db@1.42.0, "mime-db@>= 1.40.0 < 2":
|
||||
version "1.42.0"
|
||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.42.0.tgz#3e252907b4c7adb906597b4b65636272cf9e7bac"
|
||||
integrity sha512-UbfJCR4UAVRNgMpfImz05smAXK7+c+ZntjaA26ANtkXLlOe947Aag5zdIcKQULAiF9Cq4WxBi9jUs5zkA84bYQ==
|
||||
|
||||
mime-types@^2.1.12, mime-types@~2.1.19, mime-types@~2.1.24:
|
||||
version "2.1.24"
|
||||
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81"
|
||||
integrity sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==
|
||||
version "2.1.25"
|
||||
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.25.tgz#39772d46621f93e2a80a856c53b86a62156a6437"
|
||||
integrity sha512-5KhStqB5xpTAeGqKBAMgwaYMnQik7teQN4IAzC7npDv6kzeU6prfkR67bc87J1kWMPGkoaZSq1npmexMgkmEVg==
|
||||
dependencies:
|
||||
mime-db "1.40.0"
|
||||
mime-db "1.42.0"
|
||||
|
||||
mime@1.6.0:
|
||||
version "1.6.0"
|
||||
@@ -891,9 +851,9 @@ pseudomap@^1.0.2:
|
||||
integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM=
|
||||
|
||||
psl@^1.1.24:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/psl/-/psl-1.4.0.tgz#5dd26156cdb69fa1fdb8ab1991667d3f80ced7c2"
|
||||
integrity sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw==
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/psl/-/psl-1.6.0.tgz#60557582ee23b6c43719d9890fb4170ecd91e110"
|
||||
integrity sha512-SYKKmVel98NCOYXpkwUqZqh0ahZeeKfmisiLIcEZdsb+WbLv02g/dI5BUmZnIyOe7RzZtLax81nnb2HbvC2tzA==
|
||||
|
||||
punycode@^1.4.1:
|
||||
version "1.4.1"
|
||||
@@ -957,9 +917,9 @@ request@^2.88.0:
|
||||
uuid "^3.3.2"
|
||||
|
||||
resolve@^1.3.2:
|
||||
version "1.12.0"
|
||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.12.0.tgz#3fc644a35c84a48554609ff26ec52b66fa577df6"
|
||||
integrity sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==
|
||||
version "1.13.1"
|
||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.13.1.tgz#be0aa4c06acd53083505abb35f4d66932ab35d16"
|
||||
integrity sha512-CxqObCX8K8YtAhOBRg+lrcdn+LK+WYOS8tSjqSFbjtrI5PnS63QPhZl4+yKfrU9tdsbMu9Anr/amegT87M9Z6w==
|
||||
dependencies:
|
||||
path-parse "^1.0.6"
|
||||
|
||||
@@ -1078,9 +1038,9 @@ tslib@^1.8.0, tslib@^1.8.1:
|
||||
integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==
|
||||
|
||||
tslint@^5.11.0:
|
||||
version "5.20.0"
|
||||
resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.20.0.tgz#fac93bfa79568a5a24e7be9cdde5e02b02d00ec1"
|
||||
integrity sha512-2vqIvkMHbnx8acMogAERQ/IuINOq6DFqgF8/VDvhEkBqQh/x6SP0Y+OHnKth9/ZcHQSroOZwUQSN18v8KKF0/g==
|
||||
version "5.20.1"
|
||||
resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.20.1.tgz#e401e8aeda0152bc44dd07e614034f3f80c67b7d"
|
||||
integrity sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg==
|
||||
dependencies:
|
||||
"@babel/code-frame" "^7.0.0"
|
||||
builtin-modules "^1.1.1"
|
||||
@@ -1123,7 +1083,7 @@ type-is@~1.6.17, type-is@~1.6.18:
|
||||
media-typer "0.3.0"
|
||||
mime-types "~2.1.24"
|
||||
|
||||
typescript@^3.1.1:
|
||||
typescript@~3.6.4:
|
||||
version "3.6.4"
|
||||
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.6.4.tgz#b18752bb3792bc1a0281335f7f6ebf1bbfc5b91d"
|
||||
integrity sha512-unoCll1+l+YK4i4F8f22TaNVPRHcD9PA3yCuZ8g5e0qGqlVlJ/8FSateOLLSagn+Yg5+ZwuPkL8LFUc0Jcvksg==
|
||||
@@ -1169,12 +1129,12 @@ wrappy@1:
|
||||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
|
||||
|
||||
ws@^6.0.0:
|
||||
version "6.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.1.tgz#442fdf0a47ed64f59b6a5d8ff130f4748ed524fb"
|
||||
integrity sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==
|
||||
ws@^7.2.0:
|
||||
version "7.2.0"
|
||||
resolved "https://registry.yarnpkg.com/ws/-/ws-7.2.0.tgz#422eda8c02a4b5dba7744ba66eebbd84bcef0ec7"
|
||||
integrity sha512-+SqNqFbwTm/0DC18KYzIsMTnEWpLwJsiasW/O17la4iDRRIO9uaHbvKiAS3AHgTiuuWerK/brj4O6MYZkei9xg==
|
||||
dependencies:
|
||||
async-limiter "~1.0.0"
|
||||
async-limiter "^1.0.0"
|
||||
|
||||
yallist@^2.1.2:
|
||||
version "2.1.2"
|
||||
|
||||
Reference in New Issue
Block a user