Skip to main content

Question it answers

“Show me the full chronological event feed for wallet 0x…, newest first. Each row is one event with its actual payload (counterparty, amounts, token IDs, pair address, USD value), and I can filter by event type.”
A single read returns what the public API stitches together client-side from /wallets/{address}/erc20-transfers, /native-transactions, /nfts/transfers, /swaps, /approvals, and /defi/positions. Storing it pre-stitched, in your own database, is the value.

What you get

The recipe lands one row per wallet-bearing event. A wallet is “involved” if it is a non-empty from/to (or owner/approver) on the event. Six event types share one wide, flat table; per-type columns are populated as relevant: Each row also carries direction (sent · received · self · minted · burned), counterparty, tx_hash, block_number, block_timestamp, and log_index.

Source

The transform reads six per-block arrays and UNIONs them into the event feed: tokenTransfers · nativeTransfers · nftTokenTransfers · tokenSwaps · tokenApprovals · pairLiquidityChanges USD values are computed inline from the same block’s tokenPriceUpdates, with no separate price join at read time.

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 feed, optionally filtered by event_type, is a contiguous range read.

Full schema

Below is the complete read table this recipe produces. It’s the full shape: every event type’s columns in one wide row. This is a starting point: keep the columns and event types you need and drop the rest (see Schema & flexibility). Raw uint256 amounts and token_id are stored as text (they exceed numeric precision); USD columns are wide decimals so a low-decimals token × price never overflows.
The sign column drives reorg collapsing; read with FINAL or sum(sign). A single-node setup can use CollapsingMergeTree(sign) without the replication path.
MySQL is the same shape with DECIMAL(65,18) for the USD columns. position is the block-level cursor used during backfill.

Run it

Everything runs in your infrastructure: the sink writes into a database you own and you query it with plain SQL, no API and no per-call limits. Prerequisites
  • Docker and Docker Compose.
  • A Data Feeds key, a cm_live_… key with continuum:read scope (this is not your Web3 API key).

Sign up for a Data Feeds key

Log in to the Moralis admin panel to create your account and generate a cm_live_… key.
Start the sink Generate a starter pack for walletHistoryFull in the Moralis Admin Panel, pick your chain and destination database, then run it with Docker. The quickstart walks through it end to end. Choose the backfill window (MORALIS_HISTORICAL_FROM_BLOCK, e.g. tip-1000) as noted below.
Size the window deliberately. This recipe lands one row per event across six event types, so history adds up fast, tip-1000 on Ethereum is roughly 3M rows. The 9-branch transform is memory-hungry: on chain-heavy windows raise the sink’s ClickHouse memory ceiling (CLICKHOUSE_MAX_MEMORY_USAGE, default 4 GiB). See Modes for backfill vs realtime.
Then run the example reads below to confirm rows are landing.

Example reads

A wallet’s full feed, newest first (ClickHouse):
Only swaps and token transfers (the event_type filter compresses the scan further):
Per-event-type breakdown (sign-aware, cheaper than FINAL):

Other ways to consume this data

The sink is the way to get this pre-shaped table (wallet_history_full), recommended for a queryable history you own. The same underlying data is also available as the raw decoded feed, if you’d rather shape it yourself:
  • Your existing message clients: stream it with Kafka / AMQP / SQS.
  • REST / Arrow Flight: pull the decoded block stream directly.
  • Your warehouse: query it via Iceberg from Snowflake, BigQuery, Spark, or DuckDB.
Those give you the source arrays (tokenTransfers, tokenSwaps, …) to assemble yourself, not this pre-stitched wallet table. All of them read the one data lake into your infrastructure. See What are Data Feeds?.

USD valuation and fidelity gaps

  • Swaps and LP changes carry per-leg decimals, so their *_usd columns are true dollar values (raw / 10^decimals × price).
  • Token transfers have no decimals field on the transfer event, so amount_usd is raw_amount × price (unscaled). Divide by 10^token_decimals to read dollars; fold in a decimals lookup from a Token Metadata sync if you need it pre-scaled.
  • Native and NFT transfers leave USD NULL: native pricing needs a separate native-price feed, and NFT pricing is out of scope (use an NFT Trades recipe for trade-priced data).
  • Approvals leave USD NULL by design: an unlimited allowance × price is meaningless.

Lightweight variant: transaction pointers

If you only need a wallet’s transaction list (one pointer per (wallet, tx), not the full payload), there’s a slimmer variant that lands just the deduplicated pointers from the transfer arrays. Use the full feed above when you need amounts, counterparties, and USD value; use the pointer variant when you only need “which transactions touched this wallet.”

Modes

