Skip to main content

How to Get Solana Wallet Transaction History with getTransactionsForAddress

Updated on
Aug 31, 2026

15 min read

Overview

Getting transaction history for a Solana address used to take more than one call. getSignaturesForAddress returns signatures, up to 1,000 per page, and every signature needs its own getTransaction call to see what the transaction did. Building a wallet's history means looping through that fan-out page by page and stitching the results together yourself. It multiplies if the wallet holds tokens. Token transfers reference the wallet's associated token accounts (ATAs) rather than the wallet itself, so you first discover those accounts, then repeat the whole loop for each one and merge everything client-side.

Quicknode's getTransactionsForAddress returns an address's transactions and its token account activity in a single call, with server-side filtering, sorting, and cursor pagination. This guide builds a complete wallet history feed with it.


TLDR
  • Build a complete Solana wallet history feed with getTransactionsForAddress: one request per page, instead of the ~1,000 RPC calls the getSignaturesForAddress + getTransaction pattern needs
  • Set filters.tokenAccounts to pick up the token transfers a wallet-address-only history silently drops, and transactionDetails: "full" to get decoded transactions and meta inline
  • Follow the paginationToken cursor to the end of a wallet's history with a reusable async generator, then persist the token as a crash-resumable checkpoint
  • Apply four filter recipes: indexer backfill, fixed-window statement, slot-range audit, and failed-transaction forensics
  • Every script is standalone TypeScript running against a Quicknode Solana Mainnet endpoint with @solana/kit

What Is getTransactionsForAddress?

getTransactionsForAddress is a Quicknode Solana RPC method that returns an address's transaction history in a single call, including activity in the token accounts that address owns. It supports server-side filtering by slot, block time, signature position, and status, returns full decoded transactions on request, and paginates with a cursor.

The method accepts any account address: wallets, programs, program derived addresses (PDAs), vaults, and token accounts. This guide uses a wallet, because that is where the token account problem shows up. The recipes near the end apply the same helpers to program-owned accounts.

What You Will Do


  • Query a wallet's transaction history with a single RPC call
  • Include token account activity with the tokenAccounts filter and compare the result counts
  • Fetch full transaction payloads with versioned transaction support
  • Build a reusable pagination helper that follows the cursor through every page
  • Apply filter recipes for backfills, statements, slot audits, and failure forensics

