> ## 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 a squid to Portal

> Migrate an EVM Squid SDK indexer from legacy gateways and RPC to Portal.

This guide walks you through migrating an EVM indexer setup that uses `.setGateway()` (and optionally `.setRpcEndpoint()`) over to a Portal data source. It works whether or not you have already migrated to a Portal for historical data.

<Tip>
  **Prefer to run this with Claude Code, Codex, or another AI coding agent?** Install the migration skill in your squid's directory:

  ```bash theme={"system"}
  npx skills add subsquid-labs/skills/squid-sdk/migrate-to-portal
  ```

  Then open your agent in the same directory and ask it to migrate. The agent should find the skill; if it doesn't, stop it and tell it where to look in plain English (e.g. at `./.agents/skills/migrate-to-portal` if you installed the skill project-scoped for Claude).

  The skill detects your chain and follows the same steps as this guide. Skill source: [subsquid-labs/skills/squid-sdk/migrate-to-portal](https://github.com/subsquid-labs/skills/blob/main/squid-sdk/migrate-to-portal/SKILL.md).
</Tip>

<Note>
  If you're reading this because you have been self-hosting gateway-based squids after May 19, 2026 12:00 UTC and received an error requesting a gateway API key, please be aware that migrating to Portal is the recommended path, but not the only one. The alternative is to register and [access gateways with API keys](./gateway-api-key).
</Note>

## Prerequisites

* An existing squid based on `@subsquid/evm-processor`: will typically use `.setGateway()` and `.setRpcEndpoint()`, in rare cases without either or with a `.setPortal()` - all of these cases are covered
* Node.js 22+ and npm installed
* An SQD Portal URL for your dataset:
  * For public Portal: `https://portal.sqd.dev/datasets/<slug>` (find your slug on the [networks page](/en/data/networks/evm))
  * For a private Portal: get an URL from SQD or supply your own if self-hosting

## Migration steps

**Jump to:** [Reference templates](#reference-templates-evm) · [Common errors](#common-errors-evm)

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

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

    <Note>
      `@subsquid/archive-registry` and `lookupArchive` are deprecated. Remove the package and all `lookupArchive(...)` calls (we cover the data source replacement in Step 3).
    </Note>

    If your handler imports `decodeHex` or `assertNotNull` from `@subsquid/evm-processor`, move those imports too. They are exported only by their utility packages now (both pulled in transitively by `@subsquid/evm-stream` and `@subsquid/batch-processor`, so no extra install needed):

    ```diff theme={"system"}
    -import {decodeHex, assertNotNull} from '@subsquid/evm-processor'
    +import {decodeHex} from '@subsquid/util-internal-hex'
    +import {assertNotNull} from '@subsquid/util-internal'
    ```
  </Step>

  <Step title="Switch the processor to a Portal data source">
    In `src/processor.ts` or `src/main.ts`, swap the processor imports and initialization:

    ```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'
    ```

    ```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')
       // ... .addLog(...), .setFields(...), etc.
    +  .build()
    ```

    RPC endpoint and finality confirmation settings are no longer needed.

    <Accordion title="If your squid still uses the older `.setDataSource({archive, chain})` shape">
      Squids untouched since 2024 may use the pre-`setGateway` API:

      ```typescript theme={"system"}
      .setDataSource({
        archive: lookupArchive('eth-mainnet'),
        chain: 'https://rpc.ankr.com/eth',
      })
      ```

      The destination is the same. Replace the whole block with `.setPortal(...)` and drop the `lookupArchive` import (`@subsquid/archive-registry` is gone, see Step 1).
    </Accordion>

    <Accordion title="Have data & context type aliases exported alongside the processor object? Rewrite them here.">
      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` — previously `BlockData` — `Log`, `Transaction`, `Trace`, `StateDiff`). Three changes to be aware of:

      1. `BlockData<F>` is renamed to `Block<F>`. The single-block type previously called `Block` (just the header) is now `BlockHeader<F>`.
      2. The upstream `DataHandlerContext` lives in `@subsquid/batch-processor`. Its generic arguments are **flipped** (`DataHandlerContext<Block, Store>`, not `<Store, Fields>`) and it has only `{store, blocks, isHead}`. Add `log` (and `_chain` if you make RPC calls) yourself.
      3. The `Fields` type alias should be derived from the same `fields` constant you pass to `.setFields()`.

      ```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'

       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>
      +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 if you make direct RPC calls (see the optional step below)
      +}
      ```
    </Accordion>

    <Accordion title="If your squid uses `.setPortal()` alongside `.setRpcEndpoint()` (Portal beta participants)">
      Treat the `.setPortal` call in the same way as you would a `.setGateway` call - remove it alongside the processor initialization and set up a new one.
    </Accordion>

    <Accordion title="If your squid is not using real time data (.setGateway without .setRpcEndpoint)">
      Previously, using a gateway data source only (without RPC) enabled the regime when the squid only consumes and processes finalized data. This introduces a significant delay between the data appearing on chain and in the indexer, but allows you to forward data to append-only destinations, which is not possible with real time data. To enable this regime in Portal-powered squids, explicitly disable hot blocks support in the target database, e.g.

      ```ts theme={"system"}
      const db = new TypeormDatabase({supportHotBlocks: false})
      ```

      A data source streaming into such a target will automatically switch to ingesting from [`/finalized-stream`](/en/api/evm/finalized-stream) instead of [`/stream`](/en/api/evm/stream).
    </Accordion>
  </Step>

  <Step title="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. `DataSourceBuilder` fetches only the fields you list, and TypeScript will reject any unlisted access.
    </Warning>

    A minimum set typical for an EVM logs indexer:

    ```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`/`stateDiff` entries as needed. TypeScript errors at compile time will point you at any field still missing.

    The top-level `block.height` and `block.timestamp` shortcuts are also gone. Read them from `block.header.number` and `block.header.timestamp`. `block.header.height` still exists but is `@deprecated`.

    <Note>
      If your squid is from pre-`@subsquid/evm-processor@1.0.0` era, rename `evmLog` to `log`.
    </Note>
  </Step>

  <Step title="Update data requests">
    Rewrite data requests using the new `where-include-range` syntax:

    ```diff theme={"system"}
     .addLog({
    -  address: [USDC_CONTRACT_ADDRESS],
    -  topic0: [usdcAbi.events.Transfer.topic],
    -  transaction: true, // include the parent transaction
    +  where: {
    +    address: [USDC_CONTRACT_ADDRESS],
    +    topic0: [usdcAbi.events.Transfer.topic],
    +  },
    +  include: {
    +    transaction: true, // include the parent transaction
    +  },
       range: { from: 6_082_465 },
     })
    ```
  </Step>

  <Step title="Update the run function">
    Replace `processor.run()` with the unified `run` function. Add the import, create a logger, and enrich the context with `augmentBlock`:

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

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

    -processor.run(db, async (ctx) => {
    +run(dataSource, db, async (simpleCtx) => {
    +  const ctx = {
    +    ...simpleCtx,
    +    blocks: simpleCtx.blocks.map(augmentBlock),
    +    log: logger,
    +  }
       // ... handler body
     })
    ```

    <Accordion title="Used `.setPrometheusPort()` / `.setPrometheusServer()`? Move them here.">
      `PrometheusServer` is now passed to `run()` via the `prometheus` option instead of being attached to the processor. Construct one and hand it over; the runner still attaches the built-in `sqd_processor_*` metrics and calls `.serve()` itself.

      ```diff theme={"system"}
      -processor.setPrometheusPort(3000)
      -processor.setPrometheusServer(myServer)
      +import {PrometheusServer} from '@subsquid/batch-processor'
      +
      +const prometheus = new PrometheusServer()
      +prometheus.setPort(3000)
      +// prometheus.addMetricsSink({ register(registry) { /* register custom prom-client metrics */ } })
      +
      +run(dataSource, db, async (simpleCtx) => { /* ... */ }, {prometheus})
      ```

      `setPort()` overrides `PROCESSOR_PROMETHEUS_PORT` / `PROMETHEUS_PORT`. For custom metrics, register them on the server's registry via `addMetricsSink()` so they share the `/metrics` endpoint with `sqd_processor_*` — see [this branch](https://github.com/subsquid-labs/squid-evm-rt-template/tree/example/custom-metrics) for a worked example.
    </Accordion>
  </Step>

  <Step title="Add an RPC client">
    <Badge color="gray">Optional</Badge> Only do this if your batch handler makes [direct RPC calls](../../reference/evm-typegen/direct-rpc-queries) (contract state queries). Otherwise skip.

    Install the client, initialize an `RpcClient`, and enrich the batch context with a `_chain` field:

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

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

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

    ```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 `_chain` is part of the type your handlers see:

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

    Contract state queries then work as before:

    ```typescript theme={"system"}
    const usdcContract = new usdcAbi.Contract(ctx, blocks[0].header, USDC_CONTRACT_ADDRESS)
    const decimals = await usdcContract.decimals()
    ```
  </Step>

  <Step title="Smoke-test locally">
    Build and run the squid locally before deploying. If the first batch lands without TypeScript or missing-field runtime errors, you're ready to deploy:

    ```bash theme={"system"}
    npm run build
    npx squid-typeorm-migration apply
    node lib/main.js
    ```
  </Step>

  <Step title="Re-sync the squid">
    We highly recommend that all squids migrated to Portal are re-synced. This allows you to make sure that everything works as expected for the whole length of the chain and catch any bugs early.

    If your squid is deployed to the [Cloud](/en/cloud), follow the zero-downtime update procedure:

    1. Deploy your squid into a new slot.
    2. Wait for it to sync, observing the improved data fetching.
    3. Assign your production tag to the new deployment to redirect the GraphQL requests there.

    See [the slots and tags guide](/en/cloud/resources/slots-and-tags#zero-downtime-updates) for details.

    Can't afford a re-sync? Simply [re-deploy your squid](/en/cloud/reference/cli/deploy) without resetting its database.
  </Step>
</Steps>

<Accordion title="Common errors (EVM)">
  A few canonical TypeScript errors map back to specific steps above. If you hit one, jump to the fix:

  | Symptom                                                                                                  | Root cause                                                                               | Fix                                                                                                                                                                                      |
  | -------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
  | `Property 'transactionHash' does not exist on type 'Log'` (used `log` key)                               | Forgot to list `transactionHash: true` under `.setFields().log`                          | Add `transactionHash: true` (and any other fields you read) under `.setFields().log`                                                                                                     |
  | `Property 'height' does not exist on type 'BlockHeader'` (or `'timestamp'`)                              | Top-level `block.height`/`block.timestamp` shortcuts removed                             | Use `block.header.number` / `block.header.timestamp`. `block.header.height` still works (deprecated) if `.height` is baked into entity columns and you want a literal rename.            |
  | `'apiKey' does not exist in type 'GatewaySettings'`                                                      | You're on `evm-processor < 1.30.0` and tried to add `apiKey` before bumping the package. | Either (a) migrate to `evm-stream` / `.setPortal('...')`: Portal needs no API key today. (b) Bump `@subsquid/evm-processor` to `^1.30.0` to get the `apiKey` field on `GatewaySettings`. |
  | `Module '"@subsquid/evm-processor"' has no exported member 'decodeHex'`                                  | `decodeHex` is only exported by `@subsquid/util-internal-hex` now                        | See Step 1 *Install new packages*                                                                                                                                                        |
  | `Module '"@subsquid/evm-processor"' has no exported member 'assertNotNull'`                              | `assertNotNull` is only exported by `@subsquid/util-internal` now                        | Same step                                                                                                                                                                                |
  | `Module '"@subsquid/archive-registry"' has no exported member 'lookupArchive'` (or `Cannot find module`) | Package removed entirely                                                                 | Uninstall `@subsquid/archive-registry` and replace the whole data source block with `.setPortal(...)`                                                                                    |
  | `Object literal may only specify known properties, and 'evmLog' does not exist`                          | `.setFields()` key renamed in `evm-processor@^1.21.0`                                    | Rename `evmLog` to `log` in `.setFields()` (see *Expand the field selection*)                                                                                                            |
  | `Property 'transactionHash' does not exist on type 'Log'` (used `evmLog` key)                            | `evmLog` key no longer projects `transactionHash` onto `Log`                             | Rename `evmLog` to `log` in `.setFields()`                                                                                                                                               |
</Accordion>

## Reference templates (EVM)

If you want to diff against a known-good shape, these are the canonical post-migration templates:

<CardGroup cols={2}>
  <Card title="squid-evm-rt-template@with-logger" icon="github" href="https://github.com/subsquid-labs/squid-evm-rt-template/tree/with-logger">
    Portal data source, augmented context, no direct RPC calls
  </Card>

  <Card title="squid-evm-rt-template@with-rpc-client" icon="github" href="https://github.com/subsquid-labs/squid-evm-rt-template/tree/with-rpc-client">
    Same as above plus an `RpcClient` wired into the context for state queries
  </Card>
</CardGroup>

## Next steps (EVM)

<CardGroup cols={2}>
  <Card title="Portal EVM API" icon="book-open" href="/en/portal/evm/overview">
    Complete Portal API documentation for EVM
  </Card>

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