Реалізація DeFi-дашборда на сайті

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.

Розробка та обслуговування будь-яких видів сайтів:

Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Реалізація DeFi-дашборда на сайті
Складна
~2-4 тижні
Часті питання

Наші компетенції:

Етапи розробки

Останні роботи

  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Реалізація DeFi-дашборду на веб-сайті

DeFi-дашборд агрегує дані про позиції користувача в кількох протоколах: депозити в Aave, LP-позиції в Uniswap, стейкинг в Curve, баланс гаманця. Все на одному екрані з оновленням у реальному часі, дохідністю та PnL.

Складність не в окремих віджетах, а в інтеграції: кожен протокол — своє API або набір контрактів, різні формати даних, різні мережі. Плюс цінові дані для конвертації в USD.

Архітектура дашборду

defi-dashboard/
├── lib/
│   ├── protocols/
│   │   ├── aave.ts        # Subgraph / REST API
│   │   ├── uniswap-v3.ts  # Subgraph
│   │   └── curve.ts       # Curve API
│   ├── prices.ts          # CoinGecko / DeFiLlama ціни
│   └── multicall.ts       # Батчинг on-chain запитів
├── hooks/
│   ├── usePortfolio.ts    # Агрегування всіх позицій
│   ├── usePrices.ts       # Актуальні ціни токенів
│   └── useNetWorth.ts     # USD-вартість портфеля
└── components/
    ├── NetWorthCard/
    ├── PositionsList/
    ├── ProtocolCard/
    └── AllocationChart/

Отримання цінових даних

// lib/prices.ts
const COINGECKO_BASE = 'https://api.coingecko.com/api/v3';

// Кеш цін — оновлюємо раз на хвилину, не на кожний рендер
const priceCache = new Map<string, { price: number; ts: number }>();
const CACHE_TTL = 60_000;

export async function getTokenPrices(
  tokenIds: string[],
  vsCurrency = 'usd',
): Promise<Record<string, number>> {
  const stale = tokenIds.filter(id => {
    const cached = priceCache.get(id);
    return !cached || Date.now() - cached.ts > CACHE_TTL;
  });

  if (stale.length > 0) {
    const res = await fetch(
      `${COINGECKO_BASE}/simple/price?ids=${stale.join(',')}&vs_currencies=${vsCurrency}`,
    );
    const data = await res.json();
    for (const [id, prices] of Object.entries(data)) {
      priceCache.set(id, { price: (prices as Record<string, number>)[vsCurrency], ts: Date.now() });
    }
  }

  return Object.fromEntries(
    tokenIds.map(id => [id, priceCache.get(id)?.price ?? 0]),
  );
}

Aave v3 позиції через Subgraph

// lib/protocols/aave.ts
const AAVE_SUBGRAPH = 'https://api.thegraph.com/subgraphs/name/aave/protocol-v3';

interface AavePosition {
  reserve: { symbol: string; underlyingAsset: string; decimals: number };
  currentATokenBalance: string;
  currentVariableDebt: string;
  currentStableDebt: string;
  reserveLiquidationThreshold: string;
}

export async function getAavePositions(userAddress: string): Promise<AavePosition[]> {
  const query = `{
    userReserves(
      where: { user: "${userAddress.toLowerCase()}", currentATokenBalance_gt: "0" }
    ) {
      reserve { symbol underlyingAsset decimals liquidityRate }
      currentATokenBalance
      currentVariableDebt
      currentStableDebt
    }
  }`;

  const res = await fetch(AAVE_SUBGRAPH, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ query }),
    next: { revalidate: 30 },
  });

  const { data } = await res.json();
  return data.userReserves;
}

Uniswap v3 LP позиції

Uniswap v3 позиції — NFT (ERC-721 в NonfungiblePositionManager). Отримуємо через Subgraph або Uniswap API:

// lib/protocols/uniswap-v3.ts
const UNISWAP_SUBGRAPH = 'https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v3';

export async function getUniswapPositions(ownerAddress: string) {
  const query = `{
    positions(
      where: { owner: "${ownerAddress.toLowerCase()}", liquidity_gt: "0" }
      orderBy: id
      orderDirection: desc
      first: 20
    ) {
      id
      liquidity
      token0 { symbol decimals }
      token1 { symbol decimals }
      pool { feeTier sqrtPrice tick token0Price token1Price }
      depositedToken0
      depositedToken1
      collectedFeesToken0
      collectedFeesToken1
      tickLower { tickIdx }
      tickUpper { tickIdx }
    }
  }`;

  const res = await fetch(UNISWAP_SUBGRAPH, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ query }),
  });

  const { data } = await res.json();
  return data.positions;
}

Агрегування у hook'і

// hooks/usePortfolio.ts
import { useQuery } from '@tanstack/react-query';
import { useAccount } from 'wagmi';
import { getAavePositions } from '@/lib/protocols/aave';
import { getUniswapPositions } from '@/lib/protocols/uniswap-v3';
import { getTokenPrices } from '@/lib/prices';

export function usePortfolio() {
  const { address } = useAccount();

  return useQuery({
    queryKey: ['portfolio', address],
    queryFn: async () => {
      if (!address) return null;

      const [aavePositions, uniswapPositions] = await Promise.all([
        getAavePositions(address),
        getUniswapPositions(address),
      ]);

      const tokenIds = [
        ...new Set([
          ...aavePositions.map(p => p.reserve.symbol.toLowerCase()),
          'ethereum',
        ]),
      ];

      const prices = await getTokenPrices(tokenIds);

      const aaveUSD = aavePositions.reduce((sum, pos) => {
        const balance = Number(pos.currentATokenBalance) / 10 ** pos.reserve.decimals;
        const price = prices[pos.reserve.symbol.toLowerCase()] ?? 0;
        return sum + balance * price;
      }, 0);

      return {
        aavePositions,
        uniswapPositions,
        aaveUSD,
        prices,
      };
    },
    enabled: !!address,
    refetchInterval: 60_000,
  });
}

Компоненти дашборду

// components/NetWorthCard.tsx
export function NetWorthCard() {
  const { data, isLoading } = usePortfolio();

  if (isLoading) return <Skeleton className="h-32" />;

  const total = (data?.aaveUSD ?? 0) + (data?.uniswapUSD ?? 0);

  return (
    <div className="rounded-2xl border border-white/10 bg-gradient-to-br from-neutral-900 to-neutral-800 p-6">
      <p className="text-sm text-neutral-400">Чиста вартість</p>
      <p className="mt-1 text-4xl font-bold">
        ${total.toLocaleString('en-US', { maximumFractionDigits: 2 })}
      </p>
      <div className="mt-4 flex gap-6 text-sm">
        <Metric label="Aave" value={data?.aaveUSD} />
        <Metric label="Uniswap LP" value={data?.uniswapUSD} />
      </div>
    </div>
  );
}

Оновлення даних у реальному часі

Для дашборду потрібні актуальні дані без ручного оновлення сторінки:

// Ціни — кожну хвилину через react-query refetchInterval
// On-chain дані — кожні 12 секунд (один блок Ethereum)
// Subgraph дані — кожні 30 секунд (затримка індексації ~15s)

const { data } = useQuery({
  queryKey: ['aavePositions', address],
  queryFn: () => getAavePositions(address!),
  refetchInterval: 30_000,
  staleTime: 15_000,
});

Часові рамки: дашборд з одним протоколом (наприклад, тільки Aave), цінами та USD-оцінкою — 3–4 дні. Мультипротокольний дашборд з 3–4 протоколами, allocation chart, історією — 1,5–2 тижні.