> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sqd.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Step 1: Transfer events

> Index BAYC ERC-721 Transfer events on Ethereum with the Squid SDK EVM Portal stream.

# Step 1: Indexing Transfer events

In this step-by-step tutorial we will build a squid that gets data about [Bored Ape Yacht Club](https://boredapeyachtclub.com) NFTs, their transfers and owners from the [Ethereum blockchain](https://ethereum.org), indexes the NFT metadata from [IPFS](https://ipfs.tech/) and regular HTTP URLs, stores all the data in a database and serves it over a GraphQL API. Here we do the first step: build a squid that indexes only the `Transfer` events emitted by the [BAYC token contract](https://etherscan.io/address/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d).

Pre-requisites: Node.js, [Squid CLI](/en/cloud/reference/cli/installation), Docker.

## Starting with a template

Begin by retrieving the `evm` template and installing the dependencies:

```bash theme={"system"}
sqd init my-bayc-squid -t evm
cd my-bayc-squid
npm i
```

The resulting code can be found at [this commit](https://github.com/subsquid-labs/bayc-squid-1/tree/4ad7bdfed0771aa6b6e9cf0368ac7f15f09cd629).

The template ships with the packages we will use throughout the tutorial: `@subsquid/evm-stream` (the EVM data source), `@subsquid/evm-objects` (block augmentation utilities), `@subsquid/batch-processor` (the data processing loop), `@subsquid/logger` and `@subsquid/typeorm-store`. If you are starting from a bare project, install them with

```bash theme={"system"}
npm i @subsquid/evm-stream @subsquid/evm-objects @subsquid/batch-processor @subsquid/logger @subsquid/typeorm-store
```

## Interfacing with the contract ABI

First, we inspect which data is available for indexing. For EVM contracts, the metadata describing the shape of the smart contract logs, transactions and contract state methods is distributed as an [Application Binary Interface](https://docs.soliditylang.org/en/latest/abi-spec.html) (ABI) JSON file. For many popular contracts ABI files are published on Etherscan (as in the case of the BAYC NFT contract). SQD provides a [tool](../../reference/evm-typegen/generating-utility-modules) for retrieving contract ABIs from Etherscan-like APIs and generating the boilerplate for retrieving and decoding the data. For the contract of interest, this can be done with

```bash theme={"system"}
npx squid-evm-typegen src/abi 0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d#bayc
```

Here, `src/abi` is the destination folder and the `bayc` suffix sets the base name for the generated module. The generated code imports `@subsquid/evm-abi` and `@subsquid/evm-codec`; the template has them among its dependencies, but if you started from a bare project add them with `npm i @subsquid/evm-abi @subsquid/evm-codec`.

Checking out the generated `src/abi/bayc/events.ts` file we see all events listed in the ABI. Among them there is the `Transfer` event:

```typescript theme={"system"}
export const Transfer = event('0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', {
    from: indexed(address),
    to: indexed(address),
    tokenId: indexed(uint256),
})
```

The whole module is re-exported by `src/abi/bayc/index.ts`, so `import * as bayc from './abi/bayc'` gives access to `bayc.events`, `bayc.functions` and the `bayc.Contract` class. Reading about the event [elsewhere](https://eips.ethereum.org/EIPS/eip-721) we learn that it is emitted every time an NFT changes hand and that its [logs](https://medium.com/mycrypto/understanding-event-logs-on-the-ethereum-blockchain-f4ae7ba50378) contain the addresses of both involved parties, as well as the unique ID of the token. This is the data we need, so we proceed to configure our squid to retrieve it.

## Configuring the data filters

A "squid processor" is the Node.js [process](../../design) responsible for retrieving filtered blockchain data from a specialized data lake ([SQD Network](/en/network/overview)), transforming it and saving the result to a destination of choice. What data it retrieves is governed by a *data source* object. To make one that retrieves the `Transfer` events of the BAYC token contract, we configure and build it like this:

```typescript title="src/processor.ts" theme={"system"}
import {DataSourceBuilder} from '@subsquid/evm-stream'
import * as bayc from './abi/bayc'

export const CONTRACT_ADDRESS = '0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d'

export const dataSource = new DataSourceBuilder()
    .setPortal('https://portal.sqd.dev/datasets/ethereum-mainnet')
    .setBlockRange({
        from: 12_287_507,
    })
    .setFields({
        block: {
            timestamp: true
        },
        log: {
            address: true,
            topics: true,
            data: true,
            transactionHash: true
        }
    })
    .addLog({
        where: {
            address: [CONTRACT_ADDRESS],
            topic0: [bayc.events.Transfer.topic]
        }
    })
    .build()
```

Here,

* `'https://portal.sqd.dev/datasets/ethereum-mainnet'` is the URL of the public [SQD Network Portal](/en/network/overview) dataset for Ethereum mainnet. Portal is public - no API key is needed. Check out the exhaustive [supported networks list](/en/data/evm). The Portal stream covers the whole range from the first requested block up to the chain head, including real-time unfinalized blocks - no separate RPC endpoint is needed for data ingestion.
* `12_287_507` is the block at which the BAYC token contract was deployed. Can be found on the [contract's Etherscan page](https://etherscan.io/address/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d).
* The `where` filters in the argument of [`addLog()`](../../reference/evm-stream/logs) tell the data source to retrieve all event logs emitted by the BAYC contract with topic0 matching the hash of the full signature of the `Transfer` event. The hash is taken from the previously generated Typescript ABI.
* The argument of [`setFields()`](../../reference/evm-stream/field-selection) specifies the exact data we need on every retrieved item. There are no default fields, so we list everything our handler code will read: `address` and `topics` for filtering and decoding the logs, `data` for decoding, `transactionHash` for saving alongside the decoded event, plus the block `timestamp`.

See [EVM Portal stream](../../reference/evm-stream) for more options.

## Decoding the event data

The other part of squid processor configuration is the callback function used to process batches of the filtered data, the [batch handler](../../reference/batch-processor#run-and-processor). It is supplied to the `run()` function from `@subsquid/batch-processor`, typically at `src/main.ts`:

```typescript theme={"system"}
import {run} from '@subsquid/batch-processor'

run(dataSource, db, async (ctx) => {
    // data transformation and persistence code here
})
```

Here,

* `dataSource` is the data source object we just configured.
* `db` is a [`Database` implementation](../../reference/data-stores/store-interface) specific to the target data sink. We want to store the data in a PostgreSQL database and present with a GraphQL API, so we provide a [`TypeormDatabase`](../../guides/writing-to-postgres) object here. With `{supportHotBlocks: true}` it accepts the real-time unfinalized blocks served by the Portal and automatically rolls back any changes on [chain reorganizations](../../guides/advanced/unfinalized-blocks) - the handler code never sees forks.
* `ctx` is a [handler context](../../reference/batch-processor#handler-context) object that exposes a batch of data retrieved from SQD Network (at `ctx.blocks`) and any data persistence facilities derived from `db` (at `ctx.store`).

Batch handler is where the raw on-chain data is decoded, transformed and persisted. This is the part we'll be concerned with for the rest of the tutorial.

We begin by defining a batch handler decoding the `Transfer` event:

```typescript title="src/main.ts" theme={"system"}
import {run} from '@subsquid/batch-processor'
import {augmentBlock} from '@subsquid/evm-objects'
import {createLogger} from '@subsquid/logger'
import {TypeormDatabase} from '@subsquid/typeorm-store'
import {dataSource, CONTRACT_ADDRESS} from './processor'
import * as bayc from './abi/bayc'

const logger = createLogger('sqd:processor:mapping')

run(dataSource, new TypeormDatabase({supportHotBlocks: true}), async (ctx) => {
    let blocks = ctx.blocks.map(augmentBlock)

    for (let block of blocks) {
        for (let log of block.logs) {
            if (log.address === CONTRACT_ADDRESS && log.topics[0] === bayc.events.Transfer.topic) {
                let {from, to, tokenId} = bayc.events.Transfer.decode(log)
                logger.info(`Parsed a Transfer of token ${tokenId} from ${from} to ${to}`)
            }
        }
    }
})
```

This goes through all the log items in the batch, verifies that they indeed are `Transfer` events emitted by the BAYC contract and decodes the data of each log item, then logs the results to the terminal. The verification step is required because the data source does not guarantee that it won't supply any extra data, only that it *will* supply the data matching the filters. The decoding is done with the `bayc.events.Transfer.decode()` function from the Typescript ABI we previously generated.

Note two utilities used here:

* `augmentBlock()` from `@subsquid/evm-objects` decorates the raw blocks with item IDs and navigation helpers (such as `log.getTransaction()`). We will rely on the IDs it adds shortly.
* `createLogger()` from `@subsquid/logger` makes a namespaced structured logger. The handler context does not provide one, so we declare it once at the module level.

At this point the squid is ready for its first test run. Execute

```bash theme={"system"}
docker compose up -d
npm run build
npx squid-typeorm-migration apply
node -r dotenv/config lib/main.js
```

and you should see lots of lines like these in the output:

```
03:56:02 INFO  sqd:processor:mapping Parsed a Transfer of token 6325 from 0x0000000000000000000000000000000000000000 to 0xb136c6a1eb83d0b4b8e4574f28e622a57f8ef01a
03:56:02 INFO  sqd:processor:mapping Parsed a Transfer of token 6326 from 0x0000000000000000000000000000000000000000 to 0xb136c6a1eb83d0b4b8e4574f28e622a57f8ef01a
03:56:02 INFO  sqd:processor:mapping Parsed a Transfer of token 4407 from 0x082c99d47e020a00c95460d50a83338d509a0e3a to 0x5cf0e6da6ec2bd7165edcd52d3d31f2528dcf007
```

The full code can be found at [this commit](https://github.com/subsquid-labs/bayc-squid-1/tree/b7ccfc06b283a885878cddcb18483c4bf571e97c).

## Extending and persisting the data

`TypeormDatabase` requires us to define a TypeORM data model to actually send the data to the database. In SQD, the same data model is also used by the GraphQL server to generate the API schema. To avoid any potential discrepancies, processor and GraphQL server rely on a shared data model description defined at `schema.graphql` in a GraphQL schema dialect fully documented [here](../../reference/schema-files/schema-files-codegen).

TypeORM code is generated from `schema.graphql` with the [`squid-typeorm-codegen`](../../reference/typeorm-codegen) tool and must be regenerated every time the schema is changed. This is usually accompanied by regenerating the [database migrations](../../reference/typeorm-migration) and recreating the database itself. The migrations are applied before every run of the processor, ensuring that whenever any TypeORM code within the processor attempts to access the database, the database is in a state that allows it to succeed.

The main unit of data in `schema.graphql` is [entity](../../reference/schema-files/entities). These map onto [TypeORM entites](https://typeorm.io/entities) that in turn map onto database tables. We define one for `Transfer` events by replacing the file contents with

```graphql title="schema.graphql" theme={"system"}
type Transfer @entity {
    id: ID!
    tokenId: BigInt! @index
    from: String! @index
    to: String! @index
    timestamp: DateTime!
    blockNumber: Int!
    txHash: String! @index
}
```

Here,

* The `id` is a required field and the `ID` type is an alias for `String`. Entity IDs must be unique.
* `@index` decorators tell the codegen tool that the corresponding database columns should be indexed.
* Extra fields `timestamp` and `blockNumber` are added to make the resulting GraphQL API more convenient. We will fill them using the block metadata available in `ctx`.

Once we're done editing the schema, we regenerate the TypeORM code, recreate the database and regenerate the migrations:

```bash theme={"system"}
npx squid-typeorm-codegen
npm run build
docker compose down
docker compose up -d
rm -r db/migrations
npx squid-typeorm-migration generate
```

The generated code is in `src/model`. We can now import a `Transfer` entity class from there and use it to perform [various operations](../../reference/data-stores/typeorm-store) on the corresponding database table. Let us rewrite our batch handler to save the parsed `Transfer` events data to the database:

```typescript title="src/main.ts" theme={"system"}
import {run} from '@subsquid/batch-processor'
import {augmentBlock} from '@subsquid/evm-objects'
import {createLogger} from '@subsquid/logger'
import {TypeormDatabase} from '@subsquid/typeorm-store'
import {dataSource, CONTRACT_ADDRESS} from './processor'
import * as bayc from './abi/bayc'
import {Transfer} from './model'

const logger = createLogger('sqd:processor:mapping')

run(dataSource, new TypeormDatabase({supportHotBlocks: true}), async (ctx) => {
    let blocks = ctx.blocks.map(augmentBlock)
    let transfers: Transfer[] = []

    for (let block of blocks) {
        for (let log of block.logs) {
            if (log.address === CONTRACT_ADDRESS && log.topics[0] === bayc.events.Transfer.topic) {
                let {from, to, tokenId} = bayc.events.Transfer.decode(log)
                transfers.push(new Transfer({
                    id: log.id,
                    tokenId,
                    from,
                    to,
                    timestamp: new Date(block.header.timestamp),
                    blockNumber: block.header.number,
                    txHash: log.transactionHash,
                }))
            }
        }
    }

    await ctx.store.insert(transfers)
})
```

Note a few things here:

* A unique event log ID is available at `log.id`, courtesy of `augmentBlock()` - no need to generate your own!
* `tokenId` returned from the decoder is a `bigint`, matching the `BigInt!` type of the schema field, so no conversion is needed. In general the decoder output types follow the ABI: `uint256` becomes `bigint`, `address` becomes a hex `string` and so on.
* `block.header` contains block metadata that we use to fill the extra fields. The block height is at `block.header.number`; `timestamp` is in milliseconds, ready for the `Date` constructor.
* Accumulating the `Transfer` entity instances before using `ctx.store.insert()` on the whole array of them in the end allows us to get away with just one database transaction per batch. This is [crucial for achieving a good syncing performance](../../guides/advanced/batch-processing).

At this point we have a squid that indexes the data on BAYC token transfers and is capable of serving it over a GraphQL API. Full code is available at [this commit](https://github.com/subsquid-labs/bayc-squid-1/tree/aeb6268168385cc605ce04fe09d0159f708efe47). Test it by running

```bash theme={"system"}
npm run build
npx squid-typeorm-migration apply
node -r dotenv/config lib/main.js
```

then

```bash theme={"system"}
npx squid-graphql-server
```

in a separate terminal. If all is well, a GraphiQL playground should become available at [localhost:4350/graphql](http://localhost:4350/graphql).

<img src="https://mintcdn.com/sqd-2119b3c3/EyQToZpBVXIKiJeI/en/sdk/squid-sdk/evm/examples-tutorials/bayc/bayc-playground-step-one.png?fit=max&auto=format&n=EyQToZpBVXIKiJeI&q=85&s=cb456198dfbe0e13fb934ff86f2fd89e" alt="GraphiQL playground with a transfers query" width="918" height="434" data-path="en/sdk/squid-sdk/evm/examples-tutorials/bayc/bayc-playground-step-one.png" />

In the [next step](./step-two-deriving-owners-and-tokens) we will derive `Owner` and `Token` entities from the raw transfer data.
