Разработка дашборда статуса airdrop-eligibility

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1 услугВсе 1306 услуг
Разработка дашборда статуса airdrop-eligibility
Средняя
~3-5 рабочих дней
Часто задаваемые вопросы
Направления блокчейн-разработки
Этапы блокчейн-разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1258
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1170
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    873
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1092
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    563
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    830

Разработка дашборда статуса airdrop-eligibility

Airdrop дашборд — это не просто «проверь, попал ли ты». Хорошо сделанный дашборд показывает пользователю конкретно что ему не хватает, сколько баллов у него есть, какие действия ещё можно совершить до снапшота. Это retention-инструмент, который мотивирует пользователей активнее взаимодействовать с протоколом. Технически — это агрегатор данных из нескольких on-chain источников с кэшированием, потому что реальный on-chain запрос для каждого адреса при каждом посещении убьёт любой RPC.

Архитектура: источники данных

The Graph subgraph

Основной источник для on-chain активности. Subgraph индексирует события контракта и предоставляет GraphQL API. Данные для дашборда:

query UserActivity($address: String!) {
  user(id: $address) {
    totalVolume
    transactionCount
    firstInteractionTimestamp
    liquidityProvisions {
      amount
      timestamp
      pool { id symbol }
    }
    referrals { count totalVolume }
  }
}

Запросы в The Graph — бесплатны до лимита, быстры (< 200ms), не нагружают RPC ноды.

Snapshot.org API

Для tracking governance participation:

const SNAPSHOT_QUERY = `
  query Votes($voter: String!, $space: String!) {
    votes(where: { voter: $voter, space: $space }) {
      id
      proposal { id title }
      created
    }
  }
`

async function getSnapshotVotes(address: string, spaceId: string) {
  const res = await fetch('https://hub.snapshot.org/graphql', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ query: SNAPSHOT_QUERY, variables: { voter: address.toLowerCase(), space: spaceId } })
  })
  return res.json()
}

On-chain balance checks

Для прямых балансов — viem multicall батчит несколько вызовов в один RPC запрос:

import { createPublicClient, http } from 'viem'
import { mainnet } from 'viem/chains'

const client = createPublicClient({ chain: mainnet, transport: http() })

const results = await client.multicall({
  contracts: [
    { address: TOKEN_ADDRESS, abi: erc20Abi, functionName: 'balanceOf', args: [userAddress] },
    { address: STAKING_ADDRESS, abi: stakingAbi, functionName: 'stakedAmount', args: [userAddress] },
    { address: VESTING_ADDRESS, abi: vestingAbi, functionName: 'vestingInfo', args: [userAddress] },
  ]
})

Один HTTP запрос вместо трёх — критично при высоком трафике.

Система баллов и критерии

Eligibility обычно многофакторная. Типичная структура:

interface EligibilityScore {
  total: number
  breakdown: {
    volumeScore: number      // 0-40 баллов: торговый объём
    loyaltyScore: number     // 0-20 баллов: дата первого взаимодействия
    governanceScore: number  // 0-20 баллов: голосования на Snapshot
    referralScore: number    // 0-10 баллов: приведённые пользователи
    holdingScore: number     // 0-10 баллов: удержание токенов
  }
  tier: 'bronze' | 'silver' | 'gold' | 'platinum'
  estimatedAllocation: bigint | null // null до объявления
  missingCriteria: string[]
}

missingCriteria — самая полезная часть для пользователя. «Вам не хватает 2 голосований на Snapshot и $500 объёма для следующего тира» — это actionable информация.

Backend: кэширование и API

On-chain данные не меняются каждую секунду — кэшировать обязательно.

// Redis кэш с TTL
async function getUserEligibility(address: string): Promise<EligibilityScore> {
  const cacheKey = `eligibility:${address.toLowerCase()}`
  const cached = await redis.get(cacheKey)
  if (cached) return JSON.parse(cached)

  // Параллельный fetch из всех источников
  const [subgraphData, snapshotVotes, onchainBalances] = await Promise.all([
    fetchSubgraphData(address),
    fetchSnapshotVotes(address),
    fetchOnchainBalances(address),
  ])

  const score = calculateScore(subgraphData, snapshotVotes, onchainBalances)
  await redis.setex(cacheKey, 300, JSON.stringify(score)) // 5 минут TTL
  return score
}

TTL 5 минут для большинства данных достаточно. Для данных снапшота после дедлайна — кэш навсегда (данные frozen).

Frontend: React дашборд

function EligibilityDashboard() {
  const { address } = useAccount()
  const { data, isLoading } = useQuery({
    queryKey: ['eligibility', address],
    queryFn: () => fetchEligibility(address!),
    enabled: !!address,
    staleTime: 60_000, // минута
  })

  if (!address) return <ConnectWalletPrompt />
  if (isLoading) return <SkeletonDashboard />

  return (
    <div className="grid grid-cols-2 gap-4">
      <ScoreCard score={data.total} tier={data.tier} />
      <BreakdownChart breakdown={data.breakdown} />
      <MissingCriteriaList items={data.missingCriteria} />
      <EstimatedAllocation amount={data.estimatedAllocation} />
    </div>
  )
}

Прогресс-бары и gamification

Tier прогресс-бар мотивирует пользователя дойти до следующего уровня:

function TierProgress({ current, next, score }: Props) {
  const progress = ((score - current.minScore) / (next.minScore - current.minScore)) * 100

  return (
    <div>
      <div className="flex justify-between text-sm">
        <span>{current.name}: {score} pts</span>
        <span>{next.minScore - score} pts до {next.name}</span>
      </div>
      <div className="h-2 bg-gray-800 rounded">
        <div
          className="h-full bg-gradient-to-r from-purple-500 to-blue-500 rounded transition-all duration-500"
          style={{ width: `${Math.min(progress, 100)}%` }}
        />
      </div>
    </div>
  )
}

Merkle дистрибьюция и claim

После объявления аirdrop — claim интерфейс. Стандартная схема: контракт MerkleDistributor, proof генерируется на сервере:

import { StandardMerkleTree } from '@openzeppelin/merkle-tree'

// Получение proof для адреса
async function getMerkleProof(address: string): Promise<{ proof: string[], amount: bigint }> {
  const tree = await loadMerkleTree() // дерево хранится on IPFS или CDN
  const [index, [addr, amount]] = tree.entries().find(([, [a]]) => a.toLowerCase() === address.toLowerCase())
  return { proof: tree.getProof(index), amount: BigInt(amount) }
}

На фронте кнопка Claim активируется если !isClaimed && proof !== null. После клейма — отображаем tx hash и обновляем статус через useWaitForTransactionReceipt.