SOON integrates with SQD to provide powerful data indexing capabilities. This guide will walk you through using SQD’s Solana API to index and query data from SOON’s networks.
Available Networks
SOON’s data is accessible through these Subsquid archives:
const NETWORKS = {
testnet: 'https://v2.archive.subsquid.io/network/soon-testnet',
devnet: 'https://v2.archive.subsquid.io/network/soon-devnet'
mainnet: 'https://v2.archive.subsquid.io/network/soon-mainnet'
}
Understanding SQD Architecture
SQD uses a distributed worker system to efficiently serve blockchain data. Each worker handles a specific range of blocks, and queries are routed to appropriate workers through a central gateway.
Core Concepts
-
Router: The main entry point that directs queries to specific workers
-
Workers: Distributed nodes that store and serve blockchain data
-
Block Ranges: Each worker handles a specific range of blocks
-
Archive Height: The latest block available in the archive
Query Flow
1. Check Archive Height
First, verify the current height of the archive:
async function getArchiveHeight() {
const response = await fetch(
'https://v2.archive.subsquid.io/network/soon-testnet/height'
)
const height = await response.text()
return parseInt(height)
// Returns: 366804 (example)
}
2. Get Worker Assignment
Request a worker URL for your desired block range:
async function getWorker(blockNumber: number) {
const response = await fetch(
`https://v2.archive.subsquid.io/network/soon-testnet/${blockNumber}/worker`
)
const workerUrl = await response.text()
// Returns: https://lm07.sqd-archive.net/worker/query/czM6Ly9zb29uLXRlc3RuZXQtMQ
return workerUrl
}
3. Query Data
Make your data request to the assigned worker:
async function queryData(workerUrl: string, fromBlock: number) {
const response = await fetch(workerUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify({
type: "solana",
fromBlock: fromBlock,
fields: {
instruction: {
programId: true,
data: true
}
},
instructions: [
{ isCommitted: true }
]
})
})
const data = await response.json()
return data
}
Example Response:
[
{
"header": {
"number": 366800,
"hash": "11111111111111111111111111111111"
},
"instructions": [
{
"transactionIndex": 0,
"instructionAddress": [0],
"programId": "L1BLockinfo11111111111111111111111111111111",
"data": "2JyQzTaqty3gKAuyyDEir3yZird2V55..."
}
]
}
]
Query Options
Instruction Queries
Filter and fetch specific instruction data:
const instructionQuery = {
type: "solana",
fromBlock: startBlock,
fields: {
instruction: {
programId: true,
data: true,
accounts: true // Include account information
}
},
instructions: [
{
programId: ["YourProgramID"], // Filter by program
isCommitted: true
}
]
}
Transaction Queries
Get full transaction details:
const transactionQuery = {
type: "solana",
fromBlock: startBlock,
fields: {
transaction: {
feePayer: true,
instructions: true,
logs: true
}
},
transactions: [
{
feePayer: ["SpecificFeePayer"] // Optional filter
}
]
}
Log Queries
Fetch program logs:
const logQuery = {
type: "solana",
fromBlock: startBlock,
fields: {
log: {
programId: true,
message: true
}
},
logs: [
{
kind: ["log", "data"], // Filter by log type
programId: ["YourProgramID"] // Filter by program
}
]
}
Best Practices
Handling Pagination
For large datasets, implement pagination:
async function fetchAllData(startBlock: number, endBlock: number) {
let currentBlock = startBlock
const results = []
while (currentBlock <= endBlock) {
const workerUrl = await getWorker(currentBlock)
const data = await queryData(workerUrl, currentBlock)
results.push(...data)
// Update current block to last received block + 1
const lastBlock = data[data.length - 1].header.number
currentBlock = lastBlock + 1
}
return results
}
Error Handling
Implement robust error handling:
async function fetchWithRetry(workerUrl: string, query: any, retries = 3) {
for (let i = 0; i < retries; i++) {
try {
const response = await queryData(workerUrl, query)
return response
} catch (error) {
if (i === retries - 1) throw error
await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, i)))
}
}
}
Common Use Cases
Track Program Interactions
Monitor specific program activities:
const programQuery = {
type: "solana",
fromBlock: startBlock,
fields: {
instruction: {
programId: true,
data: true,
accounts: true
},
transaction: {
logs: true
}
},
instructions: [
{
programId: ["YourProgramID"],
isCommitted: true
}
]
}
Monitor Account Activity
Track account changes and interactions:
const accountQuery = {
type: "solana",
fromBlock: startBlock,
fields: {
tokenBalance: {
account: true,
amount: true,
owner: true
}
},
tokenBalances: [
{
account: ["AccountToTrack"]
}
]
}
-
Keep block ranges reasonable (suggested: ≤1000 blocks per query)
-
Cache worker URLs when querying consecutive blocks
-
Implement exponential backoff for retries
-
Consider using webhooks for real-time updates
TypeScript Support
SQD provides TypeScript typings for better developer experience:
interface SQDQuery {
type: 'solana'
fromBlock: number
fields: {
instruction?: InstructionFields
transaction?: TransactionFields
log?: LogFields
}
instructions?: InstructionFilter[]
transactions?: TransactionFilter[]
logs?: LogFilter[]
}
// Use with your API calls
const query: SQDQuery = {
type: 'solana',
fromBlock: 366800,
fields: {
instruction: {
programId: true,
data: true
}
}
}