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

> Make your first SQD Portal request for Solana data in 5 minutes.

export const QueryInterfaceSolanaQuickstart = () => {
  const useCasesConfig = [{
    id: "solana-slots",
    name: "Query Solana slots",
    network: "solana-mainnet",
    payload: {
      type: "solana",
      fromBlock: 259984800,
      toBlock: 259984801,
      fields: {
        block: {
          number: true,
          timestamp: true
        },
        transaction: {
          signatures: true,
          feePayer: true,
          err: true
        }
      },
      transactions: [{}]
    }
  }];
  const [queryFormat, setQueryFormat] = useState("curl");
  const [useCase, setUseCase] = useState("solana-slots");
  const [response, setResponse] = useState("");
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  const [viewMode, setViewMode] = useState("code");
  const [copied, setCopied] = useState(false);
  const generateCurlCommand = config => {
    const url = `https://portal.sqd.dev/datasets/${config.network}/stream`;
    const payload = JSON.stringify(config.payload, null, 2);
    return `curl --compressed -X POST '${url}' \\\n  -H 'Content-Type: application/json' \\\n  -d '${payload}'`;
  };
  useEffect(() => {
    setViewMode("code");
    setResponse("");
    setError(null);
  }, [useCase]);
  const handleRun = async () => {
    const selectedUseCase = useCasesConfig.find(uc => uc.id === useCase);
    if (!selectedUseCase) return;
    setViewMode("output");
    setLoading(true);
    setError(null);
    setResponse('');
    try {
      const url = `https://portal.sqd.dev/datasets/${selectedUseCase.network}/stream`;
      const doFetch = () => fetch(url, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify(selectedUseCase.payload)
      });
      let response = await doFetch();
      for (const retryDelay of [700, 1500, 3000]) {
        if (response.status < 500) break;
        await new Promise(resolve => setTimeout(resolve, retryDelay));
        response = await doFetch();
      }
      if (response.status === 503) {
        throw new Error('Portal is busy right now. Wait a few seconds and run the query again.');
      }
      if (!response.ok) {
        throw new Error(`API request failed: ${response.status} ${response.statusText}`);
      }
      const text = await response.text();
      if (text.trim()) {
        const lines = text.trim().split('\n');
        const parsedData = lines.map((line, index) => {
          try {
            return JSON.parse(line);
          } catch (e) {
            return {
              error: `Failed to parse line ${index + 1}`,
              raw: line
            };
          }
        });
        const formattedOutput = parsedData.map(obj => JSON.stringify(obj, null, 2)).join('\n\n');
        setResponse(formattedOutput);
      } else {
        setResponse('No data returned from API');
      }
    } catch (err) {
      setError(err.message || 'An error occurred while fetching data');
      setResponse('');
    } finally {
      setLoading(false);
    }
  };
  const CurlTok = ({s}) => <>
    {s.split(/('[^']*')/).map((seg, i) => (/^'[^']*'$/).test(seg) ? <span key={i} style={{
    color: "var(--hl-str)"
  }}>{seg}</span> : seg.split(/(curl|--compressed|-X POST|-H|-d)/).map((t2, j) => t2 === "curl" ? <span key={`${i}-${j}`} style={{
    color: "var(--hl-bool)"
  }}>{t2}</span> : (/^(--compressed|-X POST|-H|-d)$/).test(t2) ? <span key={`${i}-${j}`} style={{
    color: "var(--hl-flag)"
  }}>{t2}</span> : <span key={`${i}-${j}`}>{t2}</span>))}
  </>;
  const JsonTok = ({s}) => <>
    {s.split(/("[^"]+"\s*:)/).map((seg, i) => (/^"[^"]+"\s*:$/).test(seg) ? <span key={i} style={{
    color: "var(--hl-key)"
  }}>{seg}</span> : seg.split(/("[^"]*")/).map((s2, j) => (/^"[^"]*"$/).test(s2) ? <span key={`${i}-${j}`} style={{
    color: "var(--hl-str)"
  }}>{s2}</span> : s2.split(/\b(true|false|null|-?\d+\.?\d*)\b/).map((s3, k) => (/^(true|false|null)$/).test(s3) ? <span key={`${i}-${j}-${k}`} style={{
    color: "var(--hl-bool)"
  }}>{s3}</span> : (/^-?\d+\.?\d*$/).test(s3) ? <span key={`${i}-${j}-${k}`} style={{
    color: "var(--hl-num)"
  }}>{s3}</span> : <span key={`${i}-${j}-${k}`}>{s3}</span>)))}
  </>;
  return <div className="not-prose w-full my-6 sm:my-8">

      <div className="sqd-no-scrollbar mb-3 flex items-center gap-0.5 overflow-x-auto pb-1">
        <span className="sqd-pg-label mr-2 flex-shrink-0">Query</span>
        {useCasesConfig.map(uc => <button key={uc.id} type="button" onClick={() => setUseCase(uc.id)} aria-pressed={uc.id === useCase} className="sqd-pg-net flex flex-shrink-0 items-center gap-1.5 px-2.5 py-1 whitespace-nowrap">
            {uc.name}
          </button>)}
      </div>

      <div className="sqd-pg-panel overflow-hidden">
        <div className="sqd-pg-bar flex flex-wrap items-center gap-2 px-3 sm:px-4 py-2">
          <div className="flex items-center gap-1">
            <button type="button" onClick={() => setViewMode("code")} className={`sqd-pg-tab px-2.5 py-1 ${viewMode === "code" ? "sqd-pg-tab-active" : ""}`}>
              Request
            </button>
            <button type="button" onClick={() => setViewMode("output")} disabled={!response && !loading && !error} className={`sqd-pg-tab px-2.5 py-1 disabled:opacity-40 disabled:cursor-not-allowed ${viewMode === "output" ? "sqd-pg-tab-active" : ""}`}>
              Response
              {response ? <span className="inline-block w-1.5 h-1.5 rounded-full bg-emerald-400 ml-1.5 align-middle" /> : null}
            </button>
          </div>
          <div className="flex items-center gap-2 sm:gap-3 ml-auto">
            <button type="button" onClick={() => {
    const textToCopy = viewMode === "code" ? generateCurlCommand(useCasesConfig.find(uc => uc.id === useCase)) : response;
    navigator.clipboard.writeText(textToCopy);
    setCopied(true);
    setTimeout(() => setCopied(false), 2000);
  }} className="sqd-pg-dim text-[12px] transition-opacity hover:opacity-70" title={copied ? "Copied!" : "Copy to clipboard"}>
              {copied ? "Copied" : "Copy"}
            </button>
            <button onClick={handleRun} type="button" disabled={loading} className="sqd-btn-primary-docs flex items-center gap-1.5 px-3 sm:px-4 py-1.5 text-[13px] disabled:opacity-40 disabled:cursor-wait whitespace-nowrap focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 dark:focus:ring-offset-gray-950">
              {loading ? <>
                  <svg className="animate-spin h-3 w-3" fill="none" viewBox="0 0 24 24">
                    <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
                    <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
                  </svg>
                  Running
                </> : "Run"}
            </button>
          </div>
        </div>
        <div className={`sqd-pg-code px-3 sm:px-4 py-4 ${viewMode === "output" ? "max-h-[440px] overflow-auto" : "overflow-x-auto"}`}>
          {loading ? <div className="sqd-pg-dim flex items-center justify-center py-10 text-[13px]">Streaming from Portal...</div> : viewMode === "code" ? <div key={`code-${useCase}`}>
              {generateCurlCommand(useCasesConfig.find(uc => uc.id === useCase)).trim().split('\n').map((line, i) => <div key={`curl-line-${i}`} className="sqd-pg-line">
                  <span className="sqd-pg-num">{i + 1}</span>
                  <span style={{
    whiteSpace: 'pre-wrap',
    wordBreak: 'break-word'
  }}>
                    {line.includes('curl') || line.match(/^\s*-[XHd]/) ? <CurlTok s={line} /> : <JsonTok s={line} />}
                  </span>
                </div>)}
            </div> : response ? <div key={`response-${useCase}-${response.length}`}>
              {response.trim().split('\n').map((line, i) => <div key={`response-line-${i}`} className="sqd-pg-line">
                  <span className="sqd-pg-num">{i + 1}</span>
                  <span style={{
    whiteSpace: 'pre-wrap',
    wordBreak: 'break-word'
  }}>
                    <JsonTok s={line} />
                  </span>
                </div>)}
            </div> : error ? <div className="py-4 text-[13px] text-red-600 dark:text-red-400">Error: {error}</div> : <div className="sqd-pg-dim text-center py-12 text-[13px]">
              Hit Run to stream live data from Portal
            </div>}
        </div>
        <div className="sqd-pg-bar-b flex flex-wrap items-center gap-x-3 gap-y-1 px-4 py-2">
          <div className="sqd-pg-dim flex items-center gap-2 min-w-0 text-[12px]">
            <span className="truncate">{useCasesConfig.find(uc => uc.id === useCase)?.network + "/stream"}</span>
          </div>
          <span className="sqd-pg-dim text-[12px] ml-auto flex-shrink-0">
            {useCasesConfig.find(uc => uc.id === useCase)?.name}
          </span>
        </div>
      </div>
    </div>;
};

