skills/subsquid-labs/agent-skills/portal-query-solana-instructions

portal-query-solana-instructions

SKILL.md

When to Use This Skill

Use this skill when you need to:

  • Track Solana program interactions (Jupiter swaps, Raydium pools, etc.)
  • Monitor SPL token transfers
  • Analyze wallet activity on Solana
  • Filter by specific program functions (using discriminators)
  • Track account interactions with programs

Solana instructions are the equivalent of EVM transactions/logs - they capture on-chain program calls.


Query Structure

Basic Solana instruction query structure:

{
  "type": "solana",
  "fromBlock": 250000000,
  "toBlock": 250001000,
  "instructions": [{
    "programId": ["JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"],
    "d8": ["0xe445a52e51cb9a1d"],
    "a0": ["EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"]
  }],
  "fields": {
    "instruction": {
      "programId": true,
      "accounts": true,
      "data": true
    }
  }
}

Field explanations:

  • type: "solana" - Required for Solana chains (not "evm")
  • fromBlock/toBlock - Block range using Solana slot numbers (current ~300M+)
  • instructions - Array of instruction filter objects
  • programId - Program address (INDEXED - fast)
  • d1/d2/d4/d8 - Discriminators (INDEXED - function selectors)
  • a0-a31 - Account filters by position (INDEXED)
  • mentionsAccount - Account appears anywhere (INDEXED)

Understanding Discriminators

Discriminators are Solana's function selectors (similar to EVM sighash).

Discriminator types:

  • d1 - First 1 byte of data (SPL Token Program)
  • d2 - First 2 bytes of data
  • d4 - First 4 bytes of data
  • d8 - First 8 bytes of data (most common for Anchor programs)

Computing discriminator (Anchor):

import { sha256 } from '@noble/hashes/sha256';

function getDiscriminator(name: string): string {
  const hash = sha256(Buffer.from(`global:${name}`));
  return '0x' + Buffer.from(hash).slice(0, 8).toString('hex');
}

⚠️ Important: Discriminator values are computed from the actual program IDL and may differ between program versions. Always verify against the specific program version.


Examples

Example 1: Track Jupiter Swap Instructions

{
  "type": "solana",
  "fromBlock": 250000000,
  "toBlock": 250001000,
  "instructions": [{
    "programId": ["JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4"],
    "d8": ["0x5703feb8e7573909"]
  }],
  "fields": {
    "instruction": {
      "programId": true,
      "accounts": true,
      "data": true
    },
    "transaction": {
      "feePayer": true,
      "fee": true,
      "err": true
    }
  }
}

Dataset: solana-mainnet | Program: Jupiter Aggregator V6 | Function: sharedAccountsRoute


Example 2: Track SPL Token Transfers

{
  "type": "solana",
  "fromBlock": 250000000,
  "toBlock": 250001000,
  "instructions": [{
    "programId": ["TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA"],
    "d1": ["0x03"]
  }],
  "fields": {
    "instruction": {
      "programId": true,
      "accounts": true,
      "data": true
    }
  }
}

Dataset: solana-mainnet | Program: SPL Token Program | Instruction: Transfer (discriminator: 0x03) Notes: accounts[0] = source, accounts[1] = destination, accounts[2] = authority


Example 3: Track Wallet Activity (All Instructions)

{
  "type": "solana",
  "fromBlock": 250000000,
  "toBlock": 250001000,
  "instructions": [{
    "mentionsAccount": ["9WzDXwBbmkg8ZTbNMqUxvQRAyrZzDsGYdLVL9zYtAWWM"]
  }],
  "fields": {
    "instruction": {
      "programId": true,
      "accounts": true,
      "data": true
    },
    "transaction": {
      "feePayer": true,
      "signatures": true
    }
  }
}

Notes: mentionsAccount matches if the account appears ANYWHERE in accounts array. More expensive than a0-a31 (position-specific) filters.