What You Will Need


  • A Quicknode account with a Solana Mainnet endpoint (the method is part of Quicknode's Solana JSON-RPC API, so no add-on is required)
  • Node.js version 22 or later
  • Basic TypeScript experience and familiarity with RPC requests
  • A Solana Mainnet wallet address to inspect

Why Wallet History on Solana Is Hard

The standard RPC methods create two problems that compound each other.

The getSignaturesForAddress and getTransaction Loop

getSignaturesForAddress returns signatures only, up to 1,000 per page, walked backward with a before cursor. Every signature then needs its own getTransaction call to see what actually happened. A wallet with 1,000 transactions costs roughly 1,000 follow-up calls, each one subject to rate limits, retries, and backoff logic. There is also no server-side filtering. To find transactions from a specific timeframe, the client pages backward from the present and discards everything newer until it crosses the window.

The Token Account Gap

Token balances live in associated token accounts, not in the wallet, and a token transfer references the ATA rather than the owner. getSignaturesForAddress on the wallet address never sees those transactions. A complete history means calling getTokenAccountsByOwner to discover the wallet's token accounts, walking signature history for the wallet and each ATA separately, then merging, deduplicating, and sorting everything client-side.

Even that is not complete. getTokenAccountsByOwner only returns accounts that still exist. When a user closes a token account to reclaim rent, its history becomes undiscoverable through this pattern. getTransactionsForAddress avoids the problem entirely because the index is built on the owner relationship at ingest time, so closed token accounts remain part of the wallet's history.

getSignaturesForAddress vs. getTransactionsForAddress

In the comparison below, the call-count row assumes pulling the complete history, token transfers included, of a wallet holding 10 Solana Program Library (SPL) tokens with 1,000 total transactions.

CapabilitygetSignaturesForAddress + getTransactiongetTransactionsForAddress
RPC calls for that scenario~1,012 (one getTokenAccountsByOwner, ~11 signature pages, 1,000 getTransaction calls)1 request per page, up to 1,000 results per page
Token account (ATA) activityNot included; discover accounts yourself, then query each separatelyIncluded via filters.tokenAccounts
Closed token accountsInvisible; getTokenAccountsByOwner only returns accounts that still existIncluded; the index is built on the owner field
Merge, dedupe, sort across accountsYour codeServer side
Decoded transaction in the same responseNo, one getTransaction per signatureYes, with transactionDetails: "full"
Filter by statusPage every signature, check err client sidefilters.status
Filter by time rangePage backward and discard until you cross the windowfilters.blockTime with gte, gt, lte, lt, eq
Filter by slot rangeNo slot cursor existsfilters.slot with gte, gt, lte, lt
Oldest-first traversalPage backward to the first transaction, buffer everything, reversesortOrder: "asc"
Cursorbefore / until; you must already hold a signature to anchor onpaginationToken, returned in every response
Resume from a known signaturebefore only, backwardfilters.signature, positional in either direction
Ordering within a blockNot exposedtransactionIndex on every result

For a Solana wallet holding 10 SPL tokens with 1,000 total transactions, the getSignaturesForAddress pattern costs about 1,012 RPC calls:


  • One getTokenAccountsByOwner
  • ~11 signature pages
  • 1,000 individual getTransaction calls.

getTransactionsForAddress returns the same history in one request per page of up to 1,000 results, and it still includes any token account the wallet has since closed, which the old pattern cannot reach at any call count.

Set Up a Project

Create a project directory and install the dependencies:

mkdir gtfa-wallet-history && cd gtfa-wallet-history
npm init -y
npm pkg set type=module
npm install @solana/kit@8 tsx@4
npm install --save-dev @types/node

Create a .env file with your Quicknode endpoint and the wallet you want to inspect. You can find your endpoint on the Quicknode dashboard after creating a Solana Mainnet endpoint.

.env
QN_ENDPOINT=https://your-endpoint-name.solana-mainnet.quiknode.pro/your-token/
WALLET_ADDRESS=ANY_MAINNET_WALLET_ADDRESS

getTransactionsForAddress is a Quicknode extension to the Solana JSON-RPC API, so it is not part of the default RPC surface in Solana Kit (@solana/kit). Kit supports this directly: createJsonRpcApi builds a client for any custom method, with the same transport and response handling as the standard API. Create a shared client in gtfa.ts that the rest of the guide imports:

gtfa.ts
import { createDefaultRpcTransport, createJsonRpcApi, createRpc } from "@solana/kit";

// ---- Request types ----------------------------------------------------

type Comparison<T> = { gte?: T; gt?: T; lte?: T; lt?: T };

export type GtfaFilters = {
slot?: Comparison<number>;
blockTime?: Comparison<number> & { eq?: number }; // Unix seconds
signature?: Comparison<string>; // positional, base-58 signature
status?: "any" | "succeeded" | "failed"; // default: any
tokenAccounts?: "none" | "balanceChanged" | "all"; // default: none
};

export type GtfaConfig = {
transactionDetails?: "signatures" | "full"; // default: signatures
sortOrder?: "asc" | "desc"; // default: desc
limit?: number; // 1 to 1000, default 1000
paginationToken?: string;
commitment?: "confirmed" | "finalized";
minContextSlot?: number;
encoding?: "json" | "jsonParsed" | "base58" | "base64"; // default: json, full mode only
maxSupportedTransactionVersion?: number; // full mode only
filters?: GtfaFilters;
};

// ---- Response types ---------------------------------------------------

export type GtfaItem = {
slot: number;
transactionIndex: number;
blockTime: number | null;
// Present in signatures mode:
signature?: string;
err?: object | null;
memo?: string | null;
confirmationStatus?: "confirmed" | "finalized";
// Present in full mode:
transaction?: any;
meta?: any;
version?: "legacy" | number;
};

export type GtfaResponse = {
data: GtfaItem[];
paginationToken: string | null;
};

type GtfaApi = {
getTransactionsForAddress(address: string, config?: GtfaConfig): GtfaResponse;
};

// ---- Client -------------------------------------------------------------

function requireEnv(name: string): string {
const value = process.env[name];
if (!value) throw new Error(`Set ${name} in .env`);
return value;
}

const endpoint = requireEnv("QN_ENDPOINT");
export const walletAddress = requireEnv("WALLET_ADDRESS");

const api = createJsonRpcApi<GtfaApi>({
responseTransformer: (response: any) => {
// Surface JSON-RPC errors instead of returning undefined
if (response.error) {
throw new Error(`RPC error ${response.error.code}: ${response.error.message}`);
}
return response.result;
},
});
const transport = createDefaultRpcTransport({ url: endpoint });

export const gtfaRpc = createRpc({ api, transport });

Those types mirror the method's full parameter set, which is worth having in one place:

ParameterValuesDefault
transactionDetailssignatures, fullsignatures
sortOrderasc, descdesc
limit1 to 1000 (higher values are capped)1000
paginationTokenCursor from a prior response, formatted slot:transactionIndexNone
commitmentconfirmed, finalized (nothing lower)None
minContextSlotMinimum slot at which to evaluate the requestNone
encodingjson, jsonParsed, base58, base64 (full mode only)json
maxSupportedTransactionVersionInteger, full mode only. Omit and only legacy transactions returnNone
filters.slotgte, gt, lte, ltNone
filters.blockTimegte, gt, lte, lt, eq (Unix seconds)None
filters.signaturegte, gt, lte, lt (positional, base-58)None
filters.statusany, succeeded, failedany
filters.tokenAccountsnone, balanceChanged, allnone

This helper does three things defines request and response types that mirror the method reference, reads your endpoint and target wallet from .env, and exports a ready-to-use gtfaRpc client.

Make a First Call

Start with the method's defaults to see the baseline response shape. Create first-call.ts:

first-call.ts
import { gtfaRpc, walletAddress } from "./gtfa.js";

const result = await gtfaRpc
.getTransactionsForAddress(walletAddress, { limit: 10 })
.send();

for (const tx of result.data) {
const status = tx.err === null ? "ok" : "FAILED";
console.log(`${tx.slot}:${tx.transactionIndex} ${status} ${tx.signature}`);
}
console.log(`\npaginationToken: ${result.paginationToken}`);

Run it:

npx tsx --env-file=.env first-call.ts

Signatures, slots, and the pagination token will differ for your own wallet.

Expected results:

440303193:1866 ok 3xTVgD66gwavvNfMeS6PeQKJYEpSgpSk7DKhQD8oGSGW5vts89xM6mCDckkQZftrQXXyxptBJ2YvbcAb4CFVbEAW
440303193:1865 ok 5o7p4sFu29mHqdKfPdgzf8kTGJGZ1vRHnJVDMmF1MsPKZBiuKJnJSY4ukoemHAXs58PhMjTZgRfvD4QUmo8EPxF4
...

paginationToken: 440301811:203

Each item carries the same fields getSignaturesForAddress returns (signature, slot, err, memo, blockTime, confirmationStatus) plus transactionIndex, the transaction's position inside its block. Paired with the slot, slot:transactionIndex gives every transaction an exact position in history, which is also the format of the paginationToken at the bottom of the response.

These results only contain transactions that reference the wallet address directly, so token transfers in and out of the wallet's token accounts are missing. The next section shows how to retrieve them.

Include Token Account Activity

The filters.tokenAccounts option controls whether transactions touching the wallet's owned token accounts are part of the result. It has three settings:


  • none (default): only transactions that reference the address itself
  • balanceChanged: also include transactions that changed a balance in a token account the address owns
  • all: also include every transaction that touched any token account the address owns, even without a balance change

For a wallet history feed, balanceChanged is the setting you want. It adds deposits, withdrawals, and swaps involving the wallet's tokens, and excludes transactions that touched a token account without moving a balance, such as an account creation or a delegate approval.

Create token-accounts.ts to see the difference on your wallet:

token-accounts.ts
import { gtfaRpc, walletAddress } from "./gtfa.js";

// Bound both queries to the same 30-day window so the counts are comparable.
// Without a shared window, each query returns its own page of up to 1,000
// results covering a different slot range, and the numbers mean nothing.
const THIRTY_DAYS_AGO = Math.floor(Date.now() / 1000) - 30 * 86400;

async function countWindow(tokenAccounts: "none" | "balanceChanged") {
const result = await gtfaRpc
.getTransactionsForAddress(walletAddress, {
limit: 1000,
filters: {
tokenAccounts,
blockTime: { gte: THIRTY_DAYS_AGO },
},
})
.send();
return result.data.length;
}

const walletOnly = await countWindow("none");
const withTokens = await countWindow("balanceChanged");

console.log(`Last 30 days, wallet address only: ${walletOnly} transactions`);
console.log(`Last 30 days, with token accounts: ${withTokens} transactions`);

Run it:

npx tsx --env-file=.env token-accounts.ts

Expected results:

Last 30 days, wallet address only: 38 transactions
Last 30 days, with token accounts: 212 transactions

For any wallet that holds tokens, the second number is larger, often several times larger. Every transaction in that difference is one a getSignaturesForAddress-based history silently drops.

Both queries use the same 30-day window on purpose. Comparing two unbounded pages would compare different slot ranges, and on a busy wallet both would simply return the 1,000-result maximum. If your wallet returns 1,000 for both, narrow the window until it comes in under the cap.


Token account activity is off by default

tokenAccounts defaults to none. If you copy a minimal example into a wallet history feature and never set this filter, the feature will look correct and be wrong. Set balanceChanged for anything user-facing.

Fetch Full Transactions

So far the responses contain signatures. To render a history feed you need what each transaction did, and the old answer was one getTransaction call per signature. Setting transactionDetails: "full" returns the complete transaction and meta objects inline instead.

Create full-history.ts:

full-history.ts
import { gtfaRpc, walletAddress } from "./gtfa.js";

const result = await gtfaRpc
.getTransactionsForAddress(walletAddress, {
transactionDetails: "full",
maxSupportedTransactionVersion: 0, // required for v0 transactions
encoding: "jsonParsed",
limit: 25,
filters: { tokenAccounts: "balanceChanged" },
})
.send();

for (const tx of result.data) {
const signature = tx.transaction.signatures[0]; // moved: no top-level field in full mode
const status = tx.meta.err === null ? "ok" : "FAILED"; // moved: err lives in meta
const fee = tx.meta.fee;
const cu = tx.meta.computeUnitsConsumed;
console.log(`${tx.slot}:${tx.transactionIndex} ${status} v=${tx.version} fee=${fee} cu=${cu} ${signature}`);
}

Run it:

npx tsx --env-file=.env full-history.ts

Expected results:

440303193:1866 ok v=0 fee=5000 cu=42315 3xTVgD66gwavvNfMeS6PeQKJYEpSgpSk7DKhQD8oGSGW5vts89xM6mCDckkQZftrQXXyxptBJ2YvbcAb4CFVbEAW
440302871:412 ok v=0 fee=5000 cu=118204 5o7p4sFu29mHqdKfPdgzf8kTGJGZ1vRHnJVDMmF1MsPKZBiuKJnJSY4ukoemHAXs58PhMjTZgRfvD4QUmo8EPxF4
440301544:88 FAILED v=0 fee=5000 cu=9821 2vNJ8bXsPk4mQhRtL6yWcE3zAdFgHnJkMpQrStUvWxYz1aB2cD3eF4gH5iJ6kL7mN8oP9qR1sT2uV3wX4yZ5aB6c
...

Each item now carries transaction (accounts and instructions, parsed because of encoding: "jsonParsed"), meta (fee, pre/post balances, pre/post token balances, inner instructions, log messages, and compute units), and version. That is everything a history row, a balance-change calculation, or a debugging session needs, with zero follow-up calls.

The two modes do not return the same fields, and two fields move rather than disappear:

FieldtransactionDetails: signaturestransactionDetails: full
slotYesYes
transactionIndexYesYes
blockTimeYesYes
signatureYesMoves to transaction.signatures[0] (json and jsonParsed encodings)
errYesMoves to meta.err
memoYesNot returned
confirmationStatusYesNot returned
transactionNot returnedYes
metaNot returnedYes
versionNot returnedYes

Full mode has two behaviors that will cost you debugging time if you miss them:


Set maxSupportedTransactionVersion

If you omit maxSupportedTransactionVersion, the method returns an error whenever it encounters a versioned transaction. Version 0 transactions have accounted for the majority of Solana Mainnet activity since address lookup tables shipped in late 2022, so set it to 0 in every full-mode request. For background on the versioned transactions, see How to Use Versioned Transactions on Solana.

Page Through Complete History

A single page holds up to 1,000 results. Active wallets have more history than that, which is what the paginationToken is for: pass the token from one response into the next request, and stop when it comes back null. An async generator wraps that loop once so every later script can reuse it. Add this to the bottom of gtfa.ts:

gtfa.ts (append)
// ---- Pagination helper --------------------------------------------------

export async function* walkTransactionsForAddress(
address: string,
config: Omit<GtfaConfig, "paginationToken"> = {},
startToken?: string,
) {
let paginationToken: string | null | undefined = startToken;
do {
const page: GtfaResponse = await gtfaRpc
.getTransactionsForAddress(address, { ...config, paginationToken })
.send();
yield page; // caller gets data plus the token, useful for checkpointing
paginationToken = page.paginationToken;
} while (paginationToken);
}

The generator yields whole pages rather than individual transactions so callers can persist page.paginationToken as a checkpoint, which the backfill recipe below relies on. Fetching a wallet's entire history now looks like this, in all-history.ts:

all-history.ts
import { walkTransactionsForAddress, walletAddress } from "./gtfa.js";

let total = 0;
for await (const page of walkTransactionsForAddress(walletAddress, {
limit: 1000,
filters: { tokenAccounts: "balanceChanged" },
})) {
total += page.data.length;
console.log(`+${page.data.length} (total ${total}) next=${page.paginationToken}`);
}
console.log(`\nComplete history: ${total} transactions`);

Run it:

npx tsx --env-file=.env all-history.ts

Expected results:

+1000 (total 1000) next=440301811:203
+1000 (total 2000) next=439877402:112
+412 (total 2412) next=null

Complete history: 2412 transactions

The paginationToken encodes a position within one specific query. Reusing it with different filters, a different sort order, or a different address produces meaningless results. Change any part of the query and start from a fresh request.

Filter Recipes

The recipes below combine everything used so far into four production jobs that were previously impractical, each as a short variation on the helpers you already have.

Resume an Indexer Backfill from a Checkpoint

getSignaturesForAddress walks backward only, so replaying an account's history forward into a database meant paging to the very beginning and buffering everything in memory, with a crash sending you back to the start. Ascending sort plus a persisted cursor turns the same job into a flat-memory loop that survives restarts:

backfill.ts
import { readFileSync, writeFileSync, existsSync, unlinkSync } from "node:fs";
import { walkTransactionsForAddress, walletAddress } from "./gtfa.js";

const CHECKPOINT_FILE = "checkpoint.txt";
const startToken = existsSync(CHECKPOINT_FILE)
? readFileSync(CHECKPOINT_FILE, "utf8").trim()
: undefined;

for await (const page of walkTransactionsForAddress(
walletAddress,
{
sortOrder: "asc", // oldest first: forward replay
transactionDetails: "full",
maxSupportedTransactionVersion: 0,
limit: 1000,
filters: { tokenAccounts: "balanceChanged" },
},
startToken,
)) {
// ... write page.data to your database here ...
console.log(`Indexed ${page.data.length} transactions`);

if (page.paginationToken) {
writeFileSync(CHECKPOINT_FILE, page.paginationToken); // resume point
}
}

// Clear the checkpoint so a rerun starts fresh instead of replaying the last page
if (existsSync(CHECKPOINT_FILE)) unlinkSync(CHECKPOINT_FILE);
console.log("Backfill complete");

Run it:

npx tsx --env-file=.env backfill.ts

Expected results:

Indexed 1000 transactions
Indexed 1000 transactions
Indexed 412 transactions
Backfill complete

Generate a Fixed-Window Statement

Monthly statements, accounting exports, and activity reports need everything inside a date range and nothing outside it. filters.blockTime bounds the query server-side, so the cost tracks the window's contents rather than how far in the past it sits:

statement.ts
import { walkTransactionsForAddress, walletAddress } from "./gtfa.js";

const MARCH_1_2026 = 1772323200; // Unix seconds, UTC
const APRIL_1_2026 = 1775001600;

let count = 0;
let failed = 0;
for await (const page of walkTransactionsForAddress(walletAddress, {
sortOrder: "asc",
limit: 1000,
filters: {
blockTime: { gte: MARCH_1_2026, lt: APRIL_1_2026 },
tokenAccounts: "balanceChanged",
},
})) {
count += page.data.length;
failed += page.data.filter((tx) => tx.err !== null).length;
}
console.log(`March 2026: ${count} transactions (${failed} failed)`);

Run it:

npx tsx --env-file=.env statement.ts

Expected results:

March 2026: 184 transactions (6 failed)

Audit a Slot Range

Incident reviews and dispute resolutions need everything an account did between two specific slots. The legacy cursor is signature-based, so answering that used to start with a search for a signature near the boundary slot. filters.slot bounds it directly. Substitute a slot from your own first-call.ts output for the range below.

slot-audit.ts
import { walkTransactionsForAddress, walletAddress } from "./gtfa.js";

// Replace these with a slot from your own first-call.ts output. The lower
// bound below is roughly a day of Solana slots before the upper bound.
const AUDIT_END_SLOT = 440303193;
const AUDIT_START_SLOT = AUDIT_END_SLOT - 216000;

for await (const page of walkTransactionsForAddress(walletAddress, {
sortOrder: "asc",
limit: 1000,
filters: {
slot: { gte: AUDIT_START_SLOT, lte: AUDIT_END_SLOT },
tokenAccounts: "balanceChanged", // an audit that drops token transfers is not an audit
},
})) {
for (const tx of page.data) {
console.log(`${tx.slot}:${tx.transactionIndex} ${tx.signature}`);
}
}

Run it:

npx tsx --env-file=.env slot-audit.ts

Expected results:

440087312:204 4KcTn8sVqYbGpLm2WxRdF6hJ3zAeNuPvQtSrUwXyZ1aB2cD3eF4gH5iJ6kL7mN8oP9qR1sT2uV3wX4yZ5aB6c
440091887:1533 3xTVgD66gwavvNfMeS6PeQKJYEpSgpSk7DKhQD8oGSGW5vts89xM6mCDckkQZftrQXXyxptBJ2YvbcAb4CFVbEAW
440091887:1534 5o7p4sFu29mHqdKfPdgzf8kTGJGZ1vRHnJVDMmF1MsPKZBiuKJnJSY4ukoemHAXs58PhMjTZgRfvD4QUmo8EPxF4
...

The transactionIndex on each result means the output is a total ordering. Two transactions in the same slot are listed in their execution order, which matters when an audit hinges on which came first.

Investigate Failed Transactions

When a bot or a program-owned account starts failing, the transactions you need are buried among thousands of successful ones. Filtering by status and requesting full details returns just the failures, logs attached:

failures.ts
import { walkTransactionsForAddress, walletAddress } from "./gtfa.js";

const DAY_AGO = Math.floor(Date.now() / 1000) - 86400;

for await (const page of walkTransactionsForAddress(walletAddress, {
transactionDetails: "full",
maxSupportedTransactionVersion: 0,
limit: 100,
filters: {
status: "failed",
blockTime: { gte: DAY_AGO },
tokenAccounts: "balanceChanged", // catch failures in the address's token accounts too
},
})) {
for (const tx of page.data) {
// logMessages is null when a validator has log collection off or truncated
const logs = tx.meta.logMessages ?? [];
console.log(`\n${tx.transaction.signatures[0]}`);
console.log(` error: ${JSON.stringify(tx.meta.err)}`);
console.log(` logs: ${logs.slice(-3).join("\n ") || "none returned"}`);
}
}

Run it:

npx tsx --env-file=.env failures.ts

Expected results:

4KcTn8sVqYbGpLm2WxRdF6hJ3zAeNuPvQtSrUwXyZ1aB2cD3eF4gH5iJ6kL7mN8oP9qR1sT2uV3wX4yZ5aB6c
error: {"InstructionError":[3,{"Custom":6001}]}
logs: Program log: Error: slippage tolerance exceeded
Program JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4 consumed 84210 of 200000 compute units
Program JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4 failed: custom program error: 0x1771
...

The last few log messages usually contain the program error behind each failure, which is what you need to diagnose an overnight outage. How to Track Solana Transaction History with Logs goes deeper on reading and filtering log output.

When to Use Streams or Solana gRPC Instead

getTransactionsForAddress is built for history: everything an address has already done, filtered and ordered on the server. It is a pull-based method, so it is the wrong tool for reacting to new activity the moment it happens. For that end of the timeline, Quicknode has two options:


  • Quicknode Streams delivers filtered onchain data to a webhook, S3 bucket, or other destination as it lands, so nothing polls for new transactions. Reach for it when you want new activity pushed to your infrastructure.
  • Solana gRPC streams accounts, transactions, and blocks over a persistent connection, and it is the lowest-latency option Quicknode offers. Trading systems and liquidation bots that need updates the moment they happen belong here.

Indexers can use use getTransactionsForAddress to backfill history (the checkpoint recipe above), and Streams or Solana gRPC to keep it current from there.

Wrapping Up

The gap getTransactionsForAddress closes here is about correctness. A history feed built on getSignaturesForAddress alone silently drops token transfers that no amount of extra calls will recover. The filter recipes are small enough to drop into real systems as-is, and the checkpoint pattern in the backfill recipe is the foundation of a production indexer. Point the scripts at your own wallets, wire the backfill into a database, and add Streams or Solana gRPC when you need the feed to stay current.

If you're stuck, have questions, or just want to talk about what you're building, drop us a line on Discord or Twitter!

Frequently Asked Questions

What is getTransactionsForAddress on Solana?

getTransactionsForAddress is a Quicknode RPC method that returns the transaction history for a Solana address in a single call, with optional server-side filtering by slot, block time, signature position, status, and token account activity. It replaces the traditional pattern of combining getTokenAccountsByOwner, getSignaturesForAddress, and per-signature getTransaction calls.

How is getTransactionsForAddress different from getSignaturesForAddress?

getSignaturesForAddress returns only signatures for transactions that directly reference the address, walks backward only, and offers no filtering. getTransactionsForAddress can include activity from token accounts the address owns and return full transaction payloads inline. It also filters by slot, time, and status on the server, sorts in either direction, and paginates with a cursor it returns to you.

Why are token transfers missing from my results?

The tokenAccounts filter defaults to none, which returns only transactions that reference the address itself. Set filters.tokenAccounts to balanceChanged to include transactions that changed a balance in a token account the address owns, which is the setting most wallet history features want.

Why does the method return an error about transaction versions?

In full mode, transactions above the maxSupportedTransactionVersion produce an error. Omitting the parameter supports only legacy transactions, and version 0 accounts for the majority of current Mainnet activity. Set maxSupportedTransactionVersion to 0 in every request that uses transactionDetails full.

Can I use getTransactionsForAddress for real-time monitoring?

getTransactionsForAddress is designed for historical queries, not live delivery. For real-time data, use Quicknode Streams for reliable push delivery of filtered onchain data, or Solana gRPC for the lowest-latency streaming of accounts, transactions, and blocks. A common architecture backfills history with getTransactionsForAddress and stays current with Streams or Solana gRPC.

How do I get a Solana wallet's full transaction history?

Call getTransactionsForAddress with the wallet address and set filters.tokenAccounts to balanceChanged so token account activity is included. Set transactionDetails to full for decoded transactions, then pass the returned paginationToken back on each subsequent request until it comes back null.

Resources