Tracking Every Balance and Price Across Every Chain.

Tracking Every Balance and Price Across Every Chain.

381e35

381e35

3/24/2026

#api#balances#prices
A user's portfolio is a simple equation. Balance times price. What you hold, multiplied by what it's worth. Two numbers. One product. This should be the easiest part of any crypto application to build.
Yet, it is by far the most tedious thing we have built.
The first version took two hours. Call balanceOf for the tokens we knew about, hit a price API, multiply, display. The user behavior that we had specced nearly instantly worked. It rendered a portfolio that looked right for our test wallets. We moved on to features that were more pressing.
Then we began doing tests and users showed up with wallets we hadn't designed for. Hundreds of tokens we'd never seen. Positions in protocols we hadn't integrated. Balances that changed between page loads with no transactions in between. Prices that disagreed with what the user was seeing everywhere else. Portfolio totals that were wrong by orders of magnitude, sometimes because of a single token with a bad price, sometimes because an entire category of asset was simply invisible.
Every fix revealed a deeper problem. The simple equation was never simple.

Token Discovery

Before you can read a balance, you need to know which tokens to ask about. Every team starts with a token list. Curated sets of "important" tokens that you query speculatively. You get coverage for the majors immediately with zero infrastructure.
A user's wallet holds whatever anyone has ever sent to it. There is no manifest. There is no inbox they opted into. Every ERC-20 transfer to their address creates a balance they might care about, and the set of tokens they hold changes with every block on every chain. You cannot call balanceOf for "all tokens." The function requires a contract address you already know.
If the user holds , , and , token lists work great. If they hold a governance token from a protocol that launched last week, an LP position that isn't on any list, or a memecoin with $50M in daily volume that no community maintainer has bothered to add, the balance doesn't exist in your app. The gap between what the user holds and what your app shows grows with every token that ships outside your list.
Closing that gap means indexing every Transfer event where the user appears as a recipient, across every token contract, across every chain you support. This is a data infrastructure problem masquerading as a product feature. faviconMost teams don't realize they need it until the token list stops being sufficient, and by then the product has been shipping incomplete data for months.

Balances at Scale

Once you know which tokens to ask about, the next step seems mechanical. Call balanceOf(address) for each token. Get a uint256 back. Scale it by decimals. Done.
At small scale, this works. One token, one chain, one RPC call, one number. At real scale, it falls apart immediately.
A user with 40 tokens across 8 chains requires 320 individual balanceOf calls. Each call is a separate RPC request against a separate chain's node infrastructure. Rate limits vary by provider. Latency varies by chain. A single slow or failed response blocks the portfolio from rendering if you haven't built around it, and building around it means partial display states, retry logic, timeout handling, and loading skeletons for every individual balance. All for what was supposed to be a single number.
faviconMulticall contracts help. Batch dozens of balanceOf calls into a single RPC request per chain. This drops 320 calls to 8. Multicall introduces its own failure modes though. One reverting call in the batch can take down the entire batch unless you use the tryAggregate variant. Response payloads can exceed provider size limits. And you're dependent on a helper contract deployed at a known address on every chain you support, which isn't guaranteed.
Even with perfect batching, you're reading snapshots. The number you get back is valid for the block it was queried at. By the time it renders in the UI, the block has advanced. For most tokens this drift is negligible, and you can justify it as momentarily tolerable until you have time to resolve the issue.

Balances That Move Without Transactions

When you cache a balance from balanceOf, you're making an implicit assumption: this number stays the same until a transaction changes it. For most ERC-20 tokens, that holds. For an entire category of assets that your users hold, it's wrong.
is a rebasing token. The balance in your wallet changes every block as staking rewards accrue. There is no transfer event. There is no function call that triggers the change. The balanceOf return value simply increases over time because the contract's internal accounting updates with every oracle report. If you cached the balance ten minutes ago, it's already wrong. The user is earning yield and your UI doesn't show it.
Today, many consumers don't even know the tokens they hold are yielding because builders have no direct way to surface that without massive compute costs.
rebases based on a target price, expanding and contracting balances across every holder simultaneously. Aave's aTokens accrue interest continuously through an internal index, so your balance grows every block without any event you can subscribe to. Compound's cTokens use an exchange rate that drifts between interactions. The token balance stays the same but the underlying value it represents changes constantly.
There is no standard event for "balance changed without a transfer." faviconERC-4626 vaults expose a convertToAssets function that gives you the current value of your shares, but this is a live computation, accurate only for the block you query it at. Calling it on every render cycle for every vault position a user holds generates RPC load that seemed absurd when you were designing the system for ten tokens on one chain.
Your options are polling (expensive, still stale between polls) or understanding the accrual mechanics of each protocol well enough to compute the current balance forward from the last known state. The second option means building a protocol-specific balance model for every rebasing and yield-bearing token your users might hold. The set of those tokens grows every week.

Historical Balances Don't Exist