Make your first Portal request to access raw Solana blockchain data in under 5 minutes. No setup required, just HTTP requests.

<Tip>
  You don't have to use this API directly. The TypeScript SDKs are built on top
  of Portal and take care of query construction, streaming, decoding, and reorg
  handling: start with the [Pipes SDK
  quickstart](/en/sdk/pipes-sdk/solana/quickstart), or see the [SDK
  overview](/en/sdk/overview) for all options. Read on if you want to work with
  the raw API.
</Tip>

## What You'll Build

In this quickstart, you'll:

1. Make a simple Portal request using curl (no installation required)
2. Query recent Solana slots
3. Filter instructions from a specific program

<Info>
  This guide uses the **Public Portal** endpoint, which is free and
  rate-limited. Perfect for getting started.
</Info>

## Step 1: Your First Request

Let's query 200 slots from Solana mainnet and get block data with transaction counts. Copy and paste this command:

<Tabs>
  <Tab title="curl">
    ```bash theme={"system"}
    curl --compressed -X POST "https://portal.sqd.dev/datasets/solana-mainnet/stream" \
      -H 'Content-Type: application/json' \
      -d '{
        "type": "solana",
        "fromBlock": 259984800,
        "toBlock": 259984801,
        "fields": {
          "block": {
            "number": true,
            "timestamp": true
          },
          "transaction": {
            "signatures": true,
            "feePayer": true,
            "err": true
          }
        },
        "transactions": [{}]
      }'
    ```

    <Check>
      You should see a stream of JSON objects, one per block, showing slot numbers,
      timestamps, and transaction data including signatures, fee payers, and success
      status.
    </Check>

    <Info>Try it yourself with the interactive query interface below:</Info>

    <QueryInterfaceSolanaQuickstart />
  </Tab>

  <Tab title="TypeScript">
    First, install the client:

    ```bash theme={"system"}
    npm install @subsquid/portal-client
    ```

    Then run this code:

    ```typescript theme={"system"}
    import { DataSource } from "@subsquid/portal-client";

    const dataSource = new DataSource({
      network: "solana-mainnet",
    });

    const blocks = await dataSource.getBlocks({
      from: 259984800,
      to: 259985000,
      fields: {
        block: {
          number: true,
          timestamp: true,
        },
        transaction: {
          signatures: true,
          feePayer: true,
          err: true,
        },
      },
      transactions: [{}],
    });

    console.log(`Retrieved ${blocks.length} blocks`);
    blocks.forEach((block) => {
      const successCount = block.transactions.filter(
        (tx) => tx.err === null
      ).length;
      const failedCount = block.transactions.length - successCount;
      console.log(
        `Slot ${block.header.number}: ${block.transactions.length} transactions (${successCount} succeeded, ${failedCount} failed)`
      );
    });
    ```
  </Tab>

  <Tab title="Python">
    First, install requests:

    ```bash theme={"system"}
    pip install requests
    ```

    Then run this code:

    ```python theme={"system"}
    import requests
    import json

    url = "https://portal.sqd.dev/datasets/solana-mainnet/stream"
    headers = {"Content-Type": "application/json"}
    payload = {
        "type": "solana",
        "fromBlock": 259984800,
        "toBlock": 259985000,
        "fields": {
            "block": {
                "number": True,
                "timestamp": True
            },
            "transaction": {
                "signatures": True,
                "feePayer": True,
                "err": True
            }
        },
        "transactions": [{}]
    }

    response = requests.post(url, headers=headers, json=payload)

    # Response is newline-delimited JSON
    for line in response.text.strip().split('\n'):
        if line.strip():  # Skip empty lines
            block = json.loads(line)
            tx_count = len(block.get('transactions', []))
            success_count = sum(1 for tx in block.get('transactions', []) if tx.get('err') is None)
            failed_count = tx_count - success_count
            print(f"Slot {block['header']['number']}: {tx_count} transactions ({success_count} succeeded, {failed_count} failed)")
    ```
  </Tab>
