Solana streaming

How to track Solana wallet transfers in real time

Track Solana wallet transfers in real time by subscribing to the Anaxer transfers WebSocket stream with a wallets filter. Every SOL or token transfer a watched wallet sends or receives arrives as structured JSON with its USD value - no RPC polling or transaction decoding.

8 min readAnton Gilborn

Track Solana wallet transfers in real time by subscribing to the Anaxer transfers WebSocket stream with a wallets filter. Each time a watched wallet sends or receives SOL or a token, you get a transfer event with the sender, recipient, mint, amount and USD value - no RPC polling and no transaction decoding. This guide covers the subscription over a raw WebSocket and both SDKs, the direction and mints filters, deduping, and backfilling after a disconnect.

Who is this guide for?

This guide is for developers who need to know the moment a wallet moves funds:

  • Wallet trackers and portfolio tools
  • Copy-trading tools that want funding moves as well as swaps
  • Deposit and treasury monitors
  • Research into how funds move between wallets

To see live events before writing code, subscribe to transfers in the API Playground. For a product overview, see the Solana wallet transfers API page.

What does a transfer event look like?

Every transfer produces one transfer event. Raw amounts are strings, uiAmount is a number, and timestamps are epoch milliseconds.

{
  "type": "transfer",
  "wallet": "5FHwkW5742VKq9W8pN3mL7jR2sT6cX4bY1zD8gH9K2pQ",
  "volumeUsd": 228.5,
  "transfer": {
    "from": "5FHwkW5742VKq9W8pN3mL7jR2sT6cX4bY1zD8gH9K2pQ",
    "to": "3M6iBQx9d4h8FLpNq2sT7wJcY6zR1nGtK5uA3oXvE2mB",
    "mint": "So11111111111111111111111111111111111111112",
    "symbol": "SOL",
    "amount": "2000000000",
    "uiAmount": 2,
    "decimals": 9
  },
  "signature": "6Rk3nT2xQeB8mVdR4wLc9fJhY6zS1nGtK5uA3oXvW7qE2mCpH8dRzL4nJb9FgKtR5zXvY2mNc8hQ",
  "slot": 309847050,
  "timestamp": 1751009300000
}

The values above are illustrative. Three fields deserve a note:

  • wallet is the wallet the message is for. Compare it with transfer.from and transfer.to to get the direction.
  • volumeUsd is the USD value when the transfer was decoded. It is null when the token had no price yet.
  • transfer.mint identifies the token. SOL appears as the wrapped-SOL mint So11111111111111111111111111111111111111112 with 9 decimals. symbol is a label that can be null, so key on mint.

On the wire, each event arrives in a versioned envelope - { v, channel, type, sub, ts, data } - with the event under data.

What counts as a transfer?

The stream is deliberately narrow, so what you receive is wallet-to-wallet movement and nothing else:

  • A transfer is a wallet sending SOL or a token to a different wallet. Any amount above zero counts.
  • Moves to program accounts - deposits, payouts, a DEX's pools - are not transfers.
  • Two wallets exchanging tokens in one transaction is a swap, not two transfers. A watched wallet's swaps come on the trades stream with a wallets filter.
  • Inside a transaction that also swaps, a transfer counts only when it moves at least half of what the sender swapped in that token, such as sweeping proceeds to another wallet. App fees do not count.
  • There is no source field, and a sources filter is rejected.

The full rules are in the transfers stream reference.

How do I subscribe over a raw WebSocket?

Open the stream with your API key and send a subscribe message for the transfers channel. This runs in a browser or in Node 22 and later, which ship a global WebSocket.

const WALLETS = [
  "5FHwkW5742VKq9W8pN3mL7jR2sT6cX4bY1zD8gH9K2pQ",
  "3M6iBQx9d4h8FLpNq2sT7wJcY6zR1nGtK5uA3oXvE2mB",
];

const ws = new WebSocket("wss://api.anaxer.com/v1/stream?apiKey=YOUR_KEY");

ws.addEventListener("open", () => {
  ws.send(JSON.stringify({
    type: "subscribe",
    id: "wallet-watch",
    channel: "transfers",
    filters: { wallets: WALLETS },
  }));
});

ws.addEventListener("message", (event) => {
  const msg = JSON.parse(event.data);
  if (msg.type === "error") return console.error(msg.code, msg.message);
  if (msg.type !== "transfer") return; // connected, subscribed, pong

  const t = msg.data;
  const verb = t.wallet === t.transfer.from ? "sent" : "received";
  console.log(t.wallet, verb, t.transfer.uiAmount, t.transfer.symbol ?? t.transfer.mint, t.volumeUsd);
});

The server first sends a connected frame with your plan limits (including transfersWalletCap and transfersMintCap), then acknowledges the subscription with a subscribed frame. Problems arrive as error frames with a code and a message.