Everything above describes the difficulty of getting a user's balance right now. The moment you need to know what they held yesterday, last week, or at any point in the past, the problem changes entirely.
ERC-20 tokens at least emit Transfer events. If you've indexed every transfer for a given address, you can reconstruct a historical balance by replaying events forward from zero. It's expensive and slow, but the data exists. You can get there.
Native tokens don't give you that option.
transfers that happen inside contract execution, called internal transactions, don't emit events. When a smart contract sends to your address via a low-level call, there is no log. The only record of that transfer lives in the execution trace of the transaction that produced it. To reconstruct a historical native balance, you need traces.
Traces require debug_traceTransaction or trace_block. These RPC methods are so computationally expensive that nearly every public provider disables them. On Ethereum mainnet, a handful of providers offer trace access at premium tiers. On most L2s, traces are simply not available through any public RPC endpoint. The data exists on the nodes, but no provider will serve it to you.
Without traces, you cannot build an accurate historical native token balance. You'll capture direct EOA-to-EOA transfers from the transaction list, but you'll miss every transfer that happened inside a contract call. In DeFi, that's most of them. A user who unwrapped , received a flash loan repayment, collected protocol fees, or got liquidation proceeds: all of those native transfers are invisible without trace data.
You can call eth_getBalance at the current block and get a correct answer for that instant. But the history between point-in-time reads is still missing. Every block where an internal transaction moved in or out without a corresponding log is a gap in the story you're telling the user about their money.
Running your own archive node with trace indexing on every chain you support is the only other option. Archive nodes store the full state trie at every block height. They require terabytes of disk, significant memory, and days to sync from genesis. Per chain. And even with archive access, reconstructing a single address's native balance history means tracing every block they appeared in, extracting internal transfers, and accumulating the result. There is no shortcut query. There is no eth_getBalanceHistory. You build the history yourself from raw execution data, or you don't have it.
Most portfolio trackers show you what you hold now and nothing else. Historical portfolio value, the chart that every user expects to see the moment they connect their wallet, requires solving every one of these problems retroactively, across every block, for every asset, on every chain.

Where Prices Come From

You have a balance. To turn it into a dollar value, you need a price. There is no canonical price for a crypto asset. There are dozens of prices, and they all disagree.
Centralized exchanges report prices from their order books. faviconBinance and faviconCoinbase can quote the same asset at a $0.50 spread because their order flow and liquidity profiles are independent. Both numbers are "correct" for their venue. Neither is the price.
Decentralized exchanges derive prices from pool reserves. The price of on a Uniswap pool is a function of the ratio between the two assets. Different pools with different liquidity depths produce different prices for the same pair. A pool with $500K of total liquidity and a pool with $500M both return a "price," but the smaller pool moves 1,000x more on the same trade size.
Oracles like faviconChainlink aggregate multiple sources and post prices onchain. These are the most reliable for risk calculations, but they update on heartbeats (every hour for many pairs) or deviation thresholds (1-2% movement). Between updates, the oracle price can lag the market significantly during volatile periods, the exact moments when price accuracy matters most.
Price aggregators like CoinGecko, CoinMarketCap, and DeFiLlama combine feeds from multiple sources with differing methodologies. Volume-weighted averages, median prices, last-trade prices. The same token at the same moment returns a different number depending on which aggregator you ask.
For major tokens the disagreement is small enough that nobody notices. For everything else, it becomes the entire problem.

The Long Tail Has No Price

, , and the top 100 tokens have reliable prices from multiple sources. Aggregators cover them. Oracles track them. The data is abundant and consistent enough.
Then there's everything else.
Most tokens don't have a direct dollar quote. There is no /USD pair on Coinbase. There is no Chainlink feed. The only pricing data that exists lives in onchain liquidity pools, and those pools are almost always denominated against another token.
A token might have a single Uniswap V3 pool paired against . To get a dollar price, you need the token's price in , then 's price in dollars. That's a two-hop path. Simple ratio math: Token/WETH × WETH/USD = Token/USD.
Many tokens don't even have a pair. They might be paired against another governance token, which is paired against on a different DEX, which you then convert to dollars. Three hops. Four. Sometimes the only path from a token to a dollar denomination winds through pools on entirely different chains.
LP tokens, vault shares, and derivative positions are even further removed. The "price" of a Uniswap V3 LP NFT is a derived computation from the underlying token prices, the position's tick range, the current pool state, and accumulated fees. No API returns this number. You compute it yourself or you show nothing.
Wrapped and staked variants each need their own derivation chain. doesn't have an independent market. Its price is 's price multiplied by the wrapping exchange rate multiplied by 's dollar price. Three lookups, two multiplications. Each step in the chain is another ratio to resolve.
This is graph traversal. Given any token, find a path through available liquidity pools to a dollar-denominated asset, multiply the ratios along the way, and weight by the liquidity depth at each hop. Shallow pools at any point in the path mean the resulting price is unreliable. Deep pools across every hop mean you can trust it.

