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

# Solana Portal stream

> Use @subsquid/solana-stream with Portal and the unified batch processor to index Solana transactions, instructions, logs, balances, token balances, and rewards.

`@subsquid/solana-stream` is the Portal-native Solana data source for Squid SDK. `DataSourceBuilder.build()` returns a source that you can pass to [`run()` or `Processor`](/en/sdk/squid-sdk/reference/processors/batch-processor), or consume directly.

<Warning>
  `SolanaBatchProcessor`, `SolanaRpcClient`, `setGateway()`, `setRpc()`, and `setRpcEndpoint()` are not part of `@subsquid/solana-stream@1.1.2`. Use `setPortal()`.
</Warning>

## Complete example

```ts theme={"system"}
import {run, type FinalDatabase} from '@subsquid/batch-processor'
import {DataSourceBuilder, type FieldSelection} from '@subsquid/solana-stream'

const JUPITER = 'JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4'
const TEST_SLOT = 432_825_258

const fields = {
  block: {parentNumber: true, timestamp: true},
  instruction: {programId: true, accounts: true, data: true},
  transaction: {signatures: true, err: true, computeUnitsConsumed: true},
  log: {programId: true, kind: true, message: true},
} satisfies FieldSelection

const dataSource = new DataSourceBuilder()
  .setPortal('https://portal.sqd.dev/datasets/solana-mainnet')
  .setBlockRange({from: TEST_SLOT, to: TEST_SLOT})
  .setFields(fields)
  .addInstruction({
    where: {programId: [JUPITER]},
    include: {transaction: true, logs: true, innerInstructions: true},
  })
  .build()

let checkpoint = {
  height: TEST_SLOT - 1,
  hash: '4YqZcfbBdoADF7FDtmWFpGKwYmasi1mXzdqu7yeAE9dX',
}
const db: FinalDatabase<void> = {
  async connect() {
    return checkpoint
  },
  async transact({nextHead}, cb) {
    await cb(undefined)
    checkpoint = nextHead
  },
}

run(dataSource, db, async (ctx) => {
  for (const block of ctx.blocks) {
    console.log(block.header.number, block.instructions.length)
  }
})
```

The fixed range makes this a bounded example that exits after processing one slot. Its in-memory checkpoint is intentionally temporary; use a persistent [Database implementation](/en/sdk/squid-sdk/resources/persisting-data/overview) for an indexer that must resume after a restart.

The batch context contains `{store, blocks, isHead}`. It does not inject `ctx.log`.

## Builder methods

`setPortal(portal)` is required and accepts a URL, `PortalClientOptions`, or `PortalClient`. `build()` throws `Portal settings not set` when it is omitted.

`setBlockRange({from, to?})` applies a global range. Solana ranges use slot numbers, the same values returned as `block.header.number`. `includeAllBlocks(range?)` includes blocks without matching items.

`setFields(fields)` has no default optional fields. The selector sections are `block`, `transaction`, `instruction`, `log`, `balance`, `tokenBalance`, and `reward`.

`addQuery(query)` accepts a `Query` or `QueryBuilder` when several filters share one range. The builder also provides these shorthands:

| Method              | `where` filters                                                                              | `include` relations                                                                                                      |
| ------------------- | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `addTransaction()`  | `feePayer`                                                                                   | `instructions`, `logs`, `balances`, `tokenBalances`                                                                      |
| `addInstruction()`  | `programId`, `d1`, `d2`, `d3`, `d4`, `d8`, `discriminator`, `a0` through `a9`, `isCommitted` | `transaction`, `transactionBalances`, `transactionTokenBalances`, `transactionInstructions`, `logs`, `innerInstructions` |
| `addLog()`          | `programId`, `kind`                                                                          | `transaction`, `instruction`                                                                                             |
| `addBalance()`      | `account`                                                                                    | `transaction`, `transactionInstructions`                                                                                 |
| `addTokenBalance()` | `account`, `preProgramId`, `postProgramId`, `preMint`, `postMint`, `preOwner`, `postOwner`   | `transaction`, `transactionInstructions`                                                                                 |
| `addReward()`       | `pubkey`                                                                                     | none                                                                                                                     |

Instruction discriminators are `0x`-prefixed hexadecimal byte prefixes. The `d1`, `d2`, `d3`, `d4`, and `d8` filters require exactly that many bytes; `discriminator` accepts any prefix length.

### `build()`

Returns the Solana data source. Pass it to `run()` or `Processor` for restart-safe processing. You can also call `getHead()`, `getFinalizedHead()`, `getStream()`, `getFinalizedStream()`, and the optional `getBlocksCountInRange()` helper directly. See [Read a data source directly](/en/sdk/squid-sdk/reference/processors/batch-processor#read-a-data-source-directly).

## Block data

Each block has `header`, `transactions`, `instructions`, `logs`, `balances`, `tokenBalances`, and `rewards`.

Required header fields are:

```ts theme={"system"}
type BlockHeader = {
  number: number      // slot number
  hash: string
  parentHash: string
  parentNumber?: number
  timestamp?: number // Unix milliseconds when selected
}
```

`slot` and `parentSlot` are not field names. Use `number` and `parentNumber`.

<Warning>
  `@subsquid/solana-stream@1.1.2` accepts `height` in the TypeScript field selection, but the Portal source does not return it. Use `number` for the Solana slot and for `setBlockRange()` values.
</Warning>

The stream converts Portal's raw Solana block time from seconds to milliseconds. Pass `timestamp` directly to `new Date(timestamp)` without multiplying it by `1000`.

Balances use one account per item:

```ts theme={"system"}
type Balance = {
  transactionIndex: number
  account: string
  pre?: bigint
  post?: bigint
}
```

Token balances can contain pre-state, post-state, or both. `pre*` fields describe the value before the transaction and `post*` fields describe it after the transaction.

## Prometheus

Pass a `PrometheusServer` through the fourth `run()` argument:

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

const prometheus = new PrometheusServer()
prometheus.setPort(3000)
run(dataSource, db, handler, {prometheus})
```

Without an explicit server, `PROCESSOR_PROMETHEUS_PORT` takes precedence over `PROMETHEUS_PORT`. Metrics are disabled if neither variable is set.

## Solana typegen

`@subsquid/solana-typegen` accepts an Anchor IDL file, an HTTP(S) IDL URL, or a program address. For addresses it checks the chain and the SolanaFM registry.

```bash theme={"system"}
npx squid-solana-typegen src/abi ./abi/program.json --clean
npx squid-solana-typegen src/abi https://example.com/program.json#program
npx squid-solana-typegen src/abi <PROGRAM_ADDRESS> --solana-rpc-endpoint https://api.mainnet-beta.solana.com
```

Generated modules include instruction and event definitions. `@subsquid/solana-stream` also exports `getInstructionData()` and `getInstructionDescriptor()` for working with generated instruction metadata.
