Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion client/src/const.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,15 @@ export const blocksPerPage = 10
export const difficultyPeriod = 2016
export const maxMempoolTxs = 50
export const satoshisPerBitcoin = 100000000
export const averageNativeSegwitTransactionSize = 140
export const averageNativeSegwitTransactionVsize = 140
export const maxBlockWeight = 4000000
export const blockGridLoadingDelayMs = 100
export const blockGridTransactionSelectEvent = 'block-grid-transaction-select'
export const feeEstimateTargets = {
low: 12,
average: 3,
high: 1,
}

const configuredTargetBlockIntervalSeconds = Number(process.env.TARGET_BLOCK_INTERVAL_SECONDS)
export const targetBlockIntervalSeconds = configuredTargetBlockIntervalSeconds > 0
Expand Down
20 changes: 18 additions & 2 deletions client/src/lib/fees.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import {
averageNativeSegwitTransactionVsize,
feeEstimateTargets,
satoshisPerBitcoin,
} from '../const'

const MAX_BLOCK_VSIZE = 1000000

export const getFeeTierBoundaries = feeEst => {
const low = feeEst && feeEst[12]
, high = feeEst && feeEst[3]
const low = feeEst && feeEst[feeEstimateTargets.low]
, high = feeEst && feeEst[feeEstimateTargets.average]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You take feeEstimateTargets.average but marked it as hight. This could lead to misunderstandings in feature.

const feeEstimateTargets: {
    low: number;
    average: number;
    high: number;
}


return Number.isFinite(low) && Number.isFinite(high) && low >= 0 && high >= 0
? { low, high: Math.max(low, high) }
Expand Down Expand Up @@ -38,6 +44,16 @@ export function getConfEstimate(fee_estimates, feerate) {
return target_est ? target_est[0] : -1
}

// Estimate the USD cost of a typical native SegWit transaction at a given fee-rate.
export function estimateNativeSegwitTransactionFeeUsd(bitcoinPrice, feerate) {
return Number.isFinite(bitcoinPrice) && bitcoinPrice >= 0
&& Number.isFinite(feerate) && feerate >= 0
? (bitcoinPrice / satoshisPerBitcoin) *
feerate *
averageNativeSegwitTransactionVsize

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We render this block for both networks.

On the Liquid Network, the concept of a "Native SegWit transaction" does not apply in the same way as on Bitcoin. Liquid uses a modified transaction architecture built on the Elements codebase, where default transfers are Confidential Transactions that bundle cryptographic range proofs and Pedersen commitments for privacy.

A standard single-input, dual-output Liquid transaction is significantly larger than a plain Bitcoin Native SegWit (P2WPKH) transaction - averaging roughly 1500 to 2000 bytes/vBytes instead of ~141 vBytes due to the mandatory inclusion of size-heavy range proofs (bulletproofs) that hide asset types and amounts.

: null
}

// Squash the fee histogram into fixed feerate ranges
export function squashFeeHistogram(histogram) {
let i = 0
Expand Down
9 changes: 9 additions & 0 deletions client/src/lib/market.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export const getBitcoinPrices = marketChart =>
((marketChart && marketChart.prices) || [])
.map(price => price && price[1])
.filter(Number.isFinite)
Comment on lines +1 to +4

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to add check for array value, and we can simplify code like marketChart && marketChart.prices to marketChart?.prices

export const getBitcoinPrices = marketChart =>
  (Array.isArray(marketChart?.prices) ? marketChart.prices : [])
    .map(price => price && price[1])
    .filter(Number.isFinite)


export const getLatestBitcoinPrice = marketChart => {
const prices = getBitcoinPrices(marketChart)
return prices.length ? prices[prices.length - 1] : null
}
19 changes: 0 additions & 19 deletions client/src/lib/pending-block-details.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,6 @@ const formatTrimmedDecimal = (value) =>
export const formatPanelPercentage = (value, fallback = "N/A") =>
Number.isFinite(value) ? `${formatTrimmedDecimal(value)}%` : fallback;

export const formatFeeRate = (value, fallback = "N/A") =>
Number.isFinite(value) ? `${value.toFixed(2)} sat/vB` : fallback;

export const formatFeeBoundary = (value, fallback = "N/A") =>
Number.isFinite(value) ? value.toFixed(2) : fallback;

Expand All @@ -66,22 +63,6 @@ export const formatMegabytes = (value, fallback = "N/A") =>
export const formatCount = (value, fallback = "N/A") =>
Number.isFinite(value) ? value.toLocaleString() : fallback;

export const getLatestBitcoinPrice = (marketChart) => {
const prices = ((marketChart && marketChart.prices) || [])
.map((price) => price && price[1])
.filter(Number.isFinite);

return prices.length ? prices[prices.length - 1] : null;
};

export const formatUsd = (value, fallback = "N/A") =>
Number.isFinite(value)
? `$${value.toLocaleString("en-US", {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
})} USD`
: fallback;

export const formatTransactionDelta = (delta) =>
delta > 0
? `+ ${formatCount(delta)}`
Expand Down
53 changes: 53 additions & 0 deletions client/src/views/fee-market.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { InfoCard } from "../components/info-card";
import { feeEstimateTargets } from "../const";
import { estimateNativeSegwitTransactionFeeUsd } from "../lib/fees";
import { getLatestBitcoinPrice } from "../lib/market";
import { formatFeeRate, formatUsd } from "./util";

const getFeeMarketLevels = (t) => [
{
key: "low",
title: t`Low`,
tooltip: t`A lower-priority fee rate estimated to confirm within ${feeEstimateTargets.low} blocks.`,
},
{
key: "average",
title: t`Average`,
tooltip: t`A balanced fee rate estimated to confirm within ${feeEstimateTargets.average} blocks.`,
},
{
key: "high",
title: t`High`,
tooltip: t`A higher-priority fee rate estimated to confirm in the next block.`,
},
];

export const feeMarket = ({ feeEst, bitcoinMarketChart, t } = {}) => {
const bitcoinPrice = getLatestBitcoinPrice(bitcoinMarketChart);
const unavailable = t`N/A`;

return (
<div className="fee-market">
<p className="section-title">{t`Fee Market`}</p>
<div className="fee-market-body">
{getFeeMarketLevels(t).map(({ key, title, tooltip }) => {
const feerate = feeEst && feeEst[feeEstimateTargets[key]];
const feeUsd = estimateNativeSegwitTransactionFeeUsd(
bitcoinPrice,
feerate,
);

return (
<InfoCard
className={`fee-market-${key}`}
title={title}
tooltip={tooltip}
value={formatFeeRate(feerate, unavailable)}
footer={formatUsd(feeUsd, unavailable)}
Comment on lines +45 to +46

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can use default fallback instead of unavailable. The same in other places

/>
);
})}
</div>
</div>
);
};
3 changes: 3 additions & 0 deletions client/src/views/home.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { blks } from "./blocks";
import { transactions } from "./transactions";
import { pegInfo } from "./peg-info";
import { overview } from "./overview";
import { feeMarket } from "./fee-market";
import difficultyAdjustment from "./difficulty-adjustment";
import { isBitcoinNetwork } from "../lib/network";
import { showPegData } from "../const";
Expand All @@ -19,12 +20,14 @@ export const dashBoard = ({ t, blocks, dashboardState, loading, ...S }) => {
<div className="home-page" key="dashBoard">
{overview({ blocks: dashblocks, t, ...S })}
{blks(dashblocks, true, { t, ...S })}
{isBitcoinNetwork ? feeMarket({ t, ...S }) : ""}
<div className="dashboard-transaction-section">
{transactions(dashTxs, true, { t, ...S })}
{showPegData
? pegInfo(peg.asset, peg.txs, { t, ...S, error: peg.error })
: ""}
</div>
{!isBitcoinNetwork ? feeMarket({ t, ...S }) : ""}
{isBitcoinNetwork
? difficultyAdjustment({ blocks: dashblocks, ...S })
: ""}
Expand Down
55 changes: 15 additions & 40 deletions client/src/views/overview.js
Original file line number Diff line number Diff line change
@@ -1,47 +1,20 @@
import {
averageNativeSegwitTransactionSize,
satoshisPerBitcoin,
} from "../const";
import { feeEstimateTargets } from "../const";
import { ElapsedTime } from "../components/elapsed-time";
import { InfoCard } from "../components/info-card";
import { MempoolCongestion } from "../components/mempool-congestion";
import { ReferenceLineChart } from "../components/reference-line-chart";
import { estimateNativeSegwitTransactionFeeUsd } from "../lib/fees";
import {
getBitcoinPrices,
getLatestBitcoinPrice,
} from "../lib/market";
import { formatFeeRate, formatUsd } from "./util";

const staticRoot = process.env.STATIC_ROOT || "";

const getBitcoinPrices = (marketChart) =>
((marketChart && marketChart.prices) || [])
.map((price) => price && price[1])
.filter(Number.isFinite);

const getChartPrices = (marketChart) =>
getBitcoinPrices(marketChart).slice(-24);

const formatUsd = (value) =>
Number.isFinite(value)
? `$${value.toLocaleString("en-US", {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
})} USD`
: "";

const formatRecommendedFee = (feeEst) =>
feeEst && Number.isFinite(feeEst[3]) ? `${feeEst[3].toFixed(1)} sat/vB` : "";

const estimateNativeSegwitFeeUsd = (bitcoinPrice, feeEst) =>
Number.isFinite(bitcoinPrice) && feeEst && Number.isFinite(feeEst[3])
? formatUsd(
(bitcoinPrice / satoshisPerBitcoin) *
feeEst[3] *
averageNativeSegwitTransactionSize,
)
: "";

const getLatestPrice = (marketChart) => {
const prices = getBitcoinPrices(marketChart);
return prices.length ? prices[prices.length - 1] : null;
};

export const overview = ({
blocks,
feeEst,
Expand All @@ -51,10 +24,12 @@ export const overview = ({
} = {}) => {
const latestBlock = blocks && blocks[0];
const chartPrices = getChartPrices(bitcoinMarketChart);
const currentBitcoinPrice = getLatestPrice(bitcoinMarketChart);
const recommendedFeeUsd = estimateNativeSegwitFeeUsd(
const currentBitcoinPrice = getLatestBitcoinPrice(bitcoinMarketChart);
const recommendedFeerate =
feeEst && feeEst[feeEstimateTargets.average];
const recommendedFeeUsd = estimateNativeSegwitTransactionFeeUsd(
currentBitcoinPrice,
feeEst,
recommendedFeerate,
);
return (
<div className="overview">
Expand All @@ -78,8 +53,8 @@ export const overview = ({
<InfoCard
title={t`Recommended Fee`}
tooltip={t`Suggested rate (sat/vB) to confirm in the next block or two.`}
value={formatRecommendedFee(feeEst)}
footer={recommendedFeeUsd}
value={formatFeeRate(recommendedFeerate, t`N/A`)}
footer={formatUsd(recommendedFeeUsd, t`N/A`)}
/>

<InfoCard
Expand All @@ -96,7 +71,7 @@ export const overview = ({
<InfoCard
title={t`Bitcoin`}
iconSrc={`${staticRoot}img/icons/Bitcoin-menu-logo.svg`}
headerValue={formatUsd(currentBitcoinPrice)}
headerValue={formatUsd(currentBitcoinPrice, t`N/A`)}
body={
<ReferenceLineChart
className="overview-bitcoin-price-chart"
Expand Down
6 changes: 2 additions & 4 deletions client/src/views/pending-block-details-card.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,16 +19,14 @@ import { getFeeTierBoundaries } from "../lib/fees";
import {
formatCount,
formatFeeBoundary,
formatFeeRate,
formatMegabytes,
formatPanelPercentage,
formatPercentage,
formatTransactionDelta,
formatUsd,
formatWeight,
getLatestBitcoinPrice,
} from "../lib/pending-block-details";
import { formatSat, formatVMB } from "./util";
import { getLatestBitcoinPrice } from "../lib/market";
import { formatFeeRate, formatSat, formatUsd, formatVMB } from "./util";

// Fee estimates are inclusive upper bounds, while the grid expects inclusive
// lower thresholds. Move just beyond a boundary so an equal rate stays in the
Expand Down
14 changes: 14 additions & 0 deletions client/src/views/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,20 @@ export const formatTime = (unix, with_tz = true) => {

export const formatSat = (sats, label=nativeAssetLabel) => `${formatNumber(sat2btc(sats), NATIVE_PRECISION)} ${label}`

const formatTrimmedDecimal = value =>
value.toFixed(2).replace(/\.?0+$/, '')

export const formatFeeRate = (feerate, fallback='N/A') =>
Number.isFinite(feerate) ? `${formatTrimmedDecimal(feerate)} sat/vB` : fallback

export const formatUsd = (value, fallback='N/A') =>
Number.isFinite(value)
? `$${value.toLocaleString('en-US', {
minimumFractionDigits: 2,
maximumFractionDigits: 2,
})} USD`
: fallback

export const formatAssetAmount = (value, precision=0, t) =>
<span>
{formatNumber(precision > 0 ? moveDec(value, -precision) : value, precision)}
Expand Down
7 changes: 7 additions & 0 deletions lang/strings.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
A balanced fee rate estimated to confirm within %s blocks.
A higher-priority fee rate estimated to confirm in the next block.
A lower-priority fee rate estimated to confirm within %s blocks.
Address
Address reuse
Address: %s
Expand All @@ -10,6 +13,7 @@ Asset ID
Asset name
Asset: %s
Assets vs Liabilities
Average
Bits
BLOCK
Block Challenge
Expand Down Expand Up @@ -55,9 +59,11 @@ Esplora is currently unavailable, please try again later.
ETA
Estimated time until the peg transaction is confirmed.
Fee
Fee Market
Federation BTC Holdings
Go
Height
High
How full the block is.
In block
In best chain
Expand All @@ -81,6 +87,7 @@ Linked domain
Loading block...
Loading...
Load more
Low
Lock time
ltr
Mempool
Expand Down
Loading