Make your first Portal request to access raw Solana blockchain data in under 5 minutes. No setup required—just HTTP requests.
What You’ll Build
In this quickstart, you’ll:
- Make a simple Portal request using curl (no installation required)
- Query recent Solana slots
- Filter instructions from a specific program
This guide uses the Public Portal endpoint, which is free and
rate-limited. Perfect for getting started.
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:
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": [{}]
}'
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.
Try it yourself with the interactive query interface below:
First, install the client:npm install @subsquid/portal-client
Then run this code: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)`
);
});
First, install requests:Then run this code: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)")
Step 2: Understanding the Request
Let’s break down what you just sent:
{
"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)
}
Portal only returns the fields you request. This keeps responses fast and
bandwidth-efficient.
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:
{
"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": []
}
This format enables constant-memory streaming of massive ranges. You can
process millions of slots without loading everything into RAM.
Step 4: Filter Instructions
Track instructions from the Orca Whirlpool program. This example queries a larger range to find actual instructions:
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"]
}]
}'
You should see instructions from the Orca Whirlpool program. Portal filtered
the data before sending, saving bandwidth.
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
Next Steps
Popular Examples