Financial reporting
Financial reporting components — cost, EBITDA, energy balance, hash balance, and subsidy fee views
@tetherto/mdk-react-devkit/foundation
Financial reporting components cover cost summary, EBITDA, energy balance, hash balance, and subsidy fee views. Each is a self-contained reporting page composite — supply your API data and a <TimeframeControls> period selector; the component handles layout, charts, and metric tiles.
For operational reporting see Operational. For shared controls see the Reporting tool overview.
Prerequisites
- Complete the @tetherto/mdk-react-devkit installation and add the dependency
<MdkProvider>from@tetherto/mdk-react-adapter
Components
| Component | Description |
|---|---|
Cost | Cost summary reporting page with chart and metric tiles |
Ebitda | EBITDA reporting view with charts and metrics |
EnergyBalance | Energy balance reporting view with cost and revenue tabs |
HashBalance | Hash balance reporting view |
SubsidyFee | Subsidy fee bar chart with single-stat metric |
Cost
Single-site cost summary composite: page header, period selector slot, and a 2×2 grid of cost charts and metric tiles.
Import
import { Cost, buildCostSummaryViewModel } from '@tetherto/mdk-react-devkit/foundation'
import type { CostProps, CostSummaryResponse, FinancialDateRange } from '@tetherto/mdk-react-devkit/foundation'Props
| Prop | Status | Type | Default | Description |
|---|---|---|---|---|
metrics | Required | CostSummaryDisplayMetrics | null | none | Derived metric tiles from buildCostSummaryViewModel |
costLog | Required | ReadonlyArray<CostTimeSeriesEntry> | none | Monthly cost time-series for the bar charts |
btcPriceLog | Required | ReadonlyArray<BtcPriceTimeSeriesEntry> | none | BTC price overlay series |
totals | Required | CostSummaryMonetaryTotals | null | none | Aggregate totals for the summary tile |
dateRange | Required | FinancialDateRange | null | none | Active period — drives chart x-axis |
avgAllInCostData | Optional | ReadonlyArray<AvgAllInCostDataPoint> | none | Revenue vs cost series for the Avg All-in Cost panel |
controls | Required | ReactElement | none | Period selector slot — pass <TimeframeControls> |
setCostAction | Optional | ReactElement | none | Optional header action slot (e.g. a "Set Monthly Cost" link) |
isLoading | Optional | boolean | false | Show a loading state |
error | Optional | unknown | none | Show an error state when defined |
Data preparation
Use buildCostSummaryViewModel to transform a raw API response into the props the component expects:
import { buildCostSummaryViewModel } from '@tetherto/mdk-react-devkit/foundation'
import type { CostSummaryResponse } from '@tetherto/mdk-react-devkit/foundation'
const viewModel = buildCostSummaryViewModel({ data: apiResponse })
// → { metrics, costLog, btcPriceLog, totals }FinancialDateRange type
type FinancialDateRange = {
start: number // epoch ms
end: number // epoch ms
period?: 'daily' | 'weekly' | 'monthly' | 'yearly'
}Basic usage
import { useState, useMemo } from 'react'
import {
Cost,
buildCostSummaryViewModel,
TimeframeControls,
PERIOD,
} from '@tetherto/mdk-react-devkit/foundation'
import type { FinancialDateRange } from '@tetherto/mdk-react-devkit/foundation'
import { startOfMonth, endOfMonth } from 'date-fns'
export const CostPage = ({ apiResponse }: { apiResponse: CostSummaryResponse }) => {
const [dateRange, setDateRange] = useState<FinancialDateRange>({
start: startOfMonth(new Date()).getTime(),
end: endOfMonth(new Date()).getTime(),
period: PERIOD.MONTHLY,
})
const viewModel = useMemo(
() => buildCostSummaryViewModel({ data: apiResponse }),
[apiResponse],
)
return (
<Cost
{...viewModel}
dateRange={dateRange}
controls={
<TimeframeControls
isMonthSelectVisible
isWeekSelectVisible={false}
dateRange={{ start: dateRange.start, end: dateRange.end }}
onRangeChange={(range, opts) =>
setDateRange({ start: range[0].getTime(), end: range[1].getTime(), period: opts.period })
}
/>
}
/>
)
}Behaviour
Renders a "Cost Summary" page header, the controls slot (period selector), and the CostContent 2×2 grid of charts and metric tiles (production cost chart, operations energy chart, avg all-in cost chart, and cost metric cards).
Styling
.mdk-cost: Root element.mdk-cost__header: Title and optional action row.mdk-cost__page-title: "Cost Summary" heading.mdk-cost__controls: Controls slot wrapper
Ebitda
Renders EBITDA charts and metric tiles from a display-metrics view model. Accepts bar chart data for revenue, cost, and EBITDA series.
Import
import { Ebitda } from '@tetherto/mdk-react-devkit/foundation'
import type { EbitdaProps } from '@tetherto/mdk-react-devkit/foundation'Props
| Prop | Status | Type | Default | Description |
|---|---|---|---|---|
metrics | Required | CostSummaryDisplayMetrics | null | none | Derived metric tiles from buildCostSummaryViewModel |
ebitdaChartInput | Required | ToBarChartDataInput | null | none | EBITDA bar chart series |
btcProducedChartInput | Required | ToBarChartDataInput | null | none | BTC produced bar chart series |
hasBtcProducedAllZeros | Required | boolean | none | Hides the BTC produced series when all values are zero |
showEbitdaBarChart | Required | boolean | none | Toggle the EBITDA bar chart panel |
currentBTCPrice | Required | number | none | Live BTC price used in metric calculations |
datePicker | Required | ReactElement | none | Period selector slot |
hasDateSelection | Required | boolean | none | When false shows a "select a period" hint instead of empty data |
isLoading | Optional | boolean | false | Show a loading state |
errors | Optional | string[] | [] | Error messages displayed as an alert |
setCostHref | Optional | string | none | When provided, shows a "Set Monthly Cost" gear link in the header |
Basic usage
import { Ebitda } from '@tetherto/mdk-react-devkit/foundation'
<Ebitda
metrics={ebitdaMetrics}
ebitdaChartInput={ebitdaChartData}
btcProducedChartInput={btcProducedData}
hasBtcProducedAllZeros={false}
showEbitdaBarChart={true}
currentBTCPrice={65000}
datePicker={<TimeframeControls {...timeframeProps} />}
hasDateSelection={true}
/>Behaviour
Renders an "EBITDA" page header, period selector slot, and (when hasDateSelection is true and data is available) the EbitdaMetrics card grid and EbitdaCharts panels. Shows a "please select a period" hint when hasDateSelection is false. A full-page spinner appears while isLoading is true.
Styling
.mdk-ebitda: Root element.mdk-ebitda__header: Title and optional action row.mdk-ebitda__page-title: "EBITDA" heading.mdk-ebitda__header-actions: "Set Monthly Cost" link wrapper.mdk-ebitda__controls: Date picker slot.mdk-ebitda__loading: Spinner overlay.mdk-ebitda__error: Error / empty state container
EnergyBalance
Tabbed reporting surface showing energy cost and energy revenue balance with bar charts and period-level metric tiles.
Import
import { EnergyBalance } from '@tetherto/mdk-react-devkit/foundation'
import type { EnergyBalanceProps, EnergyBalanceViewModel } from '@tetherto/mdk-react-devkit/foundation'Props
| Prop | Status | Type | Default | Description |
|---|---|---|---|---|
viewModel | Required | EnergyBalanceViewModel | none | Full view-model from useEnergyBalance hook |
onTabChange | Required | function | none | Called when the Revenue/Cost tab changes |
onRevenueDisplayModeChange | Required | function | none | Called when the revenue chart display mode toggles |
onCostDisplayModeChange | Required | function | none | Called when the cost chart display mode toggles |
timeframeControls | Optional | ReactNode | none | Period selector slot rendered above the tabs |
setCostHref | Optional | string | none | When provided, shows a "Set Monthly Cost" gear link in the header |
isDemoMode | Optional | boolean | false | Suppresses error alerts (for demo/storybook use) |
EnergyBalanceTab type
type EnergyBalanceTab = 'revenue' | 'cost'Basic usage
import { EnergyBalance, TimeframeControls } from '@tetherto/mdk-react-devkit/foundation'
import { useEnergyBalance } from '@tetherto/mdk-react-devkit/foundation'
export const EnergyBalancePage = () => {
const viewModel = useEnergyBalance({ data: apiData, dateRange })
return (
<EnergyBalance
viewModel={viewModel}
onTabChange={(tab) => setActiveTab(tab)}
onRevenueDisplayModeChange={(mode) => setRevenueMode(mode)}
onCostDisplayModeChange={(mode) => setCostMode(mode)}
timeframeControls={<TimeframeControls {...timeframeProps} />}
/>
)
}Behaviour
Renders an "Energy Balance" page header, optional timeframe controls, and a two-tab layout:
- Energy Revenue — revenue metrics cards, revenue bar chart, downtime chart, power chart
- Energy Cost — cost metrics cards, cost bar chart, power chart
Empty and no-selection states are handled internally. A loading spinner overlays the view while data fetches.
Styling
.mdk-energy-balance: Root element.mdk-energy-balance__header: Title and optional action row.mdk-energy-balance__controls: Timeframe controls slot.mdk-energy-balance__tabs: Tab container.mdk-energy-balance__tabs-list: Tab switcher row.mdk-energy-balance__tabs-trigger: Individual tab button.mdk-energy-balance__tabs-content: Tab panel content area.mdk-energy-balance__loading: Spinner overlay.mdk-energy-balance__error: Error / empty state
HashBalance
Reporting composite for hash-rate balance across the pool, showing contribution, theoretical, and accepted hash metrics.
Import
import { HashBalance } from '@tetherto/mdk-react-devkit/foundation'
import type { HashBalanceProps, HashRevenueResponse } from '@tetherto/mdk-react-devkit/foundation'Props
All props are optional (the component renders an empty/loading state when omitted).
| Prop | Status | Type | Default | Description |
|---|---|---|---|---|
data | Optional | HashRevenueResponse | null | — | Hash revenue API response |
isLoading | Optional | boolean | false | Show a loading state |
isError | Optional | boolean | false | Show an error alert |
errorMessage | Optional | string | 'Error loading hash balance data…' | Override error message text |
initialDateRange | Optional | FinancialDateRange | current month | Starting period for the integrated timeframe controls |
onDateRangeChange | Optional | function | — | Fires when the user changes the period |
className | Optional | string | — | Root class override |
tabsClassName | Optional | string | — | Class on the tab container |
tabsListClassName | Optional | string | — | Class on the tab list |
Basic usage
import { HashBalance } from '@tetherto/mdk-react-devkit/foundation'
<HashBalance
data={hashRevenueData}
isLoading={isLoading}
onDateRangeChange={(range, query) => fetchHashBalance(query)}
/>Behaviour
Renders integrated TimeframeControls (year/month/week) with a reset button, and a two-tab layout:
- Hash Revenue — site hash revenue, network hashrate, and network hashprice charts with metric cards; includes a currency toggle (USD / BTC)
- Hash Cost — hash cost vs revenue comparison charts
Date range is managed internally unless initialDateRange is supplied; onDateRangeChange fires on every period change.
Styling
.mdk-hash-balance: Root element.mdk-hash-balance__loading: Spinner overlay.mdk-hash-balance__error: Error alert.mdk-hash-balance__tabs: Tab container.mdk-hash-balance__tabs-list: Tab row.mdk-hash-balance__tabs-trigger: Individual tab button.mdk-hash-balance__tabs-content: Tab panel
SubsidyFee
Bar chart of subsidy fees over a configurable time range alongside an average-fee metric tile.
Import
import { SubsidyFee } from '@tetherto/mdk-react-devkit/foundation'
import type { SubsidyFeesResponse } from '@tetherto/mdk-react-devkit/foundation'Props
All props are optional.
| Prop | Status | Type | Default | Description |
|---|---|---|---|---|
data | Optional | HashRevenueResponse | null | — | Hash revenue API response |
log | Optional | SubsidyFeesLogEntry[] | — | Per-block log entries (alternative to data) |
isLoading | Optional | boolean | false | Show a loading state |
isError | Optional | boolean | false | Show an error alert |
errorMessage | Optional | string | 'Error loading hash balance data…' | Override error message text |
showSummaryCards | Optional | boolean | false | Show Total Subsidy, Total Fees, and Average Fees stat cards above the charts |
onDateRangeChange | Optional | function | — | Fires when the user changes the period |
Basic usage
import { SubsidyFee } from '@tetherto/mdk-react-devkit/foundation'
<SubsidyFee
data={subsidyFeesData}
isLoading={isLoading}
showSummaryCards={true}
onDateRangeChange={(range, query) => fetchSubsidyFees(query)}
/>Behaviour
Renders integrated TimeframeControls (year/month/week) and two chart panels side by side:
- Subsidy/Fees — stacked bar chart of mining reward breakdown (subsidy vs transaction fees) with a % fee ratio overlay on the right y-axis
- Average Fees — average fees in sats/vbyte bar chart
When showSummaryCards is true, three SingleStatCard tiles (Total Subsidy, Total Fees, Average Fees) appear above the charts.
Styling
.mdk-subsidy-fee__panel: Each chart panel wrapper.mdk-subsidy-fee__panel--primary: Primary variant