Skip to main content
Track SPL token transfers to build token analytics, monitor wallet activity, or analyze token flow patterns.

Use Case

Token transfer tracking enables you to:
  • Build token transfer analytics and volume trackers
  • Monitor wallet token activity
  • Analyze transfer patterns
  • Track token mints and burns

Code Example

curl --compressed -X POST "https://portal.sqd.dev/datasets/solana-mainnet/stream" \
  -H 'Content-Type: application/json' \
  -d '{
    "type": "solana",
    "fromBlock": 280000000,
    "toBlock": 280000003,
    "fields": {
      "block": {
        "number": true,
        "timestamp": true
      },
      "tokenBalance": {
        "account": true,
        "preMint": true,
        "postMint": true,
        "preAmount": true,
        "postAmount": true,
        "preOwner": true,
        "postOwner": true
      }
    },
    "tokenBalances": [{
      "preMint": ["EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"]
    }]
  }'
import { DataSource } from "@subsquid/portal-client";

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

// USDC mint address
const USDC_MINT = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";

const blocks = await dataSource.getBlocks({
  from: 280000000,
  to: 280000003,
  fields: {
    block: { number: true, timestamp: true },
    tokenBalance: {
      account: true,
      preMint: true,
      postMint: true,
      preAmount: true,
      postAmount: true,
      preOwner: true,
      postOwner: true,
    },
  },
  tokenBalances: [
    {
      preMint: [USDC_MINT],
    },
  ],
});

// Process token transfers
for (const block of blocks) {
  for (const balance of block.tokenBalances) {
    if (balance.preAmount !== balance.postAmount) {
      const amount = BigInt(balance.postAmount) - BigInt(balance.preAmount);
      console.log({
        slot: block.header.number,
        timestamp: block.header.timestamp,
        account: balance.account,
        mint: balance.postMint,
        from: balance.preOwner,
        to: balance.postOwner,
        amount: amount.toString(),
      });
    }
  }
}
import requests
import json

url = "https://portal.sqd.dev/datasets/solana-mainnet/stream"
headers = {"Content-Type": "application/json"}

# USDC mint address
usdc_mint = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"

payload = {
    "type": "solana",
    "fromBlock": 280000000,
    "toBlock": 280000003,
    "fields": {
        "block": {
            "number": True,
            "timestamp": True
        },
        "tokenBalance": {
            "account": True,
            "preMint": True,
            "postMint": True,
            "preAmount": True,
            "postAmount": True,
            "preOwner": True,
            "postOwner": True
        }
    },
    "tokenBalances": [{
        "preMint": [usdc_mint]
    }]
}

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

for line in response.text.strip().split('\n'):
    block = json.loads(line)
    for balance in block.get('tokenBalances', []):
        if balance['preAmount'] != balance['postAmount']:
            amount = int(balance['postAmount']) - int(balance['preAmount'])
            print({
                "slot": block['header']['number'],
                "timestamp": block['header']['timestamp'],
                "account": balance['account'],
                "mint": balance['postMint'],
                "from": balance['preOwner'],
                "to": balance['postOwner'],
                "amount": amount
            })
Try it yourself with the interactive query interface below:

Key Parameters

ParameterDescription
preMintToken mint address before the transaction
postMintToken mint address after the transaction
preAmountToken balance before the transaction
postAmountToken balance after the transaction
preOwnerToken account owner before the transaction
postOwnerToken account owner after the transaction
accountToken account address

Expected Output

{
  "header": {
    "number": 259985000,
    "timestamp": 1697544779
  },
  "tokenBalances": [
    {
      "account": "TokenAccountAddress...",
      "preMint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
      "postMint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
      "preAmount": "1000000",
      "postAmount": "2000000",
      "preOwner": "SenderAddress...",
      "postOwner": "RecipientAddress..."
    }
  ]
}

Track Specific Token Account

Monitor transfers for a specific token account:
curl --compressed -X POST "https://portal.sqd.dev/datasets/solana-mainnet/stream" \
  -H 'Content-Type: application/json' \
  -d '{
    "type": "solana",
    "fromBlock": 280000000,
    "toBlock": 280000003,
    "fields": {
      "tokenBalance": {
        "account": true,
        "preAmount": true,
        "postAmount": true
      }
    },
    "tokenBalances": [{
      "account": ["SpecificTokenAccountAddress"]
    }]
  }'
const blocks = await dataSource.getBlocks({
  from: 280000000,
  to: 280000003,
  fields: {
    tokenBalance: { account: true, preAmount: true, postAmount: true },
  },
  tokenBalances: [
    {
      account: ["SpecificTokenAccountAddress"],
    },
  ],
});
payload = {
    "type": "solana",
    "fromBlock": 280000000,
    "toBlock": 280000003,
    "fields": {
        "tokenBalance": {
            "account": True,
            "preAmount": True,
            "postAmount": True
        }
    },
    "tokenBalances": [{
        "account": ["SpecificTokenAccountAddress"]
    }]
}

Performance Tips

  1. Filter by mint: Query specific tokens instead of all transfers
  2. Use slot ranges: Process data in 10k-50k slot batches
  3. Request minimal fields: Only request fields you need
  4. Filter by account: Monitor specific token accounts

Query Instructions

Track program instructions

DEX Swaps

Track DEX trading activity

Query Transactions

Monitor wallet activity

API Reference

View complete API docs