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

  1. Router: The main entry point that directs queries to specific workers

  2. Workers: Distributed nodes that store and serve blockchain data

  3. Block Ranges: Each worker handles a specific range of blocks

  4. 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"]
    }
  ]
}

Rate Limits and Performance

  • 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
    }
  }
}