Skip to content

Instantly share code, notes, and snippets.

@patcito
Last active March 18, 2026 22:49
Show Gist options
  • Select an option

  • Save patcito/6909eca82cccc01dfdbf7dc2c9c4dea3 to your computer and use it in GitHub Desktop.

Select an option

Save patcito/6909eca82cccc01dfdbf7dc2c9c4dea3 to your computer and use it in GitHub Desktop.
FT Burn Tracking - New/Modified Endpoints for PUT Dashboard

PUT Dashboard - Burn Chart & FT/Capital Ratio

API Endpoints

Burn totals (in existing /tge/metrics response)

GET /tge/metrics

Response now includes ft_burns with per-chain cumulative totals and cross-chain total (chain_id: 0).

Burn time series

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.

FT/Capital ratio

Computed client-side from existing ft_allocation and collateral_supply series (no new endpoint needed).


Components (matching existing OverviewLineChart pattern)

BurnChart.tsx

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

Dedicated hook: useBurnSeries.ts

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

FT/Capital Ratio chart (same pattern as OverviewLineChart)

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

Supported chains

Dev: Sonic (146), BNB (56). Prod: all chains (1, 56, 146, 8453, 43114).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment