How to Build a Cross-Chain DeFi Tool from Scratch (Lessons from ClaimScan)
Most developers think the hard part of building a DeFi tool is the smart contract interaction. It is not. The hard part is the data layer: getting consistent, accurate, real-time information out of four different chains, nine different platforms, and four different identity systems, and making it all feel like one coherent product.
We built ClaimScan to solve a real problem: creators earning fees across Pump.fun, Bags.fm, Clanker, Raydium, and more had no single place to see everything they were owed. What started as a straightforward idea turned into a deep dive on cross-chain data aggregation, identity resolution, and real-time streaming architecture. This is what we learned.
Why Cross-Chain Is the Hard Part
Every chain has its own RPC format, its own account model, its own token standard, and its own quirks. Solana uses a completely different wallet ecosystem from EVM chains. Base and BSC look similar on the surface but diverge in chain IDs, explorer URLs, and RPC behavior. Pump.fun uses synthetic token identifiers that don't map cleanly to on-chain addresses.
The challenge is not reading data from one chain. It is normalizing data from all of them into a single schema that your UI can consume without knowing or caring which chain it came from. If you don't design for this upfront, you end up with a tangled mess of chain-specific conditionals spread across your codebase.
The answer is an adapter pattern. Define one interface, implement it per platform, and enforce that every implementation returns the same shape.
The Adapter Pattern: One Interface, Nine Implementations
Every platform in ClaimScan has its own data source module. Each module is responsible for fetching raw data from its respective chain or protocol and normalizing it into a common schema. The schema looks like this:
platform, chain, tokenAddress, earned, claimed, unclaimed (both native token amount and USD value).
That is it. Every adapter outputs this shape. The rest of the application never needs to know whether it is looking at a Solana Pump.fun result or a BSC Clanker result. It just processes the common schema.
The practical benefit shows up when you add a new chain. Base and BSC share the same EVM read logic, so adding a new EVM-compatible chain is a config change, not a code rewrite. You add the chain ID, the RPC endpoint, and the explorer URL. The existing read logic handles the rest. This is the kind of architecture decision that saves you days of work six months later.
All adapters run in parallel. We don't wait for Pump.fun to finish before starting Raydium. Results stream in as each adapter completes, which brings us to the real-time architecture.
Identity Resolution: Mapping Social Handles to Wallets
ClaimScan accepts four input types: Twitter handles, GitHub usernames, Farcaster profiles, and raw wallet addresses. The identity resolution layer maps everything to wallet addresses before any chain queries run.
Twitter handles resolve to wallets through the Bags.fm and Bankr APIs. GitHub usernames go through Bags.fm. Farcaster profiles bridge to EVM wallets for Base, BSC, and Ethereum through the Farcaster protocol's connected address system.
The tricky part is verification status. Not every social handle maps cleanly to a single wallet, and some mappings are unverified. We store resolved identities in Supabase with an explicit verification flag so the UI can surface confidence level to the user. A result tied to a verified on-chain connection means something different than one inferred from a third-party API.
Caching resolved identities is important here. Identity resolution involves multiple external API calls per lookup. You don't want to repeat that work on every query. We cache in Supabase with a TTL that balances freshness against API rate limits.
Real-Time Streaming: SSE and React Context
Running nine adapters in parallel means results arrive at different times. You have two options: wait for all of them and show nothing until they are done, or stream results as they arrive. We chose streaming via Server-Sent Events.
The server opens an SSE stream, dispatches each adapter result as it resolves, and closes the stream when all adapters finish. The client receives a sequence of partial results and renders them incrementally.
On the React side, a LiveFeesProvider context component wraps the hero summary and the platform breakdown table. A useLiveFees() hook is consumed by both. This keeps both components in sync without prop drilling and without duplicate fetches.
The merge logic is the subtle part. At any point the user sees a combination of three data sources: cached fees from a previous query, live SSE results overlaying the cache, and optimistic claimed overlays if the user just claimed fees. The merge happens on a composite key of platform:chain:tokenAddress. Live results that don't exist in the cache yet render as virtual rows with a live: prefix on their ID.
We hit a split-data bug early on where the hero total and the platform table were diverging because they were each maintaining their own merge state. The fix was straightforward once we saw it: one context, one merge, both components read from the same source of truth. The LiveFeesProvider pattern solved it completely.
Caching Strategy: When to Cache and When to Go Live
Not all data needs the same TTL. Standard fee data for most wallets is cached for 40 minutes. Heavy creators with 500 or more records get a 2-hour TTL because the cost of querying that much on-chain data repeatedly is significant, and heavy creators tend to have older, more stable fee histories.
Live polling is a different path entirely. When a user triggers a live refresh, the query bypasses the cache and hits the chain directly. This is intentional. You want live data to be genuinely live, not a slightly fresher version of something cached 5 minutes ago.
Our caching stack uses Redis as the primary layer with an in-memory fallback. If Redis is unavailable, the app doesn't break. It falls back gracefully and continues serving requests from memory, with a smaller effective cache window.
The lesson here is to design your cache as a performance optimization, not a reliability dependency. If it goes down, your app should degrade gracefully, not hard-fail.
Pricing Data: The Three-Tier Waterfall
Displaying USD values for unclaimed fees requires current token prices. We use a three-tier waterfall: DexScreener first, Jupiter for Solana tokens that DexScreener misses, and CoinGecko for native assets (SOL, ETH, BNB).
The order matters. DexScreener has broad coverage for long-tail DeFi tokens that CoinGecko doesn't list. Jupiter has deeper Solana coverage than DexScreener for some newer tokens. CoinGecko is the most reliable source for native asset prices. Using them in this order maximizes coverage while minimizing fallback to less specific sources.
A cron job refreshes prices on a regular interval. We don't fetch prices per user request. Price data is pre-fetched, cached, and applied at query time. This keeps response times fast and avoids hammering pricing APIs with per-user traffic.
Chain-Specific Gotchas
Solana
Solana is not EVM. This sounds obvious but the implications run deeper than most developers expect. The account model is different, the transaction format is different, and the wallet ecosystem is entirely separate. You cannot reuse EVM read logic on Solana. Budget for a clean separation.
Pump.fun adds another layer of complexity with synthetic token identifiers. Tokens launched on Pump.fun and PumpSwap use IDs like SOL:pump and SOL:pumpswap in our system rather than raw mint addresses. These synthetic IDs represent the fee-earning mechanism, not the token itself. Your adapter needs to handle this mapping explicitly.
EVM Chains (Base and BSC)
Base and BSC share the same ABI structure and the same read logic in our adapter layer. The only differences are chain ID, RPC endpoint, and explorer URL. We use a chain config object to hold these values. Adding a new EVM chain means adding one entry to the config, not writing new adapter code.
Dedicated RPC providers per chain are worth the cost. Public RPC endpoints get rate-limited under real traffic. If your product depends on chain reads, use a dedicated provider. The reliability difference is substantial.
Security: What You Cannot Skip
DeFi tools attract bots. Rate limiting is not optional. We apply rate limits across all endpoints, with stricter limits on search and claim flows where abuse is most likely.
Wallet validation runs before any claim operation. You confirm the address is valid and well-formed before hitting the chain. This prevents a class of errors where a malformed address gets submitted to a contract call.
SSE streams are signed. Each stream has a request signature that the server validates before opening the connection. This prevents unauthorized clients from opening persistent connections and abusing the streaming infrastructure.
Full CSP (Content Security Policy) and HSTS headers are set at the application layer. On a Next.js app deployed to Vercel, this means explicit middleware configuration. The default headers are not sufficient for a tool handling financial data. Define your CSP allowlist explicitly and keep it tight.
What We Would Do Differently
Start with the adapter interface before writing any adapter implementations. We defined ours early but not early enough. Two adapters were written before the schema was finalized, which meant a refactor pass later. Define the contract first, then implement against it.
Build the identity resolution cache on day one. We added it mid-development and had to backfill logic. It touches every query path, so retrofitting it is more painful than building it upfront.
Design your SSE stream for partial failures from the start. If one adapter fails, the stream should continue and surface that failure as a partial result, not kill the entire stream. We handled this correctly but it required deliberate error isolation per adapter. Make that decision explicitly, not as an afterthought.
Separate your pricing refresh from your fee query path completely. We got this right but it was a conscious choice that we could have easily gotten wrong. Mixing pricing API calls into per-user request paths is a fast way to hit rate limits under load.
Need a custom dApp built for your project?
Fullstack development from frontend to smart contracts. On-chain logic, real-time UI, production-ready in weeks.
GET A FREE QUOTEHow Much Money Are DeFi Creators Leaving Unclaimed? We Tracked $2M+ to Find Out
We scanned 520+ wallets across 9 launchpads on 4 chains and found that roughly 40% of all creator fees go unclaimed. Here is what the data looks like broken down by chain, platform and creator behavior.
ClaimScan v1.5: Cross-Chain DeFi Fee Tracker Across 4 Chains and 9 Launchpads
Case study of ClaimScan, a cross-chain DeFi tool that tracks and claims creator fees across Solana, Base, Ethereum and BSC. Covers the v1.5 update: BSC support, hardened claims, security audit and CI pipeline.
Your competitors are already investing in this.
Since 2022. 408+ projects. $1.6B+ in market cap generated.
GET A FREE QUOTE