</Tabs>

## Step 2: Understanding the Request

Let's break down what you just sent:

```typescript theme={"system"}
{
  "type": "solana",              // Chain type (Solana)
  "fromBlock": 259984800,         // Starting slot number (inclusive, field name: fromBlock)
  "toBlock": 259985000,           // Ending slot number (inclusive, field name: toBlock)
  "fields": {                    // What data to return
    "block": {
      "number": true,             // Slot number (field name: number)
      "timestamp": true          // Block timestamp
    },
    "transaction": {
      "signatures": true,         // Transaction signatures
      "feePayer": true,          // Fee payer address
      "err": true                // Error status (null if successful)
    }
  },
  "transactions": [{}]            // Include transactions filter (empty object = all transactions)
}
```

<Tip>
  Portal only returns the fields you request. This keeps responses fast and
  bandwidth-efficient.
</Tip>

## Step 3: Understanding the Response

Portal returns **JSON lines (JSONL)**. Each line is a complete JSON object representing one block. Note that not every slot produces a block, so you'll see gaps in the slot numbers:

```json theme={"system"}
{
  "header": {
    "number": 259984800,
    "timestamp": 1713053593
  },
  "transactions": [
    {
      "signatures": ["5j7s8K9LmN2pQrS3tUvW4xYz5aB6cD7eF8gH9iJ0kL1mN2oP3qR4sT5uV6wX"],
      "feePayer": "9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM",
      "err": null
    }
  ]
}
{
  "header": {
    "number": 259984837,
    "timestamp": 1713053611
  },
  "transactions": [
    {
      "signatures": ["3kL4mN5oP6qR7sT8uV9wX0yZ1aB2cD3eF4gH5iJ6kL7mN8oP9qR0sT"],
      "feePayer": "7qbRF6YsyGuLUVs6Y1q64bdVrfe4ZcUUz1JRdoVNUJnm",
      "err": null
    }
  ]
}
{
  "header": {
    "number": 259984838,
    "timestamp": 1713053612
  },
  "transactions": []
}
```

