mirror of
https://github.com/mempool/mempool.git
synced 2025-01-18 05:12:35 +01:00
parent
12b3ecd078
commit
e7ddedaeb6
1338
backend/package-lock.json
generated
1338
backend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -25,19 +25,19 @@
|
|||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@types/axios": "^0.14.0",
|
||||||
|
"axios": "^0.21.0",
|
||||||
"express": "^4.17.1",
|
"express": "^4.17.1",
|
||||||
"locutus": "^2.0.12",
|
"locutus": "^2.0.12",
|
||||||
"mysql2": "^1.6.1",
|
"mysql2": "^1.6.1",
|
||||||
"node-worker-threads-pool": "^1.4.2",
|
"node-worker-threads-pool": "^1.4.2",
|
||||||
"request": "^2.88.2",
|
|
||||||
"ws": "^7.3.1"
|
"ws": "^7.3.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/compression": "^1.0.1",
|
"@types/compression": "^1.0.1",
|
||||||
"@types/express": "^4.17.2",
|
"@types/express": "^4.17.2",
|
||||||
"@types/request": "^2.48.2",
|
|
||||||
"@types/ws": "^6.0.4",
|
|
||||||
"@types/locutus": "^0.0.6",
|
"@types/locutus": "^0.0.6",
|
||||||
|
"@types/ws": "^6.0.4",
|
||||||
"tslint": "~6.1.0",
|
"tslint": "~6.1.0",
|
||||||
"typescript": "~3.9.7"
|
"typescript": "~3.9.7"
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import config from '../../config';
|
import config from '../../config';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
import * as request from 'request';
|
import axios from 'axios';
|
||||||
import { BisqBlocks, BisqBlock, BisqTransaction, BisqStats, BisqTrade } from './interfaces';
|
import { BisqBlocks, BisqBlock, BisqTransaction, BisqStats, BisqTrade } from './interfaces';
|
||||||
import { Common } from '../common';
|
import { Common } from '../common';
|
||||||
import { Block } from '../../interfaces';
|
import { Block } from '../../interfaces';
|
||||||
@ -138,18 +138,19 @@ class Bisq {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private updatePrice() {
|
private updatePrice() {
|
||||||
request('https://bisq.markets/api/trades/?market=bsq_btc', { json: true }, (err, res, trades: BisqTrade[]) => {
|
axios.get<BisqTrade[]>('https://bisq.markets/api/trades/?market=bsq_btc')
|
||||||
if (err || !Array.isArray(trades)) { return logger.err('Error updating Bisq market price: ' + err); }
|
.then((response) => {
|
||||||
|
const prices: number[] = [];
|
||||||
const prices: number[] = [];
|
response.data.forEach((trade) => {
|
||||||
trades.forEach((trade) => {
|
prices.push(parseFloat(trade.price) * 100000000);
|
||||||
prices.push(parseFloat(trade.price) * 100000000);
|
});
|
||||||
});
|
prices.sort((a, b) => a - b);
|
||||||
prices.sort((a, b) => a - b);
|
this.price = Common.median(prices);
|
||||||
this.price = Common.median(prices);
|
if (this.priceUpdateCallbackFunction) {
|
||||||
if (this.priceUpdateCallbackFunction) {
|
this.priceUpdateCallbackFunction(this.price);
|
||||||
this.priceUpdateCallbackFunction(this.price);
|
}
|
||||||
}
|
}).catch((err) => {
|
||||||
|
logger.err('Error updating Bisq market price: ' + err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import config from '../../config';
|
import config from '../../config';
|
||||||
import { Transaction, Block, MempoolInfo } from '../../interfaces';
|
import { Transaction, Block, MempoolInfo } from '../../interfaces';
|
||||||
import * as request from 'request';
|
import axios from 'axios';
|
||||||
|
|
||||||
class ElectrsApi {
|
class ElectrsApi {
|
||||||
|
|
||||||
@ -8,138 +8,48 @@ class ElectrsApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getMempoolInfo(): Promise<MempoolInfo> {
|
getMempoolInfo(): Promise<MempoolInfo> {
|
||||||
return new Promise((resolve, reject) => {
|
return axios.get<any>(config.ELECTRS.REST_API_URL + '/mempool', { timeout: 10000 })
|
||||||
request(config.ELECTRS.REST_API_URL + '/mempool', { json: true, timeout: 10000 }, (err, res, response) => {
|
.then((response) => {
|
||||||
if (err) {
|
return {
|
||||||
reject('getMempoolInfo error: ' + err.message || err);
|
size: response.data.count,
|
||||||
} else if (res.statusCode !== 200) {
|
bytes: response.data.vsize,
|
||||||
reject(response);
|
};
|
||||||
} else {
|
|
||||||
if (typeof response.count !== 'number') {
|
|
||||||
reject('Empty data');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
resolve({
|
|
||||||
size: response.count,
|
|
||||||
bytes: response.vsize,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getRawMempool(): Promise<Transaction['txid'][]> {
|
getRawMempool(): Promise<Transaction['txid'][]> {
|
||||||
return new Promise((resolve, reject) => {
|
return axios.get<Transaction['txid'][]>(config.ELECTRS.REST_API_URL + '/mempool/txids')
|
||||||
request(config.ELECTRS.REST_API_URL + '/mempool/txids', { json: true, timeout: 10000, forever: true }, (err, res, response) => {
|
.then((response) => response.data);
|
||||||
if (err) {
|
|
||||||
reject('getRawMempool error: ' + err.message || err);
|
|
||||||
} else if (res.statusCode !== 200) {
|
|
||||||
reject(response);
|
|
||||||
} else {
|
|
||||||
if (response.constructor === Array) {
|
|
||||||
resolve(response);
|
|
||||||
} else {
|
|
||||||
reject('returned invalid data');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getRawTransaction(txId: string): Promise<Transaction> {
|
getRawTransaction(txId: string): Promise<Transaction> {
|
||||||
return new Promise((resolve, reject) => {
|
return axios.get<Transaction>(config.ELECTRS.REST_API_URL + '/tx/' + txId)
|
||||||
request(config.ELECTRS.REST_API_URL + '/tx/' + txId, { json: true, timeout: 10000, forever: true }, (err, res, response) => {
|
.then((response) => response.data);
|
||||||
if (err) {
|
|
||||||
reject('getRawTransaction error: ' + err.message || err);
|
|
||||||
} else if (res.statusCode !== 200) {
|
|
||||||
reject(response);
|
|
||||||
} else {
|
|
||||||
if (response.constructor === Object) {
|
|
||||||
resolve(response);
|
|
||||||
} else {
|
|
||||||
reject('returned invalid data');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getBlockHeightTip(): Promise<number> {
|
getBlockHeightTip(): Promise<number> {
|
||||||
return new Promise((resolve, reject) => {
|
return axios.get<number>(config.ELECTRS.REST_API_URL + '/blocks/tip/height')
|
||||||
request(config.ELECTRS.REST_API_URL + '/blocks/tip/height', { json: true, timeout: 10000 }, (err, res, response) => {
|
.then((response) => response.data);
|
||||||
if (err) {
|
|
||||||
reject('getBlockHeightTip error: ' + err.message || err);
|
|
||||||
} else if (res.statusCode !== 200) {
|
|
||||||
reject(response);
|
|
||||||
} else {
|
|
||||||
resolve(response);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getTxIdsForBlock(hash: string): Promise<string[]> {
|
getTxIdsForBlock(hash: string): Promise<string[]> {
|
||||||
return new Promise((resolve, reject) => {
|
return axios.get<string[]>(config.ELECTRS.REST_API_URL + '/block/' + hash + '/txids')
|
||||||
request(config.ELECTRS.REST_API_URL + '/block/' + hash + '/txids', { json: true, timeout: 10000 }, (err, res, response) => {
|
.then((response) => response.data);
|
||||||
if (err) {
|
|
||||||
reject('getTxIdsForBlock error: ' + err.message || err);
|
|
||||||
} else if (res.statusCode !== 200) {
|
|
||||||
reject(response);
|
|
||||||
} else {
|
|
||||||
if (response.constructor === Array) {
|
|
||||||
resolve(response);
|
|
||||||
} else {
|
|
||||||
reject('returned invalid data');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getBlockHash(height: number): Promise<string> {
|
getBlockHash(height: number): Promise<string> {
|
||||||
return new Promise((resolve, reject) => {
|
return axios.get<string>(config.ELECTRS.REST_API_URL + '/block-height/' + height)
|
||||||
request(config.ELECTRS.REST_API_URL + '/block-height/' + height, { json: true, timeout: 10000 }, (err, res, response) => {
|
.then((response) => response.data);
|
||||||
if (err) {
|
|
||||||
reject('getBlockHash error: ' + err.message || err);
|
|
||||||
} else if (res.statusCode !== 200) {
|
|
||||||
reject(response);
|
|
||||||
} else {
|
|
||||||
resolve(response);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getBlocksFromHeight(height: number): Promise<string> {
|
getBlocksFromHeight(height: number): Promise<string> {
|
||||||
return new Promise((resolve, reject) => {
|
return axios.get<string>(config.ELECTRS.REST_API_URL + '/blocks/' + height)
|
||||||
request(config.ELECTRS.REST_API_URL + '/blocks/' + height, { json: true, timeout: 10000 }, (err, res, response) => {
|
.then((response) => response.data);
|
||||||
if (err) {
|
|
||||||
reject('getBlocksFromHeight error: ' + err.message || err);
|
|
||||||
} else if (res.statusCode !== 200) {
|
|
||||||
reject(response);
|
|
||||||
} else {
|
|
||||||
resolve(response);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getBlock(hash: string): Promise<Block> {
|
getBlock(hash: string): Promise<Block> {
|
||||||
return new Promise((resolve, reject) => {
|
return axios.get<Block>(config.ELECTRS.REST_API_URL + '/block/' + hash)
|
||||||
request(config.ELECTRS.REST_API_URL + '/block/' + hash, { json: true, timeout: 10000 }, (err, res, response) => {
|
.then((response) => response.data);
|
||||||
if (err) {
|
|
||||||
reject('getBlock error: ' + err.message || err);
|
|
||||||
} else if (res.statusCode !== 200) {
|
|
||||||
reject(response);
|
|
||||||
} else {
|
|
||||||
if (response.constructor === Object) {
|
|
||||||
resolve(response);
|
|
||||||
} else {
|
|
||||||
reject('getBlock returned invalid data');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import config from '../config';
|
import config from '../config';
|
||||||
import * as request from 'request';
|
import axios from 'axios';
|
||||||
import { DB } from '../database';
|
import { DB } from '../database';
|
||||||
import logger from '../logger';
|
import logger from '../logger';
|
||||||
|
|
||||||
class Donations {
|
class Donations {
|
||||||
private notifyDonationStatusCallback: ((invoiceId: string) => void) | undefined;
|
private notifyDonationStatusCallback: ((invoiceId: string) => void) | undefined;
|
||||||
private options = {
|
private options = {
|
||||||
baseUrl: config.SPONSORS.BTCPAY_URL,
|
baseURL: config.SPONSORS.BTCPAY_URL,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Authorization': config.SPONSORS.BTCPAY_AUTH,
|
'Authorization': config.SPONSORS.BTCPAY_AUTH,
|
||||||
@ -34,7 +34,7 @@ class Donations {
|
|||||||
this.notifyDonationStatusCallback = fn;
|
this.notifyDonationStatusCallback = fn;
|
||||||
}
|
}
|
||||||
|
|
||||||
$createRequest(amount: number, orderId: string): Promise<any> {
|
async $createRequest(amount: number, orderId: string): Promise<any> {
|
||||||
logger.notice('New invoice request. Handle: ' + orderId + ' Amount: ' + amount + ' BTC');
|
logger.notice('New invoice request. Handle: ' + orderId + ' Amount: ' + amount + ' BTC');
|
||||||
|
|
||||||
const postData = {
|
const postData = {
|
||||||
@ -43,30 +43,21 @@ class Donations {
|
|||||||
'currency': 'BTC',
|
'currency': 'BTC',
|
||||||
'itemDesc': 'Sponsor mempool.space',
|
'itemDesc': 'Sponsor mempool.space',
|
||||||
'notificationUrl': config.SPONSORS.BTCPAY_WEBHOOK_URL,
|
'notificationUrl': config.SPONSORS.BTCPAY_WEBHOOK_URL,
|
||||||
'redirectURL': 'https://mempool.space/about'
|
'redirectURL': 'https://mempool.space/about',
|
||||||
|
};
|
||||||
|
const response = await axios.post('/invoices', postData, this.options);
|
||||||
|
return {
|
||||||
|
id: response.data.data.id,
|
||||||
|
amount: parseFloat(response.data.data.btcPrice),
|
||||||
|
addresses: response.data.data.addresses,
|
||||||
};
|
};
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
request.post({
|
|
||||||
uri: '/invoices',
|
|
||||||
json: postData,
|
|
||||||
...this.options,
|
|
||||||
}, (err, res, body) => {
|
|
||||||
if (err) { return reject(err); }
|
|
||||||
const formattedBody = {
|
|
||||||
id: body.data.id,
|
|
||||||
amount: parseFloat(body.data.btcPrice),
|
|
||||||
addresses: body.data.addresses,
|
|
||||||
};
|
|
||||||
resolve(formattedBody);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async $handleWebhookRequest(data: any): Promise<void> {
|
async $handleWebhookRequest(data: any): Promise<void> {
|
||||||
if (!data || !data.id) {
|
if (!data || !data.id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const response = await this.getStatus(data.id);
|
const response = await this.$getStatus(data.id);
|
||||||
logger.notice(`Received BTCPayServer webhook. Invoice ID: ${data.id} Status: ${response.status} BTC Paid: ${response.btcPaid}`);
|
logger.notice(`Received BTCPayServer webhook. Invoice ID: ${data.id} Status: ${response.status} BTC Paid: ${response.btcPaid}`);
|
||||||
if (response.status !== 'complete' && response.status !== 'confirmed' && response.status !== 'paid') {
|
if (response.status !== 'complete' && response.status !== 'confirmed' && response.status !== 'paid') {
|
||||||
return;
|
return;
|
||||||
@ -128,19 +119,11 @@ class Donations {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private getStatus(id: string): Promise<any> {
|
private async $getStatus(id: string): Promise<any> {
|
||||||
return new Promise((resolve, reject) => {
|
logger.debug('Fetching status for invoice: ' + id);
|
||||||
logger.debug('Fetching status for invoice: ' + id);
|
const response = await axios.get('/invoices/' + id, this.options);
|
||||||
request.get({
|
logger.debug('Invoice status received: ' + JSON.stringify(response.data));
|
||||||
uri: '/invoices/' + id,
|
return response.data.data;
|
||||||
json: true,
|
|
||||||
...this.options,
|
|
||||||
}, (err, res, body) => {
|
|
||||||
if (err) { return reject(err); }
|
|
||||||
logger.debug('Invoice status received: ' + JSON.stringify(body.data));
|
|
||||||
resolve(body.data);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async $addDonationToDatabase(btcPaid: number, handle: string, twitter_id: number | null,
|
private async $addDonationToDatabase(btcPaid: number, handle: string, twitter_id: number | null,
|
||||||
@ -182,34 +165,21 @@ class Donations {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async $getTwitterUserData(handle: string): Promise<any> {
|
private async $getTwitterUserData(handle: string): Promise<any> {
|
||||||
return new Promise((resolve, reject) => {
|
logger.debug('Fetching Twitter API data...');
|
||||||
logger.debug('Fetching Twitter API data...');
|
const res = await axios.get(`https://api.twitter.com/1.1/users/show.json?screen_name=${handle}`, {
|
||||||
request.get({
|
headers: {
|
||||||
uri: `https://api.twitter.com/1.1/users/show.json?screen_name=${handle}`,
|
Authorization: 'Bearer ' + config.SPONSORS.TWITTER_BEARER_AUTH
|
||||||
json: true,
|
}
|
||||||
headers: {
|
|
||||||
Authorization: 'Bearer ' + config.SPONSORS.TWITTER_BEARER_AUTH
|
|
||||||
},
|
|
||||||
}, (err, res, body) => {
|
|
||||||
if (err) { return reject(err); }
|
|
||||||
logger.debug('Twitter user data fetched:' + JSON.stringify(body.data));
|
|
||||||
resolve(body);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
logger.debug('Twitter user data fetched:' + JSON.stringify(res.data));
|
||||||
|
return res.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async $downloadProfileImageBlob(url: string): Promise<string> {
|
private async $downloadProfileImageBlob(url: string): Promise<string> {
|
||||||
return new Promise((resolve, reject) => {
|
logger.debug('Fetching image blob...');
|
||||||
logger.debug('Fetching image blob...');
|
const res = await axios.get(url, { responseType: 'arraybuffer' });
|
||||||
request.get({
|
logger.debug('Image downloaded.');
|
||||||
uri: url,
|
return Buffer.from(res.data, 'utf8').toString('base64');
|
||||||
encoding: null,
|
|
||||||
}, (err, res, body) => {
|
|
||||||
if (err) { return reject(err); }
|
|
||||||
logger.debug('Image downloaded.');
|
|
||||||
resolve(Buffer.from(body, 'utf8').toString('base64'));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async refreshSponsors(): Promise<void> {
|
private async refreshSponsors(): Promise<void> {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import * as request from 'request';
|
|
||||||
import logger from '../logger';
|
import logger from '../logger';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
class FiatConversion {
|
class FiatConversion {
|
||||||
private tickers = {
|
private tickers = {
|
||||||
@ -20,13 +20,13 @@ class FiatConversion {
|
|||||||
return this.tickers;
|
return this.tickers;
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateCurrency() {
|
private async updateCurrency(): Promise<void> {
|
||||||
request('https://api.opennode.co/v1/rates', { json: true }, (err, res, body) => {
|
try {
|
||||||
if (err) { return logger.err('Error updating currency from OpenNode: ' + err); }
|
const response = await axios.get('https://api.opennode.co/v1/rates');
|
||||||
if (body && body.data) {
|
this.tickers = response.data.data;
|
||||||
this.tickers = body.data;
|
} catch (e) {
|
||||||
}
|
logger.err('Error updating currency from OpenNode: ' + e);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import * as http from 'http';
|
|||||||
import * as https from 'https';
|
import * as https from 'https';
|
||||||
import * as WebSocket from 'ws';
|
import * as WebSocket from 'ws';
|
||||||
import * as cluster from 'cluster';
|
import * as cluster from 'cluster';
|
||||||
import * as request from 'request';
|
import axios from 'axios';
|
||||||
|
|
||||||
import { checkDbConnection } from './database';
|
import { checkDbConnection } from './database';
|
||||||
import config from './config';
|
import config from './config';
|
||||||
@ -190,11 +190,13 @@ class Server {
|
|||||||
;
|
;
|
||||||
} else {
|
} else {
|
||||||
this.app
|
this.app
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'donations', (req, res) => {
|
.get(config.MEMPOOL.API_URL_PREFIX + 'donations', async (req, res) => {
|
||||||
req.pipe(request('https://mempool.space/api/v1/donations')).pipe(res);
|
const response = await axios.get('https://mempool.space/api/v1/donations', { responseType: 'stream' });
|
||||||
|
response.data.pipe(res);
|
||||||
})
|
})
|
||||||
.get(config.MEMPOOL.API_URL_PREFIX + 'donations/images/:id', (req, res) => {
|
.get(config.MEMPOOL.API_URL_PREFIX + 'donations/images/:id', async (req, res) => {
|
||||||
req.pipe(request('https://mempool.space/api/v1/donations/images/' + req.params.id)).pipe(res);
|
const response = await axios.get('https://mempool.space/api/v1/donations/images/' + req.params.id, { responseType: 'stream' });
|
||||||
|
response.data.pipe(res);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user