Compare commits
6 Commits
mononaut/s
...
5376-merge
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e3953a6dca | ||
|
|
78844f5787 | ||
|
|
79e2883ebe | ||
|
|
fdbca80920 | ||
|
|
64baade3b3 | ||
|
|
4d06636d83 |
@@ -3,10 +3,6 @@ import logger from '../../logger';
|
||||
import bitcoinClient from './bitcoin-client';
|
||||
import config from '../../config';
|
||||
|
||||
const BLOCKHASH_REGEX = /^[a-f0-9]{64}$/i;
|
||||
const TXID_REGEX = /^[a-f0-9]{64}$/i;
|
||||
const RAW_TX_REGEX = /^[a-f0-9]{2,}$/i;
|
||||
|
||||
/**
|
||||
* Define a set of routes used by the accelerator server
|
||||
* Those routes are not designed to be public
|
||||
@@ -14,7 +10,7 @@ const RAW_TX_REGEX = /^[a-f0-9]{2,}$/i;
|
||||
class BitcoinBackendRoutes {
|
||||
private static tag = 'BitcoinBackendRoutes';
|
||||
|
||||
public initRoutes(app: Application): void {
|
||||
public initRoutes(app: Application) {
|
||||
app
|
||||
.get(config.MEMPOOL.API_URL_PREFIX + 'internal/bitcoin-core/' + 'get-mempool-entry', this.disableCache, this.$getMempoolEntry)
|
||||
.post(config.MEMPOOL.API_URL_PREFIX + 'internal/bitcoin-core/' + 'decode-raw-transaction', this.disableCache, this.$decodeRawTransaction)
|
||||
@@ -30,10 +26,10 @@ class BitcoinBackendRoutes {
|
||||
|
||||
/**
|
||||
* Disable caching for bitcoin core routes
|
||||
*
|
||||
* @param req
|
||||
* @param res
|
||||
* @param next
|
||||
*
|
||||
* @param req
|
||||
* @param res
|
||||
* @param next
|
||||
*/
|
||||
private disableCache(req: Request, res: Response, next: NextFunction): void {
|
||||
res.setHeader('Pragma', 'no-cache');
|
||||
@@ -44,16 +40,16 @@ class BitcoinBackendRoutes {
|
||||
|
||||
/**
|
||||
* Exeption handler to return proper details to the accelerator server
|
||||
*
|
||||
* @param e
|
||||
* @param fnName
|
||||
* @param res
|
||||
*
|
||||
* @param e
|
||||
* @param fnName
|
||||
* @param res
|
||||
*/
|
||||
private static handleException(e: any, fnName: string, res: Response): void {
|
||||
if (typeof(e.code) === 'number') {
|
||||
res.status(400).send(JSON.stringify(e, ['code']));
|
||||
} else {
|
||||
const err = `unknown exception in ${fnName}`;
|
||||
res.status(400).send(JSON.stringify(e, ['code', 'message']));
|
||||
} else {
|
||||
const err = `exception in ${fnName}. ${e}. Details: ${JSON.stringify(e, ['code', 'message'])}`;
|
||||
logger.err(err, BitcoinBackendRoutes.tag);
|
||||
res.status(500).send(err);
|
||||
}
|
||||
@@ -62,13 +58,13 @@ class BitcoinBackendRoutes {
|
||||
private async $getMempoolEntry(req: Request, res: Response): Promise<void> {
|
||||
const txid = req.query.txid;
|
||||
try {
|
||||
if (typeof(txid) !== 'string' || txid.length !== 64 || !TXID_REGEX.test(txid)) {
|
||||
res.status(400).send(`invalid param txid. must be 64 hexadecimal characters`);
|
||||
if (typeof(txid) !== 'string' || txid.length !== 64) {
|
||||
res.status(400).send(`invalid param txid ${txid}. must be a string of 64 char`);
|
||||
return;
|
||||
}
|
||||
const mempoolEntry = await bitcoinClient.getMempoolEntry(txid);
|
||||
if (!mempoolEntry) {
|
||||
res.status(404).send();
|
||||
res.status(404).send(`no mempool entry found for txid ${txid}`);
|
||||
return;
|
||||
}
|
||||
res.status(200).send(mempoolEntry);
|
||||
@@ -80,13 +76,13 @@ class BitcoinBackendRoutes {
|
||||
private async $decodeRawTransaction(req: Request, res: Response): Promise<void> {
|
||||
const rawTx = req.body.rawTx;
|
||||
try {
|
||||
if (typeof(rawTx) !== 'string' || !RAW_TX_REGEX.test(rawTx)) {
|
||||
res.status(400).send(`invalid param rawTx. must be a string of hexadecimal characters`);
|
||||
if (typeof(rawTx) !== 'string') {
|
||||
res.status(400).send(`invalid param rawTx ${rawTx}. must be a string`);
|
||||
return;
|
||||
}
|
||||
const decodedTx = await bitcoinClient.decodeRawTransaction(rawTx);
|
||||
if (!decodedTx) {
|
||||
res.status(400).send(`unable to decode rawTx`);
|
||||
res.status(400).send(`unable to decode rawTx ${rawTx}`);
|
||||
return;
|
||||
}
|
||||
res.status(200).send(decodedTx);
|
||||
@@ -99,23 +95,23 @@ class BitcoinBackendRoutes {
|
||||
const txid = req.query.txid;
|
||||
const verbose = req.query.verbose;
|
||||
try {
|
||||
if (typeof(txid) !== 'string' || txid.length !== 64 || !TXID_REGEX.test(txid)) {
|
||||
res.status(400).send(`invalid param txid. must be 64 hexadecimal characters`);
|
||||
if (typeof(txid) !== 'string' || txid.length !== 64) {
|
||||
res.status(400).send(`invalid param txid ${txid}. must be a string of 64 char`);
|
||||
return;
|
||||
}
|
||||
if (typeof(verbose) !== 'string') {
|
||||
res.status(400).send(`invalid param verbose. must be a string representing an integer`);
|
||||
res.status(400).send(`invalid param verbose ${verbose}. must be a string representing an integer`);
|
||||
return;
|
||||
}
|
||||
const verboseNumber = parseInt(verbose, 10);
|
||||
if (typeof(verboseNumber) !== 'number') {
|
||||
res.status(400).send(`invalid param verbose. must be a valid integer`);
|
||||
res.status(400).send(`invalid param verbose ${verbose}. must be a valid integer`);
|
||||
return;
|
||||
}
|
||||
|
||||
const decodedTx = await bitcoinClient.getRawTransaction(txid, verboseNumber);
|
||||
if (!decodedTx) {
|
||||
res.status(400).send(`unable to get raw transaction`);
|
||||
res.status(400).send(`unable to get raw transaction for txid ${txid}`);
|
||||
return;
|
||||
}
|
||||
res.status(200).send(decodedTx);
|
||||
@@ -127,13 +123,13 @@ class BitcoinBackendRoutes {
|
||||
private async $sendRawTransaction(req: Request, res: Response): Promise<void> {
|
||||
const rawTx = req.body.rawTx;
|
||||
try {
|
||||
if (typeof(rawTx) !== 'string' || !RAW_TX_REGEX.test(rawTx)) {
|
||||
res.status(400).send(`invalid param rawTx. must be a string of hexadecimal characters`);
|
||||
if (typeof(rawTx) !== 'string') {
|
||||
res.status(400).send(`invalid param rawTx ${rawTx}. must be a string`);
|
||||
return;
|
||||
}
|
||||
const txHex = await bitcoinClient.sendRawTransaction(rawTx);
|
||||
if (!txHex) {
|
||||
res.status(400).send(`unable to send rawTx`);
|
||||
res.status(400).send(`unable to send rawTx ${rawTx}`);
|
||||
return;
|
||||
}
|
||||
res.status(200).send(txHex);
|
||||
@@ -145,13 +141,13 @@ class BitcoinBackendRoutes {
|
||||
private async $testMempoolAccept(req: Request, res: Response): Promise<void> {
|
||||
const rawTxs = req.body.rawTxs;
|
||||
try {
|
||||
if (typeof(rawTxs) !== 'object' || !Array.isArray(rawTxs) || rawTxs.some((tx) => typeof(tx) !== 'string' || !RAW_TX_REGEX.test(tx))) {
|
||||
res.status(400).send(`invalid param rawTxs. must be an array of strings of hexadecimal characters`);
|
||||
if (typeof(rawTxs) !== 'object') {
|
||||
res.status(400).send(`invalid param rawTxs ${JSON.stringify(rawTxs)}. must be an array of string`);
|
||||
return;
|
||||
}
|
||||
const txHex = await bitcoinClient.testMempoolAccept(rawTxs);
|
||||
if (typeof(txHex) !== 'object' || txHex.length === 0) {
|
||||
res.status(400).send(`testmempoolaccept failed for raw txs, got an empty result`);
|
||||
res.status(400).send(`testmempoolaccept failed for raw txs ${JSON.stringify(rawTxs)}, got an empty result`);
|
||||
return;
|
||||
}
|
||||
res.status(200).send(txHex);
|
||||
@@ -164,18 +160,18 @@ class BitcoinBackendRoutes {
|
||||
const txid = req.query.txid;
|
||||
const verbose = req.query.verbose;
|
||||
try {
|
||||
if (typeof(txid) !== 'string' || txid.length !== 64 || !TXID_REGEX.test(txid)) {
|
||||
res.status(400).send(`invalid param txid. must be 64 hexadecimal characters`);
|
||||
if (typeof(txid) !== 'string' || txid.length !== 64) {
|
||||
res.status(400).send(`invalid param txid ${txid}. must be a string of 64 char`);
|
||||
return;
|
||||
}
|
||||
if (typeof(verbose) !== 'string' || (verbose !== 'true' && verbose !== 'false')) {
|
||||
res.status(400).send(`invalid param verbose. must be a string ('true' | 'false')`);
|
||||
res.status(400).send(`invalid param verbose ${verbose}. must be a string ('true' | 'false')`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const ancestors = await bitcoinClient.getMempoolAncestors(txid, verbose === 'true' ? true : false);
|
||||
if (!ancestors) {
|
||||
res.status(400).send(`unable to get mempool ancestors`);
|
||||
res.status(400).send(`unable to get mempool ancestors for txid ${txid}`);
|
||||
return;
|
||||
}
|
||||
res.status(200).send(ancestors);
|
||||
@@ -188,23 +184,23 @@ class BitcoinBackendRoutes {
|
||||
const blockHash = req.query.hash;
|
||||
const verbosity = req.query.verbosity;
|
||||
try {
|
||||
if (typeof(blockHash) !== 'string' || blockHash.length !== 64 || !BLOCKHASH_REGEX.test(blockHash)) {
|
||||
res.status(400).send(`invalid param blockHash. must be 64 hexadecimal characters`);
|
||||
if (typeof(blockHash) !== 'string' || blockHash.length !== 64) {
|
||||
res.status(400).send(`invalid param blockHash ${blockHash}. must be a string of 64 char`);
|
||||
return;
|
||||
}
|
||||
if (typeof(verbosity) !== 'string') {
|
||||
res.status(400).send(`invalid param verbosity. must be a string representing an integer`);
|
||||
res.status(400).send(`invalid param verbosity ${verbosity}. must be a string representing an integer`);
|
||||
return;
|
||||
}
|
||||
const verbosityNumber = parseInt(verbosity, 10);
|
||||
if (typeof(verbosityNumber) !== 'number') {
|
||||
res.status(400).send(`invalid param verbosity. must be a valid integer`);
|
||||
res.status(400).send(`invalid param verbosity ${verbosity}. must be a valid integer`);
|
||||
return;
|
||||
}
|
||||
|
||||
const block = await bitcoinClient.getBlock(blockHash, verbosityNumber);
|
||||
if (!block) {
|
||||
res.status(400).send(`unable to get block`);
|
||||
res.status(400).send(`unable to get block for block hash ${blockHash}`);
|
||||
return;
|
||||
}
|
||||
res.status(200).send(block);
|
||||
@@ -217,18 +213,18 @@ class BitcoinBackendRoutes {
|
||||
const blockHeight = req.query.height;
|
||||
try {
|
||||
if (typeof(blockHeight) !== 'string') {
|
||||
res.status(400).send(`invalid param blockHeight, must be a string representing an integer`);
|
||||
res.status(400).send(`invalid param blockHeight ${blockHeight}, must be a string representing an integer`);
|
||||
return;
|
||||
}
|
||||
const blockHeightNumber = parseInt(blockHeight, 10);
|
||||
if (typeof(blockHeightNumber) !== 'number') {
|
||||
res.status(400).send(`invalid param blockHeight. must be a valid integer`);
|
||||
res.status(400).send(`invalid param blockHeight ${blockHeight}. must be a valid integer`);
|
||||
return;
|
||||
}
|
||||
|
||||
const block = await bitcoinClient.getBlockHash(blockHeightNumber);
|
||||
if (!block) {
|
||||
res.status(400).send(`unable to get block hash`);
|
||||
res.status(400).send(`unable to get block hash for block height ${blockHeightNumber}`);
|
||||
return;
|
||||
}
|
||||
res.status(200).send(block);
|
||||
@@ -251,4 +247,4 @@ class BitcoinBackendRoutes {
|
||||
}
|
||||
}
|
||||
|
||||
export default new BitcoinBackendRoutes;
|
||||
export default new BitcoinBackendRoutes
|
||||
@@ -22,11 +22,6 @@ import rbfCache from '../rbf-cache';
|
||||
import { calculateMempoolTxCpfp } from '../cpfp';
|
||||
import { handleError } from '../../utils/api';
|
||||
|
||||
const TXID_REGEX = /^[a-f0-9]{64}$/i;
|
||||
const BLOCK_HASH_REGEX = /^[a-f0-9]{64}$/i;
|
||||
const ADDRESS_REGEX = /^[a-z0-9]{2,120}$/i;
|
||||
const SCRIPT_HASH_REGEX = /^([a-f0-9]{2})+$/i;
|
||||
|
||||
class BitcoinRoutes {
|
||||
public initRoutes(app: Application) {
|
||||
app
|
||||
@@ -95,7 +90,7 @@ class BitcoinRoutes {
|
||||
res.set('Content-Type', 'application/json');
|
||||
res.send(result);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get init data');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,7 +109,7 @@ class BitcoinRoutes {
|
||||
const result = mempoolBlocks.getMempoolBlocks();
|
||||
res.json(result);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get mempool blocks');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,10 +121,7 @@ class BitcoinRoutes {
|
||||
const txIds: string[] = [];
|
||||
for (const _txId in req.query.txId) {
|
||||
if (typeof req.query.txId[_txId] === 'string') {
|
||||
const txid = req.query.txId[_txId].toString();
|
||||
if (TXID_REGEX.test(txid)) {
|
||||
txIds.push(txid);
|
||||
}
|
||||
txIds.push(req.query.txId[_txId].toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,22 +140,18 @@ class BitcoinRoutes {
|
||||
handleError(req, res, 400, 'Too many txids requested');
|
||||
return;
|
||||
}
|
||||
if (txids.some((txid) => !TXID_REGEX.test(txid))) {
|
||||
handleError(req, res, 400, 'Invalid txids format');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const batchedOutspends = await bitcoinApi.$getBatchedOutspends(txids);
|
||||
res.json(batchedOutspends);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get batched outspends');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
private async $getCpfpInfo(req: Request, res: Response) {
|
||||
if (!TXID_REGEX.test(req.params.txId)) {
|
||||
handleError(req, res, 501, `Invalid transaction ID`);
|
||||
if (!/^[a-fA-F0-9]{64}$/.test(req.params.txId)) {
|
||||
handleError(req, res, 501, `Invalid transaction ID.`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -196,7 +184,7 @@ class BitcoinRoutes {
|
||||
try {
|
||||
cpfpInfo = await transactionRepository.$getCpfpInfo(req.params.txId);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get CPFP info');
|
||||
handleError(req, res, 500, 'failed to get CPFP info');
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -217,10 +205,6 @@ class BitcoinRoutes {
|
||||
}
|
||||
|
||||
private async getTransaction(req: Request, res: Response) {
|
||||
if (!TXID_REGEX.test(req.params.txId)) {
|
||||
handleError(req, res, 501, `Invalid transaction ID`);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const transaction = await transactionUtils.$getTransactionExtended(req.params.txId, true, false, false, true);
|
||||
res.json(transaction);
|
||||
@@ -228,18 +212,12 @@ class BitcoinRoutes {
|
||||
let statusCode = 500;
|
||||
if (e instanceof Error && e instanceof Error && e.message && e.message.indexOf('No such mempool or blockchain transaction') > -1) {
|
||||
statusCode = 404;
|
||||
handleError(req, res, statusCode, 'No such mempool or blockchain transaction');
|
||||
return;
|
||||
}
|
||||
handleError(req, res, statusCode, 'Failed to get transaction');
|
||||
handleError(req, res, statusCode, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
private async getRawTransaction(req: Request, res: Response) {
|
||||
if (!TXID_REGEX.test(req.params.txId)) {
|
||||
handleError(req, res, 501, `Invalid transaction ID`);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const transaction: IEsploraApi.Transaction = await bitcoinApi.$getRawTransaction(req.params.txId, true);
|
||||
res.setHeader('content-type', 'text/plain');
|
||||
@@ -248,10 +226,8 @@ class BitcoinRoutes {
|
||||
let statusCode = 500;
|
||||
if (e instanceof Error && e.message && e.message.indexOf('No such mempool or blockchain transaction') > -1) {
|
||||
statusCode = 404;
|
||||
handleError(req, res, statusCode, 'No such mempool or blockchain transaction');
|
||||
return;
|
||||
}
|
||||
handleError(req, res, statusCode, 'Failed to get raw transaction');
|
||||
handleError(req, res, statusCode, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -316,18 +292,14 @@ class BitcoinRoutes {
|
||||
}
|
||||
} catch (e: any) {
|
||||
if (e instanceof Error && new RegExp(notFoundError).test(e.message)) {
|
||||
handleError(req, res, 404, notFoundError);
|
||||
handleError(req, res, 404, e.message);
|
||||
} else {
|
||||
handleError(req, res, 500, 'Failed to process PSBT');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async getTransactionStatus(req: Request, res: Response) {
|
||||
if (!TXID_REGEX.test(req.params.txId)) {
|
||||
handleError(req, res, 501, `Invalid transaction ID`);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const transaction = await transactionUtils.$getTransactionExtended(req.params.txId, true);
|
||||
res.json(transaction.status);
|
||||
@@ -335,54 +307,36 @@ class BitcoinRoutes {
|
||||
let statusCode = 500;
|
||||
if (e instanceof Error && e.message && e.message.indexOf('No such mempool or blockchain transaction') > -1) {
|
||||
statusCode = 404;
|
||||
handleError(req, res, statusCode, 'No such mempool or blockchain transaction');
|
||||
return;
|
||||
}
|
||||
handleError(req, res, statusCode, 'Failed to get transaction status');
|
||||
handleError(req, res, statusCode, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
private async getStrippedBlockTransactions(req: Request, res: Response) {
|
||||
if (!BLOCK_HASH_REGEX.test(req.params.hash)) {
|
||||
handleError(req, res, 501, `Invalid block hash`);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const transactions = await blocks.$getStrippedBlockTransactions(req.params.hash);
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 3600 * 24 * 30).toUTCString());
|
||||
res.json(transactions);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get block summary');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
private async getStrippedBlockTransaction(req: Request, res: Response) {
|
||||
if (!BLOCK_HASH_REGEX.test(req.params.hash)) {
|
||||
handleError(req, res, 501, `Invalid block hash`);
|
||||
return;
|
||||
}
|
||||
if (!TXID_REGEX.test(req.params.txid)) {
|
||||
handleError(req, res, 501, `Invalid transaction ID`);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const transaction = await blocks.$getSingleTxFromSummary(req.params.hash, req.params.txid);
|
||||
if (!transaction) {
|
||||
handleError(req, res, 404, `Transaction not found in summary`);
|
||||
handleError(req, res, 404, `transaction not found in summary`);
|
||||
return;
|
||||
}
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 3600 * 24 * 30).toUTCString());
|
||||
res.json(transaction);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get transaction from summary');
|
||||
res.status(500).send(e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
private async getBlock(req: Request, res: Response) {
|
||||
if (!BLOCK_HASH_REGEX.test(req.params.hash)) {
|
||||
handleError(req, res, 501, `Invalid block hash`);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const block = await blocks.$getBlock(req.params.hash);
|
||||
|
||||
@@ -394,69 +348,53 @@ class BitcoinRoutes {
|
||||
} else if (blockAge > 30 * day) {
|
||||
cacheDuration = 10 * day;
|
||||
} else {
|
||||
cacheDuration = 600;
|
||||
cacheDuration = 600
|
||||
}
|
||||
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * cacheDuration).toUTCString());
|
||||
res.json(block);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get block');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
private async getBlockHeader(req: Request, res: Response) {
|
||||
if (!BLOCK_HASH_REGEX.test(req.params.hash)) {
|
||||
handleError(req, res, 501, `Invalid block hash`);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const blockHeader = await bitcoinApi.$getBlockHeader(req.params.hash);
|
||||
res.setHeader('content-type', 'text/plain');
|
||||
res.send(blockHeader);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get block header');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
private async getBlockAuditSummary(req: Request, res: Response) {
|
||||
if (!BLOCK_HASH_REGEX.test(req.params.hash)) {
|
||||
handleError(req, res, 501, `Invalid block hash`);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const auditSummary = await blocks.$getBlockAuditSummary(req.params.hash);
|
||||
if (auditSummary) {
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 3600 * 24 * 30).toUTCString());
|
||||
res.json(auditSummary);
|
||||
} else {
|
||||
handleError(req, res, 404, `Audit not available`);
|
||||
handleError(req, res, 404, `audit not available`);
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get block audit summary');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
private async $getBlockTxAuditSummary(req: Request, res: Response) {
|
||||
if (!BLOCK_HASH_REGEX.test(req.params.hash)) {
|
||||
handleError(req, res, 501, `Invalid block hash`);
|
||||
return;
|
||||
}
|
||||
if (!TXID_REGEX.test(req.params.txid)) {
|
||||
handleError(req, res, 501, `Invalid transaction ID`);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const auditSummary = await blocks.$getBlockTxAuditSummary(req.params.hash, req.params.txid);
|
||||
if (auditSummary) {
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 3600 * 24 * 30).toUTCString());
|
||||
res.json(auditSummary);
|
||||
} else {
|
||||
handleError(req, res, 404, `Transaction audit not available`);
|
||||
handleError(req, res, 404, `transaction audit not available`);
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get transaction audit summary');
|
||||
res.status(500).send(e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -470,7 +408,7 @@ class BitcoinRoutes {
|
||||
return await this.getLegacyBlocks(req, res);
|
||||
}
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get blocks');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -512,7 +450,7 @@ class BitcoinRoutes {
|
||||
res.json(await blocks.$getBlocksBetweenHeight(from, to));
|
||||
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get blocks');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -547,15 +485,11 @@ class BitcoinRoutes {
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
|
||||
res.json(returnBlocks);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get blocks');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
private async getBlockTransactions(req: Request, res: Response) {
|
||||
if (!BLOCK_HASH_REGEX.test(req.params.hash)) {
|
||||
handleError(req, res, 501, `Invalid block hash`);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
loadingIndicators.setProgress('blocktxs-' + req.params.hash, 0);
|
||||
|
||||
@@ -576,7 +510,7 @@ class BitcoinRoutes {
|
||||
res.json(transactions);
|
||||
} catch (e) {
|
||||
loadingIndicators.setProgress('blocktxs-' + req.params.hash, 100);
|
||||
handleError(req, res, 500, 'Failed to get block transactions');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -585,7 +519,7 @@ class BitcoinRoutes {
|
||||
const blockHash = await bitcoinApi.$getBlockHash(parseInt(req.params.height, 10));
|
||||
res.send(blockHash);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get block at height');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -594,20 +528,16 @@ class BitcoinRoutes {
|
||||
handleError(req, res, 405, 'Address lookups cannot be used with bitcoind as backend.');
|
||||
return;
|
||||
}
|
||||
if (!ADDRESS_REGEX.test(req.params.address)) {
|
||||
handleError(req, res, 501, `Invalid address`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const addressData = await bitcoinApi.$getAddress(req.params.address);
|
||||
res.json(addressData);
|
||||
} catch (e) {
|
||||
if (e instanceof Error && e.message && (e.message.indexOf('too long') > 0 || e.message.indexOf('confirmed status') > 0)) {
|
||||
handleError(req, res, 413, e.message);
|
||||
handleError(req, res, 413, e instanceof Error ? e.message : e);
|
||||
return;
|
||||
}
|
||||
handleError(req, res, 500, 'Failed to get address');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -616,10 +546,6 @@ class BitcoinRoutes {
|
||||
handleError(req, res, 405, 'Address lookups cannot be used with bitcoind as backend.');
|
||||
return;
|
||||
}
|
||||
if (!ADDRESS_REGEX.test(req.params.address)) {
|
||||
handleError(req, res, 501, `Invalid address`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
let lastTxId: string = '';
|
||||
@@ -630,10 +556,10 @@ class BitcoinRoutes {
|
||||
res.json(transactions);
|
||||
} catch (e) {
|
||||
if (e instanceof Error && e.message && (e.message.indexOf('too long') > 0 || e.message.indexOf('confirmed status') > 0)) {
|
||||
handleError(req, res, 413, e.message);
|
||||
handleError(req, res, 413, e instanceof Error ? e.message : e);
|
||||
return;
|
||||
}
|
||||
handleError(req, res, 500, 'Failed to get address transactions');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -649,10 +575,6 @@ class BitcoinRoutes {
|
||||
handleError(req, res, 405, 'Address lookups cannot be used with bitcoind as backend.');
|
||||
return;
|
||||
}
|
||||
if (!SCRIPT_HASH_REGEX.test(req.params.scripthash)) {
|
||||
handleError(req, res, 501, `Invalid scripthash`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// electrum expects scripthashes in little-endian
|
||||
@@ -661,10 +583,10 @@ class BitcoinRoutes {
|
||||
res.json(addressData);
|
||||
} catch (e) {
|
||||
if (e instanceof Error && e.message && (e.message.indexOf('too long') > 0 || e.message.indexOf('confirmed status') > 0)) {
|
||||
handleError(req, res, 413, e.message);
|
||||
handleError(req, res, 413, e instanceof Error ? e.message : e);
|
||||
return;
|
||||
}
|
||||
handleError(req, res, 500, 'Failed to get script hash');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -673,10 +595,6 @@ class BitcoinRoutes {
|
||||
handleError(req, res, 405, 'Address lookups cannot be used with bitcoind as backend.');
|
||||
return;
|
||||
}
|
||||
if (!SCRIPT_HASH_REGEX.test(req.params.scripthash)) {
|
||||
handleError(req, res, 501, `Invalid scripthash`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// electrum expects scripthashes in little-endian
|
||||
@@ -689,10 +607,10 @@ class BitcoinRoutes {
|
||||
res.json(transactions);
|
||||
} catch (e) {
|
||||
if (e instanceof Error && e.message && (e.message.indexOf('too long') > 0 || e.message.indexOf('confirmed status') > 0)) {
|
||||
handleError(req, res, 413, e.message);
|
||||
handleError(req, res, 413, e instanceof Error ? e.message : e);
|
||||
return;
|
||||
}
|
||||
handleError(req, res, 500, 'Failed to get script hash transactions');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -705,10 +623,10 @@ class BitcoinRoutes {
|
||||
|
||||
private async getAddressPrefix(req: Request, res: Response) {
|
||||
try {
|
||||
const addressPrefix = await bitcoinApi.$getAddressPrefix(req.params.prefix);
|
||||
res.send(addressPrefix);
|
||||
const blockHash = await bitcoinApi.$getAddressPrefix(req.params.prefix);
|
||||
res.send(blockHash);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get address prefix');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -749,7 +667,7 @@ class BitcoinRoutes {
|
||||
res.setHeader('content-type', 'text/plain');
|
||||
res.send(result.toString());
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get height at tip');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -759,55 +677,39 @@ class BitcoinRoutes {
|
||||
res.setHeader('content-type', 'text/plain');
|
||||
res.send(result);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get hash at tip');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
private async getRawBlock(req: Request, res: Response) {
|
||||
if (!BLOCK_HASH_REGEX.test(req.params.hash)) {
|
||||
handleError(req, res, 501, `Invalid block hash`);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const result = await bitcoinApi.$getRawBlock(req.params.hash);
|
||||
res.setHeader('content-type', 'application/octet-stream');
|
||||
res.send(result);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get raw block');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
private async getTxIdsForBlock(req: Request, res: Response) {
|
||||
if (!BLOCK_HASH_REGEX.test(req.params.hash)) {
|
||||
handleError(req, res, 501, `Invalid block hash`);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const result = await bitcoinApi.$getTxIdsForBlock(req.params.hash);
|
||||
res.json(result);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get txids for block');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
private async validateAddress(req: Request, res: Response) {
|
||||
if (!ADDRESS_REGEX.test(req.params.address)) {
|
||||
handleError(req, res, 501, `Invalid address`);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const result = await bitcoinClient.validateAddress(req.params.address);
|
||||
res.json(result);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to validate address');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
private async getRbfHistory(req: Request, res: Response) {
|
||||
if (!TXID_REGEX.test(req.params.txId)) {
|
||||
handleError(req, res, 501, `Invalid transaction ID`);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const replacements = rbfCache.getRbfTree(req.params.txId) || null;
|
||||
const replaces = rbfCache.getReplaces(req.params.txId) || null;
|
||||
@@ -816,7 +718,7 @@ class BitcoinRoutes {
|
||||
replaces
|
||||
});
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get rbf history');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -825,7 +727,7 @@ class BitcoinRoutes {
|
||||
const result = rbfCache.getRbfTrees(false);
|
||||
res.json(result);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get rbf trees');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -834,15 +736,11 @@ class BitcoinRoutes {
|
||||
const result = rbfCache.getRbfTrees(true);
|
||||
res.json(result);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get full rbf replacements');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
private async getCachedTx(req: Request, res: Response) {
|
||||
if (!TXID_REGEX.test(req.params.txId)) {
|
||||
handleError(req, res, 501, `Invalid transaction ID`);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const result = rbfCache.getTx(req.params.txId);
|
||||
if (result) {
|
||||
@@ -851,20 +749,16 @@ class BitcoinRoutes {
|
||||
res.status(204).send();
|
||||
}
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get cached tx');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
private async getTransactionOutspends(req: Request, res: Response) {
|
||||
if (!TXID_REGEX.test(req.params.txId)) {
|
||||
handleError(req, res, 501, `Invalid transaction ID`);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const result = await bitcoinApi.$getOutspends(req.params.txId);
|
||||
res.json(result);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get transaction outspends');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -877,7 +771,7 @@ class BitcoinRoutes {
|
||||
handleError(req, res, 503, `Service Temporarily Unavailable`);
|
||||
}
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get difficulty change');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -888,8 +782,8 @@ class BitcoinRoutes {
|
||||
const txIdResult = await bitcoinApi.$sendRawTransaction(rawTx);
|
||||
res.send(txIdResult);
|
||||
} catch (e: any) {
|
||||
handleError(req, res, 400, (e.message && e.code) ? 'sendrawtransaction RPC error: ' + JSON.stringify({ code: e.code })
|
||||
: 'Failed to send raw transaction');
|
||||
handleError(req, res, 400, e.message && e.code ? 'sendrawtransaction RPC error: ' + JSON.stringify({ code: e.code, message: e.message })
|
||||
: (e.message || 'Error'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -900,8 +794,8 @@ class BitcoinRoutes {
|
||||
const txIdResult = await bitcoinClient.sendRawTransaction(txHex);
|
||||
res.send(txIdResult);
|
||||
} catch (e: any) {
|
||||
handleError(req, res, 400, (e.message && e.code) ? 'sendrawtransaction RPC error: ' + JSON.stringify({ code: e.code })
|
||||
: 'Failed to send raw transaction');
|
||||
handleError(req, res, 400, e.message && e.code ? 'sendrawtransaction RPC error: ' + JSON.stringify({ code: e.code, message: e.message })
|
||||
: (e.message || 'Error'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -912,8 +806,8 @@ class BitcoinRoutes {
|
||||
const result = await bitcoinApi.$testMempoolAccept(rawTxs, maxfeerate);
|
||||
res.send(result);
|
||||
} catch (e: any) {
|
||||
handleError(req, res, 400, (e.message && e.code) ? 'testmempoolaccept RPC error: ' + JSON.stringify({ code: e.code })
|
||||
: 'Failed to test transactions');
|
||||
handleError(req, res, 400, e.message && e.code ? 'testmempoolaccept RPC error: ' + JSON.stringify({ code: e.code, message: e.message })
|
||||
: (e.message || 'Error'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -925,8 +819,8 @@ class BitcoinRoutes {
|
||||
const result = await bitcoinClient.submitPackage(rawTxs, maxfeerate ?? undefined, maxburnamount ?? undefined);
|
||||
res.send(result);
|
||||
} catch (e: any) {
|
||||
handleError(req, res, 400, (e.message && e.code) ? 'submitpackage RPC error: ' + JSON.stringify({ code: e.code })
|
||||
: 'Failed to submit package');
|
||||
handleError(req, res, 400, e.message && e.code ? 'submitpackage RPC error: ' + JSON.stringify({ code: e.code, message: e.message })
|
||||
: (e.message || 'Error'));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import config from '../../config';
|
||||
import axios, { isAxiosError } from 'axios';
|
||||
import axios, { AxiosResponse, isAxiosError } from 'axios';
|
||||
import http from 'http';
|
||||
import { AbstractBitcoinApi, HealthCheckHost } from './bitcoin-api-abstract-factory';
|
||||
import { IEsploraApi } from './esplora-api.interface';
|
||||
import logger from '../../logger';
|
||||
import { Common } from '../common';
|
||||
import { SubmitPackageResult, TestMempoolAcceptResult } from './bitcoin-api.interface';
|
||||
import os from 'os';
|
||||
|
||||
interface FailoverHost {
|
||||
host: string,
|
||||
rtts: number[],
|
||||
@@ -20,13 +20,6 @@ interface FailoverHost {
|
||||
preferred?: boolean,
|
||||
checked: boolean,
|
||||
lastChecked?: number,
|
||||
publicDomain: string,
|
||||
hashes: {
|
||||
frontend?: string,
|
||||
backend?: string,
|
||||
electrs?: string,
|
||||
lastUpdated: number,
|
||||
}
|
||||
}
|
||||
|
||||
class FailoverRouter {
|
||||
@@ -36,21 +29,14 @@ class FailoverRouter {
|
||||
maxHeight: number = 0;
|
||||
hosts: FailoverHost[];
|
||||
multihost: boolean;
|
||||
gitHashInterval: number = 600000; // 10 minutes
|
||||
pollInterval: number = 60000; // 1 minute
|
||||
pollInterval: number = 60000;
|
||||
pollTimer: NodeJS.Timeout | null = null;
|
||||
pollConnection = axios.create();
|
||||
localHostname: string = 'localhost';
|
||||
requestConnection = axios.create({
|
||||
httpAgent: new http.Agent({ keepAlive: true })
|
||||
});
|
||||
|
||||
constructor() {
|
||||
try {
|
||||
this.localHostname = os.hostname();
|
||||
} catch (e) {
|
||||
logger.warn('Failed to set local hostname, using "localhost"');
|
||||
}
|
||||
// setup list of hosts
|
||||
this.hosts = (config.ESPLORA.FALLBACK || []).map(domain => {
|
||||
return {
|
||||
@@ -59,10 +45,6 @@ class FailoverRouter {
|
||||
rtts: [],
|
||||
rtt: Infinity,
|
||||
failures: 0,
|
||||
publicDomain: 'https://' + this.extractPublicDomain(domain),
|
||||
hashes: {
|
||||
lastUpdated: 0,
|
||||
},
|
||||
};
|
||||
});
|
||||
this.activeHost = {
|
||||
@@ -73,10 +55,6 @@ class FailoverRouter {
|
||||
socket: !!config.ESPLORA.UNIX_SOCKET_PATH,
|
||||
preferred: true,
|
||||
checked: false,
|
||||
publicDomain: `http://${this.localHostname}`,
|
||||
hashes: {
|
||||
lastUpdated: 0,
|
||||
},
|
||||
};
|
||||
this.fallbackHost = this.activeHost;
|
||||
this.hosts.unshift(this.activeHost);
|
||||
@@ -128,24 +106,6 @@ class FailoverRouter {
|
||||
host.outOfSync = false;
|
||||
}
|
||||
host.unreachable = false;
|
||||
|
||||
// update esplora git hash using the x-powered-by header from the height check
|
||||
const poweredBy = result.headers['x-powered-by'];
|
||||
if (poweredBy) {
|
||||
const match = poweredBy.match(/([a-fA-F0-9]{5,40})/);
|
||||
if (match && match[1]?.length) {
|
||||
host.hashes.electrs = match[1];
|
||||
}
|
||||
}
|
||||
|
||||
// Check front and backend git hashes less often
|
||||
if (Date.now() - host.hashes.lastUpdated > this.gitHashInterval) {
|
||||
await Promise.all([
|
||||
this.$updateFrontendGitHash(host),
|
||||
this.$updateBackendGitHash(host)
|
||||
]);
|
||||
host.hashes.lastUpdated = Date.now();
|
||||
}
|
||||
} else {
|
||||
host.outOfSync = true;
|
||||
host.unreachable = true;
|
||||
@@ -242,47 +202,6 @@ class FailoverRouter {
|
||||
}
|
||||
}
|
||||
|
||||
// methods for retrieving git hashes by host
|
||||
private async $updateFrontendGitHash(host: FailoverHost): Promise<void> {
|
||||
try {
|
||||
const url = `${host.publicDomain}/resources/config.js`;
|
||||
const response = await this.pollConnection.get<string>(url, { timeout: config.ESPLORA.FALLBACK_TIMEOUT });
|
||||
const match = response.data.match(/GIT_COMMIT_HASH\s*=\s*['"](.*?)['"]/);
|
||||
if (match && match[1]?.length) {
|
||||
host.hashes.frontend = match[1];
|
||||
}
|
||||
} catch (e) {
|
||||
// failed to get frontend build hash - do nothing
|
||||
}
|
||||
}
|
||||
|
||||
private async $updateBackendGitHash(host: FailoverHost): Promise<void> {
|
||||
try {
|
||||
const url = `${host.publicDomain}/api/v1/backend-info`;
|
||||
const response = await this.pollConnection.get<any>(url, { timeout: config.ESPLORA.FALLBACK_TIMEOUT });
|
||||
if (response.data?.gitCommit) {
|
||||
host.hashes.backend = response.data.gitCommit;
|
||||
}
|
||||
} catch (e) {
|
||||
// failed to get backend build hash - do nothing
|
||||
}
|
||||
}
|
||||
|
||||
// returns the public mempool domain corresponding to an esplora server url
|
||||
// (a bit of a hack to avoid manually specifying frontend & backend URLs for each esplora server)
|
||||
private extractPublicDomain(url: string): string {
|
||||
// force the url to start with a valid protocol
|
||||
const urlWithProtocol = url.startsWith('http') ? url : `https://${url}`;
|
||||
// parse as URL and extract the hostname
|
||||
try {
|
||||
const parsed = new URL(urlWithProtocol);
|
||||
return parsed.hostname;
|
||||
} catch (e) {
|
||||
// fallback to the original url
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
private async $query<T>(method: 'get'| 'post', path, data: any, responseType = 'json', host = this.activeHost, retry: boolean = true): Promise<T> {
|
||||
let axiosConfig;
|
||||
let url;
|
||||
@@ -462,7 +381,6 @@ class ElectrsApi implements AbstractBitcoinApi {
|
||||
unreachable: !!host.unreachable,
|
||||
checked: !!host.checked,
|
||||
lastChecked: host.lastChecked || 0,
|
||||
hashes: host.hashes,
|
||||
}));
|
||||
} else {
|
||||
return [];
|
||||
|
||||
@@ -7,7 +7,7 @@ import cpfpRepository from '../repositories/CpfpRepository';
|
||||
import { RowDataPacket } from 'mysql2';
|
||||
|
||||
class DatabaseMigration {
|
||||
private static currentVersion = 94;
|
||||
private static currentVersion = 93;
|
||||
private queryTimeout = 3600_000;
|
||||
private statisticsAddedIndexed = false;
|
||||
private uniqueLogs: string[] = [];
|
||||
@@ -801,323 +801,6 @@ class DatabaseMigration {
|
||||
`);
|
||||
await this.updateToSchemaVersion(93);
|
||||
}
|
||||
|
||||
// Unify database schema for all mempool netwoks
|
||||
// versions above 94 should not use network-specific flags
|
||||
if (databaseSchemaVersion < 94) {
|
||||
|
||||
if (!isBitcoin) {
|
||||
// Apply all the bitcoin specific migrations to non-bitcoin networks: liquid, liquidtestnet and testnet4 (!)
|
||||
// Version 5
|
||||
await this.$executeQuery('ALTER TABLE blocks ADD `reward` double unsigned NOT NULL DEFAULT "0"');
|
||||
|
||||
// Version 6
|
||||
await this.$executeQuery('ALTER TABLE blocks MODIFY `height` integer unsigned NOT NULL DEFAULT "0"');
|
||||
await this.$executeQuery('ALTER TABLE blocks MODIFY `tx_count` smallint unsigned NOT NULL DEFAULT "0"');
|
||||
await this.$executeQuery('ALTER TABLE blocks MODIFY `size` integer unsigned NOT NULL DEFAULT "0"');
|
||||
await this.$executeQuery('ALTER TABLE blocks MODIFY `weight` integer unsigned NOT NULL DEFAULT "0"');
|
||||
await this.$executeQuery('ALTER TABLE blocks MODIFY `difficulty` double NOT NULL DEFAULT "0"');
|
||||
await this.$executeQuery('ALTER TABLE blocks DROP FOREIGN KEY IF EXISTS `blocks_ibfk_1`');
|
||||
await this.$executeQuery('ALTER TABLE pools MODIFY `id` smallint unsigned AUTO_INCREMENT');
|
||||
await this.$executeQuery('ALTER TABLE blocks MODIFY `pool_id` smallint unsigned NULL');
|
||||
await this.$executeQuery('ALTER TABLE blocks ADD FOREIGN KEY (`pool_id`) REFERENCES `pools` (`id`)');
|
||||
await this.$executeQuery('ALTER TABLE blocks ADD `version` integer unsigned NOT NULL DEFAULT "0"');
|
||||
await this.$executeQuery('ALTER TABLE blocks ADD `bits` integer unsigned NOT NULL DEFAULT "0"');
|
||||
await this.$executeQuery('ALTER TABLE blocks ADD `nonce` bigint unsigned NOT NULL DEFAULT "0"');
|
||||
await this.$executeQuery('ALTER TABLE blocks ADD `merkle_root` varchar(65) NOT NULL DEFAULT ""');
|
||||
await this.$executeQuery('ALTER TABLE blocks ADD `previous_block_hash` varchar(65) NULL');
|
||||
|
||||
// Version 7
|
||||
await this.$executeQuery('DROP table IF EXISTS hashrates;');
|
||||
await this.$executeQuery(this.getCreateDailyStatsTableQuery(), await this.$checkIfTableExists('hashrates'));
|
||||
|
||||
// Version 8
|
||||
await this.$executeQuery('ALTER TABLE `hashrates` DROP INDEX `PRIMARY`');
|
||||
await this.$executeQuery('ALTER TABLE `hashrates` ADD `id` int NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST');
|
||||
await this.$executeQuery('ALTER TABLE `hashrates` ADD `share` float NOT NULL DEFAULT "0"');
|
||||
await this.$executeQuery('ALTER TABLE `hashrates` ADD `type` enum("daily", "weekly") DEFAULT "daily"');
|
||||
|
||||
// Version 9
|
||||
await this.$executeQuery('ALTER TABLE `state` CHANGE `name` `name` varchar(100)');
|
||||
await this.$executeQuery('ALTER TABLE `hashrates` ADD UNIQUE `hashrate_timestamp_pool_id` (`hashrate_timestamp`, `pool_id`)');
|
||||
|
||||
// Version 10
|
||||
await this.$executeQuery('ALTER TABLE `blocks` ADD INDEX `blockTimestamp` (`blockTimestamp`)');
|
||||
|
||||
// Version 11
|
||||
await this.$executeQuery(`ALTER TABLE blocks
|
||||
ADD avg_fee INT UNSIGNED NULL,
|
||||
ADD avg_fee_rate INT UNSIGNED NULL
|
||||
`);
|
||||
await this.$executeQuery('ALTER TABLE blocks MODIFY `reward` BIGINT UNSIGNED NOT NULL DEFAULT "0"');
|
||||
await this.$executeQuery('ALTER TABLE blocks MODIFY `median_fee` INT UNSIGNED NOT NULL DEFAULT "0"');
|
||||
await this.$executeQuery('ALTER TABLE blocks MODIFY `fees` INT UNSIGNED NOT NULL DEFAULT "0"');
|
||||
|
||||
// Version 12
|
||||
await this.$executeQuery('ALTER TABLE blocks MODIFY `fees` BIGINT UNSIGNED NOT NULL DEFAULT "0"');
|
||||
|
||||
// Version 13
|
||||
await this.$executeQuery('ALTER TABLE blocks MODIFY `difficulty` DOUBLE UNSIGNED NOT NULL DEFAULT "0"');
|
||||
await this.$executeQuery('ALTER TABLE blocks MODIFY `median_fee` BIGINT UNSIGNED NOT NULL DEFAULT "0"');
|
||||
await this.$executeQuery('ALTER TABLE blocks MODIFY `avg_fee` BIGINT UNSIGNED NOT NULL DEFAULT "0"');
|
||||
await this.$executeQuery('ALTER TABLE blocks MODIFY `avg_fee_rate` BIGINT UNSIGNED NOT NULL DEFAULT "0"');
|
||||
|
||||
// Version 14
|
||||
await this.$executeQuery('ALTER TABLE `hashrates` DROP FOREIGN KEY `hashrates_ibfk_1`');
|
||||
await this.$executeQuery('ALTER TABLE `hashrates` MODIFY `pool_id` SMALLINT UNSIGNED NOT NULL DEFAULT "0"');
|
||||
|
||||
// Version 17
|
||||
await this.$executeQuery('ALTER TABLE `pools` ADD `slug` CHAR(50) NULL');
|
||||
|
||||
// Version 18
|
||||
await this.$executeQuery('ALTER TABLE `blocks` ADD INDEX `hash` (`hash`);');
|
||||
|
||||
// Version 20
|
||||
await this.$executeQuery(this.getCreateBlocksSummariesTableQuery(), await this.$checkIfTableExists('blocks_summaries'));
|
||||
|
||||
// Version 22
|
||||
await this.$executeQuery('DROP TABLE IF EXISTS `difficulty_adjustments`');
|
||||
await this.$executeQuery(this.getCreateDifficultyAdjustmentsTableQuery(), await this.$checkIfTableExists('difficulty_adjustments'));
|
||||
|
||||
// Version 24
|
||||
await this.$executeQuery('DROP TABLE IF EXISTS `blocks_audits`');
|
||||
await this.$executeQuery(this.getCreateBlocksAuditsTableQuery(), await this.$checkIfTableExists('blocks_audits'));
|
||||
|
||||
// Version 25
|
||||
await this.$executeQuery(this.getCreateLightningStatisticsQuery(), await this.$checkIfTableExists('lightning_stats'));
|
||||
await this.$executeQuery(this.getCreateNodesQuery(), await this.$checkIfTableExists('nodes'));
|
||||
await this.$executeQuery(this.getCreateChannelsQuery(), await this.$checkIfTableExists('channels'));
|
||||
await this.$executeQuery(this.getCreateNodesStatsQuery(), await this.$checkIfTableExists('node_stats'));
|
||||
|
||||
// Version 26
|
||||
await this.$executeQuery('ALTER TABLE `lightning_stats` ADD tor_nodes int(11) NOT NULL DEFAULT "0"');
|
||||
await this.$executeQuery('ALTER TABLE `lightning_stats` ADD clearnet_nodes int(11) NOT NULL DEFAULT "0"');
|
||||
await this.$executeQuery('ALTER TABLE `lightning_stats` ADD unannounced_nodes int(11) NOT NULL DEFAULT "0"');
|
||||
|
||||
// Version 27
|
||||
await this.$executeQuery('ALTER TABLE `lightning_stats` ADD avg_capacity bigint(20) unsigned NOT NULL DEFAULT "0"');
|
||||
await this.$executeQuery('ALTER TABLE `lightning_stats` ADD avg_fee_rate int(11) unsigned NOT NULL DEFAULT "0"');
|
||||
await this.$executeQuery('ALTER TABLE `lightning_stats` ADD avg_base_fee_mtokens bigint(20) unsigned NOT NULL DEFAULT "0"');
|
||||
await this.$executeQuery('ALTER TABLE `lightning_stats` ADD med_capacity bigint(20) unsigned NOT NULL DEFAULT "0"');
|
||||
await this.$executeQuery('ALTER TABLE `lightning_stats` ADD med_fee_rate int(11) unsigned NOT NULL DEFAULT "0"');
|
||||
await this.$executeQuery('ALTER TABLE `lightning_stats` ADD med_base_fee_mtokens bigint(20) unsigned NOT NULL DEFAULT "0"');
|
||||
|
||||
// Version 28
|
||||
await this.$executeQuery(`ALTER TABLE lightning_stats MODIFY added DATE`);
|
||||
|
||||
// Version 29
|
||||
await this.$executeQuery(this.getCreateGeoNamesTableQuery(), await this.$checkIfTableExists('geo_names'));
|
||||
await this.$executeQuery('ALTER TABLE `nodes` ADD as_number int(11) unsigned NULL DEFAULT NULL');
|
||||
await this.$executeQuery('ALTER TABLE `nodes` ADD city_id int(11) unsigned NULL DEFAULT NULL');
|
||||
await this.$executeQuery('ALTER TABLE `nodes` ADD country_id int(11) unsigned NULL DEFAULT NULL');
|
||||
await this.$executeQuery('ALTER TABLE `nodes` ADD accuracy_radius int(11) unsigned NULL DEFAULT NULL');
|
||||
await this.$executeQuery('ALTER TABLE `nodes` ADD subdivision_id int(11) unsigned NULL DEFAULT NULL');
|
||||
await this.$executeQuery('ALTER TABLE `nodes` ADD longitude double NULL DEFAULT NULL');
|
||||
await this.$executeQuery('ALTER TABLE `nodes` ADD latitude double NULL DEFAULT NULL');
|
||||
|
||||
// Version 30
|
||||
await this.$executeQuery('ALTER TABLE `geo_names` CHANGE `type` `type` enum("city","country","division","continent","as_organization") NOT NULL');
|
||||
|
||||
// Version 31
|
||||
await this.$executeQuery('ALTER TABLE `prices` ADD `id` int NULL AUTO_INCREMENT UNIQUE');
|
||||
await this.$executeQuery('DROP TABLE IF EXISTS `blocks_prices`');
|
||||
await this.$executeQuery(this.getCreateBlocksPricesTableQuery(), await this.$checkIfTableExists('blocks_prices'));
|
||||
|
||||
// Version 32
|
||||
await this.$executeQuery('ALTER TABLE `blocks_summaries` ADD `template` JSON DEFAULT "[]"');
|
||||
|
||||
// Version 33
|
||||
await this.$executeQuery('ALTER TABLE `geo_names` CHANGE `type` `type` enum("city","country","division","continent","as_organization", "country_iso_code") NOT NULL');
|
||||
|
||||
// Version 34
|
||||
await this.$executeQuery('ALTER TABLE `lightning_stats` ADD clearnet_tor_nodes int(11) NOT NULL DEFAULT "0"');
|
||||
|
||||
// Version 35
|
||||
await this.$executeQuery('DELETE from `lightning_stats` WHERE added > "2021-09-19"');
|
||||
await this.$executeQuery('ALTER TABLE `lightning_stats` ADD CONSTRAINT added_unique UNIQUE (added);');
|
||||
|
||||
// Version 36
|
||||
await this.$executeQuery('ALTER TABLE `nodes` ADD status TINYINT NOT NULL DEFAULT "1"');
|
||||
|
||||
// Version 37
|
||||
await this.$executeQuery(this.getCreateLNNodesSocketsTableQuery(), await this.$checkIfTableExists('nodes_sockets'));
|
||||
|
||||
// Version 38
|
||||
await this.$executeQuery(`TRUNCATE lightning_stats`);
|
||||
await this.$executeQuery(`TRUNCATE node_stats`);
|
||||
await this.$executeQuery('ALTER TABLE `lightning_stats` CHANGE `added` `added` timestamp NULL');
|
||||
await this.$executeQuery('ALTER TABLE `node_stats` CHANGE `added` `added` timestamp NULL');
|
||||
await this.updateToSchemaVersion(38);
|
||||
|
||||
// Version 39
|
||||
await this.$executeQuery('ALTER TABLE `nodes` ADD alias_search TEXT NULL DEFAULT NULL AFTER `alias`');
|
||||
await this.$executeQuery('ALTER TABLE nodes ADD FULLTEXT(alias_search)');
|
||||
|
||||
// Version 40
|
||||
await this.$executeQuery('ALTER TABLE `nodes` ADD capacity bigint(20) unsigned DEFAULT NULL');
|
||||
await this.$executeQuery('ALTER TABLE `nodes` ADD channels int(11) unsigned DEFAULT NULL');
|
||||
await this.$executeQuery('ALTER TABLE `nodes` ADD INDEX `capacity` (`capacity`);');
|
||||
|
||||
// Version 41
|
||||
await this.$executeQuery('UPDATE channels SET closing_reason = NULL WHERE closing_reason = 1');
|
||||
|
||||
// Version 42
|
||||
await this.$executeQuery('ALTER TABLE `channels` ADD closing_resolved tinyint(1) DEFAULT 0');
|
||||
|
||||
// Version 43
|
||||
await this.$executeQuery(this.getCreateLNNodeRecordsTableQuery(), await this.$checkIfTableExists('nodes_records'));
|
||||
|
||||
// Version 44
|
||||
await this.$executeQuery('UPDATE blocks_summaries SET template = NULL');
|
||||
|
||||
// Version 45
|
||||
await this.$executeQuery('ALTER TABLE `blocks_audits` ADD fresh_txs JSON DEFAULT "[]"');
|
||||
|
||||
// Version 48
|
||||
await this.$executeQuery('ALTER TABLE `channels` ADD source_checked tinyint(1) DEFAULT 0');
|
||||
await this.$executeQuery('ALTER TABLE `channels` ADD closing_fee bigint(20) unsigned DEFAULT 0');
|
||||
await this.$executeQuery('ALTER TABLE `channels` ADD node1_funding_balance bigint(20) unsigned DEFAULT 0');
|
||||
await this.$executeQuery('ALTER TABLE `channels` ADD node2_funding_balance bigint(20) unsigned DEFAULT 0');
|
||||
await this.$executeQuery('ALTER TABLE `channels` ADD node1_closing_balance bigint(20) unsigned DEFAULT 0');
|
||||
await this.$executeQuery('ALTER TABLE `channels` ADD node2_closing_balance bigint(20) unsigned DEFAULT 0');
|
||||
await this.$executeQuery('ALTER TABLE `channels` ADD funding_ratio float unsigned DEFAULT NULL');
|
||||
await this.$executeQuery('ALTER TABLE `channels` ADD closed_by varchar(66) DEFAULT NULL');
|
||||
await this.$executeQuery('ALTER TABLE `channels` ADD single_funded tinyint(1) DEFAULT 0');
|
||||
await this.$executeQuery('ALTER TABLE `channels` ADD outputs JSON DEFAULT "[]"');
|
||||
|
||||
// Version 57
|
||||
await this.$executeQuery(`ALTER TABLE nodes MODIFY updated_at datetime NULL`);
|
||||
|
||||
// Version 60
|
||||
await this.$executeQuery('ALTER TABLE `blocks_audits` ADD sigop_txs JSON DEFAULT "[]"');
|
||||
|
||||
// Version 61
|
||||
if (! await this.$checkIfTableExists('blocks_templates')) {
|
||||
await this.$executeQuery('CREATE TABLE blocks_templates AS SELECT id, template FROM blocks_summaries WHERE template != "[]"');
|
||||
}
|
||||
await this.$executeQuery('ALTER TABLE blocks_templates MODIFY template JSON DEFAULT "[]"');
|
||||
await this.$executeQuery('ALTER TABLE blocks_templates ADD PRIMARY KEY (id)');
|
||||
await this.$executeQuery('ALTER TABLE blocks_summaries DROP COLUMN template');
|
||||
|
||||
// Version 62
|
||||
await this.$executeQuery('ALTER TABLE `blocks_audits` ADD expected_fees BIGINT UNSIGNED DEFAULT NULL');
|
||||
await this.$executeQuery('ALTER TABLE `blocks_audits` ADD expected_weight BIGINT UNSIGNED DEFAULT NULL');
|
||||
|
||||
// Version 63
|
||||
await this.$executeQuery('ALTER TABLE `blocks_audits` ADD fullrbf_txs JSON DEFAULT "[]"');
|
||||
|
||||
// Version 64
|
||||
await this.$executeQuery('ALTER TABLE `nodes` ADD features text NULL');
|
||||
|
||||
// Version 65
|
||||
await this.$executeQuery('ALTER TABLE `blocks_audits` ADD accelerated_txs JSON DEFAULT "[]"');
|
||||
|
||||
// Version 67
|
||||
await this.$executeQuery('ALTER TABLE `blocks_summaries` ADD version INT NOT NULL DEFAULT 0');
|
||||
await this.$executeQuery('ALTER TABLE `blocks_summaries` ADD INDEX `version` (`version`)');
|
||||
await this.$executeQuery('ALTER TABLE `blocks_templates` ADD version INT NOT NULL DEFAULT 0');
|
||||
await this.$executeQuery('ALTER TABLE `blocks_templates` ADD INDEX `version` (`version`)');
|
||||
|
||||
// Version 76
|
||||
await this.$executeQuery('ALTER TABLE `blocks_audits` ADD prioritized_txs JSON DEFAULT "[]"');
|
||||
|
||||
// Version 81
|
||||
await this.$executeQuery('ALTER TABLE `blocks_audits` ADD version INT NOT NULL DEFAULT 0');
|
||||
await this.$executeQuery('ALTER TABLE `blocks_audits` ADD INDEX `version` (`version`)');
|
||||
await this.$executeQuery('ALTER TABLE `blocks_audits` ADD unseen_txs JSON DEFAULT "[]"');
|
||||
|
||||
// Version 83
|
||||
await this.$executeQuery('ALTER TABLE `blocks` ADD first_seen datetime(6) DEFAULT NULL');
|
||||
|
||||
// Version 84
|
||||
await this.$executeQuery(`
|
||||
ALTER TABLE \`pools\`
|
||||
ADD INDEX \`slug\` (\`slug\`),
|
||||
ADD INDEX \`unique_id\` (\`unique_id\`)
|
||||
`);
|
||||
|
||||
// Version 85
|
||||
await this.$executeQuery(`
|
||||
ALTER TABLE \`channels\`
|
||||
ADD INDEX \`created\` (\`created\`),
|
||||
ADD INDEX \`capacity\` (\`capacity\`),
|
||||
ADD INDEX \`closing_reason\` (\`closing_reason\`),
|
||||
ADD INDEX \`closing_resolved\` (\`closing_resolved\`)
|
||||
`);
|
||||
|
||||
// Version 86
|
||||
await this.$executeQuery(`
|
||||
ALTER TABLE \`nodes\`
|
||||
ADD INDEX \`status\` (\`status\`),
|
||||
ADD INDEX \`channels\` (\`channels\`),
|
||||
ADD INDEX \`country_id\` (\`country_id\`),
|
||||
ADD INDEX \`as_number\` (\`as_number\`),
|
||||
ADD INDEX \`first_seen\` (\`first_seen\`)
|
||||
`);
|
||||
|
||||
// Version 87
|
||||
await this.$executeQuery('ALTER TABLE `nodes_sockets` ADD INDEX `type` (`type`)');
|
||||
await this.updateToSchemaVersion(87);
|
||||
|
||||
// Version 88
|
||||
await this.$executeQuery('ALTER TABLE `lightning_stats` ADD INDEX `added` (`added`)');
|
||||
|
||||
// Version 89
|
||||
await this.$executeQuery('ALTER TABLE `geo_names` ADD INDEX `names` (`names`)');
|
||||
|
||||
// Version 90
|
||||
await this.$executeQuery('ALTER TABLE `hashrates` ADD INDEX `type` (`type`)');
|
||||
|
||||
// Version 91
|
||||
await this.$executeQuery('ALTER TABLE `blocks_audits` ADD INDEX `time` (`time`)');
|
||||
}
|
||||
|
||||
if (config.MEMPOOL.NETWORK !== 'liquid') {
|
||||
// Apply all the liquid specific migrations to all other networks
|
||||
// Version 68
|
||||
await this.$executeQuery('ALTER TABLE elements_pegs ADD PRIMARY KEY (txid, txindex);');
|
||||
await this.$executeQuery(this.getCreateFederationAddressesTableQuery(), await this.$checkIfTableExists('federation_addresses'));
|
||||
await this.$executeQuery(this.getCreateFederationTxosTableQuery(), await this.$checkIfTableExists('federation_txos'));
|
||||
|
||||
// Version 71
|
||||
await this.$executeQuery('ALTER TABLE `federation_txos` ADD timelock INT NOT NULL DEFAULT 0');
|
||||
await this.$executeQuery('ALTER TABLE `federation_txos` ADD expiredAt INT NOT NULL DEFAULT 0');
|
||||
await this.$executeQuery('ALTER TABLE `federation_txos` ADD emergencyKey TINYINT NOT NULL DEFAULT 0');
|
||||
|
||||
// Version 92
|
||||
await this.$executeQuery(`
|
||||
ALTER TABLE \`elements_pegs\`
|
||||
ADD INDEX \`block\` (\`block\`),
|
||||
ADD INDEX \`datetime\` (\`datetime\`),
|
||||
ADD INDEX \`amount\` (\`amount\`),
|
||||
ADD INDEX \`bitcoinaddress\` (\`bitcoinaddress\`),
|
||||
ADD INDEX \`bitcointxid\` (\`bitcointxid\`)
|
||||
`);
|
||||
|
||||
// Version 93
|
||||
await this.$executeQuery(`
|
||||
ALTER TABLE \`federation_txos\`
|
||||
ADD INDEX \`unspent\` (\`unspent\`),
|
||||
ADD INDEX \`lastblockupdate\` (\`lastblockupdate\`),
|
||||
ADD INDEX \`blocktime\` (\`blocktime\`),
|
||||
ADD INDEX \`emergencyKey\` (\`emergencyKey\`),
|
||||
ADD INDEX \`expiredAt\` (\`expiredAt\`)
|
||||
`);
|
||||
}
|
||||
|
||||
if (config.MEMPOOL.NETWORK !== 'mainnet') {
|
||||
// Apply all the mainnet specific migrations to all other networks
|
||||
// Version 69
|
||||
await this.$executeQuery(this.getCreateAccelerationsTableQuery(), await this.$checkIfTableExists('accelerations'));
|
||||
|
||||
// Version 70
|
||||
await this.$executeQuery('ALTER TABLE accelerations MODIFY COLUMN added DATETIME;');
|
||||
|
||||
// Version 77
|
||||
await this.$executeQuery('ALTER TABLE `accelerations` ADD requested datetime DEFAULT NULL');
|
||||
}
|
||||
await this.updateToSchemaVersion(94);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,8 +3,6 @@ import { Application, Request, Response } from 'express';
|
||||
import channelsApi from './channels.api';
|
||||
import { handleError } from '../../utils/api';
|
||||
|
||||
const TXID_REGEX = /^[a-f0-9]{64}$/i;
|
||||
|
||||
class ChannelsRoutes {
|
||||
constructor() { }
|
||||
|
||||
@@ -25,7 +23,7 @@ class ChannelsRoutes {
|
||||
const channels = await channelsApi.$searchChannelsById(req.params.search);
|
||||
res.json(channels);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to search channels by id');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +39,7 @@ class ChannelsRoutes {
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
|
||||
res.json(channel);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get channel');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +70,7 @@ class ChannelsRoutes {
|
||||
res.header('X-Total-Count', channelsCount.toString());
|
||||
res.json(channels);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get channels for node');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,10 +83,7 @@ class ChannelsRoutes {
|
||||
const txIds: string[] = [];
|
||||
for (const _txId in req.query.txId) {
|
||||
if (typeof req.query.txId[_txId] === 'string') {
|
||||
const txid = req.query.txId[_txId].toString();
|
||||
if (TXID_REGEX.test(txid)) {
|
||||
txIds.push(txid);
|
||||
}
|
||||
txIds.push(req.query.txId[_txId].toString());
|
||||
}
|
||||
}
|
||||
const channels = await channelsApi.$getChannelsByTransactionId(txIds);
|
||||
@@ -113,7 +108,7 @@ class ChannelsRoutes {
|
||||
|
||||
res.json(result);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get channels by transaction ids');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +120,7 @@ class ChannelsRoutes {
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
|
||||
res.json(channels);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get penalty closed channels');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,7 +133,7 @@ class ChannelsRoutes {
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
|
||||
res.json(channels);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get channel geodata');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ class GeneralLightningRoutes {
|
||||
channels: channels,
|
||||
});
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to search for nodes and channels');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ class GeneralLightningRoutes {
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
|
||||
res.json(statistics);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get lightning statistics');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ class GeneralLightningRoutes {
|
||||
const statistics = await statisticsApi.$getLatestStatistics();
|
||||
res.json(statistics);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get lightning statistics');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ class NodesRoutes {
|
||||
const nodes = await nodesApi.$searchNodeByPublicKeyOrAlias(req.params.search);
|
||||
res.json(nodes);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to search for node');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,7 +188,7 @@ class NodesRoutes {
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
|
||||
res.json(nodes);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get node group');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,7 +204,7 @@ class NodesRoutes {
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
|
||||
res.json(node);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get node');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -216,7 +216,7 @@ class NodesRoutes {
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
|
||||
res.json(statistics);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get historical node stats');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,7 +232,7 @@ class NodesRoutes {
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
|
||||
res.json(node);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get fee histogram');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -248,7 +248,7 @@ class NodesRoutes {
|
||||
topByChannels: topChannelsNodes,
|
||||
});
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get nodes ranking');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -260,7 +260,7 @@ class NodesRoutes {
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
|
||||
res.json(topCapacityNodes);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get top nodes by capacity');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,7 +272,7 @@ class NodesRoutes {
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
|
||||
res.json(topCapacityNodes);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get top nodes by channels');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,7 +284,7 @@ class NodesRoutes {
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
|
||||
res.json(topCapacityNodes);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get oldest nodes');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,7 +296,7 @@ class NodesRoutes {
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 600).toUTCString());
|
||||
res.json(nodesPerAs);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get ISP ranking');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,7 +308,7 @@ class NodesRoutes {
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 600).toUTCString());
|
||||
res.json(worldNodes);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get world nodes');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -336,7 +336,7 @@ class NodesRoutes {
|
||||
nodes: nodes,
|
||||
});
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get nodes per country');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -363,7 +363,7 @@ class NodesRoutes {
|
||||
nodes: nodes,
|
||||
});
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get nodes per ISP');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -375,7 +375,7 @@ class NodesRoutes {
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 600).toUTCString());
|
||||
res.json(nodesPerAs);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get nodes per country');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ class LiquidRoutes {
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 60 * 60).toUTCString());
|
||||
res.json(pegs);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get pegs by month');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ class LiquidRoutes {
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 60 * 60).toUTCString());
|
||||
res.json(reserves);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get reserves by month');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ class LiquidRoutes {
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 30).toUTCString());
|
||||
res.json(currentSupply);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get pegs');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ class LiquidRoutes {
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 30).toUTCString());
|
||||
res.json(currentReserves);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get reserves');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ class LiquidRoutes {
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 30).toUTCString());
|
||||
res.json(auditStatus);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get federation audit status');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ class LiquidRoutes {
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 30).toUTCString());
|
||||
res.json(federationAddresses);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get federation addresses');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,7 +155,7 @@ class LiquidRoutes {
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 30).toUTCString());
|
||||
res.json(federationAddresses);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get federation addresses');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,7 +167,7 @@ class LiquidRoutes {
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 30).toUTCString());
|
||||
res.json(federationUtxos);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get federation utxos');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ class LiquidRoutes {
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 30).toUTCString());
|
||||
res.json(expiredUtxos);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get expired utxos');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,7 +191,7 @@ class LiquidRoutes {
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 30).toUTCString());
|
||||
res.json(federationUtxos);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get federation utxos number');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,7 +203,7 @@ class LiquidRoutes {
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 30).toUTCString());
|
||||
res.json(emergencySpentUtxos);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get emergency spent utxos');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -215,7 +215,7 @@ class LiquidRoutes {
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 30).toUTCString());
|
||||
res.json(emergencySpentUtxos);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get emergency spent utxos stats');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,7 +227,7 @@ class LiquidRoutes {
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 30).toUTCString());
|
||||
res.json(recentPegs);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get pegs list');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,7 +239,7 @@ class LiquidRoutes {
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 30).toUTCString());
|
||||
res.json(pegsVolume);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get pegs volume daily');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,7 +251,7 @@ class LiquidRoutes {
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 30).toUTCString());
|
||||
res.json(pegsCount);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get pegs count');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ class MiningRoutes {
|
||||
}
|
||||
res.status(200).send(response);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get historical prices');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ class MiningRoutes {
|
||||
if (e instanceof Error && e.message.indexOf('This mining pool does not exist') > -1) {
|
||||
handleError(req, res, 404, e.message);
|
||||
} else {
|
||||
handleError(req, res, 500, 'Failed to get pool');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -106,7 +106,7 @@ class MiningRoutes {
|
||||
if (e instanceof Error && e.message.indexOf('This mining pool does not exist') > -1) {
|
||||
handleError(req, res, 404, e.message);
|
||||
} else {
|
||||
handleError(req, res, 500, 'Failed to get blocks for pool');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -130,7 +130,7 @@ class MiningRoutes {
|
||||
res.json(pools);
|
||||
}
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get pools');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ class MiningRoutes {
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
|
||||
res.json(stats);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get pools');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,7 +158,7 @@ class MiningRoutes {
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString());
|
||||
res.json(hashrates);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get pools historical hashrate');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,7 +175,7 @@ class MiningRoutes {
|
||||
if (e instanceof Error && e.message.indexOf('This mining pool does not exist') > -1) {
|
||||
handleError(req, res, 404, e.message);
|
||||
} else {
|
||||
handleError(req, res, 500, 'Failed to get pool historical hashrate');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -204,7 +204,7 @@ class MiningRoutes {
|
||||
currentDifficulty: currentDifficulty,
|
||||
});
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get historical hashrate');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,7 +218,7 @@ class MiningRoutes {
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
|
||||
res.json(blockFees);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get historical block fees');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,7 +236,7 @@ class MiningRoutes {
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
|
||||
res.json(blockFees);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get historical block fees');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,7 +250,7 @@ class MiningRoutes {
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
|
||||
res.json(blockRewards);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get historical block rewards');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -264,7 +264,7 @@ class MiningRoutes {
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
|
||||
res.json(blockFeeRates);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get historical block fee rates');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,7 +282,7 @@ class MiningRoutes {
|
||||
weights: blockWeights
|
||||
});
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get historical block size and weight');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -294,7 +294,7 @@ class MiningRoutes {
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString());
|
||||
res.json(difficulty.map(adj => [adj.time, adj.height, adj.difficulty, adj.adjustment]));
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get historical difficulty adjustments');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -304,7 +304,7 @@ class MiningRoutes {
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
|
||||
res.json(response);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get reward stats');
|
||||
res.status(500).end();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,7 +318,7 @@ class MiningRoutes {
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
|
||||
res.json(blocksHealth.map(health => [health.time, health.height, health.match_rate]));
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get historical blocks health');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -336,7 +336,7 @@ class MiningRoutes {
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 3600 * 24).toUTCString());
|
||||
res.json(audit);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get block audit');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -359,7 +359,7 @@ class MiningRoutes {
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 300).toUTCString());
|
||||
res.json(result);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get height from timestamp');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -372,7 +372,7 @@ class MiningRoutes {
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 60).toUTCString());
|
||||
res.json(await BlocksAuditsRepository.$getBlockAuditScores(height, height - 15));
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get block audit scores');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -385,7 +385,7 @@ class MiningRoutes {
|
||||
res.setHeader('Expires', new Date(Date.now() + 1000 * 3600 * 24).toUTCString());
|
||||
res.json(audit || 'null');
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get block audit score');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -400,7 +400,7 @@ class MiningRoutes {
|
||||
}
|
||||
res.status(200).send(await AccelerationRepository.$getAccelerationInfo(req.params.slug));
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get accelerations by pool');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -416,7 +416,7 @@ class MiningRoutes {
|
||||
const height = req.params.height === undefined ? undefined : parseInt(req.params.height, 10);
|
||||
res.status(200).send(await AccelerationRepository.$getAccelerationInfo(null, height));
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get accelerations by height');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -431,7 +431,7 @@ class MiningRoutes {
|
||||
}
|
||||
res.status(200).send(await AccelerationRepository.$getAccelerationInfo(null, null, req.params.interval));
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get recent accelerations');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -446,7 +446,7 @@ class MiningRoutes {
|
||||
}
|
||||
res.status(200).send(await AccelerationRepository.$getAccelerationTotals(<string>req.query.pool, <string>req.query.interval));
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get acceleration totals');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -461,7 +461,7 @@ class MiningRoutes {
|
||||
}
|
||||
res.status(200).send(Object.values(accelerationApi.getAccelerations() || {}));
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get active accelerations');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -473,7 +473,7 @@ class MiningRoutes {
|
||||
accelerationApi.accelerationRequested(req.params.txid);
|
||||
res.status(200).send();
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to request acceleration');
|
||||
handleError(req, res, 500, e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Application, Request, Response } from 'express';
|
||||
import config from '../../config';
|
||||
import WalletApi from './wallets';
|
||||
import { handleError } from '../../utils/api';
|
||||
|
||||
class ServicesRoutes {
|
||||
public initRoutes(app: Application): void {
|
||||
@@ -19,7 +18,7 @@ class ServicesRoutes {
|
||||
const wallet = await WalletApi.getWallet(walletId);
|
||||
res.status(200).send(wallet);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get wallet');
|
||||
res.status(500).send(e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,8 +19,13 @@ interface WalletAddress {
|
||||
lastSync: number;
|
||||
}
|
||||
|
||||
interface Wallet {
|
||||
interface WalletConfig {
|
||||
url: string;
|
||||
name: string;
|
||||
apiKey: string;
|
||||
}
|
||||
|
||||
interface Wallet extends WalletConfig {
|
||||
addresses: Record<string, WalletAddress>;
|
||||
lastPoll: number;
|
||||
}
|
||||
@@ -32,10 +37,10 @@ class WalletApi {
|
||||
private syncing = false;
|
||||
|
||||
constructor() {
|
||||
this.wallets = config.WALLETS.ENABLED ? (config.WALLETS.WALLETS as string[]).reduce((acc, wallet) => {
|
||||
acc[wallet] = { name: wallet, addresses: {}, lastPoll: 0 };
|
||||
this.wallets = (config.WALLETS.WALLETS as WalletConfig[]).reduce((acc, wallet) => {
|
||||
acc[wallet.name] = { ...wallet, addresses: {}, lastPoll: 0 };
|
||||
return acc;
|
||||
}, {} as Record<string, Wallet>) : {};
|
||||
}, {} as Record<string, Wallet>);
|
||||
}
|
||||
|
||||
public getWallet(wallet: string): Record<string, WalletAddress> {
|
||||
@@ -52,16 +57,18 @@ class WalletApi {
|
||||
const wallet = this.wallets[walletKey];
|
||||
if (wallet.lastPoll < (Date.now() - POLL_FREQUENCY)) {
|
||||
try {
|
||||
const response = await axios.get(config.MEMPOOL_SERVICES.API + `/wallets/${wallet.name}`);
|
||||
const addresses: Record<string, WalletAddress> = response.data;
|
||||
const addressList: WalletAddress[] = Object.values(addresses);
|
||||
const response = await axios.get(`${wallet.url}/${wallet.name}`, { headers: { 'Authorization': `${wallet.apiKey}` } });
|
||||
const data: { walletBalances: WalletAddress[] } = response.data;
|
||||
const addresses = data.walletBalances;
|
||||
const newAddresses: Record<string, boolean> = {};
|
||||
// sync all current addresses
|
||||
for (const address of addressList) {
|
||||
for (const address of addresses) {
|
||||
await this.$syncWalletAddress(wallet, address);
|
||||
newAddresses[address.address] = true;
|
||||
}
|
||||
// remove old addresses
|
||||
for (const address of Object.keys(wallet.addresses)) {
|
||||
if (!addresses[address]) {
|
||||
if (!newAddresses[address]) {
|
||||
delete wallet.addresses[address];
|
||||
}
|
||||
}
|
||||
@@ -86,10 +93,11 @@ class WalletApi {
|
||||
const walletAddress: WalletAddress = {
|
||||
address: address.address,
|
||||
active: address.active,
|
||||
transactions: summary,
|
||||
transactions: await bitcoinApi.$getAddressTransactionSummary(address.address),
|
||||
stats: addressInfo.chain_stats,
|
||||
lastSync: Date.now(),
|
||||
};
|
||||
logger.debug(`Synced ${walletAddress.transactions?.length || 0} transactions for wallet ${wallet.name} address ${address.address}`);
|
||||
wallet.addresses[address.address] = walletAddress;
|
||||
} catch (e) {
|
||||
logger.err(`Error syncing wallet address ${address.address}: ${(e instanceof Error ? e.message : e)}`);
|
||||
@@ -142,7 +150,17 @@ class WalletApi {
|
||||
wallet.addresses[address].transactions?.push(txSummary);
|
||||
}
|
||||
if (anyMatch) {
|
||||
walletTransactions[walletKey].push(tx);
|
||||
for (const address of Object.keys({ ...funded, ...spent })) {
|
||||
if (!walletTransactions[walletKey][address]) {
|
||||
walletTransactions[walletKey][address] = [];
|
||||
}
|
||||
walletTransactions[walletKey][address].push({
|
||||
txid: tx.txid,
|
||||
value: (funded[address] ?? 0) - (spent[address] ?? 0),
|
||||
height: block.height,
|
||||
time: block.timestamp,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -150,4 +168,4 @@ class WalletApi {
|
||||
}
|
||||
}
|
||||
|
||||
export default new WalletApi();
|
||||
export default new WalletApi();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Application, Request, Response } from 'express';
|
||||
import config from '../../config';
|
||||
import statisticsApi from './statistics-api';
|
||||
import { handleError } from '../../utils/api';
|
||||
|
||||
class StatisticsRoutes {
|
||||
public initRoutes(app: Application) {
|
||||
app
|
||||
@@ -65,7 +65,7 @@ class StatisticsRoutes {
|
||||
}
|
||||
res.json(result);
|
||||
} catch (e) {
|
||||
handleError(req, res, 500, 'Failed to get statistics');
|
||||
res.status(500).send(e instanceof Error ? e.message : e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,7 +164,11 @@ interface IConfig {
|
||||
},
|
||||
WALLETS: {
|
||||
ENABLED: boolean;
|
||||
WALLETS: string[];
|
||||
WALLETS: {
|
||||
url: string;
|
||||
name: string;
|
||||
apiKey: string;
|
||||
}[];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -324,9 +324,7 @@ class Server {
|
||||
|
||||
setUpHttpApiRoutes(): void {
|
||||
bitcoinRoutes.initRoutes(this.app);
|
||||
if (config.MEMPOOL.OFFICIAL) {
|
||||
bitcoinCoreRoutes.initRoutes(this.app);
|
||||
}
|
||||
bitcoinCoreRoutes.initRoutes(this.app);
|
||||
pricesRoutes.initRoutes(this.app);
|
||||
if (config.STATISTICS.ENABLED && config.DATABASE.ENABLED && config.MEMPOOL.ENABLED) {
|
||||
statisticsRoutes.initRoutes(this.app);
|
||||
|
||||
@@ -38,12 +38,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"component": "simpleproof",
|
||||
"mobileOrder": 6,
|
||||
"props": {
|
||||
"label": "Executive Decrees",
|
||||
"key": "el_salvador_decretos"
|
||||
}
|
||||
"component": "blocks"
|
||||
},
|
||||
{
|
||||
"component": "addressTransactions",
|
||||
|
||||
331
frontend/package-lock.json
generated
331
frontend/package-lock.json
generated
@@ -23,9 +23,9 @@
|
||||
"@angular/router": "^17.3.1",
|
||||
"@angular/ssr": "^17.3.1",
|
||||
"@fortawesome/angular-fontawesome": "~0.14.1",
|
||||
"@fortawesome/fontawesome-common-types": "~6.7.2",
|
||||
"@fortawesome/fontawesome-svg-core": "~6.7.2",
|
||||
"@fortawesome/free-solid-svg-icons": "~6.7.2",
|
||||
"@fortawesome/fontawesome-common-types": "~6.6.0",
|
||||
"@fortawesome/fontawesome-svg-core": "~6.6.0",
|
||||
"@fortawesome/free-solid-svg-icons": "~6.6.0",
|
||||
"@mempool/mempool.js": "2.3.0",
|
||||
"@ng-bootstrap/ng-bootstrap": "^16.0.0",
|
||||
"@types/qrcode": "~1.5.0",
|
||||
@@ -35,6 +35,7 @@
|
||||
"domino": "^2.1.6",
|
||||
"echarts": "~5.5.0",
|
||||
"esbuild": "^0.24.0",
|
||||
"lightweight-charts": "~3.8.0",
|
||||
"ngx-echarts": "~17.2.0",
|
||||
"ngx-infinite-scroll": "^17.0.0",
|
||||
"qrcode": "1.5.1",
|
||||
@@ -61,7 +62,7 @@
|
||||
"optionalDependencies": {
|
||||
"@cypress/schematic": "^2.5.0",
|
||||
"@types/cypress": "^1.1.3",
|
||||
"cypress": "^13.17.0",
|
||||
"cypress": "^13.15.0",
|
||||
"cypress-fail-on-console-error": "~5.1.0",
|
||||
"cypress-wait-until": "^2.0.1",
|
||||
"mock-socket": "~9.3.1",
|
||||
@@ -3112,10 +3113,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@cypress/request": {
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.7.tgz",
|
||||
"integrity": "sha512-LzxlLEMbBOPYB85uXrDqvD4MgcenjRBLIns3zyhx7vTPj/0u2eQhzXvPiGcaJrV38Q9dbkExWp6cOHPJ+EtFYg==",
|
||||
"license": "Apache-2.0",
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.5.tgz",
|
||||
"integrity": "sha512-v+XHd9XmWbufxF1/bTaVm2yhbxY+TB4YtWRqF2zaXBlDNMkls34KiATz0AVDLavL3iB6bQk9/7n3oY1EoLSWGA==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"aws-sign2": "~0.7.0",
|
||||
@@ -3131,9 +3131,9 @@
|
||||
"json-stringify-safe": "~5.0.1",
|
||||
"mime-types": "~2.1.19",
|
||||
"performance-now": "^2.1.0",
|
||||
"qs": "6.13.1",
|
||||
"qs": "6.13.0",
|
||||
"safe-buffer": "^5.1.2",
|
||||
"tough-cookie": "^5.0.0",
|
||||
"tough-cookie": "^4.1.3",
|
||||
"tunnel-agent": "^0.6.0",
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
@@ -3141,22 +3141,6 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/@cypress/request/node_modules/qs": {
|
||||
"version": "6.13.1",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.1.tgz",
|
||||
"integrity": "sha512-EJPeIn0CYrGu+hli1xilKAPXODtJ12T0sP63Ijx2/khC2JtuaN3JyNIpvmnkmaEtha9ocbG4A4cMcr+TvqvwQg==",
|
||||
"license": "BSD-3-Clause",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"side-channel": "^1.0.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/@cypress/schematic": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@cypress/schematic/-/schematic-2.5.0.tgz",
|
||||
@@ -3690,33 +3674,30 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@fortawesome/fontawesome-common-types": {
|
||||
"version": "6.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.2.tgz",
|
||||
"integrity": "sha512-Zs+YeHUC5fkt7Mg1l6XTniei3k4bwG/yo3iFUtZWd/pMx9g3fdvkSK9E0FOC+++phXOka78uJcYb8JaFkW52Xg==",
|
||||
"license": "MIT",
|
||||
"version": "6.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.6.0.tgz",
|
||||
"integrity": "sha512-xyX0X9mc0kyz9plIyryrRbl7ngsA9jz77mCZJsUkLl+ZKs0KWObgaEBoSgQiYWAsSmjz/yjl0F++Got0Mdp4Rw==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@fortawesome/fontawesome-svg-core": {
|
||||
"version": "6.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.7.2.tgz",
|
||||
"integrity": "sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA==",
|
||||
"license": "MIT",
|
||||
"version": "6.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.6.0.tgz",
|
||||
"integrity": "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg==",
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-common-types": "6.7.2"
|
||||
"@fortawesome/fontawesome-common-types": "6.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@fortawesome/free-solid-svg-icons": {
|
||||
"version": "6.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.7.2.tgz",
|
||||
"integrity": "sha512-GsBrnOzU8uj0LECDfD5zomZJIjrPhIlWU82AHwa2s40FKH+kcxQaBvBo3Z4TxyZHIyX8XTDxsyA33/Vx9eFuQA==",
|
||||
"license": "(CC-BY-4.0 AND MIT)",
|
||||
"version": "6.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.6.0.tgz",
|
||||
"integrity": "sha512-IYv/2skhEDFc2WGUcqvFJkeK39Q+HyPf5GHUrT/l2pKbtgEIv1al1TKd6qStR5OIwQdN1GZP54ci3y4mroJWjA==",
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-common-types": "6.7.2"
|
||||
"@fortawesome/fontawesome-common-types": "6.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
@@ -5692,7 +5673,6 @@
|
||||
"version": "0.2.6",
|
||||
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz",
|
||||
"integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"safer-buffer": "~2.1.0"
|
||||
@@ -5727,7 +5707,6 @@
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
|
||||
"integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=0.8"
|
||||
@@ -5848,7 +5827,6 @@
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
|
||||
"integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==",
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": "*"
|
||||
@@ -5858,7 +5836,6 @@
|
||||
"version": "1.13.2",
|
||||
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz",
|
||||
"integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/axios": {
|
||||
@@ -6016,7 +5993,6 @@
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
|
||||
"integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==",
|
||||
"license": "BSD-3-Clause",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tweetnacl": "^0.14.3"
|
||||
@@ -7092,7 +7068,6 @@
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
|
||||
"integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==",
|
||||
"license": "Apache-2.0",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/chai": {
|
||||
@@ -7195,16 +7170,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ci-info": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.1.0.tgz",
|
||||
"integrity": "sha512-HutrvTNsF48wnxkzERIXOe5/mlcfFcbfCmwcg6CJnizbSue78AbDt+1cgl26zwn61WFxhcPykPfZrbqjGmBb4A==",
|
||||
"version": "3.8.0",
|
||||
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz",
|
||||
"integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/sibiraj-s"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
@@ -7979,14 +7953,13 @@
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/cypress": {
|
||||
"version": "13.17.0",
|
||||
"resolved": "https://registry.npmjs.org/cypress/-/cypress-13.17.0.tgz",
|
||||
"integrity": "sha512-5xWkaPurwkIljojFidhw8lFScyxhtiFHl/i/3zov+1Z5CmY4t9tjIdvSXfu82Y3w7wt0uR9KkucbhkVvJZLQSA==",
|
||||
"version": "13.15.0",
|
||||
"resolved": "https://registry.npmjs.org/cypress/-/cypress-13.15.0.tgz",
|
||||
"integrity": "sha512-53aO7PwOfi604qzOkCSzNlWquCynLlKE/rmmpSPcziRH6LNfaDUAklQT6WJIsD8ywxlIy+uVZsnTMCCQVd2kTw==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@cypress/request": "^3.0.6",
|
||||
"@cypress/request": "^3.0.4",
|
||||
"@cypress/xvfb": "^1.2.4",
|
||||
"@types/sinonjs__fake-timers": "8.1.1",
|
||||
"@types/sizzle": "^2.3.2",
|
||||
@@ -7997,7 +7970,6 @@
|
||||
"cachedir": "^2.3.0",
|
||||
"chalk": "^4.1.0",
|
||||
"check-more-types": "^2.24.0",
|
||||
"ci-info": "^4.0.0",
|
||||
"cli-cursor": "^3.1.0",
|
||||
"cli-table3": "~0.6.1",
|
||||
"commander": "^6.2.1",
|
||||
@@ -8012,6 +7984,7 @@
|
||||
"figures": "^3.2.0",
|
||||
"fs-extra": "^9.1.0",
|
||||
"getos": "^3.2.1",
|
||||
"is-ci": "^3.0.1",
|
||||
"is-installed-globally": "~0.4.0",
|
||||
"lazy-ass": "^1.6.0",
|
||||
"listr2": "^3.8.3",
|
||||
@@ -8026,7 +7999,6 @@
|
||||
"semver": "^7.5.3",
|
||||
"supports-color": "^8.1.1",
|
||||
"tmp": "~0.2.3",
|
||||
"tree-kill": "1.2.2",
|
||||
"untildify": "^4.0.0",
|
||||
"yauzl": "^2.10.0"
|
||||
},
|
||||
@@ -8229,7 +8201,6 @@
|
||||
"version": "1.14.1",
|
||||
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
|
||||
"integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"assert-plus": "^1.0.0"
|
||||
@@ -8716,7 +8687,6 @@
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
|
||||
"integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"jsbn": "~0.1.0",
|
||||
@@ -9935,7 +9905,6 @@
|
||||
"engines": [
|
||||
"node >=0.6.0"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/falafel": {
|
||||
@@ -9952,6 +9921,11 @@
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fancy-canvas": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/fancy-canvas/-/fancy-canvas-0.2.2.tgz",
|
||||
"integrity": "sha512-50qi8xA0QkHbjmb8h7XQ6k2fvD7y/yMfiUw9YTarJ7rWrq6o5/3CCXPouYk+XSLASvvxtjyiQLRBFt3qkE3oyA=="
|
||||
},
|
||||
"node_modules/fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
@@ -10219,7 +10193,6 @@
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
|
||||
"integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==",
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": "*"
|
||||
@@ -10427,7 +10400,6 @@
|
||||
"version": "0.1.7",
|
||||
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
|
||||
"integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"assert-plus": "^1.0.0"
|
||||
@@ -10882,7 +10854,6 @@
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz",
|
||||
"integrity": "sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"assert-plus": "^1.0.0",
|
||||
@@ -11249,6 +11220,18 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/is-ci": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz",
|
||||
"integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"ci-info": "^3.2.0"
|
||||
},
|
||||
"bin": {
|
||||
"is-ci": "bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/is-core-module": {
|
||||
"version": "2.13.1",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
|
||||
@@ -11498,7 +11481,6 @@
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
|
||||
"integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/is-unicode-supported": {
|
||||
@@ -11563,7 +11545,6 @@
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
|
||||
"integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/istanbul-lib-coverage": {
|
||||
@@ -11697,7 +11678,6 @@
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
|
||||
"integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/jsesc": {
|
||||
@@ -11726,7 +11706,6 @@
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
|
||||
"integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==",
|
||||
"license": "(AFL-2.1 OR BSD-3-Clause)",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/json-schema-traverse": {
|
||||
@@ -11744,7 +11723,6 @@
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
|
||||
"integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==",
|
||||
"license": "ISC",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/json5": {
|
||||
@@ -11805,7 +11783,6 @@
|
||||
"engines": [
|
||||
"node >=0.6.0"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"assert-plus": "1.0.0",
|
||||
@@ -12129,6 +12106,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/lightweight-charts": {
|
||||
"version": "3.8.0",
|
||||
"resolved": "https://registry.npmjs.org/lightweight-charts/-/lightweight-charts-3.8.0.tgz",
|
||||
"integrity": "sha512-7yFGnYuE1RjRJG9RwUTBz5wvF1QtjBOSW4FFlikr8Dh+/TDNt4ci+HsWSYmStgQUpawpvkCJ3j5/W25GppGj9Q==",
|
||||
"dependencies": {
|
||||
"fancy-canvas": "0.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/limiter": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz",
|
||||
@@ -14125,7 +14110,6 @@
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
|
||||
"integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/picocolors": {
|
||||
@@ -14556,6 +14540,12 @@
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/psl": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
|
||||
"integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/public-encrypt": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz",
|
||||
@@ -14671,6 +14661,12 @@
|
||||
"node": ">=0.4.x"
|
||||
}
|
||||
},
|
||||
"node_modules/querystringify": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
|
||||
"integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/queue-microtask": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||
@@ -16032,7 +16028,6 @@
|
||||
"version": "1.18.0",
|
||||
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz",
|
||||
"integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"asn1": "~0.2.3",
|
||||
@@ -16582,26 +16577,6 @@
|
||||
"readable-stream": "3"
|
||||
}
|
||||
},
|
||||
"node_modules/tldts": {
|
||||
"version": "6.1.70",
|
||||
"resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.70.tgz",
|
||||
"integrity": "sha512-/W1YVgYVJd9ZDjey5NXadNh0mJXkiUMUue9Zebd0vpdo1sU+H4zFFTaJ1RKD4N6KFoHfcXy6l+Vu7bh+bdWCzA==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tldts-core": "^6.1.70"
|
||||
},
|
||||
"bin": {
|
||||
"tldts": "bin/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/tldts-core": {
|
||||
"version": "6.1.70",
|
||||
"resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.70.tgz",
|
||||
"integrity": "sha512-RNnIXDB1FD4T9cpQRErEqw6ZpjLlGdMOitdV+0xtbsnwr4YFka1zpc7D4KD+aAn8oSG5JyFrdasZTE04qDE9Yg==",
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/tlite": {
|
||||
"version": "0.1.9",
|
||||
"resolved": "https://registry.npmjs.org/tlite/-/tlite-0.1.9.tgz",
|
||||
@@ -16646,16 +16621,27 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tough-cookie": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.0.0.tgz",
|
||||
"integrity": "sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==",
|
||||
"license": "BSD-3-Clause",
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz",
|
||||
"integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tldts": "^6.1.32"
|
||||
"psl": "^1.1.33",
|
||||
"punycode": "^2.1.1",
|
||||
"universalify": "^0.2.0",
|
||||
"url-parse": "^1.5.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/tough-cookie/node_modules/universalify": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
|
||||
"integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">= 4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/transform-ast": {
|
||||
@@ -16824,7 +16810,6 @@
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
||||
"integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"safe-buffer": "^5.0.1"
|
||||
@@ -16837,7 +16822,6 @@
|
||||
"version": "0.14.5",
|
||||
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
|
||||
"integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==",
|
||||
"license": "Unlicense",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/type": {
|
||||
@@ -17146,6 +17130,16 @@
|
||||
"querystring": "0.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/url-parse": {
|
||||
"version": "1.5.10",
|
||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
|
||||
"integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"querystringify": "^2.1.1",
|
||||
"requires-port": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/url/node_modules/punycode": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
|
||||
@@ -17213,7 +17207,6 @@
|
||||
"engines": [
|
||||
"node >=0.6.0"
|
||||
],
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"assert-plus": "^1.0.0",
|
||||
@@ -20355,9 +20348,9 @@
|
||||
}
|
||||
},
|
||||
"@cypress/request": {
|
||||
"version": "3.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.7.tgz",
|
||||
"integrity": "sha512-LzxlLEMbBOPYB85uXrDqvD4MgcenjRBLIns3zyhx7vTPj/0u2eQhzXvPiGcaJrV38Q9dbkExWp6cOHPJ+EtFYg==",
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.5.tgz",
|
||||
"integrity": "sha512-v+XHd9XmWbufxF1/bTaVm2yhbxY+TB4YtWRqF2zaXBlDNMkls34KiATz0AVDLavL3iB6bQk9/7n3oY1EoLSWGA==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"aws-sign2": "~0.7.0",
|
||||
@@ -20373,22 +20366,11 @@
|
||||
"json-stringify-safe": "~5.0.1",
|
||||
"mime-types": "~2.1.19",
|
||||
"performance-now": "^2.1.0",
|
||||
"qs": "6.13.1",
|
||||
"qs": "6.13.0",
|
||||
"safe-buffer": "^5.1.2",
|
||||
"tough-cookie": "^5.0.0",
|
||||
"tough-cookie": "^4.1.3",
|
||||
"tunnel-agent": "^0.6.0",
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"qs": {
|
||||
"version": "6.13.1",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.1.tgz",
|
||||
"integrity": "sha512-EJPeIn0CYrGu+hli1xilKAPXODtJ12T0sP63Ijx2/khC2JtuaN3JyNIpvmnkmaEtha9ocbG4A4cMcr+TvqvwQg==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"side-channel": "^1.0.6"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@cypress/schematic": {
|
||||
@@ -20667,24 +20649,24 @@
|
||||
}
|
||||
},
|
||||
"@fortawesome/fontawesome-common-types": {
|
||||
"version": "6.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.2.tgz",
|
||||
"integrity": "sha512-Zs+YeHUC5fkt7Mg1l6XTniei3k4bwG/yo3iFUtZWd/pMx9g3fdvkSK9E0FOC+++phXOka78uJcYb8JaFkW52Xg=="
|
||||
"version": "6.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.6.0.tgz",
|
||||
"integrity": "sha512-xyX0X9mc0kyz9plIyryrRbl7ngsA9jz77mCZJsUkLl+ZKs0KWObgaEBoSgQiYWAsSmjz/yjl0F++Got0Mdp4Rw=="
|
||||
},
|
||||
"@fortawesome/fontawesome-svg-core": {
|
||||
"version": "6.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.7.2.tgz",
|
||||
"integrity": "sha512-yxtOBWDrdi5DD5o1pmVdq3WMCvnobT0LU6R8RyyVXPvFRd2o79/0NCuQoCjNTeZz9EzA9xS3JxNWfv54RIHFEA==",
|
||||
"version": "6.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.6.0.tgz",
|
||||
"integrity": "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg==",
|
||||
"requires": {
|
||||
"@fortawesome/fontawesome-common-types": "6.7.2"
|
||||
"@fortawesome/fontawesome-common-types": "6.6.0"
|
||||
}
|
||||
},
|
||||
"@fortawesome/free-solid-svg-icons": {
|
||||
"version": "6.7.2",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.7.2.tgz",
|
||||
"integrity": "sha512-GsBrnOzU8uj0LECDfD5zomZJIjrPhIlWU82AHwa2s40FKH+kcxQaBvBo3Z4TxyZHIyX8XTDxsyA33/Vx9eFuQA==",
|
||||
"version": "6.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.6.0.tgz",
|
||||
"integrity": "sha512-IYv/2skhEDFc2WGUcqvFJkeK39Q+HyPf5GHUrT/l2pKbtgEIv1al1TKd6qStR5OIwQdN1GZP54ci3y4mroJWjA==",
|
||||
"requires": {
|
||||
"@fortawesome/fontawesome-common-types": "6.7.2"
|
||||
"@fortawesome/fontawesome-common-types": "6.6.0"
|
||||
}
|
||||
},
|
||||
"@goto-bus-stop/common-shake": {
|
||||
@@ -23316,9 +23298,9 @@
|
||||
"integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg=="
|
||||
},
|
||||
"ci-info": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.1.0.tgz",
|
||||
"integrity": "sha512-HutrvTNsF48wnxkzERIXOe5/mlcfFcbfCmwcg6CJnizbSue78AbDt+1cgl26zwn61WFxhcPykPfZrbqjGmBb4A==",
|
||||
"version": "3.8.0",
|
||||
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz",
|
||||
"integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==",
|
||||
"optional": true
|
||||
},
|
||||
"cipher-base": {
|
||||
@@ -23914,12 +23896,12 @@
|
||||
"peer": true
|
||||
},
|
||||
"cypress": {
|
||||
"version": "13.17.0",
|
||||
"resolved": "https://registry.npmjs.org/cypress/-/cypress-13.17.0.tgz",
|
||||
"integrity": "sha512-5xWkaPurwkIljojFidhw8lFScyxhtiFHl/i/3zov+1Z5CmY4t9tjIdvSXfu82Y3w7wt0uR9KkucbhkVvJZLQSA==",
|
||||
"version": "13.15.0",
|
||||
"resolved": "https://registry.npmjs.org/cypress/-/cypress-13.15.0.tgz",
|
||||
"integrity": "sha512-53aO7PwOfi604qzOkCSzNlWquCynLlKE/rmmpSPcziRH6LNfaDUAklQT6WJIsD8ywxlIy+uVZsnTMCCQVd2kTw==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"@cypress/request": "^3.0.6",
|
||||
"@cypress/request": "^3.0.4",
|
||||
"@cypress/xvfb": "^1.2.4",
|
||||
"@types/sinonjs__fake-timers": "8.1.1",
|
||||
"@types/sizzle": "^2.3.2",
|
||||
@@ -23930,7 +23912,6 @@
|
||||
"cachedir": "^2.3.0",
|
||||
"chalk": "^4.1.0",
|
||||
"check-more-types": "^2.24.0",
|
||||
"ci-info": "^4.0.0",
|
||||
"cli-cursor": "^3.1.0",
|
||||
"cli-table3": "~0.6.1",
|
||||
"commander": "^6.2.1",
|
||||
@@ -23945,6 +23926,7 @@
|
||||
"figures": "^3.2.0",
|
||||
"fs-extra": "^9.1.0",
|
||||
"getos": "^3.2.1",
|
||||
"is-ci": "^3.0.1",
|
||||
"is-installed-globally": "~0.4.0",
|
||||
"lazy-ass": "^1.6.0",
|
||||
"listr2": "^3.8.3",
|
||||
@@ -23959,7 +23941,6 @@
|
||||
"semver": "^7.5.3",
|
||||
"supports-color": "^8.1.1",
|
||||
"tmp": "~0.2.3",
|
||||
"tree-kill": "1.2.2",
|
||||
"untildify": "^4.0.0",
|
||||
"yauzl": "^2.10.0"
|
||||
},
|
||||
@@ -25452,6 +25433,11 @@
|
||||
"object-keys": "^1.0.6"
|
||||
}
|
||||
},
|
||||
"fancy-canvas": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/fancy-canvas/-/fancy-canvas-0.2.2.tgz",
|
||||
"integrity": "sha512-50qi8xA0QkHbjmb8h7XQ6k2fvD7y/yMfiUw9YTarJ7rWrq6o5/3CCXPouYk+XSLASvvxtjyiQLRBFt3qkE3oyA=="
|
||||
},
|
||||
"fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
@@ -26387,6 +26373,15 @@
|
||||
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz",
|
||||
"integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ=="
|
||||
},
|
||||
"is-ci": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz",
|
||||
"integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ci-info": "^3.2.0"
|
||||
}
|
||||
},
|
||||
"is-core-module": {
|
||||
"version": "2.13.1",
|
||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
|
||||
@@ -27020,6 +27015,14 @@
|
||||
"webpack-sources": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"lightweight-charts": {
|
||||
"version": "3.8.0",
|
||||
"resolved": "https://registry.npmjs.org/lightweight-charts/-/lightweight-charts-3.8.0.tgz",
|
||||
"integrity": "sha512-7yFGnYuE1RjRJG9RwUTBz5wvF1QtjBOSW4FFlikr8Dh+/TDNt4ci+HsWSYmStgQUpawpvkCJ3j5/W25GppGj9Q==",
|
||||
"requires": {
|
||||
"fancy-canvas": "0.2.2"
|
||||
}
|
||||
},
|
||||
"limiter": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz",
|
||||
@@ -28803,6 +28806,12 @@
|
||||
"event-stream": "=3.3.4"
|
||||
}
|
||||
},
|
||||
"psl": {
|
||||
"version": "1.9.0",
|
||||
"resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
|
||||
"integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==",
|
||||
"optional": true
|
||||
},
|
||||
"public-encrypt": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz",
|
||||
@@ -28894,6 +28903,12 @@
|
||||
"resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz",
|
||||
"integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM="
|
||||
},
|
||||
"querystringify": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
|
||||
"integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
|
||||
"optional": true
|
||||
},
|
||||
"queue-microtask": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||
@@ -30358,21 +30373,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"tldts": {
|
||||
"version": "6.1.70",
|
||||
"resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.70.tgz",
|
||||
"integrity": "sha512-/W1YVgYVJd9ZDjey5NXadNh0mJXkiUMUue9Zebd0vpdo1sU+H4zFFTaJ1RKD4N6KFoHfcXy6l+Vu7bh+bdWCzA==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"tldts-core": "^6.1.70"
|
||||
}
|
||||
},
|
||||
"tldts-core": {
|
||||
"version": "6.1.70",
|
||||
"resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.70.tgz",
|
||||
"integrity": "sha512-RNnIXDB1FD4T9cpQRErEqw6ZpjLlGdMOitdV+0xtbsnwr4YFka1zpc7D4KD+aAn8oSG5JyFrdasZTE04qDE9Yg==",
|
||||
"optional": true
|
||||
},
|
||||
"tlite": {
|
||||
"version": "0.1.9",
|
||||
"resolved": "https://registry.npmjs.org/tlite/-/tlite-0.1.9.tgz",
|
||||
@@ -30405,12 +30405,23 @@
|
||||
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="
|
||||
},
|
||||
"tough-cookie": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.0.0.tgz",
|
||||
"integrity": "sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==",
|
||||
"version": "4.1.4",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz",
|
||||
"integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"tldts": "^6.1.32"
|
||||
"psl": "^1.1.33",
|
||||
"punycode": "^2.1.1",
|
||||
"universalify": "^0.2.0",
|
||||
"url-parse": "^1.5.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"universalify": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
|
||||
"integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"transform-ast": {
|
||||
@@ -30746,6 +30757,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"url-parse": {
|
||||
"version": "1.5.10",
|
||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
|
||||
"integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"querystringify": "^2.1.1",
|
||||
"requires-port": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
|
||||
@@ -76,9 +76,9 @@
|
||||
"@angular/router": "^17.3.1",
|
||||
"@angular/ssr": "^17.3.1",
|
||||
"@fortawesome/angular-fontawesome": "~0.14.1",
|
||||
"@fortawesome/fontawesome-common-types": "~6.7.2",
|
||||
"@fortawesome/fontawesome-svg-core": "~6.7.2",
|
||||
"@fortawesome/free-solid-svg-icons": "~6.7.2",
|
||||
"@fortawesome/fontawesome-common-types": "~6.6.0",
|
||||
"@fortawesome/fontawesome-svg-core": "~6.6.0",
|
||||
"@fortawesome/free-solid-svg-icons": "~6.6.0",
|
||||
"@mempool/mempool.js": "2.3.0",
|
||||
"@ng-bootstrap/ng-bootstrap": "^16.0.0",
|
||||
"@types/qrcode": "~1.5.0",
|
||||
@@ -87,6 +87,7 @@
|
||||
"clipboard": "^2.0.11",
|
||||
"domino": "^2.1.6",
|
||||
"echarts": "~5.5.0",
|
||||
"lightweight-charts": "~3.8.0",
|
||||
"ngx-echarts": "~17.2.0",
|
||||
"ngx-infinite-scroll": "^17.0.0",
|
||||
"qrcode": "1.5.1",
|
||||
@@ -114,7 +115,7 @@
|
||||
"optionalDependencies": {
|
||||
"@cypress/schematic": "^2.5.0",
|
||||
"@types/cypress": "^1.1.3",
|
||||
"cypress": "^13.17.0",
|
||||
"cypress": "^13.15.0",
|
||||
"cypress-fail-on-console-error": "~5.1.0",
|
||||
"cypress-wait-until": "^2.0.1",
|
||||
"mock-socket": "~9.3.1",
|
||||
|
||||
@@ -439,39 +439,4 @@ export const fiatCurrencies = {
|
||||
code: 'ZAR',
|
||||
indexed: true,
|
||||
},
|
||||
};
|
||||
|
||||
export interface Timezone {
|
||||
offset: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export const timezones: Timezone[] = [
|
||||
{ offset: '-12', name: 'Anywhere on Earth (AoE)' },
|
||||
{ offset: '-11', name: 'Samoa Standard Time (SST)' },
|
||||
{ offset: '-10', name: 'Hawaii Standard Time (HST)' },
|
||||
{ offset: '-9', name: 'Alaska Standard Time (AKST)' },
|
||||
{ offset: '-8', name: 'Pacific Standard Time (PST)' },
|
||||
{ offset: '-7', name: 'Mountain Standard Time (MST)' },
|
||||
{ offset: '-6', name: 'Central Standard Time (CST)' },
|
||||
{ offset: '-5', name: 'Eastern Standard Time (EST)' },
|
||||
{ offset: '-4', name: 'Atlantic Standard Time (AST)' },
|
||||
{ offset: '-3', name: 'Argentina Time (ART)' },
|
||||
{ offset: '-2', name: 'Fernando de Noronha Time (FNT)' },
|
||||
{ offset: '-1', name: 'Azores Time (AZOT)' },
|
||||
{ offset: '+0', name: 'Greenwich Mean Time (GMT)' },
|
||||
{ offset: '+1', name: 'Central European Time (CET)' },
|
||||
{ offset: '+2', name: 'Eastern European Time (EET)' },
|
||||
{ offset: '+3', name: 'Moscow Standard Time (MSK)' },
|
||||
{ offset: '+4', name: 'Armenia Time (AMT)' },
|
||||
{ offset: '+5', name: 'Pakistan Standard Time (PKT)' },
|
||||
{ offset: '+6', name: 'Xinjiang Time (XJT)' },
|
||||
{ offset: '+7', name: 'Indochina Time (ICT)' },
|
||||
{ offset: '+8', name: 'Hong Kong Time (HKT)' },
|
||||
{ offset: '+9', name: 'Japan Standard Time (JST)' },
|
||||
{ offset: '+10', name: 'Australian Eastern Standard Time (AEST)' },
|
||||
{ offset: '+11', name: 'Norfolk Time (NFT)' },
|
||||
{ offset: '+12', name: 'New Zealand Standard Time (NZST)' },
|
||||
{ offset: '+13', name: 'Tonga Time (TOT)' },
|
||||
{ offset: '+14', name: 'Line Islands Time (LINT)' }
|
||||
];
|
||||
};
|
||||
@@ -612,18 +612,10 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
||||
this.processing = false;
|
||||
return;
|
||||
}
|
||||
const verificationToken = await this.$verifyBuyer(this.payments, tokenResult.token, tokenResult.details, costUSD.toFixed(2));
|
||||
if (!verificationToken) {
|
||||
console.error(`SCA verification failed`);
|
||||
this.accelerateError = 'SCA Verification Failed. Payment Declined.';
|
||||
this.processing = false;
|
||||
return;
|
||||
}
|
||||
const cardTag = md5(`${card.brand}${card.expMonth}${card.expYear}${card.last4}`.toLowerCase());
|
||||
this.servicesApiService.accelerateWithGooglePay$(
|
||||
this.tx.txid,
|
||||
tokenResult.token,
|
||||
verificationToken,
|
||||
cardTag,
|
||||
`accelerator-${this.tx.txid.substring(0, 15)}-${Math.round(new Date().getTime() / 1000)}`,
|
||||
costUSD
|
||||
@@ -751,32 +743,6 @@ export class AccelerateCheckout implements OnInit, OnDestroy {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Required in SCA Mandated Regions: Learn more at https://developer.squareup.com/docs/sca-overview
|
||||
*/
|
||||
async $verifyBuyer(payments, token, details, amount) {
|
||||
const verificationDetails = {
|
||||
amount: amount,
|
||||
currencyCode: 'USD',
|
||||
intent: 'CHARGE',
|
||||
billingContact: {
|
||||
givenName: details.card?.billing?.givenName,
|
||||
familyName: details.card?.billing?.familyName,
|
||||
phone: details.card?.billing?.phone,
|
||||
addressLines: details.card?.billing?.addressLines,
|
||||
city: details.card?.billing?.city,
|
||||
state: details.card?.billing?.state,
|
||||
countryCode: details.card?.billing?.countryCode,
|
||||
},
|
||||
};
|
||||
|
||||
const verificationResults = await payments.verifyBuyer(
|
||||
token,
|
||||
verificationDetails,
|
||||
);
|
||||
return verificationResults.token;
|
||||
}
|
||||
|
||||
/**
|
||||
* BTCPay
|
||||
*/
|
||||
|
||||
@@ -45,17 +45,14 @@ export class AddressGraphComponent implements OnChanges, OnDestroy {
|
||||
@Input() left: number | string = 70;
|
||||
@Input() widget: boolean = false;
|
||||
@Input() defaultFiat: boolean = false;
|
||||
@Input() showLegend: boolean = true;
|
||||
@Input() showYAxis: boolean = true;
|
||||
|
||||
adjustedLeft: number;
|
||||
adjustedRight: number;
|
||||
data: any[] = [];
|
||||
fiatData: any[] = [];
|
||||
hoverData: any[] = [];
|
||||
conversions: any;
|
||||
allowZoom: boolean = false;
|
||||
|
||||
initialRight = this.right;
|
||||
initialLeft = this.left;
|
||||
selected = { [$localize`:@@7e69426bd97a606d8ae6026762858e6e7c86a1fd:Balance`]: true, 'Fiat': false };
|
||||
|
||||
subscription: Subscription;
|
||||
@@ -123,7 +120,7 @@ export class AddressGraphComponent implements OnChanges, OnDestroy {
|
||||
} else if (this.conversions && this.conversions['USD']) {
|
||||
price = this.conversions['USD'];
|
||||
}
|
||||
return { ...item, price: price };
|
||||
return { ...item, price: price }
|
||||
});
|
||||
}
|
||||
}),
|
||||
@@ -184,8 +181,8 @@ export class AddressGraphComponent implements OnChanges, OnDestroy {
|
||||
const maxValue = this.data.reduce((acc, d) => Math.max(acc, Math.abs(d[1] ?? d.value[1])), 0);
|
||||
const minValue = this.data.reduce((acc, d) => Math.min(acc, Math.abs(d[1] ?? d.value[1])), maxValue);
|
||||
|
||||
this.adjustedRight = this.selected['Fiat'] ? +this.right + 40 : +this.right;
|
||||
this.adjustedLeft = this.selected[$localize`:@@7e69426bd97a606d8ae6026762858e6e7c86a1fd:Balance`] ? +this.left : +this.left - 40;
|
||||
this.right = this.selected['Fiat'] ? +this.initialRight + 40 : this.initialRight;
|
||||
this.left = this.selected[$localize`:@@7e69426bd97a606d8ae6026762858e6e7c86a1fd:Balance`] ? this.initialLeft : +this.initialLeft - 40;
|
||||
|
||||
this.chartOptions = {
|
||||
color: [
|
||||
@@ -202,10 +199,10 @@ export class AddressGraphComponent implements OnChanges, OnDestroy {
|
||||
grid: {
|
||||
top: 20,
|
||||
bottom: this.allowZoom ? 65 : 20,
|
||||
right: this.adjustedRight,
|
||||
left: this.adjustedLeft,
|
||||
right: this.right,
|
||||
left: this.left,
|
||||
},
|
||||
legend: (this.showLegend && !this.stateService.isAnyTestnet()) ? {
|
||||
legend: !this.stateService.isAnyTestnet() ? {
|
||||
data: [
|
||||
{
|
||||
name: $localize`:@@7e69426bd97a606d8ae6026762858e6e7c86a1fd:Balance`,
|
||||
@@ -316,7 +313,6 @@ export class AddressGraphComponent implements OnChanges, OnDestroy {
|
||||
type: 'value',
|
||||
position: 'left',
|
||||
axisLabel: {
|
||||
show: this.showYAxis,
|
||||
color: 'rgb(110, 112, 121)',
|
||||
formatter: (val): string => {
|
||||
let valSpan = maxValue - (this.period === 'all' ? 0 : minValue);
|
||||
@@ -347,10 +343,9 @@ export class AddressGraphComponent implements OnChanges, OnDestroy {
|
||||
{
|
||||
type: 'value',
|
||||
axisLabel: {
|
||||
show: this.showYAxis,
|
||||
color: 'rgb(110, 112, 121)',
|
||||
formatter: function(val) {
|
||||
return `$${this.amountShortenerPipe.transform(val, 3, undefined, true, true)}`;
|
||||
return `$${this.amountShortenerPipe.transform(val, 0, undefined, true)}`;
|
||||
}.bind(this)
|
||||
},
|
||||
splitLine: {
|
||||
@@ -404,8 +399,8 @@ export class AddressGraphComponent implements OnChanges, OnDestroy {
|
||||
type: 'slider',
|
||||
brushSelect: false,
|
||||
realtime: true,
|
||||
left: this.adjustedLeft,
|
||||
right: this.adjustedRight,
|
||||
left: this.left,
|
||||
right: this.right,
|
||||
selectedDataBackground: {
|
||||
lineStyle: {
|
||||
color: '#fff',
|
||||
@@ -418,7 +413,7 @@ export class AddressGraphComponent implements OnChanges, OnDestroy {
|
||||
|
||||
onChartClick(e) {
|
||||
if (this.hoverData?.length && this.hoverData[0]?.[2]?.txid) {
|
||||
this.zone.run(() => {
|
||||
this.zone.run(() => {
|
||||
const url = this.relativeUrlPipe.transform(`/tx/${this.hoverData[0][2].txid}`);
|
||||
if (e.event.event.shiftKey || e.event.event.ctrlKey || e.event.event.metaKey) {
|
||||
window.open(url);
|
||||
@@ -435,23 +430,23 @@ export class AddressGraphComponent implements OnChanges, OnDestroy {
|
||||
|
||||
onLegendSelectChanged(e) {
|
||||
this.selected = e.selected;
|
||||
this.adjustedRight = this.selected['Fiat'] ? +this.right + 40 : +this.right;
|
||||
this.adjustedLeft = this.selected[$localize`:@@7e69426bd97a606d8ae6026762858e6e7c86a1fd:Balance`] ? +this.left : +this.left - 40;
|
||||
this.right = this.selected['Fiat'] ? +this.initialRight + 40 : this.initialRight;
|
||||
this.left = this.selected[$localize`:@@7e69426bd97a606d8ae6026762858e6e7c86a1fd:Balance`] ? this.initialLeft : +this.initialLeft - 40;
|
||||
|
||||
this.chartOptions = {
|
||||
grid: {
|
||||
right: this.adjustedRight,
|
||||
left: this.adjustedLeft,
|
||||
right: this.right,
|
||||
left: this.left,
|
||||
},
|
||||
legend: {
|
||||
selected: this.selected,
|
||||
},
|
||||
dataZoom: this.allowZoom ? [{
|
||||
left: this.adjustedLeft,
|
||||
right: this.adjustedRight,
|
||||
left: this.left,
|
||||
right: this.right,
|
||||
}, {
|
||||
left: this.adjustedLeft,
|
||||
right: this.adjustedRight,
|
||||
left: this.left,
|
||||
right: this.right,
|
||||
}] : undefined
|
||||
};
|
||||
|
||||
@@ -483,7 +478,7 @@ export class AddressGraphComponent implements OnChanges, OnDestroy {
|
||||
// Add a point at today's date to make the graph end at the current time
|
||||
extendedSummary.unshift({ time: Date.now() / 1000, value: 0 });
|
||||
extendedSummary.reverse();
|
||||
|
||||
|
||||
let oneHour = 60 * 60;
|
||||
// Fill gaps longer than interval
|
||||
for (let i = 0; i < extendedSummary.length - 1; i++) {
|
||||
@@ -496,7 +491,7 @@ export class AddressGraphComponent implements OnChanges, OnDestroy {
|
||||
i += hours - 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return extendedSummary.reverse();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
</div>
|
||||
</td>
|
||||
<td class="timestamp" *ngIf="!widget" [ngClass]="{'widget': widget, 'legacy': !isMempoolModule}">
|
||||
<app-timestamp [customFormat]="'yyyy-MM-dd HH:mm:ss'" [unixTime]="block.timestamp" [hideTimeSince]="true"></app-timestamp>
|
||||
‎{{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm:ss' }}
|
||||
</td>
|
||||
<td *ngIf="auditAvailable" class="health text-right" [ngClass]="{'widget': widget, 'legacy': !isMempoolModule}">
|
||||
<a
|
||||
|
||||
@@ -305,20 +305,6 @@
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@case ('simpleproof') {
|
||||
<div class="col" style="max-height: 410px" [style.order]="isMobile && widget.mobileOrder || 8">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<a class="title-link" href="" [routerLink]="['/sp/verified' | relativeUrl]">
|
||||
<h5 class="card-title d-inline" i18n="dashboard.recent-blocks">{{ widget.props?.label }}</h5>
|
||||
<span> </span>
|
||||
<fa-icon [icon]="['fas', 'external-link-alt']" [fixedWidth]="true" style="vertical-align: text-top; font-size: 13px; color: var(--title-fg)"></fa-icon>
|
||||
</a>
|
||||
<app-simpleproof-widget [label]="widget.props.label" [key]="widget.props.key" [widget]="true"></app-simpleproof-widget>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
</div>
|
||||
@@ -329,4 +315,4 @@
|
||||
</ng-template>
|
||||
<ng-template #loadingbig>
|
||||
<span class="skeleton-loader skeleton-loader-big" ></span>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
|
||||
@@ -56,7 +56,8 @@
|
||||
</ng-template>
|
||||
</td>
|
||||
<td class="timestamp text-left">
|
||||
<app-timestamp [customFormat]="'yyyy-MM-dd HH:mm'" [unixTime]="utxo.blocktime"></app-timestamp>
|
||||
‎{{ utxo.blocktime * 1000 | date:'yyyy-MM-dd HH:mm' }}
|
||||
<div class="symbol lg-inline relative-time"><i>(<app-time kind="since" [time]="utxo.blocktime"></app-time>)</i></div>
|
||||
</td>
|
||||
<td class="expires-in text-left" [ngStyle]="{ 'color': getGradientColor(utxo.blocknumber + utxo.timelock - lastReservesBlockUpdate) }">
|
||||
{{ utxo.blocknumber + utxo.timelock - lastReservesBlockUpdate < 0 ? -(utxo.blocknumber + utxo.timelock - lastReservesBlockUpdate) : utxo.blocknumber + utxo.timelock - lastReservesBlockUpdate }} <span i18n="shared.blocks" class="symbol">blocks</span>
|
||||
|
||||
@@ -53,7 +53,8 @@
|
||||
</ng-container>
|
||||
</td>
|
||||
<td class="timestamp text-left">
|
||||
<app-timestamp [customFormat]="'yyyy-MM-dd HH:mm'" [unixTime]="peg.blocktime"></app-timestamp>
|
||||
‎{{ peg.blocktime * 1000 | date:'yyyy-MM-dd HH:mm' }}
|
||||
<div class="symbol lg-inline relative-time"><i>(<app-time kind="since" [time]="peg.blocktime"></app-time>)</i></div>
|
||||
</td>
|
||||
<td class="amount text-right" [ngClass]="{'credit': peg.amount > 0, 'debit': peg.amount < 0, 'glow-effect': peg.amount < 0 && peg.bitcoinaddress && !peg.bitcointxid}">
|
||||
<app-amount [satoshis]="peg.amount" [noFiat]="true" [forceBtc]="true" [addPlus]="true"></app-amount>
|
||||
|
||||
@@ -194,7 +194,7 @@
|
||||
<a [routerLink]="['/block' | relativeUrl, block.id]">{{ block.height }}</a>
|
||||
</td>
|
||||
<td class="timestamp">
|
||||
<app-timestamp [customFormat]="'yyyy-MM-dd HH:mm:ss'" [unixTime]="block.timestamp" [hideTimeSince]="true"></app-timestamp>
|
||||
‎{{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm:ss' }}
|
||||
</td>
|
||||
<td class="mined">
|
||||
<app-time kind="since" [time]="block.timestamp" [fastRender]="true" [showTooltip]="true"></app-time>
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
.accept-results {
|
||||
td, th {
|
||||
&.allowed {
|
||||
width: 10%;
|
||||
text-align: center;
|
||||
}
|
||||
&.txid {
|
||||
width: 50%;
|
||||
}
|
||||
&.rate {
|
||||
width: 20%;
|
||||
text-align: right;
|
||||
white-space: wrap;
|
||||
}
|
||||
&.reason {
|
||||
width: 20%;
|
||||
text-align: right;
|
||||
white-space: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 950px) {
|
||||
table-layout: auto;
|
||||
|
||||
td, th {
|
||||
&.allowed {
|
||||
width: 100px;
|
||||
}
|
||||
&.txid {
|
||||
max-width: 200px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,9 +19,6 @@
|
||||
<th class="rtt only-small">RTT</th>
|
||||
<th class="rtt only-large">RTT</th>
|
||||
<th class="height">Height</th>
|
||||
<th class="frontend only-large">Front</th>
|
||||
<th class="backend only-large">Back</th>
|
||||
<th class="electrs only-large">Electrs</th>
|
||||
</tr>
|
||||
<tr *ngFor="let host of hosts; let i = index; trackBy: trackByFn">
|
||||
<td class="rank">{{ i + 1 }}</td>
|
||||
@@ -31,15 +28,6 @@
|
||||
<td class="rtt only-small">{{ (host.rtt / 1000) | number : '1.1-1' }} {{ host.rtt == null ? '' : 's'}} {{ !host.checked ? '⏳' : (host.unreachable ? '🔥' : '✅') }}</td>
|
||||
<td class="rtt only-large">{{ host.rtt | number : '1.0-0' }} {{ host.rtt == null ? '' : 'ms'}} {{ !host.checked ? '⏳' : (host.unreachable ? '🔥' : '✅') }}</td>
|
||||
<td class="height">{{ host.latestHeight }} {{ !host.checked ? '⏳' : (host.outOfSync ? '🚫' : (host.latestHeight && host.latestHeight < maxHeight ? '🟧' : '✅')) }}</td>
|
||||
<ng-container *ngFor="let type of ['frontend', 'backend', 'electrs']">
|
||||
<td class="{{type}} only-large" [style.background-color]="host.hashes?.[type] ? '#' + host.hashes[type].slice(0, 6) : ''">
|
||||
@if (host.hashes?.[type]) {
|
||||
<a [style.color]="'white'" href="https://github.com/mempool/{{type === 'electrs' ? 'electrs' : 'mempool'}}/commit/{{ host.hashes[type] }}" target="_blank">{{ host.hashes[type].slice(0, 8) || '?' }}</a>
|
||||
} @else {
|
||||
<span>?</span>
|
||||
}
|
||||
</td>
|
||||
</ng-container>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
}
|
||||
|
||||
.status-panel {
|
||||
max-width: 1000px;
|
||||
max-width: 720px;
|
||||
margin: auto;
|
||||
padding: 1em;
|
||||
background: var(--box-bg);
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
<div class="container-xl" style="min-height: 335px" [ngClass]="{'widget': widget, 'full-height': !widget}">
|
||||
<div *ngIf="!widget" class="float-left" style="display: flex; width: 100%; align-items: center;">
|
||||
<h1>{{ label }}</h1>
|
||||
<div *ngIf="!widget && isLoading" class="spinner-border" role="status"></div>
|
||||
</div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
|
||||
@if (isLoading) {
|
||||
loading!
|
||||
<div class="spinner-wrapper">
|
||||
<div class="ml-2 spinner-border text-light" style="width: 25px; height: 25px"></div>
|
||||
</div>
|
||||
} @else if (error || !verified.length) {
|
||||
<div class="error-wrapper">
|
||||
<span>temporarily unavailable</span>
|
||||
</div>
|
||||
} @else {
|
||||
<div style="min-height: 295px">
|
||||
<table class="table table-borderless" [class.table-fixed]="widget">
|
||||
<thead>
|
||||
<th class="filename text-left" [ngClass]="{'widget': widget}" i18n="simpleproof.filename">Filename</th>
|
||||
<th class="hash text-left" [ngClass]="{'widget': widget}" i18n="simpleproof.hash">Hash</th>
|
||||
<th class="verified text-right" [ngClass]="{'widget': widget}" i18n="simpleproof.verified">Verified</th>
|
||||
<th class="proof text-right" [ngClass]="{'widget': widget}" i18n="simpleproof.proof">Proof</th>
|
||||
</thead>
|
||||
<tbody *ngIf="verifiedPage; else skeleton" [style]="isLoading ? 'opacity: 0.75' : ''">
|
||||
<tr *ngFor="let item of verifiedPage">
|
||||
<td class="filename text-left" [class]="widget ? 'widget' : ''">{{ item.file_name }}</td>
|
||||
<td class="hash text-left" [class]="widget ? 'widget' : ''">{{ item.sha256 }}</td>
|
||||
<td class="verified text-right" [class]="widget ? 'widget' : ''">
|
||||
<app-timestamp [unixTime]="item.block_time" [customFormat]="'yyyy-MM-dd'" [hideTimeSince]="true"></app-timestamp>
|
||||
</td>
|
||||
<td class="proof text-right" [class]="widget ? 'widget' : ''">
|
||||
<a [href]="item.sanitized_url" target="_blank" class="badge badge-primary badge-verify">
|
||||
<span class="icon">
|
||||
<img class="icon-img" src="/resources/sp.svg">
|
||||
</span>
|
||||
<span i18n="simpleproof.verify">Verify</span>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<ng-template #skeleton>
|
||||
<tbody>
|
||||
<tr *ngFor="let item of [].constructor(itemsPerPage)">
|
||||
<td class="filename text-left" [ngClass]="{'widget': widget}">
|
||||
<span class="skeleton-loader" style="max-width: 75px"></span>
|
||||
</td>
|
||||
<td class="hash text-left" [ngClass]="{'widget': widget}">
|
||||
<span class="skeleton-loader" style="max-width: 75px"></span>
|
||||
</td>
|
||||
<td class="verified text-right" [ngClass]="{'widget': widget}">
|
||||
<span class="skeleton-loader" style="max-width: 75px"></span>
|
||||
</td>
|
||||
<td class="proof text-right" [ngClass]="{'widget': widget}">
|
||||
<span class="skeleton-loader" style="max-width: 75px"></span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</ng-template>
|
||||
</table>
|
||||
|
||||
<ngb-pagination *ngIf="!widget" class="pagination-container float-right mt-2" [class]="isLoading ? 'disabled' : ''"
|
||||
[collectionSize]="verified.length" [rotate]="true" [maxSize]="paginationMaxSize" [pageSize]="itemsPerPage" [(page)]="page"
|
||||
(pageChange)="pageChange(page)" [boundaryLinks]="true" [ellipses]="false">
|
||||
</ngb-pagination>
|
||||
|
||||
<ng-template [ngIf]="!widget">
|
||||
<div class="clearfix"></div>
|
||||
<br>
|
||||
</ng-template>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
@@ -1,114 +0,0 @@
|
||||
.spinner-wrapper, .error-wrapper {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.spinner-border {
|
||||
height: 25px;
|
||||
width: 25px;
|
||||
margin-top: -10px;
|
||||
margin-left: -13px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.container-xl {
|
||||
max-width: 1400px;
|
||||
}
|
||||
.container-xl.widget {
|
||||
padding-left: 0px;
|
||||
padding-bottom: 0px;
|
||||
padding-right: 0px;
|
||||
}
|
||||
.container-xl.legacy {
|
||||
max-width: 1140px;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
tr, td, th {
|
||||
border: 0px;
|
||||
padding-top: 0.71rem !important;
|
||||
padding-bottom: 0.7rem !important;
|
||||
}
|
||||
|
||||
.clear-link {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.disabled {
|
||||
pointer-events: none;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.progress {
|
||||
background-color: var(--secondary);
|
||||
}
|
||||
|
||||
.filename {
|
||||
width: 50%;
|
||||
max-width: 300px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.hash {
|
||||
width: 25%;
|
||||
max-width: 700px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
td.hash {
|
||||
font-family: monospace;
|
||||
}
|
||||
.widget .hash {
|
||||
display: none;
|
||||
}
|
||||
@media (max-width: 1200px) {
|
||||
.hash {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.verified {
|
||||
width: 25%;
|
||||
}
|
||||
td.verified {
|
||||
color: var(--tertiary);
|
||||
}
|
||||
|
||||
.proof {
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
.badge-verify {
|
||||
font-size: 1.05em;
|
||||
font-weight: normal;
|
||||
background: var(--nav-bg);
|
||||
color: white;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: auto;
|
||||
|
||||
.icon {
|
||||
margin: -0.25em;
|
||||
margin-right: 0.5em;
|
||||
|
||||
.icon-img {
|
||||
width: 16px;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
import { Component, Input, SecurityContext, SimpleChanges, OnChanges } from '@angular/core';
|
||||
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
|
||||
import { ServicesApiServices } from '@app/services/services-api.service';
|
||||
import { catchError, of } from 'rxjs';
|
||||
|
||||
export interface SimpleProof {
|
||||
file_name: string;
|
||||
sha256: string;
|
||||
ots_verification: string;
|
||||
block_height: number;
|
||||
block_hash: string;
|
||||
block_time: number;
|
||||
simpleproof_url: string;
|
||||
key?: string;
|
||||
sanitized_url?: SafeResourceUrl;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-simpleproof-widget',
|
||||
templateUrl: './simpleproof-widget.component.html',
|
||||
styleUrls: ['./simpleproof-widget.component.scss'],
|
||||
})
|
||||
export class SimpleProofWidgetComponent implements OnChanges {
|
||||
@Input() key: string = window['__env']?.customize?.dashboard.widgets?.find(w => w.component ==='simpleproof')?.props?.key ?? '';
|
||||
@Input() label: string = window['__env']?.customize?.dashboard.widgets?.find(w => w.component ==='simpleproof')?.props?.label ?? 'Verified Documents';
|
||||
@Input() widget: boolean = false;
|
||||
@Input() width = 300;
|
||||
@Input() height = 400;
|
||||
|
||||
verified: SimpleProof[] = [];
|
||||
verifiedPage: SimpleProof[] = [];
|
||||
isLoading: boolean = true;
|
||||
error: boolean = false;
|
||||
page = 1;
|
||||
lastPage = 1;
|
||||
itemsPerPage = 15;
|
||||
paginationMaxSize = window.innerWidth <= 767.98 ? 3 : 5;
|
||||
|
||||
constructor(
|
||||
private servicesApiService: ServicesApiServices,
|
||||
public sanitizer: DomSanitizer,
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.loadVerifications();
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (changes.widget) {
|
||||
this.itemsPerPage = this.widget ? 6 : 15;
|
||||
}
|
||||
if (changes.key) {
|
||||
this.loadVerifications();
|
||||
}
|
||||
}
|
||||
|
||||
loadVerifications(): void {
|
||||
if (this.key) {
|
||||
this.isLoading = true;
|
||||
this.servicesApiService.getSimpleProofs$(this.key).pipe(
|
||||
catchError(() => {
|
||||
this.isLoading = false;
|
||||
this.error = true;
|
||||
return of({});
|
||||
}),
|
||||
).subscribe((data: Record<string, SimpleProof>) => {
|
||||
if (Object.keys(data).length) {
|
||||
this.verified = Object.keys(data).map(key => ({
|
||||
...data[key],
|
||||
file_name: data[key].file_name.replace('source-', '').replace('_', ' '),
|
||||
key,
|
||||
sanitized_url: this.sanitizer.bypassSecurityTrustResourceUrl(this.sanitizer.sanitize(SecurityContext.URL, data[key]['simpleproof-url']) ?? ''),
|
||||
})).sort((a, b) => b.key.localeCompare(a.key));
|
||||
this.verifiedPage = this.verified.slice((this.page - 1) * this.itemsPerPage, this.page * this.itemsPerPage);
|
||||
this.isLoading = false;
|
||||
this.error = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pageChange(page: number): void {
|
||||
this.page = page;
|
||||
this.verifiedPage = this.verified.slice((this.page - 1) * this.itemsPerPage, this.page * this.itemsPerPage);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
<div [formGroup]="timezoneForm" class="text-small text-center">
|
||||
<select formControlName="mode" class="custom-select custom-select-sm form-control-secondary form-control mx-auto" style="width: 110px;" (change)="changeMode()">
|
||||
<option value="local">UTC{{ localTimezoneOffset !== '+0' ? localTimezoneOffset : '' }} {{ localTimezoneName ? '- ' + localTimezoneName : '' }}</option>
|
||||
<option value="+0" *ngIf="localTimezoneOffset !== '+0'">UTC - Greenwich Mean Time (GMT)</option>
|
||||
<option disabled>────</option>
|
||||
<option *ngFor="let timezone of timezones" [value]="timezone.offset">UTC{{ timezone.offset !== '+0' ? timezone.offset : '' }} - {{ timezone.name }}</option>
|
||||
</select>
|
||||
</div>
|
||||
@@ -1,58 +0,0 @@
|
||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
|
||||
import { StorageService } from '@app/services/storage.service';
|
||||
import { StateService } from '@app/services/state.service';
|
||||
import { timezones } from '@app/app.constants';
|
||||
|
||||
|
||||
@Component({
|
||||
selector: 'app-timezone-selector',
|
||||
templateUrl: './timezone-selector.component.html',
|
||||
styleUrls: ['./timezone-selector.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush
|
||||
})
|
||||
export class TimezoneSelectorComponent implements OnInit {
|
||||
timezoneForm: UntypedFormGroup;
|
||||
timezones = timezones;
|
||||
localTimezoneOffset: string = '';
|
||||
localTimezoneName: string;
|
||||
|
||||
constructor(
|
||||
private formBuilder: UntypedFormBuilder,
|
||||
private stateService: StateService,
|
||||
private storageService: StorageService,
|
||||
) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.setLocalTimezone();
|
||||
this.timezoneForm = this.formBuilder.group({
|
||||
mode: ['local'],
|
||||
});
|
||||
this.stateService.timezone$.subscribe((mode) => {
|
||||
this.timezoneForm.get('mode')?.setValue(mode);
|
||||
});
|
||||
}
|
||||
|
||||
changeMode() {
|
||||
const newMode = this.timezoneForm.get('mode')?.value;
|
||||
this.storageService.setValue('timezone-preference', newMode);
|
||||
this.stateService.timezone$.next(newMode);
|
||||
}
|
||||
|
||||
setLocalTimezone() {
|
||||
const offset = new Date().getTimezoneOffset();
|
||||
const sign = offset <= 0 ? "+" : "-";
|
||||
const absOffset = Math.abs(offset);
|
||||
const hours = String(Math.floor(absOffset / 60));
|
||||
const minutes = String(absOffset % 60).padStart(2, '0');
|
||||
if (minutes === '00') {
|
||||
this.localTimezoneOffset = `${sign}${hours}`;
|
||||
} else {
|
||||
this.localTimezoneOffset = `${sign}${hours.padStart(2, '0')}:${minutes}`;
|
||||
}
|
||||
|
||||
const timezone = this.timezones.find(tz => tz.offset === this.localTimezoneOffset);
|
||||
this.timezones = this.timezones.filter(tz => tz.offset !== this.localTimezoneOffset && tz.offset !== '+0');
|
||||
this.localTimezoneName = timezone ? timezone.name : '';
|
||||
}
|
||||
}
|
||||
@@ -88,7 +88,7 @@
|
||||
<div class="field narrower mt-2">
|
||||
<div class="label" i18n="transaction.confirmed-at">Confirmed at</div>
|
||||
<div class="value">
|
||||
<app-timestamp [customFormat]="'yyyy-MM-dd HH:mm'" [unixTime]="tx.status.block_time" [hideTimeSince]="true"></app-timestamp>
|
||||
‎{{ tx.status.block_time * 1000 | date:'yyyy-MM-dd HH:mm' }}
|
||||
<div class="lg-inline">
|
||||
<i class="symbol">(<app-time kind="since" [time]="tx.status.block_time" [fastRender]="true" [showTooltip]="true"></app-time>)</i>
|
||||
</div>
|
||||
|
||||
@@ -61,7 +61,10 @@
|
||||
<tr>
|
||||
<td i18n="block.timestamp">Timestamp</td>
|
||||
<td>
|
||||
<app-timestamp [customFormat]="'yyyy-MM-dd HH:mm:ss'" [unixTime]="tx.status.block_time"></app-timestamp>
|
||||
‎{{ tx.status.block_time * 1000 | date:'yyyy-MM-dd HH:mm:ss' }}
|
||||
<div class="lg-inline">
|
||||
<i class="symbol">(<app-time kind="since" [time]="tx.status.block_time" [fastRender]="true"></app-time>)</i>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
} @else {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<app-truncate [text]="tx.txid"></app-truncate>
|
||||
</a>
|
||||
<div>
|
||||
<ng-template [ngIf]="tx.status.confirmed"><app-timestamp [customFormat]="'yyyy-MM-dd HH:mm:ss'" [unixTime]="tx.status.block_time" [hideTimeSince]="true"></app-timestamp></ng-template>
|
||||
<ng-template [ngIf]="tx.status.confirmed">‎{{ tx.status.block_time * 1000 | date:'yyyy-MM-dd HH:mm:ss' }}</ng-template>
|
||||
<ng-template [ngIf]="!tx.status.confirmed && tx.firstSeen">
|
||||
<i><app-time kind="since" [time]="tx.firstSeen" [fastRender]="true" [showTooltip]="true"></app-time></i>
|
||||
</ng-template>
|
||||
@@ -81,7 +81,7 @@
|
||||
</ng-container>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-right nowrap amount" [class]="{large: tx.largeInput}">
|
||||
<td class="text-right nowrap amount" [class]="{large: vin?.prevout?.value > 1000000000 || vin.isInscription}">
|
||||
<button *ngIf="vin.isInscription" (click)="toggleOrdData(tx.txid, 'vin', vindex)" type="button" class="btn btn-sm badge badge-ord primary" style="margin-right: 10px;">Inscription</button>
|
||||
<ng-template [ngIf]="vin.prevout && vin.prevout.asset && vin.prevout.asset !== nativeAssetId" [ngIfElse]="defaultOutput">
|
||||
<div *ngIf="assetsMinimal && assetsMinimal[vin.prevout.asset] else assetVinNotFound">
|
||||
@@ -257,7 +257,7 @@
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
</td>
|
||||
<td class="text-right nowrap amount" [class]="{large: tx.largeOutput}">
|
||||
<td class="text-right nowrap amount" [class]="{large: vout?.value > 1000000000}">
|
||||
<ng-template [ngIf]="vout.asset && vout.asset !== nativeAssetId" [ngIfElse]="defaultOutput">
|
||||
<div *ngIf="assetsMinimal && assetsMinimal[vout.asset] else assetNotFound">
|
||||
<ng-container *ngTemplateOutlet="assetBox; context:{ $implicit: vout }"></ng-container>
|
||||
|
||||
@@ -258,7 +258,6 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
||||
const hasAnnex = tx.vin[i].witness?.[tx.vin[i].witness.length - 1].startsWith('50');
|
||||
if (tx.vin[i].witness.length > (hasAnnex ? 2 : 1) && tx.vin[i].witness[tx.vin[i].witness.length - (hasAnnex ? 3 : 2)].includes('0063036f7264')) {
|
||||
tx.vin[i].isInscription = true;
|
||||
tx.largeInput = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -269,9 +268,6 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tx.largeInput = tx.largeInput || tx.vin.some(vin => (vin?.prevout?.value > 1000000000));
|
||||
tx.largeOutput = tx.vout.some(vout => (vout?.value > 1000000000));
|
||||
});
|
||||
|
||||
if (this.blockTime && this.transactions?.length && this.currency) {
|
||||
@@ -355,12 +351,8 @@ export class TransactionsListComponent implements OnInit, OnChanges {
|
||||
this.electrsApiService.getTransaction$(tx.txid)
|
||||
.subscribe((newTx) => {
|
||||
tx['@vinLoaded'] = true;
|
||||
let temp = tx.vin;
|
||||
tx.vin = newTx.vin;
|
||||
tx.fee = newTx.fee;
|
||||
for (const [index, vin] of temp.entries()) {
|
||||
newTx.vin[index].isInscription = vin.isInscription;
|
||||
}
|
||||
this.ref.markForCheck();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
<div class="box preview-box" *ngIf="(walletAddresses$ | async) as walletAddresses">
|
||||
<app-preview-title>
|
||||
<span i18n="shared.wallet">Wallet</span>
|
||||
</app-preview-title>
|
||||
<div>
|
||||
<div class="table-col">
|
||||
<table class="table table-borderless dual-col-striped table-fixed wallet-table" *ngIf="(walletStats$ | async) as walletStats">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td i18n="address.number-addresses">Addresses</td>
|
||||
<td class="wrap-cell">{{ addressStrings.length }}</td>
|
||||
<td class="spacer"></td>
|
||||
<td i18n="address.utxos">UTXOs</td>
|
||||
<td class="wrap-cell">{{ walletStats.utxos }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n="wallet.balance-btc">Balance (BTC)</td>
|
||||
<td class="wrap-cell"><app-amount [satoshis]="walletStats.balance" [noFiat]="true" [digitsInfo]="walletStats.balance > 1_000_000_000 ? '1.4-4' : '1.8-8'"></app-amount></td>
|
||||
<td class="spacer"></td>
|
||||
<td i18n="wallet.balance-usd">Balance (USD)</td>
|
||||
<td class="wrap-cell"><span class="fiat"><app-fiat [value]="walletStats.balance"></app-fiat></span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="w-100 d-block d-md-none"></div>
|
||||
<div class="col-md graph-col">
|
||||
<app-address-graph [addressSummary$]="walletSummary$" period="all" [widget]="true" [defaultFiat]="true" [height]="330" [left]="-40" [right]="-40" [showLegend]="false" [showYAxis]="false"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,31 +0,0 @@
|
||||
.title-wrapper {
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
.graph-col {
|
||||
height: 350px;
|
||||
text-align: center;
|
||||
padding: 0;
|
||||
margin-left: 2px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.table-col {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.table {
|
||||
font-size: 32px;
|
||||
|
||||
::ng-deep .symbol {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
|
||||
.fiat {
|
||||
display: block;
|
||||
}
|
||||
@@ -1,245 +0,0 @@
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { ActivatedRoute, ParamMap } from '@angular/router';
|
||||
import { switchMap, catchError, map, tap, shareReplay, startWith, scan } from 'rxjs/operators';
|
||||
import { Address, AddressTxSummary, ChainStats, Transaction } from '@interfaces/electrs.interface';
|
||||
import { StateService } from '@app/services/state.service';
|
||||
import { ApiService } from '@app/services/api.service';
|
||||
import { of, Observable, Subscription } from 'rxjs';
|
||||
import { SeoService } from '@app/services/seo.service';
|
||||
import { seoDescriptionNetwork } from '@app/shared/common.utils';
|
||||
import { WalletAddress } from '@interfaces/node-api.interface';
|
||||
import { OpenGraphService } from '../../services/opengraph.service';
|
||||
import { WebsocketService } from '../../services/websocket.service';
|
||||
|
||||
class WalletStats implements ChainStats {
|
||||
addresses: string[];
|
||||
funded_txo_count: number;
|
||||
funded_txo_sum: number;
|
||||
spent_txo_count: number;
|
||||
spent_txo_sum: number;
|
||||
tx_count: number;
|
||||
|
||||
constructor (stats: ChainStats[], addresses: string[]) {
|
||||
Object.assign(this, stats.reduce((acc, stat) => {
|
||||
acc.funded_txo_count += stat.funded_txo_count;
|
||||
acc.funded_txo_sum += stat.funded_txo_sum;
|
||||
acc.spent_txo_count += stat.spent_txo_count;
|
||||
acc.spent_txo_sum += stat.spent_txo_sum;
|
||||
return acc;
|
||||
}, {
|
||||
funded_txo_count: 0,
|
||||
funded_txo_sum: 0,
|
||||
spent_txo_count: 0,
|
||||
spent_txo_sum: 0,
|
||||
tx_count: 0,
|
||||
})
|
||||
);
|
||||
this.addresses = addresses;
|
||||
}
|
||||
|
||||
public addTx(tx: Transaction): void {
|
||||
for (const vin of tx.vin) {
|
||||
if (this.addresses.includes(vin.prevout?.scriptpubkey_address)) {
|
||||
this.spendTxo(vin.prevout.value);
|
||||
}
|
||||
}
|
||||
for (const vout of tx.vout) {
|
||||
if (this.addresses.includes(vout.scriptpubkey_address)) {
|
||||
this.fundTxo(vout.value);
|
||||
}
|
||||
}
|
||||
this.tx_count++;
|
||||
}
|
||||
|
||||
public removeTx(tx: Transaction): void {
|
||||
for (const vin of tx.vin) {
|
||||
if (this.addresses.includes(vin.prevout?.scriptpubkey_address)) {
|
||||
this.unspendTxo(vin.prevout.value);
|
||||
}
|
||||
}
|
||||
for (const vout of tx.vout) {
|
||||
if (this.addresses.includes(vout.scriptpubkey_address)) {
|
||||
this.unfundTxo(vout.value);
|
||||
}
|
||||
}
|
||||
this.tx_count--;
|
||||
}
|
||||
|
||||
private fundTxo(value: number): void {
|
||||
this.funded_txo_sum += value;
|
||||
this.funded_txo_count++;
|
||||
}
|
||||
|
||||
private unfundTxo(value: number): void {
|
||||
this.funded_txo_sum -= value;
|
||||
this.funded_txo_count--;
|
||||
}
|
||||
|
||||
private spendTxo(value: number): void {
|
||||
this.spent_txo_sum += value;
|
||||
this.spent_txo_count++;
|
||||
}
|
||||
|
||||
private unspendTxo(value: number): void {
|
||||
this.spent_txo_sum -= value;
|
||||
this.spent_txo_count--;
|
||||
}
|
||||
|
||||
get balance(): number {
|
||||
return this.funded_txo_sum - this.spent_txo_sum;
|
||||
}
|
||||
|
||||
get totalReceived(): number {
|
||||
return this.funded_txo_sum;
|
||||
}
|
||||
|
||||
get utxos(): number {
|
||||
return this.funded_txo_count - this.spent_txo_count;
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-wallet-preview',
|
||||
templateUrl: './wallet-preview.component.html',
|
||||
styleUrls: ['./wallet-preview.component.scss']
|
||||
})
|
||||
export class WalletPreviewComponent implements OnInit, OnDestroy {
|
||||
network = '';
|
||||
|
||||
addresses: Address[] = [];
|
||||
addressStrings: string[] = [];
|
||||
walletName: string;
|
||||
isLoadingWallet = true;
|
||||
wallet$: Observable<Record<string, WalletAddress>>;
|
||||
walletAddresses$: Observable<Record<string, Address>>;
|
||||
walletSummary$: Observable<AddressTxSummary[]>;
|
||||
walletStats$: Observable<WalletStats>;
|
||||
error: any;
|
||||
walletSubscription: Subscription;
|
||||
|
||||
collapseAddresses: boolean = true;
|
||||
|
||||
fullyLoaded = false;
|
||||
txCount = 0;
|
||||
received = 0;
|
||||
sent = 0;
|
||||
chainBalance = 0;
|
||||
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private stateService: StateService,
|
||||
private apiService: ApiService,
|
||||
private seoService: SeoService,
|
||||
private websocketService: WebsocketService,
|
||||
private openGraphService: OpenGraphService,
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.websocketService.want(['blocks', 'stats']);
|
||||
this.stateService.networkChanged$.subscribe((network) => this.network = network);
|
||||
this.wallet$ = this.route.paramMap.pipe(
|
||||
map((params: ParamMap) => params.get('wallet') as string),
|
||||
tap((walletName: string) => {
|
||||
this.walletName = walletName;
|
||||
this.openGraphService.waitFor('wallet-addresses-' + this.walletName);
|
||||
this.openGraphService.waitFor('wallet-data-' + this.walletName);
|
||||
this.openGraphService.waitFor('wallet-txs-' + this.walletName);
|
||||
this.seoService.setTitle($localize`:@@wallet.component.browser-title:Wallet: ${walletName}:INTERPOLATION:`);
|
||||
this.seoService.setDescription($localize`:@@meta.description.bitcoin.wallet:See mempool transactions, confirmed transactions, balance, and more for ${this.stateService.network==='liquid'||this.stateService.network==='liquidtestnet'?'Liquid':'Bitcoin'}${seoDescriptionNetwork(this.stateService.network)} wallet ${walletName}:INTERPOLATION:.`);
|
||||
}),
|
||||
switchMap((walletName: string) => this.apiService.getWallet$(walletName).pipe(
|
||||
catchError((err) => {
|
||||
this.error = err;
|
||||
this.seoService.logSoft404();
|
||||
console.log(err);
|
||||
this.openGraphService.fail('wallet-addresses-' + this.walletName);
|
||||
this.openGraphService.fail('wallet-data-' + this.walletName);
|
||||
this.openGraphService.fail('wallet-txs-' + this.walletName);
|
||||
return of({});
|
||||
})
|
||||
)),
|
||||
shareReplay(1),
|
||||
);
|
||||
|
||||
this.walletAddresses$ = this.wallet$.pipe(
|
||||
map(wallet => {
|
||||
const walletInfo: Record<string, Address> = {};
|
||||
for (const address of Object.keys(wallet)) {
|
||||
walletInfo[address] = {
|
||||
address,
|
||||
chain_stats: wallet[address].stats,
|
||||
mempool_stats: {
|
||||
funded_txo_count: 0,
|
||||
funded_txo_sum: 0,
|
||||
spent_txo_count: 0, spent_txo_sum: 0, tx_count: 0
|
||||
},
|
||||
};
|
||||
}
|
||||
return walletInfo;
|
||||
}),
|
||||
tap(() => {
|
||||
this.isLoadingWallet = false;
|
||||
})
|
||||
);
|
||||
|
||||
this.walletSubscription = this.walletAddresses$.subscribe(wallet => {
|
||||
this.addressStrings = Object.keys(wallet);
|
||||
this.addresses = Object.values(wallet);
|
||||
this.openGraphService.waitOver('wallet-addresses-' + this.walletName);
|
||||
});
|
||||
|
||||
this.walletSummary$ = this.wallet$.pipe(
|
||||
map(wallet => this.deduplicateWalletTransactions(Object.values(wallet).flatMap(address => address.transactions))),
|
||||
tap(() => {
|
||||
this.openGraphService.waitOver('wallet-txs-' + this.walletName);
|
||||
})
|
||||
);
|
||||
|
||||
this.walletStats$ = this.wallet$.pipe(
|
||||
switchMap(wallet => {
|
||||
const walletStats = new WalletStats(Object.values(wallet).map(w => w.stats), Object.keys(wallet));
|
||||
return this.stateService.walletTransactions$.pipe(
|
||||
startWith([]),
|
||||
scan((stats, newTransactions) => {
|
||||
for (const tx of newTransactions) {
|
||||
stats.addTx(tx);
|
||||
}
|
||||
return stats;
|
||||
}, walletStats),
|
||||
);
|
||||
}),
|
||||
tap(() => {
|
||||
this.openGraphService.waitOver('wallet-data-' + this.walletName);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
deduplicateWalletTransactions(walletTransactions: AddressTxSummary[]): AddressTxSummary[] {
|
||||
const transactions = new Map<string, AddressTxSummary>();
|
||||
for (const tx of walletTransactions) {
|
||||
if (transactions.has(tx.txid)) {
|
||||
transactions.get(tx.txid).value += tx.value;
|
||||
} else {
|
||||
transactions.set(tx.txid, tx);
|
||||
}
|
||||
}
|
||||
return Array.from(transactions.values()).sort((a, b) => {
|
||||
if (a.height === b.height) {
|
||||
return b.tx_position - a.tx_position;
|
||||
}
|
||||
return b.height - a.height;
|
||||
});
|
||||
}
|
||||
|
||||
normalizeAddress(address: string): string {
|
||||
if (/^[A-Z]{2,5}1[AC-HJ-NP-Z02-9]{8,100}|04[a-fA-F0-9]{128}|(02|03)[a-fA-F0-9]{64}$/.test(address)) {
|
||||
return address.toLowerCase();
|
||||
} else {
|
||||
return address;
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.walletSubscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,6 @@ import { HashrateChartPoolsComponent } from '@components/hashrates-chart-pools/h
|
||||
import { BlockHealthGraphComponent } from '@components/block-health-graph/block-health-graph.component';
|
||||
import { AddressComponent } from '@components/address/address.component';
|
||||
import { WalletComponent } from '@components/wallet/wallet.component';
|
||||
import { WalletPreviewComponent } from '@components/wallet/wallet-preview.component';
|
||||
import { AddressGraphComponent } from '@components/address-graph/address-graph.component';
|
||||
import { UtxoGraphComponent } from '@components/utxo-graph/utxo-graph.component';
|
||||
import { ActiveAccelerationBox } from '@components/acceleration/active-acceleration-box/active-acceleration-box.component';
|
||||
@@ -50,7 +49,6 @@ import { CommonModule } from '@angular/common';
|
||||
MempoolBlockComponent,
|
||||
AddressComponent,
|
||||
WalletComponent,
|
||||
WalletPreviewComponent,
|
||||
|
||||
MiningDashboardComponent,
|
||||
AcceleratorDashboardComponent,
|
||||
|
||||
@@ -32,8 +32,6 @@ export interface Transaction {
|
||||
price?: Price;
|
||||
sigops?: number;
|
||||
flags?: bigint;
|
||||
largeInput?: boolean;
|
||||
largeOutput?: boolean;
|
||||
}
|
||||
|
||||
export interface TransactionChannels {
|
||||
|
||||
@@ -144,9 +144,4 @@ export interface HealthCheckHost {
|
||||
link?: string;
|
||||
statusPage?: SafeResourceUrl;
|
||||
flag?: string;
|
||||
hashes?: {
|
||||
frontend?: string;
|
||||
backend?: string;
|
||||
electrs?: string;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<tbody>
|
||||
<tr>
|
||||
<td i18n="lightning.created">Created</td>
|
||||
<td><app-timestamp [customFormat]="'yyyy-MM-dd HH:mm'" [unixTime]="channel.created" [hideTimeSince]="true"></app-timestamp></td>
|
||||
<td>{{ channel.created | date:'yyyy-MM-dd HH:mm' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n="lightning.capacity">Capacity</td>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<ng-container *ngFor="let channel of channels;">
|
||||
<tr>
|
||||
<td class="timestamp">
|
||||
<app-timestamp [customFormat]="'yyyy-MM-dd HH:mm'" [unixTime]="channel.closing_date" [hideTimeSince]="true"></app-timestamp>
|
||||
‎{{ channel.closing_date | date:'yyyy-MM-dd HH:mm' }}
|
||||
</td>
|
||||
<td class="capacity text-right">
|
||||
<app-amount *ngIf="channel.capacity > 100000000; else smallnode" [satoshis]="channel.capacity" [digitsInfo]="'1.2-2'" [noFiat]="true"></app-amount>
|
||||
|
||||
@@ -142,12 +142,12 @@ const routes: Routes = [
|
||||
|
||||
if (window['__env']?.OFFICIAL_MEMPOOL_SPACE) {
|
||||
routes[0].children.push({
|
||||
path: 'monitoring',
|
||||
path: 'nodes',
|
||||
data: { networks: ['bitcoin', 'liquid'] },
|
||||
component: ServerHealthComponent
|
||||
});
|
||||
routes[0].children.push({
|
||||
path: 'nodes',
|
||||
path: 'network',
|
||||
data: { networks: ['bitcoin', 'liquid'] },
|
||||
component: ServerStatusComponent
|
||||
});
|
||||
|
||||
@@ -13,7 +13,6 @@ import { RbfList } from '@components/rbf-list/rbf-list.component';
|
||||
import { ServerHealthComponent } from '@components/server-health/server-health.component';
|
||||
import { ServerStatusComponent } from '@components/server-health/server-status.component';
|
||||
import { FaucetComponent } from '@components/faucet/faucet.component'
|
||||
import { SimpleProofWidgetComponent } from './components/simpleproof-widget/simpleproof-widget.component';
|
||||
|
||||
const browserWindow = window || {};
|
||||
// @ts-ignore
|
||||
@@ -131,13 +130,6 @@ if (window['__env']?.OFFICIAL_MEMPOOL_SPACE) {
|
||||
}
|
||||
}
|
||||
|
||||
if (window['__env']?.customize?.dashboard.widgets?.some(w => w.component ==='simpleproof')) {
|
||||
routes[0].children.push({
|
||||
path: 'sp/verified',
|
||||
component: SimpleProofWidgetComponent,
|
||||
});
|
||||
}
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
RouterModule.forChild(routes)
|
||||
|
||||
@@ -3,7 +3,6 @@ import { RouterModule, Routes } from '@angular/router';
|
||||
import { TransactionPreviewComponent } from '@components/transaction/transaction-preview.component';
|
||||
import { BlockPreviewComponent } from '@components/block/block-preview.component';
|
||||
import { AddressPreviewComponent } from '@components/address/address-preview.component';
|
||||
import { WalletPreviewComponent } from '@components/wallet/wallet-preview.component';
|
||||
import { PoolPreviewComponent } from '@components/pool/pool-preview.component';
|
||||
import { MasterPagePreviewComponent } from '@components/master-page-preview/master-page-preview.component';
|
||||
|
||||
@@ -21,11 +20,6 @@ const routes: Routes = [
|
||||
children: [],
|
||||
component: AddressPreviewComponent
|
||||
},
|
||||
{
|
||||
path: 'wallet/:wallet',
|
||||
children: [],
|
||||
component: WalletPreviewComponent
|
||||
},
|
||||
{
|
||||
path: 'tx/:id',
|
||||
children: [],
|
||||
|
||||
@@ -55,7 +55,7 @@ export class EtaService {
|
||||
|
||||
return {
|
||||
hashratePercentage: acceleratingHashrateFraction * 100,
|
||||
ETA: Date.now() + da.adjustedTimeAvg * mempoolPosition.block,
|
||||
ETA: Date.now() + da.timeAvg * mempoolPosition.block,
|
||||
acceleratedETA: this.calculateETAFromShares([
|
||||
{ block: mempoolPosition.block, hashrateShare: (1 - acceleratingHashrateFraction) },
|
||||
{ block: 0, hashrateShare: acceleratingHashrateFraction },
|
||||
@@ -216,7 +216,7 @@ export class EtaService {
|
||||
}
|
||||
// at max depth, the transaction is guaranteed to be mined in the next block if it hasn't already
|
||||
Q += ((max + 1) * (1-tailProb));
|
||||
const eta = da.adjustedTimeAvg * Q; // T x Q
|
||||
const eta = da.timeAvg * Q; // T x Q
|
||||
|
||||
return {
|
||||
now,
|
||||
|
||||
@@ -8,7 +8,6 @@ import { Observable, of, ReplaySubject, tap, catchError, share, filter, switchMa
|
||||
import { IBackendInfo } from '@interfaces/websocket.interface';
|
||||
import { Acceleration, AccelerationHistoryParams } from '@interfaces/node-api.interface';
|
||||
import { AccelerationStats } from '@components/acceleration/acceleration-stats/acceleration-stats.component';
|
||||
import { SimpleProof } from '../components/simpleproof-widget/simpleproof-widget.component';
|
||||
|
||||
export interface IUser {
|
||||
username: string;
|
||||
@@ -144,8 +143,8 @@ export class ServicesApiServices {
|
||||
return this.httpClient.post<any>(`${this.stateService.env.SERVICES_API}/accelerator/accelerate/applePay`, { txInput: txInput, cardTag: cardTag, token: token, referenceId: referenceId, userApprovedUSD: userApprovedUSD });
|
||||
}
|
||||
|
||||
accelerateWithGooglePay$(txInput: string, token: string, verificationToken: string, cardTag: string, referenceId: string, userApprovedUSD: number) {
|
||||
return this.httpClient.post<any>(`${this.stateService.env.SERVICES_API}/accelerator/accelerate/googlePay`, { txInput: txInput, cardTag: cardTag, token: token, verificationToken: verificationToken, referenceId: referenceId, userApprovedUSD: userApprovedUSD });
|
||||
accelerateWithGooglePay$(txInput: string, token: string, cardTag: string, referenceId: string, userApprovedUSD: number) {
|
||||
return this.httpClient.post<any>(`${this.stateService.env.SERVICES_API}/accelerator/accelerate/googlePay`, { txInput: txInput, cardTag: cardTag, token: token, referenceId: referenceId, userApprovedUSD: userApprovedUSD });
|
||||
}
|
||||
|
||||
getAccelerations$(): Observable<Acceleration[]> {
|
||||
@@ -218,8 +217,4 @@ export class ServicesApiServices {
|
||||
getPaymentStatus$(orderId: string): Observable<any> {
|
||||
return this.httpClient.get<any>(`${this.stateService.env.SERVICES_API}/payments/bitcoin/check?order_id=${orderId}`, { observe: 'response' });
|
||||
}
|
||||
|
||||
getSimpleProofs$(key: string): Observable<Record<string, SimpleProof>> {
|
||||
return this.httpClient.get<Record<string, SimpleProof>>(`${this.stateService.env.SERVICES_API}/sp/verified/${key}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,7 +186,6 @@ export class StateService {
|
||||
live2Chart$ = new Subject<OptimizedMempoolStats>();
|
||||
|
||||
viewAmountMode$: BehaviorSubject<'btc' | 'sats' | 'fiat'>;
|
||||
timezone$: BehaviorSubject<string>;
|
||||
connectionState$ = new BehaviorSubject<0 | 1 | 2>(2);
|
||||
isTabHidden$: Observable<boolean>;
|
||||
|
||||
@@ -348,9 +347,6 @@ export class StateService {
|
||||
const viewAmountModePreference = this.storageService.getValue('view-amount-mode') as 'btc' | 'sats' | 'fiat';
|
||||
this.viewAmountMode$ = new BehaviorSubject<'btc' | 'sats' | 'fiat'>(viewAmountModePreference || 'btc');
|
||||
|
||||
const timezonePreference = this.storageService.getValue('timezone-preference');
|
||||
this.timezone$ = new BehaviorSubject<string>(timezonePreference || 'local');
|
||||
|
||||
this.backend$.subscribe(backend => {
|
||||
this.backend = backend;
|
||||
});
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
<app-fiat-selector></app-fiat-selector>
|
||||
</div>
|
||||
<div class="selector">
|
||||
<app-timezone-selector></app-timezone-selector>
|
||||
<app-rate-unit-selector></app-rate-unit-selector>
|
||||
</div>
|
||||
<div class="selector d-none" [ngClass]="isServicesPage ? 'd-lg-flex' : 'd-md-flex'">
|
||||
<app-amount-selector></app-amount-selector>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<span *ngIf="seconds === undefined">-</span>
|
||||
<span *ngIf="seconds !== undefined">
|
||||
‎{{ seconds * 1000 | date: customFormat ?? 'yyyy-MM-dd HH:mm' : (stateService.timezone$ | async) }}
|
||||
‎{{ seconds * 1000 | date: customFormat ?? 'yyyy-MM-dd HH:mm' }}
|
||||
<div class="lg-inline" *ngIf="!hideTimeSince">
|
||||
<i class="symbol">(<app-time kind="since" [time]="seconds" [fastRender]="true" [precision]="precision" [minUnit]="minUnit"></app-time>)</i>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core';
|
||||
import { StateService } from '@app/services/state.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-timestamp',
|
||||
@@ -17,10 +16,6 @@ export class TimestampComponent implements OnChanges {
|
||||
|
||||
seconds: number | undefined = undefined;
|
||||
|
||||
constructor(
|
||||
public stateService: StateService,
|
||||
) { }
|
||||
|
||||
ngOnChanges(): void {
|
||||
if (this.unixTime) {
|
||||
this.seconds = this.unixTime;
|
||||
|
||||
@@ -8,12 +8,8 @@ export class AmountShortenerPipe implements PipeTransform {
|
||||
const digits = args[0] ?? 1;
|
||||
const unit = args[1] || undefined;
|
||||
const isMoney = args[2] || false;
|
||||
const sigfigs = args[3] || false; // if true, "digits" is the number of significant digits, not the number of decimal places
|
||||
|
||||
if (num < 1000) {
|
||||
if (sigfigs) {
|
||||
return Number(num.toPrecision(digits));
|
||||
}
|
||||
return num.toFixed(digits);
|
||||
}
|
||||
|
||||
@@ -29,15 +25,10 @@ export class AmountShortenerPipe implements PipeTransform {
|
||||
const rx = /\.0+$|(\.[0-9]*[1-9])0+$/;
|
||||
const item = lookup.slice().reverse().find((item) => num >= item.value);
|
||||
|
||||
if (!item) {
|
||||
return '0';
|
||||
if (unit !== undefined) {
|
||||
return item ? (num / item.value).toFixed(digits).replace(rx, '$1') + ' ' + item.symbol + unit : '0';
|
||||
} else {
|
||||
return item ? (num / item.value).toFixed(digits).replace(rx, '$1') + item.symbol : '0';
|
||||
}
|
||||
|
||||
const scaledNum = num / item.value;
|
||||
const formattedNum = Number(sigfigs ? scaledNum.toPrecision(digits) : scaledNum.toFixed(digits)).toString();
|
||||
|
||||
return unit !== undefined
|
||||
? formattedNum + ' ' + item.symbol + unit
|
||||
: formattedNum + item.symbol;
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,6 @@ import { FiatSelectorComponent } from '@components/fiat-selector/fiat-selector.c
|
||||
import { RateUnitSelectorComponent } from '@components/rate-unit-selector/rate-unit-selector.component';
|
||||
import { ThemeSelectorComponent } from '@components/theme-selector/theme-selector.component';
|
||||
import { AmountSelectorComponent } from '@components/amount-selector/amount-selector.component';
|
||||
import { TimezoneSelectorComponent } from '@components/timezone-selector/timezone-selector.component';
|
||||
import { BrowserOnlyDirective } from '@app/shared/directives/browser-only.directive';
|
||||
import { ServerOnlyDirective } from '@app/shared/directives/server-only.directive';
|
||||
import { ColoredPriceDirective } from '@app/shared/directives/colored-price.directive';
|
||||
@@ -116,7 +115,6 @@ import { CalculatorComponent } from '@components/calculator/calculator.component
|
||||
import { BitcoinsatoshisPipe } from '@app/shared/pipes/bitcoinsatoshis.pipe';
|
||||
import { HttpErrorComponent } from '@app/shared/components/http-error/http-error.component';
|
||||
import { TwitterWidgetComponent } from '@components/twitter-widget/twitter-widget.component';
|
||||
import { SimpleProofWidgetComponent } from '@components/simpleproof-widget/simpleproof-widget.component';
|
||||
import { FaucetComponent } from '@components/faucet/faucet.component';
|
||||
import { TwitterLogin } from '@components/twitter-login/twitter-login.component';
|
||||
import { BitcoinInvoiceComponent } from '@components/bitcoin-invoice/bitcoin-invoice.component';
|
||||
@@ -136,7 +134,6 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from '@app/shared/components/
|
||||
ThemeSelectorComponent,
|
||||
RateUnitSelectorComponent,
|
||||
AmountSelectorComponent,
|
||||
TimezoneSelectorComponent,
|
||||
ScriptpubkeyTypePipe,
|
||||
RelativeUrlPipe,
|
||||
NoSanitizePipe,
|
||||
@@ -236,7 +233,6 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from '@app/shared/components/
|
||||
OrdDataComponent,
|
||||
HttpErrorComponent,
|
||||
TwitterWidgetComponent,
|
||||
SimpleProofWidgetComponent,
|
||||
FaucetComponent,
|
||||
TwitterLogin,
|
||||
BitcoinInvoiceComponent,
|
||||
@@ -287,7 +283,6 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from '@app/shared/components/
|
||||
RateUnitSelectorComponent,
|
||||
ThemeSelectorComponent,
|
||||
AmountSelectorComponent,
|
||||
TimezoneSelectorComponent,
|
||||
ScriptpubkeyTypePipe,
|
||||
RelativeUrlPipe,
|
||||
Hex2asciiPipe,
|
||||
@@ -371,7 +366,6 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from '@app/shared/components/
|
||||
OrdDataComponent,
|
||||
HttpErrorComponent,
|
||||
TwitterWidgetComponent,
|
||||
SimpleProofWidgetComponent,
|
||||
TwitterLogin,
|
||||
BitcoinInvoiceComponent,
|
||||
BitcoinsatoshisPipe,
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
id="Layer_1"
|
||||
version="1.1"
|
||||
viewBox="0 0 492.10001 575.79999"
|
||||
width="492.10001"
|
||||
height="575.79999"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<!-- Generator: Adobe Illustrator 29.0.0, SVG Export Plug-In . SVG Version: 2.1.0 Build 186) -->
|
||||
<defs
|
||||
id="defs184">
|
||||
<style
|
||||
id="style182">
|
||||
.st0 {
|
||||
fill: #fff;
|
||||
}
|
||||
|
||||
.st1 {
|
||||
fill: #f88e2b;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g
|
||||
id="g216"
|
||||
transform="translate(-159.5,-152.1)">
|
||||
<polygon
|
||||
class="st0"
|
||||
points="296.6,375.6 296.6,459.1 404.9,524.2 651.6,378.5 651.6,294.6 651.6,294.5 405.3,440.4 "
|
||||
id="polygon212" />
|
||||
<polygon
|
||||
class="st1"
|
||||
points="405.5,644.5 231.3,542 231.3,335.6 405.5,235 520.9,301.7 592.1,259.8 405.5,152.1 159.5,294.1 159.5,583.1 405.5,727.9 651.6,583.1 651.6,447.1 579.7,489.4 579.7,542 "
|
||||
id="polygon214" />
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 996 B |
@@ -5,7 +5,6 @@ nvm use v20.12.0
|
||||
|
||||
# start all mempool backends that exist
|
||||
for site in mainnet mainnet-lightning testnet testnet-lightning testnet4 signet signet-lightning liquid liquidtestnet;do
|
||||
[ ! -e "${HOME}/${site}/backend/" ] && continue
|
||||
cd "${HOME}/${site}/backend/" && \
|
||||
echo "starting mempool backend: ${site}" && \
|
||||
screen -dmS "${site}" sh -c 'while true;do npm run start-production;sleep 1;done'
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"SERVER": {
|
||||
"HOST": "https://bitb.tk7.mempool.space",
|
||||
"HTTP_PORT": 8006
|
||||
},
|
||||
"MEMPOOL": {
|
||||
"HTTP_HOST": "http://127.0.0.1",
|
||||
"HTTP_PORT": 86,
|
||||
"NETWORK": "bitb"
|
||||
},
|
||||
"PUPPETEER": {
|
||||
"CLUSTER_SIZE": 8,
|
||||
"EXEC_PATH": "/usr/local/bin/chrome",
|
||||
"MAX_PAGE_AGE": 86400,
|
||||
"RENDER_TIMEOUT": 3000
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,6 @@ class Server {
|
||||
secureHost = true;
|
||||
secureMempoolHost = true;
|
||||
canonicalHost: string;
|
||||
networkName: string;
|
||||
|
||||
seoQueueLength: number = 0;
|
||||
unfurlQueueLength: number = 0;
|
||||
@@ -42,7 +41,6 @@ class Server {
|
||||
this.secureHost = config.SERVER.HOST.startsWith('https');
|
||||
this.secureMempoolHost = config.MEMPOOL.HTTP_HOST.startsWith('https');
|
||||
this.network = config.MEMPOOL.NETWORK || 'bitcoin';
|
||||
this.networkName = networks[this.network].networkName || capitalize(this.network);
|
||||
|
||||
let canonical;
|
||||
switch(config.MEMPOOL.NETWORK) {
|
||||
@@ -341,7 +339,7 @@ class Server {
|
||||
|
||||
if (matchedRoute.render) {
|
||||
ogImageUrl = `${config.SERVER.HOST}/render/${lang || 'en'}/preview${path}`;
|
||||
ogTitle = `${this.networkName} ${matchedRoute.networkMode !== 'mainnet' ? capitalize(matchedRoute.networkMode) + ' ' : ''}${matchedRoute.title}`;
|
||||
ogTitle = `${this.network ? capitalize(this.network) + ' ' : ''}${matchedRoute.networkMode !== 'mainnet' ? capitalize(matchedRoute.networkMode) + ' ' : ''}${matchedRoute.title}`;
|
||||
} else {
|
||||
ogTitle = networks[this.network].title;
|
||||
}
|
||||
@@ -396,7 +394,7 @@ class Server {
|
||||
|
||||
if (matchedRoute.render) {
|
||||
ogImageUrl = `${config.SERVER.HOST}/render/${lang || 'en'}/preview${path}`;
|
||||
ogTitle = `${this.networkName} ${matchedRoute.networkMode !== 'mainnet' ? capitalize(matchedRoute.networkMode) + ' ' : ''}${matchedRoute.title}`;
|
||||
ogTitle = `${this.network ? capitalize(this.network) + ' ' : ''}${matchedRoute.networkMode !== 'mainnet' ? capitalize(matchedRoute.networkMode) + ' ' : ''}${matchedRoute.title}`;
|
||||
}
|
||||
|
||||
if (matchedRoute.sip) {
|
||||
|
||||
@@ -85,13 +85,6 @@ const routes = {
|
||||
return `Address: ${path[0]}`;
|
||||
}
|
||||
},
|
||||
wallet: {
|
||||
render: true,
|
||||
params: 1,
|
||||
getTitle(path) {
|
||||
return `Wallet: ${path[0]}`;
|
||||
}
|
||||
},
|
||||
blocks: {
|
||||
title: "Blocks",
|
||||
fallbackImg: '/resources/previews/blocks.jpg',
|
||||
@@ -270,7 +263,6 @@ export const networks = {
|
||||
routes: {} // no routes supported
|
||||
},
|
||||
onbtc: {
|
||||
networkName: 'ONBTC',
|
||||
title: 'National Bitcoin Office of El Salvador',
|
||||
description: 'The National Bitcoin Office (ONBTC) of El Salvador under President @nayibbukele',
|
||||
fallbackImg: '/resources/onbtc/onbtc-preview.jpg',
|
||||
@@ -291,14 +283,12 @@ export const networks = {
|
||||
}
|
||||
},
|
||||
bitb: {
|
||||
networkName: 'BITB',
|
||||
title: 'BITB | Bitwise Bitcoin ETF',
|
||||
description: 'BITB provides low-cost access to bitcoin through a professionally managed fund',
|
||||
fallbackImg: '/resources/bitb/bitb-preview.jpg',
|
||||
routes: { // only dynamic routes supported
|
||||
block: routes.block,
|
||||
address: routes.address,
|
||||
wallet: routes.wallet,
|
||||
tx: routes.tx,
|
||||
mining: {
|
||||
title: "Mining",
|
||||
@@ -313,14 +303,12 @@ export const networks = {
|
||||
}
|
||||
},
|
||||
meta: {
|
||||
networkName: 'Metaplanet',
|
||||
title: 'Metaplanet Inc.',
|
||||
description: 'Secure the Future with Bitcoin',
|
||||
fallbackImg: '/resources/meta/meta-preview.png',
|
||||
routes: { // only dynamic routes supported
|
||||
block: routes.block,
|
||||
address: routes.address,
|
||||
wallet: routes.wallet,
|
||||
tx: routes.tx,
|
||||
mining: {
|
||||
title: "Mining",
|
||||
|
||||
Reference in New Issue
Block a user