> ## 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.

# NFT Transfers

> Sync every ERC-721 / ERC-1155 transfer, by contract, by wallet, or for a single (contract, token_id), as a flat event log into your own database.

### Question it answers

> "Give me every NFT transfer: by contract **0x…**, by wallet **0x…**, or for a single `(contract, token_id)`."

A single template 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](#fidelity-gaps)).

### What you get

One row per NFT transfer, mapped straight from Moralis-indexed, normalized per-block onchain data:

| Column                                           | Description                                                                              |
| ------------------------------------------------ | ---------------------------------------------------------------------------------------- |
| `token_address`                                  | The NFT contract that emitted the transfer                                               |
| `token_id`                                       | The token id, a `uint256` big decimal stored as text (never numeric); compare as strings |
| `from_address`, `to_address`                     | The two sides of the transfer                                                            |
| `amount`                                         | The ERC-1155 quantity (`1` for ERC-721)                                                  |
| `contract_type`                                  | `ERC721` or `ERC1155` (the emitted `tokenType`)                                          |
| `initiated_by`                                   | The address that initiated the transfer                                                  |
| `tx_hash`                                        | Transaction that produced the transfer                                                   |
| `block_number`, `transaction_index`, `log_index` | On-chain ordering tuple                                                                  |
| `block_timestamp`                                | Block 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

| Destination                  | Table                | Read pattern                                                                                                                                    |
| ---------------------------- | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| **ClickHouse** (first-class) | `fact_nft_transfers` | Prefix scan on `(chain_id, token_address, token_id, block_number)`; by-wallet via `bloom_filter` skip indexes; read with `FINAL` or `sum(sign)` |
| **Postgres**                 | `nft_transfers`      | Index on `(token_address, block_number DESC)`, `(token_address, token_id, block_number DESC)`, and `(from_address, …)` / `(to_address, …)`      |
| **MySQL**                    | `nft_transfers`      | Same composite indexes (ascending)                                                                                                              |

ClickHouse uses the collapsing log-table pattern (see the [templates overview](/data-feeds/templates/overview#destinations)) 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 template produces. Keep the columns you need and drop the rest (see [Schema & flexibility](/data-feeds/templates/overview#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.

<Accordion title="ClickHouse, fact_nft_transfers">
  ```sql theme={null}
  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.
</Accordion>

<Accordion title="Postgres, nft_transfers">
  ```sql theme={null}
  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 template'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.
</Accordion>

### Example reads

All transfers of an NFT contract, newest first (ClickHouse):

```sql theme={null}
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):

```sql theme={null}
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:

```sql theme={null}
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](/data-feeds/templates/overview#modes)).

<Note>
  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.
</Note>

### Multichain

The template 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 template 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 template omits the balance surface to stay a flat event stream.

## Migrating from the REST API

This template replaces the wallet NFT transfers REST endpoint. Data Feeds are **not** 1:1 replicas of the old responses: you land the underlying data in your own database and reconstruct the response shape with a query. Below: what replaces the endpoint, the field mapping (**exact** = straight from the chain, **calculated** = derived from real trades, very close, **add yourself** = off-chain signal not in the feed), and the endpoint-specific gotchas.

### GET /:address/nft/transfers

A wallet's NFT transfer history. Replaced by this template's `nft_transfers` table: a wallet's transfers are the rows where it appears on either side, and the old endpoint's `contract_addresses`, `from_block`/`to_block`, and `order` parameters become plain `WHERE` / `ORDER BY` clauses.

<Warning>
  Reproducing the full REST response needs **three templates**, not one. The transfer event carries no collection metadata or sale context. Run this template plus [NFT Collection Metadata](/data-feeds/templates/nft/nft-collection-metadata) (`token_name` / `token_symbol`) and [NFT Trades](/data-feeds/templates/nft/nft-trades) (`last_sale`) against the same database and join them.
</Warning>

| `/:address/nft/transfers`                                                               | Data Feeds                                                                               | Fidelity   |
| --------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | ---------- |
| `token_address`, `token_id`, `contract_type`                                            | `nft_transfers.*`                                                                        | exact      |
| `from_address`, `to_address`, `amount`                                                  | `nft_transfers.*`                                                                        | exact      |
| `operator`                                                                              | `nft_transfers.initiated_by`                                                             | exact      |
| `transaction_hash`, `block_number`, `block_timestamp`, `transaction_index`, `log_index` | `nft_transfers.tx_hash` / `nft_transfers.*`                                              | exact      |
| `value` (tx native value), `transaction_type` (Single/Batch)                            | the `nftTokenTransfers` source array, not the flat table (or `nftTrades` for sale price) | exact      |
| `token_name`, `token_symbol`                                                            | `nft_collection_metadata.*`                                                              | exact      |
| `last_sale` (sale facts)                                                                | `nftTrades`                                                                              | exact      |
| `last_sale` USD values                                                                  | `nftTrades` + price feed                                                                 | calculated |
| `possible_spam`, `verified_collection`                                                  | add yourself                                                                             | off-chain  |
| `from/to_address_label`, `*_entity`, `*_entity_logo`                                    | add yourself                                                                             | off-chain  |

The reconstruction query (Postgres; adapt to your destination):

```sql theme={null}
SELECT t.*,
       c.name AS token_name, c.symbol AS token_symbol,
       CASE WHEN t.from_address = lower('0xWALLET') THEN 'send' ELSE 'receive' END AS direction
FROM nft_transfers t
LEFT JOIN nft_collection_metadata c ON c.token_address = t.token_address
WHERE (t.from_address = lower('0xWALLET') OR t.to_address = lower('0xWALLET'))
ORDER BY t.block_number DESC, t.log_index DESC
LIMIT 100;
```

**Gotchas**

* **Direction is yours to derive**: the feed has no `send`/`receive` flag; compare the wallet to `from_address`/`to_address` as above. The template indexes both sides, so the OR match stays fast.
* The address labels and spam/verification flags are off-chain signals with no onchain source: drop them, bring your own lists, or layer in a label source.
* `token_id` is a `uint256` stored as text; compare as strings, never as numbers.
* Backfill depth is your choice: transfers are an event list, not current state, so index only as much history as your app serves.

### Related

<Columns cols={2}>
  <Card title="NFT Trades" href="/data-feeds/templates/nft/nft-trades" icon="tags">
    Marketplace sales with price, the trade-priced complement to raw transfers.
  </Card>

  <Card title="NFT Marketplace" href="/data-feeds/use-cases/nft-marketplace" icon="store">
    The use case these NFT templates power.
  </Card>
</Columns>
