Compare commits

..

1 Commits

Author SHA1 Message Date
dependabot[bot]
ba163bd198 Bump dtolnay/rust-toolchain
Bumps [dtolnay/rust-toolchain](https://github.com/dtolnay/rust-toolchain) from d8352f6b1d2e870bc5716e7a6d9b65c4cc244a1a to a54c7afa936fefeb4456b2dd8068152669aa8203.
- [Release notes](https://github.com/dtolnay/rust-toolchain/releases)
- [Commits](d8352f6b1d...a54c7afa93)

---
updated-dependencies:
- dependency-name: dtolnay/rust-toolchain
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-16 03:00:46 +00:00
54 changed files with 418 additions and 750 deletions

View File

@@ -35,7 +35,7 @@ jobs:
- name: Install ${{ steps.gettoolchain.outputs.toolchain }} Rust toolchain
# Latest version available on this commit is 1.71.1
# Commit date is Aug 3, 2023
uses: dtolnay/rust-toolchain@d8352f6b1d2e870bc5716e7a6d9b65c4cc244a1a
uses: dtolnay/rust-toolchain@a54c7afa936fefeb4456b2dd8068152669aa8203
with:
toolchain: ${{ steps.gettoolchain.outputs.toolchain }}

View File

@@ -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

View File

@@ -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'));
}
}

View File

