GET /tge/metrics
Response now includes ft_burns with per-chain cumulative totals and cross-chain total (chain_id: 0).
GET /tge/metrics/series?metric=ft_burns&period=all
GET /tge/metrics/series?metric=ft_burns&period=1m&chainId=146
Each point's value is cumulative burned FT (18 decimals). Human readable: value / 10^decimals.
Computed client-side from existing ft_allocation and collateral_supply series (no new endpoint needed).
import { Badge } from '../../../../../packages/ft-theme/src/ui/dashboards/Badge';
import { useMemo, useState } from 'react';
import { Dropdown, type DropdownOption } from '@ft-theme/ui/controls/Dropdown';
import { LineChart, type LineChartSeries } from '@ft-theme/ui/charts';
import { usePutDashboard } from '@ft-sdk/hooks';
import { useTranslations } from 'next-intl';
type PeriodType = 7 | 30 | 90 | 180 | 360;
const getPeriodOptions = (
t: ReturnType<typeof useTranslations>
): DropdownOption<PeriodType>[] => [
{ value: 7, label: t('1week') },
{ value: 30, label: t('1month') },
{ value: 90, label: t('3months') },
{ value: 180, label: t('6months') },
{ value: 360, label: t('1year') },
];
const formatAxisValue = (value: number): string => {
if (value >= 1_000_000) return `${(value / 1_000_000).toFixed(1)}M`;
if (value >= 1_000) return `${(value / 1_000).toFixed(1)}K`;
return value.toFixed(0);
};
const formatTooltipValue = (value: number): string => {
if (value >= 1_000_000) return `${(value / 1_000_000).toFixed(2)}M FT`;
if (value >= 1_000) return `${(value / 1_000).toFixed(2)}K FT`;
return `${value.toFixed(2)} FT`;
};
export const BurnChart = () => {
const t = useTranslations('dashboard.burnChart');
const tPeriods = useTranslations('dashboard.periods');
const ALL_PERIOD_OPTIONS = useMemo(() => getPeriodOptions(tPeriods), [tPeriods]);
const [selectedPeriod, setSelectedPeriod] = useState<DropdownOption<PeriodType>>({
value: 30,
label: tPeriods('1month'),
});
// TODO: fetch from /tge/metrics/series?metric=ft_burns&period=all
// For now, reuse usePutDashboard if burn series is added to the dashboard response,
// or create a dedicated hook (see useBurnSeries below).
const { fullData, fullDataLoading } = usePutDashboard(undefined, {
includeFullData: true,
});
const chainId = Number(process.env.NEXT_PUBLIC_RAISE_CHAIN_ID);
const dashboardData = fullData?.chains?.find(chain => chain.chainId === chainId);
// Once BE adds burn series to the dashboard response:
// const burnValues = dashboardData?.metrics?.series?.cumulativeBurnValues;
// For now, use a dedicated fetch (see hook below).
const chartData = useMemo((): LineChartSeries[] => {
// Replace with actual burn data once wired up
const burnValues: { date: string; valueUsd: string }[] = []; // from hook
if (!burnValues?.length) return [];
const now = new Date();
const cutoffDate = new Date(now);
cutoffDate.setDate(cutoffDate.getDate() - selectedPeriod.value);
const cutoffDateStr = cutoffDate.toISOString().split('T')[0];
const filtered = burnValues
.filter(p => p.date >= cutoffDateStr)
.sort((a, b) => a.date.localeCompare(b.date));
if (filtered.length === 0) return [];
return [
{
id: 'Cumulative FT Burned',
data: filtered.map(p => ({
x: p.date,
y: Number(p.valueUsd) / 1e18, // 18 decimals
})),
},
];
}, [selectedPeriod.value]);
return (
<div>
<div className="flex items-start lg:items-center justify-between mb-4">
<Badge label="Cumulative FT Burned" color="palette-other-error-default" />
<Dropdown
options={ALL_PERIOD_OPTIONS}
selectedValue={selectedPeriod}
onSelect={setSelectedPeriod}
variant="neutral"
disabled={fullDataLoading}
/>
</div>
<LineChart
key={selectedPeriod.value}
data={chartData}
height={256}
isLoading={fullDataLoading}
formatAxisValue={formatAxisValue}
formatTooltipValue={formatTooltipValue}
emptyMessage="No burn data available"
/>
</div>
);
};import { useQuery } from '@tanstack/react-query';
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:8080';
interface SeriesPoint {
timestamp: string;
value: string;
decimals: number;
}
export function useBurnSeries(period = 'all', chainId?: number) {
return useQuery({
queryKey: ['ft-burns-series', period, chainId],
queryFn: async () => {
const params = new URLSearchParams({ metric: 'ft_burns', period });
if (chainId) params.set('chainId', String(chainId));
const res = await fetch(`${API_URL}/tge/metrics/series?${params}`);
const data = await res.json();
return ((data.points ?? []) as SeriesPoint[]).map(p => ({
date: p.timestamp.split('T')[0],
valueUsd: p.value, // raw 18-decimal string
decimals: p.decimals,
}));
},
staleTime: 15_000,
refetchInterval: 30_000,
});
}This uses the existing ftPutValues and collateralPutValues series already fetched by usePutDashboard. No new API call needed.
export const RatioChart = () => {
const t = useTranslations('dashboard.ratioChart');
const tPeriods = useTranslations('dashboard.periods');
const ALL_PERIOD_OPTIONS = useMemo(() => getPeriodOptions(tPeriods), [tPeriods]);
const [selectedPeriod, setSelectedPeriod] = useState<DropdownOption<PeriodType>>({
value: 30,
label: tPeriods('1month'),
});
const { fullData, fullDataLoading } = usePutDashboard(undefined, {
includeFullData: true,
});
const chainId = Number(process.env.NEXT_PUBLIC_RAISE_CHAIN_ID);
const dashboardData = fullData?.chains?.find(chain => chain.chainId === chainId);
const series = dashboardData?.metrics?.series;
const chartData = useMemo((): LineChartSeries[] => {
const ftPutValues = series?.ftPutValues;
const collateralPutValues = series?.collateralPutValues;
if (!ftPutValues?.length || !collateralPutValues?.length) return [];
const now = new Date();
const cutoffDate = new Date(now);
cutoffDate.setDate(cutoffDate.getDate() - selectedPeriod.value);
const cutoffDateStr = cutoffDate.toISOString().split('T')[0];
const ftMap = new Map(
ftPutValues
.filter(p => p.date >= cutoffDateStr)
.map(p => [p.date, Number(p.valueUsd) / 1e8])
);
const collateralMap = new Map(
collateralPutValues
.filter(p => p.date >= cutoffDateStr)
.map(p => [p.date, Number(p.valueUsd)])
);
const commonDates = [...ftMap.keys()]
.filter(date => collateralMap.has(date))
.sort();
if (commonDates.length === 0) return [];
return [
{
id: 'FT / Capital Ratio',
data: commonDates.map(date => ({
x: date,
y: ftMap.get(date)! / collateralMap.get(date)!,
})),
},
];
}, [series, selectedPeriod.value]);
const formatRatioAxis = (value: number) => value.toFixed(2);
const formatRatioTooltip = (value: number) => `${value.toFixed(4)}x`;
return (
<div>
<div className="flex items-start lg:items-center justify-between mb-4">
<Badge label="FT Value / Investment Value" color="palette-other-information-default" />
<Dropdown
options={ALL_PERIOD_OPTIONS}
selectedValue={selectedPeriod}
onSelect={setSelectedPeriod}
variant="neutral"
disabled={fullDataLoading}
/>
</div>
<LineChart
key={selectedPeriod.value}
data={chartData}
height={256}
isLoading={fullDataLoading}
formatAxisValue={formatRatioAxis}
formatTooltipValue={formatRatioTooltip}
emptyMessage="No ratio data available"
/>
</div>
);
};Dev: Sonic (146), BNB (56). Prod: all chains (1, 56, 146, 8453, 43114).