How do I subscribe with the TypeScript SDK?

Install @anaxer/sdk (0.2.0 or later) with npm install @anaxer/sdk. The SDK handles auth, heartbeats and reconnects, and types each event.

import { connect } from "@anaxer/sdk";

const client = connect({ apiKey: process.env.ANAXER_API_KEY! });

client
  .stream("transfers", {
    wallets: ["5FHwkW5742VKq9W8pN3mL7jR2sT6cX4bY1zD8gH9K2pQ"],
  })
  .on("data", (t) => {
    const verb = t.wallet === t.transfer.from ? "sent" : "received";
    console.log(t.wallet, verb, t.transfer.uiAmount, t.transfer.symbol, t.volumeUsd);
  });

How do I subscribe with the Python SDK?

Install anaxer (0.2.0 or later) with pip install anaxer. Filters are keyword arguments in snake case, and from becomes from_ on the model.

import asyncio
import os

import anaxer

WALLETS = ["5FHwkW5742VKq9W8pN3mL7jR2sT6cX4bY1zD8gH9K2pQ"]

async def main():
    async with anaxer.connect(api_key=os.environ["ANAXER_API_KEY"]) as client:
        async with client.stream("transfers", wallets=WALLETS) as sub:
            async for t in sub:
                verb = "sent" if t.wallet == t.transfer.from_ else "received"
                print(t.wallet, verb, t.transfer.ui_amount, t.transfer.symbol, t.volume_usd)

asyncio.run(main())

Both SDKs are documented in the SDK reference.

How do I watch only incoming or outgoing transfers?

Add direction to the filters. "in" delivers only transfers a watched wallet received (wallet equals transfer.to); "out" delivers only those it sent (wallet equals transfer.from). Omit it for both.

client.stream("transfers", {
  wallets: ["5FHwkW5742VKq9W8pN3mL7jR2sT6cX4bY1zD8gH9K2pQ"],
  direction: "in",
});

direction needs wallets. Without it, the subscribe is rejected with invalid_filters.

How do I narrow the stream to specific tokens?

Add mints. Combined with wallets, you get those wallets' transfers of those tokens - for example, only SOL:

client.stream("transfers", {
  wallets: ["5FHwkW5742VKq9W8pN3mL7jR2sT6cX4bY1zD8gH9K2pQ"],
  mints: ["So11111111111111111111111111111111111111112"],
});

mints also works without wallets: you then receive every transfer of those tokens, whichever wallets are involved. minVolumeUsd and maxVolumeUsd bound the USD value; a transfer with a null volumeUsd fails any volume filter. Volume thresholds are the basis of whale alerts.

Why does the same transfer arrive twice?

With a wallets filter you get one message per watched side. A transfer between two wallets on your list arrives twice: once with wallet set to the sender, once with wallet set to the recipient. Without wallets, each transfer arrives once, with wallet set to the sender.

To keep one message per wallet, dedupe on signature + wallet. The SDK reference uses a fuller key that also tells apart several transfers in one transaction:

import type { TransferV1 } from "@anaxer/sdk";

const transferKey = (t: TransferV1) =>
  [t.signature, t.wallet, t.transfer.from, t.transfer.to, t.transfer.mint, t.transfer.amount].join("|");

Do not put symbol, volumeUsd or timestamp in the key - they can differ between a live event and the same row fetched over REST.

How do I recover transfers missed during a disconnect?

The SDKs reconnect and re-subscribe for you, but they resume the live feed only. Fill the gap with the REST endpoint GET /v1/wallets/:wallet/transfers, one call per watched wallet. It takes direction, mints (or mint), minVolumeUsd, maxVolumeUsd, limit, cursor, from and to, and returns { data, next, window }, newest first.

curl "https://api.anaxer.com/v1/wallets/5FHwkW5742VKq9W8pN3mL7jR2sT6cX4bY1zD8gH9K2pQ/transfers?from=<lastEventTimestamp>&limit=200" \
  -H "Authorization: Bearer YOUR_KEY"

With the TypeScript SDK, the whole pattern is: track the last timestamp, and backfill each wallet whenever the client reconnects. The connected event fires on the first connection and on every reconnect.

import { connect, type TransferV1 } from "@anaxer/sdk";

const WALLETS = ["5FHwkW5742VKq9W8pN3mL7jR2sT6cX4bY1zD8gH9K2pQ"];
const client = connect({ apiKey: process.env.ANAXER_API_KEY! });
const seen = new Set<string>();
let lastTs = 0;

function handle(t: TransferV1) {
  const key = [t.signature, t.wallet, t.transfer.from, t.transfer.to, t.transfer.mint, t.transfer.amount].join("|");
  if (seen.has(key)) return;
  seen.add(key);
  if (t.timestamp) lastTs = Math.max(lastTs, t.timestamp);
  console.log(t.wallet, t.transfer.uiAmount, t.transfer.symbol, t.volumeUsd);
}