Freshness vs. Cost

Every solution to staleness costs money, and most of them don't solve the problem.
Poll every balance on every chain every block? At two-second block times across eight chains, that's continuous RPC load that most providers will throttle or charge into oblivion.
Subscribe to WebSocket feeds for real-time price updates? Most providers cap connections, most feeds cover only major pairs, and WebSocket reliability requires reconnection logic, deduplication, and fallback polling anyway.
Cache aggressively and refresh on user interaction? Now your first page load is fast but wrong, and the user watches their portfolio value jump as fresh data arrives. If the jump is small, it feels glitchy. If the jump is large, it feels broken.
No single refresh strategy works across all asset types. Major token balances are stable enough to cache for minutes. Rebasing token balances need per-block resolution. Protocol positions need refresh when the underlying pool state changes. Prices for liquid pairs can tolerate a 30-second cache. Prices for illiquid pairs should be computed on demand. Vault share prices need fresh derivation every time because the exchange rate drifts continuously.
A system that treats all of these the same is too slow, too expensive, or too wrong. Usually all three.

The Transaction Boundary

Suppose you've solved all of it. You index every transfer event across every chain. You model rebasing mechanics per-protocol. You run archive nodes with trace indexing. You aggregate prices from every source, walk liquidity graph paths for the long tail, and tier your refresh strategy by asset class. Months of infrastructure work. Massive operational cost. Your portfolio display is as close to correct as offchain data can get.
Now the user wants to do something with their money.
They click "swap 50% of my ." Your app reads the balance you cached, computes 50%, and encodes that number into the transaction's faviconcalldata. The user signs. The transaction enters the mempool. Seconds pass. The transaction confirms.
Between the moment you read that balance and the moment the transaction executes, the world moved. The balance may have changed. The price certainly did. The number baked into the calldata is a snapshot of a reality that no longer exists. If the drift is small, the transaction succeeds and the user gets slightly less than expected. If the drift is large, the transaction reverts and the user pays gas for nothing.
We wrote about faviconwhy standard transactions break this way when we launched Plug's Alpha. Every one of the six fields in an Ethereum transaction is locked at signing time. The amounts, the calldata, the gas budget. All of it is a bet that nothing changes between the moment you press sign and the moment the chain executes. That constraint is tolerable when you're sending to an address. It's devastating when you're building multi-step DeFi strategies against live market state.
Every application in this space hits this wall. All of that infrastructure work, all of that effort to get the data right, and the moment it crosses from display into execution, the data is stale. You're encoding a cached past into a transaction that executes in the future. The gap between those two moments can never be fully closed.
This is where we stopped trying to close the gap and started building around it.

Intents Over Inputs

Plug compiles intents at runtime against real onchain state.
When a user builds a Plug that says "swap 50% of my balance," the transaction doesn't contain a hardcoded amount derived from the last time we polled balanceOf. The intent compiles at execution time. It reads the user's onchain balance in the same transaction, faviconcomputes 50% of it, and passes that value forward. The balance is never stale because it was never cached. It's read from chain state at the moment of execution, in the same call frame as the swap itself.
Intents are forever. A user builds a Plug once and it can fire tomorrow, next week, next month. Every time it runs, it needs to know the user's current balance, the current price of the assets involved, the current state of the protocols it touches. The entire infrastructure described above exists to serve that need: giving the user and their intents accurate data, continuously, across every chain, for as long as the intent lives.

Balance Times Price

After all of it, the transfer indexing, the rebasing models, the archive nodes, the graph traversal, the tiered caching, the runtime compilation, the user sees a number. Their portfolio value. They don't know about any of it. They don't need to. They see a number and they trust it.
That trust is the product. Everything described here exists to earn it.
Two numbers. One product. Simple equation. Enormous machine underneath making sure both numbers are right, right now, every time. The user who connects their wallet and sees their portfolio has no idea what it took to make that number accurate. They shouldn't. The moment they have to think about it, you've already failed.
Every team that builds a portfolio view will walk this same path. Token lists that stop covering what users hold. Balances that move without transactions. Prices that don't exist until you derive them through three hops of ratio math. History that requires infrastructure most teams can't justify. Freshness that costs more than the feature it supports. And at the end of all of it, the transaction boundary where cached data meets live execution and breaks.
The equation is simple. Earning the right to display it is the hardest thing you'll build.
The faviconPlug API exposes our infrastructure directly. Realtime balances, portfolio valuations, token prices with confidence metadata, and protocol position state across every chain we support. If you're building something that needs to earn that same trust without rebuilding the stack, faviconthe documentation is interactive and doesn't require a download.
If you're dead-stuck on trying to do all of this yourself first, go ahead. Reach out when you hit a hard part and I can tell you how we solved it. But if you want to skip straight to the end and start building with the data you need, faviconthe API is ready and waiting for you to plug in.