Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.moralis.com/llms.txt

Use this file to discover all available pages before exploring further.

Question it answers

“Give me every NFT transfer — by contract 0x…, by wallet 0x…, or for a single (contract, token_id).”
A single recipe serves all three access paths. It mirrors the on-chain subset of Moralis GET /nft/{address}/transfers (by contract), GET /{address}/nft/transfers (by wallet), and GET /nft/{address}/{token_id}/transfers (single token). It carries no off-chain enrichment — the source array has no collection metadata or address labels (see Fidelity gaps).

What you get

One row per NFT transfer, mapped straight from Moralis-indexed, normalized per-block onchain data:
ColumnDescription
token_addressThe NFT contract that emitted the transfer
token_idThe token id — a uint256 big decimal stored as text (never numeric); compare as strings
from_address, to_addressThe two sides of the transfer
amountThe ERC-1155 quantity (1 for ERC-721)
contract_typeERC721 or ERC1155 (the emitted tokenType)
initiated_byThe address that initiated the transfer
tx_hashTransaction that produced the transfer
block_number, transaction_index, log_indexOn-chain ordering tuple
block_timestampBlock time

Source

The transform reads one per-block array and lands one row per transfer: nftTokenTransfers Fields map straight from the source struct — tokenAddress, tokenId (as a string), fromAddress, toAddress, amount, tokenType (→ contract_type), and initiatedBy. There’s no price or metadata join — this is a flat event stream.

Destination

DestinationTableRead pattern
ClickHouse (first-class)fact_nft_transfersPrefix scan on (chain_id, token_address, token_id, block_number); by-wallet via bloom_filter skip indexes; read with FINAL or sum(sign)
Postgresnft_transfersIndex on (token_address, block_number DESC), (token_address, token_id, block_number DESC), and (from_address, …) / (to_address, …)
MySQLnft_transfersSame composite indexes (ascending)
ClickHouse uses the collapsing log-table pattern (see the recipes overview) so chain reorganizations self-correct — the +1/−1 reorg pair for a row shares an identical key and collapses cleanly. The fact table’s sort key is contract-first, so by-contract is a prefix scan on token_address and by-token-id a prefix scan on (token_address, token_id). By-wallet reads are served by bloom_filter data-skipping indexes on from_address and to_address.

Full schema

Below is the complete read table this recipe produces. Keep the columns you need and drop the rest (see Schema & flexibility). token_id and amount are stored at full uint256 width — token_id as text (hash-derived ENS / ERC-1155 ids routinely exceed any SQL numeric precision), amount as a wide decimal.
CREATE TABLE recipe_nft_transfers.fact_nft_transfers
(
    vendor_event_id     String,
    ingested_at         DateTime64(3),
    chain_id            UInt32,
    block_hash          String,
    block_number        UInt64,
    event_ts            DateTime64(3),
    token_address       String,
    token_id            String,        -- uint256 big decimal, NEVER numeric
    from_address        String,
    to_address          String,
    amount              String,        -- ERC-1155 quantity (1 for ERC-721)
    contract_type       LowCardinality(String),  -- ERC721 | ERC1155
    initiated_by        String,
    tx_hash             String,
    log_index           Nullable(UInt32),
    transaction_index   Nullable(Int32),
    sign                Int8,
    -- by-wallet skip indexes: prune granules that cannot contain the wallet.
    INDEX bf_from from_address TYPE bloom_filter(0.01) GRANULARITY 4,
    INDEX bf_to   to_address   TYPE bloom_filter(0.01) GRANULARITY 4
)
ENGINE = ReplicatedCollapsingMergeTree(
    '/clickhouse/tables/{database}/fact_nft_transfers', '{replica}', sign)
PARTITION BY (chain_id, toYYYYMM(event_ts))
ORDER BY (chain_id, token_address, token_id, block_number, vendor_event_id);
The sign column drives reorg collapsing — read with FINAL or sum(sign), never a bare WHERE sign = 1. A single-node setup can use CollapsingMergeTree(sign) without the replication path.
CREATE TABLE public.nft_transfers (
  position           BIGINT      NOT NULL,
  log_index          BIGINT,
  transaction_index  BIGINT,
  block_number       BIGINT      NOT NULL,
  block_timestamp    BIGINT      NOT NULL,    -- unix seconds
  tx_hash            TEXT        NOT NULL,
  vendor_event_id    TEXT        NOT NULL,
  token_address      TEXT        NOT NULL,
  token_id           TEXT        NOT NULL,    -- uint256 big decimal, NEVER numeric
  from_address       TEXT        NOT NULL,
  to_address         TEXT        NOT NULL,
  amount             NUMERIC(76, 0)  NOT NULL,
  contract_type      TEXT        NOT NULL,    -- ERC721 | ERC1155
  initiated_by       TEXT        NOT NULL
);