Shipped defaults: ClickHouse hybrid (backfill → realtime), Postgres / MySQL historical (one-shot backfill). For live/reorg-safe ingestion, use ClickHouse; see the overview.

Multichain

The recipe is chain-parametrized via the chain setting: point it at any supported EVM chain or Solana. On Solana, the event identity is widened to stay unique under Solana’s repeated log indices; the wallet feed it produces is identical in shape.

Powers these use cases

Accounting & Tax

The chronological, USD-valued event feed behind a ledger.

Compliance & AML

Full counterparty and transfer trail per address.

Migrating from the REST API

Coming from the Web3 Data API? This recipe replaces the wallet-activity endpoints below. Data Feeds are not 1:1 replicas of REST responses (you own the table and reshape it with SQL), so each subsection states exactly what maps, what’s calculated for you, and what you add yourself. For the general model see Data Feeds vs. the REST API; for the full endpoint index see the endpoint map.

Wallet transaction history

GET /wallets/{address}/history Replaced by this recipe’s wallet_history_full table: the same activity, flattened to one row per event instead of one object per transaction, with a per-event USD value the old endpoint never had.
Not a 1:1 swap. The old response nests events under each transaction and layers on a decoded/curated layer. You need this recipe for the events, plus Token Metadata for token names/symbols/decimals, plus a GROUP BY tx_hash reshape if your UI expects the transaction-grouped shape. Transaction-level fields (gas, nonce, receipt) come from the raw block transaction rows; see The block array.
To reproduce the endpoint’s transaction-grouped shape, group events by tx_hash (Postgres):
Good to know
  • The biggest migration task is the reshape: event-flat → transaction-grouped is a GROUP BY tx_hash. Skip it entirely if your UI prefers an event feed.
  • You gain per-event USD the endpoint never provided. Compare with a tolerance, and expect NULL/0 for unpriced tokens.
  • The decoded/categorized/labeled layer is off-chain processing, not onchain data; add it yourself or drop it.
  • Only need “which transactions touched this wallet”? Use the lightweight pointer variant instead of the full feed.
  • No per-call limit: page a wallet’s entire history with plain SQL.

Wallet native transactions

GET /{address} Replaced by this recipe filtered to event_type = 'native_transfer': the native slice of the same event feed. Direction, date, block, and ordering filters become plain WHERE / ORDER BY clauses.
Not a 1:1 swap for strict parity. The REST endpoint returns every transaction where the wallet is from or to, including value = 0 contract calls; native value movements alone miss those. For exact parity, and for gas/nonce/receipt fields, read the raw transaction rows from the block array (filter transactionFrom / transactionTo = your wallet) alongside this recipe. See The block array.
Good to know
  • No wallet-size cap: the old endpoint can error on very large wallets; your own table has no such limit.
  • Strict parity includes value = 0 sends, so use the block-array transaction rows when you need every transaction, not just value movements.
  • Run the recipe realtime (ClickHouse recommended) for a live feed of new transactions.

Wallet active chains

GET /wallets/{address}/chains Replaced by a roll-up you build: run this recipe on each chain you cover, keep a min/max activity summary per wallet per chain, and union the results in your app.
Not a 1:1 swap. Data Feeds are one feed per chain. This endpoint needs this recipe running on every chain you want to report, plus a small cross-chain union in your application layer. A wallet active on a chain you don’t index simply won’t appear; that coverage is yours to set.
Keep the per-chain read cheap with a summary view over the recipe’s table:
Good to know
  • First-seen needs full history; “active” and last-seen do not. Backfill each chain to an early block where you want a correct first transaction; a wallet’s first activity can be genesis-era.
  • Every value is exact: no prices, no off-chain data, just onchain presence and the bounding transactions.
  • Stream each chain’s feed to keep last-seen live as the wallet transacts.

Wallet stats

GET /wallets/{address}/stats Replaced by plain COUNTs: three of the five stats come straight from this recipe’s table; the NFT holdings counts need a second recipe.
Not a 1:1 swap. You need this recipe plus NFTs by Wallet. transactions, token_transfers, and nft_transfers are event_type counts over wallet_history_full; nfts and collections are counts over the wallet-NFT holdings table.
Good to know
  • These counts are cumulative over the wallet’s life: accurate totals need the full chain history backfilled (historical / hybrid from an early block). See History & backfill.
  • For transactions.total, native transfers miss value = 0 contract calls; count the raw block-array transaction rows for strict parity (same caveat as native transactions).
  • For nfts, decide whether you count held token IDs or sum ERC-1155 amounts. The endpoint counted token IDs.
  • Serving many wallets? Materialize a per-wallet stats row and update it as events land.