<Note>
  This format enables **constant-memory streaming** of massive ranges. You can
  process millions of slots without loading everything into RAM.
</Note>

## Stream semantics

Two behaviors are easy to miss on your first Portal request. Both are enforced by the API, and clients that ignore them will silently drop data.

### Filter objects vs `includeAllBlocks`

Portal returns **only blocks matching your data filters** (`instructions`, `transactions`, `logs`, `balances`, and so on). The example above uses `"transactions": [{}]` (an empty filter object) as a catch-all so every block in the range is returned.

* If you set specific filters (e.g. `instructions: [{ programId: [...] }]`), Portal returns only blocks containing matching items.
* If you drop all filters AND don't set `"includeAllBlocks": true`, Portal returns just the worker-range boundary blocks (typically 2–4 per query), not the full range. If you see far fewer blocks than expected, this is why.
* On Solana, `includeAllBlocks` defaults to `false`. (On Bitcoin it defaults to `true`; on EVM and Substrate `false`.)

### The stream can end before `toBlock`

A single HTTP response can be cut short at any point: when a worker's range ends, when a connection is recycled, or at the dataset's current head. Clients must treat one response as a **batch**, not the entire range. To continue:

1. Read the last block number (`N`) in the response.
2. Issue a new request with `fromBlock = N + 1` (and the same `toBlock`, filters, and fields).
3. Loop until you receive HTTP 204 (range is above dataset height) or you reach your `toBlock`.

If you're streaming near the chain head, also set `parentBlockHash` on each subsequent request and handle HTTP 409 (reorg). See the [full API reference](/en/portal/solana/api#stream-continuation).

## Step 4: Filter Instructions

Track instructions from the Orca Whirlpool program. This example queries a larger range to find actual instructions:

