Airdrop Eligibility Status Dashboard Development

We design and develop full-cycle blockchain solutions: from smart contract architecture to launching DeFi protocols, NFT marketplaces and crypto exchanges. Security audits, tokenomics, integration with existing infrastructure.
Showing 1 of 1 servicesAll 1306 services
Airdrop Eligibility Status Dashboard Development
Medium
~3-5 business days
FAQ
Blockchain Development Services
Blockchain Development Stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1214
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    852
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_logo-advance_0.png
    B2B Advance company logo design
    561
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    823

Airdrop Eligibility Status Dashboard Development

An airdrop dashboard isn't just "check if you qualified". A well-made dashboard shows users exactly what they're missing, how many points they have, what actions they can still take before snapshot. It's a retention tool that motivates users to engage more with the protocol. Technically—aggregating data from multiple on-chain sources with caching, because real on-chain queries for every address on every visit kills any RPC.

Architecture: Data Sources

The Graph Subgraph

Primary source for on-chain activity. Subgraph indexes contract events and provides GraphQL API. Dashboard data:

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

Graph queries—free up to limit, fast (< 200ms), don't load RPC nodes.

Snapshot.org API

For 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

For direct balances—viem multicall batches multiple calls into one RPC request:

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

One HTTP request instead of three—critical under high traffic.

Points System and Criteria

Eligibility is usually multifactorial. Typical structure:

interface EligibilityScore {
  total: number
  breakdown: {
    volumeScore: number      // 0-40 points: trading volume
    loyaltyScore: number     // 0-20 points: first interaction date
    governanceScore: number  // 0-20 points: Snapshot votes
    referralScore: number    // 0-10 points: referred users
    holdingScore: number     // 0-10 points: token holding
  }
  tier: 'bronze' | 'silver' | 'gold' | 'platinum'
  estimatedAllocation: bigint | null // null until announced
  missingCriteria: string[]
}

missingCriteria—most useful for user. "You're 2 votes on Snapshot and $500 volume away from next tier"—actionable info.

Backend: Caching and API

On-chain data doesn't change every second—caching is mandatory.

// Redis cache with 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)

  // Parallel fetch from all sources
  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 min TTL
  return score
}

5-minute TTL sufficient for most data. For snapshot data after deadline—cache forever (frozen data).

Frontend: React Dashboard

function EligibilityDashboard() {
  const { address } = useAccount()
  const { data, isLoading } = useQuery({
    queryKey: ['eligibility', address],
    queryFn: () => fetchEligibility(address!),
    enabled: !!address,
    staleTime: 60_000, // one minute
  })

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

Progress Bars and Gamification

Tier progress bar motivates users to reach next level:

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 to {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 Distribution and Claim

After airdrop announcement—claim interface. Standard scheme: MerkleDistributor contract, proof generated server-side:

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

// Get proof for address
async function getMerkleProof(address: string): Promise<{ proof: string[], amount: bigint }> {
  const tree = await loadMerkleTree() // stored on IPFS or CDN
  const [index, [addr, amount]] = tree.entries().find(([, [a]]) => a.toLowerCase() === address.toLowerCase())
  return { proof: tree.getProof(index), amount: BigInt(amount) }
}

Frontend: Claim button active if !isClaimed && proof !== null. After claim—show tx hash and update status via useWaitForTransactionReceipt.