Question it answers
“What is wallet 0x…‘s current native-asset (ETH / MATIC / …) balance?”Mirrors Moralis
GET /{address}/balance and GET /wallets/balances. It is the native-asset analog of Token Balances by Wallet: each nativeTransfers event carries the absolute post-transfer native balance for both legs, so the latest observation per (chain_id, wallet_address) is the current balance, with no replaying of deltas.
What you get
The recipe lands one row per balance observation: each native transfer is unpivoted into two observations (thefrom leg and the to leg), each carrying that wallet’s absolute native balance immediately after the transfer. There is no token_address and no token_id, because the native asset is implied by the chain.
Current balance is latest-wins: the most recent observation by
(block_number, native_seq) per wallet is the live balance.
Source
The transform reads one per-block array and unpivots it:nativeTransfers
Each transfer becomes two observations: (fromAddress, fromPostBalance) and (toAddress, toPostBalance). The zero address and empty post-balances are dropped, and toAddress (nullable on block-reward / fee-burn legs) is coalesced before those filters apply.
Destination
ClickHouse uses the collapsing log-table pattern (see the recipes overview) so chain reorganizations self-correct. The fact table’s sort key is wallet-first, so a wallet’s observations are a contiguous range read and
argMax picks the live balance. Postgres collapses observations into a current-balance materialized view; MySQL keeps a trigger-maintained state table.
Full schema
Below is the complete read table this recipe produces: observations keyed by wallet. Keep the columns you need and drop the rest (see Schema & flexibility). The rawbalance is a uint256 in wei and is stored as text (it exceeds numeric precision); divide by 10^18 (the native decimals) in your app to read whole units.
ClickHouse, fact_native_balances
ClickHouse, fact_native_balances
sign column drives reorg collapsing; read with FINAL + argMax, never a bare WHERE sign = 1. A single-node setup can use CollapsingMergeTree(sign) without the replication path.Postgres, native_balances
Postgres, native_balances
DECIMAL(65,0) for balance and a trigger-maintained native_balances state table (PK wallet_address, plus a DELETE … WHERE balance = 0 cleanup). position is the block-level cursor used during backfill. Refresh the Postgres view on a schedule: REFRESH MATERIALIZED VIEW CONCURRENTLY native_balances;.Example reads
Current native balance for a wallet (ClickHouse;argMax picks the latest observation):
GET /wallets/balances):
Modes
Shipped defaults: ClickHousehybrid (backfill → realtime), Postgres / MySQL historical (one-shot backfill). For live/reorg-safe ingestion, use ClickHouse; see the overview.
The backfill cursor (
position) is block-level, so realtime/hybrid on Postgres / MySQL is constrained by their single-column UNIQUE requirement. Run realtime/hybrid on ClickHouse; the Postgres / MySQL configs target historical backfill.Multichain
The recipe is chain-parametrized via thechain setting: point it at any supported EVM chain or Solana. On Solana, the vendor_event_id already folds in (transactionIndex, fromAddress, toAddress, amount, 'native'), so observations stay row-unique even where Solana repeats indices within a transaction. The balance surface it produces is identical in shape.
Recency: why native_seq, not log_index
nativeTransfers carry no logIndex, and transactionIndex is not unique per transfer (a single transaction with several internal native movements reuses it). “Which post-balance is current” is therefore tiebroken by native_seq, the 1-based position of each transfer within the block’s nativeTransfers array. It is a stable block-global ordinal emitted in canonical order and computed once in the shared transform, so ClickHouse argMax, Postgres DISTINCT ON, and the MySQL trigger all converge on the same current balance per wallet.
Fidelity gaps
- No USD value. Moralis
GET /{address}/balancecan return a USD-valued balance; that needs the native-asset/USD price at the block, which is not carried onnativeTransfers. Join against a native-price source (e.g. the wrapped-native pair from a Token Prices sync) to add it. - Raw wei, no decimals scaling.
balanceis the rawuint256in wei; divide by10^18in your app to read whole native units.
Migrating from the REST API
balance, exact and straight from the chain, with no other recipe required. GET /wallets/balances is the same read across many wallets.
Two workflow changes: you query your own table instead of calling an endpoint, and you must backfill history first: a wallet only has a row once it appears in a native transfer within your indexed range, so run the recipe historical or hybrid from an early block (see History & backfill). Set up the sink via the quickstart, then read the current balance as shown in Example reads above.
Field mapping
Good to know
- Exact and single-valued. Nothing off-chain and nothing to price here.
- Raw wei. Divide by
10^18in your app for whole native units. - USD value is not included. Multiply by the native asset’s price, e.g. the wrapped-native pair from a Token Prices sync.
- Freshness. Postgres / MySQL configs target one-shot
historicalstate; use ClickHouse for realtime /hybrid.
Related
Token Balances by Wallet
The ERC-20 sibling: every token a wallet holds, with balance.
Portfolio Tracking
Native balances are the base-asset leg of a wallet’s portfolio.