<CodeGroup>
  ```bash curl theme={"system"}
  curl --compressed -X POST "https://portal.sqd.dev/datasets/solana-mainnet/stream" \
    -H 'Content-Type: application/json' \
    -d '{
      "type": "solana",
      "fromBlock": 259984800,
      "toBlock": 259985000,
      "fields": {
        "block": {
          "number": true,
          "timestamp": true
        },
        "instruction": {
          "programId": true,
          "accounts": true,
          "data": true,
          "transactionIndex": true
        }
      },
      "instructions": [{
        "programId": ["whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc"]
      }]
    }'
  ```

  ```typescript TypeScript theme={"system"}
  import { DataSource } from "@subsquid/portal-client";

  const dataSource = new DataSource({
    network: "solana-mainnet",
  });

  const blocks = await dataSource.getBlocks({
    from: 259984800,
    to: 259985000,
    fields: {
      block: {
        number: true,
        timestamp: true,
      },
      instruction: {
        programId: true,
        accounts: true,
        data: true,
        transactionIndex: true,
      },
    },
    instructions: [
      {
        programId: ["whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc"], // Orca Whirlpool
      },
    ],
  });

  for (const block of blocks) {
    if (block.instructions.length > 0) {
      console.log(
        `Slot ${block.header.number}: Found ${block.instructions.length} Whirlpool instructions`
      );
      for (const instruction of block.instructions) {
        console.log(
          `  Instruction from ${instruction.programId} at tx index ${instruction.transactionIndex}`
        );
      }
    }
  }
  ```

  ```python Python theme={"system"}
  import requests
  import json

  url = "https://portal.sqd.dev/datasets/solana-mainnet/stream"
  headers = {"Content-Type": "application/json"}
  payload = {
      "type": "solana",
      "fromBlock": 259984800,
      "toBlock": 259985000,
      "fields": {
          "block": {
              "number": True,
              "timestamp": True
          },
          "instruction": {
              "programId": True,
              "accounts": True,
              "data": True,
              "transactionIndex": True
          }
      },
      "instructions": [{
          "programId": ["whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc"]
      }]
  }

  response = requests.post(url, headers=headers, json=payload)

  for line in response.text.strip().split('\n'):
      if line.strip():  # Skip empty lines
          block = json.loads(line)
          if block.get('instructions'):
              print(f"Slot {block['header']['number']}: Found {len(block['instructions'])} Whirlpool instructions")
              for instruction in block['instructions']:
                  print(f"  Instruction from {instruction['programId']} at tx index {instruction['transactionIndex']}")
  ```
</CodeGroup>

<Check>
  You should see instructions from the Orca Whirlpool program. Portal filtered
  the data before sending, saving bandwidth.
</Check>

## What You Learned

In 5 minutes, you:

* ✓ Made HTTP requests to Portal
* ✓ Queried arbitrary slot ranges
* ✓ Filtered program instructions
* ✓ Processed newline-delimited JSON responses

## Rate Limits

The Public Portal is rate-limited:

* **20 requests per 10 seconds**
* Perfect for development and testing

<CardGroup cols={2}>
  <Card title="Cloud Portal" icon="cloud" href="/en/cloud/overview">
    Production-ready managed access with higher limits
  </Card>

  <Card title="Self-Host Portal" icon="server" href="/en/portal/self-hosting">
    Run your own Portal instance with no rate limits
  </Card>
</CardGroup>

## Next Steps

<CardGroup cols={3}>
  <Card title="API Reference" icon="book" href="/en/portal/solana/api">
    Complete reference with all fields and filters
  </Card>

  {" "}

  {" "}

  <Card title="View Examples" icon="code" href="/en/portal/solana/examples/query-instructions">
    Practical examples for common use cases
  </Card>

  <Card title="Use with SDK" icon="braces" href="/en/sdk/overview">
    Build type-safe indexers with Portal as the data source
  </Card>
</CardGroup>

### Popular Examples

<CardGroup cols={2}>
  <Card title="Query Instructions" icon="code" href="/en/portal/solana/examples/query-instructions">
    Track program instructions
  </Card>

  {" "}

  {" "}

  <Card title="Query Transactions" icon="arrow-left-right" href="/en/portal/solana/examples/query-transactions">
    Monitor wallet activity
  </Card>

  {" "}

  {" "}

  <Card title="Track Token Transfers" icon="coins" href="/en/portal/solana/examples/token-transfers">
    Index SPL token activity
  </Card>

  <Card title="Index DEX Swaps" icon="repeat" href="/en/portal/solana/examples/dex-swaps">
    Build DEX analytics
  </Card>
</CardGroup>


## Related topics

- [EVM Portal Quickstart](/en/portal/evm/quickstart.md)
- [Solana Portal API](/en/portal/solana/overview.md)
- [Solana Portal API Reference](/en/portal/solana/api.md)
- [Portal for Solana](/en/cloud/resources/migrate-to-portal-on-solana.md)
- [Quickstart](/en/sdk/squid-sdk/solana/quickstart.md)