@@ -244,7 +244,7 @@ class Blocks {
*/
private async $getBlockExtended(block: IEsploraApi.Block, transactions: TransactionExtended[]): Promise<BlockExtended> {
const coinbaseTx = transactionUtils.stripCoinbaseTransaction(transactions[0]);
const blk: Partial<BlockExtended> = Object.assign({}, block);
const extras: Partial<BlockExtension> = {};
@@ -268,17 +268,15 @@ class Blocks {
extras.segwitTotalWeight = 0;
} else {
const stats: IBitcoinApi.BlockStats = await bitcoinClient.getBlockStats(block.id);
const feeStats = {
let feeStats = {
medianFee: stats.feerate_percentiles[2], // 50th percentiles
feeRange: [stats.minfeerate, stats.feerate_percentiles, stats.maxfeerate].flat(),
};
if (transactions?.length > 1) {
feeStats = Common.calcEffectiveFeeStatistics(transactions);
}
extras.medianFee = feeStats.medianFee;
extras.feeRange = feeStats.feeRange;
if (transactions?.length > 1) {
const effectiveFeeStats = Common.calcEffectiveFeeStatistics(transactions);
extras.effectiveMedianFee = effectiveFeeStats.effective_median;
extras.effectiveFeeRange = effectiveFeeStats.effective_range;
}
extras.totalFees = stats.totalfee;
extras.avgFee = stats.avgfee;
extras.avgFeeRate = stats.avgfeerate;
@@ -298,7 +296,7 @@ class Blocks {
extras.medianFeeAmt = extras.feePercentiles[3];
}
}
extras.virtualSize = block.weight / 4.0;
if (coinbaseTx?.vout.length > 0) {
extras.coinbaseAddress = coinbaseTx.vout[0].scriptpubkey_address ?? null;
@@ -1318,8 +1316,6 @@ class Blocks {
avg_fee_rate: block.extras.avgFeeRate ?? null,
median_fee_rate: block.extras.medianFee ?? null,
fee_rate_percentiles: block.extras.feeRange ?? null,
effective_median_fee_rate: block.extras.effectiveMedianFee ?? null,
effective_fee_rate_percentiles: block.extras.effectiveFeeRange ?? null,
total_inputs: block.extras.totalInputs ?? null,
total_input_amt: block.extras.totalInputAmt ?? null,
total_outputs: block.extras.totalOutputs ?? null,
@@ -1382,17 +1378,6 @@ class Blocks {
'perc_90': cleanBlock.fee_rate_percentiles[5],
'max': cleanBlock.fee_rate_percentiles[6],
};
if (cleanBlock.effective_fee_rate_percentiles) {
cleanBlock.effective_fee_rate_percentiles = {
'min': cleanBlock.effective_fee_rate_percentiles[0],
'perc_10': cleanBlock.effective_fee_rate_percentiles[1],
'perc_25': cleanBlock.effective_fee_rate_percentiles[2],
'perc_50': cleanBlock.effective_fee_rate_percentiles[3],
'perc_75': cleanBlock.effective_fee_rate_percentiles[4],
'perc_90': cleanBlock.effective_fee_rate_percentiles[5],
'max': cleanBlock.effective_fee_rate_percentiles[6],
};
}
// Re-org can happen after indexing so we need to always get the
// latest state from core

View File

@@ -1,6 +1,6 @@
import * as bitcoinjs from 'bitcoinjs-lib';
import { Request } from 'express';
import { EffectiveFeeStats, MempoolBlockWithTransactions, TransactionExtended, MempoolTransactionExtended, TransactionStripped, WorkingEffectiveFeeStats, TransactionClassified, TransactionFlags, FeeStats } from '../mempool.interfaces';
import { EffectiveFeeStats, MempoolBlockWithTransactions, TransactionExtended, MempoolTransactionExtended, TransactionStripped, WorkingEffectiveFeeStats, TransactionClassified, TransactionFlags } from '../mempool.interfaces';
import config from '../config';
import { NodeSocket } from '../repositories/NodesSocketsRepository';
import { isIP } from 'net';
@@ -856,15 +856,6 @@ export class Common {
}
}
static calcFeeStatistics(transactions: { txid: string, feePerVsize: number }[]): FeeStats {
// skip the coinbase, then sort the remaining fee rates
const sortedRates = transactions.slice(1).map(tx => tx.feePerVsize).sort((a, b) => a - b);
return {
median: Math.round(Common.getNthPercentile(50, sortedRates)),
range: [0, 10, 25, 50, 75, 90, 100].map(n => Math.round(Common.getNthPercentile(n, sortedRates))),
};
}
static calcEffectiveFeeStatistics(transactions: { weight: number, fee: number, effectiveFeePerVsize?: number, txid: string, acceleration?: boolean }[]): EffectiveFeeStats {
const sortedTxs = transactions.map(tx => { return { txid: tx.txid, weight: tx.weight, rate: tx.effectiveFeePerVsize || ((tx.fee || 0) / (tx.weight / 4)) }; }).sort((a, b) => a.rate - b.rate);
@@ -907,8 +898,8 @@ export class Common {
);
return {
effective_median: medianFeeRate,
effective_range: [
medianFee: medianFeeRate,
feeRange: [
minFee,
[10,25,50,75,90].map(n => Common.getNthPercentile(n, sortedTxs).rate),
maxFee,
@@ -1159,16 +1150,16 @@ export class OnlineFeeStatsCalculator {
}
return {
minFee: this.feeRange[0].min,
effective_median: this.feeRange[Math.floor(this.feeRange.length / 2)].avg,
medianFee: this.feeRange[Math.floor(this.feeRange.length / 2)].avg,
maxFee: this.feeRange[this.feeRange.length - 1].max,
effective_range: this.feeRange.map(f => f.avg),
feeRange: this.feeRange.map(f => f.avg),
};
}
getFeeStats(): EffectiveFeeStats {
const stats = this.getRawFeeStats();
stats.effective_range[0] = stats.minFee;
stats.effective_range[stats.effective_range.length - 1] = stats.maxFee;
stats.feeRange[0] = stats.minFee;
stats.feeRange[stats.feeRange.length - 1] = stats.maxFee;
return stats;
}
}

View File

@@ -7,7 +7,7 @@ import cpfpRepository from '../repositories/CpfpRepository';
import { RowDataPacket } from 'mysql2';
class DatabaseMigration {
private static currentVersion = 95;
private static currentVersion = 94;
private queryTimeout = 3600_000;
private statisticsAddedIndexed = false;
private uniqueLogs: string[] = [];
@@ -1118,16 +1118,6 @@ class DatabaseMigration {
}
await this.updateToSchemaVersion(94);
}
if (databaseSchemaVersion < 95) {
// Version 95
await this.$executeQuery(`
ALTER TABLE \`blocks\`
ADD \`effective_median_fee\` BIGINT UNSIGNED NOT NULL DEFAULT 0,
ADD \`effective_fee_span\` JSON DEFAULT NULL;
`);
await this.updateToSchemaVersion(95);
}
}
/**

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -63,8 +63,7 @@ class FeeApi {
}
private optimizeMedianFee(pBlock: MempoolBlock, nextBlock: MempoolBlock | undefined, previousFee?: number): number {
const medianFee = pBlock.effectiveMedianFee ?? pBlock.medianFee;
const useFee = previousFee ? (medianFee + previousFee) / 2 : medianFee;
const useFee = previousFee ? (pBlock.medianFee + previousFee) / 2 : pBlock.medianFee;
if (pBlock.blockVSize <= 500000) {
return this.defaultFee;
}

View File

@@ -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);
}
}

View File

@@ -1,6 +1,6 @@
import { GbtGenerator, GbtResult, ThreadTransaction as RustThreadTransaction, ThreadAcceleration as RustThreadAcceleration } from 'rust-gbt';
import logger from '../logger';
import { MempoolBlock, MempoolTransactionExtended, MempoolBlockWithTransactions, MempoolBlockDelta, Ancestor, CompactThreadTransaction, FeeStats, TransactionClassified, TransactionCompressed, MempoolDeltaChange, GbtCandidates, PoolTag, EffectiveFeeStats } from '../mempool.interfaces';
import { MempoolBlock, MempoolTransactionExtended, MempoolBlockWithTransactions, MempoolBlockDelta, Ancestor, CompactThreadTransaction, EffectiveFeeStats, TransactionClassified, TransactionCompressed, MempoolDeltaChange, GbtCandidates, PoolTag } from '../mempool.interfaces';
import { Common, OnlineFeeStatsCalculator } from './common';
import config from '../config';
import { Worker } from 'worker_threads';
@@ -33,8 +33,6 @@ class MempoolBlocks {
totalFees: block.totalFees,
medianFee: block.medianFee,
feeRange: block.feeRange,
effectiveMedianFee: block.effectiveMedianFee,
effectiveFeeRange: block.effectiveFeeRange,
};
});
}
@@ -529,7 +527,7 @@ class MempoolBlocks {
totalSize,
totalWeight,
totalFees,
(hasBlockStack && blockIndex === lastBlockIndex && feeStatsCalculator) ? feeStatsCalculator.getFeeStats() : undefined,
(hasBlockStack && blockIndex === lastBlockIndex && feeStatsCalculator) ? feeStatsCalculator.getRawFeeStats() : undefined,
);
};
@@ -543,20 +541,17 @@ class MempoolBlocks {
return mempoolBlocks;
}
private dataToMempoolBlocks(transactionIds: string[], transactions: MempoolTransactionExtended[], totalSize: number, totalWeight: number, totalFees: number, effectiveFeeStats?: EffectiveFeeStats ): MempoolBlockWithTransactions {
const feeStats = Common.calcFeeStatistics(transactions);
if (!effectiveFeeStats) {
effectiveFeeStats = Common.calcEffectiveFeeStatistics(transactions);
private dataToMempoolBlocks(transactionIds: string[], transactions: MempoolTransactionExtended[], totalSize: number, totalWeight: number, totalFees: number, feeStats?: EffectiveFeeStats ): MempoolBlockWithTransactions {
if (!feeStats) {
feeStats = Common.calcEffectiveFeeStatistics(transactions);
}
return {
blockSize: totalSize,
blockVSize: (totalWeight / 4), // fractional vsize to avoid rounding errors
nTx: transactionIds.length,
totalFees: totalFees,
medianFee: feeStats.median,
feeRange: feeStats.range,
effectiveMedianFee: effectiveFeeStats.effective_median,
effectiveFeeRange: effectiveFeeStats.effective_range,
medianFee: feeStats.medianFee, // Common.percentile(transactions.map((tx) => tx.effectiveFeePerVsize), config.MEMPOOL.RECOMMENDED_FEE_PERCENTILE),
feeRange: feeStats.feeRange, //Common.getFeesInRange(transactions, rangeLength),
transactionIds: transactionIds,
transactions: transactions.map((tx) => Common.classifyTransaction(tx)),
};

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);

View File

@@ -73,8 +73,6 @@ export interface MempoolBlock {
medianFee: number;
totalFees: number;
feeRange: number[];
effectiveMedianFee?: number;
effectiveFeeRange?: number[];
}
export interface MempoolBlockWithTransactions extends MempoolBlock {
@@ -290,10 +288,8 @@ export const TransactionFlags = {
export interface BlockExtension {
totalFees: number;
medianFee: number; // core median fee rate
feeRange: number[]; // core fee rate percentiles
effectiveMedianFee?: number; // effective median fee rate
effectiveFeeRange?: number[]; // effective fee rate percentiles
medianFee: number; // median fee rate
feeRange: number[]; // fee rate percentiles
reward: number;
matchRate: number | null;
expectedFees: number | null;
@@ -373,18 +369,9 @@ export interface MempoolStats {
tx_count: number;
}
// Core fee stats
// measured in individual sats/vbyte
export interface FeeStats {
median: number; // median core fee rate
range: number[]; // 0th, 10th, 25th, 50th, 75th, 90th, 100th percentiles
}
// Mempool effective fee stats
// measured in effective sats/vbyte
export interface EffectiveFeeStats {
effective_median: number; // median effective fee rate by weight
effective_range: number[]; // 2nd, 10th, 25th, 50th, 75th, 90th, 98th percentiles
medianFee: number; // median effective fee rate
feeRange: number[]; // 2nd, 10th, 25th, 50th, 75th, 90th, 98th percentiles
}
export interface WorkingEffectiveFeeStats extends EffectiveFeeStats {

View File

@@ -315,12 +315,12 @@ class AccelerationRepository {
Infinity
);
const feeStats = Common.calcEffectiveFeeStatistics(template);
boostRate = feeStats.effective_median;
boostRate = feeStats.medianFee;
}
const accelerationSummaries = accelerations.map(acc => ({
...acc,
pools: acc.pools,
}));
}))
for (const acc of accelerations) {
if (blockTxs[acc.txid] && acc.pools.includes(block.extras.pool.id)) {
const tx = blockTxs[acc.txid];

View File

@@ -33,8 +33,6 @@ interface DatabaseBlock {
totalFees: number;
medianFee: number;
feeRange: string;
effectiveMedianFee?: number;
effectiveFeeRange?: string;
reward: number;
poolId: number;
poolName: string;
@@ -79,8 +77,6 @@ const BLOCK_DB_FIELDS = `
blocks.fees AS totalFees,
blocks.median_fee AS medianFee,
blocks.fee_span AS feeRange,
blocks.effective_median_fee AS effectiveMedianFee,
blocks.effective_fee_span AS effectiveFeeRange,
blocks.reward,
pools.unique_id AS poolId,
pools.name AS poolName,
@@ -112,7 +108,7 @@ class BlocksRepository {
/**
* Save indexed block data in the database
*/
public async $saveBlockInDatabase(block: BlockExtended): Promise<void> {
public async $saveBlockInDatabase(block: BlockExtended) {
const truncatedCoinbaseSignature = block?.extras?.coinbaseSignature?.substring(0, 500);
const truncatedCoinbaseSignatureAscii = block?.extras?.coinbaseSignatureAscii?.substring(0, 500);
@@ -121,7 +117,6 @@ class BlocksRepository {
height, hash, blockTimestamp, size,
weight, tx_count, coinbase_raw, difficulty,
pool_id, fees, fee_span, median_fee,
effective_fee_span, effective_median_fee,
reward, version, bits, nonce,
merkle_root, previous_block_hash, avg_fee, avg_fee_rate,
median_timestamp, header, coinbase_address, coinbase_addresses,
@@ -133,7 +128,6 @@ class BlocksRepository {
?, ?, FROM_UNIXTIME(?), ?,
?, ?, ?, ?,
?, ?, ?, ?,
?, ?,
?, ?, ?, ?,
?, ?, ?, ?,
FROM_UNIXTIME(?), ?, ?, ?,
@@ -161,8 +155,6 @@ class BlocksRepository {
block.extras.totalFees,
JSON.stringify(block.extras.feeRange),
block.extras.medianFee,
block.extras.effectiveFeeRange ? JSON.stringify(block.extras.effectiveFeeRange) : null,
block.extras.effectiveMedianFee,
block.extras.reward,
block.version,
block.bits,
@@ -976,16 +968,16 @@ class BlocksRepository {
/**
* Save indexed effective fee statistics
*
* @param id
* @param feeStats
*
* @param id
* @param feeStats
*/
public async $saveEffectiveFeeStats(id: string, feeStats: EffectiveFeeStats): Promise<void> {
try {
await DB.query(`
UPDATE blocks SET effective_median_fee = ?, effective_fee_span = ?
UPDATE blocks SET median_fee = ?, fee_span = ?
WHERE hash = ?`,
[feeStats.effective_median, JSON.stringify(feeStats.effective_range), id]
[feeStats.medianFee, JSON.stringify(feeStats.feeRange), id]
);
} catch (e) {
logger.err(`Cannot update block fee stats. Reason: ` + (e instanceof Error ? e.message : e));
@@ -1073,13 +1065,11 @@ class BlocksRepository {
blk.weight = dbBlk.weight;
blk.previousblockhash = dbBlk.previousblockhash;
blk.mediantime = dbBlk.mediantime;
// BlockExtension
extras.totalFees = dbBlk.totalFees;
extras.medianFee = dbBlk.medianFee;
extras.feeRange = JSON.parse(dbBlk.feeRange);
extras.effectiveMedianFee = dbBlk.effectiveMedianFee;
extras.effectiveFeeRange = dbBlk.effectiveFeeRange ? JSON.parse(dbBlk.effectiveFeeRange) : null;
extras.reward = dbBlk.reward;
extras.pool = {
id: dbBlk.poolId,

View File

@@ -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",

View File

@@ -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",

View File

@@ -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)' }
];
};

View File

@@ -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
*/

View File

@@ -32,9 +32,9 @@
<td i18n="block.weight">Weight</td>
<td [innerHTML]="'&lrm;' + (block?.weight | wuBytes: 2)"></td>
</tr>
<tr *ngIf="block?.extras?.medianFee != undefined && block?.extras?.effectiveMedianFee != undefined">
<tr *ngIf="block?.extras?.medianFee != undefined">
<td class="td-width" i18n="block.median-fee">Median fee</td>
<td>~<app-fee-rate [fee]="block?.extras?.effectiveMedianFee ?? block?.extras?.medianFee" rounding="1.0-0"></app-fee-rate></td>
<td>~<app-fee-rate [fee]="block?.extras?.medianFee" rounding="1.0-0"></app-fee-rate></td>
</tr>
<ng-template [ngIf]="fees !== undefined">
<tr>

View File

@@ -132,11 +132,11 @@
<td i18n="mempool-block.fee-span">Fee span</td>
<td><app-fee-rate [fee]="block.extras?.minFee" [showUnit]="false" rounding="1.0-0"></app-fee-rate> - <app-fee-rate [fee]="block.extras?.maxFee" rounding="1.0-0"></app-fee-rate></td>
</tr>
<tr *ngIf="block.extras?.effectiveMedianFee != undefined || block.extras?.medianFee != undefined">
<tr *ngIf="block.extras?.medianFee != undefined">
<td class="td-width" i18n="block.median-fee">Median fee</td>
<td>~<app-fee-rate [fee]="block.extras?.effectiveMedianFee ?? block.extras?.medianFee" rounding="1.0-0"></app-fee-rate>
<td>~<app-fee-rate [fee]="block.extras?.medianFee" rounding="1.0-0"></app-fee-rate>
<span class="fiat">
<app-fiat [blockConversion]="blockConversion" [value]="(block.extras?.effectiveMedianFee ?? block.extras?.medianFee) * 140" digitsInfo="1.2-2"
<app-fiat [blockConversion]="blockConversion" [value]="block.extras?.medianFee * 140" digitsInfo="1.2-2"
i18n-ngbTooltip="Transaction fee tooltip" ngbTooltip="Based on average native segwit transaction of 140 vBytes"
placement="bottom"></app-fiat>
</span>

View File

@@ -792,9 +792,6 @@ export class BlockComponent implements OnInit, OnDestroy {
}
getMinBlockFee(block: BlockExtended): number {
if (block?.extras?.effectiveFeeRange) {
return block.extras.effectiveFeeRange[0];
}
if (block?.extras?.feeRange) {
// heuristic to check if feeRange is adjusted for effective rates
if (block.extras.medianFee === block.extras.feeRange[3]) {
@@ -807,9 +804,6 @@ export class BlockComponent implements OnInit, OnDestroy {
}
getMaxBlockFee(block: BlockExtended): number {
if (block?.extras?.effectiveFeeRange) {
return block.extras.effectiveFeeRange[block.extras.effectiveFeeRange.length - 1];
}
if (block?.extras?.feeRange) {
return block.extras.feeRange[block.extras.feeRange.length - 1];
}

View File

@@ -23,7 +23,7 @@
<div class="block-body">
<ng-container *ngIf="!minimal">
<div *ngIf="block?.extras; else emptyfees" [attr.data-cy]="'bitcoin-block-offset=' + offset + '-index-' + i + '-fees'" class="fees">
~<app-fee-rate [fee]="block?.extras?.effectiveMedianFee ?? block?.extras?.medianFee" unitClass="" rounding="1.0-0"></app-fee-rate>
~<app-fee-rate [fee]="block?.extras?.medianFee" unitClass="" rounding="1.0-0"></app-fee-rate>
</div>
<ng-template #emptyfees>
<div [attr.data-cy]="'bitcoin-block-offset=' + offset + '-index-' + i + '-fees'" class="fees">

View File

@@ -414,9 +414,6 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
}
getMinBlockFee(block: BlockExtended): number {
if (block?.extras?.effectiveFeeRange) {
return block.extras.effectiveFeeRange[0];
}
if (block?.extras?.feeRange) {
// heuristic to check if feeRange is adjusted for effective rates
if (block.extras.medianFee === block.extras.feeRange[3]) {
@@ -429,9 +426,6 @@ export class BlockchainBlocksComponent implements OnInit, OnChanges, OnDestroy {
}
getMaxBlockFee(block: BlockExtended): number {
if (block?.extras?.effectiveFeeRange) {
return block.extras.effectiveFeeRange[block.extras.effectiveFeeRange.length - 1];
}
if (block?.extras?.feeRange) {
return block.extras.feeRange[block.extras.feeRange.length - 1];
}

View File

@@ -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>
&lrm;{{ block.timestamp * 1000 | date:'yyyy-MM-dd HH:mm:ss' }}
</td>
<td *ngIf="auditAvailable" class="health text-right" [ngClass]="{'widget': widget, 'legacy': !isMempoolModule}">
<a

View File

@@ -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>
&lrm;{{ 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>

View File

@@ -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>
&lrm;{{ 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>

View File

@@ -57,9 +57,6 @@ export class MempoolBlockComponent implements OnInit, OnDestroy {
this.mempoolBlockIndex--;
}
const ordinal = this.getOrdinal(mempoolBlocks[this.mempoolBlockIndex]);
// prefer effective fee stats if available
mempoolBlocks[this.mempoolBlockIndex].feeRange = mempoolBlocks[this.mempoolBlockIndex].effectiveFeeRange ?? mempoolBlocks[this.mempoolBlockIndex].feeRange;
mempoolBlocks[this.mempoolBlockIndex].medianFee = mempoolBlocks[this.mempoolBlockIndex].effectiveMedianFee ?? mempoolBlocks[this.mempoolBlockIndex].medianFee;
this.ordinal$.next(ordinal);
this.seoService.setTitle(ordinal);
this.seoService.setDescription($localize`:@@meta.description.mempool-block:See stats for ${this.stateService.network==='liquid'||this.stateService.network==='liquidtestnet'?'Liquid':'Bitcoin'}${seoDescriptionNetwork(this.stateService.network)} transactions in the mempool: fee range, aggregate size, and more. Mempool blocks are updated in real-time as the network receives new transactions.`);

View File

@@ -171,8 +171,6 @@ export class MempoolBlocksComponent implements OnInit, OnChanges, OnDestroy {
block.index = this.blockIndex + i;
block.height = lastBlock.height + i + 1;
block.blink = specialBlocks[block.height]?.networks.includes(this.stateService.network || 'mainnet') ? true : false;
block.medianFee = block.effectiveMedianFee ?? block.medianFee;
block.feeRange = block.effectiveFeeRange ?? block.feeRange;
});
const stringifiedBlocks = JSON.stringify(mempoolBlocks);

View File

@@ -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>
&lrm;{{ 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>

View File

@@ -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>

View File

@@ -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 : '';
}
}

View File

@@ -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>
&lrm;{{ 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>

View File

@@ -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>
&lrm;{{ 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 {

View File

@@ -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">&lrm;{{ 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>

View File

@@ -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();
});
}

View File

@@ -30,7 +30,7 @@ export class TxFeeRatingComponent implements OnInit, OnChanges, OnDestroy {
ngOnInit() {
this.blocksSubscription = this.cacheService.loadedBlocks$.subscribe((block) => {
if (this.tx.status.confirmed && this.tx.status.block_height === block.height && (block?.extras?.effectiveMedianFee ?? block?.extras?.medianFee) > 0) {
if (this.tx.status.confirmed && this.tx.status.block_height === block.height && block?.extras?.medianFee > 0) {
this.calculateRatings(block);
this.cd.markForCheck();
}
@@ -45,7 +45,7 @@ export class TxFeeRatingComponent implements OnInit, OnChanges, OnDestroy {
this.cacheService.loadBlock(this.tx.status.block_height);
const foundBlock = this.cacheService.getCachedBlock(this.tx.status.block_height) || null;
if (foundBlock && (foundBlock?.extras?.effectiveMedianFee ?? foundBlock?.extras?.medianFee) > 0) {
if (foundBlock && foundBlock?.extras?.medianFee > 0) {
this.calculateRatings(foundBlock);
}
}
@@ -56,7 +56,7 @@ export class TxFeeRatingComponent implements OnInit, OnChanges, OnDestroy {
calculateRatings(block: BlockExtended) {
const feePervByte = this.tx.effectiveFeePerVsize || this.tx.fee / (this.tx.weight / 4);
this.medianFeeNeeded = block?.extras?.effectiveMedianFee ?? block?.extras?.medianFee;
this.medianFeeNeeded = block?.extras?.medianFee;
// Block not filled
if (block.weight < this.stateService.env.BLOCK_WEIGHT_UNITS * 0.95) {

View File

@@ -32,8 +32,6 @@ export interface Transaction {
price?: Price;
sigops?: number;
flags?: bigint;
largeInput?: boolean;
largeOutput?: boolean;
}
export interface TransactionChannels {

View File

@@ -196,8 +196,6 @@ export interface BlockExtension {
minFee?: number;
maxFee?: number;
feeRange?: number[];
effectiveMedianFee?: number;
effectiveFeeRange?: number[];
reward?: number;
coinbaseRaw?: string;
matchRate?: number;

View File

@@ -61,10 +61,8 @@ export interface MempoolBlock {
blockVSize: number;
nTx: number;
medianFee: number;
effectiveMedianFee?: number;
totalFees: number;
feeRange: number[];
effectiveFeeRange?: number[];
index: number;
isStack?: boolean;
}

View File

@@ -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>

View File

@@ -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>
&lrm;{{ 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>

View File

@@ -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,

View File

@@ -143,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[]> {

View File

@@ -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;
});

View File

@@ -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>

View File

@@ -1,6 +1,6 @@
<span *ngIf="seconds === undefined">-</span>
<span *ngIf="seconds !== undefined">
&lrm;{{ seconds * 1000 | date: customFormat ?? 'yyyy-MM-dd HH:mm' : (stateService.timezone$ | async) }}
&lrm;{{ 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>

View File

@@ -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;

View File

@@ -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';
@@ -135,7 +134,6 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from '@app/shared/components/
ThemeSelectorComponent,
RateUnitSelectorComponent,
AmountSelectorComponent,
TimezoneSelectorComponent,
ScriptpubkeyTypePipe,
RelativeUrlPipe,
NoSanitizePipe,
@@ -285,7 +283,6 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from '@app/shared/components/
RateUnitSelectorComponent,
ThemeSelectorComponent,
AmountSelectorComponent,
TimezoneSelectorComponent,
ScriptpubkeyTypePipe,
RelativeUrlPipe,
Hex2asciiPipe,