How do you list everything a wallet holds on Ethereum, Base, and Arbitrum at the same time? The textbook answer is a lot of plumbing. You connect to an RPC node per chain, fetch the native balance, pull a token list, call balanceOf for each contract, read decimals to scale the raw integer, and then go find a price for every asset. Do that across three chains and you are juggling node providers, rate limits, ABI encoding, and a pile of BigInt arithmetic before you have shown a single dollar figure.
There is a shorter path. SiftingIO exposes a single read endpoint that returns a wallet's native balance plus its ERC-20 holdings, already decoded and ready to value: GET /v1/fnd/dex/wallet/{chain}/{address}. You pass a chain slug and an address, and you get back a normalized list of positions. No node connection, no balanceOf loop, no decimals bookkeeping.
One call per chain, one schema everywhere#
The endpoint lives under the fundamentals family (/v1/fnd/*) because it is a discovery read, not a live tick. Authentication is the X-API-Key header, the same credential you use for every other REST call.
curl -H "X-API-Key: $SIFTING_KEY" \
-H "Accept-Encoding: gzip" \
"https://api.sifting.io/v1/fnd/dex/wallet/base/{address}"
The response carries the native balance and an entry per ERC-20 token, each already scaled by its decimals so you read a human number, not a 78-digit integer. Because the schema is identical on every supported chain, fanning out across networks is the same call with a different slug. The supported set includes eth, base, arbitrum, optimism, bsc, polygon, and Solana.
const chains = ['eth', 'base', 'arbitrum'];
const address = '{address}';
async function portfolio(address) {
const results = await Promise.all(
chains.map(async (chain) => {
const res = await fetch(
`https://api.sifting.io/v1/fnd/dex/wallet/${chain}/${address}`,
{ headers: { 'X-API-Key': process.env.SIFTING_KEY } }
);
if (!res.ok) throw new Error(`${chain}: ${res.status}`);
const body = await res.json();
return { chain, ...body };
})
);
return results;
}
That is the whole read layer. What used to be a per-chain RPC integration with hand-rolled token discovery collapses into three HTTP calls that all return the same shape. A dashboard developer can map the result straight into a table. A treasury analyst can pipe it into a spreadsheet export. An indie hacker can ship a balance tracker over a weekend.
Putting a value on what you found#
Balances are only half the question. The other half is what the holdings are worth, and that is where naive trackers go wrong. Reading a price from a single thin pool gives you a number that can be stale, manipulated, or simply unrepresentative when liquidity is fragmented across venues.
SiftingIO separates the two concerns on purpose. The wallet endpoint tells you the quantities. The live snapshot endpoints tell you the value, using a consensus reference rather than a single-source print. For a token that trades on centralized venues you can pull the last trade:
curl -H "X-API-Key: $SIFTING_KEY" \
"https://api.sifting.io/v1/last/trade/crypto/{symbol}"
The price you get back is not a pass-through from one place. It is a volume-and-reputation-weighted median across multiple independent venues, with outlier kills and frozen-feed detection upstream. A median has a 50 percent breakdown point, so a majority of venues have to err in the same direction before the published number moves with them. Every snapshot also carries an explicit quality flag, so you can tell a Normal reading from a Degraded one that fell back to a safer source.
For positions held as liquidity in an on-chain pool, value the pool directly with the TVL aggregation snapshot:
curl -H "X-API-Key: $SIFTING_KEY" \
"https://api.sifting.io/v1/last/tvl/{chain}/{pair}"
The pattern is consistent: /v1/fnd/dex/wallet/... for what is held, /v1/last/... for what it is worth right now. Treat the consensus price as a reference and validation layer. It is well suited to research, reporting, and cross-checking the number an execution venue is showing you. It is a synthetic reference value, not an executable depth feed, so use it to model and to sanity-check, not to route orders.
Common pitfalls#
Fanning out across chains burns your rate budget fast. Each chain is a separate request, so a five-chain refresh loop is five calls every cycle. On the free tier that is 60 requests per minute and 10,000 per month, which a one-second polling dashboard exhausts quickly. Watch X-RateLimit-Remaining on every response and back off when it trends toward zero. A 429 returns a Retry-After value in seconds; honor it rather than retrying immediately. If you are refreshing many wallets, cache the portfolio read and re-value with the cheaper snapshot calls between full refreshes.
A token in the wallet does not guarantee a clean price. Wallets accumulate airdrops, test tokens, and assets that trade only in a single shallow pool. When you ask for a live snapshot on one of those, you can get a 503 stale_snapshot whose body includes last_t and server_now, which means the freshest value is older than the freshness threshold. That is the system telling you the truth instead of handing you a fabricated price. Check the quality flag and the timestamps, and surface thin or stale positions as uncertain rather than rolling them into a confident total.
Address casing and chain slugs are easy to get wrong. EVM addresses are commonly checksummed with mixed case, but pass the slug from the documented set (eth, not ethereum, in the path) or you will get a 404 unknown_market style error that looks like the wallet is empty when it is really a bad path. Confirm the exact slugs against the markets catalog before wiring a chain into your loop.
Wiring it into a real workflow#
The two-step shape makes this composable. A portfolio tracker pulls balances once, values them with consensus snapshots, and re-prices on a timer without re-reading the chain. A risk dashboard can flag any position whose snapshot comes back Degraded. A research script can snapshot a set of wallets daily and store the normalized output, since the schema is stable across chains and over time.
The official SDKs wrap both endpoint families with matching method signatures, so the same calls work whether you reach for Go, Python, or the JavaScript client (npm install @siftingio/sdk). The data shapes line up because all three are generated from the same specs.
The free tier covers a month of history, one API key, and no credit card, which is enough to prototype a cross-chain tracker end to end before deciding what to value continuously. Read the docs for the exact response fields and the full chain list.