-- By-contract access (the recipe's primary purpose).
CREATE INDEX IF NOT EXISTS nft_transfers_token_block_idx
  ON public.nft_transfers (token_address, block_number DESC);
-- By-token-id access (single NFT provenance).
CREATE INDEX IF NOT EXISTS nft_transfers_token_id_block_idx
  ON public.nft_transfers (token_address, token_id, block_number DESC);
-- By-wallet access (either side).
CREATE INDEX IF NOT EXISTS nft_transfers_from_block_idx
  ON public.nft_transfers (from_address, block_number DESC);
CREATE INDEX IF NOT EXISTS nft_transfers_to_block_idx
  ON public.nft_transfers (to_address, block_number DESC);
-- Block-range helper.
CREATE INDEX IF NOT EXISTS nft_transfers_block_idx
  ON public.nft_transfers (block_number);
MySQL is the same shape with VARCHAR(80) for token_id and equivalent ascending composite keys. position is the block-level cursor used during backfill.

Example reads

All transfers of an NFT contract, newest first (ClickHouse):
SELECT block_number, token_id, from_address, to_address, amount, contract_type, tx_hash
FROM recipe_nft_transfers.fact_nft_transfers FINAL
WHERE chain_id = 1 AND token_address = lower('0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D')
ORDER BY block_number DESC
LIMIT 50;
Provenance of a single token (contract + token_id):
SELECT block_number, from_address, to_address, amount, tx_hash
FROM recipe_nft_transfers.fact_nft_transfers FINAL
WHERE chain_id = 1 AND token_address = lower('0xBC4C...') AND token_id = '1234'
ORDER BY block_number DESC
LIMIT 50;
All NFT transfers involving a wallet (either side), bloom-pruned:
SELECT block_number, token_address, token_id, from_address, to_address, amount
FROM recipe_nft_transfers.fact_nft_transfers FINAL
WHERE chain_id = 1
  AND (from_address = lower('0x...') OR to_address = lower('0x...'))
ORDER BY block_number DESC
LIMIT 50;

Modes

Shipped defaults: ClickHouse hybrid (backfill → realtime), Postgres / MySQL historical (one-shot backfill). For live/reorg-safe ingestion, use ClickHouse — see the overview.
The realtime reorg path needs a single-column UNIQUE on the position column, but position is block-level (many transfers share one block), so array-expanded transfer rows can only carry a composite unique. Run realtime / hybrid on ClickHouse — its log table corrects reorgs per-block via the collapsing companion table. The Postgres / MySQL shapes here are intended for historical backfill.

Multichain

The recipe is chain-parametrized — point it at any supported EVM chain or Solana. On Solana the same logIndex can be assigned to multiple events in one instruction, so the event identity is widened with (from_address, to_address, token_address, token_id, amount) to keep rows distinct; the transfer log it produces is identical in shape.

Fidelity gaps

The recipe lands only what the nftTokenTransfers array carries. Fields a Moralis NFT transfers endpoint surfaces that have no onchain source in this array are omitted:
  • Collection metadata (token_name, token_symbol) — these come from the NFT contract / a metadata indexer, not the transfer event. Out of scope for a flat transfer log; see NFT Collection Metadata for the contract-metadata pattern.
  • Address labels / entity tags (from_address_label, to_address_entity, …) — off-chain labels from a separate labelling service, not on-chain data.
  • value — the native value attached to the transfer transaction isn’t carried on the transfer event; omitted. amount (the ERC-1155 quantity) is present.
  • Pre/post NFT balances — the recipe omits the balance surface to stay a flat event stream.

NFT Trades

Marketplace sales with price — the trade-priced complement to raw transfers.

NFT Marketplace

The use case these NFT recipes power.