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

# Migrate SDK squids to Portal

> Migrate your real-time EVM indexer to Portal for better performance and reliability

<Card title="← Back to Portal Setup" icon="arrow-left" href="/en/portal/migration">
  Return to the Portal setup overview to explore other deployment options.
</Card>

This guide walks you through migrating EVM indexer setups that use RPC for real-time data ingestion to ingesting real-time data from SQD Network Portals. The guide works regardless of whether you have already migrated to using a portal for historical data.

## Prerequisites

Before starting, ensure you have:

* An existing EVM indexer using `@subsquid/evm-processor`
* Node.js and npm installed
* Access to an SQD Portal

## Migration Steps

<Steps>
  <Step title="Install New Packages">
    Replace the old processor package with the new packages:

    ```bash theme={"system"}
    npm uninstall @subsquid/evm-processor
    npm i @subsquid/evm-stream @subsquid/evm-objects @subsquid/batch-processor @subsquid/logger
    ```
  </Step>

  <Step title="Verify Portal Support">
    Make sure you have an SQD portal URL for your dataset and that real-time data is supported.

    Public Portal URLs follows this pattern:

    ```
    https://portal.sqd.dev/datasets/<dataset-slug>
    ```

    where `<dataset-slug>` is found on [the networks page](/en/data/networks/evm).
  </Step>

  <Step title="Update Processor Configuration">
    Replace your processor configuration (typically at `src/processor.ts` or `src/main.ts`) with a data source configuration.

    ### Update Imports

    Replace the processor imports with the new data source and object imports:

    ```diff theme={"system"}
    -import {EvmBatchProcessor} from '@subsquid/evm-processor'
    +import {DataSourceBuilder} from '@subsquid/evm-stream'
    +import {augmentBlock} from '@subsquid/evm-objects'
    +import {createLogger} from '@subsquid/logger'
    ```

    If your project re-exports data types (typically from `src/processor.ts`), update those imports too. The data types are now split across two packages:

    * `@subsquid/evm-stream` exports `FieldSelection`.
    * `@subsquid/evm-objects` exports the augmented block data types: `BlockHeader`, `Block` (the parent of `transactions`/`logs`/etc., previously called `BlockData`), `Log`, `Transaction`, `Trace`, `StateDiff`.

    ```diff theme={"system"}
    -import {
    -  BlockHeader,
    -  DataHandlerContext,
    -  EvmBatchProcessor,
    -  Log as _Log,
    -  Transaction as _Transaction,
    -  BlockData as _BlockData,
    -  FieldSelection,
    -} from '@subsquid/evm-processor'
    +import * as evmObjects from '@subsquid/evm-objects'
    +import {DataSourceBuilder, FieldSelection} from '@subsquid/evm-stream'
    +import type {DataHandlerContext as BaseDataHandlerContext} from '@subsquid/batch-processor'
    +import type {Logger} from '@subsquid/logger'
    ```

    ### Replace Processor Initialization

    Replace `EvmBatchProcessor` initialization with `DataSourceBuilder`:

    ```diff theme={"system"}
    -const processor = new EvmBatchProcessor()
    -  .setGateway('https://v2.archive.subsquid.io/network/ethereum-mainnet')
    -  .setRpcEndpoint('https://rpc.ankr.com/eth')
    -  .setFinalityConfirmation(75)
    +const dataSource = new DataSourceBuilder()
    +  .setPortal('https://portal.sqd.dev/datasets/ethereum-mainnet')
    ```

    <Note>
      RPC endpoint and finality confirmation settings are no longer needed.
    </Note>

    ### Update Type Exports

    If `src/processor.ts` re-exports `Fields`/`Block`/`BlockData`/`Log`/`Transaction` and a per-project context alias (often called `ProcessorContext`), rewrite them in terms of the new packages. There are three changes to be aware of:

    1. `BlockData<F>` is renamed to `Block<F>` in `@subsquid/evm-objects`. The single-block type that used to be called `Block` (just the header) is now `BlockHeader<F>`.
    2. The upstream `DataHandlerContext` lives in `@subsquid/batch-processor`. Its generic arguments are **flipped** — it is `DataHandlerContext<Block, Store>`, not `DataHandlerContext<Store, Fields>`. It also has only `{store, blocks, isHead}` — no `log` and no `_chain`. Add those yourself when defining your project's enriched context type. With the original `DataHandlerContext` no longer in your imports, this is also a natural time to rename your project alias to `DataHandlerContext` — the name fits the role better than `ProcessorContext` now that there is no processor object.
    3. The `Fields` type alias should be derived from the same `fields` constant you pass to `.setFields()`.

    ```diff theme={"system"}
    -const fields = {
    -  log: {
    -    transactionHash: true,
    -  },
    -} satisfies FieldSelection
    -
    -export type Fields = typeof fields
    -export type Block = BlockHeader<Fields>
    -export type BlockData = _BlockData<Fields>
    -export type Log = _Log<Fields>
    -export type Transaction = _Transaction<Fields>
    -export type ProcessorContext<Store> = DataHandlerContext<Store, Fields>
    +const fields = {
    +  log: {
    +    transactionHash: true,
    +  },
    +} satisfies FieldSelection
    +
    +export type Fields = typeof fields
    +export type Block = evmObjects.BlockHeader<Fields>
    +export type BlockData = evmObjects.Block<Fields>
    +export type Log = evmObjects.Log<Fields>
    +export type Transaction = evmObjects.Transaction<Fields>
    +export type DataHandlerContext<Store> = BaseDataHandlerContext<BlockData, Store> & {
    +  log: Logger
    +  // include `_chain` here as well if you make direct RPC calls — see the optional step below
    +}
    ```

    ### Expand the Field Selection

    <Warning>
      **Every field your handler reads must be listed in `.setFields()`.** With `EvmBatchProcessor`, common fields like `log.address`, `log.topics`, `log.data`, `block.timestamp` and `transaction.from/to/hash` were merged in from a built-in default set, so partial selections worked even when the handler accessed unlisted fields. In `DataSourceBuilder` only the fields you list are fetched from the portal and, at the type level, `Log<F>` / `BlockHeader<F>` / `Transaction<F>` expose only those fields. TypeScript will reject `log.address`, `log.topics`, `log.data` or `block.header.timestamp` accesses if you forgot to request them.
    </Warning>

    If your previous `fields` constant relied on the merged defaults, expand it to enumerate every field your handler actually reads. A minimum set typical for an EVM logs indexer looks like:

    ```typescript theme={"system"}
    const fields = {
      block: {
        timestamp: true,
      },
      log: {
        address: true,
        topics: true,
        data: true,
        transactionHash: true, // and anything else you were already selecting
      },
    } satisfies FieldSelection
    ```

    Add transaction/trace/state-diff entries as needed — TypeScript errors at compile time will point you at any field still missing from the selection.

    ### Update Data Requests

    Rewrite data requests using the new `where-include-range` syntax:

    <CodeGroup>
      ```typescript Old Style theme={"system"}
      // Old flat object syntax
      .addLog({
        address: [USDC_CONTRACT_ADDRESS],
        topic0: [usdcAbi.events.Transfer.topic],
        transaction: true, // include the parent transaction
        range: { from: 6_082_465 },
      })
      ```

      ```typescript New Style theme={"system"}
      // New separated syntax
      .addLog({
        where: {
          address: [USDC_CONTRACT_ADDRESS],
          topic0: [usdcAbi.events.Transfer.topic],
        },
        include: {
          transaction: true, // include the parent transaction
        },
        range: { from: 6_082_465 },
      })
      ```
    </CodeGroup>

    ### Build the Data Source

    Include a `.build()` call at the end of the data source initialization:

    ```diff theme={"system"}
    .setFields({
      log: {
        transactionHash: true,
      },
    })
    +.build()
    ```

    <Tip>
      If you pass your processor object between source files (e.g., from `src/processor.ts` to `src/main.ts`), pass the `dataSource` object in the same way.
    </Tip>
  </Step>

  <Step title="Update the Run Function">
    Replace the `processor.run()` call with the unified `run` function.

    ### Import the Run Function

    Add the `run` function import to your main file:

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

    ### Create a Logger

    Manually create a logger for your batch handler:

    ```typescript theme={"system"}
    const logger = createLogger('sqd:processor:mapping')
    ```

    ### Replace processor.run()

    Update the run call and enrich the context:

    ```diff theme={"system"}
    -processor.run(db, async (ctx) => {
    +run(dataSource, db, async (simpleCtx) => {
    +  const ctx = {
    +    ...simpleCtx,
    +    blocks: simpleCtx.blocks.map(augmentBlock),
    +    log: logger
    +  }
    ```

    <AccordionGroup>
      <Accordion title="View Complete Diff Example">
        Here's a full example of the changes up to this point:

        ```diff theme={"system"}
        --- a/src/main.ts
        +++ b/src/main.ts
        @@ -1,29 +1,43 @@
        -import {EvmBatchProcessor} from '@subsquid/evm-processor'
        +import {DataSourceBuilder} from '@subsquid/evm-stream'
        +import {augmentBlock} from '@subsquid/evm-objects'
        +import {run} from '@subsquid/batch-processor'
        +import {createLogger} from '@subsquid/logger'
         import {TypeormDatabase} from '@subsquid/typeorm-store'
         import * as usdcAbi from './abi/usdc'
         import {UsdcTransfer} from './model'
         
         const USDC_CONTRACT_ADDRESS = '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'
         
        -const processor = new EvmBatchProcessor()
        -  .setGateway('https://v2.archive.subsquid.io/network/ethereum-mainnet')
        -  .setRpcEndpoint('https://rpc.ankr.com/eth')
        -  .setFinalityConfirmation(75)
        +const dataSource = new DataSourceBuilder()
        +  .setPortal('https://portal.sqd.dev/datasets/ethereum-mainnet')
           .addLog({
        +    where: {
        +      address: [USDC_CONTRACT_ADDRESS],
        +      topic0: [usdcAbi.events.Transfer.topic],
        +    },
        +    include: {
        +      transaction: true, // include the parent transaction
        +    },
             range: { from: 6_082_465 },
        -    address: [USDC_CONTRACT_ADDRESS],
        -    topic0: [usdcAbi.events.Transfer.topic],
        -    transaction: true, // include the parent transaction
           })
           .setFields({
             log: {
               transactionHash: true,
             },
           })
        +  .build()
         
         const db = new TypeormDatabase({supportHotBlocks: true})
         
        -processor.run(db, async (ctx) => {
        +const logger = createLogger('sqd:processor:mapping')
        +
        +run(dataSource, db, async (simpleCtx) => {
        +  const ctx = {
        +    ...simpleCtx,
        +    blocks: simpleCtx.blocks.map(augmentBlock),
        +    log: logger,
        +  }
        +
           const transfers: UsdcTransfer[] = []
           for (let block of ctx.blocks) {
             for (let log of block.logs) {
        ```
      </Accordion>
    </AccordionGroup>
  </Step>

  <Step title="Add RPC Client (Optional)">
    <Warning>
      Only complete this step if you use direct RPC calls in your batch handler code. If you don't make direct contract state queries, skip this step.
    </Warning>

    If you use [direct RPC calls](/en/sdk/squid-sdk/resources/tools/typegen/state-queries?typegen=evm) in your batch handler, you'll need to add an RPC client to your context.

    ### Install RPC Client

    ```bash theme={"system"}
    npm i @subsquid/rpc-client
    ```

    ### Initialize RPC Client

    Import and initialize an `RpcClient`:

    ```typescript theme={"system"}
    import {RpcClient} from '@subsquid/rpc-client'

    const rpcClient = new RpcClient({
      url: 'https://my_rpc_url',
      rateLimit: 100
    })
    ```

    ### Enrich Context

    In your batch handler, add the `_chain` field to the context:

    ```diff theme={"system"}
      const ctx = {
        ...simpleCtx,
        blocks: simpleCtx.blocks.map(augmentBlock),
        log: logger,
    +    _chain: {
    +      client: rpcClient
    +    }
      }
    ```

    If you keep a `DataHandlerContext<Store>` alias in `src/processor.ts`, extend it so the `_chain` field is part of the type your handlers see:

    ```diff theme={"system"}
     export type DataHandlerContext<Store> = BaseDataHandlerContext<BlockData, Store> & {
       log: Logger
    +  _chain: { client: RpcClient }
     }
    ```

    ```typescript theme={"system"}
    import type {RpcClient} from '@subsquid/rpc-client'
    ```

    Now your contract state queries will work as before:

    ```typescript theme={"system"}
    const usdcContract = new usdcAbi.Contract(
      ctx,
      blocks[0].header,
      USDC_CONTRACT_ADDRESS
    )
    // Query decimals via direct call to state at blocks[0].header.height
    const decimals = await usdcContract.decimals()
    ```
  </Step>
</Steps>

## Migration Complete

<Check>
  Your indexer is now ready to source real-time data from an SQD Network portal. The portal provides improved performance and reliability compared to RPC endpoints.
</Check>

## Example Migrations

Complete migration examples for a simple USDC transfers indexer are available:

<CardGroup cols={2}>
  <Card title="Without RPC Client" icon="github" href="https://github.com/subsquid-labs/squid-evm-rt-template/tree/with-logger">
    Basic migration example without RPC client integration
  </Card>

  <Card title="With RPC Client" icon="github" href="https://github.com/subsquid-labs/squid-evm-rt-template/tree/with-rpc-client">
    Migration example including RPC client for state queries
  </Card>
</CardGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Portal API Reference" icon="book-open" href="/en/portal/evm/overview">
    Explore the complete Portal API documentation
  </Card>

  <Card title="EVM Examples" icon="code" href="/en/portal/evm/examples/query-logs">
    See more examples of Portal API usage
  </Card>
</CardGroup>
