Skip to main content

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 (the from 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 raw balance 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.
The 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.
MySQL is the same shape with 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):
All current non-zero native balances (mirrors GET /wallets/balances):
Postgres: current balance for one wallet, after a refresh:

Modes

Shipped defaults: ClickHouse hybrid (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 the chain 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}/balance can return a USD-valued balance; that needs the native-asset/USD price at the block, which is not carried on nativeTransfers. 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. balance is the raw uint256 in wei; divide by 10^18 in your app to read whole native units.

Migrating from the REST API

This recipe replaces the endpoint on its own: the latest observation per wallet is the response’s 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^18 in 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 historical state; use ClickHouse for realtime / hybrid.

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.