> ## 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 a Solana Squid SDK indexer from legacy gateways and RPC to Portal.

This guide walks you through migrating a Solana indexer setup that uses `.setGateway()` (and optionally `.setRpc()`) 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 self-host and hit a gateway API-key error after May 19, 2026 12:00 UTC, note that the Solana gateway is itself being retired — it [shrinks to the last 30 days of data on June 1, 2026 and is scheduled for full removal later this year](/announcements/gateway-deprecations-may-2026). Adding a key only buys a little time; Portal is the durable path, so migrate now.
</Note>

## Prerequisites

* An existing squid based on `@subsquid/solana-stream`: using `.setGateway()` and/or `.setRpc()`
* 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 template](#reference-template-solana) · [Common errors](#common-errors-solana)

<Steps>
  <Step title="Stop using the RPC client">
    Remove the `SolanaRpcClient` import and the `.setRpc({...})` call from your data source. Your project still compiles on your current `@subsquid/solana-stream` version after this edit; once you bump to `^1.x.x` in the next step (where `SolanaRpcClient` and `.setRpc()` disappear), you avoid a broken intermediate state.

    ```diff theme={"system"}
    -import {DataSourceBuilder, SolanaRpcClient} from '@subsquid/solana-stream'
    +import {DataSourceBuilder} from '@subsquid/solana-stream'
    ```

    ```diff theme={"system"}
       const dataSource = new DataSourceBuilder()
         .setGateway('https://v2.archive.subsquid.io/network/solana-mainnet')
    -    .setRpc({
    -      client: new SolanaRpcClient({
    -        url: process.env.SOLANA_NODE
    -      })
    -    })
    ```
  </Step>

  <Step title="Upgrade SDK packages">
    From your squid's folder, upgrade every `@subsquid/*` package to its latest release:

    ```bash theme={"system"}
    npx --yes npm-check-updates --filter "@subsquid/*" --target "@latest" --upgrade
    ```

    This should bump `@subsquid/solana-stream` to a `1.x.x` version.

    Then install:

    <Tabs>
      <Tab title="NPM">
        ```bash theme={"system"}
        npm install
        ```
      </Tab>

      <Tab title="Yarn">
        ```bash theme={"system"}
        yarn install
        ```
      </Tab>

      <Tab title="PNPM">
        ```bash theme={"system"}
        pnpm install
        ```
      </Tab>
    </Tabs>
  </Step>

  <Step title="Switch the data source to Portal">
    Replace the gateway URL with the Portal URL:

    ```diff theme={"system"}
    +  .setPortal({
    +    url: 'https://portal.sqd.dev/datasets/solana-mainnet',
    +      http: {
    +        retryAttempts: Infinity
    +      }
    +   })
    -  .setGateway('https://v2.archive.subsquid.io/network/solana-mainnet')
    ```

    (The `SolanaRpcClient` import and `.setRpc({...})` call are already gone from Step 1.)

    <Accordion title="Have data & context type aliases exported alongside the processor object? Rewrite them here.">
      If your project re-exports `Fields`/`Block`/`Transaction`/`Context` aliases that handlers consume, re-derive them against the new packages:

      ```diff theme={"system"}
      -import {DataSourceBuilder, FieldSelection, Block as _Block, Transaction as _Transaction, Instruction as _Instruction} from '@subsquid/solana-stream'
      +import {DataSourceBuilder, FieldSelection} from '@subsquid/solana-stream'
      +import * as solanaObjects from '@subsquid/solana-objects'
      +import type {DataHandlerContext as BaseDataHandlerContext} from '@subsquid/batch-processor'

       const fields = {
         block: {timestamp: true},
         transaction: {signatures: true, err: true, accountKeys: true},
       } satisfies FieldSelection

       export type Fields = typeof fields
      -export type Block = _Block<Fields>
      -export type Transaction = _Transaction<Fields>
      -export type Instruction = _Instruction<Fields>
      -export type Context = DataHandlerContext<Store, Fields>
      +export type BlockHeader = solanaObjects.BlockHeader<Fields>
      +export type Block = solanaObjects.Block<Fields>
      +export type Transaction = solanaObjects.Transaction<Fields>
      +export type Instruction = solanaObjects.Instruction<Fields>
      +export type DataHandlerContext<Store> = BaseDataHandlerContext<Block, Store>
      ```

      Use the [solana-example template](https://github.com/subsquid-labs/solana-example/tree/master) as a reference if your squid has unusual aliases.
    </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/solana/finalized-stream) instead of [`/stream`](/en/api/solana/stream).
    </Accordion>
  </Step>

  <Step title="Keep block ranges in slots">
    `DataSourceBuilder.setBlockRange()` uses the same slot-number coordinate returned
    as `block.header.number`. Keep existing slot ranges unchanged. There is no separate
    contiguous Portal-height coordinate for this API.
  </Step>

  <Step title="Replace `block.header.slot` reads">
    If your handler code reads the `slot` field of a block header, rename it to `.number`:

    ```diff theme={"system"}
    -  slot: block.header.slot,
    +  slot: block.header.number,
    ```
  </Step>

  <Step title="Expand the field selection">
    <Warning>
      **`tokenBalance.preMint` / `postMint` are the #1 silent break on real Solana migrations.** DEX squids that read these to recover swap-side mints will hit `Property 'preMint' does not exist on type 'TokenBalance'` until you add them to `.setFields().tokenBalance`. Pre-1.x `@subsquid/solana-stream` merged them in for free; the current release returns only the fields you list.
    </Warning>

    `transaction.accountKeys` was never in the old SDK's defaults either — add it if you read the fee payer via `tx.accountKeys[0]`.

    <Accordion title="Full set of fields the old `@subsquid/solana-stream` used to merge in for free">
      | Object         | Fields merged in by the old SDK                                                                                                                                            |
      | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
      | `block`        | `timestamp`                                                                                                                                                                |
      | `transaction`  | `signatures`, `err`                                                                                                                                                        |
      | `instruction`  | `programId`, `accounts`, `data`, `isCommitted`                                                                                                                             |
      | `log`          | `programId`, `kind`, `message`                                                                                                                                             |
      | `balance`      | `pre`, `post`                                                                                                                                                              |
      | `tokenBalance` | the full `pre*`/`post*` family (`preMint`, `postMint`, `preOwner`, `postOwner`, `preAmount`, `postAmount`, `preDecimals`, `postDecimals`, `preProgramId`, `postProgramId`) |
      | `reward`       | `lamports`, `rewardType`                                                                                                                                                   |
    </Accordion>

    `block.header.number` is always available and contains the slot. The current Portal source does not return `height`, even though `@subsquid/solana-stream@1.1.2` accepts it in the TypeScript field selection. Migrate height reads to `number`.

    ```diff theme={"system"}
       .setFields({
         block: { // block header fields
           timestamp: true,
         },
    +    transaction: {
    +      signatures: true,
    +      err: true,
    +      accountKeys: true,  // needed for tx.accountKeys[0] (fee payer)
    +    },
    +    tokenBalance: {
    +      preMint: true,
    +      postMint: true,
    +      // add preAmount / postAmount / preDecimals / postDecimals / preOwner / postOwner as needed
    +    },
       })
    ```

    Add `instruction`/`log`/`balance`/`reward` entries the same way. TypeScript errors at compile time will point you at any field still missing from the selection.
  </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? See the [manual workaround](./solana-resync-workaround) (not recommended; edits the status schema directly).
  </Step>
</Steps>

<Accordion title="Common errors (Solana)">
  | Symptom                                                                                                | Root cause                                                                                                   | Fix                                                                                |
  | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------- |
  | `Property 'preMint' does not exist on type 'TokenBalance'` (or `postMint`/`preOwner`/`postAmount`/...) | Pre-1.x `solana-stream` merged the `tokenBalance.pre*`/`post*` family for free; the current release does not | Add the field to `.setFields().tokenBalance` (see *Expand the field selection*)    |
  | `Property 'timestamp' does not exist on type 'BlockHeader'`                                            | `block.timestamp` was in the old SDK's default set                                                           | Add `block: {timestamp: true}` to `.setFields()`                                   |
  | `Property 'signatures' does not exist on type 'Transaction'` (or `accountKeys`/`err`)                  | Same: was in the old SDK's defaults, no longer merged in                                                     | Add to `.setFields().transaction`                                                  |
  | `Property 'height' does not exist on type 'BlockHeader'`                                               | The Portal source identifies Solana blocks by slot and does not return `height`                              | Use `block.header.number` and keep range values in the same slot-number coordinate |
  | `Module '"@subsquid/solana-stream"' has no exported member 'SolanaRpcClient'`                          | You bumped to `solana-stream@^1.x.x` but kept the import                                                     | Remove the import (see *Stop using the RPC client*)                                |
  | `Property 'apiKey' does not exist in type 'GatewaySettings'`                                           | You're calling `.setGateway({url, apiKey})` on the new SDK                                                   | Portal uses `.setPortal('...')` and needs no API key today                         |
</Accordion>

## Reference template (Solana)

If you want to diff against a known-good shape, this is the canonical post-migration template:

<Card title="solana-example@master" icon="github" href="https://github.com/subsquid-labs/solana-example/tree/master">
  Portal-based Solana indexer with the field selection, hot-block support, and types this guide produces
</Card>

## Next steps (Solana)

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

  <Card title="Solana examples" icon="code" href="/en/portal/solana/examples/dex-swaps">
    More examples of Portal API usage on Solana
  </Card>
</CardGroup>