client.stream("transfers", { wallets: WALLETS }).on("data", handle);

client.on("connected", async () => {
  if (!lastTs) return; // first connection: nothing to backfill
  const since = lastTs;
  for (const wallet of WALLETS) {
    const page = await client.wallets.transfers(wallet, { from: since, limit: 200 });
    for await (const t of page) handle(t); // walks every `next` cursor
  }
});

Each REST item is a stream event seen from that wallet, so handle works for both paths. History covers the last 7 days, and transfers are stored only from 11 November 2026 - there is nothing earlier to backfill. An unknown wallet returns 200 with an empty list. The Solana WebSocket streams guide covers reconnects in depth.

What are the plan limits?

PlanTransfers stream
FreeNot included
Starter ($39/mo)Up to 100 wallets or 25 tokens at once; wallets or mints is required
Pro ($99/mo) and EnterpriseEvery transfer with no filter, or up to 1,000 wallets and 100 tokens

The caps count what you watch at the same time, not a total. They arrive in the connected frame as transfersRequiresFilter, transfersWalletCap and transfersMintCap. See pricing for the rest of each plan.

Troubleshooting: why am I not seeing transfers?

  • plan_restricted. Your key is on the free plan, which does not include transfers.
  • invalid_filters. A Starter subscription without wallets or mints, direction without wallets, a sources filter, or a list over its cap. The message names the problem.
  • A move you expected is missing. It was a swap (look on trades), it went to a program account, or you set a volume filter and the token had no price yet.
  • Duplicates. Both ends of the transfer are on your watchlist. Dedupe as shown above.

Next steps

Get a key in the console, subscribe with your own wallets, and log your first transfer. Then add a USD threshold to build Solana whale alerts. Prefer asking questions to writing code? The Anaxer MCP server (@anaxer/mcp 0.3.0) gives Claude and Cursor list_wallet_transfers and tail_stream on transfers.

Frequently asked questions

How do I track a Solana wallet's transfers in real time?

Open a WebSocket to wss://api.anaxer.com/v1/stream with your API key and subscribe to the transfers channel with a wallets filter listing the addresses to watch. Anaxer then pushes a transfer event each time one of those wallets sends or receives SOL or a token, with the sender, recipient, mint, amount and USD value.

What counts as a transfer on the Anaxer transfers stream?

A transfer is a wallet sending SOL or a token to a different wallet. Moves to program accounts such as deposits, payouts and DEX pools are not transfers, and two wallets exchanging tokens in one transaction is a swap, which arrives on the trades stream instead. Any amount above zero counts.

Why does the same Solana transfer arrive twice on my stream?

With a wallets filter, Anaxer sends one message per watched side. A transfer between two wallets on your list therefore arrives twice, once with wallet set to the sender and once with wallet set to the recipient. Deduplicate on signature plus wallet to keep one message per wallet. Without a wallets filter, each transfer arrives once with wallet set to the sender.

How many Solana wallets can I watch on the transfers stream?

Starter ($39/mo) watches up to 100 wallets or 25 tokens at once and must send a wallets or mints filter. Pro ($99/mo) and Enterprise watch up to 1,000 wallets and 100 tokens, or send neither filter to receive every transfer. The caps count what you watch at the same time. The free plan does not include transfers.

How do I recover wallet transfers missed during a disconnect?

Store the timestamp of the last transfer you processed. After reconnecting, call GET /v1/wallets/:wallet/transfers for each watched wallet with from set to that timestamp, walk the next cursor, and deduplicate against what you already have. History covers the last 7 days and starts on 11 November 2026, the day transfers launched.

How does SOL appear in Anaxer transfer events?

SOL appears under the wrapped-SOL mint So11111111111111111111111111111111111111112 with 9 decimals and the symbol SOL. To watch only SOL transfers, put that mint in the mints filter.

Can I filter Solana wallet transfers by direction or size?

Yes. The direction filter takes in or out and needs a wallets filter: in delivers only transfers a watched wallet received, out only those it sent. minVolumeUsd and maxVolumeUsd bound the USD value. A transfer whose token had no price yet has a null volumeUsd and fails any volume filter.

Last updated November 11, 2026.

Start streaming Solana data

Anaxer gives you real-time pump.fun and PumpSwap streams and REST APIs on a flat plan. The free tier needs no card.

Written by Anton Gilborn · Senior Platform Engineer, Anaxer

Anton is a senior platform engineer at Anaxer, where he builds the real-time streaming infrastructure behind its Solana data API - token launches, graduations, DEX trades, prices, and transfers. He writes about low-latency streaming architecture and building fast on-chain apps.

Keep reading