More examples: See references/additional-examples.md for account position filtering, Raydium, Orca Whirlpool, token balance tracking, program deployments, and failed instructions.


Available Fields

Instruction Fields

{
  "programId": true,            // Program being called
  "accounts": true,             // Array of account public keys
  "data": true,                 // Instruction data (hex string)
  "computeUnitsConsumed": true, // CU used by this instruction
  "transactionIndex": true,     // Transaction position in block
  "instructionAddress": true,   // Instruction position in transaction
  "isCommitted": true,          // Success/failure status
  "error": true,                // Error details if instruction failed
  "hasDroppedLogMessages": true,// Whether log messages were dropped
  "d1": true,                   // First 1 byte of data
  "d2": true,                   // First 2 bytes of data
  "d4": true,                   // First 4 bytes of data
  "d8": true                    // First 8 bytes of data
}

Transaction Fields

{
  "feePayer": true,             // Transaction initiator (wallet)
  "fee": true,                  // Transaction fee (lamports)
  "err": true,                  // Error object (null = success)
  "signatures": true,           // Transaction signatures
  "accountKeys": true,          // All account keys in transaction
  "version": true,              // Transaction version
  "computeUnitsConsumed": true, // CU used by transaction
  "addressTableLookups": true,  // Address lookup tables
  "hasDroppedLogMessages": true // Whether log messages were dropped
}

INDEXED Fields for Filtering

Fast filterable fields (use these for filters):

  • programId - INDEXED (always filter by this first - most selective)
  • d1, d2, d4, d8 - INDEXED (discriminators)
  • a0 through a31 - INDEXED (account positions)
  • mentionsAccount - INDEXED (slower than a0-a31)
  • isCommitted - INDEXED (success/failure)

Account filtering strategy:

  • Use a0-a31 when you know the account position (faster)
  • Use mentionsAccount when position is unknown or varies

Common Mistakes

❌ Wrong Discriminator Length

{
  "instructions": [{
    "programId": ["JUP6..."],
    "d1": ["0xe4"]  // ❌ Jupiter uses d8, not d1
  }]
}

Fix: Jupiter uses 8-byte discriminators: "d8": ["0xe445a52e51cb9a1d"]


❌ Filtering Without programId

{
  "instructions": [{
    "d8": ["0xe445a52e51cb9a1d"]  // ❌ No programId filter
  }]
}

Fix: Always filter by programId first.


❌ Using EVM-Style Block Numbers

{
  "type": "solana",
  "fromBlock": 19500000  // ❌ EVM block number - way too low
}

Fix: Use Solana slot numbers: "fromBlock": 250000000 (current slot ~300M+)


❌ Forgetting Transaction Fields for Context

Include transaction context when you need fee payer or success status:

{
  "fields": {
    "instruction": {"programId": true, "accounts": true},
    "transaction": {"feePayer": true, "err": true}
  }
}

Response Format

Portal returns JSON Lines (one JSON object per line):

{"header":{"number":250000000,"hash":"...","parentHash":"...","timestamp":1234567890}}
{"instructions":[{"programId":"JUP6...","accounts":["EPjF...","So11..."],"data":"0xe445a52e..."}],"transactions":[{"feePayer":"9WzD...","fee":5000,"err":null}]}

Parsing: Split by newlines, parse each line as JSON. First line = block header.


Performance Tips

Filter selectivity order (best to worst):

  1. programId + d8 + a0 (best)
  2. programId + d8
  3. programId + mentionsAccount
  4. programId only

Block range: Solana processes ~2 slots/second. 1,000-10,000 slots ≈ 8-80 minutes of data.


Related Skills

  • portal-dataset-discovery - Find correct Solana dataset name
  • portal-query-evm-logs - EVM equivalent (for comparison)

Additional Resources

Weekly Installs
3
First Seen
Feb 26, 2026
Installed on
mcpjam3
claude-code3
replit3
junie3
windsurf3
zencoder3